From 122dad35cedab78706f45ae3b4f371abddb285cb Mon Sep 17 00:00:00 2001 From: neodk2004 Date: Fri, 26 Dec 2025 12:03:00 +0100 Subject: [PATCH] feat(auth): add dummy auth foundation with global guard --- .../20251226103251_init/migration.sql | 82 ++++++++ backend/prisma/migrations/migration_lock.toml | 3 + backend/prisma/seed.ts | 184 +++++++++--------- backend/src/app.module.ts | 15 ++ backend/src/auth/README.md | 0 backend/src/auth/auth.module.ts | 0 backend/src/auth/constants/auth.constants.ts | 0 .../src/auth/decorators/public.decorator.ts | 5 + backend/src/auth/guards/dummy-auth.guard.ts | 47 +++++ backend/src/auth/types/request-user.type.ts | 15 ++ backend/src/types/express.d.ts | 7 + docker-compose.db.yml | 18 ++ 12 files changed, 289 insertions(+), 87 deletions(-) create mode 100644 backend/prisma/migrations/20251226103251_init/migration.sql create mode 100644 backend/prisma/migrations/migration_lock.toml create mode 100644 backend/src/auth/README.md create mode 100644 backend/src/auth/auth.module.ts create mode 100644 backend/src/auth/constants/auth.constants.ts create mode 100644 backend/src/auth/decorators/public.decorator.ts create mode 100644 backend/src/auth/guards/dummy-auth.guard.ts create mode 100644 backend/src/auth/types/request-user.type.ts create mode 100644 backend/src/types/express.d.ts create mode 100644 docker-compose.db.yml diff --git a/backend/prisma/migrations/20251226103251_init/migration.sql b/backend/prisma/migrations/20251226103251_init/migration.sql new file mode 100644 index 0000000..0adff16 --- /dev/null +++ b/backend/prisma/migrations/20251226103251_init/migration.sql @@ -0,0 +1,82 @@ +-- CreateTable +CREATE TABLE "User" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "email" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Pet" ( + "id" SERIAL NOT NULL, + "name" TEXT NOT NULL, + "type" TEXT NOT NULL, + "breed" TEXT NOT NULL, + "birthDate" TIMESTAMP(3), + "gender" TEXT, + "bio" TEXT, + "avatarUrl" TEXT, + "visibility" TEXT NOT NULL DEFAULT 'public', + "ownerId" INTEGER NOT NULL, + + CONSTRAINT "Pet_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Post" ( + "id" SERIAL NOT NULL, + "content" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "petId" INTEGER NOT NULL, + "authorId" INTEGER NOT NULL, + + CONSTRAINT "Post_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Follow" ( + "id" SERIAL NOT NULL, + "followerId" INTEGER NOT NULL, + "followingId" INTEGER NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Follow_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Notification" ( + "id" SERIAL NOT NULL, + "type" TEXT NOT NULL, + "payload" JSONB NOT NULL, + "read" BOOLEAN NOT NULL DEFAULT false, + "userId" INTEGER NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Notification_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); + +-- CreateIndex +CREATE UNIQUE INDEX "Follow_followerId_followingId_key" ON "Follow"("followerId", "followingId"); + +-- AddForeignKey +ALTER TABLE "Pet" ADD CONSTRAINT "Pet_ownerId_fkey" FOREIGN KEY ("ownerId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Post" ADD CONSTRAINT "Post_petId_fkey" FOREIGN KEY ("petId") REFERENCES "Pet"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Post" ADD CONSTRAINT "Post_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Follow" ADD CONSTRAINT "Follow_followerId_fkey" FOREIGN KEY ("followerId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Follow" ADD CONSTRAINT "Follow_followingId_fkey" FOREIGN KEY ("followingId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Notification" ADD CONSTRAINT "Notification_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/backend/prisma/migrations/migration_lock.toml b/backend/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..044d57c --- /dev/null +++ b/backend/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" diff --git a/backend/prisma/seed.ts b/backend/prisma/seed.ts index 3c6deab..4302fea 100644 --- a/backend/prisma/seed.ts +++ b/backend/prisma/seed.ts @@ -3,125 +3,135 @@ import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); async function main() { - // -------- USERS -------- + console.log('DATABASE_URL:', process.env.DATABASE_URL); + + /** + * 1. USERS (ohne Passwort – Auth kommt später) + */ const users = await Promise.all([ - prisma.user.create({ - data: { name: 'Alice', email: 'alice@test.local' }, - }), - prisma.user.create({ - data: { name: 'Bob', email: 'bob@test.local' }, - }), - prisma.user.create({ - data: { name: 'Clara', email: 'clara@test.local' }, - }), - prisma.user.create({ - data: { name: 'David', email: 'david@test.local' }, - }), - prisma.user.create({ - data: { name: 'Eva', email: 'eva@test.local' }, - }), - ]); + prisma.user.upsert({ + where: { email: 'alice@onlypets.dev' }, + update: {}, + create: { + email: 'alice@onlypets.dev', + name: 'Alice', + }, + }), + prisma.user.upsert({ + where: { email: 'bob@onlypets.dev' }, + update: {}, + create: { + email: 'bob@onlypets.dev', + name: 'Bob', + }, + }), + prisma.user.upsert({ + where: { email: 'carla@onlypets.dev' }, + update: {}, + create: { + email: 'carla@onlypets.dev', + name: 'Carla', + }, + }), +]); - // -------- PET HELPERS -------- - const yearsAgo = (y: number) => - new Date(new Date().getFullYear() - y, 5, 1); - const todayBirthday = new Date( - new Date().getFullYear() - 3, - new Date().getMonth(), - new Date().getDate(), - ); - - // -------- PETS -------- + /** + * 2. PETS + */ const pets = await Promise.all([ - prisma.pet.create({ - data: { - name: 'Luna', - type: 'Katze', - breed: 'EKH', - birthDate: todayBirthday, // 🎂 Geburtstag heute - ownerId: users[0].id, - visibility: 'public', - }, - }), - prisma.pet.create({ - data: { - name: 'Max', - type: 'Hund', - breed: 'Labrador', - birthDate: yearsAgo(5), - ownerId: users[1].id, - visibility: 'followers', - }, - }), - prisma.pet.create({ - data: { - name: 'Milo', - type: 'Katze', - breed: 'BKH', - birthDate: yearsAgo(2), - ownerId: users[2].id, - visibility: 'public', - }, - }), - prisma.pet.create({ - data: { - name: 'Bella', - type: 'Hund', - breed: 'Mischling', - birthDate: yearsAgo(7), - ownerId: users[3].id, - visibility: 'public', - }, - }), - ]); + prisma.pet.create({ + data: { + name: 'Milo', + type: 'Katze', + breed: 'EKH', + birthDate: new Date('2020-05-12'), + ownerId: users[0].id, + visibility: 'public', + }, + }), + prisma.pet.create({ + data: { + name: 'Luna', + type: 'Hund', + breed: 'Labrador', + birthDate: new Date('2019-11-03'), + ownerId: users[1].id, + visibility: 'followers', + }, + }), + prisma.pet.create({ + data: { + name: 'Oscar', + type: 'Katze', + breed: 'BKH', + birthDate: new Date('2022-02-01'), + ownerId: users[2].id, + visibility: 'public', + }, + }), +]); - // -------- POSTS -------- + + /** + * 3. POSTS + */ await Promise.all([ prisma.post.create({ data: { - content: 'Luna chillt in der Sonne ☀️', - petId: pets[0].id, + content: 'Milo liebt die Sonne ☀️', authorId: users[0].id, + petId: pets[0].id, }, }), prisma.post.create({ data: { - content: 'Milo hat heute Unsinn gemacht 😼', - petId: pets[2].id, + content: 'Luna beim Spaziergang 🐕', + authorId: users[1].id, + petId: pets[1].id, + }, + }), + prisma.post.create({ + data: { + content: 'Oscar schläft schon wieder 😴', authorId: users[2].id, - }, - }), - prisma.post.create({ - data: { - content: 'Bella nach dem Spaziergang 🐕', - petId: pets[3].id, - authorId: users[3].id, + petId: pets[2].id, }, }), ]); - // -------- FOLLOWS -------- + /** + * 4. FOLLOWS + */ await Promise.all([ prisma.follow.create({ - data: { followerId: users[1].id, followingId: users[0].id }, + data: { + followerId: users[0].id, + followingId: users[1].id, + }, }), prisma.follow.create({ - data: { followerId: users[2].id, followingId: users[0].id }, + data: { + followerId: users[0].id, + followingId: users[2].id, + }, }), prisma.follow.create({ - data: { followerId: users[3].id, followingId: users[1].id }, + data: { + followerId: users[1].id, + followingId: users[2].id, + }, }), ]); - console.log('✅ Seed-Daten erfolgreich erstellt'); + console.log('✅ Seed erfolgreich ausgeführt'); } main() .catch((e) => { - console.error(e); + console.error('❌ Seed fehlgeschlagen:', e); process.exit(1); }) .finally(async () => { await prisma.$disconnect(); - }); \ No newline at end of file + }); diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index e69de29..8f2596a 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -0,0 +1,15 @@ +import { Module } from '@nestjs/common'; +import { APP_GUARD } from '@nestjs/core'; + +import { DummyAuthGuard } from './auth/guards/dummy-auth.guard'; + +@Module({ + imports: [], + providers: [ + { + provide: APP_GUARD, + useClass: DummyAuthGuard, + }, + ], +}) +export class AppModule {} diff --git a/backend/src/auth/README.md b/backend/src/auth/README.md new file mode 100644 index 0000000..e69de29 diff --git a/backend/src/auth/auth.module.ts b/backend/src/auth/auth.module.ts new file mode 100644 index 0000000..e69de29 diff --git a/backend/src/auth/constants/auth.constants.ts b/backend/src/auth/constants/auth.constants.ts new file mode 100644 index 0000000..e69de29 diff --git a/backend/src/auth/decorators/public.decorator.ts b/backend/src/auth/decorators/public.decorator.ts new file mode 100644 index 0000000..767ac49 --- /dev/null +++ b/backend/src/auth/decorators/public.decorator.ts @@ -0,0 +1,5 @@ +import { SetMetadata } from '@nestjs/common'; + +export const IS_PUBLIC_KEY = 'isPublic'; + +export const Public = () => SetMetadata(IS_PUBLIC_KEY, true); diff --git a/backend/src/auth/guards/dummy-auth.guard.ts b/backend/src/auth/guards/dummy-auth.guard.ts new file mode 100644 index 0000000..e54dee9 --- /dev/null +++ b/backend/src/auth/guards/dummy-auth.guard.ts @@ -0,0 +1,47 @@ +import { + CanActivate, + ExecutionContext, + Injectable, +} from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { Request } from 'express'; + +import { IS_PUBLIC_KEY } from '../decorators/public.decorator'; +import { RequestUser, UserRole } from '../types/request-user.type'; + +@Injectable() +export class DummyAuthGuard implements CanActivate { + constructor(private readonly reflector: Reflector) {} + + canActivate(context: ExecutionContext): boolean { + const isPublic = this.reflector.getAllAndOverride( + IS_PUBLIC_KEY, + [context.getHandler(), context.getClass()], + ); + + const request = context.switchToHttp().getRequest(); + + // Öffentliche Route → kein User nötig + if (isPublic) { + request.user = null; + return true; + } + + // Dummy-Auth nur aktiv, wenn explizit gesetzt + if (process.env.AUTH_MODE !== 'dummy') { + request.user = null; + return true; + } + + const role = (process.env.DEV_FAKE_USER_ROLE as UserRole) ?? 'USER'; + + const fakeUser: RequestUser = { + id: Number(process.env.DEV_FAKE_USER_ID ?? 0), + role, + isFake: process.env.DEV_FAKE_USER === 'true', + }; + + request.user = fakeUser; + return true; + } +} diff --git a/backend/src/auth/types/request-user.type.ts b/backend/src/auth/types/request-user.type.ts new file mode 100644 index 0000000..9fc9dea --- /dev/null +++ b/backend/src/auth/types/request-user.type.ts @@ -0,0 +1,15 @@ +export type UserRole = 'USER' | 'ADMIN' | 'MOD'; + +export interface RequestUser { + /** User-ID aus DB oder Fake-User */ + id: number; + + /** Rollenbasis für Guards & Admin-Features */ + role: UserRole; + + /** Für DEV / Dummy-Auth */ + isFake?: boolean; + + /** Optional, future-proof (z. B. Feed-Optimierung) */ + petIds?: number[]; +} diff --git a/backend/src/types/express.d.ts b/backend/src/types/express.d.ts new file mode 100644 index 0000000..f914372 --- /dev/null +++ b/backend/src/types/express.d.ts @@ -0,0 +1,7 @@ +import { RequestUser } from '../auth/types/request-user.type'; + +declare module 'express' { + interface Request { + user?: RequestUser | null; + } +} diff --git a/docker-compose.db.yml b/docker-compose.db.yml new file mode 100644 index 0000000..5cf0c26 --- /dev/null +++ b/docker-compose.db.yml @@ -0,0 +1,18 @@ +version: "3.9" + +services: + postgres: + image: postgres:16 + container_name: onlypets-postgres + restart: unless-stopped + ports: + - "55432:5432" + environment: + POSTGRES_USER: onlypets_user + POSTGRES_PASSWORD: onlypets_pass + POSTGRES_DB: onlypets + volumes: + - postgres_data:/var/lib/postgresql/data + +volumes: + postgres_data: