mirror of
https://github.com/apotdevin/thunderhub.git
synced 2026-08-13 12:33:08 +02:00
chore: initial user and team tables
This commit is contained in:
parent
11178793b9
commit
c63ea0d76f
15 changed files with 462 additions and 14 deletions
|
|
@ -4,18 +4,12 @@ const dbType = process.env.DB_TYPE || 'sqlite';
|
|||
|
||||
export default dbType === 'postgres'
|
||||
? defineConfig({
|
||||
schema: './src/server/modules/database/schema/*',
|
||||
out: './drizzle',
|
||||
schema: './src/server/modules/database/schema/pg/*',
|
||||
out: './drizzle/pg',
|
||||
dialect: 'postgresql',
|
||||
dbCredentials: {
|
||||
url: process.env.DB_POSTGRES_URL!,
|
||||
},
|
||||
})
|
||||
: defineConfig({
|
||||
schema: './src/server/modules/database/schema/*',
|
||||
out: './drizzle',
|
||||
schema: './src/server/modules/database/schema/sqlite/*',
|
||||
out: './drizzle/sqlite',
|
||||
dialect: 'sqlite',
|
||||
dbCredentials: {
|
||||
url: process.env.DB_SQLITE_PATH!,
|
||||
},
|
||||
});
|
||||
|
|
|
|||
19
drizzle/pg/0000_new_adam_warlock.sql
Normal file
19
drizzle/pg/0000_new_adam_warlock.sql
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
CREATE TABLE "teams" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"created_at" timestamp(6) DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp(6) DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "users" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"email" text NOT NULL,
|
||||
"password_hash" text NOT NULL,
|
||||
"role" text DEFAULT 'member' NOT NULL,
|
||||
"team_id" uuid NOT NULL,
|
||||
"created_at" timestamp(6) DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp(6) DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "users_email_unique" UNIQUE("email")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "users" ADD CONSTRAINT "users_team_id_teams_id_fk" FOREIGN KEY ("team_id") REFERENCES "public"."teams"("id") ON DELETE no action ON UPDATE no action;
|
||||
140
drizzle/pg/meta/0000_snapshot.json
Normal file
140
drizzle/pg/meta/0000_snapshot.json
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
{
|
||||
"id": "f2a5cebb-ac67-464a-bfc5-d0fa612c3552",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.teams": {
|
||||
"name": "teams",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp(6)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp(6)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.users": {
|
||||
"name": "users",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "uuid",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"default": "gen_random_uuid()"
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"password_hash": {
|
||||
"name": "password_hash",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"role": {
|
||||
"name": "role",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'member'"
|
||||
},
|
||||
"team_id": {
|
||||
"name": "team_id",
|
||||
"type": "uuid",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp(6)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp(6)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"users_team_id_teams_id_fk": {
|
||||
"name": "users_team_id_teams_id_fk",
|
||||
"tableFrom": "users",
|
||||
"tableTo": "teams",
|
||||
"columnsFrom": [
|
||||
"team_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"users_email_unique": {
|
||||
"name": "users_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
13
drizzle/pg/meta/_journal.json
Normal file
13
drizzle/pg/meta/_journal.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1775074089419,
|
||||
"tag": "0000_new_adam_warlock",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
19
drizzle/sqlite/0000_premium_pixie.sql
Normal file
19
drizzle/sqlite/0000_premium_pixie.sql
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
CREATE TABLE `teams` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`name` text NOT NULL,
|
||||
`created_at` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL,
|
||||
`updated_at` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE `users` (
|
||||
`id` text PRIMARY KEY NOT NULL,
|
||||
`email` text NOT NULL,
|
||||
`password_hash` text NOT NULL,
|
||||
`role` text DEFAULT 'member' NOT NULL,
|
||||
`team_id` text NOT NULL,
|
||||
`created_at` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL,
|
||||
`updated_at` text DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now')) NOT NULL,
|
||||
FOREIGN KEY (`team_id`) REFERENCES `teams`(`id`) ON UPDATE no action ON DELETE no action
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX `users_email_unique` ON `users` (`email`);
|
||||
142
drizzle/sqlite/meta/0000_snapshot.json
Normal file
142
drizzle/sqlite/meta/0000_snapshot.json
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "296005fb-1644-4912-8e82-1ebb647066e2",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"tables": {
|
||||
"teams": {
|
||||
"name": "teams",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"users": {
|
||||
"name": "users",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"password_hash": {
|
||||
"name": "password_hash",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"role": {
|
||||
"name": "role",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "'member'"
|
||||
},
|
||||
"team_id": {
|
||||
"name": "team_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"users_email_unique": {
|
||||
"name": "users_email_unique",
|
||||
"columns": [
|
||||
"email"
|
||||
],
|
||||
"isUnique": true
|
||||
}
|
||||
},
|
||||
"foreignKeys": {
|
||||
"users_team_id_teams_id_fk": {
|
||||
"name": "users_team_id_teams_id_fk",
|
||||
"tableFrom": "users",
|
||||
"tableTo": "teams",
|
||||
"columnsFrom": [
|
||||
"team_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
}
|
||||
},
|
||||
"views": {},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
||||
13
drizzle/sqlite/meta/_journal.json
Normal file
13
drizzle/sqlite/meta/_journal.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"version": "7",
|
||||
"dialect": "sqlite",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "6",
|
||||
"when": 1775074036636,
|
||||
"tag": "0000_premium_pixie",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
11
scripts/generateMigrations.sh
Executable file
11
scripts/generateMigrations.sh
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
#!/bin/sh
|
||||
|
||||
echo "Generating SQLite migrations..."
|
||||
DB_TYPE=sqlite npx drizzle-kit generate
|
||||
|
||||
echo ""
|
||||
echo "Generating PostgreSQL migrations..."
|
||||
DB_TYPE=postgres npx drizzle-kit generate
|
||||
|
||||
echo ""
|
||||
echo "Done. Migrations generated in drizzle/sqlite/ and drizzle/pg/"
|
||||
|
|
@ -1,9 +1,12 @@
|
|||
import { FactoryProvider } from '@nestjs/common';
|
||||
import { FactoryProvider, Logger } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { drizzle as drizzleSqlite } from 'drizzle-orm/better-sqlite3';
|
||||
import { drizzle as drizzlePg } from 'drizzle-orm/postgres-js';
|
||||
import { migrate as migrateSqlite } from 'drizzle-orm/better-sqlite3/migrator';
|
||||
import { migrate as migratePg } from 'drizzle-orm/postgres-js/migrator';
|
||||
import Database from 'better-sqlite3';
|
||||
import postgres from 'postgres';
|
||||
import { join } from 'path';
|
||||
|
||||
export const DRIZZLE = Symbol('DRIZZLE');
|
||||
|
||||
|
|
@ -12,20 +15,28 @@ export type DrizzleDB =
|
|||
| ReturnType<typeof drizzlePg>
|
||||
| null;
|
||||
|
||||
const logger = new Logger('DatabaseProvider');
|
||||
|
||||
export const DrizzleProvider: FactoryProvider = {
|
||||
provide: DRIZZLE,
|
||||
inject: [ConfigService],
|
||||
useFactory: (config: ConfigService): DrizzleDB => {
|
||||
useFactory: async (config: ConfigService): Promise<DrizzleDB> => {
|
||||
const dbType = config.get<string>('database.type');
|
||||
|
||||
if (!dbType) return null;
|
||||
|
||||
const migrationsRoot = join(process.cwd(), 'drizzle');
|
||||
|
||||
if (dbType === 'postgres') {
|
||||
const url = config.get<string>('database.url');
|
||||
if (!url) {
|
||||
throw new Error('DB_POSTGRES_URL is required when DB_TYPE is postgres');
|
||||
}
|
||||
return drizzlePg(postgres(url));
|
||||
const db = drizzlePg(postgres(url));
|
||||
logger.log('Running PostgreSQL migrations...');
|
||||
await migratePg(db, { migrationsFolder: join(migrationsRoot, 'pg') });
|
||||
logger.log('PostgreSQL migrations complete.');
|
||||
return db;
|
||||
}
|
||||
|
||||
if (dbType === 'sqlite') {
|
||||
|
|
@ -33,7 +44,13 @@ export const DrizzleProvider: FactoryProvider = {
|
|||
if (!path) {
|
||||
throw new Error('DB_SQLITE_PATH is required when DB_TYPE is sqlite');
|
||||
}
|
||||
return drizzleSqlite(new Database(path));
|
||||
const db = drizzleSqlite(new Database(path));
|
||||
logger.log('Running SQLite migrations...');
|
||||
migrateSqlite(db, {
|
||||
migrationsFolder: join(migrationsRoot, 'sqlite'),
|
||||
});
|
||||
logger.log('SQLite migrations complete.');
|
||||
return db;
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported DB_TYPE: ${dbType}`);
|
||||
|
|
|
|||
2
src/server/modules/database/schema/pg/index.ts
Normal file
2
src/server/modules/database/schema/pg/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export { teams } from './teams';
|
||||
export { users } from './users';
|
||||
15
src/server/modules/database/schema/pg/teams.ts
Normal file
15
src/server/modules/database/schema/pg/teams.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { pgTable, uuid, text, timestamp } from 'drizzle-orm/pg-core';
|
||||
import { sql } from 'drizzle-orm';
|
||||
|
||||
export const teams = pgTable('teams', {
|
||||
id: uuid('id')
|
||||
.primaryKey()
|
||||
.default(sql`gen_random_uuid()`),
|
||||
name: text('name').notNull(),
|
||||
created_at: timestamp('created_at', { precision: 6, mode: 'string' })
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
updated_at: timestamp('updated_at', { precision: 6, mode: 'string' })
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
});
|
||||
23
src/server/modules/database/schema/pg/users.ts
Normal file
23
src/server/modules/database/schema/pg/users.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { pgTable, uuid, text, timestamp } from 'drizzle-orm/pg-core';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { teams } from './teams';
|
||||
|
||||
export const users = pgTable('users', {
|
||||
id: uuid('id')
|
||||
.primaryKey()
|
||||
.default(sql`gen_random_uuid()`),
|
||||
email: text('email').notNull().unique(),
|
||||
password_hash: text('password_hash').notNull(),
|
||||
role: text('role', { enum: ['owner', 'admin', 'member'] })
|
||||
.notNull()
|
||||
.default('member'),
|
||||
team_id: uuid('team_id')
|
||||
.notNull()
|
||||
.references(() => teams.id),
|
||||
created_at: timestamp('created_at', { precision: 6, mode: 'string' })
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
updated_at: timestamp('updated_at', { precision: 6, mode: 'string' })
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
});
|
||||
2
src/server/modules/database/schema/sqlite/index.ts
Normal file
2
src/server/modules/database/schema/sqlite/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export { teams } from './teams';
|
||||
export { users } from './users';
|
||||
15
src/server/modules/database/schema/sqlite/teams.ts
Normal file
15
src/server/modules/database/schema/sqlite/teams.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
||||
import { sql } from 'drizzle-orm';
|
||||
|
||||
export const teams = sqliteTable('teams', {
|
||||
id: text('id')
|
||||
.primaryKey()
|
||||
.$defaultFn(() => crypto.randomUUID()),
|
||||
name: text('name').notNull(),
|
||||
created_at: text('created_at')
|
||||
.notNull()
|
||||
.default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`),
|
||||
updated_at: text('updated_at')
|
||||
.notNull()
|
||||
.default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`),
|
||||
});
|
||||
23
src/server/modules/database/schema/sqlite/users.ts
Normal file
23
src/server/modules/database/schema/sqlite/users.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { teams } from './teams';
|
||||
|
||||
export const users = sqliteTable('users', {
|
||||
id: text('id')
|
||||
.primaryKey()
|
||||
.$defaultFn(() => crypto.randomUUID()),
|
||||
email: text('email').notNull().unique(),
|
||||
password_hash: text('password_hash').notNull(),
|
||||
role: text('role', { enum: ['owner', 'admin', 'member'] })
|
||||
.notNull()
|
||||
.default('member'),
|
||||
team_id: text('team_id')
|
||||
.notNull()
|
||||
.references(() => teams.id),
|
||||
created_at: text('created_at')
|
||||
.notNull()
|
||||
.default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`),
|
||||
updated_at: text('updated_at')
|
||||
.notNull()
|
||||
.default(sql`(strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))`),
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue