diff --git a/api/src/app/decks.controller.ts b/api/src/app/decks.controller.ts index 5cf2071..27064ff 100644 --- a/api/src/app/decks.controller.ts +++ b/api/src/app/decks.controller.ts @@ -38,6 +38,8 @@ export class DecksController { reps: entry.reps, lapses: entry.lapses, isGraduated: Boolean(entry.isGraduated), + inserted: new Date(entry.inserted!!), + updated: new Date(entry.updated!!), }); } } @@ -83,6 +85,8 @@ export class DecksController { reps: entry.reps, lapses: entry.lapses, isGraduated: Boolean(entry.isGraduated), + inserted: new Date(entry.inserted!!), + updated: new Date(entry.updated!!), }); } } diff --git a/api/src/app/drizzle.service.ts b/api/src/app/drizzle.service.ts index a375177..a9a1834 100644 --- a/api/src/app/drizzle.service.ts +++ b/api/src/app/drizzle.service.ts @@ -1,6 +1,6 @@ // drizzle.service.ts import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { and, eq, isNull } from 'drizzle-orm'; +import { and, eq, isNull, sql } from 'drizzle-orm'; import { drizzle } from 'drizzle-orm/libsql'; import { Deck, User } from '../db/schema'; @@ -8,15 +8,32 @@ import { Deck, User } from '../db/schema'; export class DrizzleService { private db = drizzle('file:local.db'); + /** + * Methode zum Abrufen aller Decks eines Benutzers + */ async getDecks(user: User) { return this.db.select().from(Deck).where(eq(Deck.user, user.email)); } + /** + * Methode zum Erstellen eines neuen Decks + */ async createDeck(deckname: string, user: User) { - const result = await this.db.insert(Deck).values({ deckname, user: user.email }).returning(); + // 'inserted' und 'updated' werden automatisch von der Datenbank gesetzt + const result = await this.db + .insert(Deck) + .values({ + deckname, + user: user.email, + // 'inserted' und 'updated' werden von der DB automatisch gesetzt + }) + .returning(); return { status: 'success', deck: result }; } + /** + * Methode zum Abrufen eines Decks nach Name + */ async getDeckByName(deckname: string, user: User) { return this.db .select() @@ -24,6 +41,9 @@ export class DrizzleService { .where(and(eq(Deck.deckname, deckname), eq(Deck.user, user.email))); } + /** + * Methode zum Löschen eines Decks + */ async deleteDeck(deckname: string, user: User) { const existingDeck = await this.getDeckByName(deckname, user); if (existingDeck.length === 0) { @@ -34,6 +54,9 @@ export class DrizzleService { return { status: 'success' }; } + /** + * Methode zum Umbenennen eines Decks + */ async renameDeck(oldDeckname: string, newDeckname: string, user: User) { const existingDeck = await this.getDeckByName(oldDeckname, user); if (existingDeck.length === 0) { @@ -47,17 +70,34 @@ export class DrizzleService { await this.db .update(Deck) - .set({ deckname: newDeckname }) + .set({ + deckname: newDeckname, + updated: sql`CURRENT_TIMESTAMP`, // Setze 'updated' auf CURRENT_TIMESTAMP + }) .where(and(eq(Deck.deckname, oldDeckname), eq(Deck.user, user.email))); return { status: 'success', message: 'Deck renamed successfully' }; } - async updateImage(data: { deckname: string; bildname: string; bildid: string; boxes: Array<{ x1: number; x2: number; y1: number; y2: number }> }, user: User) { + /** + * 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 + }, + user: User, + ) { const existingDeck = await this.getDeckByName(data.deckname, user); if (existingDeck.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))); const insertedImages: any = []; @@ -75,6 +115,8 @@ export class DrizzleService { 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 }) .returning(); insertedImages.push(result); @@ -83,6 +125,9 @@ export class DrizzleService { return { status: 'success', inserted_images: insertedImages }; } + /** + * Methode zum Löschen von Bildern anhand der bildid + */ async deleteImagesByBildId(bildid: string, user: User) { const affectedDecks = await this.db .select({ deckname: Deck.deckname }) @@ -100,7 +145,7 @@ export class DrizzleService { const remainingImages = await this.db .select() .from(Deck) - .where(and(eq(Deck.deckname, deck.deckname), eq(Deck.bildid, bildid))) + .where(and(eq(Deck.deckname, deck.deckname), eq(Deck.bildid, bildid), eq(Deck.user, user.email))) .all(); if (remainingImages.length === 0) { @@ -111,7 +156,11 @@ export class DrizzleService { .all(); if (emptyDeckEntry.length === 0) { - await this.db.insert(Deck).values({ deckname: deck.deckname, user: user.email }); + await this.db.insert(Deck).values({ + deckname: deck.deckname, + user: user.email, + // 'inserted' und 'updated' werden automatisch von der DB gesetzt + }); } } } @@ -122,6 +171,9 @@ export class DrizzleService { }; } + /** + * Methode zum Verschieben eines Bildes in ein anderes Deck + */ async moveImage(bildid: string, targetDeckId: string, user: User) { const existingImages = await this.db .select() @@ -135,12 +187,18 @@ export class DrizzleService { await this.db .update(Deck) - .set({ deckname: targetDeckId }) + .set({ + deckname: targetDeckId, + updated: sql`CURRENT_TIMESTAMP`, // Setze 'updated' auf CURRENT_TIMESTAMP + }) .where(and(eq(Deck.bildid, bildid), eq(Deck.user, user.email))); return { status: 'success', moved_entries: existingImages.length }; } + /** + * Methode zum Aktualisieren einer Box + */ async updateBox( boxId: number, data: { @@ -157,6 +215,7 @@ export class DrizzleService { if (typeof data.isGraduated === 'boolean') { updateData.isGraduated = Number(data.isGraduated); } + updateData.updated = sql`CURRENT_TIMESTAMP`; // Setze 'updated' auf CURRENT_TIMESTAMP const result = await this.db .update(Deck) diff --git a/api/src/db/schema.ts b/api/src/db/schema.ts index 260228c..16512d2 100644 --- a/api/src/db/schema.ts +++ b/api/src/db/schema.ts @@ -1,3 +1,4 @@ +import { sql } from 'drizzle-orm'; import * as t from 'drizzle-orm/sqlite-core'; import { integer, real, sqliteTable as table, text } from 'drizzle-orm/sqlite-core'; @@ -20,6 +21,8 @@ export const Deck = table( lapses: integer('lapses'), isGraduated: integer('isGraduated'), user: text('user').notNull(), + inserted: text().default(sql`(CURRENT_TIMESTAMP)`), // Neue Spalte + updated: text().default(sql`(CURRENT_TIMESTAMP)`), // Neue Spalte }, table => { return { diff --git a/drizzle/0002_aromatic_zodiak.sql b/drizzle/0002_aromatic_zodiak.sql new file mode 100644 index 0000000..1e3b53c --- /dev/null +++ b/drizzle/0002_aromatic_zodiak.sql @@ -0,0 +1,2 @@ +ALTER TABLE `Deck` ADD `inserted` text DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint +ALTER TABLE `Deck` ADD `updated` text DEFAULT (CURRENT_TIMESTAMP); \ No newline at end of file diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json new file mode 100644 index 0000000..9c9abdd --- /dev/null +++ b/drizzle/meta/0002_snapshot.json @@ -0,0 +1,164 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "1e5421d8-df58-434e-93c8-85dcbe6ec9ee", + "prevId": "a3cf5e86-4f1b-4cdc-9688-cf9063ba6936", + "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 + }, + "iconindex": { + "name": "iconindex", + "type": "integer", + "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 7510470..450c270 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -15,6 +15,13 @@ "when": 1737229845910, "tag": "0001_smooth_iron_lad", "breakpoints": true + }, + { + "idx": 2, + "version": "6", + "when": 1738103858394, + "tag": "0002_aromatic_zodiak", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/app/app.component.ts b/src/app/app.component.ts index e428bf9..3083a49 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -2,8 +2,10 @@ import { CommonModule } from '@angular/common'; import { Component, inject } from '@angular/core'; import { Auth } from '@angular/fire/auth'; import { GoogleAuthProvider, signInWithPopup } from 'firebase/auth'; +import { PopoverComponent } from './components/popover.component'; import { DeckListComponent } from './deck-list.component'; import { ClickOutsideDirective } from './service/click-outside.directive'; +import { PopoverService } from './services/popover.service'; @Component({ selector: 'app-root', template: ` @@ -31,11 +33,10 @@ import { ClickOutsideDirective } from './service/click-outside.directive';