From 518d6e3e770094f5cd6095d9619a70ee584046be Mon Sep 17 00:00:00 2001 From: ledouxm Date: Tue, 12 Nov 2024 17:00:35 +0100 Subject: [PATCH] feat: draw on canvas and sync with backend and pdf --- db/migrations/910-add_picture_final_url.sql | 1 + packages/backend/openapi.json | 2 +- packages/backend/package.json | 1 + packages/backend/src/features/image.ts | 61 +++++++++++ packages/backend/src/routes/uploadRoutes.tsx | 26 ++++- .../backend/src/services/uploadService.ts | 43 +++++++- packages/backend/test.png | Bin 0 -> 53204 bytes packages/electric-client/prisma/schema.prisma | 18 ++- .../src/generated/client/index.ts | 45 ++++++-- .../src/generated/client/migrations.ts | 19 ++++ .../src/generated/client/pg-migrations.ts | 25 +++++ .../src/generated/client/prismaClient.d.ts | 38 ++++++- .../src/generated/typebox/index.ts | 6 +- .../src/generated/typebox/picture_lines.ts | 19 ++++ .../generated/typebox/picture_linesInput.ts | 19 ++++ .../src/generated/typebox/pictures.ts | 9 ++ .../src/generated/typebox/picturesInput.ts | 9 ++ .../src/generated/typebox/report.ts | 1 + .../src/generated/typebox/reportInput.ts | 1 + packages/frontend/src/api.gen.ts | 19 ++++ .../src/features/chips/useChipOptions.tsx | 4 +- packages/frontend/src/features/idb.ts | 7 +- .../src/features/upload/DrawingCanvas.tsx | 9 +- .../src/features/upload/UploadImage.tsx | 103 ++++++++++++++++-- .../frontend/src/routes/pdf.$reportId.tsx | 2 + packages/frontend/src/service-worker/sw.ts | 39 ++++++- packages/pdf/src/report.tsx | 3 +- pnpm-lock.yaml | 44 +++----- 28 files changed, 505 insertions(+), 68 deletions(-) create mode 100644 db/migrations/910-add_picture_final_url.sql create mode 100644 packages/backend/src/features/image.ts create mode 100644 packages/backend/test.png create mode 100644 packages/electric-client/src/generated/typebox/picture_lines.ts create mode 100644 packages/electric-client/src/generated/typebox/picture_linesInput.ts diff --git a/db/migrations/910-add_picture_final_url.sql b/db/migrations/910-add_picture_final_url.sql new file mode 100644 index 00000000..8ea64063 --- /dev/null +++ b/db/migrations/910-add_picture_final_url.sql @@ -0,0 +1 @@ +ALTER TABLE pictures ADD COLUMN "finalUrl" TEXT; \ No newline at end of file diff --git a/packages/backend/openapi.json b/packages/backend/openapi.json index 5852837f..0b9438b4 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"},"marianne_text":{"type":"string"},"drac_text":{"type":"string"},"udap_text":{"type":"string"}},"required":["id","department"]}},"required":["id","name","udap_id","udap"]},"token":{"type":"string"},"expiresAt":{"type":"string"},"refreshToken":{"type":"string"}},"required":["token","expiresAt","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"},"marianne_text":{"type":"string"},"drac_text":{"type":"string"},"udap_text":{"type":"string"}},"required":["id","department"]}},"required":["id","name","udap_id","udap"]},"token":{"type":"string"},"expiresAt":{"type":"string"},"refreshToken":{"type":"string"}},"required":["token","expiresAt","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"},"marianne_text":{"type":"string"},"drac_text":{"type":"string"},"udap_text":{"type":"string"}},"required":["id","department"]}},"required":["id","name","udap_id","udap"]},"token":{"type":"string"},"expiresAt":{"type":"string"},"refreshToken":{"type":"string"}},"required":["token","expiresAt","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"},"marianne_text":{"type":"string"},"drac_text":{"type":"string"},"udap_text":{"type":"string"}},"required":["id","department"]}}}}}}}},"/api/upload/image":{"post":{"responses":{"200":{"description":"Default Response"}}}},"/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","content":{"application/json":{"schema":{"type":"object","properties":{}}}}}}}}}} \ 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"},"marianne_text":{"type":"string"},"drac_text":{"type":"string"},"udap_text":{"type":"string"}},"required":["id","department"]}},"required":["id","name","udap_id","udap"]},"token":{"type":"string"},"expiresAt":{"type":"string"},"refreshToken":{"type":"string"}},"required":["token","expiresAt","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"},"marianne_text":{"type":"string"},"drac_text":{"type":"string"},"udap_text":{"type":"string"}},"required":["id","department"]}},"required":["id","name","udap_id","udap"]},"token":{"type":"string"},"expiresAt":{"type":"string"},"refreshToken":{"type":"string"}},"required":["token","expiresAt","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"},"marianne_text":{"type":"string"},"drac_text":{"type":"string"},"udap_text":{"type":"string"}},"required":["id","department"]}},"required":["id","name","udap_id","udap"]},"token":{"type":"string"},"expiresAt":{"type":"string"},"refreshToken":{"type":"string"}},"required":["token","expiresAt","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"},"marianne_text":{"type":"string"},"drac_text":{"type":"string"},"udap_text":{"type":"string"}},"required":["id","department"]}}}}}}}},"/api/upload/image":{"post":{"responses":{"200":{"description":"Default Response"}}}},"/api/upload/picture":{"get":{"parameters":[{"schema":{"type":"string"},"in":"query","name":"reportId","required":true},{"schema":{"type":"string"},"in":"query","name":"pictureId","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"object","properties":{}}}}}}}},"/api/upload/picture/{pictureId}/lines":{"post":{"parameters":[{"schema":{"type":"string"},"in":"query","name":"reportId","required":true},{"schema":{"type":"string"},"in":"path","name":"pictureId","required":true}],"responses":{"200":{"description":"Default Response","content":{"application/json":{"schema":{"type":"string"}}}}}}},"/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","content":{"application/json":{"schema":{"type":"object","properties":{}}}}}}}}}} \ No newline at end of file diff --git a/packages/backend/package.json b/packages/backend/package.json index 8bc82a00..af9a65e1 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -46,6 +46,7 @@ "@react-pdf/renderer": "^3.4.2", "@sentry/node": "^7.70.0", "@sinclair/typebox": "^0.32.20", + "canvas": "^2.11.2", "date-fns": "^3.6.0", "debug": "^4.3.4", "dotenv": "^16.4.5", diff --git a/packages/backend/src/features/image.ts b/packages/backend/src/features/image.ts new file mode 100644 index 00000000..a3fe32f5 --- /dev/null +++ b/packages/backend/src/features/image.ts @@ -0,0 +1,61 @@ +import { db } from "../db/db"; +import canvas, { createCanvas, loadImage } from "canvas"; +import fs from "fs/promises"; + +export const getPictureWithLines = async ({ pictureId }: { pictureId: string }) => { + const picture = await db.pictures.findFirst({ + where: { id: pictureId }, + }); + + const pictureLines = await db.picture_lines.findFirst({ + where: { pictureId }, + }); + + const lines = JSON.parse(pictureLines?.lines || "[]"); + const buffer = await applyLinesToPicture({ pictureUrl: picture!.url!, lines }); + + await fs.writeFile("./test.png", buffer); + + return buffer; +}; + +export const applyLinesToPicture = async ({ + pictureUrl, + lines, +}: { + pictureUrl: string; + lines: Array<{ points: { x: number; y: number }[]; color: string }>; +}) => { + try { + const image = await loadImage(pictureUrl); + + const canvas = createCanvas(image.width, image.height); + const ctx = canvas.getContext("2d"); + + ctx.drawImage(image, 0, 0); + + ctx.lineWidth = 5; + ctx.lineCap = "round"; + ctx.lineJoin = "round"; + + lines.forEach((line) => { + ctx.strokeStyle = line.color; + if (line.points.length > 0) { + ctx.beginPath(); + + ctx.moveTo(line.points[0]!.x, line.points[0]!.y); + + for (let i = 1; i < line.points.length; i++) { + ctx.lineTo(line.points[i]!.x, line.points[i]!.y); + } + + ctx.stroke(); + } + }); + + return canvas.toBuffer("image/png"); + } catch (error) { + console.error("Error processing image:", error); + throw error; + } +}; diff --git a/packages/backend/src/routes/uploadRoutes.tsx b/packages/backend/src/routes/uploadRoutes.tsx index 9696e859..12fab7fb 100644 --- a/packages/backend/src/routes/uploadRoutes.tsx +++ b/packages/backend/src/routes/uploadRoutes.tsx @@ -32,7 +32,7 @@ export const uploadPlugin: FastifyPluginAsyncTypebox = async (fastify, _) => { debug("adding url to pic", id, "for report", reportId); - await db.pictures.create({ data: { id, url, reportId, createdAt: new Date() } }); + await db.pictures.create({ data: { id, url, reportId, createdAt: new Date(), finalUrl: url } }); // try { // await db.tmp_pictures.delete({ where: { id } }); // } catch (e) {} @@ -74,6 +74,30 @@ export const uploadPlugin: FastifyPluginAsyncTypebox = async (fastify, _) => { return buffer.toString("base64"); }, ); + + fastify.post( + "/picture/:pictureId/lines", + { + schema: { + params: Type.Object({ pictureId: Type.String() }), + body: Type.Object({ + lines: Type.Array( + Type.Object({ + points: Type.Array(Type.Object({ x: Type.Number(), y: Type.Number() })), + color: Type.String(), + }), + ), + }), + response: { 200: Type.String() }, + }, + }, + async (request) => { + const { pictureId } = request.params; + const { lines } = request.body; + + return request.services.upload.handleNotifyPictureLines({ pictureId, lines }); + }, + ); }; const getFileName = (file: MultipartFile) => { diff --git a/packages/backend/src/services/uploadService.ts b/packages/backend/src/services/uploadService.ts index d3d1ea4d..dc6830de 100644 --- a/packages/backend/src/services/uploadService.ts +++ b/packages/backend/src/services/uploadService.ts @@ -3,6 +3,8 @@ import { ENV } from "../envVars"; import { makeDebug } from "../features/debug"; import { AppError } from "../features/errors"; import { S3 } from "@aws-sdk/client-s3"; +import { applyLinesToPicture, getPictureWithLines } from "../features/image"; +import { db } from "../db/db"; const client = new S3Client({ endpoint: ENV.AWS_ENDPOINT, region: ENV.AWS_REGION }); const debug = makeDebug("upload"); @@ -87,7 +89,46 @@ export class UploadService { return Buffer.from(buffer); } + + async handleNotifyPictureLines({ + pictureId, + lines, + }: { + pictureId: string; + lines: Array<{ points: { x: number; y: number }[]; color: string }>; + }) { + debug("Handling picture lines", pictureId); + const picture = await db.pictures.findFirst({ + where: { id: pictureId }, + }); + if (!picture) throw new AppError(404, "Picture not found"); + + const buffer = await applyLinesToPicture({ pictureUrl: picture.url!, lines }); + + const name = getPictureName(pictureId, pictureId, Math.round(Date.now() / 1000)); + + const bucketUrl = `${ENV.MINIO_URL}/${ENV.MINIO_BUCKET}`; + + debug("Uploading picture to S3", pictureId); + await imageClient.putObject({ + Bucket: bucketUrl, + Key: name, + Body: buffer, + ACL: "public-read", + ContentType: "image/png", + }); + + debug("Picture uploaded", pictureId); + + const url = `${bucketUrl}/${name}`; + + await db.pictures.update({ where: { id: pictureId }, data: { finalUrl: url } }); + + debug(url); + return url; + } } export const getPDFName = (reportId: string) => `${reportId}/compte_rendu.pdf`; -export const getPictureName = (reportId: string, pictureId: string) => `${reportId}/pictures/${pictureId}.png`; +export const getPictureName = (reportId: string, pictureId: string, snapshot?: number) => + `${reportId}/pictures/${pictureId}${snapshot ? `_${snapshot}` : ""}.png`; diff --git a/packages/backend/test.png b/packages/backend/test.png new file mode 100644 index 0000000000000000000000000000000000000000..b87d8fb4aa357f7850eb64c4ccfa6152b032ff05 GIT binary patch literal 53204 zcma&NbyQT}7dC!}?hrnJw4j8fG)PHzC`dCP-JJq6(t?zLbO=h9ARR+Ucb9a7NH@&9 z*YCUDwSIs87K?T7%#D4{+3`GQ?>!&2G!zN(sPO;*AXHY8dkp|+;3*n_g9RSWeWz~0 z1J*lLML7WV?=QEtFcAP40cE*YI=-2IT2YC#yb(dyVpp+}@4V0B)* zUCo$eF-F5b`9bbXXY~wRM)0HR!Cg9F+4`_W%)*X za{U-h8H~q-50`w)F= zCtU`I|2UVG++G-ztu*=~8j>g+J3QMf2)CYhp>6{WvPU8ZCZp1y``P8!;mf!e%75qo zHV#v5vMAA%xUHYv(TjUs7dj3zprcq86>4ra<_wYk#yN6iI)tmAJ_@N~QL=V5%6$5E z&(kwjwSREnVDqYKT#APW>121hB}hqAyr?{vR7LlYA@;U=C^YbAheA#*dZKFGcU>LD z4XDZCR*eW2T+%y9`r)HhTuOmEu^&EYq4MzOHh`+) z*3&gu5F+_zbYQH`RkU&3`D;e?DOL1~TguVIL$;Nr-4*0JZtMGDK9kHqKBTkVwl=Ax zT?Mn*tP6MXH^)wtP`)mm^(e9#dT)~_uNP7pUDB7(m(P52zT*()=WMVuRYWn`gW#m- zxE8J_V;#s#n~iHEYZu4N)tP(Vmca`{^tRpJe_rA+y!v={Z3Q}?NQ ztadhATw$uYkpHFdZ9Ss@{!5<)ZK>;@-{WP@u>?|UpRG+btu@+TU+PNxb)xhIq-RJv zuAhV}ysU6p^HY#A8yU}vXI1Wlk)CKIs4L%nA#a>JsYcS@@q3ukPVY2QvXv&jxI!H$ z*2IS6eB2gAyUmuqWn!E>Ml5VL3kwy0;D6kh@#*x⋙O=y}&_FvXkmz@paEGg6bCC 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�zm;L06wnszkJ$>KP6i(XV?`K9*` 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 N002ovPDHLkV1m&V9T)%r literal 0 HcmV?d00001 diff --git a/packages/electric-client/prisma/schema.prisma b/packages/electric-client/prisma/schema.prisma index 9007e24b..cd544acd 100644 --- a/packages/electric-client/prisma/schema.prisma +++ b/packages/electric-client/prisma/schema.prisma @@ -145,16 +145,26 @@ model pdf_snapshot { } model pictures { + id String @id + reportId String? + url String? + createdAt DateTime? @db.Timestamp(6) + finalUrl String? + picture_lines picture_lines[] + report report? @relation(fields: [reportId], references: [id], onDelete: Cascade, onUpdate: NoAction) +} + +model tmp_pictures { id String @id reportId String? - url String? createdAt DateTime? @db.Timestamp(6) report report? @relation(fields: [reportId], references: [id], onDelete: Cascade, onUpdate: NoAction) } -model tmp_pictures { +model picture_lines { id String @id - reportId String? + pictureId String? + lines String createdAt DateTime? @db.Timestamp(6) - report report? @relation(fields: [reportId], references: [id], onDelete: Cascade, onUpdate: NoAction) + pictures pictures? @relation(fields: [pictureId], references: [id], onDelete: Cascade, onUpdate: NoAction) } diff --git a/packages/electric-client/src/generated/client/index.ts b/packages/electric-client/src/generated/client/index.ts index a49fcff7..7d329a4a 100644 --- a/packages/electric-client/src/generated/client/index.ts +++ b/packages/electric-client/src/generated/client/index.ts @@ -23,7 +23,7 @@ export const Pdf_snapshotScalarFieldEnumSchema = z.enum(['id','report_id','html' export const Picture_linesScalarFieldEnumSchema = z.enum(['id','pictureId','lines','createdAt']); -export const PicturesScalarFieldEnumSchema = z.enum(['id','reportId','url','createdAt']); +export const PicturesScalarFieldEnumSchema = z.enum(['id','reportId','url','createdAt','finalUrl']); export const QueryModeSchema = z.enum(['default','insensitive']); @@ -120,6 +120,7 @@ export const PicturesSchema = z.object({ reportId: z.string().nullable(), url: z.string().nullable(), createdAt: z.coerce.date().nullable(), + finalUrl: z.string().nullable(), }) export type Pictures = z.infer @@ -322,6 +323,7 @@ export const PicturesSelectSchema: z.ZodType = z.object({ reportId: z.boolean().optional(), url: z.boolean().optional(), createdAt: z.boolean().optional(), + finalUrl: z.boolean().optional(), picture_lines: z.union([z.boolean(),z.lazy(() => Picture_linesFindManyArgsSchema)]).optional(), report: z.union([z.boolean(),z.lazy(() => ReportArgsSchema)]).optional(), _count: z.union([z.boolean(),z.lazy(() => PicturesCountOutputTypeArgsSchema)]).optional(), @@ -722,6 +724,7 @@ export const PicturesWhereInputSchema: z.ZodType = z. reportId: z.union([ z.lazy(() => StringNullableFilterSchema),z.string() ]).optional().nullable(), url: z.union([ z.lazy(() => StringNullableFilterSchema),z.string() ]).optional().nullable(), createdAt: z.union([ z.lazy(() => DateTimeNullableFilterSchema),z.coerce.date() ]).optional().nullable(), + finalUrl: z.union([ z.lazy(() => StringNullableFilterSchema),z.string() ]).optional().nullable(), picture_lines: z.lazy(() => Picture_linesListRelationFilterSchema).optional(), report: z.union([ z.lazy(() => ReportRelationFilterSchema),z.lazy(() => ReportWhereInputSchema) ]).optional().nullable(), }).strict(); @@ -731,6 +734,7 @@ export const PicturesOrderByWithRelationInputSchema: z.ZodType SortOrderSchema).optional(), url: z.lazy(() => SortOrderSchema).optional(), createdAt: z.lazy(() => SortOrderSchema).optional(), + finalUrl: z.lazy(() => SortOrderSchema).optional(), picture_lines: z.lazy(() => Picture_linesOrderByRelationAggregateInputSchema).optional(), report: z.lazy(() => ReportOrderByWithRelationInputSchema).optional() }).strict(); @@ -744,6 +748,7 @@ export const PicturesOrderByWithAggregationInputSchema: z.ZodType SortOrderSchema).optional(), url: z.lazy(() => SortOrderSchema).optional(), createdAt: z.lazy(() => SortOrderSchema).optional(), + finalUrl: z.lazy(() => SortOrderSchema).optional(), _count: z.lazy(() => PicturesCountOrderByAggregateInputSchema).optional(), _max: z.lazy(() => PicturesMaxOrderByAggregateInputSchema).optional(), _min: z.lazy(() => PicturesMinOrderByAggregateInputSchema).optional() @@ -757,6 +762,7 @@ export const PicturesScalarWhereWithAggregatesInputSchema: z.ZodType StringNullableWithAggregatesFilterSchema),z.string() ]).optional().nullable(), url: z.union([ z.lazy(() => StringNullableWithAggregatesFilterSchema),z.string() ]).optional().nullable(), createdAt: z.union([ z.lazy(() => DateTimeNullableWithAggregatesFilterSchema),z.coerce.date() ]).optional().nullable(), + finalUrl: z.union([ z.lazy(() => StringNullableWithAggregatesFilterSchema),z.string() ]).optional().nullable(), }).strict(); export const ReportWhereInputSchema: z.ZodType = z.object({ @@ -1358,6 +1364,7 @@ export const PicturesCreateInputSchema: z.ZodType = id: z.string(), url: z.string().optional().nullable(), createdAt: z.coerce.date().optional().nullable(), + finalUrl: z.string().optional().nullable(), picture_lines: z.lazy(() => Picture_linesCreateNestedManyWithoutPicturesInputSchema).optional(), report: z.lazy(() => ReportCreateNestedOneWithoutPicturesInputSchema).optional() }).strict(); @@ -1367,6 +1374,7 @@ export const PicturesUncheckedCreateInputSchema: z.ZodType Picture_linesUncheckedCreateNestedManyWithoutPicturesInputSchema).optional() }).strict(); @@ -1374,6 +1382,7 @@ export const PicturesUpdateInputSchema: z.ZodType = id: z.union([ z.string(),z.lazy(() => StringFieldUpdateOperationsInputSchema) ]).optional(), url: z.union([ z.string(),z.lazy(() => NullableStringFieldUpdateOperationsInputSchema) ]).optional().nullable(), createdAt: z.union([ z.coerce.date(),z.lazy(() => NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), + finalUrl: z.union([ z.string(),z.lazy(() => NullableStringFieldUpdateOperationsInputSchema) ]).optional().nullable(), picture_lines: z.lazy(() => Picture_linesUpdateManyWithoutPicturesNestedInputSchema).optional(), report: z.lazy(() => ReportUpdateOneWithoutPicturesNestedInputSchema).optional() }).strict(); @@ -1383,6 +1392,7 @@ export const PicturesUncheckedUpdateInputSchema: z.ZodType NullableStringFieldUpdateOperationsInputSchema) ]).optional().nullable(), url: z.union([ z.string(),z.lazy(() => NullableStringFieldUpdateOperationsInputSchema) ]).optional().nullable(), createdAt: z.union([ z.coerce.date(),z.lazy(() => NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), + finalUrl: z.union([ z.string(),z.lazy(() => NullableStringFieldUpdateOperationsInputSchema) ]).optional().nullable(), picture_lines: z.lazy(() => Picture_linesUncheckedUpdateManyWithoutPicturesNestedInputSchema).optional() }).strict(); @@ -1390,13 +1400,15 @@ export const PicturesCreateManyInputSchema: z.ZodType = z.object({ id: z.union([ z.string(),z.lazy(() => StringFieldUpdateOperationsInputSchema) ]).optional(), url: z.union([ z.string(),z.lazy(() => NullableStringFieldUpdateOperationsInputSchema) ]).optional().nullable(), createdAt: z.union([ z.coerce.date(),z.lazy(() => NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), + finalUrl: z.union([ z.string(),z.lazy(() => NullableStringFieldUpdateOperationsInputSchema) ]).optional().nullable(), }).strict(); export const PicturesUncheckedUpdateManyInputSchema: z.ZodType = z.object({ @@ -1404,6 +1416,7 @@ export const PicturesUncheckedUpdateManyInputSchema: z.ZodType NullableStringFieldUpdateOperationsInputSchema) ]).optional().nullable(), url: z.union([ z.string(),z.lazy(() => NullableStringFieldUpdateOperationsInputSchema) ]).optional().nullable(), createdAt: z.union([ z.coerce.date(),z.lazy(() => NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), + finalUrl: z.union([ z.string(),z.lazy(() => NullableStringFieldUpdateOperationsInputSchema) ]).optional().nullable(), }).strict(); export const ReportCreateInputSchema: z.ZodType = z.object({ @@ -2158,21 +2171,24 @@ export const PicturesCountOrderByAggregateInputSchema: z.ZodType SortOrderSchema).optional(), reportId: z.lazy(() => SortOrderSchema).optional(), url: z.lazy(() => SortOrderSchema).optional(), - createdAt: z.lazy(() => SortOrderSchema).optional() + createdAt: z.lazy(() => SortOrderSchema).optional(), + finalUrl: z.lazy(() => SortOrderSchema).optional() }).strict(); export const PicturesMaxOrderByAggregateInputSchema: z.ZodType = z.object({ id: z.lazy(() => SortOrderSchema).optional(), reportId: z.lazy(() => SortOrderSchema).optional(), url: z.lazy(() => SortOrderSchema).optional(), - createdAt: z.lazy(() => SortOrderSchema).optional() + createdAt: z.lazy(() => SortOrderSchema).optional(), + finalUrl: z.lazy(() => SortOrderSchema).optional() }).strict(); export const PicturesMinOrderByAggregateInputSchema: z.ZodType = z.object({ id: z.lazy(() => SortOrderSchema).optional(), reportId: z.lazy(() => SortOrderSchema).optional(), url: z.lazy(() => SortOrderSchema).optional(), - createdAt: z.lazy(() => SortOrderSchema).optional() + createdAt: z.lazy(() => SortOrderSchema).optional(), + finalUrl: z.lazy(() => SortOrderSchema).optional() }).strict(); export const DateTimeFilterSchema: z.ZodType = z.object({ @@ -3210,6 +3226,7 @@ export const PicturesCreateWithoutPicture_linesInputSchema: z.ZodType ReportCreateNestedOneWithoutPicturesInputSchema).optional() }).strict(); @@ -3217,7 +3234,8 @@ export const PicturesUncheckedCreateWithoutPicture_linesInputSchema: z.ZodType

= z.object({ @@ -3234,6 +3252,7 @@ export const PicturesUpdateWithoutPicture_linesInputSchema: z.ZodType StringFieldUpdateOperationsInputSchema) ]).optional(), url: z.union([ z.string(),z.lazy(() => NullableStringFieldUpdateOperationsInputSchema) ]).optional().nullable(), createdAt: z.union([ z.coerce.date(),z.lazy(() => NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), + finalUrl: z.union([ z.string(),z.lazy(() => NullableStringFieldUpdateOperationsInputSchema) ]).optional().nullable(), report: z.lazy(() => ReportUpdateOneWithoutPicturesNestedInputSchema).optional() }).strict(); @@ -3242,6 +3261,7 @@ export const PicturesUncheckedUpdateWithoutPicture_linesInputSchema: z.ZodType

NullableStringFieldUpdateOperationsInputSchema) ]).optional().nullable(), url: z.union([ z.string(),z.lazy(() => NullableStringFieldUpdateOperationsInputSchema) ]).optional().nullable(), createdAt: z.union([ z.coerce.date(),z.lazy(() => NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), + finalUrl: z.union([ z.string(),z.lazy(() => NullableStringFieldUpdateOperationsInputSchema) ]).optional().nullable(), }).strict(); export const Picture_linesCreateWithoutPicturesInputSchema: z.ZodType = z.object({ @@ -3414,6 +3434,7 @@ export const PicturesCreateWithoutReportInputSchema: z.ZodType Picture_linesCreateNestedManyWithoutPicturesInputSchema).optional() }).strict(); @@ -3421,6 +3442,7 @@ export const PicturesUncheckedCreateWithoutReportInputSchema: z.ZodType Picture_linesUncheckedCreateNestedManyWithoutPicturesInputSchema).optional() }).strict(); @@ -3499,6 +3521,7 @@ export const PicturesScalarWhereInputSchema: z.ZodType StringNullableFilterSchema),z.string() ]).optional().nullable(), url: z.union([ z.lazy(() => StringNullableFilterSchema),z.string() ]).optional().nullable(), createdAt: z.union([ z.lazy(() => DateTimeNullableFilterSchema),z.coerce.date() ]).optional().nullable(), + finalUrl: z.union([ z.lazy(() => StringNullableFilterSchema),z.string() ]).optional().nullable(), }).strict(); export const UserUpsertWithoutReportInputSchema: z.ZodType = z.object({ @@ -4002,7 +4025,8 @@ export const Picture_linesUncheckedUpdateManyWithoutPicture_linesInputSchema: z. export const PicturesCreateManyReportInputSchema: z.ZodType = z.object({ id: z.string(), url: z.string().optional().nullable(), - createdAt: z.coerce.date().optional().nullable() + createdAt: z.coerce.date().optional().nullable(), + finalUrl: z.string().optional().nullable() }).strict(); export const Tmp_picturesCreateManyReportInputSchema: z.ZodType = z.object({ @@ -4014,6 +4038,7 @@ export const PicturesUpdateWithoutReportInputSchema: z.ZodType StringFieldUpdateOperationsInputSchema) ]).optional(), url: z.union([ z.string(),z.lazy(() => NullableStringFieldUpdateOperationsInputSchema) ]).optional().nullable(), createdAt: z.union([ z.coerce.date(),z.lazy(() => NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), + finalUrl: z.union([ z.string(),z.lazy(() => NullableStringFieldUpdateOperationsInputSchema) ]).optional().nullable(), picture_lines: z.lazy(() => Picture_linesUpdateManyWithoutPicturesNestedInputSchema).optional() }).strict(); @@ -4021,6 +4046,7 @@ export const PicturesUncheckedUpdateWithoutReportInputSchema: z.ZodType StringFieldUpdateOperationsInputSchema) ]).optional(), url: z.union([ z.string(),z.lazy(() => NullableStringFieldUpdateOperationsInputSchema) ]).optional().nullable(), createdAt: z.union([ z.coerce.date(),z.lazy(() => NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), + finalUrl: z.union([ z.string(),z.lazy(() => NullableStringFieldUpdateOperationsInputSchema) ]).optional().nullable(), picture_lines: z.lazy(() => Picture_linesUncheckedUpdateManyWithoutPicturesNestedInputSchema).optional() }).strict(); @@ -4028,6 +4054,7 @@ export const PicturesUncheckedUpdateManyWithoutPicturesInputSchema: z.ZodType StringFieldUpdateOperationsInputSchema) ]).optional(), url: z.union([ z.string(),z.lazy(() => NullableStringFieldUpdateOperationsInputSchema) ]).optional().nullable(), createdAt: z.union([ z.coerce.date(),z.lazy(() => NullableDateTimeFieldUpdateOperationsInputSchema) ]).optional().nullable(), + finalUrl: z.union([ z.string(),z.lazy(() => NullableStringFieldUpdateOperationsInputSchema) ]).optional().nullable(), }).strict(); export const Tmp_picturesUpdateWithoutReportInputSchema: z.ZodType = z.object({ @@ -5617,6 +5644,10 @@ export const tableSchemas = { [ "createdAt", "TIMESTAMP" + ], + [ + "finalUrl", + "TEXT" ] ]), relations: [ diff --git a/packages/electric-client/src/generated/client/migrations.ts b/packages/electric-client/src/generated/client/migrations.ts index a76a889b..2ba0cce1 100644 --- a/packages/electric-client/src/generated/client/migrations.ts +++ b/packages/electric-client/src/generated/client/migrations.ts @@ -272,5 +272,24 @@ export default [ "CREATE TRIGGER compensation_update_main_picture_lines_pictureId_into_oplog\n AFTER UPDATE ON \"main\".\"picture_lines\"\n WHEN 1 = (SELECT flag from _electric_trigger_settings WHERE namespace = 'main' AND tablename = 'picture_lines') AND\n 1 = (SELECT value from _electric_meta WHERE key = 'compensations')\nBEGIN\n INSERT INTO _electric_oplog (namespace, tablename, optype, primaryKey, newRow, oldRow, timestamp)\n SELECT 'main', 'pictures', 'COMPENSATION', json_patch('{}', json_object('id', \"id\")), json_object('id', \"id\"), NULL, NULL\n FROM \"main\".\"pictures\" WHERE \"id\" = new.\"pictureId\";\nEND;" ], "version": "909" + }, + { + "statements": [ + "ALTER TABLE \"pictures\" ADD COLUMN \"finalUrl\" TEXT;\n", + "INSERT OR IGNORE INTO _electric_trigger_settings (namespace, tablename, flag) VALUES ('main', 'pictures', 1);", + "DROP TRIGGER IF EXISTS update_ensure_main_pictures_primarykey;", + "CREATE TRIGGER update_ensure_main_pictures_primarykey\n BEFORE UPDATE ON \"main\".\"pictures\"\nBEGIN\n SELECT\n CASE\n WHEN old.\"id\" != new.\"id\" THEN\n \t\tRAISE (ABORT, 'cannot change the value of column id as it belongs to the primary key')\n END;\nEND;", + "DROP TRIGGER IF EXISTS insert_main_pictures_into_oplog;", + "CREATE TRIGGER insert_main_pictures_into_oplog\n AFTER INSERT ON \"main\".\"pictures\"\n WHEN 1 = (SELECT flag from _electric_trigger_settings WHERE namespace = 'main' AND tablename = 'pictures')\nBEGIN\n INSERT INTO _electric_oplog (namespace, tablename, optype, primaryKey, newRow, oldRow, timestamp)\n VALUES ('main', 'pictures', 'INSERT', json_patch('{}', json_object('id', new.\"id\")), json_object('createdAt', new.\"createdAt\", 'finalUrl', new.\"finalUrl\", 'id', new.\"id\", 'reportId', new.\"reportId\", 'url', new.\"url\"), NULL, NULL);\nEND;", + "DROP TRIGGER IF EXISTS update_main_pictures_into_oplog;", + "CREATE TRIGGER update_main_pictures_into_oplog\n AFTER UPDATE ON \"main\".\"pictures\"\n WHEN 1 = (SELECT flag from _electric_trigger_settings WHERE namespace = 'main' AND tablename = 'pictures')\nBEGIN\n INSERT INTO _electric_oplog (namespace, tablename, optype, primaryKey, newRow, oldRow, timestamp)\n VALUES ('main', 'pictures', 'UPDATE', json_patch('{}', json_object('id', new.\"id\")), json_object('createdAt', new.\"createdAt\", 'finalUrl', new.\"finalUrl\", 'id', new.\"id\", 'reportId', new.\"reportId\", 'url', new.\"url\"), json_object('createdAt', old.\"createdAt\", 'finalUrl', old.\"finalUrl\", 'id', old.\"id\", 'reportId', old.\"reportId\", 'url', old.\"url\"), NULL);\nEND;", + "DROP TRIGGER IF EXISTS delete_main_pictures_into_oplog;", + "CREATE TRIGGER delete_main_pictures_into_oplog\n AFTER DELETE ON \"main\".\"pictures\"\n WHEN 1 = (SELECT flag from _electric_trigger_settings WHERE namespace = 'main' AND tablename = 'pictures')\nBEGIN\n INSERT INTO _electric_oplog (namespace, tablename, optype, primaryKey, newRow, oldRow, timestamp)\n VALUES ('main', 'pictures', 'DELETE', json_patch('{}', json_object('id', old.\"id\")), NULL, json_object('createdAt', old.\"createdAt\", 'finalUrl', old.\"finalUrl\", 'id', old.\"id\", 'reportId', old.\"reportId\", 'url', old.\"url\"), NULL);\nEND;", + "DROP TRIGGER IF EXISTS compensation_insert_main_pictures_reportId_into_oplog;", + "CREATE TRIGGER compensation_insert_main_pictures_reportId_into_oplog\n AFTER INSERT ON \"main\".\"pictures\"\n WHEN 1 = (SELECT flag from _electric_trigger_settings WHERE namespace = 'main' AND tablename = 'pictures') AND\n 1 = (SELECT value from _electric_meta WHERE key = 'compensations')\nBEGIN\n INSERT INTO _electric_oplog (namespace, tablename, optype, primaryKey, newRow, oldRow, timestamp)\n SELECT 'main', 'report', 'COMPENSATION', json_patch('{}', json_object('id', \"id\")), json_object('id', \"id\"), NULL, NULL\n FROM \"main\".\"report\" WHERE \"id\" = new.\"reportId\";\nEND;", + "DROP TRIGGER IF EXISTS compensation_update_main_pictures_reportId_into_oplog;", + "CREATE TRIGGER compensation_update_main_pictures_reportId_into_oplog\n AFTER UPDATE ON \"main\".\"pictures\"\n WHEN 1 = (SELECT flag from _electric_trigger_settings WHERE namespace = 'main' AND tablename = 'pictures') AND\n 1 = (SELECT value from _electric_meta WHERE key = 'compensations')\nBEGIN\n INSERT INTO _electric_oplog (namespace, tablename, optype, primaryKey, newRow, oldRow, timestamp)\n SELECT 'main', 'report', 'COMPENSATION', json_patch('{}', json_object('id', \"id\")), json_object('id', \"id\"), NULL, NULL\n FROM \"main\".\"report\" WHERE \"id\" = new.\"reportId\";\nEND;" + ], + "version": "910" } ] \ No newline at end of file diff --git a/packages/electric-client/src/generated/client/pg-migrations.ts b/packages/electric-client/src/generated/client/pg-migrations.ts index 8116b664..74b53c1c 100644 --- a/packages/electric-client/src/generated/client/pg-migrations.ts +++ b/packages/electric-client/src/generated/client/pg-migrations.ts @@ -358,5 +358,30 @@ export default [ "CREATE TRIGGER compensation_update_public_picture_lines_pictureId_into_oplog\n AFTER UPDATE ON \"public\".\"picture_lines\"\n FOR EACH ROW\n EXECUTE FUNCTION compensation_update_public_picture_lines_pictureId_into_oplog_function();" ], "version": "909" + }, + { + "statements": [ + "ALTER TABLE pictures ADD COLUMN \"finalUrl\" TEXT", + "INSERT INTO \"public\".\"_electric_trigger_settings\" (\"namespace\", \"tablename\", \"flag\")\n VALUES ('public', 'pictures', 1)\n ON CONFLICT DO NOTHING;", + "DROP TRIGGER IF EXISTS update_ensure_public_pictures_primarykey ON \"public\".\"pictures\";", + "CREATE OR REPLACE FUNCTION update_ensure_public_pictures_primarykey_function()\nRETURNS TRIGGER AS $$\nBEGIN\n IF OLD.\"id\" IS DISTINCT FROM NEW.\"id\" THEN\n RAISE EXCEPTION 'Cannot change the value of column id as it belongs to the primary key';\n END IF;\n RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;", + "CREATE TRIGGER update_ensure_public_pictures_primarykey\n BEFORE UPDATE ON \"public\".\"pictures\"\n FOR EACH ROW\n EXECUTE FUNCTION update_ensure_public_pictures_primarykey_function();", + "DROP TRIGGER IF EXISTS insert_public_pictures_into_oplog ON \"public\".\"pictures\";", + " CREATE OR REPLACE FUNCTION insert_public_pictures_into_oplog_function()\n RETURNS TRIGGER AS $$\n BEGIN\n DECLARE\n flag_value INTEGER;\n BEGIN\n -- Get the flag value from _electric_trigger_settings\n SELECT flag INTO flag_value FROM \"public\"._electric_trigger_settings WHERE namespace = 'public' AND tablename = 'pictures';\n\n IF flag_value = 1 THEN\n -- Insert into _electric_oplog\n INSERT INTO \"public\"._electric_oplog (namespace, tablename, optype, \"primaryKey\", \"newRow\", \"oldRow\", timestamp)\n VALUES (\n 'public',\n 'pictures',\n 'INSERT',\n json_strip_nulls(json_build_object('id', new.\"id\")),\n jsonb_build_object('createdAt', new.\"createdAt\", 'finalUrl', new.\"finalUrl\", 'id', new.\"id\", 'reportId', new.\"reportId\", 'url', new.\"url\"),\n NULL,\n NULL\n );\n END IF;\n\n RETURN NEW;\n END;\n END;\n $$ LANGUAGE plpgsql;", + "CREATE TRIGGER insert_public_pictures_into_oplog\n AFTER INSERT ON \"public\".\"pictures\"\n FOR EACH ROW\n EXECUTE FUNCTION insert_public_pictures_into_oplog_function();", + "DROP TRIGGER IF EXISTS update_public_pictures_into_oplog ON \"public\".\"pictures\";", + " CREATE OR REPLACE FUNCTION update_public_pictures_into_oplog_function()\n RETURNS TRIGGER AS $$\n BEGIN\n DECLARE\n flag_value INTEGER;\n BEGIN\n -- Get the flag value from _electric_trigger_settings\n SELECT flag INTO flag_value FROM \"public\"._electric_trigger_settings WHERE namespace = 'public' AND tablename = 'pictures';\n\n IF flag_value = 1 THEN\n -- Insert into _electric_oplog\n INSERT INTO \"public\"._electric_oplog (namespace, tablename, optype, \"primaryKey\", \"newRow\", \"oldRow\", timestamp)\n VALUES (\n 'public',\n 'pictures',\n 'UPDATE',\n json_strip_nulls(json_build_object('id', new.\"id\")),\n jsonb_build_object('createdAt', new.\"createdAt\", 'finalUrl', new.\"finalUrl\", 'id', new.\"id\", 'reportId', new.\"reportId\", 'url', new.\"url\"),\n jsonb_build_object('createdAt', old.\"createdAt\", 'finalUrl', old.\"finalUrl\", 'id', old.\"id\", 'reportId', old.\"reportId\", 'url', old.\"url\"),\n NULL\n );\n END IF;\n\n RETURN NEW;\n END;\n END;\n $$ LANGUAGE plpgsql;", + "CREATE TRIGGER update_public_pictures_into_oplog\n AFTER UPDATE ON \"public\".\"pictures\"\n FOR EACH ROW\n EXECUTE FUNCTION update_public_pictures_into_oplog_function();", + "DROP TRIGGER IF EXISTS delete_public_pictures_into_oplog ON \"public\".\"pictures\";", + " CREATE OR REPLACE FUNCTION delete_public_pictures_into_oplog_function()\n RETURNS TRIGGER AS $$\n BEGIN\n DECLARE\n flag_value INTEGER;\n BEGIN\n -- Get the flag value from _electric_trigger_settings\n SELECT flag INTO flag_value FROM \"public\"._electric_trigger_settings WHERE namespace = 'public' AND tablename = 'pictures';\n\n IF flag_value = 1 THEN\n -- Insert into _electric_oplog\n INSERT INTO \"public\"._electric_oplog (namespace, tablename, optype, \"primaryKey\", \"newRow\", \"oldRow\", timestamp)\n VALUES (\n 'public',\n 'pictures',\n 'DELETE',\n json_strip_nulls(json_build_object('id', old.\"id\")),\n NULL,\n jsonb_build_object('createdAt', old.\"createdAt\", 'finalUrl', old.\"finalUrl\", 'id', old.\"id\", 'reportId', old.\"reportId\", 'url', old.\"url\"),\n NULL\n );\n END IF;\n\n RETURN NEW;\n END;\n END;\n $$ LANGUAGE plpgsql;", + "CREATE TRIGGER delete_public_pictures_into_oplog\n AFTER DELETE ON \"public\".\"pictures\"\n FOR EACH ROW\n EXECUTE FUNCTION delete_public_pictures_into_oplog_function();", + "DROP TRIGGER IF EXISTS compensation_insert_public_pictures_reportId_into_oplog ON \"public\".\"pictures\";", + " CREATE OR REPLACE FUNCTION compensation_insert_public_pictures_reportId_into_oplog_function()\n RETURNS TRIGGER AS $$\n BEGIN\n DECLARE\n flag_value INTEGER;\n meta_value INTEGER;\n BEGIN\n SELECT flag INTO flag_value FROM \"public\"._electric_trigger_settings WHERE namespace = 'public' AND tablename = 'pictures';\n\n SELECT value INTO meta_value FROM \"public\"._electric_meta WHERE key = 'compensations';\n\n IF flag_value = 1 AND meta_value = 1 THEN\n INSERT INTO \"public\"._electric_oplog (namespace, tablename, optype, \"primaryKey\", \"newRow\", \"oldRow\", timestamp)\n SELECT\n 'public',\n 'report',\n 'COMPENSATION',\n json_strip_nulls(json_strip_nulls(json_build_object('id', \"id\"))),\n jsonb_build_object('id', \"id\"),\n NULL,\n NULL\n FROM \"public\".\"report\"\n WHERE \"id\" = NEW.\"reportId\";\n END IF;\n\n RETURN NEW;\n END;\n END;\n $$ LANGUAGE plpgsql;", + "CREATE TRIGGER compensation_insert_public_pictures_reportId_into_oplog\n AFTER INSERT ON \"public\".\"pictures\"\n FOR EACH ROW\n EXECUTE FUNCTION compensation_insert_public_pictures_reportId_into_oplog_function();", + "DROP TRIGGER IF EXISTS compensation_update_public_pictures_reportId_into_oplog ON \"public\".\"pictures\";", + " CREATE OR REPLACE FUNCTION compensation_update_public_pictures_reportId_into_oplog_function()\n RETURNS TRIGGER AS $$\n BEGIN\n DECLARE\n flag_value INTEGER;\n meta_value INTEGER;\n BEGIN\n SELECT flag INTO flag_value FROM \"public\"._electric_trigger_settings WHERE namespace = 'public' AND tablename = 'pictures';\n\n SELECT value INTO meta_value FROM \"public\"._electric_meta WHERE key = 'compensations';\n\n IF flag_value = 1 AND meta_value = 1 THEN\n INSERT INTO \"public\"._electric_oplog (namespace, tablename, optype, \"primaryKey\", \"newRow\", \"oldRow\", timestamp)\n SELECT\n 'public',\n 'report',\n 'COMPENSATION',\n json_strip_nulls(json_strip_nulls(json_build_object('id', \"id\"))),\n jsonb_build_object('id', \"id\"),\n NULL,\n NULL\n FROM \"public\".\"report\"\n WHERE \"id\" = NEW.\"reportId\";\n END IF;\n\n RETURN NEW;\n END;\n END;\n $$ LANGUAGE plpgsql;", + "CREATE TRIGGER compensation_update_public_pictures_reportId_into_oplog\n AFTER UPDATE ON \"public\".\"pictures\"\n FOR EACH ROW\n EXECUTE FUNCTION compensation_update_public_pictures_reportId_into_oplog_function();" + ], + "version": "910" } ] \ No newline at end of file diff --git a/packages/electric-client/src/generated/client/prismaClient.d.ts b/packages/electric-client/src/generated/client/prismaClient.d.ts index 108406e2..bbb02899 100644 --- a/packages/electric-client/src/generated/client/prismaClient.d.ts +++ b/packages/electric-client/src/generated/client/prismaClient.d.ts @@ -118,6 +118,7 @@ export type PicturesPayload composites: {} } @@ -6582,6 +6583,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject reportId: string | null url: string | null createdAt: Date | null + finalUrl: string | null } export type PicturesMaxAggregateOutputType = { @@ -6589,6 +6591,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject reportId: string | null url: string | null createdAt: Date | null + finalUrl: string | null } export type PicturesCountAggregateOutputType = { @@ -6596,6 +6599,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject reportId: number url: number createdAt: number + finalUrl: number _all: number } @@ -6605,6 +6609,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject reportId?: true url?: true createdAt?: true + finalUrl?: true } export type PicturesMaxAggregateInputType = { @@ -6612,6 +6617,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject reportId?: true url?: true createdAt?: true + finalUrl?: true } export type PicturesCountAggregateInputType = { @@ -6619,6 +6625,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject reportId?: true url?: true createdAt?: true + finalUrl?: true _all?: true } @@ -6700,6 +6707,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject reportId: string | null url: string | null createdAt: Date | null + finalUrl: string | null _count: PicturesCountAggregateOutputType | null _min: PicturesMinAggregateOutputType | null _max: PicturesMaxAggregateOutputType | null @@ -6724,6 +6732,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject reportId?: boolean url?: boolean createdAt?: boolean + finalUrl?: boolean picture_lines?: boolean | Pictures$picture_linesArgs report?: boolean | ReportArgs _count?: boolean | PicturesCountOutputTypeArgs @@ -6734,6 +6743,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject reportId?: boolean url?: boolean createdAt?: boolean + finalUrl?: boolean } export type PicturesInclude = { @@ -12579,7 +12589,8 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject id: 'id', reportId: 'reportId', url: 'url', - createdAt: 'createdAt' + createdAt: 'createdAt', + finalUrl: 'finalUrl' }; export type PicturesScalarFieldEnum = (typeof PicturesScalarFieldEnum)[keyof typeof PicturesScalarFieldEnum] @@ -12921,6 +12932,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject reportId?: StringNullableFilter | string | null url?: StringNullableFilter | string | null createdAt?: DateTimeNullableFilter | Date | string | null + finalUrl?: StringNullableFilter | string | null picture_lines?: Picture_linesListRelationFilter report?: XOR | null } @@ -12930,6 +12942,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject reportId?: SortOrderInput | SortOrder url?: SortOrderInput | SortOrder createdAt?: SortOrderInput | SortOrder + finalUrl?: SortOrderInput | SortOrder picture_lines?: Picture_linesOrderByRelationAggregateInput report?: ReportOrderByWithRelationInput } @@ -12943,6 +12956,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject reportId?: SortOrderInput | SortOrder url?: SortOrderInput | SortOrder createdAt?: SortOrderInput | SortOrder + finalUrl?: SortOrderInput | SortOrder _count?: PicturesCountOrderByAggregateInput _max?: PicturesMaxOrderByAggregateInput _min?: PicturesMinOrderByAggregateInput @@ -12956,6 +12970,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject reportId?: StringNullableWithAggregatesFilter | string | null url?: StringNullableWithAggregatesFilter | string | null createdAt?: DateTimeNullableWithAggregatesFilter | Date | string | null + finalUrl?: StringNullableWithAggregatesFilter | string | null } export type ReportWhereInput = { @@ -13558,6 +13573,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject id: string url?: string | null createdAt?: Date | string | null + finalUrl?: string | null picture_lines?: Picture_linesCreateNestedManyWithoutPicturesInput report?: ReportCreateNestedOneWithoutPicturesInput } @@ -13567,6 +13583,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject reportId?: string | null url?: string | null createdAt?: Date | string | null + finalUrl?: string | null picture_lines?: Picture_linesUncheckedCreateNestedManyWithoutPicturesInput } @@ -13574,6 +13591,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject id?: StringFieldUpdateOperationsInput | string url?: NullableStringFieldUpdateOperationsInput | string | null createdAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + finalUrl?: NullableStringFieldUpdateOperationsInput | string | null picture_lines?: Picture_linesUpdateManyWithoutPicturesNestedInput report?: ReportUpdateOneWithoutPicturesNestedInput } @@ -13583,6 +13601,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject reportId?: NullableStringFieldUpdateOperationsInput | string | null url?: NullableStringFieldUpdateOperationsInput | string | null createdAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + finalUrl?: NullableStringFieldUpdateOperationsInput | string | null picture_lines?: Picture_linesUncheckedUpdateManyWithoutPicturesNestedInput } @@ -13591,12 +13610,14 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject reportId?: string | null url?: string | null createdAt?: Date | string | null + finalUrl?: string | null } export type PicturesUpdateManyMutationInput = { id?: StringFieldUpdateOperationsInput | string url?: NullableStringFieldUpdateOperationsInput | string | null createdAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + finalUrl?: NullableStringFieldUpdateOperationsInput | string | null } export type PicturesUncheckedUpdateManyInput = { @@ -13604,6 +13625,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject reportId?: NullableStringFieldUpdateOperationsInput | string | null url?: NullableStringFieldUpdateOperationsInput | string | null createdAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + finalUrl?: NullableStringFieldUpdateOperationsInput | string | null } export type ReportCreateInput = { @@ -14364,6 +14386,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject reportId?: SortOrder url?: SortOrder createdAt?: SortOrder + finalUrl?: SortOrder } export type PicturesMaxOrderByAggregateInput = { @@ -14371,6 +14394,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject reportId?: SortOrder url?: SortOrder createdAt?: SortOrder + finalUrl?: SortOrder } export type PicturesMinOrderByAggregateInput = { @@ -14378,6 +14402,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject reportId?: SortOrder url?: SortOrder createdAt?: SortOrder + finalUrl?: SortOrder } export type DateTimeFilter = { @@ -15415,6 +15440,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject id: string url?: string | null createdAt?: Date | string | null + finalUrl?: string | null report?: ReportCreateNestedOneWithoutPicturesInput } @@ -15423,6 +15449,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject reportId?: string | null url?: string | null createdAt?: Date | string | null + finalUrl?: string | null } export type PicturesCreateOrConnectWithoutPicture_linesInput = { @@ -15439,6 +15466,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject id?: StringFieldUpdateOperationsInput | string url?: NullableStringFieldUpdateOperationsInput | string | null createdAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + finalUrl?: NullableStringFieldUpdateOperationsInput | string | null report?: ReportUpdateOneWithoutPicturesNestedInput } @@ -15447,6 +15475,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject reportId?: NullableStringFieldUpdateOperationsInput | string | null url?: NullableStringFieldUpdateOperationsInput | string | null createdAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + finalUrl?: NullableStringFieldUpdateOperationsInput | string | null } export type Picture_linesCreateWithoutPicturesInput = { @@ -15619,6 +15648,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject id: string url?: string | null createdAt?: Date | string | null + finalUrl?: string | null picture_lines?: Picture_linesCreateNestedManyWithoutPicturesInput } @@ -15626,6 +15656,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject id: string url?: string | null createdAt?: Date | string | null + finalUrl?: string | null picture_lines?: Picture_linesUncheckedCreateNestedManyWithoutPicturesInput } @@ -15704,6 +15735,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject reportId?: StringNullableFilter | string | null url?: StringNullableFilter | string | null createdAt?: DateTimeNullableFilter | Date | string | null + finalUrl?: StringNullableFilter | string | null } export type UserUpsertWithoutReportInput = { @@ -16208,6 +16240,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject id: string url?: string | null createdAt?: Date | string | null + finalUrl?: string | null } export type Tmp_picturesCreateManyReportInput = { @@ -16219,6 +16252,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject id?: StringFieldUpdateOperationsInput | string url?: NullableStringFieldUpdateOperationsInput | string | null createdAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + finalUrl?: NullableStringFieldUpdateOperationsInput | string | null picture_lines?: Picture_linesUpdateManyWithoutPicturesNestedInput } @@ -16226,6 +16260,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject id?: StringFieldUpdateOperationsInput | string url?: NullableStringFieldUpdateOperationsInput | string | null createdAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + finalUrl?: NullableStringFieldUpdateOperationsInput | string | null picture_lines?: Picture_linesUncheckedUpdateManyWithoutPicturesNestedInput } @@ -16233,6 +16268,7 @@ export type InputJsonValue = null | string | number | boolean | InputJsonObject id?: StringFieldUpdateOperationsInput | string url?: NullableStringFieldUpdateOperationsInput | string | null createdAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null + finalUrl?: NullableStringFieldUpdateOperationsInput | string | null } export type Tmp_picturesUpdateWithoutReportInput = { diff --git a/packages/electric-client/src/generated/typebox/index.ts b/packages/electric-client/src/generated/typebox/index.ts index cf5f77aa..e2c29bdf 100644 --- a/packages/electric-client/src/generated/typebox/index.ts +++ b/packages/electric-client/src/generated/typebox/index.ts @@ -1,7 +1,7 @@ -export * from './atdatabases_migrations_applied'; export * from './atdatabases_migrations_appliedInput'; -export * from './atdatabases_migrations_versionInput'; export * from './atdatabases_migrations_version'; +export * from './atdatabases_migrations_applied'; +export * from './atdatabases_migrations_versionInput'; export * from './clause'; export * from './clauseInput'; export * from './report'; @@ -26,3 +26,5 @@ export * from './pictures'; export * from './picturesInput'; export * from './tmp_pictures'; export * from './tmp_picturesInput'; +export * from './picture_lines'; +export * from './picture_linesInput'; diff --git a/packages/electric-client/src/generated/typebox/picture_lines.ts b/packages/electric-client/src/generated/typebox/picture_lines.ts new file mode 100644 index 00000000..11b908eb --- /dev/null +++ b/packages/electric-client/src/generated/typebox/picture_lines.ts @@ -0,0 +1,19 @@ +import { Type, Static } from "@sinclair/typebox"; + +export const picture_lines = Type.Object({ + id: Type.String(), + pictureId: Type.Optional(Type.String()), + lines: Type.String(), + createdAt: Type.Optional(Type.String()), + pictures: Type.Optional( + Type.Object({ + id: Type.String(), + reportId: Type.Optional(Type.String()), + url: Type.Optional(Type.String()), + createdAt: Type.Optional(Type.String()), + finalUrl: Type.Optional(Type.String()), + }) + ), +}); + +export type picture_linesType = Static; diff --git a/packages/electric-client/src/generated/typebox/picture_linesInput.ts b/packages/electric-client/src/generated/typebox/picture_linesInput.ts new file mode 100644 index 00000000..2cdae0db --- /dev/null +++ b/packages/electric-client/src/generated/typebox/picture_linesInput.ts @@ -0,0 +1,19 @@ +import { Type, Static } from "@sinclair/typebox"; + +export const picture_linesInput = Type.Object({ + id: Type.String(), + pictureId: Type.Optional(Type.String()), + lines: Type.String(), + createdAt: Type.Optional(Type.String()), + pictures: Type.Optional( + Type.Object({ + id: Type.String(), + reportId: Type.Optional(Type.String()), + url: Type.Optional(Type.String()), + createdAt: Type.Optional(Type.String()), + finalUrl: Type.Optional(Type.String()), + }) + ), +}); + +export type picture_linesInputType = Static; diff --git a/packages/electric-client/src/generated/typebox/pictures.ts b/packages/electric-client/src/generated/typebox/pictures.ts index 044489b6..e1125026 100644 --- a/packages/electric-client/src/generated/typebox/pictures.ts +++ b/packages/electric-client/src/generated/typebox/pictures.ts @@ -5,6 +5,15 @@ export const pictures = Type.Object({ reportId: Type.Optional(Type.String()), url: Type.Optional(Type.String()), createdAt: Type.Optional(Type.String()), + finalUrl: Type.Optional(Type.String()), + picture_lines: Type.Array( + Type.Object({ + id: Type.String(), + pictureId: Type.Optional(Type.String()), + lines: Type.String(), + createdAt: Type.Optional(Type.String()), + }) + ), report: Type.Optional( Type.Object({ id: Type.String(), diff --git a/packages/electric-client/src/generated/typebox/picturesInput.ts b/packages/electric-client/src/generated/typebox/picturesInput.ts index 63d4544a..51b025eb 100644 --- a/packages/electric-client/src/generated/typebox/picturesInput.ts +++ b/packages/electric-client/src/generated/typebox/picturesInput.ts @@ -5,6 +5,15 @@ export const picturesInput = Type.Object({ reportId: Type.Optional(Type.String()), url: Type.Optional(Type.String()), createdAt: Type.Optional(Type.String()), + finalUrl: Type.Optional(Type.String()), + picture_lines: Type.Array( + Type.Object({ + id: Type.String(), + pictureId: Type.Optional(Type.String()), + lines: Type.String(), + createdAt: Type.Optional(Type.String()), + }) + ), report: Type.Optional( Type.Object({ id: Type.String(), diff --git a/packages/electric-client/src/generated/typebox/report.ts b/packages/electric-client/src/generated/typebox/report.ts index 2a2658f3..1c0bc842 100644 --- a/packages/electric-client/src/generated/typebox/report.ts +++ b/packages/electric-client/src/generated/typebox/report.ts @@ -30,6 +30,7 @@ export const report = Type.Object({ reportId: Type.Optional(Type.String()), url: Type.Optional(Type.String()), createdAt: Type.Optional(Type.String()), + finalUrl: Type.Optional(Type.String()), }) ), user: Type.Object({ diff --git a/packages/electric-client/src/generated/typebox/reportInput.ts b/packages/electric-client/src/generated/typebox/reportInput.ts index d6faca30..40f7e6c2 100644 --- a/packages/electric-client/src/generated/typebox/reportInput.ts +++ b/packages/electric-client/src/generated/typebox/reportInput.ts @@ -30,6 +30,7 @@ export const reportInput = Type.Object({ reportId: Type.Optional(Type.String()), url: Type.Optional(Type.String()), createdAt: Type.Optional(Type.String()), + finalUrl: Type.Optional(Type.String()), }) ), user: Type.Object({ diff --git a/packages/frontend/src/api.gen.ts b/packages/frontend/src/api.gen.ts index 6e9cdbf2..0a16c35b 100644 --- a/packages/frontend/src/api.gen.ts +++ b/packages/frontend/src/api.gen.ts @@ -150,6 +150,23 @@ export namespace Endpoints { parameters: never; response: unknown; }; + export type get_Apiuploadpicture = { + method: "GET"; + path: "/api/upload/picture"; + parameters: { + query: { reportId: string; pictureId: string }; + }; + response: Partial<{}>; + }; + export type post_ApiuploadpicturePictureIdlines = { + method: "POST"; + path: "/api/upload/picture/{pictureId}/lines"; + parameters: { + query: { reportId: string }; + path: { pictureId: string }; + }; + response: string; + }; export type post_Apipdfreport = { method: "POST"; path: "/api/pdf/report"; @@ -178,11 +195,13 @@ export type EndpointByMethod = { "/api/send-reset-password": Endpoints.post_ApisendResetPassword; "/api/reset-password": Endpoints.post_ApiresetPassword; "/api/upload/image": Endpoints.post_Apiuploadimage; + "/api/upload/picture/{pictureId}/lines": Endpoints.post_ApiuploadpicturePictureIdlines; "/api/pdf/report": Endpoints.post_Apipdfreport; }; get: { "/api/refresh-token": Endpoints.get_ApirefreshToken; "/api/udaps": Endpoints.get_Apiudaps; + "/api/upload/picture": Endpoints.get_Apiuploadpicture; "/api/pdf/report": Endpoints.get_Apipdfreport; }; }; diff --git a/packages/frontend/src/features/chips/useChipOptions.tsx b/packages/frontend/src/features/chips/useChipOptions.tsx index 589f9d02..2b878812 100644 --- a/packages/frontend/src/features/chips/useChipOptions.tsx +++ b/packages/frontend/src/features/chips/useChipOptions.tsx @@ -22,7 +22,7 @@ export const useChipOptions = (key?: string) => { // keep only the most specific chip for each value const chips = Object.values(grouped).map((value) => { - if (value.length > 1) return value.find((chip) => chip.udap_id !== "ALL")!; + if (value.length > 1) return value.find((chip) => chip.udap_id !== "ALL") ?? value[0]; return value[0]; }); @@ -32,6 +32,6 @@ export const useChipOptions = (key?: string) => { const transformChip = (chip: Clause_v2) => { return { ...chip, - text: chip.text?.replaceAll("\\n", "
").replace(/\n/g, "
"), + text: chip?.text?.replaceAll("\\n", "
").replace(/\n/g, "
"), }; }; diff --git a/packages/frontend/src/features/idb.ts b/packages/frontend/src/features/idb.ts index 482858af..2cc67e76 100644 --- a/packages/frontend/src/features/idb.ts +++ b/packages/frontend/src/features/idb.ts @@ -3,13 +3,18 @@ import { createStore, del } from "idb-keyval"; export const getPicturesStore = () => createStore("toSync", "images"); export const getToUploadStore = () => createStore("toUpload", "images"); export const getUploadStatusStore = () => createStore("uploadStatus", "images"); +export const getToPingStore = () => createStore("toPing", "images"); export const syncImages = async () => { - console.log("sync"); const registration = await navigator.serviceWorker.ready; await registration.sync.register("images"); }; +export const syncPictureLines = async () => { + const registration = await navigator.serviceWorker.ready; + await registration.sync.register("picture-lines"); +}; + export const deleteImageFromIdb = async (id: string) => { await del(id, getPicturesStore()); await del(id, getToUploadStore()); diff --git a/packages/frontend/src/features/upload/DrawingCanvas.tsx b/packages/frontend/src/features/upload/DrawingCanvas.tsx index fa55eabc..f5bd98b1 100644 --- a/packages/frontend/src/features/upload/DrawingCanvas.tsx +++ b/packages/frontend/src/features/upload/DrawingCanvas.tsx @@ -8,19 +8,21 @@ import { v4 } from "uuid"; import { Picture_lines } from "@cr-vif/electric-client/frontend"; type DrawEvent = React.MouseEvent | React.TouchEvent; - +export type Line = { points: Array<{ x: number; y: number }>; color: string }; export const ImageCanvas = ({ url, pictureId, lines: dbLines, containerRef, closeModal, + notifyPictureLines, }: { - lines: Array<{ points: Array<{ x: number; y: number }>; color: string }>; url: string; containerRef: any; pictureId: string; + lines: Array; closeModal: () => void; + notifyPictureLines: (args: { pictureId: string; lines: Array }) => void; }) => { const [tool, setTool] = useState("draw"); const [lines, setLines] = useState; color: string }>>([]); @@ -34,8 +36,6 @@ export const ImageCanvas = ({ const imageRef = useRef(null); const contextRef = useRef(null); - console.log(containerRef?.current?.getBoundingClientRect()); - const isDrawing = state === "drawing" || state === "hold"; useEffect(() => { @@ -268,6 +268,7 @@ export const ImageCanvas = ({ data: { id: v4(), pictureId, lines: JSON.stringify(lines) }, }); } + notifyPictureLines({ pictureId, lines }); closeModal(); }; diff --git a/packages/frontend/src/features/upload/UploadImage.tsx b/packages/frontend/src/features/upload/UploadImage.tsx index a8258970..4e6c02e6 100644 --- a/packages/frontend/src/features/upload/UploadImage.tsx +++ b/packages/frontend/src/features/upload/UploadImage.tsx @@ -1,20 +1,28 @@ import { useState, useRef, ChangeEvent, useEffect } from "react"; import { v4 } from "uuid"; import { db } from "../../db"; -import { deleteImageFromIdb, getPicturesStore, getToUploadStore, getUploadStatusStore, syncImages } from "../idb"; +import { + deleteImageFromIdb, + getPicturesStore, + getToPingStore, + getToUploadStore, + getUploadStatusStore, + syncImages, + syncPictureLines, +} from "../idb"; import { Box, Flex, Grid, Stack, styled } from "#styled-system/jsx"; import { InputGroup } from "#components/InputGroup.tsx"; import { cx } from "#styled-system/css"; import { Tmp_pictures, Pictures, Report } from "@cr-vif/electric-client/frontend"; import { useMutation, useQuery } from "@tanstack/react-query"; import { useLiveQuery } from "electric-sql/react"; -import { get, set } from "idb-keyval"; +import { del, get, set } from "idb-keyval"; import { useFormContext } from "react-hook-form"; import Badge from "@codegouvfr/react-dsfr/Badge"; import Button from "@codegouvfr/react-dsfr/Button"; import { css } from "#styled-system/css"; import { createModal } from "@codegouvfr/react-dsfr/Modal"; -import { ImageCanvas } from "./DrawingCanvas"; +import { ImageCanvas, Line } from "./DrawingCanvas"; import { api } from "../../api"; const modal = createModal({ @@ -26,6 +34,19 @@ export const UploadImage = ({ reportId }: { reportId: string }) => { const [statusMap, setStatusMap] = useState>({}); const [selectedPicture, setSelectedPicture] = useState<{ id: string; url: string } | null>(null); + const notifyPictureLines = useMutation(async ({ pictureId, lines }: { pictureId: string; lines: Array }) => { + try { + // @ts-ignore + const result = await api.post(`/api/upload/picture/${pictureId}/lines`, { body: { lines } }); + await del(pictureId, getToPingStore()); + + return result; + } catch (e) { + await set(pictureId, true, getToPingStore()); + syncPictureLines(); + } + }); + // const linesQuery = useLiveQuery(db.picture_lines.liveMany({ where: { pictureId: selectedPicture?.id } })); const linesQuery = useQuery({ @@ -44,8 +65,6 @@ export const UploadImage = ({ reportId }: { reportId: string }) => { const picId = v4(); const buffer = await getArrayBufferFromBlob(file); - console.log("oui"); - await db.tmp_pictures.create({ data: { id: picId, reportId, createdAt: new Date() } }); await set(picId, buffer, getPicturesStore()); @@ -105,6 +124,7 @@ export const UploadImage = ({ reportId }: { reportId: string }) => { {selectedPicture ? ( setSelectedPicture(null)} + notifyPictureLines={notifyPictureLines.mutate} pictureId={selectedPicture.id} url={selectedPicture.url} containerRef={containerRef} @@ -213,12 +233,14 @@ const PictureThumbnail = ({ return "ok"; }); + const canvasRef = useRef(null); + const bgUrlQuery = useQuery({ queryKey: ["picture", picture.id, picture.url], queryFn: async () => { + // if (picture.url) return picture.finalUrl ?? picture.url; const buffer = await get(picture.id, getPicturesStore()); if (!buffer) return picture.url; - const blob = new Blob([buffer], { type: "image/png" }); return URL.createObjectURL(blob); @@ -226,6 +248,8 @@ const PictureThumbnail = ({ refetchOnWindowFocus: false, }); + const pictureLines = useLiveQuery(db.picture_lines.liveMany({ where: { pictureId: picture.id } })); + const idbStatusQuery = useQuery({ queryKey: ["picture-status", picture.id], queryFn: async () => { @@ -235,6 +259,64 @@ const PictureThumbnail = ({ enabled: !status, }); + useEffect(() => { + drawCanvas(); + }, [bgUrlQuery.data, pictureLines.results]); + + const drawCanvas = () => { + if (!canvasRef.current) return; + if (!bgUrlQuery.data) return; + if (!pictureLines.results) return; + + const canvas = canvasRef.current; + const ctx = canvas.getContext("2d")!; + + const image = new Image(); + image.src = bgUrlQuery.data; + + const dpr = window.devicePixelRatio || 1; + canvas.width = 180 * dpr; + canvas.height = 130 * dpr; + + ctx.scale(dpr, dpr); + + image.onload = () => { + const scaleX = 180 / image.width; + const scaleY = 130 / image.height; + console.log(scaleX, scaleY); + const initialScale = Math.min(scaleX, scaleY) * 1.5; + + const xOffset = (180 - image.width * initialScale) / 2; + const yOffset = (130 - image.height * initialScale) / 2; + + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.save(); + + ctx.translate(xOffset, yOffset); + ctx.scale(initialScale, initialScale); + + ctx.drawImage(image, 0, 0, image.width, image.height); + + const lines = JSON.parse(pictureLines.results?.[0]?.lines ?? "[]"); + + ctx.lineWidth = 5; + ctx.lineCap = "round"; + ctx.lineJoin = "round"; + + lines.forEach((line: any) => { + ctx.beginPath(); + ctx.strokeStyle = line.color; + if (line.points.length > 0) { + ctx.moveTo(line.points[0].x, line.points[0].y); + for (let i = 1; i < line.points.length; i++) { + ctx.lineTo(line.points[i].x, line.points[i].y); + } + ctx.stroke(); + } + }); + }; + }; + const finalStatus = picture.url ? "success" : status ?? idbStatusQuery.data ?? "uploading"; const bgUrl = bgUrlQuery.data; @@ -244,15 +326,16 @@ const PictureThumbnail = ({ {/* */} + { diff --git a/packages/frontend/src/routes/pdf.$reportId.tsx b/packages/frontend/src/routes/pdf.$reportId.tsx index 05e3f2b4..87a7f296 100644 --- a/packages/frontend/src/routes/pdf.$reportId.tsx +++ b/packages/frontend/src/routes/pdf.$reportId.tsx @@ -385,6 +385,8 @@ export const WithReport = ({ const { editor } = useContext(TextEditorContext); const [htmlString] = useState(initialHtmlString); + console.log(report); + useEffect(() => { if (!editor) return; diff --git a/packages/frontend/src/service-worker/sw.ts b/packages/frontend/src/service-worker/sw.ts index 15135b84..46fe30eb 100644 --- a/packages/frontend/src/service-worker/sw.ts +++ b/packages/frontend/src/service-worker/sw.ts @@ -1,6 +1,6 @@ import { cleanupOutdatedCaches, createHandlerBoundToURL, precacheAndRoute } from "workbox-precaching"; import { apiStore, createApiClientWithUrl, getTokenFromIdb } from "../api"; -import { getPicturesStore, getToUploadStore, getUploadStatusStore } from "../features/idb"; +import { getPicturesStore, getToPingStore, getToUploadStore, getUploadStatusStore } from "../features/idb"; import { get, keys, del, set } from "idb-keyval"; import { NavigationRoute, registerRoute } from "workbox-routing"; import { isDev } from "../envVars"; @@ -27,10 +27,43 @@ if (!isDev) { const broadcastChannel = new BroadcastChannel("sw-messages"); self.addEventListener("sync", async (event: any) => { - broadcastChannel.postMessage({ type: "sync" }); - event.waitUntil(syncMissingPictures()); + if (event.tag === "images") { + event.waitUntil(syncMissingPictures()); + } else if (event.tag === "picture-lines") { + event.waitUntil(syncPictureLines()); + } }); +const syncPictureLines = async () => { + try { + const token = await getTokenFromIdb(); + if (!token) return void console.log("no token"); + + const pictureIds = await keys(getToPingStore()); + + console.log("syncing", pictureIds.length, "picture lines"); + + const url = await get("url", apiStore); + if (!url) return void console.error("no backend url in service worker"); + + const api = createApiClientWithUrl(url); + + for (let i = 0; i < pictureIds.length; i++) { + const picId = pictureIds[i]; + console.log("syncing picture lines for", picId); + + // @ts-ignore + await api.post(`/api/upload/picture/${pictureId}/lines`, { + header: { Authorization: `Bearer ${token}` }, + } as any); + + await del(picId, getToPingStore()); + } + } catch (e) { + console.error("sync error", e); + } +}; + const syncMissingPictures = async () => { try { const token = await getTokenFromIdb(); diff --git a/packages/pdf/src/report.tsx b/packages/pdf/src/report.tsx index 8e85b1fd..3fabb9ad 100644 --- a/packages/pdf/src/report.tsx +++ b/packages/pdf/src/report.tsx @@ -9,6 +9,7 @@ Font.registerHyphenationCallback((word) => { }); export const ReportPDFDocument = ({ udap, htmlString, images, pictures }: ReportPDFDocumentProps) => { + console.log(pictures); return ( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 10d45737..3abcffe1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -76,6 +76,9 @@ importers: '@sinclair/typebox': specifier: ^0.32.20 version: 0.32.20 + canvas: + specifier: ^2.11.2 + version: 2.11.2 date-fns: specifier: ^3.6.0 version: 3.6.0 @@ -1359,7 +1362,7 @@ packages: '@babel/traverse': 7.24.1 '@babel/types': 7.24.7 convert-source-map: 2.0.0 - debug: 4.3.4 + debug: 4.3.7 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -2788,7 +2791,7 @@ packages: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.24.7 '@babel/types': 7.24.7 - debug: 4.3.4 + debug: 4.3.7 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -4062,7 +4065,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - debug: 4.3.4 + debug: 4.3.7 espree: 9.6.1 globals: 13.24.0 ignore: 5.3.1 @@ -4245,7 +4248,7 @@ packages: engines: {node: '>=10.10.0'} dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.4 + debug: 4.3.7 minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -4370,7 +4373,6 @@ packages: transitivePeerDependencies: - encoding - supports-color - optional: true /@mikecousins/react-pdf@7.1.0(pdfjs-dist@4.3.136)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-uwJMgu+PQ5ULjBmuVD+R1gk1VpEjErDHxBgBrOoEBWmOk+DjoMxPV4ymPCWo7U9zLV5KJ+ppUGFa0w68fJ5yfw==} @@ -7091,7 +7093,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 7.5.0(typescript@5.4.3) '@typescript-eslint/utils': 7.5.0(eslint@8.57.0)(typescript@5.4.3) - debug: 4.3.4 + debug: 4.3.7 eslint: 8.57.0 ts-api-utils: 1.3.0(typescript@5.4.3) typescript: 5.4.3 @@ -7115,7 +7117,7 @@ packages: dependencies: '@typescript-eslint/types': 7.5.0 '@typescript-eslint/visitor-keys': 7.5.0 - debug: 4.3.4 + debug: 4.3.7 globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 @@ -8458,7 +8460,6 @@ packages: /abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} requiresBuild: true - optional: true /abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} @@ -8476,6 +8477,7 @@ packages: /acorn-import-assertions@1.9.0(acorn@8.11.3): resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} + deprecated: package has been renamed to acorn-import-attributes peerDependencies: acorn: ^8 dependencies: @@ -8498,7 +8500,7 @@ packages: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} dependencies: - debug: 4.3.4 + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -8609,7 +8611,6 @@ packages: /aproba@2.0.0: resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} requiresBuild: true - optional: true /archiver-utils@2.1.0: resolution: {integrity: sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==} @@ -8652,7 +8653,6 @@ packages: dependencies: delegates: 1.0.0 readable-stream: 3.6.2 - optional: true /arg@5.0.1: resolution: {integrity: sha512-e0hDa9H2Z9AwFkk2qDlwhoMYE4eToKarchkQHovNdLTCYMHZHeRjI71crOh+dio4K6u1IcwubQqo79Ga4CyAQA==} @@ -8750,7 +8750,7 @@ packages: dependencies: '@fastify/error': 3.4.1 archy: 1.0.0 - debug: 4.3.4 + debug: 4.3.7 fastq: 1.17.1 transitivePeerDependencies: - supports-color @@ -9041,7 +9041,6 @@ packages: transitivePeerDependencies: - encoding - supports-color - optional: true /chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} @@ -9261,7 +9260,6 @@ packages: resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} hasBin: true requiresBuild: true - optional: true /commander@11.1.0: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} @@ -9315,7 +9313,6 @@ packages: /console-control-strings@1.1.0: resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} requiresBuild: true - optional: true /content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} @@ -9695,7 +9692,6 @@ packages: optional: true dependencies: ms: 2.1.3 - dev: true /decamelize-keys@1.1.1: resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} @@ -9716,7 +9712,6 @@ packages: requiresBuild: true dependencies: mimic-response: 2.1.0 - optional: true /decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} @@ -9847,7 +9842,6 @@ packages: /delegates@1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} requiresBuild: true - optional: true /depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} @@ -11242,7 +11236,6 @@ packages: string-width: 4.2.3 strip-ansi: 6.0.1 wide-align: 1.1.5 - optional: true /gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} @@ -11466,7 +11459,6 @@ packages: /has-unicode@2.0.1: resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} requiresBuild: true - optional: true /has-yarn@2.1.0: resolution: {integrity: sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==} @@ -11548,7 +11540,7 @@ packages: dependencies: '@tootallnate/once': 1.1.2 agent-base: 6.0.2 - debug: 4.3.4 + debug: 4.3.7 transitivePeerDependencies: - supports-color dev: true @@ -11558,7 +11550,7 @@ packages: engines: {node: '>= 6'} dependencies: agent-base: 6.0.2 - debug: 4.3.4 + debug: 4.3.7 transitivePeerDependencies: - supports-color @@ -12709,7 +12701,6 @@ packages: resolution: {integrity: sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==} engines: {node: '>=8'} requiresBuild: true - optional: true /mimic-response@3.1.0: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} @@ -12774,7 +12765,6 @@ packages: resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} engines: {node: '>=8'} requiresBuild: true - optional: true /minipass@7.0.4: resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} @@ -12858,7 +12848,6 @@ packages: /nan@2.19.0: resolution: {integrity: sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==} requiresBuild: true - optional: true /nano-css@5.6.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-T2Mhc//CepkTa3X4pUhKgbEheJHYAxD0VptuqFhDbGMUWVV2m+lkNiW/Ieuj35wrfC8Zm0l7HvssQh7zcEttSw==} @@ -12957,7 +12946,6 @@ packages: requiresBuild: true dependencies: abbrev: 1.1.1 - optional: true /normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} @@ -13071,7 +13059,6 @@ packages: console-control-strings: 1.1.0 gauge: 3.0.2 set-blocking: 2.0.0 - optional: true /nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} @@ -14762,7 +14749,6 @@ packages: decompress-response: 4.2.1 once: 1.4.0 simple-concat: 1.0.1 - optional: true /simple-get@4.0.1: resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} @@ -15226,7 +15212,6 @@ packages: minizlib: 2.1.2 mkdirp: 1.0.4 yallist: 4.0.0 - optional: true /tcp-port-used@1.0.2: resolution: {integrity: sha512-l7ar8lLUD3XS1V2lfoJlCBaeoaWo/2xfYt81hM7VlvR4RrMVFqfmzfhLVk40hAb368uitje5gPtBRL1m/DGvLA==} @@ -16193,7 +16178,6 @@ packages: requiresBuild: true dependencies: string-width: 4.2.3 - optional: true /wordwrap@1.0.0: resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}