feat(media): add public URL generation for media files and improve S3 integration
Introduce `getPublicUrl` in `S3Service` for generating public URLs. Replace custom file URL generation logic across services with the new method. Add media controller for file streaming and update related tests. Adjust frontend to display user roles instead of email in the sidebar. Update environment schema to include optional `API_URL`. Fix help page contact email.
This commit is contained in:
60
backend/src/media/media.controller.spec.ts
Normal file
60
backend/src/media/media.controller.spec.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Readable } from "node:stream";
|
||||
import { NotFoundException } from "@nestjs/common";
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import { S3Service } from "../s3/s3.service";
|
||||
import { MediaController } from "./media.controller";
|
||||
|
||||
describe("MediaController", () => {
|
||||
let controller: MediaController;
|
||||
let s3Service: S3Service;
|
||||
|
||||
const mockS3Service = {
|
||||
getFileInfo: jest.fn(),
|
||||
getFile: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [MediaController],
|
||||
providers: [{ provide: S3Service, useValue: mockS3Service }],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<MediaController>(MediaController);
|
||||
s3Service = module.get<S3Service>(S3Service);
|
||||
});
|
||||
|
||||
it("should be defined", () => {
|
||||
expect(controller).toBeDefined();
|
||||
});
|
||||
|
||||
describe("getFile", () => {
|
||||
it("should stream the file and set headers", async () => {
|
||||
const res = {
|
||||
setHeader: jest.fn(),
|
||||
} as any;
|
||||
const stream = new Readable();
|
||||
stream.pipe = jest.fn();
|
||||
|
||||
mockS3Service.getFileInfo.mockResolvedValue({
|
||||
size: 100,
|
||||
metaData: { "content-type": "image/webp" },
|
||||
});
|
||||
mockS3Service.getFile.mockResolvedValue(stream);
|
||||
|
||||
await controller.getFile("test.webp", res);
|
||||
|
||||
expect(res.setHeader).toHaveBeenCalledWith("Content-Type", "image/webp");
|
||||
expect(res.setHeader).toHaveBeenCalledWith("Content-Length", 100);
|
||||
expect(stream.pipe).toHaveBeenCalledWith(res);
|
||||
});
|
||||
|
||||
it("should throw NotFoundException if file is not found", async () => {
|
||||
mockS3Service.getFileInfo.mockRejectedValue(new Error("Not found"));
|
||||
const res = {} as any;
|
||||
|
||||
await expect(controller.getFile("invalid", res)).rejects.toThrow(
|
||||
NotFoundException,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
27
backend/src/media/media.controller.ts
Normal file
27
backend/src/media/media.controller.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Controller, Get, NotFoundException, Param, Res } from "@nestjs/common";
|
||||
import type { Response } from "express";
|
||||
import { S3Service } from "../s3/s3.service";
|
||||
|
||||
@Controller("media")
|
||||
export class MediaController {
|
||||
constructor(private readonly s3Service: S3Service) {}
|
||||
|
||||
@Get(":key(*)")
|
||||
async getFile(@Param("key") key: string, @Res() res: Response) {
|
||||
try {
|
||||
const stats = await this.s3Service.getFileInfo(key);
|
||||
const stream = await this.s3Service.getFile(key);
|
||||
|
||||
res.setHeader(
|
||||
"Content-Type",
|
||||
stats.metaData["content-type"] || "application/octet-stream",
|
||||
);
|
||||
res.setHeader("Content-Length", stats.size);
|
||||
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
|
||||
|
||||
stream.pipe(res);
|
||||
} catch (_error) {
|
||||
throw new NotFoundException("Fichier non trouvé");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { S3Module } from "../s3/s3.module";
|
||||
import { MediaController } from "./media.controller";
|
||||
import { MediaService } from "./media.service";
|
||||
import { ImageProcessorStrategy } from "./strategies/image-processor.strategy";
|
||||
import { VideoProcessorStrategy } from "./strategies/video-processor.strategy";
|
||||
|
||||
@Module({
|
||||
imports: [S3Module],
|
||||
controllers: [MediaController],
|
||||
providers: [MediaService, ImageProcessorStrategy, VideoProcessorStrategy],
|
||||
exports: [MediaService],
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user