Add OTP-first signup and product catalog PDF uploads.
Signup verifies phone before password; existing Meshkee accounts keep their password. Products can attach an optional PDF catalog (max 2MB) via Pars Pack media. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
4a9e10e614
commit
1a49576752
@@ -26,6 +26,11 @@ const IMAGE_MIME_TYPES = new Set([
|
||||
|
||||
const VIDEO_MIME_TYPES = new Set(['video/mp4', 'video/webm']);
|
||||
|
||||
const PDF_MIME_TYPES = new Set(['application/pdf']);
|
||||
|
||||
/** Product catalog PDFs are capped lower than general media uploads. */
|
||||
const PDF_MAX_FILE_SIZE_BYTES = 2 * 1024 * 1024;
|
||||
|
||||
@Injectable()
|
||||
export class MediaService {
|
||||
private readonly maxFileSizeBytes: number;
|
||||
@@ -375,6 +380,14 @@ export class MediaService {
|
||||
throw new BadRequestException('Uploaded file is empty');
|
||||
}
|
||||
|
||||
const isPdf =
|
||||
PDF_MIME_TYPES.has(file.mimetype) ||
|
||||
file.originalname.toLowerCase().endsWith('.pdf');
|
||||
|
||||
if (isPdf) {
|
||||
return this.uploadPdf(businessId, file, uploadedBy);
|
||||
}
|
||||
|
||||
if (file.size > this.maxFileSizeBytes) {
|
||||
throw new BadRequestException(
|
||||
`File ${file.originalname} exceeds the maximum allowed size`,
|
||||
@@ -409,7 +422,7 @@ export class MediaService {
|
||||
}
|
||||
|
||||
throw new BadRequestException(
|
||||
`Unsupported file type: ${file.mimetype || 'unknown'}. Use JPEG, PNG, WebP, or GIF.`,
|
||||
`Unsupported file type: ${file.mimetype || 'unknown'}. Use JPEG, PNG, WebP, GIF, or PDF.`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -443,6 +456,54 @@ export class MediaService {
|
||||
return this.serialize(created);
|
||||
}
|
||||
|
||||
private async uploadPdf(
|
||||
businessId: bigint,
|
||||
file: Express.Multer.File,
|
||||
uploadedBy: bigint,
|
||||
) {
|
||||
if (file.size > PDF_MAX_FILE_SIZE_BYTES) {
|
||||
throw new BadRequestException(
|
||||
`PDF ${file.originalname} exceeds the maximum allowed size of 2MB`,
|
||||
);
|
||||
}
|
||||
|
||||
const header = file.buffer.subarray(0, 5).toString('utf8');
|
||||
if (!header.startsWith('%PDF-')) {
|
||||
throw new BadRequestException(
|
||||
`File ${file.originalname} is not a valid PDF`,
|
||||
);
|
||||
}
|
||||
|
||||
const fileName = `${randomUUID()}.pdf`;
|
||||
const storageKey = businessMediaKey(businessId, fileName);
|
||||
const contentType = 'application/pdf';
|
||||
|
||||
const stored = await this.storage.upload({
|
||||
key: storageKey,
|
||||
body: file.buffer,
|
||||
contentType,
|
||||
});
|
||||
|
||||
const created = await this.prisma.media.create({
|
||||
data: {
|
||||
businessId,
|
||||
uploadedBy,
|
||||
mediaType: MediaType.document,
|
||||
storageDisk: stored.storageDisk,
|
||||
storagePath: stored.storagePath,
|
||||
publicUrl: stored.publicUrl,
|
||||
fileName,
|
||||
originalFileName: file.originalname,
|
||||
mimeType: contentType,
|
||||
fileSizeBytes: BigInt(file.size),
|
||||
width: null,
|
||||
height: null,
|
||||
},
|
||||
});
|
||||
|
||||
return this.serialize(created);
|
||||
}
|
||||
|
||||
private contentTypeFromFormat(format?: string) {
|
||||
switch (format) {
|
||||
case 'jpeg':
|
||||
@@ -469,6 +530,8 @@ export class MediaService {
|
||||
return '.webp';
|
||||
case 'image/gif':
|
||||
return '.gif';
|
||||
case 'application/pdf':
|
||||
return '.pdf';
|
||||
default:
|
||||
return '.jpg';
|
||||
}
|
||||
@@ -493,6 +556,8 @@ export class MediaService {
|
||||
return '.mp4';
|
||||
case 'video/webm':
|
||||
return '.webm';
|
||||
case 'application/pdf':
|
||||
return '.pdf';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user