diff --git a/BACKEND_FREEZE.md b/BACKEND_FREEZE.md new file mode 100644 index 0000000..d9788eb --- /dev/null +++ b/BACKEND_FREEZE.md @@ -0,0 +1,129 @@ +# 🧊 Backend Freeze – Frontend Ready + +**Projekt:** OnlyPets +**Stand:** Backend eingefroren (Frontend-ready) +**Datum:** 27.12.2025 + +Ziel dieses Dokuments ist es, klar festzuhalten: +> Das Backend ist technisch und strukturell abgeschlossen, +> sodass die weitere Entwicklung primär im Frontend stattfinden kann. + +Spezial-Features werden bewusst **getrennt** und **nachgelagert** umgesetzt. + +--- + +## 🧱 1. Architektur & Infrastruktur + +- NestJS stabil gebootstrapped +- Module sauber getrennt: + - Posts + - Feed + - Prisma + - Auth +- Dependency Injection stabil (keine offenen DI-Fehler) +- Prisma korrekt als Service eingebunden +- Strict TypeScript aktiv (keine `any`-Leaks) +- Ordnerstruktur final (kein späteres Refactoring notwendig) + +**Status:** ✅ abgeschlossen + +--- + +## 🔐 2. Auth-Grundlage (Phase 1) + +- Globaler `DummyAuthGuard` aktiv +- `req.user` immer definiert (`null | FakeUser`) +- Rollenmodell vorbereitet (`USER | MOD | ADMIN`) +- `@Public()` Decorator vorhanden +- Guard-first-Architektur (keine Auth-Logik in Services) + +> Echte Auth (JWT, Login) ist **bewusst nicht Teil** des Freeze-Zustands. + +**Status:** ✅ abgeschlossen (Phase-1-Scope) + +--- + +## 🗃️ 3. Datenmodell & Prisma + +- Prisma Schema stabil +- Keine redundanten oder abgeleiteten Felder + - Alter wird **nicht gespeichert** + - `birthDate` ist Single Source of Truth +- Beziehungen konsistent: + - User ↔ Pet ↔ Post +- Seed reproduzierbar & idempotent +- DB-Zugriff ausschließlich über Services + +**Status:** ✅ abgeschlossen + +--- + +## 📰 4. Feed & Content (Frontend-relevant) + +Aktive Endpoints: + +- `GET /posts` +- `GET /feed` + +Eigenschaften: +- `/feed` ist 1:1 aus `/posts` gespiegelt +- Deterministischer Feed +- Keine Random- oder Discovery-Logik +- Sortierung: `createdAt DESC` +- Inkludierte Relationen: + - `pet` + - `author` + +Smoke-Tests erfolgreich (Invoke-RestMethod). + +**Status:** ✅ abgeschlossen / frontend-ready + +--- + +## 🩺 5. Betrieb & Stabilität + +- `GET /health` Endpoint vorhanden +- Server startet ohne Errors +- Keine offenen TypeScript- oder DI-Probleme +- Lokales DEV-Setup reproduzierbar + +**Status:** ✅ abgeschlossen + +--- + +## 🧩 6. Bewusst ausgeklammerte Spezial-Features + +Diese Themen sind **nicht Teil des Backend-Freeze** und werden separat umgesetzt: + +- Discovery Feed (siehe Issue #243) +- Likes +- Kommentare +- Notifications +- Admin / Moderation +- Echte Auth (JWT, Login) + +> Diese Features sind **entkoppelt geplant**, +> sodass sie nach Live-Frontend **ohne Rewrites** ergänzt werden können. + +--- + +## 🧊 Freeze-Regel + +Ab diesem Punkt gilt für das Backend: + +- ❄️ Keine neuen Features +- ❄️ Nur Bugfixes +- ❄️ Keine API-Breaking-Changes +- ❄️ Fokus liegt auf Frontend-Entwicklung + +--- + +## ✅ Fazit + +Das Backend befindet sich im **frontend-ready Freeze-Zustand**. + +- Die API ist stabil +- Die Architektur ist vorbereitet +- Erweiterungen sind planbar, aber nicht blockierend + +➡️ **Empfohlener nächster Schritt:** Frontend entwickeln. diff --git a/DEV_SETUP.md b/DEV_SETUP.md new file mode 100644 index 0000000..631396c --- /dev/null +++ b/DEV_SETUP.md @@ -0,0 +1,26 @@ +## Lokales Dev-Setup + +Frontend: +- URL: http://localhost:3000 + +Backend: +- URL: http://localhost:3001 + +ENV: +- NEXT_PUBLIC_API_URL=http://localhost:3001 + +Hinweis: +Backend ist frontend-ready eingefroren. +Neue Features nur über separate Issues. + +# 🛠 DEV SETUP – OnlyPets + +## Ports +- Frontend: http://localhost:3000 +- Backend: http://localhost:3001 + +## Frontend (Next.js) +```bash +cd frontend +npm install +npm run dev diff --git a/backend/src/main.ts b/backend/src/main.ts index d8bb014..438b2d9 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -4,7 +4,7 @@ import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); - const port = process.env.PORT || 3000; + const port = process.env.PORT || 3001; await app.listen(port); console.log(`🚀 Backend running on http://localhost:${port}`); diff --git a/frontend/next.config.ts b/frontend/next.config.ts index e9ffa30..fa67b35 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -1,7 +1,9 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + turbopack: { + root: __dirname, + }, }; export default nextConfig; diff --git a/frontend/package.json b/frontend/package.json index bf2990e..0b7b613 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,6 +4,7 @@ "private": true, "scripts": { "dev": "next dev", + "dev": "NODE_OPTIONS=--max_old_space_size=4096 next dev", "build": "next build", "start": "next start", "lint": "eslint" diff --git a/frontend/src/lib/time.ts b/frontend/src/lib/time.ts new file mode 100644 index 0000000..67b775c --- /dev/null +++ b/frontend/src/lib/time.ts @@ -0,0 +1,20 @@ +export function timeAgo(date: string | Date) { + const ts = new Date(date).getTime(); + const diffMs = Date.now() - ts; + + const minutes = Math.floor(diffMs / 60000); + if (minutes < 1) return "gerade eben"; + if (minutes < 60) return `vor ${minutes} Minute${minutes === 1 ? "" : "n"}`; + + const hours = Math.floor(minutes / 60); + if (hours < 24) return `vor ${hours} Stunde${hours === 1 ? "" : "n"}`; + + const days = Math.floor(hours / 24); + if (days < 7) return `vor ${days} Tag${days === 1 ? "" : "en"}`; + + const weeks = Math.floor(days / 7); + if (weeks < 4) return `vor ${weeks} Woche${weeks === 1 ? "" : "n"}`; + + // Fallback: Datum kurz + return new Date(date).toLocaleDateString("de-DE"); +} diff --git a/package-lock.json b/package-lock.json index 3de41d0..41aa2a2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,8 +13,11 @@ "react-dom": "^18.0.0" }, "devDependencies": { + "autoprefixer": "^10.4.23", "eslint": "^8.40.0", "next-auth": "^4.20.0", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.18", "ts-node": "^10.9.2", "typescript": "^5.9.3" } @@ -977,6 +980,43 @@ "dev": true, "license": "Python-2.0" }, + "node_modules/autoprefixer": { + "version": "10.4.23", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", + "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001760", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1004,6 +1044,40 @@ "concat-map": "0.0.1" } }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1174,6 +1248,23 @@ "node": ">=6.0.0" } }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "dev": true, + "license": "ISC" + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -1421,6 +1512,20 @@ "dev": true, "license": "ISC" }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -1852,6 +1957,41 @@ } } }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, "node_modules/oauth": { "version": "0.9.15", "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", @@ -2005,9 +2145,10 @@ "license": "ISC" }, "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, "funding": [ { "type": "opencollective", @@ -2024,14 +2165,21 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, "node_modules/preact": { "version": "10.28.1", "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.1.tgz", @@ -2352,6 +2500,13 @@ "node": ">=8" } }, + "node_modules/tailwindcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", + "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", + "dev": true, + "license": "MIT" + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -2457,6 +2612,37 @@ "license": "MIT", "peer": true }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", diff --git a/package.json b/package.json index e856991..f0fabc8 100644 --- a/package.json +++ b/package.json @@ -14,8 +14,11 @@ "react-dom": "^18.0.0" }, "devDependencies": { + "autoprefixer": "^10.4.23", "eslint": "^8.40.0", "next-auth": "^4.20.0", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.18", "ts-node": "^10.9.2", "typescript": "^5.9.3" } diff --git a/structure.txt b/structure.txt new file mode 100644 index 0000000..a9fd9d4 Binary files /dev/null and b/structure.txt differ