Browse Source

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>
pull/5909/head^2
Arghya Das 3 weeks ago
committed by GitHub
parent
commit
697ecfe9bd
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 4
      CHANGELOG.md
  2. 8
      apps/api/src/app/admin/admin.controller.ts
  3. 38
      apps/api/src/app/admin/admin.service.ts
  4. 13
      libs/common/src/lib/interfaces/admin-user.interface.ts
  5. 4
      libs/common/src/lib/interfaces/index.ts
  6. 3
      libs/common/src/lib/interfaces/responses/admin-user-response.interface.ts
  7. 14
      libs/common/src/lib/interfaces/responses/admin-users-response.interface.ts

4
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 ## 2.215.0-beta.1 - 2025-11-05
### Added
- Added the endpoint `GET /api/v1/admin/user/:id`
### Changed ### Changed
- Improved the _Self-Hosting_ section content for the _Compare with..._ concept on the Frequently Asked Questions (FAQ) page - Improved the _Self-Hosting_ section content for the _Compare with..._ concept on the Frequently Asked Questions (FAQ) page

8
apps/api/src/app/admin/admin.controller.ts

@ -17,6 +17,7 @@ import { getAssetProfileIdentifier } from '@ghostfolio/common/helper';
import { import {
AdminData, AdminData,
AdminMarketData, AdminMarketData,
AdminUserResponse,
AdminUsersResponse, AdminUsersResponse,
EnhancedSymbolProfile, EnhancedSymbolProfile,
ScraperConfiguration ScraperConfiguration
@ -321,4 +322,11 @@ export class AdminController {
take: isNaN(take) ? undefined : take take: isNaN(take) ? undefined : take
}); });
} }
@Get('user/:id')
@HasPermission(permissions.accessAdminControl)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async getUser(@Param('id') id: string): Promise<AdminUserResponse> {
return this.adminService.getUser(id);
}
} }

38
apps/api/src/app/admin/admin.service.ts

@ -23,6 +23,7 @@ import {
AdminMarketData, AdminMarketData,
AdminMarketDataDetails, AdminMarketDataDetails,
AdminMarketDataItem, AdminMarketDataItem,
AdminUserResponse,
AdminUsersResponse, AdminUsersResponse,
AssetProfileIdentifier, AssetProfileIdentifier,
EnhancedSymbolProfile, EnhancedSymbolProfile,
@ -35,7 +36,8 @@ import {
BadRequestException, BadRequestException,
HttpException, HttpException,
Injectable, Injectable,
Logger Logger,
NotFoundException
} from '@nestjs/common'; } from '@nestjs/common';
import { import {
AssetClass, AssetClass,
@ -507,6 +509,18 @@ export class AdminService {
}; };
} }
public async getUser(id: string): Promise<AdminUserResponse> {
const [user] = await this.getUsersWithAnalytics({
where: { id }
});
if (!user) {
throw new NotFoundException(`User with ID ${id} not found`);
}
return user;
}
public async getUsers({ public async getUsers({
skip, skip,
take = Number.MAX_SAFE_INTEGER take = Number.MAX_SAFE_INTEGER
@ -516,7 +530,15 @@ export class AdminService {
}): Promise<AdminUsersResponse> { }): Promise<AdminUsersResponse> {
const [count, users] = await Promise.all([ const [count, users] = await Promise.all([
this.countUsersWithAnalytics(), this.countUsersWithAnalytics(),
this.getUsersWithAnalytics({ skip, take }) this.getUsersWithAnalytics({
skip,
take,
where: {
NOT: {
analytics: null
}
}
})
]); ]);
return { count, users }; return { count, users };
@ -814,17 +836,17 @@ export class AdminService {
private async getUsersWithAnalytics({ private async getUsersWithAnalytics({
skip, skip,
take take,
where
}: { }: {
skip?: number; skip?: number;
take?: number; take?: number;
where?: Prisma.UserWhereInput;
}): Promise<AdminUsersResponse['users']> { }): Promise<AdminUsersResponse['users']> {
let orderBy: Prisma.Enumerable<Prisma.UserOrderByWithRelationInput> = [ let orderBy: Prisma.Enumerable<Prisma.UserOrderByWithRelationInput> = [
{ createdAt: 'desc' } { createdAt: 'desc' }
]; ];
let where: Prisma.UserWhereInput;
if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) { if (this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION')) {
orderBy = [ orderBy = [
{ {
@ -833,12 +855,6 @@ export class AdminService {
} }
} }
]; ];
where = {
NOT: {
analytics: null
}
};
} }
const usersWithAnalytics = await this.prismaService.user.findMany({ const usersWithAnalytics = await this.prismaService.user.findMany({

13
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;
}

4
libs/common/src/lib/interfaces/index.ts

@ -7,6 +7,7 @@ import type {
AdminMarketData, AdminMarketData,
AdminMarketDataItem AdminMarketDataItem
} from './admin-market-data.interface'; } from './admin-market-data.interface';
import type { AdminUser } from './admin-user.interface';
import type { AssetClassSelectorOption } from './asset-class-selector-option.interface'; import type { AssetClassSelectorOption } from './asset-class-selector-option.interface';
import type { AssetProfileIdentifier } from './asset-profile-identifier.interface'; import type { AssetProfileIdentifier } from './asset-profile-identifier.interface';
import type { BenchmarkProperty } from './benchmark-property.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 { AccountsResponse } from './responses/accounts-response.interface';
import type { ActivitiesResponse } from './responses/activities-response.interface'; import type { ActivitiesResponse } from './responses/activities-response.interface';
import type { ActivityResponse } from './responses/activity-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 { AdminUsersResponse } from './responses/admin-users-response.interface';
import type { AiPromptResponse } from './responses/ai-prompt-response.interface'; import type { AiPromptResponse } from './responses/ai-prompt-response.interface';
import type { ApiKeyResponse } from './responses/api-key-response.interface'; import type { ApiKeyResponse } from './responses/api-key-response.interface';
@ -92,6 +94,8 @@ export {
AdminMarketData, AdminMarketData,
AdminMarketDataDetails, AdminMarketDataDetails,
AdminMarketDataItem, AdminMarketDataItem,
AdminUser,
AdminUserResponse,
AdminUsersResponse, AdminUsersResponse,
AiPromptResponse, AiPromptResponse,
ApiKeyResponse, ApiKeyResponse,

3
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 {}

14
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 { export interface AdminUsersResponse {
count: number; count: number;
users: { users: AdminUser[];
accountCount: number;
activityCount: number;
country: string;
createdAt: Date;
dailyApiRequests: number;
engagement: number;
id: string;
lastActivity: Date;
role: Role;
}[];
} }

Loading…
Cancel
Save