feat(auth): add dummy auth foundation with global guard

This commit is contained in:
admin committed 2025-12-26 12:03:00 +01:00
1 parent 9253cac1b2
commit 122dad35ce
12 files changed
+289 -87

No files matched your search

@@ -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;
@@ -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"
+97 -87
View File
@@ -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();
});
});
+15
View File
@@ -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 {}
View File
Whitespace-only changes.
View File
Whitespace-only changes.
Whitespace-only changes.
@@ -0,0 +1,5 @@
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
@@ -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<boolean>(
IS_PUBLIC_KEY,
[context.getHandler(), context.getClass()],
);
const request = context.switchToHttp().getRequest<Request>();
// Ö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;
}
}
@@ -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[];
}
+7
View File
@@ -0,0 +1,7 @@
import { RequestUser } from '../auth/types/request-user.type';
declare module 'express' {
interface Request {
user?: RequestUser | null;
}
}
+18
View File
@@ -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: