Trần Viết Sơn commited on
Commit
2b0b6de
2 Parent(s): 3cdf53d c114c72

Merge pull request #1 from PBL6-team-CATS/feat/backend/initial-config

Browse files
backend/README.md CHANGED
@@ -12,7 +12,7 @@ $ npm install
12
 
13
  #### 2. Initialize database
14
 
15
- - Download and install `mysql`.
16
  - Create schema name `pbl6` or whatever.
17
 
18
  #### 3. Create .env file
@@ -32,5 +32,3 @@ $ npm run start:dev
32
  ## Run tests
33
 
34
  We currently don't care about test
35
-
36
- Notice: We use `synchronize: true (src/config/database)`, so that no need to use migration
 
12
 
13
  #### 2. Initialize database
14
 
15
+ - Download and install `postgreSQL`.
16
  - Create schema name `pbl6` or whatever.
17
 
18
  #### 3. Create .env file
 
32
  ## Run tests
33
 
34
  We currently don't care about test
 
 
backend/package-lock.json CHANGED
@@ -16,6 +16,7 @@
16
  "@nestjs/typeorm": "^10.0.2",
17
  "dotenv": "^16.4.5",
18
  "mysql2": "^3.11.3",
 
19
  "reflect-metadata": "^0.2.0",
20
  "rxjs": "^7.8.1",
21
  "typeorm": "^0.3.20"
@@ -39,7 +40,7 @@
39
  "supertest": "^7.0.0",
40
  "ts-jest": "^29.1.0",
41
  "ts-loader": "^9.4.3",
42
- "ts-node": "^10.9.1",
43
  "tsconfig-paths": "^4.2.0",
44
  "typescript": "^5.1.3"
45
  }
@@ -6839,6 +6840,95 @@
6839
  "node": ">=8"
6840
  }
6841
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6842
  "node_modules/picocolors": {
6843
  "version": "1.1.0",
6844
  "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
@@ -6939,6 +7029,45 @@
6939
  "node": ">=4"
6940
  }
6941
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6942
  "node_modules/prelude-ls": {
6943
  "version": "1.2.1",
6944
  "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -7665,6 +7794,15 @@
7665
  "node": ">=0.10.0"
7666
  }
7667
  },
 
 
 
 
 
 
 
 
 
7668
  "node_modules/sprintf-js": {
7669
  "version": "1.0.3",
7670
  "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
@@ -8245,6 +8383,7 @@
8245
  "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
8246
  "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
8247
  "devOptional": true,
 
8248
  "dependencies": {
8249
  "@cspotcode/source-map-support": "^0.8.0",
8250
  "@tsconfig/node10": "^1.0.7",
 
16
  "@nestjs/typeorm": "^10.0.2",
17
  "dotenv": "^16.4.5",
18
  "mysql2": "^3.11.3",
19
+ "pg": "^8.13.0",
20
  "reflect-metadata": "^0.2.0",
21
  "rxjs": "^7.8.1",
22
  "typeorm": "^0.3.20"
 
40
  "supertest": "^7.0.0",
41
  "ts-jest": "^29.1.0",
42
  "ts-loader": "^9.4.3",
43
+ "ts-node": "^10.9.2",
44
  "tsconfig-paths": "^4.2.0",
45
  "typescript": "^5.1.3"
46
  }
 
6840
  "node": ">=8"
6841
  }
6842
  },
6843
+ "node_modules/pg": {
6844
+ "version": "8.13.0",
6845
+ "resolved": "https://registry.npmjs.org/pg/-/pg-8.13.0.tgz",
6846
+ "integrity": "sha512-34wkUTh3SxTClfoHB3pQ7bIMvw9dpFU1audQQeZG837fmHfHpr14n/AELVDoOYVDW2h5RDWU78tFjkD+erSBsw==",
6847
+ "license": "MIT",
6848
+ "dependencies": {
6849
+ "pg-connection-string": "^2.7.0",
6850
+ "pg-pool": "^3.7.0",
6851
+ "pg-protocol": "^1.7.0",
6852
+ "pg-types": "^2.1.0",
6853
+ "pgpass": "1.x"
6854
+ },
6855
+ "engines": {
6856
+ "node": ">= 8.0.0"
6857
+ },
6858
+ "optionalDependencies": {
6859
+ "pg-cloudflare": "^1.1.1"
6860
+ },
6861
+ "peerDependencies": {
6862
+ "pg-native": ">=3.0.1"
6863
+ },
6864
+ "peerDependenciesMeta": {
6865
+ "pg-native": {
6866
+ "optional": true
6867
+ }
6868
+ }
6869
+ },
6870
+ "node_modules/pg-cloudflare": {
6871
+ "version": "1.1.1",
6872
+ "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz",
6873
+ "integrity": "sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==",
6874
+ "license": "MIT",
6875
+ "optional": true
6876
+ },
6877
+ "node_modules/pg-connection-string": {
6878
+ "version": "2.7.0",
6879
+ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.7.0.tgz",
6880
+ "integrity": "sha512-PI2W9mv53rXJQEOb8xNR8lH7Hr+EKa6oJa38zsK0S/ky2er16ios1wLKhZyxzD7jUReiWokc9WK5nxSnC7W1TA==",
6881
+ "license": "MIT"
6882
+ },
6883
+ "node_modules/pg-int8": {
6884
+ "version": "1.0.1",
6885
+ "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
6886
+ "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
6887
+ "license": "ISC",
6888
+ "engines": {
6889
+ "node": ">=4.0.0"
6890
+ }
6891
+ },
6892
+ "node_modules/pg-pool": {
6893
+ "version": "3.7.0",
6894
+ "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.7.0.tgz",
6895
+ "integrity": "sha512-ZOBQForurqh4zZWjrgSwwAtzJ7QiRX0ovFkZr2klsen3Nm0aoh33Ls0fzfv3imeH/nw/O27cjdz5kzYJfeGp/g==",
6896
+ "license": "MIT",
6897
+ "peerDependencies": {
6898
+ "pg": ">=8.0"
6899
+ }
6900
+ },
6901
+ "node_modules/pg-protocol": {
6902
+ "version": "1.7.0",
6903
+ "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.7.0.tgz",
6904
+ "integrity": "sha512-hTK/mE36i8fDDhgDFjy6xNOG+LCorxLG3WO17tku+ij6sVHXh1jQUJ8hYAnRhNla4QVD2H8er/FOjc/+EgC6yQ==",
6905
+ "license": "MIT"
6906
+ },
6907
+ "node_modules/pg-types": {
6908
+ "version": "2.2.0",
6909
+ "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
6910
+ "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
6911
+ "license": "MIT",
6912
+ "dependencies": {
6913
+ "pg-int8": "1.0.1",
6914
+ "postgres-array": "~2.0.0",
6915
+ "postgres-bytea": "~1.0.0",
6916
+ "postgres-date": "~1.0.4",
6917
+ "postgres-interval": "^1.1.0"
6918
+ },
6919
+ "engines": {
6920
+ "node": ">=4"
6921
+ }
6922
+ },
6923
+ "node_modules/pgpass": {
6924
+ "version": "1.0.5",
6925
+ "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
6926
+ "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
6927
+ "license": "MIT",
6928
+ "dependencies": {
6929
+ "split2": "^4.1.0"
6930
+ }
6931
+ },
6932
  "node_modules/picocolors": {
6933
  "version": "1.1.0",
6934
  "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz",
 
7029
  "node": ">=4"
7030
  }
7031
  },
7032
+ "node_modules/postgres-array": {
7033
+ "version": "2.0.0",
7034
+ "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
7035
+ "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
7036
+ "license": "MIT",
7037
+ "engines": {
7038
+ "node": ">=4"
7039
+ }
7040
+ },
7041
+ "node_modules/postgres-bytea": {
7042
+ "version": "1.0.0",
7043
+ "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
7044
+ "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
7045
+ "license": "MIT",
7046
+ "engines": {
7047
+ "node": ">=0.10.0"
7048
+ }
7049
+ },
7050
+ "node_modules/postgres-date": {
7051
+ "version": "1.0.7",
7052
+ "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
7053
+ "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
7054
+ "license": "MIT",
7055
+ "engines": {
7056
+ "node": ">=0.10.0"
7057
+ }
7058
+ },
7059
+ "node_modules/postgres-interval": {
7060
+ "version": "1.2.0",
7061
+ "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
7062
+ "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
7063
+ "license": "MIT",
7064
+ "dependencies": {
7065
+ "xtend": "^4.0.0"
7066
+ },
7067
+ "engines": {
7068
+ "node": ">=0.10.0"
7069
+ }
7070
+ },
7071
  "node_modules/prelude-ls": {
7072
  "version": "1.2.1",
7073
  "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
 
7794
  "node": ">=0.10.0"
7795
  }
7796
  },
7797
+ "node_modules/split2": {
7798
+ "version": "4.2.0",
7799
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
7800
+ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
7801
+ "license": "ISC",
7802
+ "engines": {
7803
+ "node": ">= 10.x"
7804
+ }
7805
+ },
7806
  "node_modules/sprintf-js": {
7807
  "version": "1.0.3",
7808
  "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
 
8383
  "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
8384
  "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
8385
  "devOptional": true,
8386
+ "license": "MIT",
8387
  "dependencies": {
8388
  "@cspotcode/source-map-support": "^0.8.0",
8389
  "@tsconfig/node10": "^1.0.7",
backend/package.json CHANGED
@@ -1,4 +1,5 @@
1
  {
 
2
  "name": "backend",
3
  "version": "0.0.1",
4
  "description": "",
@@ -18,10 +19,10 @@
18
  "test:cov": "jest --coverage",
19
  "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
20
  "test:e2e": "jest --config ./test/jest-e2e.json",
21
- "typeorm": "typeorm-ts-node-commonjs",
22
- "db:migrate:generate": "npm run typeorm -d ./src/config/typeorm.ts migration:generate",
23
- "db:migrate:up": "npm run typeorm -d ./src/config/typeorm.ts migration:run",
24
- "db:migrate:down": "npm run typeorm -d ./src/config/typeorm.ts migration:revert"
25
  },
26
  "dependencies": {
27
  "@nestjs/common": "^10.0.0",
@@ -31,6 +32,7 @@
31
  "@nestjs/typeorm": "^10.0.2",
32
  "dotenv": "^16.4.5",
33
  "mysql2": "^3.11.3",
 
34
  "reflect-metadata": "^0.2.0",
35
  "rxjs": "^7.8.1",
36
  "typeorm": "^0.3.20"
@@ -54,7 +56,7 @@
54
  "supertest": "^7.0.0",
55
  "ts-jest": "^29.1.0",
56
  "ts-loader": "^9.4.3",
57
- "ts-node": "^10.9.1",
58
  "tsconfig-paths": "^4.2.0",
59
  "typescript": "^5.1.3"
60
  },
 
1
  {
2
+ "type": "module",
3
  "name": "backend",
4
  "version": "0.0.1",
5
  "description": "",
 
19
  "test:cov": "jest --coverage",
20
  "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
21
  "test:e2e": "jest --config ./test/jest-e2e.json",
22
+ "typeorm": "typeorm-ts-node-esm",
23
+ "db:migrate:generate": "npm run typeorm migration:generate -- -d ./src/config/typeorm.ts",
24
+ "db:migrate:up": "npm run typeorm migration:run -- -d ./src/config/typeorm.ts",
25
+ "db:migrate:down": "npm run typeorm migration:revert -- -d ./src/config/typeorm.ts"
26
  },
27
  "dependencies": {
28
  "@nestjs/common": "^10.0.0",
 
32
  "@nestjs/typeorm": "^10.0.2",
33
  "dotenv": "^16.4.5",
34
  "mysql2": "^3.11.3",
35
+ "pg": "^8.13.0",
36
  "reflect-metadata": "^0.2.0",
37
  "rxjs": "^7.8.1",
38
  "typeorm": "^0.3.20"
 
56
  "supertest": "^7.0.0",
57
  "ts-jest": "^29.1.0",
58
  "ts-loader": "^9.4.3",
59
+ "ts-node": "^10.9.2",
60
  "tsconfig-paths": "^4.2.0",
61
  "typescript": "^5.1.3"
62
  },
backend/src/app.controller.ts CHANGED
@@ -1,5 +1,5 @@
1
  import { Controller, Get } from '@nestjs/common';
2
- import { AppService } from './app.service';
3
 
4
  @Controller()
5
  export class AppController {
 
1
  import { Controller, Get } from '@nestjs/common';
2
+ import { AppService } from './app.service.js';
3
 
4
  @Controller()
5
  export class AppController {
backend/src/app.module.ts CHANGED
@@ -1,10 +1,13 @@
1
- import { Module } from '@nestjs/common';
2
- import { AppController } from './app.controller';
3
- import { AppService } from './app.service';
4
  import { ConfigModule } from '@nestjs/config';
5
  import { TypeOrmModule } from '@nestjs/typeorm';
6
  import { configuration } from './config/config.js';
7
  import { DatabaseConfigService } from './config/database.js';
 
 
 
8
 
9
  @Module({
10
  imports: [
@@ -16,8 +19,14 @@ import { DatabaseConfigService } from './config/database.js';
16
  imports: [ConfigModule],
17
  useClass: DatabaseConfigService,
18
  }),
 
19
  ],
20
  controllers: [AppController],
21
  providers: [AppService],
22
  })
23
- export class AppModule {}
 
 
 
 
 
 
1
+ import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
2
+ import { AppController } from './app.controller.js';
3
+ import { AppService } from './app.service.js';
4
  import { ConfigModule } from '@nestjs/config';
5
  import { TypeOrmModule } from '@nestjs/typeorm';
6
  import { configuration } from './config/config.js';
7
  import { DatabaseConfigService } from './config/database.js';
8
+ import { AppLoggerMiddleware } from './common/middlewares/app-logger.middleware.js';
9
+ import { DeviceInfoMiddleware } from './common/middlewares/device-info.middleware.js';
10
+ import { UserModule } from './modules/user/user.module.js';
11
 
12
  @Module({
13
  imports: [
 
19
  imports: [ConfigModule],
20
  useClass: DatabaseConfigService,
21
  }),
22
+ UserModule,
23
  ],
24
  controllers: [AppController],
25
  providers: [AppService],
26
  })
27
+ export class AppModule implements NestModule {
28
+ configure(consumer: MiddlewareConsumer): void {
29
+ consumer.apply(AppLoggerMiddleware).forRoutes('*');
30
+ consumer.apply(DeviceInfoMiddleware).forRoutes('*');
31
+ }
32
+ }
backend/src/common/middlewares/app-logger.middleware.ts ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Injectable, NestMiddleware, Logger } from '@nestjs/common';
2
+
3
+ import { Request, Response, NextFunction } from 'express';
4
+
5
+ @Injectable()
6
+ export class AppLoggerMiddleware implements NestMiddleware {
7
+ private logger = new Logger();
8
+
9
+ use(request: Request, response: Response, next: NextFunction): void {
10
+ const { ip, method, baseUrl: url } = request;
11
+ const userAgent = request.get('user-agent') || '';
12
+
13
+ response.on('close', () => {
14
+ const { statusCode } = response;
15
+ const contentLength = response.get('content-length');
16
+
17
+ const queries = Object.entries(request.query);
18
+ const appendUrl = queries.map((query) => query.join('=')).join('&');
19
+
20
+ this.logger.log(
21
+ `${method} ${url + (queries.length ? '?' + appendUrl : '')} ${statusCode} ${contentLength} - ${userAgent} ${ip}`,
22
+ );
23
+ });
24
+
25
+ next();
26
+ }
27
+ }
backend/src/common/middlewares/device-info.middleware.ts ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Injectable, NestMiddleware } from '@nestjs/common';
2
+ import { Request, Response, NextFunction } from 'express';
3
+
4
+ @Injectable()
5
+ export class DeviceInfoMiddleware implements NestMiddleware {
6
+ use(req: Request, res: Response, next: NextFunction) {
7
+ const deviceInfo = {
8
+ ip: req.ip,
9
+ userAgent: req.headers['user-agent'],
10
+ };
11
+ req['device-info'] = deviceInfo;
12
+ next();
13
+ }
14
+ }
backend/src/config/database.ts CHANGED
@@ -1,7 +1,12 @@
1
  import { Injectable } from '@nestjs/common';
2
  import { ConfigService } from '@nestjs/config';
3
  import { TypeOrmModuleOptions, TypeOrmOptionsFactory } from '@nestjs/typeorm';
4
- import * as path from 'path';
 
 
 
 
 
5
 
6
  @Injectable()
7
  export class DatabaseConfigService implements TypeOrmOptionsFactory {
@@ -9,14 +14,13 @@ export class DatabaseConfigService implements TypeOrmOptionsFactory {
9
 
10
  createTypeOrmOptions(): TypeOrmModuleOptions {
11
  return {
12
- type: 'mysql',
13
  host: this.configService.get('db.host'),
14
  port: this.configService.get('db.port'),
15
  username: this.configService.get('db.user_name'),
16
  database: this.configService.get('db.name'),
17
  password: this.configService.get('db.password'),
18
  entities: [path.join(__dirname, '../**/*.entity{.ts,.js}')],
19
- synchronize: true,
20
  maxQueryExecutionTime: this.configService.get('db.slow_limit'),
21
  };
22
  }
 
1
  import { Injectable } from '@nestjs/common';
2
  import { ConfigService } from '@nestjs/config';
3
  import { TypeOrmModuleOptions, TypeOrmOptionsFactory } from '@nestjs/typeorm';
4
+
5
+ import path from 'path';
6
+ import { fileURLToPath } from 'url';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
 
11
  @Injectable()
12
  export class DatabaseConfigService implements TypeOrmOptionsFactory {
 
14
 
15
  createTypeOrmOptions(): TypeOrmModuleOptions {
16
  return {
17
+ type: 'postgres',
18
  host: this.configService.get('db.host'),
19
  port: this.configService.get('db.port'),
20
  username: this.configService.get('db.user_name'),
21
  database: this.configService.get('db.name'),
22
  password: this.configService.get('db.password'),
23
  entities: [path.join(__dirname, '../**/*.entity{.ts,.js}')],
 
24
  maxQueryExecutionTime: this.configService.get('db.slow_limit'),
25
  };
26
  }
backend/src/config/typeorm.ts CHANGED
@@ -1,11 +1,16 @@
1
  import { DataSource } from 'typeorm';
2
  import { config } from 'dotenv';
 
3
  config();
4
- import * as path from 'path';
 
 
 
 
5
  const migrationsPath = 'src/migrations/*.ts';
6
 
7
  export default new DataSource({
8
- type: 'mysql',
9
  host: process.env.DB_HOST,
10
  port: Number(process.env.DB_PORT),
11
  database: process.env.DB_NAME,
 
1
  import { DataSource } from 'typeorm';
2
  import { config } from 'dotenv';
3
+ import path from 'path';
4
  config();
5
+ import { fileURLToPath } from 'url';
6
+
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+
10
  const migrationsPath = 'src/migrations/*.ts';
11
 
12
  export default new DataSource({
13
+ type: 'postgres',
14
  host: process.env.DB_HOST,
15
  port: Number(process.env.DB_PORT),
16
  database: process.env.DB_NAME,
backend/src/entities/branch.entity.ts ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ BaseEntity,
3
+ Column,
4
+ Entity,
5
+ ManyToOne,
6
+ PrimaryGeneratedColumn,
7
+ Relation,
8
+ } from 'typeorm';
9
+ import { UserEntity } from './user.entity.js';
10
+
11
+ @Entity('branches')
12
+ export class BranchEntity extends BaseEntity {
13
+ @PrimaryGeneratedColumn('uuid')
14
+ id: string;
15
+
16
+ @Column()
17
+ name: string;
18
+
19
+ @Column()
20
+ location: string;
21
+
22
+ @Column()
23
+ phone_number: string;
24
+
25
+ @ManyToOne(() => UserEntity, (user) => user.branches)
26
+ owner: Relation<UserEntity>;
27
+
28
+ @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
29
+ create_at: Date;
30
+ }
backend/src/entities/user.entity.ts CHANGED
@@ -1,4 +1,12 @@
1
- import { Entity, Column, BaseEntity, PrimaryGeneratedColumn } from 'typeorm';
 
 
 
 
 
 
 
 
2
 
3
  @Entity('users')
4
  export class UserEntity extends BaseEntity {
@@ -24,11 +32,14 @@ export class UserEntity extends BaseEntity {
24
  role_id: number;
25
 
26
  @Column({ nullable: true })
27
- decoded_password: string;
28
 
29
  @Column({ default: true })
30
  is_valid: boolean;
31
 
32
  @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
33
- create_time: Date;
 
 
 
34
  }
 
1
+ import {
2
+ Entity,
3
+ Column,
4
+ BaseEntity,
5
+ PrimaryGeneratedColumn,
6
+ OneToMany,
7
+ Relation,
8
+ } from 'typeorm';
9
+ import { BranchEntity } from './branch.entity.js';
10
 
11
  @Entity('users')
12
  export class UserEntity extends BaseEntity {
 
32
  role_id: number;
33
 
34
  @Column({ nullable: true })
35
+ hash_password: string;
36
 
37
  @Column({ default: true })
38
  is_valid: boolean;
39
 
40
  @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
41
+ create_at: Date;
42
+
43
+ @OneToMany(() => BranchEntity, (branch) => branch.owner)
44
+ branches: Relation<BranchEntity>[];
45
  }
backend/src/main.ts CHANGED
@@ -1,8 +1,8 @@
1
  import { NestFactory } from '@nestjs/core';
2
- import { AppModule } from './app.module';
3
 
4
  async function bootstrap() {
5
- const app = await NestFactory.create(AppModule);
6
  await app.listen(3000);
7
  }
8
  bootstrap();
 
1
  import { NestFactory } from '@nestjs/core';
2
+ import { AppModule } from './app.module.js';
3
 
4
  async function bootstrap() {
5
+ const app = await NestFactory.create(AppModule, { cors: true });
6
  await app.listen(3000);
7
  }
8
  bootstrap();
backend/src/migrations/1728840582079-Addf.ts ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class Addf1728840582079 implements MigrationInterface {
4
+ name = 'Addf1728840582079'
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(`CREATE TABLE "branches" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying NOT NULL, "location" character varying NOT NULL, "phone_number" character varying NOT NULL, "create_at" TIMESTAMP NOT NULL DEFAULT now(), "ownerId" uuid, CONSTRAINT "PK_7f37d3b42defea97f1df0d19535" PRIMARY KEY ("id"))`);
8
+ await queryRunner.query(`CREATE TABLE "users" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "avatar" character varying, "full_name" character varying, "phone_number" character varying, "address" character varying, "email" character varying, "role_id" integer, "hash_password" character varying, "is_valid" boolean NOT NULL DEFAULT true, "create_at" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id"))`);
9
+ await queryRunner.query(`ALTER TABLE "branches" ADD CONSTRAINT "FK_8c6ae9f9c654c4fac71bccbb7ed" FOREIGN KEY ("ownerId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
10
+ }
11
+
12
+ public async down(queryRunner: QueryRunner): Promise<void> {
13
+ await queryRunner.query(`ALTER TABLE "branches" DROP CONSTRAINT "FK_8c6ae9f9c654c4fac71bccbb7ed"`);
14
+ await queryRunner.query(`DROP TABLE "users"`);
15
+ await queryRunner.query(`DROP TABLE "branches"`);
16
+ }
17
+
18
+ }
backend/src/modules/user/dto/create-user.dto.ts ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ export interface CreateUserDto {
2
+ id: string;
3
+ full_name: string;
4
+ }
backend/src/modules/user/user.controller.ts ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import {
2
+ Controller,
3
+ Get,
4
+ Post,
5
+ Body,
6
+ Patch,
7
+ Param,
8
+ Delete,
9
+ } from '@nestjs/common';
10
+ import { UserService } from './user.service.js';
11
+ import { CreateUserDto } from './dto/create-user.dto.js';
12
+
13
+ @Controller('users')
14
+ export class UserController {
15
+ constructor(private readonly userService: UserService) {}
16
+
17
+ @Post()
18
+ async create(@Body() createUserDto: CreateUserDto) {
19
+ return this.userService.create(createUserDto);
20
+ }
21
+
22
+ @Get()
23
+ async findAll() {
24
+ return this.userService.findAll();
25
+ }
26
+
27
+ @Get(':id')
28
+ async findOne(@Param('id') id: string) {
29
+ return this.userService.findOne(id);
30
+ }
31
+
32
+ @Delete(':id')
33
+ async remove(@Param('id') id: string) {
34
+ return this.userService.remove(+id);
35
+ }
36
+ }
backend/src/modules/user/user.module.ts ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import { Module } from '@nestjs/common';
2
+ import { UserService } from './user.service.js';
3
+ import { UserController } from './user.controller.js';
4
+
5
+ @Module({
6
+ controllers: [UserController],
7
+ providers: [UserService],
8
+ })
9
+ export class UserModule {}
backend/src/modules/user/user.service.ts ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Injectable } from '@nestjs/common';
2
+ import { CreateUserDto } from './dto/create-user.dto.js';
3
+ import { UserEntity } from '../../entities/user.entity.js';
4
+
5
+ @Injectable()
6
+ export class UserService {
7
+ async create(createUserDto: CreateUserDto) {
8
+ const user = await UserEntity.create({
9
+ ...createUserDto,
10
+ }).save();
11
+ return 'This action adds a new user';
12
+ }
13
+
14
+ async findAll() {
15
+ const users = await UserEntity.find();
16
+ return users;
17
+ }
18
+
19
+ async findOne(id: string) {
20
+ return `This action returns a #${id} user`;
21
+ }
22
+
23
+ async remove(id: number) {
24
+ return `This action removes a #${id} user`;
25
+ }
26
+ }
backend/tsconfig.json CHANGED
@@ -1,21 +1,23 @@
1
  {
2
  "compilerOptions": {
3
- "module": "commonjs",
 
4
  "declaration": true,
5
  "removeComments": true,
6
  "emitDecoratorMetadata": true,
7
  "experimentalDecorators": true,
8
  "allowSyntheticDefaultImports": true,
9
- "target": "ES2021",
10
  "sourceMap": true,
11
  "outDir": "./dist",
12
  "baseUrl": "./",
13
  "incremental": true,
14
  "skipLibCheck": true,
15
- "strictNullChecks": false,
16
- "noImplicitAny": false,
17
- "strictBindCallApply": false,
18
- "forceConsistentCasingInFileNames": false,
19
- "noFallthroughCasesInSwitch": false
 
20
  }
21
  }
 
1
  {
2
  "compilerOptions": {
3
+ "module": "ES2020",
4
+ "moduleResolution": "node",
5
  "declaration": true,
6
  "removeComments": true,
7
  "emitDecoratorMetadata": true,
8
  "experimentalDecorators": true,
9
  "allowSyntheticDefaultImports": true,
10
+ "target": "ES2020",
11
  "sourceMap": true,
12
  "outDir": "./dist",
13
  "baseUrl": "./",
14
  "incremental": true,
15
  "skipLibCheck": true,
16
+ "esModuleInterop": true,
17
+ "resolveJsonModule": true,
18
+ "types": [
19
+ "jest"
20
+ ],
21
+ "allowJs": true
22
  }
23
  }