├── README.md ├── Reused Functions.zip ├── backend ├── .gitignore ├── api │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-311.pyc │ │ ├── admin.cpython-311.pyc │ │ ├── apps.cpython-311.pyc │ │ ├── models.cpython-311.pyc │ │ ├── serializer.cpython-311.pyc │ │ ├── urls.cpython-311.pyc │ │ └── views.cpython-311.pyc │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_category_post_notification_comment_bookmark.py │ │ ├── 0003_post_status.py │ │ ├── 0004_alter_bookmark_options_comment_reply_profile_author_and_more.py │ │ ├── 0005_post_profile.py │ │ ├── 0006_profile_facebook_profile_twitter.py │ │ ├── 0007_alter_post_category.py │ │ ├── 0008_alter_post_category.py │ │ ├── 0009_alter_post_category.py │ │ ├── __init__.py │ │ └── __pycache__ │ │ │ ├── 0001_initial.cpython-311.pyc │ │ │ ├── 0002_category_post_notification_comment_bookmark.cpython-311.pyc │ │ │ ├── 0003_post_status.cpython-311.pyc │ │ │ ├── 0004_alter_bookmark_options_comment_reply_profile_author_and_more.cpython-311.pyc │ │ │ ├── 0005_post_profile.cpython-311.pyc │ │ │ ├── 0006_profile_facebook_profile_twitter.cpython-311.pyc │ │ │ ├── 0007_alter_post_category.cpython-311.pyc │ │ │ ├── 0008_alter_post_category.cpython-311.pyc │ │ │ ├── 0009_alter_post_category.cpython-311.pyc │ │ │ └── __init__.cpython-311.pyc │ ├── models.py │ ├── serializer.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── backend │ ├── __init__.py │ ├── __pycache__ │ │ ├── __init__.cpython-311.pyc │ │ ├── settings.cpython-311.pyc │ │ ├── urls.cpython-311.pyc │ │ └── wsgi.cpython-311.pyc │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── db.sqlite3 ├── manage.py ├── media │ └── image │ │ ├── 0x0.webp │ │ ├── 15274003_5586333.jpg │ │ ├── 218884489_1489407691458770_8916744002593220091_n.jpg │ │ ├── 4236992_2264344.jpg │ │ ├── 8847298_4003914.jpg │ │ ├── Desphixs_1.jpg │ │ ├── Desphixs_2.jpg │ │ ├── Desphixs_2_q3BUhWS.jpg │ │ ├── Desphixs_3.jpg │ │ ├── Desphixs_3_1EcqGQF.jpg │ │ ├── avatar-3.jpg │ │ ├── avatar-lg-3.jpg │ │ ├── futuristic-sports-car-wallpaper-1920x1200_6.jpg │ │ ├── ins-gallery_5.jpg │ │ ├── post-banner_1.jpg │ │ ├── post-banner_1_1ZybySx.jpg │ │ ├── post-banner_1_UUhTNSN.jpg │ │ ├── post-xl_41.jpg │ │ ├── post-xl_41_IJhagQf.jpg │ │ ├── post-xl_41_yE1iEOe.jpg │ │ ├── store.png │ │ ├── wewe.jpg │ │ ├── wewe_XUDxYlk.jpg │ │ └── wewe_bhcJf71.jpg ├── requirements.txt └── templates │ └── email │ ├── password_reset.html │ └── password_reset.txt ├── frontend ├── .eslintrc.cjs ├── .gitignore ├── README.md ├── index.html ├── package.json ├── public │ ├── logo.png │ └── vite.svg ├── src │ ├── App.css │ ├── App.jsx │ ├── assets │ │ └── react.svg │ ├── index.css │ ├── layouts │ │ ├── MainWrapper.jsx │ │ └── PrivateRoute.jsx │ ├── main.jsx │ ├── plugin │ │ ├── Moment.js │ │ ├── Toast.js │ │ └── useUserData.js │ ├── store │ │ └── auth.js │ ├── utils │ │ ├── auth.js │ │ ├── axios.js │ │ ├── constants.js │ │ └── useAxios.js │ └── views │ │ ├── auth │ │ ├── CreatePassword.jsx │ │ ├── ForgotPassword.jsx │ │ ├── Login.jsx │ │ ├── Logout.jsx │ │ └── Register.jsx │ │ ├── core │ │ ├── Category.jsx │ │ ├── Detail.jsx │ │ ├── Index.jsx │ │ └── Search.jsx │ │ ├── dashboard │ │ ├── AddPost.jsx │ │ ├── Comments.jsx │ │ ├── Dashboard.jsx │ │ ├── EditPost.jsx │ │ ├── Notifications.jsx │ │ ├── Posts.jsx │ │ └── Profile.jsx │ │ ├── pages │ │ ├── About.jsx │ │ └── Contact.jsx │ │ └── partials │ │ ├── Footer.jsx │ │ └── Header.jsx ├── vite.config.js └── yarn.lock └── materials ├── CDNs.md ├── Frontend Code └── src │ ├── App.css │ ├── App.jsx │ ├── assets │ └── react.svg │ ├── index.css │ ├── main.jsx │ └── views │ ├── auth │ ├── CreatePassword.jsx │ ├── ForgotPassword.jsx │ ├── Login.jsx │ ├── Logout.jsx │ └── Register.jsx │ ├── core │ ├── Category.jsx │ ├── Detail.jsx │ ├── Index.jsx │ └── Search.jsx │ ├── dashboard │ ├── AddPost.jsx │ ├── Comments.jsx │ ├── Dashboard.jsx │ ├── EditPost.jsx │ ├── Notifications.jsx │ ├── Posts.jsx │ └── Profile.jsx │ ├── pages │ ├── About.jsx │ └── Contact.jsx │ └── partials │ ├── Footer.jsx │ └── Header.jsx ├── drf_yasg.md └── requirements.txt /README.md: -------------------------------------------------------------------------------- 1 | # Milestone 🪨 Tracker 🕐 2 | --- 3 | 1. Started Project on the 21 March, 2024 9:58pm 4 | 2. Completed all the frontend pages development on the 22 March, 2024 12:54am 5 | 3. Completed all the backend api views and testing on the 22 March, 2024 7:07 pm (Note: I already have some of the APIs, so Instead of rewriting I took APIs from previous projects) 6 | 4. Integrated authentication apis to frontend on the 23 March, 2024 9:55 am 7 | 5. Implemented author posts, comments and notifications on the 24 March, 2024 11:06 pm 8 | 6. (Completed V1 in 5 Days) Integrated post create api to frontend on the 25 march, 2024 5:46 pm 9 | -------------------------------------------------------------------------------- /Reused Functions.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/Reused Functions.zip -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | .env -------------------------------------------------------------------------------- /backend/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/api/__init__.py -------------------------------------------------------------------------------- /backend/api/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/api/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /backend/api/__pycache__/admin.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/api/__pycache__/admin.cpython-311.pyc -------------------------------------------------------------------------------- /backend/api/__pycache__/apps.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/api/__pycache__/apps.cpython-311.pyc -------------------------------------------------------------------------------- /backend/api/__pycache__/models.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/api/__pycache__/models.cpython-311.pyc -------------------------------------------------------------------------------- /backend/api/__pycache__/serializer.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/api/__pycache__/serializer.cpython-311.pyc -------------------------------------------------------------------------------- /backend/api/__pycache__/urls.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/api/__pycache__/urls.cpython-311.pyc -------------------------------------------------------------------------------- /backend/api/__pycache__/views.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/api/__pycache__/views.cpython-311.pyc -------------------------------------------------------------------------------- /backend/api/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from api import models as api_models 3 | 4 | class UserAdmin(admin.ModelAdmin): 5 | search_fields = ['full_name', 'username', 'email'] 6 | list_display = ['username', 'email'] 7 | 8 | class ProfileAdmin(admin.ModelAdmin): 9 | search_fields = ['user'] 10 | list_display = ['thumbnail', 'user', 'full_name'] 11 | 12 | class CategoryAdmin(admin.ModelAdmin): 13 | list_display = ["title"] 14 | 15 | class PostAdmin(admin.ModelAdmin): 16 | list_display = ["title","user","category","view"] 17 | 18 | class CommentAdmin(admin.ModelAdmin): 19 | list_display = ["post","name","email","comment"] 20 | 21 | class BookmarkAdmin(admin.ModelAdmin): 22 | list_display = ["user","post"] 23 | 24 | class NotificationAdmin(admin.ModelAdmin): 25 | list_display = ["user","post","type","seen",] 26 | 27 | admin.site.register(api_models.User, UserAdmin) 28 | admin.site.register(api_models.Profile, ProfileAdmin) 29 | admin.site.register(api_models.Category, CategoryAdmin) 30 | admin.site.register(api_models.Post, PostAdmin) 31 | admin.site.register(api_models.Comment, CommentAdmin) 32 | admin.site.register(api_models.Notification, NotificationAdmin) 33 | admin.site.register(api_models.Bookmark, BookmarkAdmin) -------------------------------------------------------------------------------- /backend/api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ApiConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'api' 7 | -------------------------------------------------------------------------------- /backend/api/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2024-03-22 13:49 2 | 3 | from django.conf import settings 4 | import django.contrib.auth.models 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | import django.utils.timezone 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | initial = True 13 | 14 | dependencies = [ 15 | ('auth', '0012_alter_user_first_name_max_length'), 16 | ] 17 | 18 | operations = [ 19 | migrations.CreateModel( 20 | name='User', 21 | fields=[ 22 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 23 | ('password', models.CharField(max_length=128, verbose_name='password')), 24 | ('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), 25 | ('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), 26 | ('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')), 27 | ('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), 28 | ('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), 29 | ('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), 30 | ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), 31 | ('username', models.CharField(max_length=100, unique=True)), 32 | ('email', models.EmailField(max_length=254, unique=True)), 33 | ('full_name', models.CharField(blank=True, max_length=100, null=True)), 34 | ('otp', models.CharField(blank=True, max_length=100, null=True)), 35 | ('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')), 36 | ('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')), 37 | ], 38 | options={ 39 | 'verbose_name': 'user', 40 | 'verbose_name_plural': 'users', 41 | 'abstract': False, 42 | }, 43 | managers=[ 44 | ('objects', django.contrib.auth.models.UserManager()), 45 | ], 46 | ), 47 | migrations.CreateModel( 48 | name='Profile', 49 | fields=[ 50 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 51 | ('image', models.FileField(blank=True, default='default/default-user.jpg', null=True, upload_to='image')), 52 | ('full_name', models.CharField(blank=True, max_length=100, null=True)), 53 | ('bio', models.TextField(blank=True, null=True)), 54 | ('about', models.TextField(blank=True, null=True)), 55 | ('country', models.CharField(blank=True, max_length=100, null=True)), 56 | ('date', models.DateTimeField(auto_now_add=True)), 57 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 58 | ], 59 | ), 60 | ] 61 | -------------------------------------------------------------------------------- /backend/api/migrations/0002_category_post_notification_comment_bookmark.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2024-03-22 14:38 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('api', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Category', 17 | fields=[ 18 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('title', models.CharField(max_length=100)), 20 | ('image', models.FileField(blank=True, null=True, upload_to='image')), 21 | ('slug', models.SlugField(blank=True, null=True, unique=True)), 22 | ], 23 | options={ 24 | 'verbose_name_plural': 'Category', 25 | }, 26 | ), 27 | migrations.CreateModel( 28 | name='Post', 29 | fields=[ 30 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 31 | ('title', models.CharField(max_length=100)), 32 | ('image', models.FileField(blank=True, null=True, upload_to='image')), 33 | ('description', models.TextField(blank=True, null=True)), 34 | ('tags', models.CharField(max_length=100)), 35 | ('view', models.IntegerField(default=0)), 36 | ('slug', models.SlugField(blank=True, null=True, unique=True)), 37 | ('date', models.DateTimeField(auto_now_add=True)), 38 | ('category', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='api.category')), 39 | ('likes', models.ManyToManyField(blank=True, related_name='likes_user', to=settings.AUTH_USER_MODEL)), 40 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 41 | ], 42 | options={ 43 | 'verbose_name_plural': 'Post', 44 | }, 45 | ), 46 | migrations.CreateModel( 47 | name='Notification', 48 | fields=[ 49 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 50 | ('type', models.CharField(choices=[('Like', 'Like'), ('Comment', 'Comment')], max_length=100)), 51 | ('seen', models.BooleanField(default=False)), 52 | ('date', models.DateTimeField(auto_now_add=True)), 53 | ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.post')), 54 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 55 | ], 56 | options={ 57 | 'verbose_name_plural': 'Notification', 58 | }, 59 | ), 60 | migrations.CreateModel( 61 | name='Comment', 62 | fields=[ 63 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 64 | ('name', models.CharField(max_length=100)), 65 | ('email', models.CharField(max_length=100)), 66 | ('comment', models.TextField()), 67 | ('date', models.DateTimeField(auto_now_add=True)), 68 | ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.post')), 69 | ], 70 | options={ 71 | 'verbose_name_plural': 'Comment', 72 | }, 73 | ), 74 | migrations.CreateModel( 75 | name='Bookmark', 76 | fields=[ 77 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 78 | ('date', models.DateTimeField(auto_now_add=True)), 79 | ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.post')), 80 | ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 81 | ], 82 | options={ 83 | 'verbose_name_plural': 'Comment', 84 | }, 85 | ), 86 | ] 87 | -------------------------------------------------------------------------------- /backend/api/migrations/0003_post_status.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2024-03-22 15:00 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('api', '0002_category_post_notification_comment_bookmark'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='post', 15 | name='status', 16 | field=models.CharField(choices=[('Active', 'Active'), ('Draft', 'Draft'), ('Disabled', 'Disabled')], default='Active', max_length=100), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /backend/api/migrations/0004_alter_bookmark_options_comment_reply_profile_author_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2024-03-22 16:04 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('api', '0003_post_status'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterModelOptions( 14 | name='bookmark', 15 | options={'verbose_name_plural': 'Bookmark'}, 16 | ), 17 | migrations.AddField( 18 | model_name='comment', 19 | name='reply', 20 | field=models.TextField(blank=True, null=True), 21 | ), 22 | migrations.AddField( 23 | model_name='profile', 24 | name='author', 25 | field=models.BooleanField(default=False), 26 | ), 27 | migrations.AlterField( 28 | model_name='notification', 29 | name='type', 30 | field=models.CharField(choices=[('Like', 'Like'), ('Comment', 'Comment'), ('Bookmark', 'Bookmark')], max_length=100), 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /backend/api/migrations/0005_post_profile.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2024-03-24 06:15 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('api', '0004_alter_bookmark_options_comment_reply_profile_author_and_more'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='post', 16 | name='profile', 17 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='api.profile'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /backend/api/migrations/0006_profile_facebook_profile_twitter.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2024-03-24 07:00 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('api', '0005_post_profile'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='profile', 15 | name='facebook', 16 | field=models.CharField(blank=True, max_length=100, null=True), 17 | ), 18 | migrations.AddField( 19 | model_name='profile', 20 | name='twitter', 21 | field=models.CharField(blank=True, max_length=100, null=True), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /backend/api/migrations/0007_alter_post_category.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2024-03-24 07:55 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('api', '0006_profile_facebook_profile_twitter'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='post', 16 | name='category', 17 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='posts', to='api.category'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /backend/api/migrations/0008_alter_post_category.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2024-03-24 17:19 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('api', '0007_alter_post_category'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='post', 16 | name='category', 17 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='post', to='api.category'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /backend/api/migrations/0009_alter_post_category.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2 on 2024-03-24 17:20 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('api', '0008_alter_post_category'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='post', 16 | name='category', 17 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='posts', to='api.category'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /backend/api/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/api/migrations/__init__.py -------------------------------------------------------------------------------- /backend/api/migrations/__pycache__/0001_initial.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/api/migrations/__pycache__/0001_initial.cpython-311.pyc -------------------------------------------------------------------------------- /backend/api/migrations/__pycache__/0002_category_post_notification_comment_bookmark.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/api/migrations/__pycache__/0002_category_post_notification_comment_bookmark.cpython-311.pyc -------------------------------------------------------------------------------- /backend/api/migrations/__pycache__/0003_post_status.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/api/migrations/__pycache__/0003_post_status.cpython-311.pyc -------------------------------------------------------------------------------- /backend/api/migrations/__pycache__/0004_alter_bookmark_options_comment_reply_profile_author_and_more.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/api/migrations/__pycache__/0004_alter_bookmark_options_comment_reply_profile_author_and_more.cpython-311.pyc -------------------------------------------------------------------------------- /backend/api/migrations/__pycache__/0005_post_profile.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/api/migrations/__pycache__/0005_post_profile.cpython-311.pyc -------------------------------------------------------------------------------- /backend/api/migrations/__pycache__/0006_profile_facebook_profile_twitter.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/api/migrations/__pycache__/0006_profile_facebook_profile_twitter.cpython-311.pyc -------------------------------------------------------------------------------- /backend/api/migrations/__pycache__/0007_alter_post_category.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/api/migrations/__pycache__/0007_alter_post_category.cpython-311.pyc -------------------------------------------------------------------------------- /backend/api/migrations/__pycache__/0008_alter_post_category.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/api/migrations/__pycache__/0008_alter_post_category.cpython-311.pyc -------------------------------------------------------------------------------- /backend/api/migrations/__pycache__/0009_alter_post_category.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/api/migrations/__pycache__/0009_alter_post_category.cpython-311.pyc -------------------------------------------------------------------------------- /backend/api/migrations/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/api/migrations/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /backend/api/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import AbstractUser 3 | from django.db.models.signals import post_save 4 | from django.utils.html import mark_safe 5 | from django.utils.text import slugify 6 | 7 | from shortuuid.django_fields import ShortUUIDField 8 | import shortuuid 9 | 10 | class User(AbstractUser): 11 | username = models.CharField(unique=True, max_length=100) 12 | email = models.EmailField(unique=True) 13 | full_name = models.CharField(max_length=100, null=True, blank=True) 14 | otp = models.CharField(max_length=100, null=True, blank=True) 15 | 16 | USERNAME_FIELD = 'email' 17 | REQUIRED_FIELDS = ['username'] 18 | 19 | def __str__(self): 20 | return self.email 21 | 22 | def save(self, *args, **kwargs): 23 | email_username, mobile = self.email.split("@") 24 | if self.full_name == "" or self.full_name == None: 25 | self.full_name = email_username 26 | if self.username == "" or self.username == None: 27 | self.username = email_username 28 | 29 | super(User, self).save(*args, **kwargs) 30 | 31 | 32 | class Profile(models.Model): 33 | user = models.OneToOneField(User, on_delete=models.CASCADE) 34 | image = models.FileField(upload_to="image", default="default/default-user.jpg", null=True, blank=True) 35 | full_name = models.CharField(max_length=100, null=True, blank=True) 36 | bio = models.TextField(null=True, blank=True) 37 | about = models.TextField(null=True, blank=True) 38 | author = models.BooleanField(default=False) 39 | country = models.CharField(max_length=100, null=True, blank=True) 40 | facebook = models.CharField(max_length=100, null=True, blank=True) 41 | twitter = models.CharField(max_length=100, null=True, blank=True) 42 | date = models.DateTimeField(auto_now_add=True) 43 | 44 | def __str__(self): 45 | if self.full_name: 46 | return str(self.full_name) 47 | else: 48 | return str(self.user.full_name) 49 | 50 | 51 | def save(self, *args, **kwargs): 52 | if self.full_name == "" or self.full_name == None: 53 | self.full_name = self.user.full_name 54 | super(Profile, self).save(*args, **kwargs) 55 | 56 | def thumbnail(self): 57 | return mark_safe('' % (self.image)) 58 | 59 | 60 | def create_user_profile(sender, instance, created, **kwargs): 61 | if created: 62 | Profile.objects.create(user=instance) 63 | 64 | def save_user_profile(sender, instance, **kwargs): 65 | instance.profile.save() 66 | 67 | post_save.connect(create_user_profile, sender=User) 68 | post_save.connect(save_user_profile, sender=User) 69 | 70 | class Category(models.Model): 71 | title = models.CharField(max_length=100) 72 | image = models.FileField(upload_to="image", null=True, blank=True) 73 | slug = models.SlugField(unique=True, null=True, blank=True) 74 | 75 | def __str__(self): 76 | return self.title 77 | 78 | class Meta: 79 | verbose_name_plural = "Category" 80 | 81 | def save(self, *args, **kwargs): 82 | if self.slug == "" or self.slug == None: 83 | self.slug = slugify(self.title) 84 | super(Category, self).save(*args, **kwargs) 85 | 86 | def post_count(self): 87 | return Post.objects.filter(category=self).count() 88 | 89 | class Post(models.Model): 90 | STATUS = ( 91 | ("Active", "Active"), 92 | ("Draft", "Draft"), 93 | ("Disabled", "Disabled"), 94 | ) 95 | 96 | user = models.ForeignKey(User, on_delete=models.CASCADE) 97 | profile = models.ForeignKey(Profile, on_delete=models.CASCADE, null=True, blank=True) 98 | title = models.CharField(max_length=100) 99 | image = models.FileField(upload_to="image", null=True, blank=True) 100 | description = models.TextField(null=True, blank=True) 101 | tags = models.CharField(max_length=100) 102 | category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, related_name='posts') 103 | status = models.CharField(max_length=100, choices=STATUS, default="Active") 104 | view = models.IntegerField(default=0) 105 | likes = models.ManyToManyField(User, blank=True, related_name="likes_user") 106 | slug = models.SlugField(unique=True, null=True, blank=True) 107 | date = models.DateTimeField(auto_now_add=True) 108 | 109 | def __str__(self): 110 | return self.title 111 | 112 | class Meta: 113 | verbose_name_plural = "Post" 114 | 115 | def save(self, *args, **kwargs): 116 | if self.slug == "" or self.slug == None: 117 | self.slug = slugify(self.title) + "-" + shortuuid.uuid()[:2] 118 | super(Post, self).save(*args, **kwargs) 119 | 120 | def comments(self): 121 | return Comment.objects.filter(post=self).order_by("-id") 122 | 123 | 124 | class Comment(models.Model): 125 | post = models.ForeignKey(Post, on_delete=models.CASCADE) 126 | name = models.CharField(max_length=100) 127 | email = models.CharField(max_length=100) 128 | comment = models.TextField() 129 | reply = models.TextField(null=True, blank=True) 130 | date = models.DateTimeField(auto_now_add=True) 131 | 132 | def __str__(self): 133 | return f"{self.post.title} - {self.name}" 134 | 135 | class Meta: 136 | verbose_name_plural = "Comment" 137 | 138 | 139 | class Bookmark(models.Model): 140 | user = models.ForeignKey(User, on_delete=models.CASCADE) 141 | post = models.ForeignKey(Post, on_delete=models.CASCADE) 142 | date = models.DateTimeField(auto_now_add=True) 143 | 144 | def __str__(self): 145 | return f"{self.post.title} - {self.user.username}" 146 | 147 | class Meta: 148 | verbose_name_plural = "Bookmark" 149 | 150 | 151 | class Notification(models.Model): 152 | NOTI_TYPE = ( ("Like", "Like"), ("Comment", "Comment"), ("Bookmark", "Bookmark")) 153 | user = models.ForeignKey(User, on_delete=models.CASCADE) 154 | post = models.ForeignKey(Post, on_delete=models.CASCADE) 155 | type = models.CharField(max_length=100, choices=NOTI_TYPE) 156 | seen = models.BooleanField(default=False) 157 | date = models.DateTimeField(auto_now_add=True) 158 | 159 | class Meta: 160 | verbose_name_plural = "Notification" 161 | 162 | def __str__(self): 163 | if self.post: 164 | return f"{self.type} - {self.post.title}" 165 | else: 166 | return "Notification" -------------------------------------------------------------------------------- /backend/api/serializer.py: -------------------------------------------------------------------------------- 1 | from django.contrib.auth.password_validation import validate_password 2 | from rest_framework_simplejwt.serializers import TokenObtainPairSerializer 3 | from rest_framework import serializers 4 | from rest_framework.validators import UniqueValidator 5 | from rest_framework_simplejwt.serializers import TokenObtainPairSerializer 6 | 7 | from api import models as api_models 8 | 9 | # Define a custom serializer that inherits from TokenObtainPairSerializer 10 | class MyTokenObtainPairSerializer(TokenObtainPairSerializer): 11 | ''' 12 | class MyTokenObtainPairSerializer(TokenObtainPairSerializer):: This line creates a new token serializer called MyTokenObtainPairSerializer that is based on an existing one called TokenObtainPairSerializer. Think of it as customizing the way tokens work. 13 | @classmethod: This line indicates that the following function is a class method, which means it belongs to the class itself and not to an instance (object) of the class. 14 | def get_token(cls, user):: This is a function (or method) that gets called when we want to create a token for a user. The user is the person who's trying to access something on the website. 15 | token = super().get_token(user): Here, it's asking for a regular token from the original token serializer (the one it's based on). This regular token is like a key to enter the website. 16 | token['full_name'] = user.full_name, token['email'] = user.email, token['username'] = user.username: This code is customizing the token by adding extra information to it. For example, it's putting the user's full name, email, and username into the token. These are like special notes attached to the key. 17 | return token: Finally, the customized token is given back to the user. Now, when this token is used, it not only lets the user in but also carries their full name, email, and username as extra information, which the website can use as needed. 18 | ''' 19 | @classmethod 20 | # Define a custom method to get the token for a user 21 | def get_token(cls, user): 22 | # Call the parent class's get_token method 23 | token = super().get_token(user) 24 | 25 | # Add custom claims to the token 26 | token['full_name'] = user.full_name 27 | token['email'] = user.email 28 | token['username'] = user.username 29 | try: 30 | token['vendor_id'] = user.vendor.id 31 | except: 32 | token['vendor_id'] = 0 33 | 34 | # ... 35 | 36 | # Return the token with custom claims 37 | return token 38 | 39 | # Define a serializer for user registration, which inherits from serializers.ModelSerializer 40 | class RegisterSerializer(serializers.ModelSerializer): 41 | # Define fields for the serializer, including password and password2 42 | password = serializers.CharField(write_only=True, required=True, validators=[validate_password]) 43 | password2 = serializers.CharField(write_only=True, required=True) 44 | 45 | class Meta: 46 | # Specify the model that this serializer is associated with 47 | model = api_models.User 48 | # Define the fields from the model that should be included in the serializer 49 | fields = ('full_name', 'email', 'password', 'password2') 50 | 51 | def validate(self, attrs): 52 | # Define a validation method to check if the passwords match 53 | if attrs['password'] != attrs['password2']: 54 | # Raise a validation error if the passwords don't match 55 | raise serializers.ValidationError({"password": "Password fields didn't match."}) 56 | 57 | # Return the validated attributes 58 | return attrs 59 | 60 | def create(self, validated_data): 61 | # Define a method to create a new user based on validated data 62 | user = api_models.User.objects.create( 63 | full_name=validated_data['full_name'], 64 | email=validated_data['email'], 65 | ) 66 | email_username, mobile = user.email.split('@') 67 | user.username = email_username 68 | 69 | # Set the user's password based on the validated data 70 | user.set_password(validated_data['password']) 71 | user.save() 72 | 73 | # Return the created user 74 | return user 75 | 76 | class UserSerializer(serializers.ModelSerializer): 77 | 78 | class Meta: 79 | model = api_models.User 80 | fields = '__all__' 81 | 82 | class ProfileSerializer(serializers.ModelSerializer): 83 | 84 | class Meta: 85 | model = api_models.Profile 86 | fields = '__all__' 87 | 88 | def to_representation(self, instance): 89 | response = super().to_representation(instance) 90 | response['user'] = UserSerializer(instance.user).data 91 | return response 92 | 93 | class PasswordResetSerializer(serializers.Serializer): 94 | email = serializers.EmailField() 95 | 96 | 97 | 98 | class CategorySerializer(serializers.ModelSerializer): 99 | post_count = serializers.SerializerMethodField() 100 | 101 | ''' 102 | category.post_set: In Django, when you define a ForeignKey relationship from one model to another 103 | (e.g., Post model having a ForeignKey relationship to the Category model), 104 | Django creates a reverse relationship from the related model back to the model that has the ForeignKey. 105 | By default, this reverse relationship is named _set. In this case, since the Post model has a 106 | ForeignKey to the Category model, Django creates a reverse relationship from Category to Post named post_set. 107 | This allows you to access all Post objects related to a Category instance. 108 | ''' 109 | def get_post_count(self, category): 110 | return category.posts.count() 111 | 112 | class Meta: 113 | model = api_models.Category 114 | fields = [ 115 | "id", 116 | "title", 117 | "image", 118 | "slug", 119 | "post_count", 120 | ] 121 | 122 | def __init__(self, *args, **kwargs): 123 | super(CategorySerializer, self).__init__(*args, **kwargs) 124 | request = self.context.get('request') 125 | if request and request.method == 'POST': 126 | self.Meta.depth = 0 127 | else: 128 | self.Meta.depth = 3 129 | 130 | class CommentSerializer(serializers.ModelSerializer): 131 | 132 | class Meta: 133 | model = api_models.Comment 134 | fields = "__all__" 135 | 136 | def __init__(self, *args, **kwargs): 137 | super(CommentSerializer, self).__init__(*args, **kwargs) 138 | request = self.context.get('request') 139 | if request and request.method == 'POST': 140 | self.Meta.depth = 0 141 | else: 142 | self.Meta.depth = 1 143 | 144 | 145 | class PostSerializer(serializers.ModelSerializer): 146 | comments = CommentSerializer(many=True) 147 | 148 | class Meta: 149 | model = api_models.Post 150 | fields = "__all__" 151 | 152 | def __init__(self, *args, **kwargs): 153 | super(PostSerializer, self).__init__(*args, **kwargs) 154 | request = self.context.get('request') 155 | if request and request.method == 'POST': 156 | self.Meta.depth = 0 157 | else: 158 | self.Meta.depth = 3 159 | 160 | 161 | 162 | class BookmarkSerializer(serializers.ModelSerializer): 163 | 164 | class Meta: 165 | model = api_models.Bookmark 166 | fields = "__all__" 167 | 168 | 169 | def __init__(self, *args, **kwargs): 170 | super(BookmarkSerializer, self).__init__(*args, **kwargs) 171 | request = self.context.get('request') 172 | if request and request.method == 'POST': 173 | self.Meta.depth = 0 174 | else: 175 | self.Meta.depth = 3 176 | 177 | class NotificationSerializer(serializers.ModelSerializer): 178 | 179 | class Meta: 180 | model = api_models.Notification 181 | fields = "__all__" 182 | 183 | def __init__(self, *args, **kwargs): 184 | super(NotificationSerializer, self).__init__(*args, **kwargs) 185 | request = self.context.get('request') 186 | if request and request.method == 'POST': 187 | self.Meta.depth = 0 188 | else: 189 | self.Meta.depth = 3 190 | 191 | class AuthorStats(serializers.Serializer): 192 | views = serializers.IntegerField(default=0) 193 | posts = serializers.IntegerField(default=0) 194 | likes = serializers.IntegerField(default=0) 195 | bookmarks = serializers.IntegerField(default=0) -------------------------------------------------------------------------------- /backend/api/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /backend/api/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from rest_framework_simplejwt.views import TokenRefreshView 3 | from api import views as api_views 4 | 5 | urlpatterns = [ 6 | # Userauths API Endpoints 7 | path('user/token/', api_views.MyTokenObtainPairView.as_view(), name='token_obtain_pair'), 8 | path('user/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), 9 | path('user/register/', api_views.RegisterView.as_view(), name='auth_register'), 10 | path('user/profile//', api_views.ProfileView.as_view(), name='user_profile'), 11 | path('user/password-reset//', api_views.PasswordEmailVerify.as_view(), name='password_reset'), 12 | path('user/password-change/', api_views.PasswordChangeView.as_view(), name='password_reset'), 13 | 14 | # Post Endpoints 15 | path('post/category/list/', api_views.CategoryListAPIView.as_view()), 16 | path('post/category/posts//', api_views.PostCategoryListAPIView.as_view()), 17 | path('post/lists/', api_views.PostListAPIView.as_view()), 18 | path('post/detail//', api_views.PostDetailAPIView.as_view()), 19 | path('post/like-post/', api_views.LikePostAPIView.as_view()), 20 | path('post/comment-post/', api_views.PostCommentAPIView.as_view()), 21 | path('post/bookmark-post/', api_views.BookmarkPostAPIView.as_view()), 22 | 23 | # Dashboard APIS 24 | path('author/dashboard/stats//', api_views.DashboardStats.as_view()), 25 | path('author/dashboard/post-list//', api_views.DashboardPostLists.as_view()), 26 | path('author/dashboard/comment-list/', api_views.DashboardCommentLists.as_view()), 27 | path('author/dashboard/noti-list//', api_views.DashboardNotificationLists.as_view()), 28 | path('author/dashboard/noti-mark-seen/', api_views.DashboardMarkNotiSeenAPIView.as_view()), 29 | path('author/dashboard/reply-comment/', api_views.DashboardPostCommentAPIView.as_view()), 30 | path('author/dashboard/post-create/', api_views.DashboardPostCreateAPIView.as_view()), 31 | path('author/dashboard/post-detail///', api_views.DashboardPostEditAPIView.as_view()), 32 | 33 | 34 | ] -------------------------------------------------------------------------------- /backend/backend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/backend/__init__.py -------------------------------------------------------------------------------- /backend/backend/__pycache__/__init__.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/backend/__pycache__/__init__.cpython-311.pyc -------------------------------------------------------------------------------- /backend/backend/__pycache__/settings.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/backend/__pycache__/settings.cpython-311.pyc -------------------------------------------------------------------------------- /backend/backend/__pycache__/urls.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/backend/__pycache__/urls.cpython-311.pyc -------------------------------------------------------------------------------- /backend/backend/__pycache__/wsgi.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/backend/__pycache__/wsgi.cpython-311.pyc -------------------------------------------------------------------------------- /backend/backend/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for backend project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /backend/backend/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | URL configuration for backend project. 3 | 4 | The `urlpatterns` list routes URLs to views. For more information please see: 5 | https://docs.djangoproject.com/en/4.2/topics/http/urls/ 6 | Examples: 7 | Function views 8 | 1. Add an import: from my_app import views 9 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 10 | Class-based views 11 | 1. Add an import: from other_app.views import Home 12 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 13 | Including another URLconf 14 | 1. Import the include() function: from django.urls import include, path 15 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 16 | """ 17 | from django.contrib import admin 18 | from django.urls import path, include 19 | from django.conf import settings 20 | from django.conf.urls.static import static 21 | 22 | from rest_framework import permissions 23 | from drf_yasg.views import get_schema_view 24 | from drf_yasg import openapi 25 | 26 | schema_view = get_schema_view( 27 | openapi.Info( 28 | title="Blog Backend APIs", 29 | default_version="v1", 30 | description="This is the documentation for the backend API", 31 | terms_of_service="http://mywbsite.com/policies/", 32 | contact=openapi.Contact(email="desphixs@gmail.com"), 33 | license=openapi.License(name="BSD Licence"), 34 | ), 35 | public=True, 36 | permission_classes = (permissions.AllowAny, ) 37 | ) 38 | 39 | urlpatterns = [ 40 | path("", schema_view.with_ui('swagger', cache_timeout=0), name="schema-swagger-ui"), 41 | path('admin/', admin.site.urls), 42 | path('api/v1/', include("api.urls")), 43 | 44 | ] 45 | 46 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 47 | urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) -------------------------------------------------------------------------------- /backend/backend/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for backend project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /backend/db.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/db.sqlite3 -------------------------------------------------------------------------------- /backend/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'backend.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /backend/media/image/0x0.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/media/image/0x0.webp -------------------------------------------------------------------------------- /backend/media/image/15274003_5586333.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/media/image/15274003_5586333.jpg -------------------------------------------------------------------------------- /backend/media/image/218884489_1489407691458770_8916744002593220091_n.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/media/image/218884489_1489407691458770_8916744002593220091_n.jpg -------------------------------------------------------------------------------- /backend/media/image/4236992_2264344.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/media/image/4236992_2264344.jpg -------------------------------------------------------------------------------- /backend/media/image/8847298_4003914.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/media/image/8847298_4003914.jpg -------------------------------------------------------------------------------- /backend/media/image/Desphixs_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/media/image/Desphixs_1.jpg -------------------------------------------------------------------------------- /backend/media/image/Desphixs_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/media/image/Desphixs_2.jpg -------------------------------------------------------------------------------- /backend/media/image/Desphixs_2_q3BUhWS.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/media/image/Desphixs_2_q3BUhWS.jpg -------------------------------------------------------------------------------- /backend/media/image/Desphixs_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/media/image/Desphixs_3.jpg -------------------------------------------------------------------------------- /backend/media/image/Desphixs_3_1EcqGQF.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/media/image/Desphixs_3_1EcqGQF.jpg -------------------------------------------------------------------------------- /backend/media/image/avatar-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/media/image/avatar-3.jpg -------------------------------------------------------------------------------- /backend/media/image/avatar-lg-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/media/image/avatar-lg-3.jpg -------------------------------------------------------------------------------- /backend/media/image/futuristic-sports-car-wallpaper-1920x1200_6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/media/image/futuristic-sports-car-wallpaper-1920x1200_6.jpg -------------------------------------------------------------------------------- /backend/media/image/ins-gallery_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/media/image/ins-gallery_5.jpg -------------------------------------------------------------------------------- /backend/media/image/post-banner_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/media/image/post-banner_1.jpg -------------------------------------------------------------------------------- /backend/media/image/post-banner_1_1ZybySx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/media/image/post-banner_1_1ZybySx.jpg -------------------------------------------------------------------------------- /backend/media/image/post-banner_1_UUhTNSN.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/media/image/post-banner_1_UUhTNSN.jpg -------------------------------------------------------------------------------- /backend/media/image/post-xl_41.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/media/image/post-xl_41.jpg -------------------------------------------------------------------------------- /backend/media/image/post-xl_41_IJhagQf.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/media/image/post-xl_41_IJhagQf.jpg -------------------------------------------------------------------------------- /backend/media/image/post-xl_41_yE1iEOe.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/media/image/post-xl_41_yE1iEOe.jpg -------------------------------------------------------------------------------- /backend/media/image/store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/media/image/store.png -------------------------------------------------------------------------------- /backend/media/image/wewe.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/media/image/wewe.jpg -------------------------------------------------------------------------------- /backend/media/image/wewe_XUDxYlk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/media/image/wewe_XUDxYlk.jpg -------------------------------------------------------------------------------- /backend/media/image/wewe_bhcJf71.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/media/image/wewe_bhcJf71.jpg -------------------------------------------------------------------------------- /backend/requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.7.2 2 | Django==4.2 3 | django-anymail==9.1 4 | django-ckeditor==6.7.0 5 | django-ckeditor-5==0.2.10 6 | django-cors-headers==3.14.0 7 | django-dotenv==1.4.2 8 | django-environ==0.9.0 9 | django-import-export==3.2.0 10 | django-jazzmin==2.6.0 11 | django-rest-framework==0.1.0 12 | djangorestframework==3.14.0 13 | djangorestframework-simplejwt==5.2.2 14 | drf-yasg==1.21.7 15 | humanize==4.6.0 16 | requests==2.31.0 17 | shortuuid==1.0.11 18 | sqlparse==0.4.4 19 | tzdata==2023.3 20 | -------------------------------------------------------------------------------- /backend/templates/email/password_reset.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 |

Hi {{ username }},

10 |

You are receiving this email because you requested a password reset, use the link below to reset your password

11 | Click here to continue 12 | 13 | -------------------------------------------------------------------------------- /backend/templates/email/password_reset.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/backend/templates/email/password_reset.txt -------------------------------------------------------------------------------- /frontend/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:react/recommended', 7 | 'plugin:react/jsx-runtime', 8 | 'plugin:react-hooks/recommended', 9 | ], 10 | ignorePatterns: ['dist', '.eslintrc.cjs'], 11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 12 | settings: { react: { version: '18.2' } }, 13 | plugins: ['react-refresh'], 14 | rules: { 15 | 'react/jsx-no-target-blank': 'off', 16 | 'react-refresh/only-export-components': [ 17 | 'warn', 18 | { allowConstantExport: true }, 19 | ], 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # React + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Django React Blog App 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@ckeditor/ckeditor5-build-classic": "^40.2.0", 14 | "@ckeditor/ckeditor5-react": "^6.2.0", 15 | "@paypal/react-paypal-js": "^8.1.3", 16 | "axios": "^1.5.1", 17 | "chart.js": "^4.4.0", 18 | "dayjs": "^1.11.10", 19 | "js-cookie": "^3.0.5", 20 | "jwt-decode": "^3.1.2", 21 | "moment": "^2.29.4", 22 | "react": "^18.2.0", 23 | "react-chartjs-2": "^5.2.0", 24 | "react-dom": "^18.2.0", 25 | "react-hook-form": "^7.48.2", 26 | "react-icons": "^4.11.0", 27 | "react-photo-album": "^2.3.0", 28 | "react-router-dom": "6.10.0", 29 | "sweetalert2": "^11.7.32", 30 | "yet-another-react-lightbox": "^3.14.0", 31 | "zustand": "^4.4.4" 32 | }, 33 | "devDependencies": { 34 | "@types/react": "^18.2.15", 35 | "@types/react-dom": "^18.2.7", 36 | "@vitejs/plugin-react": "^4.0.3", 37 | "eslint": "^8.45.0", 38 | "eslint-plugin-react": "^7.32.2", 39 | "eslint-plugin-react-hooks": "^4.6.0", 40 | "eslint-plugin-react-refresh": "^0.4.3", 41 | "prettier": "^3.0.3", 42 | "simple-zustand-devtools": "^1.1.0", 43 | "vite": "^4.4.5" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /frontend/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/desphixs/Django-and-React-Blog/54b7faa480897dde9a290f2a3f9d320df0777265/frontend/public/logo.png -------------------------------------------------------------------------------- /frontend/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | /* #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } */ 43 | -------------------------------------------------------------------------------- /frontend/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { Route, Routes, BrowserRouter } from "react-router-dom"; 3 | import Index from "./views/core/Index"; 4 | import Detail from "./views/core/Detail"; 5 | import Search from "./views/core/Search"; 6 | import Category from "./views/core/Category"; 7 | import About from "./views/pages/About"; 8 | import Contact from "./views/pages/Contact"; 9 | import Register from "./views/auth/Register"; 10 | import Login from "./views/auth/Login"; 11 | import Logout from "./views/auth/Logout"; 12 | import ForgotPassword from "./views/auth/ForgotPassword"; 13 | import CreatePassword from "./views/auth/CreatePassword"; 14 | import Dashboard from "./views/dashboard/Dashboard"; 15 | import Posts from "./views/dashboard/Posts"; 16 | import AddPost from "./views/dashboard/AddPost"; 17 | import EditPost from "./views/dashboard/EditPost"; 18 | import Comments from "./views/dashboard/Comments"; 19 | import Notifications from "./views/dashboard/Notifications"; 20 | import Profile from "./views/dashboard/Profile"; 21 | 22 | import MainWrapper from "../src/layouts/MainWrapper"; 23 | function App() { 24 | return ( 25 | <> 26 | 27 | 28 | 29 | } /> 30 | } /> 31 | } /> 32 | } /> 33 | 34 | {/* Authentication */} 35 | } /> 36 | } /> 37 | } /> 38 | } /> 39 | } /> 40 | 41 | {/* Dashboard */} 42 | } /> 43 | } /> 44 | } /> 45 | } /> 46 | } /> 47 | } /> 48 | } /> 49 | 50 | {/* Pages */} 51 | } /> 52 | } /> 53 | 54 | 55 | 56 | 57 | ); 58 | } 59 | 60 | export default App; 61 | -------------------------------------------------------------------------------- /frontend/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | /* :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | } 15 | 16 | a { 17 | font-weight: 500; 18 | color: #646cff; 19 | text-decoration: inherit; 20 | } 21 | a:hover { 22 | color: #535bf2; 23 | } 24 | 25 | body { 26 | margin: 0; 27 | display: flex; 28 | place-items: center; 29 | min-width: 320px; 30 | min-height: 100vh; 31 | } 32 | 33 | h1 { 34 | font-size: 3.2em; 35 | line-height: 1.1; 36 | } 37 | 38 | button { 39 | border-radius: 8px; 40 | border: 1px solid transparent; 41 | padding: 0.6em 1.2em; 42 | font-size: 1em; 43 | font-weight: 500; 44 | font-family: inherit; 45 | background-color: #1a1a1a; 46 | cursor: pointer; 47 | transition: border-color 0.25s; 48 | } 49 | button:hover { 50 | border-color: #646cff; 51 | } 52 | button:focus, 53 | button:focus-visible { 54 | outline: 4px auto -webkit-focus-ring-color; 55 | } 56 | 57 | @media (prefers-color-scheme: light) { 58 | :root { 59 | color: #213547; 60 | background-color: #ffffff; 61 | } 62 | a:hover { 63 | color: #747bff; 64 | } 65 | button { 66 | background-color: #f9f9f9; 67 | } 68 | } */ 69 | -------------------------------------------------------------------------------- /frontend/src/layouts/MainWrapper.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { setUser } from '../utils/auth'; 3 | 4 | const MainWrapper = ({ children }) => { 5 | // Initialize the 'loading' state variable and set its initial value to 'true' 6 | const [loading, setLoading] = useState(true); 7 | 8 | // Define a useEffect hook to handle side effects after component mounting 9 | useEffect(() => { 10 | // Define an asynchronous function 'handler' 11 | const handler = async () => { 12 | // Set the 'loading' state to 'true' to indicate the component is loading 13 | setLoading(true); 14 | 15 | // Perform an asynchronous user authentication action 16 | await setUser(); 17 | 18 | // Set the 'loading' state to 'false' to indicate the loading process has completed 19 | setLoading(false); 20 | }; 21 | 22 | // Call the 'handler' function immediately after the component is mounted 23 | handler(); 24 | }, []); 25 | 26 | // Render content conditionally based on the 'loading' state 27 | return <>{loading ? null : children}; 28 | }; 29 | 30 | export default MainWrapper; 31 | -------------------------------------------------------------------------------- /frontend/src/layouts/PrivateRoute.jsx: -------------------------------------------------------------------------------- 1 | // Import the 'Navigate' component from the 'react-router-dom' library. 2 | import { Navigate } from 'react-router-dom'; 3 | 4 | // Import the 'useAuthStore' function from a custom 'auth' store. 5 | import { useAuthStore } from '../store/auth'; 6 | 7 | // Define the 'PrivateRoute' component as a functional component that takes 'children' as a prop. 8 | const PrivateRoute = ({ children }) => { 9 | 10 | // Use the 'useAuthStore' hook to check the user's authentication status. 11 | // It appears to be using a state management solution like 'zustand' or 'mobx-state-tree'. 12 | const loggedIn = useAuthStore((state) => state.isLoggedIn)(); 13 | 14 | // Conditionally render the children if the user is authenticated. 15 | // If the user is not authenticated, redirect them to the login page. 16 | return loggedIn ? <>{children} : ; 17 | }; 18 | 19 | // Export the 'PrivateRoute' component to make it available for use in other parts of the application. 20 | export default PrivateRoute; 21 | -------------------------------------------------------------------------------- /frontend/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.jsx' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /frontend/src/plugin/Moment.js: -------------------------------------------------------------------------------- 1 | import moment from "moment"; 2 | 3 | function Moment(date) { 4 | return moment(date).format("DD MMM, YYYY."); 5 | } 6 | export default Moment; 7 | -------------------------------------------------------------------------------- /frontend/src/plugin/Toast.js: -------------------------------------------------------------------------------- 1 | import Swal from "sweetalert2"; 2 | 3 | function Toast(icon, title, text) { 4 | const Toast = Swal.mixin({ 5 | toast: true, 6 | position: "top", 7 | showConfirmButton: false, 8 | timer: 1500, 9 | timerProgressBar: true, 10 | }); 11 | 12 | return Toast.fire({ 13 | icon: icon, 14 | title: title, 15 | text: text, 16 | }); 17 | } 18 | 19 | export default Toast; 20 | -------------------------------------------------------------------------------- /frontend/src/plugin/useUserData.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Cookies from "js-cookie"; 3 | import jwtDecode from "jwt-decode"; 4 | 5 | function useUserData() { 6 | // Retrieve the access token and refresh token from browser cookies 7 | let access_token = Cookies.get("access_token"); 8 | let refresh_token = Cookies.get("refresh_token"); 9 | 10 | if (access_token && refresh_token) { 11 | // Both access and refresh tokens exist 12 | // Decode the refresh token to extract user information 13 | const token = refresh_token; 14 | const decoded = jwtDecode(token); 15 | 16 | // Extract the user's unique identifier (user_id) from the decoded token 17 | // Return the decoded user data, which may include user information 18 | return decoded; 19 | } else { 20 | // One or both tokens (access or refresh) are missing 21 | // This block handles the case when either token is not present in the cookies. 22 | // You may want to perform error handling or redirection here 23 | // if access or refresh tokens are not available, based on your application's requirements. 24 | // For instance, you can uncomment the following lines to log a message: 25 | // console.log("Access token or refresh token is missing."); 26 | // Depending on your application, you might want to display a login form 27 | // or redirect the user to a login page if the tokens are missing. 28 | // This function is expected to return `undefined` if tokens are missing. 29 | // Handle the missing tokens as needed based on your application's logic. 30 | } 31 | } 32 | 33 | export default useUserData; 34 | -------------------------------------------------------------------------------- /frontend/src/store/auth.js: -------------------------------------------------------------------------------- 1 | // Import the 'create' function from the 'zustand' library. 2 | import { create } from 'zustand'; 3 | 4 | // Import the 'mountStoreDevtool' function from the 'simple-zustand-devtools' library. 5 | import { mountStoreDevtool } from 'simple-zustand-devtools'; 6 | 7 | // Create a custom Zustand store named 'useAuthStore' using the 'create' function. 8 | const useAuthStore = create((set, get) => ({ 9 | // Define the 'allUserData' state variable and initialize it to null. 10 | allUserData: null, // Use this to store all user data 11 | 12 | // Define the 'loading' state variable and initialize it to false. 13 | loading: false, 14 | 15 | // Define a function 'user' that returns an object with user-related data. 16 | user: () => ({ 17 | user_id: get().allUserData?.user_id || null, 18 | username: get().allUserData?.username || null, 19 | }), 20 | 21 | // Define a function 'setUser' that allows setting the 'allUserData' state. 22 | setUser: (user) => set({ allUserData: user }), 23 | 24 | // Define a function 'setLoading' that allows setting the 'loading' state. 25 | setLoading: (loading) => set({ loading }), 26 | 27 | // Define a function 'isLoggedIn' that checks if 'allUserData' is not null. 28 | isLoggedIn: () => get().allUserData !== null, 29 | })); 30 | 31 | // Conditionally attach the DevTools only in a development environment. 32 | if (import.meta.env.DEV) { 33 | mountStoreDevtool('Store', useAuthStore); 34 | } 35 | 36 | // Export the 'useAuthStore' for use in other parts of the application. 37 | export { useAuthStore }; 38 | -------------------------------------------------------------------------------- /frontend/src/utils/auth.js: -------------------------------------------------------------------------------- 1 | // Importing the useAuthStore hook from the '../store/auth' file to manage authentication state 2 | import { useAuthStore } from "../store/auth"; 3 | 4 | // Importing the axios library for making HTTP requests 5 | import axios from "./axios"; 6 | 7 | // Importing jwt_decode to decode JSON Web Tokens 8 | import jwt_decode from "jwt-decode"; 9 | 10 | // Importing the Cookies library to handle browser cookies 11 | import Cookies from "js-cookie"; 12 | 13 | // Importing Swal (SweetAlert2) for displaying toast notifications 14 | import Swal from "sweetalert2"; 15 | 16 | // Configuring global toast notifications using Swal.mixin 17 | const Toast = Swal.mixin({ 18 | toast: true, 19 | position: "top", 20 | showConfirmButton: false, 21 | timer: 1500, 22 | timerProgressBar: true, 23 | }); 24 | 25 | // Function to handle user login 26 | export const login = async (email, password) => { 27 | try { 28 | // Making a POST request to obtain user tokens 29 | const { data, status } = await axios.post("user/token/", { 30 | email, 31 | password, 32 | }); 33 | 34 | // If the request is successful (status code 200), set authentication user and display success toast 35 | if (status === 200) { 36 | setAuthUser(data.access, data.refresh); 37 | 38 | // Displaying a success toast notification 39 | Toast.fire({ 40 | icon: "success", 41 | title: "Signed in successfully", 42 | }); 43 | } 44 | 45 | // Returning data and error information 46 | return { data, error: null }; 47 | } catch (error) { 48 | // Handling errors and returning data and error information 49 | return { 50 | data: null, 51 | error: error.response.data?.detail || "Something went wrong", 52 | }; 53 | } 54 | }; 55 | 56 | // Function to handle user registration 57 | export const register = async (full_name, email, password, password2) => { 58 | try { 59 | // Making a POST request to register a new user 60 | const { data } = await axios.post("user/register/", { 61 | full_name, 62 | email, 63 | password, 64 | password2, 65 | }); 66 | 67 | // Logging in the newly registered user and displaying success toast 68 | await login(email, password); 69 | 70 | // Displaying a success toast notification 71 | Toast.fire({ 72 | icon: "success", 73 | title: "Signed Up Successfully", 74 | }); 75 | 76 | // Returning data and error information 77 | return { data, error: null }; 78 | } catch (error) { 79 | // Handling errors and returning data and error information 80 | return { 81 | data: null, 82 | error: error.response.data || "Something went wrong", 83 | }; 84 | } 85 | }; 86 | 87 | // Function to handle user logout 88 | export const logout = () => { 89 | // Removing access and refresh tokens from cookies, resetting user state, and displaying success toast 90 | Cookies.remove("access_token"); 91 | Cookies.remove("refresh_token"); 92 | useAuthStore.getState().setUser(null); 93 | 94 | // Displaying a success toast notification 95 | Toast.fire({ 96 | icon: "success", 97 | title: "You have been logged out.", 98 | }); 99 | }; 100 | 101 | // Function to set the authenticated user on page load 102 | export const setUser = async () => { 103 | // Retrieving access and refresh tokens from cookies 104 | const accessToken = Cookies.get("access_token"); 105 | const refreshToken = Cookies.get("refresh_token"); 106 | 107 | // Checking if tokens are present 108 | if (!accessToken || !refreshToken) { 109 | return; 110 | } 111 | 112 | // If access token is expired, refresh it; otherwise, set the authenticated user 113 | if (isAccessTokenExpired(accessToken)) { 114 | const response = await getRefreshToken(refreshToken); 115 | setAuthUser(response.access, response.refresh); 116 | } else { 117 | setAuthUser(accessToken, refreshToken); 118 | } 119 | }; 120 | 121 | // Function to set the authenticated user and update user state 122 | export const setAuthUser = (access_token, refresh_token) => { 123 | // Setting access and refresh tokens in cookies with expiration dates 124 | Cookies.set("access_token", access_token, { 125 | expires: 1, // Access token expires in 1 day 126 | secure: true, 127 | }); 128 | 129 | Cookies.set("refresh_token", refresh_token, { 130 | expires: 7, // Refresh token expires in 7 days 131 | secure: true, 132 | }); 133 | 134 | // Decoding access token to get user information 135 | const user = jwt_decode(access_token) ?? null; 136 | 137 | // If user information is present, update user state; otherwise, set loading state to false 138 | if (user) { 139 | useAuthStore.getState().setUser(user); 140 | } 141 | useAuthStore.getState().setLoading(false); 142 | }; 143 | 144 | // Function to refresh the access token using the refresh token 145 | export const getRefreshToken = async () => { 146 | // Retrieving refresh token from cookies and making a POST request to refresh the access token 147 | const refresh_token = Cookies.get("refresh_token"); 148 | const response = await axios.post("user/token/refresh/", { 149 | refresh: refresh_token, 150 | }); 151 | 152 | // Returning the refreshed access token 153 | return response.data; 154 | }; 155 | 156 | // Function to check if the access token is expired 157 | export const isAccessTokenExpired = (accessToken) => { 158 | try { 159 | // Decoding the access token and checking if it has expired 160 | const decodedToken = jwt_decode(accessToken); 161 | return decodedToken.exp < Date.now() / 1000; 162 | } catch (err) { 163 | // Returning true if the token is invalid or expired 164 | return true; 165 | } 166 | }; 167 | -------------------------------------------------------------------------------- /frontend/src/utils/axios.js: -------------------------------------------------------------------------------- 1 | // Import the Axios library to make HTTP requests. Axios is a popular JavaScript library for this purpose. 2 | import axios from 'axios'; 3 | 4 | // Create an instance of Axios and store it in the 'apiInstance' variable. This instance will have specific configuration options. 5 | const apiInstance = axios.create({ 6 | // Set the base URL for this instance. All requests made using this instance will have this URL as their starting point. 7 | baseURL: 'http://127.0.0.1:8000/api/v1/', 8 | 9 | // Set a timeout for requests made using this instance. If a request takes longer than 5 seconds to complete, it will be canceled. 10 | timeout: 50000, // timeout after 5 seconds 11 | 12 | // Define headers that will be included in every request made using this instance. This is common for specifying the content type and accepted response type. 13 | headers: { 14 | 'Content-Type': 'application/json', // The request will be sending data in JSON format. 15 | Accept: 'application/json', // The request expects a response in JSON format. 16 | }, 17 | }); 18 | 19 | // Export the 'apiInstance' so that it can be used in other parts of the codebase. Other modules can import and use this Axios instance for making API requests. 20 | export default apiInstance; 21 | -------------------------------------------------------------------------------- /frontend/src/utils/constants.js: -------------------------------------------------------------------------------- 1 | import UserData from "../views/plugin/UserData"; 2 | 3 | export const API_BASE_URL = "http://127.0.0.1:8000/api/v1/"; 4 | export const SERVER_URL = "http://127.0.0.1:8000"; 5 | export const CLIENT_URL = "http://localhost:5173"; 6 | export const PAYPAL_CLIENT_ID = "test"; 7 | export const CURRENCY_SIGN = "$"; 8 | export const userId = UserData()?.user_id; 9 | export const teacherId = UserData()?.teacher_id; 10 | console.log(teacherId); 11 | -------------------------------------------------------------------------------- /frontend/src/utils/useAxios.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { getRefreshToken, isAccessTokenExpired, setAuthUser } from './auth'; // Import authentication-related functions 3 | import { API_BASE_URL } from './constants'; // Import the base API URL 4 | import Cookies from 'js-cookie'; // Import the 'js-cookie' library for managing cookies 5 | 6 | // Define a custom Axios instance creator function 7 | const useAxios = () => { 8 | // Retrieve the access and refresh tokens from cookies 9 | const accessToken = Cookies.get('access_token'); 10 | const refreshToken = Cookies.get('refresh_token'); 11 | 12 | 13 | 14 | // Create an Axios instance with base URL and access token in the headers 15 | const axiosInstance = axios.create({ 16 | baseURL: API_BASE_URL, 17 | headers: { Authorization: `Bearer ${accessToken}` }, 18 | }); 19 | 20 | // Add an interceptor to the Axios instance 21 | axiosInstance.interceptors.request.use(async (req) => { 22 | // Check if the access token is expired 23 | if (!isAccessTokenExpired(accessToken)) { 24 | return req; // If not expired, return the original request 25 | } 26 | 27 | // If the access token is expired, refresh it 28 | const response = await getRefreshToken(refreshToken); 29 | // console.log('Refresh Token Response:', response); 30 | // Update the application with the new access and refresh tokens 31 | setAuthUser(response.access, response.refresh); 32 | 33 | // Update the request's 'Authorization' header with the new access token 34 | req.headers.Authorization = `Bearer ${response?.data?.access}`; 35 | return req; // Return the updated request 36 | }); 37 | 38 | return axiosInstance; // Return the custom Axios instance 39 | }; 40 | 41 | export default useAxios; // Export the custom Axios instance creator function 42 | -------------------------------------------------------------------------------- /frontend/src/views/auth/CreatePassword.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import Header from "../partials/Header"; 3 | import Footer from "../partials/Footer"; 4 | import { Link, useNavigate, useSearchParams } from "react-router-dom"; 5 | import apiInstance from "../../utils/axios"; 6 | import Toast from "../../plugin/Toast"; 7 | function CreatePassword() { 8 | const [password, setPassword] = useState(""); 9 | const [confirmPassword, setConfirmPassword] = useState(""); 10 | const [isLoading, setIsLoading] = useState(false); 11 | 12 | const navigate = useNavigate(); 13 | 14 | const [searchParams] = useSearchParams(); 15 | const otp = searchParams.get("otp"); 16 | const uidb64 = searchParams.get("uidb64"); 17 | const reset_token = searchParams.get("reset_token"); 18 | 19 | const handlePasswordSubmit = (e) => { 20 | e.preventDefault(); 21 | setIsLoading(true); 22 | if (password !== confirmPassword) { 23 | setIsLoading(false); 24 | Toast().fire({ 25 | icon: "warning", 26 | text: "Password Does Not Match", 27 | }); 28 | } else { 29 | setIsLoading(true); 30 | 31 | const formdata = new FormData(); 32 | formdata.append("otp", otp); 33 | formdata.append("uidb64", uidb64); 34 | formdata.append("reset_token", reset_token); 35 | formdata.append("password", password); 36 | 37 | try { 38 | apiInstance.post(`user/password-change/`, formdata).then((res) => { 39 | Toast().fire({ 40 | icon: "success", 41 | text: "Password Changed Successfully", 42 | }); 43 | navigate("/login"); 44 | }); 45 | } catch (error) { 46 | console.log(error); 47 | Toast().fire({ 48 | icon: "error", 49 | title: "An Error Occured Try Again", 50 | }); 51 | setIsLoading(false); 52 | } 53 | } 54 | }; 55 | return ( 56 | <> 57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |

Create New Password

65 | Choose a new password for your account 66 |
67 |
68 |
69 | 72 | setPassword(e.target.value)} /> 73 |
74 | 75 |
76 | 79 | setConfirmPassword(e.target.value)} /> 80 |
81 | 82 |
83 |
84 | {isLoading === true ? ( 85 | 88 | ) : ( 89 | 92 | )} 93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |