diff --git a/prisma/models/content.prisma b/prisma/models/content.prisma index 3ac08b1..4781ee3 100644 --- a/prisma/models/content.prisma +++ b/prisma/models/content.prisma @@ -116,11 +116,12 @@ model Screenshot { user User @relation(fields: [userId], references: [id], onDelete: Cascade) objectId String - private Boolean @default(true) + private Boolean // if other users can see createdAt DateTime @default(now()) @db.Timestamptz(0) @@index([gameId, userId]) + @@index([userId]) } model Company { diff --git a/server/api/v1/screenshots/[id]/index.delete.ts b/server/api/v1/screenshots/[id]/index.delete.ts new file mode 100644 index 0000000..360dbf4 --- /dev/null +++ b/server/api/v1/screenshots/[id]/index.delete.ts @@ -0,0 +1,17 @@ +// get a specific screenshot +import aclManager from "~/server/internal/acls"; +import screenshotManager from "~/server/internal/screenshots"; + +export default defineEventHandler(async (h3) => { + const userId = await aclManager.getUserIdACL(h3, ["screenshots:delete"]); + if (!userId) throw createError({ statusCode: 403 }); + + const screenshotId = getRouterParam(h3, "id"); + if (!screenshotId) + throw createError({ + statusCode: 400, + statusMessage: "Missing screenshot ID", + }); + + return await screenshotManager.delete(screenshotId); +}); diff --git a/server/api/v1/screenshots/[id]/index.get.ts b/server/api/v1/screenshots/[id]/index.get.ts new file mode 100644 index 0000000..da84f6a --- /dev/null +++ b/server/api/v1/screenshots/[id]/index.get.ts @@ -0,0 +1,17 @@ +// get a specific screenshot +import aclManager from "~/server/internal/acls"; +import screenshotManager from "~/server/internal/screenshots"; + +export default defineEventHandler(async (h3) => { + const userId = await aclManager.getUserIdACL(h3, ["screenshots:read"]); + if (!userId) throw createError({ statusCode: 403 }); + + const screenshotId = getRouterParam(h3, "id"); + if (!screenshotId) + throw createError({ + statusCode: 400, + statusMessage: "Missing screenshot ID", + }); + + return await screenshotManager.get(screenshotId); +}); diff --git a/server/api/v1/screenshots/game/[id]/index.get.ts b/server/api/v1/screenshots/game/[id]/index.get.ts new file mode 100644 index 0000000..71addae --- /dev/null +++ b/server/api/v1/screenshots/game/[id]/index.get.ts @@ -0,0 +1,18 @@ +// get all user screenshots by game +import aclManager from "~/server/internal/acls"; +import screenshotManager from "~/server/internal/screenshots"; + +export default defineEventHandler(async (h3) => { + const userId = await aclManager.getUserIdACL(h3, ["screenshots:read"]); + if (!userId) throw createError({ statusCode: 403 }); + + const gameId = getRouterParam(h3, "id"); + if (!gameId) + throw createError({ + statusCode: 400, + statusMessage: "Missing game ID", + }); + + const results = await screenshotManager.getUserAllByGame(userId, gameId); + return results; +}); diff --git a/server/api/v1/screenshots/game/[id]/index.post.ts b/server/api/v1/screenshots/game/[id]/index.post.ts new file mode 100644 index 0000000..c06b815 --- /dev/null +++ b/server/api/v1/screenshots/game/[id]/index.post.ts @@ -0,0 +1,27 @@ +// create new screenshot +import aclManager from "~/server/internal/acls"; +import prisma from "~/server/internal/db/database"; +import screenshotManager from "~/server/internal/screenshots"; + +// TODO: make defineClientEventHandler instead? +// only clients will be upload screenshots yea?? +export default defineEventHandler(async (h3) => { + const userId = await aclManager.getUserIdACL(h3, ["screenshots:new"]); + if (!userId) throw createError({ statusCode: 403 }); + + const gameId = getRouterParam(h3, "id"); + if (!gameId) + throw createError({ + statusCode: 400, + statusMessage: "Missing game ID", + }); + + const game = await prisma.game.findUnique({ + where: { id: gameId }, + select: { id: true }, + }); + if (!game) + throw createError({ statusCode: 400, statusMessage: "Invalid game ID" }); + + await screenshotManager.upload(userId, gameId, h3.node.req); +}); diff --git a/server/api/v1/screenshots/index.get.ts b/server/api/v1/screenshots/index.get.ts new file mode 100644 index 0000000..97b9f93 --- /dev/null +++ b/server/api/v1/screenshots/index.get.ts @@ -0,0 +1,11 @@ +// get all user screenshots +import aclManager from "~/server/internal/acls"; +import screenshotManager from "~/server/internal/screenshots"; + +export default defineEventHandler(async (h3) => { + const userId = await aclManager.getUserIdACL(h3, ["screenshots:read"]); + if (!userId) throw createError({ statusCode: 403 }); + + const results = await screenshotManager.getUserAll(userId); + return results; +}); diff --git a/server/internal/acls/descriptions.ts b/server/internal/acls/descriptions.ts index d5ac10a..d9f21a6 100644 --- a/server/internal/acls/descriptions.ts +++ b/server/internal/acls/descriptions.ts @@ -22,6 +22,10 @@ export const userACLDescriptions: ObjectFromList = { "notifications:listen": "Connect to a websocket to recieve notifications.", "notifications:delete": "Delete this account's notifications.", + "screenshots:new": "Create screenshots for this account", + "screenshots:read": "Read all screenshots for this account", + "screenshots:delete": "Delete a screenshot for this account", + "collections:new": "Create collections for this account.", "collections:read": "Fetch all collections (including library).", "collections:delete": "Delete a collection for this account.", diff --git a/server/internal/acls/index.ts b/server/internal/acls/index.ts index c2570c3..96c3708 100644 --- a/server/internal/acls/index.ts +++ b/server/internal/acls/index.ts @@ -17,6 +17,10 @@ export const userACLs = [ "notifications:listen", "notifications:delete", + "screenshots:new", + "screenshots:read", + "screenshots:delete", + "collections:new", "collections:read", "collections:delete", @@ -83,6 +87,12 @@ class ACLManager { return token; } + /** + * Get userId and require one of the specified acls + * @param request + * @param acls + * @returns + */ async getUserIdACL(request: MinimumRequestObject | undefined, acls: UserACL) { if (!request) throw new Error("Native web requests not available - weird deployment?"); diff --git a/server/internal/screenshots/index.ts b/server/internal/screenshots/index.ts index e8b4911..295e26d 100644 --- a/server/internal/screenshots/index.ts +++ b/server/internal/screenshots/index.ts @@ -13,7 +13,16 @@ class ScreenshotManager { }); } - async getAllByGame(gameId: string, userId: string) { + async getUserAll(userId: string) { + const results = await prisma.screenshot.findMany({ + where: { + userId, + }, + }); + return results; + } + + async getUserAllByGame(userId: string, gameId: string) { const results = await prisma.screenshot.findMany({ where: { gameId, @@ -31,9 +40,16 @@ class ScreenshotManager { }); } - async upload(gameId: string, userId: string, inputStream: IncomingMessage) { + async upload(userId: string, gameId: string, inputStream: IncomingMessage) { const objectId = randomUUID(); - const saveStream = await objectHandler.createWithStream(objectId, {}, []); + const saveStream = await objectHandler.createWithStream( + objectId, + { + // TODO: set createAt to the time screenshot was taken + createdAt: new Date().toISOString(), + }, + [`${userId}:read`, `${userId}:delete`], + ); if (!saveStream) throw createError({ statusCode: 500, @@ -43,12 +59,12 @@ class ScreenshotManager { // pipe into object store await stream.pipeline(inputStream, saveStream); - // TODO: set createAt to the time screenshot was taken await prisma.screenshot.create({ data: { gameId, userId, objectId, + private: true, }, }); }