diff --git a/src/database/migrations/1726139426994-add-column-item_id.ts b/src/database/migrations/1726139426994-add-column-item_id.ts new file mode 100644 index 0000000..30ce865 --- /dev/null +++ b/src/database/migrations/1726139426994-add-column-item_id.ts @@ -0,0 +1,17 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddColumnItemId1726139426994 implements MigrationInterface { + name = 'AddColumnItemId1726139426994'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "users_login" ADD "item_id" uuid`); + await queryRunner.query(`ALTER TABLE "log_users_login" ADD "item_id" uuid`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "log_users_login" DROP COLUMN "item_id"`, + ); + await queryRunner.query(`ALTER TABLE "users_login" DROP COLUMN "item_id"`); + } +} diff --git a/src/database/migrations/1726141393404-add-column-item_name.ts b/src/database/migrations/1726141393404-add-column-item_name.ts new file mode 100644 index 0000000..f67870b --- /dev/null +++ b/src/database/migrations/1726141393404-add-column-item_name.ts @@ -0,0 +1,23 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddColumnItemName1726141393404 implements MigrationInterface { + name = 'AddColumnItemName1726141393404'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "users_login" ADD "item_name" character varying`, + ); + await queryRunner.query( + `ALTER TABLE "log_users_login" ADD "item_name" character varying`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "log_users_login" DROP COLUMN "item_name"`, + ); + await queryRunner.query( + `ALTER TABLE "users_login" DROP COLUMN "item_name"`, + ); + } +} diff --git a/src/modules/configuration/auth/auth.module.ts b/src/modules/configuration/auth/auth.module.ts index c16d27f..82e0c62 100644 --- a/src/modules/configuration/auth/auth.module.ts +++ b/src/modules/configuration/auth/auth.module.ts @@ -13,11 +13,15 @@ import { AuthAdminQueueController } from './infrastructure/auth-admin-queue.cont import { AuthAdminQueueOrchestrator } from './domain/auth-admin-queue.orchestrator'; import { LoginAdminQueueManager } from './domain/managers/admin-queue/login-admin-queue.manager'; import { LogoutAdminQueueManager } from './domain/managers/admin-queue/logout-admin-queue.manager'; +import { UserLoginModel } from 'src/modules/user-related/user/data/models/user-login.model'; @Module({ imports: [ ConfigModule.forRoot(), - TypeOrmModule.forFeature([UserModel], CONNECTION_NAME.DEFAULT), + TypeOrmModule.forFeature( + [UserModel, UserLoginModel], + CONNECTION_NAME.DEFAULT, + ), CqrsModule, ], controllers: [AuthController, AuthAdminQueueController], diff --git a/src/modules/configuration/auth/domain/managers/admin-queue/login-admin-queue.manager.ts b/src/modules/configuration/auth/domain/managers/admin-queue/login-admin-queue.manager.ts index 465e4eb..51ac335 100644 --- a/src/modules/configuration/auth/domain/managers/admin-queue/login-admin-queue.manager.ts +++ b/src/modules/configuration/auth/domain/managers/admin-queue/login-admin-queue.manager.ts @@ -33,6 +33,10 @@ export class LoginAdminQueueManager extends BaseCustomManager { } async process(): Promise { + const itemLogin = await this.dataService.getLoginUserByItem( + this.data.item_id, + ); + // get user active by username this.userLogin = await this.dataService.getOneByOptions({ where: { @@ -42,6 +46,7 @@ export class LoginAdminQueueManager extends BaseCustomManager { }, relations: ['user_login'], }); + if (!this.userLogin) this.throwError(); // validasi password @@ -51,13 +56,22 @@ export class LoginAdminQueueManager extends BaseCustomManager { ); if (!valid) this.throwError(); - if ( - this.userLogin.user_login && - this.userLogin.role === UserRole.QUEUE_ADMIN - ) { + const userLoginItem = await this.dataService.getOneByOptions({ + where: { + id: itemLogin?.user_id, + }, + }); + + if (this.userLogin.user_login) { throw new UnauthorizedException({ statusCode: HttpStatus.UNAUTHORIZED, - message: `Gagal! akun anda sudah login di perangkat lain.`, + message: `Akun anda sudah login di perangkat lain.`, + error: 'Unauthorized', + }); + } else if (itemLogin) { + throw new UnauthorizedException({ + statusCode: HttpStatus.UNAUTHORIZED, + message: `"${userLoginItem.name}" masih login sebagai admin antrian `, error: 'Unauthorized', }); } @@ -69,6 +83,8 @@ export class LoginAdminQueueManager extends BaseCustomManager { username: this.userLogin.username, role: this.userLogin.role, user_privilege_id: this.userLogin.user_privilege_id, + item_id: this.data.item_id, + item_name: this.data.item_name, }; Logger.debug('Sign Token Admin Queue', 'LoginAdminQueueManager'); @@ -79,22 +95,21 @@ export class LoginAdminQueueManager extends BaseCustomManager { Logger.debug('Update Refresh Token Admin Queue', 'LoginAdminQueueManager'); - const newDataUser = { refresh_token: refreshToken }; - if (this.userLogin.role === UserRole.QUEUE_ADMIN) { - Object.assign(newDataUser, { - user_login: { - user_id: this.userLogin.id, - login_token: this.token, - login_date: new Date().getTime(), - }, - }); - } // Update refresh token await this.dataService.update( this.queryRunner, this.entityTarget, { id: this.userLogin.id }, - newDataUser, + { + refresh_token: refreshToken, + user_login: { + user_id: this.userLogin.id, + login_token: this.token, + login_date: new Date().getTime(), + item_id: this.data.item_id, + item_name: this.data.item_name, + }, + }, ); await this.publishEvents(); @@ -113,6 +128,8 @@ export class LoginAdminQueueManager extends BaseCustomManager { username: this.userLogin.username, role: this.userLogin.role, token: this.token, + item_id: this.data.item_id, + item_name: this.data.item_name, }; } @@ -130,6 +147,8 @@ export class LoginAdminQueueManager extends BaseCustomManager { user_id: this.userLogin.id, username: this.userLogin.username, created_at: new Date().getTime(), + item_id: this.data.item_id, + item_name: this.data.item_name, }, }, ]; diff --git a/src/modules/configuration/auth/infrastructure/auth-admin-queue.controller.ts b/src/modules/configuration/auth/infrastructure/auth-admin-queue.controller.ts index 1503130..41da84e 100644 --- a/src/modules/configuration/auth/infrastructure/auth-admin-queue.controller.ts +++ b/src/modules/configuration/auth/infrastructure/auth-admin-queue.controller.ts @@ -1,7 +1,7 @@ import { Body, Controller, Delete, Param, Post, Put } from '@nestjs/common'; import { ExcludePrivilege, Public } from 'src/core/guards'; import { ApiBearerAuth } from '@nestjs/swagger'; -import { LoginDto } from './dto/login.dto'; +import { LoginQueueDto } from './dto/login.dto'; import { AuthAdminQueueOrchestrator } from '../domain/auth-admin-queue.orchestrator'; @Controller('v1/auth/queue') @@ -10,7 +10,7 @@ export class AuthAdminQueueController { @Post() @Public(true) - async login(@Body() body: LoginDto) { + async login(@Body() body: LoginQueueDto) { return await this.orchestrator.login(body); } diff --git a/src/modules/configuration/auth/infrastructure/dto/login.dto.ts b/src/modules/configuration/auth/infrastructure/dto/login.dto.ts index 267d433..7036ea8 100644 --- a/src/modules/configuration/auth/infrastructure/dto/login.dto.ts +++ b/src/modules/configuration/auth/infrastructure/dto/login.dto.ts @@ -11,3 +11,21 @@ export class LoginDto implements LoginRequest { @IsString() password: string; } + +export class LoginQueueDto implements LoginRequest { + @ApiProperty({ name: 'username', required: true, default: 'superadmin' }) + @IsString() + username: string; + + @ApiProperty({ name: 'password', required: true, default: 'Eigen123!' }) + @IsString() + password: string; + + @ApiProperty({ name: 'item_id', required: true, default: 'string' }) + @IsString() + item_id: string; + + @ApiProperty({ name: 'item_name', required: true, default: 'string' }) + @IsString() + item_name: string; +} diff --git a/src/modules/configuration/couch/couch.module.ts b/src/modules/configuration/couch/couch.module.ts index c1fd388..6fd848f 100644 --- a/src/modules/configuration/couch/couch.module.ts +++ b/src/modules/configuration/couch/couch.module.ts @@ -51,6 +51,7 @@ import { import { SeasonPeriodDataService } from 'src/modules/season-related/season-period/data/services/season-period-data.service'; import { SeasonPeriodModel } from 'src/modules/season-related/season-period/data/models/season-period.model'; import { TransactionDemographyModel } from 'src/modules/transaction/transaction/data/models/transaction-demography.model'; +import { UserLoginModel } from 'src/modules/user-related/user/data/models/user-login.model'; @Module({ imports: [ @@ -61,6 +62,7 @@ import { TransactionDemographyModel } from 'src/modules/transaction/transaction/ ItemRateModel, SeasonPeriodModel, UserModel, + UserLoginModel, TransactionModel, TransactionTaxModel, TransactionItemModel, diff --git a/src/modules/configuration/log/data/models/log-user-login.model.ts b/src/modules/configuration/log/data/models/log-user-login.model.ts index cda2429..f831f78 100644 --- a/src/modules/configuration/log/data/models/log-user-login.model.ts +++ b/src/modules/configuration/log/data/models/log-user-login.model.ts @@ -20,6 +20,12 @@ export class LogUserLoginModel @Column({ type: 'uuid', nullable: true }) user_id: string; + @Column({ type: 'uuid', nullable: true }) + item_id: string; + + @Column({ type: 'varchar', nullable: true }) + item_name: string; + @Column({ type: 'varchar', nullable: true }) username: string; diff --git a/src/modules/configuration/log/domain/entities/log-user-login.entity.ts b/src/modules/configuration/log/domain/entities/log-user-login.entity.ts index 308c43c..c68ab17 100644 --- a/src/modules/configuration/log/domain/entities/log-user-login.entity.ts +++ b/src/modules/configuration/log/domain/entities/log-user-login.entity.ts @@ -6,6 +6,8 @@ export interface LogUserLoginEntity extends BaseCoreEntity { type: LogUserType; role: UserRole; user_id: string; + item_id: string; + item_name: string; username: string; created_at: number; } diff --git a/src/modules/user-related/tenant/tenant.module.ts b/src/modules/user-related/tenant/tenant.module.ts index fe10160..24f54b4 100644 --- a/src/modules/user-related/tenant/tenant.module.ts +++ b/src/modules/user-related/tenant/tenant.module.ts @@ -25,11 +25,15 @@ import { UserDataService } from '../user/data/services/user-data.service'; import { UserReadService } from '../user/data/services/user-read.service'; import { TenantItemReadController } from './infrastructure/tenant-item-read.controller'; import { TenantItemDataController } from './infrastructure/tenant-item-data.controller'; +import { UserLoginModel } from '../user/data/models/user-login.model'; @Module({ imports: [ ConfigModule.forRoot(), - TypeOrmModule.forFeature([UserModel], CONNECTION_NAME.DEFAULT), + TypeOrmModule.forFeature( + [UserModel, UserLoginModel], + CONNECTION_NAME.DEFAULT, + ), CqrsModule, ], controllers: [ diff --git a/src/modules/user-related/user/data/models/user-login.model.ts b/src/modules/user-related/user/data/models/user-login.model.ts index 9f39afe..31b47e5 100644 --- a/src/modules/user-related/user/data/models/user-login.model.ts +++ b/src/modules/user-related/user/data/models/user-login.model.ts @@ -19,6 +19,12 @@ export class UserLoginModel @Column('varchar', { name: 'user_id', nullable: true }) user_id: string; + @Column({ type: 'uuid', nullable: true }) + item_id: string; + + @Column({ type: 'varchar', nullable: true }) + item_name: string; + @OneToOne(() => UserModel, (model) => model.user_login, { onDelete: 'CASCADE', onUpdate: 'CASCADE', diff --git a/src/modules/user-related/user/data/services/user-data.service.ts b/src/modules/user-related/user/data/services/user-data.service.ts index 4f69f7c..bac12c5 100644 --- a/src/modules/user-related/user/data/services/user-data.service.ts +++ b/src/modules/user-related/user/data/services/user-data.service.ts @@ -4,14 +4,24 @@ import { UserEntity } from '../../domain/entities/user.entity'; import { InjectRepository } from '@nestjs/typeorm'; import { UserModel } from '../models/user.model'; import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; -import { Repository } from 'typeorm'; +import { IsNull, Not, Repository } from 'typeorm'; +import { UserLoginModel } from '../models/user-login.model'; @Injectable() export class UserDataService extends BaseDataService { constructor( @InjectRepository(UserModel, CONNECTION_NAME.DEFAULT) private repo: Repository, + + @InjectRepository(UserLoginModel, CONNECTION_NAME.DEFAULT) + private repoLoginUser: Repository, ) { super(repo); } + + async getLoginUserByItem(itemId: string) { + return this.repoLoginUser.findOne({ + where: { item_id: itemId, user_id: Not(IsNull()) }, + }); + } } diff --git a/src/modules/user-related/user/domain/entities/user-login.entity.ts b/src/modules/user-related/user/domain/entities/user-login.entity.ts index 975223c..1c5dd0c 100644 --- a/src/modules/user-related/user/domain/entities/user-login.entity.ts +++ b/src/modules/user-related/user/domain/entities/user-login.entity.ts @@ -3,4 +3,6 @@ import { BaseCoreEntity } from 'src/core/modules/domain/entities/base-core.entit export interface UserLoginEntity extends BaseCoreEntity { login_date: number; login_token: string; + item_id: string; + item_name: string; } diff --git a/src/modules/user-related/user/domain/usecases/managers/index-user.manager.ts b/src/modules/user-related/user/domain/usecases/managers/index-user.manager.ts index 92eaa14..120e459 100644 --- a/src/modules/user-related/user/domain/usecases/managers/index-user.manager.ts +++ b/src/modules/user-related/user/domain/usecases/managers/index-user.manager.ts @@ -52,6 +52,8 @@ export class IndexUserManager extends BaseIndexManager { 'user_login.id', 'user_login.login_date', + 'user_login.item_id', + 'user_login.item_name', ]; }