logging#19, max 5MB#18,addinsert/update#14,drizzle

This commit is contained in:
Your Name
2025-01-30 16:16:20 +01:00
parent 9f25253ade
commit 5707d1bb1f
22 changed files with 1073 additions and 100 deletions

View File

@@ -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 {}

View File

@@ -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);
}
}

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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'),

View File

@@ -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();