Files
adam-acs/database/mongo_migrator.ts
Kevin L'HereecandCopilot d1c65dbf1e chore: initial commit — AdonisJS v7 TR-069 ACS
- Docker infrastructure (Dockerfile, docker-compose.yml, genieacs-sim)
- AdonisJS v7 app with Inertia.js + Vue 3
- MongoDB integration
- CWMP/TR-069 state machine engine
- Configuration engine with compliance tracking
- Branding: ADAM ACS (favicon, navbar, locales)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-29 16:46:37 +02:00

120 lines
3.2 KiB
TypeScript

import { Db } from 'mongodb'
import { readdir } from 'node:fs/promises'
import { join } from 'node:path'
import { pathToFileURL } from 'node:url'
export abstract class MongoMigration {
protected db: Db
constructor(db: Db) {
this.db = db
}
abstract up(): Promise<void>
abstract down(): Promise<void>
}
export class MigratorRunner {
private db: Db
private migrationsPath: string
constructor(db: Db, migrationsPath: string) {
this.db = db
this.migrationsPath = migrationsPath
}
private get migrationsCollection() {
return this.db.collection('_migrations')
}
async run(): Promise<string[]> {
const files = await this.getMigrationFiles()
const executed = await this.getExecutedMigrations()
const pending = files.filter((f) => !executed.includes(f))
if (pending.length === 0) {
return []
}
const batch = await this.getNextBatch()
const ran: string[] = []
for (const file of pending) {
const migration = await this.loadMigration(file)
await migration.up()
await this.migrationsCollection.insertOne({
name: file,
batch,
executed_at: new Date(),
})
ran.push(file)
}
return ran
}
async rollback(): Promise<string[]> {
const lastBatch = await this.getLastBatch()
if (lastBatch === 0) return []
const migrations = await this.migrationsCollection
.find({ batch: lastBatch })
.sort({ executed_at: -1 })
.toArray()
const rolledBack: string[] = []
for (const record of migrations) {
const migration = await this.loadMigration(record.name)
await migration.down()
await this.migrationsCollection.deleteOne({ _id: record._id })
rolledBack.push(record.name)
}
return rolledBack
}
async status(): Promise<{ name: string; batch: number | null; ran: boolean }[]> {
const files = await this.getMigrationFiles()
const executed = await this.migrationsCollection.find({}).toArray()
const executedMap = new Map(executed.map((e) => [e.name, e.batch]))
return files.map((file) => ({
name: file,
batch: executedMap.get(file) ?? null,
ran: executedMap.has(file),
}))
}
private async getMigrationFiles(): Promise<string[]> {
try {
const files = await readdir(this.migrationsPath)
return files.filter((f) => f.endsWith('.ts') || f.endsWith('.js')).sort()
} catch {
return []
}
}
private async getExecutedMigrations(): Promise<string[]> {
const docs = await this.migrationsCollection.find({}).toArray()
return docs.map((d) => d.name)
}
private async getNextBatch(): Promise<number> {
const last = await this.getLastBatch()
return last + 1
}
private async getLastBatch(): Promise<number> {
const doc = await this.migrationsCollection.find({}).sort({ batch: -1 }).limit(1).toArray()
return doc.length > 0 ? doc[0].batch : 0
}
private async loadMigration(file: string): Promise<MongoMigration> {
const filePath = join(this.migrationsPath, file)
const fileUrl = pathToFileURL(filePath).href
const module = await import(fileUrl)
const MigrationClass = module.default
return new MigrationClass(this.db)
}
}