#14: add timestamp for inserted & updated
This commit is contained in:
parent
2f35648264
commit
9f25253ade
|
|
@ -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!!),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE `Deck` ADD `inserted` text DEFAULT (CURRENT_TIMESTAMP);--> statement-breakpoint
|
||||
ALTER TABLE `Deck` ADD `updated` text DEFAULT (CURRENT_TIMESTAMP);
|
||||
|
|
@ -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": {}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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: `
|
||||
|
|
@ -34,8 +36,7 @@ import { ClickOutsideDirective } from './service/click-outside.directive';
|
|||
<img [src]="photoURL" alt="User Photo" class="w-10 h-10 rounded-full cursor-pointer" (click)="toggleDropdown()" referrerpolicy="no-referrer" crossorigin="anonymous" />
|
||||
} @else {
|
||||
<div class="image-placeholder w-10 h-10 rounded-full cursor-pointer">Image</div>
|
||||
}
|
||||
@if(showDropdown){
|
||||
} @if(showDropdown){
|
||||
<div class="absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-lg">
|
||||
<button (click)="logout()" class="block w-full text-left px-4 py-2 text-gray-700 hover:bg-gray-100">Abmelden</button>
|
||||
</div>
|
||||
|
|
@ -44,6 +45,16 @@ import { ClickOutsideDirective } from './service/click-outside.directive';
|
|||
</div>
|
||||
<app-deck-list></app-deck-list>
|
||||
</div>
|
||||
<app-popover
|
||||
[visible]="popoverVisible"
|
||||
[title]="popoverTitle"
|
||||
[message]="popoverMessage"
|
||||
[showInput]="popoverShowInput"
|
||||
[inputValue]="popoverInputValue"
|
||||
[confirmText]="popoverConfirmText"
|
||||
(confirmed)="handleConfirm($event)"
|
||||
(canceled)="handleCancel()"
|
||||
></app-popover>
|
||||
`,
|
||||
standalone: true,
|
||||
styles: `
|
||||
|
|
@ -80,7 +91,7 @@ img:hover {
|
|||
}
|
||||
|
||||
`,
|
||||
imports: [CommonModule, DeckListComponent, ClickOutsideDirective],
|
||||
imports: [CommonModule, DeckListComponent, ClickOutsideDirective, PopoverComponent],
|
||||
})
|
||||
export class AppComponent {
|
||||
isLoggedIn = false;
|
||||
|
|
@ -88,7 +99,27 @@ export class AppComponent {
|
|||
showDropdown = false;
|
||||
photoURL: string = 'https://placehold.co/40';
|
||||
// user: User | null = null;
|
||||
popoverVisible = false;
|
||||
popoverTitle = '';
|
||||
popoverMessage = '';
|
||||
popoverShowInput = false;
|
||||
popoverInputValue = '';
|
||||
popoverConfirmText = 'Confirm';
|
||||
private confirmCallback?: (inputValue?: string) => void;
|
||||
private cancelCallback?: () => void;
|
||||
|
||||
constructor(public popoverService: PopoverService) {
|
||||
this.popoverService.popoverState$.subscribe(options => {
|
||||
this.popoverVisible = true;
|
||||
this.popoverTitle = options.title;
|
||||
this.popoverMessage = options.message;
|
||||
this.popoverShowInput = options.showInput;
|
||||
this.popoverInputValue = options.inputValue;
|
||||
this.popoverConfirmText = options.confirmText;
|
||||
this.confirmCallback = options.onConfirm;
|
||||
this.cancelCallback = options.onCancel;
|
||||
});
|
||||
}
|
||||
ngOnInit() {
|
||||
// Überprüfen des Login-Status beim Start der Anwendung
|
||||
const isLoggedIn = localStorage.getItem('isLoggedIn') === 'true';
|
||||
|
|
@ -133,4 +164,17 @@ export class AppComponent {
|
|||
toggleDropdown() {
|
||||
this.showDropdown = !this.showDropdown;
|
||||
}
|
||||
handleConfirm(inputValue?: string) {
|
||||
this.popoverVisible = false;
|
||||
if (this.confirmCallback) {
|
||||
this.confirmCallback(inputValue);
|
||||
}
|
||||
}
|
||||
|
||||
handleCancel() {
|
||||
this.popoverVisible = false;
|
||||
if (this.cancelCallback) {
|
||||
this.cancelCallback();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { map, Observable, switchMap } from 'rxjs';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { map, Observable } from 'rxjs';
|
||||
|
||||
export interface Deck {
|
||||
name: string;
|
||||
|
|
@ -10,6 +10,7 @@ export interface Deck {
|
|||
export interface DeckImage {
|
||||
boxes: Box[];
|
||||
name: string;
|
||||
//bildid: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
|
|
@ -53,7 +54,7 @@ export interface OcrResult {
|
|||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class DeckService {
|
||||
private apiUrl = '/api/decks';
|
||||
|
|
@ -62,10 +63,12 @@ export class DeckService {
|
|||
|
||||
getDecks(): Observable<Deck[]> {
|
||||
return this.http.get<any[]>(this.apiUrl).pipe(
|
||||
map(decks => decks.map(deck => ({
|
||||
map(decks =>
|
||||
decks.map(deck => ({
|
||||
name: deck.name,
|
||||
images: this.groupImagesByName(deck.images)
|
||||
})))
|
||||
images: this.groupImagesByName(deck.images),
|
||||
})),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -77,7 +80,7 @@ export class DeckService {
|
|||
imageMap[image.id] = {
|
||||
name: image.name,
|
||||
id: image.id,
|
||||
boxes: []
|
||||
boxes: [],
|
||||
};
|
||||
}
|
||||
imageMap[image.id].boxes.push({
|
||||
|
|
@ -91,7 +94,7 @@ export class DeckService {
|
|||
factor: image.factor,
|
||||
reps: image.reps,
|
||||
lapses: image.lapses,
|
||||
isGraduated: image.isGraduated ? true : false
|
||||
isGraduated: image.isGraduated ? true : false,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue