feat: implement authentication and database modules with relations and group management
Added new authentication strategies (JWT and GitHub OAuth), guards, and controllers. Implemented database module, schema with relations, and group management features, including CRD operations and person-to-group associations. Integrated validation and CORS configuration.
This commit is contained in:
33
backend/src/database/database.module.ts
Normal file
33
backend/src/database/database.module.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Module, Global } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { Pool } from 'pg';
|
||||
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||
import * as schema from './schema';
|
||||
import { DatabaseService } from './database.service';
|
||||
|
||||
export const DATABASE_POOL = 'DATABASE_POOL';
|
||||
export const DRIZZLE = 'DRIZZLE';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [ConfigModule],
|
||||
providers: [
|
||||
DatabaseService,
|
||||
{
|
||||
provide: DATABASE_POOL,
|
||||
useFactory: (databaseService: DatabaseService) => {
|
||||
return databaseService.getPool();
|
||||
},
|
||||
inject: [DatabaseService],
|
||||
},
|
||||
{
|
||||
provide: DRIZZLE,
|
||||
useFactory: (databaseService: DatabaseService) => {
|
||||
return databaseService.getDb();
|
||||
},
|
||||
inject: [DatabaseService],
|
||||
},
|
||||
],
|
||||
exports: [DatabaseService, DATABASE_POOL, DRIZZLE],
|
||||
})
|
||||
export class DatabaseModule {}
|
||||
97
backend/src/database/database.service.ts
Normal file
97
backend/src/database/database.service.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||
import { Pool } from 'pg';
|
||||
import * as schema from './schema';
|
||||
import { runMigrations } from './migrations/migrate';
|
||||
|
||||
@Injectable()
|
||||
export class DatabaseService implements OnModuleInit, OnModuleDestroy {
|
||||
private readonly pool: Pool;
|
||||
private readonly db: ReturnType<typeof drizzle>;
|
||||
|
||||
constructor(private configService: ConfigService) {
|
||||
// Create the PostgreSQL pool
|
||||
const connectionString = this.getDatabaseConnectionString();
|
||||
this.pool = new Pool({
|
||||
connectionString,
|
||||
});
|
||||
|
||||
// Create the Drizzle ORM instance
|
||||
this.db = drizzle(this.pool, { schema });
|
||||
}
|
||||
|
||||
async onModuleInit() {
|
||||
// Log database connection
|
||||
console.log('Connecting to database...');
|
||||
|
||||
// Test the connection
|
||||
try {
|
||||
const client = await this.pool.connect();
|
||||
try {
|
||||
await client.query('SELECT NOW()');
|
||||
console.log('Database connection established successfully');
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to connect to database:', error.message);
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Run migrations in all environments
|
||||
const result = await runMigrations({ migrationsFolder: './src/database/migrations' });
|
||||
|
||||
// In production, we want to fail if migrations fail
|
||||
if (!result.success && this.configService.get('NODE_ENV') === 'production') {
|
||||
throw result.error;
|
||||
}
|
||||
}
|
||||
|
||||
async onModuleDestroy() {
|
||||
// Close the database connection
|
||||
await this.pool.end();
|
||||
console.log('Database connection closed');
|
||||
}
|
||||
|
||||
// Get the database connection string from environment variables
|
||||
private getDatabaseConnectionString(): string {
|
||||
// First try to get the full DATABASE_URL
|
||||
const databaseUrl = this.configService.get<string>('DATABASE_URL');
|
||||
if (databaseUrl) {
|
||||
return databaseUrl;
|
||||
}
|
||||
|
||||
// If DATABASE_URL is not provided, construct it from individual variables
|
||||
const password = this.configService.get<string>('POSTGRES_PASSWORD');
|
||||
const username = this.configService.get<string>('POSTGRES_USER');
|
||||
const host = this.configService.get<string>('POSTGRES_HOST');
|
||||
const port = this.configService.get<string>('POSTGRES_PORT');
|
||||
const database = this.configService.get<string>('POSTGRES_DB');
|
||||
|
||||
const missingVars: string[] = [];
|
||||
if (!password) missingVars.push('POSTGRES_PASSWORD');
|
||||
if (!username) missingVars.push('POSTGRES_USER');
|
||||
if (!host) missingVars.push('POSTGRES_HOST');
|
||||
if (!port) missingVars.push('POSTGRES_PORT');
|
||||
if (!database) missingVars.push('POSTGRES_DB');
|
||||
|
||||
if (missingVars.length > 0) {
|
||||
throw new Error(
|
||||
`Database configuration is missing. Missing variables: ${missingVars.join(', ')}. Please check your .env file.`,
|
||||
);
|
||||
}
|
||||
|
||||
return `postgres://${username}:${password}@${host}:${port}/${database}`;
|
||||
}
|
||||
|
||||
// Get the Drizzle ORM instance
|
||||
getDb() {
|
||||
return this.db;
|
||||
}
|
||||
|
||||
// Get the PostgreSQL pool
|
||||
getPool() {
|
||||
return this.pool;
|
||||
}
|
||||
}
|
||||
55
backend/src/database/migrations/generate-migrations.ts
Normal file
55
backend/src/database/migrations/generate-migrations.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { exec } from 'child_process';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
|
||||
/**
|
||||
* Script to generate migrations using drizzle-kit
|
||||
*
|
||||
* This script:
|
||||
* 1. Runs drizzle-kit generate to create migration files
|
||||
* 2. Ensures the migrations directory exists
|
||||
* 3. Handles errors and provides feedback
|
||||
*/
|
||||
const main = async () => {
|
||||
console.log('Generating migrations...');
|
||||
|
||||
// Ensure migrations directory exists
|
||||
const migrationsDir = path.join(__dirname);
|
||||
if (!fs.existsSync(migrationsDir)) {
|
||||
fs.mkdirSync(migrationsDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Run drizzle-kit generate command
|
||||
const command = 'npx drizzle-kit generate:pg';
|
||||
|
||||
exec(command, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.error(`Error generating migrations: ${error.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (stderr) {
|
||||
console.error(`Migration generation stderr: ${stderr}`);
|
||||
}
|
||||
|
||||
console.log(stdout);
|
||||
console.log('Migrations generated successfully');
|
||||
|
||||
// List generated migration files
|
||||
const files = fs.readdirSync(migrationsDir)
|
||||
.filter(file => file.endsWith('.sql'));
|
||||
|
||||
if (files.length === 0) {
|
||||
console.log('No migration files were generated. Your schema might be up to date.');
|
||||
} else {
|
||||
console.log('Generated migration files:');
|
||||
files.forEach(file => console.log(`- ${file}`));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
main().catch(err => {
|
||||
console.error('Migration generation failed');
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
91
backend/src/database/migrations/migrate.ts
Normal file
91
backend/src/database/migrations/migrate.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||
import { migrate } from 'drizzle-orm/node-postgres/migrator';
|
||||
import { Pool } from 'pg';
|
||||
import * as dotenv from 'dotenv';
|
||||
import * as path from 'path';
|
||||
import * as schema from '../schema';
|
||||
|
||||
/**
|
||||
* Script to run database migrations
|
||||
*
|
||||
* This script:
|
||||
* 1. Establishes a connection to the PostgreSQL database
|
||||
* 2. Creates a Drizzle ORM instance
|
||||
* 3. Runs all pending migrations
|
||||
*
|
||||
* It can be used:
|
||||
* - As a standalone script: `node dist/database/migrations/migrate.js`
|
||||
* - Integrated with NestJS application lifecycle in database.service.ts
|
||||
*/
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
||||
export const runMigrations = async (options?: { migrationsFolder?: string }) => {
|
||||
// First try to get the full DATABASE_URL
|
||||
let connectionString = process.env.DATABASE_URL;
|
||||
|
||||
// If DATABASE_URL is not provided, construct it from individual variables
|
||||
if (!connectionString) {
|
||||
const password = process.env.POSTGRES_PASSWORD || 'postgres';
|
||||
const username = process.env.POSTGRES_USER || 'postgres';
|
||||
const host = process.env.POSTGRES_HOST || 'localhost';
|
||||
const port = Number(process.env.POSTGRES_PORT || 5432);
|
||||
const database = process.env.POSTGRES_DB || 'groupmaker';
|
||||
|
||||
connectionString = `postgres://${username}:${password}@${host}:${port}/${database}`;
|
||||
}
|
||||
|
||||
// Create the PostgreSQL pool
|
||||
const pool = new Pool({
|
||||
connectionString,
|
||||
});
|
||||
|
||||
// Create the Drizzle ORM instance
|
||||
const db = drizzle(pool, { schema });
|
||||
|
||||
console.log('Running migrations...');
|
||||
|
||||
try {
|
||||
// Test the connection
|
||||
const client = await pool.connect();
|
||||
try {
|
||||
await client.query('SELECT NOW()');
|
||||
console.log('Database connection established successfully');
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
|
||||
// Determine migrations folder path
|
||||
const migrationsFolder = options?.migrationsFolder || path.join(__dirname);
|
||||
console.log(`Using migrations folder: ${migrationsFolder}`);
|
||||
|
||||
// Run migrations
|
||||
await migrate(db, { migrationsFolder });
|
||||
|
||||
console.log('Migrations completed successfully');
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Migration failed');
|
||||
console.error(error);
|
||||
return { success: false, error };
|
||||
} finally {
|
||||
await pool.end();
|
||||
console.log('Database connection closed');
|
||||
}
|
||||
};
|
||||
|
||||
// Run migrations if this script is executed directly
|
||||
if (require.main === module) {
|
||||
runMigrations()
|
||||
.then(result => {
|
||||
if (!result.success) {
|
||||
process.exit(1);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Migration failed');
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
17
backend/src/database/schema/db-schema.ts
Normal file
17
backend/src/database/schema/db-schema.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Yidhra Studio. - All Rights Reserved
|
||||
* Updated : 25/04/2025 10:33
|
||||
*
|
||||
* Unauthorized copying or redistribution of this file in source and binary forms via any medium
|
||||
* is strictly prohibited.
|
||||
*/
|
||||
|
||||
import { pgSchema } from "drizzle-orm/pg-core";
|
||||
|
||||
/**
|
||||
* Defines the PostgreSQL schema for the application.
|
||||
* All database tables are created within this schema namespace.
|
||||
* The schema name "bypass" is used to isolate the application's tables
|
||||
* from other applications that might share the same database.
|
||||
*/
|
||||
export const DbSchema = pgSchema("groupmaker");
|
||||
16
backend/src/database/schema/enums.ts
Normal file
16
backend/src/database/schema/enums.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { pgEnum } from 'drizzle-orm/pg-core';
|
||||
|
||||
/**
|
||||
* Enum for gender values
|
||||
*/
|
||||
export const gender = pgEnum('gender', ['MALE', 'FEMALE', 'NON_BINARY']);
|
||||
|
||||
/**
|
||||
* Enum for oral ease level values
|
||||
*/
|
||||
export const oralEaseLevel = pgEnum('oralEaseLevel', ['SHY', 'RESERVED', 'COMFORTABLE']);
|
||||
|
||||
/**
|
||||
* Enum for tag types
|
||||
*/
|
||||
export const tagType = pgEnum('tagType', ['PROJECT', 'PERSON']);
|
||||
25
backend/src/database/schema/groups.ts
Normal file
25
backend/src/database/schema/groups.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { pgTable, uuid, varchar, timestamp, jsonb, index } from 'drizzle-orm/pg-core';
|
||||
import { projects } from './projects';
|
||||
|
||||
/**
|
||||
* Groups table schema
|
||||
*/
|
||||
export const groups = pgTable('groups', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
name: varchar('name', { length: 100 }).notNull(),
|
||||
projectId: uuid('projectId').notNull().references(() => projects.id, { onDelete: 'cascade' }),
|
||||
metadata: jsonb('metadata').default({}),
|
||||
createdAt: timestamp('createdAt', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updatedAt', { withTimezone: true }).defaultNow().notNull()
|
||||
}, (table) => {
|
||||
return {
|
||||
nameIdx: index('group_name_idx').on(table.name),
|
||||
projectIdIdx: index('group_projectId_idx').on(table.projectId)
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Group type definitions
|
||||
*/
|
||||
export type Group = typeof groups.$inferSelect;
|
||||
export type NewGroup = typeof groups.$inferInsert;
|
||||
23
backend/src/database/schema/index.ts
Normal file
23
backend/src/database/schema/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* This file serves as the main entry point for the database schema definitions.
|
||||
* It exports all schema definitions from various modules.
|
||||
*/
|
||||
|
||||
// Export schema
|
||||
export * from './db-schema';
|
||||
|
||||
// Export enums
|
||||
export * from './enums';
|
||||
|
||||
// Export tables
|
||||
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 relations
|
||||
export * from './relations';
|
||||
25
backend/src/database/schema/personToGroup.ts
Normal file
25
backend/src/database/schema/personToGroup.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { pgTable, uuid, timestamp, index, uniqueIndex } from 'drizzle-orm/pg-core';
|
||||
import { persons } from './persons';
|
||||
import { groups } from './groups';
|
||||
|
||||
/**
|
||||
* Person to Group relation table schema
|
||||
*/
|
||||
export const personToGroup = pgTable('person_to_group', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
personId: uuid('personId').notNull().references(() => persons.id, { onDelete: 'cascade' }),
|
||||
groupId: uuid('groupId').notNull().references(() => groups.id, { onDelete: 'cascade' }),
|
||||
createdAt: timestamp('createdAt', { withTimezone: true }).defaultNow().notNull()
|
||||
}, (table) => {
|
||||
return {
|
||||
personIdIdx: index('ptg_personId_idx').on(table.personId),
|
||||
groupIdIdx: index('ptg_groupId_idx').on(table.groupId),
|
||||
personGroupUniqueIdx: uniqueIndex('ptg_person_group_unique_idx').on(table.personId, table.groupId)
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* PersonToGroup type definitions
|
||||
*/
|
||||
export type PersonToGroup = typeof personToGroup.$inferSelect;
|
||||
export type NewPersonToGroup = typeof personToGroup.$inferInsert;
|
||||
25
backend/src/database/schema/personToTag.ts
Normal file
25
backend/src/database/schema/personToTag.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { pgTable, uuid, timestamp, index, uniqueIndex } from 'drizzle-orm/pg-core';
|
||||
import { persons } from './persons';
|
||||
import { tags } from './tags';
|
||||
|
||||
/**
|
||||
* Person to Tag relation table schema
|
||||
*/
|
||||
export const personToTag = pgTable('person_to_tag', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
personId: uuid('personId').notNull().references(() => persons.id, { onDelete: 'cascade' }),
|
||||
tagId: uuid('tagId').notNull().references(() => tags.id, { onDelete: 'cascade' }),
|
||||
createdAt: timestamp('createdAt', { withTimezone: true }).defaultNow().notNull()
|
||||
}, (table) => {
|
||||
return {
|
||||
personIdIdx: index('ptt_personId_idx').on(table.personId),
|
||||
tagIdIdx: index('ptt_tagId_idx').on(table.tagId),
|
||||
personTagUniqueIdx: uniqueIndex('ptt_person_tag_unique_idx').on(table.personId, table.tagId)
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* PersonToTag type definitions
|
||||
*/
|
||||
export type PersonToTag = typeof personToTag.$inferSelect;
|
||||
export type NewPersonToTag = typeof personToTag.$inferInsert;
|
||||
35
backend/src/database/schema/persons.ts
Normal file
35
backend/src/database/schema/persons.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { pgTable, uuid, varchar, smallint, boolean, timestamp, jsonb, index } from 'drizzle-orm/pg-core';
|
||||
import { projects } from './projects';
|
||||
import { gender, oralEaseLevel } from './enums';
|
||||
|
||||
/**
|
||||
* Persons table schema
|
||||
*/
|
||||
export const persons = pgTable('persons', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
firstName: varchar('firstName', { length: 50 }).notNull(),
|
||||
lastName: varchar('lastName', { length: 50 }).notNull(),
|
||||
gender: gender('gender').notNull(),
|
||||
technicalLevel: smallint('technicalLevel').notNull(),
|
||||
hasTechnicalTraining: boolean('hasTechnicalTraining').notNull().default(false),
|
||||
frenchSpeakingLevel: smallint('frenchSpeakingLevel').notNull(),
|
||||
oralEaseLevel: oralEaseLevel('oralEaseLevel').notNull(),
|
||||
age: smallint('age'),
|
||||
projectId: uuid('projectId').notNull().references(() => projects.id, { onDelete: 'cascade' }),
|
||||
attributes: jsonb('attributes').default({}),
|
||||
createdAt: timestamp('createdAt', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updatedAt', { withTimezone: true }).defaultNow().notNull()
|
||||
}, (table) => {
|
||||
return {
|
||||
firstNameIdx: index('person_firstName_idx').on(table.firstName),
|
||||
lastNameIdx: index('person_lastName_idx').on(table.lastName),
|
||||
projectIdIdx: index('person_projectId_idx').on(table.projectId),
|
||||
nameCompositeIdx: index('person_name_composite_idx').on(table.firstName, table.lastName)
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Person type definitions
|
||||
*/
|
||||
export type Person = typeof persons.$inferSelect;
|
||||
export type NewPerson = typeof persons.$inferInsert;
|
||||
25
backend/src/database/schema/projectToTag.ts
Normal file
25
backend/src/database/schema/projectToTag.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { pgTable, uuid, timestamp, index, uniqueIndex } from 'drizzle-orm/pg-core';
|
||||
import { projects } from './projects';
|
||||
import { tags } from './tags';
|
||||
|
||||
/**
|
||||
* Project to Tag relation table schema
|
||||
*/
|
||||
export const projectToTag = pgTable('project_to_tag', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
projectId: uuid('projectId').notNull().references(() => projects.id, { onDelete: 'cascade' }),
|
||||
tagId: uuid('tagId').notNull().references(() => tags.id, { onDelete: 'cascade' }),
|
||||
createdAt: timestamp('createdAt', { withTimezone: true }).defaultNow().notNull()
|
||||
}, (table) => {
|
||||
return {
|
||||
projectIdIdx: index('pjt_projectId_idx').on(table.projectId),
|
||||
tagIdIdx: index('pjt_tagId_idx').on(table.tagId),
|
||||
projectTagUniqueIdx: uniqueIndex('pjt_project_tag_unique_idx').on(table.projectId, table.tagId)
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* ProjectToTag type definitions
|
||||
*/
|
||||
export type ProjectToTag = typeof projectToTag.$inferSelect;
|
||||
export type NewProjectToTag = typeof projectToTag.$inferInsert;
|
||||
27
backend/src/database/schema/projects.ts
Normal file
27
backend/src/database/schema/projects.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { pgTable, uuid, varchar, text, timestamp, jsonb, index } from 'drizzle-orm/pg-core';
|
||||
import { users } from './users';
|
||||
|
||||
/**
|
||||
* Projects table schema
|
||||
*/
|
||||
export const projects = pgTable('projects', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
name: varchar('name', { length: 100 }).notNull(),
|
||||
description: text('description'),
|
||||
ownerId: uuid('ownerId').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
||||
settings: jsonb('settings').default({}),
|
||||
createdAt: timestamp('createdAt', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updatedAt', { withTimezone: true }).defaultNow().notNull()
|
||||
}, (table) => {
|
||||
return {
|
||||
nameIdx: index('project_name_idx').on(table.name),
|
||||
ownerIdIdx: index('project_ownerId_idx').on(table.ownerId),
|
||||
createdAtIdx: index('project_createdAt_idx').on(table.createdAt)
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Project type definitions
|
||||
*/
|
||||
export type Project = typeof projects.$inferSelect;
|
||||
export type NewProject = typeof projects.$inferInsert;
|
||||
102
backend/src/database/schema/relations.ts
Normal file
102
backend/src/database/schema/relations.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { relations } from 'drizzle-orm';
|
||||
import { users } from './users';
|
||||
import { projects } from './projects';
|
||||
import { persons } from './persons';
|
||||
import { groups } from './groups';
|
||||
import { tags } from './tags';
|
||||
import { personToGroup } from './personToGroup';
|
||||
import { personToTag } from './personToTag';
|
||||
import { projectToTag } from './projectToTag';
|
||||
|
||||
/**
|
||||
* Define relations for users table
|
||||
*/
|
||||
export const usersRelations = relations(users, ({ many }) => ({
|
||||
projects: many(projects),
|
||||
}));
|
||||
|
||||
/**
|
||||
* Define relations for projects table
|
||||
*/
|
||||
export const projectsRelations = relations(projects, ({ one, many }) => ({
|
||||
owner: one(users, {
|
||||
fields: [projects.ownerId],
|
||||
references: [users.id],
|
||||
}),
|
||||
persons: many(persons),
|
||||
groups: many(groups),
|
||||
projectToTags: many(projectToTag),
|
||||
}));
|
||||
|
||||
/**
|
||||
* Define relations for persons table
|
||||
*/
|
||||
export const personsRelations = relations(persons, ({ one, many }) => ({
|
||||
project: one(projects, {
|
||||
fields: [persons.projectId],
|
||||
references: [projects.id],
|
||||
}),
|
||||
personToGroups: many(personToGroup),
|
||||
personToTags: many(personToTag),
|
||||
}));
|
||||
|
||||
/**
|
||||
* Define relations for groups table
|
||||
*/
|
||||
export const groupsRelations = relations(groups, ({ one, many }) => ({
|
||||
project: one(projects, {
|
||||
fields: [groups.projectId],
|
||||
references: [projects.id],
|
||||
}),
|
||||
personToGroups: many(personToGroup),
|
||||
}));
|
||||
|
||||
/**
|
||||
* Define relations for tags table
|
||||
*/
|
||||
export const tagsRelations = relations(tags, ({ many }) => ({
|
||||
personToTags: many(personToTag),
|
||||
projectToTags: many(projectToTag),
|
||||
}));
|
||||
|
||||
/**
|
||||
* Define relations for personToGroup table
|
||||
*/
|
||||
export const personToGroupRelations = relations(personToGroup, ({ one }) => ({
|
||||
person: one(persons, {
|
||||
fields: [personToGroup.personId],
|
||||
references: [persons.id],
|
||||
}),
|
||||
group: one(groups, {
|
||||
fields: [personToGroup.groupId],
|
||||
references: [groups.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
/**
|
||||
* Define relations for personToTag table
|
||||
*/
|
||||
export const personToTagRelations = relations(personToTag, ({ one }) => ({
|
||||
person: one(persons, {
|
||||
fields: [personToTag.personId],
|
||||
references: [persons.id],
|
||||
}),
|
||||
tag: one(tags, {
|
||||
fields: [personToTag.tagId],
|
||||
references: [tags.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
/**
|
||||
* Define relations for projectToTag table
|
||||
*/
|
||||
export const projectToTagRelations = relations(projectToTag, ({ one }) => ({
|
||||
project: one(projects, {
|
||||
fields: [projectToTag.projectId],
|
||||
references: [projects.id],
|
||||
}),
|
||||
tag: one(tags, {
|
||||
fields: [projectToTag.tagId],
|
||||
references: [tags.id],
|
||||
}),
|
||||
}));
|
||||
25
backend/src/database/schema/tags.ts
Normal file
25
backend/src/database/schema/tags.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { pgTable, uuid, varchar, timestamp, index } from 'drizzle-orm/pg-core';
|
||||
import { tagType } from './enums';
|
||||
|
||||
/**
|
||||
* Tags table schema
|
||||
*/
|
||||
export const tags = pgTable('tags', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
name: varchar('name', { length: 50 }).notNull(),
|
||||
color: varchar('color', { length: 7 }).notNull(),
|
||||
type: tagType('type').notNull(),
|
||||
createdAt: timestamp('createdAt', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updatedAt', { withTimezone: true }).defaultNow().notNull()
|
||||
}, (table) => {
|
||||
return {
|
||||
nameIdx: index('tag_name_idx').on(table.name),
|
||||
typeIdx: index('tag_type_idx').on(table.type)
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Tag type definitions
|
||||
*/
|
||||
export type Tag = typeof tags.$inferSelect;
|
||||
export type NewTag = typeof tags.$inferInsert;
|
||||
27
backend/src/database/schema/users.ts
Normal file
27
backend/src/database/schema/users.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { pgTable, uuid, varchar, text, timestamp, jsonb, index } from 'drizzle-orm/pg-core';
|
||||
import { DbSchema } from './db-schema';
|
||||
|
||||
/**
|
||||
* Users table schema
|
||||
*/
|
||||
export const users = pgTable('users', {
|
||||
id: uuid('id').primaryKey().defaultRandom(), // UUIDv7 for chronological order
|
||||
name: varchar('name', { length: 100 }).notNull(),
|
||||
avatar: text('avatar'), // URL from Github API
|
||||
githubId: varchar('githubId', { length: 50 }).notNull().unique(),
|
||||
gdprTimestamp: timestamp('gdprTimestamp', { withTimezone: true }),
|
||||
createdAt: timestamp('createdAt', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updatedAt', { withTimezone: true }).defaultNow().notNull(),
|
||||
metadata: jsonb('metadata').default({})
|
||||
}, (table) => {
|
||||
return {
|
||||
githubIdIdx: index('githubId_idx').on(table.githubId),
|
||||
createdAtIdx: index('createdAt_idx').on(table.createdAt)
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* User type definitions
|
||||
*/
|
||||
export type User = typeof users.$inferSelect;
|
||||
export type NewUser = typeof users.$inferInsert;
|
||||
Reference in New Issue
Block a user