From 9bb000f9370e50a2748c138b9836cf4fdb6ba7a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Patrik=20Mint=C4=9Bl?= Date: Sun, 26 Jan 2025 22:00:27 +0100 Subject: [PATCH 1/6] Deploy things --- .gitignore | 6 +++++- .nvmrc | 1 + config/patrick115.eu | 25 +++++++++++++++++++++++++ config/web.service | 1 + 4 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 .nvmrc create mode 100755 config/patrick115.eu create mode 120000 config/web.service diff --git a/.gitignore b/.gitignore index 2c3b871..7d1e4b6 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,8 @@ vite.config.ts.timestamp-* #lock files pnpm-lock.yaml package-lock.json -yarn.lock \ No newline at end of file +yarn.lock + +#deploy +script +logs diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..cc7ce7f --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v22.13.1 diff --git a/config/patrick115.eu b/config/patrick115.eu new file mode 100755 index 0000000..b728940 --- /dev/null +++ b/config/patrick115.eu @@ -0,0 +1,25 @@ +server { + listen 80; + listen [::]:80; + + server_name patrick115.eu; + + return 301 https://$host$request_uri; + +} + +server { + + listen [::]:443 ssl http2; + listen 443 ssl http2; + + server_name patrick115.eu; + + access_log /opt/NodeApps/Web/logs/access.log; + error_log /opt/NodeApps/Web/logs/error.log; + + location / { + proxy_set_header X-Real-IP $remote_addr; + proxy_pass "http://127.0.0.1:5178"; + } +} diff --git a/config/web.service b/config/web.service new file mode 120000 index 0000000..708300e --- /dev/null +++ b/config/web.service @@ -0,0 +1 @@ +/etc/systemd/system/web.service \ No newline at end of file From 2de88e42567ef4ca508c09381e37c01d893e02ef Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 30 Nov 2025 16:11:56 +0000 Subject: [PATCH 2/6] Initial plan From f736e2d2837fa3237debfae204ea64d22ab8c90e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 30 Nov 2025 16:17:16 +0000 Subject: [PATCH 3/6] Initial analysis of performance issues in gallery Co-authored-by: patrick11514 <56652391+patrick11514@users.noreply.github.com> --- pnpm-workspace.yaml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 pnpm-workspace.yaml diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..daef4c5 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +onlyBuiltDependencies: + - bcrypt + - esbuild + - sharp From 3b4bce65892dc4e200cff6a16df9bbd95b26fcc1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 30 Nov 2025 16:20:09 +0000 Subject: [PATCH 4/6] Add lazy loading and optimize image server with streaming and caching Co-authored-by: patrick11514 <56652391+patrick11514@users.noreply.github.com> --- .gitignore | 1 + pnpm-workspace.yaml | 4 -- src/components/admin/gallery.svelte | 2 +- src/components/galleryItem.svelte | 2 +- src/routes/customImages/[...name]/+server.ts | 60 +++++++++++++------- 5 files changed, 44 insertions(+), 25 deletions(-) delete mode 100644 pnpm-workspace.yaml diff --git a/.gitignore b/.gitignore index 7d1e4b6..fd50cfc 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ yarn.lock #deploy script logs +pnpm-workspace.yaml diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml deleted file mode 100644 index daef4c5..0000000 --- a/pnpm-workspace.yaml +++ /dev/null @@ -1,4 +0,0 @@ -onlyBuiltDependencies: - - bcrypt - - esbuild - - sharp diff --git a/src/components/admin/gallery.svelte b/src/components/admin/gallery.svelte index 90b7db1..19e000e 100644 --- a/src/components/admin/gallery.svelte +++ b/src/components/admin/gallery.svelte @@ -4,6 +4,6 @@
{#each images as image} - gallery item + gallery item {/each}
diff --git a/src/components/galleryItem.svelte b/src/components/galleryItem.svelte index 79f7379..d204527 100644 --- a/src/components/galleryItem.svelte +++ b/src/components/galleryItem.svelte @@ -70,7 +70,7 @@ href={admin ? `/admin/gallery/${data.id}` : `/customImages/gallery/${data.name}?format=jpeg`} target={admin ? undefined : '_blank'} > - {data.alt} + {data.alt}

{data.alt} diff --git a/src/routes/customImages/[...name]/+server.ts b/src/routes/customImages/[...name]/+server.ts index 29fa104..749c853 100644 --- a/src/routes/customImages/[...name]/+server.ts +++ b/src/routes/customImages/[...name]/+server.ts @@ -5,7 +5,10 @@ import sharp from 'sharp'; const formats = ['jpg', 'jpeg', 'png', 'webp', 'tiff']; -export const GET = (async ({ params, setHeaders, url }) => { +// Cache duration: 1 year in seconds +const CACHE_MAX_AGE = 31536000; + +export const GET = (async ({ params, url }) => { if (params.name == null) { throw error(404, 'Not found'); } @@ -18,8 +21,6 @@ export const GET = (async ({ params, setHeaders, url }) => { throw error(404, 'Not found'); } - let file = fs.readFileSync(filePath); - const searchParams = url.searchParams; let fileExtension = path.extname(filePath); let modified = false; @@ -60,7 +61,8 @@ export const GET = (async ({ params, setHeaders, url }) => { const cachePath = path.join('.cache', cacheModifiedName); if (!fs.existsSync(cachePath)) { - let image = sharp(file); + // Use streaming to process the image + const image = sharp(filePath); const imageOptions: sharp.JpegOptions & sharp.PngOptions & sharp.WebpOptions & sharp.TiffOptions = { quality: 75 @@ -69,16 +71,16 @@ export const GET = (async ({ params, setHeaders, url }) => { switch (fileExtension) { case 'jpeg': case 'jpg': - image = image.jpeg(imageOptions); + image.jpeg(imageOptions); break; case 'png': - image = image.png(imageOptions); + image.png(imageOptions); break; case 'webp': - image = image.webp(imageOptions); + image.webp(imageOptions); break; case 'tiff': - image = image.tiff(imageOptions); + image.tiff(imageOptions); break; } @@ -87,28 +89,48 @@ export const GET = (async ({ params, setHeaders, url }) => { const newWidth = meta.width ? Math.round(meta.width * (scale / 100)) : undefined; const newHeight = meta.height ? Math.round(meta.height * (scale / 100)) : undefined; - image = image.resize({ + image.resize({ width: newWidth, height: newHeight }); - const imageBuffer = await image.toBuffer(); - - fs.writeFileSync(cachePath, imageBuffer); + await image.toFile(cachePath); } - file = fs.readFileSync(cachePath); filePath = cachePath; } const fileInfo = fs.statSync(filePath); + const contentType = 'image/' + (fileExtension.startsWith('.') ? fileExtension.slice(1) : fileExtension); - setHeaders({ - 'Content-Type': 'image/' + fileExtension.startsWith('.') ? fileExtension.slice(1) : fileExtension, - 'Content-Length': fileInfo.size.toString(), - 'Last-Modified': fileInfo.mtime.toUTCString(), - 'Cache-Control': 'public, max-age=86400' + // Create a readable stream for efficient memory usage + const stream = fs.createReadStream(filePath); + + // Convert Node.js readable stream to web ReadableStream + const webStream = new ReadableStream({ + start(controller) { + stream.on('data', (chunk) => { + controller.enqueue(chunk); + }); + stream.on('end', () => { + controller.close(); + }); + stream.on('error', (err) => { + controller.error(err); + }); + }, + cancel() { + stream.destroy(); + } }); - return new Response(file); + // Return response with headers set early and streaming body + return new Response(webStream, { + headers: { + 'Content-Type': contentType, + 'Content-Length': fileInfo.size.toString(), + 'Last-Modified': fileInfo.mtime.toUTCString(), + 'Cache-Control': `public, max-age=${CACHE_MAX_AGE}, immutable` + } + }); }) satisfies RequestHandler; From 4f8b9b8c49a5afcbf55843cc41c19ee18e44c4da Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 30 Nov 2025 16:25:26 +0000 Subject: [PATCH 5/6] Improve stream cleanup and fix potential race conditions in image server Co-authored-by: patrick11514 <56652391+patrick11514@users.noreply.github.com> --- src/routes/customImages/[...name]/+server.ts | 42 ++++++++++++++------ 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/src/routes/customImages/[...name]/+server.ts b/src/routes/customImages/[...name]/+server.ts index 749c853..7707642 100644 --- a/src/routes/customImages/[...name]/+server.ts +++ b/src/routes/customImages/[...name]/+server.ts @@ -62,7 +62,7 @@ export const GET = (async ({ params, url }) => { if (!fs.existsSync(cachePath)) { // Use streaming to process the image - const image = sharp(filePath); + let image = sharp(filePath); const imageOptions: sharp.JpegOptions & sharp.PngOptions & sharp.WebpOptions & sharp.TiffOptions = { quality: 75 @@ -71,16 +71,16 @@ export const GET = (async ({ params, url }) => { switch (fileExtension) { case 'jpeg': case 'jpg': - image.jpeg(imageOptions); + image = image.jpeg(imageOptions); break; case 'png': - image.png(imageOptions); + image = image.png(imageOptions); break; case 'webp': - image.webp(imageOptions); + image = image.webp(imageOptions); break; case 'tiff': - image.tiff(imageOptions); + image = image.tiff(imageOptions); break; } @@ -89,7 +89,7 @@ export const GET = (async ({ params, url }) => { const newWidth = meta.width ? Math.round(meta.width * (scale / 100)) : undefined; const newHeight = meta.height ? Math.round(meta.height * (scale / 100)) : undefined; - image.resize({ + image = image.resize({ width: newWidth, height: newHeight }); @@ -106,20 +106,36 @@ export const GET = (async ({ params, url }) => { // Create a readable stream for efficient memory usage const stream = fs.createReadStream(filePath); + // Track cleanup function for proper resource management + let cleanup: (() => void) | null = null; + // Convert Node.js readable stream to web ReadableStream - const webStream = new ReadableStream({ + const webStream = new ReadableStream({ start(controller) { - stream.on('data', (chunk) => { + const onData = (chunk: Buffer) => { controller.enqueue(chunk); - }); - stream.on('end', () => { + }; + const onEnd = () => { + if (cleanup) cleanup(); controller.close(); - }); - stream.on('error', (err) => { + }; + const onError = (err: Error) => { + if (cleanup) cleanup(); controller.error(err); - }); + }; + + cleanup = () => { + stream.off('data', onData); + stream.off('end', onEnd); + stream.off('error', onError); + }; + + stream.on('data', onData); + stream.on('end', onEnd); + stream.on('error', onError); }, cancel() { + if (cleanup) cleanup(); stream.destroy(); } }); From 7396f4d2083e9da0f7526ed6a56e57df65f0b464 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 30 Nov 2025 16:34:56 +0000 Subject: [PATCH 6/6] Add pnpm-workspace.yaml for build dependencies configuration Co-authored-by: patrick11514 <56652391+patrick11514@users.noreply.github.com> --- .gitignore | 1 - pnpm-workspace.yaml | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 pnpm-workspace.yaml diff --git a/.gitignore b/.gitignore index fd50cfc..7d1e4b6 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,3 @@ yarn.lock #deploy script logs -pnpm-workspace.yaml diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..daef4c5 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +onlyBuiltDependencies: + - bcrypt + - esbuild + - sharp