diff --git a/packages/client/src/faro.ts b/packages/client/src/faro.ts index fa83479c..f3df5fab 100644 --- a/packages/client/src/faro.ts +++ b/packages/client/src/faro.ts @@ -2,7 +2,9 @@ * This adds information about decorator parameters to the Grafana faro. To aid with debugging. * */ export function addFaroMetaData() { - if (!window.faro) return; + if (!window.faro) { + return; + } window.faro.api.setSession({ attributes: { @@ -10,6 +12,4 @@ export function addFaroMetaData() { decorator_params: JSON.stringify(window.__DECORATOR_DATA__.params), }, }); - - console.log("Faro: Adding metadata"); } diff --git a/packages/server/src/handlers/auth-handler.ts b/packages/server/src/handlers/auth-handler.ts index ca0b5237..4bbf1404 100644 --- a/packages/server/src/handlers/auth-handler.ts +++ b/packages/server/src/handlers/auth-handler.ts @@ -9,7 +9,7 @@ import { LogoutIcon } from "decorator-shared/views/icons/logout"; import { match } from "ts-pattern"; import { clientEnv, env } from "../env/server"; import i18n from "../i18n"; -import { getNotifications } from "../notifications"; +import { fetchNotifications } from "../notifications"; import { AnchorIconButton } from "../views/anchor-icon-button"; import { ArbeidsgiverUserMenuDropdown } from "../views/header/arbeidsgiver-user-menu-dropdown"; import { UserMenuDropdown } from "../views/header/user-menu-dropdown"; @@ -68,7 +68,7 @@ const buildUsermenuHtml = async ( // @TODO: Tests for important urls, like logout const template = await match(params.context) .with("privatperson", async () => { - const notificationsResult = await getNotifications({ cookie }); + const notificationsResult = await fetchNotifications({ cookie }); return UserMenuDropdown({ name: auth.name, diff --git a/packages/server/src/handlers/search-handler.ts b/packages/server/src/handlers/search-handler.ts index d1ce22d1..a4d27e2e 100644 --- a/packages/server/src/handlers/search-handler.ts +++ b/packages/server/src/handlers/search-handler.ts @@ -20,7 +20,7 @@ const resultSchema = z.object({ ) .catch((ctx) => { console.error( - `Error validating search hit - ${ctx.error.message}`, + `Error validating search hit - ${JSON.stringify(ctx.input)}`, ); return undefined; }), diff --git a/packages/server/src/notifications.test.ts b/packages/server/src/notifications.test.ts index d3c2b89b..8f79bbfc 100644 --- a/packages/server/src/notifications.test.ts +++ b/packages/server/src/notifications.test.ts @@ -12,7 +12,7 @@ import { env } from "./env/server"; import { Varsler, archiveNotification, - getNotifications, + fetchNotifications, } from "./notifications"; import { expectOK } from "./test-expect"; @@ -42,6 +42,15 @@ describe("notifications", () => { tekst: "wat", link: "http://example.com", }, + { + eventId: "c", + type: "invalidtype", + tidspunkt: "2023-07-05T11:43:02.280367+02:00", + isMasked: false, + eksternVarslingKanaler: ["SMS", "EPOST"], + tekst: "Invalid oppgave", + link: "http://example.com", + }, ], beskjeder: [ { @@ -66,7 +75,7 @@ describe("notifications", () => { afterAll(() => server.close()); test("returns transformed notifications", async () => { - const result = await getNotifications({ + const result = await fetchNotifications({ cookie: "cookie", }); @@ -108,4 +117,13 @@ describe("notifications", () => { expectOK(response); expect(response.data).toEqual("eventId"); }); + + test("Should filter out invalid notification", async () => { + const response = await fetchNotifications({ + cookie: "cookie", + }); + + expectOK(response); + expect(response.data).not.toContain({ tekst: "Invalid oppgave" }); + }); }); diff --git a/packages/server/src/notifications.ts b/packages/server/src/notifications.ts index f6608421..7a031c90 100644 --- a/packages/server/src/notifications.ts +++ b/packages/server/src/notifications.ts @@ -3,22 +3,31 @@ import { env } from "./env/server"; import { Result, ResultType } from "./result"; import { fetchAndValidateJson } from "./lib/fetch-and-validate"; -const varselSchema = z.object({ - eventId: z.string(), - type: z.enum(["oppgave", "beskjed", "innboks"]), - tidspunkt: z.string(), - isMasked: z.boolean(), - tekst: z.string().nullable(), - link: z.string().nullable(), - eksternVarslingKanaler: z.array(z.string()), -}); +const varselSchema = z + .object({ + eventId: z.string(), + type: z.enum(["oppgave", "beskjed", "innboks"]), + tidspunkt: z.string(), + isMasked: z.boolean(), + tekst: z.string().nullable(), + link: z.string().nullable(), + eksternVarslingKanaler: z.array(z.string()), + }) + .nullable() + .catch((ctx) => { + console.error( + `Error validating notification - ${JSON.stringify(ctx.input)}`, + ); + return null; + }); const varslerSchema = z.object({ oppgaver: z.array(varselSchema), beskjeder: z.array(varselSchema), }); -type Varsel = z.infer; +type VarselNullable = z.infer; +type Varsel = NonNullable; export type Varsler = z.infer; @@ -50,34 +59,37 @@ const translateNotificationType = { innboks: "inbox", }; -const sortVarslerNewestFirst = (a: Varsel, b: Varsel) => +const sortNewestFirst = (a: Varsel, b: Varsel) => a.tidspunkt > b.tidspunkt ? -1 : 1; +const filterAndSort = (varsler: VarselNullable[]): Varsel[] => + varsler + .filter((varsel): varsel is Varsel => !!varsel) + .sort(sortNewestFirst); + const varslerToNotifications = (varsler: Varsler): Notification[] => - [ - varsler.oppgaver.sort(sortVarslerNewestFirst), - varsler.beskjeder.sort(sortVarslerNewestFirst), - ].flatMap((list) => - list.map( - (varsel: Varsel): Notification => ({ - id: varsel.eventId, - type: translateNotificationType[ - varsel.type - ] as NotificationType, - date: varsel.tidspunkt, - channels: varsel.eksternVarslingKanaler, - ...(varsel.isMasked - ? { masked: true } - : { - masked: false, - text: varsel.tekst ?? "", - link: varsel.link ?? undefined, - }), - }), - ), + [filterAndSort(varsler.oppgaver), filterAndSort(varsler.beskjeder)].flatMap( + (list) => + list.map( + (varsel): Notification => ({ + id: varsel.eventId, + type: translateNotificationType[ + varsel.type + ] as NotificationType, + date: varsel.tidspunkt, + channels: varsel.eksternVarslingKanaler, + ...(varsel.isMasked + ? { masked: true } + : { + masked: false, + text: varsel.tekst ?? "", + link: varsel.link ?? undefined, + }), + }), + ), ); -export const getNotifications = async ({ +export const fetchNotifications = async ({ cookie, }: { cookie: string; @@ -90,7 +102,10 @@ export const getNotifications = async ({ varslerSchema, ).then((result) => result.ok - ? { ...result, data: varslerToNotifications(result.data) } + ? { + ...result, + data: varslerToNotifications(result.data as Varsler), + } : result, ); }; diff --git a/packages/server/src/server.ts b/packages/server/src/server.ts index 9291dc5f..f902f2da 100644 --- a/packages/server/src/server.ts +++ b/packages/server/src/server.ts @@ -21,6 +21,7 @@ import { csrAssets } from "./views"; import { MainMenu } from "./views/header/main-menu"; import { texts } from "./texts"; import { clientTextsKeys } from "decorator-shared/types"; +import { trimTrailingSlash } from "hono/trailing-slash"; const app = new Hono(); @@ -34,6 +35,7 @@ if (env.NODE_ENV === "development" || env.IS_LOCAL_PROD) { } app.use(headers); +app.use(trimTrailingSlash()); app.get("/public/assets/*", serveStatic({})); @@ -164,9 +166,8 @@ app.get("/", async ({ req, html }) => { }); app.route("/decorator-next", app); -app.route("/decorator-next/", app); app.route("/dekoratoren", app); -app.route("/dekoratoren/", app); +app.route("/common-html/v4/navno", app); export default { ...app, diff --git a/packages/server/src/texts.ts b/packages/server/src/texts.ts index 01701a3c..434afe7f 100644 --- a/packages/server/src/texts.ts +++ b/packages/server/src/texts.ts @@ -84,7 +84,7 @@ const nb = { delskjerm_modal_stengt: "Skjermdeling er for øyeblikket stengt, prøv igjen senere.", security_level_info: - "Du har logget inn med Min ID. Hvis du logger inn med et høyere sikkerhetsnivå, får du se mer innhold og flere tjenester.", + "Du har logget inn med MinID. Hvis du logger inn med et høyere sikkerhetsnivå, får du se mer innhold og flere tjenester.", security_level_link: "Logg inn med BankID, Buypass, eller Commfides.", go_to_my_page: "Gå til Min side", my_page: "Min side", @@ -179,7 +179,7 @@ const en = { delskjerm_modal_stengt: "Screen sharing is currently closed, please try again later.", security_level_info: - "You are logged in with Min ID. If you log in with a higher security level, you will see more content and additional services.", + "You are logged in with MinID. If you log in with a higher security level, you will see more content and additional services.", security_level_link: "Log in with BankID, Buypass, or Commfides.", go_to_my_page: "Go to my page", my_page: "My page",