diff --git a/prisma/auth.schema.prisma b/prisma/auth.schema.prisma index e62e858..2939638 100644 --- a/prisma/auth.schema.prisma +++ b/prisma/auth.schema.prisma @@ -33,6 +33,9 @@ model User { email String @unique passwordHash String @map("password_hash") 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) isActive Boolean @default(true) @map("is_active") lastLoginAt DateTime? @map("last_login_at") @db.Timestamptz(3) diff --git a/src/users/users.service.ts b/src/users/users.service.ts index 1c1af9f..7c365d9 100644 --- a/src/users/users.service.ts +++ b/src/users/users.service.ts @@ -1,4 +1,5 @@ import { Injectable, NotFoundException } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; import { AuthPrismaService } from '../auth-prisma/auth-prisma.service'; import { ListUserSessionsQuery } from './dto'; @@ -9,6 +10,7 @@ type UserSessionRow = { userId: string; user: string; email: string; + avatarUrl: string | null; role: string; session: string; createdAt: Date | null; @@ -27,7 +29,10 @@ type UserSessionRow = { @Injectable() export class UsersService { - constructor(private readonly authPrisma: AuthPrismaService) {} + constructor( + private readonly authPrisma: AuthPrismaService, + private readonly configService: ConfigService, + ) {} async listSessions(query: ListUserSessionsQuery) { const keyword = query.keyword?.trim(); @@ -71,6 +76,13 @@ export class UsersService { }); const rows: UserSessionRow[] = users.flatMap((user): UserSessionRow[] => { + const avatarUrl = getUserAvatarUrl( + user.id, + user.profileImageMime, + this.configService.get('AUTH_PUBLIC_URL') + ?? this.configService.get('ATNMY_AUTH_URL') + ?? this.configService.get('AUTH_SERVER_URL'), + ); const primaryAffiliation = user.affiliations[0] ? { id: user.affiliations[0].id, @@ -90,6 +102,7 @@ export class UsersService { userId: user.id, user: user.name || user.email, email: user.email, + avatarUrl, role: user.role, session: 'μ„Έμ…˜ μ—†μŒ', createdAt: null, @@ -107,6 +120,7 @@ export class UsersService { userId: user.id, user: user.name || user.email, email: user.email, + avatarUrl, role: user.role, session: token.tokenHash.slice(0, 10), 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( isUserActive: boolean, revokedAt: Date | null,