diff --git a/server/src/config/database.js b/server/src/config/database.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/server/src/config/services.ts b/server/src/config/services.ts index 0cf55590b..8cfcb400b 100644 --- a/server/src/config/services.ts +++ b/server/src/config/services.ts @@ -1,4 +1,3 @@ -import TranslationService from "../service/system/translationService.js"; import MongoDB from "../db/MongoDB.js"; import NetworkService from "../service/infrastructure/networkService.js"; import EmailService from "../service/infrastructure/emailService.js"; @@ -81,6 +80,7 @@ import { MongoSettingsRepository, MongoNotificationsRepository, MongoIncidentRepository, + MongoTeamsRepository, IMonitorsRepository, IChecksRepository, IMonitorStatsRepository, @@ -91,14 +91,13 @@ import { ISettingsRepository, INotificationsRepository, IIncidentsRepository, + ITeamsRepository, } from "@/repositories/index.js"; import { ILogger } from "@/utils/logger.js"; import { EnvConfig } from "@/service/system/settingsService.js"; export type InitializedServices = { - //v1 settingsService: any; - translationService: any; db: any; networkService: any; emailService: any; @@ -127,6 +126,7 @@ export type InitializedServices = { settingsRepository: ISettingsRepository; notificationsRepository: INotificationsRepository; incidentsRepository: IIncidentsRepository; + teamsRepository: ITeamsRepository; }; export const initializeServices = async ({ @@ -138,9 +138,6 @@ export const initializeServices = async ({ envSettings: EnvConfig; settingsService: any; }): Promise => { - const translationService = new TranslationService(logger); - await translationService.initialize(); - // Create DB const inviteModule = new InviteModule({ InviteToken, crypto }); const statusPageModule = new StatusPageModule({ StatusPage, NormalizeData, AppSettings }); @@ -177,6 +174,8 @@ export const initializeServices = async ({ const settingsRepository = new MongoSettingsRepository(); const notificationsRepository = new MongoNotificationsRepository(); const incidentsRepository = new MongoIncidentRepository(); + const teamsRepository = new MongoTeamsRepository(); + const networkService = new NetworkService({ axios, got, @@ -260,6 +259,7 @@ export const initializeServices = async ({ invitesRepository, recoveryTokensRepository, settingsRepository, + teamsRepository, }); const diagnosticService = new DiagnosticService(); @@ -289,7 +289,6 @@ export const initializeServices = async ({ const services = { //v1 settingsService, - translationService, db, networkService, emailService, @@ -318,6 +317,7 @@ export const initializeServices = async ({ settingsRepository, notificationsRepository, incidentsRepository, + teamsRepository, }; return services; diff --git a/server/src/db/models/Team.js b/server/src/db/models/Team.js deleted file mode 100755 index 95337c201..000000000 --- a/server/src/db/models/Team.js +++ /dev/null @@ -1,14 +0,0 @@ -import mongoose from "mongoose"; -const TeamSchema = mongoose.Schema( - { - email: { - type: String, - required: true, - unique: true, - }, - }, - { - timestamps: true, - } -); -export default mongoose.model("Team", TeamSchema); diff --git a/server/src/db/models/Team.ts b/server/src/db/models/Team.ts new file mode 100755 index 000000000..608f90ad8 --- /dev/null +++ b/server/src/db/models/Team.ts @@ -0,0 +1,28 @@ +import { Schema, model, type Types } from "mongoose"; + +import type { Team } from "@/types/index.js"; + +type TeamDocumentBase = Omit & {}; +interface TeamDocument extends TeamDocumentBase { + _id: Types.ObjectId; + createdAt: Date; + updatedAt: Date; +} + +const TeamSchema = new Schema( + { + email: { + type: String, + required: true, + unique: true, + }, + }, + { + timestamps: true, + } +); +const TeamModel = model("Team", TeamSchema); + +export type { TeamDocument }; +export { TeamModel }; +export default TeamModel; diff --git a/server/src/db/models/index.ts b/server/src/db/models/index.ts index 4b71937bb..f762027ec 100644 --- a/server/src/db/models/index.ts +++ b/server/src/db/models/index.ts @@ -27,3 +27,6 @@ export { default as NotificationModel } from "@/db/models/Notification.js"; export * from "@/db/models/Incident.js"; export { default as IncidentModel } from "@/db/models/Incident.js"; + +export * from "@/db/models/Team.js"; +export { default as TeamModel } from "@/db/models/Team.js"; diff --git a/server/src/repositories/index.ts b/server/src/repositories/index.ts index 72dfd2f40..ac5125af9 100644 --- a/server/src/repositories/index.ts +++ b/server/src/repositories/index.ts @@ -27,3 +27,6 @@ export { default as MongoNotificationsRepository } from "@/repositories/notifica export * from "@/repositories/incidents/IIncidentsRepository.js"; export { default as MongoIncidentRepository } from "@/repositories/incidents/MongoIncidentRepository.js"; + +export * from "@/repositories/teams/ITeamsRepository.js"; +export { default as MongoTeamsRepository } from "@/repositories/teams/MongoTeamsRepository.js"; diff --git a/server/src/repositories/teams/ITeamsRepository.ts b/server/src/repositories/teams/ITeamsRepository.ts new file mode 100644 index 000000000..9ccdd97fb --- /dev/null +++ b/server/src/repositories/teams/ITeamsRepository.ts @@ -0,0 +1,9 @@ +import type { Team } from "@/types/index.js"; +export interface ITeamsRepository { + // create + create(email: string): Promise; + // fetch + // update + // delete + // other +} diff --git a/server/src/repositories/teams/MongoTeamsRepository.ts b/server/src/repositories/teams/MongoTeamsRepository.ts new file mode 100644 index 000000000..93a306787 --- /dev/null +++ b/server/src/repositories/teams/MongoTeamsRepository.ts @@ -0,0 +1,36 @@ +import { Team } from "@/types/index.js"; +import { TeamDocument, TeamModel } from "@/db/models/index.js"; +import { ITeamsRepository } from "@/repositories/index.js"; +import mongoose from "mongoose"; + +class MongoTeamsRepository implements ITeamsRepository { + private toStringId = (value?: mongoose.Types.ObjectId | string | null): string => { + if (!value) { + return ""; + } + return value instanceof mongoose.Types.ObjectId ? value.toString() : String(value); + }; + + private toDateString = (value?: Date | string | null): string => { + if (!value) { + return new Date(0).toISOString(); + } + return value instanceof Date ? value.toISOString() : new Date(value).toISOString(); + }; + + private toEntity = (doc: TeamDocument): Team => { + return { + id: this.toStringId(doc._id), + email: doc.email, + createdAt: this.toDateString(doc.createdAt), + updatedAt: this.toDateString(doc.updatedAt), + }; + }; + + create = async (email: string) => { + const team = await TeamModel.create({ email }); + return this.toEntity(team); + }; +} + +export default MongoTeamsRepository; diff --git a/server/src/service/business/userService.ts b/server/src/service/business/userService.ts index f6f489b8e..b9883f594 100644 --- a/server/src/service/business/userService.ts +++ b/server/src/service/business/userService.ts @@ -1,9 +1,15 @@ -import { IInvitesRepository, IMonitorsRepository, IRecoveryTokensRepository, IUsersRepository, ISettingsRepository } from "@/repositories/index.js"; -import Team from "@/db/models/Team.js"; +import { + IInvitesRepository, + IMonitorsRepository, + IRecoveryTokensRepository, + IUsersRepository, + ISettingsRepository, + ITeamsRepository, +} from "@/repositories/index.js"; import type { User } from "@/types/index.js"; import bcrypt from "bcryptjs"; import { AppError } from "@/utils/AppError.js"; -import { ISuperSimpleQueue } from "../infrastructure/SuperSimpleQueue/SuperSimpleQueue.js"; +import { ISuperSimpleQueue } from "@/service/infrastructure/SuperSimpleQueue/SuperSimpleQueue.js"; const SERVICE_NAME = "userService"; @@ -21,6 +27,7 @@ class UserService { private invitesRepository: IInvitesRepository; private recoveryTokensRepository: IRecoveryTokensRepository; private settingsRepository: ISettingsRepository; + private teamsRepository: ITeamsRepository; constructor({ crypto, @@ -34,6 +41,7 @@ class UserService { invitesRepository, recoveryTokensRepository, settingsRepository, + teamsRepository, }: { crypto: any; emailService: any; @@ -47,6 +55,7 @@ class UserService { invitesRepository: IInvitesRepository; recoveryTokensRepository: IRecoveryTokensRepository; settingsRepository: ISettingsRepository; + teamsRepository: ITeamsRepository; }) { this.emailService = emailService; this.settingsService = settingsService; @@ -59,6 +68,7 @@ class UserService { this.invitesRepository = invitesRepository; this.recoveryTokensRepository = recoveryTokensRepository; this.settingsRepository = settingsRepository; + this.teamsRepository = teamsRepository; } get serviceName() { @@ -85,10 +95,11 @@ class UserService { const jwtSecret = this.crypto.randomBytes(64).toString("hex"); await this.settingsRepository.update({ jwtSecret }); // Create a new team - const team = new Team({ - email: user.email, - }); - user.teamId = team._id; + if (!user.email) { + throw new AppError({ message: "Email is required for first user", service: SERVICE_NAME, method: "registerUser", status: 400 }); + } + const team = await this.teamsRepository.create(user.email); + user.teamId = team.id; } const newUser = await this.usersRepository.create({ ...user }, file); diff --git a/server/src/service/system/translationService.js b/server/src/service/system/translationService.js deleted file mode 100755 index 0d3893852..000000000 --- a/server/src/service/system/translationService.js +++ /dev/null @@ -1,92 +0,0 @@ -import fs from "fs"; -import path from "path"; - -class TranslationService { - static SERVICE_NAME = "TranslationService"; - constructor(logger) { - this.logger = logger; - this.translations = {}; - this._language = "en"; - this.localesDir = path.join(process.cwd(), "src/locales"); - } - - get serviceName() { - return TranslationService.SERVICE_NAME; - } - - setLanguage(language) { - this._language = language; - } - - get language() { - return this._language; - } - - async initialize() { - try { - await this.loadFromFiles(); - } catch (error) { - this.logger.error({ - message: error.message, - service: "TranslationService", - method: "initialize", - stack: error.stack, - }); - } - } - - async loadFromFiles() { - try { - if (!fs.existsSync(this.localesDir)) { - return false; - } - - const files = fs.readdirSync(this.localesDir).filter((file) => file.endsWith(".json")); - - if (files.length === 0) { - return false; - } - - for (const file of files) { - const language = file.replace(".json", ""); - const filePath = path.join(this.localesDir, file); - const content = fs.readFileSync(filePath, "utf8"); - this.translations[language] = JSON.parse(content); - } - - this.logger.info({ - message: "Translations loaded from files successfully", - service: "TranslationService", - method: "loadFromFiles", - }); - - return true; - } catch (error) { - this.logger.error({ - message: error.message, - service: "TranslationService", - method: "loadFromFiles", - stack: error.stack, - }); - return false; - } - } - - getTranslation(key) { - let language = this._language; - - try { - return this.translations[language]?.[key] || this.translations["en"]?.[key] || key; - } catch (error) { - this.logger.error({ - message: error.message, - service: "TranslationService", - method: "getTranslation", - stack: error.stack, - }); - return key; - } - } -} - -export default TranslationService; diff --git a/server/src/types/index.ts b/server/src/types/index.ts index b0b6b07c5..9464622ac 100644 --- a/server/src/types/index.ts +++ b/server/src/types/index.ts @@ -11,3 +11,4 @@ export * from "@/types/notification.js"; export * from "@/types/alert.js"; export * from "@/types/incident.js"; export * from "@/types/email.js"; +export * from "@/types/team.js"; diff --git a/server/src/types/team.ts b/server/src/types/team.ts new file mode 100644 index 000000000..1bca943a0 --- /dev/null +++ b/server/src/types/team.ts @@ -0,0 +1,6 @@ +export interface Team { + id: string; + email: string; + createdAt: string; + updatedAt: string; +}