From b22129c4dd27759892b3374a63634b8c91ff0a3d Mon Sep 17 00:00:00 2001 From: Mathis HERRIOT <197931332+0x485254@users.noreply.github.com> Date: Mon, 5 Jan 2026 15:59:15 +0100 Subject: [PATCH] chore: reformat database schema files for readability Applied consistent formatting and indentation across all schema files using Drizzle ORM to enhance code clarity and maintainability. --- backend/src/database/database.service.ts | 28 +++--- backend/src/database/schemas/api_keys.ts | 51 +++++++---- backend/src/database/schemas/audit_logs.ts | 60 ++++++++----- backend/src/database/schemas/content.ts | 79 +++++++++++------ backend/src/database/schemas/index.ts | 16 ++-- backend/src/database/schemas/rbac.ts | 99 +++++++++++++++------- backend/src/database/schemas/reports.ts | 86 +++++++++++++------ backend/src/database/schemas/sessions.ts | 51 +++++++---- backend/src/database/schemas/tags.ts | 28 +++--- backend/src/database/schemas/users.ts | 97 +++++++++++++-------- 10 files changed, 388 insertions(+), 207 deletions(-) diff --git a/backend/src/database/database.service.ts b/backend/src/database/database.service.ts index e42c837..9235013 100644 --- a/backend/src/database/database.service.ts +++ b/backend/src/database/database.service.ts @@ -6,28 +6,30 @@ * is strictly prohibited. */ -import {Injectable, Logger, OnModuleDestroy, OnModuleInit} from "@nestjs/common"; +import { + Injectable, + Logger, + OnModuleDestroy, + OnModuleInit, +} from "@nestjs/common"; import { ConfigService } from "@nestjs/config"; import { drizzle } from "drizzle-orm/node-postgres"; import { migrate } from "drizzle-orm/node-postgres/migrator"; import * as schema from "./schemas"; -import {Pool} from "pg"; +import { Pool } from "pg"; @Injectable() export class DatabaseService implements OnModuleInit, OnModuleDestroy { private readonly logger = new Logger(DatabaseService.name); - private readonly pool!: Pool + private readonly pool!: Pool; public readonly db: ReturnType; - constructor( - private configService: ConfigService - ) { + constructor(private configService: ConfigService) { // Create the PostgreSQL client const connectionString = this.getDatabaseConnectionString(); this.pool = new Pool({ connectionString }); - // Recreate drizzle with initialized pool - this.db = drizzle(this.pool, { schema }); - + // Recreate drizzle with initialized pool + this.db = drizzle(this.pool, { schema }); } async onModuleInit() { @@ -59,7 +61,9 @@ export class DatabaseService implements OnModuleInit, OnModuleDestroy { // Get the database connection string from environment variables private getDatabaseConnectionString(): string { - this.logger.debug('Getting database connection string from environment variables'); + this.logger.debug( + "Getting database connection string from environment variables", + ); const password = this.configService.get("POSTGRES_PASSWORD"); const username = this.configService.get("POSTGRES_USER"); @@ -80,7 +84,9 @@ export class DatabaseService implements OnModuleInit, OnModuleDestroy { throw new Error(errorMessage); } - this.logger.debug(`Database connection configured for ${username}@${host}:${port}/${database}`); + this.logger.debug( + `Database connection configured for ${username}@${host}:${port}/${database}`, + ); return `postgres://${username}:${password}@${host}:${port}/${database}`; } } diff --git a/backend/src/database/schemas/api_keys.ts b/backend/src/database/schemas/api_keys.ts index ea58bbe..20b3eef 100644 --- a/backend/src/database/schemas/api_keys.ts +++ b/backend/src/database/schemas/api_keys.ts @@ -1,18 +1,35 @@ -import { pgTable, varchar, timestamp, uuid, index, boolean } from 'drizzle-orm/pg-core'; -import { users } from './users'; +import { + pgTable, + varchar, + timestamp, + uuid, + index, + boolean, +} from "drizzle-orm/pg-core"; +import { users } from "./users"; -export const apiKeys = pgTable('api_keys', { - id: uuid('id').primaryKey().defaultRandom(), - userId: uuid('user_id').notNull().references(() => users.uuid, { onDelete: 'cascade' }), - keyHash: varchar('key_hash', { length: 128 }).notNull().unique(), // Haché pour la sécurité (SHA-256) - name: varchar('name', { length: 128 }).notNull(), // Nom donné par l'utilisateur (ex: "My App") - prefix: varchar('prefix', { length: 8 }).notNull(), // Pour identification visuelle (ex: "mg_...") - isActive: boolean('is_active').notNull().default(true), - lastUsedAt: timestamp('last_used_at', { withTimezone: true }), - expiresAt: timestamp('expires_at', { withTimezone: true }), - createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), - updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), -}, (table) => ({ - userIdIdx: index('api_keys_user_id_idx').on(table.userId), - keyHashIdx: index('api_keys_key_hash_idx').on(table.keyHash), -})); +export const apiKeys = pgTable( + "api_keys", + { + id: uuid("id").primaryKey().defaultRandom(), + userId: uuid("user_id") + .notNull() + .references(() => users.uuid, { onDelete: "cascade" }), + keyHash: varchar("key_hash", { length: 128 }).notNull().unique(), // Haché pour la sécurité (SHA-256) + name: varchar("name", { length: 128 }).notNull(), // Nom donné par l'utilisateur (ex: "My App") + prefix: varchar("prefix", { length: 8 }).notNull(), // Pour identification visuelle (ex: "mg_...") + isActive: boolean("is_active").notNull().default(true), + lastUsedAt: timestamp("last_used_at", { withTimezone: true }), + expiresAt: timestamp("expires_at", { withTimezone: true }), + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }) + .notNull() + .defaultNow(), + }, + (table) => ({ + userIdIdx: index("api_keys_user_id_idx").on(table.userId), + keyHashIdx: index("api_keys_key_hash_idx").on(table.keyHash), + }), +); diff --git a/backend/src/database/schemas/audit_logs.ts b/backend/src/database/schemas/audit_logs.ts index 7e25c4a..2b0793a 100644 --- a/backend/src/database/schemas/audit_logs.ts +++ b/backend/src/database/schemas/audit_logs.ts @@ -1,25 +1,43 @@ -import { pgTable, varchar, timestamp, uuid, index, jsonb } from 'drizzle-orm/pg-core'; -import { users } from './users'; +import { + pgTable, + varchar, + timestamp, + uuid, + index, + jsonb, +} from "drizzle-orm/pg-core"; +import { users } from "./users"; -export const auditLogs = pgTable('audit_logs', { - id: uuid('id').primaryKey().defaultRandom(), - userId: uuid('user_id').references(() => users.uuid, { onDelete: 'set null' }), // L'utilisateur qui a fait l'action - action: varchar('action', { length: 64 }).notNull(), // ex: 'PII_ACCESS', 'USER_DELETE', 'ROLE_CHANGE' - entityType: varchar('entity_type', { length: 64 }).notNull(), // ex: 'users', 'contents' - entityId: uuid('entity_id'), // ID de l'entité concernée - - // Détails de l'action pour la conformité - details: jsonb('details'), // Données supplémentaires (ex: quelles colonnes ont changé) - ipHash: varchar('ip_hash', { length: 64 }), // IP de l'auteur (hachée pour RGPD) - userAgent: varchar('user_agent', { length: 255 }), - - createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), -}, (table) => ({ - userIdIdx: index('audit_logs_user_id_idx').on(table.userId), - actionIdx: index('audit_logs_action_idx').on(table.action), - entityIdx: index('audit_logs_entity_idx').on(table.entityType, table.entityId), - createdAtIdx: index('audit_logs_created_at_idx').on(table.createdAt), -})); +export const auditLogs = pgTable( + "audit_logs", + { + id: uuid("id").primaryKey().defaultRandom(), + userId: uuid("user_id").references(() => users.uuid, { + onDelete: "set null", + }), // L'utilisateur qui a fait l'action + action: varchar("action", { length: 64 }).notNull(), // ex: 'PII_ACCESS', 'USER_DELETE', 'ROLE_CHANGE' + entityType: varchar("entity_type", { length: 64 }).notNull(), // ex: 'users', 'contents' + entityId: uuid("entity_id"), // ID de l'entité concernée + + // Détails de l'action pour la conformité + details: jsonb("details"), // Données supplémentaires (ex: quelles colonnes ont changé) + ipHash: varchar("ip_hash", { length: 64 }), // IP de l'auteur (hachée pour RGPD) + userAgent: varchar("user_agent", { length: 255 }), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + }, + (table) => ({ + userIdIdx: index("audit_logs_user_id_idx").on(table.userId), + actionIdx: index("audit_logs_action_idx").on(table.action), + entityIdx: index("audit_logs_entity_idx").on( + table.entityType, + table.entityId, + ), + createdAtIdx: index("audit_logs_created_at_idx").on(table.createdAt), + }), +); export type AuditLogInDb = typeof auditLogs.$inferSelect; export type NewAuditLogInDb = typeof auditLogs.$inferInsert; diff --git a/backend/src/database/schemas/content.ts b/backend/src/database/schemas/content.ts index 311b43e..9782fa3 100644 --- a/backend/src/database/schemas/content.ts +++ b/backend/src/database/schemas/content.ts @@ -1,32 +1,59 @@ -import { pgTable, varchar, timestamp, uuid, pgEnum, index, primaryKey, integer } from 'drizzle-orm/pg-core'; -import { users } from './users'; -import { tags } from './tags'; +import { + pgTable, + varchar, + timestamp, + uuid, + pgEnum, + index, + primaryKey, + integer, +} from "drizzle-orm/pg-core"; +import { users } from "./users"; +import { tags } from "./tags"; -export const contentType = pgEnum('content_type', ['meme', 'gif']); +export const contentType = pgEnum("content_type", ["meme", "gif"]); -export const contents = pgTable('contents', { - id: uuid('id').primaryKey().defaultRandom(), - userId: uuid('user_id').notNull().references(() => users.uuid, { onDelete: 'cascade' }), - type: contentType('type').notNull(), - title: varchar('title', { length: 255 }).notNull(), - storageKey: varchar('storage_key', { length: 512 }).notNull().unique(), // Clé interne S3 - mimeType: varchar('mime_type', { length: 128 }).notNull(), // Pour le Content-Type HTTP - fileSize: integer('file_size').notNull(), // Taille en octets - createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), - updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), - deletedAt: timestamp('deleted_at', { withTimezone: true }), // Soft delete -}, (table) => ({ - userIdIdx: index('contents_user_id_idx').on(table.userId), - storageKeyIdx: index('contents_storage_key_idx').on(table.storageKey), - deletedAtIdx: index('contents_deleted_at_idx').on(table.deletedAt), -})); +export const contents = pgTable( + "contents", + { + id: uuid("id").primaryKey().defaultRandom(), + userId: uuid("user_id") + .notNull() + .references(() => users.uuid, { onDelete: "cascade" }), + type: contentType("type").notNull(), + title: varchar("title", { length: 255 }).notNull(), + storageKey: varchar("storage_key", { length: 512 }).notNull().unique(), // Clé interne S3 + mimeType: varchar("mime_type", { length: 128 }).notNull(), // Pour le Content-Type HTTP + fileSize: integer("file_size").notNull(), // Taille en octets + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }) + .notNull() + .defaultNow(), + deletedAt: timestamp("deleted_at", { withTimezone: true }), // Soft delete + }, + (table) => ({ + userIdIdx: index("contents_user_id_idx").on(table.userId), + storageKeyIdx: index("contents_storage_key_idx").on(table.storageKey), + deletedAtIdx: index("contents_deleted_at_idx").on(table.deletedAt), + }), +); -export const contentsToTags = pgTable('contents_to_tags', { - contentId: uuid('content_id').notNull().references(() => contents.id, { onDelete: 'cascade' }), - tagId: uuid('tag_id').notNull().references(() => tags.id, { onDelete: 'cascade' }), -}, (t) => ({ - pk: primaryKey({ columns: [t.contentId, t.tagId] }), -})); +export const contentsToTags = pgTable( + "contents_to_tags", + { + contentId: uuid("content_id") + .notNull() + .references(() => contents.id, { onDelete: "cascade" }), + tagId: uuid("tag_id") + .notNull() + .references(() => tags.id, { onDelete: "cascade" }), + }, + (t) => ({ + pk: primaryKey({ columns: [t.contentId, t.tagId] }), + }), +); export type ContentInDb = typeof contents.$inferSelect; export type NewContentInDb = typeof contents.$inferInsert; diff --git a/backend/src/database/schemas/index.ts b/backend/src/database/schemas/index.ts index ae8f998..e9966ff 100644 --- a/backend/src/database/schemas/index.ts +++ b/backend/src/database/schemas/index.ts @@ -1,8 +1,8 @@ -export * from './users'; -export * from './rbac'; -export * from './sessions'; -export * from './api_keys'; -export * from './tags'; -export * from './content'; -export * from './reports'; -export * from './audit_logs'; \ No newline at end of file +export * from "./users"; +export * from "./rbac"; +export * from "./sessions"; +export * from "./api_keys"; +export * from "./tags"; +export * from "./content"; +export * from "./reports"; +export * from "./audit_logs"; diff --git a/backend/src/database/schemas/rbac.ts b/backend/src/database/schemas/rbac.ts index 56069bc..c4a210a 100644 --- a/backend/src/database/schemas/rbac.ts +++ b/backend/src/database/schemas/rbac.ts @@ -1,36 +1,71 @@ -import { pgTable, varchar, timestamp, uuid, primaryKey, index } from 'drizzle-orm/pg-core'; -import { users } from './users'; +import { + pgTable, + varchar, + timestamp, + uuid, + primaryKey, + index, +} from "drizzle-orm/pg-core"; +import { users } from "./users"; -export const roles = pgTable('roles', { - id: uuid('id').primaryKey().defaultRandom(), - name: varchar('name', { length: 64 }).notNull().unique(), - slug: varchar('slug', { length: 64 }).notNull().unique(), - description: varchar('description', { length: 128 }), - createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), -}, (table) => ({ - slugIdx: index('roles_slug_idx').on(table.slug), -})); +export const roles = pgTable( + "roles", + { + id: uuid("id").primaryKey().defaultRandom(), + name: varchar("name", { length: 64 }).notNull().unique(), + slug: varchar("slug", { length: 64 }).notNull().unique(), + description: varchar("description", { length: 128 }), + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + }, + (table) => ({ + slugIdx: index("roles_slug_idx").on(table.slug), + }), +); -export const permissions = pgTable('permissions', { - id: uuid('id').primaryKey().defaultRandom(), - name: varchar('name', { length: 64 }).notNull().unique(), - slug: varchar('slug', { length: 64 }).notNull().unique(), - description: varchar('description', { length: 128 }), - createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), -}, (table) => ({ - slugIdx: index('permissions_slug_idx').on(table.slug), -})); +export const permissions = pgTable( + "permissions", + { + id: uuid("id").primaryKey().defaultRandom(), + name: varchar("name", { length: 64 }).notNull().unique(), + slug: varchar("slug", { length: 64 }).notNull().unique(), + description: varchar("description", { length: 128 }), + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + }, + (table) => ({ + slugIdx: index("permissions_slug_idx").on(table.slug), + }), +); -export const rolesToPermissions = pgTable('roles_to_permissions', { - roleId: uuid('role_id').notNull().references(() => roles.id, { onDelete: 'cascade' }), - permissionId: uuid('permission_id').notNull().references(() => permissions.id, { onDelete: 'cascade' }), -}, (t) => ({ - pk: primaryKey({ columns: [t.roleId, t.permissionId] }), -})); +export const rolesToPermissions = pgTable( + "roles_to_permissions", + { + roleId: uuid("role_id") + .notNull() + .references(() => roles.id, { onDelete: "cascade" }), + permissionId: uuid("permission_id") + .notNull() + .references(() => permissions.id, { onDelete: "cascade" }), + }, + (t) => ({ + pk: primaryKey({ columns: [t.roleId, t.permissionId] }), + }), +); -export const usersToRoles = pgTable('users_to_roles', { - userId: uuid('user_id').notNull().references(() => users.uuid, { onDelete: 'cascade' }), - roleId: uuid('role_id').notNull().references(() => roles.id, { onDelete: 'cascade' }), -}, (t) => ({ - pk: primaryKey({ columns: [t.userId, t.roleId] }), -})); +export const usersToRoles = pgTable( + "users_to_roles", + { + userId: uuid("user_id") + .notNull() + .references(() => users.uuid, { onDelete: "cascade" }), + roleId: uuid("role_id") + .notNull() + .references(() => roles.id, { onDelete: "cascade" }), + }, + (t) => ({ + pk: primaryKey({ columns: [t.userId, t.roleId] }), + }), +); diff --git a/backend/src/database/schemas/reports.ts b/backend/src/database/schemas/reports.ts index f195ba8..fdf610d 100644 --- a/backend/src/database/schemas/reports.ts +++ b/backend/src/database/schemas/reports.ts @@ -1,33 +1,63 @@ -import { pgTable, varchar, timestamp, uuid, pgEnum, index, text } from 'drizzle-orm/pg-core'; -import { users } from './users'; -import { contents } from './content'; -import { tags } from './tags'; +import { + pgTable, + varchar, + timestamp, + uuid, + pgEnum, + index, + text, +} from "drizzle-orm/pg-core"; +import { users } from "./users"; +import { contents } from "./content"; +import { tags } from "./tags"; -export const reportStatus = pgEnum('report_status', ['pending', 'reviewed', 'resolved', 'dismissed']); -export const reportReason = pgEnum('report_reason', ['inappropriate', 'spam', 'copyright', 'other']); +export const reportStatus = pgEnum("report_status", [ + "pending", + "reviewed", + "resolved", + "dismissed", +]); +export const reportReason = pgEnum("report_reason", [ + "inappropriate", + "spam", + "copyright", + "other", +]); -export const reports = pgTable('reports', { - id: uuid('id').primaryKey().defaultRandom(), - reporterId: uuid('reporter_id').notNull().references(() => users.uuid, { onDelete: 'cascade' }), - - // Le signalement peut porter sur un contenu OU un tag - contentId: uuid('content_id').references(() => contents.id, { onDelete: 'cascade' }), - tagId: uuid('tag_id').references(() => tags.id, { onDelete: 'cascade' }), - - reason: reportReason('reason').notNull(), - description: text('description'), - status: reportStatus('status').default('pending').notNull(), - - expiresAt: timestamp('expires_at', { withTimezone: true }), // Pour purge automatique RGPD - createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), - updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), -}, (table) => ({ - reporterIdx: index('reports_reporter_id_idx').on(table.reporterId), - contentIdx: index('reports_content_id_idx').on(table.contentId), - tagIdx: index('reports_tag_id_idx').on(table.tagId), - statusIdx: index('reports_status_idx').on(table.status), - expiresAtIdx: index('reports_expires_at_idx').on(table.expiresAt), -})); +export const reports = pgTable( + "reports", + { + id: uuid("id").primaryKey().defaultRandom(), + reporterId: uuid("reporter_id") + .notNull() + .references(() => users.uuid, { onDelete: "cascade" }), + + // Le signalement peut porter sur un contenu OU un tag + contentId: uuid("content_id").references(() => contents.id, { + onDelete: "cascade", + }), + tagId: uuid("tag_id").references(() => tags.id, { onDelete: "cascade" }), + + reason: reportReason("reason").notNull(), + description: text("description"), + status: reportStatus("status").default("pending").notNull(), + + expiresAt: timestamp("expires_at", { withTimezone: true }), // Pour purge automatique RGPD + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }) + .notNull() + .defaultNow(), + }, + (table) => ({ + reporterIdx: index("reports_reporter_id_idx").on(table.reporterId), + contentIdx: index("reports_content_id_idx").on(table.contentId), + tagIdx: index("reports_tag_id_idx").on(table.tagId), + statusIdx: index("reports_status_idx").on(table.status), + expiresAtIdx: index("reports_expires_at_idx").on(table.expiresAt), + }), +); export type ReportInDb = typeof reports.$inferSelect; export type NewReportInDb = typeof reports.$inferInsert; diff --git a/backend/src/database/schemas/sessions.ts b/backend/src/database/schemas/sessions.ts index 5163cde..2317513 100644 --- a/backend/src/database/schemas/sessions.ts +++ b/backend/src/database/schemas/sessions.ts @@ -1,18 +1,35 @@ -import { pgTable, varchar, timestamp, uuid, index, boolean } from 'drizzle-orm/pg-core'; -import { users } from './users'; +import { + pgTable, + varchar, + timestamp, + uuid, + index, + boolean, +} from "drizzle-orm/pg-core"; +import { users } from "./users"; -export const sessions = pgTable('sessions', { - id: uuid('id').primaryKey().defaultRandom(), - userId: uuid('user_id').notNull().references(() => users.uuid, { onDelete: 'cascade' }), - refreshToken: varchar('refresh_token', { length: 512 }).notNull().unique(), - userAgent: varchar('user_agent', { length: 255 }), - ipHash: varchar('ip_hash', { length: 64 }), // IP hachée pour la protection de la vie privée (RGPD) - isValid: boolean('is_valid').notNull().default(true), - expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(), - createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), - updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), -}, (table) => ({ - userIdIdx: index('sessions_user_id_idx').on(table.userId), - refreshTokenIdx: index('sessions_refresh_token_idx').on(table.refreshToken), - expiresAtIdx: index('sessions_expires_at_idx').on(table.expiresAt), -})); +export const sessions = pgTable( + "sessions", + { + id: uuid("id").primaryKey().defaultRandom(), + userId: uuid("user_id") + .notNull() + .references(() => users.uuid, { onDelete: "cascade" }), + refreshToken: varchar("refresh_token", { length: 512 }).notNull().unique(), + userAgent: varchar("user_agent", { length: 255 }), + ipHash: varchar("ip_hash", { length: 64 }), // IP hachée pour la protection de la vie privée (RGPD) + isValid: boolean("is_valid").notNull().default(true), + expiresAt: timestamp("expires_at", { withTimezone: true }).notNull(), + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }) + .notNull() + .defaultNow(), + }, + (table) => ({ + userIdIdx: index("sessions_user_id_idx").on(table.userId), + refreshTokenIdx: index("sessions_refresh_token_idx").on(table.refreshToken), + expiresAtIdx: index("sessions_expires_at_idx").on(table.expiresAt), + }), +); diff --git a/backend/src/database/schemas/tags.ts b/backend/src/database/schemas/tags.ts index 69f43aa..26ae7f1 100644 --- a/backend/src/database/schemas/tags.ts +++ b/backend/src/database/schemas/tags.ts @@ -1,14 +1,22 @@ -import { pgTable, varchar, timestamp, uuid, index } from 'drizzle-orm/pg-core'; +import { pgTable, varchar, timestamp, uuid, index } from "drizzle-orm/pg-core"; -export const tags = pgTable('tags', { - id: uuid('id').primaryKey().defaultRandom(), - name: varchar('name', { length: 64 }).notNull().unique(), - slug: varchar('slug', { length: 64 }).notNull().unique(), - createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), - updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), -}, (table) => ({ - slugIdx: index('tags_slug_idx').on(table.slug), -})); +export const tags = pgTable( + "tags", + { + id: uuid("id").primaryKey().defaultRandom(), + name: varchar("name", { length: 64 }).notNull().unique(), + slug: varchar("slug", { length: 64 }).notNull().unique(), + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }) + .notNull() + .defaultNow(), + }, + (table) => ({ + slugIdx: index("tags_slug_idx").on(table.slug), + }), +); export type TagInDb = typeof tags.$inferSelect; export type NewTagInDb = typeof tags.$inferInsert; diff --git a/backend/src/database/schemas/users.ts b/backend/src/database/schemas/users.ts index 5a44a4f..3bef1bb 100644 --- a/backend/src/database/schemas/users.ts +++ b/backend/src/database/schemas/users.ts @@ -1,46 +1,69 @@ -import {pgTable, varchar, timestamp, uuid, pgEnum, index, boolean, customType} from 'drizzle-orm/pg-core'; +import { + pgTable, + varchar, + timestamp, + uuid, + pgEnum, + index, + boolean, + customType, +} from "drizzle-orm/pg-core"; // Type personnalisé pour les données chiffrées PGP (stockées en bytea dans Postgres) const pgpEncrypted = customType<{ data: string; driverData: string }>({ - dataType() { - return 'bytea'; - }, + dataType() { + return "bytea"; + }, }); -export const userStatus = pgEnum("user_status", ["active", "verification", "suspended", "pending", "deleted"]) +export const userStatus = pgEnum("user_status", [ + "active", + "verification", + "suspended", + "pending", + "deleted", +]); -export const users = pgTable('users', { - uuid: uuid().primaryKey().defaultRandom(), - status: userStatus('status').default('pending').notNull(), - - // Données Personnelles (PII) - Chiffrées nativement - email: pgpEncrypted('email').notNull(), - emailHash: varchar('email_hash', { length: 64 }).notNull().unique(), // Indexé pour recherche rapide et unicité - displayName: varchar('display_name', { length: 32 }), - - username: varchar('username', { length: 32 }).notNull().unique(), - passwordHash: varchar('password_hash', { length: 72 }).notNull(), - - // Sécurité - twoFactorSecret: pgpEncrypted('two_factor_secret'), - isTwoFactorEnabled: boolean('is_two_factor_enabled').notNull().default(false), - - // RGPD & Conformité - termsVersion: varchar('terms_version', { length: 16 }), // Version des CGU acceptées - privacyVersion: varchar('privacy_version', { length: 16 }), // Version de la Privacy Policy acceptée - gdprAcceptedAt: timestamp('gdpr_accepted_at', { withTimezone: true }), - - // Dates de cycle de vie (Standards Entreprise) - lastLoginAt: timestamp('last_login_at', { withTimezone: true }), - createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), - updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), - deletedAt: timestamp('deleted_at', { withTimezone: true }), // Soft delete (Droit à l'oubli) -}, (table) => ({ - uuidIdx: index('users_uuid_idx').on(table.uuid), - emailHashIdx: index('users_email_hash_idx').on(table.emailHash), - usernameIdx: index('users_username_idx').on(table.username), - statusIdx: index('users_status_idx').on(table.status), -})); +export const users = pgTable( + "users", + { + uuid: uuid().primaryKey().defaultRandom(), + status: userStatus("status").default("pending").notNull(), + + // Données Personnelles (PII) - Chiffrées nativement + email: pgpEncrypted("email").notNull(), + emailHash: varchar("email_hash", { length: 64 }).notNull().unique(), // Indexé pour recherche rapide et unicité + displayName: varchar("display_name", { length: 32 }), + + username: varchar("username", { length: 32 }).notNull().unique(), + passwordHash: varchar("password_hash", { length: 72 }).notNull(), + + // Sécurité + twoFactorSecret: pgpEncrypted("two_factor_secret"), + isTwoFactorEnabled: boolean("is_two_factor_enabled").notNull().default(false), + + // RGPD & Conformité + termsVersion: varchar("terms_version", { length: 16 }), // Version des CGU acceptées + privacyVersion: varchar("privacy_version", { length: 16 }), // Version de la Privacy Policy acceptée + gdprAcceptedAt: timestamp("gdpr_accepted_at", { withTimezone: true }), + + // Dates de cycle de vie (Standards Entreprise) + lastLoginAt: timestamp("last_login_at", { withTimezone: true }), + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }) + .notNull() + .defaultNow(), + deletedAt: timestamp("deleted_at", { withTimezone: true }), // Soft delete (Droit à l'oubli) + }, + (table) => ({ + uuidIdx: index("users_uuid_idx").on(table.uuid), + emailHashIdx: index("users_email_hash_idx").on(table.emailHash), + usernameIdx: index("users_username_idx").on(table.username), + statusIdx: index("users_status_idx").on(table.status), + }), +); export type UserInDb = typeof users.$inferSelect; export type NewUserInDb = typeof users.$inferInsert;