feat:comments
added comments feature. - this makes sure the user has the ability to comment on a certain find. it includes functionality to the api-sync layer so that multiple components can stay in sync with the comments state. - it also added the needed db migrations to the finds - it adds some ui components needed to create, list and view comments
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
import { Button } from '$lib/components/button';
|
||||
import { onMount } from 'svelte';
|
||||
import { browser } from '$app/environment';
|
||||
import { apiSync, type FindState } from '$lib/stores/api-sync';
|
||||
|
||||
// Server response type
|
||||
interface ServerFind {
|
||||
@@ -97,36 +98,6 @@
|
||||
// Initialize API sync with server data on mount
|
||||
onMount(async () => {
|
||||
if (browser && data.finds && data.finds.length > 0) {
|
||||
// Dynamically import the API sync to avoid SSR issues
|
||||
const { apiSync } = await import('$lib/stores/api-sync');
|
||||
|
||||
// Define the FindState interface locally
|
||||
interface FindState {
|
||||
id: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
latitude: string;
|
||||
longitude: string;
|
||||
locationName?: string;
|
||||
category?: string;
|
||||
isPublic: boolean;
|
||||
createdAt: Date;
|
||||
userId: string;
|
||||
username: string;
|
||||
profilePictureUrl?: string;
|
||||
media: Array<{
|
||||
id: string;
|
||||
findId: string;
|
||||
type: string;
|
||||
url: string;
|
||||
thumbnailUrl: string | null;
|
||||
orderIndex: number | null;
|
||||
}>;
|
||||
isLikedByUser: boolean;
|
||||
likeCount: number;
|
||||
isFromFriend: boolean;
|
||||
}
|
||||
|
||||
const findStates: FindState[] = data.finds.map((serverFind: ServerFind) => ({
|
||||
id: serverFind.id,
|
||||
title: serverFind.title,
|
||||
@@ -143,6 +114,7 @@
|
||||
media: serverFind.media,
|
||||
isLikedByUser: Boolean(serverFind.isLikedByUser),
|
||||
likeCount: serverFind.likeCount || 0,
|
||||
commentCount: 0,
|
||||
isFromFriend: Boolean(serverFind.isFromFriend)
|
||||
}));
|
||||
|
||||
|
||||
119
src/routes/api/finds/[findId]/comments/+server.ts
Normal file
119
src/routes/api/finds/[findId]/comments/+server.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { db } from '$lib/server/db';
|
||||
import { findComment, user } from '$lib/server/db/schema';
|
||||
import { eq, desc } from 'drizzle-orm';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
export const GET: RequestHandler = async ({ params, locals }) => {
|
||||
const session = locals.session;
|
||||
if (!session) {
|
||||
return json({ success: false, error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const findId = params.findId;
|
||||
if (!findId) {
|
||||
return json({ success: false, error: 'Find ID is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const comments = await db
|
||||
.select({
|
||||
id: findComment.id,
|
||||
findId: findComment.findId,
|
||||
content: findComment.content,
|
||||
createdAt: findComment.createdAt,
|
||||
updatedAt: findComment.updatedAt,
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
profilePictureUrl: user.profilePictureUrl
|
||||
}
|
||||
})
|
||||
.from(findComment)
|
||||
.innerJoin(user, eq(findComment.userId, user.id))
|
||||
.where(eq(findComment.findId, findId))
|
||||
.orderBy(desc(findComment.createdAt));
|
||||
|
||||
return json({
|
||||
success: true,
|
||||
data: comments,
|
||||
count: comments.length
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching comments:', error);
|
||||
return json({ success: false, error: 'Failed to fetch comments' }, { status: 500 });
|
||||
}
|
||||
};
|
||||
|
||||
export const POST: RequestHandler = async ({ params, locals, request }) => {
|
||||
const session = locals.session;
|
||||
if (!session) {
|
||||
return json({ success: false, error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const findId = params.findId;
|
||||
if (!findId) {
|
||||
return json({ success: false, error: 'Find ID is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { content } = body;
|
||||
|
||||
if (!content || typeof content !== 'string' || content.trim().length === 0) {
|
||||
return json({ success: false, error: 'Comment content is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
if (content.length > 500) {
|
||||
return json(
|
||||
{ success: false, error: 'Comment too long (max 500 characters)' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const commentId = crypto.randomUUID();
|
||||
const now = new Date();
|
||||
|
||||
const [newComment] = await db
|
||||
.insert(findComment)
|
||||
.values({
|
||||
id: commentId,
|
||||
findId,
|
||||
userId: session.userId,
|
||||
content: content.trim(),
|
||||
createdAt: now,
|
||||
updatedAt: now
|
||||
})
|
||||
.returning();
|
||||
|
||||
const commentWithUser = await db
|
||||
.select({
|
||||
id: findComment.id,
|
||||
findId: findComment.findId,
|
||||
content: findComment.content,
|
||||
createdAt: findComment.createdAt,
|
||||
updatedAt: findComment.updatedAt,
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
profilePictureUrl: user.profilePictureUrl
|
||||
}
|
||||
})
|
||||
.from(findComment)
|
||||
.innerJoin(user, eq(findComment.userId, user.id))
|
||||
.where(eq(findComment.id, commentId))
|
||||
.limit(1);
|
||||
|
||||
if (commentWithUser.length === 0) {
|
||||
return json({ success: false, error: 'Failed to create comment' }, { status: 500 });
|
||||
}
|
||||
|
||||
return json({
|
||||
success: true,
|
||||
data: commentWithUser[0]
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error creating comment:', error);
|
||||
return json({ success: false, error: 'Failed to create comment' }, { status: 500 });
|
||||
}
|
||||
};
|
||||
46
src/routes/api/finds/comments/[commentId]/+server.ts
Normal file
46
src/routes/api/finds/comments/[commentId]/+server.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
import { db } from '$lib/server/db';
|
||||
import { findComment, user } from '$lib/server/db/schema';
|
||||
import { eq, and } from 'drizzle-orm';
|
||||
import type { RequestHandler } from './$types';
|
||||
|
||||
export const DELETE: RequestHandler = async ({ params, locals }) => {
|
||||
const session = locals.session;
|
||||
if (!session) {
|
||||
return json({ success: false, error: 'Unauthorized' }, { status: 401 });
|
||||
}
|
||||
|
||||
const commentId = params.commentId;
|
||||
if (!commentId) {
|
||||
return json({ success: false, error: 'Comment ID is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const existingComment = await db
|
||||
.select()
|
||||
.from(findComment)
|
||||
.where(eq(findComment.id, commentId))
|
||||
.limit(1);
|
||||
|
||||
if (existingComment.length === 0) {
|
||||
return json({ success: false, error: 'Comment not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
if (existingComment[0].userId !== session.userId) {
|
||||
return json(
|
||||
{ success: false, error: 'Not authorized to delete this comment' },
|
||||
{ status: 403 }
|
||||
);
|
||||
}
|
||||
|
||||
await db.delete(findComment).where(eq(findComment.id, commentId));
|
||||
|
||||
return json({
|
||||
success: true,
|
||||
data: { id: commentId }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error deleting comment:', error);
|
||||
return json({ success: false, error: 'Failed to delete comment' }, { status: 500 });
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user