From 9232da69d36aad611831f7b22ae5c029bad4defd Mon Sep 17 00:00:00 2001 From: Berkay Date: Tue, 5 Aug 2025 14:42:24 +0300 Subject: [PATCH] updated event controllers and service event mtach tested --- ServicesApi/package-lock.json | 140 ++++++- ServicesApi/package.json | 1 + .../src/accounts/accounts.controller.ts | 39 +- ServicesApi/src/accounts/accounts.module.ts | 10 +- ServicesApi/src/accounts/accounts.service.ts | 20 +- .../accounts/superusers/superusers.service.ts | 3 +- ServicesApi/src/app.module.ts | 20 +- ServicesApi/src/auth/auth.module.ts | 8 +- ServicesApi/src/auth/login/login.service.ts | 4 +- .../auth/password/change/change.service.ts | 4 +- .../auth/password/create/create.service.ts | 2 +- .../src/auth/password/reset/reset.service.ts | 2 +- ServicesApi/src/auth/select/select.service.ts | 92 ++-- .../src/database/mongo/mongo.module.ts | 9 + .../src/database/mongo/mongo.provider.ts | 11 + .../src/database/mongo/mongo.service.spec.ts | 18 + .../src/database/mongo/mongo.service.ts | 257 ++++++++++++ .../src/database/redis/redis.constants.ts | 2 + .../src/database/redis/redis.interfaces.ts | 23 + .../src/database/redis/redis.module.ts | 91 ++++ .../src/database/redis/redis.service.spec.ts | 30 ++ .../redis/redis.service.ts} | 19 +- .../src/middleware/access-control.guard.ts | 6 +- ServicesApi/src/navigator/dtoValidator.ts | 30 ++ .../src/navigator/events/dtoValidator.ts | 41 ++ .../src/navigator/events/events.service.ts | 199 ++++++++- .../navigator/events/validation-example.ts | 37 ++ ServicesApi/src/navigator/menus/main.ts | 394 ++++++++++++++++++ .../services.service.spec.ts | 0 .../{services => menus}/services.service.ts | 2 +- ServicesApi/src/navigator/menus/test.ts | 13 + .../navigator/navigator.controller.spec.ts | 18 + .../src/navigator/navigator.controller.ts | 25 ++ ServicesApi/src/navigator/navigator.module.ts | 16 + ServicesApi/src/types/auth/token.ts | 4 +- ServicesApi/src/users/users.module.ts | 9 +- ServicesApi/src/users/users.service.ts | 4 +- ServicesApi/src/utils/navigator/navigator.ts | 51 +++ .../utils/{auth => navigator}/urlHandler.ts | 0 ServicesApi/src/utils/pagination-helper.ts | 1 - .../src/utils/{auth => store}/loginHandler.ts | 0 ServicesApi/src/utils/store/mongoHandler.ts | 6 + .../utils/{auth => store}/redisHandlers.ts | 4 +- ServicesApi/src/utils/utils.module.ts | 12 +- ServicesApi/tsconfig.json | 3 +- .../src/app/[locale]/(protected)/layout.tsx | 2 +- .../(protected)/office/dashboard/page.tsx | 25 ++ .../(protected)/venue/dashboard/page.tsx | 25 ++ .../src/fetchers/redis/redisService.ts | 36 ++ .../frontend/src/fetchers/token/select.tsx | 28 +- ServicesFrontEnd/frontend/src/lib/page.tsx | 10 + .../dashboard/U0QncONSk22PFxZ5xefmgx.tsx | 11 + .../src/pages/office/dashboard/mapper.ts | 18 + .../frontend/src/pages/types/page.ts | 14 + .../dashboard/IdTch3qS9aJXkvqXodAxxx.tsx | 11 + .../src/pages/venue/dashboard/mapper.ts | 18 + api_env.env | 1 + 57 files changed, 1699 insertions(+), 180 deletions(-) create mode 100644 ServicesApi/src/database/mongo/mongo.module.ts create mode 100644 ServicesApi/src/database/mongo/mongo.provider.ts create mode 100644 ServicesApi/src/database/mongo/mongo.service.spec.ts create mode 100644 ServicesApi/src/database/mongo/mongo.service.ts create mode 100644 ServicesApi/src/database/redis/redis.constants.ts create mode 100644 ServicesApi/src/database/redis/redis.interfaces.ts create mode 100644 ServicesApi/src/database/redis/redis.module.ts create mode 100644 ServicesApi/src/database/redis/redis.service.spec.ts rename ServicesApi/src/{cache.service.ts => database/redis/redis.service.ts} (85%) create mode 100644 ServicesApi/src/navigator/dtoValidator.ts create mode 100644 ServicesApi/src/navigator/events/dtoValidator.ts create mode 100644 ServicesApi/src/navigator/events/validation-example.ts create mode 100644 ServicesApi/src/navigator/menus/main.ts rename ServicesApi/src/navigator/{services => menus}/services.service.spec.ts (100%) rename ServicesApi/src/navigator/{services => menus}/services.service.ts (65%) create mode 100644 ServicesApi/src/navigator/menus/test.ts create mode 100644 ServicesApi/src/navigator/navigator.controller.spec.ts create mode 100644 ServicesApi/src/navigator/navigator.controller.ts create mode 100644 ServicesApi/src/navigator/navigator.module.ts create mode 100644 ServicesApi/src/utils/navigator/navigator.ts rename ServicesApi/src/utils/{auth => navigator}/urlHandler.ts (100%) rename ServicesApi/src/utils/{auth => store}/loginHandler.ts (100%) create mode 100644 ServicesApi/src/utils/store/mongoHandler.ts rename ServicesApi/src/utils/{auth => store}/redisHandlers.ts (98%) create mode 100644 ServicesFrontEnd/frontend/src/app/[locale]/(protected)/office/dashboard/page.tsx create mode 100644 ServicesFrontEnd/frontend/src/app/[locale]/(protected)/venue/dashboard/page.tsx create mode 100644 ServicesFrontEnd/frontend/src/lib/page.tsx create mode 100644 ServicesFrontEnd/frontend/src/pages/office/dashboard/U0QncONSk22PFxZ5xefmgx.tsx create mode 100644 ServicesFrontEnd/frontend/src/pages/office/dashboard/mapper.ts create mode 100644 ServicesFrontEnd/frontend/src/pages/types/page.ts create mode 100644 ServicesFrontEnd/frontend/src/pages/venue/dashboard/IdTch3qS9aJXkvqXodAxxx.tsx create mode 100644 ServicesFrontEnd/frontend/src/pages/venue/dashboard/mapper.ts diff --git a/ServicesApi/package-lock.json b/ServicesApi/package-lock.json index fc66ca8..1553b4d 100644 --- a/ServicesApi/package-lock.json +++ b/ServicesApi/package-lock.json @@ -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", diff --git a/ServicesApi/package.json b/ServicesApi/package.json index 55a6081..b6db907 100644 --- a/ServicesApi/package.json +++ b/ServicesApi/package.json @@ -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", diff --git a/ServicesApi/src/accounts/accounts.controller.ts b/ServicesApi/src/accounts/accounts.controller.ts index 7d22663..c50f95b 100644 --- a/ServicesApi/src/accounts/accounts.controller.ts +++ b/ServicesApi/src/accounts/accounts.controller.ts @@ -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) } } diff --git a/ServicesApi/src/accounts/accounts.module.ts b/ServicesApi/src/accounts/accounts.module.ts index 6acefd0..54f86ed 100644 --- a/ServicesApi/src/accounts/accounts.module.ts +++ b/ServicesApi/src/accounts/accounts.module.ts @@ -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], }) diff --git a/ServicesApi/src/accounts/accounts.service.ts b/ServicesApi/src/accounts/accounts.service.ts index c5db916..3d0cb07 100644 --- a/ServicesApi/src/accounts/accounts.service.ts +++ b/ServicesApi/src/accounts/accounts.service.ts @@ -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); - } } diff --git a/ServicesApi/src/accounts/superusers/superusers.service.ts b/ServicesApi/src/accounts/superusers/superusers.service.ts index badd6d2..43d3d0f 100644 --- a/ServicesApi/src/accounts/superusers/superusers.service.ts +++ b/ServicesApi/src/accounts/superusers/superusers.service.ts @@ -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; diff --git a/ServicesApi/src/app.module.ts b/ServicesApi/src/app.module.ts index e0c9bb9..7aeee8e 100644 --- a/ServicesApi/src/app.module.ts +++ b/ServicesApi/src/app.module.ts @@ -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, diff --git a/ServicesApi/src/auth/auth.module.ts b/ServicesApi/src/auth/auth.module.ts index 20d223d..d54bc89 100644 --- a/ServicesApi/src/auth/auth.module.ts +++ b/ServicesApi/src/auth/auth.module.ts @@ -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 { } diff --git a/ServicesApi/src/auth/login/login.service.ts b/ServicesApi/src/auth/login/login.service.ts index 04408c3..d9f49e5 100644 --- a/ServicesApi/src/auth/login/login.service.ts +++ b/ServicesApi/src/auth/login/login.service.ts @@ -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'; diff --git a/ServicesApi/src/auth/password/change/change.service.ts b/ServicesApi/src/auth/password/change/change.service.ts index 958c9ef..eed7272 100644 --- a/ServicesApi/src/auth/password/change/change.service.ts +++ b/ServicesApi/src/auth/password/change/change.service.ts @@ -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 { diff --git a/ServicesApi/src/auth/password/create/create.service.ts b/ServicesApi/src/auth/password/create/create.service.ts index 317d403..e1ee54f 100644 --- a/ServicesApi/src/auth/password/create/create.service.ts +++ b/ServicesApi/src/auth/password/create/create.service.ts @@ -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() diff --git a/ServicesApi/src/auth/password/reset/reset.service.ts b/ServicesApi/src/auth/password/reset/reset.service.ts index af2df32..b0d271e 100644 --- a/ServicesApi/src/auth/password/reset/reset.service.ts +++ b/ServicesApi/src/auth/password/reset/reset.service.ts @@ -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 { diff --git a/ServicesApi/src/auth/select/select.service.ts b/ServicesApi/src/auth/select/select.service.ts index c1b152e..e548c6a 100644 --- a/ServicesApi/src/auth/select/select.service.ts +++ b/ServicesApi/src/auth/select/select.service.ts @@ -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'); } diff --git a/ServicesApi/src/database/mongo/mongo.module.ts b/ServicesApi/src/database/mongo/mongo.module.ts new file mode 100644 index 0000000..88ba8ca --- /dev/null +++ b/ServicesApi/src/database/mongo/mongo.module.ts @@ -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 {} diff --git a/ServicesApi/src/database/mongo/mongo.provider.ts b/ServicesApi/src/database/mongo/mongo.provider.ts new file mode 100644 index 0000000..28bd3d4 --- /dev/null +++ b/ServicesApi/src/database/mongo/mongo.provider.ts @@ -0,0 +1,11 @@ +import { MongoClient, Db } from 'mongodb'; + +export const MongoProvider = { + provide: 'MONGO_DB', + useFactory: async (): Promise => { + const uri = 'mongodb://appuser:apppassword@10.10.2.13:27017/appdb'; + const client = new MongoClient(uri); + await client.connect(); + return client.db('appdb'); + }, +}; diff --git a/ServicesApi/src/database/mongo/mongo.service.spec.ts b/ServicesApi/src/database/mongo/mongo.service.spec.ts new file mode 100644 index 0000000..c808355 --- /dev/null +++ b/ServicesApi/src/database/mongo/mongo.service.spec.ts @@ -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); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/ServicesApi/src/database/mongo/mongo.service.ts b/ServicesApi/src/database/mongo/mongo.service.ts new file mode 100644 index 0000000..28f838c --- /dev/null +++ b/ServicesApi/src/database/mongo/mongo.service.ts @@ -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; + + 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): 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); + 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 { 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): Promise { + 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; + * 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); + */ + async findMany(filter: Filter, limit?: number, skip?: number): Promise { + 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 { + 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 { + 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).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): Promise { + 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; + * const updates = { status: 'archived', archivedAt: new Date() }; + * const result = await mongoService.updateMany(filter, updates); + * console.log(`${result.modifiedCount} users archived`); + */ + async updateMany(filter: Filter, data: Record): Promise { + 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 { + 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; + * const count = await mongoService.deleteMany(filter); + * console.log(`${count} expired sessions deleted`); + */ + async deleteMany(filter: Filter): Promise { + 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 { + if (!field || !value) { throw new Error('Field name and value are required for regex search') } + const pattern = `${prefix}${value}${suffix}`; + const query: Record = {}; + query[field] = { $regex: pattern }; + if (options) { query[field].$options = options; } + return await this.collection.find(query as unknown as Filter).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 { + 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) { + 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).toArray(); + } +} diff --git a/ServicesApi/src/database/redis/redis.constants.ts b/ServicesApi/src/database/redis/redis.constants.ts new file mode 100644 index 0000000..6ee6d05 --- /dev/null +++ b/ServicesApi/src/database/redis/redis.constants.ts @@ -0,0 +1,2 @@ +export const REDIS_CLIENT = 'REDIS_CLIENT'; +export const REDIS_OPTIONS = 'REDIS_OPTIONS'; diff --git a/ServicesApi/src/database/redis/redis.interfaces.ts b/ServicesApi/src/database/redis/redis.interfaces.ts new file mode 100644 index 0000000..19afc0f --- /dev/null +++ b/ServicesApi/src/database/redis/redis.interfaces.ts @@ -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; +} + +export interface RedisModuleAsyncOptions extends Pick { + useExisting?: Type; + useClass?: Type; + useFactory?: (...args: any[]) => Promise | RedisModuleOptions; + inject?: any[]; +} diff --git a/ServicesApi/src/database/redis/redis.module.ts b/ServicesApi/src/database/redis/redis.module.ts new file mode 100644 index 0000000..f6a6367 --- /dev/null +++ b/ServicesApi/src/database/redis/redis.module.ts @@ -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() } + } +} diff --git a/ServicesApi/src/database/redis/redis.service.spec.ts b/ServicesApi/src/database/redis/redis.service.spec.ts new file mode 100644 index 0000000..362c705 --- /dev/null +++ b/ServicesApi/src/database/redis/redis.service.spec.ts @@ -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); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/ServicesApi/src/cache.service.ts b/ServicesApi/src/database/redis/redis.service.ts similarity index 85% rename from ServicesApi/src/cache.service.ts rename to ServicesApi/src/database/redis/redis.service.ts index cdf1f18..52b5eee 100644 --- a/ServicesApi/src/cache.service.ts +++ b/ServicesApi/src/database/redis/redis.service.ts @@ -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 { 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 { const deleted = await this.client.del(key); - if (deleted === 0) { - return false; - } - return true; + return deleted === 0 ? false : true; } /** diff --git a/ServicesApi/src/middleware/access-control.guard.ts b/ServicesApi/src/middleware/access-control.guard.ts index edba33b..4ec9ade 100644 --- a/ServicesApi/src/middleware/access-control.guard.ts +++ b/ServicesApi/src/middleware/access-control.guard.ts @@ -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 { 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; } } diff --git a/ServicesApi/src/navigator/dtoValidator.ts b/ServicesApi/src/navigator/dtoValidator.ts new file mode 100644 index 0000000..420f035 --- /dev/null +++ b/ServicesApi/src/navigator/dtoValidator.ts @@ -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; +} \ No newline at end of file diff --git a/ServicesApi/src/navigator/events/dtoValidator.ts b/ServicesApi/src/navigator/events/dtoValidator.ts new file mode 100644 index 0000000..f0b332d --- /dev/null +++ b/ServicesApi/src/navigator/events/dtoValidator.ts @@ -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>>; + + @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; +} \ No newline at end of file diff --git a/ServicesApi/src/navigator/events/events.service.ts b/ServicesApi/src/navigator/events/events.service.ts index 7ffeae9..09d7f18 100644 --- a/ServicesApi/src/navigator/events/events.service.ts +++ b/ServicesApi/src/navigator/events/events.service.ts @@ -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 { + 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 { + 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; + } +} diff --git a/ServicesApi/src/navigator/events/validation-example.ts b/ServicesApi/src/navigator/events/validation-example.ts new file mode 100644 index 0000000..ad16d21 --- /dev/null +++ b/ServicesApi/src/navigator/events/validation-example.ts @@ -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)); diff --git a/ServicesApi/src/navigator/menus/main.ts b/ServicesApi/src/navigator/menus/main.ts new file mode 100644 index 0000000..089dd8d --- /dev/null +++ b/ServicesApi/src/navigator/menus/main.ts @@ -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)) +}; + diff --git a/ServicesApi/src/navigator/services/services.service.spec.ts b/ServicesApi/src/navigator/menus/services.service.spec.ts similarity index 100% rename from ServicesApi/src/navigator/services/services.service.spec.ts rename to ServicesApi/src/navigator/menus/services.service.spec.ts diff --git a/ServicesApi/src/navigator/services/services.service.ts b/ServicesApi/src/navigator/menus/services.service.ts similarity index 65% rename from ServicesApi/src/navigator/services/services.service.ts rename to ServicesApi/src/navigator/menus/services.service.ts index c432676..95db6fa 100644 --- a/ServicesApi/src/navigator/services/services.service.ts +++ b/ServicesApi/src/navigator/menus/services.service.ts @@ -1,4 +1,4 @@ import { Injectable } from '@nestjs/common'; @Injectable() -export class ServicesService {} +export class MenusService { } diff --git a/ServicesApi/src/navigator/menus/test.ts b/ServicesApi/src/navigator/menus/test.ts new file mode 100644 index 0000000..73c827d --- /dev/null +++ b/ServicesApi/src/navigator/menus/test.ts @@ -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)); diff --git a/ServicesApi/src/navigator/navigator.controller.spec.ts b/ServicesApi/src/navigator/navigator.controller.spec.ts new file mode 100644 index 0000000..bfc44b6 --- /dev/null +++ b/ServicesApi/src/navigator/navigator.controller.spec.ts @@ -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); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/ServicesApi/src/navigator/navigator.controller.ts b/ServicesApi/src/navigator/navigator.controller.ts new file mode 100644 index 0000000..3aaec77 --- /dev/null +++ b/ServicesApi/src/navigator/navigator.controller.ts @@ -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 + } + +} diff --git a/ServicesApi/src/navigator/navigator.module.ts b/ServicesApi/src/navigator/navigator.module.ts new file mode 100644 index 0000000..f213e66 --- /dev/null +++ b/ServicesApi/src/navigator/navigator.module.ts @@ -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() { } +} diff --git a/ServicesApi/src/types/auth/token.ts b/ServicesApi/src/types/auth/token.ts index 69c4938..ce4d1b5 100644 --- a/ServicesApi/src/types/auth/token.ts +++ b/ServicesApi/src/types/auth/token.ts @@ -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(), diff --git a/ServicesApi/src/users/users.module.ts b/ServicesApi/src/users/users.module.ts index d901b58..b5ccd15 100644 --- a/ServicesApi/src/users/users.module.ts +++ b/ServicesApi/src/users/users.module.ts @@ -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 { } diff --git a/ServicesApi/src/users/users.service.ts b/ServicesApi/src/users/users.service.ts index 9182d09..44a49b7 100644 --- a/ServicesApi/src/users/users.service.ts +++ b/ServicesApi/src/users/users.service.ts @@ -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[]> { return this.prisma.users.findMany({ diff --git a/ServicesApi/src/utils/navigator/navigator.ts b/ServicesApi/src/utils/navigator/navigator.ts new file mode 100644 index 0000000..ce29e0c --- /dev/null +++ b/ServicesApi/src/utils/navigator/navigator.ts @@ -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.`) } + } +} \ No newline at end of file diff --git a/ServicesApi/src/utils/auth/urlHandler.ts b/ServicesApi/src/utils/navigator/urlHandler.ts similarity index 100% rename from ServicesApi/src/utils/auth/urlHandler.ts rename to ServicesApi/src/utils/navigator/urlHandler.ts diff --git a/ServicesApi/src/utils/pagination-helper.ts b/ServicesApi/src/utils/pagination-helper.ts index 31c21d4..96c47d2 100644 --- a/ServicesApi/src/utils/pagination-helper.ts +++ b/ServicesApi/src/utils/pagination-helper.ts @@ -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); } } diff --git a/ServicesApi/src/utils/auth/loginHandler.ts b/ServicesApi/src/utils/store/loginHandler.ts similarity index 100% rename from ServicesApi/src/utils/auth/loginHandler.ts rename to ServicesApi/src/utils/store/loginHandler.ts diff --git a/ServicesApi/src/utils/store/mongoHandler.ts b/ServicesApi/src/utils/store/mongoHandler.ts new file mode 100644 index 0000000..7b5368a --- /dev/null +++ b/ServicesApi/src/utils/store/mongoHandler.ts @@ -0,0 +1,6 @@ +import { Injectable } from "@nestjs/common"; + +@Injectable() +export class MongoHandler { + constructor() { } +} \ No newline at end of file diff --git a/ServicesApi/src/utils/auth/redisHandlers.ts b/ServicesApi/src/utils/store/redisHandlers.ts similarity index 98% rename from ServicesApi/src/utils/auth/redisHandlers.ts rename to ServicesApi/src/utils/store/redisHandlers.ts index 4eef208..ebe6828 100644 --- a/ServicesApi/src/utils/auth/redisHandlers.ts +++ b/ServicesApi/src/utils/store/redisHandlers.ts @@ -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'; diff --git a/ServicesApi/src/utils/utils.module.ts b/ServicesApi/src/utils/utils.module.ts index 50300e4..2981d55 100644 --- a/ServicesApi/src/utils/utils.module.ts +++ b/ServicesApi/src/utils/utils.module.ts @@ -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 { } \ No newline at end of file diff --git a/ServicesApi/tsconfig.json b/ServicesApi/tsconfig.json index 79dd344..39869ce 100644 --- a/ServicesApi/tsconfig.json +++ b/ServicesApi/tsconfig.json @@ -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": { "@/*": ["*"] } diff --git a/ServicesFrontEnd/frontend/src/app/[locale]/(protected)/layout.tsx b/ServicesFrontEnd/frontend/src/app/[locale]/(protected)/layout.tsx index 34d36e4..1292645 100644 --- a/ServicesFrontEnd/frontend/src/app/[locale]/(protected)/layout.tsx +++ b/ServicesFrontEnd/frontend/src/app/[locale]/(protected)/layout.tsx @@ -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}; } diff --git a/ServicesFrontEnd/frontend/src/app/[locale]/(protected)/office/dashboard/page.tsx b/ServicesFrontEnd/frontend/src/app/[locale]/(protected)/office/dashboard/page.tsx new file mode 100644 index 0000000..61e92ee --- /dev/null +++ b/ServicesFrontEnd/frontend/src/app/[locale]/(protected)/office/dashboard/page.tsx @@ -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 <> +
Dashboard Page
+
+ +
+ + } + return <> +
Dashboard Page
+
You are not allowed to reach any page under {pageUrl}. Please contact your administrator.
+ ; +}; \ No newline at end of file diff --git a/ServicesFrontEnd/frontend/src/app/[locale]/(protected)/venue/dashboard/page.tsx b/ServicesFrontEnd/frontend/src/app/[locale]/(protected)/venue/dashboard/page.tsx new file mode 100644 index 0000000..8de184a --- /dev/null +++ b/ServicesFrontEnd/frontend/src/app/[locale]/(protected)/venue/dashboard/page.tsx @@ -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 <> +
Dashboard Page
+
+ +
+ + } + } + return <> +
Dashboard Page
+
You are not allowed to reach any page under {pageUrl}. Please contact your administrator.
+ ; +}; \ No newline at end of file diff --git a/ServicesFrontEnd/frontend/src/fetchers/redis/redisService.ts b/ServicesFrontEnd/frontend/src/fetchers/redis/redisService.ts index 7dc171e..72a0b8c 100644 --- a/ServicesFrontEnd/frontend/src/fetchers/redis/redisService.ts +++ b/ServicesFrontEnd/frontend/src/fetchers/redis/redisService.ts @@ -32,6 +32,7 @@ interface UpdateFieldParams { } type RScan = Promise; +type RScanToken = Promise; type RExists = Promise; type RUpdate = Promise; type RDelete = Promise; @@ -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] }); +} diff --git a/ServicesFrontEnd/frontend/src/fetchers/token/select.tsx b/ServicesFrontEnd/frontend/src/fetchers/token/select.tsx index 12a7d91..b73273e 100644 --- a/ServicesFrontEnd/frontend/src/fetchers/token/select.tsx +++ b/ServicesFrontEnd/frontend/src/fetchers/token/select.tsx @@ -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 }; diff --git a/ServicesFrontEnd/frontend/src/lib/page.tsx b/ServicesFrontEnd/frontend/src/lib/page.tsx new file mode 100644 index 0000000..86111b2 --- /dev/null +++ b/ServicesFrontEnd/frontend/src/lib/page.tsx @@ -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 }; diff --git a/ServicesFrontEnd/frontend/src/pages/office/dashboard/U0QncONSk22PFxZ5xefmgx.tsx b/ServicesFrontEnd/frontend/src/pages/office/dashboard/U0QncONSk22PFxZ5xefmgx.tsx new file mode 100644 index 0000000..646acf6 --- /dev/null +++ b/ServicesFrontEnd/frontend/src/pages/office/dashboard/U0QncONSk22PFxZ5xefmgx.tsx @@ -0,0 +1,11 @@ +'use client'; + +const DashboardU0QncONSk22PFxZ5xefmgx: React.FC = () => { + return ( +
+

DashboardU0QncONSk22PFxZ5xefmgx

+
+ ); +} + +export default DashboardU0QncONSk22PFxZ5xefmgx; \ No newline at end of file diff --git a/ServicesFrontEnd/frontend/src/pages/office/dashboard/mapper.ts b/ServicesFrontEnd/frontend/src/pages/office/dashboard/mapper.ts new file mode 100644 index 0000000..c987f97 --- /dev/null +++ b/ServicesFrontEnd/frontend/src/pages/office/dashboard/mapper.ts @@ -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 }; diff --git a/ServicesFrontEnd/frontend/src/pages/types/page.ts b/ServicesFrontEnd/frontend/src/pages/types/page.ts new file mode 100644 index 0000000..73fd955 --- /dev/null +++ b/ServicesFrontEnd/frontend/src/pages/types/page.ts @@ -0,0 +1,14 @@ +export interface Page { + [key: string]: { + name: string; + key: string; + url: string; + page: React.FC; + description: string; + isDefault: boolean; + params: Record; + events?: string[]; + tokens?: string[]; + } +} + diff --git a/ServicesFrontEnd/frontend/src/pages/venue/dashboard/IdTch3qS9aJXkvqXodAxxx.tsx b/ServicesFrontEnd/frontend/src/pages/venue/dashboard/IdTch3qS9aJXkvqXodAxxx.tsx new file mode 100644 index 0000000..45fc93b --- /dev/null +++ b/ServicesFrontEnd/frontend/src/pages/venue/dashboard/IdTch3qS9aJXkvqXodAxxx.tsx @@ -0,0 +1,11 @@ +'use client'; + +const DashboardIdTch3qS9aJXkvqXodAxxx: React.FC = () => { + return ( +
+

DashboardIdTch3qS9aJXkvqXodAxxx

+
+ ); +} + +export default DashboardIdTch3qS9aJXkvqXodAxxx; \ No newline at end of file diff --git a/ServicesFrontEnd/frontend/src/pages/venue/dashboard/mapper.ts b/ServicesFrontEnd/frontend/src/pages/venue/dashboard/mapper.ts new file mode 100644 index 0000000..0cca119 --- /dev/null +++ b/ServicesFrontEnd/frontend/src/pages/venue/dashboard/mapper.ts @@ -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 }; diff --git a/api_env.env b/api_env.env index 8158552..af627e3 100644 --- a/api_env.env +++ b/api_env.env @@ -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