diff --git a/database/entities/Attachment.ts b/database/entities/Attachment.ts index 6d0ce1a2..7b8cc853 100644 --- a/database/entities/Attachment.ts +++ b/database/entities/Attachment.ts @@ -27,9 +27,28 @@ export const attachmentToAPI = ( width: attachment.width || undefined, height: attachment.height || undefined, fps: attachment.fps || undefined, - size: attachment.size?.toString() || undefined, + size: + attachment.width && attachment.height + ? `${attachment.width}x${attachment.height}` + : undefined, duration: attachment.duration || undefined, length: attachment.size?.toString() || undefined, + aspect: + attachment.width && attachment.height + ? attachment.width / attachment.height + : undefined, + original: { + width: attachment.width || undefined, + height: attachment.height || undefined, + size: + attachment.width && attachment.height + ? `${attachment.width}x${attachment.height}` + : undefined, + aspect: + attachment.width && attachment.height + ? attachment.width / attachment.height + : undefined, + }, // Idk whether size or length is the right value }, description: attachment.description, diff --git a/server/api/api/v1/media/[id]/index.ts b/server/api/api/v1/media/[id]/index.ts new file mode 100644 index 00000000..386974c9 --- /dev/null +++ b/server/api/api/v1/media/[id]/index.ts @@ -0,0 +1,104 @@ +import { applyConfig } from "@api"; +import { errorResponse, jsonResponse } from "@response"; +import { client } from "~database/datasource"; +import { getFromRequest } from "~database/entities/User"; +import { APIRouteMeta } from "~types/api"; +import { uploadFile } from "~classes/media"; +import { getConfig } from "@config"; +import { attachmentToAPI, getUrl } from "~database/entities/Attachment"; +import { MatchedRoute } from "bun"; +import { parseRequest } from "@request"; + +export const meta: APIRouteMeta = applyConfig({ + allowedMethods: ["GET", "PUT"], + ratelimits: { + max: 10, + duration: 60, + }, + route: "/api/v1/media/:id", + auth: { + required: true, + oauthPermissions: ["write:media"], + }, +}); + +/** + * Get media information + */ +export default async ( + req: Request, + matchedRoute: MatchedRoute +): Promise => { + const { user } = await getFromRequest(req); + + if (!user) { + return errorResponse("Unauthorized", 401); + } + + const id = matchedRoute.params.id; + + const attachment = await client.attachment.findUnique({ + where: { + id, + }, + }); + + if (!attachment) { + return errorResponse("Media not found", 404); + } + + const config = getConfig(); + + switch (req.method) { + case "GET": { + if (attachment.url) { + return jsonResponse(attachmentToAPI(attachment)); + } else { + return new Response(null, { + status: 206, + }); + } + } + case "PUT": { + const { description, thumbnail } = await parseRequest<{ + thumbnail?: File; + description?: string; + focus?: string; + }>(req); + + let thumbnailUrl = attachment.thumbnail_url; + + if (thumbnail) { + const hash = await uploadFile( + thumbnail as unknown as File, + config + ); + + thumbnailUrl = hash ? getUrl(hash, config) : ""; + } + + const descriptionText = description || attachment.description; + + if ( + descriptionText !== attachment.description || + thumbnailUrl !== attachment.thumbnail_url + ) { + const newAttachment = await client.attachment.update({ + where: { + id, + }, + data: { + description: descriptionText, + thumbnail_url: thumbnailUrl, + }, + }); + + return jsonResponse(attachmentToAPI(newAttachment)); + } + + return jsonResponse(attachmentToAPI(attachment)); + } + } + + return errorResponse("Method not allowed", 405); +}; diff --git a/server/api/api/v2/media/index.ts b/server/api/api/v2/media/index.ts index 0f8c9026..ba26cdfd 100644 --- a/server/api/api/v2/media/index.ts +++ b/server/api/api/v2/media/index.ts @@ -18,11 +18,12 @@ export const meta: APIRouteMeta = applyConfig({ route: "/api/v2/media", auth: { required: true, + oauthPermissions: ["write:media"], }, }); /** - * Fetch a user + * Upload new media */ export default async (req: Request): Promise => { const { user } = await getFromRequest(req); @@ -96,8 +97,15 @@ export default async (req: Request): Promise => { // TODO: Add job to process videos and other media - return jsonResponse({ - ...attachmentToAPI(newAttachment), - url: undefined, - }); + if (isImage) { + return jsonResponse(attachmentToAPI(newAttachment)); + } else { + return jsonResponse( + { + ...attachmentToAPI(newAttachment), + url: null, + }, + 202 + ); + } }; diff --git a/tests/test-image.webp b/tests/test-image.webp new file mode 100755 index 00000000..661d0eba Binary files /dev/null and b/tests/test-image.webp differ diff --git a/types/api.ts b/types/api.ts index 16d812cc..215762b7 100644 --- a/types/api.ts +++ b/types/api.ts @@ -8,5 +8,6 @@ export interface APIRouteMeta { auth: { required: boolean; requiredOnMethods?: ("GET" | "POST" | "PUT" | "DELETE" | "PATCH")[]; + oauthPermissions?: string[]; }; }