feat(billing): cap each owner's stored file volume

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-11-02 15:44:36 +03:30
co-authored by Cursor
parent e6e90aabf3
commit 28938e8f77
20 changed files with 647 additions and 22 deletions
+8
View File
@@ -38,6 +38,7 @@ import {
} from '../common/custom-fields';
import { emptyToNull, normalizePhone } from '../contacts/normalize';
import { LocalFileStorage } from '../storage/local-file.storage';
import { StorageQuotaService } from '../billing/storage-quota.service';
import { LeadStagesService } from './lead-stages.service';
import { buildSearchWhere, ci } from '../common/search';
import { GamificationService } from '../gamification/gamification.service';
@@ -91,6 +92,7 @@ export class LeadsService {
private readonly prisma: PrismaService,
private readonly stages: LeadStagesService,
private readonly files: LocalFileStorage,
private readonly storage: StorageQuotaService,
private readonly game: GamificationService,
private readonly sms: SmsGatewayService,
private readonly outboundSms: OutboundSmsLogService,
@@ -1308,6 +1310,9 @@ export class LeadsService {
const storageKey = path
.join(actor.organizationId, 'leads', id, `logo.${ext}`)
.replace(/\\/g, '/');
const prevBytes = prevKey ? await this.files.sizeOf(prevKey) : 0;
const delta = file.size - prevBytes;
if (delta > 0) await this.storage.assertCanAdd(actor.organizationId, delta);
await this.files.save(storageKey, file.buffer);
if (prevKey && prevKey !== storageKey) {
await this.files.remove(prevKey);
@@ -1318,13 +1323,16 @@ export class LeadsService {
include: leadInclude,
});
await this.addEvent(actor, id, 'note', 'لوگو به‌روز شد', null);
if (delta) await this.storage.addLocal(actor.organizationId, delta);
return this.toDto(row);
}
async removeLogo(actor: Actor, id: string): Promise<LeadDto> {
const existing = await this.findAccessible(actor, id);
if (existing.logoStorageKey) {
const bytes = await this.files.sizeOf(existing.logoStorageKey);
await this.files.remove(existing.logoStorageKey);
if (bytes) await this.storage.addLocal(actor.organizationId, -bytes);
}
const row = await this.prisma.lead.update({
where: { id },