From 697ecfe9bd5f7be000e1aadf3013a85c821ea27f Mon Sep 17 00:00:00 2001 From: Arghya Das Date: Thu, 6 Nov 2025 01:54:52 +0530 Subject: [PATCH] Feature/add endpoint to get user by id (#5910) * Add endpoint to get user by id * Update changelog --------- Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> --- CHANGELOG.md | 4 ++ apps/api/src/app/admin/admin.controller.ts | 8 ++++ apps/api/src/app/admin/admin.service.ts | 38 +++++++++++++------ .../lib/interfaces/admin-user.interface.ts | 13 +++++++ libs/common/src/lib/interfaces/index.ts | 4 ++ .../admin-user-response.interface.ts | 3 ++ .../admin-users-response.interface.ts | 14 +------ 7 files changed, 61 insertions(+), 23 deletions(-) create mode 100644 libs/common/src/lib/interfaces/admin-user.interface.ts create mode 100644 libs/common/src/lib/interfaces/responses/admin-user-response.interface.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 801b33652..79ff17256 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## 2.215.0-beta.1 - 2025-11-05 +### Added + +- Added the endpoint `GET /api/v1/admin/user/:id` + ### Changed - Improved the _Self-Hosting_ section content for the _Compare with..._ concept on the Frequently Asked Questions (FAQ) page diff --git a/apps/api/src/app/admin/admin.controller.ts b/apps/api/src/app/admin/admin.controller.ts index 2419b0a7d..7ed7f364b 100644 --- a/apps/api/src/app/admin/admin.controller.ts +++ b/apps/api/src/app/admin/admin.controller.ts @@ -17,6 +17,7 @@ import { getAssetProfileIdentifier } from '@ghostfolio/common/helper'; import { AdminData, AdminMarketData, + AdminUserResponse, AdminUsersResponse, EnhancedSymbolProfile, ScraperConfiguration @@ -321,4 +322,11 @@ export class AdminController { take: isNaN(take) ? undefined : take }); } + + @Get('user/:id') + @HasPermission(permissions.accessAdminControl) + @UseGuards(AuthGuard('jwt'), HasPermissionGuard) + public async getUser(@Param('id') id: string): Promise { + return this.adminService.getUser(id); + } } diff --git a/apps/api/src/app/admin/admin.service.ts b/apps/api/src/app/admin/admin.service.ts index 683e72cb8..6b29b141a 100644 --- a/apps/api/src/app/admin/admin.service.ts +++ b/apps/api/src/app/admin/admin.service.ts @@ -23,6 +23,7 @@ import { AdminMarketData, AdminMarketDataDetails, AdminMarketDataItem, + AdminUserResponse, AdminUsersResponse, AssetProfileIdentifier, EnhancedSymbolProfile, @@ -35,7 +36,8 @@ import { BadRequestException, HttpException, Injectable, - Logger + Logger, + NotFoundException } from '@nestjs/common'; import { AssetClass, @@ -507,6 +509,18 @@ export class AdminService { }; } + public async getUser(id: string): Promise { + const [user] = await this.getUsersWithAnalytics({ + where: { id } + }); + + if (!user) { + throw new NotFoundException(`User with ID ${id} not found`); + } + + return user; + } + public async getUsers({ skip, take = Number.MAX_SAFE_INTEGER @@ -516,7 +530,15 @@ export class AdminService { }): Promise { const [count, users] = await Promise.all([ this.countUsersWithAnalytics(), - this.getUsersWithAnalytics({ skip, take }) + this.getUsersWithAnalytics({ + skip, + take, + where: { + NOT: { + analytics: null + } + } + }) ]); return { count, users }; @@ -814,17 +836,17 @@ export class AdminService { private async getUsersWithAnalytics({ skip, - take + take, + where }: { skip?: number; take?: number; + where?: Prisma.UserWhereInput; }): Promise { let orderBy: Prisma.Enumerable = [ { createdAt: 'desc' } ]; - let where: Prisma.UserWhereInput; - if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { orderBy = [ { @@ -833,12 +855,6 @@ export class AdminService { } } ]; - - where = { - NOT: { - analytics: null - } - }; } const usersWithAnalytics = await this.prismaService.user.findMany({ diff --git a/libs/common/src/lib/interfaces/admin-user.interface.ts b/libs/common/src/lib/interfaces/admin-user.interface.ts new file mode 100644 index 000000000..872abca90 --- /dev/null +++ b/libs/common/src/lib/interfaces/admin-user.interface.ts @@ -0,0 +1,13 @@ +import { Role } from '@prisma/client'; + +export interface AdminUser { + accountCount: number; + activityCount: number; + country: string; + createdAt: Date; + dailyApiRequests: number; + engagement: number; + id: string; + lastActivity: Date; + role: Role; +} diff --git a/libs/common/src/lib/interfaces/index.ts b/libs/common/src/lib/interfaces/index.ts index 06ecf32e8..899813f30 100644 --- a/libs/common/src/lib/interfaces/index.ts +++ b/libs/common/src/lib/interfaces/index.ts @@ -7,6 +7,7 @@ import type { AdminMarketData, AdminMarketDataItem } from './admin-market-data.interface'; +import type { AdminUser } from './admin-user.interface'; import type { AssetClassSelectorOption } from './asset-class-selector-option.interface'; import type { AssetProfileIdentifier } from './asset-profile-identifier.interface'; import type { BenchmarkProperty } from './benchmark-property.interface'; @@ -38,6 +39,7 @@ import type { AccountBalancesResponse } from './responses/account-balances-respo import type { AccountsResponse } from './responses/accounts-response.interface'; import type { ActivitiesResponse } from './responses/activities-response.interface'; import type { ActivityResponse } from './responses/activity-response.interface'; +import type { AdminUserResponse } from './responses/admin-user-response.interface'; import type { AdminUsersResponse } from './responses/admin-users-response.interface'; import type { AiPromptResponse } from './responses/ai-prompt-response.interface'; import type { ApiKeyResponse } from './responses/api-key-response.interface'; @@ -92,6 +94,8 @@ export { AdminMarketData, AdminMarketDataDetails, AdminMarketDataItem, + AdminUser, + AdminUserResponse, AdminUsersResponse, AiPromptResponse, ApiKeyResponse, diff --git a/libs/common/src/lib/interfaces/responses/admin-user-response.interface.ts b/libs/common/src/lib/interfaces/responses/admin-user-response.interface.ts new file mode 100644 index 000000000..8e93fc097 --- /dev/null +++ b/libs/common/src/lib/interfaces/responses/admin-user-response.interface.ts @@ -0,0 +1,3 @@ +import { AdminUser } from '../admin-user.interface'; + +export interface AdminUserResponse extends AdminUser {} diff --git a/libs/common/src/lib/interfaces/responses/admin-users-response.interface.ts b/libs/common/src/lib/interfaces/responses/admin-users-response.interface.ts index d9f58ee18..8dd058030 100644 --- a/libs/common/src/lib/interfaces/responses/admin-users-response.interface.ts +++ b/libs/common/src/lib/interfaces/responses/admin-users-response.interface.ts @@ -1,16 +1,6 @@ -import { Role } from '@prisma/client'; +import { AdminUser } from '../admin-user.interface'; export interface AdminUsersResponse { count: number; - users: { - accountCount: number; - activityCount: number; - country: string; - createdAt: Date; - dailyApiRequests: number; - engagement: number; - id: string; - lastActivity: Date; - role: Role; - }[]; + users: AdminUser[]; }