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`.
16 KiB
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.
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.
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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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.
// 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
// 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
// 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
// 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 :
// 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 uniquesvarchar
avec longueur limitée pour les chaînes de caractèresjsonb
pour les données structurées flexiblestimestamp
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
// 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
// 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.