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}
-

+

{/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}
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;