Ship loyalty club and form builder so production pull gets the new screens.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -11,11 +11,12 @@ import {
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
import { LEAD_FORM_FIELD_TYPES } from '@mento/shared';
|
||||
|
||||
const FIELD_TYPES = ['text', 'email', 'phone', 'textarea'] as const;
|
||||
const MAP_TO = [
|
||||
'person.firstName',
|
||||
'person.lastName',
|
||||
'person.fullName',
|
||||
'person.email',
|
||||
'person.mobile',
|
||||
'person.phone',
|
||||
@@ -23,6 +24,7 @@ const MAP_TO = [
|
||||
'deal.title',
|
||||
'deal.notes',
|
||||
'notes',
|
||||
'custom',
|
||||
] as const;
|
||||
|
||||
export class LeadFormFieldInputDto {
|
||||
@@ -36,8 +38,8 @@ export class LeadFormFieldInputDto {
|
||||
@MaxLength(120)
|
||||
label!: string;
|
||||
|
||||
@IsIn(FIELD_TYPES)
|
||||
fieldType!: (typeof FIELD_TYPES)[number];
|
||||
@IsIn([...LEAD_FORM_FIELD_TYPES])
|
||||
fieldType!: (typeof LEAD_FORM_FIELD_TYPES)[number];
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
@@ -48,6 +50,17 @@ export class LeadFormFieldInputDto {
|
||||
|
||||
@IsOptional()
|
||||
position?: number;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(160)
|
||||
placeholder?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@IsString({ each: true })
|
||||
@ArrayMaxSize(40)
|
||||
options?: string[];
|
||||
}
|
||||
|
||||
export class CreateLeadFormDto {
|
||||
@@ -56,6 +69,11 @@ export class CreateLeadFormDto {
|
||||
@MaxLength(120)
|
||||
name!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(400)
|
||||
description?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
createPerson?: boolean;
|
||||
@@ -89,11 +107,21 @@ export class CreateLeadFormDto {
|
||||
@ArrayMaxSize(50)
|
||||
assigneeIds?: string[];
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(80)
|
||||
submitLabel?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(20)
|
||||
submitColor?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => LeadFormFieldInputDto)
|
||||
@ArrayMaxSize(30)
|
||||
@ArrayMaxSize(40)
|
||||
fields?: LeadFormFieldInputDto[];
|
||||
}
|
||||
|
||||
@@ -104,6 +132,11 @@ export class UpdateLeadFormDto {
|
||||
@MaxLength(120)
|
||||
name?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(400)
|
||||
description?: string | null;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
isActive?: boolean;
|
||||
@@ -141,11 +174,21 @@ export class UpdateLeadFormDto {
|
||||
@ArrayMaxSize(50)
|
||||
assigneeIds?: string[];
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(80)
|
||||
submitLabel?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(20)
|
||||
submitColor?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => LeadFormFieldInputDto)
|
||||
@ArrayMaxSize(30)
|
||||
@ArrayMaxSize(40)
|
||||
fields?: LeadFormFieldInputDto[];
|
||||
}
|
||||
|
||||
|
||||
@@ -6,19 +6,30 @@ import {
|
||||
Param,
|
||||
Patch,
|
||||
Post,
|
||||
Res,
|
||||
StreamableFile,
|
||||
UploadedFile,
|
||||
UseGuards,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { memoryStorage } from 'multer';
|
||||
import type { Response } from 'express';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { CurrentUser } from '../auth/current-user.decorator';
|
||||
import { Roles } from '../auth/roles.decorator';
|
||||
import { RolesGuard } from '../auth/roles.guard';
|
||||
import { CreateLeadFormDto, UpdateLeadFormDto } from './dto';
|
||||
import { LeadFormsService } from './lead-forms.service';
|
||||
import { LocalFileStorage } from '../storage/local-file.storage';
|
||||
|
||||
@Controller('lead-forms')
|
||||
@UseGuards(JwtAuthGuard, RolesGuard)
|
||||
export class LeadFormsController {
|
||||
constructor(private readonly leadFormsService: LeadFormsService) {}
|
||||
constructor(
|
||||
private readonly leadFormsService: LeadFormsService,
|
||||
private readonly files: LocalFileStorage,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@Roles('owner', 'admin')
|
||||
@@ -54,6 +65,51 @@ export class LeadFormsController {
|
||||
return this.leadFormsService.update(user.organizationId, id, dto);
|
||||
}
|
||||
|
||||
@Get(':id/submissions')
|
||||
@Roles('owner', 'admin')
|
||||
submissions(
|
||||
@CurrentUser() user: { organizationId: string },
|
||||
@Param('id') id: string,
|
||||
) {
|
||||
return this.leadFormsService.listSubmissions(user.organizationId, id);
|
||||
}
|
||||
|
||||
@Post(':id/cover')
|
||||
@Roles('owner', 'admin')
|
||||
@UseInterceptors(
|
||||
FileInterceptor('file', { storage: memoryStorage(), limits: { fileSize: 4_000_000 } }),
|
||||
)
|
||||
uploadCover(
|
||||
@CurrentUser() user: { organizationId: string },
|
||||
@Param('id') id: string,
|
||||
@UploadedFile() file: Express.Multer.File,
|
||||
) {
|
||||
return this.leadFormsService.saveCover(user.organizationId, id, file);
|
||||
}
|
||||
|
||||
@Delete(':id/cover')
|
||||
@Roles('owner', 'admin')
|
||||
removeCover(
|
||||
@CurrentUser() user: { organizationId: string },
|
||||
@Param('id') id: string,
|
||||
) {
|
||||
return this.leadFormsService.removeCover(user.organizationId, id);
|
||||
}
|
||||
|
||||
@Get(':id/cover')
|
||||
@Roles('owner', 'admin')
|
||||
async cover(
|
||||
@CurrentUser() user: { organizationId: string },
|
||||
@Param('id') id: string,
|
||||
@Res({ passthrough: true }) res: Response,
|
||||
) {
|
||||
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' });
|
||||
return new StreamableFile(this.files.openReadStream(row.coverStorageKey));
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
@Roles('owner', 'admin')
|
||||
async remove(
|
||||
|
||||
@@ -13,6 +13,7 @@ import type {
|
||||
} from '@mento/shared';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { LocalFileStorage } from '../storage/local-file.storage';
|
||||
import {
|
||||
emptyToNull,
|
||||
normalizeEmail,
|
||||
@@ -21,44 +22,40 @@ import {
|
||||
|
||||
const DEFAULT_FIELDS: LeadFormFieldInput[] = [
|
||||
{
|
||||
key: 'firstName',
|
||||
label: 'نام',
|
||||
key: 'fullName',
|
||||
label: 'نام و نام خانوادگی',
|
||||
fieldType: 'text',
|
||||
required: true,
|
||||
mapTo: 'person.firstName',
|
||||
mapTo: 'person.fullName',
|
||||
placeholder: 'مثلاً علی منتظر',
|
||||
position: 0,
|
||||
},
|
||||
{
|
||||
key: 'lastName',
|
||||
label: 'نام خانوادگی',
|
||||
fieldType: 'text',
|
||||
required: true,
|
||||
mapTo: 'person.lastName',
|
||||
position: 1,
|
||||
},
|
||||
{
|
||||
key: 'mobile',
|
||||
label: 'موبایل',
|
||||
label: 'شماره تماس',
|
||||
fieldType: 'phone',
|
||||
required: true,
|
||||
mapTo: 'person.mobile',
|
||||
placeholder: '۰۹۱۲۱۲۳۴۵۶۷',
|
||||
position: 1,
|
||||
},
|
||||
{
|
||||
key: 'subject',
|
||||
label: 'موضوع درخواست',
|
||||
fieldType: 'select',
|
||||
required: true,
|
||||
mapTo: 'deal.title',
|
||||
options: ['مشاوره فروش', 'پشتیبانی', 'همکاری'],
|
||||
position: 2,
|
||||
},
|
||||
{
|
||||
key: 'email',
|
||||
label: 'ایمیل',
|
||||
fieldType: 'email',
|
||||
required: false,
|
||||
mapTo: 'person.email',
|
||||
position: 3,
|
||||
},
|
||||
{
|
||||
key: 'message',
|
||||
label: 'پیام',
|
||||
label: 'توضیحات',
|
||||
fieldType: 'textarea',
|
||||
required: false,
|
||||
mapTo: 'deal.notes',
|
||||
position: 4,
|
||||
placeholder: 'شرح درخواست خود را بنویسید',
|
||||
position: 3,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -66,15 +63,24 @@ type FormRow = Prisma.LeadFormGetPayload<{ include: { fields: true } }>;
|
||||
|
||||
@Injectable()
|
||||
export class LeadFormsService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly files: LocalFileStorage,
|
||||
) {}
|
||||
|
||||
async list(organizationId: string): Promise<LeadFormDto[]> {
|
||||
const rows = await this.prisma.leadForm.findMany({
|
||||
where: { organizationId },
|
||||
include: { fields: { orderBy: { position: 'asc' } } },
|
||||
include: {
|
||||
fields: { orderBy: { position: 'asc' } },
|
||||
_count: { select: { submissions: true } },
|
||||
},
|
||||
orderBy: { updatedAt: 'desc' },
|
||||
});
|
||||
return rows.map((row) => this.toDto(row));
|
||||
return rows.map((row) => ({
|
||||
...this.toDto(row),
|
||||
submissionCount: row._count.submissions,
|
||||
}));
|
||||
}
|
||||
|
||||
async get(organizationId: string, id: string): Promise<LeadFormDto> {
|
||||
@@ -94,6 +100,7 @@ export class LeadFormsService {
|
||||
data: {
|
||||
organizationId,
|
||||
name: dto.name.trim(),
|
||||
description: emptyToNull(dto.description ?? null),
|
||||
createPerson: dto.createPerson ?? true,
|
||||
createDeal: dto.createDeal ?? true,
|
||||
pipelineId: emptyToNull(dto.pipelineId ?? null),
|
||||
@@ -103,15 +110,10 @@ export class LeadFormsService {
|
||||
'پیام شما ثبت شد. بهزودی با شما تماس میگیریم.',
|
||||
redirectUrl: emptyToNull(dto.redirectUrl ?? null),
|
||||
assigneeIds: dto.assigneeIds ?? [],
|
||||
submitLabel: dto.submitLabel?.trim() || 'ارسال درخواست',
|
||||
submitColor: dto.submitColor?.trim() || '#14b8a6',
|
||||
fields: {
|
||||
create: fields.map((f, index) => ({
|
||||
key: f.key!,
|
||||
label: f.label,
|
||||
fieldType: f.fieldType,
|
||||
required: f.required ?? false,
|
||||
mapTo: f.mapTo,
|
||||
position: f.position ?? index,
|
||||
})),
|
||||
create: fields.map((f, index) => fieldCreateData(f, index)),
|
||||
},
|
||||
},
|
||||
include: { fields: { orderBy: { position: 'asc' } } },
|
||||
@@ -134,6 +136,13 @@ export class LeadFormsService {
|
||||
|
||||
const data: Prisma.LeadFormUpdateInput = {};
|
||||
if (dto.name !== undefined) data.name = dto.name.trim();
|
||||
if ('description' in dto) data.description = emptyToNull(dto.description ?? null);
|
||||
if (dto.submitLabel !== undefined) {
|
||||
data.submitLabel = dto.submitLabel.trim() || 'ارسال درخواست';
|
||||
}
|
||||
if (dto.submitColor !== undefined) {
|
||||
data.submitColor = dto.submitColor.trim() || '#14b8a6';
|
||||
}
|
||||
if (dto.isActive !== undefined) data.isActive = dto.isActive;
|
||||
if (dto.createPerson !== undefined) data.createPerson = dto.createPerson;
|
||||
if (dto.createDeal !== undefined) data.createDeal = dto.createDeal;
|
||||
@@ -156,14 +165,7 @@ export class LeadFormsService {
|
||||
data: {
|
||||
...data,
|
||||
fields: {
|
||||
create: fields.map((f, index) => ({
|
||||
key: f.key!,
|
||||
label: f.label,
|
||||
fieldType: f.fieldType,
|
||||
required: f.required ?? false,
|
||||
mapTo: f.mapTo,
|
||||
position: f.position ?? index,
|
||||
})),
|
||||
create: fields.map((f, index) => fieldCreateData(f, index)),
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -188,13 +190,24 @@ export class LeadFormsService {
|
||||
if (!row) throw new NotFoundException('فرم پیدا نشد یا غیرفعال است');
|
||||
return {
|
||||
name: row.name,
|
||||
description: row.description,
|
||||
thankYouMessage: row.thankYouMessage,
|
||||
fields: row.fields.map((f) => ({
|
||||
key: f.key,
|
||||
label: f.label,
|
||||
fieldType: f.fieldType,
|
||||
required: f.required,
|
||||
})),
|
||||
submitLabel: row.submitLabel || 'ارسال درخواست',
|
||||
submitColor: row.submitColor || '#14b8a6',
|
||||
hasCover: Boolean(row.coverStorageKey),
|
||||
coverPath: row.coverStorageKey
|
||||
? `/api/public/forms/${row.publicToken}/cover`
|
||||
: null,
|
||||
fields: row.fields
|
||||
.filter((f) => f.fieldType !== 'heading' || true)
|
||||
.map((f) => ({
|
||||
key: f.key,
|
||||
label: f.label,
|
||||
fieldType: f.fieldType,
|
||||
required: f.required,
|
||||
placeholder: f.placeholder,
|
||||
options: asOptions(f.options),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -209,6 +222,10 @@ export class LeadFormsService {
|
||||
if (!form) throw new NotFoundException('فرم پیدا نشد یا غیرفعال است');
|
||||
|
||||
const mapped = mapValues(form.fields, values);
|
||||
const extras = collectExtras(form.fields, values);
|
||||
if (extras) {
|
||||
mapped.notes = [mapped.notes, extras].filter(Boolean).join('\n');
|
||||
}
|
||||
|
||||
if (form.createPerson) {
|
||||
if (!mapped.firstName?.trim() && !mapped.lastName?.trim()) {
|
||||
@@ -239,6 +256,14 @@ export class LeadFormsService {
|
||||
);
|
||||
}
|
||||
|
||||
await this.prisma.leadFormSubmission.create({
|
||||
data: {
|
||||
formId: form.id,
|
||||
values: values as Prisma.InputJsonValue,
|
||||
personId,
|
||||
},
|
||||
});
|
||||
|
||||
if (form.createDeal) {
|
||||
await this.createDeal(form, mapped, personId, actorId, ownerId);
|
||||
} else if (personId && mapped.notes) {
|
||||
@@ -410,10 +435,70 @@ export class LeadFormsService {
|
||||
}
|
||||
}
|
||||
|
||||
async listSubmissions(organizationId: string, id: string) {
|
||||
await this.findInOrg(organizationId, id);
|
||||
const rows = await this.prisma.leadFormSubmission.findMany({
|
||||
where: { formId: id },
|
||||
orderBy: { createdAt: 'desc' },
|
||||
take: 200,
|
||||
});
|
||||
return rows.map((r) => ({
|
||||
id: r.id,
|
||||
values: asStringRecord(r.values),
|
||||
personId: r.personId,
|
||||
createdAt: r.createdAt.toISOString(),
|
||||
}));
|
||||
}
|
||||
|
||||
async saveCover(
|
||||
organizationId: string,
|
||||
id: string,
|
||||
file: { buffer: Buffer; originalname: string },
|
||||
) {
|
||||
if (!file?.buffer) throw new BadRequestException('فایل کاور ارسال نشده');
|
||||
const row = await this.findInOrg(organizationId, id);
|
||||
const ext = (file.originalname.split('.').pop() || 'jpg')
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]/g, '')
|
||||
.slice(0, 6);
|
||||
const key = `lead-forms/${id}/cover.${ext || 'jpg'}`;
|
||||
await this.files.save(key, file.buffer);
|
||||
if (row.coverStorageKey && row.coverStorageKey !== key) {
|
||||
await this.files.remove(row.coverStorageKey);
|
||||
}
|
||||
await this.prisma.leadForm.update({
|
||||
where: { id },
|
||||
data: { coverStorageKey: key },
|
||||
});
|
||||
return this.get(organizationId, id);
|
||||
}
|
||||
|
||||
async removeCover(organizationId: string, id: string) {
|
||||
const row = await this.findInOrg(organizationId, id);
|
||||
if (row.coverStorageKey) await this.files.remove(row.coverStorageKey);
|
||||
await this.prisma.leadForm.update({
|
||||
where: { id },
|
||||
data: { coverStorageKey: null },
|
||||
});
|
||||
return this.get(organizationId, id);
|
||||
}
|
||||
|
||||
coverKey(row: FormRow) {
|
||||
return row.coverStorageKey;
|
||||
}
|
||||
|
||||
async getByToken(token: string, onlyActive = false) {
|
||||
return this.prisma.leadForm.findFirst({
|
||||
where: { publicToken: token, ...(onlyActive ? { isActive: true } : {}) },
|
||||
include: { fields: true },
|
||||
});
|
||||
}
|
||||
|
||||
private toDto(row: FormRow): LeadFormDto {
|
||||
return {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
description: row.description,
|
||||
publicToken: row.publicToken,
|
||||
isActive: row.isActive,
|
||||
createPerson: row.createPerson,
|
||||
@@ -423,6 +508,9 @@ export class LeadFormsService {
|
||||
thankYouMessage: row.thankYouMessage,
|
||||
redirectUrl: row.redirectUrl,
|
||||
assigneeIds: row.assigneeIds,
|
||||
submitLabel: row.submitLabel || 'ارسال درخواست',
|
||||
submitColor: row.submitColor || '#14b8a6',
|
||||
hasCover: Boolean(row.coverStorageKey),
|
||||
fields: row.fields.map((f) => ({
|
||||
id: f.id,
|
||||
key: f.key,
|
||||
@@ -431,6 +519,8 @@ export class LeadFormsService {
|
||||
required: f.required,
|
||||
mapTo: f.mapTo,
|
||||
position: f.position,
|
||||
placeholder: f.placeholder,
|
||||
options: asOptions(f.options),
|
||||
})),
|
||||
publicPath: `/f/${row.publicToken}`,
|
||||
createdAt: row.createdAt.toISOString(),
|
||||
@@ -452,17 +542,30 @@ type MappedValues = {
|
||||
};
|
||||
|
||||
function mapValues(
|
||||
fields: Array<{ key: string; mapTo: string; required: boolean; label: string }>,
|
||||
fields: Array<{
|
||||
key: string;
|
||||
mapTo: string;
|
||||
required: boolean;
|
||||
label: string;
|
||||
fieldType?: string;
|
||||
}>,
|
||||
values: Record<string, string>,
|
||||
): MappedValues {
|
||||
const mapped: MappedValues = {};
|
||||
for (const field of fields) {
|
||||
if (field.fieldType === 'heading') continue;
|
||||
const raw = (values?.[field.key] ?? '').toString().trim();
|
||||
if (field.required && !raw) {
|
||||
throw new BadRequestException(`فیلد «${field.label}» الزامی است`);
|
||||
}
|
||||
if (!raw) continue;
|
||||
switch (field.mapTo) {
|
||||
case 'person.fullName': {
|
||||
const parts = raw.split(/\s+/).filter(Boolean);
|
||||
mapped.firstName = parts[0] || raw;
|
||||
mapped.lastName = parts.slice(1).join(' ') || '—';
|
||||
break;
|
||||
}
|
||||
case 'person.firstName':
|
||||
mapped.firstName = raw;
|
||||
break;
|
||||
@@ -515,6 +618,47 @@ function normalizeFields(fields: LeadFormFieldInput[]): LeadFormFieldInput[] {
|
||||
});
|
||||
}
|
||||
|
||||
function fieldCreateData(f: LeadFormFieldInput, index: number) {
|
||||
return {
|
||||
key: f.key!,
|
||||
label: f.label,
|
||||
fieldType: f.fieldType,
|
||||
required: f.required ?? false,
|
||||
mapTo: f.mapTo,
|
||||
position: f.position ?? index,
|
||||
placeholder: emptyToNull(f.placeholder ?? null),
|
||||
options: f.options?.length ? f.options : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
function asOptions(value: unknown): string[] {
|
||||
if (!Array.isArray(value)) return [];
|
||||
return value.map((v) => String(v).trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
function asStringRecord(value: unknown): Record<string, string> {
|
||||
if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
|
||||
const out: Record<string, string> = {};
|
||||
for (const [k, v] of Object.entries(value as Record<string, unknown>)) {
|
||||
if (v == null) continue;
|
||||
out[k] = typeof v === 'string' ? v : JSON.stringify(v);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function collectExtras(
|
||||
fields: Array<{ key: string; mapTo: string; label: string; fieldType: string }>,
|
||||
values: Record<string, string>,
|
||||
): string {
|
||||
const lines: string[] = [];
|
||||
for (const field of fields) {
|
||||
if (field.fieldType === 'heading' || field.mapTo !== 'custom') continue;
|
||||
const raw = (values?.[field.key] ?? '').toString().trim();
|
||||
if (raw) lines.push(`${field.label}: ${raw}`);
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function slugKey(label: string): string {
|
||||
return label
|
||||
.trim()
|
||||
|
||||
@@ -1,16 +1,41 @@
|
||||
import { Body, Controller, Get, Param, Post } from '@nestjs/common';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
NotFoundException,
|
||||
Param,
|
||||
Post,
|
||||
Res,
|
||||
StreamableFile,
|
||||
} from '@nestjs/common';
|
||||
import type { Response } from 'express';
|
||||
import { SubmitLeadFormDto } from './dto';
|
||||
import { LeadFormsService } from './lead-forms.service';
|
||||
import { LocalFileStorage } from '../storage/local-file.storage';
|
||||
|
||||
@Controller('public/forms')
|
||||
export class PublicLeadFormsController {
|
||||
constructor(private readonly leadFormsService: LeadFormsService) {}
|
||||
constructor(
|
||||
private readonly leadFormsService: LeadFormsService,
|
||||
private readonly files: LocalFileStorage,
|
||||
) {}
|
||||
|
||||
@Get(':token')
|
||||
get(@Param('token') token: string) {
|
||||
return this.leadFormsService.getPublic(token);
|
||||
}
|
||||
|
||||
@Get(':token/cover')
|
||||
async cover(
|
||||
@Param('token') token: string,
|
||||
@Res({ passthrough: true }) res: Response,
|
||||
) {
|
||||
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' });
|
||||
return new StreamableFile(this.files.openReadStream(row.coverStorageKey));
|
||||
}
|
||||
|
||||
@Post(':token/submit')
|
||||
submit(@Param('token') token: string, @Body() dto: SubmitLeadFormDto) {
|
||||
return this.leadFormsService.submit(token, dto.values ?? {});
|
||||
|
||||
Reference in New Issue
Block a user