refactor auth service

This commit is contained in:
Alex Holliday committed 2026-01-07 00:00:50 +00:00
1 parent 3611aee468
commit a3e24b5420
16 files changed
+180 -32

No files matched your search

+7 -1
View File
@@ -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(
+6
View File
@@ -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";
@@ -0,0 +1,7 @@
export interface IOrgMembershipRepository {
// create
// single fetch
// collection fetch
// update
// delete
}
@@ -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;
@@ -0,0 +1,10 @@
import { Org } from "@/types/domain/index.js";
export interface IOrgRepository {
// create
create(orgData: Partial<Org>): Promise<Org>;
// single fetch
// collection fetch
// update
// delete
}
@@ -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<OrgEntity>) => {
const org = await Org.create(orgData);
return this.toEntity(org);
};
}
export default MongoOrgRepository;
@@ -2,6 +2,7 @@ import { Role } from "@/types/domain/index.js";
export interface IRoleRepository {
// create
createMany(rolesData: Role[]): Promise<Role[]>;
// single fetch
findById(roleId: string, orgId: string): Promise<Role | null>;
// collection fetch
@@ -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<RoleEntity[]> => {
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<IRole>()
.exec();
if (!role) {
return null;
}
@@ -1,6 +1,7 @@
import { TeamMembership } from "@/types/domain/index.js";
export interface ITeamMembershipRepository {
// create
create(membership: Partial<TeamMembership>): Promise<TeamMembership>;
// single fetch
findByUserId(userId: string, teamId: string): Promise<TeamMembership | null>;
// collection fetch
@@ -13,6 +13,11 @@ class MongoTeamMembershipRepository implements ITeamMembershipRepository {
updatedAt: doc.updatedAt,
};
};
create = async (membership: Partial<TeamMembershipEntity>) => {
const created = await TeamMembership.create(membership);
return this.toEntity(created);
};
findByUserId = async (userId: string, teamId: string) => {
const membership = await TeamMembership.findOne({
userId,
@@ -2,6 +2,7 @@ import { Team } from "@/types/domain/index.js";
export interface ITeamRepository {
// create
create(team: Partial<Team>): Promise<Team>;
// single fetch
findById(teamId: string, orgId: string): Promise<Team | null>;
// collection fetch
@@ -14,6 +14,11 @@ class MongoTeamRepository implements ITeamRepository {
};
};
create = async (team: Partial<TeamEntity>) => {
const created = await Team.create(team);
return this.toEntity(created);
};
findById = async (teamId: string, orgId: string) => {
const team = await Team.findOne({
_id: teamId,
@@ -1,6 +1,7 @@
import type { User } from "@/types/domain/index.js";
export interface IUserRepository {
// create
create(userData: Partial<User>): Promise<User>;
// single fetch
findByEmail: (email: string) => Promise<User | null>;
// collection fetch
@@ -17,6 +17,11 @@ class MongoUserRepository implements IUserRepository {
};
};
create = async (userData: Partial<UserEntity>) => {
const user = await User.create(userData);
return this.toEntity(user);
};
findByEmail = async (email: string) => {
const user = await User.findOne({ email });
if (!user) return null;
+47 -28
View File
@@ -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,
+1 -1
View File
@@ -1,4 +1,4 @@
export interface OrgMembership extends Document {
export interface OrgMembership {
id: string;
orgId: string;
userId: string;