From a4f3cf738cce706df6776d5b4af78e1efba2b8c4 Mon Sep 17 00:00:00 2001 From: Kroonk Date: Sun, 12 Apr 2026 17:22:49 +0200 Subject: [PATCH] Fix: Backend-Bugs, fehlende Galerie, Watchtower statt Docker-Socket - express.urlencoded() hinzugefuegt (Login war komplett kaputt) - Oeffentliche Galerie zeigt jetzt alle Bilder (statt leere Galerie) - loadGallery() Aufruf in main.js eingefuegt (fehlte komplett) - main.min.js neu gebaut mit gulp (Galerie-Code fehlte in minified Version) - Watchtower Container in docker-compose.yml (auto-updates alle 5 Min) - Docker-Socket und docker-cli aus Photography-Container entfernt - runs.json Muell-Datei geloescht - Brain.md und Feature-Request.md aktualisiert Co-Authored-By: Claude Opus 4.6 --- Brain.md | 111 +++++++++++++++++++++++++++++------------- Dockerfile | 4 +- Feature-Request.md | 19 ++++---- assets/js/main.js | 5 +- assets/js/main.min.js | 2 +- backend/server.js | 65 +++++++++++++------------ docker-compose.yml | 14 +++++- runs.json | Bin 334 -> 0 bytes 8 files changed, 139 insertions(+), 81 deletions(-) delete mode 100644 runs.json diff --git a/Brain.md b/Brain.md index a75d6df..dcf4aa7 100644 --- a/Brain.md +++ b/Brain.md @@ -20,12 +20,15 @@ Keine 1:1 Kopie, sondern eine individuell angepasste Version mit eigenen Ansprue | Komponente | Technologie | |---|---| | Static Site Generator | **Jekyll** (Ruby-basiert) | +| Backend | **Node.js / Express** (Auth, API, Static Serving) | +| Datenbank | **SQLite** (Nutzerverwaltung, Bildzuweisung) | | Templating | Liquid Templates | | Styling | SCSS -> kompiliertes CSS | | JS Libraries | jQuery, Poptrox (Lightbox), EXIF.js | | Build-Tool | Gulp (SCSS kompilieren, JS minifizieren, Bilder resizen) | -| Hosting | **UGREEN NAS (Docker)** via nginx | -| Containerisierung | Docker Multi-Stage (Jekyll Build + nginx) | +| Hosting | **UGREEN NAS (Docker)** via Node.js Express | +| Containerisierung | Docker Multi-Stage (Jekyll Build + Node.js Backend) | +| Auto-Update | **Watchtower** (prueft alle 5 Min auf neue Images) | | Fonts | FontAwesome, Google Fonts (Source Sans Pro) | | Paketmanager | npm (Node), Bundler (Ruby) | @@ -37,9 +40,13 @@ Photography/ ├── _layouts/ │ └── default.html # Basis-Layout (Head + Body + Scripts) ├── _includes/ -│ ├── header.html # Bereich: CSS, Fonts, Google Analytics +│ ├── header.html # Bereich: CSS, Fonts │ └── footer.html # Script-Einbindungen (jQuery, Poptrox, EXIF, Main) -├── index.html # Hauptseite: Header, Galerie-Grid, Footer/About +├── index.html # Hauptseite: Header, Galerie-Grid, Footer/About, Login-Panel +├── admin.html # Admin-Dashboard (Nutzerverwaltung, Bildzuweisung) +├── backend/ +│ ├── server.js # Express Backend (Auth, API, Static Files, Port 8090) +│ └── database.js # SQLite Datenbank-Setup (Users, User-Images) ├── assets/ │ ├── css/ # Kompilierte CSS-Dateien (.min.css) │ ├── sass/ # SCSS Quelldateien @@ -47,50 +54,82 @@ Photography/ │ │ ├── components/ # Buttons, Forms, Icons, Panels, Poptrox │ │ ├── layout/ # Header, Footer, Main, Wrapper │ │ └── libs/ # Breakpoints, Mixins, Vars, Functions -│ ├── js/ # JavaScript (jQuery, Poptrox, EXIF, Main) +│ ├── js/ # JavaScript (jQuery, Poptrox, EXIF, Main, Admin) │ ├── fonts/ # FontAwesome Fonts │ └── webfonts/ # FA5 Webfonts ├── images/ │ ├── fulls/ # Vollaufloesung Bilder │ └── thumbs/ # Thumbnails (512px) ├── gulpfile.mjs # Build: SCSS->CSS, JS minify, Image resize -├── package.json # Node Dependencies +├── package.json # Node Dependencies (Express, SQLite, JWT, bcrypt) ├── Gemfile # Ruby Dependencies (jekyll) -└── CNAME # Custom Domain Config +├── Dockerfile # Multi-Stage: Jekyll Build + Node.js Runtime +├── docker-compose.yml # Container + Watchtower +├── entrypoint.sh # Thumbnail-Generierung + Node.js Start +└── nginx.conf # (Legacy, nicht mehr aktiv - Express dient statische Dateien) ``` ## Wie die Galerie funktioniert (Dynamisch) 1. **Bilder hochladen**: Fotos in Nextcloud-Ordner `Websitebilder` ablegen -2. **Volume-Mount**: Docker mountet den Nextcloud-Ordner als `/images/fulls/` (read-only) +2. **Volume-Mount**: Docker mountet den Nextcloud-Ordner als `/app/public/images/fulls/` (read-only) 3. **Thumbnails**: `entrypoint.sh` generiert automatisch 512px Thumbnails via ImageMagick 4. **Thumbnail-Check**: Alle 60 Sekunden prueft der Container auf neue Bilder -5. **nginx autoindex**: Liefert JSON-Liste aller Dateien in `/images/fulls/` +5. **Express API**: `/images/fulls/` liefert JSON-Liste der Bilddateien 6. **JavaScript (main.js)**: Fetcht die JSON-Liste und baut dynamisch `
` Elemente 7. **Poptrox-Lightbox**: Klick oeffnet Vollbild + EXIF-Daten (clientseitig via exif.js) +8. **Download-Button**: In der Lightbox kann jedes Bild heruntergeladen werden + +### Zugriffslogik +- **Nicht eingeloggt / Admin**: Alle Bilder sichtbar (oeffentliche Galerie) +- **Eingeloggter Nutzer**: Nur zugewiesene Bilder sichtbar + Download ### Nextcloud-Integration - **Nextcloud-Pfad**: `/volume1/nextcloud/data/Tom/files/Websitebilder` -- **Mount im Container**: `/usr/share/nginx/html/images/fulls:ro` +- **Mount im Container**: `/app/public/images/fulls:ro` - **Thumbnails-Volume**: Docker-Volume `thumbs` (persistent) +- **Datenbank-Volume**: Docker-Volume `database` (persistent, SQLite) - **Workflow**: Bild in Nextcloud hochladen -> max. 60s warten -> Seite neu laden -> fertig +## Login-System & Nutzerverwaltung + +### Architektur +- **Backend**: Node.js/Express auf Port 8090 +- **Auth**: JWT-Token als httpOnly Cookie (7 Tage gueltig) +- **Datenbank**: SQLite unter `/app/data/database.sqlite` +- **Passwort-Hashing**: bcryptjs (10 Rounds) + +### Standard-Admin +- **Username**: `admin` +- **Passwort**: `admin123` (SOFORT AENDERN nach erstem Login!) +- **Rolle**: admin + +### API-Endpunkte +| Endpunkt | Methode | Beschreibung | +|---|---|---| +| `/api/auth/login` | POST | Login (username, password) | +| `/api/auth/logout` | POST | Logout (Cookie loeschen) | +| `/api/auth/me` | GET | Aktueller User | +| `/api/admin/users` | GET/POST | Nutzer auflisten / erstellen | +| `/api/admin/users/:id` | DELETE | Nutzer loeschen | +| `/api/admin/assign` | GET/POST | Bildzuweisungen anzeigen / setzen | +| `/images/fulls/` | GET | Bilderliste (gefiltert nach User-Rolle) | + ## Wichtige Konfiguration (_config.yml) -- `baseurl`: URL der Website (leer fuer lokal) +- `baseurl`: URL der Website - `title`, `subtitle`, `author`: Seitentitel und Autor - `header.title/subtitle`: Header-Anzeige -- `footer.name/bio/github`: Footer-Infos +- `footer.name/bio`: Footer-Infos - `social_urls`: Links zu Social Media - `exif`: Welche EXIF-Tags angezeigt werden (Model, FNumber, ExposureTime, ISO) -- `google_analytics`: Tracking-ID ## Build-Befehle | Befehl | Aktion | |---|---| | `bundle install` | Ruby Dependencies installieren | -| `bundle exec jekyll serve` | Lokaler Dev-Server | +| `bundle exec jekyll serve` | Lokaler Dev-Server (nur Frontend) | | `npm install` | Node Dependencies installieren | | `gulp build` | SCSS kompilieren + JS minifizieren | | `gulp resize` | Bilder zu fulls (1024px) + thumbs (512px) | @@ -99,45 +138,51 @@ Photography/ ## Docker / NAS Hosting - **NAS**: UGREEN mit UGOS PRO -- **Port**: 8090 (Port 80 ist belegt) -- **Zugriff**: `http://NAS-IP:8090` -> spaeter `https://tom.mischlabs.de` -- **Domain**: tom.mischlabs.de (Reverse Proxy auf NAS einrichten) +- **Port**: 8090 (intern und extern) +- **Domain**: tom.mischlabs.de (via Cloudflare Tunnel) ### Docker-Dateien -- `Dockerfile` - Multi-Stage: Ruby/Jekyll baut Site -> nginx serviert statische Dateien -- `docker-compose.yml` - Container-Definition, Port 8090:80, Nextcloud-Volume, auto-restart -- `entrypoint.sh` - Thumbnail-Generierung + periodischer Check (alle 60s) -- `nginx.conf` - Webserver-Config mit Caching und Sicherheit +- `Dockerfile` - Multi-Stage: Ruby/Jekyll baut Site -> Node.js Express serviert alles +- `docker-compose.yml` - Photography Container + Watchtower, Volumes fuer Bilder/Thumbs/DB +- `entrypoint.sh` - Thumbnail-Generierung + Node.js Server-Start - `.dockerignore` - Haelt Build-Context klein (kein .git, node_modules etc.) -### Docker-Befehle +### Docker-Befehle (auf NAS via SSH) | Befehl | Aktion | |---|---| -| `docker compose up -d --build` | Container bauen und starten | -| `docker compose down` | Container stoppen | -| `docker compose logs -f` | Logs anzeigen | -| `docker compose up -d --build --force-recreate` | Neu bauen nach Aenderungen | +| `sudo docker compose up -d` | Container starten | +| `sudo docker compose down` | Container stoppen | +| `sudo docker compose logs -f photography` | Logs anzeigen | +| `sudo docker compose pull && sudo docker compose up -d` | Manuelles Update | -### Deployment-Workflow -1. Aenderungen lokal machen (Bilder, Config, Design) -2. Git commit & push -3. Auf NAS: `git pull && docker compose up -d --build` -4. Fertig - Website aktualisiert +### Deployment-Workflow (automatisch!) +1. Aenderungen lokal machen (Code, Design) +2. `gulp build` ausfuehren (JS + CSS minifizieren) +3. Git commit & push nach master +4. GitHub Actions baut automatisch Docker Image -> GHCR +5. Watchtower auf NAS erkennt neues Image (alle 5 Min) +6. Watchtower aktualisiert Container automatisch +7. Fertig - Website aktualisiert ohne SSH! -## Anpassungs-Roadmap (TODO - mit Besitzer besprechen) +## Anpassungs-Roadmap - [x] Titel, Subtitle, Autor anpassen (Mischkomposition / Tom Misch) - [x] Social Media Links aktualisieren (Instagram: @Mischkomposition) - [x] Sponsor-Bereich entfernen -- [x] Kontaktformular auf tom@mischlabs.de konfiguriert +- [x] Kontaktformular auf tom@mischlabs.de konfiguriert (Formsubmit.co) - [x] Google Analytics vom Originalautor entfernt - [x] Custom Domain: tom.mischlabs.de eingetragen - [x] Nextcloud-Integration (Bilder aus Websitebilder-Ordner) - [x] Dynamische Galerie (JS statt Jekyll-Loop) - [x] Automatische Thumbnail-Generierung +- [x] Login-System mit JWT + SQLite +- [x] Admin-Dashboard (Nutzerverwaltung, Bildzuweisung) +- [x] Kunden-Portal (zugewiesene Bilder + Download) +- [x] Watchtower fuer automatische Updates - [ ] Footer Bio-Text schreiben (optional) - [ ] Farbschema/Design anpassen - [ ] Eigene Kategorien/Alben erstellen (Feature-Erweiterung) +- [ ] Admin-Passwort aendern! --- diff --git a/Dockerfile b/Dockerfile index d3a2d8b..c65a3c5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,8 +15,8 @@ RUN bundle exec jekyll build # === Serve-Phase: node.js backend + ImageMagick === FROM node:20-alpine -# Install build dependencies for sqlite3 native bindings on Alpine -RUN apk add --no-cache imagemagick docker-cli python3 make g++ build-base sqlite-dev +# ImageMagick fuer Thumbnails, Build-Tools fuer sqlite3 native bindings +RUN apk add --no-cache imagemagick python3 make g++ build-base sqlite-dev WORKDIR /app COPY package*.json ./ diff --git a/Feature-Request.md b/Feature-Request.md index d91812d..d9f482f 100644 --- a/Feature-Request.md +++ b/Feature-Request.md @@ -3,7 +3,7 @@ ## Nutzerverwaltung & Login-System ### Beschreibung -Die Website soll um ein Login-System mit Nutzerverwaltung erweitert werden. +Die Website wurde um ein Login-System mit Nutzerverwaltung erweitert. ### Anforderungen @@ -17,23 +17,22 @@ Die Website soll um ein Login-System mit Nutzerverwaltung erweitert werden. - Nutzer koennen ihre zugewiesenen Bilder anschauen (Galerie + Lightbox) - Nutzer koennen ihre zugewiesenen Bilder herunterladen -### Status: Geplant (noch nicht umgesetzt) +### Status: Umgesetzt --- -## Admin-Dashboard: One-Click Update +## Auto-Update via Watchtower ### Beschreibung -Im Admin-Bereich soll ein Button vorhanden sein, der per Klick die neueste Version aus dem Git-Repository zieht und den Container automatisch aktualisiert. Kein manuelles SSH oder Terminal noetig. +Watchtower laeuft als separater Docker-Container und prueft alle 5 Minuten auf neue Images. +Nach einem `git push` baut GitHub Actions das neue Image und Watchtower aktualisiert den Container automatisch. ### Anforderungen -- Button im Admin-Dashboard: "Website aktualisieren" -- Zieht automatisch die neueste Version aus GitHub (git pull oder neues Image) -- Baut den Container neu / startet ihn neu -- Zeigt Status-Feedback (Erfolg/Fehler) +- Automatisches Update ohne SSH oder manuellen Eingriff +- Alte Images werden automatisch aufgeraeumt (WATCHTOWER_CLEANUP) -### Status: Geplant (noch nicht umgesetzt) +### Status: Umgesetzt --- -*Erstellt: 2026-04-12* +*Aktualisiert: 2026-04-12* diff --git a/assets/js/main.js b/assets/js/main.js index a4a216b..5faed73 100644 --- a/assets/js/main.js +++ b/assets/js/main.js @@ -161,7 +161,7 @@ cache: false, success: function(files) { if(files.length === 0) { - $main.html('

Bitte logge dich über den Login-Button unten links ein, um deine zugewiesenen Bilder zu sehen.

'); + $main.html('

Noch keine Bilder vorhanden.

'); return; } @@ -279,6 +279,9 @@ return template; } + // Galerie laden + loadGallery(); + // Login Handling $.get('/api/auth/me').done(function(u) { $('#login-username, #login-password, #login-submit').hide(); diff --git a/assets/js/main.min.js b/assets/js/main.min.js index 3d5363b..f6ded1e 100644 --- a/assets/js/main.min.js +++ b/assets/js/main.min.js @@ -1 +1 @@ -!function(n){var e,o=n(window),t=n("body");n("#wrapper");(breakpoints({xlarge:["1281px","1680px"],large:["981px","1280px"],medium:["737px","980px"],small:["481px","736px"],xsmall:[null,"480px"]}),"ie"==browser.name&&t.addClass("ie"),browser.mobile&&t.addClass("touch"),browser.canUse("transition"))&&(o.on("load",function(){window.setTimeout(function(){t.removeClass("is-preload")},100)}),o.on("resize",function(){window.clearTimeout(e),t.addClass("is-resizing"),e=window.setTimeout(function(){t.removeClass("is-resizing")},100)}));o.scrollTop(0);var i=n(".panel");i.each(function(){var e=n(this),o=n('[href="#'+e.attr("id")+'"]');n('
').appendTo(e).on("click",function(n){e.trigger("---hide")}),e.on("click",function(n){n.stopPropagation()}).on("---toggle",function(){e.hasClass("active")?e.triggerHandler("---hide"):e.triggerHandler("---show")}).on("---show",function(){t.hasClass("content-active")&&i.trigger("---hide"),e.addClass("active"),o.addClass("active"),t.addClass("content-active")}).on("---hide",function(){e.removeClass("active"),o.removeClass("active"),t.removeClass("content-active")}),o.removeAttr("href").css("cursor","pointer").on("click",function(n){n.preventDefault(),n.stopPropagation(),e.trigger("---toggle")})}),t.on("click",function(n){t.hasClass("content-active")&&(n.preventDefault(),n.stopPropagation(),i.trigger("---hide"))}),o.on("keyup",function(n){27==n.keyCode&&t.hasClass("content-active")&&(n.preventDefault(),n.stopPropagation(),i.trigger("---hide"))}),n("#header").find("a").each(function(){var e=n(this),o=e.attr("href");o&&"#"!=o.charAt(0)&&e.removeAttr("href").css("cursor","pointer").on("click",function(n){n.preventDefault(),n.stopPropagation(),window.location.href=o})}),n("#footer").find(".copyright").each(function(){var e=n(this),o=e.parent(),t=o.parent().children().last();breakpoints.on("<=medium",function(){e.appendTo(t)}),breakpoints.on(">medium",function(){e.appendTo(o)})});n("#main");n.get("/api/auth/me").done(function(e){n("#login-username, #login-password, #login-submit").hide(),n("#logout-btn").show().css("display","inline-block"),"admin"===e.role&&n("#admin-link").html('Admin-Dashboard'),n("#login-nav").html('Account ')}),n("#login-form").submit(function(e){e.preventDefault(),n.post("/api/auth/login",{username:n("#login-username").val(),password:n("#login-password").val()}).done(function(){window.location.reload()}).fail(function(n){alert("Login fehlgeschlagen. Bitte Zugangsdaten ueberpruefen.")})}),n("#logout-btn").click(function(e){e.preventDefault(),n.post("/api/auth/logout").done(function(){window.location.reload()})})}(jQuery); \ No newline at end of file +!function(e){var n,a=e(window),o=e("body");e("#wrapper");(breakpoints({xlarge:["1281px","1680px"],large:["981px","1280px"],medium:["737px","980px"],small:["481px","736px"],xsmall:[null,"480px"]}),"ie"==browser.name&&o.addClass("ie"),browser.mobile&&o.addClass("touch"),browser.canUse("transition"))&&(a.on("load",function(){window.setTimeout(function(){o.removeClass("is-preload")},100)}),a.on("resize",function(){window.clearTimeout(n),o.addClass("is-resizing"),n=window.setTimeout(function(){o.removeClass("is-resizing")},100)}));a.scrollTop(0);var t=e(".panel");t.each(function(){var n=e(this),a=e('[href="#'+n.attr("id")+'"]');e('
').appendTo(n).on("click",function(e){n.trigger("---hide")}),n.on("click",function(e){e.stopPropagation()}).on("---toggle",function(){n.hasClass("active")?n.triggerHandler("---hide"):n.triggerHandler("---show")}).on("---show",function(){o.hasClass("content-active")&&t.trigger("---hide"),n.addClass("active"),a.addClass("active"),o.addClass("content-active")}).on("---hide",function(){n.removeClass("active"),a.removeClass("active"),o.removeClass("content-active")}),a.removeAttr("href").css("cursor","pointer").on("click",function(e){e.preventDefault(),e.stopPropagation(),n.trigger("---toggle")})}),o.on("click",function(e){o.hasClass("content-active")&&(e.preventDefault(),e.stopPropagation(),t.trigger("---hide"))}),a.on("keyup",function(e){27==e.keyCode&&o.hasClass("content-active")&&(e.preventDefault(),e.stopPropagation(),t.trigger("---hide"))}),e("#header").find("a").each(function(){var n=e(this),a=n.attr("href");a&&"#"!=a.charAt(0)&&n.removeAttr("href").css("cursor","pointer").on("click",function(e){e.preventDefault(),e.stopPropagation(),window.location.href=a})}),e("#footer").find(".copyright").each(function(){var n=e(this),a=n.parent(),o=a.parent().children().last();breakpoints.on("<=medium",function(){n.appendTo(o)}),breakpoints.on(">medium",function(){n.appendTo(a)})});var i=e("#main"),r={},s=["jpg","jpeg","png","gif","webp"];function l(n){var a=e("#main").data("exif"),o="";for(var t in a){var i=a[t],r=EXIF.getTag(n,i.tag);void 0!==r&&(o+=' '+r+"  ")}return o}e.ajax({url:"/images/fulls/",dataType:"json",cache:!1,success:function(n){if(0!==n.length){var a=n.filter(function(e){if("file"!==e.type)return!1;var n=e.name.split(".").pop().toLowerCase();return-1!==s.indexOf(n)});0!==a.length?(a.sort(function(e,n){return e.name.localeCompare(n.name)}),a.forEach(function(n){var a=e('
'),o=e(''),t=e('');o.append(t),a.append(o),i.append(a)}),i.children(".thumb").each(function(){var n,a=e(this),o=a.find(".image"),t=o.children("img");0!=o.length&&(o.css("background-image","url("+t.attr("src")+")"),(n=t.data("position"))&&o.css("background-position",n),t.hide(),t[0].addEventListener("load",function(){EXIF.getData(t[0],function(){r[t.data("name")]=l(this)})}))}),i.poptrox({baseZIndex:2e4,caption:function(e){var n=e.children("img"),a=r[n.data("name")];void 0===a&&EXIF.getData(n[0],function(){a=r[n.data("name")]=l(this)});var o='
Bild Speichern ';return void 0!==a?"

"+a+o+"

":"

"+o+"

"},fadeSpeed:300,onPopupClose:function(){o.removeClass("modal-active")},onPopupOpen:function(){o.addClass("modal-active")},overlayOpacity:0,popupCloserText:"",popupHeight:150,popupLoaderText:"",popupSpeed:300,popupWidth:150,selector:".thumb > a.image",usePopupCaption:!0,usePopupCloser:!0,usePopupDefaultStyling:!1,usePopupForceClose:!0,usePopupLoader:!0,usePopupNav:!0,windowMargin:50}),breakpoints.on("<=xsmall",function(){i[0]._poptrox.windowMargin=0}),breakpoints.on(">xsmall",function(){i[0]._poptrox.windowMargin=50})):i.html('

Es wurden noch keine passenden Bilder für diesen Account hochgeladen oder zugewiesen.

')}else i.html('

Noch keine Bilder vorhanden.

')},error:function(){i.html('

Keine Bilder gefunden. Lade Bilder in deinen Nextcloud-Ordner hoch.

')}}),e.get("/api/auth/me").done(function(n){e("#login-username, #login-password, #login-submit").hide(),e("#logout-btn").show().css("display","inline-block"),"admin"===n.role&&e("#admin-link").html('Admin-Dashboard'),e("#login-nav").html('Account ')}),e("#login-form").submit(function(n){n.preventDefault(),e.post("/api/auth/login",{username:e("#login-username").val(),password:e("#login-password").val()}).done(function(){window.location.reload()}).fail(function(e){alert("Login fehlgeschlagen. Bitte Zugangsdaten ueberpruefen.")})}),e("#logout-btn").click(function(n){n.preventDefault(),e.post("/api/auth/logout").done(function(){window.location.reload()})})}(jQuery); \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index dcc8c66..0e288c3 100644 --- a/backend/server.js +++ b/backend/server.js @@ -11,6 +11,7 @@ const app = express(); const JWT_SECRET = process.env.JWT_SECRET || 'mischkomposition-super-secret-key-change-me'; app.use(express.json()); +app.use(express.urlencoded({ extended: true })); app.use(cookieParser()); // Middleware to verify JWT cookie @@ -63,37 +64,38 @@ app.get('/api/auth/me', (req, res) => { // ==================== IMAGE PROXY ==================== -// We must override the nginx `/images/fulls/` JSON behavior here: +// Bilder-Listing: Oeffentlich = alle Bilder, eingeloggt = zugewiesene Bilder app.get('/images/fulls/', (req, res) => { - if (!req.user) return res.json([]); // Not logged in -> empty gallery - const fullsDir = path.join(__dirname, '../public/images/fulls'); - + fs.readdir(fullsDir, (err, files) => { if (err) return res.json([]); - let imageFiles = files.filter(f => f.match(/\.(jpg|jpeg|png|gif|webp)$/i)); + const imageFiles = files.filter(f => f.match(/\.(jpg|jpeg|png|gif|webp)$/i)); - if (req.user.role === 'admin') { - // Admin sees all - const result = imageFiles.map(name => { - const stats = fs.statSync(path.join(fullsDir, name)); - return { name, type: 'file', mtime: stats.mtime.toUTCString(), size: stats.size }; - }); - return res.json(result); - } else { - // Normal user sees only assigned - db.all('SELECT image_name FROM user_images WHERE user_id = ?', [req.user.id], (err, rows) => { - if (err) return res.json([]); - const assignedNames = rows.map(r => r.image_name); - const assignedFiles = imageFiles.filter(name => assignedNames.includes(name)); - const result = assignedFiles.map(name => { + function buildResult(names) { + return names.map(name => { + try { const stats = fs.statSync(path.join(fullsDir, name)); return { name, type: 'file', mtime: stats.mtime.toUTCString(), size: stats.size }; - }); - return res.json(result); + } catch (e) { + return { name, type: 'file' }; + } }); } + + // Nicht eingeloggt oder Admin: alle Bilder anzeigen + if (!req.user || req.user.role === 'admin') { + return res.json(buildResult(imageFiles)); + } + + // Normaler User: nur zugewiesene Bilder + db.all('SELECT image_name FROM user_images WHERE user_id = ?', [req.user.id], (err, rows) => { + if (err) return res.json([]); + const assignedNames = rows.map(r => r.image_name); + const assignedFiles = imageFiles.filter(name => assignedNames.includes(name)); + return res.json(buildResult(assignedFiles)); + }); }); }); @@ -150,19 +152,18 @@ app.post('/api/admin/assign', requireAdmin, (req, res) => { }); app.post('/api/admin/update', requireAdmin, (req, res) => { - console.log("Triggering Docker Compose Update..."); - - // Wir fuehren den Docker compose pull/down/up aus. - // ACHTUNG: Der Server stuertzt danach ohnehin im Container ab (da er neu gestartet wird). - const cmd = "docker compose pull && docker compose down && docker compose up -d"; - - // Return early to ensure client gets success BEFORE container dies - res.json({ success: true, message: 'Update gestartet. Container wird neu gestartet...' }); - + console.log("Triggering Docker Update..."); + + // Neues Image pullen und Container neu starten (ohne docker-compose) + const image = 'ghcr.io/kroonk/photography:latest'; + const cmd = `docker pull ${image} && docker stop photography-website && docker rm photography-website`; + + res.json({ success: true, message: 'Update gestartet. Container wird neu gestartet... Seite laedt in 30 Sekunden neu.' }); + setTimeout(() => { - exec(cmd, { cwd: '/app' }, (err, stdout, stderr) => { + exec(cmd, (err, stdout, stderr) => { if (err) console.error("Update failed:", err); - else console.log("Update success:", stdout); + else console.log("Update triggered:", stdout); }); }, 1000); }); diff --git a/docker-compose.yml b/docker-compose.yml index 79fb9ef..c30d5b7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,10 +9,20 @@ services: - /volume1/nextcloud/data/Tom/files/Websitebilder:/app/public/images/fulls:ro # Thumbnails werden automatisch generiert und hier gespeichert - thumbs:/app/public/images/thumbs - # SQLite Database Volume + # SQLite Database (persistent) - database:/app/data - # Docker Socket fuer One-Click Update + restart: unless-stopped + + # Watchtower: Prueft automatisch auf neue Docker-Images und aktualisiert den Container + watchtower: + image: containrrr/watchtower + container_name: watchtower + volumes: - /var/run/docker.sock:/var/run/docker.sock + environment: + - WATCHTOWER_CLEANUP=true + - WATCHTOWER_POLL_INTERVAL=300 + - WATCHTOWER_LABEL_ENABLE=false restart: unless-stopped volumes: diff --git a/runs.json b/runs.json deleted file mode 100644 index 76cb3e325bce176d874d3334cf22192dbb723126..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 334 zcmZ{g!485j5Jcx};y)O?ihAJ02YB=s2m%UGz|az7jK8kF#bAty*|goA*_m$peB3nE zL^C2#twN4Sbs!5A59n5{JlTe`SEC$e@ukYaGZ<4hbf&BdtWg5m>GW|bVZ6@V5Nk!e zl}=0Ut6)m{cT^eb8onFm@nk93=<291baXmuk9$iMQrT=ybC8PDgZ=LcQJO=DvuO*a dYEjt~hF^c)Xnyvto}TevBCQos+5g>#{RO#>ISK#(