rez_core 2.1.10 → 2.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/dtos/response.d.ts +5 -0
  2. package/dist/dtos/response.js +3 -0
  3. package/dist/dtos/response.js.map +1 -0
  4. package/dist/module/filter/controller/filter.controller.js +1 -0
  5. package/dist/module/filter/controller/filter.controller.js.map +1 -1
  6. package/dist/module/filter/dto/filter-request.dto.d.ts +1 -0
  7. package/dist/module/filter/service/filter.service.js +8 -2
  8. package/dist/module/filter/service/filter.service.js.map +1 -1
  9. package/dist/module/listmaster/controller/list-master.controller.d.ts +1 -1
  10. package/dist/module/listmaster/controller/list-master.controller.js +12 -4
  11. package/dist/module/listmaster/controller/list-master.controller.js.map +1 -1
  12. package/dist/module/listmaster/service/list-master.service.d.ts +2 -1
  13. package/dist/module/listmaster/service/list-master.service.js +53 -13
  14. package/dist/module/listmaster/service/list-master.service.js.map +1 -1
  15. package/dist/module/meta/controller/entity.controller.d.ts +2 -2
  16. package/dist/module/meta/controller/entity.controller.js +71 -20
  17. package/dist/module/meta/controller/entity.controller.js.map +1 -1
  18. package/dist/module/module/controller/module-access.controller.js +3 -4
  19. package/dist/module/module/controller/module-access.controller.js.map +1 -1
  20. package/dist/module/module/repository/module-access.repository.d.ts +6 -5
  21. package/dist/module/module/repository/module-access.repository.js +22 -18
  22. package/dist/module/module/repository/module-access.repository.js.map +1 -1
  23. package/dist/module/module/service/module-access.service.d.ts +8 -8
  24. package/dist/module/module/service/module-access.service.js +7 -7
  25. package/dist/module/module/service/module-access.service.js.map +1 -1
  26. package/dist/module/user/service/role.service.d.ts +3 -2
  27. package/dist/module/user/service/role.service.js +22 -10
  28. package/dist/module/user/service/role.service.js.map +1 -1
  29. package/dist/tsconfig.build.tsbuildinfo +1 -1
  30. package/package.json +1 -1
  31. package/src/dtos/response.ts +5 -0
  32. package/src/module/filter/controller/filter.controller.ts +1 -0
  33. package/src/module/filter/dto/filter-request.dto.ts +1 -0
  34. package/src/module/filter/service/filter.service.ts +9 -1
  35. package/src/module/listmaster/controller/list-master.controller.ts +32 -3
  36. package/src/module/listmaster/service/list-master.service.ts +93 -13
  37. package/src/module/meta/controller/entity.controller.ts +101 -47
  38. package/src/module/module/controller/module-access.controller.ts +3 -5
  39. package/src/module/module/repository/module-access.repository.ts +25 -16
  40. package/src/module/module/service/module-access.service.ts +12 -9
  41. package/src/module/user/service/role.service.ts +25 -14
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rez_core",
3
- "version": "2.1.10",
3
+ "version": "2.1.11",
4
4
  "description": "",
5
5
  "author": "",
6
6
  "private": false,
@@ -0,0 +1,5 @@
1
+ export interface ServiceResult<T = any> {
2
+ success: boolean;
3
+ data?: T;
4
+ error?: string;
5
+ }
@@ -52,6 +52,7 @@ export class FilterController {
52
52
  user_id: userData.id,
53
53
  level_type: userData.level_type,
54
54
  level_id: userData.level_id,
55
+ appcode: userData.appcode,
55
56
  });
56
57
  }
57
58
 
@@ -26,4 +26,5 @@ export interface FilterRequestDto {
26
26
  user_id?: string | null;
27
27
  level_type?: string | null;
28
28
  level_id?: string | null;
29
+ appcode?: string | null;
29
30
  }
@@ -18,7 +18,7 @@ export class FilterService {
18
18
  @Inject('SavedFilterService')
19
19
  private readonly savedFilterService: SavedFilterService,
20
20
  ) {}
21
- private readonly skipLevelFilterEntities = [];
21
+ private readonly skipLevelFilterEntities = ['ROL'];
22
22
 
23
23
  private async gettab_value_counts(
24
24
  tableName: string,
@@ -79,6 +79,7 @@ export class FilterService {
79
79
  user_id,
80
80
  level_type,
81
81
  level_id,
82
+ appcode,
82
83
  } = dto;
83
84
 
84
85
  // Fetch meta
@@ -138,6 +139,13 @@ export class FilterService {
138
139
  });
139
140
  }
140
141
 
142
+ if (this.skipLevelFilterEntities.includes(entity_type)) {
143
+ baseWhere.push({
144
+ query: 'e.appcode = :appcode',
145
+ params: { appcode },
146
+ });
147
+ }
148
+
141
149
  const layoutPreference = await this.dataSource.query(
142
150
  `SELECT layout_json
143
151
  FROM cr_layout_preference
@@ -1,13 +1,42 @@
1
- import { Body, Controller, HttpCode, HttpStatus, Param, Post, Query } from '@nestjs/common';
1
+ import {
2
+ Body,
3
+ Controller,
4
+ HttpCode,
5
+ HttpStatus,
6
+ Param,
7
+ Post,
8
+ Query,
9
+ Request,
10
+ UseGuards,
11
+ } from '@nestjs/common';
2
12
  import { ListMasterService } from '../service/list-master.service';
13
+ import { JwtAuthGuard } from 'src/module/auth/guards/jwt.guard';
3
14
 
4
15
  @Controller('list-master')
5
16
  export class ListMasterController {
6
17
  constructor(private readonly service: ListMasterService) {}
7
18
 
8
19
  @Post('/getDropdownData/:type')
20
+ @UseGuards(JwtAuthGuard)
9
21
  @HttpCode(HttpStatus.OK)
10
- async getDropdownData(@Param('type') type: string, @Body() params: Record<string, string>,) {
11
- return await this.service.getDropdownOptions(type, params);
22
+ async getDropdownData(
23
+ @Param('type') type: string,
24
+ @Query() queryParams: Record<string, string>,
25
+ @Request() req,
26
+ ) {
27
+ const { inactiveIds, ...params } = queryParams;
28
+
29
+ const inactiveIdsArray = inactiveIds
30
+ ? inactiveIds.split(',').map((id) => parseInt(id, 10))
31
+ : [];
32
+
33
+ const loggedInUser = req.user.userData;
34
+
35
+ return await this.service.getDropdownOptions(
36
+ type,
37
+ params,
38
+ inactiveIdsArray,
39
+ loggedInUser,
40
+ );
12
41
  }
13
42
  }
@@ -11,6 +11,8 @@ import { firstValueFrom } from 'rxjs';
11
11
  import { HttpService } from '@nestjs/axios';
12
12
  import { EntityManager } from 'typeorm';
13
13
  import { EntityMasterService } from 'src/module/meta/service/entity-master.service';
14
+ import { UserData } from 'src/module/user/entity/user.entity';
15
+ import { log } from 'console';
14
16
 
15
17
  @Injectable()
16
18
  export class ListMasterService {
@@ -23,14 +25,24 @@ export class ListMasterService {
23
25
  private readonly httpService: HttpService,
24
26
  ) {}
25
27
 
26
- async getDropdownOptions(type: string, params: Record<string, string>) {
28
+ async getDropdownOptions(
29
+ type: string,
30
+ params: Record<string, string>,
31
+ inactiveIdsArray?: number[],
32
+ loggedInUser?: UserData,
33
+ ) {
27
34
  const config = await this.listMasterRepo.findByType(type);
28
35
 
29
36
  if (!config) throw new NotFoundException(`Type ${type} not found`);
30
37
 
31
38
  switch (config.source) {
32
39
  case 'entity':
33
- return this.fetchFromEntity(type, params);
40
+ return this.fetchFromEntity(
41
+ type,
42
+ params,
43
+ inactiveIdsArray,
44
+ loggedInUser,
45
+ );
34
46
 
35
47
  case 'master':
36
48
  return this.listItemsRepo.findItemsByType(type);
@@ -46,25 +58,93 @@ export class ListMasterService {
46
58
  private async fetchFromEntity(
47
59
  sourceList: string,
48
60
  params: Record<string, any>,
61
+ inactiveIdsArray?: number[],
62
+ loggedInUser?: UserData,
49
63
  ) {
50
64
  let result: { label: string; value: number }[] = [];
51
- const argsObjectList: any[] = [];
52
- if (sourceList) {
53
- const entityMaster =
54
- await this.entityMasterService.getEntityData(sourceList);
65
+ if (!sourceList) return result;
66
+
67
+ const entityMaster =
68
+ await this.entityMasterService.getEntityData(sourceList);
69
+ const tableName = entityMaster.db_table_name;
70
+
71
+ const applyCommonFilters = (
72
+ qb: any,
73
+ status: 'active' | null = 'active',
74
+ ) => {
75
+ if (status) {
76
+ qb.where(`${tableName}.status = :status`, { status });
77
+ }
55
78
 
56
- let query = `SELECT * FROM ${entityMaster.db_table_name} WHERE 1=1 `;
79
+ if (loggedInUser?.level_type && loggedInUser?.level_id) {
80
+ qb.andWhere(
81
+ `${tableName}.level_type = :levelType AND ${tableName}.level_id = :levelId`,
82
+ {
83
+ levelType: loggedInUser.level_type,
84
+ levelId: loggedInUser.level_id,
85
+ },
86
+ );
87
+ }
57
88
 
58
- for (let paramsKey in params) {
59
- query += `AND ${paramsKey} = ? `;
60
- argsObjectList.push(params[paramsKey]);
89
+ if (loggedInUser?.appcode && sourceList === 'ROL') {
90
+ qb.andWhere(`${tableName}.appcode = :appcode`, {
91
+ appcode: loggedInUser.appcode,
92
+ });
61
93
  }
62
94
 
63
- let results = await this.entityManager.query(query, argsObjectList);
64
- results.forEach((r) => {
65
- result.push({ label: r['name'], value: r['id'] });
95
+ for (const key in params) {
96
+ qb.andWhere(`${tableName}.${key} = :${key}`, {
97
+ [key]: params[key],
98
+ });
99
+ }
100
+
101
+ return qb;
102
+ };
103
+
104
+ // Fetch active records
105
+ const activeQuery = applyCommonFilters(
106
+ this.entityManager
107
+ .createQueryBuilder()
108
+ .select('*')
109
+ .from(tableName, tableName),
110
+ 'active',
111
+ );
112
+
113
+ const activeResults = await activeQuery.getRawMany();
114
+ const activeIds = new Set(activeResults.map((r) => r.id));
115
+
116
+ // Add active entries first
117
+ activeResults.forEach((r) => {
118
+ result.push({ label: r.name, value: r.id });
119
+ });
120
+
121
+ // Fetch inactive records (with same filters but without status condition)
122
+ if (inactiveIdsArray?.length) {
123
+ const inactiveQuery = applyCommonFilters(
124
+ this.entityManager
125
+ .createQueryBuilder()
126
+ .select('*')
127
+ .from(tableName, tableName),
128
+ null, // exclude status filter
129
+ );
130
+
131
+ inactiveQuery.andWhere(`${tableName}.id IN (:...ids)`, {
132
+ ids: inactiveIdsArray,
133
+ });
134
+
135
+ const inactiveResults = await inactiveQuery.getRawMany();
136
+
137
+ // Add only those inactive records not already present in active
138
+ inactiveResults.forEach((item) => {
139
+ if (!activeIds.has(item.id)) {
140
+ result.push({
141
+ label: `${item.name}_INACTIVE`,
142
+ value: item.id,
143
+ });
144
+ }
66
145
  });
67
146
  }
147
+
68
148
  return result;
69
149
  }
70
150
 
@@ -67,37 +67,61 @@ export class EntityController {
67
67
  @Res() res: Response,
68
68
  @Req() req: Request & { user: any },
69
69
  ) {
70
- let requestedUser = req.user.userData;
71
- let appcode = requestedUser.appcode;
72
- // let loggedInUser = await this.entityService.getEntityData(
73
- // ENTITYTYPE_USER,
74
- // requestedUser.userId,
75
- // );
76
- let loggedInUser = req.user.userData;
77
- if (!entityType) {
78
- throw new BadRequestException(
79
- 'Query parameter "entity_type" is required',
80
- );
81
- }
82
- const entityMaster =
83
- await this.entityMasterService.getEntityData(entityType);
70
+ try {
71
+ let requestedUser = req.user.userData;
72
+ let appcode = requestedUser.appcode;
73
+ // let loggedInUser = await this.entityService.getEntityData(
74
+ // ENTITYTYPE_USER,
75
+ // requestedUser.userId,
76
+ // );
77
+ let loggedInUser = req.user.userData;
78
+ if (!entityType) {
79
+ // throw new BadRequestException(
80
+ // 'Query parameter "entity_type" is required',
81
+ // );
84
82
 
85
- const entityService =
86
- await this.reflectionHelper.getBean<EntityServiceImpl>(
87
- entityMaster.entity_service,
88
- );
83
+ return res.status(HttpStatus.BAD_REQUEST).json({
84
+ success: false,
85
+ error: 'Query parameter "entity_type" is required',
86
+ });
87
+ }
88
+ const entityMaster =
89
+ await this.entityMasterService.getEntityData(entityType);
89
90
 
90
- if (entityService) {
91
- return res
92
- .status(HttpStatus.OK)
93
- .json(
94
- await entityService.createEntity(
95
- entityData,
96
- loggedInUser as UserData,
97
- null,
98
- appcode,
99
- ),
91
+ const entityService =
92
+ await this.reflectionHelper.getBean<EntityServiceImpl>(
93
+ entityMaster.entity_service,
100
94
  );
95
+
96
+ if (!entityService) {
97
+ return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({
98
+ success: false,
99
+ error: `No service found for entity_type "${entityType}"`,
100
+ });
101
+ }
102
+ const createdEntity = await entityService.createEntity(
103
+ entityData,
104
+ loggedInUser as UserData,
105
+ null,
106
+ appcode,
107
+ );
108
+
109
+ if (!createdEntity || !createdEntity.success) {
110
+ return res.status(HttpStatus.BAD_REQUEST).json({
111
+ success: false,
112
+ error: createdEntity?.error || 'Entity creation failed',
113
+ });
114
+ }
115
+
116
+ return res.status(HttpStatus.OK).json({
117
+ success: true,
118
+ data: createdEntity.data,
119
+ });
120
+ } catch (error) {
121
+ return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({
122
+ success: false,
123
+ error: error.message || 'Internal server error',
124
+ });
101
125
  }
102
126
  }
103
127
 
@@ -109,34 +133,64 @@ export class EntityController {
109
133
  @Res() response: Response,
110
134
  @Req() req: Request & { user: any },
111
135
  ) {
112
- let loggedInUser = req.user.userData;
113
- // let loggedInUser = await this.entityService.getEntityData(
114
- // ENTITYTYPE_USER,
115
- // requestedUser.userId,
116
- // );
117
- if (!entityType) {
118
- throw new BadRequestException(
119
- 'Query parameter "entity_type" is required',
120
- );
121
- }
136
+ try {
137
+ let loggedInUser = req.user.userData;
138
+ // let loggedInUser = await this.entityService.getEntityData(
139
+ // ENTITYTYPE_USER,
140
+ // requestedUser.userId,
141
+ // );
142
+ if (!entityType) {
143
+ return response.status(HttpStatus.BAD_REQUEST).json({
144
+ success: false,
145
+ error: 'Query parameter "entity_type" is required',
146
+ });
147
+ }
148
+
149
+ const entityMaster =
150
+ await this.entityMasterService.getEntityData(entityType);
151
+
152
+ const entityService =
153
+ await this.reflectionHelper.getBean<EntityServiceImpl>(
154
+ entityMaster.entity_service,
155
+ );
156
+
157
+ if (!entityService) {
158
+ return response.status(HttpStatus.INTERNAL_SERVER_ERROR).json({
159
+ success: false,
160
+ error: `No service found for entity_type "${entityType}"`,
161
+ });
162
+ }
122
163
 
123
- const entityMaster =
124
- await this.entityMasterService.getEntityData(entityType);
125
- const entityService =
126
- await this.reflectionHelper.getBean<EntityServiceImpl>(
127
- entityMaster.entity_service,
128
- );
129
- if (entityService) {
130
164
  let existingEntity = await entityService.getEntityData(entityType, id);
131
165
  if (!existingEntity) {
166
+ return response.status(HttpStatus.NOT_FOUND).json({
167
+ success: false,
168
+ error: `No entity found for id "${id}"`,
169
+ });
132
170
  }
133
171
  // let mergeEntity = JsonUtil.merge(existingEntity, entityData);
134
172
 
135
- entityData = await entityService.updateEntity(
173
+ const updatedEntity = await entityService.updateEntity(
136
174
  entityData,
137
175
  loggedInUser as UserData,
138
176
  );
139
- return response.status(HttpStatus.OK).json(entityData);
177
+
178
+ if (!updatedEntity || !updatedEntity.success) {
179
+ return response.status(HttpStatus.BAD_REQUEST).json({
180
+ success: false,
181
+ error: updatedEntity?.error || 'Entity creation failed',
182
+ });
183
+ }
184
+
185
+ return response.status(HttpStatus.OK).json({
186
+ success: true,
187
+ data: updatedEntity.data,
188
+ });
189
+ } catch (error) {
190
+ return response.status(HttpStatus.INTERNAL_SERVER_ERROR).json({
191
+ success: false,
192
+ error: error.message || 'Internal server error',
193
+ });
140
194
  }
141
195
  }
142
196
 
@@ -45,8 +45,8 @@ export class ModuleAccessController {
45
45
  ) {
46
46
  const roleIdArray = roleIds.split(',').map(Number);
47
47
  const appcode = queryAppcode || req.user.userData.appcode;
48
- const level_type = req.user.userData.appcode;
49
- const level_id = req.user.userData.appcode;
48
+ const level_type = req.user.userData.level_type;
49
+ const level_id = req.user.userData.level_id;
50
50
  return this.moduleAccessService.getAccessListing(roleIdArray, appcode, level_type, level_id);
51
51
  }
52
52
 
@@ -68,10 +68,8 @@ export class ModuleAccessController {
68
68
 
69
69
  @Post('update')
70
70
  async updateModuleAccess(@Body() moduleAccessData: any[], @Request() req) {
71
- const appcode = req.user.userData.appcode;
72
71
  return this.moduleAccessService.updateModuleAccess(
73
- moduleAccessData,
74
- appcode,
72
+ moduleAccessData
75
73
  );
76
74
  }
77
75
 
@@ -1,6 +1,6 @@
1
1
  import { BadRequestException, Injectable } from '@nestjs/common';
2
2
  import { InjectRepository } from '@nestjs/typeorm';
3
- import { In, Not, Repository , IsNull} from 'typeorm';
3
+ import { In, Not, Repository , IsNull, DataSource} from 'typeorm';
4
4
  import { Role } from 'src/module/user/entity/role.entity';
5
5
  import { ModuleAccess } from '../entity/module-access.entity';
6
6
  import { ModuleAction } from '../entity/module-action.entity';
@@ -21,10 +21,13 @@ export class ModuleAccessRepository {
21
21
 
22
22
  @InjectRepository(ModuleAction)
23
23
  private readonly moduleActionRepo: Repository<ModuleAction>,
24
+
25
+ private readonly dataSource: DataSource,
24
26
  ) {}
25
27
 
26
28
 
27
29
 
30
+
28
31
  async getRoles({
29
32
  appcode,
30
33
  level_type,
@@ -34,15 +37,23 @@ export class ModuleAccessRepository {
34
37
  level_type?: string;
35
38
  level_id?: number;
36
39
  }) {
37
- const where: any = {
38
- appcode,
39
- is_factory: [Not(1), IsNull()], // Include NULL or anything not 1
40
- };
40
+ const query = this.dataSource
41
+ .createQueryBuilder()
42
+ .select('*')
43
+ .from('cr_role', 'role')
44
+ .where('role.appcode = :appcode', { appcode })
45
+ .andWhere('(role.is_factory IS NULL OR role.is_factory != 1)');
46
+
47
+
48
+ if (level_type) {
49
+ query.andWhere('role.level_type = :level_type', { level_type });
50
+ }
41
51
 
42
- if (level_type) where.level_type = level_type;
43
- if (level_id !== undefined) where.level_id = String(level_id);
52
+ if (level_id !== undefined) {
53
+ query.andWhere('role.level_id = :level_id', { level_id: String(level_id) });
54
+ }
44
55
 
45
- const roles = await this.roleRepo.find({ where });
56
+ const roles = await query.getRawMany(); // use getRawMany since you're selecting from a raw table
46
57
 
47
58
  return roles.map((role) => ({
48
59
  label: role.name,
@@ -50,6 +61,7 @@ export class ModuleAccessRepository {
50
61
  }));
51
62
  }
52
63
 
64
+
53
65
 
54
66
  async getModules({ appcode }: { appcode: string }) {
55
67
  const modules = await this.moduleRepo.find({
@@ -73,7 +85,7 @@ export class ModuleAccessRepository {
73
85
  ) {
74
86
  let roles;
75
87
 
76
- if (roleIds.length === 1 && roleIds[0] === '-1') {
88
+ if (roleIds.length === 1 && roleIds[0] === -1) {
77
89
  // Internally call getRoles
78
90
  const fetchedRoles = await this.getRoles({
79
91
  appcode,
@@ -118,6 +130,7 @@ export class ModuleAccessRepository {
118
130
  access: access.access_flag,
119
131
  code: access.module_code,
120
132
  level_type: access.level_type,
133
+ appcode: access.appcode,
121
134
  })),
122
135
  }));
123
136
  }
@@ -214,8 +227,7 @@ export class ModuleAccessRepository {
214
227
  access_flag: number;
215
228
  appcode: string;
216
229
  level_type: string;
217
- }[],
218
- appcode: string,
230
+ }[]
219
231
  ): Promise<boolean> {
220
232
  try {
221
233
  for (const access of accessList) {
@@ -224,7 +236,7 @@ export class ModuleAccessRepository {
224
236
  role_code: access.role_code,
225
237
  module_code: access.module_code,
226
238
  action_type: access.action_type,
227
- appcode: access.appcode || appcode,
239
+ appcode: access.appcode,
228
240
  level_type: access.level_type,
229
241
  },
230
242
  });
@@ -234,10 +246,7 @@ export class ModuleAccessRepository {
234
246
  await this.moduleAccessRepo.save(existing);
235
247
  } else {
236
248
  await this.moduleAccessRepo.save(
237
- this.moduleAccessRepo.create({
238
- ...access,
239
- appcode: access.appcode || appcode,
240
- }),
249
+ await this.moduleAccessRepo.create(access),
241
250
  );
242
251
  }
243
252
  }
@@ -15,6 +15,15 @@ export class ModuleAccessService {
15
15
  private readonly menuRepository: MenuRepository,
16
16
  private readonly dataSource: DataSource,
17
17
  ) {}
18
+
19
+
20
+ async getModules({ appcode }: { appcode: string }) {
21
+ return this.moduleAccessRepository.getModules({
22
+ appcode,
23
+ });
24
+ }
25
+
26
+
18
27
  async getRoles({
19
28
  appcode,
20
29
  level_type,
@@ -48,13 +57,6 @@ export class ModuleAccessService {
48
57
  }));
49
58
  }
50
59
 
51
-
52
-
53
- async getModules({ appcode }: { appcode: string }) {
54
- return this.moduleAccessRepository.getModules({
55
- appcode,
56
- });
57
- }
58
60
  async getAccessListing(
59
61
  roleIds: number[],
60
62
  appcode: string,
@@ -94,7 +96,6 @@ export class ModuleAccessService {
94
96
  appcode: string;
95
97
  level_type: string;
96
98
  }[],
97
- appcode: string,
98
99
  ): Promise<{ success: boolean; msg: string }> {
99
100
  if (!moduleAccessData || moduleAccessData.length === 0) {
100
101
  return { success: false, msg: 'No data provided' };
@@ -102,7 +103,6 @@ export class ModuleAccessService {
102
103
 
103
104
  const result = await this.moduleAccessRepository.updateModuleAccess(
104
105
  moduleAccessData,
105
- appcode,
106
106
  );
107
107
 
108
108
  if (result) {
@@ -118,3 +118,6 @@ export class ModuleAccessService {
118
118
  return this.moduleAccessRepository.getModuleUIConfig(moduleCode, roleIds);
119
119
  }
120
120
  }
121
+
122
+
123
+ //hi