initial commit
This commit is contained in:
35
docs/architecture/1-introduction.md
Normal file
35
docs/architecture/1-introduction.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# 1. Introduction
|
||||
|
||||
## Project Overview
|
||||
|
||||
**Dofus Manager** est une application web personnelle conçue pour gérer efficacement plus de 60 personnages du MMORPG Dofus, répartis sur plusieurs comptes. L'application centralise le suivi des progressions (quêtes, donjons, succès) et permet une gestion optimisée via des équipes.
|
||||
|
||||
## Goals
|
||||
|
||||
- **Centralisation** : Un point unique pour gérer tous les personnages et comptes
|
||||
- **Suivi des progressions** : Tracker l'avancement des quêtes, donjons et succès
|
||||
- **Gestion par équipes** : Organiser les personnages en équipes pour faciliter le suivi
|
||||
- **Efficacité** : Interface rapide avec opérations bulk et filtres avancés
|
||||
|
||||
## Scope
|
||||
|
||||
| In Scope | Out of Scope |
|
||||
|----------|--------------|
|
||||
| Gestion des personnages (CRUD) | Intégration directe avec le jeu |
|
||||
| Gestion des comptes | Multi-utilisateurs |
|
||||
| Suivi des progressions | Application mobile native |
|
||||
| Gestion des équipes | Synchronisation automatique |
|
||||
| Import données DofusDB | Fonctionnalités sociales |
|
||||
| Interface responsive | Mode hors-ligne |
|
||||
|
||||
## Non-Functional Requirements
|
||||
|
||||
| Requirement | Target |
|
||||
|-------------|--------|
|
||||
| Performance | < 200ms pour les opérations courantes |
|
||||
| Disponibilité | 99% uptime (usage personnel) |
|
||||
| Sécurité | Authentification requise, données protégées |
|
||||
| Scalabilité | Support de 100+ personnages |
|
||||
| Maintenabilité | Code typé, tests automatisés |
|
||||
|
||||
---
|
||||
187
docs/architecture/10-frontend-architecture.md
Normal file
187
docs/architecture/10-frontend-architecture.md
Normal file
@@ -0,0 +1,187 @@
|
||||
# 10. Frontend Architecture
|
||||
|
||||
## State Management Strategy
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ STATE MANAGEMENT │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ TanStack Query │ │
|
||||
│ │ (Server State / Cache) │ │
|
||||
│ │ │ │
|
||||
│ │ • Characters list │ │
|
||||
│ │ • Accounts data │ │
|
||||
│ │ • Teams & members │ │
|
||||
│ │ • Progressions │ │
|
||||
│ │ • DofusDB reference data │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ Zustand │ │
|
||||
│ │ (Client/UI State) │ │
|
||||
│ │ │ │
|
||||
│ │ • Selected items (multi-select) │ │
|
||||
│ │ • UI preferences (sidebar collapsed, etc.) │ │
|
||||
│ │ • Filter states │ │
|
||||
│ │ • Modal open/close states │ │
|
||||
│ │ • Theme preference │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────┐ │
|
||||
│ │ React State │ │
|
||||
│ │ (Component-local State) │ │
|
||||
│ │ │ │
|
||||
│ │ • Form inputs │ │
|
||||
│ │ • Hover/focus states │ │
|
||||
│ │ • Animation states │ │
|
||||
│ │ • Temporary UI states │ │
|
||||
│ └─────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## TanStack Query Setup
|
||||
|
||||
```typescript
|
||||
// src/lib/client/query-client.ts
|
||||
import { QueryClient } from '@tanstack/react-query';
|
||||
|
||||
export const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 1000 * 60 * 5, // 5 minutes
|
||||
gcTime: 1000 * 60 * 30, // 30 minutes
|
||||
retry: 1,
|
||||
refetchOnWindowFocus: false,
|
||||
},
|
||||
mutations: {
|
||||
onError: (error) => {
|
||||
console.error('Mutation error:', error);
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Query keys factory
|
||||
export const queryKeys = {
|
||||
characters: {
|
||||
all: ['characters'] as const,
|
||||
list: (filters: CharacterFilters) => [...queryKeys.characters.all, 'list', filters] as const,
|
||||
detail: (id: string) => [...queryKeys.characters.all, 'detail', id] as const,
|
||||
},
|
||||
accounts: {
|
||||
all: ['accounts'] as const,
|
||||
list: () => [...queryKeys.accounts.all, 'list'] as const,
|
||||
detail: (id: string) => [...queryKeys.accounts.all, 'detail', id] as const,
|
||||
},
|
||||
teams: {
|
||||
all: ['teams'] as const,
|
||||
list: () => [...queryKeys.teams.all, 'list'] as const,
|
||||
detail: (id: string) => [...queryKeys.teams.all, 'detail', id] as const,
|
||||
},
|
||||
progressions: {
|
||||
all: ['progressions'] as const,
|
||||
list: (type?: ProgressionType) => [...queryKeys.progressions.all, 'list', type] as const,
|
||||
byCharacter: (characterId: string) => [...queryKeys.progressions.all, 'character', characterId] as const,
|
||||
},
|
||||
} as const;
|
||||
```
|
||||
|
||||
## Zustand Store
|
||||
|
||||
```typescript
|
||||
// src/lib/client/stores/ui-store.ts
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
interface UIState {
|
||||
// Sidebar
|
||||
sidebarCollapsed: boolean;
|
||||
toggleSidebar: () => void;
|
||||
|
||||
// Theme
|
||||
theme: 'light' | 'dark';
|
||||
setTheme: (theme: 'light' | 'dark') => void;
|
||||
|
||||
// Selection (for bulk actions)
|
||||
selectedCharacterIds: string[];
|
||||
selectCharacter: (id: string) => void;
|
||||
deselectCharacter: (id: string) => void;
|
||||
selectAllCharacters: (ids: string[]) => void;
|
||||
clearSelection: () => void;
|
||||
}
|
||||
|
||||
export const useUIStore = create<UIState>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
// Sidebar
|
||||
sidebarCollapsed: false,
|
||||
toggleSidebar: () => set((state) => ({ sidebarCollapsed: !state.sidebarCollapsed })),
|
||||
|
||||
// Theme
|
||||
theme: 'dark',
|
||||
setTheme: (theme) => set({ theme }),
|
||||
|
||||
// Selection
|
||||
selectedCharacterIds: [],
|
||||
selectCharacter: (id) =>
|
||||
set((state) => ({
|
||||
selectedCharacterIds: [...state.selectedCharacterIds, id]
|
||||
})),
|
||||
deselectCharacter: (id) =>
|
||||
set((state) => ({
|
||||
selectedCharacterIds: state.selectedCharacterIds.filter((i) => i !== id)
|
||||
})),
|
||||
selectAllCharacters: (ids) => set({ selectedCharacterIds: ids }),
|
||||
clearSelection: () => set({ selectedCharacterIds: [] }),
|
||||
}),
|
||||
{
|
||||
name: 'dofus-manager-ui',
|
||||
partialize: (state) => ({
|
||||
sidebarCollapsed: state.sidebarCollapsed,
|
||||
theme: state.theme,
|
||||
}),
|
||||
}
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
## Routing Structure
|
||||
|
||||
```typescript
|
||||
// src/routes/__root.tsx
|
||||
import { createRootRoute, Outlet } from '@tanstack/react-router';
|
||||
import { AppShell } from '@/components/layout/app-shell';
|
||||
|
||||
export const Route = createRootRoute({
|
||||
component: () => (
|
||||
<AppShell>
|
||||
<Outlet />
|
||||
</AppShell>
|
||||
),
|
||||
});
|
||||
|
||||
// Route tree
|
||||
/*
|
||||
src/routes/
|
||||
├── __root.tsx # Root layout with AppShell
|
||||
├── index.tsx # / → Dashboard
|
||||
├── characters/
|
||||
│ ├── index.tsx # /characters → List
|
||||
│ └── $id.tsx # /characters/:id → Detail
|
||||
├── accounts/
|
||||
│ ├── index.tsx # /accounts → List
|
||||
│ └── $id.tsx # /accounts/:id → Detail
|
||||
├── teams/
|
||||
│ ├── index.tsx # /teams → List
|
||||
│ └── $id.tsx # /teams/:id → Detail
|
||||
├── progressions/
|
||||
│ └── index.tsx # /progressions → Tracker
|
||||
└── settings/
|
||||
└── index.tsx # /settings → Settings
|
||||
*/
|
||||
```
|
||||
|
||||
---
|
||||
205
docs/architecture/11-backend-architecture.md
Normal file
205
docs/architecture/11-backend-architecture.md
Normal file
@@ -0,0 +1,205 @@
|
||||
# 11. Backend Architecture
|
||||
|
||||
## Server Functions Organization
|
||||
|
||||
```
|
||||
src/server/
|
||||
├── functions/
|
||||
│ ├── auth.ts # Authentication functions
|
||||
│ ├── characters.ts # Character CRUD
|
||||
│ ├── accounts.ts # Account CRUD
|
||||
│ ├── teams.ts # Team management
|
||||
│ ├── progressions.ts # Progression tracking
|
||||
│ └── dofusdb.ts # DofusDB sync
|
||||
│
|
||||
├── middleware/
|
||||
│ └── auth.ts # Authentication middleware
|
||||
│
|
||||
└── index.ts # Export all functions
|
||||
```
|
||||
|
||||
## Server Function Example
|
||||
|
||||
```typescript
|
||||
// src/server/functions/characters.ts
|
||||
import { createServerFn } from '@tanstack/react-start/server';
|
||||
import { z } from 'zod';
|
||||
import { db } from '@/lib/server/db';
|
||||
import { requireAuth } from '@/server/middleware/auth';
|
||||
|
||||
// Schemas
|
||||
const createCharacterSchema = z.object({
|
||||
name: z.string().min(2).max(50),
|
||||
level: z.number().int().min(1).max(200),
|
||||
classId: z.number().int().positive(),
|
||||
className: z.string(),
|
||||
serverId: z.number().int().positive(),
|
||||
serverName: z.string(),
|
||||
accountId: z.string().cuid(),
|
||||
});
|
||||
|
||||
const characterFiltersSchema = z.object({
|
||||
search: z.string().optional(),
|
||||
classIds: z.array(z.number()).optional(),
|
||||
serverIds: z.array(z.number()).optional(),
|
||||
accountIds: z.array(z.string()).optional(),
|
||||
page: z.number().int().positive().default(1),
|
||||
limit: z.number().int().min(1).max(100).default(20),
|
||||
});
|
||||
|
||||
// Functions
|
||||
export const getCharacters = createServerFn({ method: 'GET' })
|
||||
.validator((data: unknown) => characterFiltersSchema.parse(data))
|
||||
.handler(async ({ data }) => {
|
||||
const session = await requireAuth();
|
||||
const { search, classIds, serverIds, accountIds, page, limit } = data;
|
||||
|
||||
const where = {
|
||||
account: { userId: session.userId },
|
||||
...(search && {
|
||||
name: { contains: search, mode: 'insensitive' as const },
|
||||
}),
|
||||
...(classIds?.length && { classId: { in: classIds } }),
|
||||
...(serverIds?.length && { serverId: { in: serverIds } }),
|
||||
...(accountIds?.length && { accountId: { in: accountIds } }),
|
||||
};
|
||||
|
||||
const [characters, total] = await Promise.all([
|
||||
db.character.findMany({
|
||||
where,
|
||||
include: { account: { select: { id: true, name: true } } },
|
||||
skip: (page - 1) * limit,
|
||||
take: limit,
|
||||
orderBy: { name: 'asc' },
|
||||
}),
|
||||
db.character.count({ where }),
|
||||
]);
|
||||
|
||||
return {
|
||||
characters,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const createCharacter = createServerFn({ method: 'POST' })
|
||||
.validator((data: unknown) => createCharacterSchema.parse(data))
|
||||
.handler(async ({ data }) => {
|
||||
const session = await requireAuth();
|
||||
|
||||
// Verify account belongs to user
|
||||
const account = await db.account.findFirst({
|
||||
where: { id: data.accountId, userId: session.userId },
|
||||
});
|
||||
|
||||
if (!account) {
|
||||
throw new Error('Account not found');
|
||||
}
|
||||
|
||||
return db.character.create({ data });
|
||||
});
|
||||
|
||||
export const bulkDeleteCharacters = createServerFn({ method: 'POST' })
|
||||
.validator((data: unknown) => z.object({ ids: z.array(z.string().cuid()) }).parse(data))
|
||||
.handler(async ({ data }) => {
|
||||
const session = await requireAuth();
|
||||
|
||||
const result = await db.character.deleteMany({
|
||||
where: {
|
||||
id: { in: data.ids },
|
||||
account: { userId: session.userId },
|
||||
},
|
||||
});
|
||||
|
||||
return { deleted: result.count };
|
||||
});
|
||||
```
|
||||
|
||||
## Authentication Middleware
|
||||
|
||||
```typescript
|
||||
// src/server/middleware/auth.ts
|
||||
import { getWebRequest } from '@tanstack/react-start/server';
|
||||
import { db } from '@/lib/server/db';
|
||||
|
||||
interface Session {
|
||||
userId: string;
|
||||
sessionId: string;
|
||||
}
|
||||
|
||||
export async function requireAuth(): Promise<Session> {
|
||||
const request = getWebRequest();
|
||||
const sessionId = request.headers.get('cookie')
|
||||
?.split(';')
|
||||
.find(c => c.trim().startsWith('session='))
|
||||
?.split('=')[1];
|
||||
|
||||
if (!sessionId) {
|
||||
throw new Error('Unauthorized');
|
||||
}
|
||||
|
||||
const session = await db.session.findUnique({
|
||||
where: { id: sessionId },
|
||||
include: { user: true },
|
||||
});
|
||||
|
||||
if (!session || session.expiresAt < new Date()) {
|
||||
throw new Error('Session expired');
|
||||
}
|
||||
|
||||
return {
|
||||
userId: session.userId,
|
||||
sessionId: session.id,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getOptionalAuth(): Promise<Session | null> {
|
||||
try {
|
||||
return await requireAuth();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Caching Strategy
|
||||
|
||||
```typescript
|
||||
// src/lib/server/cache.ts
|
||||
import NodeCache from 'node-cache';
|
||||
|
||||
// Different TTLs for different data types
|
||||
const caches = {
|
||||
// Reference data (rarely changes)
|
||||
reference: new NodeCache({ stdTTL: 3600, checkperiod: 600 }), // 1 hour
|
||||
|
||||
// User data (changes more frequently)
|
||||
user: new NodeCache({ stdTTL: 300, checkperiod: 60 }), // 5 minutes
|
||||
};
|
||||
|
||||
export const referenceCache = {
|
||||
get: <T>(key: string): T | undefined => caches.reference.get(key),
|
||||
set: <T>(key: string, value: T): boolean => caches.reference.set(key, value),
|
||||
del: (key: string): number => caches.reference.del(key),
|
||||
flush: (): void => caches.reference.flushAll(),
|
||||
};
|
||||
|
||||
export const userCache = {
|
||||
get: <T>(key: string): T | undefined => caches.user.get(key),
|
||||
set: <T>(key: string, value: T): boolean => caches.user.set(key, value),
|
||||
del: (key: string | string[]): number => caches.user.del(key),
|
||||
flush: (): void => caches.user.flushAll(),
|
||||
|
||||
// Helper to invalidate all cache for a user
|
||||
invalidateUser: (userId: string): void => {
|
||||
const keys = caches.user.keys().filter(k => k.startsWith(`user:${userId}:`));
|
||||
caches.user.del(keys);
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
93
docs/architecture/12-project-structure.md
Normal file
93
docs/architecture/12-project-structure.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# 12. Project Structure
|
||||
|
||||
```
|
||||
dofus-manager/
|
||||
├── .github/
|
||||
│ └── workflows/
|
||||
│ └── ci.yml
|
||||
│
|
||||
├── docker/
|
||||
│ ├── Dockerfile
|
||||
│ └── docker-compose.yml
|
||||
│
|
||||
├── docs/
|
||||
│ ├── prd.md
|
||||
│ ├── front-end-spec.md
|
||||
│ └── architecture.md
|
||||
│
|
||||
├── prisma/
|
||||
│ ├── schema.prisma
|
||||
│ └── migrations/
|
||||
│
|
||||
├── public/
|
||||
│ └── favicon.ico
|
||||
│
|
||||
├── src/
|
||||
│ ├── components/
|
||||
│ │ ├── ui/ # shadcn/ui components
|
||||
│ │ ├── layout/ # Layout components
|
||||
│ │ ├── characters/ # Character feature
|
||||
│ │ ├── accounts/ # Account feature
|
||||
│ │ ├── teams/ # Team feature
|
||||
│ │ ├── progressions/ # Progression feature
|
||||
│ │ └── shared/ # Shared components
|
||||
│ │
|
||||
│ ├── lib/
|
||||
│ │ ├── utils.ts # Utility functions (cn, etc.)
|
||||
│ │ ├── errors.ts # Error types
|
||||
│ │ ├── schemas/ # Zod schemas
|
||||
│ │ │ ├── character.ts
|
||||
│ │ │ ├── account.ts
|
||||
│ │ │ ├── team.ts
|
||||
│ │ │ └── progression.ts
|
||||
│ │ ├── client/ # Client-only code
|
||||
│ │ │ ├── query-client.ts
|
||||
│ │ │ └── stores/
|
||||
│ │ │ └── ui-store.ts
|
||||
│ │ └── server/ # Server-only code
|
||||
│ │ ├── db.ts
|
||||
│ │ ├── cache.ts
|
||||
│ │ ├── logger.ts
|
||||
│ │ └── dofusdb.ts
|
||||
│ │
|
||||
│ ├── routes/
|
||||
│ │ ├── __root.tsx
|
||||
│ │ ├── index.tsx
|
||||
│ │ ├── characters/
|
||||
│ │ ├── accounts/
|
||||
│ │ ├── teams/
|
||||
│ │ ├── progressions/
|
||||
│ │ └── settings/
|
||||
│ │
|
||||
│ ├── server/
|
||||
│ │ ├── functions/
|
||||
│ │ │ ├── auth.ts
|
||||
│ │ │ ├── characters.ts
|
||||
│ │ │ ├── accounts.ts
|
||||
│ │ │ ├── teams.ts
|
||||
│ │ │ ├── progressions.ts
|
||||
│ │ │ └── dofusdb.ts
|
||||
│ │ ├── middleware/
|
||||
│ │ │ └── auth.ts
|
||||
│ │ └── index.ts
|
||||
│ │
|
||||
│ ├── styles/
|
||||
│ │ └── globals.css
|
||||
│ │
|
||||
│ └── app.tsx # App entry point
|
||||
│
|
||||
├── tests/
|
||||
│ ├── unit/
|
||||
│ ├── integration/
|
||||
│ └── e2e/
|
||||
│
|
||||
├── .env.example
|
||||
├── .gitignore
|
||||
├── biome.json
|
||||
├── package.json
|
||||
├── tsconfig.json
|
||||
├── app.config.ts # TanStack Start config
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
83
docs/architecture/13-development-workflow.md
Normal file
83
docs/architecture/13-development-workflow.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# 13. Development Workflow
|
||||
|
||||
## Local Development Setup
|
||||
|
||||
```bash
|
||||
# 1. Clone repository
|
||||
git clone <repo-url>
|
||||
cd dofus-manager
|
||||
|
||||
# 2. Install dependencies
|
||||
pnpm install
|
||||
|
||||
# 3. Setup environment
|
||||
cp .env.example .env
|
||||
# Edit .env with your values
|
||||
|
||||
# 4. Start database
|
||||
docker compose up -d postgres
|
||||
|
||||
# 5. Run migrations
|
||||
pnpm prisma migrate dev
|
||||
|
||||
# 6. Seed database (optional)
|
||||
pnpm prisma db seed
|
||||
|
||||
# 7. Start development server
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
```bash
|
||||
# .env.example
|
||||
|
||||
# Database
|
||||
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/dofus_manager?schema=public"
|
||||
|
||||
# App
|
||||
APP_URL="http://localhost:3000"
|
||||
NODE_ENV="development"
|
||||
|
||||
# Session
|
||||
SESSION_SECRET="your-secret-key-min-32-chars"
|
||||
|
||||
# Optional: DofusDB
|
||||
DOFUSDB_CACHE_TTL="3600"
|
||||
```
|
||||
|
||||
## Git Workflow
|
||||
|
||||
```
|
||||
main (production)
|
||||
│
|
||||
└── develop (staging)
|
||||
│
|
||||
├── feature/add-character-filters
|
||||
├── feature/team-management
|
||||
└── fix/progression-update-bug
|
||||
```
|
||||
|
||||
## Branch Naming
|
||||
|
||||
- `feature/*` - New features
|
||||
- `fix/*` - Bug fixes
|
||||
- `refactor/*` - Code refactoring
|
||||
- `docs/*` - Documentation updates
|
||||
- `chore/*` - Maintenance tasks
|
||||
|
||||
## Commit Convention
|
||||
|
||||
```
|
||||
<type>(<scope>): <description>
|
||||
|
||||
Types: feat, fix, docs, style, refactor, test, chore
|
||||
Scope: characters, accounts, teams, progressions, auth, ui
|
||||
|
||||
Examples:
|
||||
feat(characters): add bulk delete functionality
|
||||
fix(progressions): correct date formatting
|
||||
docs(readme): update setup instructions
|
||||
```
|
||||
|
||||
---
|
||||
215
docs/architecture/14-deployment-architecture.md
Normal file
215
docs/architecture/14-deployment-architecture.md
Normal file
@@ -0,0 +1,215 @@
|
||||
# 14. Deployment Architecture
|
||||
|
||||
## Docker Configuration
|
||||
|
||||
```dockerfile
|
||||
# docker/Dockerfile
|
||||
|
||||
# Build stage
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install pnpm
|
||||
RUN corepack enable && corepack prepare pnpm@latest --activate
|
||||
|
||||
# Copy package files
|
||||
COPY package.json pnpm-lock.yaml ./
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# Copy source and build
|
||||
COPY . .
|
||||
RUN pnpm prisma generate
|
||||
RUN pnpm build
|
||||
|
||||
# Production stage
|
||||
FROM node:20-alpine AS runner
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install pnpm
|
||||
RUN corepack enable && corepack prepare pnpm@latest --activate
|
||||
|
||||
# Create non-root user
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 app
|
||||
|
||||
# Copy built application
|
||||
COPY --from=builder --chown=app:nodejs /app/.output ./.output
|
||||
COPY --from=builder --chown=app:nodejs /app/node_modules ./node_modules
|
||||
COPY --from=builder --chown=app:nodejs /app/package.json ./package.json
|
||||
COPY --from=builder --chown=app:nodejs /app/prisma ./prisma
|
||||
|
||||
USER app
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV PORT=3000
|
||||
|
||||
CMD ["node", ".output/server/index.mjs"]
|
||||
```
|
||||
|
||||
## Docker Compose (Production)
|
||||
|
||||
```yaml
|
||||
# docker/docker-compose.yml
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: docker/Dockerfile
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://postgres:${DB_PASSWORD}@postgres:5432/dofus_manager
|
||||
- SESSION_SECRET=${SESSION_SECRET}
|
||||
- NODE_ENV=production
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- internal
|
||||
- traefik
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.dofus.rule=Host(`dofus.example.com`)"
|
||||
- "traefik.http.routers.dofus.entrypoints=websecure"
|
||||
- "traefik.http.routers.dofus.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.dofus.loadbalancer.server.port=3000"
|
||||
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
||||
- POSTGRES_DB=dofus_manager
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- internal
|
||||
|
||||
traefik:
|
||||
image: traefik:v3.0
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- "--api.dashboard=true"
|
||||
- "--providers.docker=true"
|
||||
- "--providers.docker.exposedbydefault=false"
|
||||
- "--entrypoints.web.address=:80"
|
||||
- "--entrypoints.websecure.address=:443"
|
||||
- "--entrypoints.web.http.redirections.entrypoint.to=websecure"
|
||||
- "--certificatesresolvers.letsencrypt.acme.httpchallenge=true"
|
||||
- "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
|
||||
- "--certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL}"
|
||||
- "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- letsencrypt:/letsencrypt
|
||||
networks:
|
||||
- traefik
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
letsencrypt:
|
||||
|
||||
networks:
|
||||
internal:
|
||||
traefik:
|
||||
external: true
|
||||
```
|
||||
|
||||
## GitLab CI/CD Pipeline
|
||||
|
||||
```yaml
|
||||
# .gitlab-ci.yml
|
||||
|
||||
stages:
|
||||
- test
|
||||
- build
|
||||
- deploy
|
||||
|
||||
variables:
|
||||
DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
|
||||
|
||||
# Test stage
|
||||
test:
|
||||
stage: test
|
||||
image: node:20-alpine
|
||||
before_script:
|
||||
- corepack enable
|
||||
- pnpm install --frozen-lockfile
|
||||
script:
|
||||
- pnpm lint
|
||||
- pnpm typecheck
|
||||
- pnpm test
|
||||
cache:
|
||||
key: ${CI_COMMIT_REF_SLUG}
|
||||
paths:
|
||||
- node_modules/
|
||||
|
||||
# Build stage
|
||||
build:
|
||||
stage: build
|
||||
image: docker:24
|
||||
services:
|
||||
- docker:24-dind
|
||||
before_script:
|
||||
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
|
||||
script:
|
||||
- docker build -t $DOCKER_IMAGE -f docker/Dockerfile .
|
||||
- docker push $DOCKER_IMAGE
|
||||
- docker tag $DOCKER_IMAGE $CI_REGISTRY_IMAGE:latest
|
||||
- docker push $CI_REGISTRY_IMAGE:latest
|
||||
only:
|
||||
- main
|
||||
- develop
|
||||
|
||||
# Deploy staging
|
||||
deploy_staging:
|
||||
stage: deploy
|
||||
image: alpine:latest
|
||||
before_script:
|
||||
- apk add --no-cache openssh-client
|
||||
- eval $(ssh-agent -s)
|
||||
- echo "$SSH_PRIVATE_KEY" | ssh-add -
|
||||
- mkdir -p ~/.ssh
|
||||
- echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts
|
||||
script:
|
||||
- ssh $STAGING_USER@$STAGING_HOST "cd /opt/dofus-manager && docker compose pull && docker compose up -d"
|
||||
environment:
|
||||
name: staging
|
||||
url: https://staging.dofus.example.com
|
||||
only:
|
||||
- develop
|
||||
|
||||
# Deploy production
|
||||
deploy_production:
|
||||
stage: deploy
|
||||
image: alpine:latest
|
||||
before_script:
|
||||
- apk add --no-cache openssh-client
|
||||
- eval $(ssh-agent -s)
|
||||
- echo "$SSH_PRIVATE_KEY" | ssh-add -
|
||||
- mkdir -p ~/.ssh
|
||||
- echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts
|
||||
script:
|
||||
- ssh $PROD_USER@$PROD_HOST "cd /opt/dofus-manager && docker compose pull && docker compose up -d && docker compose exec app pnpm prisma migrate deploy"
|
||||
environment:
|
||||
name: production
|
||||
url: https://dofus.example.com
|
||||
only:
|
||||
- main
|
||||
when: manual
|
||||
```
|
||||
|
||||
---
|
||||
68
docs/architecture/15-security-performance.md
Normal file
68
docs/architecture/15-security-performance.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# 15. Security & Performance
|
||||
|
||||
## Security Measures
|
||||
|
||||
### Authentication
|
||||
- Session-based authentication with secure cookies
|
||||
- Password hashing with bcrypt (cost factor 12)
|
||||
- Session expiration and rotation
|
||||
|
||||
### Input Validation
|
||||
- All inputs validated with Zod schemas
|
||||
- Server-side validation mandatory
|
||||
- Prisma parameterized queries (SQL injection prevention)
|
||||
|
||||
### Headers (via Traefik)
|
||||
```yaml
|
||||
# Security headers middleware
|
||||
http:
|
||||
middlewares:
|
||||
security-headers:
|
||||
headers:
|
||||
stsSeconds: 31536000
|
||||
stsIncludeSubdomains: true
|
||||
contentTypeNosniff: true
|
||||
frameDeny: true
|
||||
browserXssFilter: true
|
||||
referrerPolicy: "strict-origin-when-cross-origin"
|
||||
contentSecurityPolicy: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'"
|
||||
```
|
||||
|
||||
## Performance Optimizations
|
||||
|
||||
### Database
|
||||
- Indexes on foreign keys and search fields
|
||||
- Pagination for all list queries
|
||||
- Connection pooling via Prisma
|
||||
|
||||
### Caching
|
||||
- node-cache for server-side caching
|
||||
- TanStack Query for client-side caching
|
||||
- DofusDB data cached for 1 hour
|
||||
|
||||
### Frontend
|
||||
- Code splitting via TanStack Router
|
||||
- Lazy loading for routes
|
||||
- Optimistic updates for better UX
|
||||
|
||||
### Bundle Optimization
|
||||
```typescript
|
||||
// app.config.ts
|
||||
export default defineConfig({
|
||||
vite: {
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
'vendor-react': ['react', 'react-dom'],
|
||||
'vendor-tanstack': ['@tanstack/react-router', '@tanstack/react-query'],
|
||||
'vendor-ui': ['@radix-ui/react-dialog', '@radix-ui/react-select'],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
146
docs/architecture/16-testing-strategy.md
Normal file
146
docs/architecture/16-testing-strategy.md
Normal file
@@ -0,0 +1,146 @@
|
||||
# 16. Testing Strategy
|
||||
|
||||
## Testing Pyramid
|
||||
|
||||
```
|
||||
┌───────┐
|
||||
│ E2E │ ← Few, critical paths
|
||||
│ Tests │
|
||||
┌┴───────┴┐
|
||||
│Integration│ ← API & DB tests
|
||||
│ Tests │
|
||||
┌┴─────────┴┐
|
||||
│ Unit │ ← Many, fast
|
||||
│ Tests │
|
||||
└───────────┘
|
||||
```
|
||||
|
||||
## Unit Tests (Vitest)
|
||||
|
||||
```typescript
|
||||
// tests/unit/schemas/character.test.ts
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { createCharacterSchema } from '@/lib/schemas/character';
|
||||
|
||||
describe('createCharacterSchema', () => {
|
||||
it('validates correct input', () => {
|
||||
const input = {
|
||||
name: 'TestChar',
|
||||
level: 200,
|
||||
classId: 1,
|
||||
className: 'Cra',
|
||||
serverId: 1,
|
||||
serverName: 'Imagiro',
|
||||
accountId: 'clx123abc',
|
||||
};
|
||||
|
||||
expect(() => createCharacterSchema.parse(input)).not.toThrow();
|
||||
});
|
||||
|
||||
it('rejects invalid level', () => {
|
||||
const input = {
|
||||
name: 'TestChar',
|
||||
level: 250, // Invalid: max is 200
|
||||
classId: 1,
|
||||
className: 'Cra',
|
||||
serverId: 1,
|
||||
serverName: 'Imagiro',
|
||||
accountId: 'clx123abc',
|
||||
};
|
||||
|
||||
expect(() => createCharacterSchema.parse(input)).toThrow();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Integration Tests
|
||||
|
||||
```typescript
|
||||
// tests/integration/characters.test.ts
|
||||
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
||||
import { db } from '@/lib/server/db';
|
||||
|
||||
describe('Character API', () => {
|
||||
let testUserId: string;
|
||||
let testAccountId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
// Setup test data
|
||||
const user = await db.user.create({
|
||||
data: {
|
||||
email: 'test@test.com',
|
||||
passwordHash: 'hash',
|
||||
},
|
||||
});
|
||||
testUserId = user.id;
|
||||
|
||||
const account = await db.account.create({
|
||||
data: {
|
||||
name: 'TestAccount',
|
||||
userId: testUserId,
|
||||
},
|
||||
});
|
||||
testAccountId = account.id;
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
// Cleanup
|
||||
await db.user.delete({ where: { id: testUserId } });
|
||||
});
|
||||
|
||||
it('creates a character', async () => {
|
||||
const character = await db.character.create({
|
||||
data: {
|
||||
name: 'TestChar',
|
||||
level: 200,
|
||||
classId: 1,
|
||||
className: 'Cra',
|
||||
serverId: 1,
|
||||
serverName: 'Imagiro',
|
||||
accountId: testAccountId,
|
||||
},
|
||||
});
|
||||
|
||||
expect(character.name).toBe('TestChar');
|
||||
expect(character.level).toBe(200);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## E2E Tests (Playwright)
|
||||
|
||||
```typescript
|
||||
// tests/e2e/characters.spec.ts
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Characters Page', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Login
|
||||
await page.goto('/login');
|
||||
await page.fill('[name="email"]', 'test@test.com');
|
||||
await page.fill('[name="password"]', 'password');
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForURL('/');
|
||||
});
|
||||
|
||||
test('displays character list', async ({ page }) => {
|
||||
await page.goto('/characters');
|
||||
|
||||
await expect(page.locator('h1')).toContainText('Personnages');
|
||||
await expect(page.locator('table')).toBeVisible();
|
||||
});
|
||||
|
||||
test('can create a new character', async ({ page }) => {
|
||||
await page.goto('/characters');
|
||||
|
||||
await page.click('button:has-text("Ajouter")');
|
||||
await page.fill('[name="name"]', 'NewChar');
|
||||
await page.fill('[name="level"]', '100');
|
||||
await page.click('button:has-text("Créer")');
|
||||
|
||||
await expect(page.locator('text=NewChar')).toBeVisible();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
343
docs/architecture/17-coding-standards.md
Normal file
343
docs/architecture/17-coding-standards.md
Normal file
@@ -0,0 +1,343 @@
|
||||
# 17. Coding Standards
|
||||
|
||||
Cette section définit les standards de code minimaux mais critiques pour le développement. Ces règles sont conçues pour les agents IA et les développeurs.
|
||||
|
||||
## Critical Fullstack Rules
|
||||
|
||||
| Rule | Description |
|
||||
|------|-------------|
|
||||
| **Type Sharing** | Toujours définir les types partagés dans `src/types/` et les importer dans les composants et server functions. Ne jamais dupliquer les interfaces. |
|
||||
| **Server Functions Only** | Ne jamais faire d'appels HTTP directs (fetch, axios). Toujours utiliser les server functions importées depuis `@/server/*`. |
|
||||
| **Zod Validation** | Toute entrée utilisateur doit être validée avec un schéma Zod. Le schéma doit être colocalisé avec la server function. |
|
||||
| **Prisma Transactions** | Les opérations multi-tables doivent utiliser `prisma.$transaction()` pour garantir l'atomicité. |
|
||||
| **Cache Invalidation** | Après chaque mutation, invalider les caches affectés via `cacheService.invalidate()` et `queryClient.invalidateQueries()`. |
|
||||
| **Error Boundaries** | Chaque page doit être wrappée dans un ErrorBoundary. Les erreurs serveur doivent utiliser les classes de `@/lib/errors.ts`. |
|
||||
| **No Direct State Mutation** | Ne jamais muter l'état directement. Utiliser les setters de useState ou les mutations TanStack Query. |
|
||||
| **URL State for Filters** | Les filtres et la pagination doivent être stockés dans l'URL via TanStack Router, pas dans le state local. |
|
||||
| **Optimistic Updates Pattern** | Pour les actions fréquentes (toggle, update), utiliser les optimistic updates de TanStack Query. |
|
||||
| **Component Colocation** | Les hooks, types et helpers spécifiques à un composant doivent être dans le même dossier. |
|
||||
|
||||
## Naming Conventions
|
||||
|
||||
| Element | Frontend | Backend | Example |
|
||||
|---------|----------|---------|---------|
|
||||
| Components | PascalCase | — | `CharacterCard.tsx` |
|
||||
| Component files | kebab-case ou PascalCase | — | `character-card.tsx` |
|
||||
| Hooks | camelCase + use prefix | — | `useCharacters.ts` |
|
||||
| Server Functions | camelCase | camelCase | `getCharacters`, `createTeam` |
|
||||
| Server Function files | kebab-case | kebab-case | `characters.ts` |
|
||||
| Types/Interfaces | PascalCase | PascalCase | `Character`, `CreateCharacterInput` |
|
||||
| Enums | PascalCase | PascalCase | `CharacterClass`, `TeamType` |
|
||||
| Constants | SCREAMING_SNAKE | SCREAMING_SNAKE | `MAX_TEAM_SIZE` |
|
||||
| Database tables | — | snake_case | `character_progressions` |
|
||||
| Database columns | — | snake_case | `created_at`, `account_id` |
|
||||
| URL routes | kebab-case | kebab-case | `/characters`, `/bulk-progressions` |
|
||||
| Query keys | camelCase arrays | — | `['characters', 'list', filters]` |
|
||||
| CSS classes | Tailwind utilities | — | `bg-card text-foreground` |
|
||||
|
||||
## File Structure Conventions
|
||||
|
||||
### Component File Structure
|
||||
|
||||
```typescript
|
||||
// 1. Imports (grouped)
|
||||
import { useState } from 'react'; // React
|
||||
import { useQuery } from '@tanstack/react-query'; // External libs
|
||||
import { Button } from '@/components/ui/button'; // Internal UI
|
||||
import { getCharacters } from '@/server/characters'; // Server functions
|
||||
import type { Character } from '@/types'; // Types (last)
|
||||
|
||||
// 2. Types (if not in separate file)
|
||||
interface CharacterListProps {
|
||||
serverId?: string;
|
||||
}
|
||||
|
||||
// 3. Constants (if any)
|
||||
const PAGE_SIZE = 20;
|
||||
|
||||
// 4. Component
|
||||
export function CharacterList({ serverId }: CharacterListProps) {
|
||||
// 4a. Hooks (in order: router, query, state, effects, callbacks)
|
||||
const navigate = useNavigate();
|
||||
const { data, isLoading } = useQuery({ ... });
|
||||
const [selected, setSelected] = useState<string[]>([]);
|
||||
|
||||
// 4b. Early returns
|
||||
if (isLoading) return <Loading />;
|
||||
if (!data) return <EmptyState />;
|
||||
|
||||
// 4c. Render
|
||||
return (
|
||||
<div>
|
||||
{/* JSX */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// 5. Sub-components (if small and only used here)
|
||||
function CharacterRow({ character }: { character: Character }) {
|
||||
return <tr>...</tr>;
|
||||
}
|
||||
```
|
||||
|
||||
### Server Function File Structure
|
||||
|
||||
```typescript
|
||||
// 1. Imports
|
||||
import { createServerFn } from '@tanstack/start';
|
||||
import { z } from 'zod';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { cacheService } from '@/lib/cache';
|
||||
import { NotFoundError, ValidationError } from '@/lib/errors';
|
||||
|
||||
// 2. Schemas (colocated with functions)
|
||||
const createCharacterSchema = z.object({
|
||||
name: z.string().min(1).max(50),
|
||||
class: z.nativeEnum(CharacterClass),
|
||||
level: z.number().int().min(1).max(200),
|
||||
serverId: z.string().uuid(),
|
||||
accountId: z.string().uuid(),
|
||||
});
|
||||
|
||||
// 3. Server Functions (exported)
|
||||
export const getCharacters = createServerFn('GET', async (filters) => {
|
||||
// Implementation
|
||||
});
|
||||
|
||||
export const createCharacter = createServerFn('POST', async (input) => {
|
||||
const data = createCharacterSchema.parse(input);
|
||||
// Implementation
|
||||
});
|
||||
|
||||
// 4. Helper functions (private, at bottom)
|
||||
function buildWhereClause(filters: Filters): Prisma.CharacterWhereInput {
|
||||
// Implementation
|
||||
}
|
||||
```
|
||||
|
||||
## Code Quality Rules
|
||||
|
||||
### TypeScript
|
||||
|
||||
```typescript
|
||||
// ✅ DO: Explicit return types for exported functions
|
||||
export function calculateTotal(items: Item[]): number {
|
||||
return items.reduce((sum, item) => sum + item.amount, 0);
|
||||
}
|
||||
|
||||
// ❌ DON'T: Implicit any
|
||||
function processData(data) { ... }
|
||||
|
||||
// ✅ DO: Use type imports
|
||||
import type { Character } from '@/types';
|
||||
|
||||
// ❌ DON'T: Import types as values
|
||||
import { Character } from '@/types';
|
||||
|
||||
// ✅ DO: Discriminated unions for state
|
||||
type State =
|
||||
| { status: 'loading' }
|
||||
| { status: 'error'; error: Error }
|
||||
| { status: 'success'; data: Character[] };
|
||||
|
||||
// ❌ DON'T: Optional properties for state
|
||||
type State = {
|
||||
loading?: boolean;
|
||||
error?: Error;
|
||||
data?: Character[];
|
||||
};
|
||||
```
|
||||
|
||||
### React
|
||||
|
||||
```typescript
|
||||
// ✅ DO: Named exports for components
|
||||
export function CharacterCard({ character }: Props) { ... }
|
||||
|
||||
// ❌ DON'T: Default exports (except for routes)
|
||||
export default function CharacterCard() { ... }
|
||||
|
||||
// ✅ DO: Destructure props
|
||||
function CharacterCard({ character, onSelect }: Props) { ... }
|
||||
|
||||
// ❌ DON'T: Props object
|
||||
function CharacterCard(props: Props) {
|
||||
const character = props.character;
|
||||
}
|
||||
|
||||
// ✅ DO: useCallback for handlers passed to children
|
||||
const handleSelect = useCallback((id: string) => {
|
||||
setSelected(prev => [...prev, id]);
|
||||
}, []);
|
||||
|
||||
// ✅ DO: useMemo for expensive computations
|
||||
const filteredCharacters = useMemo(
|
||||
() => characters.filter(c => c.level >= 180),
|
||||
[characters]
|
||||
);
|
||||
|
||||
// ❌ DON'T: Inline objects/arrays in props (causes re-renders)
|
||||
<Table columns={[col1, col2]} /> // Creates new array each render
|
||||
```
|
||||
|
||||
### Prisma
|
||||
|
||||
```typescript
|
||||
// ✅ DO: Select only needed fields
|
||||
const characters = await prisma.character.findMany({
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
level: true,
|
||||
server: { select: { name: true } },
|
||||
},
|
||||
});
|
||||
|
||||
// ❌ DON'T: Fetch everything
|
||||
const characters = await prisma.character.findMany({
|
||||
include: {
|
||||
server: true,
|
||||
account: true,
|
||||
progressions: true,
|
||||
currencies: true,
|
||||
teamMemberships: true,
|
||||
},
|
||||
});
|
||||
|
||||
// ✅ DO: Use transactions for related operations
|
||||
await prisma.$transaction([
|
||||
prisma.teamMember.deleteMany({ where: { teamId } }),
|
||||
prisma.team.delete({ where: { id: teamId } }),
|
||||
]);
|
||||
|
||||
// ✅ DO: Count separately from data
|
||||
const [data, total] = await Promise.all([
|
||||
prisma.character.findMany({ take: 20 }),
|
||||
prisma.character.count(),
|
||||
]);
|
||||
```
|
||||
|
||||
## Import Order
|
||||
|
||||
ESLint avec `eslint-plugin-import` applique cet ordre :
|
||||
|
||||
```typescript
|
||||
// 1. React
|
||||
import { useState, useCallback } from 'react';
|
||||
|
||||
// 2. External packages (alphabetical)
|
||||
import { useQuery, useMutation } from '@tanstack/react-query';
|
||||
import { useNavigate } from '@tanstack/react-router';
|
||||
import { z } from 'zod';
|
||||
|
||||
// 3. Internal absolute imports (alphabetical by path)
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { DataTable } from '@/components/data-table/data-table';
|
||||
import { useCharacters } from '@/hooks/use-characters';
|
||||
import { prisma } from '@/lib/prisma';
|
||||
import { getCharacters } from '@/server/characters';
|
||||
|
||||
// 4. Relative imports
|
||||
import { CharacterRow } from './character-row';
|
||||
|
||||
// 5. Type imports (at the end)
|
||||
import type { Character, CharacterFilters } from '@/types';
|
||||
```
|
||||
|
||||
## ESLint & Prettier Config
|
||||
|
||||
### .eslintrc.cjs
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: { browser: true, es2022: true, node: true },
|
||||
extends: [
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
'prettier',
|
||||
],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
project: './tsconfig.json',
|
||||
},
|
||||
plugins: ['@typescript-eslint', 'import'],
|
||||
rules: {
|
||||
// TypeScript
|
||||
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
|
||||
'@typescript-eslint/consistent-type-imports': 'error',
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
|
||||
// Import order
|
||||
'import/order': [
|
||||
'error',
|
||||
{
|
||||
groups: [
|
||||
'builtin',
|
||||
'external',
|
||||
'internal',
|
||||
'parent',
|
||||
'sibling',
|
||||
'index',
|
||||
'type',
|
||||
],
|
||||
'newlines-between': 'always',
|
||||
alphabetize: { order: 'asc' },
|
||||
},
|
||||
],
|
||||
|
||||
// React
|
||||
'react-hooks/rules-of-hooks': 'error',
|
||||
'react-hooks/exhaustive-deps': 'warn',
|
||||
|
||||
// General
|
||||
'no-console': ['warn', { allow: ['warn', 'error'] }],
|
||||
},
|
||||
ignorePatterns: ['node_modules', '.output', 'dist'],
|
||||
};
|
||||
```
|
||||
|
||||
### .prettierrc
|
||||
|
||||
```json
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-tailwindcss"]
|
||||
}
|
||||
```
|
||||
|
||||
## Git Commit Standards
|
||||
|
||||
```bash
|
||||
# Format
|
||||
<type>(<scope>): <subject>
|
||||
|
||||
# Types
|
||||
feat # New feature
|
||||
fix # Bug fix
|
||||
docs # Documentation
|
||||
style # Formatting (no code change)
|
||||
refactor # Refactoring
|
||||
test # Adding tests
|
||||
chore # Build, deps, tooling
|
||||
|
||||
# Scopes (optional)
|
||||
characters, teams, accounts, progressions, currencies
|
||||
ui, api, db, auth, dofusdb
|
||||
|
||||
# Examples
|
||||
feat(characters): add bulk progression update
|
||||
fix(teams): validate account constraint on member add
|
||||
refactor(api): extract prisma helpers
|
||||
test(teams): add integration tests for constraints
|
||||
chore(deps): upgrade TanStack Query to v5.17
|
||||
```
|
||||
|
||||
---
|
||||
167
docs/architecture/18-error-handling.md
Normal file
167
docs/architecture/18-error-handling.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# 18. Error Handling
|
||||
|
||||
## Unified Error Types
|
||||
|
||||
```typescript
|
||||
// src/lib/errors.ts
|
||||
|
||||
export enum ErrorCode {
|
||||
// Validation (400)
|
||||
VALIDATION_ERROR = 'VALIDATION_ERROR',
|
||||
INVALID_INPUT = 'INVALID_INPUT',
|
||||
|
||||
// Authentication (401)
|
||||
UNAUTHORIZED = 'UNAUTHORIZED',
|
||||
SESSION_EXPIRED = 'SESSION_EXPIRED',
|
||||
|
||||
// Authorization (403)
|
||||
FORBIDDEN = 'FORBIDDEN',
|
||||
INSUFFICIENT_PERMISSIONS = 'INSUFFICIENT_PERMISSIONS',
|
||||
|
||||
// Not Found (404)
|
||||
NOT_FOUND = 'NOT_FOUND',
|
||||
RESOURCE_NOT_FOUND = 'RESOURCE_NOT_FOUND',
|
||||
|
||||
// Conflict (409)
|
||||
CONFLICT = 'CONFLICT',
|
||||
DUPLICATE_ENTRY = 'DUPLICATE_ENTRY',
|
||||
|
||||
// External Service (502)
|
||||
EXTERNAL_API_ERROR = 'EXTERNAL_API_ERROR',
|
||||
DOFUSDB_ERROR = 'DOFUSDB_ERROR',
|
||||
|
||||
// Server (500)
|
||||
INTERNAL_ERROR = 'INTERNAL_ERROR',
|
||||
DATABASE_ERROR = 'DATABASE_ERROR',
|
||||
}
|
||||
|
||||
export interface AppErrorData {
|
||||
code: ErrorCode;
|
||||
message: string;
|
||||
details?: Record<string, unknown>;
|
||||
field?: string;
|
||||
}
|
||||
|
||||
export class AppError extends Error {
|
||||
public readonly code: ErrorCode;
|
||||
public readonly statusCode: number;
|
||||
public readonly details?: Record<string, unknown>;
|
||||
public readonly field?: string;
|
||||
public readonly isOperational: boolean;
|
||||
|
||||
constructor(data: AppErrorData & { statusCode?: number }) {
|
||||
super(data.message);
|
||||
this.name = 'AppError';
|
||||
this.code = data.code;
|
||||
this.statusCode = data.statusCode ?? this.getDefaultStatusCode(data.code);
|
||||
this.details = data.details;
|
||||
this.field = data.field;
|
||||
this.isOperational = true;
|
||||
|
||||
Error.captureStackTrace(this, this.constructor);
|
||||
}
|
||||
|
||||
private getDefaultStatusCode(code: ErrorCode): number {
|
||||
const statusMap: Record<ErrorCode, number> = {
|
||||
[ErrorCode.VALIDATION_ERROR]: 400,
|
||||
[ErrorCode.INVALID_INPUT]: 400,
|
||||
[ErrorCode.UNAUTHORIZED]: 401,
|
||||
[ErrorCode.SESSION_EXPIRED]: 401,
|
||||
[ErrorCode.FORBIDDEN]: 403,
|
||||
[ErrorCode.INSUFFICIENT_PERMISSIONS]: 403,
|
||||
[ErrorCode.NOT_FOUND]: 404,
|
||||
[ErrorCode.RESOURCE_NOT_FOUND]: 404,
|
||||
[ErrorCode.CONFLICT]: 409,
|
||||
[ErrorCode.DUPLICATE_ENTRY]: 409,
|
||||
[ErrorCode.EXTERNAL_API_ERROR]: 502,
|
||||
[ErrorCode.DOFUSDB_ERROR]: 502,
|
||||
[ErrorCode.INTERNAL_ERROR]: 500,
|
||||
[ErrorCode.DATABASE_ERROR]: 500,
|
||||
};
|
||||
return statusMap[code] ?? 500;
|
||||
}
|
||||
|
||||
toJSON(): AppErrorData & { statusCode: number } {
|
||||
return {
|
||||
code: this.code,
|
||||
message: this.message,
|
||||
statusCode: this.statusCode,
|
||||
details: this.details,
|
||||
field: this.field,
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Backend Error Handler
|
||||
|
||||
```typescript
|
||||
// src/lib/server/error-handler.ts
|
||||
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { AppError, ErrorCode } from '@/lib/errors';
|
||||
|
||||
export function handlePrismaError(error: unknown): AppError {
|
||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||
switch (error.code) {
|
||||
case 'P2002':
|
||||
return new AppError({
|
||||
code: ErrorCode.DUPLICATE_ENTRY,
|
||||
message: 'Cette entrée existe déjà',
|
||||
details: { fields: error.meta?.target },
|
||||
});
|
||||
case 'P2025':
|
||||
return new AppError({
|
||||
code: ErrorCode.NOT_FOUND,
|
||||
message: 'Ressource non trouvée',
|
||||
});
|
||||
default:
|
||||
return new AppError({
|
||||
code: ErrorCode.DATABASE_ERROR,
|
||||
message: 'Erreur de base de données',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return new AppError({
|
||||
code: ErrorCode.INTERNAL_ERROR,
|
||||
message: 'Une erreur inattendue est survenue',
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## Frontend Error Handler
|
||||
|
||||
```typescript
|
||||
// src/lib/client/error-handler.ts
|
||||
|
||||
import { toast } from 'sonner';
|
||||
import { AppError, ErrorCode } from '@/lib/errors';
|
||||
|
||||
const errorMessages: Record<ErrorCode, string> = {
|
||||
[ErrorCode.VALIDATION_ERROR]: 'Données invalides',
|
||||
[ErrorCode.UNAUTHORIZED]: 'Veuillez vous connecter',
|
||||
[ErrorCode.NOT_FOUND]: 'Ressource non trouvée',
|
||||
[ErrorCode.DUPLICATE_ENTRY]: 'Cette entrée existe déjà',
|
||||
[ErrorCode.INTERNAL_ERROR]: 'Erreur serveur',
|
||||
// ... other codes
|
||||
};
|
||||
|
||||
export function handleError(error: unknown): void {
|
||||
if (error instanceof AppError) {
|
||||
const message = error.message || errorMessages[error.code];
|
||||
|
||||
if (error.statusCode >= 500) {
|
||||
toast.error('Erreur serveur', { description: message });
|
||||
} else {
|
||||
toast.warning(message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
console.error('Unexpected error:', error);
|
||||
toast.error('Une erreur inattendue est survenue');
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
131
docs/architecture/19-monitoring-observability.md
Normal file
131
docs/architecture/19-monitoring-observability.md
Normal file
@@ -0,0 +1,131 @@
|
||||
# 19. Monitoring & Observability
|
||||
|
||||
## Structured Logging (Pino)
|
||||
|
||||
```typescript
|
||||
// src/lib/server/logger.ts
|
||||
|
||||
import pino from 'pino';
|
||||
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
|
||||
export const logger = pino({
|
||||
level: process.env.LOG_LEVEL ?? (isDev ? 'debug' : 'info'),
|
||||
|
||||
transport: isDev
|
||||
? {
|
||||
target: 'pino-pretty',
|
||||
options: {
|
||||
colorize: true,
|
||||
translateTime: 'HH:MM:ss',
|
||||
ignore: 'pid,hostname',
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
|
||||
formatters: {
|
||||
level: (label) => ({ level: label }),
|
||||
},
|
||||
|
||||
base: {
|
||||
env: process.env.NODE_ENV,
|
||||
version: process.env.APP_VERSION ?? '0.0.0',
|
||||
},
|
||||
|
||||
redact: {
|
||||
paths: ['password', 'token', 'authorization'],
|
||||
censor: '[REDACTED]',
|
||||
},
|
||||
});
|
||||
|
||||
export const dbLogger = logger.child({ module: 'database' });
|
||||
export const apiLogger = logger.child({ module: 'api' });
|
||||
```
|
||||
|
||||
## Health Check Endpoint
|
||||
|
||||
```typescript
|
||||
// src/server/functions/health.ts
|
||||
|
||||
import { createServerFn } from '@tanstack/react-start/server';
|
||||
import { db } from '@/lib/server/db';
|
||||
|
||||
interface HealthStatus {
|
||||
status: 'healthy' | 'degraded' | 'unhealthy';
|
||||
timestamp: string;
|
||||
version: string;
|
||||
checks: {
|
||||
database: boolean;
|
||||
cache: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export const getHealth = createServerFn({ method: 'GET' }).handler(
|
||||
async (): Promise<HealthStatus> => {
|
||||
const checks = {
|
||||
database: false,
|
||||
cache: true,
|
||||
};
|
||||
|
||||
try {
|
||||
await db.$queryRaw`SELECT 1`;
|
||||
checks.database = true;
|
||||
} catch {
|
||||
// Database check failed
|
||||
}
|
||||
|
||||
const allHealthy = Object.values(checks).every(Boolean);
|
||||
|
||||
return {
|
||||
status: allHealthy ? 'healthy' : 'degraded',
|
||||
timestamp: new Date().toISOString(),
|
||||
version: process.env.APP_VERSION ?? '0.0.0',
|
||||
checks,
|
||||
};
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
## Application Metrics
|
||||
|
||||
```typescript
|
||||
// src/lib/server/metrics.ts
|
||||
|
||||
class MetricsCollector {
|
||||
private startTime = Date.now();
|
||||
private data = {
|
||||
requests: { total: 0, errors: 0 },
|
||||
database: { queries: 0, slowQueries: 0 },
|
||||
cache: { hits: 0, misses: 0 },
|
||||
};
|
||||
|
||||
incrementRequest(isError = false): void {
|
||||
this.data.requests.total++;
|
||||
if (isError) this.data.requests.errors++;
|
||||
}
|
||||
|
||||
incrementDbQuery(isSlow = false): void {
|
||||
this.data.database.queries++;
|
||||
if (isSlow) this.data.database.slowQueries++;
|
||||
}
|
||||
|
||||
incrementCacheHit(): void {
|
||||
this.data.cache.hits++;
|
||||
}
|
||||
|
||||
incrementCacheMiss(): void {
|
||||
this.data.cache.misses++;
|
||||
}
|
||||
|
||||
getMetrics() {
|
||||
return {
|
||||
...this.data,
|
||||
uptime: Math.floor((Date.now() - this.startTime) / 1000),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const metrics = new MetricsCollector();
|
||||
```
|
||||
|
||||
---
|
||||
66
docs/architecture/2-high-level-architecture.md
Normal file
66
docs/architecture/2-high-level-architecture.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# 2. High-Level Architecture
|
||||
|
||||
## System Architecture Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||
│ DOFUS MANAGER │
|
||||
├─────────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ CLIENT (Browser) │ │
|
||||
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌────────────┐ │ │
|
||||
│ │ │ React │ │ TanStack │ │ TanStack │ │ Zustand │ │ │
|
||||
│ │ │ Components│ │ Router │ │ Query │ │ (UI State)│ │ │
|
||||
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └────────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ │ Server Functions (RPC) │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ SERVER (TanStack Start) │ │
|
||||
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌────────────┐ │ │
|
||||
│ │ │ Server │ │ Zod │ │ Prisma │ │ node-cache│ │ │
|
||||
│ │ │ Functions │ │ Validation │ │ ORM │ │ (Cache) │ │ │
|
||||
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └────────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ DATABASE (PostgreSQL) │ │
|
||||
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌────────────┐ │ │
|
||||
│ │ │ Characters │ │ Accounts │ │ Teams │ │Progressions│ │ │
|
||||
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └────────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ EXTERNAL SERVICES │ │
|
||||
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
|
||||
│ │ │ DofusDB API │ │ │
|
||||
│ │ │ (Classes, Quêtes, Donjons, Succès) │ │ │
|
||||
│ │ └─────────────────────────────────────────────────────────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Data Flow
|
||||
|
||||
```
|
||||
User Action → React Component → TanStack Query → Server Function → Prisma → PostgreSQL
|
||||
↑ │
|
||||
└───────────────────────── Response ────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Key Architectural Decisions
|
||||
|
||||
| Decision | Choice | Rationale |
|
||||
|----------|--------|-----------|
|
||||
| Full-stack Framework | TanStack Start | Type-safe, modern React, server functions |
|
||||
| Database | PostgreSQL | Robust, JSON support, full-text search |
|
||||
| ORM | Prisma | Type-safe queries, excellent DX |
|
||||
| State Management | TanStack Query + Zustand | Server state vs UI state separation |
|
||||
| Styling | Tailwind + shadcn/ui | Rapid development, consistent design |
|
||||
| Deployment | Docker + Traefik | Easy SSL, reverse proxy, scalable |
|
||||
|
||||
---
|
||||
46
docs/architecture/20-architecture-checklist.md
Normal file
46
docs/architecture/20-architecture-checklist.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# 20. Architecture Checklist
|
||||
|
||||
| Category | Item | Status |
|
||||
|----------|------|--------|
|
||||
| **Core Architecture** | | |
|
||||
| | High-level architecture diagram | ✅ |
|
||||
| | Technology stack justified | ✅ |
|
||||
| | Data flow documented | ✅ |
|
||||
| | Scalability considered | ✅ |
|
||||
| **Data Layer** | | |
|
||||
| | Database schema defined | ✅ |
|
||||
| | Relationships documented | ✅ |
|
||||
| | Indexes identified | ✅ |
|
||||
| | Migration strategy | ✅ |
|
||||
| **API Design** | | |
|
||||
| | API contracts defined | ✅ |
|
||||
| | Input validation | ✅ |
|
||||
| | Error responses standardized | ✅ |
|
||||
| | Authentication/Authorization | ✅ |
|
||||
| **Frontend** | | |
|
||||
| | Component architecture | ✅ |
|
||||
| | State management | ✅ |
|
||||
| | Routing strategy | ✅ |
|
||||
| | Error boundaries | ✅ |
|
||||
| **Security** | | |
|
||||
| | Authentication flow | ✅ |
|
||||
| | Input sanitization | ✅ |
|
||||
| | CORS/CSP headers | ✅ |
|
||||
| | Secrets management | ✅ |
|
||||
| **Performance** | | |
|
||||
| | Caching strategy | ✅ |
|
||||
| | Database optimization | ✅ |
|
||||
| | Bundle optimization | ✅ |
|
||||
| | Loading states | ✅ |
|
||||
| **DevOps** | | |
|
||||
| | CI/CD pipeline | ✅ |
|
||||
| | Docker configuration | ✅ |
|
||||
| | Environment separation | ✅ |
|
||||
| | Deployment strategy | ✅ |
|
||||
| **Observability** | | |
|
||||
| | Logging strategy | ✅ |
|
||||
| | Health checks | ✅ |
|
||||
| | Metrics collection | ✅ |
|
||||
| | Error tracking | ✅ |
|
||||
|
||||
---
|
||||
52
docs/architecture/3-technology-stack.md
Normal file
52
docs/architecture/3-technology-stack.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# 3. Technology Stack
|
||||
|
||||
## Frontend
|
||||
|
||||
| Technology | Version | Purpose |
|
||||
|------------|---------|---------|
|
||||
| React | 19.x | UI library |
|
||||
| TanStack Router | 1.x | Type-safe routing |
|
||||
| TanStack Query | 5.x | Server state management |
|
||||
| Zustand | 5.x | Client state management |
|
||||
| Tailwind CSS | 4.x | Utility-first styling |
|
||||
| shadcn/ui | latest | Component library |
|
||||
| Lucide React | latest | Icons |
|
||||
| React Hook Form | 7.x | Form management |
|
||||
| Zod | 3.x | Schema validation |
|
||||
|
||||
## Backend
|
||||
|
||||
| Technology | Version | Purpose |
|
||||
|------------|---------|---------|
|
||||
| TanStack Start | 1.x | Full-stack framework |
|
||||
| Prisma | 6.x | Database ORM |
|
||||
| Zod | 3.x | Input validation |
|
||||
| node-cache | 5.x | In-memory caching |
|
||||
| Pino | 9.x | Structured logging |
|
||||
| bcryptjs | 2.x | Password hashing |
|
||||
|
||||
## Database
|
||||
|
||||
| Technology | Version | Purpose |
|
||||
|------------|---------|---------|
|
||||
| PostgreSQL | 16.x | Primary database |
|
||||
|
||||
## DevOps
|
||||
|
||||
| Technology | Purpose |
|
||||
|------------|---------|
|
||||
| Docker | Containerization |
|
||||
| Docker Compose | Local development & deployment |
|
||||
| Traefik | Reverse proxy, SSL termination |
|
||||
| GitLab CI | CI/CD pipeline |
|
||||
|
||||
## Development Tools
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| TypeScript | Type safety |
|
||||
| Biome | Linting & formatting |
|
||||
| Vitest | Unit testing |
|
||||
| Playwright | E2E testing |
|
||||
|
||||
---
|
||||
119
docs/architecture/4-data-models.md
Normal file
119
docs/architecture/4-data-models.md
Normal file
@@ -0,0 +1,119 @@
|
||||
# 4. Data Models
|
||||
|
||||
## Entity Relationship Diagram
|
||||
|
||||
```
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||
│ User │ │ Account │ │ Character │
|
||||
├─────────────┤ ├─────────────┤ ├─────────────┤
|
||||
│ id │ │ id │ │ id │
|
||||
│ email │──┐ │ name │──┐ │ name │
|
||||
│ password │ │ │ userId ◄─┘ │ │ level │
|
||||
│ createdAt │ └───►│ createdAt │ └───►│ classId │
|
||||
│ updatedAt │ │ updatedAt │ │ serverId │
|
||||
└─────────────┘ └─────────────┘ │ accountId ◄─┘
|
||||
│ createdAt │
|
||||
│ updatedAt │
|
||||
└──────┬──────┘
|
||||
│
|
||||
┌────────────────────────────┼────────────────────────────┐
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||
│ Team │ │TeamMember │ │ CharProgress│
|
||||
├─────────────┤ ├─────────────┤ ├─────────────┤
|
||||
│ id │◄─────────────│ teamId │ │ id │
|
||||
│ name │ │ characterId │◄─────────────│ characterId │
|
||||
│ type │ │ joinedAt │ │ progressId │
|
||||
│ userId │ └─────────────┘ │ completed │
|
||||
│ createdAt │ │ completedAt │
|
||||
│ updatedAt │ └─────────────┘
|
||||
└─────────────┘ │
|
||||
│
|
||||
▼
|
||||
┌─────────────┐
|
||||
│ Progression │
|
||||
├─────────────┤
|
||||
│ id │
|
||||
│ name │
|
||||
│ type │
|
||||
│ category │
|
||||
│ dofusDbId │
|
||||
└─────────────┘
|
||||
```
|
||||
|
||||
## Core Entities
|
||||
|
||||
### User
|
||||
```typescript
|
||||
interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
passwordHash: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
```
|
||||
|
||||
### Account
|
||||
```typescript
|
||||
interface Account {
|
||||
id: string;
|
||||
name: string;
|
||||
userId: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
```
|
||||
|
||||
### Character
|
||||
```typescript
|
||||
interface Character {
|
||||
id: string;
|
||||
name: string;
|
||||
level: number;
|
||||
classId: number;
|
||||
className: string;
|
||||
serverId: number;
|
||||
serverName: string;
|
||||
accountId: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
```
|
||||
|
||||
### Team
|
||||
```typescript
|
||||
interface Team {
|
||||
id: string;
|
||||
name: string;
|
||||
type: 'MAIN' | 'SECONDARY' | 'CUSTOM';
|
||||
userId: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
```
|
||||
|
||||
### Progression
|
||||
```typescript
|
||||
interface Progression {
|
||||
id: string;
|
||||
name: string;
|
||||
type: 'QUEST' | 'DUNGEON' | 'ACHIEVEMENT' | 'DOFUS';
|
||||
category: string;
|
||||
dofusDbId: number | null;
|
||||
}
|
||||
```
|
||||
|
||||
### CharacterProgression
|
||||
```typescript
|
||||
interface CharacterProgression {
|
||||
id: string;
|
||||
characterId: string;
|
||||
progressionId: string;
|
||||
completed: boolean;
|
||||
completedAt: Date | null;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
111
docs/architecture/5-api-specification.md
Normal file
111
docs/architecture/5-api-specification.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# 5. API Specification
|
||||
|
||||
## Server Functions Pattern
|
||||
|
||||
TanStack Start utilise des "Server Functions" - des fonctions RPC type-safe qui s'exécutent côté serveur.
|
||||
|
||||
```typescript
|
||||
// Définition
|
||||
import { createServerFn } from '@tanstack/react-start/server';
|
||||
import { z } from 'zod';
|
||||
|
||||
const inputSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
});
|
||||
|
||||
export const createCharacter = createServerFn({ method: 'POST' })
|
||||
.validator((data: unknown) => inputSchema.parse(data))
|
||||
.handler(async ({ data }) => {
|
||||
// Logique serveur avec accès DB
|
||||
return await db.character.create({ data });
|
||||
});
|
||||
|
||||
// Utilisation côté client
|
||||
const result = await createCharacter({ data: { name: 'MyChar' } });
|
||||
```
|
||||
|
||||
## API Endpoints (Server Functions)
|
||||
|
||||
### Characters
|
||||
|
||||
| Function | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `getCharacters` | GET | Liste tous les personnages avec filtres |
|
||||
| `getCharacter` | GET | Récupère un personnage par ID |
|
||||
| `createCharacter` | POST | Crée un nouveau personnage |
|
||||
| `updateCharacter` | POST | Met à jour un personnage |
|
||||
| `deleteCharacter` | POST | Supprime un personnage |
|
||||
| `bulkUpdateCharacters` | POST | Met à jour plusieurs personnages |
|
||||
| `bulkDeleteCharacters` | POST | Supprime plusieurs personnages |
|
||||
|
||||
### Accounts
|
||||
|
||||
| Function | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `getAccounts` | GET | Liste tous les comptes |
|
||||
| `getAccount` | GET | Récupère un compte par ID |
|
||||
| `createAccount` | POST | Crée un nouveau compte |
|
||||
| `updateAccount` | POST | Met à jour un compte |
|
||||
| `deleteAccount` | POST | Supprime un compte |
|
||||
|
||||
### Teams
|
||||
|
||||
| Function | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `getTeams` | GET | Liste toutes les équipes |
|
||||
| `getTeam` | GET | Récupère une équipe avec ses membres |
|
||||
| `createTeam` | POST | Crée une nouvelle équipe |
|
||||
| `updateTeam` | POST | Met à jour une équipe |
|
||||
| `deleteTeam` | POST | Supprime une équipe |
|
||||
| `addTeamMembers` | POST | Ajoute des personnages à une équipe |
|
||||
| `removeTeamMembers` | POST | Retire des personnages d'une équipe |
|
||||
|
||||
### Progressions
|
||||
|
||||
| Function | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `getProgressions` | GET | Liste toutes les progressions |
|
||||
| `getCharacterProgressions` | GET | Progressions d'un personnage |
|
||||
| `updateCharacterProgression` | POST | Met à jour une progression |
|
||||
| `bulkUpdateProgressions` | POST | Met à jour plusieurs progressions |
|
||||
| `syncFromDofusDb` | POST | Synchronise depuis DofusDB |
|
||||
|
||||
### Auth
|
||||
|
||||
| Function | Method | Description |
|
||||
|----------|--------|-------------|
|
||||
| `login` | POST | Authentification |
|
||||
| `logout` | POST | Déconnexion |
|
||||
| `getSession` | GET | Récupère la session courante |
|
||||
|
||||
## Validation Schemas
|
||||
|
||||
```typescript
|
||||
// src/lib/schemas/character.ts
|
||||
import { z } from 'zod';
|
||||
|
||||
export const createCharacterSchema = z.object({
|
||||
name: z.string().min(2).max(50),
|
||||
level: z.number().int().min(1).max(200),
|
||||
classId: z.number().int().positive(),
|
||||
serverId: z.number().int().positive(),
|
||||
accountId: z.string().uuid(),
|
||||
});
|
||||
|
||||
export const updateCharacterSchema = createCharacterSchema.partial();
|
||||
|
||||
export const characterFiltersSchema = z.object({
|
||||
search: z.string().optional(),
|
||||
classIds: z.array(z.number()).optional(),
|
||||
serverIds: z.array(z.number()).optional(),
|
||||
accountIds: z.array(z.string()).optional(),
|
||||
minLevel: z.number().optional(),
|
||||
maxLevel: z.number().optional(),
|
||||
page: z.number().int().positive().default(1),
|
||||
limit: z.number().int().min(1).max(100).default(20),
|
||||
sortBy: z.enum(['name', 'level', 'class', 'server']).default('name'),
|
||||
sortOrder: z.enum(['asc', 'desc']).default('asc'),
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
65
docs/architecture/6-components-architecture.md
Normal file
65
docs/architecture/6-components-architecture.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# 6. Components Architecture
|
||||
|
||||
## Component Hierarchy
|
||||
|
||||
```
|
||||
src/components/
|
||||
├── ui/ # shadcn/ui primitives
|
||||
│ ├── button.tsx
|
||||
│ ├── input.tsx
|
||||
│ ├── dialog.tsx
|
||||
│ ├── table.tsx
|
||||
│ ├── select.tsx
|
||||
│ ├── checkbox.tsx
|
||||
│ ├── tabs.tsx
|
||||
│ ├── card.tsx
|
||||
│ ├── badge.tsx
|
||||
│ ├── progress.tsx
|
||||
│ └── ...
|
||||
│
|
||||
├── layout/ # Layout components
|
||||
│ ├── app-shell.tsx # Main layout wrapper
|
||||
│ ├── app-sidebar.tsx # Navigation sidebar
|
||||
│ └── app-header.tsx # Top header with breadcrumbs
|
||||
│
|
||||
├── characters/ # Character feature components
|
||||
│ ├── character-list.tsx
|
||||
│ ├── character-card.tsx
|
||||
│ ├── character-form.tsx
|
||||
│ ├── character-filters.tsx
|
||||
│ └── character-bulk-actions.tsx
|
||||
│
|
||||
├── accounts/ # Account feature components
|
||||
│ ├── account-list.tsx
|
||||
│ ├── account-card.tsx
|
||||
│ └── account-form.tsx
|
||||
│
|
||||
├── teams/ # Team feature components
|
||||
│ ├── team-list.tsx
|
||||
│ ├── team-detail.tsx
|
||||
│ ├── team-form.tsx
|
||||
│ └── team-member-selector.tsx
|
||||
│
|
||||
├── progressions/ # Progression feature components
|
||||
│ ├── progression-section.tsx
|
||||
│ ├── progression-tracker.tsx
|
||||
│ └── progression-filters.tsx
|
||||
│
|
||||
└── shared/ # Shared components
|
||||
├── data-table.tsx
|
||||
├── search-input.tsx
|
||||
├── pagination.tsx
|
||||
├── confirmation-modal.tsx
|
||||
├── loading-spinner.tsx
|
||||
└── error-boundary.tsx
|
||||
```
|
||||
|
||||
## Component Design Principles
|
||||
|
||||
1. **Composition over inheritance** - Prefer composing smaller components
|
||||
2. **Props interface first** - Define TypeScript interfaces for all props
|
||||
3. **Controlled components** - Parent manages state when needed
|
||||
4. **Accessible by default** - Use semantic HTML, ARIA attributes
|
||||
5. **Responsive design** - Mobile-first approach with Tailwind
|
||||
|
||||
---
|
||||
68
docs/architecture/7-external-apis.md
Normal file
68
docs/architecture/7-external-apis.md
Normal file
@@ -0,0 +1,68 @@
|
||||
# 7. External APIs
|
||||
|
||||
## DofusDB API Integration
|
||||
|
||||
**Base URL**: `https://api.dofusdb.fr`
|
||||
|
||||
### Endpoints Used
|
||||
|
||||
| Endpoint | Purpose |
|
||||
|----------|---------|
|
||||
| `GET /classes` | Liste des classes de personnages |
|
||||
| `GET /servers` | Liste des serveurs |
|
||||
| `GET /quests` | Liste des quêtes |
|
||||
| `GET /dungeons` | Liste des donjons |
|
||||
| `GET /achievements` | Liste des succès |
|
||||
|
||||
### Integration Service
|
||||
|
||||
```typescript
|
||||
// src/lib/server/dofusdb.ts
|
||||
import NodeCache from 'node-cache';
|
||||
|
||||
const cache = new NodeCache({ stdTTL: 3600 }); // 1 hour cache
|
||||
const BASE_URL = 'https://api.dofusdb.fr';
|
||||
|
||||
interface DofusDbOptions {
|
||||
lang?: 'fr' | 'en';
|
||||
limit?: number;
|
||||
}
|
||||
|
||||
export async function fetchFromDofusDb<T>(
|
||||
endpoint: string,
|
||||
options: DofusDbOptions = {}
|
||||
): Promise<T> {
|
||||
const { lang = 'fr', limit = 100 } = options;
|
||||
const cacheKey = `dofusdb:${endpoint}:${lang}:${limit}`;
|
||||
|
||||
// Check cache
|
||||
const cached = cache.get<T>(cacheKey);
|
||||
if (cached) return cached;
|
||||
|
||||
// Fetch from API
|
||||
const url = new URL(endpoint, BASE_URL);
|
||||
url.searchParams.set('$lang', lang);
|
||||
url.searchParams.set('$limit', limit.toString());
|
||||
|
||||
const response = await fetch(url.toString(), {
|
||||
headers: { 'Accept': 'application/json' },
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`DofusDB API error: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
cache.set(cacheKey, data);
|
||||
|
||||
return data as T;
|
||||
}
|
||||
|
||||
// Specific fetchers
|
||||
export const getClasses = () => fetchFromDofusDb<DofusClass[]>('/classes');
|
||||
export const getServers = () => fetchFromDofusDb<DofusServer[]>('/servers');
|
||||
export const getQuests = () => fetchFromDofusDb<DofusQuest[]>('/quests');
|
||||
export const getDungeons = () => fetchFromDofusDb<DofusDungeon[]>('/dungeons');
|
||||
```
|
||||
|
||||
---
|
||||
114
docs/architecture/8-core-workflows.md
Normal file
114
docs/architecture/8-core-workflows.md
Normal file
@@ -0,0 +1,114 @@
|
||||
# 8. Core Workflows
|
||||
|
||||
## Character Creation Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ CHARACTER CREATION FLOW │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ Click │───▶│ Open │───▶│ Fill │───▶│ Validate │ │
|
||||
│ │ "Add" │ │ Modal │ │ Form │ │ (Zod) │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ └────┬─────┘ │
|
||||
│ │ │
|
||||
│ ┌──────────────┴───┐ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ Valid │ │ Invalid │ │
|
||||
│ └────┬─────┘ └────┬─────┘ │
|
||||
│ │ │ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌────────────┐ ┌────────────┐ │
|
||||
│ │ Server │ │ Show │ │
|
||||
│ │ Function │ │ Errors │ │
|
||||
│ └─────┬──────┘ └────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌────────────┐ │
|
||||
│ │ Prisma │ │
|
||||
│ │ Create │ │
|
||||
│ └─────┬──────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌────────────┐ │
|
||||
│ │ Invalidate │ │
|
||||
│ │ Query │ │
|
||||
│ └─────┬──────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌────────────┐ │
|
||||
│ │ Toast │ │
|
||||
│ │ Success │ │
|
||||
│ └────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Progression Update Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ PROGRESSION UPDATE FLOW │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ │
|
||||
│ │ Toggle Check │ │
|
||||
│ └──────┬───────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ Optimistic │────▶│ Server │────▶│ Database │ │
|
||||
│ │ Update │ │ Function │ │ Update │ │
|
||||
│ └──────────────┘ └──────────────┘ └──────┬───────┘ │
|
||||
│ │ │ │
|
||||
│ │ ┌──────────┴─────┐ │
|
||||
│ │ ▼ ▼ │
|
||||
│ │ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ │ Success │ │ Error │ │
|
||||
│ │ └────┬─────┘ └────┬─────┘ │
|
||||
│ │ │ │ │
|
||||
│ │ ▼ ▼ │
|
||||
│ │ ┌──────────┐ ┌──────────┐ │
|
||||
│ └────────────────────▶│ Confirm │ │ Rollback │ │
|
||||
│ │ State │ │ + Toast │ │
|
||||
│ └──────────┘ └──────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Bulk Operations Flow
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ BULK OPERATIONS FLOW │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||
│ │ Select │───▶│ Choose │───▶│ Confirm │───▶│ Process │ │
|
||||
│ │ Items │ │ Action │ │ Modal │ │ Batch │ │
|
||||
│ └──────────┘ └──────────┘ └──────────┘ └────┬─────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌────────────┐ │
|
||||
│ │ Loading │ │
|
||||
│ │ State │ │
|
||||
│ └─────┬──────┘ │
|
||||
│ │ │
|
||||
│ ┌─────────────┴────────┐ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌────────────┐ ┌────────┐│
|
||||
│ │ Success │ │ Partial││
|
||||
│ │ Toast │ │ Failure││
|
||||
│ └─────┬──────┘ └───┬────┘│
|
||||
│ │ │ │
|
||||
│ ▼ ▼ │
|
||||
│ ┌────────────┐ ┌──────────┐ │
|
||||
│ │ Invalidate │ │ Show │ │
|
||||
│ │ Queries │ │ Errors │ │
|
||||
│ └────────────┘ └──────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
175
docs/architecture/9-database-schema.md
Normal file
175
docs/architecture/9-database-schema.md
Normal file
@@ -0,0 +1,175 @@
|
||||
# 9. Database Schema
|
||||
|
||||
## Prisma Schema
|
||||
|
||||
```prisma
|
||||
// prisma/schema.prisma
|
||||
|
||||
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")
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
36
docs/architecture/architecture-summary.md
Normal file
36
docs/architecture/architecture-summary.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Architecture Summary
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ DOFUS MANAGER - FINAL ARCHITECTURE │
|
||||
├─────────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ Frontend (React + TanStack) Backend (TanStack Start) │
|
||||
│ ┌─────────────────────────┐ ┌─────────────────────────┐ │
|
||||
│ │ • TanStack Router │ │ • Server Functions │ │
|
||||
│ │ • TanStack Query │◀────────▶│ • Prisma ORM │ │
|
||||
│ │ • Zustand (UI state) │ RPC │ • Zod Validation │ │
|
||||
│ │ • shadcn/ui + Tailwind │ │ • node-cache │ │
|
||||
│ └─────────────────────────┘ └───────────┬─────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────┐ │
|
||||
│ │ PostgreSQL 16 │ │
|
||||
│ │ • 7 tables │ │
|
||||
│ │ • Full-text search │ │
|
||||
│ └─────────────────────────┘ │
|
||||
│ │
|
||||
│ Infrastructure │
|
||||
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Docker Compose + Traefik (reverse proxy + SSL) │ │
|
||||
│ │ GitLab CI/CD → VPS Deployment │ │
|
||||
│ └─────────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*Document généré avec BMAD Framework*
|
||||
*Version: 1.0.0*
|
||||
*Date: 2026-01-18*
|
||||
113
docs/architecture/index.md
Normal file
113
docs/architecture/index.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Dofus Manager - Architecture Document
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Dofus Manager - Architecture Document](#table-of-contents)
|
||||
- [Table of Contents](./table-of-contents.md)
|
||||
- [1. Introduction](./1-introduction.md)
|
||||
- [Project Overview](./1-introduction.md#project-overview)
|
||||
- [Goals](./1-introduction.md#goals)
|
||||
- [Scope](./1-introduction.md#scope)
|
||||
- [Non-Functional Requirements](./1-introduction.md#non-functional-requirements)
|
||||
- [2. High-Level Architecture](./2-high-level-architecture.md)
|
||||
- [System Architecture Diagram](./2-high-level-architecture.md#system-architecture-diagram)
|
||||
- [Data Flow](./2-high-level-architecture.md#data-flow)
|
||||
- [Key Architectural Decisions](./2-high-level-architecture.md#key-architectural-decisions)
|
||||
- [3. Technology Stack](./3-technology-stack.md)
|
||||
- [Frontend](./3-technology-stack.md#frontend)
|
||||
- [Backend](./3-technology-stack.md#backend)
|
||||
- [Database](./3-technology-stack.md#database)
|
||||
- [DevOps](./3-technology-stack.md#devops)
|
||||
- [Development Tools](./3-technology-stack.md#development-tools)
|
||||
- [4. Data Models](./4-data-models.md)
|
||||
- [Entity Relationship Diagram](./4-data-models.md#entity-relationship-diagram)
|
||||
- [Core Entities](./4-data-models.md#core-entities)
|
||||
- [User](./4-data-models.md#user)
|
||||
- [Account](./4-data-models.md#account)
|
||||
- [Character](./4-data-models.md#character)
|
||||
- [Team](./4-data-models.md#team)
|
||||
- [Progression](./4-data-models.md#progression)
|
||||
- [CharacterProgression](./4-data-models.md#characterprogression)
|
||||
- [5. API Specification](./5-api-specification.md)
|
||||
- [Server Functions Pattern](./5-api-specification.md#server-functions-pattern)
|
||||
- [API Endpoints (Server Functions)](./5-api-specification.md#api-endpoints-server-functions)
|
||||
- [Characters](./5-api-specification.md#characters)
|
||||
- [Accounts](./5-api-specification.md#accounts)
|
||||
- [Teams](./5-api-specification.md#teams)
|
||||
- [Progressions](./5-api-specification.md#progressions)
|
||||
- [Auth](./5-api-specification.md#auth)
|
||||
- [Validation Schemas](./5-api-specification.md#validation-schemas)
|
||||
- [6. Components Architecture](./6-components-architecture.md)
|
||||
- [Component Hierarchy](./6-components-architecture.md#component-hierarchy)
|
||||
- [Component Design Principles](./6-components-architecture.md#component-design-principles)
|
||||
- [7. External APIs](./7-external-apis.md)
|
||||
- [DofusDB API Integration](./7-external-apis.md#dofusdb-api-integration)
|
||||
- [Endpoints Used](./7-external-apis.md#endpoints-used)
|
||||
- [Integration Service](./7-external-apis.md#integration-service)
|
||||
- [8. Core Workflows](./8-core-workflows.md)
|
||||
- [Character Creation Flow](./8-core-workflows.md#character-creation-flow)
|
||||
- [Progression Update Flow](./8-core-workflows.md#progression-update-flow)
|
||||
- [Bulk Operations Flow](./8-core-workflows.md#bulk-operations-flow)
|
||||
- [9. Database Schema](./9-database-schema.md)
|
||||
- [Prisma Schema](./9-database-schema.md#prisma-schema)
|
||||
- [10. Frontend Architecture](./10-frontend-architecture.md)
|
||||
- [State Management Strategy](./10-frontend-architecture.md#state-management-strategy)
|
||||
- [TanStack Query Setup](./10-frontend-architecture.md#tanstack-query-setup)
|
||||
- [Zustand Store](./10-frontend-architecture.md#zustand-store)
|
||||
- [Routing Structure](./10-frontend-architecture.md#routing-structure)
|
||||
- [11. Backend Architecture](./11-backend-architecture.md)
|
||||
- [Server Functions Organization](./11-backend-architecture.md#server-functions-organization)
|
||||
- [Server Function Example](./11-backend-architecture.md#server-function-example)
|
||||
- [Authentication Middleware](./11-backend-architecture.md#authentication-middleware)
|
||||
- [Caching Strategy](./11-backend-architecture.md#caching-strategy)
|
||||
- [12. Project Structure](./12-project-structure.md)
|
||||
- [13. Development Workflow](./13-development-workflow.md)
|
||||
- [Local Development Setup](./13-development-workflow.md#local-development-setup)
|
||||
- [Environment Variables](./13-development-workflow.md#environment-variables)
|
||||
- [Git Workflow](./13-development-workflow.md#git-workflow)
|
||||
- [Branch Naming](./13-development-workflow.md#branch-naming)
|
||||
- [Commit Convention](./13-development-workflow.md#commit-convention)
|
||||
- [14. Deployment Architecture](./14-deployment-architecture.md)
|
||||
- [Docker Configuration](./14-deployment-architecture.md#docker-configuration)
|
||||
- [Docker Compose (Production)](./14-deployment-architecture.md#docker-compose-production)
|
||||
- [GitLab CI/CD Pipeline](./14-deployment-architecture.md#gitlab-cicd-pipeline)
|
||||
- [15. Security & Performance](./15-security-performance.md)
|
||||
- [Security Measures](./15-security-performance.md#security-measures)
|
||||
- [Authentication](./15-security-performance.md#authentication)
|
||||
- [Input Validation](./15-security-performance.md#input-validation)
|
||||
- [Headers (via Traefik)](./15-security-performance.md#headers-via-traefik)
|
||||
- [Performance Optimizations](./15-security-performance.md#performance-optimizations)
|
||||
- [Database](./15-security-performance.md#database)
|
||||
- [Caching](./15-security-performance.md#caching)
|
||||
- [Frontend](./15-security-performance.md#frontend)
|
||||
- [Bundle Optimization](./15-security-performance.md#bundle-optimization)
|
||||
- [16. Testing Strategy](./16-testing-strategy.md)
|
||||
- [Testing Pyramid](./16-testing-strategy.md#testing-pyramid)
|
||||
- [Unit Tests (Vitest)](./16-testing-strategy.md#unit-tests-vitest)
|
||||
- [Integration Tests](./16-testing-strategy.md#integration-tests)
|
||||
- [E2E Tests (Playwright)](./16-testing-strategy.md#e2e-tests-playwright)
|
||||
- [17. Coding Standards](./17-coding-standards.md)
|
||||
- [Critical Fullstack Rules](./17-coding-standards.md#critical-fullstack-rules)
|
||||
- [Naming Conventions](./17-coding-standards.md#naming-conventions)
|
||||
- [File Structure Conventions](./17-coding-standards.md#file-structure-conventions)
|
||||
- [Component File Structure](./17-coding-standards.md#component-file-structure)
|
||||
- [Server Function File Structure](./17-coding-standards.md#server-function-file-structure)
|
||||
- [Code Quality Rules](./17-coding-standards.md#code-quality-rules)
|
||||
- [TypeScript](./17-coding-standards.md#typescript)
|
||||
- [React](./17-coding-standards.md#react)
|
||||
- [Prisma](./17-coding-standards.md#prisma)
|
||||
- [Import Order](./17-coding-standards.md#import-order)
|
||||
- [ESLint & Prettier Config](./17-coding-standards.md#eslint-prettier-config)
|
||||
- [.eslintrc.cjs](./17-coding-standards.md#eslintrccjs)
|
||||
- [.prettierrc](./17-coding-standards.md#prettierrc)
|
||||
- [Git Commit Standards](./17-coding-standards.md#git-commit-standards)
|
||||
- [18. Error Handling](./18-error-handling.md)
|
||||
- [Unified Error Types](./18-error-handling.md#unified-error-types)
|
||||
- [Backend Error Handler](./18-error-handling.md#backend-error-handler)
|
||||
- [Frontend Error Handler](./18-error-handling.md#frontend-error-handler)
|
||||
- [19. Monitoring & Observability](./19-monitoring-observability.md)
|
||||
- [Structured Logging (Pino)](./19-monitoring-observability.md#structured-logging-pino)
|
||||
- [Health Check Endpoint](./19-monitoring-observability.md#health-check-endpoint)
|
||||
- [Application Metrics](./19-monitoring-observability.md#application-metrics)
|
||||
- [20. Architecture Checklist](./20-architecture-checklist.md)
|
||||
- [Architecture Summary](./architecture-summary.md)
|
||||
24
docs/architecture/table-of-contents.md
Normal file
24
docs/architecture/table-of-contents.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Table of Contents
|
||||
|
||||
1. [Introduction](#1-introduction)
|
||||
2. [High-Level Architecture](#2-high-level-architecture)
|
||||
3. [Technology Stack](#3-technology-stack)
|
||||
4. [Data Models](#4-data-models)
|
||||
5. [API Specification](#5-api-specification)
|
||||
6. [Components Architecture](#6-components-architecture)
|
||||
7. [External APIs](#7-external-apis)
|
||||
8. [Core Workflows](#8-core-workflows)
|
||||
9. [Database Schema](#9-database-schema)
|
||||
10. [Frontend Architecture](#10-frontend-architecture)
|
||||
11. [Backend Architecture](#11-backend-architecture)
|
||||
12. [Project Structure](#12-project-structure)
|
||||
13. [Development Workflow](#13-development-workflow)
|
||||
14. [Deployment Architecture](#14-deployment-architecture)
|
||||
15. [Security & Performance](#15-security--performance)
|
||||
16. [Testing Strategy](#16-testing-strategy)
|
||||
17. [Coding Standards](#17-coding-standards)
|
||||
18. [Error Handling](#18-error-handling)
|
||||
19. [Monitoring & Observability](#19-monitoring--observability)
|
||||
20. [Architecture Checklist](#20-architecture-checklist)
|
||||
|
||||
---
|
||||
Reference in New Issue
Block a user