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";
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
/* config options here */
|
output: "standalone",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default nextConfig;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "eslint",
|
"lint": "eslint",
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
"db:generate": "prisma generate",
|
"db:generate": "prisma generate",
|
||||||
"db:push": "prisma db push",
|
"db:push": "prisma db push",
|
||||||
"db:seed": "tsx prisma/seed.ts",
|
"db:seed": "tsx prisma/seed.ts",
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
// SQLite for local dev; switch to Postgres for production
|
// SQLite for local dev; switch to Postgres for production
|
||||||
|
|
||||||
generator client {
|
generator client {
|
||||||
provider = "prisma-client-js"
|
provider = "prisma-client-js"
|
||||||
|
binaryTargets = ["native", "debian-openssl-3.0.x", "linux-arm64-openssl-3.0.x"]
|
||||||
}
|
}
|
||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ const MOCK_ADMIN = {
|
|||||||
export async function POST(req: NextRequest) {
|
export async function POST(req: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const body = await req.json();
|
const body = await req.json();
|
||||||
const { email, password } = body;
|
const { email } = body;
|
||||||
|
|
||||||
// Accept any email for MVP; in prod validate against DB
|
// Accept any email for MVP; in prod validate against DB
|
||||||
if (!email) {
|
if (!email) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from "react";
|
import { useState, useEffect, useCallback } from "react";
|
||||||
|
import Link from "next/link";
|
||||||
import { useParams, useRouter } from "next/navigation";
|
import { useParams, useRouter } from "next/navigation";
|
||||||
import { CandidateForm } from "@/components/CandidateForm";
|
import { CandidateForm } from "@/components/CandidateForm";
|
||||||
import { DimensionCard } from "@/components/DimensionCard";
|
import { DimensionCard } from "@/components/DimensionCard";
|
||||||
@@ -70,10 +71,10 @@ export default function EvaluationDetailPage() {
|
|||||||
if (evalData?.template?.dimensions?.length > 0 && Array.isArray(templatesData)) {
|
if (evalData?.template?.dimensions?.length > 0 && Array.isArray(templatesData)) {
|
||||||
const tmpl = templatesData.find((t: { id: string }) => t.id === evalData.templateId);
|
const tmpl = templatesData.find((t: { id: string }) => t.id === evalData.templateId);
|
||||||
if (tmpl?.dimensions?.length) {
|
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 }) => ({
|
evalData.template.dimensions = evalData.template.dimensions.map((d: { id: string; suggestedQuestions?: string | null }) => ({
|
||||||
...d,
|
...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 (
|
return (
|
||||||
<div className="py-12 text-center font-mono text-xs text-zinc-600 dark:text-zinc-500">
|
<div className="py-12 text-center font-mono text-xs text-zinc-600 dark:text-zinc-500">
|
||||||
Évaluation introuvable.{" "}
|
É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
|
← dashboard
|
||||||
</a>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export default function NewEvaluationPage() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.finally(() => setLoading(false));
|
.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) => {
|
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 stored = localStorage.getItem("theme") as Theme | null;
|
||||||
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
|
||||||
const initial = stored ?? (prefersDark ? "dark" : "light");
|
const initial = stored ?? (prefersDark ? "dark" : "light");
|
||||||
setThemeState(initial);
|
queueMicrotask(() => {
|
||||||
setMounted(true);
|
setThemeState(initial);
|
||||||
|
setMounted(true);
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ describe("evaluationToCsvRows", () => {
|
|||||||
confidence: "high",
|
confidence: "high",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
} as Parameters<typeof evaluationToCsvRows>[0];
|
} as unknown as Parameters<typeof evaluationToCsvRows>[0];
|
||||||
|
|
||||||
const rows = evaluationToCsvRows(evalData);
|
const rows = evaluationToCsvRows(evalData);
|
||||||
expect(rows[0]).toContain("candidateName");
|
expect(rows[0]).toContain("candidateName");
|
||||||
|
|||||||
Reference in New Issue
Block a user