updated frontend and auth backend service

This commit is contained in:
2025-07-31 17:20:49 +03:00
parent 0ce522d04a
commit 924b538559
55 changed files with 1711 additions and 286 deletions

View File

@@ -1,4 +1,4 @@
import { IsObject, IsOptional, IsString, IsBoolean } from 'class-validator';
import { IsOptional, IsString, IsBoolean } from 'class-validator';
export class userLoginValidator {
@IsString()

View File

@@ -17,40 +17,149 @@ export class LoginService {
const foundUser = await this.prisma.users.findFirstOrThrow({
where: { email: dto.accessKey },
});
if (foundUser.password_token) {
throw new Error('Password need to be set first');
}
const isPasswordValid = this.passHandlers.check_password(
foundUser.uu_id,
dto.password,
foundUser.hash_password,
);
if (!isPasswordValid) {
throw new Error('Invalid password');
}
const foundPerson = await this.prisma.people.findFirstOrThrow({
where: { id: foundUser.id },
});
const redisData = AuthTokenSchema.parse({
people: foundPerson,
users: foundUser,
credentials: {
person_id: foundPerson.id,
person_name: foundPerson.firstname,
},
selectionList: [],
});
const accessToken = await this.redis.setLoginToRedis(
redisData,
const alreadyExists = await this.redis.callExistingLoginToken(
foundUser.uu_id,
);
return {
accessToken,
message: 'Login successful',
};
if (alreadyExists) {
return {
token: alreadyExists,
message: 'User already logged in',
};
} else {
let selectList: any[] = [];
if (foundUser.user_type === 'occupant') {
const livingSpaces = await this.prisma.build_living_space.findMany({
where: { people: { id: foundPerson.id } },
orderBy: {
build_parts: {
build: {
id: 'asc',
},
},
},
select: {
uu_id: true,
occupant_types: {
select: {
uu_id: true,
occupant_code: true,
occupant_type: true,
function_retriever: true,
},
},
build_parts: {
select: {
uu_id: true,
part_code: true,
part_no: true,
part_level: true,
human_livable: true,
api_enum_dropdown_build_parts_part_type_idToapi_enum_dropdown: {
select: {
uu_id: true,
enum_class: true,
value: true,
},
},
build: {
select: {
uu_id: true,
build_name: true,
},
},
},
},
},
});
selectList = livingSpaces;
} else if (foundUser.user_type === 'employee') {
const employees = await this.prisma.employees.findMany({
where: { people: { id: foundPerson.id } },
orderBy: {
staff: {
duties: {
departments: {
companies: {
formal_name: 'asc',
},
},
},
},
},
select: {
uu_id: true,
staff: {
select: {
uu_id: true,
staff_code: true,
function_retriever: true,
duties: {
select: {
uu_id: true,
departments: {
select: {
uu_id: true,
department_code: true,
department_name: true,
companies: {
select: {
uu_id: true,
formal_name: true,
public_name: true,
addresses: {
select: {
comment_address: true,
},
},
},
},
},
},
},
},
},
},
},
});
selectList = employees;
}
const redisData = AuthTokenSchema.parse({
people: foundPerson,
users: foundUser,
credentials: {
person_uu_id: foundPerson.uu_id,
person_name: foundPerson.firstname,
person_full_name: `${foundPerson.firstname} ${foundPerson.middle_name || ''} | ${foundPerson.birthname || ''} | ${foundPerson.surname}`,
},
selectionList: {
type: foundUser.user_type,
list: selectList,
},
});
const accessToken = await this.redis.setLoginToRedis(
redisData,
foundUser.uu_id,
);
return {
token: accessToken,
message: 'Login successful',
};
}
}
}

View File

@@ -2,5 +2,5 @@ import { IsString } from 'class-validator';
export class userSelectValidator {
@IsString()
selected_uu_id: string;
uuid: string;
}

View File

@@ -28,27 +28,53 @@ export class SelectService {
);
}
const accessToken = accessObject.key.split(':')[1];
console.log('accessToken', accessToken);
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.selected_uu_id },
where: { uu_id: dto.uuid },
omit: {
id: true,
},
});
const staff = await this.prisma.staff.findFirstOrThrow({
where: { id: employee.staff_id },
omit: {
id: true,
},
});
const duties = await this.prisma.duties.findFirstOrThrow({
where: { id: staff.duties_id },
omit: {
id: true,
},
});
const department = await this.prisma.departments.findFirstOrThrow({
where: { id: duties.department_id },
omit: {
id: true,
},
});
const duty = await this.prisma.duty.findFirstOrThrow({
where: { id: duties.duties_id },
omit: {
id: true,
},
});
const company = await this.prisma.companies.findFirstOrThrow({
where: { id: duties.company_id },
omit: {
id: true,
},
});
const employeeToken = EmployeeTokenSchema.parse({
@@ -59,9 +85,43 @@ export class SelectService {
staff: staff,
menu: null,
pages: null,
config: null,
caches: null,
selection: null,
selection: await this.prisma.employees.findFirstOrThrow({
where: { uu_id: dto.uuid },
select: {
uu_id: true,
staff: {
select: {
uu_id: true,
staff_code: true,
function_retriever: true,
duties: {
select: {
uu_id: true,
departments: {
select: {
uu_id: true,
department_code: true,
department_name: true,
companies: {
select: {
uu_id: true,
formal_name: true,
public_name: true,
addresses: {
select: {
comment_address: true,
},
},
},
},
},
},
},
},
},
},
},
}),
functionsRetriever: staff.function_retriever,
kind: UserType.employee,
});
@@ -70,7 +130,7 @@ export class SelectService {
accessToken,
employeeToken,
accessObject.value.users.uu_id,
dto.selected_uu_id,
dto.uuid,
);
return {
@@ -79,19 +139,54 @@ export class SelectService {
};
} else if (userType === 'occupant') {
const livingSpace = await this.prisma.build_living_space.findFirstOrThrow(
{ where: { uu_id: dto.selected_uu_id } },
{
where: { uu_id: dto.uuid },
omit: {
id: true,
person_id: true,
build_parts_id: true,
occupant_type_id: true,
ref_id: true,
replication_id: true,
cryp_uu_id: true,
},
},
);
const occupantType = await this.prisma.occupant_types.findFirstOrThrow({
where: { id: livingSpace.occupant_type_id },
where: { uu_id: livingSpace.occupant_type_uu_id },
omit: {
id: true,
cryp_uu_id: true,
ref_id: true,
replication_id: true,
},
});
const part = await this.prisma.build_parts.findFirstOrThrow({
where: { id: livingSpace.build_parts_id },
where: { uu_id: livingSpace.build_parts_uu_id },
omit: {
id: true,
cryp_uu_id: true,
ref_id: true,
replication_id: true,
},
});
const build = await this.prisma.build.findFirstOrThrow({
where: { id: part.build_id },
where: { uu_id: part.build_uu_id },
omit: {
id: true,
cryp_uu_id: true,
ref_id: true,
replication_id: true,
},
});
const company = await this.prisma.companies.findFirstOrThrow({
where: { uu_id: accessObject.value.users.related_company },
omit: {
id: true,
cryp_uu_id: true,
ref_id: true,
replication_id: true,
},
});
const occupantToken = OccupantTokenSchema.parse({
livingSpace: livingSpace,
@@ -103,7 +198,42 @@ export class SelectService {
pages: null,
config: null,
caches: null,
selection: null,
selection: await this.prisma.build_living_space.findFirstOrThrow({
where: { uu_id: dto.uuid },
select: {
uu_id: true,
occupant_types: {
select: {
uu_id: true,
occupant_code: true,
occupant_type: true,
function_retriever: true,
},
},
build_parts: {
select: {
uu_id: true,
part_code: true,
part_no: true,
part_level: true,
human_livable: true,
api_enum_dropdown_build_parts_part_type_idToapi_enum_dropdown: {
select: {
uu_id: true,
enum_class: true,
value: true,
},
},
build: {
select: {
uu_id: true,
build_name: true,
},
},
},
},
},
}),
functionsRetriever: occupantType.function_retriever,
kind: UserType.occupant,
});
@@ -111,7 +241,7 @@ export class SelectService {
accessToken,
occupantToken,
accessObject.value.users.uu_id,
dto.selected_uu_id,
dto.uuid,
);
return {
message: 'Select successful',

View File

@@ -19,16 +19,7 @@ export class CacheService {
if (!value) {
return null;
}
return JSON.parse(value);
}
async get_with_keys(listKeys: (string | null)[]): Promise<any | null> {
const joinKeys = this.createRegexPattern(listKeys);
const value = await this.client.get(joinKeys);
if (!value) {
return null;
}
return JSON.parse(value);
return { key, value: JSON.parse(value) };
}
async set_with_ttl(key: string, value: any, ttl: number) {

View File

@@ -5,8 +5,7 @@ import { PrismaService } from './prisma.service';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(process.env.PORT ?? 3000);
await app.listen(process.env.PORT ?? 8001);
console.log(`🚀 Uygulama çalışıyor: ${await app.getUrl()}`);
extractAndPersistRoutes(app, app.get(PrismaService));
}

View File

@@ -9,8 +9,9 @@ export type UserType = (typeof UserType)[keyof typeof UserType];
// Credentials
export const CredentialsSchema = z.object({
person_id: z.number(),
person_uu_id: z.string(),
person_name: z.string(),
full_name: z.string(),
});
export type Credentials = z.infer<typeof CredentialsSchema>;
@@ -42,7 +43,7 @@ export const AuthTokenSchema = z.object({
active: z.boolean(),
is_notification_send: z.boolean(),
is_email_send: z.boolean(),
id: z.number(),
// id: z.number(),
uu_id: z.string(),
expiry_starts: z.date(),
expiry_ends: z.date(),
@@ -77,7 +78,7 @@ export const AuthTokenSchema = z.object({
active: z.boolean(),
is_notification_send: z.boolean(),
is_email_send: z.boolean(),
id: z.number(),
// id: z.number(),
uu_id: z.string(),
expiry_starts: z.date(),
expiry_ends: z.date(),
@@ -86,14 +87,23 @@ export const AuthTokenSchema = z.object({
default_language: z.string(),
}),
credentials: CredentialsSchema,
selectionList: z.array(z.any()).optional().default([]),
selectionList: z
.object({
type: z.string(),
list: z.array(z.any()).optional().default([]),
})
.optional()
.default({
type: '',
list: [],
}),
});
export type AuthToken = z.infer<typeof AuthTokenSchema>;
export const EmployeeTokenSchema = z.object({
company: z.object({
id: z.number(),
// id: z.number(),
uu_id: z.string(),
formal_name: z.string(),
company_type: z.string(),
@@ -107,13 +117,13 @@ export const EmployeeTokenSchema = z.object({
is_blacklist: z.boolean(),
parent_id: z.number().nullable(),
workplace_no: z.string().nullable(),
official_address_id: z.number().nullable(),
// official_address_id: z.number().nullable(),
official_address_uu_id: z.string().nullable(),
top_responsible_company_id: z.number().nullable(),
top_responsible_company_uu_id: z.string().nullable(),
ref_id: z.string().nullable(),
replication_id: z.number(),
cryp_uu_id: z.string().nullable(),
// replication_id: z.number(),
// cryp_uu_id: z.string().nullable(),
created_credentials_token: z.string().nullable(),
updated_credentials_token: z.string().nullable(),
confirmed_credentials_token: z.string().nullable(),
@@ -129,17 +139,17 @@ export const EmployeeTokenSchema = z.object({
ref_int: z.number().nullable(),
}),
department: z.object({
id: z.number(),
// id: z.number(),
uu_id: z.string(),
parent_department_id: z.number().nullable(),
department_code: z.string(),
department_name: z.string(),
department_description: z.string(),
company_id: z.number(),
// company_id: z.number(),
company_uu_id: z.string(),
ref_id: z.string().nullable(),
replication_id: z.number(),
cryp_uu_id: z.string().nullable(),
// ref_id: z.string().nullable(),
// replication_id: z.number(),
// cryp_uu_id: z.string().nullable(),
created_credentials_token: z.string().nullable(),
updated_credentials_token: z.string().nullable(),
confirmed_credentials_token: z.string().nullable(),
@@ -155,14 +165,14 @@ export const EmployeeTokenSchema = z.object({
ref_int: z.number().nullable(),
}),
duty: z.object({
id: z.number(),
// id: z.number(),
uu_id: z.string(),
duty_name: z.string(),
duty_code: z.string(),
duty_description: z.string(),
ref_id: z.string().nullable(),
replication_id: z.number(),
cryp_uu_id: z.string().nullable(),
// ref_id: z.string().nullable(),
// replication_id: z.number(),
// cryp_uu_id: z.string().nullable(),
created_credentials_token: z.string().nullable(),
updated_credentials_token: z.string().nullable(),
confirmed_credentials_token: z.string().nullable(),
@@ -178,15 +188,15 @@ export const EmployeeTokenSchema = z.object({
ref_int: z.number().nullable(),
}),
employee: z.object({
id: z.number(),
// id: z.number(),
uu_id: z.string(),
staff_id: z.number(),
staff_uu_id: z.string(),
people_id: z.number(),
people_uu_id: z.string(),
ref_id: z.string().nullable(),
replication_id: z.number(),
cryp_uu_id: z.string().nullable(),
// ref_id: z.string().nullable(),
// replication_id: z.number(),
// cryp_uu_id: z.string().nullable(),
created_credentials_token: z.string().nullable(),
updated_credentials_token: z.string().nullable(),
confirmed_credentials_token: z.string().nullable(),
@@ -202,17 +212,17 @@ export const EmployeeTokenSchema = z.object({
ref_int: z.number().nullable(),
}),
staff: z.object({
id: z.number(),
// id: z.number(),
uu_id: z.string(),
staff_description: z.string(),
staff_name: z.string(),
staff_code: z.string(),
duties_id: z.number(),
// duties_id: z.number(),
duties_uu_id: z.string(),
function_retriever: z.string().nullable(),
ref_id: z.string().nullable(),
replication_id: z.number(),
cryp_uu_id: z.string().nullable(),
// ref_id: z.string().nullable(),
// replication_id: z.number(),
// cryp_uu_id: z.string().nullable(),
created_credentials_token: z.string().nullable(),
updated_credentials_token: z.string().nullable(),
confirmed_credentials_token: z.string().nullable(),
@@ -230,8 +240,6 @@ export const EmployeeTokenSchema = z.object({
menu: z.array(z.object({})).nullable(),
pages: z.array(z.string()).nullable(),
// config: z.record(z.string(), z.unknown()).nullable(),
// caches: z.record(z.string(), z.unknown()).nullable(),
selection: z.record(z.string(), z.unknown()).nullable(),
functionsRetriever: z.string(),
@@ -247,8 +255,6 @@ export const OccupantTokenSchema = z.object({
menu: z.array(z.object({})).nullable(),
pages: z.array(z.string()).nullable(),
// config: z.record(z.string(), z.unknown()).nullable(),
// caches: z.record(z.string(), z.unknown()).nullable(),
selection: z.record(z.string(), z.unknown()).nullable(),
functionsRetriever: z.string(),

View File

@@ -71,6 +71,18 @@ export class RedisHandlers {
return mergedRedisKey;
}
public mergeLoginUser(userUUID: string) {
const mergedRedisKey = `${this.AUTH_TOKEN}:*:*:${userUUID}:${userUUID}`;
this.validateRedisKey(mergedRedisKey, this.AUTH_TOKEN);
return mergedRedisKey;
}
public mergeSelectUser(userUUID: string, livingUUID: string) {
const mergedRedisKey = `${this.SELECT_TOKEN}:*:*:${userUUID}:${livingUUID}`;
this.validateRedisKey(mergedRedisKey, this.SELECT_TOKEN);
return mergedRedisKey;
}
generateSelectToken(accessToken: string, userUUID: string) {
return this.passwordService.createSelectToken(accessToken, userUUID);
}
@@ -154,6 +166,53 @@ export class RedisHandlers {
: null;
}
async callExistingLoginToken(userUUID: string): Promise<string | null> {
const mergedKey = this.mergeLoginUser(userUUID);
if (!mergedKey.includes('*')) {
throw new ForbiddenException(
'Authorization failed - No valid select keys',
);
}
const keys = await this.scanKeys(mergedKey, this.AUTH_TOKEN);
if (keys.length === 0) {
return null;
}
for (const key of keys) {
const value = await this.cacheService.get(key);
if (value) {
this.cacheService.set_with_ttl(value.key, value.value, 60 * 30);
const token = value.key.split(':')[1];
return token;
}
}
throw new ForbiddenException('Authorization failed - No valid login keys');
}
async callExistingSelectToken(
userUUID: string,
uuid: string,
): Promise<string | null> {
const mergedKey = this.mergeSelectUser(userUUID, uuid);
if (!mergedKey.includes('*')) {
throw new ForbiddenException(
'Authorization failed - No valid select keys',
);
}
const keys = await this.scanKeys(mergedKey, this.SELECT_TOKEN);
if (keys.length === 0) {
return null;
}
for (const key of keys) {
const value = await this.cacheService.get(key);
if (value) {
this.cacheService.set_with_ttl(value.key, value.value, 60 * 30);
const token = value.key.split(':')[2];
return token;
}
}
throw new ForbiddenException('Authorization failed - No valid select keys');
}
async deleteLoginFromRedis(req: Request): Promise<any> {
const mergedKey = this.mergeLoginKey(req);
return this.cacheService.delete(mergedKey);