first commit

This commit is contained in:
Johan LEROY
2026-04-21 14:14:03 +02:00
commit 9e9bc0f23d
124 changed files with 13294 additions and 0 deletions

46
src/lib/auth.ts Normal file
View File

@@ -0,0 +1,46 @@
import { SignJWT, jwtVerify } from 'jose';
import bcrypt from 'bcryptjs';
// Astro dev (Vite) expose les .env via import.meta.env ; Node runtime prod via process.env.
// On lit les deux pour couvrir tous les cas (dev, preview, prod Plesk Node).
const env = {
...(typeof process !== 'undefined' ? process.env : {}),
...(import.meta.env as Record<string, string | undefined>),
};
const ADMIN_PASSWORD_HASH = (env.ADMIN_PASSWORD_HASH as string | undefined) ?? '';
const JWT_SECRET =
(env.ADMIN_JWT_SECRET as string | undefined) ?? 'dev-secret-change-me-in-production';
const SECRET_KEY = new TextEncoder().encode(JWT_SECRET);
export const ADMIN_COOKIE = 'portfolio_admin';
export async function verifyPassword(password: string): Promise<boolean> {
if (!ADMIN_PASSWORD_HASH) {
console.error('[auth] ADMIN_PASSWORD_HASH manquant dans .env');
return false;
}
return bcrypt.compare(password, ADMIN_PASSWORD_HASH);
}
export async function createSession(): Promise<string> {
return new SignJWT({ role: 'admin' })
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('7d')
.sign(SECRET_KEY);
}
export async function verifySession(token: string | undefined): Promise<boolean> {
if (!token) return false;
try {
await jwtVerify(token, SECRET_KEY);
return true;
} catch {
return false;
}
}
export function hashPasswordCLI(password: string): string {
return bcrypt.hashSync(password, 12);
}

40
src/lib/content.ts Normal file
View File

@@ -0,0 +1,40 @@
import { readFile, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { CONTENT_SCHEMAS, type ContentKey } from '../content/schemas';
import type { z } from 'zod';
const CONTENT_DIR = join(process.cwd(), 'public', 'content');
type ContentData<K extends ContentKey> = z.infer<(typeof CONTENT_SCHEMAS)[K]>;
export async function loadContent<K extends ContentKey>(key: K): Promise<ContentData<K>> {
const filePath = join(CONTENT_DIR, `${key}.json`);
const raw = await readFile(filePath, 'utf-8');
const parsed = JSON.parse(raw);
const schema = CONTENT_SCHEMAS[key];
return schema.parse(parsed) as ContentData<K>;
}
export async function saveContent<K extends ContentKey>(
key: K,
data: unknown,
): Promise<ContentData<K>> {
const schema = CONTENT_SCHEMAS[key];
const validated = schema.parse(data) as ContentData<K>;
const filePath = join(CONTENT_DIR, `${key}.json`);
await writeFile(filePath, JSON.stringify(validated, null, 2) + '\n', 'utf-8');
return validated;
}
export async function loadAll() {
const [profile, skills, experiences, formations, projects, interests, site] = await Promise.all([
loadContent('profile'),
loadContent('skills'),
loadContent('experiences'),
loadContent('formations'),
loadContent('projects'),
loadContent('interests'),
loadContent('site'),
]);
return { profile, skills, experiences, formations, projects, interests, site };
}