From 5707d1bb1f5827520841ce48f567cd3960fb32c3 Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 30 Jan 2025 16:16:20 +0100 Subject: [PATCH] logging#19, max 5MB#18,addinsert/update#14,drizzle --- .vscode/launch.json | 13 +- api/src/app/app.module.ts | 5 +- api/src/app/decks.controller.ts | 16 +- api/src/app/drizzle.service.ts | 103 ++- api/src/app/sql-logger.service.ts | 36 + api/src/db/schema.ts | 1 - api/src/main.ts | 10 +- drizzle/0003_hard_cable.sql | 1 + drizzle/meta/0003_snapshot.json | 157 ++++ drizzle/meta/_journal.json | 7 + package-lock.json | 730 +++++++++++++++++- package.json | 1 + src/app/app.component.ts | 3 + src/app/components/popover.component.ts | 3 + src/app/deck-list.component.html | 4 +- src/app/deck-list.component.ts | 40 +- src/app/deck.service.ts | 18 +- .../edit-image-modal.component.ts | 10 +- .../move-image-modal.component.ts | 2 +- src/app/services/popover.service.ts | 6 +- src/app/training/training.component.ts | 2 +- tsconfig.json | 5 +- 22 files changed, 1073 insertions(+), 100 deletions(-) create mode 100644 api/src/app/sql-logger.service.ts create mode 100644 drizzle/0003_hard_cable.sql create mode 100644 drizzle/meta/0003_snapshot.json diff --git a/.vscode/launch.json b/.vscode/launch.json index f30a599..63d6e41 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,13 +1,14 @@ { - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { - "name": "ng serve", - "type": "chrome", - "request": "launch", - "preLaunchTask": "npm: start", - "url": "http://localhost:4200/" + "type": "node", + "request": "attach", + "name": "Attach to Nx Serve", + "port": 9229, + "sourceMaps": true, + "outFiles": ["${workspaceFolder}/dist/api/**/*.js"], + "skipFiles": ["/**"] } ] } diff --git a/api/src/app/app.module.ts b/api/src/app/app.module.ts index 3590438..7dddec0 100644 --- a/api/src/app/app.module.ts +++ b/api/src/app/app.module.ts @@ -2,10 +2,11 @@ import { Module } from '@nestjs/common'; import { DecksController } from './decks.controller'; import { DrizzleService } from './drizzle.service'; import { ProxyController } from './proxy.controller'; +import { SqlLoggerService } from './sql-logger.service'; @Module({ imports: [], - controllers: [DecksController,ProxyController], - providers: [DrizzleService], + controllers: [DecksController, ProxyController], + providers: [DrizzleService, SqlLoggerService], }) export class AppModule {} diff --git a/api/src/app/decks.controller.ts b/api/src/app/decks.controller.ts index 27064ff..dfb7ef5 100644 --- a/api/src/app/decks.controller.ts +++ b/api/src/app/decks.controller.ts @@ -25,9 +25,8 @@ export class DecksController { if (entry.bildname && entry.bildid) { decks[deckname].images.push({ name: entry.bildname, - id: entry.bildid, - iconindex: entry.iconindex, - boxid: entry.id, + bildid: entry.bildid, + id: entry.id, x1: entry.x1, x2: entry.x2, y1: entry.y1, @@ -72,9 +71,8 @@ export class DecksController { if (entry.bildname && entry.bildid) { deck.images.push({ name: entry.bildname, - id: entry.bildid, - iconindex: entry.iconindex, - boxid: entry.id, + bildid: entry.bildid, + id: entry.id, x1: entry.x1, x2: entry.x2, y1: entry.y1, @@ -141,10 +139,10 @@ export class DecksController { return this.drizzleService.moveImage(bildid, data.targetDeckId, user); } - @Put('boxes/:boxId') + @Put('boxes/:id') async updateBox( @Request() req, - @Param('boxId') boxId: number, + @Param('id') id: number, @Body() data: { due?: number; @@ -156,6 +154,6 @@ export class DecksController { }, ) { const user: User = req['user']; - return this.drizzleService.updateBox(boxId, data, user); + return this.drizzleService.updateBox(id, data, user); } } diff --git a/api/src/app/drizzle.service.ts b/api/src/app/drizzle.service.ts index a9a1834..ef6cb1a 100644 --- a/api/src/app/drizzle.service.ts +++ b/api/src/app/drizzle.service.ts @@ -1,12 +1,23 @@ // drizzle.service.ts import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { and, eq, isNull, sql } from 'drizzle-orm'; +import { and, eq, sql } from 'drizzle-orm'; import { drizzle } from 'drizzle-orm/libsql'; -import { Deck, User } from '../db/schema'; +import { Deck, SelectDeck, User } from '../db/schema'; +import { SqlLoggerService } from './sql-logger.service'; @Injectable() export class DrizzleService { - private db = drizzle('file:local.db'); + // private readonly logger = new Logger(DrizzleService.name); + private db: any; + constructor(private sqlLogger: SqlLoggerService) { + this.db = drizzle('file:local.db', { + logger: { + logQuery: (query: string, params: any[]) => { + this.sqlLogger.logQuery(query, params); + }, + }, + }); + } /** * Methode zum Abrufen aller Decks eines Benutzers @@ -80,48 +91,82 @@ export class DrizzleService { /** * Methode zum Aktualisieren eines Bildes innerhalb eines Decks - * Die Methode erhält jetzt auch den 'inserted' Timestamp */ async updateImage( data: { deckname: string; bildname: string; bildid: string; - boxes: Array<{ x1: number; x2: number; y1: number; y2: number }>; - inserted: string; // Neuer Parameter für den 'inserted' Timestamp + boxes: Array<{ x1: number; x2: number; y1: number; y2: number; id?: number }>; }, user: User, ) { - const existingDeck = await this.getDeckByName(data.deckname, user); - if (existingDeck.length === 0) { + // Schritt 1: Überprüfen, ob das Deck existiert + const existingDecks: SelectDeck[] = await this.getDeckByName(data.deckname, user); + if (existingDecks.length === 0) { throw new HttpException('Deck not found', HttpStatus.NOT_FOUND); } - // Lösche vorhandene Einträge für das Bild - await this.db.delete(Deck).where(and(eq(Deck.deckname, data.deckname), eq(Deck.bildid, data.bildid), eq(Deck.user, user.email))); + // Schritt 2: Trennen der neuen und bestehenden Einträge + const newEntries = data.boxes.filter(b => !b.id); + const existingEntries = data.boxes.filter(b => b.id); + // Schritt 3: Einfügen neuer Einträge const insertedImages: any = []; - for (let index = 0; index < data.boxes.length; index++) { - const box = data.boxes[index]; + for (let index = 0; index < newEntries.length; index++) { + const box = newEntries[index]; const result = await this.db .insert(Deck) .values({ deckname: data.deckname, bildname: data.bildname, bildid: data.bildid, - iconindex: index, x1: box.x1, x2: box.x2, y1: box.y1, y2: box.y2, user: user.email, - inserted: data.inserted, // Setze 'inserted' auf den übergebenen Wert - // 'updated' wird automatisch von der DB gesetzt + // 'inserted' und 'updated' werden automatisch von der DB gesetzt }) .returning(); insertedImages.push(result); } + // Schritt 4: Aktualisieren bestehender Einträge + for (let index = 0; index < existingEntries.length; index++) { + const box = existingEntries[index]; + const existingDeck = existingDecks.find(d => d.id === box.id); + if (existingDeck && (existingDeck.x1 !== box.x1 || existingDeck.x2 !== box.x2 || existingDeck.y1 !== box.y1 || existingDeck.y2 !== box.y2)) { + const result = await this.db + .update(Deck) + .set({ + x1: box.x1, + x2: box.x2, + y1: box.y1, + y2: box.y2, + updated: sql`CURRENT_TIMESTAMP`, // Setze 'updated' auf CURRENT_TIMESTAMP + }) + .where(and(eq(Deck.user, user.email), eq(Deck.bildid, data.bildid), eq(Deck.deckname, data.deckname), eq(Deck.id, box.id!!))); + if (result.rowsAffected === 0) { + throw new HttpException(`Box with id ${box.id} not found`, HttpStatus.NOT_FOUND); + } + } + } + + // Schritt 5: Löschen von nicht mehr vorhandenen Einträgen + // Sammle alle bestehenden IDs aus der Datenbank für das gegebene deckname und bildid + const existingIdsInDb = existingDecks.filter(entry => entry.bildid === data.bildid && entry.deckname === data.deckname).map(entry => entry.id); + + // Sammle alle eingehenden IDs aus den vorhandenen Einträgen + const incomingIds = existingEntries.map(entry => entry.id); + + // Bestimme die IDs, die in der Datenbank existieren, aber nicht mehr in den eingehenden Daten vorhanden sind + const idsToDelete = existingIdsInDb.filter(id => !incomingIds.includes(id)); + + if (idsToDelete.length > 0) { + await this.db.delete(Deck).where(and(eq(Deck.deckname, data.deckname), eq(Deck.bildid, data.bildid), eq(Deck.user, user.email), sql`id in ${idsToDelete}`)); + } + return { status: 'success', inserted_images: insertedImages }; } @@ -141,30 +186,6 @@ export class DrizzleService { await this.db.delete(Deck).where(and(eq(Deck.bildid, bildid), eq(Deck.user, user.email))); - for (const deck of affectedDecks) { - const remainingImages = await this.db - .select() - .from(Deck) - .where(and(eq(Deck.deckname, deck.deckname), eq(Deck.bildid, bildid), eq(Deck.user, user.email))) - .all(); - - if (remainingImages.length === 0) { - const emptyDeckEntry = await this.db - .select() - .from(Deck) - .where(and(eq(Deck.deckname, deck.deckname), isNull(Deck.bildid), eq(Deck.user, user.email))) - .all(); - - if (emptyDeckEntry.length === 0) { - await this.db.insert(Deck).values({ - deckname: deck.deckname, - user: user.email, - // 'inserted' und 'updated' werden automatisch von der DB gesetzt - }); - } - } - } - return { status: 'success', message: `All entries for image ID "${bildid}" have been deleted.`, @@ -200,7 +221,7 @@ export class DrizzleService { * Methode zum Aktualisieren einer Box */ async updateBox( - boxId: number, + id: number, data: { due?: number; ivl?: number; @@ -220,7 +241,7 @@ export class DrizzleService { const result = await this.db .update(Deck) .set(updateData) - .where(and(eq(Deck.id, boxId), eq(Deck.user, user.email))); + .where(and(eq(Deck.id, id), eq(Deck.user, user.email))); if (result.rowsAffected === 0) { throw new HttpException('Box not found', HttpStatus.NOT_FOUND); diff --git a/api/src/app/sql-logger.service.ts b/api/src/app/sql-logger.service.ts new file mode 100644 index 0000000..e8196f8 --- /dev/null +++ b/api/src/app/sql-logger.service.ts @@ -0,0 +1,36 @@ +// sql-logger.service.ts +import { Injectable, Logger } from '@nestjs/common'; +import type { ChalkInstance } from 'chalk'; +import chalk from 'chalk'; + +@Injectable() +export class SqlLoggerService { + private readonly logger = new Logger(SqlLoggerService.name); + private chalkInstance: ChalkInstance; + + constructor() { + this.chalkInstance = chalk; + } + + logQuery(query: string, params: any[], sourceIp?: string) { + const timestamp = new Date().toISOString().replace('T', ' ').replace('Z', ''); + + let logMessage = `[DrizzleORM] Info\t${timestamp}`; + + // Optional source IP + if (sourceIp) { + logMessage += ` IP: ${sourceIp}`; + } + + // Colored query and params using chalk directly + const coloredQuery = chalk.blueBright(`Query: ${query}`); + const coloredParams = chalk.yellow(`Params: ${JSON.stringify(params)}`); + + logMessage += ` - ${coloredQuery} - ${coloredParams}`; + + // Add timing indicator + logMessage += chalk.gray(' +2ms'); + + console.log(logMessage); + } +} diff --git a/api/src/db/schema.ts b/api/src/db/schema.ts index 16512d2..d2ba352 100644 --- a/api/src/db/schema.ts +++ b/api/src/db/schema.ts @@ -9,7 +9,6 @@ export const Deck = table( deckname: text('deckname').notNull(), bildname: text('bildname'), bildid: text('bildid'), - iconindex: integer('iconindex'), x1: real('x1'), x2: real('x2'), y1: real('y1'), diff --git a/api/src/main.ts b/api/src/main.ts index d106d8f..99d903a 100644 --- a/api/src/main.ts +++ b/api/src/main.ts @@ -5,20 +5,20 @@ import { Logger } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; -import { AppModule } from './app/app.module'; import { json, urlencoded } from 'express'; +import { AppModule } from './app/app.module'; async function bootstrap() { - const app = await NestFactory.create(AppModule); + const app = await NestFactory.create(AppModule, { + logger: ['log', 'error', 'warn', 'debug', 'verbose'], // Aktiviere alle Log-Level + }); app.use(json({ limit: '50mb' })); app.use(urlencoded({ limit: '50mb', extended: true })); const globalPrefix = 'api'; app.setGlobalPrefix(globalPrefix); const port = process.env['PORT'] || 3000; await app.listen(port); - Logger.log( - `🚀 Application is running on: http://localhost:${port}/${globalPrefix}` - ); + Logger.log(`🚀 Application is running on: http://localhost:${port}/${globalPrefix}`); } bootstrap(); diff --git a/drizzle/0003_hard_cable.sql b/drizzle/0003_hard_cable.sql new file mode 100644 index 0000000..b27b927 --- /dev/null +++ b/drizzle/0003_hard_cable.sql @@ -0,0 +1 @@ +ALTER TABLE `Deck` DROP COLUMN `iconindex`; \ No newline at end of file diff --git a/drizzle/meta/0003_snapshot.json b/drizzle/meta/0003_snapshot.json new file mode 100644 index 0000000..00f971b --- /dev/null +++ b/drizzle/meta/0003_snapshot.json @@ -0,0 +1,157 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "a0b561cf-8404-43ea-bc72-8b32671580bd", + "prevId": "1e5421d8-df58-434e-93c8-85dcbe6ec9ee", + "tables": { + "Deck": { + "name": "Deck", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "deckname": { + "name": "deckname", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bildname": { + "name": "bildname", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bildid": { + "name": "bildid", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "x1": { + "name": "x1", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "x2": { + "name": "x2", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "y1": { + "name": "y1", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "y2": { + "name": "y2", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "due": { + "name": "due", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ivl": { + "name": "ivl", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "factor": { + "name": "factor", + "type": "real", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reps": { + "name": "reps", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "lapses": { + "name": "lapses", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "isGraduated": { + "name": "isGraduated", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user": { + "name": "user", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "inserted": { + "name": "inserted", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + }, + "updated": { + "name": "updated", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(CURRENT_TIMESTAMP)" + } + }, + "indexes": { + "email_idx": { + "name": "email_idx", + "columns": [ + "id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 450c270..bda63fb 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -22,6 +22,13 @@ "when": 1738103858394, "tag": "0002_aromatic_zodiak", "breakpoints": true + }, + { + "idx": 3, + "version": "6", + "when": 1738235979220, + "tag": "0003_hard_cable", + "breakpoints": true } ] } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b724b1c..f371e93 100644 --- a/package-lock.json +++ b/package-lock.json @@ -54,6 +54,7 @@ "@swc/helpers": "~0.5.11", "@types/fabric": "^5.3.9", "@types/node": "~18.16.9", + "chalk": "^5.4.1", "concurrently": "^9.1.2", "drizzle-kit": "^0.30.2", "drizzle-orm": "^0.38.4", @@ -5205,6 +5206,23 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/console/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@jest/console/node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -5347,6 +5365,36 @@ "concat-map": "0.0.1" } }, + "node_modules/@jest/reporters/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@jest/reporters/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -5521,6 +5569,23 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/transform/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@jest/transform/node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -5556,6 +5621,23 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", @@ -7393,6 +7475,22 @@ "npm": ">=5.0.0" } }, + "node_modules/@nuxtjs/opencollective/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@nx/angular": { "version": "20.3.2", "resolved": "https://registry.npmjs.org/@nx/angular/-/angular-20.3.2.tgz", @@ -7626,6 +7724,23 @@ } } }, + "node_modules/@nx/js/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@nx/js/node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -8116,6 +8231,108 @@ "tslib": "^2.3.0" } }, + "node_modules/@nx/nx-darwin-arm64": { + "version": "20.3.2", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-arm64/-/nx-darwin-arm64-20.3.2.tgz", + "integrity": "sha512-lQOXMIPmE9o36TuZ+SX6iq7PPWa3s1fjNRqCujlviExX69245NNCMxd754gXlLrsxC1onrx/zmJciKmmEWDIiw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nx/nx-darwin-x64": { + "version": "20.3.2", + "resolved": "https://registry.npmjs.org/@nx/nx-darwin-x64/-/nx-darwin-x64-20.3.2.tgz", + "integrity": "sha512-RvvSz4QYVOYOfC8sUE63b6dy8iHk2AEI0r1FF5FCQuqE1DdTeTjPETY2sY35tRqF+mO/6oLGp2+m9ti/ysRoTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nx/nx-freebsd-x64": { + "version": "20.3.2", + "resolved": "https://registry.npmjs.org/@nx/nx-freebsd-x64/-/nx-freebsd-x64-20.3.2.tgz", + "integrity": "sha512-KBDTyGn1evlZ17pupwRUDh2wrCMuHhP2j8cOCdgF5cl7vRki8BOK9yyL6jD11d/d/6DgXzy1jmQEX4Xx+AGCug==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nx/nx-linux-arm-gnueabihf": { + "version": "20.3.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm-gnueabihf/-/nx-linux-arm-gnueabihf-20.3.2.tgz", + "integrity": "sha512-mW+OcOnJEMvs7zD3aSwEG3z5M9bI4CuUU5Q/ePmnNzWIucRHpoAMNt/Sd+yu6L4+QttvoUf967uwcMsX8l4nrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nx/nx-linux-arm64-gnu": { + "version": "20.3.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-gnu/-/nx-linux-arm64-gnu-20.3.2.tgz", + "integrity": "sha512-hbXpZqUvGY5aeEWvh0SNsiYjP1ytSM30XOT6qN6faLO2CL/7j9D2UB69SKOqF3TJOvuNU6cweFgZCxyGfXBYIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nx/nx-linux-arm64-musl": { + "version": "20.3.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-arm64-musl/-/nx-linux-arm64-musl-20.3.2.tgz", + "integrity": "sha512-HXthtN7adXCNVWs2F4wIqq2f7BcKTjsEnqg2LWV5lm4hRYvMfEvPftb0tECsEhcSQQYcvIJnLfv3vtu9HZSfVA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nx/nx-linux-x64-gnu": { "version": "20.3.2", "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-gnu/-/nx-linux-x64-gnu-20.3.2.tgz", @@ -8133,6 +8350,57 @@ "node": ">= 10" } }, + "node_modules/@nx/nx-linux-x64-musl": { + "version": "20.3.2", + "resolved": "https://registry.npmjs.org/@nx/nx-linux-x64-musl/-/nx-linux-x64-musl-20.3.2.tgz", + "integrity": "sha512-NrZ8L9of2GmYEM8GMJX6QRrLJlAwM+ds2rhdY1bxwpiyCNcD3IO/gzJlBs+kG4ly05F1u/X4k/FI5dXPpjUSgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nx/nx-win32-arm64-msvc": { + "version": "20.3.2", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-arm64-msvc/-/nx-win32-arm64-msvc-20.3.2.tgz", + "integrity": "sha512-yLjacZND7C1XmsC0jfRLSgeLWZUw2Oz+u3nXNvj5JX6YHtYTVLFnRbTAcI+pG2Y6v0Otf2GKb3VT5d1mQb8JvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nx/nx-win32-x64-msvc": { + "version": "20.3.2", + "resolved": "https://registry.npmjs.org/@nx/nx-win32-x64-msvc/-/nx-win32-x64-msvc-20.3.2.tgz", + "integrity": "sha512-oDhcctfk0UB1V+Otp1161VKNMobzkFQxGyiEIjp0CjCBa2eRHC1r35L695F1Hj0bvLQPSni9XIe9evh2taeAkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@nx/web": { "version": "20.3.2", "resolved": "https://registry.npmjs.org/@nx/web/-/web-20.3.2.tgz", @@ -8522,6 +8790,23 @@ "yargs-parser": "21.1.1" } }, + "node_modules/@nx/workspace/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/@opentelemetry/api": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", @@ -11130,6 +11415,23 @@ "@babel/core": "^7.8.0" } }, + "node_modules/babel-jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/babel-jest/node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -11961,16 +12263,13 @@ } }, "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" @@ -12448,6 +12747,36 @@ "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, + "node_modules/concurrently/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/concurrently/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -14985,6 +15314,24 @@ "concat-map": "0.0.1" } }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -16033,6 +16380,23 @@ "concat-map": "0.0.1" } }, + "node_modules/fork-ts-checker-webpack-plugin/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/fork-ts-checker-webpack-plugin/node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -17225,6 +17589,22 @@ "node": ">=12" } }, + "node_modules/http-server/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/https-proxy-agent": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", @@ -18021,6 +18401,23 @@ "concat-map": "0.0.1" } }, + "node_modules/jake/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jake/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -18084,6 +18481,23 @@ "npm": ">=6" } }, + "node_modules/jest-circus/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-circus/node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -18238,6 +18652,23 @@ "concat-map": "0.0.1" } }, + "node_modules/jest-config/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-config/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -18299,6 +18730,23 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-docblock": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", @@ -18329,6 +18777,23 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-each/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-environment-node": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", @@ -18445,6 +18910,23 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-message-util": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", @@ -18466,6 +18948,23 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-message-util/node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -18540,6 +19039,23 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-resolve/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-resolve/node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -18583,6 +19099,36 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-runner/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jest-runner/node_modules/jest-worker": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", @@ -18710,6 +19256,23 @@ "concat-map": "0.0.1" } }, + "node_modules/jest-runtime/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-runtime/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -18797,6 +19360,23 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-snapshot/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-util": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", @@ -18815,6 +19395,23 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-util/node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -18859,6 +19456,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/jest-validate/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-watcher": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", @@ -18879,6 +19493,23 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-watcher/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -19847,6 +20478,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/log-update": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", @@ -21355,6 +22002,23 @@ } } }, + "node_modules/nx/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/nx/node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -21735,6 +22399,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/ora/node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -25740,6 +26420,23 @@ "webpack": "^5.0.0" } }, + "node_modules/ts-loader/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/ts-node": { "version": "10.9.1", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", @@ -25834,6 +26531,23 @@ "node": ">=10.13.0" } }, + "node_modules/tsconfig-paths-webpack-plugin/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", diff --git a/package.json b/package.json index ec9e30f..d6de842 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "@swc/helpers": "~0.5.11", "@types/fabric": "^5.3.9", "@types/node": "~18.16.9", + "chalk": "^5.4.1", "concurrently": "^9.1.2", "drizzle-kit": "^0.30.2", "drizzle-orm": "^0.38.4", diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 3083a49..9dee6b9 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -50,6 +50,7 @@ import { PopoverService } from './services/popover.service'; [title]="popoverTitle" [message]="popoverMessage" [showInput]="popoverShowInput" + [showCancel]="popoverShowCancel" [inputValue]="popoverInputValue" [confirmText]="popoverConfirmText" (confirmed)="handleConfirm($event)" @@ -103,6 +104,7 @@ export class AppComponent { popoverTitle = ''; popoverMessage = ''; popoverShowInput = false; + popoverShowCancel = true; popoverInputValue = ''; popoverConfirmText = 'Confirm'; private confirmCallback?: (inputValue?: string) => void; @@ -114,6 +116,7 @@ export class AppComponent { this.popoverTitle = options.title; this.popoverMessage = options.message; this.popoverShowInput = options.showInput; + this.popoverShowCancel = options.showCancel; this.popoverInputValue = options.inputValue; this.popoverConfirmText = options.confirmText; this.confirmCallback = options.onConfirm; diff --git a/src/app/components/popover.component.ts b/src/app/components/popover.component.ts index d318645..027c730 100644 --- a/src/app/components/popover.component.ts +++ b/src/app/components/popover.component.ts @@ -17,7 +17,9 @@ import { FormsModule } from '@angular/forms';
+ @if(showCancel){ + } @@ -30,6 +32,7 @@ export class PopoverComponent { @Input() title: string = ''; @Input() message: string = ''; @Input() showInput: boolean = false; + @Input() showCancel: boolean = false; @Input() confirmText: string = 'Confirm'; @Input() inputValue: string = ''; @Input() visible: boolean = false; diff --git a/src/app/deck-list.component.html b/src/app/deck-list.component.html index 172f2d2..0dd1d90 100644 --- a/src/app/deck-list.component.html +++ b/src/app/deck-list.component.html @@ -67,7 +67,7 @@
- {{ image.name }} + {{ image.name }}
{{ image.name }}
@@ -98,7 +98,7 @@
- +
diff --git a/src/app/deck-list.component.ts b/src/app/deck-list.component.ts index 9921f69..e7de2e3 100644 --- a/src/app/deck-list.component.ts +++ b/src/app/deck-list.component.ts @@ -144,18 +144,19 @@ export class DeckListComponent implements OnInit { title: 'Delete Image', message: `Are you sure you want to delete the image ${image.name}?`, confirmText: 'Delete', + showCancel: true, onConfirm: () => this.confirmImageDelete(deck, image), }); } confirmImageDelete(deck: Deck, image: DeckImage): void { - const imageId = image.id; + const imageId = image.bildid; this.deckService.deleteImage(imageId).subscribe({ next: () => { this.loadDecks(); if (this.activeDeck) { - this.activeDeck.images = this.activeDeck.images.filter(img => img.id !== imageId); + this.activeDeck.images = this.activeDeck.images.filter(img => img.bildid !== imageId); this.cdr.detectChanges(); } }, @@ -166,7 +167,7 @@ export class DeckListComponent implements OnInit { // Method to edit an image in a deck editImage(deck: Deck, image: DeckImage): void { let imageSrc = null; - fetch(`/debug_images/${image.id}/original_compressed.jpg`) + fetch(`/debug_images/${image.bildid}/original_compressed.jpg`) .then(response => { if (!response.ok) { throw new Error('Network response was not ok'); @@ -282,11 +283,36 @@ export class DeckListComponent implements OnInit { onFileChange(event: any): void { const file: File = event.target.files[0]; if (!file) return; + + // Erlaubte Dateitypen + const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; + + // Prüfe den Dateityp + if (!allowedTypes.includes(file.type)) { + this.popoverService.show({ + title: 'Information', + message: 'Only JPG, PNG, GIF and WebP images are allowed', + }); + this.resetFileInput(); + return; + } + + // Prüfe die Dateigröße (5MB = 5 * 1024 * 1024 Bytes) + const maxSize = 5 * 1024 * 1024; // 5MB in Bytes + if (file.size > maxSize) { + this.popoverService.show({ + title: 'Information', + message: 'Image file size must not exceed 5MB', + }); + this.resetFileInput(); + return; + } + const fileNameElement = document.getElementById('fileName'); if (fileNameElement) { fileNameElement.textContent = file.name; } - // this.imageFile = file; + this.loading = true; const reader = new FileReader(); @@ -319,10 +345,9 @@ export class DeckListComponent implements OnInit { const xMax = Math.max(...xs); const yMin = Math.min(...ys); const yMax = Math.max(...ys); - boxes.push({ x1: xMin, x2: xMax, y1: yMin, y2: yMax }); + boxes.push({ x1: xMin, x2: xMax, y1: yMin, y2: yMax, inserted: null, updated: null }); }); - const deckImage: DeckImage = { name: imageName, id: imageId, boxes }; - //this.imageUploaded.emit({ imageSrc, deckImage }); + const deckImage: DeckImage = { name: imageName, bildid: imageId, boxes }; this.imageData = { imageSrc, deckImage }; this.resetFileInput(); @@ -333,6 +358,7 @@ export class DeckListComponent implements OnInit { }; reader.readAsDataURL(file); } + /** * Resets the file input field so the same file can be selected again. */ diff --git a/src/app/deck.service.ts b/src/app/deck.service.ts index b0aca41..86e549c 100644 --- a/src/app/deck.service.ts +++ b/src/app/deck.service.ts @@ -10,8 +10,7 @@ export interface Deck { export interface DeckImage { boxes: Box[]; name: string; - //bildid: string; - id: string; + bildid: string; } export interface Box { @@ -26,12 +25,13 @@ export interface Box { reps?: number; lapses?: number; isGraduated?: boolean; + inserted: string; + updated: string; } export interface BackendBox { bildname: string; deckid: number; - iconindex: number; id: number; x1: number; x2: number; @@ -76,15 +76,15 @@ export class DeckService { const imageMap: { [key: string]: DeckImage } = {}; images.forEach(image => { - if (!imageMap[image.id]) { - imageMap[image.id] = { + if (!imageMap[image.bildid]) { + imageMap[image.bildid] = { name: image.name, - id: image.id, + bildid: image.bildid, boxes: [], }; } - imageMap[image.id].boxes.push({ - id: image.boxid, + imageMap[image.bildid].boxes.push({ + id: image.id, x1: image.x1, x2: image.x2, y1: image.y1, @@ -95,6 +95,8 @@ export class DeckService { reps: image.reps, lapses: image.lapses, isGraduated: image.isGraduated ? true : false, + inserted: image.inserted, + updated: image.updated, }); }); diff --git a/src/app/edit-image-modal/edit-image-modal.component.ts b/src/app/edit-image-modal/edit-image-modal.component.ts index adc0ca4..9bb6e58 100644 --- a/src/app/edit-image-modal/edit-image-modal.component.ts +++ b/src/app/edit-image-modal/edit-image-modal.component.ts @@ -24,7 +24,7 @@ export class EditImageModalComponent implements AfterViewInit, OnDestroy { @ViewChild('canvas') canvasElement!: ElementRef; detectedText: string = ''; - boxes: { x1: number; x2: number; y1: number; y2: number }[] = []; + boxes: { x1: number; x2: number; y1: number; y2: number; id: number; inserted: string; updated: string }[] = []; canvas!: fabric.Canvas; maxCanvasWidth: number = 0; @@ -133,6 +133,7 @@ export class EditImageModalComponent implements AfterViewInit, OnDestroy { hasControls: true, hasBorders: true, objectCaching: false, + data: { id: box.id, inserted: box.inserted, updated: box.updated }, }); rect.on('modified', () => { @@ -155,7 +156,6 @@ export class EditImageModalComponent implements AfterViewInit, OnDestroy { }); this.updateBoxCoordinates(); - // this.detectedText = ocrResults.map(result => result.text).join('\n'); } catch (error) { console.error('Error processing image:', error); @@ -198,6 +198,9 @@ export class EditImageModalComponent implements AfterViewInit, OnDestroy { x2: Math.round(x2), y1: Math.round(y1), y2: Math.round(y2), + id: rect.data?.id, + inserted: rect.data?.inserted, + updated: rect.data?.updated, }); }); @@ -226,7 +229,6 @@ export class EditImageModalComponent implements AfterViewInit, OnDestroy { hasBorders: true, objectCaching: false, }); - rect.on('modified', () => { this.updateBoxCoordinates(); }); @@ -256,7 +258,7 @@ export class EditImageModalComponent implements AfterViewInit, OnDestroy { const data = { deckname: this.deckName, bildname: this.imageData.deckImage?.name, // this.imageFile?.name, - bildid: this.imageData.deckImage?.id, + bildid: this.imageData.deckImage?.bildid, boxes: this.boxes, }; this.deckService.saveImageData(data).subscribe({ diff --git a/src/app/move-image-modal/move-image-modal.component.ts b/src/app/move-image-modal/move-image-modal.component.ts index c7ea156..1d2e45d 100644 --- a/src/app/move-image-modal/move-image-modal.component.ts +++ b/src/app/move-image-modal/move-image-modal.component.ts @@ -26,7 +26,7 @@ export class MoveImageModalComponent { return; } - this.deckService.moveImage(this.image.id, this.selectedDeckId).subscribe({ + this.deckService.moveImage(this.image.bildid, this.selectedDeckId).subscribe({ next: () => { this.moveCompleted.emit(); this.close(); diff --git a/src/app/services/popover.service.ts b/src/app/services/popover.service.ts index 49700a7..b8479af 100644 --- a/src/app/services/popover.service.ts +++ b/src/app/services/popover.service.ts @@ -8,19 +8,22 @@ export class PopoverService { title: string; message: string; showInput: boolean; + showCancel: boolean; inputValue: string; confirmText: string; + onConfirm: () => void; onCancel?: () => void; }>(); popoverState$ = this.showPopoverSource.asObservable(); - show(options: { title: string; message: string; confirmText?: string; onConfirm?: (inputValue?: string) => void; onCancel?: () => void }) { + show(options: { title: string; message: string; confirmText?: string; showCancel?: boolean; onConfirm?: (inputValue?: string) => void; onCancel?: () => void }) { this.showPopoverSource.next({ showInput: false, inputValue: null, confirmText: 'Ok', + showCancel: false, onConfirm: (inputValue?: string) => {}, ...options, }); @@ -28,6 +31,7 @@ export class PopoverService { showWithInput(options: { title: string; message: string; confirmText: string; inputValue: string; onConfirm: (inputValue?: string) => void; onCancel?: () => void }) { this.showPopoverSource.next({ showInput: true, + showCancel: true, ...options, }); } diff --git a/src/app/training/training.component.ts b/src/app/training/training.component.ts index ced51f8..86cf16c 100644 --- a/src/app/training/training.component.ts +++ b/src/app/training/training.component.ts @@ -125,7 +125,7 @@ export class TrainingComponent implements OnInit { if (!ctx || !this.currentImageData) return; const img = new Image(); - img.src = `/debug_images/${this.currentImageData.id}/original_compressed.jpg`; + img.src = `/debug_images/${this.currentImageData.bildid}/original_compressed.jpg`; img.onload = () => { // Set the canvas size to the image size canvas.width = img.width; diff --git a/tsconfig.json b/tsconfig.json index b79fc86..7940c48 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,10 +17,7 @@ "importHelpers": true, "target": "ES2022", "module": "ES2022", - "lib": [ - "ES2022", - "dom" - ] + "lib": ["ES2022", "dom"] }, "angularCompilerOptions": { "enableI18nLegacyMessageIdFormat": false,