From 968a49bd4645300f4b7b866f8ee055652d7bc89a Mon Sep 17 00:00:00 2001 From: split Date: Thu, 22 Aug 2024 21:31:21 -0700 Subject: [PATCH] add alt text, source this code is fucking terrible. i'll clean it up later --- package-lock.json | 4 +- package.json | 2 +- .../migration.sql | 12 ++++ .../migration.sql | 3 + .../20240823034401_add_name/migration.sql | 2 + prisma/schema.prisma | 10 +++ src/lib/avatars.ts | 55 ++++++++++++++- src/lib/configuration.ts | 68 +++++++++---------- src/routes/info/[identifier]/+page.server.ts | 15 ++++ src/routes/info/[identifier]/+page.svelte | 46 +++++++++++++ src/routes/info/[identifier]/json/+server.ts | 10 +++ .../info/[identifier]/source/+server.ts | 15 ++++ src/routes/set/+page.server.ts | 37 +++++++--- src/routes/set/+page.svelte | 55 ++++++++++++--- 14 files changed, 272 insertions(+), 62 deletions(-) create mode 100644 prisma/migrations/20240823024455_add_alt_text_and_source/migration.sql create mode 100644 prisma/migrations/20240823024907_add_alt_text_and_source/migration.sql create mode 100644 prisma/migrations/20240823034401_add_name/migration.sql create mode 100644 src/routes/info/[identifier]/+page.server.ts create mode 100644 src/routes/info/[identifier]/+page.svelte create mode 100644 src/routes/info/[identifier]/json/+server.ts create mode 100644 src/routes/info/[identifier]/source/+server.ts diff --git a/package-lock.json b/package-lock.json index 2d12190..912b439 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ava-node", - "version": "1.1.1", + "version": "1.2.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ava-node", - "version": "1.1.1", + "version": "1.2.1", "dependencies": { "@fontsource-variable/inter": "^5.0.18", "@fontsource-variable/noto-sans-mono": "^5.0.20", diff --git a/package.json b/package.json index e93908e..cb1a7e1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ava-node", - "version": "1.2.1", + "version": "1.3.0", "private": true, "scripts": { "dev": "vite dev", diff --git a/prisma/migrations/20240823024455_add_alt_text_and_source/migration.sql b/prisma/migrations/20240823024455_add_alt_text_and_source/migration.sql new file mode 100644 index 0000000..1461764 --- /dev/null +++ b/prisma/migrations/20240823024455_add_alt_text_and_source/migration.sql @@ -0,0 +1,12 @@ +-- CreateTable +CREATE TABLE "Avatar" ( + "id" TEXT NOT NULL PRIMARY KEY, + "userId" TEXT NOT NULL, + CONSTRAINT "Avatar_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("userId") ON DELETE RESTRICT ON UPDATE CASCADE +); + +-- CreateIndex +CREATE UNIQUE INDEX "Avatar_id_key" ON "Avatar"("id"); + +-- CreateIndex +CREATE UNIQUE INDEX "Avatar_userId_key" ON "Avatar"("userId"); diff --git a/prisma/migrations/20240823024907_add_alt_text_and_source/migration.sql b/prisma/migrations/20240823024907_add_alt_text_and_source/migration.sql new file mode 100644 index 0000000..cbb7646 --- /dev/null +++ b/prisma/migrations/20240823024907_add_alt_text_and_source/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "Avatar" ADD COLUMN "altText" TEXT; +ALTER TABLE "Avatar" ADD COLUMN "source" TEXT; diff --git a/prisma/migrations/20240823034401_add_name/migration.sql b/prisma/migrations/20240823034401_add_name/migration.sql new file mode 100644 index 0000000..fc3b9dd --- /dev/null +++ b/prisma/migrations/20240823034401_add_name/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "User" ADD COLUMN "name" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 6700849..27a4083 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -21,4 +21,14 @@ model Token { model User { userId String @id @unique identifier String + name String? + avatar Avatar? +} + +model Avatar { + id String @id @unique @default(uuid()) + user User @relation(fields: [userId], references: [userId]) + userId String @unique + altText String? + source String? } \ No newline at end of file diff --git a/src/lib/avatars.ts b/src/lib/avatars.ts index 9fb154c..ded4325 100644 --- a/src/lib/avatars.ts +++ b/src/lib/avatars.ts @@ -4,6 +4,7 @@ import { join } from "node:path" import { prisma } from "./clientsingleton" import configuration from "./configuration" import Sharp, { type FormatEnum } from "sharp" +import type { Avatar } from "@prisma/client" // todo: make customizable export const avatarDirectory = "./.data/avatars" @@ -104,6 +105,42 @@ export async function getPathToAvatarForIdentifier(identifier: string, size: num return getPathToAvatarForUid(user?.userId, size, fmt) } +function sanitizeAvatar(avatar: Avatar | null) { + return avatar + ? { + altText: avatar.altText || "", + source: avatar.source || "", + default: false + } + : { + altText: "Default profile picture", + source: "https://git.sucks.win/split/ava", + default: true + } +} + +export async function getMetadataForIdentifier(identifier: string) { + let avatar = await prisma.avatar.findFirst({ + where: { + user: { + identifier + } + } + }) + + return sanitizeAvatar(avatar) +} + +export async function getMetadataForUserId(userId: string) { + let avatar = await prisma.avatar.findFirst({ + where: { + userId + } + }) + + return sanitizeAvatar(avatar) +} + /** * @description Render an avatar at the specified size and format * @param bin Image to rerender @@ -126,7 +163,7 @@ export async function renderAvatar(bin: ArrayBuffer|Buffer, squareSize: number, if (format) img.toFormat(format) return { - buf: await img.toBuffer(), + img, extension: format || metadata.format, requestedFormat: format, squareSize, @@ -153,7 +190,7 @@ export async function writeAvatar(avatarDir: string, renderedAvatar: Awaited e in Sharp.format) as (keyof FormatEnum)[] - || [], - output_resolutions: - process.env.IMAGES__OUTPUT_RESOLUTIONS - ?.split(",") - .map(e => parseInt(e,10)) - || [ 1024, 512, 256, 128, 64, 32 ] - } -} -export default configuration \ No newline at end of file + oauth2: { + endpoints: { + authenticate: env.OAUTH2__AUTHENTICATE!, + logout: env.OAUTH2__LOGOUT, + token: env.OAUTH2__GET_TOKEN! + }, + client: { + id: env.OAUTH2__CLIENT_ID!, + secret: env.OAUTH2__CLIENT_SECRET!, + scopes: env.OAUTH2__SCOPES! + } + }, + userinfo: { + route: env.USERINFO__ROUTE!, + identifier: env.USERINFO__IDENTIFIER! + }, + images: { + permitted_input: (env.ALLOWED_TYPES || env.IMAGES__ALLOWED_INPUT_FORMATS)?.split(',') || [], + default_resolution: parseInt((env.IMAGES__DEFAULT_RESOLUTION || '').toString(), 10) || 512, + extra_output_types: + (env.IMAGES__EXTRA_OUTPUT_FORMATS?.split(',').filter( + (e) => e in Sharp.format + ) as (keyof FormatEnum)[]) || [], + output_resolutions: env.IMAGES__OUTPUT_RESOLUTIONS?.split(',').map((e) => parseInt(e, 10)) || [ + 1024, 512, 256, 128, 64, 32 + ] + } +}; +export default configuration; diff --git a/src/routes/info/[identifier]/+page.server.ts b/src/routes/info/[identifier]/+page.server.ts new file mode 100644 index 0000000..2ca0cf0 --- /dev/null +++ b/src/routes/info/[identifier]/+page.server.ts @@ -0,0 +1,15 @@ +import { getMetadataForIdentifier } from '$lib/avatars.js'; +import { prisma } from '$lib/clientsingleton.js'; +import { error, redirect } from '@sveltejs/kit'; + +export async function load({ params : { identifier } }) { + let userInfo = await prisma.user.findFirst({ where: { identifier } }) + if (!userInfo) + throw error(404, "User not found") + let metadata = await getMetadataForIdentifier(identifier) + return { + name: userInfo.name || identifier, + identifier, + ...metadata + } +} \ No newline at end of file diff --git a/src/routes/info/[identifier]/+page.svelte b/src/routes/info/[identifier]/+page.svelte new file mode 100644 index 0000000..2257e18 --- /dev/null +++ b/src/routes/info/[identifier]/+page.svelte @@ -0,0 +1,46 @@ + + + + +

{data.name}'s avatar

+ +
+ +
+ +
Alt text
+

+ {#if data.altText} + {data.altText} + {:else} + No alt text available + {/if} +

+
+ +
Source
+

+ {#if data.source} + {data.source} + {:else} + No source available + {/if} +

\ No newline at end of file diff --git a/src/routes/info/[identifier]/json/+server.ts b/src/routes/info/[identifier]/json/+server.ts new file mode 100644 index 0000000..03eb78b --- /dev/null +++ b/src/routes/info/[identifier]/json/+server.ts @@ -0,0 +1,10 @@ +import { getMetadataForIdentifier } from '$lib/avatars.js'; +import { error, redirect } from '@sveltejs/kit'; + +export async function GET({ params : { identifier } }) { + return new Response( + JSON.stringify( + await getMetadataForIdentifier(identifier) + ) + ) +} \ No newline at end of file diff --git a/src/routes/info/[identifier]/source/+server.ts b/src/routes/info/[identifier]/source/+server.ts new file mode 100644 index 0000000..0a0a325 --- /dev/null +++ b/src/routes/info/[identifier]/source/+server.ts @@ -0,0 +1,15 @@ +import { getMetadataForIdentifier } from '$lib/avatars.js'; +import { error, redirect } from '@sveltejs/kit'; + +export async function GET({ params : { identifier } }) { + let { source } = await getMetadataForIdentifier(identifier) + let finalUrl: URL | string = `/info/${identifier}` + + if (source) { + try { + finalUrl = new URL(source) + } catch {} + } + + throw redirect(302, finalUrl) +} \ No newline at end of file diff --git a/src/routes/set/+page.server.ts b/src/routes/set/+page.server.ts index 92f6b14..7785fec 100644 --- a/src/routes/set/+page.server.ts +++ b/src/routes/set/+page.server.ts @@ -1,15 +1,17 @@ import {getRequestUser, launchLogin} from "$lib/oidc" import configuration from "$lib/configuration.js"; import { fail } from "@sveltejs/kit"; -import { avatarDirectory, setNewAvatar } from "$lib/avatars.js"; +import { avatarDirectory, getMetadataForUserId, setNewAvatar } from "$lib/avatars.js"; import { join } from "path"; +import { prisma } from "$lib/clientsingleton"; export async function load({ request, parent, url }) { const { user } = await parent(); if (!user) - launchLogin(url.toString()) + return launchLogin(url.toString()) return { url: url.toString(), + avatar: await getMetadataForUserId(user.sub), allowedImageTypes: configuration.images.permitted_input, renderSizes: configuration.images.output_resolutions } @@ -21,16 +23,33 @@ export const actions = { if (!user) return fail(401, {error: "unauthenticated"}) - let submission = await request.formData(); - let newAvatar = undefined - if (submission.get("action") != "Clear") { - newAvatar = submission.get("newAvatar") - if (newAvatar !== undefined && !(newAvatar instanceof File)) - return fail(400, {success: false, error: "incorrect entry type"}) + let submission = Object.fromEntries((await request.formData()).entries()); + let newAvatar = submission.newAvatar + let timing: Awaited> = {} + let isUploadingNewFile = submission.action == "Clear" + + if ( + !isUploadingNewFile // if action isn't already clear + && newAvatar !== undefined // and avatar is defined + && (newAvatar instanceof File && newAvatar.size > 0) + ) { if (!configuration.images.permitted_input.includes(newAvatar.type)) return fail(400, {success: false, error: `allowed types does not include ${newAvatar.type}`}) + isUploadingNewFile = true } - let timing = await setNewAvatar(user.sub, newAvatar) + + if (isUploadingNewFile) + timing = await setNewAvatar(user.sub, newAvatar as File|null || undefined) + if (await prisma.avatar.findFirst({ where: { userId: user.sub } })) + await prisma.avatar.update({ + where: { + userId: user.sub + }, + data: { + altText: typeof submission.altText == "string" ? submission.altText : null, + source: typeof submission.source == "string" ? submission.source : null, + } + }) return { success: true, diff --git a/src/routes/set/+page.svelte b/src/routes/set/+page.svelte index 2b8acd6..a1205de 100644 --- a/src/routes/set/+page.svelte +++ b/src/routes/set/+page.svelte @@ -2,37 +2,66 @@ import type { User } from "$lib/types"; import FilePreviewSet from "./FilePreviewSet.svelte"; - export let data: {user: User, url: string, allowedImageTypes: string[], renderSizes: number[]}; + export let data: { + user: User, + url: string, + avatar: { + altText: string, + source: string, + default: boolean + } + allowedImageTypes: string[], + renderSizes: number[] + }; export let form: { success: true, message: string } | { success: false, error: string } | undefined; let files: FileList; let fileSrc = `/avatar/${data.user.identifier}/` $: if (files && files.length >= 0) { - console.log(files.length) + data.avatar.altText = "", data.avatar.source = "", data.avatar.default = false fileSrc = URL.createObjectURL(files.item(0)!) } else fileSrc = `/avatar/${data.user.identifier}/`