diff --git a/apps/api/src/lead-forms/lead-forms.controller.ts b/apps/api/src/lead-forms/lead-forms.controller.ts index 3572922..ca27233 100644 --- a/apps/api/src/lead-forms/lead-forms.controller.ts +++ b/apps/api/src/lead-forms/lead-forms.controller.ts @@ -106,7 +106,7 @@ export class LeadFormsController { const form = await this.leadFormsService.get(user.organizationId, id); const row = await this.leadFormsService.getByToken(form.publicToken); if (!row?.coverStorageKey) return { ok: false }; - res.set({ 'Content-Type': 'image/jpeg' }); + res.set({ 'Content-Type': this.leadFormsService.coverContentType(row.coverStorageKey) }); return new StreamableFile(this.files.openReadStream(row.coverStorageKey)); } diff --git a/apps/api/src/lead-forms/lead-forms.service.ts b/apps/api/src/lead-forms/lead-forms.service.ts index f68c2d1..16dc598 100644 --- a/apps/api/src/lead-forms/lead-forms.service.ts +++ b/apps/api/src/lead-forms/lead-forms.service.ts @@ -25,6 +25,13 @@ import { } from '../contacts/normalize'; import { LeadStagesService } from '../leads/lead-stages.service'; +const COVER_CONTENT_TYPES: Record = { + jpg: 'image/jpeg', + jpeg: 'image/jpeg', + png: 'image/png', + webp: 'image/webp', +}; + const DEFAULT_FIELDS: LeadFormFieldInput[] = [ { key: 'fullName', @@ -222,7 +229,7 @@ export class LeadFormsService { submitColor: row.submitColor || '#14b8a6', hasCover: Boolean(row.coverStorageKey), coverPath: row.coverStorageKey - ? `/api/public/forms/${row.publicToken}/cover` + ? `/api/public/forms/${row.publicToken}/cover?v=${row.updatedAt.getTime()}` : null, fields: row.fields.map((f) => ({ key: f.key, @@ -806,7 +813,10 @@ export class LeadFormsService { .toLowerCase() .replace(/[^a-z0-9]/g, '') .slice(0, 6); - const key = `lead-forms/${id}/cover.${ext || 'jpg'}`; + if (!COVER_CONTENT_TYPES[ext]) { + throw new BadRequestException('فرمت تصویر کاور باید PNG، JPG یا WEBP باشد'); + } + const key = `lead-forms/${id}/cover.${ext}`; await this.files.save(key, file.buffer); if (row.coverStorageKey && row.coverStorageKey !== key) { await this.files.remove(row.coverStorageKey); @@ -832,6 +842,11 @@ export class LeadFormsService { return row.coverStorageKey; } + coverContentType(key: string) { + const ext = key.split('.').pop()?.toLowerCase() ?? ''; + return COVER_CONTENT_TYPES[ext] ?? 'image/jpeg'; + } + async getByToken(token: string, onlyActive = false) { return this.prisma.leadForm.findFirst({ where: { publicToken: token, ...(onlyActive ? { isActive: true } : {}) }, diff --git a/apps/api/src/lead-forms/public-lead-forms.controller.ts b/apps/api/src/lead-forms/public-lead-forms.controller.ts index dd9aa42..157ec0d 100644 --- a/apps/api/src/lead-forms/public-lead-forms.controller.ts +++ b/apps/api/src/lead-forms/public-lead-forms.controller.ts @@ -33,7 +33,10 @@ export class PublicLeadFormsController { ) { const row = await this.leadFormsService.getByToken(token); if (!row?.coverStorageKey) throw new NotFoundException('کاور پیدا نشد'); - res.set({ 'Content-Type': 'image/jpeg', 'Cache-Control': 'public, max-age=3600' }); + res.set({ + 'Content-Type': this.leadFormsService.coverContentType(row.coverStorageKey), + 'Cache-Control': 'public, max-age=3600', + }); return new StreamableFile(this.files.openReadStream(row.coverStorageKey)); } diff --git a/apps/web/src/components/MessageIntervalPicker.vue b/apps/web/src/components/MessageIntervalPicker.vue index aa3a6e8..a5e9001 100644 --- a/apps/web/src/components/MessageIntervalPicker.vue +++ b/apps/web/src/components/MessageIntervalPicker.vue @@ -1,5 +1,6 @@ @@ -116,7 +113,7 @@ onUnmounted(() => {
- +