update database

This commit is contained in:
2026-06-28 21:40:27 +03:30
parent 83770a36e6
commit 6dd5526646
9 changed files with 291 additions and 13 deletions
+12
View File
@@ -0,0 +1,12 @@
.env
.env.*
!.env.example
node_modules
dist
coverage
.git
.github
*.md
test
.vscode
.idea
+7 -1
View File
@@ -5,6 +5,9 @@ DB_USERNAME=
DB_PASSWORD= DB_PASSWORD=
DB_NAME= DB_NAME=
# CapRover production example:
# DB_HOST=srv-captain--growdb
# SMS # SMS
SMS_API_KEY= SMS_API_KEY=
SMS_SENDER= SMS_SENDER=
@@ -24,5 +27,8 @@ JWT_REFRESH_SECRET=
JWT_REFRESH_EXPIRES_IN=7d JWT_REFRESH_EXPIRES_IN=7d
# App # App
PORT=3000 PORT=4000
NODE_ENV=development NODE_ENV=development
# CORS (comma-separated origins, e.g. https://app.example.com)
# CORS_ORIGIN=
+4
View File
@@ -0,0 +1,4 @@
{
"schemaVersion": 2,
"dockerfilePath": "./Dockerfile"
}
+3
View File
@@ -12,6 +12,9 @@
"start:dev": "NODE_ENV=development nest start --watch", "start:dev": "NODE_ENV=development nest start --watch",
"start:debug": "nest start --debug --watch", "start:debug": "nest start --debug --watch",
"start:prod": "node dist/main", "start:prod": "node dist/main",
"migration:run": "typeorm migration:run -d dist/database/data-source.js",
"migration:revert": "typeorm migration:revert -d dist/database/data-source.js",
"migration:generate": "typeorm migration:generate -d src/database/data-source.ts",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"test": "jest", "test": "jest",
"test:watch": "jest --watch", "test:watch": "jest --watch",
+2 -11
View File
@@ -4,6 +4,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthModule } from './auth/auth.module'; import { AuthModule } from './auth/auth.module';
import { CustomersModule } from './customers/customers.module'; import { CustomersModule } from './customers/customers.module';
import configuration from './config/configuration'; import configuration from './config/configuration';
import { getTypeOrmOptions } from './database/typeorm-options';
import { SalonsModule } from './salons/salons.module'; import { SalonsModule } from './salons/salons.module';
import { UsersModule } from './users/users.module'; import { UsersModule } from './users/users.module';
@@ -13,17 +14,7 @@ import { UsersModule } from './users/users.module';
isGlobal: true, isGlobal: true,
load: [configuration], load: [configuration],
}), }),
TypeOrmModule.forRoot({ TypeOrmModule.forRoot(getTypeOrmOptions()),
type: 'postgres',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT as string),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
autoLoadEntities: true,
entities: [__dirname + '/**/*.entity.{js,ts}'],
synchronize: process.env.NODE_ENV !== 'production',
}),
AuthModule, AuthModule,
UsersModule, UsersModule,
SalonsModule, SalonsModule,
+13
View File
@@ -0,0 +1,13 @@
import 'dotenv/config';
import { DataSource } from 'typeorm';
export default new DataSource({
type: 'postgres',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT ?? '5432', 10),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
entities: ['src/**/*.entity.ts'],
migrations: ['src/database/migrations/*.ts'],
});
@@ -0,0 +1,223 @@
import { MigrationInterface, QueryRunner, Table, TableForeignKey } from 'typeorm';
export class InitialSchema1719600000000 implements MigrationInterface {
name = 'InitialSchema1719600000000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TYPE "public"."users_role_enum" AS ENUM('admin', 'salon-owner', 'customer')`,
);
await queryRunner.createTable(
new Table({
name: 'users',
columns: [
{
name: 'id',
type: 'uuid',
isPrimary: true,
generationStrategy: 'uuid',
default: 'gen_random_uuid()',
},
{ name: 'firstName', type: 'varchar', length: '100' },
{ name: 'lastName', type: 'varchar', length: '100' },
{ name: 'mobile', type: 'varchar', length: '11', isUnique: true },
{
name: 'role',
type: 'users_role_enum',
default: `'customer'`,
},
{ name: 'createdAt', type: 'timestamp', default: 'now()' },
{ name: 'updatedAt', type: 'timestamp', default: 'now()' },
],
}),
true,
);
await queryRunner.createTable(
new Table({
name: 'salons',
columns: [
{
name: 'id',
type: 'uuid',
isPrimary: true,
generationStrategy: 'uuid',
default: 'gen_random_uuid()',
},
{ name: 'name', type: 'varchar', length: '200' },
{ name: 'description', type: 'text', isNullable: true },
{ name: 'address', type: 'varchar', length: '500' },
{ name: 'phone', type: 'varchar', length: '11' },
{ name: 'ownerId', type: 'uuid' },
{ name: 'createdAt', type: 'timestamp', default: 'now()' },
{ name: 'updatedAt', type: 'timestamp', default: 'now()' },
],
}),
true,
);
await queryRunner.createForeignKey(
'salons',
new TableForeignKey({
columnNames: ['ownerId'],
referencedTableName: 'users',
referencedColumnNames: ['id'],
onDelete: 'CASCADE',
}),
);
await queryRunner.createTable(
new Table({
name: 'customers',
columns: [
{
name: 'id',
type: 'uuid',
isPrimary: true,
generationStrategy: 'uuid',
default: 'gen_random_uuid()',
},
{ name: 'userId', type: 'uuid', isUnique: true },
{ name: 'dateOfBirth', type: 'date' },
{ name: 'address', type: 'varchar', length: '500' },
{ name: 'createdAt', type: 'timestamp', default: 'now()' },
{ name: 'updatedAt', type: 'timestamp', default: 'now()' },
],
}),
true,
);
await queryRunner.createForeignKey(
'customers',
new TableForeignKey({
columnNames: ['userId'],
referencedTableName: 'users',
referencedColumnNames: ['id'],
onDelete: 'CASCADE',
}),
);
await queryRunner.createTable(
new Table({
name: 'otps',
columns: [
{
name: 'id',
type: 'uuid',
isPrimary: true,
generationStrategy: 'uuid',
default: 'gen_random_uuid()',
},
{ name: 'mobile', type: 'varchar', length: '11' },
{ name: 'code', type: 'varchar' },
{ name: 'expiresAt', type: 'timestamp' },
{ name: 'attempts', type: 'int', default: 0 },
{ name: 'verified', type: 'boolean', default: false },
{ name: 'createdAt', type: 'timestamp', default: 'now()' },
],
indices: [{ columnNames: ['mobile'] }],
}),
true,
);
await queryRunner.createTable(
new Table({
name: 'refresh_tokens',
columns: [
{
name: 'id',
type: 'uuid',
isPrimary: true,
generationStrategy: 'uuid',
default: 'gen_random_uuid()',
},
{ name: 'userId', type: 'uuid' },
{ name: 'tokenHash', type: 'varchar' },
{ name: 'expiresAt', type: 'timestamp' },
{ name: 'revokedAt', type: 'timestamp', isNullable: true },
{ name: 'createdAt', type: 'timestamp', default: 'now()' },
],
}),
true,
);
await queryRunner.createForeignKey(
'refresh_tokens',
new TableForeignKey({
columnNames: ['userId'],
referencedTableName: 'users',
referencedColumnNames: ['id'],
onDelete: 'CASCADE',
}),
);
await queryRunner.createTable(
new Table({
name: 'permissions',
columns: [
{
name: 'id',
type: 'uuid',
isPrimary: true,
generationStrategy: 'uuid',
default: 'gen_random_uuid()',
},
{ name: 'code', type: 'varchar', isUnique: true },
{ name: 'description', type: 'varchar' },
],
}),
true,
);
await queryRunner.createTable(
new Table({
name: 'user_permissions',
columns: [
{
name: 'id',
type: 'uuid',
isPrimary: true,
generationStrategy: 'uuid',
default: 'gen_random_uuid()',
},
{ name: 'userId', type: 'uuid' },
{ name: 'permissionId', type: 'uuid' },
],
uniques: [{ columnNames: ['userId', 'permissionId'] }],
}),
true,
);
await queryRunner.createForeignKey(
'user_permissions',
new TableForeignKey({
columnNames: ['userId'],
referencedTableName: 'users',
referencedColumnNames: ['id'],
onDelete: 'CASCADE',
}),
);
await queryRunner.createForeignKey(
'user_permissions',
new TableForeignKey({
columnNames: ['permissionId'],
referencedTableName: 'permissions',
referencedColumnNames: ['id'],
onDelete: 'CASCADE',
}),
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable('user_permissions', true);
await queryRunner.dropTable('permissions', true);
await queryRunner.dropTable('refresh_tokens', true);
await queryRunner.dropTable('otps', true);
await queryRunner.dropTable('customers', true);
await queryRunner.dropTable('salons', true);
await queryRunner.dropTable('users', true);
await queryRunner.query(`DROP TYPE "public"."users_role_enum"`);
}
}
+19
View File
@@ -0,0 +1,19 @@
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
export function getTypeOrmOptions(): TypeOrmModuleOptions {
const isProduction = process.env.NODE_ENV === 'production';
return {
type: 'postgres',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT ?? '5432', 10),
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
autoLoadEntities: true,
entities: [__dirname + '/../**/*.entity.{js,ts}'],
migrations: [__dirname + '/migrations/*.{js,ts}'],
synchronize: !isProduction,
migrationsRun: isProduction,
};
}
+7
View File
@@ -18,7 +18,14 @@ async function bootstrap() {
app.setGlobalPrefix('api'); app.setGlobalPrefix('api');
// Cors // Cors
const corsOrigin = process.env.CORS_ORIGIN;
if (corsOrigin) {
app.enableCors({
origin: corsOrigin.split(',').map((origin) => origin.trim()),
});
} else {
app.enableCors(); app.enableCors();
}
await app.listen(process.env.PORT ?? 3000); await app.listen(process.env.PORT ?? 3000);
} }