#23: users table + insert/update added
This commit is contained in:
parent
5eee7c9ac4
commit
d180cd70e8
3
api/.env
3
api/.env
|
|
@ -1,5 +1,6 @@
|
|||
DB_FILE_NAME=file:local.db
|
||||
PORT=3000
|
||||
PORT=3002
|
||||
FIREBASE_PROJECT_ID=haiki-452bd
|
||||
FIREBASE_CLIENT_EMAIL=firebase-adminsdk-fbsvc@haiki-452bd.iam.gserviceaccount.com
|
||||
FIREBASE_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDyCsRhtPYwozBy\n60A4LguqsFzJG0WwCMhvi7PIxoh1kVenwxXBBQHvgssF/jPTkqbK6orL9r15gdRc\nZK2S73OdESYlO9xCE+gq/pop1F432DrHBA6ftQIl2NHSQfkKgTKFM6Pt85e6s5mb\neIxxHr7AzGMlqu7UJbnw8Vk2+8LP3NZXwsyCEi5vg4+8UgPFVxhLnB4HYZIM8JOj\nM3q1N/KqKgl2qGl5ekZLN7QLPs6+znlVZVIeHo1vuX9xTUaa6XUA0xeZfFO3pNUd\nKLlQNAy7OFQ6GlTFTWWHnShecLPHLQdWP15rkwBPv1q7qDmtLMy4E2XLvsoErhJM\niFsbEczxAgMBAAECggEAJqGJTn7vfDvPk8fwbAcNXaTgakisCricJRGLFFR7mygj\ncWc1paUC9hNODBrScsZJUMG2fW9YNnh+SHDZM0Z8kWkXSYIQWYuL1rDkMiDvGMKu\nPu1q2BqvyRKeCoz1DrQoOBJR67yhTu8zaRkIcVWS5Hq6qFxr2fhbgRVEQ/5SzZIG\nPh+Npxdp62Xe66MB0OzmF/A5qSrXTpOOk4/Lmpoqf6PtmrOD+SetE+Aa6ELYX3pK\n30XPLiUyDS+EFPjHLA1U7frOawLRpMP6Iobu7hUzu9ASzgLKxpzbcGRsPtbVRuAQ\nzP+iV+Nyn/wMFbnnjrjlwk/jqE+NLGJnf7Jrc9vwQQKBgQD5lfRWR6MKDezty5Qu\nPGrSlKXOM+aOoTmZiaPTutYzwWHeqzfUfYsghbgR3jl7I0BTqMaGrOnYU60IDWZi\nJeK6iu68pUe8Mme3vXm15Go55UhZD9U5/4W+x0/+AVivBPUBwKUXFdgmPFkb2jBY\np1LJmXaelx2jvPM3yMXQN+5flQKBgQD4Qy6aF3V7DIVxo/F88KCGNUpEobyXsxv6\nakSV+7WEtuSf6amXHxZyiJPtjCIwzCUL458gUd6mwiQgWRy7awdKreU6BCHgqNVf\nAE5ahjeeiLOc1uu0ocNLL+g35DbBXSgSlB+hUE0+bxAxU9xjNc9UZKqIi/kGRP/G\nlxi2ZUIQ7QKBgQDUH5Ku0evL29IGuQOT2F2h5ByXiJznlDd0Ovs2NJFhI3ae3T5y\nJtFcLsomxYxtD6TYdZVlWQjWhyeEtH7T5AczLGmDg6XYWa61ByCuaxetZSV8LGy5\nAmcVoihmZZaOCdSCTM0DNdmjhZ7mgSad8nf2R6v9VconI6xDOSyGr0K1kQKBgQCp\nuhxxIpqhzlSo9aFSfpvwRRyKQVzTBZOaJu7O7zARFIzHOxNDivBoyzD/FXAGlnq5\nXxvaF761mULjjqjTBQAOMUbm3A5hLmv5sBbhUqNR0jmhf1nTu0ft7km/dFlu5wZP\ndU8OlPzKM1oJr0Cb3xzooI3qHm/YtnF7Tq+Jez6onQKBgQCfbqG5dyTAduhPZzmp\n6m4ndzzdYVoh8KiM3fUApo/u9zF3GixUFgcKKFzP1/zmD6A6UfbVT+JVmUfIVtor\nAA42lqJyOaNH9ttQjZeDXfPpbAyAoZzH0l7/U9KSOkh+2I8tMscjWFqyQ1/DGNcY\nptIRECjQrk5jL0yea0+tbpMmJQ==\n-----END PRIVATE KEY-----\n
|
||||
DATABASE_URL=postgresql://haiky:xieng7Seih@localhost:15432/haiky
|
||||
|
|
@ -3,10 +3,11 @@ import { DecksController } from './decks.controller';
|
|||
import { DrizzleService } from './drizzle.service';
|
||||
import { ProxyController } from './proxy.controller';
|
||||
import { SqlLoggerService } from './sql-logger.service';
|
||||
import { UserController } from './user.controller';
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
controllers: [DecksController, ProxyController],
|
||||
controllers: [DecksController, ProxyController, UserController],
|
||||
providers: [DrizzleService, SqlLoggerService],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
|||
import { and, eq, sql } from 'drizzle-orm';
|
||||
//import { drizzle } from 'drizzle-orm/libsql';
|
||||
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||
import { deck, SelectDeck, User } from '../db/schema';
|
||||
import { deck, InsertUser, SelectDeck, User, users } from '../db/schema';
|
||||
import { SqlLoggerService } from './sql-logger.service';
|
||||
|
||||
@Injectable()
|
||||
|
|
@ -11,16 +11,7 @@ export class DrizzleService {
|
|||
// 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);
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
|
||||
this.db = drizzle(process.env['DATABASE_URL']!, {
|
||||
//this.db = drizzle('postgresql://haiky:xieng7Seih@localhost:15432/haiky', {
|
||||
logger: {
|
||||
logQuery: (query: string, params: any[]) => {
|
||||
this.sqlLogger.logQuery(query, params);
|
||||
|
|
@ -296,4 +287,42 @@ export class DrizzleService {
|
|||
throw new HttpException(`Fehler beim Abrufen der Bild-IDs - ${error}`, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Führt den Login-Vorgang durch:
|
||||
* - Existiert der Benutzer bereits (überprüft via E-Mail), wird das Feld `lastLogin` (und ggf. weitere Felder) aktualisiert.
|
||||
* - Existiert der Benutzer nicht, wird ein neuer Datensatz mit `role: 'guest'` und `lastLogin` auf den aktuellen Zeitpunkt angelegt.
|
||||
*/
|
||||
async logIn(createUserDto: InsertUser) {
|
||||
// Prüfen, ob der Benutzer bereits existiert (hier anhand der E-Mail)
|
||||
const existingUser = await this.db.select().from(users).where(eq(users.email, createUserDto.email)).limit(1);
|
||||
|
||||
if (existingUser.length > 0) {
|
||||
// Benutzer existiert: Update des letzten Logins und ggf. weiterer Felder
|
||||
const updatedUser = await this.db
|
||||
.update(users)
|
||||
.set({
|
||||
lastLogin: new Date(), // Setzt lastLogin explizit auf den aktuellen Zeitpunkt
|
||||
// Optional: Aktualisierung von Name und sign_in_provider, falls sich diese ändern sollten
|
||||
name: createUserDto.name,
|
||||
sign_in_provider: createUserDto.sign_in_provider,
|
||||
})
|
||||
.where(eq(users.email, createUserDto.email))
|
||||
.returning();
|
||||
return updatedUser;
|
||||
} else {
|
||||
// Neuer Benutzer: Insert mit role per Default 'guest' und lastLogin auf now
|
||||
const insertedUser = await this.db
|
||||
.insert(users)
|
||||
.values({
|
||||
name: createUserDto.name,
|
||||
email: createUserDto.email,
|
||||
sign_in_provider: createUserDto.sign_in_provider,
|
||||
lastLogin: new Date(), // Setzt lastLogin auf now
|
||||
role: 'guest', // Default-Wert 'guest'
|
||||
})
|
||||
.returning();
|
||||
return insertedUser;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
// user.controller.ts
|
||||
import { Body, Controller, Post } from '@nestjs/common';
|
||||
import type { InsertUser } from '../db/schema';
|
||||
import { DrizzleService } from './drizzle.service';
|
||||
|
||||
@Controller('users')
|
||||
export class UserController {
|
||||
constructor(private readonly drizzleService: DrizzleService) {}
|
||||
|
||||
@Post()
|
||||
async createUser(@Body() createUserDto: InsertUser) {
|
||||
// Hier kannst du zusätzliche Validierungen oder Logik einbauen.
|
||||
return await this.drizzleService.logIn(createUserDto);
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,8 @@ export const deck = table(
|
|||
},
|
||||
table => [t.uniqueIndex('deck_idx').on(table.id)],
|
||||
);
|
||||
export type InsertDeck = typeof deck.$inferInsert;
|
||||
export type SelectDeck = typeof deck.$inferSelect;
|
||||
export const users = table(
|
||||
'users',
|
||||
{
|
||||
|
|
@ -34,13 +36,12 @@ export const users = table(
|
|||
email: t.varchar().notNull(),
|
||||
role: rolesEnum().default('guest'),
|
||||
sign_in_provider: t.varchar('sign_in_provider', { length: 50 }),
|
||||
inserted: t.timestamp('inserted', { mode: 'date' }).defaultNow(),
|
||||
updated: t.timestamp('updated', { mode: 'date' }).defaultNow(),
|
||||
lastLogin: t.timestamp('lastLogin', { mode: 'date' }).defaultNow(),
|
||||
},
|
||||
table => [t.uniqueIndex('users_idx').on(table.id)],
|
||||
);
|
||||
export type InsertDeck = typeof deck.$inferInsert;
|
||||
export type SelectDeck = typeof deck.$inferSelect;
|
||||
export type InsertUser = typeof users.$inferInsert;
|
||||
export type SelectUser = typeof users.$inferSelect;
|
||||
export interface User {
|
||||
name: string;
|
||||
picture: string;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
"module": "ES2020",
|
||||
"types": ["node"],
|
||||
"emitDecoratorMetadata": true,
|
||||
"isolatedModules": true,
|
||||
"target": "es2021",
|
||||
"strictNullChecks": true
|
||||
},
|
||||
|
|
|
|||
|
|
@ -25,8 +25,7 @@ CREATE TABLE "users" (
|
|||
"email" varchar NOT NULL,
|
||||
"role" "roles" DEFAULT 'guest',
|
||||
"sign_in_provider" varchar(50),
|
||||
"inserted" timestamp DEFAULT now(),
|
||||
"updated" timestamp DEFAULT now()
|
||||
"lastLogin" timestamp DEFAULT now()
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "deck_idx" ON "deck" USING btree ("id");--> statement-breakpoint
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"id": "5c983d57-b2f3-4fb4-b99e-066574c486e3",
|
||||
"id": "2bed4eae-3e6e-414b-85e2-d16e632c17ef",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
|
|
@ -202,8 +202,8 @@
|
|||
"notNull": false,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated": {
|
||||
"name": "updated",
|
||||
"lastLogin": {
|
||||
"name": "lastLogin",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
|
|
|
|||
|
|
@ -5,8 +5,8 @@
|
|||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1738963581892,
|
||||
"tag": "0000_known_stepford_cuckoos",
|
||||
"when": 1739461160843,
|
||||
"tag": "0000_fuzzy_invaders",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -90,7 +90,8 @@
|
|||
"serve": {
|
||||
"executor": "@angular/build:dev-server",
|
||||
"options": {
|
||||
"proxyConfig": "proxy.conf.json"
|
||||
"proxyConfig": "proxy.conf.json",
|
||||
"port": 4202
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"/api": {
|
||||
"target": "http://localhost:3000",
|
||||
"target": "http://localhost:3002",
|
||||
"secure": false,
|
||||
"changeOrigin": true
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import { CommonModule } from '@angular/common';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { Auth } from '@angular/fire/auth';
|
||||
import { GoogleAuthProvider, signInWithPopup } from 'firebase/auth';
|
||||
import { GoogleAuthProvider, signInWithPopup, UserCredential } 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';
|
||||
import { UserService } from './services/user.service';
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
template: `
|
||||
|
|
@ -119,7 +120,7 @@ export class AppComponent {
|
|||
private confirmCallback?: (inputValue?: string) => void;
|
||||
private cancelCallback?: () => void;
|
||||
|
||||
constructor(public popoverService: PopoverService) {
|
||||
constructor(public popoverService: PopoverService, private userService: UserService) {
|
||||
this.popoverService.popoverState$.subscribe(options => {
|
||||
this.popoverVisible = true;
|
||||
this.popoverTitle = options.title;
|
||||
|
|
@ -147,7 +148,7 @@ export class AppComponent {
|
|||
async loginWithGoogle() {
|
||||
const provider = new GoogleAuthProvider();
|
||||
try {
|
||||
const result = await signInWithPopup(this.auth, provider);
|
||||
const result: UserCredential = await signInWithPopup(this.auth, provider);
|
||||
this.isLoggedIn = true;
|
||||
this.photoURL = result.user.photoURL;
|
||||
|
||||
|
|
@ -159,6 +160,8 @@ export class AppComponent {
|
|||
|
||||
this.showDropdown = false;
|
||||
|
||||
await this.userService.logIn({ name: result.user.displayName, email: result.user.email, sign_in_provider: result.providerId });
|
||||
|
||||
console.log('Logged in with Google', result.user);
|
||||
} catch (error) {
|
||||
console.error('Google Login failed', error);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { CommonModule } from '@angular/common';
|
|||
import { AfterViewInit, Component, ElementRef, EventEmitter, Output, ViewChild } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { Modal } from 'flowbite';
|
||||
import { DeckService } from '../deck.service';
|
||||
import { DeckService } from '../services/deck.service';
|
||||
import { PopoverService } from '../services/popover.service';
|
||||
|
||||
@Component({
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ import { ChangeDetectorRef, Component, ElementRef, OnInit, ViewChild } from '@an
|
|||
import { FormsModule } from '@angular/forms';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { CreateDeckModalComponent } from './create-deck-modal/create-deck-modal.component';
|
||||
import { Box, Deck, DeckImage, DeckService, OcrResult } from './deck.service';
|
||||
import { EditImageModalComponent } from './edit-image-modal/edit-image-modal.component';
|
||||
import { MoveImageModalComponent } from './move-image-modal/move-image-modal.component';
|
||||
import { Box, Deck, DeckImage, DeckService, OcrResult } from './services/deck.service';
|
||||
import { PopoverService } from './services/popover.service';
|
||||
import { TrainingComponent } from './training/training.component';
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common';
|
|||
import { AfterViewInit, Component, ElementRef, EventEmitter, Input, OnDestroy, Output, ViewChild } from '@angular/core';
|
||||
import { fabric } from 'fabric';
|
||||
import { Modal } from 'flowbite';
|
||||
import { DeckImage, DeckService } from '../deck.service';
|
||||
import { DeckImage, DeckService } from '../services/deck.service';
|
||||
import { PopoverService } from '../services/popover.service';
|
||||
|
||||
@Component({
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { CommonModule } from '@angular/common';
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { Deck, DeckImage, DeckService } from '../deck.service';
|
||||
import { Deck, DeckImage, DeckService } from '../services/deck.service';
|
||||
import { PopoverService } from '../services/popover.service';
|
||||
|
||||
@Component({
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
import { HttpClient } from '@angular/common/http';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
|
||||
export interface User {
|
||||
name: string;
|
||||
email: string;
|
||||
sign_in_provider: string;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class UserService {
|
||||
// Passe die URL an, je nachdem wie dein Backend-Routing definiert ist.
|
||||
private apiUrl = '/api/users';
|
||||
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
/**
|
||||
* Sendet die Benutzerinformationen an das Backend.
|
||||
*/
|
||||
logIn(user: User): Promise<any> {
|
||||
return lastValueFrom(this.http.post<any>(this.apiUrl, user));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import { CommonModule } from '@angular/common';
|
||||
import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { Box, Deck, DeckImage, DeckService } from '../deck.service';
|
||||
import { Box, Deck, DeckImage, DeckService } from '../services/deck.service';
|
||||
import { PopoverService } from '../services/popover.service';
|
||||
|
||||
const LEARNING_STEPS = {
|
||||
|
|
|
|||
Loading…
Reference in New Issue