From 61bf2a0dfbd7ee7d1d8baae2bc3bb5e525e06977 Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Tue, 6 Jan 2026 22:53:54 +0000 Subject: [PATCH] use repos --- .../src/db/models/maintenance/Maintenance.ts | 6 +- server/src/db/models/monitors/StatsDaily.ts | 5 +- server/src/db/models/monitors/StatsHourly.ts | 78 ++------------ server/src/init/services.ts | 16 ++- server/src/repositories/index.ts | 15 +++ .../repositories/invites/IInviteRepository.ts | 14 +++ .../invites/MongoInviteRepository.ts | 82 ++++++++++++++ .../src/repositories/role/IRoleRepository.ts | 10 ++ .../repositories/role/MongoRoleRepository.ts | 29 +++++ .../ITeamMembershipRepository.ts | 9 ++ .../MongoTeamMembershipRepository.ts | 26 +++++ .../src/repositories/team/ITeamRepository.ts | 10 ++ .../repositories/team/MongoTeamRepository.ts | 29 +++++ .../src/repositories/user/IUserRepository.ts | 9 ++ .../repositories/user/MongoUserRepository.ts | 27 +++++ .../src/services/business/IncidentService.ts | 1 - server/src/services/business/InviteService.ts | 83 ++++++++------- server/src/types/domain/index.ts | 9 ++ server/src/types/domain/invite.ts | 14 +++ server/src/types/domain/maintenance.ts | 18 ++++ server/src/types/domain/org.ts | 39 +++++++ server/src/types/domain/orgMembership.ts | 9 ++ server/src/types/domain/role.ts | 12 +++ server/src/types/domain/statsDaily.ts | 24 +++++ server/src/types/domain/statsHourly.ts | 100 ++++++++++++++++++ server/src/types/domain/team.ts | 9 ++ server/src/types/domain/teamMembership.ts | 9 ++ server/src/types/domain/user.ts | 10 ++ 28 files changed, 589 insertions(+), 113 deletions(-) create mode 100644 server/src/repositories/invites/IInviteRepository.ts create mode 100644 server/src/repositories/invites/MongoInviteRepository.ts create mode 100644 server/src/repositories/role/IRoleRepository.ts create mode 100644 server/src/repositories/role/MongoRoleRepository.ts create mode 100644 server/src/repositories/team-membership/ITeamMembershipRepository.ts create mode 100644 server/src/repositories/team-membership/MongoTeamMembershipRepository.ts create mode 100644 server/src/repositories/team/ITeamRepository.ts create mode 100644 server/src/repositories/team/MongoTeamRepository.ts create mode 100644 server/src/repositories/user/IUserRepository.ts create mode 100644 server/src/repositories/user/MongoUserRepository.ts create mode 100644 server/src/types/domain/invite.ts create mode 100644 server/src/types/domain/maintenance.ts create mode 100644 server/src/types/domain/org.ts create mode 100644 server/src/types/domain/orgMembership.ts create mode 100644 server/src/types/domain/role.ts create mode 100644 server/src/types/domain/statsDaily.ts create mode 100644 server/src/types/domain/statsHourly.ts create mode 100644 server/src/types/domain/team.ts create mode 100644 server/src/types/domain/teamMembership.ts create mode 100644 server/src/types/domain/user.ts diff --git a/server/src/db/models/maintenance/Maintenance.ts b/server/src/db/models/maintenance/Maintenance.ts index 2d209d3f7..24c101d1c 100644 --- a/server/src/db/models/maintenance/Maintenance.ts +++ b/server/src/db/models/maintenance/Maintenance.ts @@ -1,6 +1,8 @@ import mongoose, { Schema, Document, Types } from "mongoose"; -export const MaintenanceRepeats = ["no repeat", "daily", "weekly"] as const; -export type MaintenanceRepeat = (typeof MaintenanceRepeats)[number]; +import { + type MaintenanceRepeat, + MaintenanceRepeats, +} from "@/types/domain/index.js"; export interface IMaintenance extends Document { _id: Types.ObjectId; orgId: Types.ObjectId; diff --git a/server/src/db/models/monitors/StatsDaily.ts b/server/src/db/models/monitors/StatsDaily.ts index c3ad612a6..4ee93f22f 100644 --- a/server/src/db/models/monitors/StatsDaily.ts +++ b/server/src/db/models/monitors/StatsDaily.ts @@ -1,4 +1,6 @@ import mongoose, { Schema } from "mongoose"; +import type { IStatsBase } from "./StatsHourly.js"; + import type { IInfraCpu, IInfraDiskEntry, @@ -6,8 +8,7 @@ import type { IInfraMemory, IInfraNetEntry, IPageSpeedMetrics, - IStatsBase, -} from "./StatsHourly.js"; +} from "@/types/domain/index.js"; export interface IStatsDaily extends mongoose.Document, diff --git a/server/src/db/models/monitors/StatsHourly.ts b/server/src/db/models/monitors/StatsHourly.ts index 5228433cd..ddd847b17 100644 --- a/server/src/db/models/monitors/StatsHourly.ts +++ b/server/src/db/models/monitors/StatsHourly.ts @@ -1,5 +1,13 @@ import mongoose, { Schema, Types } from "mongoose"; -import type { MonitorType } from "@/types/domain/index.js"; +import type { + IInfraCpu, + IPageSpeedMetrics, + IInfraMemory, + IInfraDiskEntry, + IInfraHost, + IInfraNetEntry, + MonitorType, +} from "@/types/domain/index.js"; // Common/base metrics across monitor types export interface IStatsBase { @@ -19,74 +27,6 @@ export interface IStatsBase { updatedAt: Date; } -// Pagespeed rollups (averages per window) -export interface IPageSpeedMetrics { - accessibility?: number; - bestPractices?: number; - seo?: number; - performance?: number; - cls?: number; - si?: number; - fcp?: number; - lcp?: number; - tbt?: number; -} - -// Infrastructure rollups -export interface IInfraCpu { - physical_core?: number; - logical_core?: number; - frequency?: number; - current_frequency?: number; - temperature?: number[]; // averaged per sensor index - free_percent?: number; - used_percent?: number; -} - -export interface IInfraMemory { - total_bytes?: number; - available_bytes?: number; - used_bytes?: number; - usage_percent?: number; -} - -export interface IInfraDiskEntry { - device?: string; - total_bytes?: number; - free_bytes?: number; - used_bytes?: number; - usage_percent?: number; - total_inodes?: number; - free_inodes?: number; - used_inodes?: number; - inodes_usage_percent?: number; - read_bytes?: number; - write_bytes?: number; - read_time?: number; - write_time?: number; -} - -export interface IInfraHost { - os?: string; - platform?: string; - kernel_version?: string; - pretty_name?: string; -} - -export interface IInfraNetEntry { - name?: string; - bytes_sent?: number; - bytes_recv?: number; - packets_sent?: number; - packets_recv?: number; - err_in?: number; - err_out?: number; - drop_in?: number; - drop_out?: number; - fifo_in?: number; - fifo_out?: number; -} - export interface IStatsHourly extends mongoose.Document, IStatsBase, diff --git a/server/src/init/services.ts b/server/src/init/services.ts index ef52e288d..40e656f64 100644 --- a/server/src/init/services.ts +++ b/server/src/init/services.ts @@ -38,17 +38,29 @@ import { MongoChecksRepository, MongoMonitorStatsRepository, MongoIncidentsRepository, + MongoUserRepository, + MongoRoleRepository, + MongoTeamRepository, + MongoInviteRepository, } from "@/repositories/index.js"; -import type { CheckEntity } from "@/types/domain/index.js"; export const initServices = async () => { const monitorRepository = new MongoMonitorRepository(); const checksRepository = new MongoChecksRepository(); const monitorStatsRepository = new MongoMonitorStatsRepository(); const incidentsRepository = new MongoIncidentsRepository(); + const userRepository = new MongoUserRepository(); + const roleRepository = new MongoRoleRepository(); + const teamRepository = new MongoTeamRepository(); + const inviteRepository = new MongoInviteRepository(); const checkService = new CheckService(checksRepository, monitorRepository); - const inviteService = new InviteService(); + const inviteService = new InviteService( + userRepository, + roleRepository, + teamRepository, + inviteRepository + ); const maintenanceService = new MaintenanceService(); const monitorStatsService = new MonitorStatsService( monitorRepository, diff --git a/server/src/repositories/index.ts b/server/src/repositories/index.ts index d9f57caeb..79d1c0cbc 100644 --- a/server/src/repositories/index.ts +++ b/server/src/repositories/index.ts @@ -12,3 +12,18 @@ export { default as MongoMonitorStatsRepository } from "@/repositories/monitor-s export type { IIncidentsRepository } from "@/repositories/incidents/IIncidentsRepository.js"; export { default as MongoIncidentsRepository } from "@/repositories/incidents/MongoIncidentsRepository.js"; + +export type { IUserRepository } from "@/repositories/user/IUserRepository.js"; +export { default as MongoUserRepository } from "@/repositories/user/MongoUserRepository.js"; + +export type { IInviteRepository } from "@/repositories/invites/IInviteRepository.js"; +export { default as MongoInviteRepository } from "@/repositories/invites/MongoInviteRepository.js"; + +export type { IRoleRepository } from "@/repositories/role/IRoleRepository.js"; +export { default as MongoRoleRepository } from "@/repositories/role/MongoRoleRepository.js"; + +export type { ITeamRepository } from "@/repositories/team/ITeamRepository.js"; +export { default as MongoTeamRepository } from "@/repositories/team/MongoTeamRepository.js"; + +export type { ITeamMembershipRepository } from "@/repositories/team-membership/ITeamMembershipRepository.js"; +export { default as MongoTeamMembershipRepository } from "@/repositories/team-membership/MongoTeamMembershipRepository.js"; diff --git a/server/src/repositories/invites/IInviteRepository.ts b/server/src/repositories/invites/IInviteRepository.ts new file mode 100644 index 000000000..7ded8df62 --- /dev/null +++ b/server/src/repositories/invites/IInviteRepository.ts @@ -0,0 +1,14 @@ +import { Invite } from "@/types/domain/index.js"; + +export interface IInviteRepository { + // create + create(inviteData: Partial): Promise; + // single fetch + findByEmail(email: string, teamId: string): Promise; + findByHash(tokenHash: string): Promise; + // collection fetch + findAll(): Promise; + // update + // delete + deleteById(id: string): Promise; +} diff --git a/server/src/repositories/invites/MongoInviteRepository.ts b/server/src/repositories/invites/MongoInviteRepository.ts new file mode 100644 index 000000000..05c16fd0d --- /dev/null +++ b/server/src/repositories/invites/MongoInviteRepository.ts @@ -0,0 +1,82 @@ +import { type IInvite, Invite } from "@/db/models/index.js"; +import type { IInviteRepository } from "@/repositories/index.js"; +import { Invite as InviteEntity } from "@/types/domain/index.js"; +import ApiError from "@/utils/ApiError.js"; + +class MongoInviteRepository implements IInviteRepository { + private toEntity = (doc: IInvite): InviteEntity => { + return { + id: doc._id.toString(), + orgId: doc.orgId.toString(), + orgRoleId: doc.orgRoleId?.toString(), + teamId: doc.teamId.toString(), + teamRoleId: doc.teamRoleId.toString(), + email: doc.email, + tokenHash: doc.tokenHash, + createdBy: doc.createdBy.toString(), + updatedBy: doc.updatedBy.toString(), + expiry: doc.expiry, + createdAt: doc.createdAt, + updatedAt: doc.updatedAt, + }; + }; + + create = async (inviteData: Partial) => { + const { + orgId, + orgRoleId, + teamId, + teamRoleId, + email, + tokenHash, + createdBy, + updatedBy, + } = inviteData; + const invite = await Invite.create({ + orgId, + ...(orgRoleId && { orgRoleId }), + teamId, + teamRoleId, + email, + tokenHash, + createdBy, + updatedBy, + }); + if (!invite) { + return null; + } + return this.toEntity(invite); + }; + + findByEmail = async (email: string, teamId: string) => { + const invite = await Invite.findOne({ + email, + teamId, + }); + if (!invite) { + return null; + } + return this.toEntity(invite); + }; + + findByHash = async (tokenHash: string) => { + const invite = await Invite.findOne({ tokenHash }); + if (!invite) return null; + return this.toEntity(invite); + }; + + findAll = async () => { + const invites = await Invite.find(); + return invites.map(this.toEntity); + }; + deleteById = async (id: string) => { + const result = await Invite.deleteOne({ _id: id }); + + if (!result.deletedCount) { + throw new ApiError("Invite not found", 404); + } + return result.deletedCount === 1; + }; +} + +export default MongoInviteRepository; diff --git a/server/src/repositories/role/IRoleRepository.ts b/server/src/repositories/role/IRoleRepository.ts new file mode 100644 index 000000000..45db0582e --- /dev/null +++ b/server/src/repositories/role/IRoleRepository.ts @@ -0,0 +1,10 @@ +import { Role } from "@/types/domain/index.js"; + +export interface IRoleRepository { + // create + // single fetch + findById(roleId: string, orgId: string): Promise; + // collection fetch + // update + // delete +} diff --git a/server/src/repositories/role/MongoRoleRepository.ts b/server/src/repositories/role/MongoRoleRepository.ts new file mode 100644 index 000000000..0efd32831 --- /dev/null +++ b/server/src/repositories/role/MongoRoleRepository.ts @@ -0,0 +1,29 @@ +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"; +class MongoRoleRepository implements IRoleRepository { + private toEntity = (doc: IRole): RoleEntity => { + return { + id: doc._id.toString(), + organizationId: doc.organizationId.toString(), + name: doc.name, + scope: doc.scope, + permissions: doc.permissions, + createdAt: doc.createdAt, + updatedAt: doc.updatedAt, + }; + }; + findById = async (roleId: string, orgId: string) => { + const role = await Role.findOne({ + _id: roleId, + organizationId: orgId, + }); + if (!role) { + return null; + } + return this.toEntity(role); + }; +} + +export default MongoRoleRepository; diff --git a/server/src/repositories/team-membership/ITeamMembershipRepository.ts b/server/src/repositories/team-membership/ITeamMembershipRepository.ts new file mode 100644 index 000000000..bf824e5c5 --- /dev/null +++ b/server/src/repositories/team-membership/ITeamMembershipRepository.ts @@ -0,0 +1,9 @@ +import { TeamMembership } from "@/types/domain/index.js"; +export interface ITeamMembershipRepository { + // create + // single fetch + findByUserId(userId: string, teamId: string): Promise; + // collection fetch + // update + // delete +} diff --git a/server/src/repositories/team-membership/MongoTeamMembershipRepository.ts b/server/src/repositories/team-membership/MongoTeamMembershipRepository.ts new file mode 100644 index 000000000..a549ec8d8 --- /dev/null +++ b/server/src/repositories/team-membership/MongoTeamMembershipRepository.ts @@ -0,0 +1,26 @@ +import { type ITeamMembership, TeamMembership } from "@/db/models/index.js"; +import type { ITeamMembershipRepository } from "@/repositories/index.js"; +import { TeamMembership as TeamMembershipEntity } from "@/types/domain/index.js"; +class MongoTeamMembershipRepository implements ITeamMembershipRepository { + private toEntity = (doc: ITeamMembership): TeamMembershipEntity => { + return { + id: doc._id.toString(), + orgId: doc.orgId.toString(), + teamId: doc.teamId.toString(), + userId: doc.userId.toString(), + roleId: doc.roleId.toString(), + createdAt: doc.createdAt, + updatedAt: doc.updatedAt, + }; + }; + findByUserId = async (userId: string, teamId: string) => { + const membership = await TeamMembership.findOne({ + userId, + teamId, + }); + if (!membership) return null; + return this.toEntity(membership); + }; +} + +export default MongoTeamMembershipRepository; diff --git a/server/src/repositories/team/ITeamRepository.ts b/server/src/repositories/team/ITeamRepository.ts new file mode 100644 index 000000000..d3517e2ff --- /dev/null +++ b/server/src/repositories/team/ITeamRepository.ts @@ -0,0 +1,10 @@ +import { Team } from "@/types/domain/index.js"; + +export interface ITeamRepository { + // create + // single fetch + findById(teamId: string, orgId: string): Promise; + // collection fetch + // update + // delete +} diff --git a/server/src/repositories/team/MongoTeamRepository.ts b/server/src/repositories/team/MongoTeamRepository.ts new file mode 100644 index 000000000..dee413011 --- /dev/null +++ b/server/src/repositories/team/MongoTeamRepository.ts @@ -0,0 +1,29 @@ +import type { ITeamRepository } from "@/repositories/index.js"; +import { type ITeam, Team } from "@/db/models/index.js"; +import { Team as TeamEntity } from "@/types/domain/index.js"; +class MongoTeamRepository implements ITeamRepository { + private toEntity = (doc: ITeam): TeamEntity => { + return { + id: doc._id.toString(), + orgId: doc.orgId.toString(), + name: doc.name, + description: doc.description, + isSystem: doc.isSystem, + createdAt: doc.createdAt, + updatedAt: doc.updatedAt, + }; + }; + + findById = async (teamId: string, orgId: string) => { + const team = await Team.findOne({ + _id: teamId, + orgId, + }); + if (!team) { + return null; + } + return this.toEntity(team); + }; +} + +export default MongoTeamRepository; diff --git a/server/src/repositories/user/IUserRepository.ts b/server/src/repositories/user/IUserRepository.ts new file mode 100644 index 000000000..3007eff3c --- /dev/null +++ b/server/src/repositories/user/IUserRepository.ts @@ -0,0 +1,9 @@ +import type { User } from "@/types/domain/index.js"; +export interface IUserRepository { + // create + // single fetch + findByEmail: (email: string) => Promise; + // collection fetch + // update + // delete +} diff --git a/server/src/repositories/user/MongoUserRepository.ts b/server/src/repositories/user/MongoUserRepository.ts new file mode 100644 index 000000000..76ec049dd --- /dev/null +++ b/server/src/repositories/user/MongoUserRepository.ts @@ -0,0 +1,27 @@ +import type { User as UserEntity } from "@/types/domain/index.js"; +import { IUserRepository } from "@/repositories/index.js"; +import { User } from "@/db/models/index.js"; +import type { IUser } from "@/db/models/index.js"; + +class MongoUserRepository implements IUserRepository { + private toEntity = (user: IUser): UserEntity => { + return { + id: user._id.toString(), + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + passwordHash: user.passwordHash, + lastLoginAt: user.lastLoginAt, + createdAt: user.createdAt, + updatedAt: user.updatedAt, + }; + }; + + findByEmail = async (email: string) => { + const user = await User.findOne({ email }); + if (!user) return null; + return this.toEntity(user); + }; +} + +export default MongoUserRepository; diff --git a/server/src/services/business/IncidentService.ts b/server/src/services/business/IncidentService.ts index 2514c6edf..2a6bd7a55 100644 --- a/server/src/services/business/IncidentService.ts +++ b/server/src/services/business/IncidentService.ts @@ -1,4 +1,3 @@ -import { Incident, Monitor } from "@/db/models/index.js"; import type { ResolutionType } from "@/types/domain/index.js"; import { getChildLogger } from "@/logger/Logger.js"; import { ThresholdEvaluationResult } from "@/services/infrastructure/StatusService.js"; diff --git a/server/src/services/business/InviteService.ts b/server/src/services/business/InviteService.ts index 7989e69c9..0b8e573d3 100644 --- a/server/src/services/business/InviteService.ts +++ b/server/src/services/business/InviteService.ts @@ -1,14 +1,14 @@ import crypto from "node:crypto"; -import { - IInvite, - Invite, - User, - IUser, - Role, - Team, - TeamMembership, -} from "@/db/models/index.js"; import ApiError from "@/utils/ApiError.js"; +import type { + IUserRepository, + IRoleRepository, + ITeamRepository, + IInviteRepository, + ITeamMembershipRepository, +} from "@/repositories/index.js"; +import { User } from "@/types/domain/index.js"; +import { Invite } from "@/types/domain/index.js"; const SERVICE_NAME = "InviteService"; export interface IInviteService { @@ -20,15 +20,31 @@ export interface IInviteService { teamId: string, teamRoleId: string ) => Promise; - getAll: () => Promise; - get: (tokenHash: string) => Promise<{ user: IUser | null; invite: IInvite }>; + getAll: () => Promise; + get: (tokenHash: string) => Promise<{ user: User | null; invite: Invite }>; delete: (id: string) => Promise; } class InviteService implements IInviteService { public SERVICE_NAME: string; - constructor() { + private userRepository: IUserRepository; + private roleRepository: IRoleRepository; + private teamRepository: ITeamRepository; + private inviteRepository: IInviteRepository; + private teamMembershipRepository: ITeamMembershipRepository; + constructor( + userRepository: IUserRepository, + roleRepository: IRoleRepository, + teamRepository: ITeamRepository, + inviteRepository: IInviteRepository, + teamMembershipRepository: ITeamMembershipRepository + ) { this.SERVICE_NAME = SERVICE_NAME; + this.userRepository = userRepository; + this.roleRepository = roleRepository; + this.teamRepository = teamRepository; + this.inviteRepository = inviteRepository; + this.teamMembershipRepository = teamMembershipRepository; } create = async ( @@ -40,40 +56,36 @@ class InviteService implements IInviteService { teamRoleId: string ) => { try { - const role = await Role.findOne({ - _id: teamRoleId, - organizationId: orgId, - }); + const role = await this.roleRepository.findById(teamRoleId, orgId); if (!role) { throw new ApiError("Role not found", 404); } - const team = await Team.findOne({ - _id: teamId, - orgId, - }); + const team = await this.teamRepository.findById(teamId, orgId); if (!team) { throw new ApiError("Team not found", 404); } // Check if already a team member - const user = await User.findOne({ email }); - const existingMembership = await TeamMembership.findOne({ - userId: user?._id, - teamId, - }); + const user = await this.userRepository.findByEmail(email); + + const existingMembership = + await this.teamMembershipRepository.findByUserId( + user?.id || "", + teamId + ); if (existingMembership) { throw new ApiError("User is already a team member", 409); } // Check if inivite already exists - const existingInvite = await Invite.findOne({ + const existingInvite = await this.inviteRepository.findByEmail( email, - teamId, - }); + teamId + ); if (existingInvite) { throw new ApiError("An invite already exists for this email", 409); @@ -82,7 +94,7 @@ class InviteService implements IInviteService { const token = crypto.randomBytes(32).toString("hex"); const tokenHash = crypto.createHash("sha256").update(token).digest("hex"); - const invite = await Invite.create({ + const invite = await this.inviteRepository.create({ orgId, ...(orgRoleId && { orgRoleId }), teamId, @@ -104,24 +116,21 @@ class InviteService implements IInviteService { get = async (token: string) => { const tokenHash = crypto.createHash("sha256").update(token).digest("hex"); - const invite = await Invite.findOne({ tokenHash }); + const invite = await this.inviteRepository.findByHash(tokenHash); if (!invite) { throw new ApiError("Invite not found", 404); } - const user = await User.findOne({ email: invite.email }); + const user = await this.userRepository.findByEmail(invite.email); + return { user, invite }; }; getAll = async () => { - return Invite.find(); + return await this.inviteRepository.findAll(); }; delete = async (id: string) => { - const result = await Invite.deleteOne({ _id: id }); - if (!result.deletedCount) { - throw new ApiError("Invite not found", 404); - } - return result.deletedCount === 1; + return await this.inviteRepository.deleteById(id); }; } diff --git a/server/src/types/domain/index.ts b/server/src/types/domain/index.ts index 42303754b..dba07ee81 100644 --- a/server/src/types/domain/index.ts +++ b/server/src/types/domain/index.ts @@ -2,3 +2,12 @@ export * from "@/types/domain/monitor.js"; export * from "@/types/domain/check.js"; export * from "@/types/domain/monitorStats.js"; export * from "@/types/domain/incident.js"; +export * from "@/types/domain/user.js"; +export * from "@/types/domain/role.js"; +export * from "@/types/domain/team.js"; +export * from "@/types/domain/teamMembership.js"; +export * from "@/types/domain/invite.js"; +export * from "@/types/domain/org.js"; +export * from "@/types/domain/orgMembership.js"; +export * from "@/types/domain/maintenance.js"; +export * from "@/types/domain/statsHourly.js"; diff --git a/server/src/types/domain/invite.ts b/server/src/types/domain/invite.ts new file mode 100644 index 000000000..613141510 --- /dev/null +++ b/server/src/types/domain/invite.ts @@ -0,0 +1,14 @@ +export interface Invite { + id: string; + orgId: string; + orgRoleId?: string; + teamId: string; + teamRoleId: string; + email: string; + tokenHash: string; + createdBy: string; + updatedBy: string; + expiry: Date; + createdAt: Date; + updatedAt: Date; +} diff --git a/server/src/types/domain/maintenance.ts b/server/src/types/domain/maintenance.ts new file mode 100644 index 000000000..07837e661 --- /dev/null +++ b/server/src/types/domain/maintenance.ts @@ -0,0 +1,18 @@ +export const MaintenanceRepeats = ["no repeat", "daily", "weekly"] as const; +export type MaintenanceRepeat = (typeof MaintenanceRepeats)[number]; + +export interface Maintenance { + id: string; + orgId: string; + teamId: string; + name: string; + isActive: boolean; + repeat: MaintenanceRepeat; + monitors: string[]; + startTime: Date; + endTime: Date; + createdBy: string; + updatedBy: string; + createdAt: Date; + updatedAt: Date; +} diff --git a/server/src/types/domain/org.ts b/server/src/types/domain/org.ts new file mode 100644 index 000000000..cad99dfac --- /dev/null +++ b/server/src/types/domain/org.ts @@ -0,0 +1,39 @@ +import { PlanKey } from "@/types/entitlements.js"; +export type SubscriptionStatus = + | "active" + | "trialing" + | "past_due" + | "canceled" + | "incomplete"; + +export interface Org { + id: string; + name: string; + ownerId: string; + + // Entitlements & plan + planKey: "free" | "pro" | "business" | "enterprise"; + + // Billing + billingCustomerId?: string; + subscriptionId?: string; + priceId: string; + priceIds?: string[]; + subscriptionStatus?: SubscriptionStatus; + currentPeriodStart?: Date; + currentPeriodEnd?: Date; + + // Cached entitlements + entitlements: { + plan: PlanKey; + monitorsMax: number; + notificationChannelsMax: number; + statusPagesMax: number; + checksIntervalMsMin: number; + teamsMax: number; + notificationsEnabled: boolean; + retentionDays: number; + }; + createdAt: Date; + updatedAt: Date; +} diff --git a/server/src/types/domain/orgMembership.ts b/server/src/types/domain/orgMembership.ts new file mode 100644 index 000000000..1927a6d0e --- /dev/null +++ b/server/src/types/domain/orgMembership.ts @@ -0,0 +1,9 @@ +export interface OrgMembership extends Document { + id: string; + orgId: string; + userId: string; + roleId?: string; + role: string; + createdAt: Date; + updatedAt: Date; +} diff --git a/server/src/types/domain/role.ts b/server/src/types/domain/role.ts new file mode 100644 index 000000000..15a411da0 --- /dev/null +++ b/server/src/types/domain/role.ts @@ -0,0 +1,12 @@ +export const ScopeTypes = ["organization", "team"] as const; +export type ScopeType = (typeof ScopeTypes)[number]; + +export interface Role { + id: string; + organizationId: string; + name: string; + scope: ScopeType; + permissions: string[]; + createdAt: Date; + updatedAt: Date; +} diff --git a/server/src/types/domain/statsDaily.ts b/server/src/types/domain/statsDaily.ts new file mode 100644 index 000000000..5b7cab01d --- /dev/null +++ b/server/src/types/domain/statsDaily.ts @@ -0,0 +1,24 @@ +import type { IStatsBase } from "@/types/domain/index.js"; + +import type { + IInfraCpu, + IInfraDiskEntry, + IInfraHost, + IInfraMemory, + IInfraNetEntry, + IPageSpeedMetrics, +} from "@/types/domain/index.js"; + +export interface IStatsDaily extends IStatsBase, IPageSpeedMetrics { + cpu?: IInfraCpu; + memory?: IInfraMemory; + disk?: IInfraDiskEntry[]; + host?: IInfraHost; + net?: IInfraNetEntry[]; + // Docker rollups + dockerRunningPercent?: number; + dockerHealthyPercent?: number; + dockerRunningContainers?: number; + dockerHealthyContainers?: number; + dockerTotalContainers?: number; +} diff --git a/server/src/types/domain/statsHourly.ts b/server/src/types/domain/statsHourly.ts new file mode 100644 index 000000000..3b6760ca1 --- /dev/null +++ b/server/src/types/domain/statsHourly.ts @@ -0,0 +1,100 @@ +import type { MonitorType } from "@/types/domain/index.js"; + +// Common/base metrics across monitor types +export interface IStatsBase { + monitorId: string; + teamId: string; + type: MonitorType; + windowStart: Date; // inclusive + windowEnd: Date; // exclusive + finalized: boolean; + count: number; // number of checks in window + avgResponseTime: number; // ms + upChecks?: number; + downChecks?: number; + avgResponseTimeUp?: number; + avgResponseTimeDown?: number; + createdAt: Date; + updatedAt: Date; +} + +// Pagespeed rollups (averages per window) +export interface IPageSpeedMetrics { + accessibility?: number; + bestPractices?: number; + seo?: number; + performance?: number; + cls?: number; + si?: number; + fcp?: number; + lcp?: number; + tbt?: number; +} + +// Infrastructure rollups +export interface IInfraCpu { + physical_core?: number; + logical_core?: number; + frequency?: number; + current_frequency?: number; + temperature?: number[]; // averaged per sensor index + free_percent?: number; + used_percent?: number; +} + +export interface IInfraMemory { + total_bytes?: number; + available_bytes?: number; + used_bytes?: number; + usage_percent?: number; +} + +export interface IInfraDiskEntry { + device?: string; + total_bytes?: number; + free_bytes?: number; + used_bytes?: number; + usage_percent?: number; + total_inodes?: number; + free_inodes?: number; + used_inodes?: number; + inodes_usage_percent?: number; + read_bytes?: number; + write_bytes?: number; + read_time?: number; + write_time?: number; +} + +export interface IInfraHost { + os?: string; + platform?: string; + kernel_version?: string; + pretty_name?: string; +} + +export interface IInfraNetEntry { + name?: string; + bytes_sent?: number; + bytes_recv?: number; + packets_sent?: number; + packets_recv?: number; + err_in?: number; + err_out?: number; + drop_in?: number; + drop_out?: number; + fifo_in?: number; + fifo_out?: number; +} + +export interface IStatsHourly extends IStatsBase, IPageSpeedMetrics { + cpu?: IInfraCpu; + memory?: IInfraMemory; + disk?: IInfraDiskEntry[]; + host?: IInfraHost; + net?: IInfraNetEntry[]; + dockerRunningPercent?: number; + dockerHealthyPercent?: number; + dockerRunningContainers?: number; + dockerHealthyContainers?: number; + dockerTotalContainers?: number; +} diff --git a/server/src/types/domain/team.ts b/server/src/types/domain/team.ts new file mode 100644 index 000000000..2319826ad --- /dev/null +++ b/server/src/types/domain/team.ts @@ -0,0 +1,9 @@ +export interface Team { + id: string; + orgId: string; + name: string; + description?: string; + isSystem?: boolean; + createdAt: Date; + updatedAt: Date; +} diff --git a/server/src/types/domain/teamMembership.ts b/server/src/types/domain/teamMembership.ts new file mode 100644 index 000000000..571b41aaf --- /dev/null +++ b/server/src/types/domain/teamMembership.ts @@ -0,0 +1,9 @@ +export interface TeamMembership { + id: string; + orgId: string; + teamId: string; + userId: string; + roleId: string; + createdAt: Date; + updatedAt: Date; +} diff --git a/server/src/types/domain/user.ts b/server/src/types/domain/user.ts new file mode 100644 index 000000000..8a82cb9b6 --- /dev/null +++ b/server/src/types/domain/user.ts @@ -0,0 +1,10 @@ +export interface User { + id: string; + firstName: string; + lastName: string; + email: string; + passwordHash: string; + lastLoginAt?: Date; + createdAt: Date; + updatedAt: Date; +}