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 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 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..7707642 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 + let image = sharp(filePath); const imageOptions: sharp.JpegOptions & sharp.PngOptions & sharp.WebpOptions & sharp.TiffOptions = { quality: 75 @@ -92,23 +94,59 @@ export const GET = (async ({ params, setHeaders, url }) => { 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); + + // 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({ + start(controller) { + const onData = (chunk: Buffer) => { + controller.enqueue(chunk); + }; + const onEnd = () => { + if (cleanup) cleanup(); + controller.close(); + }; + const onError = (err: Error) => { + if (cleanup) cleanup(); + controller.error(err); + }; + + cleanup = () => { + stream.off('data', onData); + stream.off('end', onEnd); + stream.off('error', onError); + }; - 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' + stream.on('data', onData); + stream.on('end', onEnd); + stream.on('error', onError); + }, + cancel() { + if (cleanup) cleanup(); + 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;