diff --git a/apps/web/src/hooks.server.ts b/apps/web/src/hooks.server.ts new file mode 100644 index 0000000..ea643aa --- /dev/null +++ b/apps/web/src/hooks.server.ts @@ -0,0 +1,14 @@ +import type { Handle } from '@sveltejs/kit'; + +export const handle: Handle = async ({ event, resolve }) => { + // Handler mínimo (sin sesión aún). Añadimos cabeceras de seguridad básicas. + const response = await resolve(event); + try { + response.headers.set('X-Frame-Options', 'DENY'); + response.headers.set('Referrer-Policy', 'no-referrer'); + response.headers.set('X-Content-Type-Options', 'nosniff'); + } catch { + // Ignorar si la implementación de Response no permite set() + } + return response; +}; diff --git a/apps/web/src/lib/server/db.ts b/apps/web/src/lib/server/db.ts new file mode 100644 index 0000000..8077e66 --- /dev/null +++ b/apps/web/src/lib/server/db.ts @@ -0,0 +1,39 @@ +import { Database } from 'bun:sqlite'; +import { mkdirSync } from 'fs'; +import { dirname } from 'path'; +import { resolveDbAbsolutePath } from './env'; + +function applyDefaultPragmas(instance: Database): void { + try { + instance.exec(`PRAGMA busy_timeout = 5000;`); + // Intentar activar WAL (si no es soportado, SQLite devolverá 'memory' u otro modo) + instance.query(`PRAGMA journal_mode = WAL`).get(); + instance.exec(`PRAGMA synchronous = NORMAL;`); + instance.exec(`PRAGMA wal_autocheckpoint = 1000;`); + // Asegurar claves foráneas siempre activas + instance.exec(`PRAGMA foreign_keys = ON;`); + } catch (e) { + console.warn('[web/db] No se pudieron aplicar PRAGMAs (WAL, busy_timeout...):', e); + } +} + +/** + * Abre la BD compartida sin ejecutar migraciones (las realiza el proceso del bot). + */ +export function openDb(filename: string = 'tasks.db'): Database { + const absolutePath = resolveDbAbsolutePath(filename); + + // Crear directorio padre si no existe + try { + mkdirSync(dirname(absolutePath), { recursive: true }); + } catch (err: any) { + if (err?.code !== 'EEXIST') throw err; + } + + const instance = new Database(absolutePath); + applyDefaultPragmas(instance); + return instance; +} + +// Instancia por defecto +export const db = openDb(); diff --git a/apps/web/src/lib/server/env.ts b/apps/web/src/lib/server/env.ts new file mode 100644 index 0000000..0ce9dfd --- /dev/null +++ b/apps/web/src/lib/server/env.ts @@ -0,0 +1,27 @@ +import { join, resolve } from 'path'; + +const env = process.env; + +/** + * Resuelve la ruta absoluta al archivo de la base de datos SQLite compartida. + * Prioridad: + * 1) DB_PATH (ruta completa al archivo) + * 2) DATA_DIR + filename (por defecto ./data/tasks.db) + */ +export function resolveDbAbsolutePath(filename: string = 'tasks.db'): string { + const dbPathEnv = (env.DB_PATH || '').trim(); + if (dbPathEnv) { + return resolve(dbPathEnv); + } + const dataDir = env.DATA_DIR ? String(env.DATA_DIR) : 'data'; + return resolve(join(dataDir, filename)); +} + +export const WEB_BASE_URL = (env.WEB_BASE_URL || '').trim(); +export const COOKIE_SECRET = (env.COOKIE_SECRET || '').trim(); + +const SESSION_IDLE_TTL_MIN = Number(env.SESSION_IDLE_TTL_MIN || 120); +export const sessionIdleTtlMs = Math.max(1, Math.floor(SESSION_IDLE_TTL_MIN)) * 60 * 1000; + +export const NODE_ENV = (env.NODE_ENV || 'development').trim().toLowerCase(); +export const isProd = () => NODE_ENV === 'production';