├── .env.example ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── docker ├── Dockerfile ├── docker-compose.api.yml ├── docker-compose.mongodb.yml ├── docker-compose.network.yml └── docker-compose.postgres.yml ├── eslint.config.mjs ├── fix-imports.js ├── jest.config.js ├── nodemon.json ├── package.json ├── readme.md ├── src ├── app.ts ├── app │ ├── commands │ │ ├── .gitkeep │ │ └── ExampleCommand.ts │ ├── controllers │ │ ├── .gitkeep │ │ └── ExampleController.ts │ ├── events │ │ ├── listeners │ │ │ ├── .gitkeep │ │ │ └── UserCreatedListener.ts │ │ └── subscribers │ │ │ ├── .gitkeep │ │ │ └── UserCreatedSubscriber.ts │ ├── factory │ │ └── UserFactory.ts │ ├── interfaces │ │ ├── .gitkeep │ │ └── IAppService.ts │ ├── middleware │ │ └── .gitkeep │ ├── migrations │ │ ├── 2024-09-06-create-api-token-table.ts │ │ ├── 2024-09-06-create-failed-worker-table.ts │ │ ├── 2024-09-06-create-user-table.ts │ │ └── 2024-09-06-create-worker-table.ts │ ├── models │ │ └── auth │ │ │ └── User.ts │ ├── observers │ │ └── .gitkeep │ ├── providers │ │ ├── AppServiceProvider.ts │ │ └── RoutesProvider.ts │ ├── repositories │ │ └── auth │ │ │ └── UserRepository.ts │ ├── routes │ │ └── api.ts │ ├── seeders │ │ └── .gitkeep │ ├── services │ │ └── AppService.ts │ └── validators │ │ └── user │ │ ├── CreateUserValidator.ts │ │ └── UpdateUserValidator.ts ├── config │ ├── acl.config.ts │ ├── app.config.ts │ ├── auth.config.ts │ ├── commands.config.ts │ ├── database.config.ts │ ├── events.config.ts │ ├── http.config.ts │ ├── mail.config.ts │ ├── providers.config.ts │ └── storage.config.ts ├── core │ ├── Kernel.ts │ ├── actions │ │ └── health.ts │ ├── base │ │ ├── BaseAdapter.ts │ │ ├── BaseConfig.ts │ │ ├── BaseSimpleRegister.ts │ │ ├── Factory.ts │ │ ├── Provider.ts │ │ ├── Repository.ts │ │ ├── Service.ts │ │ └── Singleton.ts │ ├── common │ │ ├── interfaces │ │ │ └── NormalizerInterface.ts │ │ └── normalizers │ │ │ └── JsonNormalizer.ts │ ├── concerns │ │ ├── HasConfigConcern.ts │ │ └── HasSimpleRegisterConcern.ts │ ├── consts │ │ ├── Environment.ts │ │ └── Errors.ts │ ├── domains │ │ ├── accessControl │ │ │ ├── interfaces │ │ │ │ └── IACLService.ts │ │ │ ├── providers │ │ │ │ └── AccessControlProvider.ts │ │ │ └── services │ │ │ │ └── BasicACLService.ts │ │ ├── auth │ │ │ ├── base │ │ │ │ └── BaseAuthAdapter.ts │ │ │ ├── commands │ │ │ │ └── GenerateJwtSecret.ts │ │ │ ├── controllers │ │ │ │ └── AuthController.ts │ │ │ ├── exceptions │ │ │ │ ├── ForbiddenResourceError.ts │ │ │ │ ├── InvalidJWTSecret.ts │ │ │ │ ├── InvalidJwtSettings.ts │ │ │ │ ├── RateLimitedExceededError.ts │ │ │ │ └── UnauthorizedError.ts │ │ │ ├── factory │ │ │ │ └── JwtFactory.ts │ │ │ ├── interfaces │ │ │ │ ├── acl │ │ │ │ │ └── IAclConfig.ts │ │ │ │ ├── adapter │ │ │ │ │ ├── AuthAdapterTypes.t.ts │ │ │ │ │ └── IAuthAdapter.ts │ │ │ │ ├── config │ │ │ │ │ └── IAuth.ts │ │ │ │ ├── jwt │ │ │ │ │ ├── IJsonWebToken.ts │ │ │ │ │ ├── IJwtAuthService.ts │ │ │ │ │ └── IJwtConfig.ts │ │ │ │ ├── models │ │ │ │ │ ├── IApiTokenModel.ts │ │ │ │ │ └── IUserModel.ts │ │ │ │ ├── repository │ │ │ │ │ ├── IApiTokenRepository.ts │ │ │ │ │ └── IUserRepository.ts │ │ │ │ ├── scope │ │ │ │ │ └── Scope.t.ts │ │ │ │ └── service │ │ │ │ │ ├── IAuthService.ts │ │ │ │ │ └── oneTimeService.ts │ │ │ ├── middleware │ │ │ │ ├── AuthorizeMiddleware.ts │ │ │ │ ├── CsrfMiddlware.ts │ │ │ │ └── OneTimeTokenMiddleware.ts │ │ │ ├── models │ │ │ │ ├── ApiToken.ts │ │ │ │ └── AuthUser.ts │ │ │ ├── observers │ │ │ │ ├── ApiTokenObserver.ts │ │ │ │ └── UserObserver.ts │ │ │ ├── providers │ │ │ │ └── AuthProvider.ts │ │ │ ├── repository │ │ │ │ ├── ApiTokenRepitory.ts │ │ │ │ └── UserRepository.ts │ │ │ ├── services │ │ │ │ ├── AuthConfig.ts │ │ │ │ ├── AuthService.ts │ │ │ │ ├── JwtAuthService.ts │ │ │ │ └── OneTimeAuthenticationService.ts │ │ │ ├── usecase │ │ │ │ ├── LoginUseCase.ts │ │ │ │ ├── LogoutUseCase.ts │ │ │ │ ├── RefreshUseCase.ts │ │ │ │ ├── RegisterUseCase.ts │ │ │ │ ├── UpdateUseCase.ts │ │ │ │ └── UserUseCase.ts │ │ │ └── utils │ │ │ │ ├── ScopeMatcher.ts │ │ │ │ ├── comparePassword.ts │ │ │ │ ├── createJwt.ts │ │ │ │ ├── decodeJwt.ts │ │ │ │ ├── generateToken.ts │ │ │ │ └── hashPassword.ts │ │ ├── broadcast │ │ │ ├── abstract │ │ │ │ ├── BroadcastEvent.ts │ │ │ │ └── Broadcaster.ts │ │ │ └── interfaces │ │ │ │ └── IBroadcaster.ts │ │ ├── cast │ │ │ ├── base │ │ │ │ └── BaseCastable.ts │ │ │ ├── concerns │ │ │ │ └── HasCastableConcern.ts │ │ │ ├── interfaces │ │ │ │ ├── CastException.ts │ │ │ │ └── IHasCastableConcern.ts │ │ │ └── service │ │ │ │ └── Castable.ts │ │ ├── collections │ │ │ ├── Collection.ts │ │ │ ├── ProxyCollectionHandler.ts │ │ │ ├── helper │ │ │ │ └── collect.ts │ │ │ └── interfaces │ │ │ │ └── ICollection.ts │ │ ├── console │ │ │ ├── base │ │ │ │ └── BaseCommand.ts │ │ │ ├── commands │ │ │ │ ├── HelpCommand.ts │ │ │ │ └── RouteListCommand.ts │ │ │ ├── exceptions │ │ │ │ ├── CommandArguementParserException.ts │ │ │ │ ├── CommandExecutionException.ts │ │ │ │ ├── CommandNotFoundException.ts │ │ │ │ ├── CommandRegisterException.ts │ │ │ │ └── CommandSignatureInvalid.ts │ │ │ ├── interfaces │ │ │ │ ├── ICommand.ts │ │ │ │ ├── ICommandBootService.ts │ │ │ │ ├── ICommandReader.ts │ │ │ │ ├── ICommandRegister.ts │ │ │ │ ├── ICommandService.ts │ │ │ │ └── IConsoleInputService.ts │ │ │ ├── parsers │ │ │ │ └── CommandArgumentParser.ts │ │ │ ├── providers │ │ │ │ └── ConsoleProvider.ts │ │ │ └── service │ │ │ │ ├── CommandBootService.ts │ │ │ │ ├── CommandReader.ts │ │ │ │ ├── CommandRegister.ts │ │ │ │ ├── ConsoleInputService.ts │ │ │ │ └── ConsoleService.ts │ │ ├── crypto │ │ │ ├── commands │ │ │ │ └── GenerateAppKey.ts │ │ │ ├── interfaces │ │ │ │ ├── BufferingEncoding.t.ts │ │ │ │ ├── ICryptoConfig.ts │ │ │ │ └── ICryptoService.ts │ │ │ ├── providers │ │ │ │ └── CryptoProvider.ts │ │ │ └── service │ │ │ │ └── CryptoService.ts │ │ ├── database │ │ │ ├── base │ │ │ │ ├── BaseDatabaseAdapter.ts │ │ │ │ ├── BaseDocumentManager.ts │ │ │ │ └── BaseSchema.ts │ │ │ ├── exceptions │ │ │ │ ├── CreateDatabaseException.ts │ │ │ │ ├── DatabaseAdapterException.ts │ │ │ │ ├── DatabaseConfigException.ts │ │ │ │ ├── DatabaseConnectionException.ts │ │ │ │ ├── DropDatabaseException.ts │ │ │ │ ├── InvalidObjectId.ts │ │ │ │ ├── InvalidSequelize.ts │ │ │ │ ├── InvalidTable.ts │ │ │ │ └── UnidentifiableDocument.ts │ │ │ ├── interfaces │ │ │ │ ├── IConnectionTypeHelpers.ts │ │ │ │ ├── IDatabaseAdapter.ts │ │ │ │ ├── IDatabaseAdapterSchema.ts │ │ │ │ ├── IDatabaseConfig.ts │ │ │ │ ├── IDatabaseSchema.ts │ │ │ │ ├── IDatabaseService.ts │ │ │ │ ├── IDocumentManager.ts │ │ │ │ ├── IDocumentValidator.ts │ │ │ │ └── IPrepareOptions.ts │ │ │ ├── providers │ │ │ │ ├── DatabaseProvider.ts │ │ │ │ └── DatabaseSetupProvider.ts │ │ │ ├── services │ │ │ │ ├── Database.ts │ │ │ │ ├── DatabaseAdapter.ts │ │ │ │ └── DatabaseConfig.ts │ │ │ └── validator │ │ │ │ └── DocumentValidator.ts │ │ ├── eloquent │ │ │ ├── Eloquent.ts │ │ │ ├── base │ │ │ │ ├── BaseExpression.ts │ │ │ │ └── BaseRelationshipResolver.ts │ │ │ ├── enums │ │ │ │ └── Direction.ts │ │ │ ├── exceptions │ │ │ │ ├── EloquentExpression.ts │ │ │ │ ├── EloquentRelationshipException.ts │ │ │ │ ├── ExpressionException.ts │ │ │ │ ├── InsertException.ts │ │ │ │ ├── InvalidMethodException.ts │ │ │ │ ├── MissingTableException.ts │ │ │ │ ├── QueryBuilderException.ts │ │ │ │ └── UpdateException.ts │ │ │ ├── interfaces │ │ │ │ ├── IEloquent.ts │ │ │ │ ├── IEloquentExpression.ts │ │ │ │ ├── IEloquentQueryBuilderService.ts │ │ │ │ ├── IEqloeuntRelationship.ts │ │ │ │ └── TEnums.ts │ │ │ ├── providers │ │ │ │ └── EloquentQueryProvider.ts │ │ │ ├── relational │ │ │ │ ├── BelongsTo.ts │ │ │ │ ├── GenericRelationship.ts │ │ │ │ ├── HasMany.ts │ │ │ │ └── With.ts │ │ │ └── services │ │ │ │ └── EloquentQueryBuilderService.ts │ │ ├── events │ │ │ ├── base │ │ │ │ ├── BaseDriver.ts │ │ │ │ ├── BaseEvent.ts │ │ │ │ ├── BaseEventListener.ts │ │ │ │ ├── BaseEventService.ts │ │ │ │ └── BaseEventSubciber.ts │ │ │ ├── commands │ │ │ │ └── WorkerCommand.ts │ │ │ ├── concerns │ │ │ │ ├── EventMockableConcern.ts │ │ │ │ └── EventWorkerConcern.ts │ │ │ ├── drivers │ │ │ │ ├── QueableDriver.ts │ │ │ │ └── SyncDriver.ts │ │ │ ├── exceptions │ │ │ │ ├── EventDispatchException.ts │ │ │ │ ├── EventDriverException.ts │ │ │ │ ├── EventInvalidPayloadException.ts │ │ │ │ ├── EventMockException.ts │ │ │ │ ├── EventNotDispatchedException.ts │ │ │ │ └── EventWorkerException.ts │ │ │ ├── interfaces │ │ │ │ ├── IBaseEvent.ts │ │ │ │ ├── IEventConstructors.ts │ │ │ │ ├── IEventDriver.ts │ │ │ │ ├── IEventListener.ts │ │ │ │ ├── IEventPayload.ts │ │ │ │ ├── IEventService.ts │ │ │ │ ├── IEventWorkerConcern.ts │ │ │ │ ├── IMockableConcern.ts │ │ │ │ ├── IQueableDriverOptions.ts │ │ │ │ └── config │ │ │ │ │ ├── IEventConfig.ts │ │ │ │ │ ├── IEventDriversConfig.ts │ │ │ │ │ └── IEventListenersConfig.ts │ │ │ ├── models │ │ │ │ ├── FailedWorkerModel.ts │ │ │ │ └── WorkerModel.ts │ │ │ ├── providers │ │ │ │ └── EventProvider.ts │ │ │ ├── registry │ │ │ │ └── EventRegistry.ts │ │ │ └── services │ │ │ │ └── EventService.ts │ │ ├── express │ │ │ └── exceptions │ │ │ │ ├── HttpContextException.ts │ │ │ │ ├── MissingSecurityError.ts │ │ │ │ ├── QueryFiltersException.ts │ │ │ │ ├── ResourceException.ts │ │ │ │ ├── RouteException.ts │ │ │ │ └── SecurityException.ts │ │ ├── formatter │ │ │ ├── base │ │ │ │ └── BaseFormatter.ts │ │ │ └── interfaces │ │ │ │ └── IFormatter.ts │ │ ├── http │ │ │ ├── base │ │ │ │ ├── Controller.ts │ │ │ │ └── Middleware.ts │ │ │ ├── context │ │ │ │ ├── HttpContext.ts │ │ │ │ ├── RequestContext.ts │ │ │ │ └── RequestContextCleaner.ts │ │ │ ├── data │ │ │ │ ├── HttpCodes.ts │ │ │ │ └── UploadedFile.ts │ │ │ ├── enums │ │ │ │ └── SecurityEnum.ts │ │ │ ├── handlers │ │ │ │ └── responseError.ts │ │ │ ├── interfaces │ │ │ │ ├── BaseRequest.ts │ │ │ │ ├── ErrorResponse.t.ts │ │ │ │ ├── IApiResponse.ts │ │ │ │ ├── IAuthorizedRequest.ts │ │ │ │ ├── IController.ts │ │ │ │ ├── IExpressable.ts │ │ │ │ ├── IHttpConfig.ts │ │ │ │ ├── IHttpContext.ts │ │ │ │ ├── IHttpService.ts │ │ │ │ ├── IMiddleware.ts │ │ │ │ ├── IRequestContext.ts │ │ │ │ ├── IRequestContextCleanUpConfig.ts │ │ │ │ ├── IRequestIdentifable.ts │ │ │ │ ├── IResourceService.ts │ │ │ │ ├── IRouter.ts │ │ │ │ ├── ISecurity.ts │ │ │ │ ├── IValidatorRequest.ts │ │ │ │ ├── Pagination.t.ts │ │ │ │ └── UploadedFile.ts │ │ │ ├── middleware │ │ │ │ ├── BasicLoggerMiddleware.ts │ │ │ │ ├── EndRequestContextMiddleware.ts │ │ │ │ ├── RequestIdMiddleware.ts │ │ │ │ ├── SecurityMiddleware.ts │ │ │ │ ├── StartSessionMiddleware.ts │ │ │ │ ├── ValidationMiddleware.ts │ │ │ │ ├── errorHandler.ts │ │ │ │ └── requestContextLoggerMiddleware.ts │ │ │ ├── providers │ │ │ │ ├── BaseRoutesProvider.ts │ │ │ │ ├── HttpErrorHandlerProvider.ts │ │ │ │ └── HttpProvider.ts │ │ │ ├── resources │ │ │ │ ├── abstract │ │ │ │ │ └── AbastractBaseResourceService.ts │ │ │ │ ├── controller │ │ │ │ │ └── ResourceController.ts │ │ │ │ └── services │ │ │ │ │ ├── ResourceCreateService.ts │ │ │ │ │ ├── ResourceDeleteService.ts │ │ │ │ │ ├── ResourceIndexService.ts │ │ │ │ │ ├── ResourceShowService.ts │ │ │ │ │ └── ResourceUpdateService.ts │ │ │ ├── response │ │ │ │ └── ApiResponse.ts │ │ │ ├── router │ │ │ │ ├── Route.ts │ │ │ │ ├── Router.ts │ │ │ │ ├── RouterBindService.ts │ │ │ │ └── RouterResource.ts │ │ │ ├── routes │ │ │ │ └── healthRoutes.ts │ │ │ ├── security │ │ │ │ ├── abstract │ │ │ │ │ └── AbstractSecurityRule.ts │ │ │ │ ├── rules │ │ │ │ │ ├── HasRoleRule.ts │ │ │ │ │ ├── RateLimitedRule.ts │ │ │ │ │ ├── ResourceOwnerRule.ts │ │ │ │ │ └── ScopeRule.ts │ │ │ │ └── services │ │ │ │ │ ├── SecurityReader.ts │ │ │ │ │ └── SecurityRules.ts │ │ │ ├── services │ │ │ │ └── HttpService.ts │ │ │ └── utils │ │ │ │ ├── Paginate.ts │ │ │ │ ├── QueryFilters.ts │ │ │ │ ├── SortOptions.ts │ │ │ │ ├── getIpAddress.ts │ │ │ │ ├── middlewareUtil.ts │ │ │ │ └── stripGuardedResourceProperties.ts │ │ ├── logger │ │ │ ├── interfaces │ │ │ │ └── ILoggerService.ts │ │ │ ├── providers │ │ │ │ └── LoggerProvider.ts │ │ │ └── services │ │ │ │ └── LoggerService.ts │ │ ├── mail │ │ │ ├── adapters │ │ │ │ ├── LocalMailDriver.ts │ │ │ │ └── NodeMailerDriver.ts │ │ │ ├── data │ │ │ │ └── Mail.ts │ │ │ ├── interfaces │ │ │ │ ├── adapter.ts │ │ │ │ ├── config.ts │ │ │ │ ├── data.ts │ │ │ │ └── services.ts │ │ │ ├── providers │ │ │ │ └── MailProvider.ts │ │ │ └── services │ │ │ │ ├── MailConfig.ts │ │ │ │ └── MailService.ts │ │ ├── make │ │ │ ├── base │ │ │ │ └── BaseMakeFileCommand.ts │ │ │ ├── commands │ │ │ │ ├── MakeCmdCommand.ts │ │ │ │ ├── MakeControllerCommand.ts │ │ │ │ ├── MakeEventCommand.ts │ │ │ │ ├── MakeFactoryCommand.ts │ │ │ │ ├── MakeListenerCommand.ts │ │ │ │ ├── MakeMiddlewareCommand.ts │ │ │ │ ├── MakeMigrationCommand.ts │ │ │ │ ├── MakeModelCommand.ts │ │ │ │ ├── MakeObserverCommand.ts │ │ │ │ ├── MakeProviderCommand.ts │ │ │ │ ├── MakeRepositoryCommand.ts │ │ │ │ ├── MakeRouteResourceCommand.ts │ │ │ │ ├── MakeRoutesCommand.ts │ │ │ │ ├── MakeSeederCommand.ts │ │ │ │ ├── MakeServiceCommand.ts │ │ │ │ ├── MakeSingletonCommand.ts │ │ │ │ ├── MakeSubscriberCommand.ts │ │ │ │ └── MakeValidatorCommand.ts │ │ │ ├── consts │ │ │ │ └── MakeTypes.ts │ │ │ ├── interfaces │ │ │ │ ├── IMakeFileArguments.ts │ │ │ │ └── IMakeOptions.ts │ │ │ ├── observers │ │ │ │ └── ArgumentObserver.ts │ │ │ ├── providers │ │ │ │ └── MakeProvider.ts │ │ │ ├── services │ │ │ │ └── MakeFileService.ts │ │ │ └── templates │ │ │ │ ├── Command.ts.template │ │ │ │ ├── Controller.ts.template │ │ │ │ ├── Factory.ts.template │ │ │ │ ├── Listener.ts.template │ │ │ │ ├── Middleware.ts.template │ │ │ │ ├── Migration.ts.template │ │ │ │ ├── Model.ts.template │ │ │ │ ├── Observer.ts.template │ │ │ │ ├── Provider.ts.template │ │ │ │ ├── Repository.ts.template │ │ │ │ ├── RouteResource.ts.template │ │ │ │ ├── Routes.ts.template │ │ │ │ ├── Seeder.ts.template │ │ │ │ ├── Service.ts.template │ │ │ │ ├── Singleton.ts.template │ │ │ │ ├── Subscriber.ts.template │ │ │ │ └── Validator.ts.template │ │ ├── migrations │ │ │ ├── base │ │ │ │ ├── BaseMigration.ts │ │ │ │ ├── BaseMigrationCommand.ts │ │ │ │ └── BaseSeeder.ts │ │ │ ├── commands │ │ │ │ ├── MigrateDownCommand.ts │ │ │ │ ├── MigrateFreshCommand.ts │ │ │ │ ├── MigrateUpCommand.ts │ │ │ │ ├── SeedDownCommand.ts │ │ │ │ └── SeedUpCommand.ts │ │ │ ├── enums │ │ │ │ └── MigrationTypeEnum.ts │ │ │ ├── exceptions │ │ │ │ └── MigrationError.ts │ │ │ ├── factory │ │ │ │ └── MigrationFactory.ts │ │ │ ├── interfaces │ │ │ │ ├── IMigration.ts │ │ │ │ ├── IMigrationConfig.ts │ │ │ │ └── IMigrationService.ts │ │ │ ├── models │ │ │ │ └── MigrationModel.ts │ │ │ ├── providers │ │ │ │ └── MigrationProvider.ts │ │ │ ├── schema │ │ │ │ └── DataTypes.ts │ │ │ └── services │ │ │ │ ├── MigrationFilesService.ts │ │ │ │ └── MigrationService.ts │ │ ├── models │ │ │ ├── base │ │ │ │ └── Model.ts │ │ │ ├── interfaces │ │ │ │ └── IModel.ts │ │ │ └── utils │ │ │ │ └── ModelScope.ts │ │ ├── mongodb │ │ │ ├── MongoDbSchema.ts │ │ │ ├── adapters │ │ │ │ └── MongoDbAdapter.ts │ │ │ ├── builder │ │ │ │ └── AggregateExpression │ │ │ │ │ ├── Join.ts │ │ │ │ │ ├── Limit.ts │ │ │ │ │ ├── Match.ts │ │ │ │ │ ├── Order.ts │ │ │ │ │ ├── Project.ts │ │ │ │ │ ├── Skip.ts │ │ │ │ │ └── index.ts │ │ │ ├── concerns │ │ │ │ └── MongoDbIdentiferConcern.ts │ │ │ ├── eloquent │ │ │ │ └── MongoDbEloquent.ts │ │ │ ├── helper │ │ │ │ └── ParseMongoDBConnectionUrl.ts │ │ │ ├── interfaces │ │ │ │ ├── IMongoConfig.ts │ │ │ │ └── IMongoDbIdentiferConcern.ts │ │ │ ├── relationship │ │ │ │ └── MongoRelationshipResolver.ts │ │ │ ├── schema │ │ │ │ └── createMigrationSchemaMongo.ts │ │ │ └── utils │ │ │ │ └── extractDefaultMongoCredentials.ts │ │ ├── observer │ │ │ ├── interfaces │ │ │ │ ├── IHasObserver.ts │ │ │ │ └── IObserver.ts │ │ │ └── services │ │ │ │ └── Observer.ts │ │ ├── postgres │ │ │ ├── PostgresSchema.ts │ │ │ ├── adapters │ │ │ │ └── PostgresAdapter.ts │ │ │ ├── builder │ │ │ │ ├── BindingsHelper.ts │ │ │ │ └── ExpressionBuilder │ │ │ │ │ ├── Clauses │ │ │ │ │ ├── DeleteFrom.ts │ │ │ │ │ ├── FromTable.ts │ │ │ │ │ ├── GroupBy.ts │ │ │ │ │ ├── Insert.ts │ │ │ │ │ ├── Joins.ts │ │ │ │ │ ├── OffsetLimit.ts │ │ │ │ │ ├── OrderBy.ts │ │ │ │ │ ├── SelectColumns.ts │ │ │ │ │ ├── Update.ts │ │ │ │ │ └── Where.ts │ │ │ │ │ └── SqlExpression.ts │ │ │ ├── eloquent │ │ │ │ └── PostgresEloquent.ts │ │ │ ├── exceptions │ │ │ │ └── InvalidSequelizeException.ts │ │ │ ├── helper │ │ │ │ └── ParsePostgresConnectionUrl.ts │ │ │ ├── interfaces │ │ │ │ ├── IPostgresAlterTableOptions.ts │ │ │ │ ├── IPostgresConfig.ts │ │ │ │ └── IPostgresQueryBuilder.ts │ │ │ ├── normalizers │ │ │ │ └── PostgresJsonNormalizer.ts │ │ │ ├── schema │ │ │ │ └── createMigrationSchemaPostgres.ts │ │ │ └── utils │ │ │ │ └── extractDefaultPostgresCredentials.ts │ │ ├── session │ │ │ ├── interfaces │ │ │ │ └── ISessionService.ts │ │ │ ├── providers │ │ │ │ └── SessionProvider.ts │ │ │ └── services │ │ │ │ └── SessionService.ts │ │ ├── setup │ │ │ ├── DTOs │ │ │ │ └── QuestionDTO.ts │ │ │ ├── actions │ │ │ │ ├── CopyEnvExampleAction.ts │ │ │ │ ├── EnableExpress.ts │ │ │ │ ├── GenerateAppKeyAction.ts │ │ │ │ ├── GenerateJwtSecretAction.ts │ │ │ │ ├── SetupDefaultDatabase.ts │ │ │ │ └── SetupDockerDatabaseScripts.ts │ │ │ ├── commands │ │ │ │ └── AppSetupCommand.ts │ │ │ ├── consts │ │ │ │ └── QuestionConsts.ts │ │ │ ├── exceptions │ │ │ │ └── InvalidDefaultCredentialsError.ts │ │ │ ├── interfaces │ │ │ │ ├── IAction.ts │ │ │ │ └── ISetupCommand.ts │ │ │ ├── providers │ │ │ │ └── SetupProvider.ts │ │ │ └── utils │ │ │ │ └── buildQuestionDTOs.ts │ │ ├── storage │ │ │ ├── Exceptions │ │ │ │ ├── FileNotFoundException.ts │ │ │ │ └── InvalidStorageFileException.ts │ │ │ ├── data │ │ │ │ └── StorageFile.ts │ │ │ ├── enums │ │ │ │ └── StorageTypes.ts │ │ │ ├── interfaces │ │ │ │ ├── IGenericStorage.ts │ │ │ │ ├── IStorageFile.ts │ │ │ │ ├── IStorageService.ts │ │ │ │ ├── StorageAdapters.ts │ │ │ │ ├── StorageConfig.ts │ │ │ │ └── meta.ts │ │ │ ├── parser │ │ │ │ └── FileSystemStorageFileParser.ts │ │ │ ├── providers │ │ │ │ └── StorageProvider.ts │ │ │ ├── services │ │ │ │ ├── AmazonS3StorageService.ts │ │ │ │ ├── FileSystemStorageService.ts │ │ │ │ └── StorageService.ts │ │ │ └── utils │ │ │ │ └── StorageUtils.ts │ │ ├── tinker │ │ │ └── services │ │ │ │ └── TinkerService.ts │ │ └── validator │ │ │ ├── abstract │ │ │ ├── AbstractDatabaseRule.ts │ │ │ └── AbstractRule.ts │ │ │ ├── base │ │ │ └── BaseCustomValidator.ts │ │ │ ├── data │ │ │ └── ValidatorResult.ts │ │ │ ├── exceptions │ │ │ └── ValidatorException.ts │ │ │ ├── interfaces │ │ │ ├── IRule.ts │ │ │ ├── IValidator.ts │ │ │ └── IValidatorResult.ts │ │ │ ├── middleware │ │ │ └── ValidatorMiddleware.ts │ │ │ ├── providers │ │ │ └── ValidatorProvider.ts │ │ │ ├── rules │ │ │ ├── AcceptedIfRule.ts │ │ │ ├── AcceptedRule.ts │ │ │ ├── ArrayRule.ts │ │ │ ├── BooleanRule.ts │ │ │ ├── DateRule.ts │ │ │ ├── EmailRule.ts │ │ │ ├── EqualsRule.ts │ │ │ ├── ExistsRule.ts │ │ │ ├── FileExtensionRule.ts │ │ │ ├── FileMimeTypeRule.ts │ │ │ ├── HasFileRule.ts │ │ │ ├── JsonRule.ts │ │ │ ├── MaxFileSizeRule.ts │ │ │ ├── MaxRule.ts │ │ │ ├── MinFileSizeRule.ts │ │ │ ├── MinRule.ts │ │ │ ├── MultipleFilesRule.ts │ │ │ ├── NullableRule.ts │ │ │ ├── NumberRule.ts │ │ │ ├── NumericRule.ts │ │ │ ├── ObjectRule.ts │ │ │ ├── RegexRule.ts │ │ │ ├── RequiredRule.ts │ │ │ ├── SameRule.ts │ │ │ ├── SingleFileRule.ts │ │ │ ├── SizeRule.ts │ │ │ ├── StringRule.ts │ │ │ ├── UniqueRule.ts │ │ │ └── UuidRule.ts │ │ │ ├── service │ │ │ └── Validator.ts │ │ │ └── utils │ │ │ ├── isFalsy.ts │ │ │ └── isTruthy.ts │ ├── exceptions │ │ ├── AdapterException.ts │ │ ├── DotNotationParserException.ts │ │ ├── FileNotFoundError.ts │ │ ├── ModelNotFound.ts │ │ ├── UnexpectedAttributeError.ts │ │ ├── UninitializedContainerError.ts │ │ └── ValidationError.ts │ ├── interfaces │ │ ├── ClassConstructor.t.ts │ │ ├── IEnvService.ts │ │ ├── IFactory.ts │ │ ├── ILarascriptProviders.ts │ │ ├── IPackageJsonService.ts │ │ ├── IProvider.ts │ │ ├── IQueable.ts │ │ ├── IRepository.ts │ │ ├── IService.ts │ │ └── concerns │ │ │ ├── IDispatchable.ts │ │ │ ├── IExecutable.ts │ │ │ ├── IHasAttributes.ts │ │ │ ├── IHasConfigConcern.ts │ │ │ ├── INameable.ts │ │ │ └── ISimpleRegister.ts │ ├── models │ │ ├── broadcast │ │ │ └── AttributeChangeListener.ts │ │ └── utils │ │ │ └── ProxyModelHandler.ts │ ├── providers │ │ └── LarascriptProviders.ts │ ├── services │ │ ├── App.ts │ │ ├── EnvService.ts │ │ └── PackageJsonService.ts │ ├── util │ │ ├── MoveObjectToProperty.ts │ │ ├── PrefixedPropertyGrouper.ts │ │ ├── captureError.ts │ │ ├── castObject.ts │ │ ├── checksum.ts │ │ ├── compose.ts │ │ ├── data │ │ │ └── DotNotation │ │ │ │ ├── DataExtractor │ │ │ │ └── DotNotationDataExtrator.ts │ │ │ │ └── Parser │ │ │ │ └── DotNotationParser.ts │ │ ├── deepClone.ts │ │ ├── minExecTime.ts │ │ ├── parseBooleanFromString.ts │ │ ├── prettifyStack.ts │ │ ├── replaceEnvValue.ts │ │ ├── returns │ │ │ └── returnOrThrow.ts │ │ ├── str │ │ │ ├── Str.ts │ │ │ └── forceString.ts │ │ └── uuid │ │ │ └── generateUuidV4.ts │ └── utility │ │ └── uuid.ts ├── setup.ts ├── tests │ ├── app │ │ └── .gitkeep │ ├── customSequencer.ts │ ├── larascript │ │ ├── auth │ │ │ └── authLoginUser.test.ts │ │ ├── broadcast │ │ │ └── broadcast.test.ts │ │ ├── casts │ │ │ ├── cast.test.ts │ │ │ ├── castObject.test.ts │ │ │ └── modelCasts.test.ts │ │ ├── collection │ │ │ └── collection.test.ts │ │ ├── concerns │ │ │ └── simpleRegister.test.ts │ │ ├── config │ │ │ └── testConfig.ts │ │ ├── crypto │ │ │ └── crypto.test.ts │ │ ├── database │ │ │ ├── dbConnection.test.ts │ │ │ ├── dbCreateDropDatabase.test.ts │ │ │ ├── dbDropAllTables.test.ts │ │ │ ├── dbSchema.test.ts │ │ │ ├── dbService.test.ts │ │ │ └── dbTableName.test.ts │ │ ├── eloquent │ │ │ ├── eloquentAggregates.test.ts │ │ │ ├── eloquentClone.test.ts │ │ │ ├── eloquentDelete.test.ts │ │ │ ├── eloquentDifferentQueryBuildTypes.test.ts │ │ │ ├── eloquentDistinct.test.ts │ │ │ ├── eloquentFetching.test.ts │ │ │ ├── eloquentInsert.test.ts │ │ │ ├── eloquentJoins.test.ts │ │ │ ├── eloquentOrdering.test.ts │ │ │ ├── eloquentRawQuery.test.ts │ │ │ ├── eloquentRelationships.test.ts │ │ │ ├── eloquentTransactions.test.ts │ │ │ ├── eloquentUpdate.test.ts │ │ │ ├── eloquentWhere.test.ts │ │ │ └── models │ │ │ │ ├── TestBlogPost.ts │ │ │ │ ├── TestDepartmentModel.ts │ │ │ │ ├── TestEmailModel.ts │ │ │ │ ├── TestEmployee.ts │ │ │ │ ├── TestEmployeeModel.ts │ │ │ │ └── TestPeopleModel.ts │ │ ├── env │ │ │ └── EnvService.test.ts │ │ ├── events │ │ │ ├── eventAuthUserRegistered.test.ts │ │ │ ├── eventQueableFailed.test.ts │ │ │ ├── eventQueableSuccess.test.ts │ │ │ ├── eventSync.test.ts │ │ │ ├── events │ │ │ │ ├── TestEventQueueAddAlwaysFailsEventToQueue.ts │ │ │ │ ├── TestEventQueueAlwaysFailsEvent.ts │ │ │ │ ├── TestEventQueueCalledFromWorkerEvent.ts │ │ │ │ ├── TestEventQueueEvent.ts │ │ │ │ ├── TestEventSyncBadPayloadEvent.ts │ │ │ │ ├── TestEventSyncEvent.ts │ │ │ │ └── auth │ │ │ │ │ ├── TestUserCreatedListener.ts │ │ │ │ │ └── TestUserCreatedSubscriber.ts │ │ │ ├── helpers │ │ │ │ └── createWorketTables.ts │ │ │ ├── listeners │ │ │ │ └── TestListener.ts │ │ │ ├── modelEventsLifecycle.test.ts │ │ │ └── subscribers │ │ │ │ └── TestSubscriber.ts │ │ ├── factory │ │ │ ├── TestMovieFakerFactory.ts │ │ │ ├── TestUserFactory.ts │ │ │ └── factory.test.ts │ │ ├── helpers │ │ │ ├── parseMongoDbConnectionUrl.test.ts │ │ │ └── parsePostgresConnectionUrl.test.ts │ │ ├── make │ │ │ ├── make.test.ts │ │ │ └── makeTestHelper.ts │ │ ├── migration │ │ │ ├── migration.test.ts │ │ │ ├── migrations │ │ │ │ ├── test-create-api-token-table.ts │ │ │ │ ├── test-create-failed-worker-table.ts │ │ │ │ ├── test-create-user-table.ts │ │ │ │ ├── test-create-worker-table.ts │ │ │ │ └── test-migration.ts │ │ │ ├── models │ │ │ │ └── TestMigrationModel.ts │ │ │ ├── seeder.test.ts │ │ │ └── seeders │ │ │ │ └── test-seeder-model.ts │ │ ├── models │ │ │ ├── modelAttr.test.ts │ │ │ ├── modelCrud.test.ts │ │ │ ├── modelDirty.test.ts │ │ │ ├── modelEncryption.test.ts │ │ │ ├── modelObserver.test.ts │ │ │ ├── models │ │ │ │ ├── TestApiTokenModel.ts │ │ │ │ ├── TestDirtyModel.ts │ │ │ │ ├── TestEncryptionModel.ts │ │ │ │ ├── TestFailedWorkerModel.ts │ │ │ │ ├── TestModel.ts │ │ │ │ ├── TestMovie.ts │ │ │ │ ├── TestObserverModel.ts │ │ │ │ ├── TestUser.ts │ │ │ │ └── TestWorkerModel.ts │ │ │ └── observers │ │ │ │ └── TestObserver.ts │ │ ├── postTest.ts │ │ ├── providers │ │ │ ├── TestAuthProvider.ts │ │ │ ├── TestConsoleProvider.ts │ │ │ ├── TestCryptoProvider.ts │ │ │ ├── TestDatabaseProvider.ts │ │ │ ├── TestEventProvider.ts │ │ │ └── TestMigrationProvider.ts │ │ ├── repositories │ │ │ ├── TestApiTokenRepository.ts │ │ │ ├── TestPeopleRepository.ts │ │ │ ├── TestUserRepository.ts │ │ │ └── repository.test.test.ts │ │ ├── runApp.test.ts │ │ ├── session │ │ │ └── session.test.ts │ │ ├── utils │ │ │ └── DotNotation │ │ │ │ ├── dotNotationDataExtractor.test.ts │ │ │ │ └── dotNotationParser.test.ts │ │ └── validator │ │ │ ├── rules │ │ │ ├── ValidatorDate.test.ts │ │ │ ├── ValidatorEmail.test.ts │ │ │ ├── ValidatorExceptions.test.ts │ │ │ ├── ValidatorJson.test.ts │ │ │ ├── ValidatorMax.test.ts │ │ │ ├── ValidatorMin.test.ts │ │ │ ├── ValidatorNumeric.test.ts │ │ │ ├── ValidatorRegex.test.ts │ │ │ ├── ValidatorSame.test.ts │ │ │ ├── ValidatorSize.test.ts │ │ │ ├── ValidatorUuid.test.ts │ │ │ ├── validatorAccepted.test.ts │ │ │ ├── validatorAcceptedIf.test.ts │ │ │ ├── validatorArray.test.ts │ │ │ ├── validatorBoolean.test.ts │ │ │ ├── validatorEquals.test.ts │ │ │ ├── validatorExists.test.ts │ │ │ ├── validatorNullable.test.ts │ │ │ ├── validatorNumber.test.ts │ │ │ ├── validatorObject.test.ts │ │ │ ├── validatorRequired.test.ts │ │ │ ├── validatorString.test.ts │ │ │ └── validatorUnique.test.ts │ │ │ ├── validatorBaseValidator.test.ts │ │ │ ├── validatorCustomMessages.test.ts │ │ │ ├── validatorCustomValidation.test.ts │ │ │ ├── validatorUsage.test.ts │ │ │ ├── validatorValidatedData.test.ts │ │ │ └── validators │ │ │ ├── TestCreateUserCustomValidator.ts │ │ │ ├── TestCreateUserValidator.ts │ │ │ └── TestUpdateUserValidator.ts │ └── testHelper.ts └── tinker.ts ├── standards.md ├── storage ├── tmp │ └── .gitkeep └── uploads │ └── .gitkeep ├── tsconfig.json ├── webpack.config.js └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | APP_ENV=development 2 | APP_PORT=5000 3 | APP_KEY= 4 | 5 | JWT_SECRET= 6 | JWT_EXPIRES_IN_MINUTES=60 7 | 8 | ENABLE_AUTH_ROUTES=true 9 | ENABLE_AUTH_ROUTES_ALLOW_CREATE=true 10 | 11 | # Database Options 12 | DATABASE_DEFAULT_CONNECTION=postgres 13 | DATABASE_CONNECTIONS_KEEP_ALIVE= 14 | DATABASE_ENABLE_LOGGING=true 15 | # Postgres 16 | DATABASE_POSTGRES_CONNECTION=postgres 17 | DATABASE_POSTGRES_URI= 18 | # MongoDB 19 | DATABASE_MONGODB_CONNECTION=mongodb 20 | DATABASE_MONGODB_URI= 21 | 22 | ENABLE_EXPRESS=true 23 | ENABLE_REQUEST_LOGGING=true 24 | ENABLE_BOUND_ROUTE_DETAILS=true 25 | 26 | AWS_ACCESS_KEY_ID= 27 | AWS_SECRET_ACCESS_KEY= 28 | S3_REGION=eu-west-2 29 | S3_BUCKET= 30 | 31 | MAIL_DRIVER=local 32 | NODEMAILER_HOST= 33 | NODEMAILER_PORT=587 34 | NODEMAILER_SECURE=false 35 | NODEMAILER_AUTH_USER= 36 | NODEMAILER_AUTH_PASS= -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "visbydev.folder-path-color" 4 | ] 5 | } -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20 2 | WORKDIR /app 3 | COPY . . 4 | RUN yarn install && yarn build 5 | EXPOSE 5000 6 | CMD ["yarn", "start"] 7 | -------------------------------------------------------------------------------- /docker/docker-compose.api.yml: -------------------------------------------------------------------------------- 1 | name: larascript 2 | 3 | services: 4 | api: 5 | container_name: larascript-api 6 | build: 7 | context: .. 8 | dockerfile: docker/Dockerfile 9 | ports: 10 | - "5000:5000" 11 | volumes: 12 | - /app/node_modules 13 | working_dir: /app 14 | networks: 15 | - app-network 16 | 17 | networks: 18 | app-network: 19 | external: true 20 | name: larascript -------------------------------------------------------------------------------- /docker/docker-compose.mongodb.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | name: larascript 4 | 5 | services: 6 | mongodb: 7 | container_name: larascript-mongodb 8 | image: mongo:latest 9 | ports: 10 | - "27017:27017" 11 | environment: 12 | MONGO_INITDB_ROOT_USERNAME: root 13 | MONGO_INITDB_ROOT_PASSWORD: example 14 | LARASCRIPT_DEFAULT_CREDENTIALS: mongodb://root:example@localhost:27017/app?authSource=admin 15 | volumes: 16 | - mongodb_data:/data/db 17 | - mongodb_keyfile:/data 18 | networks: 19 | - app-network 20 | healthcheck: 21 | test: ["CMD", "mongosh", "admin", "-u", "root", "-p", "example", "--eval", "db.adminCommand('ping')"] 22 | interval: 10s 23 | timeout: 5s 24 | retries: 5 25 | start_period: 40s 26 | 27 | volumes: 28 | mongodb_data: 29 | mongodb_keyfile: 30 | 31 | networks: 32 | app-network: 33 | external: true 34 | name: larascript -------------------------------------------------------------------------------- /docker/docker-compose.network.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | name: larascript 4 | 5 | services: 6 | network-setup: 7 | image: alpine 8 | command: "true" 9 | networks: 10 | - app-network 11 | 12 | networks: 13 | app-network: 14 | name: larascript 15 | driver: bridge 16 | -------------------------------------------------------------------------------- /docker/docker-compose.postgres.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | name: larascript 4 | 5 | services: 6 | postgres: 7 | container_name: larascript-postgres 8 | image: postgres:latest 9 | ports: 10 | - "5432:5432" 11 | environment: 12 | POSTGRES_DB: app 13 | POSTGRES_USER: root 14 | POSTGRES_PASSWORD: example 15 | LARASCRIPT_DEFAULT_CREDENTIALS: postgres://root:example@localhost:5432/app 16 | volumes: 17 | - postgres_data:/var/lib/postgresql/data 18 | networks: 19 | - app-network 20 | 21 | volumes: 22 | postgres_data: 23 | 24 | networks: 25 | app-network: 26 | external: true 27 | name: larascript -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | moduleNameMapper: { 5 | '^@src/(.*)$': '/src/$1' 6 | }, 7 | testMatch: ['/src/**/*.test.ts'], 8 | testSequencer: '/src/tests/customSequencer.ts' 9 | }; -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": "ts", 4 | "exec": "ts-node ./src/app.ts" 5 | } -------------------------------------------------------------------------------- /src/app/commands/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-shepherd/larascript-framework/1471b26b1184603264c1bdf35d3b8d39d190989d/src/app/commands/.gitkeep -------------------------------------------------------------------------------- /src/app/commands/ExampleCommand.ts: -------------------------------------------------------------------------------- 1 | import BaseCommand from "@src/core/domains/console/base/BaseCommand"; 2 | 3 | export default class ExampleCommand extends BaseCommand { 4 | 5 | signature: string = 'app:example'; 6 | 7 | async execute() { 8 | // Handle the logic 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /src/app/controllers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-shepherd/larascript-framework/1471b26b1184603264c1bdf35d3b8d39d190989d/src/app/controllers/.gitkeep -------------------------------------------------------------------------------- /src/app/controllers/ExampleController.ts: -------------------------------------------------------------------------------- 1 | import Controller from "@src/core/domains/http/base/Controller"; 2 | import HttpContext from "@src/core/domains/http/context/HttpContext"; 3 | 4 | class ExampleController extends Controller { 5 | 6 | async example(context: HttpContext) { 7 | this.jsonResponse({ 8 | id: context.getId(), 9 | message: 'Hello World!', 10 | }, 200) 11 | } 12 | 13 | } 14 | 15 | export default ExampleController; 16 | -------------------------------------------------------------------------------- /src/app/events/listeners/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-shepherd/larascript-framework/1471b26b1184603264c1bdf35d3b8d39d190989d/src/app/events/listeners/.gitkeep -------------------------------------------------------------------------------- /src/app/events/listeners/UserCreatedListener.ts: -------------------------------------------------------------------------------- 1 | import BaseEventListener from "@src/core/domains/events/base/BaseEventListener"; 2 | import EventRegistry from "@src/core/domains/events/registry/EventRegistry"; 3 | 4 | class UserCreatedListener extends BaseEventListener { 5 | 6 | /** 7 | * Optional method to execute before the subscribers are dispatched. 8 | */ 9 | async execute(): Promise { 10 | 11 | // const payload = this.getPayload(); 12 | 13 | // Handle logic 14 | } 15 | 16 | } 17 | 18 | export default EventRegistry.registerListener(UserCreatedListener); -------------------------------------------------------------------------------- /src/app/events/subscribers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-shepherd/larascript-framework/1471b26b1184603264c1bdf35d3b8d39d190989d/src/app/events/subscribers/.gitkeep -------------------------------------------------------------------------------- /src/app/events/subscribers/UserCreatedSubscriber.ts: -------------------------------------------------------------------------------- 1 | import BaseEventSubscriber from "@src/core/domains/events/base/BaseEventSubciber"; 2 | import SyncDriver from "@src/core/domains/events/drivers/SyncDriver"; 3 | import EventRegistry from "@src/core/domains/events/registry/EventRegistry"; 4 | 5 | class UserCreatedSubscriber extends BaseEventSubscriber { 6 | 7 | protected namespace: string = 'auth'; 8 | 9 | constructor(payload) { 10 | super(payload, SyncDriver); 11 | } 12 | 13 | getQueueName(): string { 14 | return 'default'; 15 | } 16 | 17 | async execute(): Promise { 18 | // const payload = this.getPayload(); 19 | 20 | // Handle logic 21 | } 22 | 23 | } 24 | 25 | export default EventRegistry.registerSubscriber(UserCreatedSubscriber); -------------------------------------------------------------------------------- /src/app/factory/UserFactory.ts: -------------------------------------------------------------------------------- 1 | import User from "@src/app/models/auth/User"; 2 | import { GROUPS, ROLES } from "@src/config/acl.config"; 3 | import Factory from "@src/core/base/Factory"; 4 | import { cryptoService } from "@src/core/domains/crypto/service/CryptoService"; 5 | import { IModelAttributes } from "@src/core/domains/models/interfaces/IModel"; 6 | 7 | class UserFactory extends Factory { 8 | 9 | protected model = User; 10 | 11 | getDefinition(): IModelAttributes | null { 12 | return { 13 | email: this.faker.internet.email(), 14 | hashedPassword: cryptoService().hash(this.faker.internet.password()), 15 | roles: [ROLES.USER], 16 | groups: [GROUPS.USER], 17 | firstName: this.faker.person.firstName(), 18 | lastName: this.faker.person.lastName(), 19 | createdAt: new Date(), 20 | updatedAt: new Date(), 21 | } 22 | } 23 | 24 | } 25 | 26 | export default UserFactory; 27 | -------------------------------------------------------------------------------- /src/app/interfaces/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-shepherd/larascript-framework/1471b26b1184603264c1bdf35d3b8d39d190989d/src/app/interfaces/.gitkeep -------------------------------------------------------------------------------- /src/app/interfaces/IAppService.ts: -------------------------------------------------------------------------------- 1 | import { IAppConfig } from "@src/config/app.config"; 2 | 3 | export interface IAppService { 4 | getConfig(): IAppConfig; 5 | boot(): Promise; 6 | } -------------------------------------------------------------------------------- /src/app/middleware/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-shepherd/larascript-framework/1471b26b1184603264c1bdf35d3b8d39d190989d/src/app/middleware/.gitkeep -------------------------------------------------------------------------------- /src/app/migrations/2024-09-06-create-api-token-table.ts: -------------------------------------------------------------------------------- 1 | import ApiToken from "@src/core/domains/auth/models/ApiToken"; 2 | import { authJwt } from "@src/core/domains/auth/services/JwtAuthService"; 3 | import BaseMigration from "@src/core/domains/migrations/base/BaseMigration"; 4 | 5 | export class CreateApiTokenMigration extends BaseMigration { 6 | 7 | group?: string = 'app:setup'; 8 | 9 | async up(): Promise { 10 | await this.schema.createTable(ApiToken.getTable(), authJwt().getCreateApiTokenTableSchema()) 11 | } 12 | 13 | async down(): Promise { 14 | await this.schema.dropTable(ApiToken.getTable()); 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/app/observers/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-shepherd/larascript-framework/1471b26b1184603264c1bdf35d3b8d39d190989d/src/app/observers/.gitkeep -------------------------------------------------------------------------------- /src/app/providers/AppServiceProvider.ts: -------------------------------------------------------------------------------- 1 | import AppService from "@src/app/services/AppService"; 2 | import BaseProvider from "@src/core/base/Provider"; 3 | import { app } from "@src/core/services/App"; 4 | 5 | class AppServiceProvider extends BaseProvider { 6 | 7 | public async register(): Promise { 8 | 9 | const appService = new AppService(this.config); 10 | 11 | this.bind('app', appService); 12 | this.bind('app.config', () => appService.getConfig()); 13 | } 14 | 15 | async boot(): Promise { 16 | await app('app').boot(); 17 | } 18 | 19 | } 20 | 21 | export default AppServiceProvider; -------------------------------------------------------------------------------- /src/app/providers/RoutesProvider.ts: -------------------------------------------------------------------------------- 1 | import apiRoutes from "@src/app/routes/api"; 2 | import CsrfMiddleware from "@src/core/domains/auth/middleware/CsrfMiddlware"; 3 | import { authJwt } from "@src/core/domains/auth/services/JwtAuthService"; 4 | import BaseRoutesProvider from "@src/core/domains/http/providers/BaseRoutesProvider"; 5 | import healthRoutes from "@src/core/domains/http/routes/healthRoutes"; 6 | import { app } from "@src/core/services/App"; 7 | 8 | 9 | class RoutesProvider extends BaseRoutesProvider { 10 | 11 | /** 12 | * Registers the routes to the express service 13 | */ 14 | public async boot(): Promise { 15 | 16 | const httpService = app('http'); 17 | 18 | // Bind routes 19 | httpService.bindRoutes(authJwt().getRouter()) 20 | httpService.bindRoutes(CsrfMiddleware.getRouter()) 21 | httpService.bindRoutes(healthRoutes); 22 | httpService.bindRoutes(apiRoutes); 23 | 24 | } 25 | 26 | 27 | } 28 | 29 | 30 | export default RoutesProvider; -------------------------------------------------------------------------------- /src/app/repositories/auth/UserRepository.ts: -------------------------------------------------------------------------------- 1 | import User from "@src/app/models/auth/User"; 2 | import AuthUserRepository from '@src/core/domains/auth/repository/UserRepository'; 3 | 4 | 5 | export default class UserRepository extends AuthUserRepository { 6 | 7 | constructor() { 8 | super(User) 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /src/app/routes/api.ts: -------------------------------------------------------------------------------- 1 | import ExampleController from "@src/app/controllers/ExampleController" 2 | import Route from "@src/core/domains/http/router/Route" 3 | 4 | export default Route.group(router => { 5 | 6 | router.get('/example', [ExampleController, 'example']) 7 | 8 | }) 9 | -------------------------------------------------------------------------------- /src/app/seeders/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-shepherd/larascript-framework/1471b26b1184603264c1bdf35d3b8d39d190989d/src/app/seeders/.gitkeep -------------------------------------------------------------------------------- /src/app/services/AppService.ts: -------------------------------------------------------------------------------- 1 | import { IAppService } from "@src/app/interfaces/IAppService"; 2 | import Service from "@src/core/base/Service"; 3 | 4 | class AppService extends Service implements IAppService { 5 | 6 | public async boot(): Promise { 7 | console.log('[AppService] Booting...'); 8 | } 9 | 10 | /** 11 | * @returns The app configuration. 12 | * Usage: app('app').getConfig() 13 | */ 14 | public getConfig() { 15 | return this.config; 16 | } 17 | 18 | } 19 | 20 | export default AppService -------------------------------------------------------------------------------- /src/app/validators/user/UpdateUserValidator.ts: -------------------------------------------------------------------------------- 1 | import BaseCustomValidator from "@src/core/domains/validator/base/BaseCustomValidator"; 2 | import { IRulesObject } from "@src/core/domains/validator/interfaces/IRule"; 3 | import MinRule from "@src/core/domains/validator/rules/MinRule"; 4 | import NullableRule from "@src/core/domains/validator/rules/NullableRule"; 5 | import StringRule from "@src/core/domains/validator/rules/StringRule"; 6 | 7 | class UpdateUserValidator extends BaseCustomValidator { 8 | 9 | protected rules: IRulesObject = { 10 | password: [new NullableRule(), new MinRule(6)], 11 | firstName: [new NullableRule(), new StringRule()], 12 | lastName: [new NullableRule(), new StringRule()] 13 | } 14 | 15 | } 16 | 17 | export default UpdateUserValidator -------------------------------------------------------------------------------- /src/config/acl.config.ts: -------------------------------------------------------------------------------- 1 | import { IAclConfig } from "@src/core/domains/auth/interfaces/acl/IAclConfig"; 2 | 3 | // Define available groups 4 | export const GROUPS = { 5 | USER: 'group_user', 6 | ADMIN: 'group_admin', 7 | } as const 8 | 9 | // Define available roles 10 | export const ROLES = { 11 | USER: 'role_user', 12 | ADMIN: 'role_admin' 13 | } as const 14 | 15 | /** 16 | * ACL configuration 17 | */ 18 | export const aclConfig: IAclConfig = { 19 | 20 | // Default user group 21 | defaultGroup: GROUPS.USER, 22 | 23 | // List of groups 24 | groups: [ 25 | { 26 | name: GROUPS.USER, 27 | roles: [ROLES.USER] 28 | }, 29 | { 30 | name: GROUPS.ADMIN, 31 | roles: [ROLES.USER, ROLES.ADMIN] 32 | } 33 | ], 34 | 35 | // List of roles, scopes and other permissions 36 | roles: [ 37 | { 38 | name: ROLES.ADMIN, 39 | scopes: [] 40 | }, 41 | { 42 | name: ROLES.USER, 43 | scopes: [] 44 | }, 45 | ], 46 | 47 | } -------------------------------------------------------------------------------- /src/config/app.config.ts: -------------------------------------------------------------------------------- 1 | import { EnvironmentDevelopment, EnvironmentType } from '@src/core/consts/Environment'; 2 | 3 | // App configuration type definition 4 | export type IAppConfig = { 5 | appKey: string; 6 | env: EnvironmentType; 7 | appName: string; 8 | } 9 | 10 | // App configuration 11 | const appConfig: IAppConfig = { 12 | 13 | appName: 'Larascript Framework', 14 | 15 | // App key 16 | appKey: process.env.APP_KEY ?? '', 17 | 18 | // Environment 19 | env: (process.env.APP_ENV as EnvironmentType) ?? EnvironmentDevelopment, 20 | 21 | }; 22 | 23 | export default appConfig; 24 | -------------------------------------------------------------------------------- /src/config/commands.config.ts: -------------------------------------------------------------------------------- 1 | import ExampleCommand from '@src/app/commands/ExampleCommand'; 2 | import { ICommandConstructor } from '@src/core/domains/console/interfaces/ICommand'; 3 | 4 | /** 5 | * Register your custom commands here. 6 | * Commands will be available through the CLI using: 7 | * yarn dev --args 8 | */ 9 | const commandsConfig: ICommandConstructor[] = [ 10 | ExampleCommand, 11 | ] 12 | 13 | export default commandsConfig; 14 | -------------------------------------------------------------------------------- /src/config/storage.config.ts: -------------------------------------------------------------------------------- 1 | import { StorageConfig } from "@src/core/domains/storage/interfaces/StorageConfig"; 2 | 3 | /** 4 | * Storage configuration object 5 | * @typedef {Object} StorageConfig 6 | * @property {('fs'|'s3')} driver - The storage driver to use. Available options: 7 | * - 'fs': File system storage driver 8 | * - 's3': Amazon S3 storage driver 9 | */ 10 | export const config: StorageConfig = { 11 | driver: process.env.STORAGE_DRIVER ?? 'fs', 12 | storageDir: 'storage', 13 | uploadsDir: 'storage/uploads', 14 | s3: { 15 | accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? '', 16 | secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? '', 17 | bucket: process.env.S3_BUCKET ?? '', 18 | region: process.env.S3_REGION ?? '' 19 | } 20 | } as const 21 | 22 | -------------------------------------------------------------------------------- /src/core/base/BaseConfig.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import HasConfigConcern from "@src/core/concerns/HasConfigConcern"; 3 | import compose from "@src/core/util/compose"; 4 | 5 | export default class BaseConfig extends compose(class {}, HasConfigConcern) { 6 | 7 | declare getConfig: () => T; 8 | 9 | declare setConfig: (config: unknown) => void; 10 | 11 | } -------------------------------------------------------------------------------- /src/core/base/Service.ts: -------------------------------------------------------------------------------- 1 | import IService from "@src/core/interfaces/IService"; 2 | 3 | /** 4 | * Service base class 5 | * 6 | * @abstract 7 | * @class Service 8 | * @implements {IService} 9 | */ 10 | export default abstract class Service implements IService { 11 | 12 | /** 13 | * Service configuration 14 | * 15 | * @protected 16 | * @type {Config|null} 17 | */ 18 | protected config!: Config | null; 19 | 20 | /** 21 | * Creates an instance of Service 22 | * 23 | * @constructor 24 | * @param {Config|null} [config=null] - Service configuration 25 | */ 26 | constructor(config: Config | null = null) { 27 | this.config = config 28 | } 29 | 30 | /** 31 | * Returns service configuration 32 | * 33 | * @returns {Config|null} - Service configuration 34 | */ 35 | public getConfig(): Config | null { 36 | return this.config; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/core/common/interfaces/NormalizerInterface.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | export interface NormalizerInterface { 3 | normalize(...args: any[]): unknown; 4 | denormalize(...args: any[]): unknown; 5 | } -------------------------------------------------------------------------------- /src/core/concerns/HasConfigConcern.ts: -------------------------------------------------------------------------------- 1 | import { TClassConstructor } from "@src/core/interfaces/ClassConstructor.t"; 2 | import { IHasConfigConcern } from "@src/core/interfaces/concerns/IHasConfigConcern"; 3 | 4 | const HasConfigConcern = (Base: TClassConstructor) => { 5 | return class extends Base implements IHasConfigConcern { 6 | 7 | config: unknown; 8 | 9 | /** 10 | * Retrieves the current configuration. 11 | * 12 | * @template T - The expected type of the configuration. 13 | * @returns {T} The current configuration cast to the specified type. 14 | */ 15 | getConfig(): T { 16 | return this.config as T 17 | } 18 | 19 | /** 20 | * Sets the current configuration. 21 | * 22 | * @param {unknown} config - The new configuration. 23 | */ 24 | setConfig(config: unknown): void { 25 | this.config = config; 26 | } 27 | 28 | } 29 | } 30 | 31 | export default HasConfigConcern -------------------------------------------------------------------------------- /src/core/concerns/HasSimpleRegisterConcern.ts: -------------------------------------------------------------------------------- 1 | import BaseSimpleRegister from "@src/core/base/BaseSimpleRegister"; 2 | 3 | const HasSimpleRegisterConcern = () => { 4 | return class extends BaseSimpleRegister {} 5 | } 6 | 7 | export default HasSimpleRegisterConcern -------------------------------------------------------------------------------- /src/core/consts/Environment.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Environment type constants 3 | */ 4 | export const EnvironmentDevelopment = 'development'; 5 | export const EnvironmentProduction = 'production'; 6 | export const EnvironmentTesting = 'testing'; 7 | 8 | /** 9 | * Environment type 10 | */ 11 | export type EnvironmentType = typeof EnvironmentDevelopment | typeof EnvironmentProduction | typeof EnvironmentTesting; 12 | 13 | export type EnvironmentConfig = { 14 | environment: EnvironmentType 15 | } 16 | 17 | /** 18 | * Environment constants 19 | */ 20 | const Environment = Object.freeze({ 21 | development: EnvironmentDevelopment, 22 | production: EnvironmentProduction, 23 | testing: EnvironmentTesting, 24 | }); 25 | 26 | export default Environment 27 | -------------------------------------------------------------------------------- /src/core/consts/Errors.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Error constants 3 | * 4 | * These constants are used to provide a standardized set of error messages 5 | * for the application. They can be used in catch blocks to provide a more 6 | * user-friendly error message. 7 | */ 8 | export default Object.freeze({ 9 | InternalServerError: 'Internal Server Error', 10 | }) 11 | -------------------------------------------------------------------------------- /src/core/domains/accessControl/interfaces/IACLService.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { IAclConfig, IAclGroup, IAclRole } from "@src/core/domains/auth/interfaces/acl/IAclConfig" 3 | import { IUserModel } from "@src/core/domains/auth/interfaces/models/IUserModel" 4 | 5 | export interface IBasicACLService { 6 | getConfig(): IAclConfig 7 | getDefaultGroup(): IAclGroup 8 | getGroup(group: string | IAclGroup): IAclGroup 9 | getGroupRoles(group: string | IAclGroup): IAclRole[] 10 | getGroupScopes(group: string | IAclGroup): string[] 11 | getRoleScopesFromUser(user: IUserModel): string[] 12 | getRoleScopes(role: string | string[]): string[] 13 | getRole(role: string): IAclRole 14 | assignRoleToUser(user: IUserModel, role: string | string[]): Promise 15 | assignGroupToUser(user: IUserModel, group: string | string[]): Promise 16 | removeRoleFromUser(user: IUserModel, role: string | string[]): Promise 17 | removeGroupFromUser(user: IUserModel, group: string | string[]): Promise 18 | } 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/core/domains/accessControl/providers/AccessControlProvider.ts: -------------------------------------------------------------------------------- 1 | import { aclConfig } from "@src/config/acl.config"; 2 | import BaseProvider from "@src/core/base/Provider"; 3 | import { IAclConfig } from "@src/core/domains/auth/interfaces/acl/IAclConfig"; 4 | import BasicACLService from "@src/core/domains/accessControl/services/BasicACLService"; 5 | 6 | class AccessControlProvider extends BaseProvider { 7 | 8 | config: IAclConfig = aclConfig 9 | 10 | async register(): Promise { 11 | this.bind('acl.basic', new BasicACLService(this.config)); 12 | } 13 | 14 | } 15 | 16 | export default AccessControlProvider; 17 | -------------------------------------------------------------------------------- /src/core/domains/auth/commands/GenerateJwtSecret.ts: -------------------------------------------------------------------------------- 1 | import BaseCommand from "@src/core/domains/console/base/BaseCommand"; 2 | import { IEnvService } from "@src/core/interfaces/IEnvService"; 3 | import EnvService from "@src/core/services/EnvService"; 4 | 5 | class GenerateJwtSecret extends BaseCommand { 6 | 7 | signature = 'auth:generate-jwt-secret'; 8 | 9 | private envService: IEnvService = new EnvService(); 10 | 11 | async execute() { 12 | 13 | this.input.writeLine('--- Generate JWT Secret ---'); 14 | const answer = await this.input.askQuestion('Re-generate JWT Secret? This will update your .env file. (y/n)') 15 | 16 | 17 | if (answer !== 'y') { 18 | return; 19 | } 20 | 21 | const secret = require('crypto').randomBytes(64).toString('hex'); 22 | 23 | await this.envService.updateValues({ JWT_SECRET: secret }); 24 | 25 | this.input.writeLine('Successfully generated jwt secret!'); 26 | } 27 | 28 | } 29 | 30 | export default GenerateJwtSecret -------------------------------------------------------------------------------- /src/core/domains/auth/exceptions/ForbiddenResourceError.ts: -------------------------------------------------------------------------------- 1 | export default class ForbiddenResourceError extends Error { 2 | 3 | constructor(message: string = 'You do not have permission to access this resource') { 4 | super(message); 5 | this.name = 'ForbiddenResourceError'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/auth/exceptions/InvalidJWTSecret.ts: -------------------------------------------------------------------------------- 1 | export default class InvalidJWTSecret extends Error { 2 | 3 | constructor(message: string = 'Invalid JWT Secret. Use "yarn dev auth:generate-jwt-secret --no-db" to generate a new secret') { 4 | super(message); 5 | this.name = 'InvalidJWTSecret'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/auth/exceptions/InvalidJwtSettings.ts: -------------------------------------------------------------------------------- 1 | export default class InvalidJwtSettingsException extends Error { 2 | 3 | constructor(message: string = 'Invalid JWT settings') { 4 | super(message); 5 | this.name = 'InvalidJwtSettingsException'; 6 | } 7 | 8 | 9 | } -------------------------------------------------------------------------------- /src/core/domains/auth/exceptions/RateLimitedExceededError.ts: -------------------------------------------------------------------------------- 1 | export default class RateLimitedExceededError extends Error { 2 | 3 | constructor(message: string = 'Too many requests. Try again later.') { 4 | super(message); 5 | this.name = 'RateLimitedExceededError'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/auth/exceptions/UnauthorizedError.ts: -------------------------------------------------------------------------------- 1 | export default class UnauthorizedError extends Error { 2 | 3 | constructor(message: string = 'Unauthorized') { 4 | super(message); 5 | this.name = 'UnauthorizeError'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/auth/factory/JwtFactory.ts: -------------------------------------------------------------------------------- 1 | import { IJSonWebToken } from "@src/core/domains/auth/interfaces/jwt/IJsonWebToken" 2 | 3 | 4 | /** 5 | * Factory for creating JWT tokens. 6 | * 7 | * @class JWTTokenFactory 8 | */ 9 | export default class JwtFactory { 10 | 11 | /** 12 | * Creates a new JWT token from a user ID and token. 13 | * 14 | * @param {string} userId - The user ID. 15 | * @param {string} token - The token. 16 | * @returns {IJSonWebToken} A new JWT token. 17 | */ 18 | static createUserIdAndPayload(userId: string, token: string): IJSonWebToken { 19 | return { 20 | uid: userId, 21 | token 22 | } 23 | } 24 | 25 | } 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/core/domains/auth/interfaces/acl/IAclConfig.ts: -------------------------------------------------------------------------------- 1 | export interface IAclConfig { 2 | defaultGroup: string; 3 | groups: IAclGroup[]; 4 | roles: IAclRole[]; 5 | } 6 | 7 | export interface IAclGroup { 8 | name: string; 9 | roles: string[]; 10 | } 11 | 12 | export interface IAclRole { 13 | name: string; 14 | scopes: string[]; 15 | } 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/core/domains/auth/interfaces/adapter/AuthAdapterTypes.t.ts: -------------------------------------------------------------------------------- 1 | import { BaseAdapterTypes } from "@src/core/base/BaseAdapter"; 2 | import { IJwtAuthService } from "@src/core/domains/auth/interfaces/jwt/IJwtAuthService"; 3 | import { IAuthAdapter } from "@src/core/domains/auth/interfaces/adapter/IAuthAdapter"; 4 | 5 | export type BaseAuthAdapterTypes = BaseAdapterTypes & { 6 | default: IJwtAuthService 7 | } -------------------------------------------------------------------------------- /src/core/domains/auth/interfaces/adapter/IAuthAdapter.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { IAclConfig } from "@src/core/domains/auth/interfaces/acl/IAclConfig"; 3 | import { IBaseAuthConfig } from "@src/core/domains/auth/interfaces/config/IAuth"; 4 | import { IRouter } from "@src/core/domains/http/interfaces/IRouter"; 5 | import { IUserModel } from "@src/core/domains/auth/interfaces/models/IUserModel"; 6 | 7 | export interface AuthAdapterConstructor { 8 | new (config: Adapter['config'], aclConfig: IAclConfig): Adapter; 9 | } 10 | 11 | export interface IAuthAdapter { 12 | boot(): Promise; 13 | config: TConfig; 14 | getConfig(): TConfig; 15 | setConfig(config: TConfig): void; 16 | getRouter(): IRouter; 17 | 18 | authorizeUser(user: IUserModel): void; 19 | user(): Promise; 20 | check(): Promise; 21 | } 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/core/domains/auth/interfaces/config/IAuth.ts: -------------------------------------------------------------------------------- 1 | import { AuthAdapterConstructor } from "@src/core/domains/auth/interfaces/adapter/IAuthAdapter"; 2 | 3 | export interface IBaseAuthConfig { 4 | name: string; 5 | adapter: AuthAdapterConstructor 6 | } 7 | -------------------------------------------------------------------------------- /src/core/domains/auth/interfaces/jwt/IJsonWebToken.ts: -------------------------------------------------------------------------------- 1 | export interface IJSonWebToken { 2 | uid: string; 3 | token: string; 4 | iat?: number; 5 | exp?: number; 6 | } -------------------------------------------------------------------------------- /src/core/domains/auth/interfaces/models/IUserModel.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { IModel, ModelConstructor } from "@src/core/domains/models/interfaces/IModel"; 3 | 4 | export interface UserConstructor extends ModelConstructor {} 5 | 6 | export interface IUserModel extends IModel { 7 | getEmail(): string | null; 8 | setEmail(email: string): Promise; 9 | getHashedPassword(): string | null; 10 | setHashedPassword(hashedPassword: string): Promise; 11 | getRoles(): string[]; 12 | setRoles(roles: string[]): Promise; 13 | hasRole(role: string | string[]): boolean; 14 | getGroups(): string[]; 15 | setGroups(groups: string[]): Promise; 16 | hasScope(scope: string): boolean; 17 | hasScopes(scopes: string[]): boolean; 18 | } 19 | -------------------------------------------------------------------------------- /src/core/domains/auth/interfaces/repository/IApiTokenRepository.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { IApiTokenModel } from "@src/core/domains/auth/interfaces/models/IApiTokenModel"; 3 | 4 | export interface IApiTokenRepository { 5 | findOneActiveToken(token: string): Promise 6 | revokeToken(apiToken: IApiTokenModel): Promise 7 | revokeAllTokens(userId: string | number): Promise 8 | } -------------------------------------------------------------------------------- /src/core/domains/auth/interfaces/repository/IUserRepository.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { IUserModel } from "@src/core/domains/auth/interfaces/models/IUserModel"; 3 | 4 | export interface IUserRepository { 5 | create(attributes?: IUserModel): IUserModel 6 | findById(id: string | number): Promise 7 | findByIdOrFail(id: string | number): Promise 8 | findByEmail(email: string): Promise 9 | } -------------------------------------------------------------------------------- /src/core/domains/auth/interfaces/scope/Scope.t.ts: -------------------------------------------------------------------------------- 1 | export type Scope = 'read' | 'write' | 'create' | 'delete' | 'all'; -------------------------------------------------------------------------------- /src/core/domains/auth/interfaces/service/IAuthService.ts: -------------------------------------------------------------------------------- 1 | 2 | import { AuthAdapters } from "@src/config/auth.config"; 3 | import { IBasicACLService } from "@src/core/domains/accessControl/interfaces/IACLService"; 4 | import { IUserModel } from "@src/core/domains/auth/interfaces/models/IUserModel"; 5 | 6 | import { IUserRepository } from "@src/core/domains/auth/interfaces/repository/IUserRepository"; 7 | 8 | export interface IAuthService { 9 | acl(): IBasicACLService; 10 | boot(): Promise 11 | getDefaultAdapter(): AuthAdapters['default'] 12 | getJwtAdapter(): AuthAdapters['jwt'] 13 | check(): Promise 14 | user(): Promise 15 | getUserRepository(): IUserRepository 16 | } 17 | -------------------------------------------------------------------------------- /src/core/domains/auth/interfaces/service/oneTimeService.ts: -------------------------------------------------------------------------------- 1 | import { ApiTokenModelOptions, IApiTokenModel } from "@src/core/domains/auth/interfaces/models/IApiTokenModel"; 2 | import { IUserModel } from "@src/core/domains/auth/interfaces/models/IUserModel"; 3 | 4 | type SingleUseTokenOptions = Required> 5 | 6 | export interface IOneTimeAuthenticationService { 7 | getScope(): string; 8 | createSingleUseToken(user: IUserModel, scopes?: string[], options?: SingleUseTokenOptions): Promise; 9 | validateSingleUseToken(apiToken: IApiTokenModel): boolean; 10 | } 11 | -------------------------------------------------------------------------------- /src/core/domains/auth/providers/AuthProvider.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | import { aclConfig } from "@src/config/acl.config"; 4 | import { authConfig } from "@src/config/auth.config"; 5 | import BaseProvider from "@src/core/base/Provider"; 6 | import GenerateJwtSecret from "@src/core/domains/auth/commands/GenerateJwtSecret"; 7 | import Auth from "@src/core/domains/auth/services/AuthService"; 8 | import { app } from "@src/core/services/App"; 9 | 10 | class AuthProvider extends BaseProvider{ 11 | 12 | protected config = authConfig 13 | 14 | protected aclConfig = aclConfig 15 | 16 | async register() { 17 | const authService = new Auth(this.config, this.aclConfig); 18 | await authService.boot(); 19 | 20 | // Bind services 21 | this.bind('auth', authService); 22 | this.bind('auth.jwt', (() => authService.getDefaultAdapter())()) 23 | 24 | // Register commands 25 | app('console').register(GenerateJwtSecret) 26 | } 27 | 28 | 29 | } 30 | 31 | export default AuthProvider; 32 | 33 | -------------------------------------------------------------------------------- /src/core/domains/auth/utils/comparePassword.ts: -------------------------------------------------------------------------------- 1 | import bcrypt from 'bcryptjs' 2 | 3 | /** 4 | * Compares a plain text password with a hashed password. 5 | * 6 | * @param password The plain text password 7 | * @param hashedPassword The hashed password 8 | * @returns true if the password matches the hashed password, false otherwise 9 | * @deprecated Use cryptoService().verifyHash instead 10 | */ 11 | export default (password: string, hashedPassword: string): boolean => bcrypt.compareSync(password, hashedPassword) 12 | -------------------------------------------------------------------------------- /src/core/domains/auth/utils/createJwt.ts: -------------------------------------------------------------------------------- 1 | import jwt, { SignOptions } from 'jsonwebtoken' 2 | 3 | /** 4 | * Creates a JWT token 5 | * @param secret The secret to sign the token with 6 | * @param data The data to be stored in the token 7 | * @param expiresIn The time until the token expires (default is 1 hour) 8 | * @returns The created JWT token as a string 9 | */ 10 | export default (secret: string, data: object, expiresIn: string = '1h'): string => { 11 | return jwt.sign(data, secret, { expiresIn } as SignOptions) 12 | } 13 | -------------------------------------------------------------------------------- /src/core/domains/auth/utils/decodeJwt.ts: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken' 2 | import { IJSonWebToken } from '@src/core/domains/auth/interfaces/jwt/IJsonWebToken' 3 | 4 | /** 5 | * Decodes a JWT token using the provided secret. 6 | * 7 | * @param {string} secret - The secret to use to decode the token. 8 | * @param {string} token - The JWT token to decode. 9 | * @returns {IJSonWebToken} The decoded token. 10 | */ 11 | export default (secret: string, token: string): IJSonWebToken => { 12 | return jwt.verify(token, secret) as IJSonWebToken 13 | } 14 | -------------------------------------------------------------------------------- /src/core/domains/auth/utils/generateToken.ts: -------------------------------------------------------------------------------- 1 | import crypto, { BinaryToTextEncoding } from 'crypto' 2 | 3 | /** 4 | * Generates a random token of the given size (in bytes) and encoding. 5 | * @param {number} [size=64] The size of the token in bytes. 6 | * @param {BinaryToTextEncoding} [bufferEncoding='hex'] The encoding to use when converting the buffer to a string. 7 | * @returns {string} A random token as a string. 8 | */ 9 | export default (size: number = 64, bufferEncoding: BinaryToTextEncoding = 'hex'): string => crypto.randomBytes(size).toString(bufferEncoding) 10 | -------------------------------------------------------------------------------- /src/core/domains/auth/utils/hashPassword.ts: -------------------------------------------------------------------------------- 1 | import bcryptjs from 'bcryptjs' 2 | 3 | /** 4 | * Hashes a password using bcryptjs with a given salt (default is 10) 5 | * @param password The password to hash 6 | * @param salt The salt to use for hashing (optional, default is 10) 7 | * @returns The hashed password 8 | * @deprecated Use cryptoService().hash instead 9 | */ 10 | export default (password: string, salt: number = 10): string => bcryptjs.hashSync(password, salt) 11 | 12 | -------------------------------------------------------------------------------- /src/core/domains/cast/base/BaseCastable.ts: -------------------------------------------------------------------------------- 1 | import { IHasCastableConcern } from "@src/core/domains/cast/interfaces/IHasCastableConcern"; 2 | import { TClassConstructor } from "@src/core/interfaces/ClassConstructor.t"; 3 | import compose from "@src/core/util/compose"; 4 | import HasCastableConcern from "@src/core/domains/cast/concerns/HasCastableConcern"; 5 | 6 | const BaseCastable: TClassConstructor = compose(class {}, HasCastableConcern) 7 | 8 | export default BaseCastable -------------------------------------------------------------------------------- /src/core/domains/cast/concerns/HasCastableConcern.ts: -------------------------------------------------------------------------------- 1 | import { TClassConstructor } from "@src/core/interfaces/ClassConstructor.t"; 2 | import { TCastableType } from "@src/core/domains/cast/interfaces/IHasCastableConcern"; 3 | import Castable from "@src/core/domains/cast/service/Castable"; 4 | 5 | const HasCastableConcernMixin = (Base: TClassConstructor) => { 6 | return class extends Base { 7 | 8 | private castable = new Castable(); 9 | 10 | getCastFromObject(data: Record, casts = this.casts): ReturnType { 11 | return this.castable.getCastFromObject(data, casts); 12 | } 13 | 14 | getCast(data: unknown, type: TCastableType): T { 15 | return this.castable.getCast(data, type); 16 | } 17 | 18 | isValidType(type: TCastableType): boolean { 19 | return this.castable.isValidType(type); 20 | } 21 | 22 | casts = {}; 23 | 24 | } 25 | } 26 | 27 | export default HasCastableConcernMixin; -------------------------------------------------------------------------------- /src/core/domains/cast/interfaces/CastException.ts: -------------------------------------------------------------------------------- 1 | export default class CastException extends Error { 2 | 3 | constructor(message: string = 'Cast Exception') { 4 | super(message); 5 | this.name = 'CastException'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/cast/interfaces/IHasCastableConcern.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | export type TCastableType = 3 | | 'string' 4 | | 'number' 5 | | 'boolean' 6 | | 'array' 7 | | 'object' 8 | | 'date' 9 | | 'integer' 10 | | 'float' 11 | | 'bigint' 12 | | 'null' 13 | | 'undefined' 14 | | 'symbol' 15 | | 'map' 16 | | 'set'; 17 | 18 | export interface IHasCastableConcern { 19 | casts: Record; 20 | getCastFromObject(data: Record, casts?: TCasts): ReturnType; 21 | getCast(data: unknown, type: TCastableType): T; 22 | isValidType(type: TCastableType): boolean; 23 | } 24 | 25 | export type TCasts = Record; -------------------------------------------------------------------------------- /src/core/domains/collections/helper/collect.ts: -------------------------------------------------------------------------------- 1 | import Collection from "@src/core/domains/collections/Collection"; 2 | 3 | 4 | /** 5 | * Creates a new proxy collection from the given items. The proxy collection 6 | * supports accessing the underlying items using numerical indexes. 7 | * 8 | * @template T The type of the items in the collection. 9 | * @param {T[]} [items=[]] The items to create a collection from. 10 | * @returns {Collection} A new proxy collection with the given items. 11 | */ 12 | export const collect = (items: T[] = []): Collection => { 13 | return Collection.collect(items) 14 | } 15 | 16 | export default collect -------------------------------------------------------------------------------- /src/core/domains/console/exceptions/CommandArguementParserException.ts: -------------------------------------------------------------------------------- 1 | export default class CommandArguementParserException extends Error { 2 | 3 | constructor(message: string = 'Command Arguement Parser failed') { 4 | super(message); 5 | this.name = 'CommandArguementParserException'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/console/exceptions/CommandExecutionException.ts: -------------------------------------------------------------------------------- 1 | export default class CommandExecutionException extends Error { 2 | 3 | constructor(message: string = 'Command failed to execute') { 4 | super(message); 5 | this.name = 'CommandNotFound'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/console/exceptions/CommandNotFoundException.ts: -------------------------------------------------------------------------------- 1 | export default class CommandNotFoundException extends Error { 2 | 3 | constructor(message: string = 'Command not found') { 4 | super(message); 5 | this.name = 'CommandNotFound'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/console/exceptions/CommandRegisterException.ts: -------------------------------------------------------------------------------- 1 | export default class CommandRegisterException extends Error { 2 | 3 | constructor(name: string) { 4 | super(`Command '${name}' could not be registered. A command with the same signature may already exist.`); 5 | this.name = 'CommandRegisterException'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/console/exceptions/CommandSignatureInvalid.ts: -------------------------------------------------------------------------------- 1 | export default class CommandSignatureInvalid extends Error { 2 | 3 | constructor(message: string = 'Invalid Command Signature') { 4 | super(message); 5 | this.name = 'CommandSignatureInvalid'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/console/interfaces/ICommandBootService.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { KernelOptions } from "@src/core/Kernel"; 3 | 4 | /** 5 | * Interface for a service that can boot the console interface 6 | */ 7 | interface ICommandBootService 8 | { 9 | 10 | /** 11 | * Get the kernel options based on the args and options 12 | * @param args The arguments passed from the command line 13 | * @param options The options passed to the kernel 14 | * @returns The kernel options 15 | */ 16 | getKernelOptions(args: string[], options: KernelOptions): KernelOptions; 17 | 18 | /** 19 | * Boot the kernel 20 | * @param args The arguments passed from the command line 21 | * @returns A promise that resolves when the kernel is booted 22 | */ 23 | boot(args: string[]): Promise; 24 | } 25 | 26 | 27 | export default ICommandBootService -------------------------------------------------------------------------------- /src/core/domains/console/interfaces/ICommandReader.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | export interface ICommandReader { 3 | handle: (...args: any[]) => any; 4 | } -------------------------------------------------------------------------------- /src/core/domains/crypto/commands/GenerateAppKey.ts: -------------------------------------------------------------------------------- 1 | import EnvService from "@src/core/services/EnvService"; 2 | import BaseCommand from "@src/core/domains/console/base/BaseCommand"; 3 | import { cryptoService } from "@src/core/domains/crypto/service/CryptoService"; 4 | 5 | class GenerateAppKey extends BaseCommand { 6 | 7 | signature = 'app:generate-key' 8 | 9 | description = 'Generate a new app key' 10 | 11 | envService = new EnvService(); 12 | 13 | async execute() { 14 | 15 | const confirm = await this.input.askQuestion('Are you sure you want to generate a new app key? (y/n)') 16 | 17 | if (confirm !== 'y') { 18 | console.log('App key generation cancelled.') 19 | return 20 | } 21 | 22 | console.log('Generating app key...') 23 | 24 | const appKey = cryptoService().generateAppKey() 25 | 26 | await this.envService.updateValues({ 27 | APP_KEY: appKey 28 | }) 29 | 30 | console.log(`App key generated: ${appKey}`) 31 | } 32 | 33 | } 34 | 35 | export default GenerateAppKey 36 | -------------------------------------------------------------------------------- /src/core/domains/crypto/interfaces/BufferingEncoding.t.ts: -------------------------------------------------------------------------------- 1 | export type BufferEncoding = 2 | | "ascii" 3 | | "utf8" 4 | | "utf-8" 5 | | "utf16le" 6 | | "utf-16le" 7 | | "ucs2" 8 | | "ucs-2" 9 | | "base64" 10 | | "base64url" 11 | | "latin1" 12 | | "binary" 13 | | "hex"; -------------------------------------------------------------------------------- /src/core/domains/crypto/interfaces/ICryptoConfig.ts: -------------------------------------------------------------------------------- 1 | export interface ICryptoConfig { 2 | appKey: string 3 | } 4 | 5 | -------------------------------------------------------------------------------- /src/core/domains/crypto/interfaces/ICryptoService.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { BufferEncoding } from "@src/core/domains/crypto/interfaces/BufferingEncoding.t" 3 | 4 | export interface ICryptoService { 5 | generateBytesAsString(length?: number, encoding?: BufferEncoding): string 6 | encrypt(string: string): string 7 | decrypt(string: string): string 8 | hash(string: string): string 9 | verifyHash(string: string, hashWithSalt: string): boolean 10 | generateAppKey(): string 11 | } 12 | -------------------------------------------------------------------------------- /src/core/domains/crypto/providers/CryptoProvider.ts: -------------------------------------------------------------------------------- 1 | import appConfig, { IAppConfig } from "@src/config/app.config"; 2 | import BaseProvider from "@src/core/base/Provider"; 3 | import GenerateAppKey from "@src/core/domains/crypto/commands/GenerateAppKey"; 4 | import CryptoService from "@src/core/domains/crypto/service/CryptoService"; 5 | import { app } from "@src/core/services/App"; 6 | 7 | class CryptoProvider extends BaseProvider { 8 | 9 | config: IAppConfig = appConfig; 10 | 11 | async register(): Promise { 12 | 13 | const config = { 14 | appKey: this.config.appKey 15 | } 16 | const cryptoService = new CryptoService(config) 17 | 18 | // Bind the crypto service 19 | this.bind('crypto', cryptoService) 20 | 21 | // Register commands 22 | app('console').registerService().registerAll([ 23 | GenerateAppKey 24 | ]) 25 | } 26 | 27 | } 28 | 29 | export default CryptoProvider -------------------------------------------------------------------------------- /src/core/domains/database/exceptions/CreateDatabaseException.ts: -------------------------------------------------------------------------------- 1 | 2 | export default class CreateDatabaseException extends Error { 3 | 4 | constructor(message: string = 'Create Database Exception') { 5 | super(message); 6 | this.name = 'CreateDatabaseException'; 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /src/core/domains/database/exceptions/DatabaseAdapterException.ts: -------------------------------------------------------------------------------- 1 | 2 | export default class DatabaseAdapterException extends Error { 3 | 4 | constructor(message: string = 'Database Adapter Exception') { 5 | super(message); 6 | this.name = 'DatabaseAdapterException'; 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /src/core/domains/database/exceptions/DatabaseConfigException.ts: -------------------------------------------------------------------------------- 1 | 2 | export default class DatabaseConfigException extends Error { 3 | 4 | constructor(message: string = 'Database Config Exception') { 5 | super(message); 6 | this.name = 'DatabaseConfigException'; 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /src/core/domains/database/exceptions/DatabaseConnectionException.ts: -------------------------------------------------------------------------------- 1 | 2 | export default class DatabaseConnectionException extends Error { 3 | 4 | constructor(message: string = 'Invalid Database Connection') { 5 | super(message); 6 | this.name = 'InvalidDatabaseConnection'; 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /src/core/domains/database/exceptions/DropDatabaseException.ts: -------------------------------------------------------------------------------- 1 | 2 | export default class DropDatabaseException extends Error { 3 | 4 | constructor(message: string = 'Drop Database Exception') { 5 | super(message); 6 | this.name = 'DropDatabaseException'; 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /src/core/domains/database/exceptions/InvalidObjectId.ts: -------------------------------------------------------------------------------- 1 | 2 | export default class InvalidObjectId extends Error { 3 | 4 | constructor(message: string = 'Invalid ObjectId') { 5 | super(message); 6 | this.name = 'InvalidObjectId'; 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /src/core/domains/database/exceptions/InvalidSequelize.ts: -------------------------------------------------------------------------------- 1 | 2 | export default class InvalidSequelize extends Error { 3 | 4 | constructor(message: string = 'Invalid Sequelize') { 5 | super(message); 6 | this.name = 'InvalidSequelize'; 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /src/core/domains/database/exceptions/InvalidTable.ts: -------------------------------------------------------------------------------- 1 | 2 | export default class MissingTable extends Error { 3 | 4 | constructor(message: string = 'Table name was not specified') { 5 | super(message); 6 | this.name = 'MissingTable'; 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /src/core/domains/database/exceptions/UnidentifiableDocument.ts: -------------------------------------------------------------------------------- 1 | 2 | export default class InvalidDocument extends Error { 3 | 4 | constructor(message: string = 'An id property was expected but not found') { 5 | super(message); 6 | this.name = 'UnidentifiableDocument'; 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /src/core/domains/database/interfaces/IConnectionTypeHelpers.ts: -------------------------------------------------------------------------------- 1 | import MongoDbAdapter from "@src/core/domains/mongodb/adapters/MongoDbAdapter"; 2 | import PostgresAdapter from "@src/core/domains/postgres/adapters/PostgresAdapter"; 3 | 4 | /** 5 | * Type helper for the connection adapters 6 | */ 7 | export type IConnectionTypeHelpers = { 8 | ['default']: PostgresAdapter; 9 | ['postgres']: PostgresAdapter; 10 | ['mongodb']: MongoDbAdapter; 11 | } -------------------------------------------------------------------------------- /src/core/domains/database/interfaces/IDatabaseConfig.ts: -------------------------------------------------------------------------------- 1 | import { IDatabaseAdapter } from "@src/core/domains/database/interfaces/IDatabaseAdapter"; 2 | import { TClassConstructor } from "@src/core/interfaces/ClassConstructor.t"; 3 | 4 | export interface IDatabaseGenericConnectionConfig { 5 | connectionName: string; 6 | adapter: TClassConstructor; 7 | uri: string, 8 | options: Options; 9 | } 10 | 11 | export interface IDatabaseConfig { 12 | enableLogging?: boolean; 13 | onBootConnect?: boolean; 14 | defaultConnectionName: string; 15 | keepAliveConnections: string; 16 | connections: IDatabaseGenericConnectionConfig[]; 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/core/domains/database/interfaces/IPrepareOptions.ts: -------------------------------------------------------------------------------- 1 | export interface IPrepareOptions { 2 | jsonStringify?: string[]; 3 | jsonParse?: string[]; 4 | } -------------------------------------------------------------------------------- /src/core/domains/eloquent/enums/Direction.ts: -------------------------------------------------------------------------------- 1 | const Direction = { 2 | ASC: 'asc', 3 | DESC: 'desc' 4 | } as const 5 | 6 | export default Direction -------------------------------------------------------------------------------- /src/core/domains/eloquent/exceptions/EloquentExpression.ts: -------------------------------------------------------------------------------- 1 | export default class EloquentException extends Error { 2 | 3 | constructor(message: string = 'Eloquent Exception') { 4 | super(message); 5 | this.name = 'EloquentException'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/eloquent/exceptions/EloquentRelationshipException.ts: -------------------------------------------------------------------------------- 1 | export default class EloquentRelationshipException extends Error { 2 | 3 | constructor(message: string = 'Eloquent Relationship Exception') { 4 | super(message); 5 | this.name = 'EloquentRelationshipException'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/eloquent/exceptions/ExpressionException.ts: -------------------------------------------------------------------------------- 1 | export default class ExpressionException extends Error { 2 | 3 | constructor(message: string = 'Expression Exception') { 4 | super(message); 5 | this.name = 'ExpressionException'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/eloquent/exceptions/InsertException.ts: -------------------------------------------------------------------------------- 1 | export default class InsertException extends Error { 2 | 3 | constructor(message: string = 'Insert Exception') { 4 | super(message); 5 | this.name = 'InsertException'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/eloquent/exceptions/InvalidMethodException.ts: -------------------------------------------------------------------------------- 1 | export default class InvalidMethodException extends Error { 2 | 3 | constructor(message: string = 'Invalid Method Exception') { 4 | super(message); 5 | this.name = 'InvalidMethodException'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/eloquent/exceptions/MissingTableException.ts: -------------------------------------------------------------------------------- 1 | export default class MissingTableException extends Error { 2 | 3 | constructor(message: string = 'Table name was not specified') { 4 | super(message); 5 | this.name = 'MissingTableException'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/eloquent/exceptions/QueryBuilderException.ts: -------------------------------------------------------------------------------- 1 | export default class QueryBuilderException extends Error { 2 | 3 | constructor(message: string = 'Query Builder Exception') { 4 | super(message); 5 | this.name = 'QueryBuilderException'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/eloquent/exceptions/UpdateException.ts: -------------------------------------------------------------------------------- 1 | export default class UpdateException extends Error { 2 | 3 | constructor(message: string = 'Update Exception') { 4 | super(message); 5 | this.name = 'UpdateException'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/eloquent/interfaces/IEloquentQueryBuilderService.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { IEloquent } from "@src/core/domains/eloquent/interfaces/IEloquent"; 3 | import { IModel } from "@src/core/domains/models/interfaces/IModel"; 4 | import { TClassConstructor } from "@src/core/interfaces/ClassConstructor.t"; 5 | 6 | export interface IEloquentQueryBuilderService { 7 | builder(modelCtor: TClassConstructor, connectionName?: string): IEloquent; 8 | } -------------------------------------------------------------------------------- /src/core/domains/eloquent/interfaces/IEqloeuntRelationship.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import Collection from "@src/core/domains/collections/Collection"; 3 | import { IEloquent, IRelationship } from "@src/core/domains/eloquent/interfaces/IEloquent"; 4 | import { IModel, IModelAttributes } from "@src/core/domains/models/interfaces/IModel"; 5 | 6 | export interface IRelationshipResolver { 7 | resolveData(model: Model, relationship: IRelationship, connection: string): Promise>; 8 | attachEloquentRelationship(eloquent: IEloquent, relationship: IRelationship, relationshipName: string): IEloquent; 9 | } 10 | 11 | export interface IRelationshipResolverConstructor { 12 | new (connection: string): IRelationshipResolver; 13 | } -------------------------------------------------------------------------------- /src/core/domains/eloquent/interfaces/TEnums.ts: -------------------------------------------------------------------------------- 1 | export type TDirection = 'asc' | 'desc' -------------------------------------------------------------------------------- /src/core/domains/eloquent/providers/EloquentQueryProvider.ts: -------------------------------------------------------------------------------- 1 | import BaseProvider from "@src/core/base/Provider"; 2 | import EloquentQueryBuilderService from "@src/core/domains/eloquent/services/EloquentQueryBuilderService"; 3 | 4 | class EloquentQueryProvider extends BaseProvider { 5 | 6 | /** 7 | * EloquentQueryProvider class 8 | * 9 | * This provider is responsible for registering the QueryService into the App container. 10 | * The QueryService provides a fluent interface for database queries using the Eloquent ORM pattern. 11 | * 12 | * @extends BaseProvider 13 | * @see BaseProvider 14 | * @see EloquentQueryBuilderService 15 | */ 16 | async register() { 17 | this.bind('query', new EloquentQueryBuilderService()); 18 | } 19 | 20 | } 21 | 22 | export default EloquentQueryProvider; -------------------------------------------------------------------------------- /src/core/domains/events/base/BaseEventSubciber.ts: -------------------------------------------------------------------------------- 1 | import BaseEvent from "@src/core/domains/events/base/BaseEvent"; 2 | import { IBaseSubscriber } from "@src/core/domains/events/interfaces/IBaseEvent"; 3 | 4 | class BaseEventSubscriber extends BaseEvent implements IBaseSubscriber { 5 | 6 | type: 'subscriber' = 'subscriber'; 7 | 8 | } 9 | 10 | export default BaseEventSubscriber -------------------------------------------------------------------------------- /src/core/domains/events/drivers/SyncDriver.ts: -------------------------------------------------------------------------------- 1 | import BaseDriver from "@src/core/domains/events/base/BaseDriver"; 2 | import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; 3 | 4 | class SyncDriver extends BaseDriver { 5 | 6 | async dispatch(event: IBaseEvent): Promise { 7 | await event.execute(); 8 | } 9 | 10 | } 11 | 12 | export default SyncDriver -------------------------------------------------------------------------------- /src/core/domains/events/exceptions/EventDispatchException.ts: -------------------------------------------------------------------------------- 1 | export default class EventDispatchException extends Error { 2 | 3 | constructor(message: string = 'Event Dispatch Exception') { 4 | super(message); 5 | this.name = 'EventDispatchException'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/events/exceptions/EventDriverException.ts: -------------------------------------------------------------------------------- 1 | export default class EventDriverException extends Error { 2 | 3 | constructor(message: string = 'Event Driver Exception') { 4 | super(message); 5 | this.name = 'EventDriverException'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/events/exceptions/EventInvalidPayloadException.ts: -------------------------------------------------------------------------------- 1 | export default class EventInvalidPayloadException extends Error { 2 | 3 | constructor(message: string = 'Invalid payload') { 4 | super(message); 5 | this.name = 'EventInvalidPayloadException'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/events/exceptions/EventMockException.ts: -------------------------------------------------------------------------------- 1 | export default class EventMockException extends Error { 2 | 3 | constructor(message: string = 'Mock Exception') { 4 | super(message); 5 | this.name = 'MockException'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/events/exceptions/EventNotDispatchedException.ts: -------------------------------------------------------------------------------- 1 | export default class EventNotDispatchedException extends Error { 2 | 3 | constructor(message: string = 'Event Not Dispatched Exception') { 4 | super(message); 5 | this.name = 'EventNotDispatchedException'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/events/exceptions/EventWorkerException.ts: -------------------------------------------------------------------------------- 1 | export default class EventWorkerException extends Error { 2 | 3 | constructor(message: string = 'Event Worker Exception') { 4 | super(message); 5 | this.name = 'EventWorkerException'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/events/interfaces/IEventConstructors.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { IBaseEvent, IBaseListener, IBaseSubscriber } from "@src/core/domains/events/interfaces/IBaseEvent"; 3 | 4 | export interface EventConstructor { 5 | new (...args: any[]): IBaseEvent; 6 | } 7 | 8 | export interface ListenerConstructor { 9 | new (...args: any[]): IBaseListener; 10 | } 11 | 12 | export interface SubscriberConstructor { 13 | new (...args: any[]): IBaseSubscriber; 14 | } 15 | -------------------------------------------------------------------------------- /src/core/domains/events/interfaces/IEventDriver.ts: -------------------------------------------------------------------------------- 1 | 2 | import { IDispatchable } from "@src/core/interfaces/concerns/IDispatchable"; 3 | import { INameable } from "@src/core/interfaces/concerns/INameable"; 4 | 5 | export default interface IEventDriver extends INameable, IDispatchable {} -------------------------------------------------------------------------------- /src/core/domains/events/interfaces/IEventListener.ts: -------------------------------------------------------------------------------- 1 | import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; 2 | import { INameable } from "@src/core/interfaces/concerns/INameable"; 3 | 4 | export interface IEventListener extends INameable, IBaseEvent { 5 | 6 | } -------------------------------------------------------------------------------- /src/core/domains/events/interfaces/IEventPayload.ts: -------------------------------------------------------------------------------- 1 | export type TSerializableValues = number | string | boolean | undefined | null; 2 | 3 | export type TISerializablePayload = Record | TSerializableValues; -------------------------------------------------------------------------------- /src/core/domains/events/interfaces/IMockableConcern.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { IBaseEvent } from "@src/core/domains/events/interfaces/IBaseEvent"; 3 | import { TClassConstructor } from "@src/core/interfaces/ClassConstructor.t"; 4 | 5 | 6 | export type TMockableEventCallback = (payload: TPayload) => boolean; 7 | 8 | export interface IMockableConcern { 9 | mockEvent(event: TClassConstructor): void; 10 | 11 | mockEventDispatched(event: IBaseEvent): void; 12 | 13 | resetMockEvents(): void; 14 | 15 | assertDispatched(eventCtor: TClassConstructor, callback?: TMockableEventCallback): boolean; 16 | } -------------------------------------------------------------------------------- /src/core/domains/events/interfaces/IQueableDriverOptions.ts: -------------------------------------------------------------------------------- 1 | import { IModel } from "@src/core/domains/models/interfaces/IModel"; 2 | import { TClassConstructor } from "@src/core/interfaces/ClassConstructor.t"; 3 | 4 | export interface IQueableDriverOptions { 5 | queueName: string; 6 | retries: number; 7 | failedCollection: string; 8 | runAfterSeconds: number; 9 | workerModelCtor: TClassConstructor; 10 | } -------------------------------------------------------------------------------- /src/core/domains/events/interfaces/config/IEventConfig.ts: -------------------------------------------------------------------------------- 1 | import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; 2 | import { IEventDriversConfig } from "@src/core/domains/events/interfaces/config/IEventDriversConfig"; 3 | import { TClassConstructor } from "@src/core/interfaces/ClassConstructor.t"; 4 | import { TListenersConfigOption } from "@src/core/domains/events/interfaces/config/IEventListenersConfig"; 5 | 6 | export interface IEventConfig { 7 | defaultDriver: TClassConstructor; 8 | drivers: IEventDriversConfig; 9 | listeners: TListenersConfigOption[]; 10 | } -------------------------------------------------------------------------------- /src/core/domains/events/interfaces/config/IEventDriversConfig.ts: -------------------------------------------------------------------------------- 1 | import IEventDriver from "@src/core/domains/events/interfaces/IEventDriver"; 2 | import { TClassConstructor } from "@src/core/interfaces/ClassConstructor.t"; 3 | 4 | export interface IEventDriversConfigOption { 5 | driverCtor: TClassConstructor, 6 | options?: Record; 7 | } 8 | 9 | export type TEventDriversRegister = Record; 10 | 11 | export interface IEventDriversConfig 12 | { 13 | [key: string]: IEventDriversConfigOption 14 | } -------------------------------------------------------------------------------- /src/core/domains/events/interfaces/config/IEventListenersConfig.ts: -------------------------------------------------------------------------------- 1 | 2 | import { ListenerConstructor, SubscriberConstructor } from "@src/core/domains/events/interfaces/IEventConstructors"; 3 | 4 | export type TListenersConfigOption = { 5 | listener: ListenerConstructor; 6 | subscribers: SubscriberConstructor[] 7 | } 8 | 9 | export type TListenersMap = Map 10 | 11 | export type IEventListenersConfig = TListenersConfigOption[] -------------------------------------------------------------------------------- /src/core/domains/express/exceptions/HttpContextException.ts: -------------------------------------------------------------------------------- 1 | class HttpContextException extends Error { 2 | 3 | constructor(message: string) { 4 | super(message); 5 | this.name = 'HttpContextException'; 6 | } 7 | 8 | } 9 | 10 | export default HttpContextException; -------------------------------------------------------------------------------- /src/core/domains/express/exceptions/MissingSecurityError.ts: -------------------------------------------------------------------------------- 1 | export default class MissingSecurityError extends Error { 2 | 3 | constructor(message: string = 'Missing security for this route') { 4 | super(message); 5 | this.name = 'MissingSecurityError'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/express/exceptions/QueryFiltersException.ts: -------------------------------------------------------------------------------- 1 | export default class QueryFiltersException extends Error { 2 | 3 | constructor(message: string = 'Query Filters Exception') { 4 | super(message); 5 | this.name = 'QueryFiltersException'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/express/exceptions/ResourceException.ts: -------------------------------------------------------------------------------- 1 | class ResourceException extends Error { 2 | 3 | constructor(message: string) { 4 | super(message); 5 | this.name = 'ResourceException'; 6 | } 7 | 8 | } 9 | 10 | export default ResourceException; -------------------------------------------------------------------------------- /src/core/domains/express/exceptions/RouteException.ts: -------------------------------------------------------------------------------- 1 | class RouteException extends Error { 2 | 3 | constructor(message: string) { 4 | super(message); 5 | this.name = 'RouteException'; 6 | } 7 | 8 | } 9 | 10 | export default RouteException; -------------------------------------------------------------------------------- /src/core/domains/express/exceptions/SecurityException.ts: -------------------------------------------------------------------------------- 1 | class SecurityException extends Error { 2 | 3 | constructor(message: string) { 4 | super(message); 5 | this.name = 'SecurityException'; 6 | } 7 | 8 | } 9 | 10 | export default SecurityException; -------------------------------------------------------------------------------- /src/core/domains/formatter/base/BaseFormatter.ts: -------------------------------------------------------------------------------- 1 | import { IFormatter } from "@src/core/domains/formatter/interfaces/IFormatter" 2 | 3 | abstract class BaseFormatter implements IFormatter { 4 | 5 | /** 6 | * The formatter options. 7 | */ 8 | formatterOptions?: Options 9 | 10 | // eslint-disable-next-line no-unused-vars 11 | abstract format(...args: any[]): T; 12 | 13 | /** 14 | * Sets the formatter options. 15 | * @param options The options to set. 16 | * @returns {this} The formatter instance. 17 | */ 18 | setFormatterOptions(options?: Options): this { 19 | this.formatterOptions = options 20 | return this 21 | } 22 | 23 | /** 24 | * Gets the formatter options. 25 | * @returns {Options | undefined} The formatter options. 26 | */ 27 | getFormatterOptions(): Options | undefined { 28 | return this.formatterOptions 29 | } 30 | 31 | } 32 | 33 | export default BaseFormatter -------------------------------------------------------------------------------- /src/core/domains/formatter/interfaces/IFormatter.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | export interface IFormatter { 3 | formatterOptions?: Options; 4 | format(...args: any[]): T; 5 | setFormatterOptions(options?: Options): this; 6 | getFormatterOptions(): Options | undefined; 7 | } -------------------------------------------------------------------------------- /src/core/domains/http/data/UploadedFile.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | 3 | import { TUploadedFile, TUploadedFileData } from "@src/core/domains/http/interfaces/UploadedFile"; 4 | 5 | class UploadedFile implements TUploadedFile { 6 | 7 | data: TUploadedFileData; 8 | 9 | constructor(data: TUploadedFileData) { 10 | this.data = data 11 | } 12 | 13 | getFilename(): string { 14 | return this.data?.filename 15 | } 16 | 17 | getMimeType(): string { 18 | return this.data?.mimetype 19 | } 20 | 21 | getFilepath(): string { 22 | return this.data.file 23 | } 24 | 25 | getField(): string { 26 | return this.data.field 27 | } 28 | 29 | getSizeKb() { 30 | const stats = fs.statSync(this.getFilepath()) 31 | const fileSizeInBytes = stats.size 32 | return fileSizeInBytes / 1024 33 | } 34 | 35 | getData(): T { 36 | return this.data as T 37 | } 38 | 39 | } 40 | 41 | export default UploadedFile -------------------------------------------------------------------------------- /src/core/domains/http/enums/SecurityEnum.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * The default condition for when the security check should be executed. 4 | */ 5 | export const ALWAYS = 'always'; 6 | 7 | /** 8 | * The security rule identifiers. 9 | */ 10 | export const SecurityEnum = { 11 | RESOURCE_OWNER: 'resourceOwner', 12 | HAS_ROLE: 'hasRole', 13 | HAS_SCOPE: 'hasScope', 14 | RATE_LIMITED: 'rateLimited', 15 | ENABLE_SCOPES: 'enableScopes', 16 | CUSTOM: 'custom' 17 | } as const; -------------------------------------------------------------------------------- /src/core/domains/http/handlers/responseError.ts: -------------------------------------------------------------------------------- 1 | import { AppSingleton } from '@src/core/services/App'; 2 | import { Request, Response } from 'express'; 3 | 4 | /** 5 | * Utility function to send an error response to the client. 6 | * 7 | * If the app is not in production mode, the error is logged to the console. 8 | * 9 | * @param req Express Request object 10 | * @param res Express Response object 11 | * @param err The error to log and send 12 | * @param code The HTTP status code to send (default: 500) 13 | */ 14 | export default (req: Request, res: Response, err: Error, code: number = 500) => { 15 | if (AppSingleton.env() === 'production') { 16 | res.status(code).send({ error: 'Something went wrong' }) 17 | return; 18 | } 19 | 20 | AppSingleton.container('logger').error(err, err.stack) 21 | 22 | // Format the stack trace by splitting it into an array of lines 23 | const stackLines = err.stack ? err.stack.split('\n').map(line => line.trim()) : []; 24 | 25 | res.status(code).send({ 26 | error: err.message, 27 | stack: stackLines 28 | }) 29 | } -------------------------------------------------------------------------------- /src/core/domains/http/interfaces/BaseRequest.ts: -------------------------------------------------------------------------------- 1 | import IAuthorizedRequest from "@src/core/domains/http/interfaces/IAuthorizedRequest"; 2 | import { IRequestIdentifiable } from "@src/core/domains/http/interfaces/IRequestIdentifable"; 3 | import { ISecurityRequest } from "@src/core/domains/http/interfaces/ISecurity"; 4 | import { IValidatorRequest } from "@src/core/domains/http/interfaces/IValidatorRequest"; 5 | import { Request } from "express"; 6 | 7 | import { TUploadedFile } from "@src/core/domains/http/interfaces/UploadedFile"; 8 | 9 | /** 10 | * TBaseRequest combines multiple request interfaces to create a comprehensive request type. 11 | * It extends Express's Request and includes: 12 | * - Authorization capabilities (IAuthorizedRequest) 13 | * - Request validation (IValidatorRequest) 14 | * - Security features (ISecurityRequest) 15 | * - Request identification (IRequestIdentifiable) 16 | */ 17 | export type TBaseRequest = Request & IAuthorizedRequest & IValidatorRequest & ISecurityRequest & IRequestIdentifiable & { files: TUploadedFile | TUploadedFile[] | undefined }; -------------------------------------------------------------------------------- /src/core/domains/http/interfaces/ErrorResponse.t.ts: -------------------------------------------------------------------------------- 1 | export type TResponseErrorMessages = { 2 | errors: string[]; 3 | } 4 | -------------------------------------------------------------------------------- /src/core/domains/http/interfaces/IApiResponse.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | 3 | export interface IApiResponse { 4 | build(): TApiResponse; 5 | setData(data: Data): this; 6 | getData(): Data; 7 | getCode(): number; 8 | getTotalCount(): number | undefined; 9 | setAdditionalMeta(additionalMeta: Record): this; 10 | getAdditionalMeta(): Record; 11 | } 12 | 13 | export type TApiResponse = { 14 | meta: Record; 15 | data: Data; 16 | } 17 | 18 | -------------------------------------------------------------------------------- /src/core/domains/http/interfaces/IAuthorizedRequest.ts: -------------------------------------------------------------------------------- 1 | 2 | import { Request } from 'express'; 3 | import { IApiTokenModel } from '@src/core/domains/auth/interfaces/models/IApiTokenModel'; 4 | import { IUserModel } from '@src/core/domains/auth/interfaces/models/IUserModel'; 5 | 6 | export default interface IAuthorizedRequest extends Request { 7 | user?: IUserModel | null; 8 | apiToken?: IApiTokenModel | null; 9 | } -------------------------------------------------------------------------------- /src/core/domains/http/interfaces/IController.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import HttpContext from "@src/core/domains/http/context/HttpContext" 3 | import { TRouteItem } from "@src/core/domains/http/interfaces/IRouter" 4 | 5 | export interface ControllerConstructor { 6 | new (context: HttpContext): IController 7 | executeAction(action: string, context: HttpContext): Promise 8 | } 9 | 10 | export interface IController { 11 | setContext(context: HttpContext): void 12 | getRouteOptions(): TRouteItem | undefined 13 | } -------------------------------------------------------------------------------- /src/core/domains/http/interfaces/IExpressable.ts: -------------------------------------------------------------------------------- 1 | import { TRouteItem } from "@src/core/domains/http/interfaces/IRouter"; 2 | 3 | export interface IExpressable { 4 | // eslint-disable-next-line no-unused-vars 5 | toExpressable(routeItem?: TRouteItem): T 6 | } -------------------------------------------------------------------------------- /src/core/domains/http/interfaces/IHttpConfig.ts: -------------------------------------------------------------------------------- 1 | import { TExpressMiddlewareFnOrClass } from "@src/core/domains/http/interfaces/IMiddleware"; 2 | import express from "express"; 3 | 4 | // eslint-disable-next-line no-unused-vars 5 | export type ExtendExpressFn = (app: express.Application) => void 6 | 7 | export default interface IHttpConfig { 8 | enabled: boolean; 9 | port: number; 10 | globalMiddlewares?: (express.RequestHandler | TExpressMiddlewareFnOrClass)[]; 11 | currentRequestCleanupDelay?: number; 12 | extendExpress?: ExtendExpressFn 13 | csrf?: { 14 | methods?: string[]; 15 | headerName?: string; 16 | ttl?: number; 17 | exclude?: string[]; 18 | } 19 | logging?: { 20 | boundRouteDetails?: boolean; 21 | requests?: boolean; 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /src/core/domains/http/interfaces/IHttpService.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import IExpressConfig from "@src/core/domains/http/interfaces/IHttpConfig"; 3 | import { IRoute, IRouter, TRouteItem } from "@src/core/domains/http/interfaces/IRouter"; 4 | import express from "express"; 5 | 6 | export default interface IHttpService { 7 | init(): void; 8 | bindRoutes(route: IRouter): void; 9 | getExpress(): express.Express; 10 | listen(): Promise; 11 | getConfig(): IExpressConfig | null; 12 | isEnabled(): boolean; 13 | route(): IRoute; 14 | getRegisteredRoutes(): TRouteItem[]; 15 | } -------------------------------------------------------------------------------- /src/core/domains/http/interfaces/IRequestContext.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { TBaseRequest } from "@src/core/domains/http/interfaces/BaseRequest"; 3 | 4 | export interface IRequestContextData extends Map> {} 5 | 6 | export type IPDatesArrayTTL = { value: T; ttlSeconds: number | null; createdAt: Date }; 7 | 8 | export type IPContextData = Map>>; 9 | 10 | export interface IRequestContext { 11 | setByRequest(req: TBaseRequest, key: string, value: T): this; 12 | getByRequest(req: TBaseRequest, key?: string): T | undefined; 13 | setByIpAddress(req: TBaseRequest, key: string, value: T, ttlSeconds?: number): this; 14 | getByIpAddress(req: TBaseRequest, key?: string): T | undefined; 15 | endRequestContext(req: TBaseRequest): void; 16 | getRequestContext(): IRequestContextData; 17 | setRequestContext(context: IRequestContextData): this; 18 | getIpContext(): IPContextData; 19 | setIpContext(context: IPContextData): this; 20 | } -------------------------------------------------------------------------------- /src/core/domains/http/interfaces/IRequestContextCleanUpConfig.ts: -------------------------------------------------------------------------------- 1 | export interface IRequestContextCleanUpConfig { 2 | delayInSeconds: number 3 | } -------------------------------------------------------------------------------- /src/core/domains/http/interfaces/IRequestIdentifable.ts: -------------------------------------------------------------------------------- 1 | import { Request } from "express"; 2 | 3 | export interface IRequestIdentifiable extends Request { 4 | id: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/core/domains/http/interfaces/IResourceService.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export interface IPageOptions { 4 | page: number; 5 | pageSize?: number; 6 | skip?: number; 7 | } -------------------------------------------------------------------------------- /src/core/domains/http/interfaces/ISecurity.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import HttpContext from '@src/core/domains/http/context/HttpContext'; 3 | import { Request } from 'express'; 4 | 5 | export type TSecurityRuleOptions = { 6 | id: string; 7 | when: string[] | null; 8 | never: string[] | null; 9 | ruleOptions?: RuleOptions; 10 | } 11 | 12 | export type TSecurityRuleConstructor = { 13 | new (...args: any[]): Rule 14 | } 15 | 16 | export interface ISecurityRule { 17 | setRuleOptions(options: RuleOptions): ISecurityRule; 18 | getRuleOptions(): RuleOptions 19 | getId(): string 20 | getWhen(): string[] | null 21 | getNever(): string[] | null 22 | execute(context: HttpContext, ...args: any[]): Promise 23 | } 24 | 25 | export interface ISecurityRequest extends Request { 26 | security?: ISecurityRule[] 27 | } -------------------------------------------------------------------------------- /src/core/domains/http/interfaces/IValidatorRequest.ts: -------------------------------------------------------------------------------- 1 | import { CustomValidatorConstructor } from "@src/core/domains/validator/interfaces/IValidator"; 2 | 3 | export interface IValidatorRequest { 4 | validator?: CustomValidatorConstructor 5 | } -------------------------------------------------------------------------------- /src/core/domains/http/interfaces/Pagination.t.ts: -------------------------------------------------------------------------------- 1 | export type TPagination = { 2 | total?: number; 3 | page?: number; 4 | pageSize?: number; 5 | nextPage?: number; 6 | previousPage?: number; 7 | } -------------------------------------------------------------------------------- /src/core/domains/http/interfaces/UploadedFile.ts: -------------------------------------------------------------------------------- 1 | export type TUploadedFile = { 2 | getFilename(): string; 3 | getMimeType(): string; 4 | getFilepath(): string; 5 | getField(): string; 6 | getSizeKb(): number; 7 | getData(): Data 8 | } 9 | 10 | /** 11 | * Based on expected types using yahoo/express-busboy 12 | * 13 | * @ref https://www.npmjs.com/package/express-busboy 14 | */ 15 | export type TUploadedFileData = { 16 | done: boolean; 17 | encoding: string; 18 | field: string; 19 | file: string; 20 | filename: string; 21 | mimetype: string; 22 | truncated: boolean; 23 | uuid: string; 24 | } -------------------------------------------------------------------------------- /src/core/domains/http/middleware/EndRequestContextMiddleware.ts: -------------------------------------------------------------------------------- 1 | import Middleware from "@src/core/domains/http/base/Middleware"; 2 | import HttpContext from "@src/core/domains/http/context/HttpContext"; 3 | import { app } from "@src/core/services/App"; 4 | 5 | /** 6 | * Middleware that ends the current request context and removes all associated values. 7 | */ 8 | class EndRequestContextMiddleware extends Middleware { 9 | 10 | /** 11 | * Executes the end request context middleware 12 | * 13 | * @param context - The HTTP context containing request and response objects 14 | */ 15 | async execute(context: HttpContext): Promise { 16 | context.getResponse().once('finish', () => { 17 | app('requestContext').endRequestContext(context.getRequest()) 18 | }) 19 | 20 | this.next() 21 | } 22 | 23 | } 24 | 25 | export default EndRequestContextMiddleware -------------------------------------------------------------------------------- /src/core/domains/http/middleware/ValidationMiddleware.ts: -------------------------------------------------------------------------------- 1 | import Middleware from '@src/core/domains/http/base/Middleware'; 2 | import HttpContext from '@src/core/domains/http/context/HttpContext'; 3 | import responseError from '@src/core/domains/http/handlers/responseError'; 4 | 5 | class ValidationMiddleware extends Middleware<{ scopes: string[] }> { 6 | 7 | async execute(context: HttpContext): Promise { 8 | try { 9 | 10 | 11 | } 12 | catch (error) { 13 | 14 | if(error instanceof Error) { 15 | responseError(context.getRequest(), context.getResponse(), error) 16 | return; 17 | } 18 | } 19 | } 20 | 21 | } 22 | 23 | export default ValidationMiddleware; -------------------------------------------------------------------------------- /src/core/domains/http/middleware/requestContextLoggerMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { EnvironmentDevelopment } from "@src/core/consts/Environment"; 2 | import Middleware from "@src/core/domains/http/base/Middleware"; 3 | import HttpContext from "@src/core/domains/http/context/HttpContext"; 4 | import { AppSingleton } from "@src/core/services/App"; 5 | 6 | /** 7 | * Middleware to log the request context 8 | */ 9 | class RequestContextLoggerMiddleware extends Middleware { 10 | 11 | async execute(context: HttpContext): Promise { 12 | if (AppSingleton.env() !== EnvironmentDevelopment) { 13 | this.next() 14 | return; 15 | } 16 | 17 | context.getResponse().once('finish', () => { 18 | AppSingleton.container('logger').info('requestContext: ', AppSingleton.container('requestContext').getRequestContext()) 19 | AppSingleton.container('logger').info('ipContext: ', AppSingleton.container('requestContext').getIpContext()) 20 | }) 21 | 22 | this.next() 23 | } 24 | 25 | } 26 | 27 | 28 | export default RequestContextLoggerMiddleware -------------------------------------------------------------------------------- /src/core/domains/http/providers/BaseRoutesProvider.ts: -------------------------------------------------------------------------------- 1 | import BaseProvider from "@src/core/base/Provider"; 2 | 3 | class BaseRoutesProvider extends BaseProvider { 4 | 5 | async register(): Promise { 6 | this.bind('routes.provided', true); 7 | } 8 | 9 | } 10 | 11 | 12 | export default BaseRoutesProvider; -------------------------------------------------------------------------------- /src/core/domains/http/routes/healthRoutes.ts: -------------------------------------------------------------------------------- 1 | import health from '@src/core/actions/health'; 2 | import Route from '@src/core/domains/http/router/Route'; 3 | 4 | /** 5 | * Health routes 6 | */ 7 | export default Route.group(router => { 8 | router.get('/health', health) 9 | }) 10 | -------------------------------------------------------------------------------- /src/core/domains/http/utils/getIpAddress.ts: -------------------------------------------------------------------------------- 1 | import { Request } from "express"; 2 | 3 | /** 4 | * Get the IP address of the request 5 | * @param req The request object 6 | * @returns The IP address of the request 7 | */ 8 | const getIpAddress = (req: Request): string => { 9 | return req.socket.remoteAddress as string 10 | } 11 | 12 | 13 | export default getIpAddress -------------------------------------------------------------------------------- /src/core/domains/logger/interfaces/ILoggerService.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import winston from "winston"; 3 | 4 | export interface ILoggerService 5 | { 6 | getLogger(): winston.Logger; 7 | 8 | info(...args: any[]): void; 9 | warn(...args: any[]): void; 10 | error(...args: any[]): void; 11 | debug(...args: any[]): void; 12 | verbose(...args: any[]): void; 13 | console(...args: any[]): void; 14 | exception(err: Error): void; 15 | } -------------------------------------------------------------------------------- /src/core/domains/logger/providers/LoggerProvider.ts: -------------------------------------------------------------------------------- 1 | import BaseProvider from "@src/core/base/Provider"; 2 | import LoggerService from "@src/core/domains/logger/services/LoggerService"; 3 | 4 | class LoggerProvider extends BaseProvider { 5 | 6 | async register(): Promise { 7 | 8 | const loggerService = new LoggerService(); 9 | 10 | // We will boot the logger here to provide it early for other providers 11 | loggerService.boot(); 12 | 13 | // Bind the logger service to the container 14 | this.bind('logger', loggerService); 15 | 16 | } 17 | 18 | } 19 | 20 | export default LoggerProvider -------------------------------------------------------------------------------- /src/core/domains/mail/adapters/LocalMailDriver.ts: -------------------------------------------------------------------------------- 1 | import { app } from "@src/core/services/App"; 2 | 3 | import Mail from "@src/core/domains/mail/data/Mail"; 4 | import { MailAdapter } from "@src/core/domains/mail/interfaces/adapter"; 5 | 6 | class LocalMailDriver implements MailAdapter { 7 | 8 | // eslint-disable-next-line no-unused-vars 9 | constructor(options: object = {}) { } 10 | 11 | getOptions(): T { 12 | return {} as T 13 | } 14 | 15 | async send(mail: Mail): Promise { 16 | app('logger').info('Email', JSON.stringify({ 17 | to: mail.getTo(), 18 | from: mail.getFrom(), 19 | subject: mail.getSubject(), 20 | body: mail.getBody(), 21 | attachments: mail.getAttachments() 22 | })) 23 | } 24 | 25 | } 26 | 27 | export default LocalMailDriver -------------------------------------------------------------------------------- /src/core/domains/mail/interfaces/adapter.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { BaseAdapterTypes } from "@src/core/base/BaseAdapter"; 3 | 4 | import { IMail } from "@src/core/domains/mail/interfaces/data"; 5 | 6 | export type BaseMailAdapters = BaseAdapterTypes & { 7 | local: MailAdapter 8 | } 9 | 10 | export type MailAdapterConstructor = new (options: any) => MailAdapter 11 | 12 | export interface MailAdapter { 13 | send(mail: IMail): Promise 14 | getOptions(): T 15 | } -------------------------------------------------------------------------------- /src/core/domains/mail/interfaces/config.ts: -------------------------------------------------------------------------------- 1 | import { MailAdapterConstructor } from "@src/core/domains/mail/interfaces/adapter"; 2 | 3 | export type MailAdapterConfig = MailAdapterConfigItem[] 4 | 5 | export interface MailAdapterConfigItem { 6 | name: string; 7 | driver: MailAdapterConstructor; 8 | options: Record 9 | } 10 | 11 | export interface IMailConfig { 12 | default: string; 13 | drivers: MailAdapterConfigItem[] 14 | } -------------------------------------------------------------------------------- /src/core/domains/mail/interfaces/data.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | export interface IMailOptions { 3 | to: string | string[]; 4 | from: string; 5 | subject: string; 6 | body: string; 7 | attachments?: any[]; 8 | options: Record 9 | } 10 | 11 | export interface IMail { 12 | getConfig(): T 13 | getOptions>(): Options; 14 | getTo(): string | string[]; 15 | setTo(to: string | string[]): void; 16 | getFrom(): string; 17 | setFrom(from: string): void; 18 | getSubject(): string; 19 | setSubject(subject: string): void; 20 | getBody(): string; 21 | setBody(body: string): void; 22 | getAttachments(): T[] | undefined; 23 | setAttachments(attachments: any[] | undefined): void; 24 | } -------------------------------------------------------------------------------- /src/core/domains/mail/interfaces/services.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { MailAdapters } from "@src/config/mail.config"; 3 | 4 | import { MailAdapter } from "@src/core/domains/mail/interfaces/adapter"; 5 | import { IMail } from "@src/core/domains/mail/interfaces/data"; 6 | 7 | export interface IMailService { 8 | boot(): void; 9 | send(mail: IMail): Promise; 10 | getDefaultDriver(): MailAdapter; 11 | getDriver(name: keyof MailAdapters): T; 12 | local(): MailAdapter; 13 | nodeMailer(): MailAdapter; 14 | } -------------------------------------------------------------------------------- /src/core/domains/mail/providers/MailProvider.ts: -------------------------------------------------------------------------------- 1 | import { mailConfig } from "@src/config/mail.config"; 2 | import BaseProvider from "@src/core/base/Provider"; 3 | import { app } from "@src/core/services/App"; 4 | 5 | import { IMailConfig } from "@src/core/domains/mail/interfaces/config"; 6 | import MailService from "@src/core/domains/mail/services/MailService"; 7 | 8 | class MailProvider extends BaseProvider { 9 | 10 | config: IMailConfig = mailConfig 11 | 12 | async register(): Promise { 13 | this.bind('mail', new MailService(this.config)) 14 | } 15 | 16 | async boot(): Promise { 17 | app('mail').boot() 18 | } 19 | 20 | } 21 | 22 | export default MailProvider -------------------------------------------------------------------------------- /src/core/domains/mail/services/MailConfig.ts: -------------------------------------------------------------------------------- 1 | import { IMailConfig, MailAdapterConfigItem } from "@src/core/domains/mail/interfaces/config" 2 | 3 | class MailConfig { 4 | 5 | public static drivers(drivers: IMailConfig['drivers']): IMailConfig['drivers'] { 6 | return drivers 7 | } 8 | 9 | public static define({ name, driver, options = {} }: MailAdapterConfigItem): MailAdapterConfigItem { 10 | return { name, driver, options } 11 | } 12 | 13 | } 14 | 15 | export default MailConfig -------------------------------------------------------------------------------- /src/core/domains/make/commands/MakeCmdCommand.ts: -------------------------------------------------------------------------------- 1 | import BaseMakeFileCommand from "@src/core/domains/make/base/BaseMakeFileCommand"; 2 | 3 | export default class MakeCmdCommand extends BaseMakeFileCommand { 4 | 5 | constructor() { 6 | super({ 7 | signature: 'make:command', 8 | description: 'Create a new command', 9 | makeType: 'Command', 10 | args: ['name'], 11 | endsWith: 'Command' 12 | }) 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /src/core/domains/make/commands/MakeControllerCommand.ts: -------------------------------------------------------------------------------- 1 | import BaseMakeFileCommand from "@src/core/domains/make/base/BaseMakeFileCommand"; 2 | 3 | export default class MakeController extends BaseMakeFileCommand { 4 | 5 | constructor() { 6 | super({ 7 | signature: 'make:controller', 8 | description: 'Create a new controller', 9 | makeType: 'Controller', 10 | args: ['name'], 11 | endsWith: 'Controller', 12 | startWithLowercase: false 13 | }) 14 | 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/core/domains/make/commands/MakeEventCommand.ts: -------------------------------------------------------------------------------- 1 | import BaseMakeFileCommand from "@src/core/domains/make/base/BaseMakeFileCommand"; 2 | 3 | export default class MakeEventCommand extends BaseMakeFileCommand { 4 | 5 | constructor() { 6 | super({ 7 | signature: 'make:event', 8 | description: 'Create a new subscriber event', 9 | makeType: 'Subscriber', 10 | args: ['name'], 11 | endsWith: 'SubscriberEvent' 12 | }) 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /src/core/domains/make/commands/MakeFactoryCommand.ts: -------------------------------------------------------------------------------- 1 | import BaseMakeFileCommand from "@src/core/domains/make/base/BaseMakeFileCommand"; 2 | 3 | export default class MakeValidatorCommand extends BaseMakeFileCommand { 4 | 5 | constructor() { 6 | super({ 7 | signature: 'make:factory', 8 | description: 'Create a factory', 9 | makeType: 'Factory', 10 | args: ['name'], 11 | endsWith: 'Factory', 12 | startWithLowercase: false 13 | }) 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /src/core/domains/make/commands/MakeListenerCommand.ts: -------------------------------------------------------------------------------- 1 | import BaseMakeFileCommand from "@src/core/domains/make/base/BaseMakeFileCommand"; 2 | 3 | export default class MakeListenerCommand extends BaseMakeFileCommand { 4 | 5 | constructor() { 6 | super({ 7 | signature: 'make:listener', 8 | description: 'Create a new listener event', 9 | makeType: 'Listener', 10 | args: ['name'], 11 | endsWith: 'ListenerEvent' 12 | }) 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /src/core/domains/make/commands/MakeMiddlewareCommand.ts: -------------------------------------------------------------------------------- 1 | import BaseMakeFileCommand from "@src/core/domains/make/base/BaseMakeFileCommand"; 2 | 3 | export default class MakeMiddlewareCommand extends BaseMakeFileCommand { 4 | 5 | constructor() { 6 | super({ 7 | signature: 'make:middleware', 8 | description: 'Create a new middleware', 9 | makeType: 'Middleware', 10 | args: ['name'], 11 | endsWith: 'Middleware', 12 | startWithLowercase: false 13 | }) 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /src/core/domains/make/commands/MakeMigrationCommand.ts: -------------------------------------------------------------------------------- 1 | import BaseMakeFileCommand from "@src/core/domains/make/base/BaseMakeFileCommand"; 2 | import MigrationFileService from "@src/core/domains/migrations/services/MigrationFilesService"; 3 | 4 | export default class MakeMigrationCommand extends BaseMakeFileCommand { 5 | 6 | constructor() { 7 | super({ 8 | signature: 'make:migration', 9 | description: 'Create a new migration', 10 | makeType: 'Migration', 11 | args: ['name'], 12 | customFilename: (name: string) => { 13 | return (new MigrationFileService).createDateFilename(name) 14 | } 15 | }) 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /src/core/domains/make/commands/MakeModelCommand.ts: -------------------------------------------------------------------------------- 1 | import BaseMakeFileCommand from "@src/core/domains/make/base/BaseMakeFileCommand"; 2 | 3 | export default class MakeModelCommand extends BaseMakeFileCommand { 4 | 5 | constructor() { 6 | super({ 7 | signature: 'make:model', 8 | description: 'Create a new model', 9 | makeType: 'Model', 10 | endsWith: 'Model', 11 | args: ['name', 'collection'], 12 | argsOptional: ['collection'], 13 | }) 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /src/core/domains/make/commands/MakeObserverCommand.ts: -------------------------------------------------------------------------------- 1 | import BaseMakeFileCommand from "@src/core/domains/make/base/BaseMakeFileCommand"; 2 | 3 | export default class MakeObserverCommand extends BaseMakeFileCommand { 4 | 5 | constructor() { 6 | super({ 7 | signature: 'make:observer', 8 | description: 'Create a new observer', 9 | makeType: 'Observer', 10 | args: ['name'], 11 | endsWith: 'Observer' 12 | }) 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /src/core/domains/make/commands/MakeProviderCommand.ts: -------------------------------------------------------------------------------- 1 | import BaseMakeFileCommand from "@src/core/domains/make/base/BaseMakeFileCommand"; 2 | 3 | export default class MakeProviderCommand extends BaseMakeFileCommand { 4 | 5 | constructor() { 6 | super({ 7 | signature: 'make:provider', 8 | description: 'Create a new provider', 9 | makeType: 'Provider', 10 | args: ['name'], 11 | endsWith: 'Provider' 12 | }) 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /src/core/domains/make/commands/MakeRepositoryCommand.ts: -------------------------------------------------------------------------------- 1 | import BaseMakeFileCommand from "@src/core/domains/make/base/BaseMakeFileCommand"; 2 | 3 | export default class MakeRepositoryCommand extends BaseMakeFileCommand { 4 | 5 | constructor() { 6 | super({ 7 | signature: 'make:repository', 8 | description: 'Create a new repository', 9 | makeType: 'Repository', 10 | args: ['name', 'collection'], 11 | endsWith: 'Repository' 12 | }) 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /src/core/domains/make/commands/MakeRouteResourceCommand.ts: -------------------------------------------------------------------------------- 1 | import BaseMakeFileCommand from "@src/core/domains/make/base/BaseMakeFileCommand"; 2 | 3 | export default class MakeRouteResourceCommand extends BaseMakeFileCommand { 4 | 5 | constructor() { 6 | super({ 7 | signature: 'make:route-resource', 8 | description: 'Create a new route resource', 9 | makeType: 'RouteResource', 10 | args: ['name'], 11 | endsWith: 'RouteResource', 12 | startWithLowercase: true 13 | }) 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /src/core/domains/make/commands/MakeRoutesCommand.ts: -------------------------------------------------------------------------------- 1 | import BaseMakeFileCommand from "@src/core/domains/make/base/BaseMakeFileCommand"; 2 | 3 | export default class MakeRoutesCommand extends BaseMakeFileCommand { 4 | 5 | constructor() { 6 | super({ 7 | signature: 'make:routes', 8 | description: 'Create a new routing file', 9 | makeType: 'Routes', 10 | args: ['name'], 11 | endsWith: 'Routes', 12 | startWithLowercase: true 13 | }) 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /src/core/domains/make/commands/MakeSeederCommand.ts: -------------------------------------------------------------------------------- 1 | import BaseMakeFileCommand from "@src/core/domains/make/base/BaseMakeFileCommand"; 2 | import MigrationFileService from "@src/core/domains/migrations/services/MigrationFilesService"; 3 | 4 | export default class MakeSeederCommand extends BaseMakeFileCommand { 5 | 6 | constructor() { 7 | super({ 8 | signature: 'make:seeder', 9 | description: 'Creates a new database seeder', 10 | makeType: 'Seeder', 11 | args: ['name'], 12 | endsWith: 'Seeder', 13 | customFilename: (name: string) => { 14 | return (new MigrationFileService).createDateFilename(name) 15 | } 16 | }) 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/core/domains/make/commands/MakeServiceCommand.ts: -------------------------------------------------------------------------------- 1 | import BaseMakeFileCommand from "@src/core/domains/make/base/BaseMakeFileCommand"; 2 | 3 | export default class MakeServiceCommand extends BaseMakeFileCommand { 4 | 5 | constructor() { 6 | super({ 7 | signature: 'make:service', 8 | description: 'Create a new service', 9 | makeType: 'Service', 10 | args: ['name'], 11 | endsWith: 'Service' 12 | }) 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /src/core/domains/make/commands/MakeSingletonCommand.ts: -------------------------------------------------------------------------------- 1 | import BaseMakeFileCommand from "@src/core/domains/make/base/BaseMakeFileCommand"; 2 | 3 | export default class MakeSingletonCommand extends BaseMakeFileCommand { 4 | 5 | constructor() { 6 | super({ 7 | signature: 'make:singleton', 8 | description: 'Create a new singleton service', 9 | makeType: 'Singleton', 10 | args: ['name'], 11 | endsWith: 'Singleton' 12 | }) 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /src/core/domains/make/commands/MakeSubscriberCommand.ts: -------------------------------------------------------------------------------- 1 | import BaseMakeFileCommand from "@src/core/domains/make/base/BaseMakeFileCommand"; 2 | 3 | export default class MakeSubscriberCommand extends BaseMakeFileCommand { 4 | 5 | constructor() { 6 | super({ 7 | signature: 'make:subscriber', 8 | description: 'Create a new subscriber event', 9 | makeType: 'Subscriber', 10 | args: ['name'], 11 | endsWith: 'SubscriberEvent' 12 | }) 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /src/core/domains/make/commands/MakeValidatorCommand.ts: -------------------------------------------------------------------------------- 1 | import BaseMakeFileCommand from "@src/core/domains/make/base/BaseMakeFileCommand"; 2 | 3 | export default class MakeValidatorCommand extends BaseMakeFileCommand { 4 | 5 | constructor() { 6 | super({ 7 | signature: 'make:validator', 8 | description: 'Create a validator', 9 | makeType: 'Validator', 10 | args: ['name'], 11 | endsWith: 'Validator', 12 | startWithLowercase: false 13 | }) 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /src/core/domains/make/interfaces/IMakeFileArguments.ts: -------------------------------------------------------------------------------- 1 | export interface IMakeFileArguments { 2 | name: string; 3 | collection?: string; 4 | } -------------------------------------------------------------------------------- /src/core/domains/make/interfaces/IMakeOptions.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | export interface IMakeOptions { 3 | signature: string; 4 | description: string; 5 | makeType: string; 6 | args: string[]; 7 | argsOptional?: string[]; 8 | defaultArgs?: string[]; 9 | endsWith?: string; 10 | startWithLowercase?: boolean; 11 | customFilename?: (name: string) => string; 12 | } -------------------------------------------------------------------------------- /src/core/domains/make/templates/Command.ts.template: -------------------------------------------------------------------------------- 1 | import BaseCommand from "@src/core/domains/console/base/BaseCommand"; 2 | 3 | class #name# extends BaseCommand { 4 | 5 | signature: string = 'app:#name#'; 6 | 7 | description = ''; 8 | 9 | keepProcessAlive = false; 10 | 11 | /** 12 | * Execute the command 13 | * 14 | * @returns {Promise} 15 | */ 16 | async execute() { 17 | 18 | /** 19 | * Your command logic 20 | */ 21 | } 22 | 23 | } 24 | 25 | export default #name# -------------------------------------------------------------------------------- /src/core/domains/make/templates/Controller.ts.template: -------------------------------------------------------------------------------- 1 | import Controller from "@src/core/domains/http/base/Controller"; 2 | import HttpContext from "@src/core/domains/http/context/HttpContext"; 3 | 4 | class #name# extends Controller { 5 | 6 | async invoke(context: HttpContext) { 7 | this.jsonResponse({}, 200) 8 | } 9 | 10 | } 11 | 12 | export default #name#; 13 | -------------------------------------------------------------------------------- /src/core/domains/make/templates/Factory.ts.template: -------------------------------------------------------------------------------- 1 | import Factory from "@src/core/base/Factory"; 2 | import { IModelAttributes } from "@src/core/domains/models/interfaces/IModel"; 3 | 4 | class #name# extends Factory { 5 | 6 | protected model = Model; 7 | 8 | getDefinition(): IModelAttributes | null { 9 | return { 10 | name: this.faker.word.words(2) 11 | } 12 | } 13 | 14 | } 15 | 16 | export default #name#; 17 | -------------------------------------------------------------------------------- /src/core/domains/make/templates/Listener.ts.template: -------------------------------------------------------------------------------- 1 | import BaseEventListener from "@src/core/domains/events/base/BaseEventListener"; 2 | import EventRegistry from "@src/core/domains/events/registry/EventRegistry"; 3 | 4 | type #name#Payload = { 5 | // ... 6 | } 7 | 8 | class #name# extends BaseEventListener<#name#Payload> { 9 | 10 | /** 11 | * Optional method to execute before the subscribers are dispatched. 12 | */ 13 | async execute(): Promise { 14 | 15 | // eslint-disable-next-line no-unused-vars 16 | const payload = this.getPayload(); 17 | 18 | // Handle logic 19 | } 20 | 21 | } 22 | 23 | export default EventRegistry.registerListener(#name#) -------------------------------------------------------------------------------- /src/core/domains/make/templates/Middleware.ts.template: -------------------------------------------------------------------------------- 1 | import Middleware from "@src/core/domains/http/base/Middleware"; 2 | import HttpContext from "@src/core/domains/http/context/HttpContext"; 3 | 4 | export type T#name#Config = { 5 | // ... 6 | } 7 | 8 | class #name# extends Middleware { 9 | 10 | /** 11 | * Executes the validator middleware 12 | * @param context - The HTTP context 13 | */ 14 | public async execute(context: HttpContext): Promise { 15 | 16 | const request = context.getRequest() 17 | const response = context.getResponse() 18 | const config = this.getConfig() 19 | 20 | // Logic 21 | 22 | this.next(); 23 | } 24 | 25 | } 26 | 27 | export default #name#; 28 | -------------------------------------------------------------------------------- /src/core/domains/make/templates/Repository.ts.template: -------------------------------------------------------------------------------- 1 | import Model from "@src/app/models/Model"; 2 | import Repository from "@src/core/base/Repository"; 3 | 4 | export default class #name# extends Repository { 5 | 6 | constructor() { 7 | super(Model) 8 | } 9 | 10 | } -------------------------------------------------------------------------------- /src/core/domains/make/templates/RouteResource.ts.template: -------------------------------------------------------------------------------- 1 | import Route from "@src/core/domains/http/router/Route"; 2 | 3 | export default Route.group({ 4 | prefix: '/', 5 | middlewares: [], 6 | }, router => { 7 | 8 | router.resource({ 9 | prefix: '/', 10 | resource: PlaceholderModel, 11 | middlewares: [], 12 | paginate: { 13 | pageSize: 10, 14 | allowPageSizeOverride: true, 15 | }, 16 | sorting: { 17 | fieldKey: 'sort', 18 | directionKey: 'direction', 19 | defaultField: 'createdAt', 20 | defaultDirection: 'asc' 21 | } 22 | }) 23 | 24 | }) 25 | -------------------------------------------------------------------------------- /src/core/domains/make/templates/Routes.ts.template: -------------------------------------------------------------------------------- 1 | import Route from "@src/core/domains/express/routing/Route"; 2 | import RouteGroup from "@src/core/domains/express/routing/RouteGroup"; 3 | import { Request, Response } from 'express'; 4 | 5 | /** 6 | * Note:Remember to bind your routes to express in the AppProvider (@src/app/providers/AppProvider) 7 | * 8 | * Example: 9 | * App.container('express').bindRoutes(#name#); 10 | */ 11 | const #name# = RouteGroup([ 12 | Route({ 13 | name: 'index', 14 | method: 'get', 15 | path: '/', 16 | action: (req: Request, res: Response) => { 17 | res.send({ 18 | success: true 19 | }) 20 | } 21 | }), 22 | ]) 23 | 24 | export default #name#; -------------------------------------------------------------------------------- /src/core/domains/make/templates/Seeder.ts.template: -------------------------------------------------------------------------------- 1 | import { faker } from "@faker-js/faker"; 2 | import User from "@src/app/models/auth/User"; 3 | import { GROUPS, ROLES } from "@src/config/acl.config"; 4 | import hashPassword from "@src/core/domains/auth/utils/hashPassword"; 5 | import BaseSeeder from "@src/core/domains/migrations/base/BaseSeeder"; 6 | 7 | export class #name# extends BaseSeeder { 8 | 9 | async up(): Promise < void> { 10 | 11 | // Change the example to suit your needs 12 | await User.query().insert([ 13 | { 14 | email: faker.internet.email(), 15 | password: faker.internet.password(), 16 | hashedPassword: hashPassword(faker.internet.password()), 17 | groups: [GROUPS.USER], 18 | roles: [ROLES.USER], 19 | firstName: faker.person.firstName(), 20 | lastName: faker.person.lastName(), 21 | } 22 | ]) 23 | 24 | } 25 | 26 | } 27 | 28 | export default #name# -------------------------------------------------------------------------------- /src/core/domains/make/templates/Service.ts.template: -------------------------------------------------------------------------------- 1 | import Service from "@src/core/base/Service"; 2 | 3 | export interface I#name#Data { 4 | 5 | } 6 | 7 | export class #name# extends Service { 8 | 9 | constructor(config: I#name#Data) { 10 | super(config) 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /src/core/domains/make/templates/Singleton.ts.template: -------------------------------------------------------------------------------- 1 | import Singleton from "@src/core/base/Singleton"; 2 | 3 | export interface I#name#Data { 4 | 5 | } 6 | 7 | export class #name# extends Singleton { 8 | 9 | constructor(config: I#name#Data) { 10 | super(config) 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /src/core/domains/make/templates/Subscriber.ts.template: -------------------------------------------------------------------------------- 1 | import BaseEventSubscriber from "@src/core/domains/events/base/BaseEventSubciber"; 2 | import SyncDriver from "@src/core/domains/events/drivers/SyncDriver"; 3 | import EventRegistry from "@src/core/domains/events/registry/EventRegistry"; 4 | 5 | type #name#Payload = { 6 | // ... 7 | } 8 | 9 | class #name# extends BaseEventSubscriber<#name#Payload> { 10 | 11 | protected namespace: string = 'default'; 12 | 13 | constructor(payload) { 14 | super(payload, SyncDriver); 15 | } 16 | 17 | getQueueName(): string { 18 | return 'default'; 19 | } 20 | 21 | async execute(): Promise { 22 | const payload = this.getPayload(); 23 | 24 | // Handle logic 25 | } 26 | 27 | } 28 | 29 | export default EventRegistry.registerSubscriber(#name#) -------------------------------------------------------------------------------- /src/core/domains/make/templates/Validator.ts.template: -------------------------------------------------------------------------------- 1 | import BaseCustomValidator from "@src/core/domains/validator/base/BaseCustomValidator"; 2 | import { IRulesObject } from "@src/core/domains/validator/interfaces/IRule"; 3 | import RequiredRule from "@src/core/domains/validator/rules/RequiredRule"; 4 | 5 | class #name# extends BaseCustomValidator { 6 | 7 | protected rules: IRulesObject = { 8 | id: [new RequiredRule()], 9 | } 10 | 11 | protected messages: Record = { 12 | 'id.required': 'The id is required.', 13 | } 14 | 15 | } 16 | 17 | export default #name# -------------------------------------------------------------------------------- /src/core/domains/migrations/base/BaseSeeder.ts: -------------------------------------------------------------------------------- 1 | import BaseMigration from "@src/core/domains/migrations/base/BaseMigration"; 2 | import { MigrationType } from "@src/core/domains/migrations/interfaces/IMigration"; 3 | 4 | /** 5 | * BaseSeeder class serves as the foundation for all database seeders. 6 | */ 7 | abstract class BaseSeeder extends BaseMigration { 8 | 9 | /** 10 | * The type of migration 11 | */ 12 | migrationType = 'seeder' as MigrationType; 13 | 14 | /** 15 | * Optional down method. 16 | * 17 | * @return {Promise} 18 | */ 19 | down(): Promise { 20 | return Promise.resolve(); 21 | } 22 | 23 | } 24 | 25 | export default BaseSeeder; -------------------------------------------------------------------------------- /src/core/domains/migrations/commands/MigrateDownCommand.ts: -------------------------------------------------------------------------------- 1 | 2 | import BaseMigrationCommand from "@src/core/domains/migrations/base/BaseMigrationCommand"; 3 | 4 | class MigrateDownCommand extends BaseMigrationCommand { 5 | 6 | /** 7 | * Signature for the command. 8 | */ 9 | public signature: string = 'migrate:down'; 10 | 11 | description = 'Rollback migrations'; 12 | 13 | /** 14 | * Execute the command. 15 | */ 16 | async execute() { 17 | // Read the arguments 18 | const batch = this.getArguementByKey('batch')?.value; 19 | 20 | // Run the migrations 21 | const schemaMigrationService = this.getSchemaMigrationService(); 22 | await schemaMigrationService.boot(); 23 | await schemaMigrationService.down({ batch: batch ? parseInt(batch) : undefined }); 24 | 25 | this.input.writeLine('Migrations down successfully'); 26 | } 27 | 28 | } 29 | 30 | export default MigrateDownCommand 31 | -------------------------------------------------------------------------------- /src/core/domains/migrations/commands/SeedUpCommand.ts: -------------------------------------------------------------------------------- 1 | 2 | import BaseMigrationCommand from "@src/core/domains/migrations/base/BaseMigrationCommand"; 3 | 4 | class SeedUpCommand extends BaseMigrationCommand { 5 | 6 | /** 7 | * The signature of the command 8 | */ 9 | public signature: string = 'db:seed'; 10 | 11 | description = 'Run all seeders. Usage: db:seed --file=filename --group=group'; 12 | 13 | 14 | /** 15 | * Execute the command 16 | */ 17 | async execute() { 18 | // Read the arguments 19 | const file = this.getArguementByKey('file')?.value; 20 | const group = this.getArguementByKey('group')?.value; 21 | 22 | // Run the migrations 23 | const schemaMigrationService = this.getSeederMigrationService(); 24 | await schemaMigrationService.boot(); 25 | await schemaMigrationService.up({ filterByFileName: file, group: group }); 26 | 27 | this.input.writeLine('Seeds up successfully'); 28 | } 29 | 30 | } 31 | 32 | export default SeedUpCommand 33 | -------------------------------------------------------------------------------- /src/core/domains/migrations/enums/MigrationTypeEnum.ts: -------------------------------------------------------------------------------- 1 | const MigrationTypeEnum = { 2 | seeder: 'seeder', 3 | schema: 'schema' 4 | } as const; 5 | 6 | export default MigrationTypeEnum -------------------------------------------------------------------------------- /src/core/domains/migrations/exceptions/MigrationError.ts: -------------------------------------------------------------------------------- 1 | export default class MigrationError extends Error { 2 | 3 | constructor(message: string = 'Migration error') { 4 | super(message); 5 | this.name = 'MigrationError'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/migrations/factory/MigrationFactory.ts: -------------------------------------------------------------------------------- 1 | import { MigrationType } from "@src/core/domains/migrations/interfaces/IMigration"; 2 | import MigrationModel from "@src/core/domains/migrations/models/MigrationModel"; 3 | import { IModel, ModelConstructor } from "@src/core/domains/models/interfaces/IModel"; 4 | 5 | 6 | type Props = { 7 | name: string; 8 | batch: number; 9 | checksum: string; 10 | type: MigrationType; 11 | appliedAt: Date; 12 | } 13 | 14 | class MigrationFactory { 15 | 16 | /** 17 | * Create a migration model 18 | * @param param 19 | * @returns 20 | */ 21 | create({ name, batch, checksum, type, appliedAt }: Props, modelCtor: ModelConstructor = MigrationModel): IModel { 22 | return new modelCtor({ 23 | name, 24 | batch, 25 | checksum, 26 | type, 27 | appliedAt 28 | }); 29 | } 30 | 31 | } 32 | 33 | export default MigrationFactory -------------------------------------------------------------------------------- /src/core/domains/migrations/interfaces/IMigration.ts: -------------------------------------------------------------------------------- 1 | import { IDatabaseAdapter } from "@src/core/domains/database/interfaces/IDatabaseAdapter"; 2 | import { TClassConstructor } from "@src/core/interfaces/ClassConstructor.t"; 3 | 4 | /** 5 | * The type of migration 6 | */ 7 | export type MigrationType = 'schema' | 'seeder'; 8 | 9 | export interface IMigration { 10 | 11 | /** 12 | * This should be set only if this migration should run on a specific database provider 13 | */ 14 | databaseAdapter?: TClassConstructor; 15 | 16 | /** 17 | * Specify the group this migration belongs to 18 | */ 19 | group?: string; 20 | 21 | /** 22 | * Specify the type of migration 23 | */ 24 | migrationType: 'schema' | 'seeder'; 25 | 26 | /** 27 | * Run the migrations up 28 | */ 29 | up(): Promise 30 | 31 | /** 32 | * Run the migrations down 33 | */ 34 | down(): Promise 35 | 36 | /** 37 | * Check if the current database provider matches databaseProvider 38 | */ 39 | shouldUp(): boolean; 40 | } -------------------------------------------------------------------------------- /src/core/domains/migrations/interfaces/IMigrationConfig.ts: -------------------------------------------------------------------------------- 1 | import { ModelConstructor } from "@src/core/domains/models/interfaces/IModel"; 2 | 3 | export interface IMigrationConfig 4 | { 5 | schemaMigrationDir?: string; 6 | seederMigrationDir?: string; 7 | keepProcessAlive?: boolean; 8 | modelCtor?: ModelConstructor; 9 | } -------------------------------------------------------------------------------- /src/core/domains/migrations/interfaces/IMigrationService.ts: -------------------------------------------------------------------------------- 1 | import { MigrationType } from "@src/core/domains/migrations/interfaces/IMigration"; 2 | 3 | /* eslint-disable no-unused-vars */ 4 | export interface IMigrationServiceOptions { 5 | filterByFileName?: string 6 | group?: string 7 | batch?: number 8 | } 9 | 10 | export interface IMigrationService { 11 | boot(): Promise; 12 | up(options: IMigrationServiceOptions): Promise; 13 | down(options: IMigrationServiceOptions): Promise; 14 | getMigrationType(): MigrationType; 15 | } -------------------------------------------------------------------------------- /src/core/domains/migrations/schema/DataTypes.ts: -------------------------------------------------------------------------------- 1 | import { DataTypes } from "sequelize"; 2 | 3 | export default DataTypes; -------------------------------------------------------------------------------- /src/core/domains/mongodb/builder/AggregateExpression/Limit.ts: -------------------------------------------------------------------------------- 1 | class Limit { 2 | 3 | /** 4 | * Constructs a MongoDB pipeline stage for limiting the number of documents. 5 | * 6 | * @param limit - The maximum number of documents to return. If null, no limit is applied. 7 | * @returns An object representing the $limit stage in a MongoDB aggregation pipeline, or null if no limit is specified. 8 | */ 9 | static getPipeline(limit: number | null): object | null { 10 | if(!limit) return null; 11 | 12 | return { $limit: limit } 13 | } 14 | 15 | } 16 | 17 | export default Limit -------------------------------------------------------------------------------- /src/core/domains/mongodb/builder/AggregateExpression/Project.ts: -------------------------------------------------------------------------------- 1 | import { TColumnOption } from "@src/core/domains/eloquent/interfaces/IEloquent"; 2 | 3 | class Project { 4 | 5 | /** 6 | * Builds the $project stage of the aggregation pipeline 7 | * @param columns - The columns to project 8 | * @returns The $project pipeline stage or null if no columns are specified 9 | */ 10 | static getPipeline(columns: TColumnOption[] | null): object | null { 11 | if(!columns?.length) return null; 12 | 13 | if(columns.length === 1 && columns[0].column === '*') { 14 | return null 15 | } 16 | 17 | const project = {}; 18 | 19 | columns.forEach(column => { 20 | if(column.column) { 21 | project[column.column] = 1 22 | } 23 | }) 24 | 25 | return { $project: project }; 26 | } 27 | 28 | } 29 | 30 | export default Project -------------------------------------------------------------------------------- /src/core/domains/mongodb/builder/AggregateExpression/Skip.ts: -------------------------------------------------------------------------------- 1 | class Skip { 2 | 3 | /** 4 | * Converts the offset property from the query builder into its MongoDB pipeline representation. 5 | * 6 | * Example: { $skip: 10 } 7 | * 8 | * @param {number} offset - The offset property from the query builder. 9 | * @returns {object} The MongoDB pipeline representation of the offset property, or null if no offset is specified. 10 | */ 11 | static getPipeline(offset: number | null): object | null { 12 | if(!offset) return null; 13 | return { $skip: offset } 14 | } 15 | 16 | } 17 | 18 | export default Skip -------------------------------------------------------------------------------- /src/core/domains/mongodb/interfaces/IMongoConfig.ts: -------------------------------------------------------------------------------- 1 | 2 | import { IDatabaseGenericConnectionConfig } from '@src/core/domains/database/interfaces/IDatabaseConfig'; 3 | import { MongoClientOptions } from 'mongodb'; 4 | 5 | export interface IMongoConfig extends IDatabaseGenericConnectionConfig {} -------------------------------------------------------------------------------- /src/core/domains/mongodb/interfaces/IMongoDbIdentiferConcern.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { IDatabaseDocument } from "@src/core/domains/database/interfaces/IDocumentManager"; 3 | import { ObjectId } from "mongodb"; 4 | 5 | export interface IMongoDbIdentiferConcern { 6 | 7 | convertToObjectId(id: string | ObjectId): ObjectId; 8 | 9 | convertObjectIdToStringInDocument(document: IDatabaseDocument): IDatabaseDocument; 10 | 11 | } -------------------------------------------------------------------------------- /src/core/domains/mongodb/schema/createMigrationSchemaMongo.ts: -------------------------------------------------------------------------------- 1 | import MongoDbAdapter from "@src/core/domains/mongodb/adapters/MongoDbAdapter"; 2 | 3 | /** 4 | * Creates the migrations schema for MongoDB 5 | * 6 | * @returns {Promise} 7 | */ 8 | const createMigrationSchemaMongo = async (adapter: MongoDbAdapter, tableName: string = 'migrations') => { 9 | const db = adapter.getDb(); 10 | 11 | if ((await db.listCollections().toArray()).map(c => c.name).includes(tableName)) { 12 | return; 13 | } 14 | 15 | await db.createCollection(tableName); 16 | } 17 | 18 | export default createMigrationSchemaMongo -------------------------------------------------------------------------------- /src/core/domains/mongodb/utils/extractDefaultMongoCredentials.ts: -------------------------------------------------------------------------------- 1 | import { AppSingleton } from "@src/core/services/App" 2 | import fs from "fs" 3 | import path from "path" 4 | 5 | /** 6 | * Extracts the default MongoDB credentials from the `docker-compose.mongodb.yml` file. 7 | */ 8 | export const extractDefaultMongoCredentials = () => { 9 | try { 10 | const dockerComposePath = path.resolve('@src/../', 'docker/docker-compose.mongodb.yml') 11 | const contents = fs.readFileSync(dockerComposePath, 'utf8') 12 | 13 | const pattern = /LARASCRIPT_DEFAULT_CREDENTIALS:\s?(.+)/ 14 | const match = pattern.exec(contents) 15 | 16 | if (match?.[1]) { 17 | return match?.[1] 18 | } 19 | } 20 | catch (err) { 21 | AppSingleton.container('logger').error(err) 22 | } 23 | 24 | return null; 25 | } -------------------------------------------------------------------------------- /src/core/domains/observer/interfaces/IHasObserver.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { IObserver } from "@src/core/domains/observer/interfaces/IObserver"; 3 | import { TClassConstructor } from "@src/core/interfaces/ClassConstructor.t"; 4 | 5 | export type ObserveConstructor = TClassConstructor; 6 | 7 | export default interface IHasObserver { 8 | setObserverConstructor(observerConstructor: ObserveConstructor | undefined): void; 9 | getObserver(): IObserver | undefined; 10 | setObserveProperty(attribute: string, method: string): void; 11 | } -------------------------------------------------------------------------------- /src/core/domains/observer/interfaces/IObserver.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | export type IObserverEvent = keyof IObserver; 3 | 4 | export interface IObserver { 5 | creating(data: ReturnType): Promise; 6 | created(data: ReturnType): Promise; 7 | updating(data: ReturnType): Promise; 8 | updated(data: ReturnType): Promise; 9 | saving(data: ReturnType): Promise; 10 | saved(data: ReturnType): Promise; 11 | deleting(data: ReturnType): Promise; 12 | deleted(data: ReturnType): Promise; 13 | on(name: IObserverEvent, data: ReturnType): Promise; 14 | onCustom(customName: string, data: ReturnType): Promise; 15 | } -------------------------------------------------------------------------------- /src/core/domains/postgres/builder/ExpressionBuilder/Clauses/DeleteFrom.ts: -------------------------------------------------------------------------------- 1 | import ExpressionException from "@src/core/domains/eloquent/exceptions/ExpressionException"; 2 | import SqlExpression from "@src/core/domains/postgres/builder/ExpressionBuilder/SqlExpression"; 3 | 4 | class DeleteFrom { 5 | 6 | /** 7 | * Converts a table name to a SQL string that can be used for a FROM clause. 8 | * 9 | * @param table - The table name to convert to a SQL string. 10 | * @param abbreviation - The abbreviation for the table name. 11 | * @returns The SQL string for the FROM clause. 12 | */ 13 | static toSql(table: string, abbreviation?: string | null): string { 14 | table = SqlExpression.formatTableNameWithQuotes(table); 15 | 16 | if(table?.length === 0) { 17 | throw new ExpressionException('Table name is required'); 18 | } 19 | 20 | let sql = `DELETE FROM ${table}`; 21 | 22 | if(abbreviation) { 23 | sql += ` ${abbreviation}` 24 | } 25 | 26 | return sql 27 | } 28 | 29 | } 30 | 31 | export default DeleteFrom -------------------------------------------------------------------------------- /src/core/domains/postgres/builder/ExpressionBuilder/Clauses/FromTable.ts: -------------------------------------------------------------------------------- 1 | import SqlExpression from "@src/core/domains/postgres/builder/ExpressionBuilder/SqlExpression"; 2 | 3 | class FromTable { 4 | 5 | /** 6 | * Converts a table name to a SQL string that can be used for a FROM clause. 7 | * 8 | * @param table - The table name to convert to a SQL string. 9 | * @param abbreviation - The abbreviation for the table name. 10 | * @returns The SQL string for the FROM clause. 11 | */ 12 | static toSql(table: string, abbreviation?: string | null): string { 13 | table = SqlExpression.formatTableNameWithQuotes(table); 14 | 15 | let sql = `FROM ${table}`; 16 | 17 | if(abbreviation) { 18 | sql += ` ${abbreviation}` 19 | } 20 | 21 | return sql 22 | } 23 | 24 | } 25 | 26 | export default FromTable -------------------------------------------------------------------------------- /src/core/domains/postgres/builder/ExpressionBuilder/Clauses/OffsetLimit.ts: -------------------------------------------------------------------------------- 1 | import { TOffsetLimit } from "@src/core/domains/eloquent/interfaces/IEloquent"; 2 | 3 | class OffsetLimit { 4 | 5 | /** 6 | * Converts the offset property from the query builder into its SQL representation. 7 | * 8 | * Example: LIMIT 10 OFFSET 10 9 | * 10 | * @param {TOffsetLimit} offset - The offset property from the query builder. 11 | * @returns {string} The SQL-safe LIMIT and OFFSET clause. 12 | */ 13 | static toSql({ limit, offset}: TOffsetLimit = {}, prefix: string = ''): string { 14 | 15 | if(!offset && !limit) return '' 16 | 17 | let sql = `${prefix}`; 18 | 19 | if(limit) { 20 | sql += `LIMIT ${limit}`; 21 | } 22 | 23 | if(offset) { 24 | sql += sql.length ? ' ' : ''; 25 | sql +=`OFFSET ${offset}` 26 | } 27 | 28 | return sql 29 | } 30 | 31 | } 32 | 33 | export default OffsetLimit -------------------------------------------------------------------------------- /src/core/domains/postgres/exceptions/InvalidSequelizeException.ts: -------------------------------------------------------------------------------- 1 | 2 | export default class InvalidSequelizeException extends Error { 3 | 4 | constructor(message: string = 'Invalid Sequelize Exception') { 5 | super(message); 6 | this.name = 'InvalidSequelizeException'; 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /src/core/domains/postgres/interfaces/IPostgresConfig.ts: -------------------------------------------------------------------------------- 1 | 2 | import { IDatabaseGenericConnectionConfig } from '@src/core/domains/database/interfaces/IDatabaseConfig'; 3 | import { Options as SequelizeOptions } from 'sequelize/types/sequelize'; 4 | 5 | export interface IPostgresConfig extends IDatabaseGenericConnectionConfig {} -------------------------------------------------------------------------------- /src/core/domains/postgres/interfaces/IPostgresQueryBuilder.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-shepherd/larascript-framework/1471b26b1184603264c1bdf35d3b8d39d190989d/src/core/domains/postgres/interfaces/IPostgresQueryBuilder.ts -------------------------------------------------------------------------------- /src/core/domains/postgres/utils/extractDefaultPostgresCredentials.ts: -------------------------------------------------------------------------------- 1 | import { AppSingleton } from "@src/core/services/App" 2 | import fs from "fs" 3 | import path from "path" 4 | 5 | export const extractDefaultPostgresCredentials = () => { 6 | try { 7 | const dockerComposePath = path.resolve('@src/../', 'docker/docker-compose.postgres.yml') 8 | const contents = fs.readFileSync(dockerComposePath, 'utf8') 9 | 10 | const pattern = /LARASCRIPT_DEFAULT_CREDENTIALS:\s?(.+)/ 11 | const match = pattern.exec(contents) 12 | 13 | if (match?.[1]) { 14 | return match?.[1] 15 | } 16 | } 17 | catch (err) { 18 | AppSingleton.container('logger').error(err) 19 | } 20 | 21 | return null; 22 | } -------------------------------------------------------------------------------- /src/core/domains/session/interfaces/ISessionService.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | export type TSessionData = Record; 3 | export type TSessionObject = { id: string, data: T }; 4 | 5 | export interface ISessionService { 6 | start(sessionId?: string): Promise; 7 | runWithSession( 8 | callback: () => Promise | R, 9 | data?: T 10 | ): Promise; 11 | createSession(data?: T): TSessionObject; 12 | getSession(): TSessionObject; 13 | getSessionId(): string; 14 | getSessionData(): T; 15 | setSessionData(data: T): void; 16 | updateSessionData(data: T): void; 17 | } -------------------------------------------------------------------------------- /src/core/domains/session/providers/SessionProvider.ts: -------------------------------------------------------------------------------- 1 | import BaseProvider from "@src/core/base/Provider"; 2 | import SessionService from "@src/core/domains/session/services/SessionService"; 3 | 4 | class SessionProvider extends BaseProvider{ 5 | 6 | async register(): Promise { 7 | this.bind('session', new SessionService()) 8 | } 9 | 10 | } 11 | 12 | export default SessionProvider 13 | -------------------------------------------------------------------------------- /src/core/domains/setup/actions/CopyEnvExampleAction.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { IAction } from '@src/core/domains/setup/interfaces/IAction'; 3 | import { ISetupCommand } from '@src/core/domains/setup/interfaces/ISetupCommand'; 4 | 5 | class CopyEnvExampleAction implements IAction { 6 | 7 | async handle(ref: ISetupCommand): Promise { 8 | if(fs.existsSync(ref.env.envPath)) { 9 | ref.writeLine('Warning: Skipping copying .env.example to .env because .env already exists'); 10 | return; 11 | } 12 | 13 | ref.env.copyFileFromEnvExample(); 14 | 15 | ref.writeLine('Successfully copied .env.example to .env'); 16 | } 17 | 18 | } 19 | 20 | export default CopyEnvExampleAction -------------------------------------------------------------------------------- /src/core/domains/setup/actions/EnableExpress.ts: -------------------------------------------------------------------------------- 1 | import { IAction } from '@src/core/domains/setup/interfaces/IAction'; 2 | import { ISetupCommand } from '@src/core/domains/setup/interfaces/ISetupCommand'; 3 | import QuestionDTO from '@src/core/domains/setup/DTOs/QuestionDTO'; 4 | 5 | class EnableExpress implements IAction { 6 | 7 | async handle(ref: ISetupCommand, question: QuestionDTO): Promise { 8 | const answerIsEnabled = question.getAnswer() === 'y' || question.getAnswer() === 'yes'; 9 | 10 | ref.env.updateValues({ EXPRESS_ENABLED: answerIsEnabled ? 'true' : 'false' }); 11 | } 12 | 13 | } 14 | 15 | export default EnableExpress -------------------------------------------------------------------------------- /src/core/domains/setup/actions/GenerateAppKeyAction.ts: -------------------------------------------------------------------------------- 1 | import QuestionDTO from "@src/core/domains/setup/DTOs/QuestionDTO"; 2 | import { IAction } from "@src/core/domains/setup/interfaces/IAction"; 3 | import { ISetupCommand } from "@src/core/domains/setup/interfaces/ISetupCommand"; 4 | import { cryptoService } from "@src/core/domains/crypto/service/CryptoService"; 5 | 6 | class GenerateAppKeyAction implements IAction { 7 | 8 | async handle(ref: ISetupCommand, question: QuestionDTO): Promise { 9 | const answerIsYes = question.getAnswer() === 'y' || question.getAnswer() === 'yes'; 10 | 11 | if(!answerIsYes) { 12 | return; 13 | } 14 | 15 | const appKey = cryptoService().generateAppKey() 16 | 17 | await ref.env.updateValues({ APP_KEY: appKey }); 18 | 19 | ref.writeLine('Successfully generated app key!'); 20 | } 21 | 22 | } 23 | 24 | export default GenerateAppKeyAction -------------------------------------------------------------------------------- /src/core/domains/setup/actions/GenerateJwtSecretAction.ts: -------------------------------------------------------------------------------- 1 | import { IAction } from "@src/core/domains/setup/interfaces/IAction"; 2 | import { ISetupCommand } from "@src/core/domains/setup/interfaces/ISetupCommand"; 3 | import QuestionDTO from "@src/core/domains/setup/DTOs/QuestionDTO"; 4 | 5 | class GenerateJwtSecretAction implements IAction { 6 | 7 | async handle(ref: ISetupCommand, question: QuestionDTO): Promise { 8 | const answerIsYes = question.getAnswer() === 'y' || question.getAnswer() === 'yes'; 9 | 10 | if(!answerIsYes) { 11 | return; 12 | } 13 | 14 | const secret = require('crypto').randomBytes(64).toString('hex'); 15 | 16 | await ref.env.updateValues({ JWT_SECRET: secret }); 17 | 18 | ref.writeLine('Successfully generated jwt secret!'); 19 | } 20 | 21 | } 22 | 23 | export default GenerateJwtSecretAction -------------------------------------------------------------------------------- /src/core/domains/setup/consts/QuestionConsts.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * List of question IDs 4 | * Value: Should match the property in .env 5 | * Value can also be any arbitrary string if it belongs to a statement 6 | */ 7 | export const QuestionIDs = { 8 | selectDb: 'SELECT_DB', 9 | selectDefaultDb: 'SELECT_DEFAULT_DB', 10 | copyEnvExample: 'COPY_ENV_EXAMPLE', 11 | appPort: 'APP_PORT', 12 | appKey: 'APP_KEY', 13 | enableExpress: 'ENABLE_EXPRESS', 14 | enableAuthRoutes: 'ENABLE_AUTH_ROUTES', 15 | enableAuthRoutesAllowCreate: 'ENABLE_AUTH_ROUTES_ALLOW_CREATE', 16 | jwtSecret: 'JWT_SECRET', 17 | mongodbDefaultUri: 'MONGODB_DEFAULT_URI' 18 | } as const; -------------------------------------------------------------------------------- /src/core/domains/setup/exceptions/InvalidDefaultCredentialsError.ts: -------------------------------------------------------------------------------- 1 | export default class InvalidDefaultCredentialsError extends Error { 2 | 3 | constructor(message: string = 'The default credentials are invalid or could not be found') { 4 | super(message); 5 | this.name = 'InvalidDefaultCredentialsError'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/setup/interfaces/IAction.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { ISetupCommand } from '@src/core/domains/setup/interfaces/ISetupCommand'; 3 | 4 | export type ActionCtor = new (...args: any[]) => T; 5 | 6 | export interface IAction { 7 | handle: (ref: ISetupCommand, ...args: any[]) => Promise; 8 | } -------------------------------------------------------------------------------- /src/core/domains/setup/interfaces/ISetupCommand.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { IConsoleInputService } from '@src/core/domains/console/interfaces/IConsoleInputService'; 3 | import { IEnvService } from "@src/core/interfaces/IEnvService"; 4 | 5 | export interface ISetupCommand { 6 | env: IEnvService; 7 | input: IConsoleInputService; 8 | writeLine(line: string): void; 9 | } -------------------------------------------------------------------------------- /src/core/domains/setup/providers/SetupProvider.ts: -------------------------------------------------------------------------------- 1 | import BaseProvider from "@src/core/base/Provider"; 2 | import AppSetupCommand from "@src/core/domains/setup/commands/AppSetupCommand"; 3 | import { app } from "@src/core/services/App"; 4 | 5 | class SetupProvider extends BaseProvider { 6 | 7 | async register(): Promise { 8 | this.log('Registering SetupProvider'); 9 | 10 | // Register the setup commands 11 | app('console').registerService().registerAll([ 12 | AppSetupCommand 13 | ]) 14 | } 15 | 16 | } 17 | 18 | export default SetupProvider -------------------------------------------------------------------------------- /src/core/domains/storage/Exceptions/FileNotFoundException.ts: -------------------------------------------------------------------------------- 1 | export default class FileNotFoundException extends Error { 2 | 3 | constructor(message = 'File not found') { 4 | super(message) 5 | this.name = 'FileNotFoundException' 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/storage/Exceptions/InvalidStorageFileException.ts: -------------------------------------------------------------------------------- 1 | export default class InvalidStorageFileException extends Error { 2 | 3 | constructor(message = 'Invalid Storage File') { 4 | super(message) 5 | this.name = 'InvalidStorageFileException' 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/domains/storage/enums/StorageTypes.ts: -------------------------------------------------------------------------------- 1 | export const StorageTypes = { 2 | fs: 'fs', 3 | s3: 's3', 4 | } as const -------------------------------------------------------------------------------- /src/core/domains/storage/interfaces/IGenericStorage.ts: -------------------------------------------------------------------------------- 1 | import { IStorageFile } from "@src/core/domains/storage/interfaces/IStorageFile"; 2 | 3 | /* eslint-disable no-unused-vars */ 4 | export interface IGenericStorage { 5 | put(file: IStorageFile | string, destination?: string): Promise; 6 | get(file: IStorageFile | string, ...args: unknown[]): Promise; 7 | delete(file: IStorageFile | string): Promise; 8 | } -------------------------------------------------------------------------------- /src/core/domains/storage/interfaces/IStorageFile.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | export interface IStorageFile { 3 | getKey(): string; 4 | getSource(): string | undefined; 5 | toObject(): object; 6 | getPresignedUrl(): string | undefined; 7 | getMetaValue(key: string): T | undefined; 8 | } -------------------------------------------------------------------------------- /src/core/domains/storage/interfaces/IStorageService.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | 3 | import { TUploadedFile } from "@src/core/domains/http/interfaces/UploadedFile"; 4 | import StorageFile from "@src/core/domains/storage/data/StorageFile"; 5 | import AmazonS3StorageService from "@src/core/domains/storage/services/AmazonS3StorageService"; 6 | import FileSystemStorageService from "@src/core/domains/storage/services/FileSystemStorageService"; 7 | import { IGenericStorage } from "@src/core/domains/storage/interfaces/IGenericStorage"; 8 | import { IStorageFile } from "@src/core/domains/storage/interfaces/IStorageFile"; 9 | import { FileSystemMeta } from "@src/core/domains/storage/interfaces/meta"; 10 | 11 | export interface IStorageService extends IGenericStorage { 12 | driver(key: string): IGenericStorage 13 | moveUploadedFile(file: TUploadedFile, destination?: string): Promise; 14 | getStorageDirectory(): string; 15 | getUploadsDirectory(): string; 16 | toStorageFile(fullPath: string): StorageFile; 17 | s3(): AmazonS3StorageService; 18 | fileSystem(): FileSystemStorageService; 19 | } -------------------------------------------------------------------------------- /src/core/domains/storage/interfaces/StorageAdapters.ts: -------------------------------------------------------------------------------- 1 | import { BaseAdapterTypes } from "@src/core/base/BaseAdapter"; 2 | 3 | export interface StorageAdapters extends BaseAdapterTypes { 4 | fileSystem: unknown; 5 | s3: unknown; 6 | } -------------------------------------------------------------------------------- /src/core/domains/storage/interfaces/StorageConfig.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface StorageConfig { 3 | driver: string; 4 | storageDir: string; 5 | uploadsDir: string; 6 | s3: { 7 | accessKeyId: string 8 | secretAccessKey: string 9 | bucket: string 10 | region: string 11 | } 12 | } -------------------------------------------------------------------------------- /src/core/domains/storage/interfaces/meta.ts: -------------------------------------------------------------------------------- 1 | export type S3Meta = { 2 | Bucket?: string 3 | ETag?: string 4 | Key?: string 5 | Location?: string 6 | ServerSideEncryption?: string 7 | } 8 | 9 | export type FileSystemMeta = { 10 | fullPath: string 11 | } -------------------------------------------------------------------------------- /src/core/domains/storage/providers/StorageProvider.ts: -------------------------------------------------------------------------------- 1 | import { config } from '@src/config/storage.config'; 2 | import BaseProvider from "@src/core/base/Provider"; 3 | import StorageService from "@src/core/domains/storage/services/StorageService"; 4 | 5 | import { StorageTypes } from "@src/core/domains/storage/enums/StorageTypes"; 6 | import AmazonS3StorageService from "@src/core/domains/storage/services/AmazonS3StorageService"; 7 | import FileSystemStorageService from "@src/core/domains/storage/services/FileSystemStorageService"; 8 | 9 | class StorageProvider extends BaseProvider { 10 | 11 | async register(): Promise { 12 | const storage = new StorageService(config); 13 | storage.addAdapterOnce(StorageTypes.fs, new FileSystemStorageService()); 14 | storage.addAdapterOnce(StorageTypes.s3, new AmazonS3StorageService(config.s3)); 15 | this.bind('storage', storage); 16 | } 17 | 18 | } 19 | 20 | export default StorageProvider; 21 | -------------------------------------------------------------------------------- /src/core/domains/validator/exceptions/ValidatorException.ts: -------------------------------------------------------------------------------- 1 | class ValidatorException extends Error { 2 | 3 | constructor(message: string = 'Validation failed') { 4 | super(message) 5 | } 6 | 7 | } 8 | 9 | export default ValidatorException 10 | -------------------------------------------------------------------------------- /src/core/domains/validator/interfaces/IRule.ts: -------------------------------------------------------------------------------- 1 | import { IHasHttpContext } from "@src/core/domains/http/interfaces/IHttpContext" 2 | 3 | /* eslint-disable no-unused-vars */ 4 | export interface IRuleConstructor { 5 | new (...args: any[]): IRule 6 | } 7 | 8 | export interface IRulesObject { 9 | [key: string]: IRule | IRule[] 10 | } 11 | 12 | export interface IRuleError { 13 | [key: string]: string[] 14 | } 15 | 16 | 17 | export interface IRule extends IHasHttpContext { 18 | setDotNotationPath(field: string): this 19 | getDotNotationPath(): string 20 | setData(data: unknown): this 21 | setAttributes(attributes: unknown): this 22 | setAttribute(attribute: string): this 23 | getAttribute(): string; 24 | setMessages(messages: Record): this 25 | setOtherRuleNames(names: string[]): this 26 | validate(): Promise 27 | getError(): IRuleError 28 | getCustomError(): IRuleError | undefined 29 | getName(): string 30 | } 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/core/domains/validator/interfaces/IValidatorResult.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { IValidatorAttributes } from "@src/core/domains/validator/interfaces/IValidator"; 3 | 4 | export interface IValidatorResult { 5 | passes(): boolean; 6 | fails(): boolean; 7 | errors(): Record; 8 | validated(): T; 9 | mergeErrors(errors: Record): void; 10 | updatePasses(): void; 11 | } 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/core/domains/validator/providers/ValidatorProvider.ts: -------------------------------------------------------------------------------- 1 | import BaseProvider from "@src/core/base/Provider"; 2 | import { validatorFn } from "@src/core/domains/validator/service/Validator"; 3 | 4 | class ValidatorProvider extends BaseProvider { 5 | 6 | async register(): Promise { 7 | this.log('Registering ValidatorProvider'); 8 | 9 | // Bind a helper function for on the fly validation 10 | this.bind('validatorFn', validatorFn); 11 | 12 | } 13 | 14 | } 15 | 16 | export default ValidatorProvider -------------------------------------------------------------------------------- /src/core/domains/validator/rules/AcceptedRule.ts: -------------------------------------------------------------------------------- 1 | 2 | import AbstractRule from "@src/core/domains/validator/abstract/AbstractRule"; 3 | import { IRule } from "@src/core/domains/validator/interfaces/IRule"; 4 | import isTruthy from "@src/core/domains/validator/utils/isTruthy"; 5 | 6 | class AcceptedRule extends AbstractRule implements IRule { 7 | 8 | protected name: string = 'accepted' 9 | 10 | protected errorTemplate: string = 'The :attribute field must be accepted.'; 11 | 12 | public async test(): Promise { 13 | return isTruthy(this.getData()) 14 | } 15 | 16 | } 17 | 18 | 19 | export default AcceptedRule; 20 | -------------------------------------------------------------------------------- /src/core/domains/validator/rules/ArrayRule.ts: -------------------------------------------------------------------------------- 1 | 2 | import AbstractRule from "@src/core/domains/validator/abstract/AbstractRule"; 3 | import { IRule } from "@src/core/domains/validator/interfaces/IRule"; 4 | 5 | class ArrayRule extends AbstractRule implements IRule { 6 | 7 | protected name: string = 'array' 8 | 9 | protected errorTemplate: string = 'The :attribute field must be an array.'; 10 | 11 | public async test(): Promise { 12 | return Array.isArray(this.getData()) 13 | } 14 | 15 | } 16 | 17 | 18 | export default ArrayRule; 19 | -------------------------------------------------------------------------------- /src/core/domains/validator/rules/BooleanRule.ts: -------------------------------------------------------------------------------- 1 | 2 | import AbstractRule from "@src/core/domains/validator/abstract/AbstractRule"; 3 | import { IRule } from "@src/core/domains/validator/interfaces/IRule"; 4 | 5 | class BooleanRule extends AbstractRule implements IRule { 6 | 7 | protected name: string = 'is_boolean' 8 | 9 | protected errorTemplate: string = 'The :attribute field must be a boolean.'; 10 | 11 | public async test(): Promise { 12 | return typeof this.getData() === 'boolean' 13 | } 14 | 15 | } 16 | 17 | 18 | export default BooleanRule; 19 | -------------------------------------------------------------------------------- /src/core/domains/validator/rules/DateRule.ts: -------------------------------------------------------------------------------- 1 | import AbstractRule from "@src/core/domains/validator/abstract/AbstractRule"; 2 | import { IRule, IRuleError } from "@src/core/domains/validator/interfaces/IRule"; 3 | 4 | class DateRule extends AbstractRule implements IRule { 5 | 6 | protected name: string = 'date'; 7 | 8 | protected errorTemplate: string = 'The :attribute must be a valid date.'; 9 | 10 | public async test(): Promise { 11 | 12 | if (typeof this.getData() !== 'string') { 13 | return false; 14 | } 15 | 16 | const date = new Date(this.getData() as string); 17 | return date instanceof Date && !isNaN(date.getTime()); 18 | } 19 | 20 | getError(): IRuleError { 21 | return { 22 | [this.getDotNotationPath()]: [ 23 | this.formatErrorMessage() 24 | ] 25 | } 26 | } 27 | 28 | } 29 | 30 | export default DateRule; -------------------------------------------------------------------------------- /src/core/domains/validator/rules/EmailRule.ts: -------------------------------------------------------------------------------- 1 | import AbstractRule from "@src/core/domains/validator/abstract/AbstractRule"; 2 | import { IRule, IRuleError } from "@src/core/domains/validator/interfaces/IRule"; 3 | 4 | class EmailRule extends AbstractRule<{}> implements IRule { 5 | 6 | protected name: string = 'email'; 7 | 8 | protected errorTemplate: string = 'The :attribute field must be a valid email address.'; 9 | 10 | // RFC 5322 compliant email regex 11 | private emailRegex: RegExp = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; 12 | 13 | public async test(): Promise { 14 | 15 | if (typeof this.getData() !== 'string') { 16 | return false; 17 | } 18 | 19 | return this.emailRegex.test(this.getData() as string); 20 | } 21 | 22 | getError(): IRuleError { 23 | return { 24 | [this.getDotNotationPath()]: [ 25 | this.formatErrorMessage({}) 26 | ] 27 | }; 28 | } 29 | 30 | } 31 | 32 | export default EmailRule; -------------------------------------------------------------------------------- /src/core/domains/validator/rules/EqualsRule.ts: -------------------------------------------------------------------------------- 1 | 2 | import AbstractRule from "@src/core/domains/validator/abstract/AbstractRule"; 3 | import { IRule, IRuleError } from "@src/core/domains/validator/interfaces/IRule"; 4 | 5 | type TEqualsOptions = { 6 | matches: unknown 7 | } 8 | 9 | class EqualsRule extends AbstractRule implements IRule { 10 | 11 | protected name: string = 'equals' 12 | 13 | protected errorTemplate: string = 'The :attribute field must be equal to :matches.'; 14 | 15 | constructor(matches: unknown) { 16 | super({ matches }); 17 | } 18 | 19 | public async test(): Promise { 20 | return this.getData() === this.options.matches 21 | } 22 | 23 | getError(): IRuleError { 24 | return { 25 | [this.getDotNotationPath()]: [ 26 | this.formatErrorMessage({ 27 | matches: this.options.matches 28 | }) 29 | ] 30 | } 31 | } 32 | 33 | } 34 | 35 | 36 | export default EqualsRule; 37 | -------------------------------------------------------------------------------- /src/core/domains/validator/rules/HasFileRule.ts: -------------------------------------------------------------------------------- 1 | 2 | import AbstractRule from "@src/core/domains/validator/abstract/AbstractRule"; 3 | import { IRule } from "@src/core/domains/validator/interfaces/IRule"; 4 | 5 | class HasFileRule extends AbstractRule implements IRule { 6 | 7 | protected name: string = 'required' 8 | 9 | protected errorTemplate: string = 'The :attribute field must be a file.'; 10 | 11 | public async test(): Promise { 12 | const files = this.getHttpContext().getFiles(this.getAttribute()) 13 | const tests = files?.some(file => typeof file !== 'undefined') ?? false 14 | 15 | return tests 16 | } 17 | 18 | } 19 | 20 | 21 | export default HasFileRule; 22 | -------------------------------------------------------------------------------- /src/core/domains/validator/rules/JsonRule.ts: -------------------------------------------------------------------------------- 1 | import AbstractRule from "@src/core/domains/validator/abstract/AbstractRule"; 2 | import { IRule, IRuleError } from "@src/core/domains/validator/interfaces/IRule"; 3 | 4 | class JsonRule extends AbstractRule implements IRule { 5 | 6 | protected name: string = 'json'; 7 | 8 | protected errorTemplate: string = 'The :attribute must be a valid JSON string.'; 9 | 10 | public async test(): Promise { 11 | if (typeof this.getData() !== 'string') { 12 | return false; 13 | } 14 | 15 | try { 16 | JSON.parse(this.getData() as string); 17 | } 18 | // eslint-disable-next-line no-unused-vars 19 | catch (e) { 20 | return false; 21 | } 22 | 23 | return true; 24 | } 25 | 26 | getError(): IRuleError { 27 | return { 28 | [this.getDotNotationPath()]: [ 29 | this.formatErrorMessage() 30 | ] 31 | } 32 | } 33 | 34 | } 35 | 36 | export default JsonRule; -------------------------------------------------------------------------------- /src/core/domains/validator/rules/MultipleFilesRule.ts: -------------------------------------------------------------------------------- 1 | 2 | import AbstractRule from "@src/core/domains/validator/abstract/AbstractRule"; 3 | import { IRule } from "@src/core/domains/validator/interfaces/IRule"; 4 | 5 | 6 | class MultipleFilesRule extends AbstractRule implements IRule { 7 | 8 | protected name: string = 'required' 9 | 10 | protected errorTemplate: string = 'The :attribute field expects multiple files.'; 11 | 12 | public async test(): Promise { 13 | const files = this.getHttpContext().getFiles(this.getAttribute()) ?? [] 14 | 15 | return files?.length >= 1 16 | } 17 | 18 | } 19 | 20 | 21 | export default MultipleFilesRule; 22 | -------------------------------------------------------------------------------- /src/core/domains/validator/rules/NullableRule.ts: -------------------------------------------------------------------------------- 1 | 2 | import AbstractRule from "@src/core/domains/validator/abstract/AbstractRule"; 3 | import { IRule } from "@src/core/domains/validator/interfaces/IRule"; 4 | 5 | class NullableRule extends AbstractRule implements IRule { 6 | 7 | protected name: string = 'nullable' 8 | 9 | protected errorTemplate: string = ''; 10 | 11 | public async test(): Promise { 12 | return true 13 | } 14 | 15 | } 16 | 17 | export default NullableRule; 18 | -------------------------------------------------------------------------------- /src/core/domains/validator/rules/NumberRule.ts: -------------------------------------------------------------------------------- 1 | 2 | import AbstractRule from "@src/core/domains/validator/abstract/AbstractRule"; 3 | import { IRule } from "@src/core/domains/validator/interfaces/IRule"; 4 | 5 | class IsNumber extends AbstractRule implements IRule { 6 | 7 | protected name: string = 'number' 8 | 9 | protected errorTemplate: string = 'The :attribute field must be a number.'; 10 | 11 | public async test(): Promise { 12 | return typeof this.getData() === 'number' 13 | } 14 | 15 | } 16 | 17 | 18 | export default IsNumber; 19 | -------------------------------------------------------------------------------- /src/core/domains/validator/rules/RegexRule.ts: -------------------------------------------------------------------------------- 1 | import AbstractRule from "@src/core/domains/validator/abstract/AbstractRule"; 2 | import { IRule, IRuleError } from "@src/core/domains/validator/interfaces/IRule"; 3 | 4 | type TRegexOptions = { 5 | pattern: RegExp 6 | } 7 | 8 | class RegexRule extends AbstractRule implements IRule { 9 | 10 | protected name: string = 'regex'; 11 | 12 | protected errorTemplate: string = 'The :attribute field format is invalid.'; 13 | 14 | constructor(pattern: string | RegExp) { 15 | super({ pattern: pattern instanceof RegExp ? pattern : new RegExp(pattern) }); 16 | } 17 | 18 | public async test(): Promise { 19 | if (this.dataUndefinedOrNull()) return false 20 | if (this.nullableString()) return true 21 | 22 | const value = String(this.getData()); 23 | return this.options.pattern.test(value); 24 | } 25 | 26 | getError(): IRuleError { 27 | return { 28 | [this.getDotNotationPath()]: [ 29 | this.formatErrorMessage() 30 | ] 31 | }; 32 | } 33 | 34 | } 35 | 36 | export default RegexRule; -------------------------------------------------------------------------------- /src/core/domains/validator/rules/RequiredRule.ts: -------------------------------------------------------------------------------- 1 | 2 | import AbstractRule from "@src/core/domains/validator/abstract/AbstractRule"; 3 | import { IRule } from "@src/core/domains/validator/interfaces/IRule"; 4 | 5 | class RequiredRule extends AbstractRule implements IRule { 6 | 7 | protected name: string = 'required' 8 | 9 | protected errorTemplate: string = 'The :attribute field is required.'; 10 | 11 | public async test(): Promise { 12 | if(this.dataUndefinedOrNull()) return false 13 | 14 | if(typeof this.getData() === 'string') { 15 | return this.getData() !== '' 16 | } 17 | 18 | if(Array.isArray(this.getData())) { 19 | return (this.getData() as unknown[]).length > 0 20 | } 21 | 22 | if(typeof this.getData() === 'object') { 23 | return Object.keys(this.getData() as Record).length > 0 24 | } 25 | 26 | return true 27 | } 28 | 29 | } 30 | 31 | 32 | export default RequiredRule; 33 | -------------------------------------------------------------------------------- /src/core/domains/validator/rules/SingleFileRule.ts: -------------------------------------------------------------------------------- 1 | 2 | import AbstractRule from "@src/core/domains/validator/abstract/AbstractRule"; 3 | import { IRule, IRuleError } from "@src/core/domains/validator/interfaces/IRule"; 4 | 5 | 6 | class SingleFileRule extends AbstractRule implements IRule { 7 | 8 | protected name: string = 'required' 9 | 10 | protected errorTemplate: string = 'The :attribute field expects a single file but :amount were provided.'; 11 | 12 | protected amount!: number; 13 | 14 | public async test(): Promise { 15 | const files = this.getHttpContext().getFiles(this.getAttribute()) 16 | this.amount = files?.length ?? -1 17 | return files?.length === 1 18 | } 19 | 20 | public getError(): IRuleError { 21 | return { 22 | [this.getDotNotationPath()]: [ 23 | this.formatErrorMessage({ 24 | amount: this.amount 25 | }) 26 | ] 27 | } 28 | } 29 | 30 | } 31 | 32 | 33 | export default SingleFileRule; 34 | -------------------------------------------------------------------------------- /src/core/domains/validator/rules/StringRule.ts: -------------------------------------------------------------------------------- 1 | 2 | import AbstractRule from "@src/core/domains/validator/abstract/AbstractRule"; 3 | import { IRule } from "@src/core/domains/validator/interfaces/IRule"; 4 | 5 | class StringRule extends AbstractRule implements IRule { 6 | 7 | protected name: string = 'string' 8 | 9 | protected errorTemplate: string = 'The :attribute field must be a string.'; 10 | 11 | public async test(): Promise { 12 | 13 | if(Array.isArray(this.getData())) { 14 | return (this.getData() as unknown[]).every(item => typeof item === 'string') 15 | } 16 | 17 | return typeof this.getData() === 'string' 18 | 19 | } 20 | 21 | } 22 | 23 | 24 | export default StringRule; 25 | -------------------------------------------------------------------------------- /src/core/domains/validator/rules/UuidRule.ts: -------------------------------------------------------------------------------- 1 | import AbstractRule from "@src/core/domains/validator/abstract/AbstractRule"; 2 | import { IRule, IRuleError } from "@src/core/domains/validator/interfaces/IRule"; 3 | import { isUuid } from "@src/core/utility/uuid"; 4 | 5 | class UuidRule extends AbstractRule<{}> implements IRule { 6 | 7 | protected name: string = 'uuid'; 8 | 9 | protected errorTemplate: string = 'The :attribute field must be a valid UUID.'; 10 | 11 | constructor() { 12 | super(); 13 | this.options = {}; 14 | } 15 | 16 | public async test(): Promise { 17 | return isUuid(this.getData()); 18 | } 19 | 20 | getError(): IRuleError { 21 | return { 22 | [this.getDotNotationPath()]: [ 23 | this.formatErrorMessage({}) 24 | ] 25 | }; 26 | } 27 | 28 | } 29 | 30 | export default UuidRule; -------------------------------------------------------------------------------- /src/core/domains/validator/utils/isFalsy.ts: -------------------------------------------------------------------------------- 1 | const isFalsy = (value: unknown): boolean => { 2 | return value === false || value === 'false' || value === 0 || value === '0' || value === 'no' || value === 'off'; 3 | } 4 | 5 | export default isFalsy; 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/core/domains/validator/utils/isTruthy.ts: -------------------------------------------------------------------------------- 1 | const isTruthy = (value: unknown): boolean => { 2 | return value === true || value === 'true' || value === 1 || value === '1' || value === 'yes' || value === 'on'; 3 | } 4 | 5 | export default isTruthy; 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/core/exceptions/AdapterException.ts: -------------------------------------------------------------------------------- 1 | export default class AdapterException extends Error { 2 | 3 | constructor(message: string = 'Adapter Exception') { 4 | super(message); 5 | this.name = 'AdapterException'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/exceptions/DotNotationParserException.ts: -------------------------------------------------------------------------------- 1 | export default class DotNotationParserException extends Error { 2 | 3 | constructor(message: string = 'Dot Notation Parser Exception') { 4 | super(message); 5 | 6 | this.name = 'DotNotationParserException'; 7 | } 8 | 9 | 10 | } -------------------------------------------------------------------------------- /src/core/exceptions/FileNotFoundError.ts: -------------------------------------------------------------------------------- 1 | export default class FileNotFoundError extends Error { 2 | 3 | constructor(message: string = 'File not found') { 4 | super(message); 5 | this.name = 'FileNotFoundError'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/exceptions/ModelNotFound.ts: -------------------------------------------------------------------------------- 1 | export default class ModelNotFound extends Error { 2 | 3 | constructor(message: string = 'Model not found') { 4 | super(message); 5 | this.name = 'ModelNotFound'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/exceptions/UnexpectedAttributeError.ts: -------------------------------------------------------------------------------- 1 | export default class UnexpectedAttributeError extends Error { 2 | 3 | constructor(message: string = 'Unexpected attribute') { 4 | super(message); 5 | this.name = 'UnexpectedAttributeError'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/exceptions/UninitializedContainerError.ts: -------------------------------------------------------------------------------- 1 | export default class UninitializedContainerError extends Error { 2 | 3 | constructor(name: string) { 4 | super(`Container '${name}' has not been initalized`); 5 | this.name = 'UninitializedContainerError'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/exceptions/ValidationError.ts: -------------------------------------------------------------------------------- 1 | export default class ValidationError extends Error { 2 | 3 | constructor(message: string) { 4 | super(message); 5 | this.name = 'ValidationError'; 6 | } 7 | 8 | } -------------------------------------------------------------------------------- /src/core/interfaces/ClassConstructor.t.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Represents a constructor type for classes that can be instantiated. 3 | * 4 | * @description 5 | * TClassConstructor is a generic type that describes the shape of a class constructor. 6 | * It can be used to pass references to classes that can be initialized with the 'new' keyword. 7 | * 8 | * @typeparam T - The type of the instance that will be created by the constructor. 9 | * Defaults to 'any' if not specified. 10 | * 11 | * @example 12 | * class MyClass {} 13 | * 14 | * function createInstance(ctor: TClassConstructor): T { 15 | * return new ctor(); 16 | * } 17 | * 18 | * const instance = createInstance(MyClass); 19 | */ 20 | // eslint-disable-next-line no-unused-vars 21 | export type TClassConstructor = new (...args: any[]) => T -------------------------------------------------------------------------------- /src/core/interfaces/IEnvService.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | export interface IEnvService { 3 | envPath: string; 4 | envExamplePath: string 5 | updateValues(props: Record, filePath?: string): Promise; 6 | fetchAndUpdateContent(filePath: string, props: Record): Promise; 7 | readFileContents(filePath: string): Promise; 8 | copyFileFromEnvExample(from?: string, to?: string): void; 9 | } -------------------------------------------------------------------------------- /src/core/interfaces/IFactory.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { IModel } from "@src/core/domains/models/interfaces/IModel"; 3 | 4 | export type FactoryConstructor = { 5 | new (...args: any[]): IFactory 6 | } 7 | 8 | export default interface IFactory { 9 | create(...args: any[]): Model; 10 | make(data?: Model['attributes']): Model; 11 | getDefinition(): Model['attributes']; 12 | 13 | } -------------------------------------------------------------------------------- /src/core/interfaces/IProvider.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Interface for providers 3 | * 4 | * Providers are responsible for setting up services and configurations 5 | * for the application. They are registered in the App container and 6 | * can be booted when the application is ready. 7 | * 8 | * @interface IProvider 9 | */ 10 | export interface IProvider { 11 | 12 | /** 13 | * Registers the provider 14 | * 15 | * Called when the provider is being registered 16 | * Use this method to set up any initial configurations or services 17 | * 18 | * @returns {Promise} 19 | */ 20 | register(): Promise; 21 | 22 | /** 23 | * Boots the provider 24 | * 25 | * Called after all providers have been registered 26 | * Use this method to perform any actions that require other services to be available 27 | * 28 | * @returns {Promise} 29 | */ 30 | boot(): Promise; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/core/interfaces/IQueable.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Options for queuing a job. 3 | * 4 | * @property {string} queue - The name of the queue to use. 5 | */ 6 | export default interface IQueableOptions { 7 | 8 | /** 9 | * The name of the queue to use. 10 | */ 11 | queue: string; 12 | } 13 | -------------------------------------------------------------------------------- /src/core/interfaces/IService.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | /** 3 | * A service is a class that provides a specific functionality to the application. 4 | * It usually has a single responsibility and can be used by other services or controllers. 5 | * 6 | * @template T - The type of the service. 7 | */ 8 | export type ServiceConstructor = new (...args: any[]) => T; 9 | 10 | /** 11 | * The base interface for all services. 12 | * It provides a single method, `getConfig`, which returns the configuration for the service. 13 | */ 14 | export default interface IService { 15 | 16 | /** 17 | * Returns the configuration for the service. 18 | * 19 | * @param args - The arguments to pass to the service. 20 | * @returns The configuration for the service. 21 | */ 22 | getConfig(...args: any[]): any; 23 | } 24 | -------------------------------------------------------------------------------- /src/core/interfaces/concerns/IDispatchable.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | export interface IDispatchable 3 | { 4 | dispatch(...args: unknown[]): Promise; 5 | } -------------------------------------------------------------------------------- /src/core/interfaces/concerns/IExecutable.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | export interface IExecutable 3 | { 4 | execute(...args: any[]): Promise; 5 | } -------------------------------------------------------------------------------- /src/core/interfaces/concerns/IHasAttributes.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | import { IModelAttributes } from "@src/core/domains/models/interfaces/IModel"; 3 | 4 | 5 | export interface IHasAttributesSetAttributeOptions { 6 | broadcast: boolean; 7 | } 8 | 9 | export interface IHasAttributes { 10 | 11 | attributes: Attributes | null; 12 | 13 | original: Attributes | null; 14 | 15 | attrSync(key: K, value?: unknown): Attributes[K] | null | undefined; 16 | 17 | setAttribute(key: keyof Attributes, value?: unknown): Promise; 18 | 19 | getAttributeSync(key: K): Attributes[K] | null 20 | 21 | getAttributes(...args: any[]): Attributes | null; 22 | 23 | getOriginal(key: keyof Attributes): Attributes[keyof Attributes] | null 24 | 25 | getDirty(): Record | null 26 | 27 | isDirty(): boolean; 28 | } -------------------------------------------------------------------------------- /src/core/interfaces/concerns/IHasConfigConcern.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | export interface IHasConfigConcern 3 | { 4 | getConfig(): T; 5 | 6 | setConfig(config: TConfig): void; 7 | } -------------------------------------------------------------------------------- /src/core/interfaces/concerns/INameable.ts: -------------------------------------------------------------------------------- 1 | export interface INameable { 2 | getName(): string; 3 | } -------------------------------------------------------------------------------- /src/core/models/broadcast/AttributeChangeListener.ts: -------------------------------------------------------------------------------- 1 | import BroadcastListener from "@src/core/domains/broadcast/abstract/BroadcastEvent"; 2 | import { IModelAttributes } from "@src/core/domains/models/interfaces/IModel"; 3 | 4 | export type AttributeChangePayload = { 5 | key: string; 6 | value: unknown; 7 | attributes: IModelAttributes; 8 | } 9 | 10 | /** 11 | * Event payload for attribute change events. 12 | * @typedef {Object} AttributeChangePayload 13 | * @property {string} key - The key of the attribute that changed. 14 | * @property {unknown} value - The new value of the attribute. 15 | * @property {IModelAttributes} attributes - The current attributes of the model. 16 | */ 17 | class AttributeChangeListener extends BroadcastListener { 18 | 19 | getListenerName(): string { 20 | return 'onAttributeChange' 21 | } 22 | 23 | getPayload() { 24 | return this.payload 25 | } 26 | 27 | } 28 | 29 | export default AttributeChangeListener -------------------------------------------------------------------------------- /src/core/util/captureError.ts: -------------------------------------------------------------------------------- 1 | import { AppSingleton } from "@src/core/services/App" 2 | 3 | const captureError = async (callbackFn: () => Promise): Promise => { 4 | try { 5 | return await callbackFn() 6 | } 7 | catch (err) { 8 | if (err instanceof Error && err?.message) { 9 | AppSingleton.container('logger').error(`): `, err.message, err.stack) 10 | } 11 | throw err 12 | } 13 | } 14 | 15 | export default captureError -------------------------------------------------------------------------------- /src/core/util/castObject.ts: -------------------------------------------------------------------------------- 1 | import { TCasts } from "@src/core/domains/cast/interfaces/IHasCastableConcern"; 2 | import BaseCastable from "@src/core/domains/cast/base/BaseCastable"; 3 | 4 | const castObject = (data: unknown, casts: TCasts): ReturnType => { 5 | return new BaseCastable().getCastFromObject(data as Record, casts) 6 | } 7 | 8 | export default castObject -------------------------------------------------------------------------------- /src/core/util/compose.ts: -------------------------------------------------------------------------------- 1 | import { TClassConstructor } from "@src/core/interfaces/ClassConstructor.t"; 2 | 3 | const compose = (BaseClass: TClassConstructor, ...mixins) => { 4 | return mixins.reduce((Class, mixinFunc) => mixinFunc(Class), BaseClass); 5 | } 6 | 7 | export default compose -------------------------------------------------------------------------------- /src/core/util/deepClone.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash' 2 | 3 | export function deepClone(obj: T): T { 4 | return _.cloneDeep(obj) as T 5 | } -------------------------------------------------------------------------------- /src/core/util/minExecTime.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars 2 | type MinExecTimeFn = (...args: any[]) => Promise; 3 | 4 | const minExecTime = async (minMs: number, fn: MinExecTimeFn): Promise => { 5 | const start = Date.now(); 6 | const result = await fn(); 7 | const end = Date.now(); 8 | const duration = end - start; 9 | 10 | if (duration < minMs) { 11 | await sleepMs(minMs - duration); 12 | } 13 | 14 | return result; 15 | } 16 | 17 | const sleepMs = (ms: number) => { 18 | return new Promise(resolve => setTimeout(resolve, ms)); 19 | } 20 | 21 | export default minExecTime; 22 | -------------------------------------------------------------------------------- /src/core/util/parseBooleanFromString.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Parse a boolean value from a string. 3 | * 4 | * If the value is undefined, the defaultValue is returned. 5 | * 6 | * @param {string | undefined} value The value to parse. 7 | * @param {boolean} defaultValue The default value to return if the value is undefined. 8 | * @returns {boolean} The parsed boolean value. 9 | */ 10 | export default (value: string | undefined, defaultValue: 'true' | 'false'): boolean => { 11 | if (value === undefined) { 12 | return defaultValue === 'true'; 13 | } 14 | 15 | return value === 'true'; 16 | }; 17 | -------------------------------------------------------------------------------- /src/core/util/prettifyStack.ts: -------------------------------------------------------------------------------- 1 | export const prettifyStack = (str?: string) => str ? str?.split('\n').map(line => line.trim()) : undefined -------------------------------------------------------------------------------- /src/core/util/replaceEnvValue.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Replaces a property in the .env content string with a new value 4 | * 5 | * @param {string} key The key of the property to replace 6 | * @param {string} value The new value to set 7 | * @param {string} content The entire .env file content as a string 8 | * @returns {string} The updated content string 9 | */ 10 | const replaceEnvValue = (key: string, value: string, content: string): string => { 11 | const pattern = new RegExp(`(${key}=.*)`, 'g'); 12 | let match; 13 | while ((match = pattern.exec(content)) !== null) { 14 | content = content.replace(match[0], `${key}=${value}`); 15 | } 16 | 17 | return content; 18 | } 19 | 20 | export default replaceEnvValue; 21 | 22 | -------------------------------------------------------------------------------- /src/core/util/returns/returnOrThrow.ts: -------------------------------------------------------------------------------- 1 | export type ReturnOrThrow = { 2 | shouldThrow: boolean; 3 | throwable: Error; 4 | returns?: T; 5 | } 6 | 7 | 8 | /** 9 | * @typedef {Object} ReturnOrThrow 10 | * @property {boolean} shouldThrow - Whether to throw an error or not 11 | * @property {Error} throwable - The error to throw 12 | * @property {T} [returns] - The value to return 13 | */ 14 | 15 | /** 16 | * Returns a value or throws an error 17 | * @param {ReturnOrThrow} param0 - The options to return or throw 18 | * @returns {T} The value to return 19 | */ 20 | const returnOrThrow = ({ shouldThrow, throwable, returns }: ReturnOrThrow): T => { 21 | if (shouldThrow && throwable) { 22 | throw throwable; 23 | } 24 | if (returns !== undefined) { 25 | return returns; 26 | } 27 | throw new Error('Invalid properties'); 28 | }; 29 | 30 | export default returnOrThrow; 31 | -------------------------------------------------------------------------------- /src/core/util/str/forceString.ts: -------------------------------------------------------------------------------- 1 | const forceString = (value: unknown): string => { 2 | if (typeof value === 'string') { 3 | return value 4 | } 5 | 6 | return String(value) 7 | } 8 | 9 | 10 | export default forceString 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/core/util/uuid/generateUuidV4.ts: -------------------------------------------------------------------------------- 1 | import { v4 } from 'uuid'; 2 | 3 | /** 4 | * Generates a random UUID v4. 5 | * 6 | * @returns {string} A random UUID v4. 7 | */ 8 | export const generateUuidV4 = (): string => v4(); 9 | -------------------------------------------------------------------------------- /src/core/utility/uuid.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Utility functions for UUID validation and manipulation 3 | */ 4 | 5 | // UUID v4 regex pattern 6 | const UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; 7 | 8 | /** 9 | * Checks if a given value is a valid UUID v4 string 10 | * @param value The value to check 11 | * @returns boolean indicating if the value is a valid UUID v4 12 | */ 13 | export function isUuid(value: unknown): boolean { 14 | if (value === undefined || value === null) { 15 | return false; 16 | } 17 | 18 | if (typeof value !== 'string') { 19 | return false; 20 | } 21 | 22 | return UUID_V4_REGEX.test(value); 23 | } 24 | 25 | /** 26 | * Validates a UUID and throws an error if invalid 27 | * @param value The UUID to validate 28 | * @throws Error if the UUID is invalid 29 | */ 30 | export function validateUuid(value: unknown): void { 31 | if (!isUuid(value)) { 32 | throw new Error('Invalid UUID format'); 33 | } 34 | } 35 | 36 | export default { 37 | isUuid, 38 | validateUuid 39 | }; -------------------------------------------------------------------------------- /src/setup.ts: -------------------------------------------------------------------------------- 1 | import 'tsconfig-paths/register'; 2 | 3 | import appConfig from '@src/config/app.config'; 4 | import ConsoleProvider from '@src/core/domains/console/providers/ConsoleProvider'; 5 | import CryptoProvider from '@src/core/domains/crypto/providers/CryptoProvider'; 6 | import DatabaseSetupProvider from '@src/core/domains/database/providers/DatabaseSetupProvider'; 7 | import LoggerProvider from '@src/core/domains/logger/providers/LoggerProvider'; 8 | import SetupProvider from '@src/core/domains/setup/providers/SetupProvider'; 9 | import Kernel from "@src/core/Kernel"; 10 | import { AppSingleton } from '@src/core/services/App'; 11 | 12 | (async () => { 13 | require('dotenv').config(); 14 | 15 | await Kernel.boot({ 16 | ...appConfig, 17 | environment: 'testing', 18 | providers: [ 19 | new LoggerProvider(), 20 | new ConsoleProvider(), 21 | new DatabaseSetupProvider(), 22 | new CryptoProvider(), 23 | new SetupProvider() 24 | ] 25 | }, {}) 26 | 27 | await AppSingleton.container('console').readerService(['app:setup']).handle(); 28 | })(); -------------------------------------------------------------------------------- /src/tests/app/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-shepherd/larascript-framework/1471b26b1184603264c1bdf35d3b8d39d190989d/src/tests/app/.gitkeep -------------------------------------------------------------------------------- /src/tests/larascript/config/testConfig.ts: -------------------------------------------------------------------------------- 1 | import { IAppConfig } from '@src/config/app.config'; 2 | import { EnvironmentTesting } from '@src/core/consts/Environment'; 3 | 4 | require('dotenv').config(); 5 | 6 | const testAppConfig: IAppConfig = { 7 | appKey: 'test', 8 | env: EnvironmentTesting 9 | }; 10 | 11 | export default testAppConfig; 12 | -------------------------------------------------------------------------------- /src/tests/larascript/events/events/TestEventQueueAddAlwaysFailsEventToQueue.ts: -------------------------------------------------------------------------------- 1 | 2 | import BaseEvent from "@src/core/domains/events/base/BaseEvent"; 3 | import QueueableDriver from "@src/core/domains/events/drivers/QueableDriver"; 4 | import EventRegistry from "@src/core/domains/events/registry/EventRegistry"; 5 | import { AppSingleton } from "@src/core/services/App"; 6 | import TestEventQueueAlwaysFailsEvent from "@src/tests/larascript/events/events/TestEventQueueAlwaysFailsEvent"; 7 | 8 | 9 | class TestEventQueueAddAlwaysFailsEventToQueue extends BaseEvent { 10 | 11 | protected namespace: string = 'testing'; 12 | 13 | constructor() { 14 | super(null, QueueableDriver) 15 | } 16 | 17 | getQueueName(): string { 18 | return 'testQueue'; 19 | } 20 | 21 | async execute(): Promise { 22 | console.log('Executed TestEventQueueAddAlwaysFailsEventToQueue', this.getPayload(), this.getName()) 23 | 24 | await AppSingleton.container('events').dispatch(new TestEventQueueAlwaysFailsEvent()) 25 | } 26 | 27 | } 28 | 29 | export default EventRegistry.register(TestEventQueueAddAlwaysFailsEventToQueue) -------------------------------------------------------------------------------- /src/tests/larascript/events/events/TestEventQueueAlwaysFailsEvent.ts: -------------------------------------------------------------------------------- 1 | 2 | import BaseEvent from "@src/core/domains/events/base/BaseEvent"; 3 | import EventRegistry from "@src/core/domains/events/registry/EventRegistry"; 4 | 5 | 6 | class TestEventQueueAlwaysFailsEvent extends BaseEvent { 7 | 8 | protected namespace: string = 'testing'; 9 | 10 | getQueueName(): string { 11 | return 'testQueue'; 12 | } 13 | 14 | async execute(): Promise { 15 | console.log('Executed TestEventQueueAlwaysFailsEvent', this.getPayload(), this.getName()) 16 | throw new Error('Always fails'); 17 | } 18 | 19 | } 20 | 21 | export default EventRegistry.register(TestEventQueueAlwaysFailsEvent) -------------------------------------------------------------------------------- /src/tests/larascript/events/events/TestEventQueueCalledFromWorkerEvent.ts: -------------------------------------------------------------------------------- 1 | 2 | import { TCasts } from "@src/core/domains/cast/interfaces/IHasCastableConcern"; 3 | import BaseEvent from "@src/core/domains/events/base/BaseEvent"; 4 | import SyncDriver from "@src/core/domains/events/drivers/SyncDriver"; 5 | import EventRegistry from "@src/core/domains/events/registry/EventRegistry"; 6 | 7 | class TestEventQueueCalledFromWorkerEvent extends BaseEvent { 8 | 9 | protected namespace: string = 'testing'; 10 | 11 | casts: TCasts = { 12 | createdAt: "date" 13 | } 14 | 15 | constructor(payload) { 16 | super(payload, SyncDriver) 17 | } 18 | 19 | } 20 | 21 | export default EventRegistry.register(TestEventQueueCalledFromWorkerEvent) -------------------------------------------------------------------------------- /src/tests/larascript/events/events/TestEventQueueEvent.ts: -------------------------------------------------------------------------------- 1 | 2 | import BaseEvent from "@src/core/domains/events/base/BaseEvent"; 3 | import QueueableDriver from "@src/core/domains/events/drivers/QueableDriver"; 4 | import EventRegistry from "@src/core/domains/events/registry/EventRegistry"; 5 | import { events } from "@src/core/domains/events/services/EventService"; 6 | import TestEventQueueCalledFromWorkerEvent from "@src/tests/larascript/events/events/TestEventQueueCalledFromWorkerEvent"; 7 | 8 | class TestEventQueueEvent extends BaseEvent { 9 | 10 | protected namespace: string = 'testing'; 11 | 12 | constructor(payload) { 13 | super(payload, QueueableDriver) 14 | } 15 | 16 | getQueueName(): string { 17 | return 'testQueue'; 18 | } 19 | 20 | async execute(): Promise { 21 | console.log('Executed TestEventQueueEvent', this.getPayload(), this.getName()) 22 | events().dispatch(new TestEventQueueCalledFromWorkerEvent(this.getPayload())) 23 | } 24 | 25 | } 26 | 27 | export default EventRegistry.register(TestEventQueueEvent) -------------------------------------------------------------------------------- /src/tests/larascript/events/events/TestEventSyncBadPayloadEvent.ts: -------------------------------------------------------------------------------- 1 | 2 | import BaseEvent from "@src/core/domains/events/base/BaseEvent"; 3 | import EventRegistry from "@src/core/domains/events/registry/EventRegistry"; 4 | 5 | 6 | class TestEventSyncBadPayloadEvent extends BaseEvent { 7 | 8 | protected namespace: string = 'testing'; 9 | 10 | constructor(payload) { 11 | super(payload); 12 | } 13 | 14 | async execute(): Promise { 15 | console.log('Executed TestEventSyncBadPayloadEvent', this.getPayload(), this.getName()) 16 | } 17 | 18 | getName(): string { 19 | return 'TestEventSyncBadPayloadEvent' 20 | } 21 | 22 | } 23 | 24 | export default EventRegistry.register(TestEventSyncBadPayloadEvent) -------------------------------------------------------------------------------- /src/tests/larascript/events/events/TestEventSyncEvent.ts: -------------------------------------------------------------------------------- 1 | 2 | import BaseEvent from "@src/core/domains/events/base/BaseEvent"; 3 | import EventRegistry from "@src/core/domains/events/registry/EventRegistry"; 4 | 5 | 6 | class TestEventSyncEvent extends BaseEvent<{hello: string}> { 7 | 8 | protected namespace: string = 'testing'; 9 | 10 | async execute(): Promise { 11 | console.log('Executed TestEventSyncEvent', this.getPayload(), this.getName()) 12 | } 13 | 14 | } 15 | 16 | export default EventRegistry.register(TestEventSyncEvent) -------------------------------------------------------------------------------- /src/tests/larascript/events/events/auth/TestUserCreatedListener.ts: -------------------------------------------------------------------------------- 1 | import BaseEventListener from "@src/core/domains/events/base/BaseEventListener"; 2 | import EventRegistry from "@src/core/domains/events/registry/EventRegistry"; 3 | 4 | class TestUserCreatedListener extends BaseEventListener { 5 | 6 | protected namespace: string = 'testing'; 7 | 8 | async execute(): Promise { 9 | console.log('Executed TestUserCreatedListener', this.getPayload(), this.getName()) 10 | } 11 | 12 | } 13 | 14 | export default EventRegistry.registerListener(TestUserCreatedListener) -------------------------------------------------------------------------------- /src/tests/larascript/events/events/auth/TestUserCreatedSubscriber.ts: -------------------------------------------------------------------------------- 1 | import BaseEventSubscriber from "@src/core/domains/events/base/BaseEventSubciber"; 2 | import SyncDriver from "@src/core/domains/events/drivers/SyncDriver"; 3 | import EventRegistry from "@src/core/domains/events/registry/EventRegistry"; 4 | 5 | class TestUserCreatedSubscriber extends BaseEventSubscriber { 6 | 7 | protected namespace: string = 'testing'; 8 | 9 | constructor(payload) { 10 | super(payload, SyncDriver); 11 | } 12 | 13 | getQueueName(): string { 14 | return 'default'; 15 | } 16 | 17 | async execute(): Promise { 18 | const payload = this.getPayload(); 19 | 20 | console.log('User was created', payload); 21 | } 22 | 23 | } 24 | 25 | export default EventRegistry.registerSubscriber(TestUserCreatedSubscriber) -------------------------------------------------------------------------------- /src/tests/larascript/events/listeners/TestListener.ts: -------------------------------------------------------------------------------- 1 | import BaseEventListener from "@src/core/domains/events/base/BaseEventListener"; 2 | import SyncDriver from "@src/core/domains/events/drivers/SyncDriver"; 3 | import EventRegistry from "@src/core/domains/events/registry/EventRegistry"; 4 | 5 | class TestListener extends BaseEventListener { 6 | 7 | constructor(payload: { hello: string }) { 8 | super(payload, SyncDriver); 9 | } 10 | 11 | 12 | async execute(): Promise { 13 | console.log('Executed TestListener', this.getPayload(), this.getName()); 14 | } 15 | 16 | } 17 | 18 | export default EventRegistry.register(TestListener) -------------------------------------------------------------------------------- /src/tests/larascript/events/subscribers/TestSubscriber.ts: -------------------------------------------------------------------------------- 1 | import BaseEventSubscriber from "@src/core/domains/events/base/BaseEventSubciber"; 2 | import EventRegistry from "@src/core/domains/events/registry/EventRegistry"; 3 | import TestEventSyncEvent from "@src/tests/larascript/events/events/TestEventSyncEvent"; 4 | 5 | 6 | class TestSubscriber extends BaseEventSubscriber { 7 | 8 | async execute(): Promise { 9 | console.log('Executed TestSubscriber', this.getPayload(), this.getName()) 10 | 11 | this.getEventService().dispatch(new TestEventSyncEvent(this.getPayload() as { hello: string })); 12 | } 13 | 14 | } 15 | 16 | export default EventRegistry.register(TestSubscriber) -------------------------------------------------------------------------------- /src/tests/larascript/factory/TestMovieFakerFactory.ts: -------------------------------------------------------------------------------- 1 | import Factory from "@src/core/base/Factory"; 2 | import { TestMovieModel } from "@src/tests/larascript/models/models/TestMovie"; 3 | 4 | class TestMovieFactory extends Factory { 5 | 6 | protected model = TestMovieModel; 7 | 8 | getDefinition(): TestMovieModel['attributes'] { 9 | return { 10 | authorId: this.faker.number.int({ min: 1, max: 100 }).toString(), 11 | name: this.faker.person.fullName(), 12 | yearReleased: this.faker.number.int({ min: 1900, max: 2000 }), 13 | createdAt: this.faker.date.past(), 14 | updatedAt: this.faker.date.recent() 15 | } 16 | } 17 | 18 | } 19 | 20 | export default TestMovieFactory -------------------------------------------------------------------------------- /src/tests/larascript/factory/TestUserFactory.ts: -------------------------------------------------------------------------------- 1 | import Factory from '@src/core/base/Factory'; 2 | import { IModel, IModelAttributes, ModelConstructor } from '@src/core/domains/models/interfaces/IModel'; 3 | import TestUser from '@src/tests/larascript/models/models/TestUser'; 4 | 5 | /** 6 | * Factory for creating User models. 7 | * 8 | * @class UserFactory 9 | * @extends {Factory} 10 | */ 11 | export default class TestUserFactory extends Factory { 12 | 13 | protected model: ModelConstructor> = TestUser; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/tests/larascript/helpers/parsePostgresConnectionUrl.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from '@jest/globals'; 2 | import ParsePostgresConnectionUrl from '@src/core/domains/postgres/helper/ParsePostgresConnectionUrl'; 3 | 4 | describe('test parsing a postgres connection string', () => { 5 | 6 | test('parse a postgres connection string', () => { 7 | const exampleConnectionString = 'postgres://username:password@localhost:5432/database'; 8 | 9 | const parsedConnectionString = ParsePostgresConnectionUrl.parse(exampleConnectionString); 10 | 11 | expect(parsedConnectionString).toEqual({ 12 | host: 'localhost', 13 | port: 5432, 14 | username: 'username', 15 | password: 'password', 16 | database: 'database' 17 | }) 18 | }) 19 | 20 | }); -------------------------------------------------------------------------------- /src/tests/larascript/migration/migrations/test-create-api-token-table.ts: -------------------------------------------------------------------------------- 1 | import ApiToken from "@src/core/domains/auth/models/ApiToken"; 2 | import BaseMigration from "@src/core/domains/migrations/base/BaseMigration"; 3 | import { DataTypes } from "sequelize"; 4 | 5 | export class CreateApiTokenMigration extends BaseMigration { 6 | 7 | // Specify the database provider if this migration should run on a particular database. 8 | // Uncomment and set to 'mongodb', 'postgres', or another supported provider. 9 | // If left commented out, the migration will run only on the default provider. 10 | // databaseProvider: 'mongodb' | 'postgres' = 'postgres'; 11 | 12 | group?: string = 'testing'; 13 | 14 | table = ApiToken.getTable() 15 | 16 | async up(): Promise { 17 | await this.schema.createTable(this.table, { 18 | userId: DataTypes.STRING, 19 | token: DataTypes.STRING, 20 | scopes: DataTypes.JSON, 21 | revokedAt: DataTypes.DATE 22 | }) 23 | } 24 | 25 | async down(): Promise { 26 | await this.schema.dropTable(this.table); 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /src/tests/larascript/migration/migrations/test-migration.ts: -------------------------------------------------------------------------------- 1 | import BaseMigration from "@src/core/domains/migrations/base/BaseMigration"; 2 | import { AppSingleton } from "@src/core/services/App"; 3 | import { DataTypes } from "sequelize"; 4 | 5 | class TestMigration extends BaseMigration { 6 | 7 | group?: string = 'testing'; 8 | 9 | async up(): Promise { 10 | await AppSingleton.container('db').schema().createTable('tests', { 11 | name: DataTypes.STRING, 12 | age: DataTypes.INTEGER 13 | }) 14 | } 15 | 16 | async down(): Promise { 17 | await AppSingleton.container('db').schema().dropTable('tests') 18 | } 19 | 20 | } 21 | 22 | export default TestMigration -------------------------------------------------------------------------------- /src/tests/larascript/migration/models/TestMigrationModel.ts: -------------------------------------------------------------------------------- 1 | import MigrationModel, { MigrationModelData } from "@src/core/domains/migrations/models/MigrationModel"; 2 | 3 | /** 4 | * Model for test migrations stored in the database. 5 | */ 6 | class TestMigrationModel extends MigrationModel { 7 | 8 | constructor(data: MigrationModelData | null) { 9 | super(data); 10 | } 11 | 12 | } 13 | 14 | /** 15 | * The default migration model. 16 | */ 17 | export default TestMigrationModel 18 | -------------------------------------------------------------------------------- /src/tests/larascript/migration/seeders/test-seeder-model.ts: -------------------------------------------------------------------------------- 1 | import BaseSeeder from "@src/core/domains/migrations/base/BaseSeeder"; 2 | import { SeederTestModel } from "@src/tests/larascript/migration/seeder.test"; 3 | 4 | export class Seeder extends BaseSeeder { 5 | 6 | group?: string = 'testing'; 7 | 8 | async up(): Promise { 9 | 10 | const john = SeederTestModel.create({ name: 'John' }) 11 | await john.save(); 12 | 13 | const jane = SeederTestModel.create({ name: 'Jane' }) 14 | await jane.save(); 15 | 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /src/tests/larascript/models/modelAttr.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | import { describe, expect, test } from '@jest/globals'; 3 | import TestModel from '@src/tests/larascript/models/models/TestModel'; 4 | import testHelper from '@src/tests/testHelper'; 5 | 6 | describe('test model attr', () => { 7 | 8 | beforeAll(async () => { 9 | await testHelper.testBootApp() 10 | }) 11 | 12 | test('attr', async () => { 13 | const model = new TestModel({ 14 | name: 'John' 15 | }); 16 | expect(await model.attr('name')).toEqual('John'); 17 | 18 | await model.attr('name', 'Jane'); 19 | expect(await model.attr('name')).toEqual('Jane'); 20 | 21 | const modelNoProperties = new TestModel(null); 22 | await modelNoProperties.attr('name', 'John') 23 | expect(await modelNoProperties.attr('name')).toEqual('John'); 24 | }) 25 | }); -------------------------------------------------------------------------------- /src/tests/larascript/models/models/TestApiTokenModel.ts: -------------------------------------------------------------------------------- 1 | import ApiToken, { ApiTokenAttributes } from '@src/core/domains/auth/models/ApiToken'; 2 | import TestUser from '@src/tests/larascript/models/models/TestUser'; 3 | 4 | class TestApiTokenModel extends ApiToken { 5 | 6 | table: string = 'api_tokens'; 7 | 8 | constructor(data: ApiTokenAttributes | null = null) { 9 | super(data); 10 | this.setUserModelCtor(TestUser) 11 | } 12 | 13 | } 14 | 15 | export default TestApiTokenModel 16 | -------------------------------------------------------------------------------- /src/tests/larascript/models/models/TestFailedWorkerModel.ts: -------------------------------------------------------------------------------- 1 | import { FailedWorkerModelAttributes } from "@src/core/domains/events/interfaces/IEventWorkerConcern"; 2 | import FailedWorkerModel from "@src/core/domains/events/models/FailedWorkerModel"; 3 | 4 | export default class TestFailedWorkerModel extends FailedWorkerModel { 5 | 6 | public table: string = 'testsWorkerFailed' 7 | 8 | constructor(data: FailedWorkerModelAttributes | null = null) { 9 | super(data ?? {} as FailedWorkerModelAttributes) 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /src/tests/larascript/models/models/TestModel.ts: -------------------------------------------------------------------------------- 1 | import Model from "@src/core/domains/models/base/Model"; 2 | import { IModelAttributes } from "@src/core/domains/models/interfaces/IModel"; 3 | 4 | export interface TestModelData extends IModelAttributes { 5 | name: string 6 | } 7 | 8 | class TestModel extends Model { 9 | 10 | public table: string = 'tests'; 11 | 12 | public fields: string[] = [ 13 | 'name', 14 | 'createdAt', 15 | 'updatedAt' 16 | ] 17 | 18 | } 19 | 20 | export default TestModel -------------------------------------------------------------------------------- /src/tests/larascript/models/models/TestMovie.ts: -------------------------------------------------------------------------------- 1 | import Model from "@src/core/domains/models/base/Model"; 2 | import { IModelAttributes } from "@src/core/domains/models/interfaces/IModel"; 3 | import TestMovieFactory from "@src/tests/larascript/factory/TestMovieFakerFactory"; 4 | 5 | export interface TestMovieModelData extends IModelAttributes { 6 | authorId?: string; 7 | name?: string; 8 | yearReleased?: number; 9 | } 10 | export class TestMovieModel extends Model { 11 | 12 | protected factory = TestMovieFactory; 13 | 14 | public table: string = 'tests'; 15 | 16 | public fields: string[] = [ 17 | 'authorId', 18 | 'name', 19 | 'yearReleased', 20 | 'createdAt', 21 | 'updatedAt' 22 | ] 23 | 24 | } -------------------------------------------------------------------------------- /src/tests/larascript/models/models/TestUser.ts: -------------------------------------------------------------------------------- 1 | import User, { UserAttributes } from "@src/app/models/auth/User"; 2 | import { IModelEvents } from "@src/core/domains/models/interfaces/IModel"; 3 | import TestUserCreatedListener from "@src/tests/larascript/events/events/auth/TestUserCreatedListener"; 4 | 5 | 6 | /** 7 | * User model 8 | * 9 | * Represents a user in the database. 10 | */ 11 | export default class TestUser extends User { 12 | 13 | public table: string = 'users'; 14 | 15 | constructor(data: UserAttributes | null = null) { 16 | super(data); 17 | } 18 | 19 | protected events?: IModelEvents | undefined = { 20 | created: TestUserCreatedListener 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/tests/larascript/models/models/TestWorkerModel.ts: -------------------------------------------------------------------------------- 1 | import { WorkerModelAttributes } from "@src/core/domains/events/interfaces/IEventWorkerConcern"; 2 | import WorkerModel from "@src/core/domains/events/models/WorkerModel"; 3 | 4 | export default class TestWorkerModel extends WorkerModel { 5 | 6 | table = 'testsWorker' 7 | 8 | constructor(data: WorkerModelAttributes | null = null) { 9 | super(data ?? {} as WorkerModelAttributes) 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /src/tests/larascript/models/observers/TestObserver.ts: -------------------------------------------------------------------------------- 1 | import Observer from "@src/core/domains/observer/services/Observer"; 2 | import { TestObserverModelData } from "@src/tests/larascript/models/models/TestObserverModel"; 3 | 4 | class TestObserver extends Observer { 5 | 6 | async creating(data: TestObserverModelData): Promise { 7 | data.number = 1; 8 | return data 9 | } 10 | 11 | 12 | onNameChange = (attributes: TestObserverModelData) => { 13 | attributes.name = 'Bob' 14 | return attributes; 15 | } 16 | 17 | } 18 | 19 | export default TestObserver -------------------------------------------------------------------------------- /src/tests/larascript/providers/TestConsoleProvider.ts: -------------------------------------------------------------------------------- 1 | import BaseProvider from "@src/core/base/Provider"; 2 | import ConsoleService from "@src/core/domains/console/service/ConsoleService"; 3 | import readline from 'readline'; 4 | 5 | class TestConsoleProvider extends BaseProvider { 6 | 7 | async register(): Promise { 8 | 9 | this.bind('readline', readline.createInterface({ 10 | input: process.stdin, 11 | output: process.stdout, 12 | })); 13 | this.bind('console', new ConsoleService()); 14 | } 15 | 16 | } 17 | 18 | export default TestConsoleProvider -------------------------------------------------------------------------------- /src/tests/larascript/providers/TestCryptoProvider.ts: -------------------------------------------------------------------------------- 1 | import { IAppConfig } from "@src/config/app.config"; 2 | import { EnvironmentTesting } from "@src/core/consts/Environment"; 3 | import CryptoProvider from "@src/core/domains/crypto/providers/CryptoProvider"; 4 | 5 | class TestCryptoProvider extends CryptoProvider { 6 | 7 | config: IAppConfig = { 8 | env: EnvironmentTesting, 9 | appKey: 'test-app-key', 10 | appName: 'Larascript Framework' 11 | } 12 | 13 | } 14 | 15 | export default TestCryptoProvider -------------------------------------------------------------------------------- /src/tests/larascript/providers/TestMigrationProvider.ts: -------------------------------------------------------------------------------- 1 | import { IMigrationConfig } from "@src/core/domains/migrations/interfaces/IMigrationConfig"; 2 | import MigrationProvider from "@src/core/domains/migrations/providers/MigrationProvider"; 3 | import TestMigrationModel from "@src/tests/larascript/migration/models/TestMigrationModel"; 4 | 5 | class TestMigrationProvider extends MigrationProvider { 6 | 7 | protected config: IMigrationConfig = { 8 | keepProcessAlive: true, 9 | schemaMigrationDir: '@src/../src/tests/larascript/migration/migrations', 10 | seederMigrationDir: '@src/../src/tests/larascript/migration/seeders', 11 | modelCtor: TestMigrationModel 12 | } 13 | 14 | } 15 | 16 | export default TestMigrationProvider -------------------------------------------------------------------------------- /src/tests/larascript/repositories/TestApiTokenRepository.ts: -------------------------------------------------------------------------------- 1 | import ApiTokenRepository from "@src/core/domains/auth/repository/ApiTokenRepitory"; 2 | import TestApiTokenModel from "@src/tests/larascript/models/models/TestApiTokenModel"; 3 | 4 | 5 | export default class TestApiTokenRepository extends ApiTokenRepository { 6 | 7 | constructor() { 8 | super() 9 | this.setModelCtor(TestApiTokenModel) 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /src/tests/larascript/repositories/TestPeopleRepository.ts: -------------------------------------------------------------------------------- 1 | import Repository from "@src/core/base/Repository"; 2 | import TestPeopleModel from "@src/tests/larascript/eloquent/models/TestPeopleModel"; 3 | export default class TestPeopleRepository extends Repository { 4 | 5 | constructor(connectionName?: string) { 6 | super(TestPeopleModel, connectionName) 7 | } 8 | 9 | /** 10 | * Finds one record with name equal to 'Jane' 11 | * @returns {Promise} 12 | */ 13 | findOneJane() { 14 | return this.findOne({ 15 | name: 'Jane' 16 | }) 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/tests/larascript/repositories/TestUserRepository.ts: -------------------------------------------------------------------------------- 1 | import UserRepository from "@src/app/repositories/auth/UserRepository"; 2 | import TestUser from "@src/tests/larascript/models/models/TestUser"; 3 | 4 | 5 | export default class TestUserRepository extends UserRepository { 6 | 7 | constructor() { 8 | super() 9 | this.setModelCtor(TestUser) 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /src/tests/larascript/validator/rules/validatorNumber.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | import { describe } from '@jest/globals'; 3 | import IsNumber from '@src/core/domains/validator/rules/NumberRule'; 4 | import Validator from '@src/core/domains/validator/service/Validator'; 5 | 6 | describe('test validation', () => { 7 | 8 | test('isNumber, passes', async () => { 9 | const validator = Validator.make({ 10 | numberField: [new IsNumber()] 11 | }) 12 | 13 | const result = await validator.validate({ 14 | numberField: 123 15 | }) 16 | 17 | 18 | expect(result.passes()).toBe(true) 19 | }) 20 | 21 | test('isNumber, fails with non-number value', async () => { 22 | const validator = Validator.make({ 23 | numberField: [new IsNumber()] 24 | }) 25 | 26 | const result = await validator.validate({ 27 | objectField: 'non-number' 28 | }) 29 | 30 | expect(result.passes()).toBe(false) 31 | }) 32 | 33 | }); -------------------------------------------------------------------------------- /src/tests/larascript/validator/validators/TestUpdateUserValidator.ts: -------------------------------------------------------------------------------- 1 | import BaseCustomValidator from "@src/core/domains/validator/base/BaseCustomValidator"; 2 | import { IRulesObject } from "@src/core/domains/validator/interfaces/IRule"; 3 | import MinRule from "@src/core/domains/validator/rules/MinRule"; 4 | import NullableRule from "@src/core/domains/validator/rules/NullableRule"; 5 | import StringRule from "@src/core/domains/validator/rules/StringRule"; 6 | 7 | class TestUpdateUserValidator extends BaseCustomValidator { 8 | 9 | protected rules: IRulesObject = { 10 | password: [new NullableRule(), new MinRule(6)], 11 | firstName: [new NullableRule(), new StringRule()], 12 | lastName: [new NullableRule(), new StringRule()] 13 | } 14 | 15 | } 16 | 17 | export default TestUpdateUserValidator -------------------------------------------------------------------------------- /src/tinker.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | 3 | import 'dotenv/config'; 4 | import 'tsconfig-paths/register'; 5 | 6 | import TinkerService from '@src/core/domains/tinker/services/TinkerService'; 7 | import { app } from '@src/core/services/App'; 8 | 9 | 10 | 11 | (async () => { 12 | 13 | // Boot the application 14 | await TinkerService.boot({ 15 | useTestDb: false 16 | }); 17 | 18 | // Useful services for debugging 19 | const db = app('db'); 20 | const events = app('events'); 21 | const logger = app('logger'); 22 | const query = app('query'); 23 | const validator = app('validatorFn'); 24 | 25 | // Add your code here 26 | 27 | })(); -------------------------------------------------------------------------------- /storage/tmp/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-shepherd/larascript-framework/1471b26b1184603264c1bdf35d3b8d39d190989d/storage/tmp/.gitkeep -------------------------------------------------------------------------------- /storage/uploads/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ben-shepherd/larascript-framework/1471b26b1184603264c1bdf35d3b8d39d190989d/storage/uploads/.gitkeep -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "paths": { 5 | "@src/*": [ 6 | "src/*" 7 | ] 8 | }, 9 | "module": "CommonJS", 10 | "target": "es2020", 11 | "moduleResolution": "node", 12 | "outDir": "./dist", 13 | "esModuleInterop": true, 14 | "strict": true, 15 | "noImplicitAny": false, 16 | "forceConsistentCasingInFileNames": true 17 | }, 18 | "exclude": [ 19 | "node_modules", 20 | "dist" 21 | ] 22 | } --------------------------------------------------------------------------------