diff --git a/confiture-rest-api/package.json b/confiture-rest-api/package.json index 5f51bdea..a77dd76c 100644 --- a/confiture-rest-api/package.json +++ b/confiture-rest-api/package.json @@ -28,6 +28,7 @@ "bcrypt": "^5.1.0", "class-transformer": "^0.5.1", "class-validator": "^0.13.2", + "express-http-proxy": "^2.0.0", "got": "^11.8.5", "handlebars": "^4.7.7", "joi": "^17.6.0", diff --git a/confiture-rest-api/prisma/migrations/20240530143825_remove_file_urls/migration.sql b/confiture-rest-api/prisma/migrations/20240530143825_remove_file_urls/migration.sql new file mode 100644 index 00000000..9be7fdd9 --- /dev/null +++ b/confiture-rest-api/prisma/migrations/20240530143825_remove_file_urls/migration.sql @@ -0,0 +1,10 @@ +/* + Warnings: + + - You are about to drop the column `thumbnailUrl` on the `StoredFile` table. All the data in the column will be lost. + - You are about to drop the column `url` on the `StoredFile` table. All the data in the column will be lost. + +*/ +-- AlterTable +ALTER TABLE "StoredFile" DROP COLUMN "thumbnailUrl", +DROP COLUMN "url"; diff --git a/confiture-rest-api/prisma/schema.prisma b/confiture-rest-api/prisma/schema.prisma index 14f33036..9e6d6004 100644 --- a/confiture-rest-api/prisma/schema.prisma +++ b/confiture-rest-api/prisma/schema.prisma @@ -169,13 +169,10 @@ model AuditTrace { model StoredFile { id Int @id @default(autoincrement()) originalFilename String - url String size Int - // S3 storage key + // S3 storage keys key String - - thumbnailUrl String thumbnailKey String criterionResult CriterionResult? @relation(fields: [criterionResultId], references: [id], onDelete: Cascade, onUpdate: Cascade) diff --git a/confiture-rest-api/src/audits/audit.service.ts b/confiture-rest-api/src/audits/audit.service.ts index c1ca725f..4256b6eb 100644 --- a/confiture-rest-api/src/audits/audit.service.ts +++ b/confiture-rest-api/src/audits/audit.service.ts @@ -477,9 +477,6 @@ export class AuditService { ) ]); - const publicUrl = this.fileStorageService.getPublicUrl(key); - const thumbnailUrl = this.fileStorageService.getPublicUrl(thumbnailKey); - const storedFile = await this.prisma.storedFile.create({ data: { criterionResult: { @@ -495,10 +492,7 @@ export class AuditService { key, originalFilename: file.originalname, size: file.size, - url: publicUrl, - - thumbnailKey, - thumbnailUrl + thumbnailKey } }); @@ -929,8 +923,9 @@ export class AuditService { userImpact: r.userImpact, quickWin: r.quickWin, exampleImages: r.exampleImages.map((img) => ({ - url: img.url, - filename: img.originalFilename + filename: img.originalFilename, + key: img.key, + thumbnailKey: img.thumbnailKey })) })) }; diff --git a/confiture-rest-api/src/audits/dto/audit-report.dto.ts b/confiture-rest-api/src/audits/dto/audit-report.dto.ts index f7baacf2..dd36b4b5 100644 --- a/confiture-rest-api/src/audits/dto/audit-report.dto.ts +++ b/confiture-rest-api/src/audits/dto/audit-report.dto.ts @@ -207,8 +207,10 @@ class ReportCriterionResult { } class ExampleImage { - /** @example "https://example.com/mon-image.jpg" */ - url: string; /** @example "mon-image.jpg" */ filename: string; + /** @example "audit/xxxx/my-image.jpg" */ + key: string; + /** @example "audit/xxxx/my-image_thumbnail.jpg" */ + thumbnailKey: string; } diff --git a/confiture-rest-api/src/audits/file-storage.service.ts b/confiture-rest-api/src/audits/file-storage.service.ts index a555ab06..b5b44725 100644 --- a/confiture-rest-api/src/audits/file-storage.service.ts +++ b/confiture-rest-api/src/audits/file-storage.service.ts @@ -35,7 +35,7 @@ export class FileStorageService { } getPublicUrl(key: string): string { - return `${this.config.get("S3_VIRTUAL_HOST")}${key}`; + return `${this.config.get("FRONT_BASE_URL")}/${key}}`; } async deleteStoredFile(key: string) { diff --git a/confiture-rest-api/src/main.ts b/confiture-rest-api/src/main.ts index eb66293b..c3dcdd3b 100644 --- a/confiture-rest-api/src/main.ts +++ b/confiture-rest-api/src/main.ts @@ -3,6 +3,7 @@ import { NestFactory } from "@nestjs/core"; import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger"; import type { NestExpressApplication } from "@nestjs/platform-express"; import morgan from "morgan"; +import proxy from "express-http-proxy"; import { AppModule } from "./app.module"; @@ -17,6 +18,7 @@ async function bootstrap() { app.useBodyParser("json", { limit: "500kb" }); app.use(morgan(process.env.NODE_ENV !== "production" ? "dev" : "common")); + app.use("/uploads", proxy(process.env.S3_VIRTUAL_HOST)); app.setGlobalPrefix("/api"); app.useGlobalPipes(new ValidationPipe({ whitelist: true })); diff --git a/confiture-web-app/config/config_nginx/dpl/sites-enabled/frontend.conf b/confiture-web-app/config/config_nginx/dpl/sites-enabled/frontend.conf index 6d5e39bd..0d93c3e6 100644 --- a/confiture-web-app/config/config_nginx/dpl/sites-enabled/frontend.conf +++ b/confiture-web-app/config/config_nginx/dpl/sites-enabled/frontend.conf @@ -31,7 +31,6 @@ server { # auth_basic off; # allow all; #} - location / { # This is due to nginx and the try_files behavior below, it will always @@ -49,9 +48,9 @@ server { # This seperate location is so the no cache policy only applies to the index and nothing else. location @index { - add_header Cache-Control no-cache; - expires 0; - try_files /index.html =404; + add_header Cache-Control no-cache; + expires 0; + try_files /index.html =404; } # # reverse proxy for backend @@ -70,12 +69,12 @@ server { # proxy_pass $upstream_backend; proxy_pass http://backend:8085; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; # pass on real client's IP - proxy_set_header CLIENT_IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto https; - proxy_read_timeout 900; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; # pass on real client's IP + proxy_set_header CLIENT_IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + proxy_read_timeout 900; proxy_buffers 16 16k; proxy_buffer_size 16k; @@ -83,5 +82,23 @@ server { } + location /uploads/ { + if ($request_method !~ ^(OPTIONS|GET|HEAD)$ ) { + return 405; + } + + proxy_pass http://backend:8085; + + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; # pass on real client's IP + proxy_set_header CLIENT_IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + + proxy_read_timeout 900; + proxy_buffers 16 16k; + proxy_buffer_size 16k; + } + } diff --git a/confiture-web-app/public/_redirects b/confiture-web-app/public/_redirects index 28a9f957..cf12d6ab 100644 --- a/confiture-web-app/public/_redirects +++ b/confiture-web-app/public/_redirects @@ -1,2 +1,3 @@ -/api/* https://confiture-api.herokuapp.com/api/:splat 200 -/* /index.html 200 +/api/* https://confiture-api.herokuapp.com/api/:splat 200 +/uploads/* https://confiture-api.herokuapp.com/uploads/:splat 200 +/* /index.html 200 \ No newline at end of file diff --git a/confiture-web-app/src/components/audit/CriteriumNotCompliantAccordion.vue b/confiture-web-app/src/components/audit/CriteriumNotCompliantAccordion.vue index 92819d7e..08d8838a 100644 --- a/confiture-web-app/src/components/audit/CriteriumNotCompliantAccordion.vue +++ b/confiture-web-app/src/components/audit/CriteriumNotCompliantAccordion.vue @@ -7,6 +7,7 @@ import LazyAccordion from "./LazyAccordion.vue"; import MarkdownHelpButton from "./MarkdownHelpButton.vue"; import { RadioColor } from "../ui/Radio.vue"; import RadioGroup from "../ui/RadioGroup.vue"; +import { getUploadUrl } from "../../utils"; const props = defineProps<{ id: string; @@ -162,13 +163,13 @@ const isOffline = useIsOffline(); diff --git a/confiture-web-app/src/components/report/ReportErrors.vue b/confiture-web-app/src/components/report/ReportErrors.vue index fa0d7461..0cdc0a40 100644 --- a/confiture-web-app/src/components/report/ReportErrors.vue +++ b/confiture-web-app/src/components/report/ReportErrors.vue @@ -14,6 +14,7 @@ import { formatStatus, formatUserImpact, pluralize } from "../../utils"; import CriteriumTestsAccordion from "../audit/CriteriumTestsAccordion.vue"; import LazyAccordion from "../audit/LazyAccordion.vue"; import MarkdownRenderer from "../ui/MarkdownRenderer.vue"; +import { getUploadUrl } from "../../utils"; const report = useReportStore(); @@ -516,15 +517,19 @@ function getPage(pageId: number | string) { > Ouvrir l’image dans une nouvelle fenêtre - + @@ -636,15 +641,19 @@ function getPage(pageId: number | string) { > Ouvrir l’image dans une nouvelle fenêtre - + diff --git a/confiture-web-app/src/types/report.ts b/confiture-web-app/src/types/report.ts index 86e2494d..207725fe 100644 --- a/confiture-web-app/src/types/report.ts +++ b/confiture-web-app/src/types/report.ts @@ -46,7 +46,8 @@ export interface AuditReport { results: Array< Omit & { exampleImages: { - url: string; + key: string; + thumbnailKey: string; filename: string; }[]; } diff --git a/confiture-web-app/src/types/types.ts b/confiture-web-app/src/types/types.ts index 41d84b97..d3bc244f 100644 --- a/confiture-web-app/src/types/types.ts +++ b/confiture-web-app/src/types/types.ts @@ -111,9 +111,8 @@ export interface ExampleImage { id: number; originalFilename: string; size: number; - url: string; - - thumbnailUrl: string; + key: string; + thumbnailKey: string; } export interface CriteriumResult { diff --git a/confiture-web-app/src/utils.ts b/confiture-web-app/src/utils.ts index 8ad9f066..36088e57 100644 --- a/confiture-web-app/src/utils.ts +++ b/confiture-web-app/src/utils.ts @@ -231,3 +231,7 @@ export function waitForElement(selector: string): Promise { }); }); } + +export function getUploadUrl(key: string): string { + return `/uploads/${key}`; +} diff --git a/confiture-web-app/vite.config.ts b/confiture-web-app/vite.config.ts index 6574c875..82b9cceb 100644 --- a/confiture-web-app/vite.config.ts +++ b/confiture-web-app/vite.config.ts @@ -30,7 +30,8 @@ export default defineConfig({ ], server: { proxy: { - "/api": "http://localhost:4000" + "/api": "http://localhost:4000", + "/uploads": "http://localhost:4000" }, port: 3000 } diff --git a/yarn.lock b/yarn.lock index d6a8949e..cce051b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3736,7 +3736,7 @@ debug@4, debug@4.3.4, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4: dependencies: ms "2.1.2" -debug@^3.1.0: +debug@^3.0.1, debug@^3.1.0: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== @@ -4069,6 +4069,11 @@ es-module-lexer@^1.2.1: resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.4.1.tgz#41ea21b43908fe6a287ffcbe4300f790555331f5" integrity sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w== +es6-promise@^4.1.1: + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + esbuild@^0.18.10: version "0.18.20" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6" @@ -4348,6 +4353,15 @@ expand-template@^2.0.3: resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== +express-http-proxy@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/express-http-proxy/-/express-http-proxy-2.0.0.tgz#c47f4d8b53e8d9c2b9a2f1d158eedf6eb84828a5" + integrity sha512-TXxcPFTWVUMSEmyM6iX2sT/JtmqhqngTq29P+eXTVFdtxZrTmM8THUYK59rUXiln0FfPGvxEpGRnVrgvHksXDw== + dependencies: + debug "^3.0.1" + es6-promise "^4.1.1" + raw-body "^2.3.0" + express@4.18.2: version "4.18.2" resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" @@ -6990,7 +7004,7 @@ raw-body@2.5.1: iconv-lite "0.4.24" unpipe "1.0.0" -raw-body@2.5.2: +raw-body@2.5.2, raw-body@^2.3.0: version "2.5.2" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==