feat: add ViewCounter enhancements and file upload progress tracking
- Improved `ViewCounter` with visibility-based view increment using `IntersectionObserver` and 50% video progress tracking. - Added real-time file upload progress updates via Socket.io, including status and percentage feedback. - Integrated `ViewCounter` dynamically into `ContentCard` and removed redundant instances from static pages. - Updated backend upload logic to emit progress updates at different stages via the `EventsGateway`.
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { AuthModule } from "../auth/auth.module";
|
||||
import { MediaModule } from "../media/media.module";
|
||||
import { RealtimeModule } from "../realtime/realtime.module";
|
||||
import { S3Module } from "../s3/s3.module";
|
||||
import { ContentsController } from "./contents.controller";
|
||||
import { ContentsService } from "./contents.service";
|
||||
import { ContentsRepository } from "./repositories/contents.repository";
|
||||
|
||||
@Module({
|
||||
imports: [S3Module, AuthModule, MediaModule],
|
||||
imports: [S3Module, AuthModule, MediaModule, RealtimeModule],
|
||||
controllers: [ContentsController],
|
||||
providers: [ContentsService, ContentsRepository],
|
||||
exports: [ContentsRepository],
|
||||
|
||||
@@ -14,6 +14,7 @@ import type {
|
||||
} from "../common/interfaces/media.interface";
|
||||
import type { IStorageService } from "../common/interfaces/storage.interface";
|
||||
import { MediaService } from "../media/media.service";
|
||||
import { EventsGateway } from "../realtime/events.gateway";
|
||||
import { S3Service } from "../s3/s3.service";
|
||||
import { CreateContentDto } from "./dto/create-content.dto";
|
||||
import { UploadContentDto } from "./dto/upload-content.dto";
|
||||
@@ -29,6 +30,7 @@ export class ContentsService {
|
||||
@Inject(MediaService) private readonly mediaService: IMediaService,
|
||||
private readonly configService: ConfigService,
|
||||
@Inject(CACHE_MANAGER) private cacheManager: Cache,
|
||||
private readonly eventsGateway: EventsGateway,
|
||||
) {}
|
||||
|
||||
private async clearContentsCache() {
|
||||
@@ -48,6 +50,11 @@ export class ContentsService {
|
||||
data: UploadContentDto,
|
||||
) {
|
||||
this.logger.log(`Uploading and processing file for user ${userId}`);
|
||||
this.eventsGateway.sendToUser(userId, "upload_progress", {
|
||||
status: "starting",
|
||||
progress: 0,
|
||||
});
|
||||
|
||||
// 0. Validation du format et de la taille
|
||||
const allowedMimeTypes = [
|
||||
"image/png",
|
||||
@@ -60,13 +67,25 @@ export class ContentsService {
|
||||
];
|
||||
|
||||
if (!allowedMimeTypes.includes(file.mimetype)) {
|
||||
this.eventsGateway.sendToUser(userId, "upload_progress", {
|
||||
status: "error",
|
||||
message: "Format de fichier non supporté",
|
||||
});
|
||||
throw new BadRequestException(
|
||||
"Format de fichier non supporté. Formats acceptés: png, jpeg, jpg, webp, webm, mp4, mov, gif.",
|
||||
);
|
||||
}
|
||||
|
||||
const isGif = file.mimetype === "image/gif";
|
||||
const isVideo = file.mimetype.startsWith("video/");
|
||||
// Autodétermination du type si non fourni ou pour valider
|
||||
let contentType: "meme" | "gif" | "video" = "meme";
|
||||
if (file.mimetype === "image/gif") {
|
||||
contentType = "gif";
|
||||
} else if (file.mimetype.startsWith("video/")) {
|
||||
contentType = "video";
|
||||
}
|
||||
|
||||
const isGif = contentType === "gif";
|
||||
const isVideo = contentType === "video";
|
||||
let maxSizeKb: number;
|
||||
|
||||
if (isGif) {
|
||||
@@ -78,23 +97,39 @@ export class ContentsService {
|
||||
}
|
||||
|
||||
if (file.size > maxSizeKb * 1024) {
|
||||
this.eventsGateway.sendToUser(userId, "upload_progress", {
|
||||
status: "error",
|
||||
message: "Fichier trop volumineux",
|
||||
});
|
||||
throw new BadRequestException(
|
||||
`Fichier trop volumineux. Limite pour ${isGif ? "GIF" : isVideo ? "vidéo" : "image"}: ${maxSizeKb} Ko.`,
|
||||
);
|
||||
}
|
||||
|
||||
// 1. Scan Antivirus
|
||||
this.eventsGateway.sendToUser(userId, "upload_progress", {
|
||||
status: "scanning",
|
||||
progress: 20,
|
||||
});
|
||||
const scanResult = await this.mediaService.scanFile(
|
||||
file.buffer,
|
||||
file.originalname,
|
||||
);
|
||||
if (scanResult.isInfected) {
|
||||
this.eventsGateway.sendToUser(userId, "upload_progress", {
|
||||
status: "error",
|
||||
message: "Fichier infecté",
|
||||
});
|
||||
throw new BadRequestException(
|
||||
`Le fichier est infecté par ${scanResult.virusName}`,
|
||||
);
|
||||
}
|
||||
|
||||
// 2. Transcodage
|
||||
this.eventsGateway.sendToUser(userId, "upload_progress", {
|
||||
status: "processing",
|
||||
progress: 40,
|
||||
});
|
||||
let processed: MediaProcessingResult;
|
||||
if (file.mimetype.startsWith("image/") && file.mimetype !== "image/gif") {
|
||||
// Image -> WebP (format moderne, bien supporté)
|
||||
@@ -110,17 +145,34 @@ export class ContentsService {
|
||||
}
|
||||
|
||||
// 3. Upload vers S3
|
||||
this.eventsGateway.sendToUser(userId, "upload_progress", {
|
||||
status: "uploading_s3",
|
||||
progress: 70,
|
||||
});
|
||||
const key = `contents/${userId}/${Date.now()}-${uuidv4()}.${processed.extension}`;
|
||||
await this.s3Service.uploadFile(key, processed.buffer, processed.mimeType);
|
||||
this.logger.log(`File uploaded successfully to S3: ${key}`);
|
||||
|
||||
// 4. Création en base de données
|
||||
return await this.create(userId, {
|
||||
this.eventsGateway.sendToUser(userId, "upload_progress", {
|
||||
status: "saving",
|
||||
progress: 90,
|
||||
});
|
||||
const content = await this.create(userId, {
|
||||
...data,
|
||||
type: contentType, // Utiliser le type autodéterminé
|
||||
storageKey: key,
|
||||
mimeType: processed.mimeType,
|
||||
fileSize: processed.size,
|
||||
});
|
||||
|
||||
this.eventsGateway.sendToUser(userId, "upload_progress", {
|
||||
status: "completed",
|
||||
progress: 100,
|
||||
contentId: content.id,
|
||||
});
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
async findAll(options: {
|
||||
|
||||
Reference in New Issue
Block a user