165 lines
6.0 KiB
JavaScript
165 lines
6.0 KiB
JavaScript
const CompanyStampRepository = require('../../../repositories/stamp/CompanyStampRepository');
|
|
const UnitOfWork = require('../../../database/UnitOfWork');
|
|
const { logger } = require('../../../middleware/logger');
|
|
|
|
const ALLOWED_MIME = new Set(['image/png', 'image/jpeg', 'image/webp']);
|
|
const MAX_IMAGE_BYTES = 500 * 1024; // 500 KB
|
|
const MAX_BASE64_LENGTH = Math.ceil((MAX_IMAGE_BYTES / 3) * 4) + 16; // safety
|
|
const STAMP_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
const _cache = new Map(); // companyId -> { stamp, ts }
|
|
const PRIMARY_COMPANY_ID = 1; // Our own company (single stamp policy)
|
|
|
|
function stripDataUri(b64, providedMime) {
|
|
if (!b64) return { pure: '', mime: providedMime };
|
|
const m = /^data:([\w/+.-]+);base64,(.+)$/i.exec(b64);
|
|
if (m) return { mime: m[1], pure: m[2] };
|
|
return { pure: b64, mime: providedMime };
|
|
}
|
|
|
|
function validateBase64(str) {
|
|
return /^[A-Za-z0-9+/=\r\n]+$/.test(str);
|
|
}
|
|
|
|
function decodeSizeApprox(base64) {
|
|
const cleaned = base64.replace(/[\r\n]/g, '');
|
|
const len = cleaned.length;
|
|
if (!len) return 0;
|
|
const padding = (cleaned.endsWith('==') ? 2 : cleaned.endsWith('=') ? 1 : 0);
|
|
return (len * 3) / 4 - padding;
|
|
}
|
|
|
|
class CompanyStampService {
|
|
async uploadStamp({ user, base64, mimeType, label, activate = false }) {
|
|
if (!user || user.user_type !== 'company')
|
|
throw new Error('Only company users can upload stamps');
|
|
|
|
const companyId = user.id || user.userId;
|
|
|
|
// Enforce single-stamp rule for primary company BEFORE any mutation
|
|
if (companyId === PRIMARY_COMPANY_ID) {
|
|
const existingPrimary = await CompanyStampRepository.findByCompanyId(PRIMARY_COMPANY_ID);
|
|
if (existingPrimary && existingPrimary.length) {
|
|
const err = new Error('Primary company stamp already exists');
|
|
err.code = 'PRIMARY_STAMP_EXISTS';
|
|
err.existing = existingPrimary[0];
|
|
throw err; // controller will translate to preview response
|
|
}
|
|
}
|
|
|
|
const { pure, mime } = stripDataUri(base64, mimeType);
|
|
const finalMime = mime || mimeType;
|
|
if (!ALLOWED_MIME.has(finalMime)) throw new Error('Unsupported MIME type');
|
|
if (!validateBase64(pure)) throw new Error('Invalid base64 data');
|
|
if (pure.length > MAX_BASE64_LENGTH) throw new Error('Image too large (base64 length)');
|
|
const bytes = decodeSizeApprox(pure);
|
|
if (bytes > MAX_IMAGE_BYTES) throw new Error('Image exceeds size limit');
|
|
|
|
const uow = new UnitOfWork();
|
|
await uow.start();
|
|
try {
|
|
// default inactive until optionally activated
|
|
const created = await CompanyStampRepository.create(
|
|
{
|
|
company_id: companyId,
|
|
label,
|
|
mime_type: finalMime,
|
|
image_base64: pure,
|
|
is_active: !!activate // treat activate flag directly (single stamp ok)
|
|
},
|
|
uow.connection
|
|
);
|
|
|
|
if (activate) {
|
|
// For primary company single stamp: no need to deactivate; for others keep old logic
|
|
if (companyId !== PRIMARY_COMPANY_ID) {
|
|
await CompanyStampRepository.deactivateAllForCompany(companyId, uow.connection);
|
|
await CompanyStampRepository.activate(created.id, companyId, uow.connection);
|
|
created.is_active = true;
|
|
}
|
|
_cache.delete(companyId);
|
|
}
|
|
|
|
await uow.commit();
|
|
logger.info('CompanyStampService.uploadStamp:success', { id: created.id, activate, companyId });
|
|
return created;
|
|
} catch (e) {
|
|
await uow.rollback(e);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
async listCompanyStamps(user) {
|
|
if (!user || user.user_type !== 'company')
|
|
throw new Error('Forbidden');
|
|
return CompanyStampRepository.findByCompanyId(user.id || user.userId);
|
|
}
|
|
|
|
async getActiveStampForCompany(companyId) {
|
|
const cached = _cache.get(companyId);
|
|
const now = Date.now();
|
|
if (cached && now - cached.ts < STAMP_CACHE_TTL_MS) return cached.stamp;
|
|
|
|
const stamp = await CompanyStampRepository.findActiveByCompanyId(companyId);
|
|
_cache.set(companyId, { stamp, ts: now });
|
|
return stamp;
|
|
}
|
|
|
|
async getMyActive(user) {
|
|
if (!user || user.user_type !== 'company')
|
|
throw new Error('Forbidden');
|
|
return this.getActiveStampForCompany(user.id || user.userId);
|
|
}
|
|
|
|
async activateStamp(id, user) {
|
|
if (!user || user.user_type !== 'company')
|
|
throw new Error('Forbidden');
|
|
const companyId = user.id || user.userId;
|
|
const uow = new UnitOfWork();
|
|
await uow.start();
|
|
try {
|
|
const stamp = await CompanyStampRepository.findById(id, uow.connection);
|
|
if (!stamp || stamp.company_id !== companyId) throw new Error('Not found');
|
|
await CompanyStampRepository.deactivateAllForCompany(companyId, uow.connection);
|
|
await CompanyStampRepository.activate(id, companyId, uow.connection);
|
|
await uow.commit();
|
|
_cache.delete(companyId);
|
|
return { ...stamp, is_active: true };
|
|
} catch (e) {
|
|
await uow.rollback(e);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
async deleteStamp(id, user) {
|
|
if (!user || user.user_type !== 'company')
|
|
throw new Error('Forbidden');
|
|
const ok = await CompanyStampRepository.delete(id, user.id || user.userId);
|
|
if (ok) _cache.delete(user.id || user.userId);
|
|
return ok;
|
|
}
|
|
|
|
async getProfitPlanetStamp() {
|
|
// Tries cache (reuse normal cache key)
|
|
const cached = _cache.get(PRIMARY_COMPANY_ID);
|
|
const now = Date.now();
|
|
if (cached && now - cached.ts < STAMP_CACHE_TTL_MS) return cached.stamp;
|
|
// Accept active first; if none (single stamp not active), just any
|
|
let stamp = await CompanyStampRepository.findActiveByCompanyId(PRIMARY_COMPANY_ID);
|
|
if (!stamp) {
|
|
const all = await CompanyStampRepository.findByCompanyId(PRIMARY_COMPANY_ID);
|
|
stamp = all && all[0] ? all[0] : null;
|
|
}
|
|
_cache.set(PRIMARY_COMPANY_ID, { stamp, ts: now });
|
|
return stamp;
|
|
}
|
|
|
|
// Convenience for placeholder
|
|
async getProfitPlanetSignatureTag(sizes = { maxW: 300, maxH: 300 }) {
|
|
const stamp = await this.getProfitPlanetStamp();
|
|
if (!stamp) return '';
|
|
return `<img src="data:${stamp.mime_type};base64,${stamp.image_base64}" style="max-width:${sizes.maxW}px;max-height:${sizes.maxH}px;">`;
|
|
}
|
|
}
|
|
|
|
module.exports = new CompanyStampService();
|