Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0d6a1c5de | ||
|
|
d678d80116 | ||
|
|
bce973679b | ||
|
|
45f705a104 | ||
|
|
c4f17d5f78 | ||
|
|
247f6ea272 | ||
|
|
ebcfec4ec7 | ||
|
|
e22ccfeda3 | ||
|
|
3729639596 | ||
|
|
4887f599fd | ||
|
|
69db8de6d7 | ||
|
|
7bb5f339c0 | ||
|
|
5982a1bfd1 | ||
|
|
f46a24ee4e | ||
|
|
76c4d5c9bf |
@@ -1,4 +1,4 @@
|
||||
FROM node:20-alpine AS build
|
||||
FROM node:22-alpine AS build
|
||||
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
@@ -7,7 +7,7 @@ RUN npm ci || npm install
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM node:20-alpine AS production
|
||||
FROM node:22-alpine AS production
|
||||
|
||||
WORKDIR /app
|
||||
RUN npm install -g serve
|
||||
|
||||
@@ -28,7 +28,12 @@ export const env = {
|
||||
FRONTEND_URL: process.env.FRONTEND_URL || 'http://localhost:5173',
|
||||
|
||||
// Upload
|
||||
MAX_FILE_SIZE: parseInt(process.env.MAX_FILE_SIZE || '5242880', 10),
|
||||
// Bilder werden ohnehin auf 1600px heruntergerechnet, die Grenze muss nur
|
||||
// gross genug fuer ein unbearbeitetes Handyfoto sein.
|
||||
MAX_FILE_SIZE: parseInt(process.env.MAX_FILE_SIZE || String(20 * 1024 * 1024), 10),
|
||||
// PDFs werden unveraendert abgelegt. Eine Getraenkekarte mit Bildern liegt
|
||||
// schnell bei 15-20 MB, deshalb ein eigener, groesserer Wert.
|
||||
MAX_PDF_SIZE: parseInt(process.env.MAX_PDF_SIZE || String(40 * 1024 * 1024), 10),
|
||||
};
|
||||
|
||||
// Validate required environment variables
|
||||
|
||||
@@ -73,9 +73,11 @@ fastify.register(jwt, {
|
||||
},
|
||||
});
|
||||
|
||||
// Der globale Wert ist die Obergrenze fuer alles. Die Routen setzen ihn per
|
||||
// request.file({ limits }) auf ihren eigenen, engeren Wert herunter.
|
||||
fastify.register(multipart, {
|
||||
limits: {
|
||||
fileSize: env.MAX_FILE_SIZE,
|
||||
fileSize: Math.max(env.MAX_FILE_SIZE, env.MAX_PDF_SIZE),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import { contentSections } from '../db/schema.js';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { saveUploadedImage } from '../services/upload.service.js';
|
||||
import { dropImageIfUnused, extractImageUrls } from '../services/image-refs.service.js';
|
||||
import { env } from '../config/env.js';
|
||||
|
||||
// Fastify JSON schema for content section body
|
||||
const contentBodyJsonSchema = {
|
||||
@@ -106,7 +107,7 @@ const contentRoute: FastifyPluginAsync = async (fastify) => {
|
||||
preHandler: [fastify.authenticate],
|
||||
}, async (request, reply) => {
|
||||
try {
|
||||
const file = await (request as any).file();
|
||||
const file = await (request as any).file({ limits: { fileSize: env.MAX_FILE_SIZE } });
|
||||
if (!file) {
|
||||
return reply.code(400).send({ error: 'No file uploaded' });
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { events } from '../db/schema.js';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { dropImageIfUnused } from '../services/image-refs.service.js';
|
||||
import { saveUploadedImage } from '../services/upload.service.js';
|
||||
import { env } from '../config/env.js';
|
||||
|
||||
/** Raeumt eine Bilddatei weg, ohne dass ein Fehler die Antwort kippt. */
|
||||
async function dropUnusedImage(fastify: any, url: string | null | undefined, reason: string) {
|
||||
@@ -102,7 +103,7 @@ const eventsRoute: FastifyPluginAsync = async (fastify) => {
|
||||
}, async (request, reply) => {
|
||||
try {
|
||||
// Expect a single file field named "file"
|
||||
const file = await (request as any).file();
|
||||
const file = await (request as any).file({ limits: { fileSize: env.MAX_FILE_SIZE } });
|
||||
if (!file) {
|
||||
return reply.code(400).send({ error: 'No file uploaded' });
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { galleryImages } from '../db/schema.js';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { dropImageIfUnused } from '../services/image-refs.service.js';
|
||||
import { saveUploadedImage } from '../services/upload.service.js';
|
||||
import { env } from '../config/env.js';
|
||||
|
||||
/** Raeumt eine Bilddatei weg, ohne dass ein Fehler die Antwort kippt. */
|
||||
async function dropUnusedImage(fastify: any, url: string | null | undefined, reason: string) {
|
||||
@@ -81,7 +82,7 @@ const galleryRoute: FastifyPluginAsync = async (fastify) => {
|
||||
}, async (request, reply) => {
|
||||
try {
|
||||
// Expect a single file field named "file"
|
||||
const file = await (request as any).file();
|
||||
const file = await (request as any).file({ limits: { fileSize: env.MAX_FILE_SIZE } });
|
||||
if (!file) {
|
||||
return reply.code(400).send({ error: 'No file uploaded' });
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ const pdfRoute: FastifyPluginAsync = async (fastify) => {
|
||||
}
|
||||
|
||||
try {
|
||||
const file = await (request as any).file();
|
||||
const file = await (request as any).file({ limits: { fileSize: env.MAX_PDF_SIZE } });
|
||||
if (!file) {
|
||||
return reply.code(400).send({ error: 'No file uploaded' });
|
||||
}
|
||||
@@ -63,7 +63,7 @@ const pdfRoute: FastifyPluginAsync = async (fastify) => {
|
||||
const buffer = Buffer.concat(chunks);
|
||||
|
||||
if ((file.file as any)?.truncated) {
|
||||
const limit = Math.round(env.MAX_FILE_SIZE / 1024 / 1024);
|
||||
const limit = Math.round(env.MAX_PDF_SIZE / 1024 / 1024);
|
||||
return reply.code(413).send({ error: `PDF too large. Maximum is ${limit} MB` });
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ const pdfRoute: FastifyPluginAsync = async (fastify) => {
|
||||
|
||||
} catch (err: any) {
|
||||
if (err?.code === 'FST_REQ_FILE_TOO_LARGE') {
|
||||
const limit = Math.round(env.MAX_FILE_SIZE / 1024 / 1024);
|
||||
const limit = Math.round(env.MAX_PDF_SIZE / 1024 / 1024);
|
||||
return reply.code(413).send({ error: `PDF too large. Maximum is ${limit} MB` });
|
||||
}
|
||||
fastify.log.error({ err }, 'PDF upload failed');
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^5.12.0"
|
||||
"astro": "^7.2.1"
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 214 KiB |
|
Before Width: | Height: | Size: 214 KiB |
|
Before Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 50 KiB |
|
Before Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 214 KiB |
|
Before Width: | Height: | Size: 120 KiB |
|
Before Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 157 KiB |
|
Before Width: | Height: | Size: 157 KiB |
|
Before Width: | Height: | Size: 214 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 170 KiB |
|
Before Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 162 KiB |
|
Before Width: | Height: | Size: 168 KiB |
|
Before Width: | Height: | Size: 129 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 523 KiB |
|
Before Width: | Height: | Size: 523 KiB |
|
Before Width: | Height: | Size: 523 KiB |
|
Before Width: | Height: | Size: 523 KiB |
|
Before Width: | Height: | Size: 469 KiB |
|
Before Width: | Height: | Size: 523 KiB |
|
Before Width: | Height: | Size: 398 KiB |
|
Before Width: | Height: | Size: 604 KiB |
|
Before Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 567 KiB |
|
Before Width: | Height: | Size: 469 KiB |
|
Before Width: | Height: | Size: 728 KiB |
|
Before Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 161 KiB |
|
Before Width: | Height: | Size: 167 KiB |
|
Before Width: | Height: | Size: 1021 KiB |
|
Before Width: | Height: | Size: 1021 KiB |
|
Before Width: | Height: | Size: 2.0 MiB |
|
Before Width: | Height: | Size: 2.6 MiB |
|
Before Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 2.6 MiB |
|
Before Width: | Height: | Size: 1.7 MiB |
|
Before Width: | Height: | Size: 2.6 MiB |
|
Before Width: | Height: | Size: 488 KiB |
|
After Width: | Height: | Size: 84 KiB |
|
After Width: | Height: | Size: 88 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 90 KiB |
@@ -7,19 +7,19 @@ const { id } = Astro.props;
|
||||
<h2 class="title">Drinks</h2>
|
||||
|
||||
<p class="note">
|
||||
Ob frisch gezapftes Pint, edler Whisky oder ein gemütliches Glas Wein – bei uns genießt du das Leben in entspannter Atmosphäre. Auch Cocktails dürfen nicht fehlen: Vieles kreieren wir selbst. Sláinte!
|
||||
Ob ein frisch gezapftes Pint, ein edler Tropfen Whiskey oder ein gemütliches Glas Wein – hier kannst du in entspannter Atmosphäre das Leben genießen.
|
||||
</p>
|
||||
|
||||
<a href="/pdf/Getraenke_Gallus_2025.pdf" class="card-link" target="_blank" rel="noopener noreferrer">Getränkekarte</a>
|
||||
<a href="/pdf/gallus-getraenkekarte-2026.pdf" class="card-link" target="_blank" rel="noopener noreferrer">Getränkekarte</a>
|
||||
|
||||
<h3 class="monats-hit">Monats Hit</h3>
|
||||
|
||||
<div class="mate-vodka">
|
||||
<div class="circle" title="Mate Vodka">
|
||||
<img src="/images/MonthlyHit.png" alt="Monats Hit" class="circle-image" />
|
||||
<div class="circle" title="mit oder ohne Schuss ;)">
|
||||
<img src="/images/content/mit-oder-ohne-schuss.avif" alt="Monats Hit" class="circle-image" />
|
||||
<span class="circle-label"></span>
|
||||
</div>
|
||||
<div>Mate Vodka</div>
|
||||
<div>mit oder ohne Schuss ;)</div>
|
||||
</div>
|
||||
|
||||
<p class="note">
|
||||
@@ -28,15 +28,15 @@ const { id } = Astro.props;
|
||||
|
||||
<div class="circle-row">
|
||||
<div class="circle whiskey-circle" title="Whiskey 1">
|
||||
<img src="/images/whiskey/Whiskey1.png" alt="Whiskey 1" class="circle-image" />
|
||||
<img src="/images/content/whiskey-1.avif" alt="Whiskey 1" class="circle-image" />
|
||||
<span class="circle-label"></span>
|
||||
</div>
|
||||
<div class="circle whiskey-circle" title="Whiskey 2">
|
||||
<img src="/images/whiskey/Whiskey2.png" alt="Whiskey 2" class="circle-image" />
|
||||
<img src="/images/content/whiskey-2.avif" alt="Whiskey 2" class="circle-image" />
|
||||
<span class="circle-label"></span>
|
||||
</div>
|
||||
<div class="circle whiskey-circle" title="Whiskey 3">
|
||||
<img src="/images/whiskey/Whiskey3.png" alt="Whiskey 3" class="circle-image" />
|
||||
<img src="/images/content/whiskey-3.avif" alt="Whiskey 3" class="circle-image" />
|
||||
<span class="circle-label"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -28,27 +28,20 @@ Plätze sind begrenzt! Jetzt reservieren unter 🍀WA 077 232 27 70
|
||||
`,
|
||||
},
|
||||
{
|
||||
image: "/images/events/msogsj4l-bmlcxc.jpeg",
|
||||
title: "SG Fest",
|
||||
date: "2026-08-14",
|
||||
image: "/images/events/noeg.avif",
|
||||
title: "NOeg",
|
||||
date: "2026-09-16",
|
||||
description: `
|
||||
Das Stadt-Fest steht vor der Tür! Wir haben OFFEN ab 17:00Uhr mit Karaoke! Wir freuen uns auf euch!
|
||||
🎤 Singe die größten Hits auf Spanisch, Italienisch, Französisch, Türkisch oder vielleicht sogar Japanisch?!
|
||||
✨ Lass das Gallus zum Leben erwachen – bei unserer multilingualen Karaoke-Nacht!
|
||||
`,
|
||||
},
|
||||
{
|
||||
image: "/images/events/mr82c7eh-yp87hy.jpeg",
|
||||
title: "Richie Live",
|
||||
date: "2026-08-27",
|
||||
image: "/images/events/yutaro.avif",
|
||||
title: "Yutaro",
|
||||
date: "2026-09-17",
|
||||
description: `
|
||||
SOLD OUT! - Richie hat das Image eines Weltenbummlers und Musikkünstlers. In der Ostschweiz ist er bereits bestens bekannt.🍀Warteliste: WA 077 232 27 70
|
||||
`,
|
||||
},
|
||||
{
|
||||
image: "/images/events/mrnpux59-b3075p.jpeg",
|
||||
title: "Hannes LIVE",
|
||||
date: "2026-09-10",
|
||||
description: `
|
||||
Er ist zurück!🥰🍀Johannes ist wieder bei uns! Eine weitere unvergessliche Irish-Night!!🍀Sei dabei! Reservationen🍀via WA 077 232 27 70
|
||||
Mit seiner Gitarre und seiner markanten Stimme verbindet er Singer-Songwriter-Sound mit Einflüssen aus Rock, Blues und Progressive Rock. 🍀Reservationen🍀via WA 077 232 27 70
|
||||
`,
|
||||
},
|
||||
{
|
||||
@@ -59,21 +52,52 @@ Plätze sind begrenzt! Jetzt reservieren unter 🍀WA 077 232 27 70
|
||||
SOLD OUT! - First time on stage – und das direkt im Gallus Pub!🎤✨
|
||||
Danielle & Nina bringen Irish Folk & liebevolle Cover Songs.🍀Warteliste: WA 077 232 27 70
|
||||
`,
|
||||
},
|
||||
{
|
||||
image: "/images/events/dirty-karaoke.avif",
|
||||
title: "Dirty Karaoke",
|
||||
date: "2026-10-07",
|
||||
description: `
|
||||
*Nicht für alle Ohren geeignet! Ob eindeutig zweideutig, hemmungslos daneben oder einfach nur zum Fremdschämen – an diesem Abend gilt: Was gesungen wird, bleibt auf der Bühne.😉
|
||||
`,
|
||||
},
|
||||
{
|
||||
image: "/images/events/voices.avif",
|
||||
title: "Voices",
|
||||
date: "2026-10-22",
|
||||
description: `
|
||||
Ein Abend voller Songs, Stimmen & Live Music.
|
||||
Mit besonderen Stimmen, guter Stimmung und ganz viel musikalischer Energie. 🍀Reservationen🍀via WA 077 232 27 70
|
||||
`,
|
||||
},
|
||||
{
|
||||
image: "/images/events/disney-karaoke.avif",
|
||||
title: "Disney Karaoke",
|
||||
date: "2026-10-28",
|
||||
description: `
|
||||
Die nächste Disney Karaoke Night steht an – und passend zur Gruselweek: wer mit Grusel-Kostüm kommt, kriegt einen Glitzer-Shot auf's Haus.🎤✨
|
||||
`,
|
||||
},
|
||||
{
|
||||
image: "/images/events/katy-elias.avif",
|
||||
title: "Katy & Elias",
|
||||
date: "2026-11-21",
|
||||
description: `
|
||||
ACHTUNG Kurz-Konzert 19:30-20:30! Danach geht’s für die beiden direkt weiter zu einem zweiten Konzert in der Lukas Bar in St. Georgen.
|
||||
🍀Reservationen🍀via WA 077 232 27 70
|
||||
`,
|
||||
}
|
||||
];
|
||||
|
||||
const images = [
|
||||
{ src: "/images/gallery/mndhrrtl-s4o8dn.png", alt: "mndhrrtl-s4o8dn.png" },
|
||||
{ src: "/images/gallery/mndhtjvz-965yra.jpeg", alt: "mndhtjvz-965yra.jpeg" },
|
||||
{ src: "/images/gallery/mndi3625-lnnggc.jpeg", alt: "mndi3625-lnnggc.jpeg" },
|
||||
{ src: "/images/gallery/mndi7upa-qxukqg.jpeg", alt: "mndi7upa-qxukqg.jpeg" },
|
||||
{ src: "/images/gallery/mndi8kzg-oazjp8.jpeg", alt: "mndi8kzg-oazjp8.jpeg" },
|
||||
{ src: "/images/gallery/mndiabuo-m29xj0.jpeg", alt: "mndiabuo-m29xj0.jpeg" },
|
||||
{ src: "/images/gallery/mndicr59-n8suoy.jpeg", alt: "mndicr59-n8suoy.jpeg" },
|
||||
{ src: "/images/gallery/mndieqns-mjs7xe.jpeg", alt: "mndieqns-mjs7xe.jpeg" },
|
||||
{ src: "/images/gallery/mndif8nv-aptjf6.jpeg", alt: "mndif8nv-aptjf6.jpeg" },
|
||||
{ src: "/images/gallery/mndiggta-hnldkv.jpeg", alt: "mndiggta-hnldkv.jpeg" },
|
||||
{ src: "/images/gallery/mndih8lp-woprt4.webp", alt: "mndih8lp-woprt4.webp" }
|
||||
{ src: "/images/gallery/halloween.avif", alt: "Halloween" },
|
||||
{ src: "/images/gallery/sangallerruum.avif", alt: "Sangallerruum" },
|
||||
{ src: "/images/gallery/welovegalluspub.avif", alt: "WeLoveGallusPub" },
|
||||
{ src: "/images/gallery/raphi-sabrina.jpeg", alt: "Raphi&Sabrina" },
|
||||
{ src: "/images/gallery/pubquiz.avif", alt: "PubQuiz" }
|
||||
];
|
||||
---
|
||||
|
||||
|
||||