feat(F-151): completed feature

This commit is contained in:
chattie
2026-08-21 22:14:29 +02:00
parent c4d0206d5d
commit ef79c54006
9 changed files with 270 additions and 12 deletions

View File

@@ -7,8 +7,11 @@
const fs = require('fs');
const path = require('path');
const { randomUUID } = require('crypto');
const argon2 = require('argon2');
const { Client } = require('pg');
const DEFAULT_STORE_ID = '00000000-0000-0000-0000-000000000001';
// ── Read DATABASE_URL from .env ──────────────────────────────────────────────
const envPath = path.join(__dirname, '..', '.env');
if (fs.existsSync(envPath)) {
@@ -46,6 +49,7 @@ async function seed(client) {
console.log('Seeding database...\n');
// ── Clear existing seed data ──────────────────────────────────────────
await sql(client, 'DELETE FROM inventory_movements', [], 'clear-movements');
await sql(client, 'DELETE FROM inventory_stock', [], 'clear-stock');
await sql(client, 'DELETE FROM pricing_variant_prices', [], 'clear-prices');
await sql(client, 'DELETE FROM catalog_product_variants', [], 'clear-variants');
@@ -262,8 +266,8 @@ async function seed(client) {
);
await sql(
client,
'INSERT INTO inventory_stock (variant_id, available, reserved) VALUES ($1,$2,$3) ON CONFLICT (variant_id) DO UPDATE SET available=EXCLUDED.available, reserved=EXCLUDED.reserved',
[variantId, 25, 0],
'INSERT INTO inventory_stock (variant_id, store_id, available, reserved) VALUES ($1,$2,$3,$4) ON CONFLICT (variant_id, store_id) DO UPDATE SET available=EXCLUDED.available, reserved=EXCLUDED.reserved',
[variantId, DEFAULT_STORE_ID, 25, 0],
`stock-${sku}`,
);
}
@@ -291,6 +295,26 @@ async function seed(client) {
);
console.log('+ 1 shipping zone + 2 methods');
// ── 5. Backoffice admin ────────────────────────────────────────────
const adminEmail = process.env.SEED_ADMIN_EMAIL || 'admin@mdv.local';
const adminPassword = process.env.SEED_ADMIN_PASSWORD || 'Admin123456!';
if (process.env.NODE_ENV === 'production' && !process.env.SEED_ADMIN_PASSWORD) {
throw new Error('SEED_ADMIN_PASSWORD must be set when NODE_ENV=production');
}
const adminHash = await argon2.hash(adminPassword);
await sql(
client,
`INSERT INTO backoffice_users (email, password_hash, role, mfa_enrolled)
VALUES ($1, $2, 'admin', false)
ON CONFLICT (email) DO UPDATE
SET password_hash = EXCLUDED.password_hash,
role = 'admin',
updated_at = now()`,
[adminEmail, adminHash],
'backoffice-admin',
);
console.log(`+ backoffice admin (${adminEmail})`);
await client.query('COMMIT');
console.log('\nSeed complete!');
}

View File

@@ -1,4 +1,4 @@
import type { FastifyInstance, FastifyReply } from 'fastify';
import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
import type { FastifySchema } from 'fastify';
import type pg from 'pg';
import { z } from 'zod';
@@ -123,7 +123,7 @@ export async function registerBackofficeRoutes(
const input = parseResult.data;
try {
const result = await login.execute(input);
setCookie(reply, result.token, true);
setCookie(reply, result.token, isSecureRequest(request));
return reply
.code(200)
.send({ id: result.user.id, email: result.user.email, role: result.user.role });
@@ -142,7 +142,7 @@ export async function registerBackofficeRoutes(
app.post('/backoffice/auth/logout', { schema: logoutSchema }, async (request, reply) => {
const token = request.cookies[BACKOFFICE_SESSION_COOKIE_NAME];
await logout.execute(token);
clearCookie(reply, true);
clearCookie(reply, isSecureRequest(request));
return reply.code(204).send();
});
@@ -159,6 +159,12 @@ export async function registerBackofficeRoutes(
});
}
function isSecureRequest(request: FastifyRequest): boolean {
const forwardedProto = request.headers['x-forwarded-proto'];
const proto = Array.isArray(forwardedProto) ? forwardedProto[0] : forwardedProto;
return request.protocol === 'https' || proto === 'https';
}
function setCookie(reply: FastifyReply, token: string, secure: boolean): void {
void reply.setCookie(BACKOFFICE_SESSION_COOKIE_NAME, token, {
path: '/',