diff --git a/server/src/db/models/index.ts b/server/src/db/models/index.ts index 0969eafc6..537633bfd 100644 --- a/server/src/db/models/index.ts +++ b/server/src/db/models/index.ts @@ -5,3 +5,5 @@ export { Role } from "./auth/Role.js"; export type { IRole } from "./auth/Role.js"; export type { IInvite } from "./auth/Invite.js"; export { Invite } from "./auth/Invite.js"; +export { Migration } from "./migration/Migration.js"; +export type { IMigration } from "./migration/Migration.js"; diff --git a/server/src/db/models/migration/Migration.ts b/server/src/db/models/migration/Migration.ts new file mode 100644 index 000000000..cf76bb48e --- /dev/null +++ b/server/src/db/models/migration/Migration.ts @@ -0,0 +1,34 @@ +import mongoose, { Schema, Document, Types } from "mongoose"; + +export interface IMigration extends Document { + _id: Types.ObjectId; + name: string; + runAt: Date; + success: boolean; + createdAt: Date; + updatedAt: Date; +} + +const migrationSchema = new Schema( + { + name: { + type: String, + required: true, + }, + runAt: { + type: Date, + required: false, + }, + success: { + type: Boolean, + required: false, + }, + }, + { + timestamps: true, + } +); + +migrationSchema.index({ name: 1 }, { unique: true }); + +export const Migration = mongoose.model("Migration", migrationSchema); diff --git a/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.js b/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.ts similarity index 90% rename from server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.js rename to server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.ts index 8f6988de4..b44487e87 100644 --- a/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.js +++ b/server/src/db/mongo/migration/0001_migrateStatusWindowThreshold.ts @@ -1,4 +1,5 @@ import Monitor from "../../models/Monitor.js"; +export const MIGRATION_NAME = "0001_migrateStatusWindowThreshold"; async function migrateStatusWindowThreshold() { try { const monitors = await Monitor.find({ statusWindowThreshold: { $lt: 1 } }); diff --git a/server/src/db/mongo/migration/0002_migrateUsers.ts b/server/src/db/mongo/migration/0002_migrateUsers.ts index 196ac6793..a7cdb76fc 100644 --- a/server/src/db/mongo/migration/0002_migrateUsers.ts +++ b/server/src/db/mongo/migration/0002_migrateUsers.ts @@ -1,5 +1,8 @@ import { User, Role } from "../../models/index.js"; +import { Types } from "mongoose"; + import { DEFAULT_ROLES } from "../../../service/v2/business/AuthService.js"; +export const MIGRATION_NAME = "0002_migrateUsers"; async function migrateUsers() { try { // Create roles if they don't exist @@ -21,20 +24,24 @@ async function migrateUsers() { $or: [{ version: { $exists: false } }, { version: { $lt: 2 } }], }); for (const user of users) { + const newRoleIds: Types.ObjectId[] = []; + for (const role of user.role || []) { if (role === "superadmin") { const superAdminRole = roles.find((role) => role.name === "SuperAdmin"); - user.roles.push(superAdminRole!._id); + newRoleIds.push(superAdminRole!._id); } if (role === "admin") { const managerRole = roles.find((role) => role.name === "Manager"); - user.roles.push(managerRole!._id); + newRoleIds.push(managerRole!._id); } if (role === "user") { const memberRole = roles.find((role) => role.name === "Member"); - user.roles.push(memberRole!._id); + newRoleIds.push(memberRole!._id); } } + const merged = [...new Set([...user.roles, ...newRoleIds])]; + user.roles = merged; user.version = 2; await user.save(); } diff --git a/server/src/db/mongo/migration/index.js b/server/src/db/mongo/migration/index.js deleted file mode 100644 index 2a1da4148..000000000 --- a/server/src/db/mongo/migration/index.js +++ /dev/null @@ -1,9 +0,0 @@ -import { migrateStatusWindowThreshold } from "./0001_migrateStatusWindowThreshold.js"; -import { migrateUsers } from "./0002_migrateUsers.js"; - -const runMigrations = async () => { - await migrateStatusWindowThreshold(); - await migrateUsers(); -}; - -export { runMigrations }; diff --git a/server/src/db/mongo/migration/index.ts b/server/src/db/mongo/migration/index.ts new file mode 100644 index 000000000..293e0905f --- /dev/null +++ b/server/src/db/mongo/migration/index.ts @@ -0,0 +1,25 @@ +import { migrateStatusWindowThreshold, MIGRATION_NAME as MIGRATION_0001 } from "./0001_migrateStatusWindowThreshold.js"; +import { migrateUsers, MIGRATION_NAME as MIGRATION_0002 } from "./0002_migrateUsers.js"; +import { Migration, IMigration } from "../../models/index.js"; + +const runMigrations = async () => { + const migrations = [ + { name: MIGRATION_0001, migration: migrateStatusWindowThreshold }, + { name: MIGRATION_0002, migration: migrateUsers }, + ]; + + const migrationNames = migrations.map((m) => m.name); + + const appliedMigrations = await Migration.find({ name: { $in: migrationNames }, success: true }); + + const migrationsToRun = migrations.filter((m) => !appliedMigrations.some((am: IMigration) => am.name === m.name)); + + for (const { name, migration } of migrationsToRun) { + console.log(`Running migration: ${name}`); + const success = await migration(); + await Migration.updateOne({ name }, { $set: { runAt: new Date(), success } }, { upsert: true }); + console.log(`Migration ${name} completed with status: ${success ? "success" : "failure"}`); + } +}; + +export { runMigrations };