be
This commit is contained in:
1 parent
9248870716
commit
77f943a7c7
9 files changed
+169
-64
No files matched your search
@@ -68,7 +68,7 @@ export const initServices = async () => {
|
||||
inviteRepository,
|
||||
teamMembershipRepository
|
||||
);
|
||||
const maintenanceService = new MaintenanceService();
|
||||
const maintenanceService = new MaintenanceService(monitorRepository);
|
||||
const monitorStatsService = new MonitorStatsService(
|
||||
monitorRepository,
|
||||
monitorStatsRepository
|
||||
|
||||
@@ -33,3 +33,6 @@ export { default as MongoOrgRepository } from "@/repositories/org/MongoOrgReposi
|
||||
|
||||
export type { IOrgMembershipRepository } from "@/repositories/org-membership/IOrgMembershopRepository.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";
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { Maintenance } from "@/types/domain/index.js";
|
||||
export interface IMaintenanceRepository {
|
||||
// create
|
||||
create(maintenanceData: Partial<Maintenance>): Promise<Maintenance>;
|
||||
// single fetch
|
||||
findById(maintenanceId: string, teamId: string): Promise<Maintenance | null>;
|
||||
// collection fetch
|
||||
findByTeamId(teamId: string): Promise<Maintenance[]>;
|
||||
findActive(): Promise<Maintenance[]>;
|
||||
// update
|
||||
updateById(
|
||||
maintenanceId: string,
|
||||
teamId: string,
|
||||
patchData: Partial<Maintenance>
|
||||
): Promise<Maintenance | null>;
|
||||
// delete
|
||||
deleteById(maintenanceId: string, teamId: string): Promise<boolean>;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import type { IMaintenanceRepository } from "@/repositories/maintenance/IMaintenanceRepository.js";
|
||||
import { type IMaintenance, Maintenance } from "@/db/models/index.js";
|
||||
import type { Maintenance as MaintenanceEntity } from "@/types/domain/index.js";
|
||||
import { MaintenanceRepeats } from "@/types/domain/index.js";
|
||||
|
||||
class MongoMaintenanceRepository implements IMaintenanceRepository {
|
||||
private toEntity = (doc: IMaintenance): MaintenanceEntity => {
|
||||
return {
|
||||
id: doc._id.toString(),
|
||||
orgId: doc.orgId.toString(),
|
||||
teamId: doc.teamId.toString(),
|
||||
name: doc.name,
|
||||
isActive: doc.isActive,
|
||||
repeat: doc.repeat,
|
||||
monitors: doc.monitors?.map((m) => m.toString()) ?? [],
|
||||
startTime: doc.startTime,
|
||||
endTime: doc.endTime,
|
||||
createdBy: doc.createdBy.toString(),
|
||||
updatedBy: doc.updatedBy.toString(),
|
||||
createdAt: doc.createdAt,
|
||||
updatedAt: doc.updatedAt,
|
||||
};
|
||||
};
|
||||
|
||||
create = async (data: Partial<MaintenanceEntity>) => {
|
||||
const maintenance = await Maintenance.create(data);
|
||||
return this.toEntity(maintenance);
|
||||
};
|
||||
|
||||
findById = async (maintenanceId: string, teamId: string) => {
|
||||
const maintenance = await Maintenance.findOne({
|
||||
_id: maintenanceId,
|
||||
teamId,
|
||||
});
|
||||
if (!maintenance) return null;
|
||||
return this.toEntity(maintenance);
|
||||
};
|
||||
|
||||
findByTeamId = async (teamId: string) => {
|
||||
const maintennaces = await Maintenance.find({ teamId });
|
||||
return maintennaces.map(this.toEntity);
|
||||
};
|
||||
|
||||
findActive = async () => {
|
||||
const now = new Date();
|
||||
|
||||
const activeMaintenances = await Maintenance.find({
|
||||
isActive: true,
|
||||
$or: [
|
||||
{ repeat: { $in: MaintenanceRepeats } },
|
||||
{ startTime: { $lte: now }, endTime: { $gte: now } },
|
||||
],
|
||||
});
|
||||
return activeMaintenances.map(this.toEntity);
|
||||
};
|
||||
|
||||
updateById = async (
|
||||
maintenanceId: string,
|
||||
teamId: string,
|
||||
patchData: Partial<MaintenanceEntity>
|
||||
) => {
|
||||
const updatedMaintenance = await Maintenance.findOneAndUpdate(
|
||||
{ _id: maintenanceId, teamId },
|
||||
[
|
||||
{
|
||||
$set: patchData,
|
||||
},
|
||||
],
|
||||
{ new: true, runValidators: true }
|
||||
);
|
||||
if (!updatedMaintenance) return null;
|
||||
return this.toEntity(updatedMaintenance);
|
||||
};
|
||||
|
||||
deleteById = async (maintenanceId: string, teamId: string) => {
|
||||
const result = await Maintenance.deleteOne({ _id: maintenanceId, teamId });
|
||||
return result.deletedCount === 1;
|
||||
};
|
||||
}
|
||||
|
||||
export default MongoMaintenanceRepository;
|
||||
@@ -84,4 +84,5 @@ export interface IMonitorRepository {
|
||||
|
||||
// Counts
|
||||
countByOrgId(orgId: string): Promise<number>;
|
||||
countByIdAndTeamId(monitorIds: string[], teamId: string): Promise<number>;
|
||||
}
|
||||
@@ -232,6 +232,13 @@ class MongoMonitorRepository implements IMonitorRepository {
|
||||
countByOrgId(orgId: string): Promise<number> {
|
||||
return Monitor.countDocuments({ orgId });
|
||||
}
|
||||
|
||||
countByIdAndTeamId = async (monitorIds: string[], teamId: string) => {
|
||||
return await Monitor.countDocuments({
|
||||
_id: { $in: monitorIds },
|
||||
teamId: teamId,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default MongoMonitorRepository;
|
||||
@@ -7,8 +7,7 @@ import type {
|
||||
IInviteRepository,
|
||||
ITeamMembershipRepository,
|
||||
} from "@/repositories/index.js";
|
||||
import { User } from "@/types/domain/index.js";
|
||||
import { Invite } from "@/types/domain/index.js";
|
||||
import { User, Invite } from "@/types/domain/index.js";
|
||||
|
||||
const SERVICE_NAME = "InviteService";
|
||||
export interface IInviteService {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Monitor, IMaintenance, Maintenance } from "@/db/models/index.js";
|
||||
import {
|
||||
IMonitorRepository,
|
||||
IMaintenanceRepository,
|
||||
} from "@/repositories/index.js";
|
||||
import ApiError from "@/utils/ApiError.js";
|
||||
import { MaintenanceRepeats, UserContext } from "@/types/domain/index.js";
|
||||
import { UserContext, Maintenance } from "@/types/domain/index.js";
|
||||
|
||||
const SERVICE_NAME = "MaintenanceService";
|
||||
const DAY_MS = 24 * 60 * 60 * 1000;
|
||||
@@ -9,26 +12,26 @@ const WEEK_MS = 7 * DAY_MS;
|
||||
export interface IMaintenanceService {
|
||||
create: (
|
||||
userContext: UserContext,
|
||||
maintenance: IMaintenance
|
||||
) => Promise<IMaintenance>;
|
||||
getAll: (teamId: string) => Promise<IMaintenance[]>;
|
||||
get: (teamId: string, id: string) => Promise<IMaintenance>;
|
||||
maintenance: Maintenance
|
||||
) => Promise<Maintenance>;
|
||||
get: (teamId: string, id: string) => Promise<Maintenance>;
|
||||
getAll: (teamId: string) => Promise<Maintenance[]>;
|
||||
toggleActive: (
|
||||
teamId: string,
|
||||
userContext: UserContext,
|
||||
id: string
|
||||
) => Promise<IMaintenance>;
|
||||
) => Promise<Maintenance>;
|
||||
update: (
|
||||
teamId: string,
|
||||
userContext: UserContext,
|
||||
id: string,
|
||||
updateData: Partial<IMaintenance>
|
||||
) => Promise<IMaintenance>;
|
||||
updateData: Partial<Maintenance>
|
||||
) => Promise<Maintenance>;
|
||||
delete: (teamId: string, id: string) => Promise<boolean>;
|
||||
isInMaintenance: (monitorId: string) => Promise<boolean>;
|
||||
}
|
||||
|
||||
type MaintenanceCache = Map<string, IMaintenance[]>;
|
||||
type MaintenanceCache = Map<string, Maintenance[]>;
|
||||
|
||||
class MaintenanceService implements IMaintenanceService {
|
||||
public SERVICE_NAME: string;
|
||||
@@ -36,19 +39,27 @@ class MaintenanceService implements IMaintenanceService {
|
||||
private lastRefresh: number;
|
||||
private CACHE_TTL_MS = 60 * 1000;
|
||||
|
||||
constructor() {
|
||||
// Repositories
|
||||
private maintenanceRepository: IMaintenanceRepository;
|
||||
private monitorRepository: IMonitorRepository;
|
||||
constructor(
|
||||
maintenanceRepository: IMaintenanceRepository,
|
||||
monitorRepository: IMonitorRepository
|
||||
) {
|
||||
this.SERVICE_NAME = SERVICE_NAME;
|
||||
this.maintenanceCache = new Map();
|
||||
this.lastRefresh = 0;
|
||||
this.monitorRepository = monitorRepository;
|
||||
this.maintenanceRepository = maintenanceRepository;
|
||||
}
|
||||
|
||||
create = async (userContext: UserContext, maintenanceData: IMaintenance) => {
|
||||
create = async (userContext: UserContext, maintenanceData: Maintenance) => {
|
||||
// Make sure monitors belong to current team
|
||||
const monitorIds = maintenanceData.monitors || [];
|
||||
const count = await Monitor.countDocuments({
|
||||
_id: { $in: monitorIds },
|
||||
teamId: userContext.currentTeamId,
|
||||
});
|
||||
const count = await this.monitorRepository.countByIdAndTeamId(
|
||||
monitorIds,
|
||||
userContext.currentTeamId || ""
|
||||
);
|
||||
|
||||
if (count !== monitorIds.length) {
|
||||
throw new ApiError(
|
||||
@@ -57,7 +68,7 @@ class MaintenanceService implements IMaintenanceService {
|
||||
);
|
||||
}
|
||||
|
||||
const maintenance = await Maintenance.create({
|
||||
const maintenance = await this.maintenanceRepository.create({
|
||||
...maintenanceData,
|
||||
orgId: userContext.orgId,
|
||||
teamId: userContext.currentTeamId,
|
||||
@@ -68,7 +79,7 @@ class MaintenanceService implements IMaintenanceService {
|
||||
};
|
||||
|
||||
get = async (teamId: string, id: string) => {
|
||||
const maintenance = await Maintenance.findOne({ _id: id, teamId });
|
||||
const maintenance = await this.maintenanceRepository.findById(id, teamId);
|
||||
if (!maintenance) {
|
||||
throw new ApiError("Maintenance not found", 404);
|
||||
}
|
||||
@@ -76,7 +87,7 @@ class MaintenanceService implements IMaintenanceService {
|
||||
};
|
||||
|
||||
getAll = async (teamId: string) => {
|
||||
return Maintenance.find({ teamId });
|
||||
return this.maintenanceRepository.findByTeamId(teamId);
|
||||
};
|
||||
|
||||
toggleActive = async (
|
||||
@@ -84,18 +95,14 @@ class MaintenanceService implements IMaintenanceService {
|
||||
userContext: UserContext,
|
||||
id: string
|
||||
) => {
|
||||
const updatedMaintenance = await Maintenance.findOneAndUpdate(
|
||||
{ _id: id, teamId },
|
||||
[
|
||||
{
|
||||
$set: {
|
||||
isActive: { $not: "$isActive" },
|
||||
updatedBy: userContext.sub,
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
},
|
||||
],
|
||||
{ new: true }
|
||||
const updatedMaintenance = await this.maintenanceRepository.updateById(
|
||||
id,
|
||||
teamId,
|
||||
{
|
||||
isActive: { $not: "$isActive" },
|
||||
updatedBy: userContext.sub,
|
||||
updatedAt: new Date(),
|
||||
}
|
||||
);
|
||||
if (!updatedMaintenance) {
|
||||
throw new ApiError("Maintenance not found", 404);
|
||||
@@ -107,14 +114,14 @@ class MaintenanceService implements IMaintenanceService {
|
||||
teamId: string,
|
||||
userContext: UserContext,
|
||||
id: string,
|
||||
updateData: Partial<IMaintenance>
|
||||
updateData: Partial<Maintenance>
|
||||
) => {
|
||||
// Make sure monitors belong to current team
|
||||
const monitorIds = updateData.monitors || [];
|
||||
const count = await Monitor.countDocuments({
|
||||
_id: { $in: monitorIds },
|
||||
teamId: userContext.currentTeamId,
|
||||
});
|
||||
const count = await this.monitorRepository.countByIdAndTeamId(
|
||||
monitorIds,
|
||||
userContext.currentTeamId || ""
|
||||
);
|
||||
|
||||
if (count !== monitorIds.length) {
|
||||
throw new ApiError(
|
||||
@@ -123,7 +130,7 @@ class MaintenanceService implements IMaintenanceService {
|
||||
);
|
||||
}
|
||||
|
||||
const allowedFields: (keyof IMaintenance)[] = [
|
||||
const allowedFields: (keyof Maintenance)[] = [
|
||||
"name",
|
||||
"monitors",
|
||||
"repeat",
|
||||
@@ -131,25 +138,22 @@ class MaintenanceService implements IMaintenanceService {
|
||||
"endTime",
|
||||
"isActive",
|
||||
];
|
||||
const safeUpdate: Partial<IMaintenance> = {};
|
||||
const safeUpdate: Partial<Maintenance> = {};
|
||||
for (const field of allowedFields) {
|
||||
if (updateData[field] !== undefined) {
|
||||
(safeUpdate as any)[field] = updateData[field];
|
||||
}
|
||||
}
|
||||
|
||||
const updatedMaintenance = await Maintenance.findOneAndUpdate(
|
||||
{ _id: id, teamId },
|
||||
const updatedMaintenance = await this.maintenanceRepository.updateById(
|
||||
id,
|
||||
teamId,
|
||||
{
|
||||
$set: {
|
||||
...safeUpdate,
|
||||
updatedAt: new Date(),
|
||||
updatedBy: userContext.sub,
|
||||
},
|
||||
},
|
||||
{ new: true, runValidators: true }
|
||||
...safeUpdate,
|
||||
updatedAt: new Date(),
|
||||
updatedBy: userContext.sub,
|
||||
}
|
||||
);
|
||||
|
||||
if (!updatedMaintenance) {
|
||||
throw new ApiError("Failed to update maintenance", 500);
|
||||
}
|
||||
@@ -158,23 +162,15 @@ class MaintenanceService implements IMaintenanceService {
|
||||
};
|
||||
|
||||
delete = async (teamId: string, id: string) => {
|
||||
const result = await Maintenance.deleteOne({ _id: id, teamId });
|
||||
if (!result.deletedCount) {
|
||||
const didDelete = await this.maintenanceRepository.deleteById(id, teamId);
|
||||
if (!didDelete) {
|
||||
throw new ApiError("Maintenance not found", 404);
|
||||
}
|
||||
return result.deletedCount === 1;
|
||||
return didDelete;
|
||||
};
|
||||
|
||||
private refreshCache = async () => {
|
||||
const now = new Date();
|
||||
|
||||
const activeMaintenances = await Maintenance.find({
|
||||
isActive: true,
|
||||
$or: [
|
||||
{ repeat: { $in: MaintenanceRepeats } },
|
||||
{ startTime: { $lte: now }, endTime: { $gte: now } },
|
||||
],
|
||||
}).lean();
|
||||
const activeMaintenances = await this.maintenanceRepository.findActive();
|
||||
|
||||
// Reset cache
|
||||
const newCache = new Map();
|
||||
|
||||
@@ -6,7 +6,7 @@ export interface Maintenance {
|
||||
orgId: string;
|
||||
teamId: string;
|
||||
name: string;
|
||||
isActive: boolean;
|
||||
isActive: boolean | unknown;
|
||||
repeat: MaintenanceRepeat;
|
||||
monitors: string[];
|
||||
startTime: Date;
|
||||
|
||||
Reference in new issue
Block a user