From 3bda07de58a599912c023f2686478e4726570144 Mon Sep 17 00:00:00 2001 From: Thombrix Date: Fri, 5 Apr 2024 04:31:56 +0200 Subject: [PATCH 1/7] tags are now an attribute of user and and tag no longer has users attribute & added two route, random_user and user_by_id --- backend/kodecupid/urls.py | 20 ++++++- .../0004_remove_tag_users_user_tags.py | 22 ++++++++ backend/kodecupidapp/models/tag.py | 1 - backend/kodecupidapp/models/user.py | 1 + backend/kodecupidapp/views/__init__.py | 3 +- backend/kodecupidapp/views/swipe.py | 38 +++++++++++++ backend/kodecupidapp/views/tag.py | 4 +- backend/kodecupidapp/views/user.py | 54 ++++--------------- frontend/src/plugins/axiosPlugin.js | 21 +++++--- 9 files changed, 108 insertions(+), 56 deletions(-) create mode 100644 backend/kodecupidapp/migrations/0004_remove_tag_users_user_tags.py create mode 100644 backend/kodecupidapp/views/swipe.py diff --git a/backend/kodecupid/urls.py b/backend/kodecupid/urls.py index fb76a05..37d0cf7 100644 --- a/backend/kodecupid/urls.py +++ b/backend/kodecupid/urls.py @@ -16,7 +16,17 @@ """ from django.contrib import admin from django.urls import path -from kodecupidapp.views import UserView, TagView, PictureView, LikeView, UserTagView + +from kodecupidapp.views import ( + UserView, + TagView, + PictureView, + LikeView, + UserTagView, + user_by_id, + random_user +) + from rest_framework_simplejwt.views import ( TokenObtainPairView, TokenRefreshView, @@ -24,11 +34,17 @@ urlpatterns = [ path('admin/', admin.site.urls), + path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), path('api/token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), + path('api/user/', UserView.as_view(), name='user'), path('api/picture/', PictureView.as_view(), name='picture'), path('api/tags/', TagView.as_view(), name='tag-list'), path('api/like/', LikeView.as_view(), name='like-create'), - path('api/user/tags', UserTagView.as_view(), name='user-tag') + + path('api/user/tags', UserTagView.as_view(), name='user-tag'), + + path('api/user/random', random_user, name='random-user'), + path('api/user/', user_by_id, name='user-by-id') ] \ No newline at end of file diff --git a/backend/kodecupidapp/migrations/0004_remove_tag_users_user_tags.py b/backend/kodecupidapp/migrations/0004_remove_tag_users_user_tags.py new file mode 100644 index 0000000..996b36f --- /dev/null +++ b/backend/kodecupidapp/migrations/0004_remove_tag_users_user_tags.py @@ -0,0 +1,22 @@ +# Generated by Django 5.0.4 on 2024-04-03 22:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('kodecupidapp', '0003_like_unique_like'), + ] + + operations = [ + migrations.RemoveField( + model_name='tag', + name='users', + ), + migrations.AddField( + model_name='user', + name='tags', + field=models.ManyToManyField(related_name='tags', to='kodecupidapp.tag'), + ), + ] diff --git a/backend/kodecupidapp/models/tag.py b/backend/kodecupidapp/models/tag.py index 9f0b963..cd419af 100644 --- a/backend/kodecupidapp/models/tag.py +++ b/backend/kodecupidapp/models/tag.py @@ -2,6 +2,5 @@ class Tag(models.Model): name = models.CharField(max_length=100) - users = models.ManyToManyField('kodecupidapp.User', related_name='tags') created = models.DateTimeField(auto_now_add=True) updated = models.DateTimeField(auto_now=True) \ No newline at end of file diff --git a/backend/kodecupidapp/models/user.py b/backend/kodecupidapp/models/user.py index a064b99..2a8e7a9 100644 --- a/backend/kodecupidapp/models/user.py +++ b/backend/kodecupidapp/models/user.py @@ -6,4 +6,5 @@ class User(AbstractUser): # Add additional fields bio = models.CharField(max_length=100) looking_for = models.CharField(max_length=100) + tags = models.ManyToManyField('kodecupidapp.Tag', related_name='tags') pfp = models.ForeignKey(Picture, related_name='pfp', on_delete=models.CASCADE, null=True, blank=True) diff --git a/backend/kodecupidapp/views/__init__.py b/backend/kodecupidapp/views/__init__.py index d9623b4..a3d39b4 100644 --- a/backend/kodecupidapp/views/__init__.py +++ b/backend/kodecupidapp/views/__init__.py @@ -1,4 +1,5 @@ from .like import LikeView from .user import UserView, UserTagView from .tag import TagView -from .picture import PictureView \ No newline at end of file +from .picture import PictureView +from .swipe import random_user, user_by_id \ No newline at end of file diff --git a/backend/kodecupidapp/views/swipe.py b/backend/kodecupidapp/views/swipe.py new file mode 100644 index 0000000..9b46b2b --- /dev/null +++ b/backend/kodecupidapp/views/swipe.py @@ -0,0 +1,38 @@ +from rest_framework import status +from rest_framework.decorators import api_view, permission_classes +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from ..serializers import UserSerializer + +from ..models import User + +import random + + +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def user_by_id(request, id): + if User.objects.filter(id = id).exists(): + user = User.objects.get(id = id) + serializer = UserSerializer(user, many=False) + return Response(serializer.data, status=status.HTTP_200_OK) + else: + return Response({"message": "User not found."}, status=status.HTTP_404_NOT_FOUND) + +@api_view(['GET']) +@permission_classes([IsAuthenticated]) +def random_user(request): + users = User.objects.exclude(id=request.user.id) + + # TODO: retreive only the one not liked by the user ! + + if len(users) == 0: + return Response({"message": "Looks like you liked them all"}, status=status.HTTP_404_NOT_FOUND) + + user = random.choice(users) + + serializer = UserSerializer(user, many=False) + + return Response(serializer.data, status=status.HTTP_200_OK) + + diff --git a/backend/kodecupidapp/views/tag.py b/backend/kodecupidapp/views/tag.py index 698da39..a8db001 100644 --- a/backend/kodecupidapp/views/tag.py +++ b/backend/kodecupidapp/views/tag.py @@ -26,10 +26,10 @@ def post(self, request): tag_name = serializer.validated_data['name'] tag = get_object_or_404(Tag, name=tag_name) - if user in tag.users.all(): + if tag in user.tags.all(): return Response({'message': 'User already has tag'}, status=status.HTTP_400_BAD_REQUEST) - tag.users.add(user) + user.tags.add(tag) return Response({'message': 'Tag added to user'}, status=status.HTTP_201_CREATED) diff --git a/backend/kodecupidapp/views/user.py b/backend/kodecupidapp/views/user.py index 0ce1405..8fb02e1 100644 --- a/backend/kodecupidapp/views/user.py +++ b/backend/kodecupidapp/views/user.py @@ -2,11 +2,9 @@ from rest_framework.response import Response from rest_framework import status from rest_framework.permissions import AllowAny, IsAuthenticated -from ..models import User, Tag +from ..models import User from ..serializers import UserSerializer, UserRegistrationSerializer, UserConfigurationSerializer, TagSerializer -import random - class UserView(APIView): def get_permissions(self): @@ -17,6 +15,7 @@ def get_permissions(self): def post(self, request): serializer = UserRegistrationSerializer(data=request.data) + if serializer.is_valid(): serializer.save() return Response({"message": "User created successfully."}, status=status.HTTP_201_CREATED) @@ -24,61 +23,28 @@ def post(self, request): def patch(self, request): serializer = UserConfigurationSerializer(request.user, data=request.data) + if serializer.is_valid(): serializer.save() return Response({"message": "User configured successfully."}, status=status.HTTP_204_NO_CONTENT) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def get(self, request): - if "id" in request.query_params: - id = request.query_params["id"] - - if id == "random": - try: - users = User.objects.all() - user = random.choice(users) - # while loop not safe if only 1 user (infinite loop) - while user.id == request.user.id: - user = random.choice(users) - serializer = UserSerializer(user, many=False) - return Response(serializer.data, status=status.HTTP_200_OK) - except: - return Response({"message": "No random user could be found."}, status=status.HTTP_404_NOT_FOUND) + + id = request.user.id - else: - if User.objects.filter(id = id).exists(): - user = User.objects.get(id = id) - serializer = UserSerializer(user, many=False) - return Response(serializer.data, status=status.HTTP_200_OK) - else: - return Response({"message": "User not found."}, status=status.HTTP_404_NOT_FOUND) - else: - id = request.user.id - if User.objects.filter(id = id).exists(): - user = User.objects.get(id = id) - serializer = UserSerializer(user, many=False) - return Response(serializer.data, status=status.HTTP_200_OK) - return Response({"message": "User not found."}, status=status.HTTP_404_NOT_FOUND) - - - def delete(self, request): - id = int(request.data["id"]) if User.objects.filter(id = id).exists(): - if id == request.user.id: - user = User.objects.get(id = id) - user.delete() - return Response({"message": "User successfully deleted."},status=status.HTTP_204_NO_CONTENT) - return Response({"message": "User cannot be deleted."}, status=status.HTTP_403_FORBIDDEN) + user = User.objects.get(id = id) + serializer = UserSerializer(user, many=False) + return Response(serializer.data, status=status.HTTP_200_OK) + return Response({"message": "User not found."}, status=status.HTTP_404_NOT_FOUND) - class UserTagView(APIView): def get(self, request): user = request.user - tags = Tag.objects.filter(users=user) - - serializer = TagSerializer(tags, many=True) + serializer = TagSerializer(user.tags, many=True) return Response(serializer.data) \ No newline at end of file diff --git a/frontend/src/plugins/axiosPlugin.js b/frontend/src/plugins/axiosPlugin.js index 1f6782e..ef8725b 100644 --- a/frontend/src/plugins/axiosPlugin.js +++ b/frontend/src/plugins/axiosPlugin.js @@ -13,19 +13,28 @@ export default { originalRequest._retry = true; const refreshToken = localStorage.getItem('refreshToken'); - return axios.post(store.routes['USER_TOKEN_REFRESH'], { + return axios.post(store.routes['USER_TOKEN_REFRESH'], + { 'refresh': refreshToken - }, { + }, + { withCredentials: true - }).then((response) => { - if (response.status === 200) { + }) + .then((response) => + { + if (response.status === 200) + { localStorage.setItem('accessToken', response.data.access); axios.defaults.headers.common['Authorization'] = `Bearer ${localStorage.getItem('accessToken')}`; return axios(originalRequest); - } else { + } + else + { router.push({ name: 'signin',replace: true, force: true }); } - }).catch(() => { + }) + .catch(() => + { router.push({ name: 'signin',replace: true, force: true }); }); } From 88fd6691ce61865b37a591bd9b54a8be3e0d9641 Mon Sep 17 00:00:00 2001 From: Thombrix Date: Fri, 5 Apr 2024 05:06:47 +0200 Subject: [PATCH 2/7] refactor frontend to use the new routes & enabled hot reload --- frontend/src/configs/constants.js | 5 +++-- frontend/src/views/account/EditView.vue | 4 ++-- frontend/src/views/account/ShowView.vue | 2 +- frontend/src/views/cupid/SearchView.vue | 2 +- frontend/vite.config.js | 3 +++ 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/frontend/src/configs/constants.js b/frontend/src/configs/constants.js index 3cb643a..dbc1df0 100644 --- a/frontend/src/configs/constants.js +++ b/frontend/src/configs/constants.js @@ -18,14 +18,15 @@ export const API_ROUTES = { USER_SIGNIN: API_SERVER_URL+'/api/token/', USER_TOKEN_REFRESH: API_SERVER_URL+'/api/token/refresh/', - USER_DETAIL: API_SERVER_URL+'/api/user/', + USER: API_SERVER_URL+'/api/user/', + USER_RANDOM: API_SERVER_URL+'/api/user/random', TAG_LIST: API_SERVER_URL+'/api/tags/', USER_TAG_ADD: API_SERVER_URL+'/api/tags/', USER_TAG_REMOVE: API_SERVER_URL+'/api/tags/', - USER_TAGS: API_SERVER_URL+'/api/user/tags', + USER_TAGS: API_SERVER_URL+'/api/user/tags' }; diff --git a/frontend/src/views/account/EditView.vue b/frontend/src/views/account/EditView.vue index 592c700..ed39c33 100644 --- a/frontend/src/views/account/EditView.vue +++ b/frontend/src/views/account/EditView.vue @@ -107,7 +107,7 @@ const handleSubmit = async () => { const jsonForm = JSON.stringify(user.value); - axios.patch(store.routes['USER_DETAIL'], jsonForm, { + axios.patch(store.routes['USER'], jsonForm, { headers: { 'Content-Type': 'application/json' } @@ -124,7 +124,7 @@ const handleSubmit = async () => { }; const fetchUser = async () => { - axios.get(store.routes['USER_DETAIL']).catch((error) => { + axios.get(store.routes['USER']).catch((error) => { console.error(error.response.data); setError(error.response.data,'error'); return error diff --git a/frontend/src/views/account/ShowView.vue b/frontend/src/views/account/ShowView.vue index 381405f..7809eca 100644 --- a/frontend/src/views/account/ShowView.vue +++ b/frontend/src/views/account/ShowView.vue @@ -51,7 +51,7 @@ const user = ref({ const fetchUser = async ()=> { - await axios.get(store.routes['USER_DETAIL']).catch((error) => { + await axios.get(store.routes['USER']).catch((error) => { console.error(error.response.data); setError(error.response.data,'error'); return error diff --git a/frontend/src/views/cupid/SearchView.vue b/frontend/src/views/cupid/SearchView.vue index 0af380e..3b71949 100644 --- a/frontend/src/views/cupid/SearchView.vue +++ b/frontend/src/views/cupid/SearchView.vue @@ -42,7 +42,7 @@ const dislike = async () => { }; const fetchUser = async () => { - axios.get(store.routes['USER_SEARCH'], {params : {id : "random"}}).catch((error) => { + axios.get(store.routes['USER_RANDOM']).catch((error) => { console.error(error.response.data); setError(error.response.data,'error'); return error diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 9ac5a5d..0c4899e 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -11,6 +11,9 @@ export default defineConfig({ }, server: { port: 80, + watch: { + usePolling: true, + }, }, plugins: [ vue(), From 17b93b6d58da397bb6052741b807364857a0c1b4 Mon Sep 17 00:00:00 2001 From: Thombrix Date: Fri, 12 Apr 2024 00:23:15 +0200 Subject: [PATCH 3/7] test pictures --- backend/kodecupid/urls.py | 4 +- backend/kodecupidapp/views/picture.py | 14 ++--- frontend/src/components/ImageUploader.vue | 60 +++++++++++++++++++++ frontend/src/components/PicturesDisplay.vue | 51 ++++++++++++++++++ frontend/src/configs/constants.js | 4 +- frontend/src/views/account/EditView.vue | 5 ++ frontend/src/views/cupid/SearchView.vue | 1 + 7 files changed, 130 insertions(+), 9 deletions(-) create mode 100644 frontend/src/components/ImageUploader.vue create mode 100644 frontend/src/components/PicturesDisplay.vue diff --git a/backend/kodecupid/urls.py b/backend/kodecupid/urls.py index 37d0cf7..82ef28e 100644 --- a/backend/kodecupid/urls.py +++ b/backend/kodecupid/urls.py @@ -45,6 +45,6 @@ path('api/user/tags', UserTagView.as_view(), name='user-tag'), - path('api/user/random', random_user, name='random-user'), - path('api/user/', user_by_id, name='user-by-id') + path('api/user/random/', random_user, name='random-user'), + path('api/user//', user_by_id, name='user-by-id') ] \ No newline at end of file diff --git a/backend/kodecupidapp/views/picture.py b/backend/kodecupidapp/views/picture.py index a1be098..57af8f1 100644 --- a/backend/kodecupidapp/views/picture.py +++ b/backend/kodecupidapp/views/picture.py @@ -12,16 +12,18 @@ class PictureView(APIView): permission_classes = [IsAuthenticated] def post(self, request): - serializer = PictureSerializer(data=request.data) + + modified_data = request.data.copy() + modified_data['user'] = request.user.id + + serializer = PictureSerializer(data=modified_data) if serializer.is_valid(): - if request.user.id == int(request.data["user"]): - serializer.save() - return Response({"message": "Picture added successfully."}, status=status.HTTP_201_CREATED) - return Response({"message": "Picture cannot be added to another user."}, status=status.HTTP_403_FORBIDDEN) + serializer.save() + return Response({"message": "Picture added successfully."}, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) def get(self, request): - id = request.data["id"] + id = request.user.id if Picture.objects.filter(id = id).exists(): pic = Picture.objects.get(id = id) serializer = PictureSerializer(pic, many=False) diff --git a/frontend/src/components/ImageUploader.vue b/frontend/src/components/ImageUploader.vue new file mode 100644 index 0000000..978d2b0 --- /dev/null +++ b/frontend/src/components/ImageUploader.vue @@ -0,0 +1,60 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/PicturesDisplay.vue b/frontend/src/components/PicturesDisplay.vue new file mode 100644 index 0000000..2c38d34 --- /dev/null +++ b/frontend/src/components/PicturesDisplay.vue @@ -0,0 +1,51 @@ + + + + + + \ No newline at end of file diff --git a/frontend/src/configs/constants.js b/frontend/src/configs/constants.js index dbc1df0..fe249cf 100644 --- a/frontend/src/configs/constants.js +++ b/frontend/src/configs/constants.js @@ -26,7 +26,9 @@ export const API_ROUTES = { USER_TAG_ADD: API_SERVER_URL+'/api/tags/', USER_TAG_REMOVE: API_SERVER_URL+'/api/tags/', - USER_TAGS: API_SERVER_URL+'/api/user/tags' + USER_TAGS: API_SERVER_URL+'/api/user/tags', + + USER_PICTURE: API_SERVER_URL+'/api/picture/' }; diff --git a/frontend/src/views/account/EditView.vue b/frontend/src/views/account/EditView.vue index ed39c33..04529b0 100644 --- a/frontend/src/views/account/EditView.vue +++ b/frontend/src/views/account/EditView.vue @@ -60,6 +60,10 @@ + + + + @@ -72,6 +76,7 @@ import { ref } from 'vue'; import { onMounted } from 'vue'; import axios from 'axios'; import { computed } from 'vue'; +import ImageUploader from '@/components/ImageUploader.vue'; const user = ref({ username: ref(''), diff --git a/frontend/src/views/cupid/SearchView.vue b/frontend/src/views/cupid/SearchView.vue index 3b71949..cc9e46b 100644 --- a/frontend/src/views/cupid/SearchView.vue +++ b/frontend/src/views/cupid/SearchView.vue @@ -17,6 +17,7 @@ \ No newline at end of file diff --git a/frontend/src/views/account/ShowView.vue b/frontend/src/views/account/ShowView.vue index 7809eca..5a9398e 100644 --- a/frontend/src/views/account/ShowView.vue +++ b/frontend/src/views/account/ShowView.vue @@ -1,5 +1,5 @@ \ No newline at end of file diff --git a/frontend/src/views/authentication/SigninView.vue b/frontend/src/views/authentication/SigninView.vue index eb2b8f3..7029f3f 100644 --- a/frontend/src/views/authentication/SigninView.vue +++ b/frontend/src/views/authentication/SigninView.vue @@ -34,16 +34,10 @@ \ No newline at end of file diff --git a/frontend/src/views/authentication/SignupView.vue b/frontend/src/views/authentication/SignupView.vue index e892143..77b7c09 100644 --- a/frontend/src/views/authentication/SignupView.vue +++ b/frontend/src/views/authentication/SignupView.vue @@ -34,10 +34,7 @@ \ No newline at end of file diff --git a/frontend/src/views/cupid/MatchView.vue b/frontend/src/views/cupid/MatchView.vue index 5aad56c..7943c62 100644 --- a/frontend/src/views/cupid/MatchView.vue +++ b/frontend/src/views/cupid/MatchView.vue @@ -20,24 +20,13 @@ \ No newline at end of file diff --git a/frontend/src/components/PeopleCard.vue b/frontend/src/components/PeopleCard.vue index 6e8d07e..bef6584 100644 --- a/frontend/src/components/PeopleCard.vue +++ b/frontend/src/components/PeopleCard.vue @@ -1,15 +1,18 @@