Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/core/base/Factory.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { faker } from "@faker-js/faker";
import { ICtor } from "@src/core/interfaces/ICtor";
import IFactory from "@src/core/interfaces/IFactory";

import { IModel } from "../interfaces/IModel";
import IModelAttributes from "../interfaces/IModelData";
import { IModel } from "@src/core/interfaces/IModel";
import IModelAttributes from "@src/core/interfaces/IModelData";

/**
* Abstract base class for factories that create instances of a specific model.
Expand Down
2 changes: 1 addition & 1 deletion src/core/domains/auth/factory/userFactory.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import User, { IUserData } from '@src/app/models/auth/User';
import User from '@src/app/models/auth/User';
import Factory from '@src/core/base/Factory';

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
export default class CommandRegisterException extends Error {

constructor(name: string) {
super(`Command '${name}' could not be registered`);
super(`Command '${name}' could not be registered. A command with the same signature may already exist.`);
this.name = 'CommandRegisterException';
}

Expand Down
5 changes: 5 additions & 0 deletions src/core/domains/database/base/BaseDatabaseSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ abstract class BaseDatabaseSchema<Provider extends IDatabaseProvider = IDatabase
*/
abstract tableExists(name: string): Promise<boolean>;

/**
* Abstract method to drop all tables in the database
*/
abstract dropAllTables(): Promise<void>;

}

export default BaseDatabaseSchema
5 changes: 5 additions & 0 deletions src/core/domains/database/interfaces/IDatabaseSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,9 @@ export interface IDatabaseSchema {
* @throws Error if the method is not implemented
*/
alterTable(name: string, ...args: any[]): Promise<void>

/**
* Drop all tables in the database
*/
dropAllTables(): Promise<void>;
}
17 changes: 17 additions & 0 deletions src/core/domains/database/schema/MongoDBSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,23 @@ class MongoDBSchema extends BaseDatabaseSchema {
throw new Error("Method not implemented.");
}

/**
* Drop all tables in the database
*
* @returns A promise resolving when all tables have been dropped
*/
async dropAllTables(): Promise<void> {
const mongoClient = this.driver.getClient();
const db = mongoClient.db();

const collections = await db.listCollections().toArray();

for(const collection of collections) {
await db.dropCollection(collection.name);
}

}

}

export default MongoDBSchema
9 changes: 9 additions & 0 deletions src/core/domains/database/schema/PostgresSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,15 @@ class PostgresSchema extends BaseDatabaseSchema<Postgres> {
return await queryInterface.tableExists(tableName);
}

/**
* Drop all tables in the database
*/
async dropAllTables(): Promise<void> {
const sequelize = this.driver.getClient();
const queryInterface = sequelize.getQueryInterface();
await queryInterface.dropAllTables();
}

}

export default PostgresSchema
2 changes: 1 addition & 1 deletion src/core/domains/database/validator/DocumentValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class DocumentValidator implements IDocumentValidator {
if(document.id) {
const response = returnOrThrow<boolean>({
shouldThrow: this.throwExceptions,
throwable: new InvalidDocument(`An id property was not expected but found at index ${i}`),
throwable: new InvalidDocument(`An id property was NOT expected but found at index ${i}`),
returns: false
})

Expand Down
2 changes: 1 addition & 1 deletion src/core/domains/make/base/BaseMakeFileCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export default class BaseMakeFileCommand extends BaseCommand {
const name = this.getArguementByKey('name')?.value as string;

if(this.makeFileService.existsInTargetDirectory()) {
throw new CommandExecutionException(`File already exists with name '${name}'`);
throw new CommandExecutionException(`File already exists with name '${name}', full path: ${this.makeFileService.getTargetDirFullPath()}`);
}

// Write the new file
Expand Down
19 changes: 19 additions & 0 deletions src/core/domains/make/commands/MakeSeederCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import BaseMakeFileCommand from "@src/core/domains/make/base/BaseMakeFileCommand";
import MigrationFileService from "@src/core/domains/migrations/services/MigrationFilesService";

export default class MakeSeederCommand extends BaseMakeFileCommand {

constructor() {
super({
signature: 'make:seeder',
description: 'Creates a new database seeder',
makeType: 'Seeder',
args: ['name'],
endsWith: 'Seeder',
customFilename: (name: string) => {
return (new MigrationFileService).createDateFilename(name)
}
})
}

}
2 changes: 2 additions & 0 deletions src/core/domains/make/consts/MakeTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const targetDirectories: Record<string, string> = {
Action: `${APP_PATH}/actions`,
Validator: `${APP_PATH}/validators`,
Migration: `${APP_PATH}/migrations`,
Seeder: `${APP_PATH}/seeders`,
} as const;

/**
Expand All @@ -44,6 +45,7 @@ export const templates: Record<string, string> = {
Action: `${TEMPLATE_PATH}/Action.ts.template`,
Validator: `${TEMPLATE_PATH}/Validator.ts.template`,
Migration: `${TEMPLATE_PATH}/Migration.ts.template`,
Seeder: `${TEMPLATE_PATH}/Seeder.ts.template`,
} as const;

export default Object.freeze({
Expand Down
2 changes: 1 addition & 1 deletion src/core/domains/make/observers/ArgumentObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class ArgumentObserver<T extends IMakeFileArguments = IMakeFileArguments> extend
return data
}

if(!data.name.endsWith(options.endsWith)) {
if(!data.name.toLowerCase().endsWith(options.endsWith.toLowerCase())) {
data.name = `${data.name}${options.endsWith}`
}

Expand Down
2 changes: 2 additions & 0 deletions src/core/domains/make/providers/MakeProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import MakeSingletonCommand from "@src/core/domains/make/commands/MakeSingletonC
import MakeSubscriberCommand from "@src/core/domains/make/commands/MakeSubscriberCommand";
import MakeValidatorCommand from "@src/core/domains/make/commands/MakeValidatorCommand";
import { App } from "@src/core/services/App";
import MakeSeederCommand from "@src/core/domains/make/commands/MakeSeederCommand";

export default class MakeProvider extends BaseProvider {

Expand All @@ -35,6 +36,7 @@ export default class MakeProvider extends BaseProvider {
MakeActionCommand,
MakeValidatorCommand,
MakeMigrationCommand,
MakeSeederCommand
])
}

Expand Down
32 changes: 32 additions & 0 deletions src/core/domains/make/templates/Seeder.ts.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { faker } from "@faker-js/faker";
import hashPassword from "@src/core/domains/auth/utils/hashPassword";
import BaseSeeder from "@src/core/domains/migrations/base/BaseSeeder";

import User, { IUserData } from "../models/auth/User";

export class #name# extends BaseSeeder {

async up(): Promise<void> {

// Example:
// for (let i = 0; i < 10; i++) {

// const data: IUserData = {
// email: faker.internet.email(),
// password: faker.internet.password(),
// hashedPassword: hashPassword(faker.internet.password()),
// roles: ['user'],
// groups: [],
// firstName: faker.person.firstName(),
// lastName: faker.person.lastName(),
// }

// const user = new User(data);
// await user.save();
// }

}

}

export default #name#
7 changes: 6 additions & 1 deletion src/core/domains/migrations/base/BaseMigration.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IMigration } from "@src/core/domains/migrations/interfaces/IMigration";
import { IMigration, MigrationType } from "@src/core/domains/migrations/interfaces/IMigration";
import { App } from "@src/core/services/App";

/**
Expand All @@ -20,6 +20,11 @@ abstract class BaseMigration implements IMigration {
*/
protected readonly documentManager = App.container('db').documentManager()

/**
* Define the type of migration.
*/
migrationType = 'schema' as MigrationType;

/**
* databaseProvider specifies which database system this migration is designed for.
* If undefined, the migration will run on the default provider.
Expand Down
55 changes: 55 additions & 0 deletions src/core/domains/migrations/base/BaseMigrationCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import BaseCommand from "@src/core/domains/console/base/BaseCommand";
import MigrationTypeEnum from "@src/core/domains/migrations/enums/MigrationTypeEnum";
import MigrationError from "@src/core/domains/migrations/exceptions/MigrationError";
import { IMigrationConfig } from "@src/core/domains/migrations/interfaces/IMigrationConfig";
import { IMigrationService } from "@src/core/domains/migrations/interfaces/IMigrationService";
import MigrationService from "@src/core/domains/migrations/services/MigrationService";


abstract class BaseMigrationCommand extends BaseCommand {

config!: IMigrationConfig;

/**
* Constructor
* @param config
*/
constructor(config: IMigrationConfig = {}) {
super(config);
this.keepProcessAlive = config?.keepProcessAlive ?? this.keepProcessAlive;
}

/**
* Get the migration service for schema migrations
* @returns
*/
getSchemaMigrationService(): IMigrationService {
if(typeof this.config.schemaMigrationDir !== 'string') {
throw new MigrationError('Schema migration directory is not set');
}

return new MigrationService({
migrationType: MigrationTypeEnum.schema,
directory: this.config.schemaMigrationDir
});
}

/**
* Get the migration service for seeder migrations
* @returns An instance of IMigrationService configured for seeder migrations
*/
getSeederMigrationService(): IMigrationService {
if(typeof this.config.seederMigrationDir !== 'string') {
throw new MigrationError('Seeder migration directory is not set');
}

return new MigrationService({
migrationType: MigrationTypeEnum.seeder,
directory: this.config.seederMigrationDir
});
}


}

export default BaseMigrationCommand
25 changes: 25 additions & 0 deletions src/core/domains/migrations/base/BaseSeeder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import BaseMigration from "@src/core/domains/migrations/base/BaseMigration";
import { MigrationType } from "@src/core/domains/migrations/interfaces/IMigration";

/**
* BaseSeeder class serves as the foundation for all database seeders.
*/
abstract class BaseSeeder extends BaseMigration {

/**
* The type of migration
*/
migrationType = 'seeder' as MigrationType;

/**
* Optional down method.
*
* @return {Promise<void>}
*/
down(): Promise<void> {
return Promise.resolve();
}

}

export default BaseSeeder;
23 changes: 6 additions & 17 deletions src/core/domains/migrations/commands/MigrateDownCommand.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import BaseCommand from "@src/core/domains/console/base/BaseCommand";
import { IMigrationConfig } from "@src/core/domains/migrations/interfaces/IMigrationConfig";
import MigrationService from "@src/core/domains/migrations/services/MigrationService";

class MigrateDownCommand extends BaseCommand {
import BaseMigrationCommand from "@src/core/domains/migrations/base/BaseMigrationCommand";

class MigrateDownCommand extends BaseMigrationCommand {

/**
* Signature for the command.
Expand All @@ -11,16 +10,6 @@ class MigrateDownCommand extends BaseCommand {

description = 'Rollback migrations';

/**
* Constructor.
* @param config
*/
constructor(config: IMigrationConfig = {}) {
super(config);
// Allow for configurable keepProcessAlive for testing purposes
this.keepProcessAlive = config?.keepProcessAlive ?? this.keepProcessAlive;
}

/**
* Execute the command.
*/
Expand All @@ -29,9 +18,9 @@ class MigrateDownCommand extends BaseCommand {
const batch = this.getArguementByKey('batch')?.value;

// Run the migrations
const service = new MigrationService(this.config);
await service.boot();
await service.down({ batch: batch ? parseInt(batch) : undefined });
const schemaMigrationService = this.getSchemaMigrationService();
await schemaMigrationService.boot();
await schemaMigrationService.down({ batch: batch ? parseInt(batch) : undefined });
}

}
Expand Down
46 changes: 46 additions & 0 deletions src/core/domains/migrations/commands/MigrateFreshCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@

import BaseMigrationCommand from "@src/core/domains/migrations/base/BaseMigrationCommand";
import { App } from "@src/core/services/App";

/**
* MigrateFresh class handles running fresh migrations
*/
class MigrateFreshCommand extends BaseMigrationCommand {

/**
* The signature of the command
*/
public signature: string = 'migrate:fresh';

description = 'Drops all tables and runs fresh migrations';


/**
* Execute the command
*/
async execute() {
if(!await this.confirm()) {
return;
}

// Get the db schema helper
const schema = App.container('db').schema();

// Drop all tables
await schema.dropAllTables();

// Handle migrate:up
const console = App.container('console');
await console.reader(['migrate:up']).handle();
}

private async confirm(): Promise<boolean> {
this.input.writeLine('--- Confirm Action ---');
const answer = await this.input.askQuestion('Are you sure you want to drop all tables and run fresh migrations? (y/n)');
return answer === 'y';

}

}

export default MigrateFreshCommand
Loading