From c1bb1fd29d57c383c2e7504bb57282feee986492 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Wed, 7 Jan 2026 22:18:48 +0000 Subject: [PATCH] refactor me service --- .../pages/maintenance/MaintenanceCreate.tsx | 4 +- server/src/init/services.ts | 10 +- server/src/repositories/index.ts | 5 +- .../INotificationChannelRepository.ts | 7 ++ .../MongoNotificationChannelRepository.ts | 5 + ...ository.ts => IOrgMembershipRepository.ts} | 0 .../src/repositories/user/IUserRepository.ts | 1 + .../repositories/user/MongoUserRepository.ts | 6 ++ server/src/services/business/MeService.ts | 93 ++++++++++++------- 9 files changed, 91 insertions(+), 40 deletions(-) create mode 100644 server/src/repositories/notification-channels/INotificationChannelRepository.ts create mode 100644 server/src/repositories/notification-channels/MongoNotificationChannelRepository.ts rename server/src/repositories/org-membership/{IOrgMembershopRepository.ts => IOrgMembershipRepository.ts} (100%) diff --git a/client/src/pages/maintenance/MaintenanceCreate.tsx b/client/src/pages/maintenance/MaintenanceCreate.tsx index 9d754a490..9747d8172 100644 --- a/client/src/pages/maintenance/MaintenanceCreate.tsx +++ b/client/src/pages/maintenance/MaintenanceCreate.tsx @@ -4,12 +4,12 @@ import { useNavigate } from "react-router"; import { usePost, useGet } from "@/hooks/UseApi"; import type { ApiResponse } from "@/types/api"; import type { IMonitor } from "@/types/monitor"; -import type { IMaintenance } from "@/types/maintenance"; +import type { Maintenance } from "@/types/maintenance"; import type { FormValues } from "@/pages/maintenance/MaintenanceForm"; const MaintenanceCreatePage = () => { const navigate = useNavigate(); - const { post, loading } = usePost(); + const { post, loading } = usePost(); const { response, loading: monitorsLoading } = useGet>("/monitors"); diff --git a/server/src/init/services.ts b/server/src/init/services.ts index 4429f1abb..29c121a75 100644 --- a/server/src/init/services.ts +++ b/server/src/init/services.ts @@ -125,7 +125,15 @@ export const initServices = async () => { teamRepository, teamMembershipRepository ); - const meService = new MeService(entitlementsProvider); + const meService = new MeService( + entitlementsProvider, + userRepository, + orgRepository, + orgMembershipRepository, + teamRepository, + teamMembershipRepository, + roleRepository + ); const monitorService = new MonitorService( jobQueue, monitorRepository, diff --git a/server/src/repositories/index.ts b/server/src/repositories/index.ts index 08e943270..8ad9ec686 100644 --- a/server/src/repositories/index.ts +++ b/server/src/repositories/index.ts @@ -31,8 +31,11 @@ export { default as MongoTeamMembershipRepository } from "@/repositories/team-me export type { IOrgRepository } from "@/repositories/org/IOrgRepository.js"; export { default as MongoOrgRepository } from "@/repositories/org/MongoOrgRepository.js"; -export type { IOrgMembershipRepository } from "@/repositories/org-membership/IOrgMembershopRepository.js"; +export type { IOrgMembershipRepository } from "@/repositories/org-membership/IOrgMembershipRepository.js"; export { default as MongoOrgMembershipRepository } from "@/repositories/org-membership/MongoOrgMembershipRepository.js"; export type { IMaintenanceRepository } from "@/repositories/maintenance/IMaintenanceRepository.js"; export { default as MongoMaintenanceRepository } from "@/repositories/maintenance/MongoMaintenanceRepository.js"; + +export type { INotificationChannelRepository } from "@/repositories/notification-channels/INotificationChannelRepository.js"; +export { default as MongoNotificationChannelRepository } from "@/repositories/notification-channels/MongoNotificationChannelRepository.js"; diff --git a/server/src/repositories/notification-channels/INotificationChannelRepository.ts b/server/src/repositories/notification-channels/INotificationChannelRepository.ts new file mode 100644 index 000000000..048525eb0 --- /dev/null +++ b/server/src/repositories/notification-channels/INotificationChannelRepository.ts @@ -0,0 +1,7 @@ +export interface INotificationChannelRepository { + // create + // single fetch + // collection fetch + // update + // delete +} diff --git a/server/src/repositories/notification-channels/MongoNotificationChannelRepository.ts b/server/src/repositories/notification-channels/MongoNotificationChannelRepository.ts new file mode 100644 index 000000000..ef68ffd36 --- /dev/null +++ b/server/src/repositories/notification-channels/MongoNotificationChannelRepository.ts @@ -0,0 +1,5 @@ +import { INotificationChannelRepository } from "@/repositories/index.js"; + +class MongoNotificationRepository implements INotificationChannelRepository {} + +export default MongoNotificationRepository; diff --git a/server/src/repositories/org-membership/IOrgMembershopRepository.ts b/server/src/repositories/org-membership/IOrgMembershipRepository.ts similarity index 100% rename from server/src/repositories/org-membership/IOrgMembershopRepository.ts rename to server/src/repositories/org-membership/IOrgMembershipRepository.ts diff --git a/server/src/repositories/user/IUserRepository.ts b/server/src/repositories/user/IUserRepository.ts index f99595118..effb87211 100644 --- a/server/src/repositories/user/IUserRepository.ts +++ b/server/src/repositories/user/IUserRepository.ts @@ -4,6 +4,7 @@ export interface IUserRepository { create(userData: Partial): Promise; // single fetch findByEmail: (email: string) => Promise; + findByUserId: (userId: string) => Promise; // collection fetch // update updateById: ( diff --git a/server/src/repositories/user/MongoUserRepository.ts b/server/src/repositories/user/MongoUserRepository.ts index 8fe5b0deb..e343a690e 100644 --- a/server/src/repositories/user/MongoUserRepository.ts +++ b/server/src/repositories/user/MongoUserRepository.ts @@ -28,6 +28,12 @@ class MongoUserRepository implements IUserRepository { return this.toEntity(user); }; + findByUserId = async (userId: string) => { + const user = await User.findById(userId); + if (!user) return null; + return this.toEntity(user); + }; + updateById = async (userId: string, updateData: Partial) => { const result = await User.findOneAndUpdate( { _id: userId }, diff --git a/server/src/services/business/MeService.ts b/server/src/services/business/MeService.ts index fcf440d59..14bb6783d 100644 --- a/server/src/services/business/MeService.ts +++ b/server/src/services/business/MeService.ts @@ -1,16 +1,15 @@ -import { - User, - Org, - OrgMembership, - Team, - TeamMembership, - Role, - IRole, -} from "@/db/models/index.js"; import ApiError from "@/utils/ApiError.js"; import type { IEntitlementsProvider } from "@/services/system/EntitlementsService.js"; import type { Entitlements } from "@/types/entitlements.js"; import type { UserReturnable } from "@/types/domain/index.js"; +import type { + IUserRepository, + IOrgRepository, + IOrgMembershipRepository, + ITeamRepository, + ITeamMembershipRepository, + IRoleRepository, +} from "@/repositories/index.js"; export interface IMeService { me(userId: string): Promise; @@ -26,73 +25,95 @@ const SERVICE_NAME = "MeService"; class MeService implements IMeService { public SERVICE_NAME = SERVICE_NAME; private entitlementsProvider: IEntitlementsProvider; + private userRepository: IUserRepository; + private orgRepository: IOrgRepository; + private orgMembershipRepository: IOrgMembershipRepository; + private teamRepository: ITeamRepository; + private teamMembershipRepository: ITeamMembershipRepository; + private roleRepository: IRoleRepository; - constructor(entitlementsProvider: IEntitlementsProvider) { + constructor( + entitlementsProvider: IEntitlementsProvider, + userRepository: IUserRepository, + orgRepository: IOrgRepository, + orgMembershipRepository: IOrgMembershipRepository, + teamRepository: ITeamRepository, + teamMembershipRepository: ITeamMembershipRepository, + roleRepository: IRoleRepository + ) { this.entitlementsProvider = entitlementsProvider; + this.userRepository = userRepository; + this.orgRepository = orgRepository; + this.orgMembershipRepository = orgMembershipRepository; + this.teamRepository = teamRepository; + this.teamMembershipRepository = teamMembershipRepository; + this.roleRepository = roleRepository; } me = async (userId: string): Promise => { // Need to get teamIds - const user = await User.findById(userId); + const user = await this.userRepository.findByUserId(userId); if (!user) { throw new ApiError("User not found", 404); } // Find OrgMembership - const orgMembership = await OrgMembership.findOne({ - userId: user._id, - }).lean(); + const orgMembership = await this.orgMembershipRepository.findByUserId( + user.id + ); if (!orgMembership) { throw new ApiError("User is not part of any organization"); } // Find org and roles - const org = await Org.findById(orgMembership.orgId).lean(); + const org = await this.orgRepository.findById(orgMembership.orgId); if (!org) { throw new ApiError("Organization not found"); } - const orgRoles = await Role.findById(orgMembership.roleId).lean(); - - // Get teams - const teamMembershipWithRoles = await TeamMembership.find({ - userId: user._id, - }) - .populate<{ roleId: IRole }>("roleId") - .lean(); - - const teamIds = teamMembershipWithRoles.map((tm) => tm.teamId.toString()); - - const teams = await Team.find({ _id: { $in: teamIds } }).select( - "_id, name" + const orgRole = await this.roleRepository.findById( + orgMembership.roleId || "", + org.id ); - const teamMap = new Map(teams.map((t) => [t._id.toString(), t])); - const returnableTeams = teamMembershipWithRoles.map((tm) => { - const team = teamMap.get(tm.teamId.toString()); + // Get teams + const teamMembershipsWithRole = + await this.teamMembershipRepository.findByUserIdWithRole(user.id); + + if (!teamMembershipsWithRole) { + throw new ApiError("Team memberships not found for user"); + } + + const teamIds = teamMembershipsWithRole.map((tm) => tm.teamId); + + const teams = await this.teamRepository.findManyById(teamIds); + + const teamMap = new Map(teams.map((t) => [t.id, t])); + const returnableTeams = teamMembershipsWithRole.map((tm) => { + const team = teamMap.get(tm.teamId); if (!team) { throw new ApiError("Team not found for membership"); } return { - id: team._id.toString(), + id: team.id, name: team.name, - permissions: tm.roleId?.permissions ?? [], + permissions: tm.role?.permissions ?? [], }; }); const entitlements: Entitlements = - await this.entitlementsProvider.getForOrg(org._id.toString()); + await this.entitlementsProvider.getForOrg(org.id); const returnableUser: UserReturnable = { - id: user._id.toString(), + id: user.id, email: user.email, firstName: user.firstName, lastName: user.lastName, org: { name: org.name, planKey: org.planKey, - permissions: orgRoles?.permissions || [], + permissions: orgRole?.permissions || [], }, teams: returnableTeams, entitlements,