updated event controllers and service event mtach tested

This commit is contained in:
Berkay 2025-08-05 14:42:24 +03:00
parent aa8f0b8f31
commit 9232da69d3
57 changed files with 1699 additions and 180 deletions

View File

@ -17,6 +17,7 @@
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"ioredis": "^5.6.1",
"mongodb": "^6.18.0",
"otplib": "^12.0.1",
"qrcode": "^1.5.4",
"redis": "^5.6.1",
@ -1957,6 +1958,15 @@
"node": ">=8"
}
},
"node_modules/@mongodb-js/saslprep": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz",
"integrity": "sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ==",
"license": "MIT",
"dependencies": {
"sparse-bitfield": "^3.0.3"
}
},
"node_modules/@napi-rs/nice": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.0.4.tgz",
@ -3477,6 +3487,21 @@
"integrity": "sha512-y7pa/oEJJ4iGYBxOpfAKn5b9+xuihvzDVnC/OSvlVnGxVg0pOqmjiMafiJ1KVNQEaPZf9HsEp5icEwGg8uIe5Q==",
"license": "MIT"
},
"node_modules/@types/webidl-conversions": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
"integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==",
"license": "MIT"
},
"node_modules/@types/whatwg-url": {
"version": "11.0.5",
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
"integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
"license": "MIT",
"dependencies": {
"@types/webidl-conversions": "*"
}
},
"node_modules/@types/yargs": {
"version": "17.0.33",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz",
@ -4805,6 +4830,15 @@
"node-int64": "^0.4.0"
}
},
"node_modules/bson": {
"version": "6.10.4",
"resolved": "https://registry.npmjs.org/bson/-/bson-6.10.4.tgz",
"integrity": "sha512-WIsKqkSC0ABoBJuT1LEX+2HEvNmNKKgnTAyd0fL8qzK4SH2i9NXg+t08YtdZp/V9IZ33cxe3iV4yM0qg8lMQng==",
"license": "Apache-2.0",
"engines": {
"node": ">=16.20.1"
}
},
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
@ -8646,6 +8680,12 @@
"node": ">= 4.0.0"
}
},
"node_modules/memory-pager": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
"license": "MIT"
},
"node_modules/merge-descriptors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
@ -8813,6 +8853,62 @@
"mkdirp": "bin/cmd.js"
}
},
"node_modules/mongodb": {
"version": "6.18.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.18.0.tgz",
"integrity": "sha512-fO5ttN9VC8P0F5fqtQmclAkgXZxbIkYRTUi1j8JO6IYwvamkhtYDilJr35jOPELR49zqCJgXZWwCtW7B+TM8vQ==",
"license": "Apache-2.0",
"dependencies": {
"@mongodb-js/saslprep": "^1.1.9",
"bson": "^6.10.4",
"mongodb-connection-string-url": "^3.0.0"
},
"engines": {
"node": ">=16.20.1"
},
"peerDependencies": {
"@aws-sdk/credential-providers": "^3.188.0",
"@mongodb-js/zstd": "^1.1.0 || ^2.0.0",
"gcp-metadata": "^5.2.0",
"kerberos": "^2.0.1",
"mongodb-client-encryption": ">=6.0.0 <7",
"snappy": "^7.2.2",
"socks": "^2.7.1"
},
"peerDependenciesMeta": {
"@aws-sdk/credential-providers": {
"optional": true
},
"@mongodb-js/zstd": {
"optional": true
},
"gcp-metadata": {
"optional": true
},
"kerberos": {
"optional": true
},
"mongodb-client-encryption": {
"optional": true
},
"snappy": {
"optional": true
},
"socks": {
"optional": true
}
}
},
"node_modules/mongodb-connection-string-url": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz",
"integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==",
"license": "Apache-2.0",
"dependencies": {
"@types/whatwg-url": "^11.0.2",
"whatwg-url": "^14.1.0 || ^13.0.0"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@ -9639,7 +9735,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@ -10613,6 +10708,15 @@
"node": ">=0.10.0"
}
},
"node_modules/sparse-bitfield": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
"integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
"license": "MIT",
"dependencies": {
"memory-pager": "^1.0.2"
}
},
"node_modules/spdx-correct": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
@ -11340,6 +11444,18 @@
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/tr46": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
"integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
"license": "MIT",
"dependencies": {
"punycode": "^2.3.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/tree-kill": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
@ -11867,6 +11983,15 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
}
},
"node_modules/webpack": {
"version": "5.100.2",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.100.2.tgz",
@ -12058,6 +12183,19 @@
"url": "https://opencollective.com/webpack"
}
},
"node_modules/whatwg-url": {
"version": "14.2.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
"integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
"license": "MIT",
"dependencies": {
"tr46": "^5.1.0",
"webidl-conversions": "^7.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@ -28,6 +28,7 @@
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
"ioredis": "^5.6.1",
"mongodb": "^6.18.0",
"otplib": "^12.0.1",
"qrcode": "^1.5.4",
"redis": "^5.6.1",

View File

@ -8,53 +8,36 @@ import {
Body,
HttpCode,
UseGuards,
ForbiddenException,
NotFoundException,
Req,
Query,
} from '@nestjs/common';
import { AccountsService } from './accounts.service';
import { AuthControlGuard, EndpointControlGuard } from '../middleware/access-control.guard';
import { RedisHandlers } from '../utils/auth/redisHandlers';
import { AuthControlGuard, EndpointControlGuard } from '@/src/middleware/access-control.guard';
import { Navigator } from '@/src/utils/navigator/navigator';
@Controller('accounts')
export class AccountsController {
constructor(private accountsService: AccountsService, private redisHandler: RedisHandlers) { }
constructor(
private accountsService: AccountsService,
private navigator: Navigator
) { }
@Get('events')
@HttpCode(200)
@UseGuards(AuthControlGuard)
async getEvents(@Query() query: any) {
const { userToken } = query;
const events = await this.accountsService.infoEvents(userToken)
try {
return { events, message: "Events fetched successfully" };
} catch (error) {
console.error('Error getting events:', error);
throw new ForbiddenException(`Error retrieving events. Please contact your system administrator.`);
}
if (!userToken) { throw new NotFoundException('User token is missing or null') }
const events = await this.navigator.getInfos(this.accountsService, userToken)
return { events, message: "Events fetched successfully" };
}
@Post('filter')
@HttpCode(200)
@UseGuards(AuthControlGuard, EndpointControlGuard)
async filterAccounts(@Body() query: any, @Req() req: any) {
// Get request drive token from acess control guard and retrieve related Service
const relatedService = this.accountsService.getService(req)
if (!relatedService) { throw new Error(`No service found for drive token: ${req.driveToken}`) }
try {
// Get function mapper from related
if (!relatedService.mapper) { throw new Error(`Mapper in ${relatedService.constructor.name} is missing or null`) }
// Get redis select token object from redis
const selectObject = await this.redisHandler.getSelectFromRedis(req);
if (!selectObject) { throw new Error(`Select object is missing or null`) }
if (!selectObject.value.events) { throw new Error(`Events in select object is missing or null`) }
const eventKey = Object.entries(selectObject.value.events).filter((key) => key.includes(req.driveToken))[0]
if (!eventKey) { throw new Error(`No event is registered for this user ${req.driveToken}`) }
// Get function to call from related service mapper
const functionToCall = relatedService.mapper[eventKey.join(":")];
if (!functionToCall || typeof functionToCall !== 'function') { throw new Error(`No function found for drive token: ${req.driveToken}`); }
return await functionToCall(query);
} catch (error) { throw new ForbiddenException(`This user is not allowed to access this endpoint. Please contact your system administrator.`) }
return await this.navigator.getFunction(req, this.accountsService.mapper, query)
}
}

View File

@ -2,24 +2,26 @@ import { Module } from '@nestjs/common';
import { AccountsService } from './accounts.service';
import { AccountsController } from './accounts.controller';
import { PrismaModule } from '@/prisma/prisma.module';
import { CacheService } from '../cache.service';
import { CacheService } from '../database/redis/redis.service';
import { UtilsModule } from '../utils/utils.module';
import { RedisModule } from '../database/redis/redis.module';
import {
AuthControlGuard,
EndpointControlGuard,
} from '@/src/middleware/access-control.guard';
import { SuperUsersService } from './superusers/superusers.service';
import { UrlHandler } from '../utils/auth/urlHandler';
import { UrlHandler } from '../utils/navigator/urlHandler';
import { Navigator } from '@/src/utils/navigator/navigator';
@Module({
imports: [PrismaModule, UtilsModule],
imports: [PrismaModule, UtilsModule, RedisModule],
providers: [
AccountsService,
CacheService,
AuthControlGuard,
EndpointControlGuard,
SuperUsersService,
UrlHandler,
Navigator,
],
controllers: [AccountsController],
})

View File

@ -1,5 +1,4 @@
import { Injectable } from '@nestjs/common';
import { PaginationInfo } from '../utils/pagination-helper';
import { SuperUsersService } from './superusers/superusers.service';
@Injectable()
@ -10,24 +9,7 @@ export class AccountsService {
private superUsersService: SuperUsersService,
) {
this.mapper = {
"j0adQOsJBR0xq24dxLKdDU9EQRmt4gzE05CmhA": superUsersService,
"j0adQOsJBR0xq24dxLKdDU9EQRmt4gzE05CmhA": this.superUsersService,
}
}
getService(request: any) {
const driveToken = request.driveToken
const secondPartOfDriveToken = driveToken.split(":")[1]
if (!secondPartOfDriveToken) { throw new Error('Drive token is missing or null') }
return this.mapper[secondPartOfDriveToken];
}
async supersUserFilter(query: any & { page?: number; pageSize?: number }): Promise<{ pagination: PaginationInfo; data: any[] }> {
return this.superUsersService.filter(query);
}
async infoEvents(userToken: string) {
const relatedMapper = this.getService(userToken)
if (!relatedMapper) { throw new Error(`No service found for user token: ${userToken}`) }
return relatedMapper.infoEvents(userToken);
}
}

View File

@ -2,7 +2,7 @@ import { PaginationHelper } from '@/src/utils/pagination-helper';
import { Injectable } from '@nestjs/common';
import { PaginationInfo } from '@/src/utils/pagination-helper';
import { PrismaService } from '@/src/prisma.service';
import { UrlHandler } from '@/src/utils/auth/urlHandler';
import { UrlHandler } from '@/src/utils/navigator/urlHandler';
@Injectable()
export class SuperUsersService {
@ -36,7 +36,6 @@ export class SuperUsersService {
async infoEvents(userToken: string) { return Object.entries(this.events).filter(([key]) => key.endsWith(userToken)) }
async filter(query: any & { page?: number; pageSize?: number }): Promise<{ pagination: PaginationInfo; data: any[] }> {
console.log("supersServiceFilter query", query)
const result = await this.paginationHelper.findWithPagination(query, this.prisma.account_records);
const { pagination, data } = result;

View File

@ -7,32 +7,26 @@ import {
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UsersModule } from './users/users.module';
import { PrismaModule } from '@/prisma/prisma.module';
import { PrismaModule } from '../prisma/prisma.module';
import { AccountsModule } from './accounts/accounts.module';
import { AuthModule } from './auth/auth.module';
import { RedisModule } from '@liaoliaots/nestjs-redis';
import { CacheService } from './cache.service';
import { RedisModule } from './database/redis/redis.module';
import { LoggerMiddleware } from '@/src/middleware/logger.middleware';
import { DiscoveryModule } from '@nestjs/core';
const redisConfig = {
host: '10.10.2.15',
port: 6379,
password: 'your_strong_password_here',
};
import { NavigatorModule } from './navigator/navigator.module';
const modulesList = [UsersModule, AccountsModule, AuthModule];
const serviceModuleList = [
PrismaModule,
RedisModule.forRoot({ config: redisConfig }),
RedisModule.forRootWithConfig(true),
DiscoveryModule,
];
const controllersList = [AppController];
const providersList = [AppService, CacheService];
const exportsList = [CacheService];
const providersList = [AppService];
const exportsList = [];
@Module({
imports: [...serviceModuleList, ...modulesList],
imports: [...serviceModuleList, ...modulesList, NavigatorModule],
controllers: controllersList,
providers: providersList,
exports: exportsList,

View File

@ -11,9 +11,12 @@ import { ResetPasswordService } from './password/reset/reset.service';
import { ChangePasswordService } from './password/change/change.service';
import { VerifyOtpService } from './password/verify-otp/verify-otp.service';
import { DisconnectService } from './disconnect/disconnect.service';
import { MongoModule } from '@/src/database/mongo/mongo.module';
import { MongoService } from '@/src/database/mongo/mongo.service';
import { NavigatorModule } from '../navigator/navigator.module';
@Module({
imports: [UtilsModule],
imports: [UtilsModule, MongoModule, NavigatorModule],
controllers: [AuthController],
providers: [
AuthService,
@ -25,8 +28,9 @@ import { DisconnectService } from './disconnect/disconnect.service';
ResetPasswordService,
VerifyOtpService,
DisconnectService,
MongoService,
PrismaService,
],
exports: [AuthService],
})
export class AuthModule {}
export class AuthModule { }

View File

@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common';
import { userLoginValidator } from '@/src/auth/login/dtoValidator';
import { RedisHandlers } from '@/src/utils/auth/redisHandlers';
import { PasswordHandlers } from '@/src/utils/auth/loginHandler';
import { RedisHandlers } from '@/src/utils/store/redisHandlers';
import { PasswordHandlers } from '@/src/utils/store/loginHandler';
import { PrismaService } from '@/src/prisma.service';
import { AuthTokenSchema } from '@/src/types/auth/token';

View File

@ -5,8 +5,8 @@ import {
UnauthorizedException,
} from '@nestjs/common';
import { userChangePasswordValidator } from './dtoValidator';
import { RedisHandlers } from '@/src/utils/auth/redisHandlers';
import { PasswordHandlers } from '@/src/utils/auth/loginHandler';
import { RedisHandlers } from '@/src/utils/store/redisHandlers';
import { PasswordHandlers } from '@/src/utils/store/loginHandler';
@Injectable()
export class ChangePasswordService {

View File

@ -1,6 +1,6 @@
import { userCreatePasswordValidator } from './dtoValidator';
import { PrismaService } from '@/src/prisma.service';
import { PasswordHandlers } from '@/src/utils/auth/loginHandler';
import { PasswordHandlers } from '@/src/utils/store/loginHandler';
import { Injectable, BadRequestException } from '@nestjs/common';
@Injectable()

View File

@ -1,7 +1,7 @@
import { Injectable, BadRequestException } from '@nestjs/common';
import { userResetPasswordValidator } from './dtoValidator';
import { PrismaService } from '@/src/prisma.service';
import { PasswordHandlers } from '@/src/utils/auth/loginHandler';
import { PasswordHandlers } from '@/src/utils/store/loginHandler';
@Injectable()
export class ResetPasswordService {

View File

@ -1,52 +1,29 @@
import {
Injectable,
BadRequestException,
UnauthorizedException,
NotAcceptableException,
} from '@nestjs/common';
import { Injectable, UnauthorizedException, NotAcceptableException } from '@nestjs/common';
import { userSelectValidator } from '@/src/auth/select/dtoValidator';
import { RedisHandlers } from '@/src/utils/auth/redisHandlers';
import {
EmployeeTokenSchema,
OccupantTokenSchema,
TokenDictInterface,
UserType,
} from '@/src/types/auth/token';
import { RedisHandlers } from '@/src/utils/store/redisHandlers';
import { EmployeeTokenSchema, OccupantTokenSchema, UserType } from '@/src/types/auth/token';
import { PrismaService } from '@/src/prisma.service';
// No need to import Prisma client types directly
import { MongoService } from '@/src/database/mongo/mongo.service';
import { EventsService } from '@/src/navigator/events/events.service';
@Injectable()
export class SelectService {
constructor(
private readonly redis: RedisHandlers,
private readonly prisma: PrismaService,
private readonly mongoService: MongoService,
private readonly eventService: EventsService
) { }
async run(dto: userSelectValidator, req: Request) {
const accessObject = await this.redis.getLoginFromRedis(req);
if (!accessObject) {
throw new UnauthorizedException(
'Authorization failed. Please login to continue',
);
}
if (!accessObject) { throw new UnauthorizedException('Authorization failed. Please login to continue') }
const accessToken = accessObject.key.split(':')[1];
const existingSelectToken = await this.redis.callExistingSelectToken(
accessObject.value.users.uu_id,
dto.uuid,
);
if (existingSelectToken) {
return {
message: 'Select successful',
token: existingSelectToken,
};
}
const existingSelectToken = await this.redis.callExistingSelectToken(accessObject.value.users.uu_id, dto.uuid);
if (existingSelectToken) { return { message: 'Select successful', token: existingSelectToken } }
const userType = accessObject.value.users.user_type;
if (userType === 'employee') {
const employee = await this.prisma.employees.findFirstOrThrow({
where: { uu_id: dto.uuid },
omit: {
id: true,
},
});
const employee = await this.prisma.employees.findFirstOrThrow({ where: { uu_id: dto.uuid }, omit: { id: true } });
const staff = await this.prisma.staff.findFirstOrThrow({
where: { uu_id: employee.staff_uu_id },
select: {
@ -54,6 +31,8 @@ export class SelectService {
staff_code: true,
user_type_id: true,
duties_id: true,
staff_name: true,
staff_description: true,
duties_uu_id: true,
created_credentials_token: true,
updated_credentials_token: true,
@ -78,29 +57,20 @@ export class SelectService {
});
const duties = await this.prisma.duties.findFirstOrThrow({
where: { id: staff.duties_id },
omit: {
id: true,
},
omit: { id: true },
});
const department = await this.prisma.departments.findFirstOrThrow({
where: { id: duties.department_id },
omit: {
id: true,
},
omit: { id: true },
});
const duty = await this.prisma.duty.findFirstOrThrow({
where: { id: duties.duties_id },
omit: {
id: true,
},
omit: { id: true },
});
const company = await this.prisma.companies.findFirstOrThrow({
where: { id: duties.company_id },
omit: {
id: true,
},
omit: { id: true },
});
const staffUserType = staff.user_type_id ?
await this.prisma.user_types.findFirst({
where: { id: staff.user_type_id },
@ -109,7 +79,6 @@ export class SelectService {
type_token: true
}
}) : null;
const employeeToken = EmployeeTokenSchema.parse({
uuid: dto.uuid,
company: company,
@ -166,6 +135,10 @@ export class SelectService {
functionsRetriever: staffUserType?.token,
kind: UserType.employee,
});
// Render page and menu
// const collection = this.mongoService.getDb(`Events/${company.uu_id}`)
const events = ""
const tokenSelect = await this.redis.setSelectToRedis(
accessToken,
@ -173,11 +146,7 @@ export class SelectService {
accessObject.value.users.uu_id,
dto.uuid,
);
return {
message: 'Select successful',
token: tokenSelect,
};
return { message: 'Select successful', token: tokenSelect };
} else if (userType === 'occupant') {
const livingSpace = await this.prisma.build_living_space.findFirstOrThrow({
where: { uu_id: dto.uuid },
@ -187,11 +156,9 @@ export class SelectService {
occupant_type_uu_id: true
}
});
const occupantType = await this.prisma.occupant_types.findFirstOrThrow({
where: { uu_id: livingSpace.occupant_type_uu_id }
});
const userTypeInfo = occupantType.user_type_uu_id ?
await this.prisma.user_types.findFirst({
where: { uu_id: occupantType.user_type_uu_id },
@ -203,7 +170,6 @@ export class SelectService {
token: true
}
}) : null;
const part = await this.prisma.build_parts.findFirstOrThrow({
where: { uu_id: livingSpace.build_parts_uu_id },
select: {
@ -222,7 +188,6 @@ export class SelectService {
}
}
});
const build = await this.prisma.build.findFirstOrThrow({
where: { uu_id: part.build_uu_id },
select: {
@ -230,7 +195,6 @@ export class SelectService {
build_name: true
}
});
const company = await this.prisma.companies.findFirstOrThrow({
where: { uu_id: accessObject.value.users.related_company },
select: {
@ -243,7 +207,6 @@ export class SelectService {
ref_int: true
}
});
const occupantToken = OccupantTokenSchema.parse({
uuid: dto.uuid,
livingSpace: livingSpace,
@ -282,16 +245,17 @@ export class SelectService {
kind: UserType.occupant
});
// Render page and menu
const eventsObject = await this.eventService.getEventsOccupants(livingSpace.uu_id)
eventsObject && (occupantToken.events = eventsObject)
const tokenSelect = await this.redis.setSelectToRedis(
accessToken,
occupantToken,
accessObject.value.users.uu_id,
dto.uuid
);
return {
message: 'Select successful',
token: tokenSelect
};
return { message: 'Select successful', token: tokenSelect };
} else {
throw new NotAcceptableException('Invalid user type');
}

View File

@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { MongoService } from './mongo.service';
import { MongoProvider } from './mongo.provider';
@Module({
providers: [MongoProvider, MongoService],
exports: [MongoService, MongoProvider, 'MONGO_DB']
})
export class MongoModule {}

View File

@ -0,0 +1,11 @@
import { MongoClient, Db } from 'mongodb';
export const MongoProvider = {
provide: 'MONGO_DB',
useFactory: async (): Promise<Db> => {
const uri = 'mongodb://appuser:apppassword@10.10.2.13:27017/appdb';
const client = new MongoClient(uri);
await client.connect();
return client.db('appdb');
},
};

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { MongoService } from './mongo.service';
describe('MongoService', () => {
let service: MongoService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [MongoService],
}).compile();
service = module.get<MongoService>(MongoService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@ -0,0 +1,257 @@
import { Injectable, Inject } from '@nestjs/common';
import { Db, Document, Collection, Filter, ObjectId, UpdateResult } from 'mongodb';
@Injectable()
export class MongoService {
private collection: Collection<Document>;
constructor(@Inject('MONGO_DB') private readonly db: Db) { this.collection = this.db.collection('mongoCache') }
async set(collectionName: string) { this.collection = this.db.collection(collectionName) }
async getDb() { return this.collection }
/**
* Find a document by UUID or create it if it doesn't exist
* @param data Document data with UUID field
* @returns The found or created document
*
* @example
* Create a new user or retrieve existing one
* const userData = { uuid: 'TOKEN:12345:user', name: 'John Doe', email: 'john@example.com' };
* const user = await mongoService.findOrCreate(userData);
*/
async findOrCreate(data: Record<string, any>): Promise<{ data: Document, isCreated: boolean }> {
if (!data.uuid) { throw new Error('UUID is required for findOrCreate operation') }
// Use direct UUID lookup instead of regex for exact match
const existingDoc = await this.collection.findOne({ uuid: data.uuid } as Filter<Document>);
if (existingDoc) { return { data: existingDoc, isCreated: false } }
const insertResult = await this.collection.insertOne(data);
if (!insertResult.acknowledged) { throw new Error('Failed to insert document') }
return { data: await this.getOne(insertResult.insertedId), isCreated: true };
}
/**
* Get all documents from the collection
* @returns Array of all documents
*
* @example
* Get all users in the collection
* const allUsers = await mongoService.getAll();
*/
async getAll(): Promise<Document[]> { return await this.collection.find().toArray() }
/**
* Find a document by ID key using regex pattern
* @param idKey ID key to search for
* @returns The found document
* @throws Error if document is not found
*
* @example
* Find a user by ID key
* const user = await mongoService.findOne('12345');
* This will search for documents with uuid matching pattern ^TOKEN:12345:
*/
async findOne(filter: Filter<Document>): Promise<Document> {
const result = await this.collection.findOne(filter);
if (!result) { throw new Error(`Document with ID key ${filter} not found`) }
return result;
}
/**
* Find multiple documents using a filter
* @param filter MongoDB filter
* @param limit Optional limit of results (default: no limit)
* @param skip Optional number of documents to skip (default: 0)
* @returns Array of matching documents
*
* @example
* Find active users with pagination
* const filter = { active: true } as Filter<Document>;
* const activeUsers = await mongoService.findMany(filter, 10, 20); // limit 10, skip 20
*
* @example
* Find users by role
* const admins = await mongoService.findMany({ role: 'admin' } as Filter<Document>);
*/
async findMany(filter: Filter<Document>, limit?: number, skip?: number): Promise<Document[]> {
let query = this.collection.find(filter);
if (typeof skip === 'number') { query = query.skip(skip) }
if (typeof limit === 'number') { query = query.limit(limit) }
return await query.toArray();
}
/**
* Get a document by its MongoDB ObjectId
* @param id MongoDB ObjectId
* @returns The found document
* @throws Error if document is not found
*
* @example
* Get a user by ObjectId
* const userId = new ObjectId('507f1f77bcf86cd799439011');
* const user = await mongoService.getOne(userId);
*/
async getOne(id: ObjectId): Promise<Document> {
const result = await this.collection.findOne({ _id: id });
if (!result) { throw new Error(`Document with ID ${id.toString()} not found`) }
return result;
}
/**
* Find documents by regex pattern on UUID field
* @param idKey ID key to search for
* @returns Array of matching documents
*
* @example
* Find all users with a specific ID key pattern
* const users = await mongoService.findByRegex('12345');
* This will return all documents with uuid matching pattern ^TOKEN:12345:
*/
async findByRegex(idKey: string): Promise<Document[]> {
if (!idKey) { throw new Error('ID key is required for regex search') }
const pattern = `^${idKey}`;
return await this.collection.find({ uuid: { $regex: pattern } } as Filter<Document>).toArray();
}
/**
* Update a single document by its MongoDB ObjectId
* @param id MongoDB ObjectId
* @param data Data to update
* @returns The updated document
* @throws Error if document is not found or update fails
*
* @example
* Update a user's profile
* const userId = new ObjectId('507f1f77bcf86cd799439011');
* const updates = { name: 'Jane Doe', lastLogin: new Date() };
* const updatedUser = await mongoService.updateOne(userId, updates);
*/
async updateOne(id: ObjectId, data: Record<string, any>): Promise<Document> {
const updateResult = await this.collection.updateOne(
{ _id: id },
{ $set: data }
);
if (!updateResult.acknowledged) { throw new Error('Update operation failed') }
if (updateResult.matchedCount === 0) { throw new Error(`Document with ID ${id.toString()} not found`) }
return await this.getOne(id);
}
/**
* Update multiple documents matching a filter
* @param filter MongoDB filter
* @param data Data to update
* @returns Update result with count of modified documents
* @throws Error if update fails
*
* @example
* Mark all inactive users as archived
* const filter = { active: false } as Filter<Document>;
* const updates = { status: 'archived', archivedAt: new Date() };
* const result = await mongoService.updateMany(filter, updates);
* console.log(`${result.modifiedCount} users archived`);
*/
async updateMany(filter: Filter<Document>, data: Record<string, any>): Promise<UpdateResult> {
const updateResult = await this.collection.updateMany(filter, { $set: data });
if (!updateResult.acknowledged) { throw new Error('Update operation failed') }
return updateResult;
}
/**
* Delete a document by its MongoDB ObjectId
* @param id MongoDB ObjectId
* @returns True if document was deleted, false otherwise
*
* @example
* Delete a user account
* const userId = new ObjectId('507f1f77bcf86cd799439011');
* const deleted = await mongoService.deleteOne(userId);
* if (deleted) console.log('User successfully deleted');
*/
async deleteOne(id: ObjectId): Promise<boolean> {
const deleteResult = await this.collection.deleteOne({ _id: id });
return deleteResult.acknowledged && deleteResult.deletedCount > 0;
}
/**
* Delete multiple documents matching a filter
* @param filter MongoDB filter
* @returns Number of deleted documents
*
* @example
* Delete all expired sessions
* const filter = { expiresAt: { $lt: new Date() } } as Filter<Document>;
* const count = await mongoService.deleteMany(filter);
* console.log(`${count} expired sessions deleted`);
*/
async deleteMany(filter: Filter<Document>): Promise<number> {
const deleteResult = await this.collection.deleteMany(filter);
return deleteResult.acknowledged ? deleteResult.deletedCount : 0;
}
/**
* Find documents by regex pattern on any specified field
* @param field The field name to apply the regex filter on
* @param value The value to search for in the field
* @param options Optional regex options (e.g., 'i' for case-insensitive)
* @param prefix Optional prefix to add before the value (default: '')
* @param suffix Optional suffix to add after the value (default: '')
* @returns Array of matching documents
*
* @example
* Find users with email from a specific domain (case-insensitive)
* const gmailUsers = await mongoService.findByFieldRegex('email', 'gmail.com', 'i');
*
* @example
* Find users with names starting with 'J'
* const usersStartingWithJ = await mongoService.findByFieldRegex('name', 'J', 'i', '^');
*
* @example
* Find users with phone numbers ending in specific digits
* const specificPhoneUsers = await mongoService.findByFieldRegex('phone', '5555', '', '', '$');
*/
async findByFieldRegex(field: string, value: string, options?: string, prefix: string = '', suffix: string = ''): Promise<Document[]> {
if (!field || !value) { throw new Error('Field name and value are required for regex search') }
const pattern = `${prefix}${value}${suffix}`;
const query: Record<string, any> = {};
query[field] = { $regex: pattern };
if (options) { query[field].$options = options; }
return await this.collection.find(query as unknown as Filter<Document>).toArray();
}
/**
* Find documents by regex pattern across all fields (including nested)
* @param value The value to search for
* @param options Optional regex options (e.g., 'i' for case-insensitive)
* @returns Array of matching documents
*
* @example
* Find any document containing a specific value anywhere
* const docs = await mongoService.findByRegexAcrossFields('someValue', 'i');
*/
async findByRegexAcrossFields(value: string, options?: string, searchType: 'value' | 'key' | 'both' = 'value'): Promise<Document[]> {
if (!value) { throw new Error('Search value is required') }
const query: any = { $or: [] };
if (searchType === 'value' || searchType === 'both') {
query.$or.push(
{ '$expr': { $regexMatch: { input: { $toString: '$$ROOT' }, regex: value, options } } },
{ 'data': { $type: 'object', $regex: value, $options: options } }
);
}
if (searchType === 'key' || searchType === 'both') {
query.$where = function () {
const searchRegex = new RegExp(value, options);
function checkKeys(obj: Record<string, any>) {
for (const key in obj) {
if (searchRegex.test(key)) return true; if (obj[key] && typeof obj[key] === 'object') { if (checkKeys(obj[key])) return true }
}
return false;
} return checkKeys(this)
}.toString();
}
return await this.collection.find(query as unknown as Filter<Document>).toArray();
}
}

View File

@ -0,0 +1,2 @@
export const REDIS_CLIENT = 'REDIS_CLIENT';
export const REDIS_OPTIONS = 'REDIS_OPTIONS';

View File

@ -0,0 +1,23 @@
import { ModuleMetadata, Type } from '@nestjs/common';
export interface RedisModuleOptions {
config?: {
host?: string;
port?: number;
password?: string;
db?: number;
keyPrefix?: string;
[key: string]: any;
};
}
export interface RedisOptionsFactory {
createRedisOptions(): Promise<RedisModuleOptions> | RedisModuleOptions;
}
export interface RedisModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
useExisting?: Type<RedisOptionsFactory>;
useClass?: Type<RedisOptionsFactory>;
useFactory?: (...args: any[]) => Promise<RedisModuleOptions> | RedisModuleOptions;
inject?: any[];
}

View File

@ -0,0 +1,91 @@
import Redis from 'ioredis';
import { DynamicModule, Module, OnApplicationShutdown, Provider } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { CacheService } from './redis.service';
import { REDIS_CLIENT, REDIS_OPTIONS } from './redis.constants';
import { RedisModuleOptions, RedisModuleAsyncOptions } from './redis.interfaces';
@Module({})
export class RedisModule implements OnApplicationShutdown {
constructor(private moduleRef: ModuleRef) { }
/**
* Registers the module with default configuration.
*
* @param isGlobal - Register in the global scope
* @returns A DynamicModule
*/
static forRootWithConfig(isGlobal = false): DynamicModule {
const redisConfig = { host: '10.10.2.15', port: 6379, password: 'your_strong_password_here' };
return RedisModule.forRoot({ config: redisConfig }, isGlobal);
}
/**
* Registers the module synchronously.
*
* @param options - The module options
* @param isGlobal - Register in the global scope
* @returns A DynamicModule
*/
static forRoot(options?: RedisModuleOptions, isGlobal = false): DynamicModule {
const redisOptionsProvider: Provider = {
provide: REDIS_OPTIONS,
useValue: options || {},
};
const redisClientProvider: Provider = {
provide: REDIS_CLIENT,
useFactory: () => {
if (!options || !options.config) { return new Redis() }
const { host, port, password } = options.config;
return new Redis({ host, port, password });
},
};
return {
module: RedisModule,
providers: [redisOptionsProvider, redisClientProvider, CacheService],
exports: [CacheService],
global: isGlobal,
};
}
/**
* Registers the module asynchronously.
*
* @param options - The async module options
* @param isGlobal - Register in the global scope
* @returns A DynamicModule
*/
static forRootAsync(options: RedisModuleAsyncOptions, isGlobal = false): DynamicModule {
const redisOptionsProvider: Provider = {
provide: REDIS_OPTIONS,
useFactory: options.useFactory || (() => ({})),
inject: options.inject || [],
};
const redisClientProvider: Provider = {
provide: REDIS_CLIENT,
useFactory: (redisOptions: RedisModuleOptions) => {
if (!redisOptions || !redisOptions.config) { return new Redis() }
const { host, port, password } = redisOptions.config;
return new Redis({ host, port, password })
},
inject: [REDIS_OPTIONS],
};
return {
module: RedisModule,
imports: options.imports || [],
providers: [redisOptionsProvider, redisClientProvider, CacheService],
exports: [CacheService],
global: isGlobal,
};
}
async onApplicationShutdown() {
const client = this.moduleRef.get(REDIS_CLIENT, { strict: false });
if (client && typeof client.quit === 'function') { await client.quit() }
}
}

View File

@ -0,0 +1,30 @@
import { Test, TestingModule } from '@nestjs/testing';
import { CacheService } from './redis.service';
import Redis from 'ioredis';
import { REDIS_CLIENT } from './redis.constants';
describe('CacheService', () => {
let service: CacheService;
let mockRedisClient: Redis;
beforeEach(async () => {
// Create a mock Redis client
mockRedisClient = new Redis();
const module: TestingModule = await Test.createTestingModule({
providers: [
CacheService,
{
provide: REDIS_CLIENT,
useValue: mockRedisClient,
},
],
}).compile();
service = module.get<CacheService>(CacheService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View File

@ -1,13 +1,13 @@
import { Injectable } from '@nestjs/common';
import { RedisService } from '@liaoliaots/nestjs-redis';
import Redis from 'ioredis';
import { Injectable, Inject } from '@nestjs/common';
import { REDIS_CLIENT } from './redis.constants';
@Injectable()
export class CacheService {
private client: Redis;
constructor(private readonly redisService: RedisService) {
this.client = this.redisService.getOrThrow();
constructor(@Inject(REDIS_CLIENT) private readonly redisClient: Redis) {
this.client = redisClient;
}
async set(key: string, value: any) {
@ -16,10 +16,8 @@ export class CacheService {
async get(key: string): Promise<any | null> {
const value = await this.client.get(key);
if (!value) {
return null;
}
return { key, value: JSON.parse(value) };
if (!value) { return null }
return { key, value: JSON.parse(value) }
}
async set_with_ttl(key: string, value: any, ttl: number) {
@ -32,10 +30,7 @@ export class CacheService {
async delete(key: string): Promise<boolean> {
const deleted = await this.client.del(key);
if (deleted === 0) {
return false;
}
return true;
return deleted === 0 ? false : true;
}
/**

View File

@ -4,8 +4,8 @@ import {
Injectable,
ForbiddenException,
} from '@nestjs/common';
import { RedisHandlers } from '@/src/utils/auth/redisHandlers';
import { UrlHandler } from '@/src/utils/auth/urlHandler';
import { RedisHandlers } from '@/src/utils/store/redisHandlers';
import { UrlHandler } from '@/src/utils/navigator/urlHandler';
@Injectable()
export class AuthControlGuard implements CanActivate {
@ -14,7 +14,6 @@ export class AuthControlGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest();
const accessToken = this.cacheService.mergeLoginKey(req);
// console.log('AuthControlGuard', accessToken);
return true;
}
}
@ -34,7 +33,6 @@ export class EndpointControlGuard implements CanActivate {
const driveToken = await this.urlHandler.getSecureUrlToken(keyUrl);
const accessObject = await this.cacheService.getSelectFromRedis(req);
req.driveToken = `${driveToken}:${accessObject?.value.functionsRetriever}`;
console.log('EndpointControlGuard driveToken: ', driveToken);
return true;
}
}

View File

@ -0,0 +1,30 @@
import { IsString, IsObject, IsOptional, IsNumber } from 'class-validator';
export class mongoSetValidator {
@IsString()
collectionName: string;
@IsObject()
data: object;
}
export class mongoGetValidator {
@IsString()
collectionName: string;
@IsString()
@IsOptional()
regexKey: string;
@IsObject()
@IsOptional()
filter: object;
@IsNumber()
@IsOptional()
limit: number;
@IsNumber()
@IsOptional()
skip: number;
}

View File

@ -0,0 +1,41 @@
import { IsString, IsObject, IsOptional, IsNumber, ValidateNested } from 'class-validator';
// { # collection Events:Build-UUIDv4 | Events:Company-UUIDv4 : "userUUID" : { "userTypeToken" : { "siteUrlToken" : "eventKey" } } }
// const jsonData = { 'USER-UUID(V4)': { 'j0adQOsJBR0xq24dxLKdDU9EQRmt4gzE05CmhA': { 'e6hewIe7YqbQZHO3': 'qt5P0xoeThjNT9EuWfwBgxsntHY5ydRtKFr1pgKGcgxx' } } };
export class EventsSetterValidator {
@IsObject()
data: Record<string, Record<string, Record<string, string>>>;
@IsString()
dutyUUID: string; // UUID of employee or occupant
}
export class EventsGetterValidator {
@IsString()
collectionName: string;
@IsString()
dutyUUID: string; // UUID of employee or occupant
@IsString()
@IsOptional()
regexKey?: string;
@IsString()
@IsOptional()
searchType?: 'value' | 'key' | 'both';
@IsObject()
@IsOptional()
filter?: object = {};
@IsNumber()
@IsOptional()
limit?: number = 1;
@IsNumber()
@IsOptional()
skip?: number = 0;
}

View File

@ -1,4 +1,199 @@
import { Injectable } from '@nestjs/common';
import { Body, Injectable, NotFoundException } from '@nestjs/common';
import { MongoService } from '@/src/database/mongo/mongo.service';
import { PrismaService } from '@/src/prisma.service';
import { EventsGetterValidator, EventsSetterValidator } from '@/src/navigator/events/dtoValidator';
import { Document } from 'mongodb';
type SearchType = 'value' | 'key' | 'both';
@Injectable()
export class EventsService {}
export class EventsService {
// const result = await eventsService.getEventsOccupants({
// collectionName: 'Events:Build-UUIDv4',
// regexKey: 'qt5P0xoeThjNT9EuWfwBgxsntHY5ydRtKFr1pgKGcgxx'
// });
// const result = await eventsService.getEventsOccupants({
// collectionName: 'Events:Build-UUIDv4',
// regexKey: 'e6hewIe7YqbQZHO3',
// searchType: 'key'
// });
// const result = await eventsService.getEventsOccupants({
// collectionName: 'Events:Build-UUIDv4',
// regexKey: 'e6hewIe7YqbQZHO3',
// searchType: 'both'
// });
constructor(private mongoService: MongoService, private prisma: PrismaService) { }
seperator = "/"
private async getBuildUUID(uuid: string) {
const livingSpace = await this.prisma.build_living_space.findFirstOrThrow({
where: { uu_id: uuid },
select: {
people: {
select: {
users: {
select: {
uu_id: true
}
}
}
},
build_parts: {
select: {
build: {
select: {
uu_id: true
}
}
}
}
}
});
const userUUID = livingSpace.people.users[0].uu_id
const buildUUID = livingSpace.build_parts.build.uu_id
return { userUUID, buildUUID }
}
private async getCompanyUUID(uuid: string) {
const employee = await this.prisma.employees.findFirstOrThrow({
where: { uu_id: uuid },
select: {
people: {
select: {
users: {
select: {
uu_id: true
}
}
}
},
staff: {
select: {
duties: {
select: {
company_uu_id: true
}
}
}
}
}
});
const userUUID = employee.people?.users[0].uu_id
const companyUUID = employee.staff?.duties.company_uu_id
return { userUUID, companyUUID }
}
private validateCollectionName(collectionName: string) {
if (!collectionName) {
throw new NotFoundException('Collection name is required')
}
}
private async setupMongoCollection(collectionName: string, buildUUID: string) {
await this.mongoService.set(collectionName);
await this.mongoService.set(`EVENTS${this.seperator}${buildUUID}`);
}
private async findByRegexKey(regexKey: string, searchType: SearchType = 'value'): Promise<Document[]> {
const tokens = await this.mongoService.findByRegexAcrossFields(regexKey, 'i', searchType);
if (tokens.length === 0) {
throw new NotFoundException('Token not found')
}
return tokens;
}
private async findByFilter(filter: object): Promise<Document[]> {
const tokens = await this.mongoService.findMany(filter);
if (tokens.length === 0) {
throw new NotFoundException('Token not found')
}
return tokens;
}
async getEventsOccupants(livingSpaceUUID: string) {
const eventsObject = {}
const { userUUID, buildUUID } = await this.getBuildUUID(livingSpaceUUID);
const collectionKey = `Events/${buildUUID}`
this.validateCollectionName(collectionKey);
await this.mongoService.set(collectionKey)
const eventsResponse = await this.mongoService.findOne({ [userUUID]: { $exists: true } });
if (eventsResponse && typeof eventsResponse === 'object') {
const mapOfEvents = eventsResponse[userUUID];
if (mapOfEvents && typeof mapOfEvents === 'object') {
const userTypeTokenKey = Object.keys(mapOfEvents)[0];
const userTypeTokenValue = mapOfEvents[userTypeTokenKey];
if (userTypeTokenValue && typeof userTypeTokenValue === 'object') {
for (const siteUrlTokenKey of Object.keys(userTypeTokenValue)) {
const siteUrlTokenValue = userTypeTokenValue[siteUrlTokenKey];
eventsObject[`${siteUrlTokenKey}:${userTypeTokenKey}`] = siteUrlTokenValue
}
}
}
}
return eventsObject
}
async getEventsEmployees(@Body() body: EventsGetterValidator) {
const companyUUID = await this.getCompanyUUID(body.dutyUUID);
if (!companyUUID) { throw new NotFoundException('Company not found') }
await this.mongoService.set(`EVENTS${this.seperator}${companyUUID}`);
if (body.regexKey) {
// Use the new flattened search for regex with search type
const searchType: SearchType = body.searchType as SearchType || 'value';
const tokens = await this.mongoService.findByRegexAcrossFields(body.regexKey, 'i', searchType);
if (tokens.length === 0) { throw new NotFoundException('Token not found') }
return { data: tokens, message: 'Tokens found' };
} else if (body.filter) {
const tokens = await this.mongoService.findMany(body.filter);
if (tokens.length === 0) { throw new NotFoundException('Token not found') }
return { data: tokens, message: 'Tokens found' };
} else { throw new NotFoundException('Regex key or filter is required') }
}
private async setSavedEventToMapper(data: any, useruuid: string) {
await this.mongoService.set(`MAP${this.seperator}EVENTS`);
const events = await this.mongoService.findOrCreate({ uuid: `EVENTS:${useruuid}:${data.uuid}`, data });
}
private async deleteSavedEventFromMapper(data: any, useruuid: string) {
await this.mongoService.set(`MAP${this.seperator}EVENTS`);
const events = await this.mongoService.deleteMany({ uuid: `EVENTS:${useruuid}:${data.uuid}` });
return events;
}
async setEventsEmployees(@Body() body: EventsSetterValidator) {
const companyUUID = await this.getCompanyUUID(body.dutyUUID);
if (!companyUUID) { throw new NotFoundException('Company not found') }
await this.mongoService.set(`EVENTS${this.seperator}${companyUUID}`);
const events = await this.mongoService.findOrCreate(body.data);
// await this.setSavedEventToMapper(events, body.dutyUUID);
return events;
}
async setEventsOccupants(@Body() body: EventsSetterValidator) {
const buildUUID = await this.getBuildUUID(body.dutyUUID);
if (!buildUUID) { throw new NotFoundException('Build not found') }
await this.mongoService.set(`EVENTS${this.seperator}${buildUUID}`);
const events = await this.mongoService.findOrCreate(body.data);
return events;
}
async deleteEventsEmployees(@Body() body: EventsGetterValidator) {
const companyUUID = await this.getCompanyUUID(body.dutyUUID);
if (!companyUUID) { throw new NotFoundException('Company not found') }
await this.mongoService.set(`EVENTS${this.seperator}${companyUUID}`);
const events = await this.mongoService.deleteMany({ uuid: { $regex: body.regexKey, $options: 'i' } });
return events;
}
async deleteEventsOccupants(@Body() body: EventsGetterValidator) {
const buildUUID = await this.getBuildUUID(body.dutyUUID);
if (!buildUUID) { throw new NotFoundException('Build not found') }
await this.mongoService.set(`EVENTS${this.seperator}${buildUUID}`);
const events = await this.mongoService.deleteMany({ uuid: { $regex: body.regexKey, $options: 'i' } });
return events;
}
}

View File

@ -0,0 +1,37 @@
import { validate } from 'class-validator';
import { plainToClass } from 'class-transformer';
import { EventsSetterValidator } from './dtoValidator';
// Example JSON data to validate
const jsonData = {
collectionName: 'Events:Build-UUIDv4',
data: {
'USER-UUID(V4)': {
'j0adQOsJBR0xq24dxLKdDU9EQRmt4gzE05CmhA': {
'e6hewIe7YqbQZHO3': 'qt5P0xoeThjNT9EuWfwBgxsntHY5ydRtKFr1pgKGcgxx'
}
}
}
};
async function validateEventData() {
const eventData = plainToClass(EventsSetterValidator, jsonData);
const errors = await validate(eventData, {
whitelist: true,
forbidNonWhitelisted: true,
validationError: { target: false }
});
if (errors.length > 0) {
console.log('Validation failed. Errors:', JSON.stringify(errors, null, 2));
return false;
} else {
console.log('Validation successful!');
console.log('Validated data:', JSON.stringify(eventData, null, 2));
return true;
}
}
validateEventData()
.then(isValid => console.log('Is valid:', isValid))
.catch(err => console.error('Error during validation:', err));

View File

@ -0,0 +1,394 @@
export interface interfaceMenu {
"key": string;
"icon": string;
"text": { "tr": string, "en": string };
"page": string | null;
"token": string | null;
"color": string;
"subs": interfaceMenu[] | null;
}
export interface interfaceMapper {
[key: string]: string;
}
export interface interfaceMenus {
"Menu": interfaceMenu[];
"Mapper": interfaceMapper;
}
function generateMapperKey(keys: string[]): string {
return keys.join(':') + ':';
}
function generateMapper(menu: interfaceMenu[], parentKeys: string[] = []): interfaceMapper {
let mapper: interfaceMapper = {};
for (const item of menu) {
const currentKeys = [...parentKeys, item.key];
// If this item has a page, add it to the mapper
if (item.page) {
mapper[item.page] = generateMapperKey(currentKeys);
}
if (item.subs) {
const subMapper = generateMapper(item.subs, currentKeys);
mapper = { ...mapper, ...subMapper };
}
}
return mapper;
}
function generateDynamicMapper(menus: interfaceMenu[]): interfaceMapper {
return generateMapper(menus);
}
{/*
*/}
const menuForEmployeeDefinition = [
{
key: "a6EoBlTPSgGbUQELbyRwMA",
icon: "",
text: { "tr": "Dashboard", "en": "Dashboard" },
page: "/dashboard",
token: null,
color: "",
subs: null,
},
{
key: "NV2kI8NERmqrNgIeiUYojQ",
icon: "",
text: { "tr": "Bireysel", "en": "Individual" },
page: null,
token: null,
color: "",
subs: [
{
key: "xnhFAyi3Sp2qVWcVcR6m9w",
icon: "",
text: { "tr": "Birey", "en": "Person" },
page: "/person",
token: null,
color: "",
subs: [
{
key: "7wdsqwCQSmXRsRPC9GSgwx",
icon: "",
text: { "tr": "Oluştur", "en": "Create" },
page: "/person/create",
token: null,
color: "",
subs: null
},
{
key: "56O8WRP4TyC7F8bc1vjXgx",
icon: "",
text: { "tr": "Güncelle", "en": "Update" },
page: "/person/update",
token: null,
color: "",
subs: null
},
{
key: "RPaESp64SUmjNyEY1WUE8Q",
icon: "",
text: { "tr": "Sil", "en": "Delete" },
page: "/person/delete",
token: null,
color: "",
subs: null
}
]
},
{
key: "qcRK3EPQSoLSWkJFhtWOwx",
icon: "",
text: { "tr": "Kullanıcı", "en": "User" },
page: "/users",
token: null,
color: "",
subs: [
{
key: "PqNGe0SaQKeyUGyzJoSLwx",
icon: "",
text: { "tr": "Oluştur", "en": "Create" },
page: "/users/create",
token: null,
color: "",
subs: null
},
{
key: "ruvQlE7wQzqHqUvCNIoUnA",
icon: "",
text: { "tr": "Güncelle", "en": "Update" },
page: "/users/update",
token: null,
color: "",
subs: null
},
{
key: "DfDStf1dTBCRShNQeb5pZA",
icon: "",
text: { "tr": "Sil", "en": "Delete" },
page: "/users/delete",
token: null,
color: "",
subs: null
}
]
}
]
},
{
key: "ALV19bQ8S7q8LpOkdRDMwx",
icon: "",
text: { "tr": "Bina", "en": "Build" },
page: null,
token: null,
color: "",
subs: [
{
key: "eToBYS4DTEKseVYMJLNZwx",
icon: "",
text: { "tr": "Binalar", "en": "Building" },
page: null,
token: null,
color: "",
subs: [
{
key: "EkR7p6qmRN2Wb1GLsH5aEQ",
icon: "",
text: { "tr": "Oluştur", "en": "Create" },
page: "/building/build/create",
token: null,
color: "",
subs: null
},
{
key: "qcoHwABjSli04D7xeWGOHQ",
icon: "",
text: { "tr": "Güncelle", "en": "Update" },
page: "/building/build/update",
token: null,
color: "",
subs: null
},
{
key: "vC2oPkjRfudvBDlNReeRAx",
icon: "",
text: { "tr": "Sil", "en": "Delete" },
page: "/building/build/delete",
token: null,
color: "",
subs: null
}
],
},
{
key: "NFte61RnTHGPWlnoUItHAx",
icon: "",
text: { "tr": "Daireler", "en": "Parts" },
page: null,
token: null,
color: "",
subs: [
{
key: "7o6QNpelSpmxpJxTedEj4w",
icon: "",
text: { "tr": "Oluştur", "en": "Create" },
page: "/building/parts/create",
token: null,
color: "",
subs: null
},
{
key: "rP6idRkyToLcxwpalCxgxx",
icon: "OBKPalaMQwWhQmQ9Ni0y6Q",
text: { "tr": "Güncelle", "en": "Update" },
page: "/building/parts/update",
token: null,
color: "",
subs: null
},
{
key: "CBNaWzVqRaSpWaPTM54PbA",
icon: "",
text: { "tr": "Sil", "en": "Delete" },
page: "/building/parts/delete",
token: null,
color: "",
subs: null
}
],
},
{
key: "NFte61RnTHGPWlnoUItHAx",
icon: "",
text: { "tr": "Alanlar", "en": "Area" },
page: null,
token: null,
color: "",
subs: []
}
],
},
{
key: "yzvyvqMhQ06TdC9paOw4Ax",
icon: "",
text: { "tr": "Yönetim", "en": "Management" },
page: null,
token: null,
color: "",
subs: [
{
key: "DEumSZtaTSKiDsD1VJPQxx",
icon: "",
text: { "tr": "Bütçe", "en": "Budget" },
page: "/management/budget",
token: null,
color: "",
subs: [
{
key: "PIPD61aZRveFZ6GGfK3VYw",
icon: "",
text: { "tr": "Eylemler", "en": "Actions" },
page: "/management/budget/actions",
token: null,
color: "",
subs: null,
},
{
key: "",
icon: "",
text: { "tr": "Durum", "en": "Status" },
page: "/management/budget/status",
token: null,
color: "",
subs: null,
}
],
},
],
},
{
key: "RHI0bthYRjWWf4tBaPBdgx",
icon: "",
text: { "tr": "Toplantılar", "en": "Meetings" },
page: "/meetings",
token: null,
color: "",
subs: [
{
key: "OESxDOI6S4eNcdeRCrKIjQ",
icon: "",
text: { "tr": "Yıllık", "en": "Annual" },
page: "/meetings/annual",
token: null,
color: "",
subs: [
{
key: "MhEHidsRWyHdCqtHJOcvAx",
icon: "",
text: { "tr": "Oluştur", "en": "Create" },
page: "/meetings/annual/create",
token: null,
color: "",
subs: null,
},
{
key: "xhnSW4hWSDuJyREMjXOivA",
icon: "",
text: { "tr": "Kapat", "en": "Close" },
page: "/meetings/annual/close",
token: null,
color: "",
subs: null,
},
],
},
{
key: "A4raUDNFTpZ7mPfqJBGSwx",
icon: "",
text: { "tr": "Acil", "en": "Emergency" },
page: "/meetings/emergency",
token: null,
color: "",
subs: [
{
key: "T3Fd0C5Tf2V1dZhiZuNQxx",
icon: "",
text: { "tr": "Oluştur", "en": "Create" },
page: "/meetings/emergency/create",
token: null,
color: "",
subs: null,
},
{
key: "L1ogOYhSl6BDPstufiSwxx",
icon: "",
text: { "tr": "Kapat", "en": "Close" },
page: "/meetings/emergency/close",
token: null,
color: "",
subs: null,
},
],
},
{
key: "vwzmxtBoQFW62YHes5OZAg",
icon: "",
text: { "tr": "Katılımlar", "en": "Participations" },
page: "/meetings/participations",
token: null,
color: "",
subs: [],
}
],
}
];
const menuForOccupantDefinition = [
{
key: "dzFGPzZJRgmft4HrrTeBtQ",
icon: "",
text: { "tr": "Pano", "en": "Dashboard" },
page: "/dashboard",
token: null,
color: "",
subs: [],
},
]
const config = {
FirstLayerColor: "#ebc334",
SecondLayerColor: "#18910d",
ThirdLayerColor: "#2825c4",
employeePrefix: "/office",
occupantPrefix: "/venue"
}
function applyColorsAndPrefixes(menu: interfaceMenu[], isEmployee: boolean, config: any, layer: number = 1): interfaceMenu[] {
return menu.map(item => {
const newItem = { ...item };
if (layer === 1) newItem.color = config.FirstLayerColor;
else if (layer === 2) newItem.color = config.SecondLayerColor;
else if (layer >= 3) newItem.color = config.ThirdLayerColor;
if (newItem.page) { newItem.page = `${isEmployee ? config.employeePrefix : config.occupantPrefix}${newItem.page}` }
if (newItem.subs) { newItem.subs = applyColorsAndPrefixes(newItem.subs, isEmployee, layer + 1) }
return newItem;
});
}
export const occupantMenus: interfaceMenus = {
Menu: applyColorsAndPrefixes(menuForOccupantDefinition, false, config),
Mapper: generateDynamicMapper(applyColorsAndPrefixes(menuForOccupantDefinition, false, config))
};
export const employeeMenus: interfaceMenus = {
Menu: applyColorsAndPrefixes(menuForEmployeeDefinition, true, config),
Mapper: generateDynamicMapper(applyColorsAndPrefixes(menuForEmployeeDefinition, true, config))
};

View File

@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class ServicesService {}
export class MenusService { }

View File

@ -0,0 +1,13 @@
import { employeeMenus, occupantMenus } from './main';
console.log('Employee Menu Mapper:');
console.log(JSON.stringify(employeeMenus.Mapper, null, 2));
console.log('\nEmployee Menu Structure (with colors):');
console.log(JSON.stringify(employeeMenus.Menu, null, 2));
console.log('\nOccupant Menu Mapper:');
console.log(JSON.stringify(occupantMenus.Mapper, null, 2));
console.log('\nOccupant Menu Structure (with colors):');
console.log(JSON.stringify(occupantMenus.Menu, null, 2));

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { NavigatorController } from './navigator.controller';
describe('NavigatorController', () => {
let controller: NavigatorController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [NavigatorController],
}).compile();
controller = module.get<NavigatorController>(NavigatorController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@ -0,0 +1,25 @@
import { Controller, Post, NotFoundException, Body } from '@nestjs/common';
import { MongoService } from '@/src/database/mongo/mongo.service';
import { mongoSetValidator, mongoGetValidator } from '@/src/navigator/dtoValidator';
@Controller('navigator')
export class NavigatorController {
constructor(private mongoService: MongoService) { }
@Post('event/set')
async setEvent(@Body() body: any) { }
@Post('event/get')
async getEvent(@Body() body: any) {
// Get all events from backend statics & Get users registered event from mongo service
}
@Post('page/set')
async setPage(@Body() body: any) { }
@Post('page/get')
async getPage(@Body() body: any) {
// Get all pages from Frontend & Get users registered page from mongo service
}
}

View File

@ -0,0 +1,16 @@
import { Module } from '@nestjs/common';
import { MongoModule } from '@/src/database/mongo/mongo.module';
import { MenusService } from '@/src/navigator/menus/services.service';
import { NavigatorController } from '@/src/navigator/navigator.controller';
import { EventsService } from './events/events.service';
import { PrismaService } from '@/src/prisma.service';
@Module({
controllers: [NavigatorController],
imports: [MongoModule],
providers: [MenusService, EventsService, PrismaService],
exports: [MenusService, EventsService, PrismaService]
})
export class NavigatorModule {
constructor() { }
}

View File

@ -240,7 +240,7 @@ export const EmployeeTokenSchema = z.object({
menu: z.array(z.object({})).nullable(),
pages: z.array(z.string()).nullable(),
events: z.array(z.string()).nullable(),
events: z.record(z.string(), z.string()).nullable(),
selection: z.record(z.string(), z.unknown()).nullable(),
typeToken: z.string(),
@ -258,7 +258,7 @@ export const OccupantTokenSchema = z.object({
menu: z.array(z.object({})).nullable(),
pages: z.array(z.string()).nullable(),
events: z.array(z.string()).nullable(),
events: z.record(z.string(), z.string()).nullable(),
selection: z.record(z.string(), z.unknown()).nullable(),
typeToken: z.string(),

View File

@ -2,13 +2,14 @@ import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { PrismaModule } from '@/prisma/prisma.module';
import { CacheService } from '../cache.service';
import { CacheService } from '../database/redis/redis.service';
import { UtilsModule } from '../utils/utils.module';
import { RedisModule } from '../database/redis/redis.module';
@Module({
imports: [PrismaModule, UtilsModule],
providers: [UsersService, CacheService],
imports: [PrismaModule, UtilsModule, RedisModule],
providers: [UsersService],
controllers: [UsersController],
exports: [UsersService],
})
export class UsersModule {}
export class UsersModule { }

View File

@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from '@/src/prisma.service';
import { Prisma, users } from '@prisma/client';
import { CacheService } from '../cache.service';
import { CacheService } from '../database/redis/redis.service';
import { PaginationHelper, PaginationInfo } from '../utils/pagination-helper';
@Injectable()
@ -10,7 +10,7 @@ export class UsersService {
private prisma: PrismaService,
private cacheService: CacheService,
private paginationHelper: PaginationHelper,
) {}
) { }
async findAll(filter: any): Promise<Partial<users>[]> {
return this.prisma.users.findMany({

View File

@ -0,0 +1,51 @@
import { ForbiddenException, MisdirectedException, Injectable } from "@nestjs/common";
import { RedisHandlers } from "../store/redisHandlers";
@Injectable()
export class Navigator {
constructor(private redisHandler: RedisHandlers) { }
async getInfos(mainService: any, userToken: string) {
// Get asked service by userToken
const mainServiceMapper = mainService?.mapper
if (!mainServiceMapper) { throw new ForbiddenException(`Mapper in ${mainService.constructor.name} is missing or null`) }
// Get related events from mainServiceMapper by userToken
const relatedService = mainServiceMapper?.[userToken]
if (!relatedService) { throw new MisdirectedException(`No service found for drive token: ${userToken}`) }
// Call event infos from relatedService
return await relatedService.infoEvents(userToken);
}
async getService(request: any, mapper: any) {
// Get request drive token from acess control guard and retrieve related Service
const driveToken = request.driveToken
if (!driveToken) { throw new Error('Drive token is missing or null') }
// Get second part of drive token which is user type token
const secondPartOfDriveToken = driveToken.split(":")[1]
if (!secondPartOfDriveToken) { throw new Error('Drive token is missing or null') }
// Get related service from mapper which function maps registered events to functions
return mapper[secondPartOfDriveToken];
}
async getFunction(request: any, mapper: any, query: any) {
const relatedService = await this.getService(request, mapper)
if (!relatedService) { throw new Error(`No service found for drive token: ${request.driveToken}`) }
try {
// Get function mapper from related service
if (!relatedService.mapper) { throw new Error(`Mapper in ${relatedService.constructor.name} is missing or null`) }
// Get redis select token object from redis
const selectObject = await this.redisHandler.getSelectFromRedis(request);
if (!selectObject) { throw new Error(`Select object is missing or null`) }
if (!selectObject.value.events) { throw new Error(`Events in select object is missing or null`) }
const eventKey = Object.entries(selectObject.value.events).filter((key) => key.includes(request.driveToken))[0]
if (!eventKey) { throw new Error(`No event is registered for this user ${request.driveToken}`) }
// Get function to call from related service mapper
const functionToCall = relatedService.mapper[eventKey.join(":")];
if (!functionToCall || typeof functionToCall !== 'function') {
throw new Error(`No function found for drive token: ${request.driveToken}`);
}
return await functionToCall(query);
} catch (error) { throw new ForbiddenException(`This user is not allowed to access this endpoint. Please contact your system administrator.`) }
}
}

View File

@ -60,7 +60,6 @@ export class PaginationHelper {
query: any & { page?: number; pageSize?: number },
service: ModelDelegate,
): Promise<{ data: any[]; pagination: PaginationInfo }> {
console.log("findWithPagination query", query)
return this.paginate(service, query);
}
}

View File

@ -0,0 +1,6 @@
import { Injectable } from "@nestjs/common";
@Injectable()
export class MongoHandler {
constructor() { }
}

View File

@ -3,8 +3,8 @@ import {
TokenDictInterface,
AuthToken,
AuthTokenSchema,
} from '@/src/types/auth/token';
import { CacheService } from '@/src/cache.service';
} from '../../types/auth/token';
import { CacheService } from '../../database/redis/redis.service';
import { PasswordHandlers } from './loginHandler';
import { Injectable, ForbiddenException } from '@nestjs/common';

View File

@ -1,18 +1,18 @@
import { Module } from '@nestjs/common';
import { PaginationHelper } from './pagination-helper';
import { PrismaService } from '@/src/prisma.service';
import { RedisHandlers } from './auth/redisHandlers';
import { PasswordHandlers } from './auth/loginHandler';
import { CacheService } from '@/src/cache.service';
import { RedisHandlers } from './store/redisHandlers';
import { PasswordHandlers } from './store/loginHandler';
import { RedisModule } from '../database/redis/redis.module';
@Module({
imports: [RedisModule],
providers: [
PaginationHelper,
PrismaService,
RedisHandlers,
PasswordHandlers,
CacheService,
],
exports: [PaginationHelper, RedisHandlers, PasswordHandlers, CacheService],
exports: [PaginationHelper, RedisHandlers, PasswordHandlers],
})
export class UtilsModule { }
export class UtilsModule { }

View File

@ -1,6 +1,6 @@
{
"compilerOptions": {
"types": ["node"],
"types": ["node", "jest"],
"typeRoots": ["./node_modules/@types"],
"module": "commonjs",
"declaration": true,
@ -19,7 +19,6 @@
"noImplicitAny": false,
"strictBindCallApply": false,
"noFallthroughCasesInSwitch": false,
// "baseUrl": "./src",
"paths": {
"@/*": ["*"]
}

View File

@ -33,6 +33,6 @@ export default async function ProtectedLayout({
const headersList = await headers();
// const locale = getLocaleFromPath(removeSubStringFromPath(headersList));
const removedLocaleRoute = removeSubStringFromPath(headersList);
console.log('Removed locale route:', removedLocaleRoute);
// console.log('Removed locale route:', removedLocaleRoute);
return <>{children}</>;
}

View File

@ -0,0 +1,25 @@
'use server';
import React from 'react';
import { dashboardPages } from '@/pages/office/dashboard/mapper';
import { renderPage } from '@/lib/page';
import { getSelectToken } from '@/fetchers/token/select';
export default async function DashboardPage() {
const pageUrl = "/office/dashboard";
const selectToken = await getSelectToken();
const RenderPage = renderPage(selectToken, pageUrl, dashboardPages);
if (RenderPage) {
return <>
<div>Dashboard Page</div>
<div className='flex align-center justify-center h-screen w-screen mt-10 text-2xl'>
<RenderPage />
</div>
</>
}
return <>
<div>Dashboard Page</div>
<div>You are not allowed to reach any page under {pageUrl}. Please contact your administrator.</div>
</>;
};

View File

@ -0,0 +1,25 @@
'use server';
import React from 'react';
import { dashboardPages } from '@/pages/venue/dashboard/mapper';
import { renderPage } from '@/lib/page';
import { getSelectTokenObject } from '@/fetchers/token/select';
export default async function DashboardPage() {
const pageUrl = "/venue/dashboard";
const selectToken = await getSelectTokenObject();
if (!selectToken) {
const RenderPage = renderPage(selectToken, pageUrl, dashboardPages);
if (RenderPage) {
return <>
<div>Dashboard Page</div>
<div className='flex align-center justify-center h-screen w-screen mt-10 text-2xl'>
<RenderPage />
</div>
</>
}
}
return <>
<div>Dashboard Page</div>
<div>You are not allowed to reach any page under {pageUrl}. Please contact your administrator.</div>
</>;
};

View File

@ -32,6 +32,7 @@ interface UpdateFieldParams<T> {
}
type RScan = Promise<string | null>;
type RScanToken = Promise<any | null>;
type RExists = Promise<boolean>;
type RUpdate = Promise<void>;
type RDelete = Promise<void>;
@ -122,3 +123,38 @@ export async function scanByRKeyDouble(params: DScanParams): RScan {
} while (cursor !== "0");
return keys.length > 0 ? keys[0] : null;
}
export async function scanByRKeySingleToken(params: SScanParams): RScan {
const pattern = redisScanAccess(params.rKey);
const keys: string[] = [];
let cursor = "0";
do {
const [nextCursor, matchedKeys] = await redis.scan(
cursor,
"MATCH",
pattern
);
cursor = nextCursor;
keys.push(...matchedKeys);
} while (cursor !== "0");
if (keys.length === 0) return null;
return await getJSON({ key: keys[0] });
}
export async function scanByRKeyDoubleToken(params: DScanParams): RScan {
const pattern = redisScanSelect(params.rKey, params.sKey);
console.log("pattern", pattern);
const keys: string[] = [];
let cursor = "0";
do {
const [nextCursor, matchedKeys] = await redis.scan(
cursor,
"MATCH",
pattern
);
cursor = nextCursor;
keys.push(...matchedKeys);
} while (cursor !== "0");
if (keys.length === 0) return null;
return await getJSON({ key: keys[0] });
}

View File

@ -1,6 +1,6 @@
'use server';
import { AuthError } from "@/fetchers/types/base";
import { scanByRKeyDouble } from "@/fetchers/redis/redisService";
import { scanByRKeyDouble, scanByRKeyDoubleToken, scanByRKeySingleToken } from "@/fetchers/redis/redisService";
import { getPlainAccessToken } from "./access";
import { nextCrypto } from "@/fetchers/base";
import { getCookieSelectToken, removeCookieTokens, setCookieSelectToken } from "./cookies";
@ -54,6 +54,30 @@ async function getSelectToken() {
catch (error) { throw new AuthError("No select token found in headers") }
}
async function getSelectTokenObject() {
try {
const plainAccessToken = await getPlainAccessToken();
const plainSelectToken = await getPlainSelectToken();
console.log('plainAccessToken', plainAccessToken);
console.log('plainSelectToken', plainSelectToken);
const scanToken = await scanByRKeyDoubleToken({ rKey: plainAccessToken, sKey: plainSelectToken });
if (!scanToken) throw new AuthError("Select token is invalid");
return scanToken;
}
catch (error) { throw new AuthError("No select token found in headers") }
}
async function getAccessTokenObject() {
try {
const plainAccessToken = await getPlainAccessToken();
console.log('plainAccessToken', plainAccessToken);
const scanToken = await scanByRKeySingleToken({ rKey: plainAccessToken });
if (!scanToken) throw new AuthError("Access token is invalid");
return scanToken;
}
catch (error) { throw new AuthError("No access token found in headers") }
}
async function setSelectToken(token: string) {
console.log('setSelectToken is triggered...');
await setCookieSelectToken(token);
@ -75,4 +99,4 @@ async function removeSelectToken() {
await removeCookieTokens();
}
export { getSelectToken, setSelectToken, removeSelectToken, getPlainSelectToken, isSelectTokenValid };
export { getSelectToken, setSelectToken, removeSelectToken, getPlainSelectToken, isSelectTokenValid, getSelectTokenObject, getAccessTokenObject };

View File

@ -0,0 +1,10 @@
function renderPage(selectToken: any, pageUrl: string, dashboardPages: any) {
const subPageKey = selectToken.pages[pageUrl];
if (Object.keys(dashboardPages).includes(subPageKey)) {
const subPage = dashboardPages[subPageKey as keyof typeof dashboardPages];
if (subPage) { if (subPage.page) { return subPage.page } }
} return null;
}
export { renderPage };

View File

@ -0,0 +1,11 @@
'use client';
const DashboardU0QncONSk22PFxZ5xefmgx: React.FC = () => {
return (
<div>
<h1>DashboardU0QncONSk22PFxZ5xefmgx</h1>
</div>
);
}
export default DashboardU0QncONSk22PFxZ5xefmgx;

View File

@ -0,0 +1,18 @@
import { Page } from "@/pages/types/page";
import DashboardU0QncONSk22PFxZ5xefmgx from "./U0QncONSk22PFxZ5xefmgx";
const dashboardPages: Page = {
"qY56XMEr08wJkNvOR6EYQZKMVdTQEfHdLXGzzxcKU24E:U0QncONSk22PFxZ5xefmgx": {
name: "DashboardU0QncONSk22PFxZ5xefmgx",
key: "U0QncONSk22PFxZ5xefmgx",
url: "/venue/dashboard",
page: DashboardU0QncONSk22PFxZ5xefmgx,
description: "Dashboard",
isDefault: true,
params: {},
events: ["Aevent", "Aevent", "Aevent"],
tokens: ["TOKEN", "TOKEN", "TOKEN", "TOKEN"]
}
};
export { dashboardPages };

View File

@ -0,0 +1,14 @@
export interface Page {
[key: string]: {
name: string;
key: string;
url: string;
page: React.FC;
description: string;
isDefault: boolean;
params: Record<string, boolean>;
events?: string[];
tokens?: string[];
}
}

View File

@ -0,0 +1,11 @@
'use client';
const DashboardIdTch3qS9aJXkvqXodAxxx: React.FC = () => {
return (
<div>
<h1>DashboardIdTch3qS9aJXkvqXodAxxx</h1>
</div>
);
}
export default DashboardIdTch3qS9aJXkvqXodAxxx;

View File

@ -0,0 +1,18 @@
import { Page } from "@/pages/types/page";
import DashboardIdTch3qS9aJXkvqXodAxxx from "./IdTch3qS9aJXkvqXodAxxx";
const dashboardPages: Page = {
"IbGpchaw3muiY7y9rnV0EJYoPy5XoOOrITT9JlfIbqwE:IdTch3qS9aJXkvqXodAxxx": {
name: "DashboardIdTch3qS9aJXkvqXodAxxx",
key: "IdTch3qS9aJXkvqXodAxxx",
url: "/venue/dashboard",
page: DashboardIdTch3qS9aJXkvqXodAxxx,
description: "Dashboard",
isDefault: true,
params: {},
events: ["Aevent", "Aevent", "Aevent"],
tokens: ["TOKEN", "TOKEN", "TOKEN", "TOKEN"]
}
};
export { dashboardPages };

View File

@ -5,6 +5,7 @@ MONGO_PORT=27017
MONGO_USER=appuser
MONGO_AUTH_DB=appdb
MONGO_PASSWORD=apppassword
MONGO_URL=mongodb://appuser:apppassword@10.10.2.13:27017/appdb
POSTGRES_USER=postgres
POSTGRES_PASSWORD=password