- 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>
120 lines
3.2 KiB
TypeScript
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)
|
|
}
|
|
}
|