diff --git a/package-lock.json b/package-lock.json index 8823864..d1af0fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2562,28 +2562,6 @@ "license": "Apache-2.0", "peer": true }, - "node_modules/@near-js/providers/node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "node_modules/@near-js/signers": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/@near-js/signers/-/signers-0.2.2.tgz", @@ -8959,86 +8937,6 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, - "node_modules/bare-addon-resolve": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/bare-addon-resolve/-/bare-addon-resolve-1.9.4.tgz", - "integrity": "sha512-unn6Vy/Yke6F99vg/7tcrvM2KUvIhTNniaSqDbam4AWkd4NhvDVSrQiRYVlNzUV2P7SPobkCK7JFVxrJk9btCg==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "bare-module-resolve": "^1.10.0", - "bare-semver": "^1.0.0" - }, - "peerDependencies": { - "bare-url": "*" - }, - "peerDependenciesMeta": { - "bare-url": { - "optional": true - } - } - }, - "node_modules/bare-module-resolve": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/bare-module-resolve/-/bare-module-resolve-1.11.1.tgz", - "integrity": "sha512-DCxeT9i8sTs3vUMA3w321OX/oXtNEu5EjObQOnTmCdNp5RXHBAvAaBDHvAi9ta0q/948QPz+co6SsGi6aQMYRg==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "bare-semver": "^1.0.0" - }, - "peerDependencies": { - "bare-url": "*" - }, - "peerDependenciesMeta": { - "bare-url": { - "optional": true - } - } - }, - "node_modules/bare-os": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz", - "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "engines": { - "bare": ">=1.14.0" - } - }, - "node_modules/bare-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", - "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "bare-os": "^3.0.1" - } - }, - "node_modules/bare-semver": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bare-semver/-/bare-semver-1.0.1.tgz", - "integrity": "sha512-UtggzHLiTrmFOC/ogQ+Hy7VfoKoIwrP1UFcYtTxoCUdLtsIErT8+SWtOC2DH/snT9h+xDrcBEPcwKei1mzemgg==", - "license": "Apache-2.0", - "optional": true, - "peer": true - }, - "node_modules/bare-url": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.1.6.tgz", - "integrity": "sha512-FgjDeR+/yDH34By4I0qB5NxAoWv7dOTYcOXwn73kr+c93HyC2lU6tnjifqUe33LKMJcDyCYPQjEAqgOQiXkE2Q==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "bare-path": "^3.0.0" - } - }, "node_modules/base-x": { "version": "3.0.11", "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", @@ -12460,18 +12358,6 @@ "node": ">= 4" } }, - "node_modules/immer": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", - "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", - "license": "MIT", - "optional": true, - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -18028,21 +17914,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/require-addon": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/require-addon/-/require-addon-1.1.0.tgz", - "integrity": "sha512-KbXAD5q2+v1GJnkzd8zzbOxchTkStSyJZ9QwoCq3QwEXAaIlG3wDYRZGzVD357jmwaGY7hr5VaoEAL0BkF0Kvg==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "bare-addon-resolve": "^1.3.0", - "bare-url": "^2.1.0" - }, - "engines": { - "bare": ">=1.10.0" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -18817,17 +18688,6 @@ "node": ">= 14" } }, - "node_modules/sodium-native": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/sodium-native/-/sodium-native-4.3.3.tgz", - "integrity": "sha512-OnxSlN3uyY8D0EsLHpmm2HOFmKddQVvEMmsakCrXUzSd8kjjbzL413t4ZNF3n0UxSwNgwTyUvkmZHTfuCeiYSw==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "require-addon": "^1.1.0" - } - }, "node_modules/sonic-boom": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-2.8.0.tgz", diff --git a/src/app/api/(comment)/comment/create/route.ts b/src/app/api/(comment)/comment/create/route.ts new file mode 100644 index 0000000..df62a92 --- /dev/null +++ b/src/app/api/(comment)/comment/create/route.ts @@ -0,0 +1,87 @@ +/** + * POST /api/comment/create + * + * Body: { message: string, user_id: string, payout_id: string } + * Returns: { success: boolean, comment: Comment, message: string } + */ +import { commentCreateSchema } from "@/components/modules/comment/schema/comment.schema"; +import { handleDatabaseError, prisma } from "@/lib/prisma"; +import { NextResponse } from "next/server"; + +export async function POST(request: Request) { + try { + const body = await request.json(); + const parsed = commentCreateSchema.safeParse(body); + + if (!parsed.success) { + return NextResponse.json( + { error: "Invalid data", details: parsed.error.errors }, + { status: 400 }, + ); + } + + const { message, user_id, payout_id } = parsed.data; + + // Find the user in the database + const user = await prisma.user.findUnique({ + where: { user_id }, + select: { user_id: true, is_active: true }, + }); + + if (!user) { + return NextResponse.json({ error: "User not found" }, { status: 404 }); + } + + if (!user.is_active) { + return NextResponse.json( + { error: "User account is not active" }, + { status: 403 }, + ); + } + + // Find the payout in the database + const payout = await prisma.payout.findUnique({ + where: { payout_id }, + select: { + payout_id: true, + status: true, + created_by: true, + }, + }); + + if (!payout) { + return NextResponse.json({ error: "Payout not found" }, { status: 404 }); + } + + // Create the comment + const comment = await prisma.comment.create({ + data: { + message, + user_id, + payout_id, + }, + include: { + user: { + select: { + user_id: true, + username: true, + profile_url: true, + bio: true, + }, + }, + }, + }); + + return NextResponse.json( + { + success: true, + comment, + message: "Comment created successfully", + }, + { status: 201 }, + ); + } catch (error) { + const { message, status } = handleDatabaseError(error); + return NextResponse.json({ error: message }, { status }); + } +} diff --git a/src/app/api/(comment)/comment/delete/[id]/route.ts b/src/app/api/(comment)/comment/delete/[id]/route.ts new file mode 100644 index 0000000..ac42aff --- /dev/null +++ b/src/app/api/(comment)/comment/delete/[id]/route.ts @@ -0,0 +1,72 @@ +/** + * DELETE /api/comment/delete/[id] + * Deletes an existing comment (only author can edit) + * + * Params: { id: string } + * Body: { user_id: string } + * Returns: { success: boolean, message: string } + */ +import { handleDatabaseError, prisma } from "@/lib/prisma"; +import { NextResponse } from "next/server"; + +export async function DELETE( + request: Request, + { params }: { params: Promise<{ id: string }> }, +) { + try { + const { id } = await params; + const body = await request.json(); + + if (!id || id === "undefined" || id === "null") { + return NextResponse.json( + { error: "Invalid comment ID parameter" }, + { status: 400 }, + ); + } + + // Get user_id from request body + const user_id: string = body.user_id; + + if (!user_id) { + return NextResponse.json( + { error: "User ID is required" }, + { status: 400 }, + ); + } + + // Find the comment in the database + const existingComment = await prisma.comment.findUnique({ + where: { comment_id: id }, + select: { + comment_id: true, + user_id: true, + payout_id: true, + }, + }); + + if (!existingComment) { + return NextResponse.json({ error: "Comment not found" }, { status: 404 }); + } + + // Verify that the user owns this comment + if (existingComment.user_id !== user_id) { + return NextResponse.json( + { error: "You can only delete your own comments" }, + { status: 403 }, + ); + } + + // Delete the comment + await prisma.comment.delete({ + where: { comment_id: id }, + }); + + return NextResponse.json({ + success: true, + message: "Comment deleted successfully", + }); + } catch (error) { + const { message, status } = handleDatabaseError(error); + return NextResponse.json({ error: message }, { status }); + } +} diff --git a/src/app/api/(comment)/comment/find-by-payout/[payout_id]/route.ts b/src/app/api/(comment)/comment/find-by-payout/[payout_id]/route.ts new file mode 100644 index 0000000..237c3a2 --- /dev/null +++ b/src/app/api/(comment)/comment/find-by-payout/[payout_id]/route.ts @@ -0,0 +1,59 @@ +/** + * GET /api/comment/find-by-payout/[payout_id] + * Retrieves comments from a specific payout + * + * Params: { payout_id: string } + * Returns: { success: boolean, comments: Comment[], count: number } + */ +import { handleDatabaseError, prisma } from "@/lib/prisma"; +import { NextResponse } from "next/server"; + +export async function GET({ + params, +}: { params: Promise<{ payout_id: string }> }) { + try { + const { payout_id } = await params; + + if (!payout_id || payout_id === "undefined" || payout_id === "null") { + return NextResponse.json( + { error: "Invalid payout_id parameter" }, + { status: 400 }, + ); + } + + // Find the payout in the database + const payout = await prisma.payout.findUnique({ + where: { payout_id }, + select: { payout_id: true }, + }); + + if (!payout) { + return NextResponse.json({ error: "Payout not found" }, { status: 404 }); + } + + // Get comments for the payout, ordered chronologically + const comments = await prisma.comment.findMany({ + where: { payout_id }, + include: { + user: { + select: { + user_id: true, + username: true, + profile_url: true, + bio: true, + }, + }, + }, + orderBy: { created_at: "asc" }, + }); + + return NextResponse.json({ + success: true, + comments, + count: comments.length, + }); + } catch (error) { + const { message, status } = handleDatabaseError(error); + return NextResponse.json({ error: message }, { status }); + } +} diff --git a/src/app/api/(comment)/comment/update/[id]/route.ts b/src/app/api/(comment)/comment/update/[id]/route.ts new file mode 100644 index 0000000..a530ce0 --- /dev/null +++ b/src/app/api/(comment)/comment/update/[id]/route.ts @@ -0,0 +1,87 @@ +/** + * PATCH /api/comment/update/[id] + * Update an existing comment (only author can edit) + * + * Params: { id: string } + * Body: { message: string, user_id: string } + * Returns: { success: boolean, comment: Comment, message: string } + */ +import { commentUpdateSchema } from "@/components/modules/comment/schema/comment.schema"; +import { handleDatabaseError, prisma } from "@/lib/prisma"; +import { NextResponse } from "next/server"; +import { cookies } from "next/headers"; +import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs"; + +export async function PATCH( + request: Request, + { params }: { params: Promise<{ id: string }> }, +) { + try { + const { id } = await params; + const body = await request.json(); + const parsed = commentUpdateSchema.safeParse(body); + + if (!parsed.success) { + return NextResponse.json( + { error: "Invalid data", details: parsed.error.errors }, + { status: 400 }, + ); + } + + const { message } = parsed.data; + + // Get authenticated user from server-side session + const supabase = createRouteHandlerClient({ cookies }); + const { + data: { user }, + } = await supabase.auth.getUser(); + + if (!user) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); + } + + // Find the comment and verify ownership + const existingComment = await prisma.comment.findUnique({ + where: { comment_id: id }, + select: { + comment_id: true, + user_id: true, + payout_id: true, + }, + }); + + if (!existingComment) { + return NextResponse.json({ error: "Comment not found" }, { status: 404 }); + } + + // Verify that the user owns this comment + if (existingComment.user_id !== user.id) { + return NextResponse.json({ error: "Forbidden" }, { status: 403 }); + } + + // Update the comment + const updatedComment = await prisma.comment.update({ + where: { comment_id: id }, + data: { message }, + include: { + user: { + select: { + user_id: true, + username: true, + profile_url: true, + bio: true, + }, + }, + }, + }); + + return NextResponse.json({ + success: true, + comment: updatedComment, + message: "Comment updated successfully", + }); + } catch (error) { + const { message, status } = handleDatabaseError(error); + return NextResponse.json({ error: message }, { status }); + } +}