Refactor Docker Compose configuration to use dynamic volume paths, update deployment workflow to create necessary directories, and enhance Prisma schema with public visibility for evaluations. Improve access control in API routes and adjust evaluation creation logic to include public visibility. Fix minor issues in login and evaluation pages for better user experience.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 2m17s
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 2m17s
This commit is contained in:
@@ -17,5 +17,7 @@ jobs:
|
||||
DOCKER_BUILDKIT: 1
|
||||
COMPOSE_DOCKER_CLI_BUILD: 1
|
||||
AUTH_SECRET: ${{ secrets.AUTH_SECRET }}
|
||||
DB_VOLUME_PATH: ${{ variables.DB_VOLUME_PATH }}
|
||||
run: |
|
||||
mkdir -p "${DB_VOLUME_PATH:-./data/db}"
|
||||
docker compose up -d --build
|
||||
|
||||
@@ -6,8 +6,5 @@ services:
|
||||
environment:
|
||||
- DATABASE_URL=file:/data/db/dev.db
|
||||
volumes:
|
||||
- db-data:/data/db
|
||||
- ${DB_VOLUME_PATH:-./data/db}:/data/db
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
db-data:
|
||||
|
||||
26
prisma/migrations/20260220120133_add_is_public/migration.sql
Normal file
26
prisma/migrations/20260220120133_add_is_public/migration.sql
Normal file
@@ -0,0 +1,26 @@
|
||||
-- RedefineTables
|
||||
PRAGMA defer_foreign_keys=ON;
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Evaluation" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"candidateName" TEXT NOT NULL,
|
||||
"candidateRole" TEXT NOT NULL,
|
||||
"candidateTeam" TEXT,
|
||||
"evaluatorName" TEXT NOT NULL,
|
||||
"evaluatorId" TEXT,
|
||||
"evaluationDate" DATETIME NOT NULL,
|
||||
"templateId" TEXT NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'draft',
|
||||
"findings" TEXT,
|
||||
"recommendations" TEXT,
|
||||
"isPublic" BOOLEAN NOT NULL DEFAULT false,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
CONSTRAINT "Evaluation_evaluatorId_fkey" FOREIGN KEY ("evaluatorId") REFERENCES "User" ("id") ON DELETE SET NULL ON UPDATE CASCADE,
|
||||
CONSTRAINT "Evaluation_templateId_fkey" FOREIGN KEY ("templateId") REFERENCES "Template" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||
);
|
||||
INSERT INTO "new_Evaluation" ("candidateName", "candidateRole", "candidateTeam", "createdAt", "evaluationDate", "evaluatorId", "evaluatorName", "findings", "id", "recommendations", "status", "templateId", "updatedAt") SELECT "candidateName", "candidateRole", "candidateTeam", "createdAt", "evaluationDate", "evaluatorId", "evaluatorName", "findings", "id", "recommendations", "status", "templateId", "updatedAt" FROM "Evaluation";
|
||||
DROP TABLE "Evaluation";
|
||||
ALTER TABLE "new_Evaluation" RENAME TO "Evaluation";
|
||||
PRAGMA foreign_keys=ON;
|
||||
PRAGMA defer_foreign_keys=OFF;
|
||||
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "sqlite"
|
||||
@@ -63,6 +63,7 @@ model Evaluation {
|
||||
dimensionScores DimensionScore[]
|
||||
auditLogs AuditLog[]
|
||||
sharedWith EvaluationShare[]
|
||||
isPublic Boolean @default(false) // visible par tous (ex. démo)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import bcrypt from "bcryptjs";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
@@ -434,7 +435,6 @@ async function main() {
|
||||
});
|
||||
if (!template) throw new Error("Template not found");
|
||||
|
||||
const bcrypt = require("bcryptjs");
|
||||
const adminHash = bcrypt.hashSync("admin123", 10);
|
||||
const admin = await prisma.user.upsert({
|
||||
where: { email: "admin@cars-front.local" },
|
||||
@@ -492,6 +492,7 @@ async function main() {
|
||||
evaluationDate: new Date(2025, 1, 15 + i),
|
||||
templateId: template.id,
|
||||
status: i === 0 ? "submitted" : "draft",
|
||||
isPublic: true, // démo visible par tous
|
||||
findings:
|
||||
i === 0
|
||||
? "Bonne maîtrise des outils et des prompts. Conception et exploration à renforcer. Alignement NFR correct."
|
||||
|
||||
@@ -3,15 +3,21 @@ import { Prisma } from "@prisma/client";
|
||||
import { auth } from "@/auth";
|
||||
import { prisma } from "@/lib/db";
|
||||
|
||||
async function canAccessEvaluation(evaluationId: string, userId: string, isAdmin: boolean) {
|
||||
async function canAccessEvaluation(
|
||||
evaluationId: string,
|
||||
userId: string,
|
||||
isAdmin: boolean,
|
||||
readOnly = false
|
||||
) {
|
||||
if (isAdmin) return true;
|
||||
const eval_ = await prisma.evaluation.findUnique({
|
||||
where: { id: evaluationId },
|
||||
select: { evaluatorId: true, sharedWith: { select: { userId: true } } },
|
||||
select: { evaluatorId: true, isPublic: true, sharedWith: { select: { userId: true } } },
|
||||
});
|
||||
if (!eval_) return false;
|
||||
if (eval_.evaluatorId === userId) return true;
|
||||
if (eval_.sharedWith.some((s) => s.userId === userId)) return true;
|
||||
if (readOnly && eval_.isPublic) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -45,7 +51,8 @@ export async function GET(
|
||||
const hasAccess = await canAccessEvaluation(
|
||||
id,
|
||||
session.user.id,
|
||||
session.user.role === "admin"
|
||||
session.user.role === "admin",
|
||||
true // read-only: public evals accessibles en lecture
|
||||
);
|
||||
if (!hasAccess) {
|
||||
return NextResponse.json({ error: "Accès refusé" }, { status: 403 });
|
||||
|
||||
@@ -23,6 +23,7 @@ export async function GET(req: NextRequest) {
|
||||
OR: [
|
||||
{ evaluatorId: userId },
|
||||
{ sharedWith: { some: { userId } } },
|
||||
{ isPublic: true },
|
||||
],
|
||||
}),
|
||||
},
|
||||
|
||||
@@ -77,7 +77,7 @@ export default function LoginPage() {
|
||||
<p className="mt-4 font-mono text-xs text-zinc-500 dark:text-zinc-400">
|
||||
Pas de compte ?{" "}
|
||||
<Link href="/auth/signup" className="text-cyan-600 dark:text-cyan-400 hover:underline">
|
||||
S'inscrire
|
||||
S'inscrire
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -25,7 +25,7 @@ export default function NewEvaluationPage() {
|
||||
const display = session.user.name || session.user.email || "";
|
||||
setForm((f) => ({ ...f, evaluatorName: display }));
|
||||
}
|
||||
}, [session?.user?.name, session?.user?.email]);
|
||||
}, [session?.user]);
|
||||
|
||||
useEffect(() => {
|
||||
fetch("/api/templates")
|
||||
|
||||
@@ -86,13 +86,13 @@ export function DimensionCard({ dimension, score, index, evaluationId, onScoreCh
|
||||
useEffect(() => {
|
||||
if (evaluationId && typeof window !== "undefined") {
|
||||
const stored = getStoredExpanded(evaluationId, dimension.id);
|
||||
if (stored !== null) setExpanded(stored);
|
||||
if (stored !== null) queueMicrotask(() => setExpanded(stored));
|
||||
}
|
||||
}, [evaluationId, dimension.id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (collapseAllTrigger != null && collapseAllTrigger > 0) {
|
||||
setExpanded(false);
|
||||
queueMicrotask(() => setExpanded(false));
|
||||
if (evaluationId) setStoredExpanded(evaluationId, dimension.id, false);
|
||||
}
|
||||
}, [collapseAllTrigger, evaluationId, dimension.id]);
|
||||
|
||||
@@ -32,11 +32,11 @@ export function ShareModal({
|
||||
sharedWith,
|
||||
onUpdate,
|
||||
}: ShareModalProps) {
|
||||
if (!isOpen) return null;
|
||||
|
||||
const [shareUserId, setShareUserId] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
const availableUsers = users.filter(
|
||||
(u) => u.id !== evaluatorId && !sharedWith.some((s) => s.user.id === u.id)
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user