diff --git a/src/v2/routes/collection/add-contributor.ts b/src/v2/routes/collection/add-contributor.ts index e69de29..4e5b594 100644 --- a/src/v2/routes/collection/add-contributor.ts +++ b/src/v2/routes/collection/add-contributor.ts @@ -0,0 +1,122 @@ +import { AppHandler } from "../handler" +import { getConnection } from "@/v2/db/turso" +import { userCollection, userCollectionCollaborators } from "@/v2/db/schema" +import { and, eq } from "drizzle-orm" +import { createRoute } from "@hono/zod-openapi" +import { GenericResponses } from "@/v2/lib/response-schemas" +import { z } from "@hono/zod-openapi" +import { AuthSessionManager } from "@/v2/lib/managers/auth/user-session-manager" + +const paramsSchema = z.object({ + id: z.string().openapi({ + param: { + name: "id", + in: "path", + description: "The ID of the collection to add contributor to", + required: true, + }, + }), + contributorId: z.string().openapi({ + param: { + name: "contributorId", + in: "path", + description: "The user ID of the contribitor to add.", + required: true, + }, + }), +}) + +const responseSchema = z.object({ + success: z.literal(true), +}) + +const openRoute = createRoute({ + path: "/{id}/contributor/{contributorId}/add", + method: "post", + summary: "Add contributor to collection", + description: + "Add a contributor to a collection, you must be the collection owner to add a contributor.", + tags: ["Collection"], + request: { + params: paramsSchema, + }, + responses: { + 200: { + description: + "Returns true if the contributor was added to the collection.", + content: { + "application/json": { + schema: responseSchema, + }, + }, + }, + ...GenericResponses, + }, +}) + +export const AddContributorToCollectionRoute = (handler: AppHandler) => { + handler.openapi(openRoute, async (ctx) => { + const { id, contributorId } = ctx.req.valid("param") + const { drizzle } = await getConnection(ctx.env) + + const authSessionManager = new AuthSessionManager(ctx) + const { user } = await authSessionManager.validateSession() + + const [existingCollection] = await drizzle + .select({ + id: userCollection.id, + userId: userCollection.userId, + }) + .from(userCollection) + .where(eq(userCollection.id, id)) + + if (!existingCollection || existingCollection.userId !== user.id) { + return ctx.json( + { + success: false, + message: "Unauthorized", + }, + 401 + ) + } + + const [contributor] = await drizzle + .select({ + collectionId: userCollectionCollaborators.collectionId, + collaboratorId: userCollectionCollaborators.collaboratorId, + }) + .from(userCollectionCollaborators) + .where( + and( + eq(userCollectionCollaborators.collectionId, id), + eq( + userCollectionCollaborators.collaboratorId, + contributorId + ) + ) + ) + .limit(1) + + if (contributor) { + return ctx.json( + { + success: false, + message: "Contributor already exists", + }, + 400 + ) + } + + await drizzle.insert(userCollectionCollaborators).values({ + collectionId: id, + collaboratorId: contributorId, + }) + + return ctx.json( + { + success: true, + }, + 200 + ) + }) +} diff --git a/src/v2/routes/collection/handler.ts b/src/v2/routes/collection/handler.ts index 7855c02..e8764eb 100644 --- a/src/v2/routes/collection/handler.ts +++ b/src/v2/routes/collection/handler.ts @@ -8,6 +8,9 @@ import { UnlikeCollectionByIDRoute } from "./unlike-collection" import { LikeCollectionByIdRoute } from "./like-collection" import { AddAssetToCollectionRoute } from "./add-asset" import { RemoveAssetFromCollectionRoute } from "./remove-asset" +import { AddContributorToCollectionRoute } from "./add-contributor" +import { RemoveContributorFromCollectionRoute } from "./remove-contributor" +import { UpdateContributorStatusRoute } from "./update-contributor-status" const handler = new OpenAPIHono<{ Bindings: Bindings; Variables: Variables }>() @@ -20,6 +23,10 @@ DeleteCollectionRoute(handler) AddAssetToCollectionRoute(handler) RemoveAssetFromCollectionRoute(handler) +UpdateContributorStatusRoute(handler) + +AddContributorToCollectionRoute(handler) +RemoveContributorFromCollectionRoute(handler) LikeCollectionByIdRoute(handler) UnlikeCollectionByIDRoute(handler) diff --git a/src/v2/routes/collection/remove-contributor.ts b/src/v2/routes/collection/remove-contributor.ts index e69de29..c01f3d5 100644 --- a/src/v2/routes/collection/remove-contributor.ts +++ b/src/v2/routes/collection/remove-contributor.ts @@ -0,0 +1,129 @@ +import { AppHandler } from "../handler" +import { getConnection } from "@/v2/db/turso" +import { userCollection, userCollectionCollaborators } from "@/v2/db/schema" +import { and, eq } from "drizzle-orm" +import { createRoute } from "@hono/zod-openapi" +import { GenericResponses } from "@/v2/lib/response-schemas" +import { z } from "@hono/zod-openapi" +import { AuthSessionManager } from "@/v2/lib/managers/auth/user-session-manager" + +const paramsSchema = z.object({ + id: z.string().openapi({ + param: { + name: "id", + in: "path", + description: "The ID of the collection to remove contributor from", + required: true, + }, + }), + contributorId: z.string().openapi({ + param: { + name: "contributorId", + in: "path", + description: "The user ID of the contribitor to remove.", + required: true, + }, + }), +}) + +const responseSchema = z.object({ + success: z.literal(true), +}) + +const openRoute = createRoute({ + path: "/{id}/contributor/{contributorId}/remove", + method: "post", + summary: "Remove collection contributor", + description: + "Remove a contributor from a collection, you must be the collection owner to remove a contributor.", + tags: ["Collection"], + request: { + params: paramsSchema, + }, + responses: { + 200: { + description: + "Returns true if the contributor was removed from the collection.", + content: { + "application/json": { + schema: responseSchema, + }, + }, + }, + ...GenericResponses, + }, +}) + +export const RemoveContributorFromCollectionRoute = (handler: AppHandler) => { + handler.openapi(openRoute, async (ctx) => { + const { id, contributorId } = ctx.req.valid("param") + const { drizzle } = await getConnection(ctx.env) + + const authSessionManager = new AuthSessionManager(ctx) + const { user } = await authSessionManager.validateSession() + + const [existingCollection] = await drizzle + .select({ + id: userCollection.id, + userId: userCollection.userId, + }) + .from(userCollection) + .where(eq(userCollection.id, id)) + + if (!existingCollection || existingCollection.userId !== user.id) { + return ctx.json( + { + success: false, + message: "Unauthorized", + }, + 401 + ) + } + + const [contributor] = await drizzle + .select({ + collectionId: userCollectionCollaborators.collectionId, + collaboratorId: userCollectionCollaborators.collaboratorId, + }) + .from(userCollectionCollaborators) + .where( + and( + eq(userCollectionCollaborators.collectionId, id), + eq( + userCollectionCollaborators.collaboratorId, + contributorId + ) + ) + ) + .limit(1) + + if (!contributor) { + return ctx.json( + { + success: false, + message: "Contributor not found in collection", + }, + 400 + ) + } + + await drizzle + .delete(userCollectionCollaborators) + .where( + and( + eq(userCollectionCollaborators.collectionId, id), + eq( + userCollectionCollaborators.collaboratorId, + contributorId + ) + ) + ) + + return ctx.json( + { + success: true, + }, + 200 + ) + }) +} diff --git a/src/v2/routes/collection/update-contributor-status.ts b/src/v2/routes/collection/update-contributor-status.ts index e69de29..1b97117 100644 --- a/src/v2/routes/collection/update-contributor-status.ts +++ b/src/v2/routes/collection/update-contributor-status.ts @@ -0,0 +1,156 @@ +import { AppHandler } from "../handler" +import { getConnection } from "@/v2/db/turso" +import { userCollection, userCollectionCollaborators } from "@/v2/db/schema" +import { and, eq } from "drizzle-orm" +import { createRoute } from "@hono/zod-openapi" +import { GenericResponses } from "@/v2/lib/response-schemas" +import { z } from "@hono/zod-openapi" +import type { CollaboratorsRoles } from "@/v2/db/schema" +import { AuthSessionManager } from "@/v2/lib/managers/auth/user-session-manager" + +const paramsSchema = z.object({ + id: z.string().openapi({ + param: { + name: "id", + in: "path", + description: "The ID of the collection to add contributor to", + required: true, + }, + }), + contributorId: z.string().openapi({ + param: { + name: "contributorId", + in: "path", + description: "The user ID of the contribitor to add.", + required: true, + }, + }), +}) + +const bodySchema = z.object({ + role: z.string().optional().openapi({ + description: "The role of the contributor.", + }), +}) + +const responseSchema = z.object({ + success: z.literal(true), +}) + +const openRoute = createRoute({ + path: "/{id}/contributor/{contributorId}/update", + method: "patch", + summary: "Modify collection contributor", + description: + "Modify the role of a contributor in a collection, you must be the collection owner to modify a contributor.", + tags: ["Collection"], + request: { + params: paramsSchema, + body: { + content: { + "application/json": { + schema: bodySchema, + }, + }, + }, + }, + responses: { + 200: { + description: + "Returns true if the contributor was modified in the collection.", + content: { + "application/json": { + schema: responseSchema, + }, + }, + }, + ...GenericResponses, + }, +}) + +export const UpdateContributorStatusRoute = (handler: AppHandler) => { + handler.openapi(openRoute, async (ctx) => { + const { id, contributorId } = ctx.req.valid("param") + const { role } = ctx.req.valid("json") + + if (role && !["viewer", "collaborator", "editor"].includes(role)) { + return ctx.json( + { + success: false, + message: "Invalid role", + }, + 400 + ) + } + + const { drizzle } = await getConnection(ctx.env) + + const authSessionManager = new AuthSessionManager(ctx) + const { user } = await authSessionManager.validateSession() + + const [existingCollection] = await drizzle + .select({ + id: userCollection.id, + userId: userCollection.userId, + }) + .from(userCollection) + .where(eq(userCollection.id, id)) + + if (!existingCollection || existingCollection.userId !== user.id) { + return ctx.json( + { + success: false, + message: "Unauthorized", + }, + 401 + ) + } + + const [contributor] = await drizzle + .select({ + collectionId: userCollectionCollaborators.collectionId, + collaboratorId: userCollectionCollaborators.collaboratorId, + }) + .from(userCollectionCollaborators) + .where( + and( + eq(userCollectionCollaborators.collectionId, id), + eq( + userCollectionCollaborators.collaboratorId, + contributorId + ) + ) + ) + .limit(1) + + if (!contributor) { + return ctx.json( + { + success: false, + message: "Contributor not found", + }, + 400 + ) + } + + await drizzle + .update(userCollectionCollaborators) + .set({ role: role as CollaboratorsRoles }) + .where( + and( + eq(userCollectionCollaborators.collectionId, id), + eq( + userCollectionCollaborators.collaboratorId, + contributorId + ) + ) + ) + + return ctx.json( + { + success: true, + }, + 200 + ) + }) +}