diff --git a/innungsapp/apps/admin/Dockerfile b/innungsapp/apps/admin/Dockerfile index 91f2744..abdbb86 100644 --- a/innungsapp/apps/admin/Dockerfile +++ b/innungsapp/apps/admin/Dockerfile @@ -1,9 +1,12 @@ # ============================================= # Stage 1: Dependencies # ============================================= -FROM node:20-alpine AS deps +FROM node:20-slim AS deps RUN corepack enable && corepack prepare pnpm@9.12.0 --activate +# Install OpenSSL for Prisma +RUN apt-get update && apt-get install -y openssl ca-certificates && rm -rf /var/lib/apt/lists/* + WORKDIR /app # Copy workspace config files @@ -17,9 +20,12 @@ RUN pnpm install --frozen-lockfile # ============================================= # Stage 2: Build # ============================================= -FROM node:20-alpine AS builder +FROM node:20-slim AS builder RUN corepack enable && corepack prepare pnpm@9.12.0 --activate +# Install OpenSSL for Prisma +RUN apt-get update && apt-get install -y openssl ca-certificates && rm -rf /var/lib/apt/lists/* + WORKDIR /app COPY --from=deps /app/node_modules ./node_modules @@ -28,7 +34,7 @@ COPY --from=deps /app/packages/shared/node_modules ./packages/shared/node_module COPY . . -# Generate Prisma client +# Generate Prisma client for Alpine Linux RUN pnpm --filter @innungsapp/shared prisma:generate # Build the admin app @@ -39,9 +45,12 @@ RUN pnpm --filter @innungsapp/admin build # ============================================= # Stage 3: Production Runner # ============================================= -FROM node:20-alpine AS runner +FROM node:20-slim AS runner RUN corepack enable && corepack prepare pnpm@9.12.0 --activate +# Install OpenSSL for Prisma +RUN apt-get update && apt-get install -y openssl ca-certificates wget && rm -rf /var/lib/apt/lists/* + WORKDIR /app ENV NODE_ENV=production @@ -51,17 +60,20 @@ ENV NEXT_TELEMETRY_DISABLED=1 RUN addgroup --system --gid 1001 nodejs && \ adduser --system --uid 1001 nextjs -# Copy built output +# Copy built output (standalone includes all necessary node_modules) COPY --from=builder /app/apps/admin/.next/standalone ./ COPY --from=builder /app/apps/admin/.next/static ./apps/admin/.next/static COPY --from=builder /app/apps/admin/public ./apps/admin/public # Copy Prisma schema + migrations for runtime migrations COPY --from=builder /app/packages/shared/prisma ./packages/shared/prisma -COPY --from=builder /app/node_modules/.pnpm ./node_modules/.pnpm -COPY --from=builder /app/node_modules/@prisma ./node_modules/@prisma -COPY --from=builder /app/node_modules/prisma ./node_modules/prisma -COPY --from=builder /app/node_modules/.bin/prisma ./node_modules/.bin/prisma + +# Copy Prisma Engine binaries directly to .next/server (where Next.js looks for them) +COPY --from=builder /app/node_modules/.pnpm/@prisma+client@5.22.0_prisma@5.22.0/node_modules/.prisma/client/libquery_engine-debian-openssl-3.0.x.so.node /app/apps/admin/.next/server/ +COPY --from=builder /app/node_modules/.pnpm/@prisma+client@5.22.0_prisma@5.22.0/node_modules/.prisma/client/schema.prisma /app/apps/admin/.next/server/ + +# Install Prisma CLI globally for runtime migrations +RUN npm install -g prisma@5.22.0 # Create uploads directory RUN mkdir -p /app/uploads && chown nextjs:nodejs /app/uploads diff --git a/innungsapp/apps/admin/app/[slug]/dashboard/mitglieder/page.tsx b/innungsapp/apps/admin/app/[slug]/dashboard/mitglieder/page.tsx index 83c8351..3031908 100644 --- a/innungsapp/apps/admin/app/[slug]/dashboard/mitglieder/page.tsx +++ b/innungsapp/apps/admin/app/[slug]/dashboard/mitglieder/page.tsx @@ -64,13 +64,13 @@ export default async function MitgliederPage(props: { } }) - const adminUserIds = new Set(admins.map(a => a.userId)) + const adminUserIds = new Set(admins.map((a: typeof admins[number]) => a.userId)) // Map userId → member record so admin entries show real member data - const memberByUserId = new Map(members.filter(m => m.userId).map(m => [m.userId!, m])) + const memberByUserId = new Map(members.filter((m: typeof members[number]) => m.userId).map((m: typeof members[number]) => [m.userId!, m])) const combinedList = [ // Include admins only if there's no status filter, or if filtering for 'aktiv' - ...(!statusFilter || statusFilter === 'aktiv' ? admins.map(a => { + ...(!statusFilter || statusFilter === 'aktiv' ? admins.map((a: typeof admins[number]) => { const m = memberByUserId.get(a.user.id) return { id: m ? m.id : `admin-${a.user.id}`, @@ -86,7 +86,7 @@ export default async function MitgliederPage(props: { role: 'Administrator', } }) : []), - ...members.filter(m => !adminUserIds.has(m.userId ?? '')).map(m => ({ + ...members.filter((m: typeof members[number]) => !adminUserIds.has(m.userId ?? '')).map((m: typeof members[number]) => ({ id: m.id, name: m.name, betrieb: m.betrieb, @@ -101,7 +101,7 @@ export default async function MitgliederPage(props: { })) ] - combinedList.sort((a, b) => a.name.localeCompare(b.name)) + combinedList.sort((a: typeof combinedList[number], b: typeof combinedList[number]) => a.name.localeCompare(b.name)) return (
diff --git a/innungsapp/apps/admin/app/[slug]/dashboard/news/[id]/page.tsx b/innungsapp/apps/admin/app/[slug]/dashboard/news/[id]/page.tsx index 351c7db..1e0dba8 100644 --- a/innungsapp/apps/admin/app/[slug]/dashboard/news/[id]/page.tsx +++ b/innungsapp/apps/admin/app/[slug]/dashboard/news/[id]/page.tsx @@ -43,7 +43,7 @@ export default function NewsEditPage({ params }: { params: Promise<{ id: string setBody(news.body) setKategorie(news.kategorie) if (news.attachments) { - setAttachments(news.attachments.map(a => ({ ...a, sizeBytes: a.sizeBytes ?? 0 }))) + setAttachments(news.attachments.map((a: typeof news.attachments[number]) => ({ ...a, sizeBytes: a.sizeBytes ?? 0 }))) } } }, [news]) diff --git a/innungsapp/apps/admin/app/[slug]/dashboard/news/page.tsx b/innungsapp/apps/admin/app/[slug]/dashboard/news/page.tsx index 408b0c5..62b0d05 100644 --- a/innungsapp/apps/admin/app/[slug]/dashboard/news/page.tsx +++ b/innungsapp/apps/admin/app/[slug]/dashboard/news/page.tsx @@ -30,8 +30,8 @@ export default async function NewsPage() { orderBy: [{ publishedAt: 'desc' }, { createdAt: 'desc' }], }) - const published = news.filter((n) => n.publishedAt) - const drafts = news.filter((n) => !n.publishedAt) + const published = news.filter((n: typeof news[number]) => n.publishedAt) + const drafts = news.filter((n: typeof news[number]) => !n.publishedAt) return (
@@ -56,7 +56,7 @@ export default async function NewsPage() {
- {drafts.map((n) => ( + {drafts.map((n: typeof drafts[number]) => ( - {published.map((n) => ( + {published.map((n: typeof published[number]) => (

{n.title}

@@ -96,7 +96,7 @@ export default async function NewsPage() {
{n.title} diff --git a/innungsapp/apps/admin/app/[slug]/dashboard/page.tsx b/innungsapp/apps/admin/app/[slug]/dashboard/page.tsx index daaa870..f9ddf86 100644 --- a/innungsapp/apps/admin/app/[slug]/dashboard/page.tsx +++ b/innungsapp/apps/admin/app/[slug]/dashboard/page.tsx @@ -68,7 +68,7 @@ export default async function DashboardPage() {
- {recentNews.map((n) => ( + {recentNews.map((n: typeof recentNews[number]) => (

{n.title}

@@ -99,7 +99,7 @@ export default async function DashboardPage() { {nextTermine.length === 0 && (

Keine bevorstehenden Termine

)} - {nextTermine.map((t) => ( + {nextTermine.map((t: typeof nextTermine[number]) => (

diff --git a/innungsapp/apps/admin/app/[slug]/dashboard/stellen/neu/page.tsx b/innungsapp/apps/admin/app/[slug]/dashboard/stellen/neu/page.tsx index 0295e2c..cafad80 100644 --- a/innungsapp/apps/admin/app/[slug]/dashboard/stellen/neu/page.tsx +++ b/innungsapp/apps/admin/app/[slug]/dashboard/stellen/neu/page.tsx @@ -64,13 +64,13 @@ export default function StelleNeuPage() { required value={form.memberId} onChange={(e) => { - const selected = members?.find((m) => m.id === e.target.value) + const selected = members?.find((m: NonNullable[number]) => m.id === e.target.value) setForm({ ...form, memberId: e.target.value, sparte: selected?.sparte ?? form.sparte }) }} className={inputClass} > - {members?.map((m) => ( + {members?.map((m: NonNullable[number]) => ( diff --git a/innungsapp/apps/admin/docker-entrypoint.sh b/innungsapp/apps/admin/docker-entrypoint.sh index 2cbed26..26c7bab 100644 --- a/innungsapp/apps/admin/docker-entrypoint.sh +++ b/innungsapp/apps/admin/docker-entrypoint.sh @@ -9,10 +9,10 @@ MIGRATIONS_DIR="./packages/shared/prisma/migrations" set -- "$MIGRATIONS_DIR"/*/migration.sql if [ -f "$1" ]; then echo "Applying Prisma migrations..." - node_modules/.bin/prisma migrate deploy --schema=./packages/shared/prisma/schema.prisma + npx prisma migrate deploy --schema=./packages/shared/prisma/schema.prisma else echo "No Prisma migrations found. Syncing schema with db push..." - node_modules/.bin/prisma db push --skip-generate --schema=./packages/shared/prisma/schema.prisma + npx prisma db push --skip-generate --schema=./packages/shared/prisma/schema.prisma fi echo "Starting Next.js server..." diff --git a/innungsapp/apps/admin/next.config.ts b/innungsapp/apps/admin/next.config.ts index 6e978b2..0bd439c 100644 --- a/innungsapp/apps/admin/next.config.ts +++ b/innungsapp/apps/admin/next.config.ts @@ -4,6 +4,13 @@ const nextConfig: NextConfig = { transpilePackages: ['@innungsapp/shared'], output: process.env.DOCKER_BUILD ? 'standalone' : undefined, experimental: {}, + // Include Prisma binaries in standalone build + outputFileTracingIncludes: { + '/': [ + './node_modules/.pnpm/@prisma+client@5.22.0_prisma@5.22.0/node_modules/.prisma/**/*', + './node_modules/.pnpm/@prisma+client@5.22.0_prisma@5.22.0/node_modules/@prisma/client/**/*', + ], + }, webpack: (config, { dev }) => { if (dev) { // Avoid filesystem cache writes on very low-disk dev machines (ENOSPC).