updated event controllers and service event mtach tested

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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