show notice creators with admin avatars
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-07-10 11:11:32 +09:00
parent 609c345739
commit 6311f8a553
5 changed files with 86 additions and 8 deletions
+4
View File
@@ -19,10 +19,14 @@ model LauncherNotice {
startsAt DateTime? @map("starts_at") @db.Timestamptz(3)
endsAt DateTime? @map("ends_at") @db.Timestamptz(3)
priority Int @default(0)
createdById String? @map("created_by_id") @db.Uuid
createdByName String? @map("created_by_name")
createdByEmail String? @map("created_by_email")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3)
updatedAt DateTime @updatedAt @map("updated_at") @db.Timestamptz(3)
@@index([appKey, isActive, priority])
@@index([createdByEmail])
@@map("launcher_notices")
}
@@ -0,0 +1,16 @@
DO $$
BEGIN
IF to_regclass('public.launcher_notices') IS NOT NULL THEN
ALTER TABLE "launcher_notices"
ADD COLUMN IF NOT EXISTS "created_by_id" UUID,
ADD COLUMN IF NOT EXISTS "created_by_name" TEXT,
ADD COLUMN IF NOT EXISTS "created_by_email" TEXT;
END IF;
END $$;
DO $$
BEGIN
IF to_regclass('public.launcher_notices') IS NOT NULL THEN
CREATE INDEX IF NOT EXISTS "launcher_notices_created_by_email_idx" ON "launcher_notices"("created_by_email");
END IF;
END $$;
+6 -2
View File
@@ -7,6 +7,7 @@ import {
Patch,
Post,
Query,
Req,
} from '@nestjs/common';
import {
@@ -31,8 +32,11 @@ export class NoticesController {
}
@Post()
create(@Body() body: CreateLauncherNoticeDto) {
return this.noticesService.create(body);
create(
@Body() body: CreateLauncherNoticeDto,
@Req() request: { admin?: { id: string; name: string; email: string } },
) {
return this.noticesService.create(body, request.admin);
}
@Patch(':id')
+2 -1
View File
@@ -1,11 +1,12 @@
import { Module } from '@nestjs/common';
import { AuthPrismaModule } from '../auth-prisma/auth-prisma.module';
import { LauncherPrismaModule } from '../launcher-prisma/launcher-prisma.module';
import { NoticesController } from './notices.controller';
import { NoticesService } from './notices.service';
@Module({
imports: [LauncherPrismaModule],
imports: [AuthPrismaModule, LauncherPrismaModule],
controllers: [NoticesController],
providers: [NoticesService],
})
+58 -5
View File
@@ -1,16 +1,66 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Prisma } from '@prisma/launcher-client';
import { AuthPrismaService } from '../auth-prisma/auth-prisma.service';
import { LauncherPrismaService } from '../launcher-prisma/launcher-prisma.service';
import { getUserAvatarUrl } from '../users/users.service';
import {
CreateLauncherNoticeDto,
ListLauncherNoticesQuery,
UpdateLauncherNoticeDto,
} from './dto';
type AdminRequester = {
id: string;
name: string;
email: string;
};
@Injectable()
export class NoticesService {
constructor(private readonly launcherPrisma: LauncherPrismaService) {}
constructor(
private readonly authPrisma: AuthPrismaService,
private readonly configService: ConfigService,
private readonly launcherPrisma: LauncherPrismaService,
) {}
private creatorData(requester?: AdminRequester) {
return requester
? {
createdById: requester.id,
createdByName: requester.name,
createdByEmail: requester.email,
}
: {};
}
private async withCreatorAvatars<T extends { createdByEmail?: string | null }>(items: T[]) {
const emails = [...new Set(items.map((item) => item.createdByEmail).filter((email): email is string => Boolean(email)))];
if (emails.length === 0) return items.map((item) => ({ ...item, createdByAvatarUrl: null }));
const users = await this.authPrisma.user.findMany({
where: { email: { in: emails } },
select: {
id: true,
email: true,
profileImageMime: true,
},
});
const userByEmail = new Map(users.map((user) => [user.email, user]));
const authBaseUrl = this.configService.get<string>('AUTH_PUBLIC_URL')
?? this.configService.get<string>('ATNMY_AUTH_URL')
?? this.configService.get<string>('AUTH_EXTERNAL_URL')
?? 'https://auth.autonomystudio.net';
return items.map((item) => {
const user = item.createdByEmail ? userByEmail.get(item.createdByEmail) : null;
return {
...item,
createdByAvatarUrl: getUserAvatarUrl(user?.id ?? '', user?.profileImageMime, authBaseUrl),
};
});
}
async list(query: ListLauncherNoticesQuery) {
const keyword = query.keyword?.trim();
@@ -37,11 +87,11 @@ export class NoticesService {
});
return {
notices: notices.map((notice, index) => ({
notices: await this.withCreatorAvatars(notices.map((notice, index) => ({
...notice,
no: notices.length - index,
status: getNoticeStatus(notice, now),
})),
}))),
};
}
@@ -57,9 +107,12 @@ export class NoticesService {
return { notice };
}
async create(body: CreateLauncherNoticeDto) {
async create(body: CreateLauncherNoticeDto, requester?: AdminRequester) {
const notice = await this.launcherPrisma.launcherNotice.create({
data: toCreateNoticeData(body),
data: {
...toCreateNoticeData(body),
...this.creatorData(requester),
},
});
return { notice };