Repository: ever-co/ever-gauzy
Pull request #8949: Apps
Author: login: rahul-rocket
Created: 2025-05-13T07:24:24Z | Merged: 2025-05-13T07:24:34Z

# PR

- [x] Have you followed the [contributing guidelines](https://github.com/ever-co/ever-gauzy/blob/master/.github/CONTRIBUTING.md)?
- [x] Have you explained what your changes do, and why they add value?

**Please note: we will close your PR without comment if you do not check the boxes above and provide ALL requested information.**

---



Diff:
diff --git a/apps/agent/src/app/app.component.ts b/apps/agent/src/app/app.component.ts
index 80baf764176..c7063d660e9 100644
--- a/apps/agent/src/app/app.component.ts
+++ b/apps/agent/src/app/app.component.ts
@@ -17,7 +17,8 @@ import { AppService } from './app.service';
 @Component({
 	selector: 'agent-root',
 	template: '<router-outlet></router-outlet>',
-	styleUrls: ['./app.component.scss']
+	styleUrls: ['./app.component.scss'],
+	standalone: false
 })
 export class AppComponent implements OnInit, AfterViewInit {
 	constructor(
diff --git a/apps/gauzy/src/app/app.module.ts b/apps/gauzy/src/app/app.module.ts
index ef68f157fb9..f8ab802056e 100644
--- a/apps/gauzy/src/app/app.module.ts
+++ b/apps/gauzy/src/app/app.module.ts
@@ -216,7 +216,6 @@ const FEATURE_MODULES = [
 			provide: GAUZY_ENV,
 			useValue: environment
 		},
-
 		provideHttpClient(withInterceptorsFromDi())
 	]
 })
diff --git a/packages/core/src/lib/bootstrap/index.ts b/packages/core/src/lib/bootstrap/index.ts
index 91369684546..bd253082299 100644
--- a/packages/core/src/lib/bootstrap/index.ts
+++ b/packages/core/src/lib/bootstrap/index.ts
@@ -77,6 +77,9 @@ export async function bootstrap(pluginConfig?: Partial<ApplicationPluginConfig>)
 	});
 	console.timeEnd(chalk.yellow('✔ Create NestJS Application Time'));
 
+	// Set query parser to extended (In Express v5, query parameters are no longer parsed using the qs library by default.)
+	app.set('query parser', 'extended');
+
 	// Register custom entity fields for Mikro ORM
 	await registerMikroOrmCustomFields(config);
 
diff --git a/packages/core/src/lib/core/crud/pagination.helper.ts b/packages/core/src/lib/core/crud/pagination.helper.ts
index c07cd50bdb9..0943621beea 100644
--- a/packages/core/src/lib/core/crud/pagination.helper.ts
+++ b/packages/core/src/lib/core/crud/pagination.helper.ts
@@ -43,22 +43,28 @@ export const convertNativeParameters = (parameters: any): any => {
 };
 
 /**
- * Parse object to specific type
+ * Recursively parses an object, applying a callback to all non-object leaf values.
  *
- * @param source
- * @returns
+ * @param source - The object to parse.
+ * @param callback - The function to apply to each primitive value.
+ * @returns The modified object.
  */
-export function parseObject(source: Object, callback: Function) {
-	if (isObject(source)) {
-		for (const key in source) {
-			if (isObject(source[key])) {
-				if (!isClassInstance(source[key])) {
-					parseObject(source[key], callback);
-				}
-			} else {
-				Object.assign(source, { [key]: callback(source[key]) });
+export function parseObject<T extends object>(source: T, callback: (value: any) => any): T {
+	if (!isObject(source)) {
+		return source;
+	}
+
+	for (const key of Object.keys(source)) {
+		const value = source[key];
+
+		if (isObject(value)) {
+			if (!isClassInstance(value)) {
+				source[key] = parseObject(value, callback);
 			}
+		} else {
+			source[key] = callback(value);
 		}
 	}
+
 	return source;
 }
diff --git a/packages/core/src/lib/organization/dto/organization-find-options.dto.ts b/packages/core/src/lib/organization/dto/organization-find-options.dto.ts
index 93aa71f7e6d..7d985c66cf1 100644
--- a/packages/core/src/lib/organization/dto/organization-find-options.dto.ts
+++ b/packages/core/src/lib/organization/dto/organization-find-options.dto.ts
@@ -1,3 +1,3 @@
-import { OptionsRelations } from "../../core/crud";
+import { OptionsRelations } from '../../core/crud';
 
-export class OrganizationFindOptionsDTO<T = any> extends OptionsRelations<T> {}
\ No newline at end of file
+export class OrganizationFindOptionsDTO<T = any> extends OptionsRelations<T> {}
diff --git a/packages/core/src/lib/shared/dto/relations-query.dto.ts b/packages/core/src/lib/shared/dto/relations-query.dto.ts
index fec66747dbf..647f1a9a027 100644
--- a/packages/core/src/lib/shared/dto/relations-query.dto.ts
+++ b/packages/core/src/lib/shared/dto/relations-query.dto.ts
@@ -4,12 +4,22 @@ import { IsArray, IsOptional } from 'class-validator';
 import { IBaseRelationsEntityModel } from '@gauzy/contracts';
 
 /**
- * Get relations request DTO validation
+ * Validates and transforms 'relations' query parameter.
  */
 export class RelationsQueryDTO implements IBaseRelationsEntityModel {
-	@ApiPropertyOptional({ type: () => Array, isArray: true })
+	@ApiPropertyOptional({ type: () => [String], isArray: true })
 	@IsOptional()
 	@IsArray()
-	@Transform(({ value }: TransformFnParams) => (value ? value.map((element: string) => element.trim()) : []))
+	@Transform(({ value }: TransformFnParams) => {
+		if (typeof value === 'string') {
+			// If it's a comma-separated string, split into array
+			return value.split(',').map((item) => item.trim());
+		}
+		if (Array.isArray(value)) {
+			// If it's already an array, trim all elements
+			return value.map((item: string) => item.trim());
+		}
+		return [];
+	})
 	readonly relations: string[] = [];
 }
diff --git a/packages/core/src/lib/user/dto/find-me-query.dto.ts b/packages/core/src/lib/user/dto/find-me-query.dto.ts
index fda31c551a3..e7dce4a9548 100644
--- a/packages/core/src/lib/user/dto/find-me-query.dto.ts
+++ b/packages/core/src/lib/user/dto/find-me-query.dto.ts
@@ -16,7 +16,7 @@ export class FindMeQueryDTO extends RelationsQueryDTO implements IFindMeUser {
 	 */
 	@ApiPropertyOptional({ type: Boolean })
 	@IsOptional()
-	@Transform(({ value }: TransformFnParams) => (value ? parseToBoolean(value) : false))
+	@Transform(({ value }: TransformFnParams) => parseToBoolean(value))
 	readonly includeEmployee: boolean;
 
 	/**
@@ -26,6 +26,6 @@ export class FindMeQueryDTO extends RelationsQueryDTO implements IFindMeUser {
 	 */
 	@ApiPropertyOptional({ type: Boolean })
 	@IsOptional()
-	@Transform(({ value }: TransformFnParams) => (value ? parseToBoolean(value) : false))
+	@Transform(({ value }: TransformFnParams) => parseToBoolean(value))
 	readonly includeOrganization: boolean;
 }
diff --git a/packages/core/src/lib/user/dto/update-preferred-component-layout.dto.ts b/packages/core/src/lib/user/dto/update-preferred-component-layout.dto.ts
index 3802dbb8720..4abbf90e474 100644
--- a/packages/core/src/lib/user/dto/update-preferred-component-layout.dto.ts
+++ b/packages/core/src/lib/user/dto/update-preferred-component-layout.dto.ts
@@ -1,11 +1,10 @@
-import { ApiProperty } from "@nestjs/swagger";
-import { IsEnum, IsNotEmpty } from "class-validator";
-import { ComponentLayoutStyleEnum, IUserUpdateInput } from "@gauzy/contracts";
+import { ApiProperty } from '@nestjs/swagger';
+import { IsEnum, IsNotEmpty } from 'class-validator';
+import { ComponentLayoutStyleEnum, IUserUpdateInput } from '@gauzy/contracts';
 
 export class UpdatePreferredComponentLayoutDTO implements IUserUpdateInput {
-
 	@ApiProperty({ type: () => String, enum: ComponentLayoutStyleEnum })
 	@IsNotEmpty()
-    @IsEnum(ComponentLayoutStyleEnum)
-    readonly preferredComponentLayout: ComponentLayoutStyleEnum;
-}
\ No newline at end of file
+	@IsEnum(ComponentLayoutStyleEnum)
+	readonly preferredComponentLayout: ComponentLayoutStyleEnum;
+}
diff --git a/packages/core/src/lib/user/dto/update-preferred-language.dto.ts b/packages/core/src/lib/user/dto/update-preferred-language.dto.ts
index d9d4f4dfa19..e0b5df3c00e 100644
--- a/packages/core/src/lib/user/dto/update-preferred-language.dto.ts
+++ b/packages/core/src/lib/user/dto/update-preferred-language.dto.ts
@@ -1,11 +1,10 @@
-import { ApiProperty } from "@nestjs/swagger";
-import { IsNotEmpty, IsEnum } from "class-validator";
-import { IUserUpdateInput, LanguagesEnum } from "@gauzy/contracts";
+import { ApiProperty } from '@nestjs/swagger';
+import { IsNotEmpty, IsEnum } from 'class-validator';
+import { IUserUpdateInput, LanguagesEnum } from '@gauzy/contracts';
 
 export class UpdatePreferredLanguageDTO implements IUserUpdateInput {
-
 	@ApiProperty({ type: () => String, enum: LanguagesEnum })
 	@IsNotEmpty()
-    @IsEnum(LanguagesEnum)
-    readonly preferredLanguage: LanguagesEnum;
-}
\ No newline at end of file
+	@IsEnum(LanguagesEnum)
+	readonly preferredLanguage: LanguagesEnum;
+}
diff --git a/packages/core/src/lib/user/user.controller.ts b/packages/core/src/lib/user/user.controller.ts
index 428dcbd8c08..14c2b63c5e9 100644
--- a/packages/core/src/lib/user/user.controller.ts
+++ b/packages/core/src/lib/user/user.controller.ts
@@ -1,7 +1,6 @@
 // Modified code from https://github.com/xmlking/ngx-starter-kit.
 // MIT License, see https://github.com/xmlking/ngx-starter-kit/blob/develop/LICENSE
 // Copyright (c) 2018 Sumanth Chinthagunta
-
 import {
 	Controller,
 	Get,
@@ -57,7 +56,7 @@ export class UserController extends CrudController<User> {
 	@ApiResponse({ status: HttpStatus.OK, description: 'Found current user', type: User })
 	@ApiResponse({ status: HttpStatus.NOT_FOUND, description: 'Record not found' })
 	@Get('/me')
-	@UseValidationPipe({ whitelist: true })
+	@UseValidationPipe({ whitelist: true, transform: true })
 	async findMe(@Query() options: FindMeQueryDTO): Promise<IUser> {
 		return await this._userService.findMeUser(options);
 	}
diff --git a/packages/core/src/lib/user/user.service.ts b/packages/core/src/lib/user/user.service.ts
index f434e505482..0674c9b07af 100644
--- a/packages/core/src/lib/user/user.service.ts
+++ b/packages/core/src/lib/user/user.service.ts
@@ -124,13 +124,11 @@ export class UserService extends TenantAwareCrudService<User> {
 
 		// Fetch the user along with requested relations (excluding employee).
 		const user = await this.findMe(options.relations);
-
 		console.log('findMe found User with Id:', user.id);
 
 		// Fetch employee details if 'includeEmployee' is true
 		if (options.includeEmployee) {
 			const relations = options.includeOrganization ? { organization: true } : [];
-
 			employee = await this._employeeService.findOneByUserId(user.id, { relations });
 		}
 
@@ -147,7 +145,7 @@ export class UserService extends TenantAwareCrudService<User> {
 	 * @param relations An array of strings indicating which relations of the user to include.
 	 * @returns A Promise resolving to the IUser object with the desired relations.
 	 */
-	private async findMe(relations: string[]): Promise<IUser> {
+	private async findMe(relations: string[] = []): Promise<IUser> {
 		try {
 			// Get the current user's ID from the RequestContext
 			const userId = RequestContext.currentUserId();
diff --git a/packages/ui-core/common/src/index.ts b/packages/ui-core/common/src/index.ts
index 989bc5753f0..0cd4e22d664 100644
--- a/packages/ui-core/common/src/index.ts
+++ b/packages/ui-core/common/src/index.ts
@@ -3,5 +3,6 @@
  */
 export * from './lib/common.module';
 export * from './lib/constants';
+export * from './lib/http-params';
 export * from './lib/interfaces';
 export * from './lib/utils/shared-utils';
diff --git a/packages/ui-core/common/src/lib/http-params/build-http-params.ts b/packages/ui-core/common/src/lib/http-params/build-http-params.ts
new file mode 100644
index 00000000000..db030c3e5ac
--- /dev/null
+++ b/packages/ui-core/common/src/lib/http-params/build-http-params.ts
@@ -0,0 +1,48 @@
+import { HttpParams } from '@angular/common/http';
+import { CustomHttpParamEncoder } from './custom-http-param-encoder'; // Correct path
+
+/**
+ * Builds HttpParams from a nested object or array recursively.
+ */
+export function buildHttpParams(query: any, prefix: string = ''): HttpParams {
+	let params = new HttpParams({ encoder: new CustomHttpParamEncoder() });
+	return addToParams(params, query, prefix);
+}
+
+/**
+ * Recursively adds query parameters to HttpParams.
+ *
+ * @param params - HttpParams to add query parameters to
+ * @param query - Nested object or array to add query parameters from
+ * @param prefix - Prefix for the query parameters
+ * @returns HttpParams with query parameters added
+ */
+function addToParams(params: HttpParams, query: any, prefix: string): HttpParams {
+	Object.entries(query).forEach(([key, value]) => {
+		const paramKey = prefix ? `${prefix}[${key}]` : key;
+
+		if (value === null || value === undefined) {
+			// Skip null or undefined values
+			return;
+		}
+
+		if (Array.isArray(value)) {
+			// Append each array item individually
+			value.forEach((item) => {
+				if (typeof item === 'object') {
+					params = addToParams(params, item, paramKey);
+				} else {
+					params = params.append(paramKey, item.toString());
+				}
+			});
+		} else if (typeof value === 'object') {
+			// Recursively handle nested objects
+			params = addToParams(params, value, paramKey);
+		} else {
+			// Primitive value (string, number, boolean)
+			params = params.append(paramKey, value.toString());
+		}
+	});
+
+	return params;
+}
diff --git a/packages/ui-core/common/src/lib/http-params/custom-http-param-encoder.ts b/packages/ui-core/common/src/lib/http-params/custom-http-param-encoder.ts
new file mode 100644
index 00000000000..d769ef85a9c
--- /dev/null
+++ b/packages/ui-core/common/src/lib/http-params/custom-http-param-encoder.ts
@@ -0,0 +1,22 @@
+import { HttpParameterCodec } from '@angular/common/http';
+
+/**
+ * Custom HttpParamCodec to correctly encode nested query params
+ */
+export class CustomHttpParamEncoder implements HttpParameterCodec {
+	encodeKey(key: string): string {
+		return encodeURIComponent(key);
+	}
+
+	encodeValue(value: string): string {
+		return encodeURIComponent(value);
+	}
+
+	decodeKey(key: string): string {
+		return decodeURIComponent(key);
+	}
+
+	decodeValue(value: string): string {
+		return decodeURIComponent(value);
+	}
+}
diff --git a/packages/ui-core/common/src/lib/http-params/index.ts b/packages/ui-core/common/src/lib/http-params/index.ts
new file mode 100644
index 00000000000..a22c8e02288
--- /dev/null
+++ b/packages/ui-core/common/src/lib/http-params/index.ts
@@ -0,0 +1 @@
+export { buildHttpParams } from './build-http-params';
diff --git a/packages/ui-core/core/src/lib/services/app/app-init-service.ts b/packages/ui-core/core/src/lib/services/app/app-init-service.ts
index 8f9ce8a5f8f..712c300bf24 100644
--- a/packages/ui-core/core/src/lib/services/app/app-init-service.ts
+++ b/packages/ui-core/core/src/lib/services/app/app-init-service.ts
@@ -53,7 +53,7 @@ export class AppInitService {
 				this._store.featureTenant = tenant.featureOrganizations.filter((item) => !item.organizationId);
 			}
 		} catch (error) {
-			console.log(error);
+			console.log('Error on init', error);
 		}
 	}
 }
diff --git a/packages/ui-core/core/src/lib/services/users/users.service.ts b/packages/ui-core/core/src/lib/services/users/users.service.ts
index ded528264cd..bfea9d6d8f6 100644
--- a/packages/ui-core/core/src/lib/services/users/users.service.ts
+++ b/packages/ui-core/core/src/lib/services/users/users.service.ts
@@ -1,81 +1,95 @@
-import { Injectable } from '@angular/core';
+import { inject, Injectable } from '@angular/core';
 import { HttpClient } from '@angular/common/http';
 import { firstValueFrom } from 'rxjs';
-import { IUser, IUserFindInput, IUserUpdateInput } from '@gauzy/contracts';
-import { API_PREFIX, toParams } from '@gauzy/ui-core/common';
+import { ID, IUser, IUserUpdateInput } from '@gauzy/contracts';
+import { API_PREFIX, buildHttpParams } from '@gauzy/ui-core/common';
 
-@Injectable()
+@Injectable({ providedIn: 'root' })
 export class UsersService {
-	constructor(private readonly http: HttpClient) {}
-
-	API_URL = `${API_PREFIX}/user`;
+	private http = inject(HttpClient);
 
 	/**
 	 * Retrieves the current user's details, optionally including specified relations and employee data.
 	 *
-	 * @param relations An array of strings indicating relations to include in the response.
-	 * @param includeEmployee A boolean flag to include employee details in the response.
-	 * @returns A Promise resolving to the IUser object.
+	 * @param relations - An array of relation names to include in the response.
+	 * @param includeEmployee - Whether to include employee details.
+	 * @returns A promise that resolves to the IUser object.
 	 */
-	getMe(relations: string[] = [], includeEmployee: boolean = false): Promise<IUser> {
-		return firstValueFrom(
-			this.http.get<IUser>(`${this.API_URL}/me`, {
-				params: toParams({ relations, includeEmployee })
-			})
-		);
+	async getMe(relations: string[] = [], includeEmployee: boolean = false): Promise<IUser> {
+		const params = buildHttpParams({ relations, includeEmployee });
+		return firstValueFrom(this.http.get<IUser>(`${API_PREFIX}/user/me`, { params }));
 	}
 
-	getUserByEmail(emailId: string): Promise<IUser> {
-		return firstValueFrom(this.http.get<IUser>(`${this.API_URL}/email/${emailId}`));
+	/**
+	 * Retrieves a user by their email address.
+	 *
+	 * @param emailId - The email address of the user to retrieve.
+	 * @returns A Promise that resolves with the user information.
+	 */
+	async getUserByEmail(emailId: string): Promise<IUser> {
+		return await firstValueFrom(this.http.get<IUser>(`${API_PREFIX}/user/email/${emailId}`));
 	}
 
-	getUserById(id: string, relations?: string[]): Promise<IUser> {
+	/**
+	 * Retrieves a user by their unique ID, optionally including related entities.
+	 *
+	 * @param id - The unique identifier of the user to retrieve.
+	 * @param relations - (Optional) An array of related entity names to include in the result.
+	 * @returns A Promise that resolves with the user information.
+	 */
+	async getUserById(id: string, relations?: string[]): Promise<IUser> {
 		const data = JSON.stringify({ relations });
-		return firstValueFrom(
-			this.http.get<IUser>(`${this.API_URL}/${id}`, {
-				params: { data }
-			})
-		);
-	}
-
-	getAll(relations?: string[], findInput?: IUserFindInput): Promise<{ items: IUser[]; total: number }> {
-		const data = JSON.stringify({ relations, findInput });
-		return firstValueFrom(
-			this.http.get<{ items: IUser[]; total: number }>(`${this.API_URL}`, {
-				params: { data }
-			})
-		);
+		return await firstValueFrom(this.http.get<IUser>(`${API_PREFIX}/user/${id}`, { params: { data } }));
 	}
 
-	update(userId: string, updateInput: IUserUpdateInput) {
-		return firstValueFrom(this.http.put(`${this.API_URL}/${userId}`, updateInput));
+	/**
+	 * Updates the user information for a specific user ID.
+	 *
+	 * @param id - The unique identifier of the user to update.
+	 * @param input - An object containing the updated user details.
+	 * @returns A Promise that resolves with the server's response after updating the user.
+	 */
+	async update(id: ID, input: IUserUpdateInput): Promise<any> {
+		return await firstValueFrom(this.http.put(`${API_PREFIX}/user/${id}`, input));
 	}
 
-	delete(userId, user) {
-		return firstValueFrom(this.http.delete(`${this.API_URL}/${userId}`, user));
+	/**
+	 * Deletes a user by their ID.
+	 *
+	 * @param id - The unique identifier of the user to delete.
+	 * @param user - Additional user data or options (if required by the API) to be passed in the request.
+	 * @returns A Promise that resolves with the server's response after deleting the user.
+	 */
+	async delete(id: ID, user: any): Promise<any> {
+		return await firstValueFrom(this.http.delete(`${API_PREFIX}/user/${id}`, { body: user }));
 	}
 
-	deleteAllData() {
-		return firstValueFrom(this.http.delete(`${this.API_URL}/reset`));
+	/**
+	 * Deletes all user-related data from the system.
+	 *
+	 * @returns A Promise that resolves once all user data has been successfully deleted.
+	 */
+	async deleteAllData(): Promise<any> {
+		return await firstValueFrom(this.http.delete(`${API_PREFIX}/user/reset`));
 	}
 
 	/**
-	 * Update user preferred language
+	 * Updates the user's preferred language setting.
 	 *
-	 * @param input
-	 * @returns
+	 * @param input - An object containing the user update information, including the new preferred language.
+	 * @returns A Promise that resolves once the preferred language has been successfully updated.
 	 */
-	updatePreferredLanguage(input: IUserUpdateInput) {
-		return firstValueFrom(this.http.put(`${this.API_URL}/preferred-language`, input));
+	async updatePreferredLanguage(input: IUserUpdateInput): Promise<any> {
+		return await firstValueFrom(this.http.put(`${API_PREFIX}/user/preferred-language`, input));
 	}
 
 	/**
-	 * Update user preferred component layout
+	 * Updates the user's preferred component layout setting.
 	 *
-	 * @param input
-	 * @returns
+	 * @param input - An object containing the user update information, including the new preferred layout preference.
+	 * @returns A Promise that resolves once the preferred layout has been successfully updated.
 	 */
-	updatePreferredComponentLayout(input: IUserUpdateInput) {
-		return firstValueFrom(this.http.put(`${this.API_URL}/preferred-layout`, input));
+	async updatePreferredComponentLayout(input: IUserUpdateInput): Promise<any> {
+		return await firstValueFrom(this.http.put(`${API_PREFIX}/user/preferred-layout`, input));
 	}
 }
diff --git a/packages/ui-core/shared/src/lib/invite/invite-mutation/invite-mutation.module.ts b/packages/ui-core/shared/src/lib/invite/invite-mutation/invite-mutation.module.ts
index 259ac362322..b5bc592df8e 100644
--- a/packages/ui-core/shared/src/lib/invite/invite-mutation/invite-mutation.module.ts
+++ b/packages/ui-core/shared/src/lib/invite/invite-mutation/invite-mutation.module.ts
@@ -7,8 +7,7 @@ import {
 	OrganizationContactService,
 	OrganizationDepartmentsService,
 	OrganizationProjectsService,
-	OrganizationsService,
-	UsersService
+	OrganizationsService
 } from '@gauzy/ui-core/core';
 import { InviteMutationComponent } from './invite-mutation.component';
 import { InviteFormsModule } from '../forms/invite-forms.module';
@@ -29,8 +28,7 @@ import { InviteFormsModule } from '../forms/invite-forms.module';
 		OrganizationsService,
 		OrganizationProjectsService,
 		OrganizationContactService,
-		OrganizationDepartmentsService,
-		UsersService
+		OrganizationDepartmentsService
 	]
 })
 export class InviteMutationModule {}
diff --git a/packages/ui-core/shared/src/lib/user/edit-profile-form/edit-profile-form.module.ts b/packages/ui-core/shared/src/lib/user/edit-profile-form/edit-profile-form.module.ts
index 3d3a79e2c18..b3843a8dc69 100644
--- a/packages/ui-core/shared/src/lib/user/edit-profile-form/edit-profile-form.module.ts
+++ b/packages/ui-core/shared/src/lib/user/edit-profile-form/edit-profile-form.module.ts
@@ -12,7 +12,7 @@ import {
 } from '@nebular/theme';
 import { NgSelectModule } from '@ng-select/ng-select';
 import { TranslateModule } from '@ngx-translate/core';
-import { OrganizationsService, RoleService, UsersService } from '@gauzy/ui-core/core';
+import { OrganizationsService, RoleService } from '@gauzy/ui-core/core';
 import { EditProfileFormComponent } from './edit-profile-form.component';
 import { UserFormsModule } from '../forms/user-forms.module';
 import { ImageUploaderModule } from '../../image-uploader/image-uploader.module';
@@ -50,6 +50,6 @@ import { TimeZoneSelectorModule } from '../../modules/selectors';
 	],
 	exports: [EditProfileFormComponent],
 	declarations: [EditProfileFormComponent],
-	providers: [OrganizationsService, UsersService, RoleService]
+	providers: [OrganizationsService, RoleService]
 })
 export class EditProfileFormModule {}
diff --git a/packages/ui-core/shared/src/lib/user/user-mutation/user-mutation.module.ts b/packages/ui-core/shared/src/lib/user/user-mutation/user-mutation.module.ts
index 19833fe1859..1f7a1c452ea 100644
--- a/packages/ui-core/shared/src/lib/user/user-mutation/user-mutation.module.ts
+++ b/packages/ui-core/shared/src/lib/user/user-mutation/user-mutation.module.ts
@@ -3,7 +3,6 @@ import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
 import { NbCardModule, NbButtonModule, NbIconModule } from '@nebular/theme';
 import { TranslateModule } from '@ngx-translate/core';
-import { UsersService } from '@gauzy/ui-core/core';
 import { UserMutationComponent } from './user-mutation.component';
 import { UserFormsModule } from '../forms/user-forms.module';
 
@@ -19,6 +18,6 @@ import { UserFormsModule } from '../forms/user-forms.module';
 	],
 	exports: [UserMutationComponent],
 	declarations: [UserMutationComponent],
-	providers: [UsersService]
+	providers: []
 })
 export class UserMutationModule {}
diff --git a/packages/ui-core/theme/src/lib/components/theme-sidebar/theme-settings/components/layout-selector/layout-selector.component.ts b/packages/ui-core/theme/src/lib/components/theme-sidebar/theme-settings/components/layout-selector/layout-selector.component.ts
index 7d2076fe705..06c920c0b26 100644
--- a/packages/ui-core/theme/src/lib/components/theme-sidebar/theme-settings/components/layout-selector/layout-selector.component.ts
+++ b/packages/ui-core/theme/src/lib/components/theme-sidebar/theme-settings/components/layout-selector/layout-selector.component.ts
@@ -7,10 +7,10 @@ import { Store } from '@gauzy/ui-core/core';
 
 @UntilDestroy({ checkProperties: true })
 @Component({
-    selector: 'gauzy-layout-selector',
-    templateUrl: './layout-selector.component.html',
-    styleUrls: ['./layout-selector.component.scss'],
-    standalone: false
+	selector: 'gauzy-layout-selector',
+	templateUrl: './layout-selector.component.html',
+	styleUrls: ['./layout-selector.component.scss'],
+	standalone: false
 })
 export class LayoutSelectorComponent implements OnInit, OnDestroy {
 	user: IUser;
@@ -55,19 +55,20 @@ export class LayoutSelectorComponent implements OnInit, OnDestroy {
 	}
 
 	/**
-	 * Changed User Selected Preferred Component Layout
+	 * Updates the user's preferred component layout.
 	 *
-	 * @param payload
-	 * @returns
+	 * @param input - User update payload containing layout preferences.
 	 */
-	private async changePreferredComponentLayout(payload: IUserUpdateInput) {
+	private async changePreferredComponentLayout(input: IUserUpdateInput): Promise<void> {
 		if (!this.user) {
+			console.warn('No user available. Skipping preferred layout update.');
 			return;
 		}
+
 		try {
-			await this.userService.updatePreferredComponentLayout(payload);
+			await this.userService.updatePreferredComponentLayout(input);
 		} catch (error) {
-			console.error(`Failed to update user preferred component layout`);
+			console.error('Failed to update user preferred component layout:', error);
 		}
 	}
 
diff --git a/packages/ui-core/theme/src/lib/components/theme-sidebar/theme-settings/components/theme-language-selector/theme-language-selector.component.ts b/packages/ui-core/theme/src/lib/components/theme-sidebar/theme-settings/components/theme-language-selector/theme-language-selector.component.ts
index 482ef857f4b..044701ebaf5 100644
--- a/packages/ui-core/theme/src/lib/components/theme-sidebar/theme-settings/components/theme-language-selector/theme-language-selector.component.ts
+++ b/packages/ui-core/theme/src/lib/components/theme-sidebar/theme-settings/components/theme-language-selector/theme-language-selector.component.ts
@@ -9,15 +9,29 @@ import { ThemeLanguageSelectorService } from './theme-language-selector.service'
 
 @UntilDestroy({ checkProperties: true })
 @Component({
-    selector: 'ngx-theme-language-selector',
-    styleUrls: ['./theme-language-selector.component.scss'],
-    templateUrl: './theme-language-selector.component.html',
-    standalone: false
+	selector: 'ngx-theme-language-selector',
+	styleUrls: ['./theme-language-selector.component.scss'],
+	templateUrl: './theme-language-selector.component.html',
+	standalone: false
 })
 export class ThemeLanguageSelectorComponent implements OnInit, OnDestroy, AfterViewInit {
 	user: IUser;
 	languages: ILanguage[] = [];
 
+	/**
+	 * Get preferred language
+	 */
+	public get preferredLanguage(): LanguagesEnum {
+		return this._selectorService.preferredLanguage;
+	}
+
+	/**
+	 * Set preferred language
+	 */
+	public set preferredLanguage(value: LanguagesEnum) {
+		this._selectorService.preferredLanguage = value;
+	}
+
 	constructor(
 		private readonly _store: Store,
 		private readonly _userService: UsersService,
@@ -113,36 +127,22 @@ export class ThemeLanguageSelectorComponent implements OnInit, OnDestroy, AfterV
 	}
 
 	/**
-	 * Changed User Selected Preferred Language
+	 * Updates the user's preferred language.
 	 *
-	 * @param payload
-	 * @returns
+	 * @param input - User update payload containing preferred language information.
 	 */
-	private async changePreferredLanguage(payload: IUserUpdateInput) {
-		if (!this.user || !this.user.tenantId) {
+	private async changePreferredLanguage(input: IUserUpdateInput): Promise<void> {
+		if (!this.user?.tenantId) {
+			console.warn('User or tenantId not available. Skipping preferred language update.');
 			return;
 		}
 
 		try {
-			await this._userService.updatePreferredLanguage(payload);
+			await this._userService.updatePreferredLanguage(input);
 		} catch (error) {
-			console.error(`Failed to update user preferred language`);
+			console.error('Failed to update user preferred language:', error);
 		}
 	}
 
-	/**
-	 * Get preferred language
-	 */
-	public get preferredLanguage(): LanguagesEnum {
-		return this._selectorService.preferredLanguage;
-	}
-
-	/**
-	 * Set preferred language
-	 */
-	public set preferredLanguage(value: LanguagesEnum) {
-		this._selectorService.preferredLanguage = value;
-	}
-
 	ngOnDestroy(): void {}
 }
diff --git a/packages/ui-core/theme/src/lib/theme.module.ts b/packages/ui-core/theme/src/lib/theme.module.ts
index e12cc1e6f1c..c6e55780701 100644
--- a/packages/ui-core/theme/src/lib/theme.module.ts
+++ b/packages/ui-core/theme/src/lib/theme.module.ts
@@ -33,8 +33,7 @@ import {
 	LanguagesService,
 	OrganizationsService,
 	SelectorService,
-	UsersOrganizationsService,
-	UsersService
+	UsersOrganizationsService
 } from '@gauzy/ui-core/core';
 import {
 	DirectivesModule,
@@ -123,7 +122,6 @@ const EXPORT_IMPORT = [ThemeSidebarModule, MatRippleModule];
 	exports: [...EXPORT_IMPORT, ...PIPES, ...COMPONENTS],
 	declarations: [...COMPONENTS, ...PIPES],
 	providers: [
-		UsersService,
 		SelectorService,
 		UsersOrganizationsService,
 		OrganizationsService,
diff --git a/packages/utils/src/lib/parse-to-boolean.ts b/packages/utils/src/lib/parse-to-boolean.ts
index 4d39f8a645a..20ddf2cdeb2 100644
--- a/packages/utils/src/lib/parse-to-boolean.ts
+++ b/packages/utils/src/lib/parse-to-boolean.ts
@@ -1,35 +1,33 @@
 /**
  * Converts a given input into a boolean value.
- * If the input is `undefined` or `null`, it returns `false`.
+ * Handles strings like 'true', '1', 'false', '0', and also actual booleans and numbers.
  *
  * @param value - The input to convert to a boolean.
  * @returns A boolean representation of the given input.
  */
 export const parseToBoolean = (value: any): boolean => {
 	if (value === undefined || value === null) {
-		return false; // Return false for undefined or null
+		return false;
 	}
 
-	try {
-		const parsed = JSON.parse(value); // Attempt to parse as JSON
-		if (typeof parsed === 'boolean') {
-			return parsed; // Return if it's already a boolean
-		}
+	if (typeof value === 'boolean') {
+		return value;
+	}
 
-		// Convert numbers: 0 is false, other numbers are true
-		if (typeof parsed === 'number') {
-			return parsed !== 0;
-		}
+	if (typeof value === 'number') {
+		return value !== 0;
+	}
 
-		// Convert common truthy/falsy strings
-		if (typeof parsed === 'string') {
-			const lowerCase = parsed.toLowerCase().trim();
-			return lowerCase === 'true' || lowerCase === '1'; // Consider 'true' and '1' as true
+	if (typeof value === 'string') {
+		const normalized = value.toLowerCase().trim();
+		if (normalized === 'true' || normalized === '1') {
+			return true;
+		}
+		if (normalized === 'false' || normalized === '0') {
+			return false;
 		}
-
-		return Boolean(parsed); // Fallback to Boolean conversion
-	} catch (error) {
-		// Handle JSON parse errors
-		return false; // Return false on parsing errors
 	}
+
+	// Fallback
+	return false;
 };


Reviews:
user: login: greptile-apps[bot]
body: 
## PR Summary

This PR implements various improvements across the codebase, focusing on HTTP parameter handling, service providers optimization, and code quality enhancements.

- Added new `buildHttpParams` utility with `CustomHttpParamEncoder` for consistent HTTP parameter handling across the application
- Optimized service providers by removing redundant `UsersService` registrations from various modules, relying on root-level provision
- Enhanced error handling and logging in user-related services with more descriptive messages and proper null checks
- Added `transform: true` to validation pipes for proper DTO transformation of query parameters
- Set query parser to 'extended' mode in NestJS bootstrap for Express v5 compatibility

The changes appear well-structured and improve code maintainability while fixing potential issues with parameter handling and service injection patterns.

<sub>24 file(s) reviewed, 9 comment(s)</sub>
<sub>[Edit PR Review Bot Settings](https://app.greptile.com/review/github) | [Greptile](https://greptile.com?utm_source=greptile_expert&utm_medium=github&utm_campaign=code_reviews&utm_content=ever-gauzy_8949)</sub>
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T07:29:01Z

Review comments:
diff_hunk: @@ -43,22 +43,28 @@ export const convertNativeParameters = (parameters: any): any => {
 };
 
 /**
- * Parse object to specific type
+ * Recursively parses an object, applying a callback to all non-object leaf values.
  *
- * @param source
- * @returns
+ * @param source - The object to parse.
+ * @param callback - The function to apply to each primitive value.
+ * @returns The modified object.
  */
-export function parseObject(source: Object, callback: Function) {
-	if (isObject(source)) {
-		for (const key in source) {
-			if (isObject(source[key])) {
-				if (!isClassInstance(source[key])) {
-					parseObject(source[key], callback);
-				}
-			} else {
-				Object.assign(source, { [key]: callback(source[key]) });
+export function parseObject<T extends object>(source: T, callback: (value: any) => any): T {
path: packages/core/src/lib/core/crud/pagination.helper.ts
user: login: greptile-apps[bot]
body: style: T extends object allows null as a valid type, should use Record&lt;string, any&gt; instead
created_at: 2025-05-13T07:25:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,12 +4,22 @@ import { IsArray, IsOptional } from 'class-validator';
 import { IBaseRelationsEntityModel } from '@gauzy/contracts';
 
 /**
- * Get relations request DTO validation
+ * Validates and transforms 'relations' query parameter.
  */
 export class RelationsQueryDTO implements IBaseRelationsEntityModel {
-	@ApiPropertyOptional({ type: () => Array, isArray: true })
+	@ApiPropertyOptional({ type: () => [String], isArray: true })
 	@IsOptional()
 	@IsArray()
-	@Transform(({ value }: TransformFnParams) => (value ? value.map((element: string) => element.trim()) : []))
+	@Transform(({ value }: TransformFnParams) => {
+		if (typeof value === 'string') {
+			// If it's a comma-separated string, split into array
+			return value.split(',').map((item) => item.trim());
+		}
+		if (Array.isArray(value)) {
+			// If it's already an array, trim all elements
+			return value.map((item: string) => item.trim());
path: packages/core/src/lib/shared/dto/relations-query.dto.ts
user: login: greptile-apps[bot]
body: logic: No type checking on array items - could throw if array contains non-string values
created_at: 2025-05-13T07:25:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,12 +4,22 @@ import { IsArray, IsOptional } from 'class-validator';
 import { IBaseRelationsEntityModel } from '@gauzy/contracts';
 
 /**
- * Get relations request DTO validation
+ * Validates and transforms 'relations' query parameter.
  */
 export class RelationsQueryDTO implements IBaseRelationsEntityModel {
-	@ApiPropertyOptional({ type: () => Array, isArray: true })
+	@ApiPropertyOptional({ type: () => [String], isArray: true })
 	@IsOptional()
 	@IsArray()
-	@Transform(({ value }: TransformFnParams) => (value ? value.map((element: string) => element.trim()) : []))
+	@Transform(({ value }: TransformFnParams) => {
+		if (typeof value === 'string') {
+			// If it's a comma-separated string, split into array
+			return value.split(',').map((item) => item.trim());
path: packages/core/src/lib/shared/dto/relations-query.dto.ts
user: login: greptile-apps[bot]
body: style: Empty string items after split will result in empty strings in array - consider filtering them out

```suggestion
			return value.split(',').map((item) => item.trim()).filter(Boolean);
```
created_at: 2025-05-13T07:25:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,48 @@
+import { HttpParams } from '@angular/common/http';
+import { CustomHttpParamEncoder } from './custom-http-param-encoder'; // Correct path
+
+/**
+ * Builds HttpParams from a nested object or array recursively.
+ */
+export function buildHttpParams(query: any, prefix: string = ''): HttpParams {
+	let params = new HttpParams({ encoder: new CustomHttpParamEncoder() });
path: packages/ui-core/common/src/lib/http-params/build-http-params.ts
user: login: greptile-apps[bot]
body: style: consider adding type safety by using a generic type parameter instead of any
created_at: 2025-05-13T07:26:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,22 @@
+import { HttpParameterCodec } from '@angular/common/http';
+
+/**
+ * Custom HttpParamCodec to correctly encode nested query params
+ */
+export class CustomHttpParamEncoder implements HttpParameterCodec {
+	encodeKey(key: string): string {
+		return encodeURIComponent(key);
+	}
+
+	encodeValue(value: string): string {
+		return encodeURIComponent(value);
+	}
+
+	decodeKey(key: string): string {
+		return decodeURIComponent(key);
+	}
+
+	decodeValue(value: string): string {
+		return decodeURIComponent(value);
+	}
path: packages/ui-core/common/src/lib/http-params/custom-http-param-encoder.ts
user: login: greptile-apps[bot]
body: style: decodeURIComponent can throw URIError if input is malformed. Consider wrapping in try/catch to handle invalid encoded strings gracefully.
created_at: 2025-05-13T07:27:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,81 +1,95 @@
-import { Injectable } from '@angular/core';
+import { inject, Injectable } from '@angular/core';
 import { HttpClient } from '@angular/common/http';
 import { firstValueFrom } from 'rxjs';
-import { IUser, IUserFindInput, IUserUpdateInput } from '@gauzy/contracts';
-import { API_PREFIX, toParams } from '@gauzy/ui-core/common';
+import { ID, IUser, IUserUpdateInput } from '@gauzy/contracts';
+import { API_PREFIX, buildHttpParams } from '@gauzy/ui-core/common';
 
-@Injectable()
+@Injectable({ providedIn: 'root' })
 export class UsersService {
-	constructor(private readonly http: HttpClient) {}
-
-	API_URL = `${API_PREFIX}/user`;
+	private http = inject(HttpClient);
 
 	/**
 	 * Retrieves the current user's details, optionally including specified relations and employee data.
 	 *
-	 * @param relations An array of strings indicating relations to include in the response.
-	 * @param includeEmployee A boolean flag to include employee details in the response.
-	 * @returns A Promise resolving to the IUser object.
+	 * @param relations - An array of relation names to include in the response.
+	 * @param includeEmployee - Whether to include employee details.
+	 * @returns A promise that resolves to the IUser object.
 	 */
-	getMe(relations: string[] = [], includeEmployee: boolean = false): Promise<IUser> {
-		return firstValueFrom(
-			this.http.get<IUser>(`${this.API_URL}/me`, {
-				params: toParams({ relations, includeEmployee })
-			})
-		);
+	async getMe(relations: string[] = [], includeEmployee: boolean = false): Promise<IUser> {
+		const params = buildHttpParams({ relations, includeEmployee });
+		return firstValueFrom(this.http.get<IUser>(`${API_PREFIX}/user/me`, { params }));
 	}
 
-	getUserByEmail(emailId: string): Promise<IUser> {
-		return firstValueFrom(this.http.get<IUser>(`${this.API_URL}/email/${emailId}`));
+	/**
+	 * Retrieves a user by their email address.
+	 *
+	 * @param emailId - The email address of the user to retrieve.
+	 * @returns A Promise that resolves with the user information.
+	 */
+	async getUserByEmail(emailId: string): Promise<IUser> {
+		return await firstValueFrom(this.http.get<IUser>(`${API_PREFIX}/user/email/${emailId}`));
 	}
 
-	getUserById(id: string, relations?: string[]): Promise<IUser> {
+	/**
+	 * Retrieves a user by their unique ID, optionally including related entities.
+	 *
+	 * @param id - The unique identifier of the user to retrieve.
+	 * @param relations - (Optional) An array of related entity names to include in the result.
+	 * @returns A Promise that resolves with the user information.
+	 */
+	async getUserById(id: string, relations?: string[]): Promise<IUser> {
 		const data = JSON.stringify({ relations });
-		return firstValueFrom(
-			this.http.get<IUser>(`${this.API_URL}/${id}`, {
-				params: { data }
-			})
-		);
-	}
-
-	getAll(relations?: string[], findInput?: IUserFindInput): Promise<{ items: IUser[]; total: number }> {
-		const data = JSON.stringify({ relations, findInput });
-		return firstValueFrom(
-			this.http.get<{ items: IUser[]; total: number }>(`${this.API_URL}`, {
-				params: { data }
-			})
-		);
+		return await firstValueFrom(this.http.get<IUser>(`${API_PREFIX}/user/${id}`, { params: { data } }));
path: packages/ui-core/core/src/lib/services/users/users.service.ts
user: login: greptile-apps[bot]
body: logic: relations parameter is stringified but sent as URL param 'data' - this may cause encoding issues. Should use buildHttpParams like getMe() method

```suggestion
		const params = buildHttpParams({ relations });
		return await firstValueFrom(this.http.get<IUser>(`${API_PREFIX}/user/${id}`, { params }));
```
created_at: 2025-05-13T07:27:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,81 +1,95 @@
-import { Injectable } from '@angular/core';
+import { inject, Injectable } from '@angular/core';
 import { HttpClient } from '@angular/common/http';
 import { firstValueFrom } from 'rxjs';
-import { IUser, IUserFindInput, IUserUpdateInput } from '@gauzy/contracts';
-import { API_PREFIX, toParams } from '@gauzy/ui-core/common';
+import { ID, IUser, IUserUpdateInput } from '@gauzy/contracts';
+import { API_PREFIX, buildHttpParams } from '@gauzy/ui-core/common';
 
-@Injectable()
+@Injectable({ providedIn: 'root' })
 export class UsersService {
-	constructor(private readonly http: HttpClient) {}
-
-	API_URL = `${API_PREFIX}/user`;
+	private http = inject(HttpClient);
 
 	/**
 	 * Retrieves the current user's details, optionally including specified relations and employee data.
 	 *
-	 * @param relations An array of strings indicating relations to include in the response.
-	 * @param includeEmployee A boolean flag to include employee details in the response.
-	 * @returns A Promise resolving to the IUser object.
+	 * @param relations - An array of relation names to include in the response.
+	 * @param includeEmployee - Whether to include employee details.
+	 * @returns A promise that resolves to the IUser object.
 	 */
-	getMe(relations: string[] = [], includeEmployee: boolean = false): Promise<IUser> {
-		return firstValueFrom(
-			this.http.get<IUser>(`${this.API_URL}/me`, {
-				params: toParams({ relations, includeEmployee })
-			})
-		);
+	async getMe(relations: string[] = [], includeEmployee: boolean = false): Promise<IUser> {
+		const params = buildHttpParams({ relations, includeEmployee });
+		return firstValueFrom(this.http.get<IUser>(`${API_PREFIX}/user/me`, { params }));
 	}
 
-	getUserByEmail(emailId: string): Promise<IUser> {
-		return firstValueFrom(this.http.get<IUser>(`${this.API_URL}/email/${emailId}`));
+	/**
+	 * Retrieves a user by their email address.
+	 *
+	 * @param emailId - The email address of the user to retrieve.
+	 * @returns A Promise that resolves with the user information.
+	 */
+	async getUserByEmail(emailId: string): Promise<IUser> {
+		return await firstValueFrom(this.http.get<IUser>(`${API_PREFIX}/user/email/${emailId}`));
 	}
 
-	getUserById(id: string, relations?: string[]): Promise<IUser> {
+	/**
+	 * Retrieves a user by their unique ID, optionally including related entities.
+	 *
+	 * @param id - The unique identifier of the user to retrieve.
+	 * @param relations - (Optional) An array of related entity names to include in the result.
+	 * @returns A Promise that resolves with the user information.
+	 */
+	async getUserById(id: string, relations?: string[]): Promise<IUser> {
 		const data = JSON.stringify({ relations });
-		return firstValueFrom(
-			this.http.get<IUser>(`${this.API_URL}/${id}`, {
-				params: { data }
-			})
-		);
-	}
-
-	getAll(relations?: string[], findInput?: IUserFindInput): Promise<{ items: IUser[]; total: number }> {
-		const data = JSON.stringify({ relations, findInput });
-		return firstValueFrom(
-			this.http.get<{ items: IUser[]; total: number }>(`${this.API_URL}`, {
-				params: { data }
-			})
-		);
+		return await firstValueFrom(this.http.get<IUser>(`${API_PREFIX}/user/${id}`, { params: { data } }));
 	}
 
-	update(userId: string, updateInput: IUserUpdateInput) {
-		return firstValueFrom(this.http.put(`${this.API_URL}/${userId}`, updateInput));
+	/**
+	 * Updates the user information for a specific user ID.
+	 *
+	 * @param id - The unique identifier of the user to update.
+	 * @param input - An object containing the updated user details.
+	 * @returns A Promise that resolves with the server's response after updating the user.
+	 */
+	async update(id: ID, input: IUserUpdateInput): Promise<any> {
+		return await firstValueFrom(this.http.put(`${API_PREFIX}/user/${id}`, input));
 	}
path: packages/ui-core/core/src/lib/services/users/users.service.ts
user: login: greptile-apps[bot]
body: style: return type Promise&lt;any&gt; is too permissive - should be Promise&lt;IUser | UpdateResult&gt;
created_at: 2025-05-13T07:27:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,81 +1,95 @@
-import { Injectable } from '@angular/core';
+import { inject, Injectable } from '@angular/core';
 import { HttpClient } from '@angular/common/http';
 import { firstValueFrom } from 'rxjs';
-import { IUser, IUserFindInput, IUserUpdateInput } from '@gauzy/contracts';
-import { API_PREFIX, toParams } from '@gauzy/ui-core/common';
+import { ID, IUser, IUserUpdateInput } from '@gauzy/contracts';
+import { API_PREFIX, buildHttpParams } from '@gauzy/ui-core/common';
 
-@Injectable()
+@Injectable({ providedIn: 'root' })
 export class UsersService {
-	constructor(private readonly http: HttpClient) {}
-
-	API_URL = `${API_PREFIX}/user`;
+	private http = inject(HttpClient);
 
 	/**
 	 * Retrieves the current user's details, optionally including specified relations and employee data.
 	 *
-	 * @param relations An array of strings indicating relations to include in the response.
-	 * @param includeEmployee A boolean flag to include employee details in the response.
-	 * @returns A Promise resolving to the IUser object.
+	 * @param relations - An array of relation names to include in the response.
+	 * @param includeEmployee - Whether to include employee details.
+	 * @returns A promise that resolves to the IUser object.
 	 */
-	getMe(relations: string[] = [], includeEmployee: boolean = false): Promise<IUser> {
-		return firstValueFrom(
-			this.http.get<IUser>(`${this.API_URL}/me`, {
-				params: toParams({ relations, includeEmployee })
-			})
-		);
+	async getMe(relations: string[] = [], includeEmployee: boolean = false): Promise<IUser> {
+		const params = buildHttpParams({ relations, includeEmployee });
+		return firstValueFrom(this.http.get<IUser>(`${API_PREFIX}/user/me`, { params }));
 	}
 
-	getUserByEmail(emailId: string): Promise<IUser> {
-		return firstValueFrom(this.http.get<IUser>(`${this.API_URL}/email/${emailId}`));
+	/**
+	 * Retrieves a user by their email address.
+	 *
+	 * @param emailId - The email address of the user to retrieve.
+	 * @returns A Promise that resolves with the user information.
+	 */
+	async getUserByEmail(emailId: string): Promise<IUser> {
+		return await firstValueFrom(this.http.get<IUser>(`${API_PREFIX}/user/email/${emailId}`));
 	}
 
-	getUserById(id: string, relations?: string[]): Promise<IUser> {
+	/**
+	 * Retrieves a user by their unique ID, optionally including related entities.
+	 *
+	 * @param id - The unique identifier of the user to retrieve.
+	 * @param relations - (Optional) An array of related entity names to include in the result.
+	 * @returns A Promise that resolves with the user information.
+	 */
+	async getUserById(id: string, relations?: string[]): Promise<IUser> {
 		const data = JSON.stringify({ relations });
-		return firstValueFrom(
-			this.http.get<IUser>(`${this.API_URL}/${id}`, {
-				params: { data }
-			})
-		);
-	}
-
-	getAll(relations?: string[], findInput?: IUserFindInput): Promise<{ items: IUser[]; total: number }> {
-		const data = JSON.stringify({ relations, findInput });
-		return firstValueFrom(
-			this.http.get<{ items: IUser[]; total: number }>(`${this.API_URL}`, {
-				params: { data }
-			})
-		);
+		return await firstValueFrom(this.http.get<IUser>(`${API_PREFIX}/user/${id}`, { params: { data } }));
 	}
 
-	update(userId: string, updateInput: IUserUpdateInput) {
-		return firstValueFrom(this.http.put(`${this.API_URL}/${userId}`, updateInput));
+	/**
+	 * Updates the user information for a specific user ID.
+	 *
+	 * @param id - The unique identifier of the user to update.
+	 * @param input - An object containing the updated user details.
+	 * @returns A Promise that resolves with the server's response after updating the user.
+	 */
+	async update(id: ID, input: IUserUpdateInput): Promise<any> {
+		return await firstValueFrom(this.http.put(`${API_PREFIX}/user/${id}`, input));
 	}
 
-	delete(userId, user) {
-		return firstValueFrom(this.http.delete(`${this.API_URL}/${userId}`, user));
+	/**
+	 * Deletes a user by their ID.
+	 *
+	 * @param id - The unique identifier of the user to delete.
+	 * @param user - Additional user data or options (if required by the API) to be passed in the request.
+	 * @returns A Promise that resolves with the server's response after deleting the user.
+	 */
+	async delete(id: ID, user: any): Promise<any> {
+		return await firstValueFrom(this.http.delete(`${API_PREFIX}/user/${id}`, { body: user }));
 	}
path: packages/ui-core/core/src/lib/services/users/users.service.ts
user: login: greptile-apps[bot]
body: style: user parameter type 'any' should be properly typed with an interface
created_at: 2025-05-13T07:27:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,81 +1,95 @@
-import { Injectable } from '@angular/core';
+import { inject, Injectable } from '@angular/core';
 import { HttpClient } from '@angular/common/http';
 import { firstValueFrom } from 'rxjs';
-import { IUser, IUserFindInput, IUserUpdateInput } from '@gauzy/contracts';
-import { API_PREFIX, toParams } from '@gauzy/ui-core/common';
+import { ID, IUser, IUserUpdateInput } from '@gauzy/contracts';
+import { API_PREFIX, buildHttpParams } from '@gauzy/ui-core/common';
 
-@Injectable()
+@Injectable({ providedIn: 'root' })
 export class UsersService {
-	constructor(private readonly http: HttpClient) {}
-
-	API_URL = `${API_PREFIX}/user`;
+	private http = inject(HttpClient);
 
 	/**
 	 * Retrieves the current user's details, optionally including specified relations and employee data.
 	 *
-	 * @param relations An array of strings indicating relations to include in the response.
-	 * @param includeEmployee A boolean flag to include employee details in the response.
-	 * @returns A Promise resolving to the IUser object.
+	 * @param relations - An array of relation names to include in the response.
+	 * @param includeEmployee - Whether to include employee details.
+	 * @returns A promise that resolves to the IUser object.
 	 */
-	getMe(relations: string[] = [], includeEmployee: boolean = false): Promise<IUser> {
-		return firstValueFrom(
-			this.http.get<IUser>(`${this.API_URL}/me`, {
-				params: toParams({ relations, includeEmployee })
-			})
-		);
+	async getMe(relations: string[] = [], includeEmployee: boolean = false): Promise<IUser> {
+		const params = buildHttpParams({ relations, includeEmployee });
+		return firstValueFrom(this.http.get<IUser>(`${API_PREFIX}/user/me`, { params }));
 	}
 
-	getUserByEmail(emailId: string): Promise<IUser> {
-		return firstValueFrom(this.http.get<IUser>(`${this.API_URL}/email/${emailId}`));
+	/**
+	 * Retrieves a user by their email address.
+	 *
+	 * @param emailId - The email address of the user to retrieve.
+	 * @returns A Promise that resolves with the user information.
+	 */
+	async getUserByEmail(emailId: string): Promise<IUser> {
+		return await firstValueFrom(this.http.get<IUser>(`${API_PREFIX}/user/email/${emailId}`));
 	}
path: packages/ui-core/core/src/lib/services/users/users.service.ts
user: login: greptile-apps[bot]
body: logic: emailId parameter should be URL encoded to handle special characters in email addresses

```suggestion
	async getUserByEmail(emailId: string): Promise<IUser> {
		return await firstValueFrom(this.http.get<IUser>(`${API_PREFIX}/user/email/${encodeURIComponent(emailId)}`));
	}
```
created_at: 2025-05-13T07:27:42Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-13T07:24:32Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: skip review by coderabbit.ai -->

> [!IMPORTANT]
> ## Review skipped
> 
> Auto reviews are disabled on base/target branches other than the default branch.
> 
> 
> 
> Please check the settings in the CodeRabbit UI or the `.coderabbit.yaml` file in this repository. To trigger a single review, invoke the `@coderabbitai review` command.
> 
> You can disable this status message by setting the `reviews.review_status` to `false` in the CodeRabbit configuration file.

<!-- end of auto-generated comment: skip review by coderabbit.ai -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=ever-co/ever-gauzy&utm_content=8949):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: nx-cloud[bot]
created_at: 2025-05-13T07:49:41Z
body: 
View your [CI Pipeline Execution ↗](https://cloud.nx.app/cipes/6822f994104a47136dc3cea8?utm_source=pull-request&utm_medium=comment) for commit a57ab785c2480fcd7a80f491d242d49babc0f366.

| Command | Status | Duration | Result |
|---------|--------|----------:|--------|
| `nx build desktop --base-href ./` | ✅ Succeeded | 1m 56s | [View ↗](https://cloud.nx.app/runs/338baY28Il?utm_source=pull-request&utm_medium=comment) |
| `nx build desktop-api --output-path=dist/apps/de...` | ✅ Succeeded | 32s | [View ↗](https://cloud.nx.app/runs/R6I9ijDhju?utm_source=pull-request&utm_medium=comment) |
| `nx run api:desktop-api` | ✅ Succeeded | 1m 23s | [View ↗](https://cloud.nx.app/runs/LtK6k0jJgL?utm_source=pull-request&utm_medium=comment) |
| `nx run gauzy:desktop-ui --base-href ./` | ✅ Succeeded | 3m 47s | [View ↗](https://cloud.nx.app/runs/RhDWTdDrst?utm_source=pull-request&utm_medium=comment) |
| `nx build gauzy -c=production --prod --verbose` | ✅ Succeeded | 3m 22s | [View ↗](https://cloud.nx.app/runs/ZGBwz3K0VE?utm_source=pull-request&utm_medium=comment) |
| `nx build desktop-ui-lib --configuration=production` | ✅ Succeeded | 31s | [View ↗](https://cloud.nx.app/runs/RYhC3aC3kO?utm_source=pull-request&utm_medium=comment) |
| `nx build plugin-integration-wakatime` | ✅ Succeeded | <1s | [View ↗](https://cloud.nx.app/runs/3k07PZ5F7C?utm_source=pull-request&utm_medium=comment) |
| `nx build desktop-lib` | ✅ Succeeded | <1s | [View ↗](https://cloud.nx.app/runs/H6p8zCs1n9?utm_source=pull-request&utm_medium=comment) |
| `Additional runs (62)` | ✅ Succeeded | ... | [View ↗](https://cloud.nx.app/cipes/6822f994104a47136dc3cea8?utm_source=pull-request&utm_medium=comment) |

---

☁️ [Nx Cloud](https://cloud.nx.app?utm_source=pull-request&utm_medium=comment) last updated this comment at `2025-05-13 08:07:17` UTC

<!-- NX_CLOUD_APP_COMMENT_END -->
author_association: NONE