diff --git a/server/src/init/services.ts b/server/src/init/services.ts index 5d23813e5..0f9961fcb 100644 --- a/server/src/init/services.ts +++ b/server/src/init/services.ts @@ -43,6 +43,7 @@ import { MongoTeamRepository, MongoInviteRepository, MongoTeamMembershipRepository, + MongoOrgRepository, } from "@/repositories/index.js"; export const initServices = async () => { @@ -55,6 +56,7 @@ export const initServices = async () => { const teamRepository = new MongoTeamRepository(); const inviteRepository = new MongoInviteRepository(); const teamMembershipRepository = new MongoTeamMembershipRepository(); + const mongoOrgRepository = new MongoOrgRepository(); const checkService = new CheckService(checksRepository, monitorRepository); const inviteService = new InviteService( @@ -108,7 +110,11 @@ export const initServices = async () => { const authService = new AuthService( jobQueue, entitlementsProvider, - monitorRepository + monitorRepository, + userRepository, + mongoOrgRepository, + teamRepository, + teamMembershipRepository ); const meService = new MeService(entitlementsProvider); const monitorService = new MonitorService( diff --git a/server/src/repositories/index.ts b/server/src/repositories/index.ts index 79d1c0cbc..05d45d6ca 100644 --- a/server/src/repositories/index.ts +++ b/server/src/repositories/index.ts @@ -27,3 +27,9 @@ export { default as MongoTeamRepository } from "@/repositories/team/MongoTeamRep export type { ITeamMembershipRepository } from "@/repositories/team-membership/ITeamMembershipRepository.js"; export { default as MongoTeamMembershipRepository } from "@/repositories/team-membership/MongoTeamMembershipRepository.js"; + +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 { default as MongoOrgMembershipRepository } from "@/repositories/org-membership/MongoOrgMembershipRepository.js"; diff --git a/server/src/repositories/org-membership/IOrgMembershopRepository.ts b/server/src/repositories/org-membership/IOrgMembershopRepository.ts new file mode 100644 index 000000000..df2b9ef3d --- /dev/null +++ b/server/src/repositories/org-membership/IOrgMembershopRepository.ts @@ -0,0 +1,7 @@ +export interface IOrgMembershipRepository { + // create + // single fetch + // collection fetch + // update + // delete +} diff --git a/server/src/repositories/org-membership/MongoOrgMembershipRepository.ts b/server/src/repositories/org-membership/MongoOrgMembershipRepository.ts new file mode 100644 index 000000000..dc149ab15 --- /dev/null +++ b/server/src/repositories/org-membership/MongoOrgMembershipRepository.ts @@ -0,0 +1,19 @@ +import { type IOrgMembership, OrgMembership } from "@/db/models/index.js"; +import { IOrgMembershipRepository } from "@/repositories/index.js"; +import { OrgMembership as OrgMembershipEntity } from "@/types/domain/index.js"; + +class MongoOrgMembershipRepository implements IOrgMembershipRepository { + private toEntity = (doc: IOrgMembership): OrgMembershipEntity => { + return { + id: doc._id.toString(), + orgId: doc.orgId.toString(), + userId: doc.userId.toString(), + roleId: doc.roleId?.toString(), + role: doc.roleId?.toString() || "", + createdAt: doc.createdAt, + updatedAt: doc.updatedAt, + }; + }; +} + +export default MongoOrgMembershipRepository; diff --git a/server/src/repositories/org/IOrgRepository.ts b/server/src/repositories/org/IOrgRepository.ts new file mode 100644 index 000000000..15bfa80ac --- /dev/null +++ b/server/src/repositories/org/IOrgRepository.ts @@ -0,0 +1,10 @@ +import { Org } from "@/types/domain/index.js"; + +export interface IOrgRepository { + // create + create(orgData: Partial): Promise; + // single fetch + // collection fetch + // update + // delete +} diff --git a/server/src/repositories/org/MongoOrgRepository.ts b/server/src/repositories/org/MongoOrgRepository.ts new file mode 100644 index 000000000..46e374545 --- /dev/null +++ b/server/src/repositories/org/MongoOrgRepository.ts @@ -0,0 +1,34 @@ +import { type IOrg, Org } from "@/db/models/index.js"; +import type { IOrgRepository } from "@/repositories/index.js"; +import { Org as OrgEntity } from "@/types/domain/index.js"; + +class MongoOrgRepository implements IOrgRepository { + toEntity = (doc: IOrg): OrgEntity => { + return { + id: doc._id.toString(), + name: doc.name.toString(), + ownerId: doc.ownerId.toString(), + planKey: doc.planKey, + + // Billing + billingCustomerId: doc.billingCustomerId, + subscriptionId: doc.subscriptionId, + priceId: doc.priceId, + priceIds: doc.priceIds, + subscriptionStatus: doc.subscriptionStatus, + currentPeriodStart: doc.currentPeriodStart, + currentPeriodEnd: doc.currentPeriodEnd, + + entitlements: doc.entitlements, + createdAt: doc.createdAt, + updatedAt: doc.updatedAt, + }; + }; + + create = async (orgData: Partial) => { + const org = await Org.create(orgData); + return this.toEntity(org); + }; +} + +export default MongoOrgRepository; diff --git a/server/src/repositories/role/IRoleRepository.ts b/server/src/repositories/role/IRoleRepository.ts index 45db0582e..9d7060c65 100644 --- a/server/src/repositories/role/IRoleRepository.ts +++ b/server/src/repositories/role/IRoleRepository.ts @@ -2,6 +2,7 @@ import { Role } from "@/types/domain/index.js"; export interface IRoleRepository { // create + createMany(rolesData: Role[]): Promise; // single fetch findById(roleId: string, orgId: string): Promise; // collection fetch diff --git a/server/src/repositories/role/MongoRoleRepository.ts b/server/src/repositories/role/MongoRoleRepository.ts index 0efd32831..95dc1be4e 100644 --- a/server/src/repositories/role/MongoRoleRepository.ts +++ b/server/src/repositories/role/MongoRoleRepository.ts @@ -1,7 +1,9 @@ +import { Types } from "mongoose"; import type { IRoleRepository } from "./IRoleRepository.js"; import { Role } from "@/db/models/index.js"; import type { IRole } from "@/db/models/index.js"; -import { Role as RoleEntity } from "@/types/domain/index.js"; +import type { Role as RoleEntity } from "@/types/domain/index.js"; + class MongoRoleRepository implements IRoleRepository { private toEntity = (doc: IRole): RoleEntity => { return { @@ -14,11 +16,37 @@ class MongoRoleRepository implements IRoleRepository { updatedAt: doc.updatedAt, }; }; + + createMany = async (rolesData: RoleEntity[]): Promise => { + const docs = rolesData.map((role) => ({ + ...role, + organizationId: role.organizationId + ? new Types.ObjectId(role.organizationId) + : undefined, + })); + + const insertedRoles = await Role.insertMany(docs); + return insertedRoles.map((r) => { + return this.toEntity({ + _id: r._id, + organizationId: r.organizationId || new Types.ObjectId(), + name: r.name, + scope: r.scope, + permissions: r.permissions, + createdAt: r.createdAt, + updatedAt: r.updatedAt, + }); + }); + }; + findById = async (roleId: string, orgId: string) => { const role = await Role.findOne({ _id: roleId, organizationId: orgId, - }); + }) + .lean() + .exec(); + if (!role) { return null; } diff --git a/server/src/repositories/team-membership/ITeamMembershipRepository.ts b/server/src/repositories/team-membership/ITeamMembershipRepository.ts index bf824e5c5..9dff65003 100644 --- a/server/src/repositories/team-membership/ITeamMembershipRepository.ts +++ b/server/src/repositories/team-membership/ITeamMembershipRepository.ts @@ -1,6 +1,7 @@ import { TeamMembership } from "@/types/domain/index.js"; export interface ITeamMembershipRepository { // create + create(membership: Partial): Promise; // single fetch findByUserId(userId: string, teamId: string): Promise; // collection fetch diff --git a/server/src/repositories/team-membership/MongoTeamMembershipRepository.ts b/server/src/repositories/team-membership/MongoTeamMembershipRepository.ts index a549ec8d8..0521326e1 100644 --- a/server/src/repositories/team-membership/MongoTeamMembershipRepository.ts +++ b/server/src/repositories/team-membership/MongoTeamMembershipRepository.ts @@ -13,6 +13,11 @@ class MongoTeamMembershipRepository implements ITeamMembershipRepository { updatedAt: doc.updatedAt, }; }; + create = async (membership: Partial) => { + const created = await TeamMembership.create(membership); + return this.toEntity(created); + }; + findByUserId = async (userId: string, teamId: string) => { const membership = await TeamMembership.findOne({ userId, diff --git a/server/src/repositories/team/ITeamRepository.ts b/server/src/repositories/team/ITeamRepository.ts index d3517e2ff..f23f388c0 100644 --- a/server/src/repositories/team/ITeamRepository.ts +++ b/server/src/repositories/team/ITeamRepository.ts @@ -2,6 +2,7 @@ import { Team } from "@/types/domain/index.js"; export interface ITeamRepository { // create + create(team: Partial): Promise; // single fetch findById(teamId: string, orgId: string): Promise; // collection fetch diff --git a/server/src/repositories/team/MongoTeamRepository.ts b/server/src/repositories/team/MongoTeamRepository.ts index dee413011..233ddb29d 100644 --- a/server/src/repositories/team/MongoTeamRepository.ts +++ b/server/src/repositories/team/MongoTeamRepository.ts @@ -14,6 +14,11 @@ class MongoTeamRepository implements ITeamRepository { }; }; + create = async (team: Partial) => { + const created = await Team.create(team); + return this.toEntity(created); + }; + findById = async (teamId: string, orgId: string) => { const team = await Team.findOne({ _id: teamId, diff --git a/server/src/repositories/user/IUserRepository.ts b/server/src/repositories/user/IUserRepository.ts index 3007eff3c..37011492f 100644 --- a/server/src/repositories/user/IUserRepository.ts +++ b/server/src/repositories/user/IUserRepository.ts @@ -1,6 +1,7 @@ import type { User } from "@/types/domain/index.js"; export interface IUserRepository { // create + create(userData: Partial): Promise; // single fetch findByEmail: (email: string) => Promise; // collection fetch diff --git a/server/src/repositories/user/MongoUserRepository.ts b/server/src/repositories/user/MongoUserRepository.ts index 76ec049dd..585635af5 100644 --- a/server/src/repositories/user/MongoUserRepository.ts +++ b/server/src/repositories/user/MongoUserRepository.ts @@ -17,6 +17,11 @@ class MongoUserRepository implements IUserRepository { }; }; + create = async (userData: Partial) => { + const user = await User.create(userData); + return this.toEntity(user); + }; + findByEmail = async (email: string) => { const user = await User.findOne({ email }); if (!user) return null; diff --git a/server/src/services/business/AuthService.ts b/server/src/services/business/AuthService.ts index e1270d624..13a90b90c 100644 --- a/server/src/services/business/AuthService.ts +++ b/server/src/services/business/AuthService.ts @@ -13,7 +13,6 @@ import { ITokenizedUser, Check, NotificationChannel, - IInvite, } from "@/db/models/index.js"; import { Types } from "mongoose"; import ApiError from "@/utils/ApiError.js"; @@ -23,7 +22,13 @@ import { Plans } from "@/types/entitlements.js"; import { PERMISSIONS } from "@/types/permissions.js"; import type { Entitlements } from "@/types/entitlements.js"; import type { IEntitlementsProvider } from "@/services/system/EntitlementsService.js"; -import type { IMonitorRepository } from "@/repositories/index.js"; +import type { + IMonitorRepository, + IUserRepository, + IOrgRepository, + ITeamRepository, + ITeamMembershipRepository, +} from "@/repositories/index.js"; import type { Invite } from "@/types/domain/index.js"; import mongoose from "mongoose"; @@ -71,15 +76,28 @@ class AuthService implements IAuthService { private jobQueue: IJobQueue; private entitlementsProvider: IEntitlementsProvider; private monitorRepository: IMonitorRepository; + private userRepository: IUserRepository; + private orgRepository: IOrgRepository; + private teamRepository: ITeamRepository; + private teamMembershipRepository: ITeamMembershipRepository; + constructor( jobQueue: IJobQueue, entitlementsProvider: IEntitlementsProvider, - monitorRepository: IMonitorRepository + monitorRepository: IMonitorRepository, + userRepository: IUserRepository, + orgRepository: IOrgRepository, + teamRepository: ITeamRepository, + teamMembershipRepository: ITeamMembershipRepository ) { this.SERVICE_NAME = SERVICE_NAME; this.jobQueue = jobQueue; this.entitlementsProvider = entitlementsProvider; this.monitorRepository = monitorRepository; + this.userRepository = userRepository; + this.orgRepository = orgRepository; + this.teamRepository = teamRepository; + this.teamMembershipRepository = teamMembershipRepository; } async register(signupData: RegisterData) { @@ -102,26 +120,27 @@ class AuthService implements IAuthService { passwordHash, }; - const user = await User.create(newUserData); - created.user = user._id; + const user = await this.userRepository.create(newUserData); + created.user = user.id; - const org = await Org.create({ + const org = await this.orgRepository.create({ name: `${user.firstName}'s Org`, - ownerId: user._id, + ownerId: user.id, planKey: "free", entitlements: Plans["free"], }); - created.org = org._id; + + created.org = org.id; const roles = await Role.insertMany([ { - organizationId: org._id, + organizationId: org.id, name: "Org Admin", scope: "organization", permissions: ["*"], }, { - organizationId: org._id, + organizationId: org.id, name: "Org Member", scope: "organization", permissions: [ @@ -142,7 +161,7 @@ class AuthService implements IAuthService { ], }, { - organizationId: org._id, + organizationId: org.id, name: "Team Admin", permissions: [ PERMISSIONS.monitors.all, @@ -153,7 +172,7 @@ class AuthService implements IAuthService { scope: "team", }, { - organizationId: org._id, + organizationId: org.id, name: "Team Member", permissions: [ PERMISSIONS.monitors.read, @@ -168,45 +187,45 @@ class AuthService implements IAuthService { created.roles = roles.map((r) => r._id); const membership = await OrgMembership.create({ - userId: user._id, - orgId: org._id, + userId: user.id, + orgId: org.id, roleId: roles[0]?._id, }); created.orgMembership = membership._id; - const team = await Team.create({ + const team = await this.teamRepository.create({ name: "Default Team", - orgId: org._id, + orgId: org.id, description: "This is your default team", isSystem: true, }); - created.team = team._id; + created.team = team.id; - const teamMembership = await TeamMembership.create({ - orgId: org._id, - userId: user._id, - teamId: team._id, - roleId: roles[2]?._id, + const teamMembership = await this.teamMembershipRepository.create({ + orgId: org.id, + userId: user.id, + teamId: team.id, + roleId: roles[2]?._id.toString(), }); - created.teamMembership = teamMembership._id; + created.teamMembership = teamMembership.id; const tokenizedUser: ITokenizedUser = { - sub: user._id.toString(), + sub: user.id, email: user.email, - orgId: org._id.toString(), + orgId: org.id, }; const returnableTeam = { - id: team._id.toString(), + id: team.id, name: team.name, permissions: roles[2]?.permissions || [], }; const entitlements: Entitlements = - await this.entitlementsProvider.getForOrg(org._id.toString()); + await this.entitlementsProvider.getForOrg(org.id); const returnableUser: IUserReturnable = { - id: user._id.toString(), + id: user.id, email: user.email, firstName: user.firstName, lastName: user.lastName, diff --git a/server/src/types/domain/orgMembership.ts b/server/src/types/domain/orgMembership.ts index 1927a6d0e..ecba64fd4 100644 --- a/server/src/types/domain/orgMembership.ts +++ b/server/src/types/domain/orgMembership.ts @@ -1,4 +1,4 @@ -export interface OrgMembership extends Document { +export interface OrgMembership { id: string; orgId: string; userId: string;