diff --git a/backend/Dockerfile b/backend/Dockerfile index c44d951..ddc1df3 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -39,8 +39,9 @@ RUN npm ci --omit=dev || npm install --production && \ RUN apk del python3 make g++ vips-dev # Copy built files from builder +# Hinweis: Drizzle-Migrationen werden nicht kopiert - das Schema wird zur +# Laufzeit von initDatabase() angelegt, src/db/migrations existiert nicht. COPY --from=builder /app/dist ./dist -COPY --from=builder /app/src/db/migrations ./dist/db/migrations # Copy migration script and migrated images COPY --from=builder /app/migrate-production.js ./migrate-production.js diff --git a/backend/src/services/git.service.ts b/backend/src/services/git.service.ts index b78f36c..2909c15 100644 --- a/backend/src/services/git.service.ts +++ b/backend/src/services/git.service.ts @@ -1,43 +1,84 @@ import simpleGit, { SimpleGit } from 'simple-git'; -import { mkdir, rm } from 'fs/promises'; +import { mkdir, rm, cp } from 'fs/promises'; +import { existsSync } from 'fs'; import path from 'path'; import { env } from '../config/env.js'; export class GitService { private git: SimpleGit; private workspaceDir: string; + private parentDir: string; private repoUrl: string; private token: string; constructor() { this.workspaceDir = env.GIT_WORKSPACE_DIR; + this.parentDir = path.dirname(this.workspaceDir); this.repoUrl = env.GIT_REPO_URL; this.token = env.GIT_TOKEN; this.git = simpleGit(); } async initialize() { - // Ensure workspace directory exists - await mkdir(this.workspaceDir, { recursive: true }); + // Elternverzeichnis muss existieren - simple-git startet git von dort aus + await mkdir(this.parentDir, { recursive: true }); // Add token to repo URL for authentication const authenticatedUrl = this.repoUrl.replace( 'https://', - `https://oauth2:${this.token}@` + `https://oauth2:${encodeURIComponent(this.token)}@` ); - try { - // Check if repo already exists - await this.git.cwd(this.workspaceDir); - await this.git.status(); - console.log('Repository already exists, pulling latest...'); - await this.git.pull(); - } catch { - // Clone if doesn't exist + let usable = false; + + if (existsSync(path.join(this.workspaceDir, '.git'))) { + try { + this.git = simpleGit(this.workspaceDir); + await this.git.status(); + console.log('Repository already exists, pulling latest...'); + await this.git.pull(); + usable = true; + } catch (error) { + // Token aus der Meldung entfernen - git gibt die Remote-URL mit aus + const msg = error instanceof Error ? error.message : String(error); + console.warn( + 'Existing workspace unusable, re-cloning:', + msg.replace(/\/\/[^@\s/]*@/g, '//***@') + ); + } + } + + if (!usable) { console.log('Cloning repository...'); + + // Hochgeladene Bilder liegen im Workspace und wuerden beim Loeschen + // verschwinden - vorher wegsichern, nach dem Clone zurueckspielen + const imagesDir = path.join(this.workspaceDir, 'public', 'images'); + const backupDir = path.join(this.parentDir, '.workspace-images-backup'); + const hasImages = existsSync(imagesDir); + + await rm(backupDir, { recursive: true, force: true }); + if (hasImages) { + await cp(imagesDir, backupDir, { recursive: true }); + } + await rm(this.workspaceDir, { recursive: true, force: true }); + + // Aus dem existierenden Elternverzeichnis klonen, nicht aus dem + // soeben geloeschten Zielverzeichnis (sonst: spawn git ENOENT) + this.git = simpleGit(this.parentDir); await this.git.clone(authenticatedUrl, this.workspaceDir); - await this.git.cwd(this.workspaceDir); + this.git = simpleGit(this.workspaceDir); + + if (hasImages) { + // force: false -> was schon im Repo liegt, bleibt unangetastet + await cp(backupDir, imagesDir, { + recursive: true, + force: false, + errorOnExist: false, + }); + await rm(backupDir, { recursive: true, force: true }); + } } // Configure git user @@ -47,6 +88,14 @@ export class GitService { async commitAndPush(message: string): Promise { await this.git.add('.'); + + const status = await this.git.status(); + if (status.isClean()) { + // Nichts zu committen - aktuellen Stand zurueckgeben statt zu scheitern + const current = await this.git.log({ maxCount: 1 }); + return current.latest?.hash || ''; + } + await this.git.commit(message); await this.git.push('origin', 'main'); @@ -59,7 +108,15 @@ export class GitService { } async reset() { - await this.git.reset(['--hard', 'HEAD']); - await this.git.clean('f', ['-d']); + // Nur zuruecksetzen wenn wirklich ein Repo da ist - sonst laeuft git + // im Prozess-Arbeitsverzeichnis und raeumt dort auf + if (!existsSync(path.join(this.workspaceDir, '.git'))) { + return; + } + + const git = simpleGit(this.workspaceDir); + await git.reset(['--hard', 'HEAD']); + // Uploads ausnehmen - die sind noch nicht committed und waeren sonst weg + await git.clean('f', ['-d', '-e', 'public/images']); } }