feat: add prisma
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -11,3 +11,5 @@ count.txt
|
|||||||
.output
|
.output
|
||||||
.vinxi
|
.vinxi
|
||||||
todos.json
|
todos.json
|
||||||
|
|
||||||
|
/generated/prisma
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ src/server/
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// src/server/functions/characters.ts
|
// src/server/functions/characters.ts
|
||||||
import { createServerFn } from '@tanstack/react-start/server';
|
import { createServerFn } from "@tanstack/react-start/server";
|
||||||
import { z } from 'zod';
|
import { z } from "zod";
|
||||||
import { db } from '@/lib/server/db';
|
import { db } from "@/lib/server/db";
|
||||||
import { requireAuth } from '@/server/middleware/auth';
|
import { requireAuth } from "@/server/middleware/auth";
|
||||||
|
|
||||||
// Schemas
|
// Schemas
|
||||||
const createCharacterSchema = z.object({
|
const createCharacterSchema = z.object({
|
||||||
@@ -48,7 +48,7 @@ const characterFiltersSchema = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Functions
|
// Functions
|
||||||
export const getCharacters = createServerFn({ method: 'GET' })
|
export const getCharacters = createServerFn({ method: "GET" })
|
||||||
.validator((data: unknown) => characterFiltersSchema.parse(data))
|
.validator((data: unknown) => characterFiltersSchema.parse(data))
|
||||||
.handler(async ({ data }) => {
|
.handler(async ({ data }) => {
|
||||||
const session = await requireAuth();
|
const session = await requireAuth();
|
||||||
@@ -57,7 +57,7 @@ export const getCharacters = createServerFn({ method: 'GET' })
|
|||||||
const where = {
|
const where = {
|
||||||
account: { userId: session.userId },
|
account: { userId: session.userId },
|
||||||
...(search && {
|
...(search && {
|
||||||
name: { contains: search, mode: 'insensitive' as const },
|
name: { contains: search, mode: "insensitive" as const },
|
||||||
}),
|
}),
|
||||||
...(classIds?.length && { classId: { in: classIds } }),
|
...(classIds?.length && { classId: { in: classIds } }),
|
||||||
...(serverIds?.length && { serverId: { in: serverIds } }),
|
...(serverIds?.length && { serverId: { in: serverIds } }),
|
||||||
@@ -70,7 +70,7 @@ export const getCharacters = createServerFn({ method: 'GET' })
|
|||||||
include: { account: { select: { id: true, name: true } } },
|
include: { account: { select: { id: true, name: true } } },
|
||||||
skip: (page - 1) * limit,
|
skip: (page - 1) * limit,
|
||||||
take: limit,
|
take: limit,
|
||||||
orderBy: { name: 'asc' },
|
orderBy: { name: "asc" },
|
||||||
}),
|
}),
|
||||||
db.character.count({ where }),
|
db.character.count({ where }),
|
||||||
]);
|
]);
|
||||||
@@ -86,7 +86,7 @@ export const getCharacters = createServerFn({ method: 'GET' })
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
export const createCharacter = createServerFn({ method: 'POST' })
|
export const createCharacter = createServerFn({ method: "POST" })
|
||||||
.validator((data: unknown) => createCharacterSchema.parse(data))
|
.validator((data: unknown) => createCharacterSchema.parse(data))
|
||||||
.handler(async ({ data }) => {
|
.handler(async ({ data }) => {
|
||||||
const session = await requireAuth();
|
const session = await requireAuth();
|
||||||
@@ -97,14 +97,16 @@ export const createCharacter = createServerFn({ method: 'POST' })
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!account) {
|
if (!account) {
|
||||||
throw new Error('Account not found');
|
throw new Error("Account not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
return db.character.create({ data });
|
return db.character.create({ data });
|
||||||
});
|
});
|
||||||
|
|
||||||
export const bulkDeleteCharacters = createServerFn({ method: 'POST' })
|
export const bulkDeleteCharacters = createServerFn({ method: "POST" })
|
||||||
.validator((data: unknown) => z.object({ ids: z.array(z.string().cuid()) }).parse(data))
|
.validator((data: unknown) =>
|
||||||
|
z.object({ ids: z.array(z.string().cuid()) }).parse(data),
|
||||||
|
)
|
||||||
.handler(async ({ data }) => {
|
.handler(async ({ data }) => {
|
||||||
const session = await requireAuth();
|
const session = await requireAuth();
|
||||||
|
|
||||||
@@ -123,8 +125,8 @@ export const bulkDeleteCharacters = createServerFn({ method: 'POST' })
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// src/server/middleware/auth.ts
|
// src/server/middleware/auth.ts
|
||||||
import { getWebRequest } from '@tanstack/react-start/server';
|
import { getWebRequest } from "@tanstack/react-start/server";
|
||||||
import { db } from '@/lib/server/db';
|
import { db } from "@/lib/server/db";
|
||||||
|
|
||||||
interface Session {
|
interface Session {
|
||||||
userId: string;
|
userId: string;
|
||||||
@@ -133,13 +135,14 @@ interface Session {
|
|||||||
|
|
||||||
export async function requireAuth(): Promise<Session> {
|
export async function requireAuth(): Promise<Session> {
|
||||||
const request = getWebRequest();
|
const request = getWebRequest();
|
||||||
const sessionId = request.headers.get('cookie')
|
const sessionId = request.headers
|
||||||
?.split(';')
|
.get("cookie")
|
||||||
.find(c => c.trim().startsWith('session='))
|
?.split(";")
|
||||||
?.split('=')[1];
|
.find((c) => c.trim().startsWith("session="))
|
||||||
|
?.split("=")[1];
|
||||||
|
|
||||||
if (!sessionId) {
|
if (!sessionId) {
|
||||||
throw new Error('Unauthorized');
|
throw new Error("Unauthorized");
|
||||||
}
|
}
|
||||||
|
|
||||||
const session = await db.session.findUnique({
|
const session = await db.session.findUnique({
|
||||||
@@ -148,7 +151,7 @@ export async function requireAuth(): Promise<Session> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!session || session.expiresAt < new Date()) {
|
if (!session || session.expiresAt < new Date()) {
|
||||||
throw new Error('Session expired');
|
throw new Error("Session expired");
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -170,7 +173,7 @@ export async function getOptionalAuth(): Promise<Session | null> {
|
|||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// src/lib/server/cache.ts
|
// src/lib/server/cache.ts
|
||||||
import NodeCache from 'node-cache';
|
import NodeCache from "node-cache";
|
||||||
|
|
||||||
// Different TTLs for different data types
|
// Different TTLs for different data types
|
||||||
const caches = {
|
const caches = {
|
||||||
@@ -196,7 +199,9 @@ export const userCache = {
|
|||||||
|
|
||||||
// Helper to invalidate all cache for a user
|
// Helper to invalidate all cache for a user
|
||||||
invalidateUser: (userId: string): void => {
|
invalidateUser: (userId: string): void => {
|
||||||
const keys = caches.user.keys().filter(k => k.startsWith(`user:${userId}:`));
|
const keys = caches.user
|
||||||
|
.keys()
|
||||||
|
.filter((k) => k.startsWith(`user:${userId}:`));
|
||||||
caches.user.del(keys);
|
caches.user.del(keys);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,32 +16,33 @@ Draft
|
|||||||
2. Docker Compose configuration with app service and PostgreSQL 16
|
2. Docker Compose configuration with app service and PostgreSQL 16
|
||||||
3. Prisma configured and connected to PostgreSQL
|
3. Prisma configured and connected to PostgreSQL
|
||||||
4. shadcn/ui installed with base components (Button, Input, Card, Table)
|
4. shadcn/ui installed with base components (Button, Input, Card, Table)
|
||||||
5. ESLint + Prettier configured with recommended rules
|
5. Biome configured for linting and formatting
|
||||||
6. GitLab CI pipeline: build, lint, test stages
|
6. Gitea Actions workflow: build, lint, test stages
|
||||||
7. Dockerfile multi-stage pour production build
|
7. Dockerfile multi-stage pour production build
|
||||||
8. README avec instructions de setup local
|
8. README avec instructions de setup local
|
||||||
9. Application démarre et affiche une page d'accueil "Dofus Manager"
|
9. Application démarre et affiche une page d'accueil "Dofus Manager"
|
||||||
|
10. Health check endpoint `/api/health` pour Docker healthcheck
|
||||||
|
|
||||||
## Tasks / Subtasks
|
## Tasks / Subtasks
|
||||||
|
|
||||||
- [ ] Task 1: Initialize TanStack Start project (AC: 1)
|
- [x] Task 1: Initialize TanStack Start project (AC: 1)
|
||||||
- [x] Create new TanStack Start project with `pnpm create @tanstack/start`
|
- [x] Create new TanStack Start project with `pnpm create @tanstack/start`
|
||||||
- [ ] Configure `tsconfig.json` with strict mode enabled
|
- [x] Configure `tsconfig.json` with strict mode enabled
|
||||||
- [ ] Configure path aliases (`@/` pointing to `src/`)
|
- [x] Configure path aliases (`@/` pointing to `src/`)
|
||||||
- [ ] Verify TypeScript strict compilation works
|
- [x] Verify TypeScript strict compilation works
|
||||||
|
|
||||||
- [ ] Task 2: Setup Docker environment (AC: 2, 7)
|
- [x] Task 2: Setup Docker environment (AC: 2, 7)
|
||||||
- [ ] Create `docker/` directory
|
- [x] Create `docker/` directory
|
||||||
- [ ] Create `docker/Dockerfile` with multi-stage build (builder + runner)
|
- [x] Create `docker/Dockerfile` with multi-stage build (builder + runner)
|
||||||
- [ ] Create `docker/docker-compose.yml` with app and postgres services
|
- [x] Create `docker/docker-compose.yml` with app and postgres services
|
||||||
- [ ] Create `docker/docker-compose.dev.yml` for local development (postgres only)
|
- [x] Create `docker/docker-compose.dev.yml` for local development (postgres only)
|
||||||
- [ ] Configure PostgreSQL 16-alpine with healthcheck
|
- [x] Configure PostgreSQL 16-alpine with healthcheck
|
||||||
- [ ] Test database connectivity
|
- [x] Test database connectivity
|
||||||
|
|
||||||
- [ ] Task 3: Configure Prisma ORM (AC: 3)
|
- [ ] Task 3: Configure Prisma ORM (AC: 3)
|
||||||
- [ ] Install Prisma dependencies (`prisma`, `@prisma/client`)
|
- [x] Install Prisma dependencies (`prisma`, `@prisma/client`)
|
||||||
- [ ] Initialize Prisma with `pnpm prisma init`
|
- [x] Initialize Prisma with `pnpm prisma init`
|
||||||
- [ ] Configure `prisma/schema.prisma` with PostgreSQL provider
|
- [x] Configure `prisma/schema.prisma` with PostgreSQL provider
|
||||||
- [ ] Create `src/lib/server/db.ts` for Prisma client singleton
|
- [ ] Create `src/lib/server/db.ts` for Prisma client singleton
|
||||||
- [ ] Create `.env.example` with DATABASE_URL template
|
- [ ] Create `.env.example` with DATABASE_URL template
|
||||||
- [ ] Verify Prisma connects to database
|
- [ ] Verify Prisma connects to database
|
||||||
@@ -60,12 +61,12 @@ Draft
|
|||||||
- [ ] Add lint and format scripts to `package.json`
|
- [ ] Add lint and format scripts to `package.json`
|
||||||
- [ ] Verify linting works on project files
|
- [ ] Verify linting works on project files
|
||||||
|
|
||||||
- [ ] Task 6: Setup GitLab CI/CD pipeline (AC: 6)
|
- [ ] Task 6: Setup Gitea Actions workflow (AC: 6)
|
||||||
- [ ] Create `.gitlab-ci.yml` with stages: test, build, deploy
|
- [ ] Create `.gitea/workflows/ci.yml`
|
||||||
- [ ] Configure test stage: lint, typecheck, test
|
- [ ] Configure test job: lint, typecheck, test
|
||||||
- [ ] Configure build stage: Docker image build and push
|
- [ ] Configure build job: Docker image build and push
|
||||||
- [ ] Configure deploy stages (staging/production) with manual triggers
|
- [ ] Configure deploy jobs (staging/production) with manual triggers
|
||||||
- [ ] Add caching for node_modules
|
- [ ] Add caching for pnpm store
|
||||||
|
|
||||||
- [ ] Task 7: Create README documentation (AC: 8)
|
- [ ] Task 7: Create README documentation (AC: 8)
|
||||||
- [ ] Document project overview
|
- [ ] Document project overview
|
||||||
@@ -81,7 +82,13 @@ Draft
|
|||||||
- [ ] Create `src/styles/globals.css` with Tailwind imports
|
- [ ] Create `src/styles/globals.css` with Tailwind imports
|
||||||
- [ ] Verify application starts and renders correctly
|
- [ ] Verify application starts and renders correctly
|
||||||
|
|
||||||
- [ ] Task 9: Final verification
|
- [ ] Task 9: Create health check endpoint (AC: 10)
|
||||||
|
- [ ] Create `src/routes/api/health.ts` server function
|
||||||
|
- [ ] Return JSON `{ status: "ok", timestamp: Date }`
|
||||||
|
- [ ] Optionally check database connectivity
|
||||||
|
- [ ] Verify endpoint responds at `GET /api/health`
|
||||||
|
|
||||||
|
- [ ] Task 10: Final verification
|
||||||
- [ ] Run `pnpm dev` and verify app starts
|
- [ ] Run `pnpm dev` and verify app starts
|
||||||
- [ ] Run `pnpm lint` and verify no errors
|
- [ ] Run `pnpm lint` and verify no errors
|
||||||
- [ ] Run `pnpm typecheck` and verify no errors
|
- [ ] Run `pnpm typecheck` and verify no errors
|
||||||
@@ -118,7 +125,7 @@ Draft
|
|||||||
|
|
||||||
- Docker
|
- Docker
|
||||||
- Docker Compose
|
- Docker Compose
|
||||||
- GitLab CI
|
- Gitea Actions
|
||||||
|
|
||||||
**Dev Tools:**
|
**Dev Tools:**
|
||||||
|
|
||||||
@@ -131,9 +138,13 @@ Draft
|
|||||||
|
|
||||||
```
|
```
|
||||||
dofus-manager/
|
dofus-manager/
|
||||||
|
├── .gitea/
|
||||||
|
│ └── workflows/
|
||||||
|
│ └── ci.yml
|
||||||
├── docker/
|
├── docker/
|
||||||
│ ├── Dockerfile
|
│ ├── Dockerfile
|
||||||
│ └── docker-compose.yml
|
│ ├── docker-compose.yml
|
||||||
|
│ └── docker-compose.dev.yml
|
||||||
├── prisma/
|
├── prisma/
|
||||||
│ ├── schema.prisma
|
│ ├── schema.prisma
|
||||||
│ └── migrations/
|
│ └── migrations/
|
||||||
@@ -156,7 +167,9 @@ dofus-manager/
|
|||||||
│ │ └── logger.ts
|
│ │ └── logger.ts
|
||||||
│ ├── routes/
|
│ ├── routes/
|
||||||
│ │ ├── __root.tsx
|
│ │ ├── __root.tsx
|
||||||
│ │ └── index.tsx
|
│ │ ├── index.tsx
|
||||||
|
│ │ └── api/
|
||||||
|
│ │ └── health.ts
|
||||||
│ ├── styles/
|
│ ├── styles/
|
||||||
│ │ └── globals.css
|
│ │ └── globals.css
|
||||||
│ └── app.tsx
|
│ └── app.tsx
|
||||||
@@ -187,22 +200,66 @@ dofus-manager/
|
|||||||
- PostgreSQL 16-alpine with healthcheck
|
- PostgreSQL 16-alpine with healthcheck
|
||||||
- Traefik labels for reverse proxy (production)
|
- Traefik labels for reverse proxy (production)
|
||||||
|
|
||||||
### GitLab CI/CD [Source: architecture/14-deployment-architecture.md#gitlab-cicd-pipeline]
|
### Gitea Actions [Adapted from architecture]
|
||||||
|
|
||||||
**Stages:** test, build, deploy
|
**Workflow file:** `.gitea/workflows/ci.yml`
|
||||||
|
|
||||||
**Test stage:**
|
**Jobs:** test, build, deploy
|
||||||
|
|
||||||
- image: node:20-alpine
|
**Test job:**
|
||||||
- Commands: pnpm lint, pnpm typecheck, pnpm test
|
|
||||||
- Cache node_modules
|
|
||||||
|
|
||||||
**Build stage:**
|
- runs-on: ubuntu-latest
|
||||||
|
- Steps: checkout, setup pnpm, setup node, install, lint, typecheck, test
|
||||||
|
- Cache pnpm store
|
||||||
|
|
||||||
- image: docker:24
|
**Build job:**
|
||||||
- Build and push Docker image to registry
|
|
||||||
|
- needs: test
|
||||||
|
- Build and push Docker image
|
||||||
- Only on main/develop branches
|
- Only on main/develop branches
|
||||||
|
|
||||||
|
**Exemple de workflow:**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: CI
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main, develop]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: pnpm/action-setup@v2
|
||||||
|
with:
|
||||||
|
version: 9
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
|
cache: "pnpm"
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
- run: pnpm lint
|
||||||
|
- run: pnpm typecheck
|
||||||
|
- run: pnpm test
|
||||||
|
|
||||||
|
build:
|
||||||
|
needs: test
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop'
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: docker/setup-buildx-action@v3
|
||||||
|
- uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: docker/Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: registry.example.com/dofus-manager:${{ github.sha }}
|
||||||
|
```
|
||||||
|
|
||||||
### Environment Variables [Source: architecture/13-development-workflow.md#environment-variables]
|
### Environment Variables [Source: architecture/13-development-workflow.md#environment-variables]
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -234,9 +291,45 @@ SESSION_SECRET="your-secret-key-min-32-chars"
|
|||||||
- Types/Interfaces: PascalCase
|
- Types/Interfaces: PascalCase
|
||||||
- Constants: SCREAMING_SNAKE_CASE
|
- Constants: SCREAMING_SNAKE_CASE
|
||||||
|
|
||||||
### Important Discrepancy Note
|
### Health Check Endpoint [Source: architecture/19-monitoring-observability.md]
|
||||||
|
|
||||||
AC #5 specifies "ESLint + Prettier" but the architecture documents (3-technology-stack.md) specify **Biome** for linting and formatting. Recommend following the architecture document and using Biome instead, as it's the project standard.
|
**Endpoint:** `GET /api/health`
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"timestamp": "2026-01-19T10:00:00.000Z",
|
||||||
|
"database": "connected"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Implementation avec TanStack Start:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// src/routes/api/health.ts
|
||||||
|
import { createAPIFileRoute } from "@tanstack/start/api";
|
||||||
|
import { prisma } from "@/lib/server/db";
|
||||||
|
|
||||||
|
export const Route = createAPIFileRoute("/api/health")({
|
||||||
|
GET: async () => {
|
||||||
|
let dbStatus = "disconnected";
|
||||||
|
try {
|
||||||
|
await prisma.$queryRaw`SELECT 1`;
|
||||||
|
dbStatus = "connected";
|
||||||
|
} catch {
|
||||||
|
dbStatus = "error";
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.json({
|
||||||
|
status: "ok",
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
database: dbStatus,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
@@ -264,9 +357,10 @@ AC #5 specifies "ESLint + Prettier" but the architecture documents (3-technology
|
|||||||
|
|
||||||
## Change Log
|
## Change Log
|
||||||
|
|
||||||
| Date | Version | Description | Author |
|
| Date | Version | Description | Author |
|
||||||
| ---------- | ------- | ---------------------- | -------- |
|
| ---------- | ------- | --------------------------------------------- | -------- |
|
||||||
| 2026-01-19 | 1.0 | Initial story creation | SM Agent |
|
| 2026-01-19 | 1.0 | Initial story creation | SM Agent |
|
||||||
|
| 2026-01-19 | 1.1 | Gitea Actions, Biome, Health endpoint ajoutés | SM Agent |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"test": "vitest run"
|
"test": "vitest run"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@prisma/client": "^7.2.0",
|
||||||
"@tanstack/react-devtools": "^0.7.0",
|
"@tanstack/react-devtools": "^0.7.0",
|
||||||
"@tanstack/react-router": "^1.132.0",
|
"@tanstack/react-router": "^1.132.0",
|
||||||
"@tanstack/react-router-devtools": "^1.132.0",
|
"@tanstack/react-router-devtools": "^1.132.0",
|
||||||
@@ -30,6 +31,7 @@
|
|||||||
"@types/react-dom": "^19.2.0",
|
"@types/react-dom": "^19.2.0",
|
||||||
"@vitejs/plugin-react": "^5.0.4",
|
"@vitejs/plugin-react": "^5.0.4",
|
||||||
"jsdom": "^27.0.0",
|
"jsdom": "^27.0.0",
|
||||||
|
"prisma": "^7.2.0",
|
||||||
"typescript": "^5.7.2",
|
"typescript": "^5.7.2",
|
||||||
"vite": "^7.1.7",
|
"vite": "^7.1.7",
|
||||||
"vitest": "^3.0.5",
|
"vitest": "^3.0.5",
|
||||||
|
|||||||
666
pnpm-lock.yaml
generated
666
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
14
prisma.config.ts
Normal file
14
prisma.config.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// This file was generated by Prisma, and assumes you have installed the following:
|
||||||
|
// npm install --save-dev prisma dotenv
|
||||||
|
import "dotenv/config";
|
||||||
|
import { defineConfig } from "prisma/config";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
schema: "prisma/schema.prisma",
|
||||||
|
migrations: {
|
||||||
|
path: "prisma/migrations",
|
||||||
|
},
|
||||||
|
datasource: {
|
||||||
|
url: process.env["DATABASE_URL"],
|
||||||
|
},
|
||||||
|
});
|
||||||
165
prisma/schema.prisma
Normal file
165
prisma/schema.prisma
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// USER & AUTHENTICATION
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
model User {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
email String @unique
|
||||||
|
passwordHash String @map("password_hash")
|
||||||
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
|
updatedAt DateTime @updatedAt @map("updated_at")
|
||||||
|
|
||||||
|
accounts Account[]
|
||||||
|
teams Team[]
|
||||||
|
sessions Session[]
|
||||||
|
|
||||||
|
@@map("users")
|
||||||
|
}
|
||||||
|
|
||||||
|
model Session {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
userId String @map("user_id")
|
||||||
|
expiresAt DateTime @map("expires_at")
|
||||||
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
|
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@index([userId])
|
||||||
|
@@index([expiresAt])
|
||||||
|
@@map("sessions")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// CORE ENTITIES
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
model Account {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
userId String @map("user_id")
|
||||||
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
|
updatedAt DateTime @updatedAt @map("updated_at")
|
||||||
|
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
characters Character[]
|
||||||
|
|
||||||
|
@@unique([userId, name])
|
||||||
|
@@index([userId])
|
||||||
|
@@map("accounts")
|
||||||
|
}
|
||||||
|
|
||||||
|
model Character {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
level Int @default(1)
|
||||||
|
classId Int @map("class_id")
|
||||||
|
className String @map("class_name")
|
||||||
|
serverId Int @map("server_id")
|
||||||
|
serverName String @map("server_name")
|
||||||
|
accountId String @map("account_id")
|
||||||
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
|
updatedAt DateTime @updatedAt @map("updated_at")
|
||||||
|
|
||||||
|
account Account @relation(fields: [accountId], references: [id], onDelete: Cascade)
|
||||||
|
teamMembers TeamMember[]
|
||||||
|
progressions CharacterProgression[]
|
||||||
|
|
||||||
|
@@unique([accountId, name])
|
||||||
|
@@index([accountId])
|
||||||
|
@@index([classId])
|
||||||
|
@@index([serverId])
|
||||||
|
@@index([level])
|
||||||
|
@@map("characters")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// TEAMS
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
enum TeamType {
|
||||||
|
MAIN
|
||||||
|
SECONDARY
|
||||||
|
CUSTOM
|
||||||
|
}
|
||||||
|
|
||||||
|
model Team {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
type TeamType @default(CUSTOM)
|
||||||
|
userId String @map("user_id")
|
||||||
|
createdAt DateTime @default(now()) @map("created_at")
|
||||||
|
updatedAt DateTime @updatedAt @map("updated_at")
|
||||||
|
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
members TeamMember[]
|
||||||
|
|
||||||
|
@@unique([userId, name])
|
||||||
|
@@index([userId])
|
||||||
|
@@map("teams")
|
||||||
|
}
|
||||||
|
|
||||||
|
model TeamMember {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
teamId String @map("team_id")
|
||||||
|
characterId String @map("character_id")
|
||||||
|
joinedAt DateTime @default(now()) @map("joined_at")
|
||||||
|
|
||||||
|
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
||||||
|
character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@unique([teamId, characterId])
|
||||||
|
@@index([teamId])
|
||||||
|
@@index([characterId])
|
||||||
|
@@map("team_members")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// PROGRESSIONS
|
||||||
|
// ============================================
|
||||||
|
|
||||||
|
enum ProgressionType {
|
||||||
|
QUEST
|
||||||
|
DUNGEON
|
||||||
|
ACHIEVEMENT
|
||||||
|
DOFUS
|
||||||
|
}
|
||||||
|
|
||||||
|
model Progression {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
type ProgressionType
|
||||||
|
category String
|
||||||
|
dofusDbId Int? @unique @map("dofusdb_id")
|
||||||
|
|
||||||
|
characterProgressions CharacterProgression[]
|
||||||
|
|
||||||
|
@@index([type])
|
||||||
|
@@index([category])
|
||||||
|
@@map("progressions")
|
||||||
|
}
|
||||||
|
|
||||||
|
model CharacterProgression {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
characterId String @map("character_id")
|
||||||
|
progressionId String @map("progression_id")
|
||||||
|
completed Boolean @default(false)
|
||||||
|
completedAt DateTime? @map("completed_at")
|
||||||
|
|
||||||
|
character Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
|
||||||
|
progression Progression @relation(fields: [progressionId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
@@unique([characterId, progressionId])
|
||||||
|
@@index([characterId])
|
||||||
|
@@index([progressionId])
|
||||||
|
@@index([completed])
|
||||||
|
@@map("character_progressions")
|
||||||
|
}
|
||||||
0
src/lib/server/db.ts
Normal file
0
src/lib/server/db.ts
Normal file
Reference in New Issue
Block a user