├── .gitignore
├── blog_platform
├── blog
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-310.pyc
│ │ ├── admin.cpython-310.pyc
│ │ ├── apps.cpython-310.pyc
│ │ ├── models.cpython-310.pyc
│ │ ├── serializers.cpython-310.pyc
│ │ ├── urls.cpython-310.pyc
│ │ └── views.cpython-310.pyc
│ ├── admin.py
│ ├── apps.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ ├── 0002_rename_comments_comment_rename_tags_tag.py
│ │ ├── 0003_post_image.py
│ │ ├── __init__.py
│ │ └── __pycache__
│ │ │ ├── 0001_initial.cpython-310.pyc
│ │ │ ├── 0002_rename_comments_comment_rename_tags_tag.cpython-310.pyc
│ │ │ ├── 0003_post_image.cpython-310.pyc
│ │ │ └── __init__.cpython-310.pyc
│ ├── models.py
│ ├── serializers.py
│ ├── templates
│ │ └── email_templates
│ │ │ └── password_reset_email.html
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── blog_platform
│ ├── __init__.py
│ ├── __pycache__
│ │ ├── __init__.cpython-310.pyc
│ │ ├── settings.cpython-310.pyc
│ │ ├── urls.cpython-310.pyc
│ │ └── wsgi.cpython-310.pyc
│ ├── asgi.py
│ ├── settings.py
│ ├── urls.py
│ ├── view.py
│ └── wsgi.py
├── db.sqlite3
├── email.log
├── manage.py
└── requirements.txt
└── blog_platform_front
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
└── src
├── App.css
├── App.js
├── App.test.js
├── components
├── Auth
│ ├── LogOut.jsx
│ ├── Login.jsx
│ ├── LoginButton.jsx
│ ├── PasswordResetConfirmation.jsx
│ ├── PasswordRest.jsx
│ ├── PrivateRoutes.jsx
│ ├── Register.jsx
│ └── Token.jsx
├── BlogPosts
│ ├── BlogPostDetail.jsx
│ ├── BlogPostForm.jsx
│ ├── BlogPostFull.jsx
│ ├── BlogPostList.jsx
│ ├── DashboardButton.jsx
│ └── MyBlogPosts.jsx
├── Categories
│ └── CategoryList.jsx
├── Comments
│ ├── CommentForm.jsx
│ └── CommentList.jsx
└── Tags
│ └── TagList.jsx
├── config.js
├── index.css
├── index.js
├── logo.svg
├── pages
├── DashBoard.jsx
└── HomePage.jsx
├── reportWebVitals.js
└── setupTests.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode/
2 | myenv/
3 | /blog_platform/blog/__pycache__/*
4 | /blog_platform/blog/migrations/__pycache__/*
5 | /blog_platform/blog_platform/__pycache__/*
6 | /blog_platform/django.log
7 | /blog_platform/env
8 | pass.txt
9 | /blog_platform/db.sqlite3
10 | /sample_images
11 |
--------------------------------------------------------------------------------
/blog_platform/blog/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform/blog/__init__.py
--------------------------------------------------------------------------------
/blog_platform/blog/__pycache__/__init__.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform/blog/__pycache__/__init__.cpython-310.pyc
--------------------------------------------------------------------------------
/blog_platform/blog/__pycache__/admin.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform/blog/__pycache__/admin.cpython-310.pyc
--------------------------------------------------------------------------------
/blog_platform/blog/__pycache__/apps.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform/blog/__pycache__/apps.cpython-310.pyc
--------------------------------------------------------------------------------
/blog_platform/blog/__pycache__/models.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform/blog/__pycache__/models.cpython-310.pyc
--------------------------------------------------------------------------------
/blog_platform/blog/__pycache__/serializers.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform/blog/__pycache__/serializers.cpython-310.pyc
--------------------------------------------------------------------------------
/blog_platform/blog/__pycache__/urls.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform/blog/__pycache__/urls.cpython-310.pyc
--------------------------------------------------------------------------------
/blog_platform/blog/__pycache__/views.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform/blog/__pycache__/views.cpython-310.pyc
--------------------------------------------------------------------------------
/blog_platform/blog/admin.py:
--------------------------------------------------------------------------------
1 | from django.contrib import admin
2 | from .models import Post, Category, Tag, Comment
3 |
4 | admin.site.register(Post)
5 | admin.site.register(Category)
6 | admin.site.register(Tag)
7 | admin.site.register(Comment)
8 |
--------------------------------------------------------------------------------
/blog_platform/blog/apps.py:
--------------------------------------------------------------------------------
1 | from django.apps import AppConfig
2 |
3 |
4 | class BlogConfig(AppConfig):
5 | default_auto_field = 'django.db.models.BigAutoField'
6 | name = 'blog'
7 |
--------------------------------------------------------------------------------
/blog_platform/blog/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.2.4 on 2023-08-18 15:36
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 | initial = True
11 |
12 | dependencies = [
13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
14 | ]
15 |
16 | operations = [
17 | migrations.CreateModel(
18 | name='Category',
19 | fields=[
20 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21 | ('name', models.CharField(max_length=100)),
22 | ],
23 | ),
24 | migrations.CreateModel(
25 | name='Tags',
26 | fields=[
27 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
28 | ('name', models.CharField(max_length=100)),
29 | ],
30 | ),
31 | migrations.CreateModel(
32 | name='Post',
33 | fields=[
34 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
35 | ('title', models.CharField(max_length=200)),
36 | ('content', models.TextField()),
37 | ('created_at', models.DateTimeField(auto_now_add=True)),
38 | ('updated_at', models.DateTimeField(auto_now=True)),
39 | ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
40 | ('category', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.category')),
41 | ('tags', models.ManyToManyField(to='blog.tags')),
42 | ],
43 | ),
44 | migrations.CreateModel(
45 | name='Comments',
46 | fields=[
47 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
48 | ('content', models.TextField()),
49 | ('created_at', models.DateTimeField(auto_now_add=True)),
50 | ('author', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
51 | ('post', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='blog.post')),
52 | ],
53 | ),
54 | ]
55 |
--------------------------------------------------------------------------------
/blog_platform/blog/migrations/0002_rename_comments_comment_rename_tags_tag.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.2.4 on 2023-08-18 15:47
2 |
3 | from django.conf import settings
4 | from django.db import migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | migrations.swappable_dependency(settings.AUTH_USER_MODEL),
11 | ('blog', '0001_initial'),
12 | ]
13 |
14 | operations = [
15 | migrations.RenameModel(
16 | old_name='Comments',
17 | new_name='Comment',
18 | ),
19 | migrations.RenameModel(
20 | old_name='Tags',
21 | new_name='Tag',
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/blog_platform/blog/migrations/0003_post_image.py:
--------------------------------------------------------------------------------
1 | # Generated by Django 4.2.4 on 2023-10-23 16:00
2 |
3 | from django.db import migrations, models
4 |
5 |
6 | class Migration(migrations.Migration):
7 |
8 | dependencies = [
9 | ('blog', '0002_rename_comments_comment_rename_tags_tag'),
10 | ]
11 |
12 | operations = [
13 | migrations.AddField(
14 | model_name='post',
15 | name='image',
16 | field=models.ImageField(blank=True, null=True, upload_to='blog_images/'),
17 | ),
18 | ]
19 |
--------------------------------------------------------------------------------
/blog_platform/blog/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform/blog/migrations/__init__.py
--------------------------------------------------------------------------------
/blog_platform/blog/migrations/__pycache__/0001_initial.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform/blog/migrations/__pycache__/0001_initial.cpython-310.pyc
--------------------------------------------------------------------------------
/blog_platform/blog/migrations/__pycache__/0002_rename_comments_comment_rename_tags_tag.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform/blog/migrations/__pycache__/0002_rename_comments_comment_rename_tags_tag.cpython-310.pyc
--------------------------------------------------------------------------------
/blog_platform/blog/migrations/__pycache__/0003_post_image.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform/blog/migrations/__pycache__/0003_post_image.cpython-310.pyc
--------------------------------------------------------------------------------
/blog_platform/blog/migrations/__pycache__/__init__.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform/blog/migrations/__pycache__/__init__.cpython-310.pyc
--------------------------------------------------------------------------------
/blog_platform/blog/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from django.contrib.auth.models import User
3 |
4 |
5 | class Category(models.Model):
6 | name = models.CharField(max_length=100)
7 |
8 | def __str__(self):
9 | return self.name
10 |
11 |
12 | class Tag(models.Model):
13 | name = models.CharField(max_length=100)
14 |
15 | def __str__(self):
16 | return self.name
17 |
18 |
19 | class Post(models.Model):
20 | title = models.CharField(max_length=200)
21 | content = models.TextField()
22 | image = models.ImageField(upload_to='blog_images/', null=True, blank=True)
23 | author = models.ForeignKey(User, on_delete=models.CASCADE)
24 | category = models.ForeignKey(Category, on_delete=models.CASCADE)
25 | tags = models.ManyToManyField(Tag)
26 | created_at = models.DateTimeField(auto_now_add=True)
27 | updated_at = models.DateTimeField(auto_now=True)
28 |
29 | def __str__(self):
30 | return self.title
31 |
32 |
33 | class Comment(models.Model):
34 | post = models.ForeignKey(Post, on_delete=models.CASCADE)
35 | author = models.ForeignKey(User, on_delete=models.CASCADE)
36 | content = models.TextField()
37 | created_at = models.DateTimeField(auto_now_add=True)
38 |
39 | def __str__(self):
40 | return f"Commnet by {self.author.username} on {self.post.title}"
41 |
--------------------------------------------------------------------------------
/blog_platform/blog/serializers.py:
--------------------------------------------------------------------------------
1 | from rest_framework import serializers
2 | from .models import Post, Category, Tag, User, Comment
3 |
4 |
5 | class CategorySerializer(serializers.ModelSerializer):
6 | class Meta:
7 | model = Category
8 | fields = '__all__'
9 |
10 |
11 | class TagSerializer(serializers.ModelSerializer):
12 | class Meta:
13 | model = Tag
14 | fields = '__all__'
15 |
16 |
17 | class UserSerializer(serializers.ModelSerializer):
18 | class Meta:
19 | model = User
20 | fields = ('username', 'email', 'password', 'first_name', 'last_name')
21 | extra_kwargs = {'password': {'write_only': True}}
22 |
23 | # validation for exisiting usernames and email addresses.
24 | def validate(self, data):
25 | username = data.get('username')
26 | email = data.get('email')
27 |
28 | if User.objects.filter(username=username).exists():
29 | raise serializers.ValidationError(
30 | {'username': 'Username already exists'})
31 | if User.objects.filter(email=email).exists():
32 | raise serializers.ValidationError(
33 | {'email': 'Email Address already exists'})
34 |
35 | return data
36 |
37 |
38 | class PasswordResetSerializer(serializers.Serializer):
39 | email = serializers.EmailField()
40 |
41 | def validate_email(self, value):
42 | # Check if the email exists in your database
43 | try:
44 | user = User.objects.get(email=value)
45 | print(user)
46 |
47 | except User.DoesNotExist:
48 | raise serializers.ValidationError(
49 | "Email address not found in our database.")
50 |
51 | return value
52 |
53 |
54 | class PostSerializer(serializers.ModelSerializer):
55 |
56 | author = UserSerializer(read_only=True)
57 |
58 | class Meta:
59 | model = Post
60 | fields = '__all__'
61 |
62 |
63 | class CommentSerializer(serializers.ModelSerializer):
64 | post = PostSerializer()
65 | author = UserSerializer()
66 |
67 | class Meta:
68 | model = Comment
69 | fields = '__all__'
70 |
--------------------------------------------------------------------------------
/blog_platform/blog/templates/email_templates/password_reset_email.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Password Reset
5 |
6 |
7 | Hello {{ user.username }},
8 | Click the link below to reset your password:
9 | Reset Password
10 | If you did not request a password reset, please ignore this email.
11 |
12 |
13 |
--------------------------------------------------------------------------------
/blog_platform/blog/tests.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 | # Create your tests here.
4 |
--------------------------------------------------------------------------------
/blog_platform/blog/urls.py:
--------------------------------------------------------------------------------
1 | from django.urls import path, include
2 | from rest_framework.routers import DefaultRouter
3 | from . import views
4 | from django.contrib.auth import views as auth_views
5 |
6 |
7 | router = DefaultRouter()
8 | router.register(r'posts', views.PostModelViewSet)
9 | router.register(r'my_posts', views.PostModelViewSet)
10 | router.register(r'categories', views.CategoryViewSet)
11 | router.register(r'tags', views.TagViewSet)
12 | router.register(r'comments', views.CommentViewSet)
13 | router.register(r'users', views.UserViewSet)
14 |
15 | urlpatterns = [
16 | path('', include(router.urls)),
17 | path('postlist', views.PostListView.as_view(), name='postlist'),
18 | path('postlist//',
19 | views.PostDetailView.as_view(), name='post_detail'),
20 |
21 | # Start - User Authenticatio Related Urls
22 | path('token', views.CustomAuthToken.as_view(), name='token_obtain'),
23 | path('register', views.UserRegistrationView.as_view(), name='register'),
24 | path('logout', views.LogOutView.as_view(), name='logout'),
25 | path('password_reset', views.password_reset, name='password_reset'),
26 | path('password_reset/confirm///',
27 | views.password_reset_confirm, name='password_reset_confirm'),
28 |
29 |
30 | path('password-reset/done/', auth_views.PasswordResetDoneView.as_view(),
31 | name='password_reset_done'),
32 |
33 | path('password-reset-complete/', auth_views.PasswordResetCompleteView.as_view(),
34 | name='password_reset_complete'),
35 | # END - User Authenticatio Related Urls
36 | ]
37 |
--------------------------------------------------------------------------------
/blog_platform/blog/views.py:
--------------------------------------------------------------------------------
1 | from django.http import JsonResponse
2 |
3 | # For Define API Views
4 | from rest_framework import generics, viewsets, status
5 | from rest_framework.response import Response
6 | from .models import Post, Category, Tag, Comment, User
7 | from .serializers import PostSerializer, CategorySerializer, TagSerializer, CommentSerializer, UserSerializer, PasswordResetSerializer
8 |
9 | # For User Authentication
10 | from rest_framework.permissions import IsAuthenticated, IsAuthenticatedOrReadOnly
11 | from rest_framework.authtoken.views import ObtainAuthToken, APIView
12 | from rest_framework.authtoken.models import Token
13 | from django.contrib.auth.hashers import make_password
14 | from django.contrib.auth.forms import UserCreationForm, SetPasswordForm
15 |
16 | # For PasswordRest
17 | from rest_framework.decorators import api_view, permission_classes
18 | from rest_framework.permissions import AllowAny
19 | from django.core import mail
20 | from django.template.loader import render_to_string
21 | from django.utils.http import urlsafe_base64_encode, urlsafe_base64_decode
22 | from django.utils.encoding import force_bytes
23 | from django.contrib.auth.tokens import default_token_generator
24 | from django.contrib.auth import get_user_model
25 |
26 |
27 | # List all Post Public
28 | class PostListView(generics.ListCreateAPIView):
29 | queryset = Post.objects.all()
30 | serializer_class = PostSerializer
31 | permission_classes = [IsAuthenticatedOrReadOnly]
32 |
33 |
34 | # View Post Details Public
35 | class PostDetailView(generics.RetrieveAPIView):
36 | serializer_class = PostSerializer
37 | permission_classes = [IsAuthenticatedOrReadOnly]
38 | lookup_field = 'id'
39 |
40 | def get_queryset(self):
41 | query = Post.objects.filter(id=self.kwargs.get('id'))
42 | return query
43 |
44 |
45 | # List Posts Private
46 | class PostModelViewSet(viewsets.ModelViewSet):
47 | queryset = Post.objects.all()
48 | serializer_class = PostSerializer
49 | permission_classes = [IsAuthenticated]
50 |
51 | def get_queryset(self):
52 | return Post.objects.filter(author=self.request.user)
53 |
54 | def perform_create(self, serializer):
55 | serializer.save(author=self.request.user)
56 |
57 |
58 | class CategoryListCreateView(generics.ListCreateAPIView):
59 | queryset = Category.objects.all()
60 | serializer_class = CategorySerializer
61 | permission_classes = [IsAuthenticatedOrReadOnly]
62 |
63 |
64 | class CategoryViewSet(viewsets.ModelViewSet):
65 | queryset = Category.objects.all()
66 | serializer_class = CategorySerializer
67 | permission_classes = [IsAuthenticated]
68 |
69 |
70 | class TagListCrateView(generics.ListCreateAPIView):
71 | queryset = Tag.objects.all()
72 | serializer_class = TagSerializer
73 | permission_classes = [IsAuthenticatedOrReadOnly]
74 |
75 |
76 | class TagViewSet(viewsets.ModelViewSet):
77 | queryset = Tag.objects.all()
78 | serializer_class = TagSerializer
79 | permission_classes = [IsAuthenticated]
80 |
81 |
82 | class CommentListCreateView(generics.ListCreateAPIView):
83 | queryset = Comment.objects.all()
84 | serializer_class = CommentSerializer
85 | permission_classes = [IsAuthenticatedOrReadOnly]
86 |
87 |
88 | class CommentViewSet(viewsets.ModelViewSet):
89 | queryset = Comment.objects.all()
90 | serializer_class = CommentSerializer
91 | permission_classes = [IsAuthenticated]
92 |
93 |
94 | class UserViewSet(viewsets.ModelViewSet):
95 | queryset = User.objects.all()
96 | serializer_class = UserSerializer
97 | permission_classes = [IsAuthenticated]
98 |
99 |
100 | def home(request):
101 | return JsonResponse({'message': 'Testing App'})
102 |
103 |
104 | # For User Authentication
105 | class CustomUserCreateForm(UserCreationForm):
106 | class Meta:
107 | model = User
108 | fields = ['email', 'first_name', 'last_name']
109 |
110 |
111 | class CustomAuthToken(ObtainAuthToken):
112 | def post(self, request, *args, **kwargs):
113 | serializer = self.serializer_class(
114 | data=request.data, context={'request': request})
115 | serializer.is_valid(raise_exception=True)
116 | user = serializer.validated_data['user']
117 | token, created = Token.objects.get_or_create(user=user)
118 |
119 | return Response({'token': token.key, 'user_id': user.id, 'user_name': user.username})
120 |
121 |
122 | class UserRegistrationView(generics.CreateAPIView):
123 | queryset = User.objects.all()
124 | serializer_class = UserSerializer
125 |
126 | def perform_create(self, serializer):
127 | hashed_password = make_password(serializer.validated_data['password'])
128 | serializer.validated_data['password'] = hashed_password
129 | user = serializer.save()
130 | Token.objects.create(user=user)
131 |
132 |
133 | class LogOutView(APIView):
134 | permission_class = [IsAuthenticated]
135 |
136 | def post(self, request):
137 | user = request.user
138 | token = request.auth
139 |
140 | if user is not None and token is not None:
141 | # Logout the user by deleting their token.
142 | token.delete()
143 | return Response({'detail': 'Logout successful'}, status=status.HTTP_200_OK)
144 | else:
145 | return Response({'detail': 'Authentication required'}, status=status.HTTP_401_UNAUTHORIZED)
146 |
147 |
148 | @api_view(['POST'])
149 | @permission_classes([AllowAny])
150 | def password_reset(request):
151 | serializer = PasswordResetSerializer(data=request.data)
152 |
153 | if serializer.is_valid():
154 | email = serializer.validated_data['email']
155 | try:
156 | user = User.objects.get(email=email)
157 | except User.DoesNotExist:
158 | return Response({'detail': 'No user with this email address.'}, status=status.HTTP_400_BAD_REQUEST)
159 |
160 | # Generate a password reset token and send an email with a link to reset the password.
161 | token = default_token_generator.make_token(user)
162 | uid = urlsafe_base64_encode(force_bytes(user.id))
163 | frontend_url = "http://localhost:3000"
164 | context = {
165 | 'user': user,
166 | 'domain': frontend_url,
167 | 'uid': urlsafe_base64_encode(force_bytes(user.id)),
168 | 'token': token,
169 | }
170 |
171 | subject = 'Password Reset'
172 | message = render_to_string(
173 | 'email_templates/password_reset_email.html', context)
174 | from_email = 'noreply@example.com'
175 | recipient_list = [email]
176 | mail.send_mail(subject, message, from_email, recipient_list)
177 |
178 | return Response({'detail': 'Password reset email sent.'}, status=status.HTTP_200_OK)
179 |
180 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
181 |
182 |
183 | @api_view(['POST'])
184 | @permission_classes([AllowAny])
185 | def password_reset_confirm(request, uidb64, token):
186 | User = get_user_model()
187 | try:
188 | # Decode the uidb64 to get the user's ID
189 | user_id = urlsafe_base64_decode(uidb64).decode()
190 | user = User.objects.get(id=user_id)
191 |
192 | # Check if the token is valid
193 | if default_token_generator.check_token(user, token):
194 | if request.method == 'POST':
195 | new_password = request.data.get('new_password1')
196 | form = SetPasswordForm(user, request.data)
197 | if form.is_valid():
198 | # Set the new password
199 | user.set_password(new_password)
200 | user.save()
201 | return JsonResponse({'detail': 'Password reset successful'}, status=status.HTTP_200_OK)
202 | else:
203 | print(form.errors)
204 | return JsonResponse({'errors': form.errors}, status=status.HTTP_400_BAD_REQUEST)
205 | else:
206 | return JsonResponse({'detail': 'Invalid token'}, status=status.HTTP_400_BAD_REQUEST)
207 | except User.DoesNotExist:
208 | return JsonResponse({'detail': 'User not found'}, status=status.HTTP_400_BAD_REQUEST)
209 |
--------------------------------------------------------------------------------
/blog_platform/blog_platform/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform/blog_platform/__init__.py
--------------------------------------------------------------------------------
/blog_platform/blog_platform/__pycache__/__init__.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform/blog_platform/__pycache__/__init__.cpython-310.pyc
--------------------------------------------------------------------------------
/blog_platform/blog_platform/__pycache__/settings.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform/blog_platform/__pycache__/settings.cpython-310.pyc
--------------------------------------------------------------------------------
/blog_platform/blog_platform/__pycache__/urls.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform/blog_platform/__pycache__/urls.cpython-310.pyc
--------------------------------------------------------------------------------
/blog_platform/blog_platform/__pycache__/wsgi.cpython-310.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform/blog_platform/__pycache__/wsgi.cpython-310.pyc
--------------------------------------------------------------------------------
/blog_platform/blog_platform/asgi.py:
--------------------------------------------------------------------------------
1 | """
2 | ASGI config for blog_platform 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', 'blog_platform.settings')
15 |
16 | application = get_asgi_application()
17 |
--------------------------------------------------------------------------------
/blog_platform/blog_platform/settings.py:
--------------------------------------------------------------------------------
1 | """
2 | Django settings for blog_platform project.
3 |
4 | Generated by 'django-admin startproject' using Django 4.2.4.
5 |
6 | For more information on this file, see
7 | https://docs.djangoproject.com/en/4.2/topics/settings/
8 |
9 | For the full list of settings and their values, see
10 | https://docs.djangoproject.com/en/4.2/ref/settings/
11 | """
12 |
13 | from pathlib import Path
14 | import os
15 |
16 | # Build paths inside the project like this: BASE_DIR / 'subdir'.
17 | BASE_DIR = Path(__file__).resolve().parent.parent
18 |
19 |
20 | # Quick-start development settings - unsuitable for production
21 | # See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
22 |
23 | # SECURITY WARNING: keep the secret key used in production secret!
24 | SECRET_KEY = 'django-insecure-u4*$!fqaeb*^1fyalki4mnk7ir#tj(=hmvn!)bri2x&oh$hrle'
25 |
26 | # SECURITY WARNING: don't run with debug turned on in production!
27 | DEBUG = True
28 |
29 | ALLOWED_HOSTS = []
30 |
31 |
32 | # Application definition
33 |
34 | INSTALLED_APPS = [
35 | 'django.contrib.admin',
36 | 'django.contrib.auth',
37 | 'django.contrib.contenttypes',
38 | 'django.contrib.sessions',
39 | 'django.contrib.messages',
40 | 'django.contrib.staticfiles',
41 | 'corsheaders',
42 | 'rest_framework',
43 | 'rest_framework.authtoken',
44 | 'blog',
45 | ]
46 |
47 | REST_FRAMEWORK = {
48 | 'DEFAULT_AUTHENTICATION_CLASSES': [
49 | 'rest_framework.authentication.TokenAuthentication',
50 | ],
51 | }
52 |
53 | MIDDLEWARE = [
54 | 'corsheaders.middleware.CorsMiddleware',
55 | 'django.middleware.security.SecurityMiddleware',
56 | 'django.contrib.sessions.middleware.SessionMiddleware',
57 | 'django.middleware.csrf.CsrfViewMiddleware',
58 | 'django.contrib.auth.middleware.AuthenticationMiddleware',
59 | 'django.contrib.messages.middleware.MessageMiddleware',
60 | 'django.middleware.clickjacking.XFrameOptionsMiddleware',
61 | 'django.middleware.common.CommonMiddleware',
62 | ]
63 |
64 | AUTHENTICATION_BACKENDS = [
65 | 'django.contrib.auth.backends.ModelBackend',
66 | ]
67 |
68 | LOGIN_URL = '/login/'
69 |
70 | LOGIN_REDIRECT_URL = '/home/'
71 |
72 | ROOT_URLCONF = 'blog_platform.urls'
73 |
74 | TEMPLATES = [
75 | {
76 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
77 | 'DIRS': [],
78 | 'APP_DIRS': True,
79 | 'OPTIONS': {
80 | 'context_processors': [
81 | 'django.template.context_processors.debug',
82 | 'django.template.context_processors.request',
83 | 'django.contrib.auth.context_processors.auth',
84 | 'django.contrib.messages.context_processors.messages',
85 | ],
86 | },
87 | },
88 | ]
89 |
90 | WSGI_APPLICATION = 'blog_platform.wsgi.application'
91 |
92 |
93 | # Database
94 | # https://docs.djangoproject.com/en/4.2/ref/settings/#databases
95 |
96 | DATABASES = {
97 | 'default': {
98 | 'ENGINE': 'django.db.backends.postgresql',
99 | 'NAME': 'blogdb',
100 | 'USER': 'postgres',
101 | 'PASSWORD': 'postgres',
102 | 'HOST': 'localhost',
103 | 'PORT': '5432',
104 | }
105 | }
106 |
107 |
108 | # Password validation
109 | # https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
110 |
111 | AUTH_PASSWORD_VALIDATORS = [
112 | {
113 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
114 | },
115 | {
116 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
117 | },
118 | {
119 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
120 | },
121 | {
122 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
123 | },
124 | ]
125 |
126 |
127 | # Internationalization
128 | # https://docs.djangoproject.com/en/4.2/topics/i18n/
129 |
130 | LANGUAGE_CODE = 'en-us'
131 |
132 | TIME_ZONE = 'UTC'
133 |
134 | USE_I18N = True
135 |
136 | USE_TZ = True
137 |
138 |
139 | # Static files (CSS, JavaScript, Images)
140 | # https://docs.djangoproject.com/en/4.2/howto/static-files/
141 |
142 | STATIC_URL = 'static/'
143 |
144 | # Default primary key field type
145 | # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
146 |
147 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
148 |
149 | CORS_ALLOW_ALL_ORIGINS = False
150 | CORS_ALLOW_CREDENTIALS = True
151 | # CORS_ALLOW_HEADERS = ['X-CSRFTOKEN', 'content-type']
152 |
153 | CORS_ALLOWED_ORIGINS = [
154 | 'http://localhost:3000',
155 | ]
156 |
157 | CORS_ALLOW_METHODS = [
158 | "GET",
159 | "POST",
160 | "PUT",
161 | "DELETE",
162 | "OPTIONS",
163 | ]
164 |
165 | EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
166 | EMAIL_HOST = 'smtp.gmail.com'
167 | EMAIL_HOST_USER = 'ridmi.developer@gmail.com'
168 | # App Password Generated From Google
169 | EMAIL_HOST_PASSWORD = 'xefv qfrc awxt fuya'
170 | EMAIL_PORT = 587
171 | EMAIL_USE_TLS = True
172 | EMAIL_USE_SSL = False
173 |
174 | LOGGING = {
175 | 'version': 1,
176 | 'disable_existing_loggers': False,
177 | 'handlers': {
178 | 'file': {
179 | 'level': 'DEBUG',
180 | 'class': 'logging.FileHandler',
181 | 'filename': 'django.log', # Change this to the desired log file path
182 | },
183 | },
184 | 'root': {
185 | 'handlers': ['file'],
186 | 'level': 'INFO', # Change this to 'DEBUG' for more detailed logs
187 | },
188 | }
189 |
190 |
191 | # Blog Post Images save to:
192 | MEDIA_URL = '/media/'
193 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
194 |
--------------------------------------------------------------------------------
/blog_platform/blog_platform/urls.py:
--------------------------------------------------------------------------------
1 | """
2 | URL configuration for blog_platform 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 include, path
19 |
20 | # Image Sevrve and Debuging
21 | from django.views.static import serve
22 | from django.conf import settings
23 | from django.conf.urls.static import static
24 | urlpatterns = [
25 | path('admin/', admin.site.urls),
26 | path('media/', serve, {'document_root': settings.MEDIA_ROOT}),
27 | path('api/', include('blog.urls')),
28 | ]
29 | # Image Debuging
30 | if settings.DEBUG:
31 | urlpatterns += static(settings.MEDIA_URL,
32 | document_root=settings.MEDIA_ROOT)
33 |
--------------------------------------------------------------------------------
/blog_platform/blog_platform/view.py:
--------------------------------------------------------------------------------
1 | from django.shortcuts import render
2 | from django.http import JsonResponse
3 |
4 |
5 | def home(request):
6 | return JsonResponse({'message': 'Testing Page'})
7 |
--------------------------------------------------------------------------------
/blog_platform/blog_platform/wsgi.py:
--------------------------------------------------------------------------------
1 | """
2 | WSGI config for blog_platform 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', 'blog_platform.settings')
15 |
16 | application = get_wsgi_application()
17 |
--------------------------------------------------------------------------------
/blog_platform/db.sqlite3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform/db.sqlite3
--------------------------------------------------------------------------------
/blog_platform/email.log:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform/email.log
--------------------------------------------------------------------------------
/blog_platform/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', 'blog_platform.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 |
--------------------------------------------------------------------------------
/blog_platform/requirements.txt:
--------------------------------------------------------------------------------
1 | asgiref==3.7.2
2 | Django==5.0
3 | django-cors-headers==4.3.1
4 | djangorestframework==3.14.0
5 | Pillow==10.1.0
6 | psycopg2==2.9.9
7 | pytz==2023.3.post1
8 | sqlparse==0.4.4
9 | typing_extensions==4.9.0
10 | tzdata==2023.3
11 |
--------------------------------------------------------------------------------
/blog_platform_front/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/blog_platform_front/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
13 |
14 | The page will reload when you make changes.\
15 | You may also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!**
35 |
36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
39 |
40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
48 | ### Code Splitting
49 |
50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
51 |
52 | ### Analyzing the Bundle Size
53 |
54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
55 |
56 | ### Making a Progressive Web App
57 |
58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
59 |
60 | ### Advanced Configuration
61 |
62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
63 |
64 | ### Deployment
65 |
66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67 |
68 | ### `npm run build` fails to minify
69 |
70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
71 |
--------------------------------------------------------------------------------
/blog_platform_front/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "blog_platform_front",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.17.0",
7 | "@testing-library/react": "^13.4.0",
8 | "@testing-library/user-event": "^13.5.0",
9 | "axios": "^1.5.1",
10 | "dompurify": "^3.0.6",
11 | "react": "^18.2.0",
12 | "react-dom": "^18.2.0",
13 | "react-quill": "^2.0.0",
14 | "react-router-dom": "^6.16.0",
15 | "react-scripts": "5.0.1",
16 | "styled-components": "^6.0.8",
17 | "web-vitals": "^2.1.4"
18 | },
19 | "scripts": {
20 | "start": "react-scripts start",
21 | "build": "react-scripts build",
22 | "test": "react-scripts test",
23 | "eject": "react-scripts eject"
24 | },
25 | "eslintConfig": {
26 | "extends": [
27 | "react-app",
28 | "react-app/jest"
29 | ]
30 | },
31 | "browserslist": {
32 | "production": [
33 | ">0.2%",
34 | "not dead",
35 | "not op_mini all"
36 | ],
37 | "development": [
38 | "last 1 chrome version",
39 | "last 1 firefox version",
40 | "last 1 safari version"
41 | ]
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/blog_platform_front/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform_front/public/favicon.ico
--------------------------------------------------------------------------------
/blog_platform_front/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/blog_platform_front/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform_front/public/logo192.png
--------------------------------------------------------------------------------
/blog_platform_front/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform_front/public/logo512.png
--------------------------------------------------------------------------------
/blog_platform_front/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/blog_platform_front/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/blog_platform_front/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/blog_platform_front/src/App.js:
--------------------------------------------------------------------------------
1 | import './App.css';
2 | import { BrowserRouter as Router, Routes, Route, Navigate} from 'react-router-dom';
3 | import HomePage from './pages/HomePage';
4 | import BlogPostFull from './components/BlogPosts/BlogPostFull';
5 | import LogIn from './components/Auth/Login'
6 | import Register from './components/Auth/Register';
7 | import PasswordReset from './components/Auth/PasswordRest';
8 | import PasswordResetConfirmation from './components/Auth/PasswordResetConfirmation';
9 | import DashBoard from './pages/DashBoard';
10 | import PrivateRoutes from './components/Auth/PrivateRoutes';
11 | import {token} from './components/Auth/Token';
12 |
13 | function App() {
14 | const isAuthorized = !!token
15 |
16 | return (
17 |
18 |
19 | } />
20 | } />} />
21 | } />
22 | } />
23 | } />
24 | } />
25 | ) : ()} />
26 |
27 |
28 | );
29 | }
30 |
31 | export default App;
32 |
--------------------------------------------------------------------------------
/blog_platform_front/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render();
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/blog_platform_front/src/components/Auth/LogOut.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import axios from "axios";
3 | import API_BASE_URL from '../../config';
4 | import { token, userData} from "./Token";
5 |
6 | const LogOut = () => {
7 | const handleLogout = async () => {
8 |
9 | try{
10 | if(token){
11 | await axios.post(`${API_BASE_URL}/api/logout`, null, {headers: {Authorization: `Token ${token}`, 'Content-Type': 'application/json',}});
12 | localStorage.removeItem('token');
13 | localStorage.removeItem(userData)
14 | window.location.href = '/login';
15 | }
16 |
17 | }catch (error) {
18 | console.error('LogOut Fail: ', error);
19 | }
20 | }
21 |
22 | return (
23 |
24 | )
25 |
26 | }
27 |
28 | export default LogOut
--------------------------------------------------------------------------------
/blog_platform_front/src/components/Auth/Login.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Link } from 'react-router-dom';
3 | import axios from "axios";
4 | import API_BASE_URL from '../../config';
5 |
6 | const LogIn = () => {
7 | const [username, setUsername] = useState('');
8 | const [password, setPassword] = useState('');
9 | const [error, setError] = useState('');
10 |
11 | const handleLogin = async (e) =>{
12 | e.preventDefault();
13 | try{
14 | const response = await axios.post(`${API_BASE_URL}/api/token`, {username, password});
15 | const token = response.data.token; // Assuming the response contains the token upon successful login.
16 | localStorage.setItem('token', token); // Store the token securely (e.g., in local storage).
17 |
18 | const userData = {
19 | id: response.data.user_id,
20 | username: response.data.user_name,
21 | }
22 | localStorage.setItem('userData',JSON.stringify(userData))
23 | window.location.href = '/'; // Redirect to the home page or any desired page.
24 | }catch (error) {
25 | setError('Invalid Login Credentials')
26 | }
27 | };
28 |
29 | return(
30 |
31 |
Login
32 | {error &&
{error}
}
33 |
38 |
Forgot Your Password?
39 |
New to Our Platform? Please Register
40 |
41 | )
42 |
43 | }
44 |
45 | export default LogIn
--------------------------------------------------------------------------------
/blog_platform_front/src/components/Auth/LoginButton.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | const LogInButton = () => {
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
12 | export default LogInButton
--------------------------------------------------------------------------------
/blog_platform_front/src/components/Auth/PasswordResetConfirmation.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useParams, Link } from 'react-router-dom';
3 | import axios from 'axios';
4 | import API_BASE_URL from "../../config";
5 |
6 | const PasswordResetConfirmation = () => {
7 | const { uid, token } = useParams()
8 | const [password, setPassword] = useState('');
9 | const [confirmPassword, setConfirmPassword] = useState('');
10 | const [success, setSuccess] = useState('');
11 | const [error, setError] = useState('')
12 |
13 | const handlePasswordResetConfirmation = async (e) => {
14 | e.preventDefault();
15 | try {
16 | const response = await axios.post(`${API_BASE_URL}/api/password_reset/confirm/${uid}/${token}/`, {
17 | new_password1: password,
18 | new_password2: confirmPassword,
19 | });
20 | const responseData = response.data;
21 | if (response.status === 200) {
22 | // Password reset was successful
23 | alert(responseData.detail)
24 | setSuccess(responseData.detail);
25 | } else {
26 | // Handle other status codes or error responses if needed
27 | console.log(responseData.detail);
28 | }
29 | setSuccess(true);
30 | } catch (error) {
31 | if(error.response){
32 | const errorData = error.response.data;
33 | console.error(errorData)
34 | if(errorData.errors){
35 | setError('Your password should contain at least 8 characters, including at least one letter (a-z or A-Z) and at least one number (0-9).')
36 | }else{
37 | setError(`${errorData.detail}`)
38 | }
39 | }else{
40 | setError(error.message)
41 | console.error(error.message);
42 | }
43 |
44 | }
45 |
46 | };
47 | return (
48 |
49 | {success ? (
50 |
{success} Please navigate to the Login Page to access your blog account.
51 | ) : (
52 | <>
53 | {error &&
{error}
}
54 |
71 | >
72 | )}
73 |
74 | );
75 | };
76 |
77 | export default PasswordResetConfirmation;
78 |
--------------------------------------------------------------------------------
/blog_platform_front/src/components/Auth/PasswordRest.jsx:
--------------------------------------------------------------------------------
1 | import React, {useState} from "react";
2 | import axios from "axios";
3 | import API_BASE_URL from "../../config";
4 |
5 | const PasswordReset = () => {
6 | const [email, setEmail] = useState('');
7 | const [success, setSuccess] = useState(false);
8 | const [error, setError] = useState('');
9 |
10 | const handleSubmit = async (e) => {
11 | e.preventDefault();
12 | try{
13 | await axios.post(`${API_BASE_URL}/api/password_reset`, {email});
14 | setSuccess(true)
15 | }catch(error){
16 | if(error.response.status === 400){
17 | const errorData = error.response.data;
18 | console.log(errorData)
19 | if(errorData.email){
20 | setError(`${errorData.email[0]}`)
21 | }
22 |
23 | }else{
24 | setError(error)
25 | }
26 |
27 | }
28 |
29 | }
30 |
31 | return(
32 |
33 | {error &&
{error}
}
34 | {success ? (
Password reset email sent. Check your inbox.
)
35 | : (
36 |
37 |
41 |
42 | )
43 |
44 | }
45 |
46 | )
47 | }
48 |
49 | export default PasswordReset;
--------------------------------------------------------------------------------
/blog_platform_front/src/components/Auth/PrivateRoutes.jsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { useNavigate } from "react-router-dom";
3 |
4 | const PrivateRoutes = ({authenticated, children}) => {
5 | const navigate = useNavigate()
6 |
7 |
8 | useEffect(() => {
9 | if(!authenticated){
10 | navigate('/login') // Redirect to the login page if not authenticated
11 | }
12 | }, [authenticated, navigate])
13 |
14 |
15 |
16 |
17 | return (
18 | authenticated ? children : null
19 | )
20 | }
21 |
22 | export default PrivateRoutes;
--------------------------------------------------------------------------------
/blog_platform_front/src/components/Auth/Register.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState} from "react";
2 | import { Link } from 'react-router-dom'
3 | import axios from "axios";
4 | import API_BASE_URL from "../../config";
5 |
6 | const Register = () => {
7 | const [formData, setFormData] = useState({
8 | 'first_name': '',
9 | 'last_name': '',
10 | 'email': '',
11 | 'username': '',
12 | 'password': '',
13 | 'confirm_password': '',
14 |
15 |
16 | })
17 | const [error, setError] = useState('');
18 |
19 | const handleChange = (e) => {
20 | const {name, value} = e.target;
21 | setFormData({
22 | ...formData,
23 | [name]: value,
24 | });
25 | }
26 |
27 | const formSubmission = async (e) => {
28 | e.preventDefault();
29 | if(formData.password !== formData.confirm_password){
30 | setError('Passwords does not match.!')
31 | return;
32 | }
33 | const hasNullOrEmpytValue = Object.values(formData).some(value => value === null || value === undefined || value === '');
34 | if(hasNullOrEmpytValue){
35 | setError('Please fill in all required fields.')
36 | return;
37 | }
38 | try{
39 | await axios.post(`${API_BASE_URL}/api/register`, formData);
40 | window.location.href = '/login';
41 | }catch(error){
42 | if(error.response.status === 400){
43 | const errorData = error.response.data;
44 | console.log(errorData)
45 | if(errorData.username){
46 | setError(`Registration Failed: ${errorData.username[0]}`)
47 | }
48 | if(errorData.email){
49 | setError(`Registration Failed: ${errorData.email[0]}`)
50 | }
51 |
52 | }else{
53 | setError(`Registration Failed: ${error}`)
54 | }
55 | }
56 |
57 | }
58 |
59 | return(
60 | <>
61 | {error && {error}
}
62 |
71 | Already a member? Login here.
72 | >
73 | )
74 | }
75 |
76 | export default Register
77 |
--------------------------------------------------------------------------------
/blog_platform_front/src/components/Auth/Token.jsx:
--------------------------------------------------------------------------------
1 | const token = localStorage.getItem('token');
2 | const userDataJSON = localStorage.getItem('userData')
3 | const userData = userDataJSON ? JSON.parse(userDataJSON) : null
4 |
5 | export {token, userData};
--------------------------------------------------------------------------------
/blog_platform_front/src/components/BlogPosts/BlogPostDetail.jsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform_front/src/components/BlogPosts/BlogPostDetail.jsx
--------------------------------------------------------------------------------
/blog_platform_front/src/components/BlogPosts/BlogPostForm.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import ReactQuill from 'react-quill';
3 | import 'react-quill/dist/quill.snow.css';
4 | import axios from 'axios';
5 | import API_BASE_URL from '../../config';
6 | import { useNavigate, useParams } from 'react-router-dom';
7 | import { token, userData } from '../Auth/Token';
8 |
9 | const BlogPostForm = () => {
10 | const navigate = useNavigate()
11 | const {id} = useParams()
12 | const isEditing = !!id
13 | const [formData, setFormData] = useState({
14 | title: '',
15 | content: '',
16 | image: null,
17 | category: '',
18 | tags: [],
19 | author: userData.id,
20 | })
21 | const [categories, setCategories]= useState([])
22 | const [tags, setTags]= useState([])
23 | const [error, setError] = useState('')
24 | const [authorDetails,setAuthorDetails] = useState({})
25 |
26 | useEffect(() => {
27 | if(isEditing){
28 | axios.get(`${API_BASE_URL}/api/posts/${id}`, {headers: {Authorization: `Token ${token}`, 'Content-Type': 'application/json',}})
29 | .then(response => {
30 | setFormData(response.data)
31 | })
32 | .catch(error => {
33 | setError(`Error fetching blog post: ${error}`)
34 | console.error(`Error fetching blog post: ${error}`);
35 | })
36 | }
37 |
38 | //Fetch Category List
39 | axios.get(`${API_BASE_URL}/api/categories`, {headers: {Authorization: `Token ${token}`, 'Content-Type': 'application/json',}})
40 | .then(response => {
41 | setCategories(response.data)
42 | })
43 | .catch(error => {
44 | setError(`Error fetching categories: ${error}`)
45 | console.error(`Error fetching categories: ${error}`);
46 | })
47 |
48 | //Fetch Tags
49 | axios.get(`${API_BASE_URL}/api/tags`, {headers: {Authorization: `Token ${token}`, 'Content-Type': 'application/json',}})
50 | .then(response => {
51 | setTags(response.data)
52 | })
53 | .catch(error => {
54 | setError(`Error fetching tags: ${error}`)
55 | console.error(`Error fetching tags: ${error}`);
56 | })
57 |
58 | //Fetch Author Details
59 | axios.get(`${API_BASE_URL}/api/users/${formData.author}`, {headers: {Authorization: `Token ${token}`, 'Content-Type': 'application/json',}})
60 | .then(response => {
61 | const fetchedAuthorDetails = response.data;
62 | setAuthorDetails(fetchedAuthorDetails)
63 | })
64 | .catch(error => {
65 | console.error(`Error fetching author data: ${error}`);
66 | });
67 |
68 | }, [id, isEditing]);
69 |
70 | const handleChange = (e) => {
71 | const { name, value, type } = e.target
72 | if(type === 'checkbox'){
73 | const selectedTags = formData.tags.includes(value) ? formData.tags.filter(tag => tag !== value)
74 | : [...formData.tags, value]
75 | setFormData({
76 | ...formData,
77 | [name]: selectedTags,
78 | })
79 | }else{
80 | setFormData({
81 | ...formData,
82 | [name]: value,
83 | })
84 | }
85 | }
86 |
87 | const handleImageChange = (e) => {
88 | const image = e.target.files[0]
89 | setFormData({
90 | ...formData,
91 | image: image,
92 | })
93 | }
94 |
95 | const handleContentChange = (newContent) => {
96 | setFormData({
97 | ...formData,
98 | content: newContent,
99 | })
100 | }
101 |
102 | const handleSubmission = (e) => {
103 | e.preventDefault();
104 | const postData = new FormData();
105 | postData.append('title', formData.title)
106 | postData.append('content', formData.content)
107 | postData.append('category', formData.category)
108 | postData.append('image', formData.image)
109 | postData.append('author', formData.author)
110 | formData.tags.forEach((tagId) => {
111 | postData.append('tags', tagId);
112 | })
113 |
114 | if(id){
115 | //Update blog post
116 | axios.put(`${API_BASE_URL}/api/posts/${id}/`, postData, {headers: {Authorization: `Token ${token}`, 'Content-Type': 'multipart/form-data',}})
117 | .then(response => {
118 | navigate('/');
119 | })
120 | .catch(error => {
121 | setError(`Error updating blog post: ${error}`)
122 | console.error(`Error updating blog post: ${error}`);
123 | })
124 | }else{
125 | axios.post(`${API_BASE_URL}/api/posts/`, postData, {headers: {Authorization: `Token ${token}`, 'Content-Type': 'multipart/form-data',}})
126 | .then(response => {
127 | navigate('/')
128 | })
129 | .catch(error => {
130 | setError(`Error creating blog post: ${error}`)
131 | console.error(`Error creating blog post: ${error}`);
132 | console.error('Error response:', error.response)
133 | })
134 | }
135 | }
136 |
137 | return(
138 |
139 |
{isEditing ? 'Edit Blog Post' : 'Create New Blog Post'}
140 | {error &&
{error}
}
141 |
Author: {authorDetails.username}
142 |
174 |
175 | )
176 | }
177 |
178 | export default BlogPostForm
--------------------------------------------------------------------------------
/blog_platform_front/src/components/BlogPosts/BlogPostFull.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import axios from "axios";
3 | import API_BASE_URL from '../../config';
4 | import { useParams, useNavigate } from "react-router-dom";
5 | import {token} from "../Auth/Token";
6 |
7 | const BlogPostFull = () =>{
8 | const { id } = useParams();
9 | const [post, setPost] = useState(null);
10 | const navigate = useNavigate();
11 |
12 | useEffect(() => {
13 | axios.get(`${API_BASE_URL}/api/postlist/${id}/`)
14 | .then((response) => {
15 | setPost(response.data);
16 | })
17 | .catch((error) => {
18 | alert(`Error fetching blog post:
19 | ${error}`);
20 | })
21 | },[]);
22 |
23 | const handleEditClick = (postId) =>{
24 | navigate(`/dashboard/?id=${postId}`)
25 | }
26 |
27 | return(
28 |
29 |
30 |
{post && post.title}
31 |
{post && post.author.username}
32 | {token &&
}
33 | {post && post.image &&
34 |
35 | {post &&

}
36 |
37 | }
38 |
41 |
Back To Article List
42 |
43 | )
44 |
45 |
46 | }
47 |
48 | export default BlogPostFull;
--------------------------------------------------------------------------------
/blog_platform_front/src/components/BlogPosts/BlogPostList.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect} from "react";
2 | import axios from "axios";
3 | import DOMPurify from 'dompurify';
4 | import API_BASE_URL from '../../config';
5 | import LoginButton from "../Auth/LoginButton"
6 | import LogOut from "../Auth/LogOut";
7 | import {token} from "../Auth/Token";
8 | import DashboadButton from "./DashboardButton";
9 |
10 | const BlogPostList = () => {
11 |
12 | const [posts, setPosts] = useState([]);
13 |
14 | useEffect(() => {
15 | axios.get(`${API_BASE_URL}/api/postlist`)
16 | .then(response => {
17 | setPosts(response.data);
18 | })
19 | .catch(error => {
20 | alert(`Error fetching blog posts:
21 | ${error}`);
22 | })
23 | }, [])
24 |
25 | const getPostReadMore = (content) => {
26 | const words = content.split(' ');
27 | if (words.length > 50){
28 | return words.slice(0, 50).join(' ') + '...';
29 | }else{
30 | return words.join(' ');
31 | }
32 | }
33 |
34 | return(
35 |
36 |
Articles
37 |
{token
38 | ? <> >
39 | : }
40 |
41 | {posts.map(post => (
42 | -
43 |
44 |
{post.author.username}
45 |
46 | Read More
47 |
48 | ))}
49 |
50 |
51 | )
52 | }
53 |
54 | export default BlogPostList;
--------------------------------------------------------------------------------
/blog_platform_front/src/components/BlogPosts/DashboardButton.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | const DashboadButton = () => {
5 | return (
6 |
7 |
8 |
9 | );
10 | }
11 |
12 | export default DashboadButton
--------------------------------------------------------------------------------
/blog_platform_front/src/components/BlogPosts/MyBlogPosts.jsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react"
2 | import axios from "axios"
3 | import API_BASE_URL from "../../config"
4 | import { token } from "../Auth/Token"
5 | import DOMPurify from 'dompurify';
6 |
7 | const MyBlogPost = () => {
8 | const [blogposts, setBlogPost] = useState([]);
9 |
10 | useEffect(() => {
11 | if(token){
12 | axios.get(`${API_BASE_URL}/api/my_posts`, {
13 | headers: {
14 | Authorization: `Token ${token}`
15 | }
16 | })
17 | .then(response => {
18 | console.log(response.data)
19 | setBlogPost(response.data)
20 | })
21 | .catch(error => {
22 | console.log(`Error fetching user-specific blog posts: ${error}`)
23 | alert(`Error fetching user-specific blog posts: ${error}`)
24 | })
25 | }
26 | },[]);
27 |
28 | const getPostReadMore = (content) => {
29 | const words = content.split(' ');
30 | if (words.length > 50){
31 | return words.slice(0, 50).join(' ') + '...';
32 | }else{
33 | return words.join(' ');
34 | }
35 | }
36 |
37 | return(
38 |
39 |
My Blog Posts
40 |
41 | {blogposts.map(post => (
42 | -
43 |
44 |
{post.author.username}
45 |
46 | Read More
47 |
48 | ))}
49 |
50 |
51 | );
52 | }
53 |
54 | export default MyBlogPost
--------------------------------------------------------------------------------
/blog_platform_front/src/components/Categories/CategoryList.jsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform_front/src/components/Categories/CategoryList.jsx
--------------------------------------------------------------------------------
/blog_platform_front/src/components/Comments/CommentForm.jsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform_front/src/components/Comments/CommentForm.jsx
--------------------------------------------------------------------------------
/blog_platform_front/src/components/Comments/CommentList.jsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform_front/src/components/Comments/CommentList.jsx
--------------------------------------------------------------------------------
/blog_platform_front/src/components/Tags/TagList.jsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/koheimizuno/Blog-Django-React-PostgreSQL/71981fcd65e39386567797722268cc5987cbc129/blog_platform_front/src/components/Tags/TagList.jsx
--------------------------------------------------------------------------------
/blog_platform_front/src/config.js:
--------------------------------------------------------------------------------
1 | const API_BASE_URL = 'http://127.0.0.1:8000'; // Replace with your actual backend URL
2 |
3 | export default API_BASE_URL;
4 |
--------------------------------------------------------------------------------
/blog_platform_front/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/blog_platform_front/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | const root = ReactDOM.createRoot(document.getElementById('root'));
8 | root.render(
9 |
10 |
11 |
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/blog_platform_front/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/blog_platform_front/src/pages/DashBoard.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import {token} from "../components/Auth/Token";
3 | import LogOut from "../components/Auth/LogOut";
4 | import BlogPostForm from "../components/BlogPosts/BlogPostForm";
5 | import MyBlogPost from "../components/BlogPosts/MyBlogPosts";
6 |
7 | const DashBoard = () => {
8 | const [displayComponent, setDisplayComponent] = useState('create_post')
9 | return(
10 | <>
11 | {token && }
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | {displayComponent === 'create_post' && }
23 | {displayComponent ==='my_posts' && }
24 |
25 |
26 |
27 | >
28 |
29 | )
30 | }
31 |
32 | export default DashBoard;
--------------------------------------------------------------------------------
/blog_platform_front/src/pages/HomePage.jsx:
--------------------------------------------------------------------------------
1 | import BlogPostList from '../components/BlogPosts/BlogPostList';
2 |
3 | const HomePage = () => {
4 | return(
5 | <>
6 |
7 | >
8 | )
9 | }
10 |
11 |
12 | export default HomePage;
--------------------------------------------------------------------------------
/blog_platform_front/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/blog_platform_front/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------