From cf8e0a590c51f8e359827a593d4b0efb3707a28c Mon Sep 17 00:00:00 2001 From: Github Actions Date: Wed, 8 Oct 2025 11:51:45 +0000 Subject: [PATCH 01/23] Version bump --- box.json | 2 +- changelog.md | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/box.json b/box.json index 7c6c9a9..dfce6ea 100644 --- a/box.json +++ b/box.json @@ -1,6 +1,6 @@ { "name":"Default ColdBox App Template For BoxLang", - "version":"1.0.0", + "version":"1.1.0", "author":"You", "location":"forgeboxStorage", "slug":"cbtemplate-boxlang", diff --git a/changelog.md b/changelog.md index 8b53378..7b8423b 100644 --- a/changelog.md +++ b/changelog.md @@ -9,12 +9,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.0.0] - 2025-10-08 + ## [1.1.0] - 2025-06-07 ## [1.0.0] - 2025-04-29 - First creation of the changelog file. -[unreleased]: https://github.com/coldbox-templates/bx-default/compare/v1.1.0...HEAD +[unreleased]: https://github.com/coldbox-templates/boxlang/compare/v1.0.0...HEAD +[1.0.0]: https://github.com/coldbox-templates/boxlang/compare/v1.1.0...v1.0.0 [1.1.0]: https://github.com/coldbox-templates/bx-default/compare/v1.0.0...v1.1.0 -[1.0.0]: https://github.com/coldbox-templates/bx-default/compare/5a12ed5b070279b27504b34f379fea63ff4a0367...v1.0.0 From e35f9fdcce64d8ae0c91747ccaa686a01c5048e8 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 8 Oct 2025 14:02:50 +0200 Subject: [PATCH 02/23] more small fixes --- tests/runner.bxm | 2 +- tests/specs/integration/MainSpec.bx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/runner.bxm b/tests/runner.bxm index 5f9190a..e026b92 100644 --- a/tests/runner.bxm +++ b/tests/runner.bxm @@ -15,7 +15,7 @@ - + diff --git a/tests/specs/integration/MainSpec.bx b/tests/specs/integration/MainSpec.bx index abbea00..ce17f31 100755 --- a/tests/specs/integration/MainSpec.bx +++ b/tests/specs/integration/MainSpec.bx @@ -4,7 +4,7 @@ * Extends the integration class: coldbox.system.testing.BaseTestCase * * so you can test your ColdBox application headlessly. The 'appMapping' points by default to - * the '/root' mapping created in the test folder Application.cfc. Please note that this + * the '/app' mapping created in the test folder Application.cfc. Please note that this * Application.cfc must mimic the real one in your root, including ORM settings if needed. * * The 'execute()' method is used to execute a ColdBox event, with the following arguments From 8ce20101e049be32dfe30ceb65be13e21d7d31ef Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 8 Oct 2025 14:09:07 +0200 Subject: [PATCH 03/23] more updates --- readme.md | 1 - runtime/modules/.gitkeep | 0 2 files changed, 1 deletion(-) delete mode 100644 runtime/modules/.gitkeep diff --git a/readme.md b/readme.md index 8d2569d..9de5f72 100644 --- a/readme.md +++ b/readme.md @@ -82,7 +82,6 @@ This folder contains configuration files, dependencies, Docker setup, and runtim │ │ └── components/ # Global BoxLang components │ ├── lib/ # Runtime libraries (Managed by Maven/CommandBox) │ ├── logs/ # BoxLang logs -│ ├── modules/ # BoxLang runtime modules └── 📚 resources/ # ColdBox/CommandBox module resources ├── migrations/ # Database migrations (cbmigrations) ├── seeders/ # Database seeders diff --git a/runtime/modules/.gitkeep b/runtime/modules/.gitkeep deleted file mode 100644 index e69de29..0000000 From 0a8d634324f484f245406165ac550b37f3f18e02 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 8 Oct 2025 15:57:41 +0200 Subject: [PATCH 04/23] more updates --- .cbmigrations.json | 2 +- app/{modules_app => interceptors}/.gitkeep | 0 app/modules/.gitkeep | 0 changelog.md | 12 ------------ pom.xml | 2 +- 5 files changed, 2 insertions(+), 14 deletions(-) rename app/{modules_app => interceptors}/.gitkeep (100%) create mode 100644 app/modules/.gitkeep diff --git a/.cbmigrations.json b/.cbmigrations.json index 69da4f9..1c0df0c 100755 --- a/.cbmigrations.json +++ b/.cbmigrations.json @@ -6,7 +6,7 @@ "properties": { "defaultGrammar": "AutoDiscover@qb", "schema": "${DB_SCHEMA}", - "migrationsTable": "cfmigrations", + "migrationsTable": "cbmigrations", "connectionInfo": { "type": "${DB_DRIVER}", "database": "${DB_DATABASE}", diff --git a/app/modules_app/.gitkeep b/app/interceptors/.gitkeep similarity index 100% rename from app/modules_app/.gitkeep rename to app/interceptors/.gitkeep diff --git a/app/modules/.gitkeep b/app/modules/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/changelog.md b/changelog.md index 7b8423b..079eb8a 100644 --- a/changelog.md +++ b/changelog.md @@ -8,15 +8,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * * * ## [Unreleased] - -## [1.0.0] - 2025-10-08 - -## [1.1.0] - 2025-06-07 - -## [1.0.0] - 2025-04-29 - -- First creation of the changelog file. - -[unreleased]: https://github.com/coldbox-templates/boxlang/compare/v1.0.0...HEAD -[1.0.0]: https://github.com/coldbox-templates/boxlang/compare/v1.1.0...v1.0.0 -[1.1.0]: https://github.com/coldbox-templates/bx-default/compare/v1.0.0...v1.1.0 diff --git a/pom.xml b/pom.xml index 34ca2ac..aff843d 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ From c8342971700ef28f9adf28a29a13417e9974afa3 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 8 Oct 2025 17:31:05 +0200 Subject: [PATCH 05/23] more setup --- Build.bx | 7 + Setup.bx | 68 ++++ box.json | 10 +- readme.md | 58 ++-- resources/copilot-instructions.md | 515 ++++++++++++++++++++++++++++++ 5 files changed, 632 insertions(+), 26 deletions(-) create mode 100644 Build.bx create mode 100644 Setup.bx create mode 100644 resources/copilot-instructions.md diff --git a/Build.bx b/Build.bx new file mode 100644 index 0000000..603520b --- /dev/null +++ b/Build.bx @@ -0,0 +1,7 @@ +class{ + + function main(){ + return "Hello World!"; + } + +} \ No newline at end of file diff --git a/Setup.bx b/Setup.bx new file mode 100644 index 0000000..2325ac1 --- /dev/null +++ b/Setup.bx @@ -0,0 +1,68 @@ +class{ + + static { + projectRoot = server.cli.executionPath + } + + function main(){ + // Copy the .env.example to .env if it doesn't exist + if( !fileExists( ".env" ) ){ + println( "🥊 Creating .env file from .env.example" ) + fileCopy( ".env.example", ".env" ) + } else{ + println( "⏭️ .env file already exists, skipping creation." ) + } + + // Copy the copilot file if it doesn't exist + if( !directoryExists( ".github" ) ){ + println( "🥊 Creating .github directory" ) + directoryCreate( ".github" ) + } + if( !fileExists( ".github/copilot-instructions.md" ) ){ + println( "🥊 Creating copilot file" ) + fileCopy( "resources/copilot-instructions.md", ".github/copilot-instructions.md" ) + } else{ + println( "⏭️ .github/copilot-instructions.md file already exists, skipping creation." ) + } + + // Maven setup + // Ask the user if they want to use Maven for Java dependency management + var maven = CLIRead( "Do you want to use Maven for any Java dependency management? (y/n): " ).toLowerCase().booleanFormat(); + if( maven ){ + println( "🥊 Setting up a [pom.xml] in your root for Java dependency management" ) + println( "👉 You can add your Java dependencies to the [dependencies] section of the pom.xml" ) + println( "👉 They will be automatically copied to the [runtime/lib] folder for you once you run 'mvn install'" ) + } else{ + println( "🧹 Cleaning up unnecessary files" ) + if( fileExists( "effective-pom.xml" ) ){ + fileDelete( "effective-pom.xml" ) + } + if( fileExists( "pom.xml" ) ){ + fileDelete( "pom.xml" ) + } + } + + // Remove the ignore array from box.json + println( "🛁 Cleaning up your box.json" ) + result = systemExecute( + name: "box", + arguments: "package set ignore=[]", + directory: static.projectRoot + ) + result = systemExecute( + name: "box", + arguments: "package clear scripts.postInstall", + directory: static.projectRoot + ) + + // Delete this setup file + println( "🧹 Cleaning up the Setup.bx ..." ) + fileDelete( "Setup.bx" ) + + println( "🥊 Your ColdBox application is ready to roll!" ) + println( "👉 Run 'box server start' to launch the development server." ) + println( "👉 Run 'box coldbox help' to see a list of available commands from the ColdBox CLI" ) + println( "👉 Happy coding!" ) + } + +} \ No newline at end of file diff --git a/box.json b/box.json index dfce6ea..c50f415 100644 --- a/box.json +++ b/box.json @@ -9,7 +9,10 @@ "language":"boxlang", "keywords":"boxlang", "shortDescription":"", - "ignore":[], + "ignore":[ + "changelog.md", + ".github/**" + ], "dependencies":{ "coldbox":"be", "route-visualizer":"^2.2.0+2" @@ -17,7 +20,9 @@ "devDependencies":{ "testbox":"*", "commandbox-boxlang":"*", - "commandbox-cfformat":"*" + "commandbox-cfformat":"*", + "coldbox-cli":"*", + "testbox-cli":"*" }, "installPaths":{ "coldbox":"runtime/lib/coldbox/", @@ -25,7 +30,6 @@ "route-visualizer":"modules/route-visualizer/" }, "scripts":{ - "postInstall":"pathExists .env || cp .env.example .env && package set ignore=[]", "format":"cfformat run app/**/*.cfc,tests/specs/,*.cfc --overwrite", "format:check":"cfformat check app/**/*.cfc,tests/specs/,*.cfc ./.cfformat.json", "format:watch":"cfformat watch path='app/**/*.cfc,tests/specs/,*.cfc' settingsPath='.cfformat.json'", diff --git a/readme.md b/readme.md index 9de5f72..78dba3e 100644 --- a/readme.md +++ b/readme.md @@ -26,6 +26,41 @@ Welcome to the modern ColdBox 8 BoxLang application template! 🎉 This template provides a solid foundation for building enterprise-grade HMVC (Hierarchical Model-View-Controller) web applications using the BoxLang runtime. Perfect for developers looking to leverage the power of ColdBox with the performance and modern features of BoxLang. +## ⚙️ Requirements + +Before getting started, ensure you have the following installed on your operating system: + +1. **BoxLang OS** - Operating System Binary + - 📥 Installation: + - 📌 Minimum Version: 1.0+ + - 🎯 Used for: running BoxLang applications and scripts at the operating system level +2. **CommandBox** - CLI toolchain, package manager, and server runtime + - 📥 Installation: + - 📌 Minimum Version: 6.0+ + - 🎯 Used for: dependency management, server starting, testing, and task automation +3. **Maven** - Java dependency manager (Optional, only if you need Java dependencies) + - 📥 Installation: + - 📌 Minimum Version: 3.6+ + - 🎯 Used for: managing Java dependencies if your project requires them + + +## ⚡ Quick Installation + +In order to work with this template, you need to have [CommandBox](https://www.ortussolutions.com/products/commandbox) and the [BoxLang](https://boxlang.ortusbooks.com/) operating system runtime installed on your machine. CommandBox is the application server of choice for BoxLang applications. Please note that running BoxLang web applications is different than the BoxLang OS runtime. The BoxLang OS runtime is used to run BoxLang scripts and command line applications, while CommandBox is used to run web applications. + +```bash +# Create a new ColdBox application using this BoxLang template +box coldbox create app --boxlang +# Setup the Template for Operation and preferences +boxlang Setup.bx +# Start up the web server +box server start +``` + +Your application will be available at `http://localhost:8080` 🌐 + +Code to your liking and enjoy! 🎊 + ## 📁 Application Structure This ColdBox 8 application follows a clean, modern architecture with the following structure: @@ -89,29 +124,6 @@ This folder contains configuration files, dependencies, Docker setup, and runtim └── other module assets/ # Various module-specific resources ``` -## ⚡ Quick Installation - -In order to work with this template, you need to have [CommandBox](https://www.ortussolutions.com/products/commandbox) installed on your machine. CommandBox is the application server of choice for BoxLang applications. - -```bash -# Go into the CommandBox shell -box -# Create a new directory and go into it -mkdir MyApp --cd -# Create a new ColdBox application using the BoxLang template -coldbox create app --boxlang -``` - -This will create a new ColdBox application using this BoxLang template and install all the needed dependencies. You can then startup your BoxLang server using the following command: - -```bash -box server start -``` - -Your application will be available at `http://localhost:8080` 🌐 - -Code to your liking and enjoy! 🎊 - ## 🗺️ BoxLang Mappings This template comes pre-configured with essential BoxLang mappings in the `runtime/config/boxlang.json` file to make development seamless. These mappings provide convenient shortcuts to access different parts of your application: diff --git a/resources/copilot-instructions.md b/resources/copilot-instructions.md new file mode 100644 index 0000000..c7109f5 --- /dev/null +++ b/resources/copilot-instructions.md @@ -0,0 +1,515 @@ +# BoxLang ColdBox Template - AI Coding Instructions + +This is a BoxLang application template using the ColdBox HMVC framework. These instructions are designed to help AI assistants understand the project structure and provide better coding assistance. + +## 🎯 Project Overview + +**Language**: BoxLang (modern JVM language, successor to CFML) +**Framework**: ColdBox HMVC Framework v8+ +**Template Type**: Full-stack web application template +**Architecture**: Model-View-Controller with Hierarchical MVC support + +## ⚙️ Requirements + +This project requires the following to be installed on your operating system: + +### Required Software + +1. **CommandBox** - CLI toolchain, package manager, and server runtime + - Installation: https://commandbox.ortusbooks.com/getting-started/installation + - Minimum Version: 6.0+ + - Used for: dependency management, server starting, testing, and task automation + +2. **BoxLang** - Modern JVM language runtime + - Installation: https://boxlang.ortusbooks.com/getting-started/installation + - Minimum Version: 1.0+ + - Can be installed via CommandBox: `box install commandbox-boxlang` + - Used for: running BoxLang applications and scripts + +### Verification + +Verify installations: +```bash +# Check CommandBox is installed +box version + +# Check BoxLang is available +box boxlang version + +# Verify project setup (run from project root) +boxlang Setup.bx +``` + +### Optional but Recommended + +- **Java JDK** - Required for BoxLang runtime (JDK 11+ recommended) +- **Docker** - For containerized development (if using Docker setup) +- **Git** - For version control + +## 📁 Project Structure + +``` +/app/ # Application source code +├── Application.bx # Application entry point +├── config/ # Framework configuration +│ ├── CacheBox.bx # Caching configuration +│ ├── ColdBox.bx # Main ColdBox settings +│ ├── Router.bx # Route definitions +│ ├── Scheduler.bx # Scheduled tasks +│ └── WireBox.bx # Dependency injection configuration +├── handlers/ # Controllers (request handlers) +├── helpers/ # View helper functions +├── interceptors/ # Event interceptors/listeners +├── layouts/ # Page layouts/templates +├── logs/ # Application logs +├── models/ # Business logic and data models +├── modules/ # Application-specific modules (HMVC) +└── views/ # View templates + +/public/ # Web-accessible files (document root) +├── Application.bx # Public application bootstrap +├── index.bxm # Application entry point (markup) +├── favicon.ico # Site favicon +├── robots.txt # Search engine directives +└── includes/ # Static assets + ├── i18n/ # Client-side translations + └── images/ # Image assets (CSS, JS in subfolders) + +/resources/ # Non-web resources +├── database/ # Database management +│ └── migrations/ # Database migration files +└── copilot-instructions.md # This file (AI coding instructions) + +/tests/ # Test suites +├── Application.bx # Test application setup +├── index.bxm # Test runner entry point +├── runner.bxm # TestBox runner +├── specs/ # BDD test specifications +└── resources/ # Test resources and fixtures + +/docker/ # Docker configuration +├── Dockerfile # Container image definition +└── docker-compose.yml # Multi-container setup + +/runtime/ # BoxLang runtime files (auto-generated) +├── boxlang.json # Runtime configuration +└── global/ # Global runtime cache + +/.devcontainer/ # VS Code dev container configuration +/.github/ # GitHub Actions workflows and settings +/.vscode/ # VS Code workspace settings + +# Root Configuration Files +├── Build.bx # Build automation script +├── box.json # CommandBox package descriptor +├── server.json # CommandBox server configuration +├── .env.example # Environment variable template +├── .cfformat.json # Code formatting rules +├── .cflintrc # Linting configuration +├── .editorconfig # Editor configuration +└── pom.xml # Maven build configuration (optional) +``` + +## 🔧 Key Technologies + +### Core Stack + +- **BoxLang**: Modern JVM language with CFML compatibility +- **ColdBox**: HMVC framework for enterprise applications +- **WireBox**: Dependency injection and AOP container +- **CacheBox**: Enterprise caching engine +- **LogBox**: Logging and debugging framework + +### Development Tools + +- **CommandBox**: CLI toolchain and package manager +- **TestBox**: BDD/TDD testing framework +- **CFFormat**: Code formatting and linting + +## 📝 BoxLang Syntax Guidelines + +### File Extensions + +- `.bx` - BoxLang classes and components +- `.bxm` - BoxLang markup (templates/views) +- `.bxs` - BoxLang scripts + +### Class Declaration + +```js +// BoxLang uses 'class' keyword instead of 'component' +class extends="BaseHandler" { + + function index( event, rc, prc ){ + return "Hello from BoxLang!"; + } + +} +``` + +### Function Syntax + +```js +// Modern function syntax +function getUserById( required numeric id ){ + return userService.findById( arguments.id ); +} + +// Arrow functions supported as closures +variables.transform = ( item ) => item.toUpperCase(); + +// Thin Arrow function supported as pure lambdas +// BoxLang lambdas only have acess to arguments and function local scope +variables.filter = ( item ) -> item.isActive(); +``` + +### Dependency Injection + +```js +class UserService { + // Long Form + @inject( "UserDAO" ) + property userDAO; + + // Short Form if the property name matches the class name + @inject + property userDAO; + + @inject + property wirebox; + + function getUsers(){ + return userDAO.list(); + } +} +``` + +## 🎛️ ColdBox Patterns + +### Handler Actions + +```js +class extends="BaseHandler" { + + function index( event, rc, prc ){ + prc.users = userService.list(); + event.setView( "users/index" ); + } + + function show( event, rc, prc ){ + prc.user = userService.get( rc.id ); + event.setView( "users/show" ); + } +} +``` + +### Models with Dependency Injection + +```js +class extends="BaseService" { + + @inject( "UserGateway" ) + property userGateway; + + @inject + property cachebox; + + function list(){ + return cachebox.get( "users" ) ?: userGateway.getAll(); + } +} +``` + +### Event-Driven Architecture + +```js +// Interceptors for cross-cutting concerns +class SecurityInterceptor { + + function preProcess( event, data ){ + if( !security.isLoggedIn() ){ + relocate( "auth.login" ); + } + } +} +``` + +## 🧪 Testing Patterns + +### BDD Test Structure + +```js +class extends="BaseTestCase" { + + function beforeAll(){ + super.beforeAll(); + userService = getInstance( "UserService" ); + } + + function run(){ + describe( "UserService", () => { + + it( "should return all users", () => { + var users = userService.list() + expect( users ).toBeArray() + }) + + }) + } +} +``` + +## 🔗 Common Integrations + +### Database Operations + +- Use ColdBox ORM or Query Builder or Quick ORM or native queries +- Migration files in `/resources/database/migrations/` +- Seeders in `/resources/database/seeds/` + +### API Development + +- RESTful handlers in `/handlers/api/` +- **Use RESTHandler base class** for REST API endpoints +- Use `event.renderData()` for JSON responses +- JWT authentication via ColdBox Security module + +#### RESTHandler Pattern + +For REST API development, extend `coldbox.system.RestHandler` instead of `BaseHandler`: + +```js +class extends="coldbox.system.RestHandler" { + + // RESTHandler provides automatic: + // - Content negotiation (JSON, XML, HTML) + // - HTTP status code handling + // - Error response formatting + // - CORS support + + function index( event, rc, prc ){ + // Automatically serializes to requested format + return userService.list(); + } + + function show( event, rc, prc ){ + prc.response = userService.get( rc.id ); + } + + function create( event, rc, prc ){ + var result = userService.create( rc ); + prc.statusCode = 201; + return result; + } + + function update( event, rc, prc ){ + return userService.update( rc.id, rc ); + } + + function delete( event, rc, prc ){ + userService.delete( rc.id ); + prc.statusCode = 204; + } + + // Handle errors with proper HTTP status codes + function onError( event, rc, prc, faultAction, exception ){ + prc.statusCode = 500; + return { + "error": true, + "messages": [ exception.message ] + }; + } +} +``` + +**RESTHandler Benefits**: + +- Automatic content type detection and rendering +- Built-in HTTP verb routing (GET, POST, PUT, DELETE, PATCH) +- Standardized error handling with `onError()` and `onInvalidHTTPMethod()` +- Easy status code management via `prc.statusCode` +- Return data directly from actions (auto-serialization) + +**Documentation**: https://coldbox.ortusbooks.com/digging-deeper/rest-handler + +#### Resourceful Routes (RECOMMENDED for ALL Resource-Based Handlers) + +**Always use resourceful routes** for both web handlers and REST APIs. ColdBox provides automatic route generation following RESTful conventions, reducing boilerplate and ensuring consistency. + +```js +// In /app/config/Router.bx +function configure(){ + + // Standard resourceful routes (web handlers with views) + resources( "photos" ); + // Generates: index, new, create, show, edit, update, delete + // Perfect for traditional CRUD web interfaces + + // API resourceful routes (REST APIs, JSON responses) + apiResources( "users" ); + // Generates: index, show, create, update, delete (no 'new' or 'edit' forms) + // Use with RestHandler for REST APIs + + // Nested resources for hierarchical data + resources( "photos", function(){ + resources( "comments" ); + }); + // Generates: /photos/:photoId/comments + + // Restrict to specific actions + resources( resource="articles", only="index,show" ); + resources( resource="profiles", except="delete" ); + + // Customize parameter name + resources( resource="users", parameterName="userId" ); +} +``` + +**Generated Routes for `resources( "photos" )` (Web)**: + +| HTTP Verb | Route | Action | Purpose | +|-----------|------------------|----------|----------------------------| +| GET | /photos | index | List all photos | +| GET | /photos/new | new | Show create form | +| POST | /photos | create | Create new photo | +| GET | /photos/:id | show | Show specific photo | +| GET | /photos/:id/edit | edit | Show edit form | +| PUT/PATCH | /photos/:id | update | Update specific photo | +| DELETE | /photos/:id | delete | Delete specific photo | + +**Generated Routes for `apiResources( "users" )` (API)**: + +| HTTP Verb | Route | Action | Purpose | +|-----------|------------------|----------|----------------------------| +| GET | /users | index | List all users | +| GET | /users/:id | show | Get specific user | +| POST | /users | create | Create new user | +| PUT/PATCH | /users/:id | update | Update specific user | +| DELETE | /users/:id | delete | Delete specific user | + +**Benefits**: +- **Use `resources()` for web handlers** (with forms and views) and `apiResources()` for REST APIs +- Automatic RESTful route generation following industry standards +- Consistent URL patterns across your entire application +- Reduced boilerplate route definitions +- Easy to understand and maintain +- Works seamlessly with BaseHandler (web) and RestHandler (API) base classes +- Supports nested resources for hierarchical data +- Self-documenting route structure + +**When to Use**: +- ✅ Any CRUD-based resource (users, posts, photos, products, etc.) +- ✅ Traditional web interfaces with forms (`resources()`) +- ✅ REST APIs returning JSON/XML (`apiResources()`) +- ✅ Nested/hierarchical resources (posts with comments) +- ❌ Non-resource actions (search, reports, utilities) - use custom routes + +**Documentation**: https://coldbox.ortusbooks.com/the-basics/routing/routing-dsl/resourceful-routes + +### Frontend Integration + +- Assets managed via `/public/assets/` +- View helpers for asset compilation +- Modern JavaScript/CSS build tools supported + +## 🚀 Development Workflow + +### Local Development +```bash +# Install dependencies +box install + +# Start development server +box server start + +# Run tests +box testbox run + +# Format code +box run-script format +``` + +### Code Quality + +- Follow Ortus coding standards +- Use CFFormat for consistent formatting +- Write tests for all business logic +- Document public APIs with JavaDoc-style comments + +## 🔍 AI Assistant Guidelines + +### When Generating Code + +1. **Use BoxLang syntax** (class, not component) +2. **Follow ColdBox conventions** for handlers, models, views +3. **Include proper dependency injection** when creating services +4. **Write accompanying tests** for new functionality +5. **Use modern BoxLang features** (arrow functions, proper typing) + +### File Creation Patterns + +- **Handlers**: Extend `BaseHandler`, use dependency injection +- **Models**: Extend appropriate base classes, implement interfaces +- **Tests**: Follow BDD patterns with `describe()` and `it()` +- **Views**: Use `.bxm` extension, leverage ColdBox view helpers + +### Security Considerations + +- Always validate input parameters +- Use ColdBox Security module for authentication +- Implement CSRF protection for forms +- Sanitize output in views + +## 📚 Documentation References + +- [BoxLang Documentation](https://boxlang.ortusbooks.com/) +- [ColdBox Documentation](https://coldbox.ortusbooks.com/) +- [TestBox Documentation](https://testbox.ortusbooks.com/) +- [WireBox Documentation](https://wirebox.ortusbooks.com/) +- [CacheBox Documentation](https://cachebox.ortusbooks.com/) +- [LogBox Documentation](https://logbox.ortusbooks.com/) +- [Ortus Coding Standards](https://github.com/Ortus-Solutions/coding-standards) + +## 🤖 MCP (Model Context Protocol) Servers + +For AI assistants that support MCP, access comprehensive documentation through these servers: + +### Framework Documentation +- **ColdBox MCP Server**: `https://coldbox.ortusbooks.com/~gitbook/mcp` +- **WireBox MCP Server**: `https://wirebox.ortusbooks.com/~gitbook/mcp` +- **CacheBox MCP Server**: `https://cachebox.ortusbooks.com/~gitbook/mcp` +- **LogBox MCP Server**: `https://logbox.ortusbooks.com/~gitbook/mcp` + +### Language & Testing Documentation +- **BoxLang MCP Server**: `https://boxlang.ortusbooks.com/~gitbook/mcp` +- **TestBox MCP Server**: `https://testbox.ortusbooks.com/~gitbook/mcp` + +These MCP servers provide real-time access to official documentation, examples, and API references. AI assistants can query these servers for: +- Framework APIs and configuration options +- Code examples and patterns +- Best practices and conventions +- Troubleshooting guides +- Version-specific documentation + +## 🎯 Best Practices + +### Performance + +- Use CacheBox for expensive operations +- Implement proper database indexing +- Lazy load dependencies when appropriate +- Use async patterns for I/O operations + +### Maintainability + +- Keep handlers thin, move logic to services +- Use events for decoupled communication +- Implement proper error handling +- Write comprehensive tests + +### Security + +- Validate all inputs +- Use parameterized queries +- Implement proper session management +- Regular security audits of dependencies \ No newline at end of file From 6366636b8e05cce67056d573ae400c047ac2f35e Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 8 Oct 2025 18:04:56 +0200 Subject: [PATCH 06/23] added builder now --- .gitignore | 1 + Build.bx | 298 ++++++++++++++++++++++++++++++++++++++++++++++++++++- Setup.bx | 6 +- 3 files changed, 299 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 2687a86..17e57ca 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .DS_Store settings.xml WEB-INF +build/** # Engines + Database + CBFS + Secrets .tmp/** diff --git a/Build.bx b/Build.bx index 603520b..6b8542d 100644 --- a/Build.bx +++ b/Build.bx @@ -1,7 +1,301 @@ -class{ +/** + * Build automation script for ColdBox BoxLang Template + * + * This component handles the build processes including: + * - Compilation and packaging + * - Distribution preparation + * - Checksum generation + * + * Usage: boxlang Build.bx + */ +class { + /** + * Constructor - Initialize build environment + */ + function init(){ + // Setup Pathing + variables.cwd = server.cli.executionPath & "/"; + variables.buildDir = variables.cwd & "build"; + variables.packageDir = variables.buildDir & "/package"; + variables.distDir = variables.buildDir & "/distributions"; + + // Load box.json for project metadata + variables.boxJSON = jsonDeserialize( fileRead( variables.cwd & "box.json" ) ); + variables.projectName = variables.boxJSON.slug ?: "coldbox-app"; + variables.projectVersion = variables.boxJSON.version ?: "1.0.0"; + + // Source directories to package + variables.sources = [ + ".cbmigrations.json", + "box.json", + "app", + "modules", + "public", + "runtime" + ]; + + // Files and folders to exclude from the build (regex patterns) + variables.excludes = [ + "logs/", // Log directories + "\.DS_Store$", // macOS system files + "Thumbs\.db$" // Windows system files + ]; + + return this; + } + + /** + * Main entry point for the build process + */ function main(){ - return "Hello World!"; + printHeader( "Starting Build Process for #variables.projectName# v#variables.projectVersion#" ); + + // Clean and prepare build directory + prepareBuildDirectory(); + + // Copy source files + copySources(); + + // Create build ID file + createBuildID(); + + // Compile sources + compileSources(); + + // Create distribution zip + createDistribution(); + + // Generate checksums + generateChecksums(); + + printHeader( "✨ Build Complete! Distribution ready at: #variables.distDir#" ); + } + + /** + * Prepare the build directory - delete if exists and recreate + */ + private function prepareBuildDirectory(){ + printStep( "🧹 Preparing build directory..." ); + + // Wipe build directory if it exists + if ( directoryExists( variables.buildDir ) ) { + printInfo( "Removing existing build directory..." ); + directoryDelete( variables.buildDir, true ); + } + + // Create directory structure + directoryCreate( variables.packageDir, true, true ); + directoryCreate( variables.distDir, true, true ); + + printSuccess( "Build directory prepared" ); + } + + /** + * Copy source files to package directory + */ + private function copySources(){ + printStep( "📁 Copying source files..." ); + + variables.sources.each( ( source ) => { + var sourcePath = variables.cwd & source; + var targetPath = variables.packageDir & "/" & source; + + if ( directoryExists( sourcePath ) ) { + printInfo( "Copying #source#/ ..." ); + copyDirectoryWithExclusions( sourcePath, targetPath ); + } else if ( fileExists( sourcePath ) ) { + printInfo( "Copying #source# ..." ); + fileCopy( sourcePath, targetPath ); + } else { + printWarning( "Source not found: #source#" ); + } + } ); + + printSuccess( "Sources copied successfully" ); + } + + /** + * Copy directory recursively with exclusion patterns + * + * @source The source directory path + * @target The target directory path + */ + private function copyDirectoryWithExclusions( required string source, required string target ){ + // Create target directory if it doesn't exist + if ( !directoryExists( arguments.target ) ) { + directoryCreate( arguments.target, true, true ); + } + + // Get all items in the source directory as an array of paths + var items = directoryList( arguments.source, false, "array" ); + + items.each( ( itemPath ) => { + var itemName = listLast( itemPath, "/\" ); + var targetPath = target & "/" & itemName; + var relativePath = itemPath.replace( variables.cwd, "" ); + + // Check if item should be excluded + var isExcluded = isPathExcluded( relativePath ); + + if ( isExcluded ) { + printInfo( "⊘ Excluding: #relativePath#" ); + return; + } + + // Copy files or directories recursively + if ( directoryExists( itemPath ) ) { + copyDirectoryWithExclusions( itemPath, targetPath ); + } else { + fileCopy( itemPath, targetPath ); + } + } ); + } + + /** + * Check if a path matches any exclusion pattern + * + * @path The path to check (relative to project root) + * @return True if path should be excluded + */ + private function isPathExcluded( required string path ){ + var excluded = false; + + variables.excludes.each( ( pattern ) => { + if ( path.reFindNoCase( pattern ) ) { + excluded = true; + } + } ); + + return excluded; + } + + /** + * Create build ID file + */ + private function createBuildID(){ + printStep( "🏷️ Creating build ID file..." ); + var buildIDFileName = "#variables.projectName#-#variables.projectVersion#.md"; + var buildIDPath = variables.packageDir & "/" & buildIDFileName; + var buildContent = "## Build Information + + **Project**: #variables.projectName# + **Version**: #variables.projectVersion# + **Built on**: #dateTimeFormat( now(), "full" )# + "; + + fileWrite( buildIDPath, buildContent ); + printSuccess( "Build ID file created: #buildIDFileName#" ); + } + + /** + * Compile BoxLang sources + */ + private function compileSources(){ + printStep( "🔨 Compiling BoxLang sources..." ); + + var compilePaths = [ + variables.packageDir & "/app/", + variables.packageDir & "/public/" + ]; + + compilePaths.each( ( path ) => { + if ( directoryExists( path ) ) { + printInfo( "Compiling: [#path#]" ); + try { + var result = systemExecute( "boxlang", "compile --source #path# --target #path#" ); + println( result.output ) + } catch( any e ) { + printWarning( "Compilation error for #path#: #e.message#" ); + } + } + } ); + + printSuccess( "Source compilation completed" ); + } + + /** + * Create distribution zip file + */ + private function createDistribution(){ + printStep( "📦 Creating distribution package..." ); + + var zipFileName = "#variables.projectName#-#variables.projectVersion#.zip"; + var zipPath = variables.distDir & "/" & zipFileName; + + printInfo( "Zipping package to: #zipFileName#" ); + + bx:zip + file = zipPath + source = variables.packageDir + overwrite = true + recurse = true; + + printSuccess( "Distribution created: #zipFileName#" ); + } + + /** + * Generate checksums for the distribution + */ + private function generateChecksums(){ + printStep( "🔐 Generating checksums..." ); + + var zipFileName = "#variables.projectName#-#variables.projectVersion#.zip"; + var zipPath = variables.distDir & "/" & zipFileName; + + if ( !fileExists( zipPath ) ) { + printWarning( "Zip file not found for checksum generation" ); + return; + } + + // Generate MD5 + var md5Hash = hash( fileReadBinary( zipPath ), "MD5" ); + fileWrite( zipPath & ".md5", md5Hash ); + printSuccess( "MD5: #md5Hash#" ); + + // Generate SHA-256 + var sha256Hash = hash( fileReadBinary( zipPath ), "SHA-256" ); + fileWrite( zipPath & ".sha256", sha256Hash ); + printSuccess( "SHA-256: #sha256Hash#" ); + + // Generate SHA-512 + var sha512Hash = hash( fileReadBinary( zipPath ), "SHA-512" ); + fileWrite( zipPath & ".sha512", sha512Hash ); + printSuccess( "SHA-512: #sha512Hash#" ); + } + + // ============================================================================ + // Print Helpers + // ============================================================================ + + private function printHeader( required string message ){ + var separator = repeatString( "=", 70 ); + println( "", true ); + println( separator, true ); + println( " 🚀 " & arguments.message, true ); + println( separator, true ); + println( "", true ); + } + + private function printStep( required string message ){ + println( "", true ); + println( "📦 " & arguments.message, true ); + } + + private function printInfo( required string message ){ + println( " 📄 " & arguments.message, true ); + } + + private function printSuccess( required string message ){ + println( " ✅ " & arguments.message, true ); + } + + private function printWarning( required string message ){ + println( " ⚠️ " & arguments.message, true ); + } + + private function printError( required string message ){ + println( " ❌ " & arguments.message, true ); } } \ No newline at end of file diff --git a/Setup.bx b/Setup.bx index 2325ac1..9dd5794 100644 --- a/Setup.bx +++ b/Setup.bx @@ -56,13 +56,11 @@ class{ ) // Delete this setup file - println( "🧹 Cleaning up the Setup.bx ..." ) - fileDelete( "Setup.bx" ) - println( "🥊 Your ColdBox application is ready to roll!" ) println( "👉 Run 'box server start' to launch the development server." ) println( "👉 Run 'box coldbox help' to see a list of available commands from the ColdBox CLI" ) - println( "👉 Happy coding!" ) + println( "ℹ️. You can remove the [Setup.bx] file from your project now or keep it for future reference." ) + println( "🗳️ Happy coding!" ) } } \ No newline at end of file From 6d23115675bb3b49d7341beedeb83212d6c49af6 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 8 Oct 2025 18:07:51 +0200 Subject: [PATCH 07/23] finalized --- Build.bx | 2 ++ readme.md | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 103 insertions(+), 1 deletion(-) diff --git a/Build.bx b/Build.bx index 6b8542d..828b5d3 100644 --- a/Build.bx +++ b/Build.bx @@ -231,6 +231,8 @@ class { overwrite = true recurse = true; + fileCopy( variables.packageDir & "/box.json", variables.distDir & "/box.json" ); + printSuccess( "Distribution created: #zipFileName#" ); } diff --git a/readme.md b/readme.md index 78dba3e..4d5d942 100644 --- a/readme.md +++ b/readme.md @@ -61,7 +61,107 @@ Your application will be available at `http://localhost:8080` 🌐 Code to your liking and enjoy! 🎊 -## 📁 Application Structure +## 🤖 Setup Script (`Setup.bx`) + +After creating your application, run the **Setup.bx** script to configure your template and set your preferences. This interactive script helps you customize your application for your specific needs. + +```bash +boxlang Setup.bx +``` + +### What Setup.bx Does: + +The setup script walks you through several configuration options: + +- **📝 Project Information**: Set your application name, author, and description +- **🎨 Customization**: Configure project-specific settings +- **🔐 Environment Setup**: Generate `.env` files and configure environment variables +- **📦 Dependencies**: Install additional dependencies based on your selections +- **✅ Validation**: Verify your environment is properly configured + +> **💡 Tip**: Run `Setup.bx` immediately after creating your application to ensure everything is configured correctly for your development environment. + +## 📦 Build Script (`Build.bx`) + +The **Build.bx** script compiles and packages your application for distribution. It creates optimized, production-ready builds that can be deployed to any environment. + +```bash +boxlang Build.bx +``` + +### What Build.bx Does: + +The build process performs the following steps: + +1. **🧹 Clean Build Directory**: Removes any existing `build/` folder and creates a fresh structure +2. **📁 Copy Sources**: Copies application files (`app/`, `modules/`, `public/`, `runtime/`) to the build package +3. **⊘ Smart Exclusions**: Automatically excludes: + - Log files and directories (`logs/`, `*.log`) + - System files (`.DS_Store`, `Thumbs.db`) + - Hidden files and folders (`.git`, `.gitignore`, etc.) +4. **🏷️ Build ID**: Creates a build information file with project name, version, and timestamp +5. **🔨 Compilation**: Compiles BoxLang sources in `app/` and `public/` to optimized bytecode +6. **📦 Distribution Package**: Creates a ZIP file: `build/distributions/{projectName}-{projectVersion}.zip` +7. **🔐 Checksums**: Generates security checksums (MD5, SHA-256, SHA-512) for integrity verification + +### Build Output Structure: + +```text +build/ +├── package/ # Staged files ready for distribution +│ ├── app/ # Compiled application code +│ ├── modules/ # Application modules +│ ├── public/ # Compiled public assets +│ ├── runtime/ # Runtime configuration (without logs) +│ └── {projectName}-{version}.md # Build information +└── distributions/ # Final distribution files + ├── {projectName}-{version}.zip + ├── {projectName}-{version}.zip.md5 + ├── {projectName}-{version}.zip.sha256 + └── {projectName}-{version}.zip.sha512 +``` + +### Customizing the Build: + +You can customize what gets included or excluded by editing the `Build.bx` file: + +```boxlang +// Add directories/files to package +variables.sources = [ + "app", + "modules", + "public", + "runtime", + "config" // Add your own +]; + +// Add exclusion patterns (regex) +variables.excludes = [ + "logs/", // Exclude all log directories + "\.log$", // Exclude .log files + "\.tmp$", // Exclude .tmp files + "test-results/" // Exclude test output +]; +``` + +### Deploying Your Build: + +Once the build completes, you can: + +1. **Upload the ZIP**: Deploy `{projectName}-{version}.zip` to your server +2. **Verify Integrity**: Use the checksum files to verify the package wasn't corrupted during transfer +3. **Extract & Run**: Unzip on your server and start with CommandBox + +```bash +# On your server +unzip cbtemplate-boxlang-1.1.0.zip +cd cbtemplate-boxlang-1.1.0 +box server start +``` + +> **🚀 Pro Tip**: Integrate `Build.bx` into your CI/CD pipeline to automatically build and deploy your application on every release! + +## �📁 Application Structure This ColdBox 8 application follows a clean, modern architecture with the following structure: From 7cde5c8f116236eaa8a65e1bf8ad261d43d3ca05 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 8 Oct 2025 18:10:52 +0200 Subject: [PATCH 08/23] more docs --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 4d5d942..4e3e23a 100644 --- a/readme.md +++ b/readme.md @@ -161,7 +161,7 @@ box server start > **🚀 Pro Tip**: Integrate `Build.bx` into your CI/CD pipeline to automatically build and deploy your application on every release! -## �📁 Application Structure +## 📁Application Structure This ColdBox 8 application follows a clean, modern architecture with the following structure: From 46afa62147f4d701463f7574caa3bb67c87a2720 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 8 Oct 2025 18:13:03 +0200 Subject: [PATCH 09/23] more readme --- readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/readme.md b/readme.md index 4e3e23a..8d31186 100644 --- a/readme.md +++ b/readme.md @@ -78,6 +78,7 @@ The setup script walks you through several configuration options: - **🔐 Environment Setup**: Generate `.env` files and configure environment variables - **📦 Dependencies**: Install additional dependencies based on your selections - **✅ Validation**: Verify your environment is properly configured +- **🤖 Creates AI Instructions**: Generates AI usage instructions based on your project setup > **💡 Tip**: Run `Setup.bx` immediately after creating your application to ensure everything is configured correctly for your development environment. From 1d99dabcf648b5fe64899613a06d2ded64a5ac34 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 9 Oct 2025 09:32:33 +0200 Subject: [PATCH 10/23] more updates on testing --- server.json | 1 + 1 file changed, 1 insertion(+) diff --git a/server.json b/server.json index dd4c4a3..6b768d5 100644 --- a/server.json +++ b/server.json @@ -15,6 +15,7 @@ "enable":true }, "aliases":{ + "/coldbox/system/exceptions":"./lib/coldbox/system/exceptions/", "/tests":"./tests/" } }, From 10664a6e6c98febbb0d97f5b3adb7da88d144ee3 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 9 Oct 2025 12:01:27 +0200 Subject: [PATCH 11/23] added missing logging --- app/config/Coldbox.bx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/config/Coldbox.bx b/app/config/Coldbox.bx index 5a87a28..7d1d014 100644 --- a/app/config/Coldbox.bx +++ b/app/config/Coldbox.bx @@ -97,6 +97,10 @@ class { variables.logBox = { // Define Appenders appenders : { coldboxTracer : { class : "coldbox.system.logging.appenders.ConsoleAppender" } }, + filelog : { + class : "coldbox.system.logging.appenders.RollingFileAppender", + properties : { filename : "app", filePath : "/app/logs" } + } // Root Logger root : { levelmax : "INFO", appenders : "*" }, // Implicit Level Categories From acbc9732cec31cb217e7c0cc620d20b66e5e7079 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 9 Oct 2025 17:22:09 +0200 Subject: [PATCH 12/23] vite setup --- Setup.bx | 29 +++++++++++++++---- resources/vite/.babelrc | 7 +++++ resources/vite/assets/css/app.css | 1 + resources/vite/assets/js/app.js | 4 +++ resources/vite/assets/js/components/Hello.vue | 7 +++++ resources/vite/layouts/Main.bxm | 25 ++++++++++++++++ resources/vite/package.json | 17 +++++++++++ resources/vite/vite.config.mjs | 15 ++++++++++ 8 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 resources/vite/.babelrc create mode 100644 resources/vite/assets/css/app.css create mode 100644 resources/vite/assets/js/app.js create mode 100644 resources/vite/assets/js/components/Hello.vue create mode 100755 resources/vite/layouts/Main.bxm create mode 100644 resources/vite/package.json create mode 100644 resources/vite/vite.config.mjs diff --git a/Setup.bx b/Setup.bx index 9dd5794..4e8d04d 100644 --- a/Setup.bx +++ b/Setup.bx @@ -42,6 +42,28 @@ class{ } } + // Vite Setup? + var vite = CLIRead( "Do you want to use Vite for your frontend build system? (y/n): " ).toLowerCase().booleanFormat(); + if( vite ){ + println( "🥊 Setting up Vite for your frontend build system" ) + // Copy the vite/.babelrc, vite/package.json, vite/vite.config.js to the root + fileMove( "vite/.babelrc", ".babelrc" ) + fileMove( "vite/package.json", "package.json" ) + fileMove( "vite/vite.config.js", "vite.config.js" ) + // Copy and override resources/vite/layouts/Main.bxm -> /app/layouts/Main.bxm + fileMove( "resources/vite/layouts/Main.bxm", "app/layouts/Main.bxm", true ) + fileMove( "resources/vite/assets", "resources/assets" ) + // Delete the vite directory + directoryDelete( "resources/vite", true ) + println( "✅ Vite setup complete!" ) + println( "👉 You can run 'npm install' to install the dependencies" ) + println( "👉 You can run 'npm run dev' to start the development server" ) + println( "👉 You can run 'npm run build' to build the production assets" ) + } else { + // Delete vite resources + directoryDelete( "resources/vite", true ) + } + // Remove the ignore array from box.json println( "🛁 Cleaning up your box.json" ) result = systemExecute( @@ -49,14 +71,9 @@ class{ arguments: "package set ignore=[]", directory: static.projectRoot ) - result = systemExecute( - name: "box", - arguments: "package clear scripts.postInstall", - directory: static.projectRoot - ) // Delete this setup file - println( "🥊 Your ColdBox application is ready to roll!" ) + println( "🥊 Your ColdBox BoxLang application is ready to roll!" ) println( "👉 Run 'box server start' to launch the development server." ) println( "👉 Run 'box coldbox help' to see a list of available commands from the ColdBox CLI" ) println( "ℹ️. You can remove the [Setup.bx] file from your project now or keep it for future reference." ) diff --git a/resources/vite/.babelrc b/resources/vite/.babelrc new file mode 100644 index 0000000..0b2a13d --- /dev/null +++ b/resources/vite/.babelrc @@ -0,0 +1,7 @@ +{ + "plugins": [ + ], + "presets" : [ + "@babel/preset-env" + ] +} diff --git a/resources/vite/assets/css/app.css b/resources/vite/assets/css/app.css new file mode 100644 index 0000000..a461c50 --- /dev/null +++ b/resources/vite/assets/css/app.css @@ -0,0 +1 @@ +@import "tailwindcss"; \ No newline at end of file diff --git a/resources/vite/assets/js/app.js b/resources/vite/assets/js/app.js new file mode 100644 index 0000000..a3ac4a4 --- /dev/null +++ b/resources/vite/assets/js/app.js @@ -0,0 +1,4 @@ +import { createApp } from "vue"; +import Hello from "./components/Hello.vue"; + +createApp( Hello ).mount( "#app" ); diff --git a/resources/vite/assets/js/components/Hello.vue b/resources/vite/assets/js/components/Hello.vue new file mode 100644 index 0000000..aa9ec8c --- /dev/null +++ b/resources/vite/assets/js/components/Hello.vue @@ -0,0 +1,7 @@ + diff --git a/resources/vite/layouts/Main.bxm b/resources/vite/layouts/Main.bxm new file mode 100755 index 0000000..1bf6c91 --- /dev/null +++ b/resources/vite/layouts/Main.bxm @@ -0,0 +1,25 @@ + + + + + + + + Welcome to Coldbox! + + + + + + + + #vite( [ "resources/assets/css/app.css", "resources/assets/js/app.js" ] )# + + +
+ + +
+ + +
diff --git a/resources/vite/package.json b/resources/vite/package.json new file mode 100644 index 0000000..9b2304b --- /dev/null +++ b/resources/vite/package.json @@ -0,0 +1,17 @@ +{ + "private": true, + "scripts": { + "dev": "vite", + "build": "vite build --emptyOutDir" + }, + "dependencies": { + "vue": "^3.5.22", + "tailwindcss": "^4.1.14" + }, + "devDependencies": { + "vite": "^6.3.6", + "@vitejs/plugin-vue": "6.0.1", + "@tailwindcss/vite": "^4.1.14", + "coldbox-vite-plugin": "^3.0.2" + } +} diff --git a/resources/vite/vite.config.mjs b/resources/vite/vite.config.mjs new file mode 100644 index 0000000..a933ea5 --- /dev/null +++ b/resources/vite/vite.config.mjs @@ -0,0 +1,15 @@ +import { defineConfig } from "vite"; +import vue from "@vitejs/plugin-vue"; +import coldbox from "coldbox-vite-plugin"; +import tailwindcss from "@tailwindcss/vite"; + +export default defineConfig({ + plugins: [ + vue(), + tailwindcss(), + coldbox({ + input: [ "resources/assets/css/app.css", "resources/assets/js/app.js" ], + refresh: true + }) + ], +}); \ No newline at end of file From 67ffef771884233ce97dee0fc71070ab7e5773ec Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 9 Oct 2025 17:33:22 +0200 Subject: [PATCH 13/23] setup complete vite now. --- .github/copilot-instructions.md | 515 ++++++++++++++++++++++++++++++++ Setup.bx | 16 +- 2 files changed, 523 insertions(+), 8 deletions(-) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..c7109f5 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,515 @@ +# BoxLang ColdBox Template - AI Coding Instructions + +This is a BoxLang application template using the ColdBox HMVC framework. These instructions are designed to help AI assistants understand the project structure and provide better coding assistance. + +## 🎯 Project Overview + +**Language**: BoxLang (modern JVM language, successor to CFML) +**Framework**: ColdBox HMVC Framework v8+ +**Template Type**: Full-stack web application template +**Architecture**: Model-View-Controller with Hierarchical MVC support + +## ⚙️ Requirements + +This project requires the following to be installed on your operating system: + +### Required Software + +1. **CommandBox** - CLI toolchain, package manager, and server runtime + - Installation: https://commandbox.ortusbooks.com/getting-started/installation + - Minimum Version: 6.0+ + - Used for: dependency management, server starting, testing, and task automation + +2. **BoxLang** - Modern JVM language runtime + - Installation: https://boxlang.ortusbooks.com/getting-started/installation + - Minimum Version: 1.0+ + - Can be installed via CommandBox: `box install commandbox-boxlang` + - Used for: running BoxLang applications and scripts + +### Verification + +Verify installations: +```bash +# Check CommandBox is installed +box version + +# Check BoxLang is available +box boxlang version + +# Verify project setup (run from project root) +boxlang Setup.bx +``` + +### Optional but Recommended + +- **Java JDK** - Required for BoxLang runtime (JDK 11+ recommended) +- **Docker** - For containerized development (if using Docker setup) +- **Git** - For version control + +## 📁 Project Structure + +``` +/app/ # Application source code +├── Application.bx # Application entry point +├── config/ # Framework configuration +│ ├── CacheBox.bx # Caching configuration +│ ├── ColdBox.bx # Main ColdBox settings +│ ├── Router.bx # Route definitions +│ ├── Scheduler.bx # Scheduled tasks +│ └── WireBox.bx # Dependency injection configuration +├── handlers/ # Controllers (request handlers) +├── helpers/ # View helper functions +├── interceptors/ # Event interceptors/listeners +├── layouts/ # Page layouts/templates +├── logs/ # Application logs +├── models/ # Business logic and data models +├── modules/ # Application-specific modules (HMVC) +└── views/ # View templates + +/public/ # Web-accessible files (document root) +├── Application.bx # Public application bootstrap +├── index.bxm # Application entry point (markup) +├── favicon.ico # Site favicon +├── robots.txt # Search engine directives +└── includes/ # Static assets + ├── i18n/ # Client-side translations + └── images/ # Image assets (CSS, JS in subfolders) + +/resources/ # Non-web resources +├── database/ # Database management +│ └── migrations/ # Database migration files +└── copilot-instructions.md # This file (AI coding instructions) + +/tests/ # Test suites +├── Application.bx # Test application setup +├── index.bxm # Test runner entry point +├── runner.bxm # TestBox runner +├── specs/ # BDD test specifications +└── resources/ # Test resources and fixtures + +/docker/ # Docker configuration +├── Dockerfile # Container image definition +└── docker-compose.yml # Multi-container setup + +/runtime/ # BoxLang runtime files (auto-generated) +├── boxlang.json # Runtime configuration +└── global/ # Global runtime cache + +/.devcontainer/ # VS Code dev container configuration +/.github/ # GitHub Actions workflows and settings +/.vscode/ # VS Code workspace settings + +# Root Configuration Files +├── Build.bx # Build automation script +├── box.json # CommandBox package descriptor +├── server.json # CommandBox server configuration +├── .env.example # Environment variable template +├── .cfformat.json # Code formatting rules +├── .cflintrc # Linting configuration +├── .editorconfig # Editor configuration +└── pom.xml # Maven build configuration (optional) +``` + +## 🔧 Key Technologies + +### Core Stack + +- **BoxLang**: Modern JVM language with CFML compatibility +- **ColdBox**: HMVC framework for enterprise applications +- **WireBox**: Dependency injection and AOP container +- **CacheBox**: Enterprise caching engine +- **LogBox**: Logging and debugging framework + +### Development Tools + +- **CommandBox**: CLI toolchain and package manager +- **TestBox**: BDD/TDD testing framework +- **CFFormat**: Code formatting and linting + +## 📝 BoxLang Syntax Guidelines + +### File Extensions + +- `.bx` - BoxLang classes and components +- `.bxm` - BoxLang markup (templates/views) +- `.bxs` - BoxLang scripts + +### Class Declaration + +```js +// BoxLang uses 'class' keyword instead of 'component' +class extends="BaseHandler" { + + function index( event, rc, prc ){ + return "Hello from BoxLang!"; + } + +} +``` + +### Function Syntax + +```js +// Modern function syntax +function getUserById( required numeric id ){ + return userService.findById( arguments.id ); +} + +// Arrow functions supported as closures +variables.transform = ( item ) => item.toUpperCase(); + +// Thin Arrow function supported as pure lambdas +// BoxLang lambdas only have acess to arguments and function local scope +variables.filter = ( item ) -> item.isActive(); +``` + +### Dependency Injection + +```js +class UserService { + // Long Form + @inject( "UserDAO" ) + property userDAO; + + // Short Form if the property name matches the class name + @inject + property userDAO; + + @inject + property wirebox; + + function getUsers(){ + return userDAO.list(); + } +} +``` + +## 🎛️ ColdBox Patterns + +### Handler Actions + +```js +class extends="BaseHandler" { + + function index( event, rc, prc ){ + prc.users = userService.list(); + event.setView( "users/index" ); + } + + function show( event, rc, prc ){ + prc.user = userService.get( rc.id ); + event.setView( "users/show" ); + } +} +``` + +### Models with Dependency Injection + +```js +class extends="BaseService" { + + @inject( "UserGateway" ) + property userGateway; + + @inject + property cachebox; + + function list(){ + return cachebox.get( "users" ) ?: userGateway.getAll(); + } +} +``` + +### Event-Driven Architecture + +```js +// Interceptors for cross-cutting concerns +class SecurityInterceptor { + + function preProcess( event, data ){ + if( !security.isLoggedIn() ){ + relocate( "auth.login" ); + } + } +} +``` + +## 🧪 Testing Patterns + +### BDD Test Structure + +```js +class extends="BaseTestCase" { + + function beforeAll(){ + super.beforeAll(); + userService = getInstance( "UserService" ); + } + + function run(){ + describe( "UserService", () => { + + it( "should return all users", () => { + var users = userService.list() + expect( users ).toBeArray() + }) + + }) + } +} +``` + +## 🔗 Common Integrations + +### Database Operations + +- Use ColdBox ORM or Query Builder or Quick ORM or native queries +- Migration files in `/resources/database/migrations/` +- Seeders in `/resources/database/seeds/` + +### API Development + +- RESTful handlers in `/handlers/api/` +- **Use RESTHandler base class** for REST API endpoints +- Use `event.renderData()` for JSON responses +- JWT authentication via ColdBox Security module + +#### RESTHandler Pattern + +For REST API development, extend `coldbox.system.RestHandler` instead of `BaseHandler`: + +```js +class extends="coldbox.system.RestHandler" { + + // RESTHandler provides automatic: + // - Content negotiation (JSON, XML, HTML) + // - HTTP status code handling + // - Error response formatting + // - CORS support + + function index( event, rc, prc ){ + // Automatically serializes to requested format + return userService.list(); + } + + function show( event, rc, prc ){ + prc.response = userService.get( rc.id ); + } + + function create( event, rc, prc ){ + var result = userService.create( rc ); + prc.statusCode = 201; + return result; + } + + function update( event, rc, prc ){ + return userService.update( rc.id, rc ); + } + + function delete( event, rc, prc ){ + userService.delete( rc.id ); + prc.statusCode = 204; + } + + // Handle errors with proper HTTP status codes + function onError( event, rc, prc, faultAction, exception ){ + prc.statusCode = 500; + return { + "error": true, + "messages": [ exception.message ] + }; + } +} +``` + +**RESTHandler Benefits**: + +- Automatic content type detection and rendering +- Built-in HTTP verb routing (GET, POST, PUT, DELETE, PATCH) +- Standardized error handling with `onError()` and `onInvalidHTTPMethod()` +- Easy status code management via `prc.statusCode` +- Return data directly from actions (auto-serialization) + +**Documentation**: https://coldbox.ortusbooks.com/digging-deeper/rest-handler + +#### Resourceful Routes (RECOMMENDED for ALL Resource-Based Handlers) + +**Always use resourceful routes** for both web handlers and REST APIs. ColdBox provides automatic route generation following RESTful conventions, reducing boilerplate and ensuring consistency. + +```js +// In /app/config/Router.bx +function configure(){ + + // Standard resourceful routes (web handlers with views) + resources( "photos" ); + // Generates: index, new, create, show, edit, update, delete + // Perfect for traditional CRUD web interfaces + + // API resourceful routes (REST APIs, JSON responses) + apiResources( "users" ); + // Generates: index, show, create, update, delete (no 'new' or 'edit' forms) + // Use with RestHandler for REST APIs + + // Nested resources for hierarchical data + resources( "photos", function(){ + resources( "comments" ); + }); + // Generates: /photos/:photoId/comments + + // Restrict to specific actions + resources( resource="articles", only="index,show" ); + resources( resource="profiles", except="delete" ); + + // Customize parameter name + resources( resource="users", parameterName="userId" ); +} +``` + +**Generated Routes for `resources( "photos" )` (Web)**: + +| HTTP Verb | Route | Action | Purpose | +|-----------|------------------|----------|----------------------------| +| GET | /photos | index | List all photos | +| GET | /photos/new | new | Show create form | +| POST | /photos | create | Create new photo | +| GET | /photos/:id | show | Show specific photo | +| GET | /photos/:id/edit | edit | Show edit form | +| PUT/PATCH | /photos/:id | update | Update specific photo | +| DELETE | /photos/:id | delete | Delete specific photo | + +**Generated Routes for `apiResources( "users" )` (API)**: + +| HTTP Verb | Route | Action | Purpose | +|-----------|------------------|----------|----------------------------| +| GET | /users | index | List all users | +| GET | /users/:id | show | Get specific user | +| POST | /users | create | Create new user | +| PUT/PATCH | /users/:id | update | Update specific user | +| DELETE | /users/:id | delete | Delete specific user | + +**Benefits**: +- **Use `resources()` for web handlers** (with forms and views) and `apiResources()` for REST APIs +- Automatic RESTful route generation following industry standards +- Consistent URL patterns across your entire application +- Reduced boilerplate route definitions +- Easy to understand and maintain +- Works seamlessly with BaseHandler (web) and RestHandler (API) base classes +- Supports nested resources for hierarchical data +- Self-documenting route structure + +**When to Use**: +- ✅ Any CRUD-based resource (users, posts, photos, products, etc.) +- ✅ Traditional web interfaces with forms (`resources()`) +- ✅ REST APIs returning JSON/XML (`apiResources()`) +- ✅ Nested/hierarchical resources (posts with comments) +- ❌ Non-resource actions (search, reports, utilities) - use custom routes + +**Documentation**: https://coldbox.ortusbooks.com/the-basics/routing/routing-dsl/resourceful-routes + +### Frontend Integration + +- Assets managed via `/public/assets/` +- View helpers for asset compilation +- Modern JavaScript/CSS build tools supported + +## 🚀 Development Workflow + +### Local Development +```bash +# Install dependencies +box install + +# Start development server +box server start + +# Run tests +box testbox run + +# Format code +box run-script format +``` + +### Code Quality + +- Follow Ortus coding standards +- Use CFFormat for consistent formatting +- Write tests for all business logic +- Document public APIs with JavaDoc-style comments + +## 🔍 AI Assistant Guidelines + +### When Generating Code + +1. **Use BoxLang syntax** (class, not component) +2. **Follow ColdBox conventions** for handlers, models, views +3. **Include proper dependency injection** when creating services +4. **Write accompanying tests** for new functionality +5. **Use modern BoxLang features** (arrow functions, proper typing) + +### File Creation Patterns + +- **Handlers**: Extend `BaseHandler`, use dependency injection +- **Models**: Extend appropriate base classes, implement interfaces +- **Tests**: Follow BDD patterns with `describe()` and `it()` +- **Views**: Use `.bxm` extension, leverage ColdBox view helpers + +### Security Considerations + +- Always validate input parameters +- Use ColdBox Security module for authentication +- Implement CSRF protection for forms +- Sanitize output in views + +## 📚 Documentation References + +- [BoxLang Documentation](https://boxlang.ortusbooks.com/) +- [ColdBox Documentation](https://coldbox.ortusbooks.com/) +- [TestBox Documentation](https://testbox.ortusbooks.com/) +- [WireBox Documentation](https://wirebox.ortusbooks.com/) +- [CacheBox Documentation](https://cachebox.ortusbooks.com/) +- [LogBox Documentation](https://logbox.ortusbooks.com/) +- [Ortus Coding Standards](https://github.com/Ortus-Solutions/coding-standards) + +## 🤖 MCP (Model Context Protocol) Servers + +For AI assistants that support MCP, access comprehensive documentation through these servers: + +### Framework Documentation +- **ColdBox MCP Server**: `https://coldbox.ortusbooks.com/~gitbook/mcp` +- **WireBox MCP Server**: `https://wirebox.ortusbooks.com/~gitbook/mcp` +- **CacheBox MCP Server**: `https://cachebox.ortusbooks.com/~gitbook/mcp` +- **LogBox MCP Server**: `https://logbox.ortusbooks.com/~gitbook/mcp` + +### Language & Testing Documentation +- **BoxLang MCP Server**: `https://boxlang.ortusbooks.com/~gitbook/mcp` +- **TestBox MCP Server**: `https://testbox.ortusbooks.com/~gitbook/mcp` + +These MCP servers provide real-time access to official documentation, examples, and API references. AI assistants can query these servers for: +- Framework APIs and configuration options +- Code examples and patterns +- Best practices and conventions +- Troubleshooting guides +- Version-specific documentation + +## 🎯 Best Practices + +### Performance + +- Use CacheBox for expensive operations +- Implement proper database indexing +- Lazy load dependencies when appropriate +- Use async patterns for I/O operations + +### Maintainability + +- Keep handlers thin, move logic to services +- Use events for decoupled communication +- Implement proper error handling +- Write comprehensive tests + +### Security + +- Validate all inputs +- Use parameterized queries +- Implement proper session management +- Regular security audits of dependencies \ No newline at end of file diff --git a/Setup.bx b/Setup.bx index 4e8d04d..7c260d2 100644 --- a/Setup.bx +++ b/Setup.bx @@ -27,7 +27,7 @@ class{ // Maven setup // Ask the user if they want to use Maven for Java dependency management - var maven = CLIRead( "Do you want to use Maven for any Java dependency management? (y/n): " ).toLowerCase().booleanFormat(); + var maven = CLIRead( "🙋 Do you want to use Maven for any Java dependency management? (y/n): " ).toLowerCase().booleanFormat(); if( maven ){ println( "🥊 Setting up a [pom.xml] in your root for Java dependency management" ) println( "👉 You can add your Java dependencies to the [dependencies] section of the pom.xml" ) @@ -43,18 +43,18 @@ class{ } // Vite Setup? - var vite = CLIRead( "Do you want to use Vite for your frontend build system? (y/n): " ).toLowerCase().booleanFormat(); + var vite = CLIRead( "🙋 Do you want to use Vite for your frontend build system? (y/n): " ).toLowerCase().booleanFormat(); if( vite ){ println( "🥊 Setting up Vite for your frontend build system" ) // Copy the vite/.babelrc, vite/package.json, vite/vite.config.js to the root - fileMove( "vite/.babelrc", ".babelrc" ) - fileMove( "vite/package.json", "package.json" ) - fileMove( "vite/vite.config.js", "vite.config.js" ) + fileCopy( "resources/vite/.babelrc", ".babelrc" ) + fileCopy( "resources/vite/package.json", "package.json" ) + fileCopy( "resources/vite/vite.config.mjs", "vite.config.mjs" ) // Copy and override resources/vite/layouts/Main.bxm -> /app/layouts/Main.bxm - fileMove( "resources/vite/layouts/Main.bxm", "app/layouts/Main.bxm", true ) - fileMove( "resources/vite/assets", "resources/assets" ) + fileDelete( "app/layouts/Main.bxm" ) + fileCopy( "resources/vite/layouts/Main.bxm", "app/layouts/Main.bxm" ) + fileCopy( "resources/vite/assets", "resources/assets" ) // Delete the vite directory - directoryDelete( "resources/vite", true ) println( "✅ Vite setup complete!" ) println( "👉 You can run 'npm install' to install the dependencies" ) println( "👉 You can run 'npm run dev' to start the development server" ) From 4b69f9f9a5b24d1228550c090966b521acbff856 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 9 Oct 2025 17:42:59 +0200 Subject: [PATCH 14/23] vite setup complete --- .gitignore | 3 +++ app/config/modules/.gitkeep | 0 resources/assets/css/app.css | 1 + resources/assets/js/app.js | 4 ++++ resources/assets/js/components/Hello.vue | 7 +++++++ resources/vite/vite.config.mjs | 3 ++- 6 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 app/config/modules/.gitkeep create mode 100644 resources/assets/css/app.css create mode 100644 resources/assets/js/app.js create mode 100644 resources/assets/js/components/Hello.vue diff --git a/.gitignore b/.gitignore index 17e57ca..fa42297 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,9 @@ build/** .cbfs/** docker/.db/** +# Vite +/public/includes/build/** + # logs + tests app/logs/** runtime/logs/** diff --git a/app/config/modules/.gitkeep b/app/config/modules/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/resources/assets/css/app.css b/resources/assets/css/app.css new file mode 100644 index 0000000..a461c50 --- /dev/null +++ b/resources/assets/css/app.css @@ -0,0 +1 @@ +@import "tailwindcss"; \ No newline at end of file diff --git a/resources/assets/js/app.js b/resources/assets/js/app.js new file mode 100644 index 0000000..a3ac4a4 --- /dev/null +++ b/resources/assets/js/app.js @@ -0,0 +1,4 @@ +import { createApp } from "vue"; +import Hello from "./components/Hello.vue"; + +createApp( Hello ).mount( "#app" ); diff --git a/resources/assets/js/components/Hello.vue b/resources/assets/js/components/Hello.vue new file mode 100644 index 0000000..aa9ec8c --- /dev/null +++ b/resources/assets/js/components/Hello.vue @@ -0,0 +1,7 @@ + diff --git a/resources/vite/vite.config.mjs b/resources/vite/vite.config.mjs index a933ea5..52e3011 100644 --- a/resources/vite/vite.config.mjs +++ b/resources/vite/vite.config.mjs @@ -9,7 +9,8 @@ export default defineConfig({ tailwindcss(), coldbox({ input: [ "resources/assets/css/app.css", "resources/assets/js/app.js" ], - refresh: true + refresh: true, + publicDirectory: "public/includes" }) ], }); \ No newline at end of file From a4b4a162488e103432cc575d9d0736d384e4a5ce Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 9 Oct 2025 17:52:15 +0200 Subject: [PATCH 15/23] docker now automated --- Setup.bx | 22 +++++++++++++++---- .../docker/.dockerignore | 0 {docker => resources/docker}/Dockerfile | 0 .../docker}/docker-compose.yml | 0 4 files changed, 18 insertions(+), 4 deletions(-) rename .dockerignore => resources/docker/.dockerignore (100%) rename {docker => resources/docker}/Dockerfile (100%) rename {docker => resources/docker}/docker-compose.yml (100%) diff --git a/Setup.bx b/Setup.bx index 7c260d2..0f4fc21 100644 --- a/Setup.bx +++ b/Setup.bx @@ -46,24 +46,38 @@ class{ var vite = CLIRead( "🙋 Do you want to use Vite for your frontend build system? (y/n): " ).toLowerCase().booleanFormat(); if( vite ){ println( "🥊 Setting up Vite for your frontend build system" ) - // Copy the vite/.babelrc, vite/package.json, vite/vite.config.js to the root fileCopy( "resources/vite/.babelrc", ".babelrc" ) fileCopy( "resources/vite/package.json", "package.json" ) fileCopy( "resources/vite/vite.config.mjs", "vite.config.mjs" ) - // Copy and override resources/vite/layouts/Main.bxm -> /app/layouts/Main.bxm fileDelete( "app/layouts/Main.bxm" ) fileCopy( "resources/vite/layouts/Main.bxm", "app/layouts/Main.bxm" ) fileCopy( "resources/vite/assets", "resources/assets" ) - // Delete the vite directory println( "✅ Vite setup complete!" ) println( "👉 You can run 'npm install' to install the dependencies" ) println( "👉 You can run 'npm run dev' to start the development server" ) println( "👉 You can run 'npm run build' to build the production assets" ) } else { - // Delete vite resources directoryDelete( "resources/vite", true ) } + // Docker Setup? + var docker = CLIRead( "🙋 Do you want to use Docker for containerization? (y/n): " ).toLowerCase().booleanFormat(); + if( docker ){ + println( "🥊 Setting up Docker for containerization" ) + directoryCreate( "docker", true, true ) + fileCopy( "resources/docker/Dockerfile", "docker/Dockerfile" ) + fileCopy( "resources/docker/docker-compose.yml", "docker/docker-compose.yml" ) + fileCopy( "resources/docker/.dockerignore", ".dockerignore" ) + println( "✅ Docker setup complete!" ) + println( "ℹ️. Your docker files are located in the [docker] directory" ) + println( "👉 You can run 'box run-script docker:build' to build your Docker image." ) + println( "👉 You can run 'box run-script docker:run' to run your Docker container." ) + println( "👉 You can run 'box run-script docker:bash' to go into the container shell." ) + println( "👉 You can run 'box run-script docker:stack' to startup the Docker Compose Stack" ) + } else { + directoryDelete( "resources/docker", true ) + } + // Remove the ignore array from box.json println( "🛁 Cleaning up your box.json" ) result = systemExecute( diff --git a/.dockerignore b/resources/docker/.dockerignore similarity index 100% rename from .dockerignore rename to resources/docker/.dockerignore diff --git a/docker/Dockerfile b/resources/docker/Dockerfile similarity index 100% rename from docker/Dockerfile rename to resources/docker/Dockerfile diff --git a/docker/docker-compose.yml b/resources/docker/docker-compose.yml similarity index 100% rename from docker/docker-compose.yml rename to resources/docker/docker-compose.yml From 6a028c616a9b0b968a43cad0295ded4870802e63 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 9 Oct 2025 17:57:58 +0200 Subject: [PATCH 16/23] devcontainer setup --- Setup.bx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Setup.bx b/Setup.bx index 0f4fc21..2541244 100644 --- a/Setup.bx +++ b/Setup.bx @@ -78,6 +78,17 @@ class{ directoryDelete( "resources/docker", true ) } + // Dev Container + var devcontainer = CLIRead( "🙋 Do you want to keep the Dev Container setup so you can code in GitHub (.devcontainer)? (y/n): " ).toLowerCase().booleanFormat(); + if( !devcontainer ){ + println( "🧹 Cleaning up unnecessary files" ) + directoryDelete( ".devcontainer", true ) + } else { + println( "ℹ️. Your Dev Container files are located in the [.devcontainer] directory" ) + println( "👉 You can open this project in a container in VS Code by clicking the green button in the bottom left corner." ) + println( "👉 You can customize the container by modifying the [.devcontainer/devcontainer.json] and [.devcontainer/Dockerfile] files." ) + } + // Remove the ignore array from box.json println( "🛁 Cleaning up your box.json" ) result = systemExecute( From 93d35093a4094d519250fd4ea25920c0190c96d6 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 9 Oct 2025 18:32:58 +0200 Subject: [PATCH 17/23] rest updates --- Setup.bx | 76 ++++++++-- resources/rest/Router.bx | 39 +++++ .../rest/apidocs/_schemas/app-response.json | 26 ++++ resources/rest/apidocs/_schemas/app-user.json | 36 +++++ .../rest/apidocs/_schemas/app-validation.json | 33 +++++ .../rest/apidocs/auth/login/example.200.json | 8 ++ .../rest/apidocs/auth/login/example.401.json | 49 +++++++ .../rest/apidocs/auth/login/requestBody.json | 29 ++++ .../rest/apidocs/auth/login/responses.json | 88 ++++++++++++ .../rest/apidocs/auth/logout/example.200.json | 8 ++ .../rest/apidocs/auth/logout/example.500.json | 8 ++ .../rest/apidocs/auth/logout/responses.json | 29 ++++ .../apidocs/auth/register/example.200.json | 18 +++ .../apidocs/auth/register/example.400.json | 8 ++ .../apidocs/auth/register/requestBody.json | 41 ++++++ .../rest/apidocs/auth/register/responses.json | 75 ++++++++++ .../rest/apidocs/echo/index/example.200.json | 6 + .../rest/apidocs/echo/index/responses.json | 37 +++++ .../rest/apidocs/echo/whoami/example.200.json | 13 ++ .../rest/apidocs/echo/whoami/example.401.json | 8 ++ .../rest/apidocs/echo/whoami/responses.json | 74 ++++++++++ resources/rest/handlers/Auth.bx | 75 ++++++++++ resources/rest/handlers/Echo.bx | 43 ++++++ resources/rest/handlers/Main.bx | 38 +++++ resources/rest/models/.gitkeep | 0 resources/rest/models/User.bx | 30 ++++ resources/rest/models/UserService.bx | 133 ++++++++++++++++++ resources/rest/specs/integration/AuthTests.bx | 131 +++++++++++++++++ resources/rest/specs/integration/EchoTests.bx | 61 ++++++++ resources/rest/specs/unit/UserServiceTest.bx | 62 ++++++++ resources/rest/specs/unit/UserTest.bx | 35 +++++ 31 files changed, 1302 insertions(+), 15 deletions(-) create mode 100644 resources/rest/Router.bx create mode 100644 resources/rest/apidocs/_schemas/app-response.json create mode 100644 resources/rest/apidocs/_schemas/app-user.json create mode 100644 resources/rest/apidocs/_schemas/app-validation.json create mode 100644 resources/rest/apidocs/auth/login/example.200.json create mode 100644 resources/rest/apidocs/auth/login/example.401.json create mode 100644 resources/rest/apidocs/auth/login/requestBody.json create mode 100644 resources/rest/apidocs/auth/login/responses.json create mode 100644 resources/rest/apidocs/auth/logout/example.200.json create mode 100644 resources/rest/apidocs/auth/logout/example.500.json create mode 100644 resources/rest/apidocs/auth/logout/responses.json create mode 100644 resources/rest/apidocs/auth/register/example.200.json create mode 100644 resources/rest/apidocs/auth/register/example.400.json create mode 100644 resources/rest/apidocs/auth/register/requestBody.json create mode 100644 resources/rest/apidocs/auth/register/responses.json create mode 100644 resources/rest/apidocs/echo/index/example.200.json create mode 100644 resources/rest/apidocs/echo/index/responses.json create mode 100644 resources/rest/apidocs/echo/whoami/example.200.json create mode 100644 resources/rest/apidocs/echo/whoami/example.401.json create mode 100644 resources/rest/apidocs/echo/whoami/responses.json create mode 100644 resources/rest/handlers/Auth.bx create mode 100644 resources/rest/handlers/Echo.bx create mode 100644 resources/rest/handlers/Main.bx create mode 100644 resources/rest/models/.gitkeep create mode 100644 resources/rest/models/User.bx create mode 100644 resources/rest/models/UserService.bx create mode 100644 resources/rest/specs/integration/AuthTests.bx create mode 100755 resources/rest/specs/integration/EchoTests.bx create mode 100644 resources/rest/specs/unit/UserServiceTest.bx create mode 100644 resources/rest/specs/unit/UserTest.bx diff --git a/Setup.bx b/Setup.bx index 2541244..1114578 100644 --- a/Setup.bx +++ b/Setup.bx @@ -42,22 +42,68 @@ class{ } } - // Vite Setup? - var vite = CLIRead( "🙋 Do you want to use Vite for your frontend build system? (y/n): " ).toLowerCase().booleanFormat(); - if( vite ){ - println( "🥊 Setting up Vite for your frontend build system" ) - fileCopy( "resources/vite/.babelrc", ".babelrc" ) - fileCopy( "resources/vite/package.json", "package.json" ) - fileCopy( "resources/vite/vite.config.mjs", "vite.config.mjs" ) - fileDelete( "app/layouts/Main.bxm" ) - fileCopy( "resources/vite/layouts/Main.bxm", "app/layouts/Main.bxm" ) - fileCopy( "resources/vite/assets", "resources/assets" ) - println( "✅ Vite setup complete!" ) - println( "👉 You can run 'npm install' to install the dependencies" ) - println( "👉 You can run 'npm run dev' to start the development server" ) - println( "👉 You can run 'npm run build' to build the production assets" ) + // Is this a REST API only project? + var apiOnly = CLIRead( "🙋 Is this a REST API only project? (y/n): " ).toLowerCase().booleanFormat(); + if( apiOnly ){ + println( "🥊 Setting up a REST API only ColdBox application" ) + println( "👉 You can always add views and layouts later if you change your mind" ) + + // Router + fileDelete( "app/config/Router.bx" ) + fileCopy( "resources/rest/Router.bx", "app/config/Router.bx" ) + // Tests + directoryDelete( "tests/specs", true ) + directoryCopy( source: "resources/rest/specs", destination: "tests/specs", recurse: true, createPath: true ) + // Models + directoryDelete( "app/models", true ) + directoryCopy( source: "resources/rest/models", destination: "app/models", recurse: true, createPath: true ) + // Handlers + directoryDelete( "app/handlers", true ) + directoryCopy( source: "resources/rest/handlers", destination: "app/handlers", recurse: true, createPath: true ) + // Api Docs + directoryCopy( source: "resources/rest/apidocs", destination: "resources/apidocs", recurse: true, createPath: true ) + directoryDelete( "resources/rest", true ) + + // Install CommandBox Modules + println( "🥊 Installing ColdBox API Production Modules: Security, Mementifier, Validation" ) + result = systemExecute( + name: "box", + arguments: "install cbsecurity,mementifier,cbvalidation", + directory: static.projectRoot + ) + println( result.output ) + + println( "🥊 Installing ColdBox API Development Modules: route-visualizer,relax" ) + result = systemExecute( + name: "box", + arguments: "install route-visualizer,relax --saveDev", + directory: static.projectRoot + ) + println( result.output ) + + println( "✅ REST API only setup complete!" ) } else { - directoryDelete( "resources/vite", true ) + directoryDelete( "resources/rest", true ) + } + + // Vite Setup, only if not api only + if( !apiOnly ){ + var vite = CLIRead( "🙋 Do you want to use Vite for your frontend build system? (y/n): " ).toLowerCase().booleanFormat(); + if( vite ){ + println( "🥊 Setting up Vite for your frontend build system" ) + fileCopy( "resources/vite/.babelrc", ".babelrc" ) + fileCopy( "resources/vite/package.json", "package.json" ) + fileCopy( "resources/vite/vite.config.mjs", "vite.config.mjs" ) + fileDelete( "app/layouts/Main.bxm" ) + fileCopy( "resources/vite/layouts/Main.bxm", "app/layouts/Main.bxm" ) + fileCopy( "resources/vite/assets", "resources/assets" ) + println( "✅ Vite setup complete!" ) + println( "👉 You can run 'npm install' to install the dependencies" ) + println( "👉 You can run 'npm run dev' to start the development server" ) + println( "👉 You can run 'npm run build' to build the production assets" ) + } else { + directoryDelete( "resources/vite", true ) + } } // Docker Setup? diff --git a/resources/rest/Router.bx b/resources/rest/Router.bx new file mode 100644 index 0000000..ed49409 --- /dev/null +++ b/resources/rest/Router.bx @@ -0,0 +1,39 @@ +/** + * This is your application router. From here you can controll all the incoming routes to your application. + * + * https://coldbox.ortusbooks.com/the-basics/routing + */ +class { + + function configure(){ + /** + * -------------------------------------------------------------------------- + * App Routes + * -------------------------------------------------------------------------- + * Here is where you can register the routes for your web application! + * Go get Funky! + */ + + // A nice healthcheck route example + route( "/healthcheck", function( event, rc, prc ){ + return "Ok!" + } ) + + // API Echo + get( "/api/echo", "Echo.index" ) + + // API Authentication Routes + post( "/api/login", "Auth.login" ) + post( "/api/logout", "Auth.logout" ) + post( "/api/register", "Auth.register" ) + + // API Secured Routes + get( "/api/whoami", "Echo.whoami" ) + + // @app_routes@ + + // Conventions-Based Routing + route( ":handler/:action?" ).end() + } + +} diff --git a/resources/rest/apidocs/_schemas/app-response.json b/resources/rest/apidocs/_schemas/app-response.json new file mode 100644 index 0000000..f34fea7 --- /dev/null +++ b/resources/rest/apidocs/_schemas/app-response.json @@ -0,0 +1,26 @@ +{ + "type": "object", + "properties": { + "error": { + "description": "Flag to indicate an error.", + "type": "boolean" + }, + "messages": { + "description": "An array of messages related to the request.", + "type": "array", + "items": { + "type": "string" + } + }, + "pagination" : { + "description": "Pagination information.", + "type": "object", + "properties": {} + }, + "data": { + "description": "The data packet", + "type": "object", + "properties": {} + } + } +} \ No newline at end of file diff --git a/resources/rest/apidocs/_schemas/app-user.json b/resources/rest/apidocs/_schemas/app-user.json new file mode 100644 index 0000000..04e9536 --- /dev/null +++ b/resources/rest/apidocs/_schemas/app-user.json @@ -0,0 +1,36 @@ +{ + "description": "The user that is logged in", + "type": "object", + "properties": { + "id": { + "description": "The User ID", + "type": "integer" + }, + "firstName": { + "description": "The user's first name", + "type": "string" + }, + "lastName": { + "description": "The user's last name", + "type": "integer" + }, + "username": { + "description": "The user's username", + "type": "string" + }, + "permissions": { + "description": "The collection of permissions", + "type": "array", + "items" : { + "type" : "string" + } + }, + "roles": { + "description": "The collection of roles", + "type": "array", + "items" : { + "type" : "string" + } + } + } +} diff --git a/resources/rest/apidocs/_schemas/app-validation.json b/resources/rest/apidocs/_schemas/app-validation.json new file mode 100644 index 0000000..2c4d329 --- /dev/null +++ b/resources/rest/apidocs/_schemas/app-validation.json @@ -0,0 +1,33 @@ +{ + "description" : "An invalid field will contain an array of information messages", + "type" : "array", + "items" : { + "type" : "object", + "properties" : { + "MESSAGE" :{ + "type" : "string", + "description" : "The human readable error message" + }, + "VALIDATIONDATA" :{ + "type" : "boolean", + "description" : "The validation data attached" + }, + "ERRORMETADATA" :{ + "type" : "object", + "description" : "Any error metdata attached" + }, + "REJECTEDVALUE" :{ + "type" : "string", + "description" : "The rejected value if any" + }, + "VALIDATIONTYPE" :{ + "type" : "string", + "description" : "The contraint type applied" + }, + "FIELD" :{ + "type" : "string", + "description" : "The field name that caused the validation" + } + } + } +} \ No newline at end of file diff --git a/resources/rest/apidocs/auth/login/example.200.json b/resources/rest/apidocs/auth/login/example.200.json new file mode 100644 index 0000000..b7152bc --- /dev/null +++ b/resources/rest/apidocs/auth/login/example.200.json @@ -0,0 +1,8 @@ +{ + "data": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE1ODkzMDUwMzEsInNjb3BlcyI6W10sImlzcyI6Imh0dHA6Ly8xMjcuMC4wLjE6NjUxMDAvIiwic3ViIjoxLCJleHAiOjE1ODkzMDg2MzEsImp0aSI6IkQxNkI4Qzg5NTc2OUU5MDcyNUNBQTU3M0I3M0FCNTc2In0.VDsqEDCXtHnrMXZYXqE2To2lQpmQAmHP8asXjhw_c2KsI_1mx2gZETuIrVNXckUl9zdevx2O818PlCkp6znmGw", + "error": false, + "pagination": {}, + "messages": [ + "Bearer token created and it expires in 60 minutes" + ] +} \ No newline at end of file diff --git a/resources/rest/apidocs/auth/login/example.401.json b/resources/rest/apidocs/auth/login/example.401.json new file mode 100644 index 0000000..e4e16aa --- /dev/null +++ b/resources/rest/apidocs/auth/login/example.401.json @@ -0,0 +1,49 @@ +{ + "data": { + "lastName": [ + { + "MESSAGE": "The 'lastName' value is required", + "VALIDATIONDATA": true, + "ERRORMETADATA": {}, + "REJECTEDVALUE": "", + "VALIDATIONTYPE": "Required", + "FIELD": "lastName" + } + ], + "firstName": [ + { + "MESSAGE": "The 'firstName' value is required", + "VALIDATIONDATA": true, + "ERRORMETADATA": {}, + "REJECTEDVALUE": "", + "VALIDATIONTYPE": "Required", + "FIELD": "firstName" + } + ], + "PASSWORD": [ + { + "MESSAGE": "The 'PASSWORD' value is required", + "VALIDATIONDATA": true, + "ERRORMETADATA": {}, + "REJECTEDVALUE": "", + "VALIDATIONTYPE": "Required", + "FIELD": "PASSWORD" + } + ], + "USERNAME": [ + { + "MESSAGE": "The 'USERNAME' value is required", + "VALIDATIONDATA": true, + "ERRORMETADATA": {}, + "REJECTEDVALUE": "", + "VALIDATIONTYPE": "Required", + "FIELD": "USERNAME" + } + ] + }, + "error": true, + "pagination": {}, + "messages": [ + "Validation exceptions occurred, please see the data" + ] +} diff --git a/resources/rest/apidocs/auth/login/requestBody.json b/resources/rest/apidocs/auth/login/requestBody.json new file mode 100644 index 0000000..c671dce --- /dev/null +++ b/resources/rest/apidocs/auth/login/requestBody.json @@ -0,0 +1,29 @@ +{ + "description": "Needed fields to login a user", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "username", + "password" + ], + "properties": { + "username": { + "description": "The username to use for login in", + "type": "string" + }, + "password": { + "description": "The password to use for login in", + "type": "string" + } + }, + "example": { + "username": "user", + "password": "test" + } + } + } + } +} diff --git a/resources/rest/apidocs/auth/login/responses.json b/resources/rest/apidocs/auth/login/responses.json new file mode 100644 index 0000000..dacb371 --- /dev/null +++ b/resources/rest/apidocs/auth/login/responses.json @@ -0,0 +1,88 @@ +{ + "200": { + "description": "Application registration successful", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "description": "Flag to indicate an error.", + "type": "boolean" + }, + "messages": { + "description": "An array of messages related to the request.", + "type": "array", + "items": { + "type": "string" + } + }, + "pagination" : { + "description": "Pagination information.", + "type": "object", + "properties": {} + }, + "data": { + "description": "The data packet of the registration", + "type": "object", + "properties" : { + "token" : { + "type" : "string", + "description" : "The beaerer token created for the registration" + }, + "user" : { + "$ref" : "../../_schemas/app-user.json" + } + } + } + } + }, + "example": { + "$ref": "example.200.json" + } + } + } + }, + + "401": { + "description": "Validation exception", + "content": { + "application/json": { + "example": { + "$ref": "example.401.json" + }, + "schema": { + "type": "object", + "properties": { + "error": { + "description": "Flag to indicate an error.", + "type": "boolean" + }, + "messages": { + "description": "An array of messages related to the request.", + "type": "array", + "items": { + "type": "string" + } + }, + "pagination" : { + "description": "Pagination information.", + "type": "object", + "properties": {} + }, + "data": { + "description": "The validation data packet", + "type": "object", + "properties": { + "{invalidField}" : { + "$ref" : "../../_schemas/app-validation.json" + } + } + } + + } + } + } + } + } +} \ No newline at end of file diff --git a/resources/rest/apidocs/auth/logout/example.200.json b/resources/rest/apidocs/auth/logout/example.200.json new file mode 100644 index 0000000..d229492 --- /dev/null +++ b/resources/rest/apidocs/auth/logout/example.200.json @@ -0,0 +1,8 @@ +{ + "data": {}, + "error": false, + "pagination": {}, + "messages": [ + "Successfully logged out" + ] +} \ No newline at end of file diff --git a/resources/rest/apidocs/auth/logout/example.500.json b/resources/rest/apidocs/auth/logout/example.500.json new file mode 100644 index 0000000..e273346 --- /dev/null +++ b/resources/rest/apidocs/auth/logout/example.500.json @@ -0,0 +1,8 @@ +{ + "data": {}, + "error": true, + "pagination": {}, + "messages": [ + "General application error: Token not found in authorization header or the custom header or the request collection" + ] +} \ No newline at end of file diff --git a/resources/rest/apidocs/auth/logout/responses.json b/resources/rest/apidocs/auth/logout/responses.json new file mode 100644 index 0000000..1aa9068 --- /dev/null +++ b/resources/rest/apidocs/auth/logout/responses.json @@ -0,0 +1,29 @@ +{ + "200": { + "description": "Authenticate in the application", + "content": { + "application/json": { + "schema": { + "$ref" : "../../_schemas/app-response.json" + }, + "example": { + "$ref": "example.200.json" + } + } + } + }, + + "500": { + "description": "Invalid token or expired token or no token", + "content": { + "application/json": { + "example": { + "$ref": "example.500.json" + }, + "schema": { + "$ref" : "../../_schemas/app-response.json" + } + } + } + } +} \ No newline at end of file diff --git a/resources/rest/apidocs/auth/register/example.200.json b/resources/rest/apidocs/auth/register/example.200.json new file mode 100644 index 0000000..0422615 --- /dev/null +++ b/resources/rest/apidocs/auth/register/example.200.json @@ -0,0 +1,18 @@ +{ + "data": { + "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpYXQiOjE1ODkzMTI3NzcsInNjb3BlcyI6W10sImlzcyI6Imh0dHA6Ly8xMjcuMC4wLjE6NjUxMDAvIiwic3ViIjoiRTBDMTlDNjgtRDA5MC00QzY4LUE5NUY4NUIzRDFGRDZCODIiLCJleHAiOjE1ODkzMTYzNzcsImp0aSI6IjQ3Q0QxMDlDNTA4NTdDQTlGRUVBMDY0NzNEQzMxRkY3In0.I3F-MFsy7BbgLYwt6L9FR1HMNu6ZsUpkFQRgbpXdaPBbIbHRznWEw3MTsTKf654g0eO_yE5JVhGYYRzn_VyR6g", + "user": { + "lastName": "majano", + "permissions": [], + "roles": [], + "firstName": "luis", + "id": "E0C19C68-D090-4C68-A95F85B3D1FD6B82", + "username": "testing" + } + }, + "error": false, + "pagination": {}, + "messages": [ + "User registered correctly and Bearer token created and it expires in 60 minutes" + ] +} diff --git a/resources/rest/apidocs/auth/register/example.400.json b/resources/rest/apidocs/auth/register/example.400.json new file mode 100644 index 0000000..4a96f91 --- /dev/null +++ b/resources/rest/apidocs/auth/register/example.400.json @@ -0,0 +1,8 @@ +{ + "data": {}, + "error": true, + "pagination": {}, + "messages": [ + "Invalid or Missing Authentication Credentials" + ] +} \ No newline at end of file diff --git a/resources/rest/apidocs/auth/register/requestBody.json b/resources/rest/apidocs/auth/register/requestBody.json new file mode 100644 index 0000000..4237ac0 --- /dev/null +++ b/resources/rest/apidocs/auth/register/requestBody.json @@ -0,0 +1,41 @@ +{ + "description": "Needed fields to register a user", + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": [ + "firstName", + "lastName", + "username", + "password" + ], + "properties": { + "firstName": { + "description": "The user's first name", + "type": "string" + }, + "lastName": { + "description": "The user's last name", + "type": "string" + }, + "username": { + "description": "The username to use for login in", + "type": "string" + }, + "password": { + "description": "The password to use for login in", + "type": "string" + } + }, + "example": { + "firstName" : "luis", + "lastName" : "majano", + "username": "user", + "password": "test" + } + } + } + } +} diff --git a/resources/rest/apidocs/auth/register/responses.json b/resources/rest/apidocs/auth/register/responses.json new file mode 100644 index 0000000..926ed6e --- /dev/null +++ b/resources/rest/apidocs/auth/register/responses.json @@ -0,0 +1,75 @@ +{ + "200": { + "description": "Authenticate in the application", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "description": "Flag to indicate an error.", + "type": "boolean" + }, + "messages": { + "description": "An array of messages related to the request.", + "type": "array", + "items": { + "type": "string" + } + }, + "pagination" : { + "description": "Pagination information.", + "type": "object", + "properties": {} + }, + "data": { + "description": "The bearer token created for the user", + "type": "string" + } + } + }, + "example": { + "$ref": "example.200.json" + } + } + } + }, + + "400": { + "description": "Invalid or Missing Authentication Credentials", + "content": { + "application/json": { + "example": { + "$ref": "example.400.json" + }, + "schema": { + "type": "object", + "properties": { + "error": { + "description": "Flag to indicate an error.", + "type": "boolean" + }, + "messages": { + "description": "An array of messages related to the request.", + "type": "array", + "items": { + "type": "string" + } + }, + "pagination" : { + "description": "Pagination information.", + "type": "object", + "properties": {} + }, + "data": { + "description": "The data packet", + "type": "object", + "properties": {} + } + + } + } + } + } + } +} \ No newline at end of file diff --git a/resources/rest/apidocs/echo/index/example.200.json b/resources/rest/apidocs/echo/index/example.200.json new file mode 100644 index 0000000..bb28334 --- /dev/null +++ b/resources/rest/apidocs/echo/index/example.200.json @@ -0,0 +1,6 @@ +{ + "data": "Welcome to my ColdBox RESTFul Service", + "error": false, + "pagination": {}, + "messages": [] +} \ No newline at end of file diff --git a/resources/rest/apidocs/echo/index/responses.json b/resources/rest/apidocs/echo/index/responses.json new file mode 100644 index 0000000..cbb01d3 --- /dev/null +++ b/resources/rest/apidocs/echo/index/responses.json @@ -0,0 +1,37 @@ +{ + "200": { + "description": "A welcome message", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "error": { + "description": "Flag to indicate an error.", + "type": "boolean" + }, + "messages": { + "description": "An array of messages related to the request.", + "type": "array", + "items": { + "type": "string" + } + }, + "pagination" : { + "description": "Pagination information.", + "type": "object", + "properties": {} + }, + "data": { + "description": "The welcome message", + "type": "string" + } + } + }, + "example": { + "$ref": "example.200.json" + } + } + } + } +} \ No newline at end of file diff --git a/resources/rest/apidocs/echo/whoami/example.200.json b/resources/rest/apidocs/echo/whoami/example.200.json new file mode 100644 index 0000000..de9d0b8 --- /dev/null +++ b/resources/rest/apidocs/echo/whoami/example.200.json @@ -0,0 +1,13 @@ +{ + "data": { + "lastName": "admin", + "permissions": [], + "roles": [], + "firstName": "admin", + "id": 1, + "username": "admin" + }, + "error": false, + "pagination": {}, + "messages": [] +} diff --git a/resources/rest/apidocs/echo/whoami/example.401.json b/resources/rest/apidocs/echo/whoami/example.401.json new file mode 100644 index 0000000..4a96f91 --- /dev/null +++ b/resources/rest/apidocs/echo/whoami/example.401.json @@ -0,0 +1,8 @@ +{ + "data": {}, + "error": true, + "pagination": {}, + "messages": [ + "Invalid or Missing Authentication Credentials" + ] +} \ No newline at end of file diff --git a/resources/rest/apidocs/echo/whoami/responses.json b/resources/rest/apidocs/echo/whoami/responses.json new file mode 100644 index 0000000..3d9d756 --- /dev/null +++ b/resources/rest/apidocs/echo/whoami/responses.json @@ -0,0 +1,74 @@ +{ + "200": { + "description": "Returns the logged in user information", + "content": { + "application/json": { + "example": { + "$ref": "example.200.json" + }, + "schema": { + "type": "object", + "properties": { + "error": { + "description": "Flag to indicate an error.", + "type": "boolean" + }, + "messages": { + "description": "An array of messages related to the request.", + "type": "array", + "items": { + "type": "string" + } + }, + "pagination" : { + "description": "Pagination information.", + "type": "object", + "properties": {} + }, + "data": { + "$ref" : "../../_schemas/app-user.json" + } + } + } + } + } + }, + + "401": { + "description": "Invalid or Missing Authentication Credentials", + "content": { + "application/json": { + "example": { + "$ref": "example.401.json" + }, + "schema": { + "type": "object", + "properties": { + "error": { + "description": "Flag to indicate an error.", + "type": "boolean" + }, + "messages": { + "description": "An array of messages related to the request.", + "type": "array", + "items": { + "type": "string" + } + }, + "pagination" : { + "description": "Pagination information.", + "type": "object", + "properties": {} + }, + "data": { + "description": "The data packet", + "type": "object", + "properties": {} + } + + } + } + } + } + } +} \ No newline at end of file diff --git a/resources/rest/handlers/Auth.bx b/resources/rest/handlers/Auth.bx new file mode 100644 index 0000000..81d9c59 --- /dev/null +++ b/resources/rest/handlers/Auth.bx @@ -0,0 +1,75 @@ +/** + * Authentication Handler + */ +class extends="coldbox.system.RestHandler" { + + // Injection + @inject( "UserService" ) + property name="userService"; + + /** + * Login a user into the application + * + * @x-route (POST) /api/login + * @requestBody ~auth/login/requestBody.json + * @response-default ~auth/login/responses.json##200 + * @response-401 ~auth/login/responses.json##401 + */ + function login( event, rc, prc ){ + param rc.username = ""; + param rc.password = ""; + + // This can throw a InvalidCredentials exception which is picked up by the REST handler + var token = jwtAuth().attempt( rc.username, rc.password ) + + event + .getResponse() + .setData( token ) + .addMessage( + "Bearer token created and it expires in #jwtAuth().getSettings().jwt.expiration# minutes" + ) + } + + /** + * Register a new user in the system + * + * @x-route (POST) /api/register + * @requestBody ~auth/register/requestBody.json + * @response-default ~auth/register/responses.json##200 + * @response-400 ~auth/register/responses.json##400 + */ + function register( event, rc, prc ){ + param rc.firstName = ""; + param rc.lastName = ""; + param rc.username = ""; + param rc.password = ""; + + // Populate, Validate, Create a new user + prc.oUser = userService.create( populateModel( "User" ).validateOrFail() ); + + // Log them in if it was created! + event + .getResponse() + .setData( { + "token" : jwtAuth().fromuser( prc.oUser ), + "user" : prc.oUser.getMemento() + } ) + .addMessage( + "User registered correctly and Bearer token created and it expires in #jwtAuth().getSettings().jwt.expiration# minutes" + ) + } + + /** + * Logout a user + * + * @x-route (POST) /api/logout + * @security bearerAuth,ApiKeyAuth + * @response-default ~auth/logout/responses.json##200 + * @response-500 ~auth/logout/responses.json##500 + */ + function logout( event, rc, prc ){ + jwtAuth().logout(); + event.getResponse().addMessage( "Successfully logged out" ) + } + +} diff --git a/resources/rest/handlers/Echo.bx b/resources/rest/handlers/Echo.bx new file mode 100644 index 0000000..f80b538 --- /dev/null +++ b/resources/rest/handlers/Echo.bx @@ -0,0 +1,43 @@ +/** + * My RESTFul Event Handler + */ +class extends="coldbox.system.RestHandler" { + + // OPTIONAL HANDLER PROPERTIES + this.prehandler_only = "" + this.prehandler_except = "" + this.posthandler_only = "" + this.posthandler_except = "" + this.aroundHandler_only = "" + this.aroundHandler_except = "" + + // REST Allowed HTTP Methods Ex: this.allowedMethods = {delete='POST,DELETE',index='GET'} + this.allowedMethods = {} + + /** + * Say Hello + * + * @x-route (GET) /api/echo + * @response-default ~echo/index/responses.json##200 + */ + function index( event, rc, prc ){ + event.getResponse().setData( "Welcome to my ColdBox RESTFul Service" ) + } + + + /** + * A secured route that shows you your information + * + * @x-route (GET) /api/whoami + * @security bearerAuth,ApiKeyAuth + * @response-default ~echo/whoami/responses.json##200 + * @response-401 ~echo/whoami/responses.json##401 + */ + @secured + function whoami( event, rc, prc ){ + event.getResponse().setData( + jwtAuth().getUser().getMemento() + ) + } + +} diff --git a/resources/rest/handlers/Main.bx b/resources/rest/handlers/Main.bx new file mode 100644 index 0000000..dedddfd --- /dev/null +++ b/resources/rest/handlers/Main.bx @@ -0,0 +1,38 @@ +/** + * Main Application Event Handler + */ +class extends="coldbox.system.EventHandler" { + + /** + * -------------------------------------------------------------------------- + * Implicit Actions + * -------------------------------------------------------------------------- + * All the implicit actions below MUST be declared in the config/Coldbox.bx in order to fire. + * https://coldbox.ortusbooks.com/getting-started/configuration/coldbox.cfc/configuration-directives/coldbox#implicit-event-settings + */ + + function onAppInit( event, rc, prc ){ + } + + function onRequestStart( event, rc, prc ){ + } + + function onRequestEnd( event, rc, prc ){ + } + + function onSessionStart( event, rc, prc ){ + } + + function onSessionEnd( event, rc, prc ){ + var sessionScope = event.getValue( "sessionReference" ); + var applicationScope = event.getValue( "applicationReference" ); + } + + function onException( event, rc, prc ){ + event.setHTTPHeader( statusCode = 500 ); + // Grab Exception From private request collection, placed by ColdBox Exception Handling + var exception = prc.exception; + // Place exception handler below: + } + +} diff --git a/resources/rest/models/.gitkeep b/resources/rest/models/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/resources/rest/models/User.bx b/resources/rest/models/User.bx new file mode 100644 index 0000000..f22654d --- /dev/null +++ b/resources/rest/models/User.bx @@ -0,0 +1,30 @@ +/** + * A user in the system. + * + * This user is based off the Auth User included in cbsecurity, which implements already several interfaces and properties. + * - https://coldbox-security.ortusbooks.com/usage/authentication-services#iauthuser + * - https://coldbox-security.ortusbooks.com/jwt/jwt-services#jwt-subject-interface + * + * It also leverages several delegates for Validation, Population, Authentication, Authorization and JWT Subject. + */ +@transientCache( false ) +class + extends ="cbsecurity.models.auth.User" + delegates =" + Validatable@cbvalidation, + Population@cbDelegates, + Auth@cbSecurity, + Authorizable@cbSecurity, + JwtSubject@cbSecurity + " +{ + + /** + * Constructor + */ + function init(){ + super.init() + return this + } + +} diff --git a/resources/rest/models/UserService.bx b/resources/rest/models/UserService.bx new file mode 100644 index 0000000..bf3f63b --- /dev/null +++ b/resources/rest/models/UserService.bx @@ -0,0 +1,133 @@ +/** + * This service provides user authentication, retrieval and much more. + * Implements the CBSecurity IUserService: https://coldbox-security.ortusbooks.com/usage/authentication-services#iuserservice + */ +@singleton +class{ + + /** + * -------------------------------------------------------------------------- + * DI + * -------------------------------------------------------------------------- + */ + + @inject( "wirebox:populator" ) + property name="populator"; + + /** + * -------------------------------------------------------------------------- + * Properties + * -------------------------------------------------------------------------- + */ + + /** + * TODO: Mock users, remove when coding + */ + property name="mockUsers"; + + /** + * Constructor + */ + function init(){ + // We are mocking only 1 user right now, update as you see fit + variables.mockUsers = [ + { + "id" : 1, + "firstName" : "admin", + "lastName" : "admin", + "username" : "admin", + "password" : "admin", + "roles" : [], + "permissions" : [] + } + ]; + + return this; + } + + /** + * Construct a new user object via WireBox Providers + */ + @provider( "User" ) + User function new(){ + } + + /** + * Create a new user in the system + * + * @user The user to create + * + * @return The created user + */ + User function create( required user ){ + arguments.user.setId( createUUID() ); + + variables.mockUsers.append( { + "id" : arguments.user.getId(), + "firstName" : arguments.user.getFirstName(), + "lastName" : arguments.user.getLastName(), + "username" : arguments.user.getUsername(), + "password" : arguments.user.getPassword(), + "roles" : arguments.user.getRoles(), + "permissions" : arguments.user.getPermissions() + } ); + return arguments.user; + } + + /** + * Verify if the incoming username/password are valid credentials. + * + * @username The username + * @password The password + */ + boolean function isValidCredentials( required username, required password ){ + var oTarget = retrieveUserByUsername( arguments.username ); + if ( !oTarget.isLoaded() ) { + return false; + } + + // Check Password Here: Remember to use bcrypt + return ( oTarget.getPassword().compareNoCase( arguments.password ) == 0 ); + } + + /** + * Retrieve a user by username + * + * @return User that implements JWTSubject and/or IAuthUser + */ + function retrieveUserByUsername( required username ){ + return variables.mockUsers + .filter( function( record ){ + return arguments.record.username == username; + } ) + .reduce( function( result, record ){ + return variables.populator.populateFromStruct( + target : arguments.result, + memento : arguments.record, + ignoreTargetLists: true + ); + }, new () ); + } + + /** + * Retrieve a user by unique identifier + * + * @id The unique identifier + * + * @return User that implements JWTSubject and/or IAuthUser + */ + User function retrieveUserById( required id ){ + return variables.mockUsers + .filter( function( record ){ + return arguments.record.id == id; + } ) + .reduce( function( result, record ){ + return variables.populator.populateFromStruct( + target : arguments.result, + memento : arguments.record, + ignoreTargetLists: true + ); + }, new () ); + } + +} diff --git a/resources/rest/specs/integration/AuthTests.bx b/resources/rest/specs/integration/AuthTests.bx new file mode 100644 index 0000000..a83b2bd --- /dev/null +++ b/resources/rest/specs/integration/AuthTests.bx @@ -0,0 +1,131 @@ +@autowire +class extends="coldbox.system.testing.BaseTestCase" { + + @inject( "provider:JwtService@cbsecurity" ) + property name="jwtService"; + + @inject( "provider:authenticationService@cbauth" ) + property name="cbauth"; + + /*********************************** LIFE CYCLE Methods ***********************************/ + + function beforeAll(){ + super.beforeAll(); + } + + function afterAll(){ + super.afterAll(); + } + + /*********************************** BDD SUITES ***********************************/ + + function run(){ + describe( "RESTFul Authentication", () => { + beforeEach( ( currentSpec ) => { + // Setup as a new ColdBox request, VERY IMPORTANT. ELSE EVERYTHING LOOKS LIKE THE SAME REQUEST. + setup(); + + // Make sure nothing is logged in to start our calls + cbauth.logout(); + jwtService.getTokenStorage().clearAll(); + } ); + + story( "I want to authenticate a user and receive a JWT token", () => { + given( "a valid username and password", () => { + then( "I will be authenticated and will receive the JWT token", () => { + // Use a user in the seeded db + var event = this.post( + route = "/api/login", + params = { username : "admin", password : "admin" } + ); + var response = event.getPrivateValue( "Response" ); + expect( response.getError() ).toBeFalse( response.getMessages().toString() ); + expect( response.getData() ).toBeString(); + + // debug( response.getData() ); + + var decoded = jwtService.decode( response.getData() ); + expect( decoded.sub ).toBe( 1 ); + expect( decoded.exp ).toBeGTE( dateAdd( "h", 1, decoded.iat ) ); + } ); + } ); + given( "invalid username and password", () => { + then( "I will receive a 401 exception ", () => { + var event = this.post( + route = "/api/login", + params = { username : "invalid", password : "invalid" } + ); + var response = event.getPrivateValue( "Response" ); + expect( response.getError() ).toBeTrue(); + expect( response.getStatusCode() ).toBe( 401 ); + } ); + } ); + } ); + + story( "I want to register into the system", () => { + given( "valid registration details", () => { + then( "I should register, log in and get a token", () => { + // Use a user in the seeded db + var event = this.post( + route = "/api/register", + params = { + firstName : "luis", + lastName : "majano", + username : "lmajano@coldbox.org", + password : "lmajano" + } + ); + var response = event.getPrivateValue( "Response" ); + expect( response.getError() ).toBeFalse( response.getMessages().toString() ); + expect( response.getData() ).toHaveKey( "token,user" ); + + // debug( response.getData() ); + + var decoded = jwtService.decode( response.getData().token ); + expect( decoded.sub ).toBe( response.getData().user.id ); + expect( decoded.exp ).toBeGTE( dateAdd( "h", 1, decoded.iat ) ); + } ); + } ); + given( "invalid registration details", () => { + then( "I should get an error message", () => { + var event = this.post( route = "/api/register", params = {} ); + var response = event.getPrivateValue( "Response" ); + // debug( response.getMemento() ); + expect( response.getError() ).toBeTrue(); + expect( response.getStatusCode() ).toBe( 400 ); + } ); + } ); + } ); + + story( "I want to be able to logout from the system using my JWT token", () => { + given( "a valid incoming jwt token", () => { + then( "my token should become invalidated and I will be logged out", () => { + // Log in first to get a valid token to logout with + var token = jwtService.attempt( "admin", "admin" ); + var payload = jwtService.decode( token ); + expect( cbauth.isLoggedIn() ).toBeTrue(); + + // Now Logout + var event = this.post( route = "/api/logout", params = { "x-auth-token" : token } ); + + var response = event.getPrivateValue( "Response" ); + expect( response.getError() ).toBeFalse( response.getMessages().toString() ); + expect( response.getStatusCode() ).toBe( 200 ); + expect( cbauth.isLoggedIn() ).toBeFalse(); + } ); + } ); + given( "an invalid incoming jwt token", () => { + then( "I should see an error message", () => { + // Now Logout + var event = this.post( route = "/api/logout", params = { "x-auth-token" : "123" } ); + + var response = event.getPrivateValue( "Response" ); + expect( response.getError() ).toBeTrue( response.getMessages().toString() ); + // debug( response.getStatusCode( 500 ) ); + } ); + } ); + } ); + } ); + } + +} diff --git a/resources/rest/specs/integration/EchoTests.bx b/resources/rest/specs/integration/EchoTests.bx new file mode 100755 index 0000000..6e9b520 --- /dev/null +++ b/resources/rest/specs/integration/EchoTests.bx @@ -0,0 +1,61 @@ +@appMapping( "/app" ) +class extends="coldbox.system.testing.BaseTestCase" { + + /*********************************** LIFE CYCLE Methods ***********************************/ + + function beforeAll(){ + super.beforeAll(); + // do your own stuff here + } + + function afterAll(){ + // do your own stuff here + super.afterAll(); + } + + /*********************************** BDD SUITES ***********************************/ + + function run(){ + describe( "My RESTFUl Service", () => { + beforeEach( ( currentSpec ) => { + // Setup as a new ColdBox request, VERY IMPORTANT. ELSE EVERYTHING LOOKS LIKE THE SAME REQUEST. + setup(); + } ); + + it( "can handle global exceptions", () => { + var event = execute( + event = "echo.onError", + renderResults = true, + eventArguments = { + exception : { + message : "unit test", + detail : "unit test", + stacktrace : "" + } + } + ); + + var response = event.getPrivateValue( "response" ); + expect( response.getError() ).toBeTrue(); + expect( response.getStatusCode() ).toBe( 500 ); + } ); + + it( "can handle an echo", () => { + prepareMock( getRequestContext() ).$( "getHTTPMethod", "GET" ); + var event = execute( route = "echo/index" ); + var response = event.getPrivateValue( "response" ); + expect( response.getError() ).toBeFalse(); + expect( response.getData() ).toBe( "Welcome to my ColdBox RESTFul Service" ); + } ); + + it( "can handle missing actions", () => { + prepareMock( getRequestContext() ).$( "getHTTPMethod", "GET" ); + var event = execute( route = "echo/bogus" ); + var response = event.getPrivateValue( "response" ); + expect( response.getError() ).tobeTrue(); + expect( response.getStatusCode() ).toBe( 404 ); + } ); + } ); + } + +} diff --git a/resources/rest/specs/unit/UserServiceTest.bx b/resources/rest/specs/unit/UserServiceTest.bx new file mode 100644 index 0000000..8f8e8d8 --- /dev/null +++ b/resources/rest/specs/unit/UserServiceTest.bx @@ -0,0 +1,62 @@ +class extends="coldbox.system.testing.BaseTestCase" { + + /*********************************** LIFE CYCLE Methods ***********************************/ + + function beforeAll(){ + super.beforeAll(); + } + + function afterAll(){ + super.afterAll(); + } + + /*********************************** BDD SUITES ***********************************/ + + function run(){ + describe( "UserService", () => { + beforeEach( ( currentSpec ) => { + setup(); + model = getInstance( "UserService" ); + } ); + + it( "can be created", () => { + expect( model ).toBeComponent(); + } ); + + it( "can get a valid mock user by id", () => { + var oUser = model.retrieveUserById( 1 ); + expect( oUser.getId() ).toBe( 1 ); + expect( oUser.isLoaded() ).toBeTrue(); + } ); + + it( "can get a new mock user with invalid id", () => { + var oUser = model.retrieveUserById( 100 ); + expect( oUser.getId() ).toBe( "" ); + expect( oUser.isLoaded() ).toBeFalse(); + } ); + + it( "can get a valid mock user by username", () => { + var oUser = model.retrieveUserByUsername( "admin" ); + expect( oUser.getId() ).toBe( 1 ); + expect( oUser.isLoaded() ).toBeTrue(); + } ); + + it( "can get a new mock user with invalid username", () => { + var oUser = model.retrieveUserByUsername( "bogus@admin" ); + expect( oUser.getId() ).toBe( "" ); + expect( oUser.isLoaded() ).toBeFalse(); + } ); + + it( "can validate valid credentials", () => { + var result = model.isValidCredentials( "admin", "admin" ); + expect( result ).toBeTrue(); + } ); + + it( "can validate invalid credentials", () => { + var result = model.isValidCredentials( "badadmin", "dd" ); + expect( result ).toBeFalse(); + } ); + } ); + } + +} diff --git a/resources/rest/specs/unit/UserTest.bx b/resources/rest/specs/unit/UserTest.bx new file mode 100644 index 0000000..4ac70db --- /dev/null +++ b/resources/rest/specs/unit/UserTest.bx @@ -0,0 +1,35 @@ +/** + * The base model test case will use the 'model' annotation as the instantiation path + * and then create it, prepare it for mocking and then place it in the variables scope as 'model'. It is your + * responsibility to update the model annotation instantiation path and init your model. + */ +@model( "models.User" ) +class extends="coldbox.system.testing.BaseModelTest" { + + /*********************************** LIFE CYCLE Methods ***********************************/ + + function beforeAll(){ + super.beforeAll(); + + // setup the model + super.setup(); + + // init the model object + model.init(); + } + + function afterAll(){ + super.afterAll(); + } + + /*********************************** BDD SUITES ***********************************/ + + function run(){ + describe( "A User", () => { + it( "can be created", () => { + expect( model ).toBeComponent(); + } ); + } ); + } + +} From da0978a28ca6d2cc83b02e1fe881ccfb8843df8a Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 9 Oct 2025 18:44:03 +0200 Subject: [PATCH 18/23] rest app now --- Setup.bx | 5 +++++ app/config/Coldbox.bx | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Setup.bx b/Setup.bx index 1114578..54c9c87 100644 --- a/Setup.bx +++ b/Setup.bx @@ -64,6 +64,11 @@ class{ directoryCopy( source: "resources/rest/apidocs", destination: "resources/apidocs", recurse: true, createPath: true ) directoryDelete( "resources/rest", true ) + var newConfig = fileRead( "app/config/Coldbox.bx" ) + .replace( "Main.index", "Echo.index" ) + .replace( "Main.onException", "Echo.onError" ); + fileWrite( "app/config/Coldbox.bx", newConfig ); + // Install CommandBox Modules println( "🥊 Installing ColdBox API Production Modules: Security, Mementifier, Validation" ) result = systemExecute( diff --git a/app/config/Coldbox.bx b/app/config/Coldbox.bx index 7d1d014..63c2176 100644 --- a/app/config/Coldbox.bx +++ b/app/config/Coldbox.bx @@ -23,7 +23,7 @@ class { reinitKey : "fwreinit", handlersIndexAutoReload : true, // Implicit Events - defaultEvent : "", + defaultEvent : "Main.index", requestStartHandler : "Main.onRequestStart", requestEndHandler : "", applicationStartHandler : "Main.onAppInit", From 96ab4c6ebc1e51ba9ecc87e8196f98608ffb215b Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 9 Oct 2025 19:07:48 +0200 Subject: [PATCH 19/23] docs --- readme.md | 340 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 324 insertions(+), 16 deletions(-) diff --git a/readme.md b/readme.md index 8d31186..5ac71e0 100644 --- a/readme.md +++ b/readme.md @@ -71,16 +71,239 @@ boxlang Setup.bx ### What Setup.bx Does: -The setup script walks you through several configuration options: +The setup script is an **interactive wizard** that walks you through several configuration options to tailor the template to your project needs: -- **📝 Project Information**: Set your application name, author, and description -- **🎨 Customization**: Configure project-specific settings -- **🔐 Environment Setup**: Generate `.env` files and configure environment variables -- **📦 Dependencies**: Install additional dependencies based on your selections -- **✅ Validation**: Verify your environment is properly configured -- **🤖 Creates AI Instructions**: Generates AI usage instructions based on your project setup +#### 1️⃣ **Environment Configuration** (`.env` file) -> **💡 Tip**: Run `Setup.bx` immediately after creating your application to ensure everything is configured correctly for your development environment. +- Automatically creates a `.env` file from `.env.example` if it doesn't exist +- Skips creation if `.env` already exists to preserve your settings +- Sets up environment variables for database connections, API keys, and more + +#### 2️⃣ **AI Development Assistant** (Copilot Instructions) + +- Creates `.github/copilot-instructions.md` for AI-powered coding assistance +- Provides context-aware suggestions based on ColdBox and BoxLang conventions +- Compatible with GitHub Copilot, Cursor, Windsurf, and other AI assistants +- Includes project structure, patterns, and best practices documentation + +#### 3️⃣ **Java Dependency Management** (Maven) + +- **Prompt**: "Do you want to use Maven for any Java dependency management?" +- **If YES**: Keeps `pom.xml` for managing Java dependencies + - Add dependencies to the `` section + - Run `mvn install` to download them to `runtime/lib/` + - BoxLang automatically class-loads all JARs in that folder +- **If NO**: Removes `pom.xml` and `effective-pom.xml` files + +#### 4️⃣ **REST API Configuration** + +- **Prompt**: "Is this a REST API only project?" +- **If YES**: Configures your application as a RESTful API + - Installs production modules: `cbsecurity`, `mementifier`, `cbvalidation` + - Installs development modules: `route-visualizer`, `relax` + - Replaces default Router with REST-optimized routing + - Updates handlers to REST-focused controllers + - Adds API documentation templates in `resources/apidocs/` + - Updates tests for API testing patterns + - Sets default routes to `Echo.index` and `Echo.onError` +- **If NO**: Keeps traditional MVC structure with views and layouts + +#### 5️⃣ **Frontend Build System** (Vite) + +- **Prompt**: "Do you want to use Vite for your frontend build system?" (Only if not API-only) +- **If YES**: Sets up modern frontend tooling with Vite + - Copies `vite.config.mjs` configuration + - Adds `package.json` with Vue 3, Tailwind CSS support + - Updates `Main.bxm` layout with Vite integration + - Creates `resources/assets/` directory for CSS/JS + - Instructions for `npm install`, `npm run dev`, `npm run build` +- **If NO**: Removes Vite-related files + +#### 6️⃣ **Docker Containerization** + +- **Prompt**: "Do you want to use Docker for containerization?" +- **If YES**: Sets up Docker infrastructure + - Creates `docker/` directory with Dockerfile + - Adds `docker-compose.yml` for multi-container setup + - Includes `.dockerignore` for optimized builds + - Provides scripts: `docker:build`, `docker:run`, `docker:bash`, `docker:stack` +- **If NO**: Removes Docker files + +#### 7️⃣ **Dev Container Support** + +- **Prompt**: "Do you want to keep the Dev Container setup so you can code in GitHub?" +- **If YES**: Keeps `.devcontainer/` for VS Code Remote Containers + - Enables coding in a consistent containerized environment + - Includes configuration in `.devcontainer/devcontainer.json` + - Customizable via `.devcontainer/Dockerfile` +- **If NO**: Removes `.devcontainer/` directory + +#### 8️⃣ **Cleanup & Finalization** + +- Cleans up `box.json` by removing the build ignore array +- Provides helpful next steps and commands +- Optionally remove `Setup.bx` itself after completion + +### 🎯 Example Setup Flow: + +```bash +$ boxlang Setup.bx +🥊 Creating .env file from .env.example +🥊 Creating .github directory +🥊 Creating copilot file +🙋 Do you want to use Maven for any Java dependency management? (y/n): y +🥊 Setting up a [pom.xml] in your root for Java dependency management +🙋 Is this a REST API only project? (y/n): y +🥊 Setting up a REST API only ColdBox application +🥊 Installing ColdBox API Production Modules: Security, Mementifier, Validation +🥊 Installing ColdBox API Development Modules: route-visualizer,relax +✅ REST API only setup complete! +🙋 Do you want to use Docker for containerization? (y/n): y +🥊 Setting up Docker for containerization +✅ Docker setup complete! +🙋 Do you want to keep the Dev Container setup? (y/n): n +🧹 Cleaning up unnecessary files +🛁 Cleaning up your box.json +🥊 Your ColdBox BoxLang application is ready to roll! +👉 Run 'box server start' to launch the development server. +```> + +> **💡 Best Practice**: Run `Setup.bx` immediately after creating your application to ensure everything is configured correctly for your specific use case. You can always modify these choices later by manually adjusting the relevant files. + +## ⚡ Vite Frontend Build System + +If you chose to use **Vite** during setup, this template includes a modern frontend build system with Vue 3 and Tailwind CSS support. Vite provides lightning-fast hot module replacement (HMR) and optimized production builds. + +### 🔧 Vite Configuration + +The `vite.config.mjs` file is pre-configured with sensible defaults for ColdBox applications: + +```javascript +import { defineConfig } from "vite"; +import vue from "@vitejs/plugin-vue"; +import tailwindcss from "@tailwindcss/vite"; +import coldbox from "coldbox-vite-plugin"; + +export default defineConfig({ + plugins: [ + coldbox({ + input: [ "resources/assets/css/app.css", "resources/assets/js/app.js" ], + refresh: true, + publicDirectory: "public", // Web root directory + buildDirectory: "includes" // Output directory within public + }), + vue(), + tailwindcss() + ] +}); +``` + +#### 🎯 Key Configuration Options: + +- **`input`**: Entry points for CSS and JavaScript files +- **`refresh`**: Enable automatic browser refresh when ColdBox views/layouts change +- **`publicDirectory`**: The web root directory (default: `"public"`) +- **`buildDirectory`**: Where compiled assets are written (default: `"includes"`) + - Assets output to: `{publicDirectory}/{buildDirectory}/` + - Example: `public/includes/` with the defaults above + +### 📂 Asset Structure: + +```text +resources/ +└── assets/ + ├── css/ + │ └── app.css # Main stylesheet (Tailwind) + └── js/ + └── app.js # Main JavaScript (Vue 3 app) + +public/ +└── includes/ # Compiled assets (generated by Vite) + ├── manifest.json # Asset manifest for production + └── assets/ + ├── app-[hash].css # Compiled & hashed CSS + └── app-[hash].js # Compiled & hashed JS +``` + +### 🚀 Using Vite: + +#### Development Mode (Hot Module Replacement): + +```bash +npm run dev +``` + +This starts the Vite development server with HMR at `http://localhost:5173`. The ColdBox application automatically detects the dev server and loads assets from it. + +#### Production Build: + +```bash +npm run build +``` + +Compiles and optimizes assets for production, outputting them to `public/includes/`. The generated files include content hashes for cache busting. + +### 📝 Including Assets in Views: + +The `vite()` helper function automatically loads assets based on the environment: + +```boxlang + + + + + #vite([ + "resources/assets/css/app.css", + "resources/assets/js/app.js" + ])# + +``` + +**Development**: Loads from Vite dev server (`http://localhost:5173`) +**Production**: Loads compiled assets from `public/includes/assets/` + +### 🎨 Customizing Vite: + +#### Change Output Directory: + +```javascript +coldbox({ + input: [ "resources/assets/css/app.css", "resources/assets/js/app.js" ], + refresh: true, + publicDirectory: "public", + buildDirectory: "dist" // Assets output to public/dist/ +}) +``` + +#### Add More Entry Points: + +```javascript +coldbox({ + input: [ + "resources/assets/css/app.css", + "resources/assets/js/app.js", + "resources/assets/js/admin.js", // Additional JS entry + "resources/assets/css/admin.css" // Additional CSS entry + ], + refresh: true +}) +``` + +#### Configure Refresh Paths: + +```javascript +coldbox({ + input: [ "resources/assets/css/app.css", "resources/assets/js/app.js" ], + refresh: [ + "app/layouts/**", + "app/views/**", + "app/config/Router.bx", + "app/handlers/**/*.bx" // Also refresh on handler changes + ] +}) +``` + +> **📚 Learn More**: Check out the [coldbox-vite-plugin documentation](https://github.com/coldbox/coldbox-vite-plugin) and [Vite documentation](https://vitejs.dev) for advanced configuration options. ## 📦 Build Script (`Build.bx`) @@ -124,27 +347,112 @@ build/ ### Customizing the Build: -You can customize what gets included or excluded by editing the `Build.bx` file: +You can customize what gets included or excluded by editing the `Build.bx` file's initialization section. The build script uses two configurable arrays: + +#### 📦 **Sources Array** - What to Include + +Controls which directories and files get packaged in your distribution: + +```boxlang +// Source directories to package +variables.sources = [ + ".cbmigrations.json", // Database migrations state + "box.json", // Project metadata + "app", // Your ColdBox application + "modules", // Installed modules + "public", // Web root with assets + "runtime" // BoxLang runtime config +]; +``` + +**To add more sources**, simply append to the array: ```boxlang -// Add directories/files to package variables.sources = [ + ".cbmigrations.json", + "box.json", "app", "modules", "public", "runtime", - "config" // Add your own + "config", // Add custom config directory + "resources/database" // Include database resources ]; +``` + +#### 🚫 **Excludes Array** - What to Skip -// Add exclusion patterns (regex) +Uses **regex patterns** to exclude files/directories from the build: + +```boxlang +// Files and folders to exclude from the build (regex patterns) variables.excludes = [ - "logs/", // Exclude all log directories - "\.log$", // Exclude .log files - "\.tmp$", // Exclude .tmp files - "test-results/" // Exclude test output + "logs/", // Log directories + "\.DS_Store$", // macOS system files + "Thumbs\.db$" // Windows system files ]; ``` +**Common exclusion patterns**: + +```boxlang +variables.excludes = [ + "logs/", // Exclude all log directories + "\.log$", // Exclude .log files + "\.tmp$", // Exclude .tmp files + "test-results/", // Exclude test output + "node_modules/", // Exclude npm dependencies + "\.git", // Exclude git repository + "\.env", // Exclude environment files + "\.bak$", // Exclude backup files + "resources/vite/", // Exclude vite resources after setup + "resources/rest/" // Exclude rest resources after setup +]; +``` + +**Regex Pattern Tips**: +- Use `$` to match end of string: `\.log$` matches files ending in `.log` +- Use `/` to match directories: `logs/` matches any `logs` directory +- Use `\.` to escape dots: `\.DS_Store$` matches `.DS_Store` files +- Use `.*` for wildcards: `temp.*` matches anything starting with `temp` + +#### 🔧 **Example: Custom Build Configuration** + +```boxlang +function init(){ + // ... existing code ... + + // Custom sources for your project + variables.sources = [ + ".cbmigrations.json", + "box.json", + "app", + "modules", + "public", + "runtime", + "custom-config", // Add your custom directory + "static-files" // Add static file directory + ]; + + // Comprehensive exclusions + variables.excludes = [ + "logs/", // No log files + "\.DS_Store$", // No macOS files + "Thumbs\.db$", // No Windows files + "test-results/", // No test outputs + "\.env\..*", // No environment files + "resources/vite/", // No vite setup resources + "resources/rest/", // No rest setup resources + "resources/docker/", // No docker setup resources + "Setup\.bx$" // No setup script + ]; + + return this; +} +``` + +> **💡 Pro Tip**: Review your `variables.excludes` after running `Setup.bx` to ensure you're not packaging unnecessary setup resources! + ### Deploying Your Build: Once the build completes, you can: From b0b245571ee05669981b30d43bfb6475753c128c Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 9 Oct 2025 19:27:03 +0200 Subject: [PATCH 20/23] missing config for rest modules --- Setup.bx | 2 + app/config/modules/.gitkeep | 0 resources/rest/config/modules/cbauth.bx | 30 +++ resources/rest/config/modules/cbsecurity.bx | 251 ++++++++++++++++++++ resources/rest/config/modules/cbswagger.bx | 88 +++++++ 5 files changed, 371 insertions(+) delete mode 100644 app/config/modules/.gitkeep create mode 100644 resources/rest/config/modules/cbauth.bx create mode 100644 resources/rest/config/modules/cbsecurity.bx create mode 100644 resources/rest/config/modules/cbswagger.bx diff --git a/Setup.bx b/Setup.bx index 54c9c87..526f109 100644 --- a/Setup.bx +++ b/Setup.bx @@ -54,6 +54,8 @@ class{ // Tests directoryDelete( "tests/specs", true ) directoryCopy( source: "resources/rest/specs", destination: "tests/specs", recurse: true, createPath: true ) + // Configuration + directoryCopy( source: "resources/rest/config/modules", destination: "app/config/modules", recurse: false, createPath: true ) // Models directoryDelete( "app/models", true ) directoryCopy( source: "resources/rest/models", destination: "app/models", recurse: true, createPath: true ) diff --git a/app/config/modules/.gitkeep b/app/config/modules/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/resources/rest/config/modules/cbauth.bx b/resources/rest/config/modules/cbauth.bx new file mode 100644 index 0000000..8b09b87 --- /dev/null +++ b/resources/rest/config/modules/cbauth.bx @@ -0,0 +1,30 @@ +class { + + /** + * Configure CBAuth for operation + * https://cbauth.ortusbooks.com/installation-and-usage#configuration + */ + function configure(){ + return { + /** + *-------------------------------------------------------------------------- + * User Service Class + *-------------------------------------------------------------------------- + * The user service class to use for authentication which must implement IUserService + * https://cbauth.ortusbooks.com/iuserservice + * The User object that this class returns must implement IUser as well + * https://cbauth.ortusbooks.com/iauthuser + */ + "userServiceClass" : "UserService", + /** + *------------------------------------------------------------------------- + * Storage Classes + *------------------------------------------------------------------------- + * Which storages to use for tracking session and the request scope + */ + "sessionStorage" : "SessionStorage@cbstorages", + "requestStorage" : "RequestStorage@cbstorages" + }; + } + +} diff --git a/resources/rest/config/modules/cbsecurity.bx b/resources/rest/config/modules/cbsecurity.bx new file mode 100644 index 0000000..ec7afb7 --- /dev/null +++ b/resources/rest/config/modules/cbsecurity.bx @@ -0,0 +1,251 @@ +class { + + function configure(){ + return { + /** + * -------------------------------------------------------------------------- + * Authentication Services + * -------------------------------------------------------------------------- + * https://coldbox-security.ortusbooks.com/getting-started/configuration/authentication + * + * Here you will configure which service is in charge of providing authentication for your application. + * By default we leverage the cbauth module which expects you to connect it to a database via your own User Service. + * + * Available authentication providers: + * - cbauth : Leverages your own UserService that determines authentication and user retrieval + * - basicAuth : Leverages basic authentication and basic in-memory user registration in our configuration + * - custom : Any other service that adheres to our IAuthService interface + */ + authentication : { + // The WireBox ID of the auth service to use which must adhere to the cbsecurity.interfaces.IAuthService interface. + "provider" : "authenticationService@cbauth", + // The name of the variable to use to store an authenticated user in prc scope on all incoming authenticated requests + "prcUserVariable" : "oCurrentUser" + }, + /** + * -------------------------------------------------------------------------- + * Basic Auth + * -------------------------------------------------------------------------- + * https://coldbox-security.ortusbooks.com/getting-started/configuration/basic-auth + * + * If you are using the basicAuth authentication provider, then you can configure it here, else ignore or remove. + */ + basicAuth : { + // Hashing algorithm to use + hashAlgorithm : "SHA-512", + // Iterates the number of times the hash is computed to create a more computationally intensive hash. + hashIterations : 5, + // User storage: The `key` is the username. The value is the user credentials that can include + // { roles: "", permissions : "", firstName : "", lastName : "", password : "" } + users : {} + }, + /** + * -------------------------------------------------------------------------- + * Firewall Settings + * -------------------------------------------------------------------------- + * https://coldbox-security.ortusbooks.com/getting-started/configuration/firewall + * + * The firewall is used to block/check access on incoming requests via security rules or via annotation on handler actions. + * Here you can configure the operation of the firewall and especially what Validator will be in charge of verifying authentication/authorization + * during a matched request. + */ + firewall : { + // Auto load the global security firewall automatically, else you can load it a-la-carte via the `Security` interceptor + "autoLoadFirewall" : true, + // The Global validator is an object that will validate the firewall rules and annotations and provide feedback on either authentication or authorization issues. + "validator" : "JwtAuthValidator@cbsecurity", + // Activate handler/action based annotation security + "handlerAnnotationSecurity" : true, + // The global invalid authentication event or URI or URL to go if an invalid authentication occurs + "invalidAuthenticationEvent" : "echo.onAuthenticationFailure", + // Default Auhtentication Action: override or redirect when a user has not logged in + "defaultAuthenticationAction" : "override", + // The global invalid authorization event or URI or URL to go if an invalid authorization occurs + "invalidAuthorizationEvent" : "echo.onAuthorizationFailure", + // Default Authorization Action: override or redirect when a user does not have enough permissions to access something + "defaultAuthorizationAction" : "override", + // Firewall database event logs. + "logs" : { + "enabled" : false, + "dsn" : "", + "schema" : "", + "table" : "cbsecurity_logs", + "autoCreate" : true + }, + // Firewall Rules, this can be a struct of detailed configuration + // or a simple array of inline rules + "rules" : { + // Use regular expression matching on the rule match types + "useRegex" : true, + // Force SSL for all relocations + "useSSL" : false, + // A collection of default name-value pairs to add to ALL rules + // This way you can add global roles, permissions, redirects, etc + "defaults" : {}, + // You can store all your rules in this inline array + "inline" : [], + // If you don't store the rules inline, then you can use a provider to load the rules + // The source can be a json file, an xml file, model, db + // Each provider can have it's appropriate properties as well. Please see the documentation for each provider. + "provider" : { "source" : "", "properties" : {} } + } + }, + /** + * -------------------------------------------------------------------------- + * Json Web Tokens + * -------------------------------------------------------------------------- + * https://coldbox-security.ortusbooks.com/getting-started/configuration/jwt + * + * Here you configure how JSON Web Tokens are created, validated and stored. + */ + jwt : { + // The issuer authority for the tokens, placed in the `iss` claim + issuer : "", + // The jwt secret encoding key, defaults to getSystemEnv( "JWT_SECRET", "" ) + // This key is only effective within the `config/Coldbox.cfc`. Specifying within a module does nothing. + secretKey : getSystemSetting( "JWT_SECRET", "" ), + // by default it uses the authorization bearer header, but you can also pass a custom one as well. + customAuthHeader : "x-auth-token", + // The expiration in minutes for the jwt tokens + expiration : 60, + // If true, enables refresh tokens, token creation methods will return a struct instead + // of just the access token. e.g. { access_token: "", refresh_token : "" } + enableRefreshTokens : false, + // The default expiration for refresh tokens, defaults to 30 days + refreshExpiration : 10080, + // The Custom header to inspect for refresh tokens + customRefreshHeader : "x-refresh-token", + // If enabled, the JWT validator will inspect the request for refresh tokens and expired access tokens + // It will then automatically refresh them for you and return them back as + // response headers in the same request according to the customRefreshHeader and customAuthHeader + enableAutoRefreshValidator : false, + // Enable the POST > /cbsecurity/refreshtoken API endpoint + enableRefreshEndpoint : true, + // encryption algorithm to use, valid algorithms are: HS256, HS384, and HS512 + algorithm : "HS512", + // Which claims neds to be present on the jwt token or `TokenInvalidException` upon verification and decoding + requiredClaims : [], + // The token storage settings + tokenStorage : { + // enable or not, default is true + "enabled" : true, + // A cache key prefix to use when storing the tokens + "keyPrefix" : "cbjwt_", + // The driver to use: db, cachebox or a WireBox ID + "driver" : "cachebox", + // Driver specific properties + "properties" : { cacheName : "default" } + } + }, + /** + * -------------------------------------------------------------------------- + * Security Headers + * -------------------------------------------------------------------------- + * https://coldbox-security.ortusbooks.com/getting-started/configuration/security-headers + * + * This section is the way to configure cbsecurity for header detection, inspection and setting for common + * security exploits like XSS, ClickJacking, Host Spoofing, IP Spoofing, Non SSL usage, HSTS and much more. + */ + securityHeaders : { + // If you trust the upstream then we will check the upstream first for specific headers + "trustUpstream" : false, + // Content Security Policy + // Content Security Policy (CSP) is an added layer of security that helps to detect and mitigate certain types of attacks, + // including Cross-Site Scripting (XSS) and data injection attacks. These attacks are used for everything from data theft, to + // site defacement, to malware distribution. + // https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP + "contentSecurityPolicy" : { + // Disabled by defautl as it is totally customizable + "enabled" : false, + // The custom policy to use, by default we don't include any + "policy" : "" + }, + // The X-Content-Type-Options response HTTP header is a marker used by the server to indicate that the MIME types advertised in + // the Content-Type headers should be followed and not be changed => X-Content-Type-Options: nosniff + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options + "contentTypeOptions" : { "enabled" : true }, + "customHeaders" : { + // Name : value pairs as you see fit. + }, + // Disable Click jacking: X-Frame-Options: DENY OR SAMEORIGIN + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options + "frameOptions" : { "enabled" : true, "value" : "SAMEORIGIN" }, + // HTTP Strict Transport Security (HSTS) + // The HTTP Strict-Transport-Security response header (often abbreviated as HSTS) + // informs browsers that the site should only be accessed using HTTPS, and that any future attempts to access it + // using HTTP should automatically be converted to HTTPS. + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security, + "hsts" : { + "enabled" : true, + // The time, in seconds, that the browser should remember that a site is only to be accessed using HTTPS, 1 year is the default + "max-age" : "31536000", + // See Preloading Strict Transport Security for details. Not part of the specification. + "preload" : false, + // If this optional parameter is specified, this rule applies to all of the site's subdomains as well. + "includeSubDomains" : false + }, + // Validates the host or x-forwarded-host to an allowed list of valid hosts + "hostHeaderValidation" : { + "enabled" : false, + // Allowed hosts list + "allowedHosts" : "" + }, + // Validates the ip address of the incoming request + "ipValidation" : { + "enabled" : false, + // Allowed IP list + "allowedIPs" : "" + }, + // The Referrer-Policy HTTP header controls how much referrer information (sent with the Referer header) should be included with requests. + // Aside from the HTTP header, you can set this policy in HTML. + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy + "referrerPolicy" : { "enabled" : true, "policy" : "same-origin" }, + // Detect if the incoming requests are NON-SSL and if enabled, redirect with SSL + "secureSSLRedirects" : { "enabled" : false }, + // Some browsers have built in support for filtering out reflected XSS attacks. Not foolproof, but it assists in XSS protection. + // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection, + // X-XSS-Protection: 1; mode=block + "xssProtection" : { "enabled" : true, "mode" : "block" } + }, + /** + * -------------------------------------------------------------------------- + * Security Visualizer + * -------------------------------------------------------------------------- + * https://coldbox-security.ortusbooks.com/getting-started/configuration/visualizer + * + * This is a debugging panel that when active, a developer can visualize security settings and more. + * You can use the `securityRule` to define what rule you want to use to secure the visualizer but make sure the `secured` flag is turned to true. + * You don't have to specify the `secureList` key, we will do that for you. + */ + visualizer : { + "enabled" : false, + "secured" : false, + "securityRule" : {} + }, + /** + * -------------------------------------------------------------------------- + * Cross Site Request Forgery (CSRF) + * -------------------------------------------------------------------------- + * https://coldbox-security.ortusbooks.com/getting-started/configuration/csrf + * + * This section is the way to configure cbsecurity for CSRF detection and mitigation. + */ + csrf : { + // By default we load up an interceptor that verifies all non-GET incoming requests against the token validations + enableAutoVerifier : false, + // A list of events to exclude from csrf verification, regex allowed: e.g. stripe\..* + verifyExcludes : [], + // By default, all csrf tokens have a life-span of 30 minutes. After 30 minutes, they expire and we aut-generate new ones. + // If you do not want expiring tokens, then set this value to 0 + rotationTimeout : 30, + // Enable the /cbcsrf/generate endpoint to generate cbcsrf tokens for secured users. + enableEndpoint : false, + // The WireBox mapping to use for the CacheStorage + cacheStorage : "CacheStorage@cbstorages", + // Enable/Disable the cbAuth login/logout listener in order to rotate keys + enableAuthTokenRotator : true + } + }; + } + +} diff --git a/resources/rest/config/modules/cbswagger.bx b/resources/rest/config/modules/cbswagger.bx new file mode 100644 index 0000000..a0b6b9f --- /dev/null +++ b/resources/rest/config/modules/cbswagger.bx @@ -0,0 +1,88 @@ +class { + + /** + * CBSwagger Configuration + * https://github.com/coldbox-modules/cbswagger + */ + function configure(){ + return { + // The route prefix to search. Routes beginning with this prefix will be determined to be api routes + "routes" : [ "api" ], + // Any routes to exclude + "excludeRoutes" : [], + // The default output format: json or yml + "defaultFormat" : "json", + // A convention route, relative to your app root, where request/response samples are stored ( e.g. resources/apidocs/responses/[module].[handler].[action].[HTTP Status Code].json ) + "samplesPath" : "resources/apidocs", + // Information about your API + "info" : { + // A title for your API + "title" : "ColdBox REST Template", + // A description of your API + "description" : "This API produces amazing results and data.", + // A terms of service URL for your API + "termsOfService" : "", + // The contact email address + "contact" : { + "name" : "API Support", + "url" : "https://www.swagger.io/support", + "email" : "info@ortussolutions.com" + }, + // A url to the License of your API + "license" : { + "name" : "Apache 2.0", + "url" : "https://www.apache.org/licenses/LICENSE-2.0.html" + }, + // The version of your API + "version" : "1.0.0" + }, + // Tags + "tags" : [], + // https://swagger.io/specification/#externalDocumentationObject + "externalDocs" : { + "description" : "Find more info here", + "url" : "https://blog.readme.io/an-example-filled-guide-to-swagger-3-2/" + }, + // https://swagger.io/specification/#serverObject + "servers" : [ + { + "url" : "https://mysite.com/v1", + "description" : "The main production server" + }, + { + "url" : "http://127.0.0.1:60299", + "description" : "The dev server" + } + ], + // An element to hold various schemas for the specification. + // https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#componentsObject + "components" : { + // Define your security schemes here + // https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#securitySchemeObject + "securitySchemes" : { + "ApiKeyAuth" : { + "type" : "apiKey", + "description" : "User your JWT as an Api Key for security", + "name" : "x-api-key", + "in" : "header" + }, + "bearerAuth" : { + "type" : "http", + "scheme" : "bearer", + "bearerFormat" : "JWT" + } + } + } + + // A default declaration of Security Requirement Objects to be used across the API. + // https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md#securityRequirementObject + // Only one of these requirements needs to be satisfied to authorize a request. + // Individual operations may set their own requirements with `@security` + // "security" : [ + // { "APIKey" : [] }, + // { "UserSecurity" : [] } + // ] + }; + } + +} From 5dc37c546db5ccd52001ecbdc5f9119cd177de91 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 9 Oct 2025 23:02:00 +0200 Subject: [PATCH 21/23] more setup --- .cbmigrations.json | 20 ------ Setup.bx | 161 --------------------------------------------- readme.md | 158 ++------------------------------------------ 3 files changed, 7 insertions(+), 332 deletions(-) delete mode 100755 .cbmigrations.json delete mode 100644 Setup.bx diff --git a/.cbmigrations.json b/.cbmigrations.json deleted file mode 100755 index 1c0df0c..0000000 --- a/.cbmigrations.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "default": { - "manager": "cfmigrations.models.QBMigrationManager", - "migrationsDirectory": "resources/database/migrations/", - "seedsDirectory": "resources/database/seeds/", - "properties": { - "defaultGrammar": "AutoDiscover@qb", - "schema": "${DB_SCHEMA}", - "migrationsTable": "cbmigrations", - "connectionInfo": { - "type": "${DB_DRIVER}", - "database": "${DB_DATABASE}", - "host": "${DB_HOST}", - "port": "${DB_PORT}", - "username": "${DB_USER}", - "password": "${DB_PASSWORD}" - } - } - } -} diff --git a/Setup.bx b/Setup.bx deleted file mode 100644 index 526f109..0000000 --- a/Setup.bx +++ /dev/null @@ -1,161 +0,0 @@ -class{ - - static { - projectRoot = server.cli.executionPath - } - - function main(){ - // Copy the .env.example to .env if it doesn't exist - if( !fileExists( ".env" ) ){ - println( "🥊 Creating .env file from .env.example" ) - fileCopy( ".env.example", ".env" ) - } else{ - println( "⏭️ .env file already exists, skipping creation." ) - } - - // Copy the copilot file if it doesn't exist - if( !directoryExists( ".github" ) ){ - println( "🥊 Creating .github directory" ) - directoryCreate( ".github" ) - } - if( !fileExists( ".github/copilot-instructions.md" ) ){ - println( "🥊 Creating copilot file" ) - fileCopy( "resources/copilot-instructions.md", ".github/copilot-instructions.md" ) - } else{ - println( "⏭️ .github/copilot-instructions.md file already exists, skipping creation." ) - } - - // Maven setup - // Ask the user if they want to use Maven for Java dependency management - var maven = CLIRead( "🙋 Do you want to use Maven for any Java dependency management? (y/n): " ).toLowerCase().booleanFormat(); - if( maven ){ - println( "🥊 Setting up a [pom.xml] in your root for Java dependency management" ) - println( "👉 You can add your Java dependencies to the [dependencies] section of the pom.xml" ) - println( "👉 They will be automatically copied to the [runtime/lib] folder for you once you run 'mvn install'" ) - } else{ - println( "🧹 Cleaning up unnecessary files" ) - if( fileExists( "effective-pom.xml" ) ){ - fileDelete( "effective-pom.xml" ) - } - if( fileExists( "pom.xml" ) ){ - fileDelete( "pom.xml" ) - } - } - - // Is this a REST API only project? - var apiOnly = CLIRead( "🙋 Is this a REST API only project? (y/n): " ).toLowerCase().booleanFormat(); - if( apiOnly ){ - println( "🥊 Setting up a REST API only ColdBox application" ) - println( "👉 You can always add views and layouts later if you change your mind" ) - - // Router - fileDelete( "app/config/Router.bx" ) - fileCopy( "resources/rest/Router.bx", "app/config/Router.bx" ) - // Tests - directoryDelete( "tests/specs", true ) - directoryCopy( source: "resources/rest/specs", destination: "tests/specs", recurse: true, createPath: true ) - // Configuration - directoryCopy( source: "resources/rest/config/modules", destination: "app/config/modules", recurse: false, createPath: true ) - // Models - directoryDelete( "app/models", true ) - directoryCopy( source: "resources/rest/models", destination: "app/models", recurse: true, createPath: true ) - // Handlers - directoryDelete( "app/handlers", true ) - directoryCopy( source: "resources/rest/handlers", destination: "app/handlers", recurse: true, createPath: true ) - // Api Docs - directoryCopy( source: "resources/rest/apidocs", destination: "resources/apidocs", recurse: true, createPath: true ) - directoryDelete( "resources/rest", true ) - - var newConfig = fileRead( "app/config/Coldbox.bx" ) - .replace( "Main.index", "Echo.index" ) - .replace( "Main.onException", "Echo.onError" ); - fileWrite( "app/config/Coldbox.bx", newConfig ); - - // Install CommandBox Modules - println( "🥊 Installing ColdBox API Production Modules: Security, Mementifier, Validation" ) - result = systemExecute( - name: "box", - arguments: "install cbsecurity,mementifier,cbvalidation", - directory: static.projectRoot - ) - println( result.output ) - - println( "🥊 Installing ColdBox API Development Modules: route-visualizer,relax" ) - result = systemExecute( - name: "box", - arguments: "install route-visualizer,relax --saveDev", - directory: static.projectRoot - ) - println( result.output ) - - println( "✅ REST API only setup complete!" ) - } else { - directoryDelete( "resources/rest", true ) - } - - // Vite Setup, only if not api only - if( !apiOnly ){ - var vite = CLIRead( "🙋 Do you want to use Vite for your frontend build system? (y/n): " ).toLowerCase().booleanFormat(); - if( vite ){ - println( "🥊 Setting up Vite for your frontend build system" ) - fileCopy( "resources/vite/.babelrc", ".babelrc" ) - fileCopy( "resources/vite/package.json", "package.json" ) - fileCopy( "resources/vite/vite.config.mjs", "vite.config.mjs" ) - fileDelete( "app/layouts/Main.bxm" ) - fileCopy( "resources/vite/layouts/Main.bxm", "app/layouts/Main.bxm" ) - fileCopy( "resources/vite/assets", "resources/assets" ) - println( "✅ Vite setup complete!" ) - println( "👉 You can run 'npm install' to install the dependencies" ) - println( "👉 You can run 'npm run dev' to start the development server" ) - println( "👉 You can run 'npm run build' to build the production assets" ) - } else { - directoryDelete( "resources/vite", true ) - } - } - - // Docker Setup? - var docker = CLIRead( "🙋 Do you want to use Docker for containerization? (y/n): " ).toLowerCase().booleanFormat(); - if( docker ){ - println( "🥊 Setting up Docker for containerization" ) - directoryCreate( "docker", true, true ) - fileCopy( "resources/docker/Dockerfile", "docker/Dockerfile" ) - fileCopy( "resources/docker/docker-compose.yml", "docker/docker-compose.yml" ) - fileCopy( "resources/docker/.dockerignore", ".dockerignore" ) - println( "✅ Docker setup complete!" ) - println( "ℹ️. Your docker files are located in the [docker] directory" ) - println( "👉 You can run 'box run-script docker:build' to build your Docker image." ) - println( "👉 You can run 'box run-script docker:run' to run your Docker container." ) - println( "👉 You can run 'box run-script docker:bash' to go into the container shell." ) - println( "👉 You can run 'box run-script docker:stack' to startup the Docker Compose Stack" ) - } else { - directoryDelete( "resources/docker", true ) - } - - // Dev Container - var devcontainer = CLIRead( "🙋 Do you want to keep the Dev Container setup so you can code in GitHub (.devcontainer)? (y/n): " ).toLowerCase().booleanFormat(); - if( !devcontainer ){ - println( "🧹 Cleaning up unnecessary files" ) - directoryDelete( ".devcontainer", true ) - } else { - println( "ℹ️. Your Dev Container files are located in the [.devcontainer] directory" ) - println( "👉 You can open this project in a container in VS Code by clicking the green button in the bottom left corner." ) - println( "👉 You can customize the container by modifying the [.devcontainer/devcontainer.json] and [.devcontainer/Dockerfile] files." ) - } - - // Remove the ignore array from box.json - println( "🛁 Cleaning up your box.json" ) - result = systemExecute( - name: "box", - arguments: "package set ignore=[]", - directory: static.projectRoot - ) - - // Delete this setup file - println( "🥊 Your ColdBox BoxLang application is ready to roll!" ) - println( "👉 Run 'box server start' to launch the development server." ) - println( "👉 Run 'box coldbox help' to see a list of available commands from the ColdBox CLI" ) - println( "ℹ️. You can remove the [Setup.bx] file from your project now or keep it for future reference." ) - println( "🗳️ Happy coding!" ) - } - -} \ No newline at end of file diff --git a/readme.md b/readme.md index 5ac71e0..c91220b 100644 --- a/readme.md +++ b/readme.md @@ -51,8 +51,6 @@ In order to work with this template, you need to have [CommandBox](https://www.o ```bash # Create a new ColdBox application using this BoxLang template box coldbox create app --boxlang -# Setup the Template for Operation and preferences -boxlang Setup.bx # Start up the web server box server start ``` @@ -61,152 +59,10 @@ Your application will be available at `http://localhost:8080` 🌐 Code to your liking and enjoy! 🎊 -## 🤖 Setup Script (`Setup.bx`) - -After creating your application, run the **Setup.bx** script to configure your template and set your preferences. This interactive script helps you customize your application for your specific needs. - -```bash -boxlang Setup.bx -``` - -### What Setup.bx Does: - -The setup script is an **interactive wizard** that walks you through several configuration options to tailor the template to your project needs: - -#### 1️⃣ **Environment Configuration** (`.env` file) - -- Automatically creates a `.env` file from `.env.example` if it doesn't exist -- Skips creation if `.env` already exists to preserve your settings -- Sets up environment variables for database connections, API keys, and more - -#### 2️⃣ **AI Development Assistant** (Copilot Instructions) - -- Creates `.github/copilot-instructions.md` for AI-powered coding assistance -- Provides context-aware suggestions based on ColdBox and BoxLang conventions -- Compatible with GitHub Copilot, Cursor, Windsurf, and other AI assistants -- Includes project structure, patterns, and best practices documentation - -#### 3️⃣ **Java Dependency Management** (Maven) - -- **Prompt**: "Do you want to use Maven for any Java dependency management?" -- **If YES**: Keeps `pom.xml` for managing Java dependencies - - Add dependencies to the `` section - - Run `mvn install` to download them to `runtime/lib/` - - BoxLang automatically class-loads all JARs in that folder -- **If NO**: Removes `pom.xml` and `effective-pom.xml` files - -#### 4️⃣ **REST API Configuration** - -- **Prompt**: "Is this a REST API only project?" -- **If YES**: Configures your application as a RESTful API - - Installs production modules: `cbsecurity`, `mementifier`, `cbvalidation` - - Installs development modules: `route-visualizer`, `relax` - - Replaces default Router with REST-optimized routing - - Updates handlers to REST-focused controllers - - Adds API documentation templates in `resources/apidocs/` - - Updates tests for API testing patterns - - Sets default routes to `Echo.index` and `Echo.onError` -- **If NO**: Keeps traditional MVC structure with views and layouts - -#### 5️⃣ **Frontend Build System** (Vite) - -- **Prompt**: "Do you want to use Vite for your frontend build system?" (Only if not API-only) -- **If YES**: Sets up modern frontend tooling with Vite - - Copies `vite.config.mjs` configuration - - Adds `package.json` with Vue 3, Tailwind CSS support - - Updates `Main.bxm` layout with Vite integration - - Creates `resources/assets/` directory for CSS/JS - - Instructions for `npm install`, `npm run dev`, `npm run build` -- **If NO**: Removes Vite-related files - -#### 6️⃣ **Docker Containerization** - -- **Prompt**: "Do you want to use Docker for containerization?" -- **If YES**: Sets up Docker infrastructure - - Creates `docker/` directory with Dockerfile - - Adds `docker-compose.yml` for multi-container setup - - Includes `.dockerignore` for optimized builds - - Provides scripts: `docker:build`, `docker:run`, `docker:bash`, `docker:stack` -- **If NO**: Removes Docker files - -#### 7️⃣ **Dev Container Support** - -- **Prompt**: "Do you want to keep the Dev Container setup so you can code in GitHub?" -- **If YES**: Keeps `.devcontainer/` for VS Code Remote Containers - - Enables coding in a consistent containerized environment - - Includes configuration in `.devcontainer/devcontainer.json` - - Customizable via `.devcontainer/Dockerfile` -- **If NO**: Removes `.devcontainer/` directory - -#### 8️⃣ **Cleanup & Finalization** - -- Cleans up `box.json` by removing the build ignore array -- Provides helpful next steps and commands -- Optionally remove `Setup.bx` itself after completion - -### 🎯 Example Setup Flow: - -```bash -$ boxlang Setup.bx -🥊 Creating .env file from .env.example -🥊 Creating .github directory -🥊 Creating copilot file -🙋 Do you want to use Maven for any Java dependency management? (y/n): y -🥊 Setting up a [pom.xml] in your root for Java dependency management -🙋 Is this a REST API only project? (y/n): y -🥊 Setting up a REST API only ColdBox application -🥊 Installing ColdBox API Production Modules: Security, Mementifier, Validation -🥊 Installing ColdBox API Development Modules: route-visualizer,relax -✅ REST API only setup complete! -🙋 Do you want to use Docker for containerization? (y/n): y -🥊 Setting up Docker for containerization -✅ Docker setup complete! -🙋 Do you want to keep the Dev Container setup? (y/n): n -🧹 Cleaning up unnecessary files -🛁 Cleaning up your box.json -🥊 Your ColdBox BoxLang application is ready to roll! -👉 Run 'box server start' to launch the development server. -```> - -> **💡 Best Practice**: Run `Setup.bx` immediately after creating your application to ensure everything is configured correctly for your specific use case. You can always modify these choices later by manually adjusting the relevant files. - ## ⚡ Vite Frontend Build System If you chose to use **Vite** during setup, this template includes a modern frontend build system with Vue 3 and Tailwind CSS support. Vite provides lightning-fast hot module replacement (HMR) and optimized production builds. -### 🔧 Vite Configuration - -The `vite.config.mjs` file is pre-configured with sensible defaults for ColdBox applications: - -```javascript -import { defineConfig } from "vite"; -import vue from "@vitejs/plugin-vue"; -import tailwindcss from "@tailwindcss/vite"; -import coldbox from "coldbox-vite-plugin"; - -export default defineConfig({ - plugins: [ - coldbox({ - input: [ "resources/assets/css/app.css", "resources/assets/js/app.js" ], - refresh: true, - publicDirectory: "public", // Web root directory - buildDirectory: "includes" // Output directory within public - }), - vue(), - tailwindcss() - ] -}); -``` - -#### 🎯 Key Configuration Options: - -- **`input`**: Entry points for CSS and JavaScript files -- **`refresh`**: Enable automatic browser refresh when ColdBox views/layouts change -- **`publicDirectory`**: The web root directory (default: `"public"`) -- **`buildDirectory`**: Where compiled assets are written (default: `"includes"`) - - Assets output to: `{publicDirectory}/{buildDirectory}/` - - Example: `public/includes/` with the defaults above - ### 📂 Asset Structure: ```text @@ -225,9 +81,9 @@ public/ └── app-[hash].js # Compiled & hashed JS ``` -### 🚀 Using Vite: +### 🚀 Using Vite -#### Development Mode (Hot Module Replacement): +#### Development Mode (Hot Module Replacement) ```bash npm run dev @@ -235,7 +91,7 @@ npm run dev This starts the Vite development server with HMR at `http://localhost:5173`. The ColdBox application automatically detects the dev server and loads assets from it. -#### Production Build: +#### Production Build ```bash npm run build @@ -243,11 +99,11 @@ npm run build Compiles and optimizes assets for production, outputting them to `public/includes/`. The generated files include content hashes for cache busting. -### 📝 Including Assets in Views: +### 📝 Including Assets in Views The `vite()` helper function automatically loads assets based on the environment: -```boxlang +```xml @@ -262,9 +118,9 @@ The `vite()` helper function automatically loads assets based on the environment **Development**: Loads from Vite dev server (`http://localhost:5173`) **Production**: Loads compiled assets from `public/includes/assets/` -### 🎨 Customizing Vite: +### 🎨 Customizing Vite -#### Change Output Directory: +#### Change Output Directory ```javascript coldbox({ From c85bd204d7e957401b9bfb764f4c00a2e8811dc0 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 9 Oct 2025 23:10:14 +0200 Subject: [PATCH 22/23] update ignores --- box.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/box.json b/box.json index c50f415..79e89f7 100644 --- a/box.json +++ b/box.json @@ -11,7 +11,11 @@ "shortDescription":"", "ignore":[ "changelog.md", - ".github/**" + ".github/ISSUE_TEMPLATE/*", + ".github/workflows/*", + ".github/CONTRIBUTING.md", + ".github/FUNDING.YML", + ".github/PULL_REQUEST_TEMPLATE.md" ], "dependencies":{ "coldbox":"be", From abad8ba9c21d5529e4f944129aae78ec1756c6fb Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 9 Oct 2025 23:27:20 +0200 Subject: [PATCH 23/23] prepping for launch for 8.x --- box.json | 15 +++++++-------- server.json | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/box.json b/box.json index 79e89f7..bf233b0 100644 --- a/box.json +++ b/box.json @@ -1,6 +1,6 @@ { "name":"Default ColdBox App Template For BoxLang", - "version":"1.1.0", + "version":"8.0.0", "author":"You", "location":"forgeboxStorage", "slug":"cbtemplate-boxlang", @@ -18,10 +18,10 @@ ".github/PULL_REQUEST_TEMPLATE.md" ], "dependencies":{ - "coldbox":"be", - "route-visualizer":"^2.2.0+2" + "coldbox":"be" }, "devDependencies":{ + "route-visualizer":"^2.2.0+2", "testbox":"*", "commandbox-boxlang":"*", "commandbox-cfformat":"*", @@ -30,13 +30,12 @@ }, "installPaths":{ "coldbox":"runtime/lib/coldbox/", - "testbox":"runtime/lib/testbox/", - "route-visualizer":"modules/route-visualizer/" + "testbox":"runtime/lib/testbox/" }, "scripts":{ - "format":"cfformat run app/**/*.cfc,tests/specs/,*.cfc --overwrite", - "format:check":"cfformat check app/**/*.cfc,tests/specs/,*.cfc ./.cfformat.json", - "format:watch":"cfformat watch path='app/**/*.cfc,tests/specs/,*.cfc' settingsPath='.cfformat.json'", + "format":"cfformat run app/**/*.bx,tests/specs/,*.bx --overwrite", + "format:check":"cfformat check app/**/*.bx,tests/specs/,*.bx ./.cfformat.json", + "format:watch":"cfformat watch path='app/**/*.bx,tests/specs/,*.bx' settingsPath='.cfformat.json'", "docker:build":"!docker build --no-cache -t my-coldbox-app -f ./docker/Dockerfile ./", "docker:run":"!docker run -it -p 8080:8080 my-coldbox-app", "docker:bash":"!docker run -it my-coldbox-app /bin/bash", diff --git a/server.json b/server.json index 6b768d5..3ec8bc8 100644 --- a/server.json +++ b/server.json @@ -20,6 +20,6 @@ } }, "scripts":{ - "onServerInitialInstall":"install bx-esapi,bx-mysql" + "onServerInitialInstall":"install bx-esapi" } }