feat(web): user detail page ()

feat: user detail page
This commit is contained in:
Jason Rasmussen 2025-05-12 16:50:26 -04:00 committed by GitHub
parent eb8dfa283e
commit 3066c8198c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 640 additions and 160 deletions

View file

@ -1,5 +1,6 @@
import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { AssetStatsDto, AssetStatsResponseDto } from 'src/dtos/asset.dto';
import { AuthDto } from 'src/dtos/auth.dto';
import { UserPreferencesResponseDto, UserPreferencesUpdateDto } from 'src/dtos/user-preferences.dto';
import {
@ -57,6 +58,16 @@ export class UserAdminController {
return this.service.delete(auth, id, dto);
}
@Get(':id/statistics')
@Authenticated({ permission: Permission.ADMIN_USER_READ, admin: true })
getUserStatisticsAdmin(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@Query() dto: AssetStatsDto,
): Promise<AssetStatsResponseDto> {
return this.service.getStatistics(auth, id, dto);
}
@Get(':id/preferences')
@Authenticated({ permission: Permission.ADMIN_USER_READ, admin: true })
getUserPreferencesAdmin(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserPreferencesResponseDto> {

View file

@ -4,7 +4,7 @@ import { IsBoolean, IsEmail, IsEnum, IsNotEmpty, IsNumber, IsString, Min } from
import { User, UserAdmin } from 'src/database';
import { UserAvatarColor, UserMetadataKey, UserStatus } from 'src/enum';
import { UserMetadataItem } from 'src/types';
import { Optional, PinCode, ValidateBoolean, toEmail, toSanitized } from 'src/validation';
import { Optional, PinCode, ValidateBoolean, ValidateUUID, toEmail, toSanitized } from 'src/validation';
export class UserUpdateMeDto {
@Optional()
@ -67,6 +67,9 @@ export const mapUser = (entity: User | UserAdmin): UserResponseDto => {
export class UserAdminSearchDto {
@ValidateBoolean({ optional: true })
withDeleted?: boolean;
@ValidateUUID({ optional: true })
id?: string;
}
export class UserAdminCreateDto {

View file

@ -14,6 +14,7 @@ import { asUuid } from 'src/utils/database';
type Upsert = Insertable<DbUserMetadata>;
export interface UserListFilter {
id?: string;
withDeleted?: boolean;
}
@ -141,12 +142,13 @@ export class UserRepository {
{ name: 'with deleted', params: [{ withDeleted: true }] },
{ name: 'without deleted', params: [{ withDeleted: false }] },
)
getList({ withDeleted }: UserListFilter = {}) {
getList({ id, withDeleted }: UserListFilter = {}) {
return this.db
.selectFrom('users')
.select(columns.userAdmin)
.select(withMetadata)
.$if(!withDeleted, (eb) => eb.where('users.deletedAt', 'is', null))
.$if(!!id, (eb) => eb.where('users.id', '=', id!))
.orderBy('createdAt', 'desc')
.execute();
}

View file

@ -1,5 +1,6 @@
import { BadRequestException, ForbiddenException, Injectable } from '@nestjs/common';
import { SALT_ROUNDS } from 'src/constants';
import { AssetStatsDto, AssetStatsResponseDto, mapStats } from 'src/dtos/asset.dto';
import { AuthDto } from 'src/dtos/auth.dto';
import { UserPreferencesResponseDto, UserPreferencesUpdateDto, mapPreferences } from 'src/dtos/user-preferences.dto';
import {
@ -18,7 +19,10 @@ import { getPreferences, getPreferencesPartial, mergePreferences } from 'src/uti
@Injectable()
export class UserAdminService extends BaseService {
async search(auth: AuthDto, dto: UserAdminSearchDto): Promise<UserAdminResponseDto[]> {
const users = await this.userRepository.getList({ withDeleted: dto.withDeleted });
const users = await this.userRepository.getList({
id: dto.id,
withDeleted: dto.withDeleted,
});
return users.map((user) => mapUserAdmin(user));
}
@ -109,6 +113,11 @@ export class UserAdminService extends BaseService {
return mapUserAdmin(user);
}
async getStatistics(auth: AuthDto, id: string, dto: AssetStatsDto): Promise<AssetStatsResponseDto> {
const stats = await this.assetRepository.getStatistics(auth.user.id, dto);
return mapStats(stats);
}
async getPreferences(auth: AuthDto, id: string): Promise<UserPreferencesResponseDto> {
await this.findOrFail(id, { withDeleted: true });
const metadata = await this.userRepository.getMetadata(id);