add avatar user list
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-07-09 11:20:23 +09:00
parent dceb20b26a
commit ca35c30f87
2 changed files with 32 additions and 1 deletions
+3
View File
@@ -33,6 +33,9 @@ model User {
email String @unique email String @unique
passwordHash String @map("password_hash") passwordHash String @map("password_hash")
name String? name String?
profileImageData Bytes? @map("profile_image_data")
profileImageMime String? @map("profile_image_mime")
profileImageUpdatedAt DateTime? @map("profile_image_updated_at") @db.Timestamptz(3)
role UserRole @default(USER) role UserRole @default(USER)
isActive Boolean @default(true) @map("is_active") isActive Boolean @default(true) @map("is_active")
lastLoginAt DateTime? @map("last_login_at") @db.Timestamptz(3) lastLoginAt DateTime? @map("last_login_at") @db.Timestamptz(3)
+29 -1
View File
@@ -1,4 +1,5 @@
import { Injectable, NotFoundException } from '@nestjs/common'; import { Injectable, NotFoundException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { AuthPrismaService } from '../auth-prisma/auth-prisma.service'; import { AuthPrismaService } from '../auth-prisma/auth-prisma.service';
import { ListUserSessionsQuery } from './dto'; import { ListUserSessionsQuery } from './dto';
@@ -9,6 +10,7 @@ type UserSessionRow = {
userId: string; userId: string;
user: string; user: string;
email: string; email: string;
avatarUrl: string | null;
role: string; role: string;
session: string; session: string;
createdAt: Date | null; createdAt: Date | null;
@@ -27,7 +29,10 @@ type UserSessionRow = {
@Injectable() @Injectable()
export class UsersService { export class UsersService {
constructor(private readonly authPrisma: AuthPrismaService) {} constructor(
private readonly authPrisma: AuthPrismaService,
private readonly configService: ConfigService,
) {}
async listSessions(query: ListUserSessionsQuery) { async listSessions(query: ListUserSessionsQuery) {
const keyword = query.keyword?.trim(); const keyword = query.keyword?.trim();
@@ -71,6 +76,13 @@ export class UsersService {
}); });
const rows: UserSessionRow[] = users.flatMap((user): UserSessionRow[] => { const rows: UserSessionRow[] = users.flatMap((user): UserSessionRow[] => {
const avatarUrl = getUserAvatarUrl(
user.id,
user.profileImageMime,
this.configService.get<string>('AUTH_PUBLIC_URL')
?? this.configService.get<string>('ATNMY_AUTH_URL')
?? this.configService.get<string>('AUTH_SERVER_URL'),
);
const primaryAffiliation = user.affiliations[0] const primaryAffiliation = user.affiliations[0]
? { ? {
id: user.affiliations[0].id, id: user.affiliations[0].id,
@@ -90,6 +102,7 @@ export class UsersService {
userId: user.id, userId: user.id,
user: user.name || user.email, user: user.name || user.email,
email: user.email, email: user.email,
avatarUrl,
role: user.role, role: user.role,
session: '세션 없음', session: '세션 없음',
createdAt: null, createdAt: null,
@@ -107,6 +120,7 @@ export class UsersService {
userId: user.id, userId: user.id,
user: user.name || user.email, user: user.name || user.email,
email: user.email, email: user.email,
avatarUrl,
role: user.role, role: user.role,
session: token.tokenHash.slice(0, 10), session: token.tokenHash.slice(0, 10),
createdAt: token.createdAt, createdAt: token.createdAt,
@@ -194,6 +208,20 @@ export class UsersService {
} }
} }
function getUserAvatarUrl(
userId: string,
mimeType?: string | null,
authBaseUrl?: string | null,
) {
if (!mimeType || !authBaseUrl) {
return null;
}
const normalizedBaseUrl = authBaseUrl.replace(/\/+$/, '');
return `${normalizedBaseUrl}/auth/users/${encodeURIComponent(userId)}/avatar`;
}
function getSessionState( function getSessionState(
isUserActive: boolean, isUserActive: boolean,
revokedAt: Date | null, revokedAt: Date | null,