import fs from 'node:fs'; import path from 'node:path'; import * as settingsRepo from '../settings/repository.js'; import { logoDir } from '../../middleware/uploadLogo.js'; import { logAudit } from '../../utils/audit.js'; import { requireEditVersion, requireUpdated } from '../../utils/editVersion.js'; import { HttpError } from '../../utils/http.js'; const COMPANY_KEYS = ['company_name', 'company_logo_filename', 'company_logo_mime', 'company_edit_version']; const DEFAULT_NAME = 'LinkedCorp'; export async function getCompanyInfo() { const values = await settingsRepo.getAll(COMPANY_KEYS); return { name: values.company_name || DEFAULT_NAME, edit_version: Number(values.company_edit_version), hasLogo: !!values.company_logo_filename && ['image/png', 'image/jpeg', 'image/webp'].includes(values.company_logo_mime), // El propio nombre de archivo (unico por subida) sirve de version para cache-busting del logo. logoVersion: values.company_logo_filename || null, }; } export async function getCompanyLogoFile() { const values = await settingsRepo.getAll(['company_logo_filename', 'company_logo_mime']); if (!values.company_logo_filename) return null; if (!['image/png', 'image/jpeg', 'image/webp'].includes(values.company_logo_mime)) return null; const filePath = path.join(logoDir, values.company_logo_filename); if (!fs.existsSync(filePath)) return null; return { filePath, mime: values.company_logo_mime || 'application/octet-stream' }; } export async function updateCompanyInfo({ name, file, editVersion }, actorId) { requireEditVersion(editVersion); const entries = {}; let oldLogoFilename; if (name !== undefined) { const trimmed = (name || '').trim(); if (!trimmed) throw new HttpError(400, 'El nombre de la empresa es obligatorio'); entries.company_name = trimmed; } if (file) { const bytes = await fs.promises.readFile(file.path); const png = bytes.subarray(0, 8).equals(Buffer.from([137, 80, 78, 71, 13, 10, 26, 10])); const jpeg = bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff; const webp = bytes.toString('ascii', 0, 4) === 'RIFF' && bytes.toString('ascii', 8, 12) === 'WEBP'; const actualMime = png ? 'image/png' : jpeg ? 'image/jpeg' : webp ? 'image/webp' : null; if (!actualMime || file.mimetype !== actualMime) { await fs.promises.unlink(file.path).catch(() => {}); throw new HttpError(400, 'El contenido del logo no coincide con PNG, JPG o WEBP'); } const values = await settingsRepo.getAll(['company_logo_filename']); oldLogoFilename = values.company_logo_filename; entries.company_logo_filename = file.filename; entries.company_logo_mime = actualMime; } try { requireUpdated(await settingsRepo.setManyIfVersion(entries, 'company_edit_version', editVersion)); } catch (err) { if (file) await fs.promises.unlink(file.path).catch(() => {}); throw err; } if (file && oldLogoFilename && oldLogoFilename !== file.filename) { await fs.promises.unlink(path.join(logoDir, oldLogoFilename)).catch(() => {}); } await logAudit({ userId: actorId, action: 'update', entityType: 'settings:company', entityId: null }); return getCompanyInfo(); }