diff --git a/bun.lockb b/bun.lockb
index 9df74ce..3e6971a 100755
Binary files a/bun.lockb and b/bun.lockb differ
diff --git a/imagemagick.policy.xml b/imagemagick.policy.xml
new file mode 100644
index 0000000..34f5341
--- /dev/null
+++ b/imagemagick.policy.xml
@@ -0,0 +1,134 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/package.json b/package.json
index fa40232..ee2b869 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,8 @@
"@fontsource-variable/inter": "^5.0.18",
"@fontsource-variable/noto-sans-mono": "^5.0.20",
"@prisma/client": "5.16.2",
- "magickwand.js": "^1.1.0"
+ "magickwand.js": "^1.1.0",
+ "mime": "^4.0.4"
},
"trustedDependencies": [
"magickwand.js"
diff --git a/src/lib/avatars.ts b/src/lib/avatars.ts
index 6357bdb..1e62eb4 100644
--- a/src/lib/avatars.ts
+++ b/src/lib/avatars.ts
@@ -1,14 +1,19 @@
-import { readdir } from "node:fs/promises"
+import { mkdir, readdir, rm } from "node:fs/promises"
import { existsSync } from "node:fs"
-import { basename, join } from "node:path"
+import { join } from "node:path"
import { prisma } from "./clientsingleton"
+import configuration from "./configuration"
+import { Magick, MagickCore } from "magickwand.js"
+import mime from "mime"
+
+Magick.SetSecurityPolicy(await Bun.file('./imagemagick.policy.xml').text())
// todo: make customizable
export const avatarDirectory = "./.data/avatars"
export const defaultAvatarDirectory = "./static/default/"
-export const renderSizes = [ 512, 256, 128, 64, 32 ]
+export const renderSizes = [ 1024, 512, 256, 128, 64, 32 ]
-export async function getPathToAvatarForUid(uid?: string, size: string = "index") {
+export async function getPathToAvatarForUid(uid?: string, size: string = "512") {
if (uid?.includes("/"))
throw Error("UID cannot include /")
@@ -22,12 +27,68 @@ export async function getPathToAvatarForUid(uid?: string, size: string = "index"
return join(userAvatarDirectory, targetAvatar)
}
-export async function getPathToAvatarForIdentifier(identifier: string, size: string = "index") {
+export async function getPathToAvatarForIdentifier(identifier: string, size: string = "512") {
let user = await prisma.user.findFirst({
where: {
identifier
}
})
- return getPathToAvatarForUid(user?.userId || "", size)
+ return getPathToAvatarForUid(user?.userId, size)
+}
+
+export async function rerenderAvatar(bin: ArrayBuffer, squareSize?: number) {
+ let img = new Magick.Image;
+ // read file
+ await img.readAsync(new Magick.Blob(bin),"")
+ if (squareSize) {
+ // resize, but don't upscale, while filling without squishing
+ await img.scaleAsync(`${squareSize}x${squareSize}^>`)
+ }
+ // center crop
+ const size = img.size()
+ squareSize = Math.min(size.width(), size.height())
+ await img.extentAsync(`${squareSize}x${squareSize}`, MagickCore.CenterGravity)
+
+ // return avatar buffer
+ let tempBlob = new Magick.Blob()
+ await img.writeAsync(tempBlob)
+ return tempBlob.dataAsync()
+}
+
+export async function setNewAvatar(uid: string, avatar?: File) {
+ if (uid?.includes("/"))
+ throw Error("UID cannot include /")
+
+ // Delete current avatar directory
+ const userAvatarDirectory = join(avatarDirectory, uid)
+ await rm(userAvatarDirectory, { recursive: true, force: true })
+
+ if (!avatar) return {} // we don't need to set a new one
+
+ // make a new directory
+ mkdir(userAvatarDirectory, { recursive: true })
+
+ let time: Record = {}
+
+ // render all images and write to disk
+ let avatarData = await avatar.arrayBuffer()
+ let fileExtension = mime.getExtension(avatar.type)
+ for (let x of renderSizes) {
+ console.log(x)
+ try {
+ let start = Date.now()
+ let rerenderedAvatarData = await rerenderAvatar(avatarData, x)
+ await Bun.write(
+ join(userAvatarDirectory, `${x}.${fileExtension}`),
+ rerenderedAvatarData
+ )
+ time[x] = Date.now()-start
+ } catch (e) { // clear pfp and throw if error encountered
+ await rm(userAvatarDirectory, { recursive: true, force: true })
+ throw e
+ }
+ }
+
+ return time
}
\ No newline at end of file
diff --git a/src/lib/oidc.ts b/src/lib/oidc.ts
index 32c7612..8bab640 100644
--- a/src/lib/oidc.ts
+++ b/src/lib/oidc.ts
@@ -96,7 +96,7 @@ export async function getUserInfo(id: string) {
if (userInfoCache.has(tokenInfo.owner))
userInfo = userInfoCache.get(tokenInfo.owner)
else {
- let userInfoRequest = await fetchUserInfo(tokenInfo.token)
+ let userInfoRequest = await fetchUserInfo(tokenInfo.token)
if (!userInfoRequest.ok) {
// assume that token has expired.
// try fetching a new one
@@ -108,7 +108,7 @@ export async function getUserInfo(id: string) {
})
if (!token) return // refresh failed. back out
- prisma.token.update({
+ await prisma.token.update({
where: { id },
data: {
token: token.access_token,
@@ -121,6 +121,21 @@ export async function getUserInfo(id: string) {
}
userInfo = await userInfoRequest.json()
+
+ // update user
+ console.log('aaa')
+ await prisma.user.upsert({
+ where: {
+ userId: userInfo.sub,
+ },
+ update: {
+ identifier: userInfo[configuration.userinfo.identifier]
+ },
+ create: {
+ userId: userInfo.sub,
+ identifier: userInfo[configuration.userinfo.identifier]
+ }
+ })
// cache userinfo
userInfoCache.set(tokenInfo.owner, userInfo)
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index a1c0c36..548890f 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -50,7 +50,7 @@
nav > * {
display: flex; /* Flexbox fixes everything! */
}
- a {
+ a, small {
color: var(--link)
}
code, pre {
diff --git a/src/routes/set/+page.server.ts b/src/routes/set/+page.server.ts
index c750195..f01e9c2 100644
--- a/src/routes/set/+page.server.ts
+++ b/src/routes/set/+page.server.ts
@@ -1,5 +1,8 @@
-import {launchLogin} from "$lib/oidc"
+import {getRequestUser, launchLogin} from "$lib/oidc"
import configuration from "$lib/configuration.js";
+import { fail } from "@sveltejs/kit";
+import { avatarDirectory, renderSizes, setNewAvatar } from "$lib/avatars.js";
+import { join } from "path";
export async function load({ request, parent }) {
const { user } = await parent();
if (!user)
@@ -7,6 +10,31 @@ export async function load({ request, parent }) {
return {
url: request.url,
- allowedImageTypes: configuration.allowed_types
+ allowedImageTypes: configuration.allowed_types,
+ renderSizes
+ }
+}
+
+export const actions = {
+ set: async ({request, cookies}) => {
+ let user = await getRequestUser(request, cookies);
+ if (!user)
+ return fail(401, {error: "unauthenticated"})
+
+ let submission = await request.formData();
+ let newAvatar = submission.get("newAvatar")
+ if (newAvatar !== undefined && !(newAvatar instanceof File))
+ return fail(400, {success: false, error: "incorrect entry type"})
+ if (!configuration.allowed_types.includes(newAvatar.type))
+ return fail(400, {success: false, error: `allowed types does not include ${newAvatar.type}`})
+
+ let time = await setNewAvatar(user.sub, newAvatar)
+
+ return {
+ success: true,
+ message: Object.entries(time)
+ .map(([res, time]) => `${res}x${res} took ${time}ms to render`)
+ .join("\n")
+ }
}
}
\ No newline at end of file
diff --git a/src/routes/set/+page.svelte b/src/routes/set/+page.svelte
index d9808e9..845a132 100644
--- a/src/routes/set/+page.svelte
+++ b/src/routes/set/+page.svelte
@@ -2,7 +2,8 @@
import type { User } from "$lib/types";
import FilePreviewSet from "./FilePreviewSet.svelte";
- export let data: {user: User, url: string, allowedImageTypes: string[]};
+ export let data: {user: User, url: string, 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}/`
@@ -66,18 +67,30 @@
Avatar URLs...
- {#each ["", "32", "64", "128", "256", "512"] as variant}
+ {#each ["", ...data.renderSizes] as variant}
- {new URL(`/avatar/${data.user.identifier}/${variant}`, data.url)}
{/each}
-
+{#if form}
+ {#if form.success}
+
+ Avatar set successfully
+
+
+ {:else}
+ An error occurred: {form.error}
+ {/if}
+{/if}
{#key fileSrc}
diff --git a/static/default/1024.png b/static/default/1024.png
new file mode 100644
index 0000000..cee6e06
Binary files /dev/null and b/static/default/1024.png differ