Skip to content

Commit

Permalink
Fix/Feat: comment counters, actions based on role
Browse files Browse the repository at this point in the history
  • Loading branch information
siinghd committed Mar 9, 2024
1 parent a25a53c commit c89eec7
Show file tree
Hide file tree
Showing 17 changed files with 302 additions and 36 deletions.
2 changes: 2 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ model Comment {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
votes Vote[]
isPinned Boolean @default(false)
}

model Vote {
Expand All @@ -196,3 +197,4 @@ enum CommentType {
INTRO
DEFAULT
}

91 changes: 83 additions & 8 deletions src/actions/comment/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ import {
InputTypeApproveIntroComment,
InputTypeCreateComment,
InputTypeDeleteComment,
InputTypePinComment,
InputTypeUpdateComment,
ReturnTypeApproveIntroComment,
ReturnTypeCreateComment,
ReturnTypeDeleteComment,
ReturnTypePinComment,
ReturnTypeUpdateComment,
} from './types';
import { authOptions } from '@/lib/auth';
Expand All @@ -17,11 +19,13 @@ import {
CommentApproveIntroSchema,
CommentDeleteSchema,
CommentInsertSchema,
CommentPinSchema,
CommentUpdateSchema,
} from './schema';
import { createSafeAction } from '@/lib/create-safe-action';
import { CommentType, Prisma } from '@prisma/client';
import { revalidatePath } from 'next/cache';
import { ROLES } from '../types';

export const getComments = async (
q: Prisma.CommentFindManyArgs,
Expand All @@ -39,11 +43,30 @@ export const getComments = async (
if (!parentComment) {
delete q.where?.parentId;
}
const pinnedComment = await prisma.comment.findFirst({
where: {
contentId: q.where?.contentId,
isPinned: true,
...(parentId ? { parentId: parseInt(parentId.toString(), 10) } : {}),
},
include: q.include,
});
if (pinnedComment) {
q.where = {
...q.where,
NOT: {
id: pinnedComment.id,
},
};
}

const comments = await prisma.comment.findMany(q);
const combinedComments = pinnedComment
? [pinnedComment, ...comments]
: comments;

return {
comments,
comments: combinedComments,
parentComment,
};
};
Expand Down Expand Up @@ -268,10 +291,15 @@ const updateCommentHandler = async (
const approveIntroCommentHandler = async (
data: InputTypeApproveIntroComment,
): Promise<ReturnTypeApproveIntroComment> => {
const { content_comment_ids, approved, adminPassword } = data;
const session = await getServerSession(authOptions);
const { content_comment_ids, approved, adminPassword, currentPath } = data;

if (adminPassword !== process.env.ADMIN_SECRET) {
return { error: 'Unauthorized' };
if (adminPassword) {
if (adminPassword !== process.env.ADMIN_SECRET) {
return { error: 'Unauthorized' };
}
} else if (!session || !session.user || session.user.role !== ROLES.ADMIN) {
return { error: 'Unauthorized ' };
}

const [contentId, commentId] = content_comment_ids.split(';');
Expand Down Expand Up @@ -315,7 +343,9 @@ const approveIntroCommentHandler = async (
},
});
});

if (currentPath) {
revalidatePath(currentPath);
}
return { data: updatedComment! };
} catch (error) {
return { error: 'Failed to update comment.' };
Expand All @@ -331,21 +361,24 @@ const deleteCommentHandler = async (
return { error: 'Unauthorized or insufficient permissions' };
}

const { commentId, adminPassword } = data;
const { commentId } = data;
const userId = session.user.id;

try {
const existingComment = await prisma.comment.findUnique({
where: { id: commentId },
include: {
parent: true,
},
});

if (!existingComment) {
return { error: 'Comment not found.' };
}

if (
existingComment.userId !== userId &&
adminPassword !== process.env.ADMIN_SECRET
session.user?.role !== ROLES.ADMIN ||
existingComment.userId !== userId
) {
return { error: 'Unauthorized to delete this comment.' };
}
Expand All @@ -365,6 +398,15 @@ const deleteCommentHandler = async (
await prisma.comment.deleteMany({
where: { parentId: commentId },
});
await prisma.content.update({
where: { id: existingComment.contentId },
data: { commentsCount: { decrement: 1 } },
});
} else {
await prisma.comment.update({
where: { id: existingComment.parentId },
data: { repliesCount: { decrement: 1 } },
});
}

// Then delete the comment itself
Expand All @@ -383,6 +425,38 @@ const deleteCommentHandler = async (
}
};

const pinCommentHandler = async (
data: InputTypePinComment,
): Promise<ReturnTypePinComment> => {
const { commentId, contentId, currentPath } = data;
const session = await getServerSession(authOptions);

if (!session || !session.user || session.user.role !== ROLES.ADMIN) {
return { error: 'Unauthorized or insufficient permissions' };
}
let updatedComment;
try {
await prisma.$transaction(async (prisma) => {
// Unpin any currently pinned comment for the content
await prisma.comment.updateMany({
where: { contentId, isPinned: true },
data: { isPinned: false },
});

updatedComment = await prisma.comment.update({
where: { id: commentId },
data: { isPinned: true },
});
});
if (currentPath) {
revalidatePath(currentPath);
}
return { data: updatedComment };
} catch (error: any) {
return { error: error.message || 'Failed to pin comment.' };
}
};

export const createMessage = createSafeAction(
CommentInsertSchema,
createCommentHandler,
Expand All @@ -399,3 +473,4 @@ export const approveComment = createSafeAction(
CommentApproveIntroSchema,
approveIntroCommentHandler,
);
export const pinComment = createSafeAction(CommentPinSchema, pinCommentHandler);
6 changes: 6 additions & 0 deletions src/actions/comment/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,15 @@ export const CommentApproveIntroSchema = z.object({
content_comment_ids: z.string(),
approved: z.boolean().optional(),
adminPassword: z.string().optional(),
currentPath: z.string().optional(),
});
export const CommentDeleteSchema = z.object({
adminPassword: z.string().optional(),
commentId: z.number(),
currentPath: z.string().optional(),
});
export const CommentPinSchema = z.object({
commentId: z.number(),
contentId: z.number(),
currentPath: z.string().optional(),
});
8 changes: 6 additions & 2 deletions src/actions/comment/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import {
CommentUpdateSchema,
CommentDeleteSchema,
CommentApproveIntroSchema,
CommentPinSchema,
} from './schema';
import { Delete } from '../types';
import { User, Comment } from '@prisma/client';
import { User, Comment, Vote } from '@prisma/client';

export type InputTypeCreateComment = z.infer<typeof CommentInsertSchema>;
export type ReturnTypeCreateComment = ActionState<
Expand All @@ -33,7 +34,10 @@ export type ReturnTypeDeleteComment = ActionState<
InputTypeDeleteComment,
Delete
>;
export type InputTypePinComment = z.infer<typeof CommentPinSchema>;
export type ReturnTypePinComment = ActionState<InputTypePinComment, Comment>;

export interface ExtendedComment extends Comment {
user: User;
user?: User;
votes?: Vote[];
}
5 changes: 5 additions & 0 deletions src/actions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,8 @@ export enum CommentFilter {
export type Delete = {
message: string;
};

export enum ROLES {
ADMIN = 'admin',
USER = 'user',
}
2 changes: 1 addition & 1 deletion src/app/admin/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export default function Courses() {
const { register, handleSubmit } = useForm<FormInput>();

const onSubmit: SubmitHandler<FormInput> = async (data) => {
console.log(data);
// console.log(data);
await fetch('/api/admin/course', {
body: JSON.stringify(data),
method: 'POST',
Expand Down
1 change: 1 addition & 0 deletions src/app/courses/[...courseId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { QueryParams } from '@/actions/types';

const checkAccess = async (courseId: string) => {
const session = await getServerSession(authOptions);

if (!session?.user) {
return false;
}
Expand Down
4 changes: 3 additions & 1 deletion src/components/Copy-to-clipbord.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ const CopyToClipboard = ({
return (
<div>
<button onClick={handleCopyClick}>
<CopyIcon size={15} />
<div className="flex items-center gap-1">
Copy <CopyIcon size={15} />
</div>
</button>
</div>
);
Expand Down
7 changes: 6 additions & 1 deletion src/components/NotionRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ export const NotionRenderer = ({ id }: { id: string }) => {
return (
<div>
<div style={{}}>
<NotionRendererLib recordMap={data} fullPage={true} darkMode={true} className='z-10'/>
<NotionRendererLib
recordMap={data}
fullPage={true}
darkMode={true}
className="z-10"
/>
</div>
</div>
);
Expand Down
14 changes: 7 additions & 7 deletions src/components/Signin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ const Signin = () => {
const password = useRef('');

const handleSubmit = async (e?: React.FormEvent<HTMLButtonElement>) => {
if(e){
if (e) {
e.preventDefault();
}

if (!email.current || !password.current) {
setRequiredError({
emailReq: email.current ? false : true,
Expand All @@ -36,7 +36,6 @@ const Signin = () => {
return;
}


const res = await signIn('credentials', {
username: email.current,
password: password.current,
Expand Down Expand Up @@ -96,12 +95,13 @@ const Signin = () => {
}));
password.current = e.target.value;
}}
onKeyDown={async (e)=>{
if(e.key === "Enter"){
onKeyDown={async (e) => {
if (e.key === 'Enter') {
setIsPasswordVisible(false);
handleSubmit();
}}}
/>
}
}}
/>
<button
className="inset-y-0 right-0 flex items-center px-4 text-gray-600"
onClick={togglePasswordVisibility}
Expand Down
2 changes: 1 addition & 1 deletion src/components/VideoPlayer2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ export const VideoPlayer: FunctionComponent<VideoPlayerProps> = ({
}
}, [searchParams, playerRef.current]);
return (
<div
<div
data-vjs-player
className="mx-auto md:max-w-[calc(100vw-3rem)] 2xl:max-w-[calc(100vw-17rem)]"
>
Expand Down
47 changes: 47 additions & 0 deletions src/components/comment/CommentApproveForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
'use client';

import { approveComment } from '@/actions/comment';
import { useAction } from '@/hooks/useAction';
import { CheckIcon } from 'lucide-react';
import { usePathname } from 'next/navigation';
import React from 'react';
import { toast } from 'sonner';

const CommentApproveForm = ({
commentId,
contentId,
}: {
commentId: number;
contentId: number;
}) => {
const currentPath = usePathname();

const { execute } = useAction(approveComment, {
onSuccess: () => {
toast('Comment Approved');
},
onError: (error) => {
toast.error(error);
},
});
const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();

execute({
content_comment_ids: `${contentId};${commentId}`,
approved: true,
currentPath,
});
};
return (
<form onSubmit={handleFormSubmit}>
<button type="submit">
<div className="flex gap-1 items-center">
Approve Chapters <CheckIcon className="w-4 h-4" />
</div>
</button>
</form>
);
};

export default CommentApproveForm;
4 changes: 3 additions & 1 deletion src/components/comment/CommentDeleteForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ const CommentDeleteForm = ({ commentId }: { commentId: number }) => {
return (
<form onSubmit={handleFormSubmit}>
<button type="submit">
<Trash2Icon className="w-4 h-4" />
<div className="flex gap-1 items-center">
Delete <Trash2Icon className="w-4 h-4" />
</div>
</button>
</form>
);
Expand Down
Loading

0 comments on commit c89eec7

Please sign in to comment.