brief-20/docs/implementation/DATABASE_SCHEMA_PLAN.md
Avnyr cf292de428 docs: update documentation to reflect module completion and testing progress
Updated `PROJECT_STATUS.md` with completed modules (`auth`, `groups`, `tags`) and unit testing progress, including marked tests for controllers and services as done. Added logical and conceptual database models (`DATABASE_SCHEMA_PLAN.md`) and revised implementation statuses in `IMPLEMENTATION_GUIDE.md`.
2025-05-15 20:57:59 +02:00

572 lines
16 KiB
Markdown

# Plan du Schéma de Base de Données
Ce document détaille le plan du schéma de base de données pour l'application de création de groupes, utilisant DrizzleORM avec PostgreSQL.
## 1. Vue d'Ensemble
Le schéma de base de données est conçu pour supporter les fonctionnalités suivantes :
- Gestion des utilisateurs et authentification
- Création et gestion de projets
- Gestion des personnes et de leurs attributs
- Création et gestion de groupes
- Système de tags pour catégoriser les personnes et les projets
### 1.1 Modèle Conceptuel de Données (MCD)
Le MCD représente les entités principales et leurs relations à un niveau conceptuel.
```mermaid
erDiagram
USER ||--o{ PROJECT : "possède"
USER ||--o{ PROJECT_COLLABORATOR : "collabore sur"
PROJECT ||--o{ PERSON : "contient"
PROJECT ||--o{ GROUP : "organise"
PROJECT ||--o{ PROJECT_COLLABORATOR : "a des collaborateurs"
PROJECT }o--o{ TAG : "est catégorisé par"
PERSON }o--o{ GROUP : "appartient à"
PERSON }o--o{ TAG : "est catégorisé par"
USER {
uuid id PK
string githubId
string name
string avatar
string role
datetime gdprTimestamp
}
PROJECT {
uuid id PK
string name
string description
json settings
uuid ownerId FK
boolean isPublic
}
PERSON {
uuid id PK
string name
string email
int technicalLevel
string gender
json attributes
uuid projectId FK
}
GROUP {
uuid id PK
string name
string description
json settings
uuid projectId FK
}
TAG {
uuid id PK
string name
string description
string color
enum type
}
PROJECT_COLLABORATOR {
uuid projectId FK
uuid userId FK
}
```
### 1.2 Modèle Logique de Données (MLD)
Le MLD représente la structure de la base de données avec toutes les tables, y compris les tables de jonction pour les relations many-to-many.
```mermaid
erDiagram
users ||--o{ projects : "owns"
users ||--o{ project_collaborators : "collaborates on"
projects ||--o{ persons : "contains"
projects ||--o{ groups : "organizes"
projects ||--o{ project_collaborators : "has collaborators"
projects ||--o{ project_to_tag : "is categorized by"
persons ||--o{ person_to_group : "belongs to"
persons ||--o{ person_to_tag : "is categorized by"
groups ||--o{ person_to_group : "contains"
tags ||--o{ person_to_tag : "categorizes"
tags ||--o{ project_to_tag : "categorizes"
users {
uuid id PK
string github_id
string name
string avatar
string role
datetime gdpr_timestamp
datetime created_at
datetime updated_at
}
projects {
uuid id PK
string name
string description
json settings
uuid owner_id FK
boolean is_public
datetime created_at
datetime updated_at
}
persons {
uuid id PK
string name
string email
int technical_level
string gender
json attributes
uuid project_id FK
datetime created_at
datetime updated_at
}
groups {
uuid id PK
string name
string description
json settings
uuid project_id FK
datetime created_at
datetime updated_at
}
tags {
uuid id PK
string name
string description
string color
enum type
datetime created_at
datetime updated_at
}
person_to_group {
uuid id PK
uuid person_id FK
uuid group_id FK
datetime created_at
}
person_to_tag {
uuid id PK
uuid person_id FK
uuid tag_id FK
datetime created_at
}
project_to_tag {
uuid id PK
uuid project_id FK
uuid tag_id FK
datetime created_at
}
project_collaborators {
uuid id PK
uuid project_id FK
uuid user_id FK
datetime created_at
}
```
## 2. Tables Principales
### 2.1 Table `users`
Stocke les informations des utilisateurs de l'application.
```typescript
// src/database/schema/users.ts
import { pgTable, uuid, varchar, timestamp, boolean } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: uuid('id').defaultRandom().primaryKey(),
githubId: varchar('github_id', { length: 255 }).notNull().unique(),
name: varchar('name', { length: 255 }).notNull(),
avatar: varchar('avatar', { length: 1024 }),
role: varchar('role', { length: 50 }).notNull().default('USER'),
gdprTimestamp: timestamp('gdpr_timestamp').notNull(),
createdAt: timestamp('created_at').notNull().defaultNow(),
updatedAt: timestamp('updated_at').notNull().defaultNow(),
});
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
```
### 2.2 Table `projects`
Stocke les informations des projets créés par les utilisateurs.
```typescript
// src/database/schema/projects.ts
import { pgTable, uuid, varchar, text, timestamp, boolean, jsonb } from 'drizzle-orm/pg-core';
import { users } from './users';
export const projects = pgTable('projects', {
id: uuid('id').defaultRandom().primaryKey(),
name: varchar('name', { length: 255 }).notNull(),
description: text('description'),
settings: jsonb('settings').notNull().default({}),
userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
isPublic: boolean('is_public').notNull().default(false),
createdAt: timestamp('created_at').notNull().defaultNow(),
updatedAt: timestamp('updated_at').notNull().defaultNow(),
});
export type Project = typeof projects.$inferSelect;
export type NewProject = typeof projects.$inferInsert;
```
### 2.3 Table `persons`
Stocke les informations des personnes qui seront placées dans les groupes.
```typescript
// src/database/schema/persons.ts
import { pgTable, uuid, varchar, text, integer, timestamp, jsonb } from 'drizzle-orm/pg-core';
import { projects } from './projects';
export const persons = pgTable('persons', {
id: uuid('id').defaultRandom().primaryKey(),
name: varchar('name', { length: 255 }).notNull(),
email: varchar('email', { length: 255 }),
technicalLevel: integer('technical_level'),
gender: varchar('gender', { length: 50 }),
attributes: jsonb('attributes').notNull().default({}),
projectId: uuid('project_id').notNull().references(() => projects.id, { onDelete: 'cascade' }),
createdAt: timestamp('created_at').notNull().defaultNow(),
updatedAt: timestamp('updated_at').notNull().defaultNow(),
});
export type Person = typeof persons.$inferSelect;
export type NewPerson = typeof persons.$inferInsert;
```
### 2.4 Table `groups`
Stocke les informations des groupes créés dans le cadre d'un projet.
```typescript
// src/database/schema/groups.ts
import { pgTable, uuid, varchar, text, timestamp, jsonb } from 'drizzle-orm/pg-core';
import { projects } from './projects';
export const groups = pgTable('groups', {
id: uuid('id').defaultRandom().primaryKey(),
name: varchar('name', { length: 255 }).notNull(),
description: text('description'),
settings: jsonb('settings').notNull().default({}),
projectId: uuid('project_id').notNull().references(() => projects.id, { onDelete: 'cascade' }),
createdAt: timestamp('created_at').notNull().defaultNow(),
updatedAt: timestamp('updated_at').notNull().defaultNow(),
});
export type Group = typeof groups.$inferSelect;
export type NewGroup = typeof groups.$inferInsert;
```
### 2.5 Table `tags`
Stocke les tags qui peuvent être associés aux personnes et aux projets.
```typescript
// src/database/schema/tags.ts
import { pgTable, uuid, varchar, text, timestamp, pgEnum } from 'drizzle-orm/pg-core';
export const tagTypeEnum = pgEnum('tag_type', ['PROJECT', 'PERSON']);
export const tags = pgTable('tags', {
id: uuid('id').defaultRandom().primaryKey(),
name: varchar('name', { length: 255 }).notNull(),
description: text('description'),
color: varchar('color', { length: 50 }),
type: tagTypeEnum('type').notNull(),
createdAt: timestamp('created_at').notNull().defaultNow(),
updatedAt: timestamp('updated_at').notNull().defaultNow(),
});
export type Tag = typeof tags.$inferSelect;
export type NewTag = typeof tags.$inferInsert;
```
## 3. Tables de Relations
### 3.1 Table `personToGroup`
Relation many-to-many entre les personnes et les groupes.
```typescript
// src/database/schema/personToGroup.ts
import { pgTable, uuid, primaryKey } from 'drizzle-orm/pg-core';
import { persons } from './persons';
import { groups } from './groups';
export const personToGroup = pgTable('person_to_group', {
personId: uuid('person_id').notNull().references(() => persons.id, { onDelete: 'cascade' }),
groupId: uuid('group_id').notNull().references(() => groups.id, { onDelete: 'cascade' }),
}, (t) => ({
pk: primaryKey({ columns: [t.personId, t.groupId] }),
}));
```
### 3.2 Table `personToTag`
Relation many-to-many entre les personnes et les tags.
```typescript
// src/database/schema/personToTag.ts
import { pgTable, uuid, primaryKey } from 'drizzle-orm/pg-core';
import { persons } from './persons';
import { tags } from './tags';
export const personToTag = pgTable('person_to_tag', {
personId: uuid('person_id').notNull().references(() => persons.id, { onDelete: 'cascade' }),
tagId: uuid('tag_id').notNull().references(() => tags.id, { onDelete: 'cascade' }),
}, (t) => ({
pk: primaryKey({ columns: [t.personId, t.tagId] }),
}));
```
### 3.3 Table `projectToTag`
Relation many-to-many entre les projets et les tags.
```typescript
// src/database/schema/projectToTag.ts
import { pgTable, uuid, primaryKey } from 'drizzle-orm/pg-core';
import { projects } from './projects';
import { tags } from './tags';
export const projectToTag = pgTable('project_to_tag', {
projectId: uuid('project_id').notNull().references(() => projects.id, { onDelete: 'cascade' }),
tagId: uuid('tag_id').notNull().references(() => tags.id, { onDelete: 'cascade' }),
}, (t) => ({
pk: primaryKey({ columns: [t.projectId, t.tagId] }),
}));
```
### 3.4 Table `projectCollaborators`
Relation many-to-many entre les projets et les utilisateurs pour la collaboration.
```typescript
// src/database/schema/projectCollaborators.ts
import { pgTable, uuid, primaryKey, varchar } from 'drizzle-orm/pg-core';
import { projects } from './projects';
import { users } from './users';
export const projectCollaborators = pgTable('project_collaborators', {
projectId: uuid('project_id').notNull().references(() => projects.id, { onDelete: 'cascade' }),
userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
role: varchar('role', { length: 50 }).notNull().default('VIEWER'),
}, (t) => ({
pk: primaryKey({ columns: [t.projectId, t.userId] }),
}));
```
## 4. Fichier d'Index
Fichier qui exporte toutes les tables pour faciliter leur utilisation.
```typescript
// src/database/schema/index.ts
export * from './users';
export * from './projects';
export * from './persons';
export * from './groups';
export * from './tags';
export * from './personToGroup';
export * from './personToTag';
export * from './projectToTag';
export * from './projectCollaborators';
```
## 5. Configuration de DrizzleORM
### 5.1 Module de Base de Données
```typescript
// src/database/database.module.ts
import { Module, Global } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';
import * as schema from './schema';
@Global()
@Module({
imports: [ConfigModule],
providers: [
{
provide: 'DATABASE_CONNECTION',
inject: [ConfigService],
useFactory: async (configService: ConfigService) => {
const connectionString = configService.get<string>('DATABASE_URL');
const pool = new Pool({ connectionString });
return drizzle(pool, { schema });
},
},
],
exports: ['DATABASE_CONNECTION'],
})
export class DatabaseModule {}
```
## 6. Migrations
### 6.1 Configuration des Migrations
```typescript
// drizzle.config.ts
import type { Config } from 'drizzle-kit';
import * as dotenv from 'dotenv';
dotenv.config();
export default {
schema: './src/database/schema/*.ts',
out: './src/database/migrations',
driver: 'pg',
dbCredentials: {
connectionString: process.env.DATABASE_URL,
},
} satisfies Config;
```
### 6.2 Script de Migration
```typescript
// src/database/migrations/migrate.ts
import { drizzle } from 'drizzle-orm/node-postgres';
import { migrate } from 'drizzle-orm/node-postgres/migrator';
import { Pool } from 'pg';
import * as dotenv from 'dotenv';
dotenv.config();
async function main() {
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
const db = drizzle(pool);
console.log('Running migrations...');
await migrate(db, { migrationsFolder: './src/database/migrations' });
console.log('Migrations completed successfully!');
await pool.end();
}
main().catch((err) => {
console.error('Migration failed!', err);
process.exit(1);
});
```
## 7. Optimisations
### 7.1 Indexation
Pour optimiser les performances des requêtes fréquentes, nous ajouterons des index sur les colonnes suivantes :
```typescript
// Exemple d'ajout d'index sur la table persons
import { pgTable, uuid, varchar, text, integer, timestamp, jsonb, index } from 'drizzle-orm/pg-core';
import { projects } from './projects';
export const persons = pgTable('persons', {
id: uuid('id').defaultRandom().primaryKey(),
name: varchar('name', { length: 255 }).notNull(),
email: varchar('email', { length: 255 }),
technicalLevel: integer('technical_level'),
gender: varchar('gender', { length: 50 }),
attributes: jsonb('attributes').notNull().default({}),
projectId: uuid('project_id').notNull().references(() => projects.id, { onDelete: 'cascade' }),
createdAt: timestamp('created_at').notNull().defaultNow(),
updatedAt: timestamp('updated_at').notNull().defaultNow(),
}, (table) => ({
nameIdx: index('person_name_idx').on(table.name),
projectIdIdx: index('person_project_id_idx').on(table.projectId),
technicalLevelIdx: index('person_technical_level_idx').on(table.technicalLevel),
}));
```
### 7.2 Types de Données Optimisés
Utilisation de types de données optimisés pour réduire l'espace de stockage et améliorer les performances :
- `uuid` pour les identifiants uniques
- `varchar` avec longueur limitée pour les chaînes de caractères
- `jsonb` pour les données structurées flexibles
- `timestamp` pour les dates et heures
### 7.3 Contraintes d'Intégrité
Utilisation de contraintes d'intégrité référentielle pour garantir la cohérence des données :
- Clés primaires sur toutes les tables
- Clés étrangères avec actions en cascade pour la suppression
- Contraintes d'unicité sur les colonnes appropriées
## 8. Requêtes Communes
### 8.1 Récupération d'un Projet avec ses Personnes et Groupes
```typescript
// Exemple de requête pour récupérer un projet avec ses personnes et groupes
const getProjectWithPersonsAndGroups = async (db, projectId) => {
const project = await db.query.projects.findFirst({
where: (projects, { eq }) => eq(projects.id, projectId),
with: {
persons: true,
groups: {
with: {
persons: true,
},
},
},
});
return project;
};
```
### 8.2 Récupération des Personnes avec leurs Tags
```typescript
// Exemple de requête pour récupérer les personnes avec leurs tags
const getPersonsWithTags = async (db, projectId) => {
const persons = await db.query.persons.findMany({
where: (persons, { eq }) => eq(persons.projectId, projectId),
with: {
tags: {
columns: {
name: true,
color: true,
},
},
},
});
return persons;
};
```
## 9. Conclusion
Ce schéma de base de données fournit une structure solide pour l'application de création de groupes, avec une conception qui prend en compte les performances, la flexibilité et l'intégrité des données. Les relations entre les entités sont clairement définies, et les types de données sont optimisés pour les besoins de l'application.
L'utilisation de DrizzleORM permet une intégration transparente avec NestJS et offre une expérience de développement type-safe, facilitant la maintenance et l'évolution du schéma au fil du temps.