Skip to content

Commit

Permalink
migration
Browse files Browse the repository at this point in the history
  • Loading branch information
LinboLen committed Jul 14, 2024
1 parent c192132 commit e8c3078
Show file tree
Hide file tree
Showing 5 changed files with 633 additions and 0 deletions.
116 changes: 116 additions & 0 deletions libs/fedaco/src/migrations/database-migration-repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import type { ConnectionResolverInterface } from '../interface/connection-resolver-interface';
import type { MigrationRepositoryInterface } from './migration-repository-interface';

export class DatabaseMigrationRepository implements MigrationRepositoryInterface {
/*The database connection resolver instance.*/
_resolver: ConnectionResolverInterface;
/*The name of the migration table.*/
_table: string;
/*The name of the database connection to use.*/
_connection: string;

/*Create a new database migration repository instance.*/
public constructor(resolver: ConnectionResolverInterface, table: string) {
this._table = table;
this._resolver = resolver;
}

/*Get the completed migrations.*/
public async getRan() {
return await this.table()
.orderBy('batch', 'asc')
.orderBy('migration', 'asc')
.pluck('migration') as Promise<any[]>;
}

/*Get the list of migrations.*/
public async getMigrations(steps: number) {
const query = this.table().where('batch', '>=', '1');
return query.orderBy('batch', 'desc').orderBy('migration', 'desc').take(steps).get();
}

/*Get the list of the migrations by batch number.*/
public async getMigrationsByBatch(batch: number) {
return this.table().where('batch', batch).orderBy('migration', 'desc').get();
}

/*Get the last migration batch.*/
public async getLast() {
const query = this.table().where('batch', await this.getLastBatchNumber());
return query.orderBy('migration', 'desc').get();
}

/*Get the completed migrations with their batch numbers.*/
public async getMigrationBatches() {
return this.table()
.orderBy('batch', 'asc')
.orderBy('migration', 'asc')
.pluck('batch', 'migration');
}

/*Log that a migration was run.*/
public async log(file: string, batch: number) {
const record = {
'migration': file,
'batch' : batch
};
await this.table().insert(record);
}

/*Remove a migration from the log.*/
public async delete(migration: any) {
this.table().where('migration', migration.migration).delete();
}

/*Get the next migration batch number.*/
public async getNextBatchNumber() {
return (await this.getLastBatchNumber()) + 1;
}

/*Get the last migration batch number.*/
public async getLastBatchNumber() {
return this.table().max('batch');
}

/*Create the migration repository data store.*/
public createRepository() {
const schema = this.getConnection().getSchemaBuilder();
schema.create(this._table, table => {
table.increments('id');
table.string('migration');
table.integer('batch');
});
}

/*Determine if the migration repository exists.*/
public repositoryExists() {
const schema = this.getConnection().getSchemaBuilder();
return schema.hasTable(this._table);
}

/*Delete the migration repository data store.*/
public async deleteRepository() {
const schema = this.getConnection().getSchemaBuilder();
await schema.drop(this._table);
}

/*Get a query builder for the migration table.*/
protected table() {
return this.getConnection().table(this.table).useWriteConnection();
}

/*Get the connection resolver instance.*/
public getConnectionResolver() {
return this._resolver;
}

/*Resolve the database connection instance.*/
public getConnection() {
return this._resolver.connection(this._connection);
}

/*Set the information source to gather data.*/
public setSource(name: string) {
this._connection = name;
}
}
102 changes: 102 additions & 0 deletions libs/fedaco/src/migrations/migration-creator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { isBlank } from '@gradii/nanofn';
import { format } from 'date-fns';

export class MigrationCreator {
/*The filesystem instance.*/
files: Filesystem;
/*The custom app stubs directory.*/
customStubPath: string;
/*The registered post create hooks.*/
postCreate: any[] = [];

/*Create a new migration creator instance.*/
public constructor(files: Filesystem, customStubPath: string) {
this.files = files;
this.customStubPath = customStubPath;
}

/*Create a new migration at the given path.*/
public create(name: string, path: string, table: string | null = null, create = false) {
this.ensureMigrationDoesntAlreadyExist(name, path);
const stub = this.getStub(table, create);
path = this.getPath(name, path);
this.files.ensureDirectoryExists(dirname(path));
this.files.put(path, this.populateStub(stub, table));
this.firePostCreateHooks(table, path);
return path;
}

/*Ensure that a migration with the given name doesn't already exist.*/
protected ensureMigrationDoesntAlreadyExist(name: string, migrationPath: string = null) {
if (!empty(migrationPath)) {
const migrationFiles = this.files.glob(migrationPath + '/*.php');
for (const migrationFile of migrationFiles) {
this.files.requireOnce(migrationFile);
}
}
if (class_exists(className = this.getClassName(name))) {
throw new InvalidArgumentException('"A {$className} class already exists."');
}
}

/*Get the migration stub file.*/
protected getStub(table: string | null, create: boolean) {
let stub;
if (isBlank(table)) {
stub = this.files.exists(
customPath = this.customStubPath + '/migration.stub') ? customPath : this.stubPath() + '/migration.stub';
} else if (create) {
stub = this.files.exists(
customPath = this.customStubPath + '/migration.create.stub') ? customPath : this.stubPath() + '/migration.create.stub';
} else {
stub = this.files.exists(
customPath = this.customStubPath + '/migration.update.stub') ? customPath : this.stubPath() + '/migration.update.stub';
}
return this.files.get(stub);
}

/*Populate the place-holders in the migration stub.*/
protected populateStub(stub: string, table: string | null) {
if (!isBlank(table)) {
var stub = str_replace(['DummyTable', '{{ table }}', '{{table}}'], table, stub);
}
return stub;
}

/*Get the class name of a migration name.*/
protected getClassName(name: string) {
return Str.studly(name);
}

/*Get the full path to the migration.*/
protected getPath(name: string, path: string) {
return path + '/' + this.getDatePrefix() + '_' + name + '.php';
}

/*Fire the registered post create hooks.*/
protected firePostCreateHooks(table: string | null, path: string) {
for (const callback of this.postCreate) {
callback(table, path);
}
}

/*Register a post migration create hook.*/
public afterCreate(callback: Function) {
this.postCreate.push(callback);
}

/*Get the date prefix for the migration.*/
protected getDatePrefix() {
return format(new Date(), 'yyyy_MM_dd_HHmmss');
}

/*Get the path to the stubs.*/
public stubPath() {
return __DIR__ + '/stubs';
}

/*Get the filesystem instance.*/
public getFilesystem() {
return this.files;
}
}
37 changes: 37 additions & 0 deletions libs/fedaco/src/migrations/migration-repository-interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export interface MigrationRepositoryInterface {
/*Get the completed migrations.*/
getRan(): Promise<any[]>;

/*Get the list of migrations.*/
getMigrations(steps: number): Promise<any[]>;

/*Get the list of the migrations by batch.*/
getMigrationsByBatch(batch: number);

/*Get the last migration batch.*/
getLast();

/*Get the completed migrations with their batch numbers.*/
getMigrationBatches();

/*Log that a migration was run.*/
log(file: string, batch: number);

/*Remove a migration from the log.*/
delete(migration: object);

/*Get the next migration batch number.*/
getNextBatchNumber();

/*Create the migration repository data store.*/
createRepository();

/*Determine if the migration repository exists.*/
repositoryExists();

/*Delete the migration repository data store.*/
deleteRepository();

/*Set the information source to gather data.*/
setSource(name: string);
}
11 changes: 11 additions & 0 deletions libs/fedaco/src/migrations/migration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export class Migration {
/*The name of the database connection to use.*/
_connection: string | null;
/*Enables, if supported, wrapping the migration within a transaction.*/
_withinTransaction = true;

/*Get the migration connection name.*/
public getConnection() {
return this._connection;
}
}
Loading

0 comments on commit e8c3078

Please sign in to comment.