diff --git a/app/Dockerfile b/Dockerfile similarity index 55% rename from app/Dockerfile rename to Dockerfile index 554d4de..93196c4 100644 --- a/app/Dockerfile +++ b/Dockerfile @@ -1,36 +1,47 @@ # 1. Stage: Abhängigkeiten installieren FROM node:20-alpine AS deps +RUN apk add --no-cache libc6-compat WORKDIR /app + COPY package.json package-lock.json* ./ RUN npm ci -# 2. Stage: Builder (Der Code wird kompiliert) +# 2. Stage: Builder (Code kompilieren) FROM node:20-alpine AS builder WORKDIR /app + COPY --from=deps /app/node_modules ./node_modules COPY . . -# Hier werden auch die Environment Variables für den Build gebraucht -# (In Dokploy setzt du diese später, aber für den Build ignorieren wir ESLint Fehler oft) -ENV NEXT_TELEMETRY_DISABLED 1 + +ENV NEXT_TELEMETRY_DISABLED=1 + RUN npm run build -# 3. Stage: Runner (Das eigentliche Image für Dokploy - winzig klein) +# 3. Stage: Runner (Production Image) FROM node:20-alpine AS runner WORKDIR /app -ENV NODE_ENV production -ENV NEXT_TELEMETRY_DISABLED 1 + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs -# Wir kopieren nur das Nötigste aus dem Builder COPY --from=builder /app/public ./public + +# Set correct permissions for prerender cache +RUN mkdir .next +RUN chown nextjs:nodejs .next + +# Kopiere den Standalone-Build und Static-Files COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static USER nextjs -EXPOSE 3000 -ENV PORT 3000 -ENV HOSTNAME "0.0.0.0" -CMD ["node", "server.js"] \ No newline at end of file +EXPOSE 3000 + +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +CMD ["node", "server.js"] diff --git a/app/globals.css b/app/globals.css index 77d3522..f5e3edc 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,76 +1,80 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - +@import "tailwindcss"; + +@theme { + --color-background: hsl(0 0% 100%); + --color-foreground: hsl(0 0% 3.9%); + --color-card: hsl(0 0% 100%); + --color-card-foreground: hsl(0 0% 3.9%); + --color-popover: hsl(0 0% 100%); + --color-popover-foreground: hsl(0 0% 3.9%); + --color-primary: hsl(0 0% 9%); + --color-primary-foreground: hsl(0 0% 98%); + --color-secondary: hsl(0 0% 96.1%); + --color-secondary-foreground: hsl(0 0% 9%); + --color-muted: hsl(0 0% 96.1%); + --color-muted-foreground: hsl(0 0% 45.1%); + --color-accent: hsl(0 0% 96.1%); + --color-accent-foreground: hsl(0 0% 9%); + --color-destructive: hsl(0 84.2% 60.2%); + --color-destructive-foreground: hsl(0 0% 98%); + --color-border: hsl(0 0% 89.8%); + --color-input: hsl(0 0% 89.8%); + --color-ring: hsl(0 0% 3.9%); + --radius: 0.5rem; +} + @layer base { :root { --background: 0 0% 100%; --foreground: 0 0% 3.9%; - --card: 0 0% 100%; --card-foreground: 0 0% 3.9%; - --popover: 0 0% 100%; --popover-foreground: 0 0% 3.9%; - --primary: 0 0% 9%; --primary-foreground: 0 0% 98%; - --secondary: 0 0% 96.1%; --secondary-foreground: 0 0% 9%; - --muted: 0 0% 96.1%; --muted-foreground: 0 0% 45.1%; - --accent: 0 0% 96.1%; --accent-foreground: 0 0% 9%; - --destructive: 0 84.2% 60.2%; --destructive-foreground: 0 0% 98%; - --border: 0 0% 89.8%; --input: 0 0% 89.8%; --ring: 0 0% 3.9%; - --radius: 0.5rem; } - + .dark { --background: 0 0% 3.9%; --foreground: 0 0% 98%; - --card: 0 0% 3.9%; --card-foreground: 0 0% 98%; - --popover: 0 0% 3.9%; --popover-foreground: 0 0% 98%; - --primary: 0 0% 98%; --primary-foreground: 0 0% 9%; - --secondary: 0 0% 14.9%; --secondary-foreground: 0 0% 98%; - --muted: 0 0% 14.9%; --muted-foreground: 0 0% 63.9%; - --accent: 0 0% 14.9%; --accent-foreground: 0 0% 98%; - --destructive: 0 62.8% 30.6%; --destructive-foreground: 0 0% 98%; - --border: 0 0% 14.9%; --input: 0 0% 14.9%; --ring: 0 0% 83.1%; } -} - -@layer base { + * { - @apply border-border; + border-color: hsl(var(--border)); } + body { - @apply bg-background text-foreground; + background-color: hsl(var(--background)); + color: hsl(var(--foreground)); } } diff --git a/app/layout.tsx b/app/layout.tsx index 9afb911..f35a0f0 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -24,7 +24,7 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + diff --git a/lib/auth/utils.ts b/lib/auth/utils.ts index ede1a94..dd57d20 100644 --- a/lib/auth/utils.ts +++ b/lib/auth/utils.ts @@ -3,14 +3,15 @@ import { DrizzleAdapter } from "@auth/drizzle-adapter"; import { DefaultSession, getServerSession, NextAuthOptions } from "next-auth"; import { Adapter } from "next-auth/adapters"; import { redirect } from "next/navigation"; -import { env } from "@/lib/env.mjs" - +import { z } from "zod"; +import AzureADProvider from "next-auth/providers/azure-ad"; declare module "next-auth" { interface Session { user: DefaultSession["user"] & { id: string; }; + accessToken?: string; } } @@ -24,16 +25,43 @@ export type AuthSession = { } | null; }; +const envSchema = z.object({ + AZURE_AD_CLIENT_ID: z.string().min(1), + AZURE_AD_CLIENT_SECRET: z.string().min(1), +}); + +export const env = envSchema.parse(process.env); + export const authOptions: NextAuthOptions = { adapter: DrizzleAdapter(db) as Adapter, callbacks: { - session: ({ session, user }) => { - session.user.id = user.id; + jwt: async ({ token, account }) => { + if (account) { + token.accessToken = account.access_token; + } + return token; + }, + session: ({ session, token, user }) => { + if (user) { + session.user.id = user.id; + } + if (token?.accessToken) { + session.accessToken = token.accessToken as string; + } return session; }, }, providers: [ - + AzureADProvider({ + clientId: env.AZURE_AD_CLIENT_ID, + clientSecret: env.AZURE_AD_CLIENT_SECRET, + tenantId: "common", // Multi-Tenancy Support + authorization: { + params: { + scope: "openid profile email offline_access User.Read", + }, + }, + }), ], }; diff --git a/lib/env.mjs b/lib/env.mjs index fe4402c..2aa65b3 100644 --- a/lib/env.mjs +++ b/lib/env.mjs @@ -11,7 +11,7 @@ export const env = createEnv({ NEXTAUTH_SECRET: process.env.NODE_ENV === "production" ? z.string().min(1) - : z.string().min(1).optional(), + : z.string().optional(), NEXTAUTH_URL: z.preprocess( // This makes Vercel deployments not fail if you don't set NEXTAUTH_URL // Since NextAuth.js automatically uses the VERCEL_URL if present. @@ -19,15 +19,21 @@ export const env = createEnv({ // VERCEL_URL doesn't include `https` so it cant be validated as a URL process.env.VERCEL_URL ? z.string().min(1) : z.string().url() ), - RESEND_API_KEY: z.string().min(1), - STRIPE_SECRET_KEY: z.string().min(1), - STRIPE_WEBHOOK_SECRET: z.string().min(1), + + // Azure AD (Microsoft Entra ID) - optional in development + AZURE_AD_CLIENT_ID: z.string().optional(), + AZURE_AD_CLIENT_SECRET: z.string().optional(), + + // Optional in development + RESEND_API_KEY: z.string().optional(), + STRIPE_SECRET_KEY: z.string().optional(), + STRIPE_WEBHOOK_SECRET: z.string().optional(), }, client: { - NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: z.string().min(1), - NEXT_PUBLIC_STRIPE_PRO_PRICE_ID: z.string().min(1), - NEXT_PUBLIC_STRIPE_MAX_PRICE_ID: z.string().min(1), - NEXT_PUBLIC_STRIPE_ULTRA_PRICE_ID: z.string().min(1), // NEXT_PUBLIC_PUBLISHABLE_KEY: z.string().min(1), + NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY: z.string().optional(), + NEXT_PUBLIC_STRIPE_PRO_PRICE_ID: z.string().optional(), + NEXT_PUBLIC_STRIPE_MAX_PRICE_ID: z.string().optional(), + NEXT_PUBLIC_STRIPE_ULTRA_PRICE_ID: z.string().optional(), }, // If you're using Next.js < 13.4.4, you'll need to specify the runtimeEnv manually // runtimeEnv: { diff --git a/next.config.ts b/next.config.ts index 66e1566..9397a74 100644 --- a/next.config.ts +++ b/next.config.ts @@ -2,6 +2,7 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { /* config options here */ + output: "standalone", // Erforderlich für Docker-Deployment reactCompiler: true, };