Skip to content

Commit

Permalink
fix(backend): use correct serializer fields
Browse files Browse the repository at this point in the history
  • Loading branch information
umitcan07 committed Nov 20, 2024
1 parent dc10e12 commit a261471
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 148 deletions.
40 changes: 10 additions & 30 deletions backend/core/serializers/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from rest_framework import serializers

from ..models import (CustomUser, ForumQuestion, Quiz, QuizQuestion, QuizQuestionChoice, RateQuiz,
Tag, ForumBookmark, ForumAnswer, ForumUpvote, ForumDownvote, TakeQuiz, ForumAnswerUpvote, ForumAnswerDownvote)
Tag, ForumBookmark, ForumAnswer, ForumUpvote, ForumDownvote, TakeQuiz, ForumAnswerDownvote, ForumAnswerUpvote)
from .forum_vote_serializer import ForumUpvoteSerializer, ForumDownvoteSerializer
from .take_quiz_serializer import TakeQuizSerializer

Expand Down Expand Up @@ -51,22 +51,20 @@ class Meta:
class ForumAnswerSerializer(serializers.ModelSerializer):
author = UserInfoSerializer(read_only=True)
upvotes_count = serializers.SerializerMethodField()
is_upvoted = serializers.SerializerMethodField()
downvotes_count = serializers.SerializerMethodField()
is_my_answer = serializers.SerializerMethodField()
is_upvoted = serializers.SerializerMethodField()
is_downvoted = serializers.SerializerMethodField()


class Meta:
model = ForumAnswer
fields = ('id', 'answer', 'author', 'created_at', 'upvotes_count', 'is_upvoted', 'downvotes_count', 'is_downvoted')
read_only_fields = ('author', 'created_at', 'upvotes_count', 'is_upvoted', 'downvotes_count', 'is_downvoted')

fields = ('id', 'answer', 'author', 'created_at', 'is_my_answer', 'is_upvoted', 'is_downvoted', 'upvotes_count', 'downvotes_count')
read_only_fields = ('author', 'created_at', 'upvotes_count', 'downvotes_count', 'is_my_answer', 'is_upvoted', 'is_downvoted')

def get_is_my_answer(self, obj):
user = self.context['request'].user
if not user.is_authenticated:
return False
return obj.author == user
return None
return obj.id if obj.author == user else None

def get_is_upvoted(self, obj):
user = self.context['request'].user
Expand All @@ -81,35 +79,17 @@ def get_is_downvoted(self, obj):
return None
downvote = ForumAnswerDownvote.objects.filter(user=user, forum_answer=obj).first()
return downvote.id if downvote else None

def create(self, validated_data):
return ForumAnswer.objects.create(**validated_data)

def update(self, instance, validated_data):
instance.answer = validated_data.get('answer', instance.answer)
instance.save()
return instance

def get_upvotes_count(self, obj):
return obj.upvotes.count()

def get_is_upvoted(self, obj):
user = self.context['request'].user
if not user.is_authenticated:
return None
upvote = ForumAnswerUpvote.objects.filter(user=user, forum_answer=obj).first()
return upvote.id if upvote else None

def get_downvotes_count(self, obj):
return obj.downvotes.count()

def get_is_downvoted(self, obj):
user = self.context['request'].user
if not user.is_authenticated:
return False
downvote = ForumAnswerDownvote.objects.filter(user=user, forum_answer=obj).first()
return downvote.id if downvote else None


def create(self, validated_data):
return ForumAnswer.objects.create(**validated_data)

class ForumQuestionSerializer(serializers.ModelSerializer):
tags = TagSerializer(many=True) # For nested representation of tags
Expand Down
8 changes: 3 additions & 5 deletions client/src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ import { loginAction, loginLoader } from "./routes/Auth/Login.data";
import { logoutLoader } from "./routes/Auth/Logout.data";
import { registerAction } from "./routes/Auth/Register.data";
import {
answerForumAction,
bookmarkForumAction,
downvoteForumAction,
forumLoader,
upvoteForumAction,
} from "./routes/Forum/Forum.data";
import { forumQuestionLoader, postAction } from "./routes/Forum/Question.data";
import { forumQuestionLoader } from "./routes/Forum/Question.data";
import { homeLoader } from "./routes/Home/Home.data";
import { QuizPage } from "./routes/Quiz/Quiz";
import { quizLoader } from "./routes/Quiz/Quiz.data";
Expand Down Expand Up @@ -65,7 +66,6 @@ export const routes: RouteObject[] = [
path: "forum/:questionId",
element: <ForumQuestion />,
loader: forumQuestionLoader,
action: postAction,
children: [
{
path: "bookmark",
Expand All @@ -81,9 +81,7 @@ export const routes: RouteObject[] = [
},
{
path: "answer",
action: () => {
return true;
},
action: answerForumAction,
},
],
},
Expand Down
8 changes: 4 additions & 4 deletions client/src/routes/Auth/Login.data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const loginResponseSuccessSchema = object({
});

const loginResponseErrorSchema = object({
error: string(),
detail: string(),
});

const loginResponseSchema = union([
Expand Down Expand Up @@ -58,16 +58,16 @@ export const loginAction = async ({ request }: { request: Request }) => {
return { error: "Invalid response" };
}

if ("error" in responseOutput) {
if ("detail" in responseOutput) {
useToastStore.getState().add({
id: `login-error-${requestOutput.username}-${requestOutput.password}`,
type: "error",
data: {
message: responseOutput.error,
message: responseOutput.detail,
description: "What do you do when you forget your keys?",
},
});
return { error: responseOutput.error };
return { error: responseOutput.detail };
}

const options = persistentLogin ? { expires: 30 } : undefined;
Expand Down
16 changes: 9 additions & 7 deletions client/src/routes/Auth/Logout.data.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { LoaderFunction, redirect } from "react-router";
import { USER, USER_TOKEN_ACCESS, USER_TOKEN_REFRESH } from "../../constants";
import Cookies from "js-cookie";
import { LoaderFunction, redirect } from "react-router-typesafe";
import { USER } from "../../constants";
import { useToastStore } from "../../store";

export const logoutLoader = (() => {
localStorage.removeItem(USER_TOKEN_ACCESS);
localStorage.removeItem(USER_TOKEN_REFRESH);
localStorage.removeItem(USER);
sessionStorage.removeItem(USER_TOKEN_ACCESS);
sessionStorage.removeItem(USER_TOKEN_REFRESH);
Cookies.remove("access_token");
Cookies.remove("refresh_token");

sessionStorage.removeItem(USER);
localStorage.removeItem(USER);

useToastStore.getState().add({
id: "logout-success",
type: "info",
Expand All @@ -18,5 +19,6 @@ export const logoutLoader = (() => {
"No this is not the end, lift up your head. Somewhere we'll meet again.",
},
});

return redirect("/login");
}) satisfies LoaderFunction;
105 changes: 73 additions & 32 deletions client/src/routes/Forum/Forum.data.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import { LoaderFunction } from "react-router";
import { number, object, safeParse, string } from "valibot";
import { ActionFunction, LoaderFunction } from "react-router";
import { safeParse } from "valibot";
import apiClient from "../../api";
import { USER } from "../../constants";
import { useToastStore } from "../../store";
import { logger } from "../../utils";
import { forumSchema } from "./Forum.schema";
import {
forumAnswerSchema,
forumBookmarkSchema,
forumDownvoteSchema,
forumSchema,
forumUpvoteSchema,
} from "./Forum.schema";

export const forumLoader = (async ({ request }) => {
const url = new URL(request.url);
Expand All @@ -14,8 +22,12 @@ export const forumLoader = (async ({ request }) => {
params: { page, per_page },
});

const { output, success } = safeParse(forumSchema, response.data);

const { output, success, issues } = safeParse(
forumSchema,
response.data,
);
console.log(issues);
console.log(output);
if (!success) {
throw new Error("Failed to parse forum response");
}
Expand All @@ -27,21 +39,7 @@ export const forumLoader = (async ({ request }) => {
}
}) satisfies LoaderFunction;

const forumUpvoteSchema = object({
id: number(),
user: number(),
forum_question: number(),
created_at: string(), // ISO date string
});

const forumDownvoteSchema = object({
id: number(),
user: number(),
forum_question: number(),
created_at: string(), // ISO date string
});

export const upvoteForumAction = async ({ request }: { request: Request }) => {
export const upvoteForumAction = (async ({ request }: { request: Request }) => {
try {
console.log("Processing upvote action...");

Expand Down Expand Up @@ -87,9 +85,9 @@ export const upvoteForumAction = async ({ request }: { request: Request }) => {
logger.error("Error in upvoteForumAction", error);
throw new Error("Failed to process upvote action");
}
};
}) satisfies ActionFunction;

export const downvoteForumAction = async ({
export const downvoteForumAction = (async ({
request,
}: {
request: Request;
Expand Down Expand Up @@ -139,16 +137,9 @@ export const downvoteForumAction = async ({
logger.error("Error in downvoteForumAction", error);
throw new Error("Failed to process downvote action");
}
};

const forumBookmarkSchema = object({
id: number(),
user: number(),
forum_question: number(),
created_at: string(), // ISO date string
});
}) satisfies ActionFunction;

export const bookmarkForumAction = async ({
export const bookmarkForumAction = (async ({
request,
}: {
request: Request;
Expand Down Expand Up @@ -196,4 +187,54 @@ export const bookmarkForumAction = async ({
logger.error("Error in bookmarkForumAction", error);
throw new Error("Failed to process bookmark action");
}
};
}) satisfies ActionFunction;

export const answerForumAction = (async ({ request, params }) => {
// if not logged in, redirect to login page and add a toast

const user = sessionStorage.getObject(USER) || localStorage.getObject(USER);
if (!user) {
useToastStore.getState().add({
id: "not-logged-in",
type: "info",
data: {
message: "Log in to answer forum question",
description: "You need to log in to answer forum questions.",
},
});
}

const formData = await request.formData();
const answer = formData.get("answer");
const questionId = params.questionId;

try {
const response = await apiClient.post(
`forum-questions/${questionId}/answers/`,
{
answer,
},
);

const { issues, success } = safeParse(forumAnswerSchema, response.data);

if (!success) {
logger.error("Response validation failed", issues);
throw new Error("Invalid response from answer creation");
}

useToastStore.getState().add({
id: `answer-success-${postId}`,
type: "success",
data: {
message: "Answer created successfully",
description: "Your answer has been posted.",
},
});

return response;
} catch (error) {
logger.error("Error in answerForumAction", error);
throw new Error("Failed to process answer action");
}
}) satisfies ActionFunction;
33 changes: 29 additions & 4 deletions client/src/routes/Forum/Forum.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { array, InferInput, nullable, number, object, string } from "valibot";

export type Answer = InferInput<typeof answerSchema>;

const Tagschema = object({
const tagSchema = object({
name: string(),
linked_data_id: string(),
description: string(),
Expand All @@ -18,7 +18,7 @@ export const authorSchema = object({
});

export const answerSchema = object({
id: string(),
id: number(),
answer: string(),
author: authorSchema,
created_at: string(),
Expand All @@ -32,7 +32,7 @@ export const forumQuestionSchema = object({
id: number(),
title: string(),
question: string(),
tags: array(Tagschema),
tags: array(tagSchema),
author: authorSchema,
created_at: string(),
answers_count: number(),
Expand All @@ -51,5 +51,30 @@ export const forumSchema = object({
results: array(forumQuestionSchema),
});

export type Tag = InferInput<typeof Tagschema>;
export const forumBookmarkSchema = object({
id: number(),
user: number(),
forum_question: number(),
created_at: string(), // ISO date string
});

export const forumUpvoteSchema = object({
id: number(),
user: number(),
forum_question: number(),
created_at: string(), // ISO date string
});

export const forumDownvoteSchema = object({
id: number(),
user: number(),
forum_question: number(),
created_at: string(), // ISO date string
});

export const forumAnswerSchema = object({
answer: string(),
});

export type Tag = InferInput<typeof tagSchema>;
export type ForumQuestion = InferInput<typeof forumQuestionSchema>;
Loading

0 comments on commit a261471

Please sign in to comment.