Update Next.js configuration for standalone output; add type checking script to package.json; enhance Prisma schema with binary targets; modify authentication API to only require email; improve evaluation detail page with Link component; add ESLint directive in new evaluation page; adjust theme provider for state setting; refine export-utils test type assertion.
This commit is contained in:
12
.dockerignore
Normal file
12
.dockerignore
Normal file
@@ -0,0 +1,12 @@
|
||||
node_modules
|
||||
.next
|
||||
.git
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
*.log
|
||||
.DS_Store
|
||||
coverage
|
||||
.playwright
|
||||
prisma/dev.db
|
||||
prisma/*.db
|
||||
51
Dockerfile
Normal file
51
Dockerfile
Normal file
@@ -0,0 +1,51 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
FROM node:20-bookworm-slim AS base
|
||||
RUN apt-get update -y && apt-get install -y openssl && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Dependencies
|
||||
FROM base AS deps
|
||||
WORKDIR /app
|
||||
RUN corepack enable pnpm
|
||||
COPY package.json pnpm-lock.yaml* ./
|
||||
RUN pnpm install --frozen-lockfile 2>/dev/null || pnpm install
|
||||
|
||||
# Builder
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
RUN corepack enable pnpm
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
RUN pnpm exec prisma generate
|
||||
RUN pnpm run build
|
||||
|
||||
# Runner
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/prisma ./prisma
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV PORT=3000
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
|
||||
COPY docker-entrypoint.sh /entrypoint.sh
|
||||
RUN apt-get update -y && apt-get install -y gosu && rm -rf /var/lib/apt/lists/* \
|
||||
&& chmod +x /entrypoint.sh
|
||||
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
CMD ["sh", "-c", "npx prisma db push && npx prisma db seed && node server.js"]
|
||||
2
Dockerfile.dev
Normal file
2
Dockerfile.dev
Normal file
@@ -0,0 +1,2 @@
|
||||
FROM node:20-bookworm-slim
|
||||
RUN apt-get update -y && apt-get install -y openssl && rm -rf /var/lib/apt/lists/*
|
||||
21
docker-compose.dev.yml
Normal file
21
docker-compose.dev.yml
Normal file
@@ -0,0 +1,21 @@
|
||||
# Dev avec hot reload (source montée)
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.dev
|
||||
image: iag-dev-evaluator-dev
|
||||
working_dir: /app
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- DATABASE_URL=file:/data/db/dev.db
|
||||
- WATCHPACK_POLLING=true
|
||||
volumes:
|
||||
- .:/app
|
||||
- db-data:/data/db
|
||||
- /app/node_modules # anonymous volume pour éviter écrasement
|
||||
command: sh -c "corepack enable pnpm && pnpm install && pnpm exec prisma generate && pnpm exec prisma db push && pnpm run dev"
|
||||
|
||||
volumes:
|
||||
db-data:
|
||||
29
docker-compose.postgres.yml
Normal file
29
docker-compose.postgres.yml
Normal file
@@ -0,0 +1,29 @@
|
||||
# Variante Postgres (schema.prisma doit avoir provider = "postgresql")
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://evaluator:secret@db:5432/evaluator
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
restart: unless-stopped
|
||||
|
||||
db:
|
||||
image: postgres:16-alpine
|
||||
environment:
|
||||
POSTGRES_USER: evaluator
|
||||
POSTGRES_PASSWORD: secret
|
||||
POSTGRES_DB: evaluator
|
||||
volumes:
|
||||
- pg-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U evaluator"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
volumes:
|
||||
pg-data:
|
||||
13
docker-compose.yml
Normal file
13
docker-compose.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- DATABASE_URL=file:/data/db/dev.db
|
||||
volumes:
|
||||
- db-data:/data/db
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
db-data:
|
||||
5
docker-entrypoint.sh
Normal file
5
docker-entrypoint.sh
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
mkdir -p /data/db
|
||||
chown -R nextjs:nodejs /data/db
|
||||
exec gosu nextjs "$@"
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
/* config options here */
|
||||
output: "standalone",
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "eslint",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"db:generate": "prisma generate",
|
||||
"db:push": "prisma db push",
|
||||
"db:seed": "tsx prisma/seed.ts",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
binaryTargets = ["native", "debian-openssl-3.0.x", "linux-arm64-openssl-3.0.x"]
|
||||
}
|
||||
|
||||
datasource db {
|
||||
|
||||
@@ -13,7 +13,7 @@ const MOCK_ADMIN = {
|
||||
export async function POST(req: NextRequest) {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { email, password } = body;
|
||||
const { email } = body;
|
||||
|
||||
// Accept any email for MVP; in prod validate against DB
|
||||
if (!email) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import Link from "next/link";
|
||||
import { useParams, useRouter } from "next/navigation";
|
||||
import { CandidateForm } from "@/components/CandidateForm";
|
||||
import { DimensionCard } from "@/components/DimensionCard";
|
||||
@@ -70,10 +71,10 @@ export default function EvaluationDetailPage() {
|
||||
if (evalData?.template?.dimensions?.length > 0 && Array.isArray(templatesData)) {
|
||||
const tmpl = templatesData.find((t: { id: string }) => t.id === evalData.templateId);
|
||||
if (tmpl?.dimensions?.length) {
|
||||
const dimMap = new Map(tmpl.dimensions.map((d: { id: string }) => [d.id, d]));
|
||||
const dimMap = new Map(tmpl.dimensions.map((d: { id: string; suggestedQuestions?: string | null }) => [d.id, d]));
|
||||
evalData.template.dimensions = evalData.template.dimensions.map((d: { id: string; suggestedQuestions?: string | null }) => ({
|
||||
...d,
|
||||
suggestedQuestions: d.suggestedQuestions ?? dimMap.get(d.id)?.suggestedQuestions,
|
||||
suggestedQuestions: d.suggestedQuestions ?? (dimMap.get(d.id) as { suggestedQuestions?: string | null } | undefined)?.suggestedQuestions,
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -187,9 +188,9 @@ export default function EvaluationDetailPage() {
|
||||
return (
|
||||
<div className="py-12 text-center font-mono text-xs text-zinc-600 dark:text-zinc-500">
|
||||
Évaluation introuvable.{" "}
|
||||
<a href="/" className="text-cyan-600 dark:text-cyan-400 hover:underline">
|
||||
<Link href="/" className="text-cyan-600 dark:text-cyan-400 hover:underline">
|
||||
← dashboard
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ export default function NewEvaluationPage() {
|
||||
}
|
||||
})
|
||||
.finally(() => setLoading(false));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- run once on mount to fetch templates and set initial templateId
|
||||
}, []);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
|
||||
@@ -20,8 +20,10 @@ export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||
const stored = localStorage.getItem("theme") as Theme | null;
|
||||
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||
const initial = stored ?? (prefersDark ? "dark" : "light");
|
||||
queueMicrotask(() => {
|
||||
setThemeState(initial);
|
||||
setMounted(true);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -79,7 +79,7 @@ describe("evaluationToCsvRows", () => {
|
||||
confidence: "high",
|
||||
},
|
||||
],
|
||||
} as Parameters<typeof evaluationToCsvRows>[0];
|
||||
} as unknown as Parameters<typeof evaluationToCsvRows>[0];
|
||||
|
||||
const rows = evaluationToCsvRows(evalData);
|
||||
expect(rows[0]).toContain("candidateName");
|
||||
|
||||
Reference in New Issue
Block a user