feat(announcements): add locale audience targeting and published-home news

This commit is contained in:
2026-02-12 23:05:39 +01:00
parent 741883465c
commit 60c9035743
10 changed files with 93 additions and 12 deletions

View File

@@ -1,11 +1,13 @@
import { z } from "zod"
export const announcementPlacementSchema = z.enum(["global_top", "homepage"])
export const announcementLocaleSchema = z.enum(["de", "en", "es", "fr"])
export const createAnnouncementInputSchema = z.object({
title: z.string().min(1).max(180),
message: z.string().min(1).max(500),
placement: announcementPlacementSchema.default("global_top"),
targetLocales: z.array(announcementLocaleSchema).default([]),
priority: z.number().int().min(0).default(100),
ctaLabel: z.string().max(120).nullable().optional(),
ctaHref: z.string().max(500).nullable().optional(),
@@ -19,6 +21,7 @@ export const updateAnnouncementInputSchema = z.object({
title: z.string().min(1).max(180).optional(),
message: z.string().min(1).max(500).optional(),
placement: announcementPlacementSchema.optional(),
targetLocales: z.array(announcementLocaleSchema).optional(),
priority: z.number().int().min(0).optional(),
ctaLabel: z.string().max(120).nullable().optional(),
ctaHref: z.string().max(500).nullable().optional(),

View File

@@ -0,0 +1,2 @@
ALTER TABLE "Announcement"
ADD COLUMN "targetLocales" TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[];

View File

@@ -405,6 +405,7 @@ model Announcement {
title String
message String
placement String
targetLocales String[] @default([])
priority Int @default(100)
ctaLabel String?
ctaHref String?

View File

@@ -41,13 +41,18 @@ describe("announcements service", () => {
it("queries only visible announcements in the given placement", async () => {
mockDb.announcement.findMany.mockResolvedValue([])
await listActiveAnnouncements("homepage")
await listActiveAnnouncements("homepage", new Date("2026-02-12T10:00:00.000Z"), "en")
expect(mockDb.announcement.findMany).toHaveBeenCalledTimes(1)
expect(mockDb.announcement.findMany.mock.calls[0]?.[0]).toMatchObject({
where: {
placement: "homepage",
isVisible: true,
AND: [
{
OR: [{ targetLocales: { isEmpty: true } }, { targetLocales: { has: "en" } }],
},
],
},
})
})

View File

@@ -13,6 +13,7 @@ export type PublicAnnouncement = {
ctaLabel: string | null
ctaHref: string | null
placement: string
targetLocales: string[]
priority: number
}
@@ -50,13 +51,26 @@ export async function deleteAnnouncement(id: string) {
export async function listActiveAnnouncements(
placement: AnnouncementPlacement,
now = new Date(),
locale?: string,
): Promise<PublicAnnouncement[]> {
const localeFilter =
locale && locale.length > 0
? {
AND: [
{
OR: [{ targetLocales: { isEmpty: true } }, { targetLocales: { has: locale } }],
},
],
}
: undefined
const announcements = await db.announcement.findMany({
where: {
placement,
isVisible: true,
OR: [{ startsAt: null }, { startsAt: { lte: now } }],
AND: [{ OR: [{ endsAt: null }, { endsAt: { gte: now } }] }],
...(localeFilter ?? {}),
},
orderBy: [{ priority: "asc" }, { createdAt: "desc" }],
select: {
@@ -66,6 +80,7 @@ export async function listActiveAnnouncements(
ctaLabel: true,
ctaHref: true,
placement: true,
targetLocales: true,
priority: true,
},
})