diff --git a/packages/backend/openapi.json b/packages/backend/openapi.json index 18e18e24..beb759b9 100644 --- a/packages/backend/openapi.json +++ b/packages/backend/openapi.json @@ -1 +1 @@ -{"openapi":"3.1.0","info":{"title":"CR VIF API","description":"CR VIF API Documentation","version":"1.0"},"components":{"schemas":{}},"paths":{"/api/create-user":{"post":{"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"udap_id":{"type":"string"},"email":{"type":"string"},"password":{"type":"string"}},"required":["name","udap_id","email","password"]}}},"required":true},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"udap_id":{"type":"string"},"udap":{"type":"object","properties":{"id":{"type":"string"},"department":{"type":"string"},"completeCoords":{"type":"string"},"visible":{"type":"boolean"},"name":{"type":"string"},"address":{"type":"string"},"zipCode":{"type":"string"},"city":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"}},"required":["id","department"]}},"required":["id","name","udap_id","udap"]},"token":{"type":"string"},"refreshToken":{"type":"string"}},"required":["token","refreshToken"]}}}}}}},"/api/login":{"post":{"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"},"password":{"type":"string"}},"required":["email","password"]}}},"required":true},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"udap_id":{"type":"string"},"udap":{"type":"object","properties":{"id":{"type":"string"},"department":{"type":"string"},"completeCoords":{"type":"string"},"visible":{"type":"boolean"},"name":{"type":"string"},"address":{"type":"string"},"zipCode":{"type":"string"},"city":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"}},"required":["id","department"]}},"required":["id","name","udap_id","udap"]},"token":{"type":"string"},"refreshToken":{"type":"string"}},"required":["token","refreshToken"]}}}}}}},"/api/refresh-token":{"get":{"parameters":[{"schema":{"type":"string"},"in":"query","name":"token","required":true},{"schema":{"type":"string"},"in":"query","name":"refreshToken","required":false}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"udap_id":{"type":"string"},"udap":{"type":"object","properties":{"id":{"type":"string"},"department":{"type":"string"},"completeCoords":{"type":"string"},"visible":{"type":"boolean"},"name":{"type":"string"},"address":{"type":"string"},"zipCode":{"type":"string"},"city":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"}},"required":["id","department"]}},"required":["id","name","udap_id","udap"]},"token":{"type":"string"},"refreshToken":{"type":"string"}},"required":["token","refreshToken"]}}}}}}},"/api/send-reset-password":{"post":{"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"}},"required":["email"]}}},"required":true},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]}}}}}}},"/api/reset-password":{"post":{"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"temporaryLink":{"type":"string"},"newPassword":{"type":"string"}},"required":["temporaryLink","newPassword"]}}},"required":true},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]}}}}}}},"/api/udaps":{"get":{"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"department":{"type":"string"},"completeCoords":{"type":"string"},"visible":{"type":"boolean"},"name":{"type":"string"},"address":{"type":"string"},"zipCode":{"type":"string"},"city":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"}},"required":["id","department"]}}}}}}}},"/api/pdf/report":{"post":{"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"htmlString":{"type":"string"},"reportId":{"type":"string"},"recipients":{"type":"string"}},"required":["htmlString","reportId","recipients"]}}},"required":true},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"string"}}}}}}}}} \ No newline at end of file +{"openapi":"3.1.0","info":{"title":"CR VIF API","description":"CR VIF API Documentation","version":"1.0"},"components":{"schemas":{}},"paths":{"/api/create-user":{"post":{"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"udap_id":{"type":"string"},"email":{"type":"string"},"password":{"type":"string"}},"required":["name","udap_id","email","password"]}}},"required":true},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"udap_id":{"type":"string"},"udap":{"type":"object","properties":{"id":{"type":"string"},"department":{"type":"string"},"completeCoords":{"type":"string"},"visible":{"type":"boolean"},"name":{"type":"string"},"address":{"type":"string"},"zipCode":{"type":"string"},"city":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"}},"required":["id","department"]}},"required":["id","name","udap_id","udap"]},"token":{"type":"string"},"refreshToken":{"type":"string"}},"required":["token","refreshToken"]}}}}}}},"/api/login":{"post":{"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"},"password":{"type":"string"}},"required":["email","password"]}}},"required":true},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"udap_id":{"type":"string"},"udap":{"type":"object","properties":{"id":{"type":"string"},"department":{"type":"string"},"completeCoords":{"type":"string"},"visible":{"type":"boolean"},"name":{"type":"string"},"address":{"type":"string"},"zipCode":{"type":"string"},"city":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"}},"required":["id","department"]}},"required":["id","name","udap_id","udap"]},"token":{"type":"string"},"refreshToken":{"type":"string"}},"required":["token","refreshToken"]}}}}}}},"/api/refresh-token":{"get":{"parameters":[{"schema":{"type":"string"},"in":"query","name":"token","required":true},{"schema":{"type":"string"},"in":"query","name":"refreshToken","required":false}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"user":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"udap_id":{"type":"string"},"udap":{"type":"object","properties":{"id":{"type":"string"},"department":{"type":"string"},"completeCoords":{"type":"string"},"visible":{"type":"boolean"},"name":{"type":"string"},"address":{"type":"string"},"zipCode":{"type":"string"},"city":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"}},"required":["id","department"]}},"required":["id","name","udap_id","udap"]},"token":{"type":"string"},"refreshToken":{"type":"string"}},"required":["token","refreshToken"]}}}}}}},"/api/send-reset-password":{"post":{"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"email":{"type":"string"}},"required":["email"]}}},"required":true},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]}}}}}}},"/api/reset-password":{"post":{"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"temporaryLink":{"type":"string"},"newPassword":{"type":"string"}},"required":["temporaryLink","newPassword"]}}},"required":true},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{"message":{"type":"string"}},"required":["message"]}}}}}}},"/api/udaps":{"get":{"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"department":{"type":"string"},"completeCoords":{"type":"string"},"visible":{"type":"boolean"},"name":{"type":"string"},"address":{"type":"string"},"zipCode":{"type":"string"},"city":{"type":"string"},"phone":{"type":"string"},"email":{"type":"string"}},"required":["id","department"]}}}}}}}},"/api/pdf/report":{"post":{"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"htmlString":{"type":"string"},"reportId":{"type":"string"},"recipients":{"type":"string"}},"required":["htmlString","reportId","recipients"]}}},"required":true},"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"string"}}}}}},"get":{"parameters":[{"schema":{"type":"string"},"in":"query","name":"reportId","required":true}],"responses":{"200":{"description":"Default Response"}}}}}} \ No newline at end of file diff --git a/packages/backend/src/envVars.ts b/packages/backend/src/envVars.ts index 9a499d99..feca2f26 100644 --- a/packages/backend/src/envVars.ts +++ b/packages/backend/src/envVars.ts @@ -20,6 +20,7 @@ const envSchema = z.object({ PORT: stringOrNumberAsNumber.default(3001), AWS_BUCKET_NAME: z.string(), AWS_REGION: z.string(), + AWS_ENDPOINT: z.string(), DEBUG: z.string().default("cr-vif:*"), FRONTEND_URL: z.string(), EMAIL_HOST: z.string(), diff --git a/packages/backend/src/routes/pdfRoutes.tsx b/packages/backend/src/routes/pdfRoutes.tsx index 5211d261..ea761797 100644 --- a/packages/backend/src/routes/pdfRoutes.tsx +++ b/packages/backend/src/routes/pdfRoutes.tsx @@ -5,6 +5,7 @@ import { Udap } from "@cr-vif/electric-client/frontend"; import { authenticate } from "./authMiddleware"; import { db } from "../db/db"; import { sendReportMail } from "../features/mail"; +import { getPDFName } from "../services/uploadService"; export const pdfPlugin: FastifyPluginAsyncTypebox = async (fastify, _) => { fastify.addHook("preHandler", authenticate); @@ -15,7 +16,7 @@ export const pdfPlugin: FastifyPluginAsyncTypebox = async (fastify, _) => { const pdf = await generatePdf({ htmlString, udap }); - const name = `${reportId}/compte_rendu.pdf`; + const name = getPDFName(reportId); const url = await request.services.upload.addPDFToReport({ reportId, @@ -35,6 +36,23 @@ export const pdfPlugin: FastifyPluginAsyncTypebox = async (fastify, _) => { return url; }); + + fastify.get( + "/report", + { + schema: { + querystring: Type.Object({ reportId: Type.String() }), + response: { 200: Type.Any() }, + }, + }, + async (request) => { + console.log("salut"); + const { reportId } = request.query; + const buffer = await request.services.upload.getReportPDF({ reportId }); + + return buffer.toString("base64"); + }, + ); }; const generatePdf = async ({ htmlString, udap }: { htmlString: string; udap: Udap }) => { diff --git a/packages/backend/src/services/uploadService.ts b/packages/backend/src/services/uploadService.ts index 31ddac79..b3329b02 100644 --- a/packages/backend/src/services/uploadService.ts +++ b/packages/backend/src/services/uploadService.ts @@ -1,19 +1,38 @@ -import { S3Client, ListBucketsCommand, PutObjectCommand } from "@aws-sdk/client-s3"; +import { S3Client, ListBucketsCommand, PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3"; import { Upload } from "@aws-sdk/lib-storage"; import { ENV } from "../envVars"; +import { makeDebug } from "../features/debug"; +import { db } from "../db/db"; +import { AppError } from "../features/errors"; -const client = new S3Client({ region: ENV.AWS_REGION }); +const client = new S3Client({ endpoint: ENV.AWS_ENDPOINT, region: ENV.AWS_REGION }); const command = new ListBucketsCommand(""); - +const debug = makeDebug("upload"); export const upload = async () => {}; export class UploadService { async addPDFToReport({ reportId, buffer, name }: { reportId: string; buffer: Buffer; name: string }) { + debug("Uploading PDF to S3", reportId); const command = new PutObjectCommand({ Bucket: ENV.AWS_BUCKET_NAME, Body: buffer, Key: name }); await client.send(command); - const url = `https://${ENV.AWS_BUCKET_NAME}.s3.${ENV.AWS_REGION}.amazonaws.com/${name}`; + const url = `https://${ENV.AWS_BUCKET_NAME}.s3.${ENV.AWS_REGION}.scw.cloud/${name}`; + debug(url); return url; } + + async getReportPDF({ reportId }: { reportId: string }) { + const name = getPDFName(reportId); + + const command = new GetObjectCommand({ Bucket: ENV.AWS_BUCKET_NAME, Key: name }); + const response = await client.send(command); + + const buffer = await response.Body?.transformToByteArray(); + if (!buffer) throw new AppError(404, "PDF not found"); + + return Buffer.from(buffer); + } } + +export const getPDFName = (reportId: string) => `${reportId}/compte_rendu.pdf`; diff --git a/packages/frontend/src/api.gen.ts b/packages/frontend/src/api.gen.ts index f367debf..0624b792 100644 --- a/packages/frontend/src/api.gen.ts +++ b/packages/frontend/src/api.gen.ts @@ -137,6 +137,14 @@ export namespace Endpoints { }; response: string; }; + export type get_Apipdfreport = { + method: "GET"; + path: "/api/pdf/report"; + parameters: { + query: { reportId: string }; + }; + response: unknown; + }; // } @@ -153,6 +161,7 @@ export type EndpointByMethod = { get: { "/api/refresh-token": Endpoints.get_ApirefreshToken; "/api/udaps": Endpoints.get_Apiudaps; + "/api/pdf/report": Endpoints.get_Apipdfreport; }; }; diff --git a/packages/frontend/src/contexts/AuthContext.tsx b/packages/frontend/src/contexts/AuthContext.tsx index 13f74997..ed788bf8 100644 --- a/packages/frontend/src/contexts/AuthContext.tsx +++ b/packages/frontend/src/contexts/AuthContext.tsx @@ -44,7 +44,6 @@ export const AuthProvider = ({ children }: PropsWithChildren) => { console.log("token refreshed"); setDataAndSaveInStorage(resp); } - console.log(resp); return resp; } catch (e) { console.error("refreshTokenQuery error", e); diff --git a/packages/frontend/src/features/ReportActions.tsx b/packages/frontend/src/features/ReportActions.tsx index 1cbc9c8f..2738e3dd 100644 --- a/packages/frontend/src/features/ReportActions.tsx +++ b/packages/frontend/src/features/ReportActions.tsx @@ -10,6 +10,7 @@ import { v4 } from "uuid"; import { omit } from "pastable"; import { ReportWithUser } from "./ReportList"; import { useNavigate } from "@tanstack/react-router"; +import { api } from "../api"; export const ReportActions = forwardRef(({ report }, ref) => { const user = useUser()!; @@ -18,6 +19,11 @@ export const ReportActions = forwardRef { + const buffer = await api.get("/api/pdf/report", { query: { reportId: report.id } }); + return downloadFile(`data:application/pdf;base64,${buffer}`); + }); + const deleteMutation = useDeleteMutation(); const duplicateMutation = useMutation(async () => { const payload = omit(report, ["id", "createdAt", "pdf", "user", "title"]); @@ -50,7 +56,7 @@ export const ReportActions = forwardRef ) : null} {report.pdf ? ( - downloadFile(report.pdf!)} /> + downloadPdfMutation.mutate()} /> ) : null} duplicateMutation.mutate()} />