feat: comments
fix: use select instead of dropdown for filter on /market
This commit is contained in:
parent
800b5d1a09
commit
bd05b269fe
22 changed files with 2715 additions and 97 deletions
125
website/src/routes/api/coin/[coinSymbol]/comments/+server.ts
Normal file
125
website/src/routes/api/coin/[coinSymbol]/comments/+server.ts
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
import { auth } from '$lib/auth';
|
||||
import { error, json } from '@sveltejs/kit';
|
||||
import { db } from '$lib/server/db';
|
||||
import { comment, coin, user, commentLike } from '$lib/server/db/schema';
|
||||
import { eq, and, desc, sql } from 'drizzle-orm';
|
||||
|
||||
export async function GET({ params, request }) {
|
||||
const session = await auth.api.getSession({
|
||||
headers: request.headers
|
||||
});
|
||||
|
||||
const { coinSymbol } = params;
|
||||
const normalizedSymbol = coinSymbol.toUpperCase();
|
||||
|
||||
try {
|
||||
const [coinData] = await db
|
||||
.select({ id: coin.id })
|
||||
.from(coin)
|
||||
.where(eq(coin.symbol, normalizedSymbol))
|
||||
.limit(1);
|
||||
|
||||
if (!coinData) {
|
||||
return json({ message: 'Coin not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
const commentsQuery = db
|
||||
.select({
|
||||
id: comment.id,
|
||||
content: comment.content,
|
||||
likesCount: comment.likesCount,
|
||||
createdAt: comment.createdAt,
|
||||
updatedAt: comment.updatedAt,
|
||||
userId: user.id,
|
||||
userName: user.name,
|
||||
userUsername: user.username,
|
||||
userImage: user.image,
|
||||
userBio: user.bio,
|
||||
userCreatedAt: user.createdAt,
|
||||
isLikedByUser: session?.user ?
|
||||
sql<boolean>`EXISTS(SELECT 1 FROM ${commentLike} WHERE ${commentLike.userId} = ${session.user.id} AND ${commentLike.commentId} = ${comment.id})` :
|
||||
sql<boolean>`FALSE`
|
||||
})
|
||||
.from(comment)
|
||||
.innerJoin(user, eq(comment.userId, user.id))
|
||||
.where(and(eq(comment.coinId, coinData.id), eq(comment.isDeleted, false)))
|
||||
.orderBy(desc(comment.createdAt));
|
||||
|
||||
const comments = await commentsQuery;
|
||||
|
||||
return json({ comments });
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch comments:', err);
|
||||
return json({ message: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST({ request, params }) {
|
||||
const session = await auth.api.getSession({
|
||||
headers: request.headers
|
||||
});
|
||||
|
||||
if (!session?.user) {
|
||||
throw error(401, 'Not authenticated');
|
||||
}
|
||||
|
||||
const { coinSymbol } = params;
|
||||
const { content } = await request.json();
|
||||
|
||||
if (!content || content.trim().length === 0) {
|
||||
throw error(400, 'Comment content is required');
|
||||
}
|
||||
|
||||
if (content.length > 500) {
|
||||
throw error(400, 'Comment must be 500 characters or less');
|
||||
}
|
||||
|
||||
const normalizedSymbol = coinSymbol.toUpperCase();
|
||||
const userId = Number(session.user.id);
|
||||
|
||||
try {
|
||||
const [coinData] = await db
|
||||
.select({ id: coin.id })
|
||||
.from(coin)
|
||||
.where(eq(coin.symbol, normalizedSymbol))
|
||||
.limit(1);
|
||||
|
||||
if (!coinData) {
|
||||
throw error(404, 'Coin not found');
|
||||
}
|
||||
|
||||
const [newComment] = await db
|
||||
.insert(comment)
|
||||
.values({
|
||||
userId,
|
||||
coinId: coinData.id,
|
||||
content: content.trim()
|
||||
})
|
||||
.returning();
|
||||
|
||||
const [commentWithUser] = await db
|
||||
.select({
|
||||
id: comment.id,
|
||||
content: comment.content,
|
||||
likesCount: comment.likesCount,
|
||||
createdAt: comment.createdAt,
|
||||
updatedAt: comment.updatedAt,
|
||||
userId: comment.userId,
|
||||
userName: user.name,
|
||||
userUsername: user.username,
|
||||
userImage: user.image,
|
||||
userBio: user.bio,
|
||||
userCreatedAt: user.createdAt,
|
||||
isLikedByUser: sql<boolean>`FALSE`
|
||||
})
|
||||
.from(comment)
|
||||
.innerJoin(user, eq(comment.userId, user.id))
|
||||
.where(eq(comment.id, newComment.id))
|
||||
.limit(1);
|
||||
|
||||
return json({ comment: commentWithUser });
|
||||
} catch (e) {
|
||||
console.error('Error creating comment:', e);
|
||||
throw error(500, 'Failed to create comment');
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
import { error, json } from '@sveltejs/kit';
|
||||
import { db } from '$lib/server/db';
|
||||
import { comment, commentLike, coin } from '$lib/server/db/schema';
|
||||
import { eq, and, sql } from 'drizzle-orm';
|
||||
import type { RequestHandler } from './$types';
|
||||
import { auth } from '$lib/auth';
|
||||
|
||||
export const POST: RequestHandler = async ({ request, params }) => {
|
||||
const session = await auth.api.getSession({
|
||||
headers: request.headers
|
||||
});
|
||||
|
||||
if (!session?.user) {
|
||||
return json({ message: 'Not authenticated' }, { status: 401 });
|
||||
}
|
||||
|
||||
const commentId = parseInt(params.id);
|
||||
const { coinSymbol } = params;
|
||||
const userId = Number(session.user.id);
|
||||
|
||||
if (isNaN(commentId)) {
|
||||
return json({ message: 'Invalid comment ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
// Verify the comment exists and belongs to the specified coin
|
||||
const [commentData] = await db
|
||||
.select()
|
||||
.from(comment)
|
||||
.innerJoin(coin, eq(comment.coinId, coin.id))
|
||||
.where(and(eq(comment.id, commentId), eq(coin.symbol, coinSymbol)));
|
||||
|
||||
if (!commentData) {
|
||||
return json({ message: 'Comment not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Check if user already liked this comment
|
||||
const [existingLike] = await db
|
||||
.select()
|
||||
.from(commentLike)
|
||||
.where(and(eq(commentLike.userId, userId), eq(commentLike.commentId, commentId)));
|
||||
|
||||
if (existingLike) {
|
||||
return json({ message: 'Comment already liked' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Add like and increment count
|
||||
await db.transaction(async (tx) => {
|
||||
await tx.insert(commentLike).values({ userId, commentId });
|
||||
|
||||
await tx
|
||||
.update(comment)
|
||||
.set({ likesCount: sql`${comment.likesCount} + 1` })
|
||||
.where(eq(comment.id, commentId));
|
||||
});
|
||||
|
||||
return json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Failed to like comment:', error);
|
||||
return json({ message: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
};
|
||||
|
||||
export const DELETE: RequestHandler = async ({ request, params }) => {
|
||||
const session = await auth.api.getSession({
|
||||
headers: request.headers
|
||||
});
|
||||
|
||||
if (!session?.user) {
|
||||
throw error(401, 'Not authenticated');
|
||||
}
|
||||
|
||||
const commentId = parseInt(params.id);
|
||||
const { coinSymbol } = params;
|
||||
const userId = Number(session.user.id);
|
||||
|
||||
if (isNaN(commentId)) {
|
||||
return json({ message: 'Invalid comment ID' }, { status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
// Verify the comment exists and belongs to the specified coin
|
||||
const [commentData] = await db
|
||||
.select()
|
||||
.from(comment)
|
||||
.innerJoin(coin, eq(comment.coinId, coin.id))
|
||||
.where(and(eq(comment.id, commentId), eq(coin.symbol, coinSymbol)));
|
||||
|
||||
if (!commentData) {
|
||||
return json({ message: 'Comment not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
// Check if user has liked this comment
|
||||
const [existingLike] = await db
|
||||
.select()
|
||||
.from(commentLike)
|
||||
.where(and(eq(commentLike.userId, userId), eq(commentLike.commentId, commentId)));
|
||||
|
||||
if (!existingLike) {
|
||||
return json({ message: 'Comment not liked' }, { status: 400 });
|
||||
}
|
||||
|
||||
// Remove like and decrement count
|
||||
await db.transaction(async (tx) => {
|
||||
await tx
|
||||
.delete(commentLike)
|
||||
.where(and(eq(commentLike.userId, userId), eq(commentLike.commentId, commentId)));
|
||||
|
||||
await tx
|
||||
.update(comment)
|
||||
.set({ likesCount: sql`GREATEST(0, ${comment.likesCount} - 1)` })
|
||||
.where(eq(comment.id, commentId));
|
||||
});
|
||||
|
||||
return json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Failed to unlike comment:', error);
|
||||
return json({ message: 'Internal server error' }, { status: 500 });
|
||||
}
|
||||
};
|
||||
Reference in a new issue