From a4840c7370402103fc2a07c7369fd5dfb86dc3d9 Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Thu, 9 Jul 2026 22:23:32 -0700 Subject: [PATCH] Fixing issues --- api/src/db/migrator.ts | 6 +- .../middlewares/authorization-middleware.ts | 2 +- .../users/find-from-auth0-token-service.ts | 35 ++++++++ api/src/services/users/index.ts | 1 + web/src/api/notifications-api.ts | 85 +++++++++++++++++++ .../components/common/ResponsiveDialog.vue | 2 +- web/src/pages/errors/ForbiddenPage.vue | 2 +- .../pages/errors/InternalServerErrorPage.vue | 2 +- web/src/pages/errors/NotFoundPage.vue | 2 +- web/src/pages/errors/UnauthorizedPage.vue | 2 +- web/src/plugins/vuetify-plugin.ts | 1 - web/src/use/use-user.ts | 26 ------ 12 files changed, 132 insertions(+), 34 deletions(-) create mode 100644 api/src/services/users/find-from-auth0-token-service.ts create mode 100644 web/src/api/notifications-api.ts diff --git a/api/src/db/migrator.ts b/api/src/db/migrator.ts index 3fa44cd..f8e9e01 100644 --- a/api/src/db/migrator.ts +++ b/api/src/db/migrator.ts @@ -1,5 +1,6 @@ import express, { Request, Response } from "express" import { join } from "path" +import { isArray } from "lodash" import { NODE_ENV } from "@/config" import dbMigrationClient from "@/db/db-migration-client" @@ -35,7 +36,10 @@ export class Migrator { this.migrationRouter.get("/seed/:environment", async (req: Request, res: Response) => { try { - await this.seedUp(req.params.environment) + const environment = isArray(req.params.environment) + ? req.params.environment[0] + : req.params.environment + await this.seedUp(environment) } catch (err) { logger.error(err) } diff --git a/api/src/middlewares/authorization-middleware.ts b/api/src/middlewares/authorization-middleware.ts index eb6ccdd..65e7c1a 100644 --- a/api/src/middlewares/authorization-middleware.ts +++ b/api/src/middlewares/authorization-middleware.ts @@ -38,7 +38,7 @@ export async function authorizationMiddleware( try { const token = req.headers.authorization || "" - const user = await Users.EnsureFromAuth0TokenService.perform(token) + const user = await Users.FindFromAuth0TokenService.perform(token) req.currentUser = user return next() } catch (error) { diff --git a/api/src/services/users/find-from-auth0-token-service.ts b/api/src/services/users/find-from-auth0-token-service.ts new file mode 100644 index 0000000..b37f6b2 --- /dev/null +++ b/api/src/services/users/find-from-auth0-token-service.ts @@ -0,0 +1,35 @@ +import { auth0Integration } from "@/integrations" +import { User } from "@/models" +import { Op } from "@sequelize/core" +import BaseService from "@/services/base-service" + +export class FindFromAuth0TokenService extends BaseService { + constructor(private token: string) { + super() + } + + async perform(): Promise { + const { auth0Subject, email } = await auth0Integration.getUserInfo(this.token) + + const existingUser = await User.withScope(["asCurrentUser"]).findOne({ + where: { auth0Subject }, + }) + + if (existingUser) { + return existingUser + } + + const firstTimeUser = await User.withScope(["asCurrentUser"]).findOne({ + where: { [Op.or]: [{ auth0Subject: email }, { email: email }] }, + }) + + if (firstTimeUser) { + await firstTimeUser.update({ auth0Subject }) + return firstTimeUser + } + + throw new Error("No user found for this token.") + } +} + +export default FindFromAuth0TokenService diff --git a/api/src/services/users/index.ts b/api/src/services/users/index.ts index edeffe5..2793bd6 100644 --- a/api/src/services/users/index.ts +++ b/api/src/services/users/index.ts @@ -4,3 +4,4 @@ export { DestroyService } from "./destroy-service" // Special Services export { EnsureFromAuth0TokenService } from "./ensure-from-auth0-token-service" +export { FindFromAuth0TokenService } from "./find-from-auth0-token-service" diff --git a/web/src/api/notifications-api.ts b/web/src/api/notifications-api.ts new file mode 100644 index 0000000..7779d11 --- /dev/null +++ b/web/src/api/notifications-api.ts @@ -0,0 +1,85 @@ +import http from "@/api/http-client" +import { type Policy } from "@/api/base-api" + +// Keep in sync with api/src/models/notification.ts +export enum NotificationSourceTypes { + SYSTEM = "system", +} + +export type Notification = { + id: number + userId: number + isRead: boolean + readDate: string | null + title: string + subtitle: string | null + href: string | null + sourceType: NotificationSourceTypes + createdAt: string + updatedAt: string +} + +export type NotificationWhereOptions = { + userId?: number + isRead?: boolean + sourceType?: NotificationSourceTypes +} + +export type NotificationFiltersOptions = { + createdTodayInUserTimezone?: string +} + +export const notificationsApi = { + async list( + params: { + where?: NotificationWhereOptions + filters?: NotificationFiltersOptions + page?: number + perPage?: number + } = {} + ): Promise<{ + notifications: Notification[] + totalCount: number + }> { + const { data } = await http.get("/api/notifications", { + params, + }) + return data + }, + async get(notificationId: number): Promise<{ + notification: Notification + policy: Policy + }> { + const { data } = await http.get(`/api/notifications/${notificationId}`) + return data + }, + async create(attributes: Partial): Promise<{ + notification: Notification + }> { + const { data } = await http.post("/api/notifications", attributes) + return data + }, + async update( + notificationId: number, + attributes: Partial + ): Promise<{ + notification: Notification + }> { + const { data } = await http.patch(`/api/notifications/${notificationId}`, attributes) + return data + }, + async delete(notificationId: number): Promise { + const { data } = await http.delete(`/api/notifications/${notificationId}`) + return data + }, + + // Special actions + async markAll({ isRead }: { isRead: boolean }): Promise { + const { data } = await http.post("/api/notifications/mark-all", { + isRead, + }) + return data + }, +} + +export default notificationsApi diff --git a/web/src/components/common/ResponsiveDialog.vue b/web/src/components/common/ResponsiveDialog.vue index b8d5d00..595ace6 100644 --- a/web/src/components/common/ResponsiveDialog.vue +++ b/web/src/components/common/ResponsiveDialog.vue @@ -46,7 +46,7 @@ import { computed } from "vue" import { useDisplay } from "vuetify" -import HeaderActionsCard from "@/components/shared/cards/HeaderActionsCard.vue" +import HeaderActionsCard from "@/components/common/HeaderActionsCard.vue" const props = withDefaults( defineProps<{ diff --git a/web/src/pages/errors/ForbiddenPage.vue b/web/src/pages/errors/ForbiddenPage.vue index a8baa60..2b91c8b 100644 --- a/web/src/pages/errors/ForbiddenPage.vue +++ b/web/src/pages/errors/ForbiddenPage.vue @@ -48,7 +48,7 @@