This commit is contained in:
Kirill
2026-05-25 21:14:19 +05:00
parent af582a813f
commit 09c5e0cd50
8 changed files with 112 additions and 160 deletions
+10 -92
View File
@@ -191,6 +191,7 @@
"resolved": "https://registry.npmjs.org/@dicebear/core/-/core-9.4.2.tgz", "resolved": "https://registry.npmjs.org/@dicebear/core/-/core-9.4.2.tgz",
"integrity": "sha512-MF0042+Z3s8PGZKZLySfhft28bUa3B1iq0e5NSjCvY8gfMi5aIH/iRJGRJa1N9Jz1BNkxYb4yvJ/N9KO8Z6Y+w==", "integrity": "sha512-MF0042+Z3s8PGZKZLySfhft28bUa3B1iq0e5NSjCvY8gfMi5aIH/iRJGRJa1N9Jz1BNkxYb4yvJ/N9KO8Z6Y+w==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/json-schema": "^7.0.15" "@types/json-schema": "^7.0.15"
}, },
@@ -462,29 +463,6 @@
"@dicebear/core": "^9.0.0" "@dicebear/core": "^9.0.0"
} }
}, },
"node_modules/@emnapi/core": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.2.1",
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/runtime": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@emnapi/wasi-threads": { "node_modules/@emnapi/wasi-threads": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
@@ -1637,9 +1615,6 @@
"arm" "arm"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1654,9 +1629,6 @@
"arm" "arm"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1671,9 +1643,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1688,9 +1657,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1705,9 +1671,6 @@
"loong64" "loong64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1722,9 +1685,6 @@
"loong64" "loong64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1739,9 +1699,6 @@
"ppc64" "ppc64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1756,9 +1713,6 @@
"ppc64" "ppc64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1773,9 +1727,6 @@
"riscv64" "riscv64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1790,9 +1741,6 @@
"riscv64" "riscv64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1807,9 +1755,6 @@
"s390x" "s390x"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1824,9 +1769,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -1841,9 +1783,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2103,9 +2042,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2120,9 +2056,6 @@
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2137,9 +2070,6 @@
"loong64" "loong64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2154,9 +2084,6 @@
"loong64" "loong64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2171,9 +2098,6 @@
"ppc64" "ppc64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2188,9 +2112,6 @@
"riscv64" "riscv64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2205,9 +2126,6 @@
"riscv64" "riscv64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2222,9 +2140,6 @@
"s390x" "s390x"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2239,9 +2154,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"glibc"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2256,9 +2168,6 @@
"x64" "x64"
], ],
"dev": true, "dev": true,
"libc": [
"musl"
],
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
@@ -2467,6 +2376,7 @@
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@@ -3117,6 +3027,7 @@
"integrity": "sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ==", "integrity": "sha512-loXy6bWOoP3EP6JA7jo6p5jMpBJmHmsNZM5SFRHLdh1MGOPurMnNBj4ZlAbaqUAaQWbCr7jHV4P7gzAyryZWkQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.2", "@eslint-community/regexpp": "^4.12.2",
@@ -3173,6 +3084,7 @@
"integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"eslint-config-prettier": "bin/cli.js" "eslint-config-prettier": "bin/cli.js"
}, },
@@ -3720,6 +3632,7 @@
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"optional": true, "optional": true,
@@ -4373,6 +4286,7 @@
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@@ -4517,6 +4431,7 @@
"integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"prettier": "bin/prettier.cjs" "prettier": "bin/prettier.cjs"
}, },
@@ -4547,6 +4462,7 @@
"devOptional": true, "devOptional": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"dependencies": { "dependencies": {
"@prisma/engines": "5.22.0" "@prisma/engines": "5.22.0"
}, },
@@ -5259,6 +5175,7 @@
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"napi-postinstall": "^0.3.4" "napi-postinstall": "^0.3.4"
}, },
@@ -5312,6 +5229,7 @@
"integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==", "integrity": "sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.27.0", "esbuild": "^0.27.0",
"fdir": "^6.5.0", "fdir": "^6.5.0",
+1 -1
View File
@@ -4,7 +4,7 @@
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "node --env-file=.env --watch src/index.js", "dev": "node --env-file=.env --watch-path=./src src/index.js",
"dev:classic": "node --watch src/index.js", "dev:classic": "node --watch src/index.js",
"start": "node src/index.js", "start": "node src/index.js",
"db:migrate": "prisma migrate dev", "db:migrate": "prisma migrate dev",
Binary file not shown.
+2 -2
View File
@@ -1,7 +1,7 @@
import { avataaars } from '@dicebear/collection' import { initials } from '@dicebear/collection'
import { createAvatar } from '@dicebear/core' import { createAvatar } from '@dicebear/core'
const DEFAULT_STYLE = avataaars const DEFAULT_STYLE = initials
export async function generateAvatar(seed) { export async function generateAvatar(seed) {
const avatar = createAvatar(DEFAULT_STYLE, { seed: String(seed) }) const avatar = createAvatar(DEFAULT_STYLE, { seed: String(seed) })
+29 -4
View File
@@ -49,8 +49,17 @@ export async function getOrCreateResized(uuid, width, format, subdir = '') {
await fs.promises.mkdir(path.dirname(cachePath), { recursive: true }) await fs.promises.mkdir(path.dirname(cachePath), { recursive: true })
const sharp = (await import('sharp')).default let sharpModule
let pipeline = sharp(originalPath) try {
sharpModule = (await import('sharp')).default
} catch (err) {
const msg = `Failed to load sharp image processing library: ${err.message}`
throw Object.assign(new Error(msg), { cause: err, code: 'SHARP_LOAD_ERROR' })
}
let pipeline
try {
pipeline = sharpModule(originalPath)
if (width) { if (width) {
pipeline = pipeline.resize(width, null, { withoutEnlargement: true }) pipeline = pipeline.resize(width, null, { withoutEnlargement: true })
@@ -58,6 +67,10 @@ export async function getOrCreateResized(uuid, width, format, subdir = '') {
const options = format === 'avif' ? { quality: 75, effort: 4 } : { quality: 80 } const options = format === 'avif' ? { quality: 75, effort: 4 } : { quality: 80 }
await pipeline[format](options).toFile(cachePath) await pipeline[format](options).toFile(cachePath)
} catch (err) {
const msg = `Failed to resize image ${originalPath} to ${width}w ${format}: ${err.message}`
throw Object.assign(new Error(msg), { cause: err, code: 'SHARP_RESIZE_ERROR' })
}
return { path: cachePath, isNew: true } return { path: cachePath, isNew: true }
} }
@@ -75,17 +88,29 @@ export async function generateAllSizes(uuid, subdir, originalPath) {
const cacheDir = path.join(CACHE_DIR, cacheSubdir) const cacheDir = path.join(CACHE_DIR, cacheSubdir)
await fs.promises.mkdir(cacheDir, { recursive: true }) await fs.promises.mkdir(cacheDir, { recursive: true })
const sharp = (await import('sharp')).default let sharpModule
const source = sharp(originalPath) try {
sharpModule = (await import('sharp')).default
} catch (err) {
const msg = `Failed to load sharp image processing library: ${err.message}`
throw Object.assign(new Error(msg), { cause: err, code: 'SHARP_LOAD_ERROR' })
}
const source = sharpModule(originalPath)
for (const width of VALID_WIDTHS) { for (const width of VALID_WIDTHS) {
for (const format of SUPPORTED_FORMATS) { for (const format of SUPPORTED_FORMATS) {
const cacheFileName = `${uuid}_w${width}.${format}` const cacheFileName = `${uuid}_w${width}.${format}`
const cachePath = path.join(CACHE_DIR, cacheSubdir, cacheFileName) const cachePath = path.join(CACHE_DIR, cacheSubdir, cacheFileName)
try {
const pipeline = source.clone().resize(width, null, { withoutEnlargement: true }) const pipeline = source.clone().resize(width, null, { withoutEnlargement: true })
const options = format === 'avif' ? { quality: 75, effort: 4 } : { quality: 80 } const options = format === 'avif' ? { quality: 75, effort: 4 } : { quality: 80 }
await pipeline[format](options).toFile(cachePath) await pipeline[format](options).toFile(cachePath)
} catch (err) {
const msg = `Failed to generate ${width}w ${format} for ${originalPath}: ${err.message}`
throw Object.assign(new Error(msg), { cause: err, code: 'SHARP_RESIZE_ERROR' })
}
} }
} }
} }
+1 -1
View File
@@ -112,7 +112,7 @@ export async function registerAuthRoutes(fastify) {
passwordHash, passwordHash,
displayName: displayName || null, displayName: displayName || null,
avatar: avatarUri, avatar: avatarUri,
avatarStyle: 'avataaars', avatarStyle: 'initials',
}, },
}) })
+1 -1
View File
@@ -90,7 +90,7 @@ async function findOrCreateUserFromOAuth({ provider, providerUserId, accessToken
email, email,
displayName: norm ? norm.split('@')[0] : 'Пользователь', displayName: norm ? norm.split('@')[0] : 'Пользователь',
avatar: await generateAvatar(email), avatar: await generateAvatar(email),
avatarStyle: 'avataaars', avatarStyle: 'initials',
}, },
}) })
await prisma.oAuthAccount.create({ await prisma.oAuthAccount.create({
+9
View File
@@ -11,7 +11,12 @@ const CACHE_CONTROL_SHORT = 'public, max-age=86400'
*/ */
export function registerUploadsResized(fastify) { export function registerUploadsResized(fastify) {
fastify.get('/uploads-resized/*', async (request, reply) => { fastify.get('/uploads-resized/*', async (request, reply) => {
try {
const rawPath = request.params['*'] const rawPath = request.params['*']
if (typeof rawPath !== 'string') {
return reply.code(400).send({ error: 'Invalid request: missing file path' })
}
const url = new URL(request.url, 'http://localhost') const url = new URL(request.url, 'http://localhost')
const widthParam = url.searchParams.get('w') const widthParam = url.searchParams.get('w')
@@ -68,5 +73,9 @@ export function registerUploadsResized(fastify) {
reply.header('Cache-Control', CACHE_CONTROL_IMMUTABLE) reply.header('Cache-Control', CACHE_CONTROL_IMMUTABLE)
reply.header('Content-Type', format === 'avif' ? 'image/avif' : 'image/webp') reply.header('Content-Type', format === 'avif' ? 'image/avif' : 'image/webp')
return reply.send(fs.createReadStream(result.path)) return reply.send(fs.createReadStream(result.path))
} catch (error) {
request.log.error({ err: error, url: request.url }, 'uploads-resized route error')
return reply.code(500).send({ error: error.message || 'Image resize failed' })
}
}) })
} }