139 lines
3.5 KiB
TypeScript
139 lines
3.5 KiB
TypeScript
import { Injectable, BadRequestException } from '@nestjs/common';
|
|
import { PrismaService } from '../prisma/prisma.service';
|
|
import * as path from 'path';
|
|
import * as fs from 'fs';
|
|
|
|
@Injectable()
|
|
export class MediaService {
|
|
constructor(private prisma: PrismaService) {}
|
|
|
|
async getAllMedia() {
|
|
const items = await this.prisma.media.findMany({
|
|
orderBy: { createdAt: 'desc' },
|
|
});
|
|
|
|
return items.map((item) => {
|
|
let cleanFilename = item.filename;
|
|
// Check if filename contains common mojibake patterns from latin1 decoding
|
|
if (cleanFilename && /[ØÙÚÛ]/.test(cleanFilename)) {
|
|
try {
|
|
const decoded = Buffer.from(cleanFilename, 'latin1').toString('utf8');
|
|
if (decoded && !decoded.includes('')) {
|
|
cleanFilename = decoded;
|
|
}
|
|
} catch {
|
|
// Keep original if decoding fails
|
|
}
|
|
}
|
|
return {
|
|
...item,
|
|
filename: cleanFilename,
|
|
};
|
|
});
|
|
}
|
|
|
|
async uploadFile(file: Express.Multer.File) {
|
|
if (!file) {
|
|
throw new BadRequestException('No file uploaded');
|
|
}
|
|
|
|
const uploadDir = path.join(process.cwd(), 'uploads');
|
|
if (!fs.existsSync(uploadDir)) {
|
|
fs.mkdirSync(uploadDir, { recursive: true });
|
|
}
|
|
|
|
// Fix Multer/Busboy ISO-8859-1 (latin1) to UTF-8 filename encoding for Persian/Arabic/Unicode characters
|
|
let originalname = file.originalname;
|
|
try {
|
|
originalname = Buffer.from(file.originalname, 'latin1').toString('utf8');
|
|
} catch {
|
|
originalname = file.originalname;
|
|
}
|
|
|
|
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
|
|
const ext = path.extname(originalname);
|
|
const filename = `${uniqueSuffix}${ext}`;
|
|
const filePath = path.join(uploadDir, filename);
|
|
|
|
fs.writeFileSync(filePath, file.buffer);
|
|
|
|
// Assuming we serve static files from '/uploads' route prefix
|
|
const url = `/uploads/${filename}`;
|
|
|
|
const media = await this.prisma.media.create({
|
|
data: {
|
|
filename: originalname,
|
|
url,
|
|
mimetype: file.mimetype,
|
|
size: file.size,
|
|
},
|
|
});
|
|
|
|
return media;
|
|
}
|
|
|
|
async deleteMedia(id: string) {
|
|
const media = await this.prisma.media.findUnique({ where: { id } });
|
|
if (!media) throw new BadRequestException('Media not found');
|
|
|
|
const filePath = path.join(
|
|
process.cwd(),
|
|
'uploads',
|
|
path.basename(media.url),
|
|
);
|
|
if (fs.existsSync(filePath)) {
|
|
fs.unlinkSync(filePath);
|
|
}
|
|
|
|
await this.prisma.media.delete({ where: { id } });
|
|
return { success: true };
|
|
}
|
|
|
|
async deleteManyMedia(ids: string[]) {
|
|
const mediaItems = await this.prisma.media.findMany({
|
|
where: { id: { in: ids } },
|
|
});
|
|
|
|
for (const media of mediaItems) {
|
|
const filePath = path.join(
|
|
process.cwd(),
|
|
'uploads',
|
|
path.basename(media.url),
|
|
);
|
|
if (fs.existsSync(filePath)) {
|
|
try {
|
|
fs.unlinkSync(filePath);
|
|
} catch {
|
|
// Ignore individual unlink errors
|
|
}
|
|
}
|
|
}
|
|
|
|
await this.prisma.media.deleteMany({
|
|
where: { id: { in: ids } },
|
|
});
|
|
|
|
return { success: true, count: mediaItems.length };
|
|
}
|
|
|
|
async updateMedia(
|
|
id: string,
|
|
data: {
|
|
altText?: string;
|
|
title?: string;
|
|
description?: string;
|
|
caption?: string;
|
|
},
|
|
) {
|
|
return this.prisma.media.update({
|
|
where: { id },
|
|
data: {
|
|
altText: data.altText,
|
|
title: data.title,
|
|
description: data.description,
|
|
caption: data.caption,
|
|
},
|
|
});
|
|
}
|
|
}
|