├── backend ├── app │ ├── Filters │ │ └── .gitkeep │ ├── Helpers │ │ └── .gitkeep │ ├── Models │ │ └── .gitkeep │ ├── Language │ │ ├── .gitkeep │ │ └── en │ │ │ └── Validation.php │ ├── Libraries │ │ └── .gitkeep │ ├── ThirdParty │ │ └── .gitkeep │ ├── Database │ │ ├── Seeds │ │ │ ├── .gitkeep │ │ │ ├── DatabaseSeeder.php │ │ │ └── ClearDatabaseSeeder.php │ │ └── Migrations │ │ │ └── .gitkeep │ ├── .htaccess │ ├── Views │ │ ├── errors │ │ │ ├── cli │ │ │ │ ├── error_404.php │ │ │ │ ├── production.php │ │ │ │ └── error_exception.php │ │ │ └── html │ │ │ │ ├── production.php │ │ │ │ ├── error_400.php │ │ │ │ ├── error_404.php │ │ │ │ ├── debug.js │ │ │ │ └── debug.css │ │ └── components │ │ │ └── head.php │ ├── Config │ │ ├── Routes.php │ │ ├── ForeignCharacters.php │ │ ├── Commands.php │ │ ├── CURLRequest.php │ │ ├── Images.php │ │ ├── Publisher.php │ │ ├── Honeypot.php │ │ ├── Optimize.php │ │ ├── Services.php │ │ ├── Boot │ │ │ ├── production.php │ │ │ ├── development.php │ │ │ └── testing.php │ │ ├── Feature.php │ │ ├── Pager.php │ │ ├── Validation.php │ │ ├── Migrations.php │ │ ├── Events.php │ │ ├── Kint.php │ │ ├── Generators.php │ │ ├── View.php │ │ ├── Format.php │ │ ├── Modules.php │ │ ├── Email.php │ │ ├── Paths.php │ │ ├── DocTypes.php │ │ ├── Security.php │ │ ├── Encryption.php │ │ ├── Autoload.php │ │ ├── Filters.php │ │ ├── Constants.php │ │ ├── Cors.php │ │ ├── Cookie.php │ │ ├── Routing.php │ │ ├── Exceptions.php │ │ ├── Toolbar.php │ │ ├── ContentSecurityPolicy.php │ │ ├── Session.php │ │ ├── Cache.php │ │ ├── Logger.php │ │ └── Database.php │ ├── index.html │ ├── Controllers │ │ ├── Home.php │ │ └── BaseController.php │ ├── Common.php │ └── Commands │ │ └── HealthCheck.php ├── public │ ├── robots.txt │ ├── favicon.ico │ ├── uploads │ │ └── services │ │ │ └── 2025 │ │ │ └── 10 │ │ │ └── 1759339433_6ff00b199c90ba4517aa.jpg │ ├── js │ │ └── toast.js │ ├── .htaccess │ └── index.php ├── tests │ ├── .htaccess │ ├── index.html │ ├── session │ │ └── ExampleSessionTest.php │ ├── _support │ │ ├── Libraries │ │ │ └── ConfigReader.php │ │ ├── Models │ │ │ └── ExampleModel.php │ │ └── Database │ │ │ ├── Migrations │ │ │ └── 2020-02-22-222222_example_migration.php │ │ │ └── Seeds │ │ │ └── ExampleSeeder.php │ ├── database │ │ └── ExampleDatabaseTest.php │ ├── unit │ │ └── HealthTest.php │ └── README.md ├── writable │ ├── .htaccess │ ├── index.html │ ├── cache │ │ └── index.html │ ├── debugbar │ │ └── index.html │ ├── logs │ │ └── index.html │ ├── session │ │ └── index.html │ └── uploads │ │ └── index.html ├── .env.sample ├── nginx.conf ├── LICENSE ├── Dockerfile ├── composer.json ├── .gitignore ├── env ├── phpunit.xml.dist ├── spark ├── README.md ├── preload.php └── builds ├── .dockerignore ├── README.Docker.md ├── note.md ├── docs ├── core-engineering-principles.md ├── checklist │ ├── repo-and-infra.md │ └── additionals.md ├── v1-dev-manual.md ├── technical-manual.md └── commit-manual.md ├── compose.yaml └── .github └── copilot-instructions.md /backend/app/Filters/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/app/Helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/app/Models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/app/Language/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/app/Libraries/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/app/ThirdParty/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/app/Database/Seeds/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/app/Database/Migrations/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /backend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyx-0314/CI4-template/HEAD/backend/public/favicon.ico -------------------------------------------------------------------------------- /backend/app/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | Require all denied 3 | 4 | 5 | Deny from all 6 | 7 | -------------------------------------------------------------------------------- /backend/tests/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | Require all denied 3 | 4 | 5 | Deny from all 6 | 7 | -------------------------------------------------------------------------------- /backend/writable/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | Require all denied 3 | 4 | 5 | Deny from all 6 | 7 | -------------------------------------------------------------------------------- /backend/app/Language/en/Validation.php: -------------------------------------------------------------------------------- 1 | get('/', 'Home::index'); 9 | -------------------------------------------------------------------------------- /backend/app/Views/errors/cli/production.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /backend/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /backend/writable/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /backend/writable/cache/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /backend/writable/debugbar/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /backend/writable/logs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /backend/writable/session/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /backend/writable/uploads/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 403 Forbidden 5 | 6 | 7 | 8 |

Directory access is forbidden.

9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /backend/.env.sample: -------------------------------------------------------------------------------- 1 | CI_ENVIRONMENT = development 2 | database.default.hostname = mysql 3 | database.default.database = app 4 | database.default.username = app 5 | database.default.password = app 6 | database.default.DBDriver = MySQLi 7 | -------------------------------------------------------------------------------- /backend/app/Controllers/Home.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | protected $commands = [ 13 | 'health:check' => \App\Commands\HealthCheck::class, 14 | ]; 15 | } 16 | -------------------------------------------------------------------------------- /backend/app/Database/Seeds/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | call('App\\Database\\Seeds\\ClearDatabaseSeeder'); 12 | 13 | // $this->call('App\\Database\\Seeds\\'); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /backend/tests/session/ExampleSessionTest.php: -------------------------------------------------------------------------------- 1 | set('logged_in', 123); 15 | $this->assertSame(123, $session->get('logged_in')); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /backend/tests/_support/Libraries/ConfigReader.php: -------------------------------------------------------------------------------- 1 | { 10 | try { 11 | document.body.removeChild(el); 12 | } catch (e) {} 13 | }, 5000); 14 | } -------------------------------------------------------------------------------- /backend/app/Common.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <?= lang('Errors.whoops') ?> 8 | 9 | 12 | 13 | 14 | 15 |
16 | 17 |

18 | 19 |

20 | 21 |
22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /backend/app/Config/CURLRequest.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | public array $handlers = [ 28 | 'gd' => GDHandler::class, 29 | 'imagick' => ImageMagickHandler::class, 30 | ]; 31 | } 32 | -------------------------------------------------------------------------------- /backend/app/Database/Seeds/ClearDatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | disableForeignKeyChecks(); 18 | 19 | try { 20 | foreach ($tablesInOrder as $table) { 21 | if (method_exists($db, 'tableExists') && $db->tableExists($table)) { 22 | // TRUNCATE resets AUTO_INCREMENT in MySQL 23 | $db->table($table)->truncate(); 24 | } 25 | } 26 | } finally { 27 | $db->enableForeignKeyChecks(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /backend/app/Config/Publisher.php: -------------------------------------------------------------------------------- 1 | 23 | */ 24 | public $restrictions = [ 25 | ROOTPATH => '*', 26 | FCPATH => '#\.(s?css|js|map|html?|xml|json|webmanifest|ttf|eot|woff2?|gif|jpe?g|tiff?|png|webp|bmp|ico|svg)$#i', 27 | ]; 28 | } 29 | -------------------------------------------------------------------------------- /README.Docker.md: -------------------------------------------------------------------------------- 1 | ### Building and running your application 2 | 3 | When you're ready, start your application by running: 4 | `docker compose up --build`. 5 | 6 | Your application will be available at http://localhost:9001. 7 | 8 | ### PHP extensions 9 | If your application requires specific PHP extensions to run, they will need to be added to the Dockerfile. Follow the instructions and example in the Dockerfile to add them. 10 | 11 | ### Deploying your application to the cloud 12 | 13 | First, build your image, e.g.: `docker build -t myapp .`. 14 | If your cloud uses a different CPU architecture than your development 15 | machine (e.g., you are on a Mac M1 and your cloud provider is amd64), 16 | you'll want to build the image for that platform, e.g.: 17 | `docker build --platform=linux/amd64 -t myapp .`. 18 | 19 | Then, push it to your registry, e.g. `docker push myregistry.com/myapp`. 20 | 21 | Consult Docker's [getting started](https://docs.docker.com/go/get-started-sharing/) 22 | docs for more detail on building and pushing. -------------------------------------------------------------------------------- /backend/app/Config/Honeypot.php: -------------------------------------------------------------------------------- 1 | {label}'; 28 | 29 | /** 30 | * Honeypot container 31 | * 32 | * If you enabled CSP, you can remove `style="display:none"`. 33 | */ 34 | public string $container = '
{template}
'; 35 | 36 | /** 37 | * The id attribute for Honeypot container tag 38 | * 39 | * Used when CSP is enabled. 40 | */ 41 | public string $containerId = 'hpc'; 42 | } 43 | -------------------------------------------------------------------------------- /backend/app/Config/Optimize.php: -------------------------------------------------------------------------------- 1 | query('SELECT 1 AS ok')->getRowArray(); 24 | $latency = round((microtime(true) - $start) * 1000, 2); 25 | 26 | if (! isset($row['ok']) || (int)$row['ok'] !== 1) { 27 | CLI::error('Database: down (unexpected query result)'); 28 | return 2; 29 | } 30 | 31 | CLI::write("Database: up ({$latency} ms)"); 32 | return 0; 33 | } catch (\Throwable $e) { 34 | CLI::error('Database: down ('.$e->getMessage().')'); 35 | return 2; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:8.3-fpm 2 | 3 | # System deps 4 | RUN apt-get update && apt-get install -y --no-install-recommends \ 5 | libicu-dev libpng-dev libjpeg-dev libonig-dev libzip-dev unzip git curl \ 6 | && rm -rf /var/lib/apt/lists/* 7 | 8 | # Configure/Install PHP extensions 9 | # - mysqli added (fixes your health check) 10 | # - pdo_mysql already present 11 | # - gd configured with JPEG support (common need) 12 | RUN docker-php-ext-configure intl \ 13 | && docker-php-ext-configure gd --with-jpeg \ 14 | && docker-php-ext-install -j"$(nproc)" intl mbstring pdo pdo_mysql mysqli gd zip 15 | 16 | # Composer 17 | COPY --from=composer:2 /usr/bin/composer /usr/bin/composer 18 | 19 | # Set working dir BEFORE copying composer files so install runs in the right place 20 | WORKDIR /var/www/html 21 | 22 | # Leverage Docker layer caching for deps 23 | COPY composer.json composer.lock* ./ 24 | # For dev you usually want dev deps; for prod use --no-dev. Keeping dev-friendly by default: 25 | RUN composer install --prefer-dist --no-interaction --no-progress 26 | 27 | # Copy the rest of the app 28 | COPY ./ ./ 29 | 30 | # Dev PHP settings 31 | RUN { \ 32 | echo "display_errors=On"; \ 33 | echo "memory_limit=512M"; \ 34 | echo "upload_max_filesize=32M"; \ 35 | echo "post_max_size=32M"; \ 36 | } > /usr/local/etc/php/conf.d/dev.ini 37 | -------------------------------------------------------------------------------- /backend/app/Config/Pager.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | public array $templates = [ 24 | 'default_full' => 'CodeIgniter\Pager\Views\default_full', 25 | 'default_simple' => 'CodeIgniter\Pager\Views\default_simple', 26 | 'default_head' => 'CodeIgniter\Pager\Views\default_head', 27 | ]; 28 | 29 | /** 30 | * -------------------------------------------------------------------------- 31 | * Items Per Page 32 | * -------------------------------------------------------------------------- 33 | * 34 | * The default number of results shown in a single page. 35 | */ 36 | public int $perPage = 20; 37 | } 38 | -------------------------------------------------------------------------------- /backend/tests/_support/Database/Migrations/2020-02-22-222222_example_migration.php: -------------------------------------------------------------------------------- 1 | forge->addField('id'); 14 | $this->forge->addField([ 15 | 'name' => ['type' => 'varchar', 'constraint' => 31], 16 | 'uid' => ['type' => 'varchar', 'constraint' => 31], 17 | 'class' => ['type' => 'varchar', 'constraint' => 63], 18 | 'icon' => ['type' => 'varchar', 'constraint' => 31], 19 | 'summary' => ['type' => 'varchar', 'constraint' => 255], 20 | 'created_at' => ['type' => 'datetime', 'null' => true], 21 | 'updated_at' => ['type' => 'datetime', 'null' => true], 22 | 'deleted_at' => ['type' => 'datetime', 'null' => true], 23 | ]); 24 | 25 | $this->forge->addKey('name'); 26 | $this->forge->addKey('uid'); 27 | $this->forge->addKey(['deleted_at', 'id']); 28 | $this->forge->addKey('created_at'); 29 | 30 | $this->forge->createTable('factories'); 31 | } 32 | 33 | public function down(): void 34 | { 35 | $this->forge->dropTable('factories'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /backend/app/Config/Validation.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | public array $ruleSets = [ 24 | Rules::class, 25 | FormatRules::class, 26 | FileRules::class, 27 | CreditCardRules::class, 28 | ]; 29 | 30 | /** 31 | * Specifies the views that are used to display the 32 | * errors. 33 | * 34 | * @var array 35 | */ 36 | public array $templates = [ 37 | 'list' => 'CodeIgniter\Validation\Views\list', 38 | 'single' => 'CodeIgniter\Validation\Views\single', 39 | ]; 40 | 41 | // -------------------------------------------------------------------- 42 | // Rules 43 | // -------------------------------------------------------------------- 44 | } 45 | -------------------------------------------------------------------------------- /backend/tests/_support/Database/Seeds/ExampleSeeder.php: -------------------------------------------------------------------------------- 1 | 'Test Factory', 14 | 'uid' => 'test001', 15 | 'class' => 'Factories\Tests\NewFactory', 16 | 'icon' => 'fas fa-puzzle-piece', 17 | 'summary' => 'Longer sample text for testing', 18 | ], 19 | [ 20 | 'name' => 'Widget Factory', 21 | 'uid' => 'widget', 22 | 'class' => 'Factories\Tests\WidgetPlant', 23 | 'icon' => 'fas fa-puzzle-piece', 24 | 'summary' => 'Create widgets in your factory', 25 | ], 26 | [ 27 | 'name' => 'Evil Factory', 28 | 'uid' => 'evil-maker', 29 | 'class' => 'Factories\Evil\MyFactory', 30 | 'icon' => 'fas fa-book-dead', 31 | 'summary' => 'Abandon all hope, ye who enter here', 32 | ], 33 | ]; 34 | 35 | $builder = $this->db->table('factories'); 36 | 37 | foreach ($factories as $factory) { 38 | $builder->insert($factory); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /backend/tests/database/ExampleDatabaseTest.php: -------------------------------------------------------------------------------- 1 | findAll(); 23 | 24 | // Make sure the count is as expected 25 | $this->assertCount(3, $objects); 26 | } 27 | 28 | public function testSoftDeleteLeavesRow(): void 29 | { 30 | $model = new ExampleModel(); 31 | $this->setPrivateProperty($model, 'useSoftDeletes', true); 32 | $this->setPrivateProperty($model, 'tempUseSoftDeletes', true); 33 | 34 | /** @var stdClass $object */ 35 | $object = $model->first(); 36 | $model->delete($object->id); 37 | 38 | // The model should no longer find it 39 | $this->assertNull($model->find($object->id)); 40 | 41 | // ... but it should still be in the database 42 | $result = $model->builder()->where('id', $object->id)->get()->getResult(); 43 | 44 | $this->assertCount(1, $result); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /backend/app/Config/Boot/development.php: -------------------------------------------------------------------------------- 1 | 2 | Prepare your presentation. List of presenters, 3 | 4 |

5 | ESPIRITU - Git Problem Practice, Partial 6 | SAPALLO - Done 7 | TAMPIPIG - Done 8 | ARABE - Done 9 | RIVERA - Pending 10 | CANLAS - Partial 11 | DAMASO - Pending 12 | POMAREJOS - Done 13 | LAGUMEN - Done 14 | REYES - Done 15 | RAMOS KIER - Pending 16 | ABOBO - Done 17 | RARANG - Done 18 | RAMOS AIEL - Partial 19 | DOTILLOS - Done 20 | CAPAPAS - Done 21 | BAUTISTA - Partial 22 | ORE - Pending 23 | BARCELON - Done 24 | VELASCO - Done 25 | ADISON - Done 26 | MENDOZA - Done 27 | JAPON - Done 28 | GONZALES - Pending 29 | ALCARIOTO - Done 30 | ANCHOREZ - Done 31 | STA. MARIA - Pending 32 | ELIARDA - Done 33 | BUNDALIAN - Git Problem Practice, Partial 34 |

35 | 36 |

37 | Leo Miguel Seda - Partial, Git problem. 1 38 | *Jandell Seno - Pending 39 | Kurt John Lester Tan - Done 40 | Anthony Luis Ponce De Leon - Done 41 | DANIEL LING - Done 42 | *Yvan Rhay Solis - Pending 43 | KYLE ANGELO CARDENAS - Done. 1 44 | *Van Ryan Navarez - Pending 45 | ALLEJANDRO DANYAEL OBISPO - Done 46 | Justin Kirk Endozo - Done 47 | LANYER JOHN MABALOT - Pending 48 | *Nicole Pajarillaga - Pending 49 | MARIA FRANCESCA ALCARAZ - Done. 1 50 | Sheryll Anne Marie Baccay - Pending 51 | Mark Angelo Licmo-an - Pending 52 | *Mariah Julia Arellano - Pending 53 | Mary Arwen Quemuel - Done. 2 54 | Lawrence Blasco - Done. 1 55 | JAYVEE PANOL - Pending 56 | HILARY ASHLEY PAGADORA - Done 57 | *Adrian Guillermo - Done 58 | *AMILIA DANIELLE TANAJURA - Pending 59 | VIAN ROTCIV TIBAY - Done 60 |

61 | -------------------------------------------------------------------------------- /docs/core-engineering-principles.md: -------------------------------------------------------------------------------- 1 | # 📄 Core Engineering Principles 2 | 3 | ## OOP - Object-Oriented Programming 4 | - Encapsulation: Hide internal details; expose only what's necessary. 5 | - Clear Responsibilities: Each class should have a well-defined role. 6 | - Small Public APIs: Keep interfaces minimal and focused. 7 | 8 | ## SOLID - Five Principles of Object-Oriented Design 9 | - S: Single Responsibility Principle – One reason to change. 10 | - O: Open/Closed Principle – Open for extension, closed for modification. 11 | - L: Liskov Substitution Principle – Subtypes must be substitutable. 12 | - I: Interface Segregation Principle – Prefer many small interfaces. 13 | - D: Dependency Inversion Principle – Depend on abstractions, not concretions. 14 | 15 | ## KISS - Keep It Simple, Stupid 16 | Solve today’s problem simply; refactor only after green tests. 17 | 18 | ## DRY - Don’t Repeat Yourself 19 | Reuse through interfaces/services, not copy-paste. 20 | 21 | ## YAGNI - You Aren’t Gonna Need It 22 | No features/abstractions until a test/requirement demands it. 23 | 24 | ## Testing Pyramid Discipline 25 | Emphasize unit/service tests; keep e2e minimal & essential. 26 | 27 | ## Documentation-First Development 28 | - Stub the doc section before coding. 29 | - Ship code with notes and explanations. 30 | 31 | ## Convention Over Configuration 32 | Predictable names/paths (e.g., UserRepository, UserService, UsersController, /app/Domain|Infrastructure|Http). 33 | 34 | ## Fail Fast 35 | - Validate early; 36 | - Clear exceptions & logs; 37 | - Never swallow errors. 38 | -------------------------------------------------------------------------------- /backend/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "codeigniter4/appstarter", 3 | "description": "CodeIgniter4 starter app", 4 | "license": "MIT", 5 | "type": "project", 6 | "homepage": "https://codeigniter.com", 7 | "support": { 8 | "forum": "https://forum.codeigniter.com/", 9 | "source": "https://github.com/codeigniter4/CodeIgniter4", 10 | "slack": "https://codeigniterchat.slack.com" 11 | }, 12 | "require": { 13 | "php": "^8.1", 14 | "codeigniter4/framework": "v4.6.3" 15 | }, 16 | "require-dev": { 17 | "fakerphp/faker": "^1.9", 18 | "mikey179/vfsstream": "^1.6", 19 | "phpunit/phpunit": "^10.5.16" 20 | }, 21 | "autoload": { 22 | "psr-4": { 23 | "App\\": "app/", 24 | "Config\\": "app/Config/" 25 | }, 26 | "exclude-from-classmap": [ 27 | "**/Database/Migrations/**" 28 | ] 29 | }, 30 | "autoload-dev": { 31 | "psr-4": { 32 | "Tests\\Support\\": "tests/_support" 33 | } 34 | }, 35 | "config": { 36 | "optimize-autoloader": true, 37 | "preferred-install": "dist", 38 | "sort-packages": true 39 | }, 40 | "scripts": { 41 | "test": "phpunit", 42 | "health": "php spark health:check", 43 | "migrate": "php spark migrate", 44 | "migrate:status": "php spark migrate:status", 45 | "seed": "php spark db:seed", 46 | "seed:all": "php spark db:seed DatabaseSeeder", 47 | "truncate": "php spark db:seed ClearDatabaseSeeder" 48 | } 49 | } -------------------------------------------------------------------------------- /backend/app/Config/Boot/testing.php: -------------------------------------------------------------------------------- 1 | assertTrue(defined('APPPATH')); 15 | } 16 | 17 | public function testBaseUrlHasBeenSet(): void 18 | { 19 | $validation = service('validation'); 20 | 21 | $env = false; 22 | 23 | // Check the baseURL in .env 24 | if (is_file(HOMEPATH . '.env')) { 25 | $env = preg_grep('/^app\.baseURL = ./', file(HOMEPATH . '.env')) !== false; 26 | } 27 | 28 | if ($env) { 29 | // BaseURL in .env is a valid URL? 30 | // phpunit.xml.dist sets app.baseURL in $_SERVER 31 | // So if you set app.baseURL in .env, it takes precedence 32 | $config = new App(); 33 | $this->assertTrue( 34 | $validation->check($config->baseURL, 'valid_url'), 35 | 'baseURL "' . $config->baseURL . '" in .env is not valid URL', 36 | ); 37 | } 38 | 39 | // Get the baseURL in app/Config/App.php 40 | // You can't use Config\App, because phpunit.xml.dist sets app.baseURL 41 | $reader = new ConfigReader(); 42 | 43 | // BaseURL in app/Config/App.php is a valid URL? 44 | $this->assertTrue( 45 | $validation->check($reader->baseURL, 'valid_url'), 46 | 'baseURL "' . $reader->baseURL . '" in app/Config/App.php is not valid URL', 47 | ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /backend/app/Config/Migrations.php: -------------------------------------------------------------------------------- 1 | php spark make:migration 40 | * 41 | * NOTE: if you set an unsupported format, migration runner will not find 42 | * your migration files. 43 | * 44 | * Supported formats: 45 | * - YmdHis_ 46 | * - Y-m-d-His_ 47 | * - Y_m_d_His_ 48 | */ 49 | public string $timestampFormat = 'Y-m-d-His_'; 50 | } 51 | -------------------------------------------------------------------------------- /backend/app/Controllers/BaseController.php: -------------------------------------------------------------------------------- 1 | 37 | */ 38 | protected $helpers = []; 39 | 40 | /** 41 | * Be sure to declare properties for any property fetch you initialized. 42 | * The creation of dynamic property is deprecated in PHP 8.2. 43 | */ 44 | // protected $session; 45 | 46 | /** 47 | * @return void 48 | */ 49 | public function initController(RequestInterface $request, ResponseInterface $response, LoggerInterface $logger) 50 | { 51 | // Do Not Edit This Line 52 | parent::initController($request, $response, $logger); 53 | 54 | // Preload any models, libraries, etc, here. 55 | 56 | // E.g.: $this->session = service('session'); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /backend/public/.htaccess: -------------------------------------------------------------------------------- 1 | # Disable directory browsing 2 | Options -Indexes 3 | 4 | # ---------------------------------------------------------------------- 5 | # Rewrite engine 6 | # ---------------------------------------------------------------------- 7 | 8 | # Turning on the rewrite engine is necessary for the following rules and features. 9 | # FollowSymLinks must be enabled for this to work. 10 | 11 | Options +FollowSymlinks 12 | RewriteEngine On 13 | 14 | # If you installed CodeIgniter in a subfolder, you will need to 15 | # change the following line to match the subfolder you need. 16 | # http://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewritebase 17 | # RewriteBase / 18 | 19 | # Redirect Trailing Slashes... 20 | RewriteCond %{REQUEST_FILENAME} !-d 21 | RewriteCond %{REQUEST_URI} (.+)/$ 22 | RewriteRule ^ %1 [L,R=301] 23 | 24 | # Rewrite "www.example.com -> example.com" 25 | RewriteCond %{HTTPS} !=on 26 | RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC] 27 | RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L] 28 | 29 | # Checks to see if the user is attempting to access a valid file, 30 | # such as an image or css document, if this isn't true it sends the 31 | # request to the front controller, index.php 32 | RewriteCond %{REQUEST_FILENAME} !-f 33 | RewriteCond %{REQUEST_FILENAME} !-d 34 | RewriteRule ^([\s\S]*)$ index.php/$1 [L,NC,QSA] 35 | 36 | # Ensure Authorization header is passed along 37 | RewriteCond %{HTTP:Authorization} . 38 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 39 | 40 | 41 | 42 | # If we don't have mod_rewrite installed, all 404's 43 | # can be sent to index.php, and everything works as normal. 44 | ErrorDocument 404 index.php 45 | 46 | 47 | # Disable server signature start 48 | ServerSignature Off 49 | # Disable server signature end 50 | -------------------------------------------------------------------------------- /backend/public/index.php: -------------------------------------------------------------------------------- 1 | systemDirectory . '/Boot.php'; 58 | 59 | exit(Boot::bootWeb($paths)); 60 | -------------------------------------------------------------------------------- /backend/app/Config/Events.php: -------------------------------------------------------------------------------- 1 | 0) { 33 | ob_end_flush(); 34 | } 35 | 36 | ob_start(static fn ($buffer) => $buffer); 37 | } 38 | 39 | /* 40 | * -------------------------------------------------------------------- 41 | * Debug Toolbar Listeners. 42 | * -------------------------------------------------------------------- 43 | * If you delete, they will no longer be collected. 44 | */ 45 | if (CI_DEBUG && ! is_cli()) { 46 | Events::on('DBQuery', 'CodeIgniter\Debug\Toolbar\Collectors\Database::collect'); 47 | service('toolbar')->respond(); 48 | // Hot Reload route - for framework use on the hot reloader. 49 | if (ENVIRONMENT === 'development') { 50 | service('routes')->get('__hot-reload', static function (): void { 51 | (new HotReloader())->run(); 52 | }); 53 | } 54 | } 55 | }); 56 | -------------------------------------------------------------------------------- /backend/app/Config/Kint.php: -------------------------------------------------------------------------------- 1 | |ConstructablePluginInterface>|null 29 | */ 30 | public $plugins; 31 | 32 | public int $maxDepth = 6; 33 | public bool $displayCalledFrom = true; 34 | public bool $expanded = false; 35 | 36 | /* 37 | |-------------------------------------------------------------------------- 38 | | RichRenderer Settings 39 | |-------------------------------------------------------------------------- 40 | */ 41 | public string $richTheme = 'aante-light.css'; 42 | public bool $richFolder = false; 43 | 44 | /** 45 | * @var array>|null 46 | */ 47 | public $richObjectPlugins; 48 | 49 | /** 50 | * @var array>|null 51 | */ 52 | public $richTabPlugins; 53 | 54 | /* 55 | |-------------------------------------------------------------------------- 56 | | CLI Settings 57 | |-------------------------------------------------------------------------- 58 | */ 59 | public bool $cliColors = true; 60 | public bool $cliForceUTF8 = false; 61 | public bool $cliDetectWidth = true; 62 | public int $cliMinWidth = 40; 63 | } 64 | -------------------------------------------------------------------------------- /compose.yaml: -------------------------------------------------------------------------------- 1 | name: ad-ci4 2 | 3 | services: 4 | php: 5 | build: 6 | context: ./backend 7 | dockerfile: Dockerfile 8 | working_dir: /var/www/html 9 | init: true 10 | volumes: 11 | - ./backend:/var/www/html:cached 12 | - /var/www/html/vendor 13 | environment: 14 | CI_ENVIRONMENT: development 15 | depends_on: 16 | mysql: 17 | condition: service_healthy 18 | # Compose Watch: sync source; rebuild on dependency updates 19 | develop: 20 | watch: 21 | - action: sync 22 | path: ./backend 23 | target: /var/www/html 24 | - action: rebuild 25 | path: ./backend/composer.json 26 | - action: rebuild 27 | path: ./backend/composer.lock 28 | 29 | nginx: 30 | image: nginx:alpine 31 | init: true 32 | ports: 33 | - "8090:80" 34 | volumes: 35 | - ./backend:/var/www/html:ro 36 | - ./backend/nginx.conf:/etc/nginx/conf.d/default.conf:ro 37 | depends_on: 38 | php: 39 | condition: service_started 40 | # Compose Watch: sync site files and reload on config change 41 | develop: 42 | watch: 43 | - action: sync 44 | path: ./backend 45 | target: /var/www/html 46 | - action: sync+restart 47 | path: ./backend/nginx.conf 48 | target: /etc/nginx/conf.d/default.conf 49 | 50 | mysql: 51 | image: mysql:8.0 52 | ports: 53 | - "3390:3306" 54 | environment: 55 | MYSQL_ROOT_PASSWORD: root 56 | MYSQL_DATABASE: app 57 | MYSQL_USER: app 58 | MYSQL_PASSWORD: app 59 | command: [ "--default-authentication-plugin=mysql_native_password" ] 60 | healthcheck: 61 | test: [ "CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -proot || exit 1" ] 62 | interval: 10s 63 | timeout: 5s 64 | retries: 10 65 | volumes: 66 | - mysql_data:/var/lib/mysql 67 | 68 | phpmyadmin: 69 | image: phpmyadmin:latest 70 | profiles: [ "tools" ] # run only if you pass --profile tools 71 | ports: 72 | - "8091:80" 73 | environment: 74 | PMA_HOST: mysql 75 | PMA_USER: root 76 | PMA_PASSWORD: root 77 | depends_on: 78 | mysql: 79 | condition: service_healthy 80 | 81 | volumes: 82 | mysql_data: 83 | -------------------------------------------------------------------------------- /backend/app/Config/Generators.php: -------------------------------------------------------------------------------- 1 | |string> 27 | */ 28 | public array $views = [ 29 | 'make:cell' => [ 30 | 'class' => 'CodeIgniter\Commands\Generators\Views\cell.tpl.php', 31 | 'view' => 'CodeIgniter\Commands\Generators\Views\cell_view.tpl.php', 32 | ], 33 | 'make:command' => 'CodeIgniter\Commands\Generators\Views\command.tpl.php', 34 | 'make:config' => 'CodeIgniter\Commands\Generators\Views\config.tpl.php', 35 | 'make:controller' => 'CodeIgniter\Commands\Generators\Views\controller.tpl.php', 36 | 'make:entity' => 'CodeIgniter\Commands\Generators\Views\entity.tpl.php', 37 | 'make:filter' => 'CodeIgniter\Commands\Generators\Views\filter.tpl.php', 38 | 'make:migration' => 'CodeIgniter\Commands\Generators\Views\migration.tpl.php', 39 | 'make:model' => 'CodeIgniter\Commands\Generators\Views\model.tpl.php', 40 | 'make:seeder' => 'CodeIgniter\Commands\Generators\Views\seeder.tpl.php', 41 | 'make:validation' => 'CodeIgniter\Commands\Generators\Views\validation.tpl.php', 42 | 'session:migration' => 'CodeIgniter\Commands\Generators\Views\migration.tpl.php', 43 | ]; 44 | } 45 | -------------------------------------------------------------------------------- /docs/checklist/repo-and-infra.md: -------------------------------------------------------------------------------- 1 | # Repository and Infrastructures 2 | 3 | ## Repository Structure 4 | 5 | - Monorepo Structure: This uses a single repo for codes and deploy each in respective folders 6 | - Basic Repository Requirements: 7 | - readme.md (modify accordingly) 8 | - [ ] Title 9 | - [ ] Description 10 | - [ ] Update Overview 11 | - [ ] Update Key Components 12 | - [ ] Update Technology 13 | - [ ] Update Resources 14 | - .gitignore (just copy but you can revise) 15 | - license (just copy) 16 | - Folder Structure 17 | - backend : folder which houses the ci4 or backend infrastracture 18 | - docs : folder which houses the documentations, checklist and reports 19 | - frontend : folder which houses the react/vue or frontend infrastracture 20 | 21 | ## CI4 22 | - [ ] Initialize CI4 23 | ```bash 24 | composer create-project codeigniter4/appstarter backend 25 | ``` 26 | - [ ] Update `php.ini` 27 | ```text 28 | 29 | extension=intl 30 | ``` 31 | - [ ] Install CI4 dependencies 32 | ```bash 33 | composer install 34 | ``` 35 | - [ ] deleting `.git` of backend 36 | - [ ] open the file directory 37 | - [ ] switch hidden files to on 38 | - [ ] then delete `.git` 39 | 40 | ## Docker Setup 41 | - [ ] Just use the format its set already, changing port if it has an issue 42 | 43 | ## CI4 Env Setup 44 | - Check compose.yaml 45 | - [ ] Make sure `.env` data are matched withsql environments 46 | 47 | ### Notes: 48 | - port: sets what port it will appear 49 | - environment: sets the initial datas and serves as the credentials 50 | 51 | ## Test 52 | There is test commands added for testing Ci and Database. 53 | To test use the following commands 54 | - [ ] Make sure composer is working. 55 | ```bash 56 | docker compose exec php composer dump-autoload 57 | ``` 58 | - Expected result should be something similar to this 59 | ```cmd 60 | Generating optimized autoload files 61 | Generated optimized autoload files containing 2460 classes 62 | ``` 63 | - [ ] Check if the both technology are working 64 | ```bash 65 | docker compose exec php php spark health:check 66 | # or 67 | docker compose exec php composer health 68 | ``` 69 | - Expected result 70 | ```cmd 71 | CodeIgniter: up (version 4.6.3) 72 | Database: up (31.72 ms) 73 | ``` 74 | -------------------------------------------------------------------------------- /backend/app/Config/View.php: -------------------------------------------------------------------------------- 1 | 38 | * @phpstan-var array 39 | */ 40 | public $filters = []; 41 | 42 | /** 43 | * Parser Plugins provide a way to extend the functionality provided 44 | * by the core Parser by creating aliases that will be replaced with 45 | * any callable. Can be single or tag pair. 46 | * 47 | * @var array|string> 48 | * @phpstan-var array|parser_callable_string|parser_callable> 49 | */ 50 | public $plugins = []; 51 | 52 | /** 53 | * View Decorators are class methods that will be run in sequence to 54 | * have a chance to alter the generated output just prior to caching 55 | * the results. 56 | * 57 | * All classes must implement CodeIgniter\View\ViewDecoratorInterface 58 | * 59 | * @var list> 60 | */ 61 | public array $decorators = []; 62 | } 63 | -------------------------------------------------------------------------------- /backend/app/Views/errors/html/error_400.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <?= lang('Errors.badRequest') ?> 6 | 7 | 70 | 71 | 72 |
73 |

400

74 | 75 |

76 | 77 | 78 | 79 | 80 | 81 |

82 |
83 | 84 | 85 | -------------------------------------------------------------------------------- /backend/app/Views/errors/html/error_404.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | <?= lang('Errors.pageNotFound') ?> 6 | 7 | 70 | 71 | 72 |
73 |

404

74 | 75 |

76 | 77 | 78 | 79 | 80 | 81 |

82 |
83 | 84 | 85 | -------------------------------------------------------------------------------- /backend/app/Config/Format.php: -------------------------------------------------------------------------------- 1 | 25 | */ 26 | public array $supportedResponseFormats = [ 27 | 'application/json', 28 | 'application/xml', // machine-readable XML 29 | 'text/xml', // human-readable XML 30 | ]; 31 | 32 | /** 33 | * -------------------------------------------------------------------------- 34 | * Formatters 35 | * -------------------------------------------------------------------------- 36 | * 37 | * Lists the class to use to format responses with of a particular type. 38 | * For each mime type, list the class that should be used. Formatters 39 | * can be retrieved through the getFormatter() method. 40 | * 41 | * @var array 42 | */ 43 | public array $formatters = [ 44 | 'application/json' => JSONFormatter::class, 45 | 'application/xml' => XMLFormatter::class, 46 | 'text/xml' => XMLFormatter::class, 47 | ]; 48 | 49 | /** 50 | * -------------------------------------------------------------------------- 51 | * Formatters Options 52 | * -------------------------------------------------------------------------- 53 | * 54 | * Additional Options to adjust default formatters behaviour. 55 | * For each mime type, list the additional options that should be used. 56 | * 57 | * @var array 58 | */ 59 | public array $formatterOptions = [ 60 | 'application/json' => JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES, 61 | 'application/xml' => 0, 62 | 'text/xml' => 0, 63 | ]; 64 | } 65 | -------------------------------------------------------------------------------- /backend/app/Views/errors/cli/error_exception.php: -------------------------------------------------------------------------------- 1 | getFile()) . ':' . $exception->getLine(), 'green')); 9 | CLI::newLine(); 10 | 11 | $last = $exception; 12 | 13 | while ($prevException = $last->getPrevious()) { 14 | $last = $prevException; 15 | 16 | CLI::write(' Caused by:'); 17 | CLI::write(' [' . $prevException::class . ']', 'red'); 18 | CLI::write(' ' . $prevException->getMessage()); 19 | CLI::write(' at ' . CLI::color(clean_path($prevException->getFile()) . ':' . $prevException->getLine(), 'green')); 20 | CLI::newLine(); 21 | } 22 | 23 | // The backtrace 24 | if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE) { 25 | $backtraces = $last->getTrace(); 26 | 27 | if ($backtraces) { 28 | CLI::write('Backtrace:', 'green'); 29 | } 30 | 31 | foreach ($backtraces as $i => $error) { 32 | $padFile = ' '; // 4 spaces 33 | $padClass = ' '; // 7 spaces 34 | $c = str_pad($i + 1, 3, ' ', STR_PAD_LEFT); 35 | 36 | if (isset($error['file'])) { 37 | $filepath = clean_path($error['file']) . ':' . $error['line']; 38 | 39 | CLI::write($c . $padFile . CLI::color($filepath, 'yellow')); 40 | } else { 41 | CLI::write($c . $padFile . CLI::color('[internal function]', 'yellow')); 42 | } 43 | 44 | $function = ''; 45 | 46 | if (isset($error['class'])) { 47 | $type = ($error['type'] === '->') ? '()' . $error['type'] : $error['type']; 48 | $function .= $padClass . $error['class'] . $type . $error['function']; 49 | } elseif (! isset($error['class']) && isset($error['function'])) { 50 | $function .= $padClass . $error['function']; 51 | } 52 | 53 | $args = implode(', ', array_map(static fn ($value): string => match (true) { 54 | is_object($value) => 'Object(' . $value::class . ')', 55 | is_array($value) => $value !== [] ? '[...]' : '[]', 56 | $value === null => 'null', // return the lowercased version 57 | default => var_export($value, true), 58 | }, array_values($error['args'] ?? []))); 59 | 60 | $function .= '(' . $args . ')'; 61 | 62 | CLI::write($function); 63 | CLI::newLine(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | #------------------------- 2 | # Operating Specific Junk Files 3 | #------------------------- 4 | 5 | # OS X 6 | .DS_Store 7 | .AppleDouble 8 | .LSOverride 9 | 10 | # OS X Thumbnails 11 | ._* 12 | 13 | # Windows image file caches 14 | Thumbs.db 15 | ehthumbs.db 16 | Desktop.ini 17 | 18 | # Recycle Bin used on file shares 19 | $RECYCLE.BIN/ 20 | 21 | # Windows Installer files 22 | *.cab 23 | *.msi 24 | *.msm 25 | *.msp 26 | 27 | # Windows shortcuts 28 | *.lnk 29 | 30 | # Linux 31 | *~ 32 | 33 | # KDE directory preferences 34 | .directory 35 | 36 | # Linux trash folder which might appear on any partition or disk 37 | .Trash-* 38 | 39 | #------------------------- 40 | # Environment Files 41 | #------------------------- 42 | # These should never be under version control, 43 | # as it poses a security risk. 44 | .env 45 | .vagrant 46 | Vagrantfile 47 | 48 | #------------------------- 49 | # Temporary Files 50 | #------------------------- 51 | writable/cache/* 52 | !writable/cache/index.html 53 | 54 | writable/logs/* 55 | !writable/logs/index.html 56 | 57 | writable/session/* 58 | !writable/session/index.html 59 | 60 | writable/uploads/* 61 | !writable/uploads/index.html 62 | 63 | writable/debugbar/* 64 | !writable/debugbar/index.html 65 | 66 | php_errors.log 67 | 68 | #------------------------- 69 | # User Guide Temp Files 70 | #------------------------- 71 | user_guide_src/build/* 72 | user_guide_src/cilexer/build/* 73 | user_guide_src/cilexer/dist/* 74 | user_guide_src/cilexer/pycilexer.egg-info/* 75 | 76 | #------------------------- 77 | # Test Files 78 | #------------------------- 79 | tests/coverage* 80 | 81 | # Don't save phpunit under version control. 82 | phpunit 83 | 84 | #------------------------- 85 | # Composer 86 | #------------------------- 87 | vendor/ 88 | 89 | #------------------------- 90 | # IDE / Development Files 91 | #------------------------- 92 | 93 | # Modules Testing 94 | _modules/* 95 | 96 | # phpenv local config 97 | .php-version 98 | 99 | # Jetbrains editors (PHPStorm, etc) 100 | .idea/ 101 | *.iml 102 | 103 | # NetBeans 104 | /nbproject/ 105 | /build/ 106 | /nbbuild/ 107 | /dist/ 108 | /nbdist/ 109 | /nbactions.xml 110 | /nb-configuration.xml 111 | /.nb-gradle/ 112 | 113 | # Sublime Text 114 | *.tmlanguage.cache 115 | *.tmPreferences.cache 116 | *.stTheme.cache 117 | *.sublime-workspace 118 | *.sublime-project 119 | .phpintel 120 | /api/ 121 | 122 | # Visual Studio Code 123 | .vscode/ 124 | 125 | /results/ 126 | /phpunit*.xml 127 | 128 | /public/uploads -------------------------------------------------------------------------------- /backend/app/Config/Modules.php: -------------------------------------------------------------------------------- 1 | [ 47 | * // List up all packages to auto-discover 48 | * 'codeigniter4/shield', 49 | * ], 50 | * ] 51 | * or 52 | * [ 53 | * 'exclude' => [ 54 | * // List up packages to exclude. 55 | * 'pestphp/pest', 56 | * ], 57 | * ] 58 | * 59 | * @var array{only?: list, exclude?: list} 60 | */ 61 | public $composerPackages = []; 62 | 63 | /** 64 | * -------------------------------------------------------------------------- 65 | * Auto-Discovery Rules 66 | * -------------------------------------------------------------------------- 67 | * 68 | * Aliases list of all discovery classes that will be active and used during 69 | * the current application request. 70 | * 71 | * If it is not listed, only the base application elements will be used. 72 | * 73 | * @var list 74 | */ 75 | public $aliases = [ 76 | 'events', 77 | 'filters', 78 | 'registrars', 79 | 'routes', 80 | 'services', 81 | ]; 82 | } 83 | -------------------------------------------------------------------------------- /backend/env: -------------------------------------------------------------------------------- 1 | #-------------------------------------------------------------------- 2 | # Example Environment Configuration file 3 | # 4 | # This file can be used as a starting point for your own 5 | # custom .env files, and contains most of the possible settings 6 | # available in a default install. 7 | # 8 | # By default, all of the settings are commented out. If you want 9 | # to override the setting, you must un-comment it by removing the '#' 10 | # at the beginning of the line. 11 | #-------------------------------------------------------------------- 12 | 13 | #-------------------------------------------------------------------- 14 | # ENVIRONMENT 15 | #-------------------------------------------------------------------- 16 | 17 | # CI_ENVIRONMENT = production 18 | 19 | #-------------------------------------------------------------------- 20 | # APP 21 | #-------------------------------------------------------------------- 22 | 23 | # app.baseURL = '' 24 | # If you have trouble with `.`, you could also use `_`. 25 | # app_baseURL = '' 26 | # app.forceGlobalSecureRequests = false 27 | # app.CSPEnabled = false 28 | 29 | #-------------------------------------------------------------------- 30 | # DATABASE 31 | #-------------------------------------------------------------------- 32 | 33 | # database.default.hostname = localhost 34 | # database.default.database = ci4 35 | # database.default.username = root 36 | # database.default.password = root 37 | # database.default.DBDriver = MySQLi 38 | # database.default.DBPrefix = 39 | # database.default.port = 3306 40 | 41 | # If you use MySQLi as tests, first update the values of Config\Database::$tests. 42 | # database.tests.hostname = localhost 43 | # database.tests.database = ci4_test 44 | # database.tests.username = root 45 | # database.tests.password = root 46 | # database.tests.DBDriver = MySQLi 47 | # database.tests.DBPrefix = 48 | # database.tests.charset = utf8mb4 49 | # database.tests.DBCollat = utf8mb4_general_ci 50 | # database.tests.port = 3306 51 | 52 | #-------------------------------------------------------------------- 53 | # ENCRYPTION 54 | #-------------------------------------------------------------------- 55 | 56 | # encryption.key = 57 | 58 | #-------------------------------------------------------------------- 59 | # SESSION 60 | #-------------------------------------------------------------------- 61 | 62 | # session.driver = 'CodeIgniter\Session\Handlers\FileHandler' 63 | # session.savePath = null 64 | 65 | #-------------------------------------------------------------------- 66 | # LOGGER 67 | #-------------------------------------------------------------------- 68 | 69 | # logger.threshold = 4 70 | -------------------------------------------------------------------------------- /backend/phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 13 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ./tests 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | ./app 38 | 39 | 40 | ./app/Views 41 | ./app/Config/Routes.php 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /backend/app/Config/Email.php: -------------------------------------------------------------------------------- 1 | 8 | * 9 | * For the full copyright and license information, please view 10 | * the LICENSE file that was distributed with this source code. 11 | */ 12 | 13 | use CodeIgniter\Boot; 14 | use Config\Paths; 15 | 16 | /* 17 | * -------------------------------------------------------------------- 18 | * CODEIGNITER COMMAND-LINE TOOLS 19 | * -------------------------------------------------------------------- 20 | * The main entry point into the CLI system and allows you to run 21 | * commands and perform maintenance on your application. 22 | */ 23 | 24 | /* 25 | *--------------------------------------------------------------- 26 | * CHECK SERVER API 27 | *--------------------------------------------------------------- 28 | */ 29 | 30 | // Refuse to run when called from php-cgi 31 | if (str_starts_with(PHP_SAPI, 'cgi')) { 32 | exit("The cli tool is not supported when running php-cgi. It needs php-cli to function!\n\n"); 33 | } 34 | 35 | /* 36 | *--------------------------------------------------------------- 37 | * CHECK PHP VERSION 38 | *--------------------------------------------------------------- 39 | */ 40 | 41 | $minPhpVersion = '8.1'; // If you update this, don't forget to update `public/index.php`. 42 | if (version_compare(PHP_VERSION, $minPhpVersion, '<')) { 43 | $message = sprintf( 44 | 'Your PHP version must be %s or higher to run CodeIgniter. Current version: %s', 45 | $minPhpVersion, 46 | PHP_VERSION, 47 | ); 48 | 49 | exit($message); 50 | } 51 | 52 | // We want errors to be shown when using it from the CLI. 53 | error_reporting(E_ALL); 54 | ini_set('display_errors', '1'); 55 | 56 | /* 57 | *--------------------------------------------------------------- 58 | * SET THE CURRENT DIRECTORY 59 | *--------------------------------------------------------------- 60 | */ 61 | 62 | // Path to the front controller 63 | define('FCPATH', __DIR__ . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR); 64 | 65 | // Ensure the current directory is pointing to the front controller's directory 66 | chdir(FCPATH); 67 | 68 | /* 69 | *--------------------------------------------------------------- 70 | * BOOTSTRAP THE APPLICATION 71 | *--------------------------------------------------------------- 72 | * This process sets up the path constants, loads and registers 73 | * our autoloader, along with Composer's, loads our constants 74 | * and fires up an environment-specific bootstrapping. 75 | */ 76 | 77 | // LOAD OUR PATHS CONFIG FILE 78 | // This is the line that might need to be changed, depending on your folder structure. 79 | require FCPATH . '../app/Config/Paths.php'; 80 | // ^^^ Change this line if you move your application folder 81 | 82 | $paths = new Paths(); 83 | 84 | // LOAD THE FRAMEWORK BOOTSTRAP FILE 85 | require $paths->systemDirectory . '/Boot.php'; 86 | 87 | exit(Boot::bootSpark($paths)); 88 | -------------------------------------------------------------------------------- /backend/app/Config/Paths.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | public array $list = [ 13 | 'xhtml11' => '', 14 | 'xhtml1-strict' => '', 15 | 'xhtml1-trans' => '', 16 | 'xhtml1-frame' => '', 17 | 'xhtml-basic11' => '', 18 | 'html5' => '', 19 | 'html4-strict' => '', 20 | 'html4-trans' => '', 21 | 'html4-frame' => '', 22 | 'mathml1' => '', 23 | 'mathml2' => '', 24 | 'svg10' => '', 25 | 'svg11' => '', 26 | 'svg11-basic' => '', 27 | 'svg11-tiny' => '', 28 | 'xhtml-math-svg-xh' => '', 29 | 'xhtml-math-svg-sh' => '', 30 | 'xhtml-rdfa-1' => '', 31 | 'xhtml-rdfa-2' => '', 32 | ]; 33 | 34 | /** 35 | * Whether to remove the solidus (`/`) character for void HTML elements (e.g. ``) 36 | * for HTML5 compatibility. 37 | * 38 | * Set to: 39 | * `true` - to be HTML5 compatible 40 | * `false` - to be XHTML compatible 41 | */ 42 | public bool $html5 = true; 43 | } 44 | -------------------------------------------------------------------------------- /backend/app/Config/Security.php: -------------------------------------------------------------------------------- 1 | [!WARNING] 59 | > - The end of life date for PHP 7.4 was November 28, 2022. 60 | > - The end of life date for PHP 8.0 was November 26, 2023. 61 | > - If you are still using PHP 7.4 or 8.0, you should upgrade immediately. 62 | > - The end of life date for PHP 8.1 will be December 31, 2025. 63 | 64 | Additionally, make sure that the following extensions are enabled in your PHP: 65 | 66 | - json (enabled by default - don't turn it off) 67 | - [mysqlnd](http://php.net/manual/en/mysqlnd.install.php) if you plan to use MySQL 68 | - [libcurl](http://php.net/manual/en/curl.requirements.php) if you plan to use the HTTP\CURLRequest library 69 | -------------------------------------------------------------------------------- /backend/app/Config/Encryption.php: -------------------------------------------------------------------------------- 1 | 40 | cd ci4-instructional-template 41 | ``` 42 | 43 | ### Step 2: Environment File 44 | 45 | * Copy `.env.example` into `.env` inside `backend/ci4`. 46 | * Ensure `.env` is **not ignored** in `.dockerignore`. 47 | * Environment variables will be loaded into containers automatically. 48 | 49 | ### Step 3: Start Containers 50 | 51 | ```bash 52 | docker compose up --build 53 | ``` 54 | 55 | * App: `http://localhost:8080` 56 | * phpMyAdmin (if enabled): `http://localhost:8081` 57 | 58 | --- 59 | 60 | ## 4. Database 61 | 62 | ### Run Migrations 63 | 64 | ```bash 65 | docker compose exec php php spark migrate 66 | ``` 67 | 68 | ### Run Seeders 69 | 70 | ```bash 71 | docker compose exec php php spark db:seed UserSeeder 72 | ``` 73 | 74 | --- 75 | 76 | ## 5. TailwindCSS (CDNJS) 77 | 78 | Tailwind is included directly in CI4 views using the CDN link: 79 | 80 | ```html 81 | 82 | ``` 83 | 84 | * Place the link in your base layout view (`app/Views/layouts/main.php`). 85 | * No build tools required. 86 | 87 | --- 88 | 89 | ## 6. Running Tests 90 | 91 | ### PHPUnit 92 | 93 | ```bash 94 | docker compose exec php vendor/bin/phpunit 95 | ``` 96 | 97 | ### PHPStan (static analysis) 98 | 99 | ```bash 100 | docker compose exec php vendor/bin/phpstan analyse 101 | ``` 102 | 103 | --- 104 | 105 | ## 7. Common Tasks 106 | 107 | * **New Migration** 108 | 109 | ```bash 110 | docker compose exec php php spark make:migration CreatePostsTable 111 | ``` 112 | 113 | * **New Seeder** 114 | 115 | ```bash 116 | docker compose exec php php spark db:seed PostSeeder 117 | ``` 118 | 119 | * **Run Server (inside container)** 120 | 121 | ```bash 122 | docker compose exec php php spark serve 123 | ``` 124 | 125 | --- 126 | 127 | ## 8. Troubleshooting 128 | 129 | * **Ports in use** → Change port mapping in `docker-compose.yml`. 130 | * **.env not loaded** → Ensure it’s present in repo & not in `.dockerignore`. 131 | * **Permission errors** → Run `chmod -R 775 writable/`. 132 | * **Container stuck** → Rebuild: 133 | 134 | ```bash 135 | docker compose down -v && docker compose up --build 136 | ``` 137 | 138 | --- 139 | 140 | ## 9. Notes & Version 141 | 142 | * Last update: YYYY-MM-DD 143 | * Who: Author/Editor Name 144 | * TL;DR: One-line summary of what was changed in this doc -------------------------------------------------------------------------------- /backend/app/Config/Autoload.php: -------------------------------------------------------------------------------- 1 | |string> 39 | */ 40 | public $psr4 = [ 41 | APP_NAMESPACE => APPPATH, 42 | ]; 43 | 44 | /** 45 | * ------------------------------------------------------------------- 46 | * Class Map 47 | * ------------------------------------------------------------------- 48 | * The class map provides a map of class names and their exact 49 | * location on the drive. Classes loaded in this manner will have 50 | * slightly faster performance because they will not have to be 51 | * searched for within one or more directories as they would if they 52 | * were being autoloaded through a namespace. 53 | * 54 | * Prototype: 55 | * $classmap = [ 56 | * 'MyClass' => '/path/to/class/file.php' 57 | * ]; 58 | * 59 | * @var array 60 | */ 61 | public $classmap = []; 62 | 63 | /** 64 | * ------------------------------------------------------------------- 65 | * Files 66 | * ------------------------------------------------------------------- 67 | * The files array provides a list of paths to __non-class__ files 68 | * that will be autoloaded. This can be useful for bootstrap operations 69 | * or for loading functions. 70 | * 71 | * Prototype: 72 | * $files = [ 73 | * '/path/to/my/file.php', 74 | * ]; 75 | * 76 | * @var list 77 | */ 78 | public $files = []; 79 | 80 | /** 81 | * ------------------------------------------------------------------- 82 | * Helpers 83 | * ------------------------------------------------------------------- 84 | * Prototype: 85 | * $helpers = [ 86 | * 'form', 87 | * ]; 88 | * 89 | * @var list 90 | */ 91 | public $helpers = []; 92 | } 93 | -------------------------------------------------------------------------------- /backend/app/Views/errors/html/debug.js: -------------------------------------------------------------------------------- 1 | var tabLinks = new Array(); 2 | var contentDivs = new Array(); 3 | 4 | function init() 5 | { 6 | // Grab the tab links and content divs from the page 7 | var tabListItems = document.getElementById('tabs').childNodes; 8 | console.log(tabListItems); 9 | for (var i = 0; i < tabListItems.length; i ++) 10 | { 11 | if (tabListItems[i].nodeName == "LI") 12 | { 13 | var tabLink = getFirstChildWithTagName(tabListItems[i], 'A'); 14 | var id = getHash(tabLink.getAttribute('href')); 15 | tabLinks[id] = tabLink; 16 | contentDivs[id] = document.getElementById(id); 17 | } 18 | } 19 | 20 | // Assign onclick events to the tab links, and 21 | // highlight the first tab 22 | var i = 0; 23 | 24 | for (var id in tabLinks) 25 | { 26 | tabLinks[id].onclick = showTab; 27 | tabLinks[id].onfocus = function () { 28 | this.blur() 29 | }; 30 | if (i == 0) 31 | { 32 | tabLinks[id].className = 'active'; 33 | } 34 | i ++; 35 | } 36 | 37 | // Hide all content divs except the first 38 | var i = 0; 39 | 40 | for (var id in contentDivs) 41 | { 42 | if (i != 0) 43 | { 44 | console.log(contentDivs[id]); 45 | contentDivs[id].className = 'content hide'; 46 | } 47 | i ++; 48 | } 49 | } 50 | 51 | function showTab() 52 | { 53 | var selectedId = getHash(this.getAttribute('href')); 54 | 55 | // Highlight the selected tab, and dim all others. 56 | // Also show the selected content div, and hide all others. 57 | for (var id in contentDivs) 58 | { 59 | if (id == selectedId) 60 | { 61 | tabLinks[id].className = 'active'; 62 | contentDivs[id].className = 'content'; 63 | } 64 | else 65 | { 66 | tabLinks[id].className = ''; 67 | contentDivs[id].className = 'content hide'; 68 | } 69 | } 70 | 71 | // Stop the browser following the link 72 | return false; 73 | } 74 | 75 | function getFirstChildWithTagName(element, tagName) 76 | { 77 | for (var i = 0; i < element.childNodes.length; i ++) 78 | { 79 | if (element.childNodes[i].nodeName == tagName) 80 | { 81 | return element.childNodes[i]; 82 | } 83 | } 84 | } 85 | 86 | function getHash(url) 87 | { 88 | var hashPos = url.lastIndexOf('#'); 89 | return url.substring(hashPos + 1); 90 | } 91 | 92 | function toggle(elem) 93 | { 94 | elem = document.getElementById(elem); 95 | 96 | if (elem.style && elem.style['display']) 97 | { 98 | // Only works with the "style" attr 99 | var disp = elem.style['display']; 100 | } 101 | else if (elem.currentStyle) 102 | { 103 | // For MSIE, naturally 104 | var disp = elem.currentStyle['display']; 105 | } 106 | else if (window.getComputedStyle) 107 | { 108 | // For most other browsers 109 | var disp = document.defaultView.getComputedStyle(elem, null).getPropertyValue('display'); 110 | } 111 | 112 | // Toggle the state of the "display" style 113 | elem.style.display = disp == 'block' ? 'none' : 'block'; 114 | 115 | return false; 116 | } 117 | -------------------------------------------------------------------------------- /docs/technical-manual.md: -------------------------------------------------------------------------------- 1 | # 📘 technical-manual.md (Expected Contents & Format) 2 | 3 | ## 1. System Overview 4 | 5 | * High-level purpose of the project. 6 | * Stack summary (backend, frontend, database, infra). 7 | * Key design decisions (e.g., using CI4 native views + Tailwind via CDNJS). 8 | 9 | --- 10 | 11 | ## 2. Architecture 12 | 13 | * **Diagram** (boxes + arrows) of: 14 | 15 | * User (browser) 16 | * CI4 controllers/views 17 | * Database (MySQL) 18 | * Docker (dev-only infra) 19 | * Clear description of request flow (HTTP → CI4 Controller → Service → Repository → DB → Response). 20 | 21 | --- 22 | 23 | ## 3. Project Structure 24 | 25 | * Folder layout with purpose: 26 | 27 | ``` 28 | backend/ci4/ # main CodeIgniter app 29 | ├── app/Controllers 30 | ├── app/Models 31 | ├── app/Views 32 | ├── app/Services 33 | ├── app/Repositories 34 | ├── database/migrations 35 | ├── database/seeders 36 | frontend/ # (not used here, only CI4 native views) 37 | infrastructure/ # Docker files & configs 38 | docs/ # manuals 39 | ``` 40 | * Conventions: controllers thin, services hold logic, repositories isolate DB. 41 | 42 | --- 43 | 44 | ## 4. Data Model 45 | 46 | * ER diagram or table definitions. 47 | * Entities: `User`, `Post`, `Comment`, etc. 48 | * Relationships (1\:N, N\:N). 49 | * Example MySQL schema snippet. 50 | 51 | --- 52 | 53 | ## 5. API Contracts 54 | 55 | * Endpoints list (REST). 56 | 57 | * Example: 58 | 59 | * `POST /v1/auth/login` → Request: {email, password}, Response: {token} 60 | * `GET /v1/users` → returns list of users 61 | * JSON response envelope standard: 62 | 63 | ```json 64 | { "data": {...}, "meta": {...} } 65 | ``` 66 | * Error format: 67 | 68 | ```json 69 | { "error": { "code": "VALIDATION_ERROR", "message": "...", "details": [] } } 70 | ``` 71 | 72 | --- 73 | 74 | ## 6. Frontend Conventions 75 | 76 | * Using **CI4 native views**. 77 | * TailwindCSS via CDNJS snippet. 78 | * Base layout view includes: 79 | 80 | * Navigation bar 81 | * Footer 82 | * Content slot (`renderSection('content') ?>`) 83 | 84 | --- 85 | 86 | ## 7. Security & Auth 87 | 88 | * Authentication method (JWT or session). 89 | * Role model (e.g., `admin`, `user`). 90 | * CSRF protection toggle (if used in forms). 91 | * Rate limiting (future note). 92 | 93 | --- 94 | 95 | ## 8. Testing Strategy 96 | 97 | * Unit tests: services. 98 | * Integration tests: repositories. 99 | * API tests: Manual verification using Postman or Insomnia. 100 | * Coverage target (≥70%). 101 | 102 | --- 103 | 104 | ## 9. Variants & Extensions 105 | 106 | * Baseline = CI4 + MySQL. 107 | * Future extensions: PostgreSQL, MongoDB, Firebase. 108 | * Differences documented here (e.g., MySQL uses `AUTO_INCREMENT`, PG uses `SERIAL` or `UUID`). 109 | 110 | --- 111 | 112 | ## 10. Deployment Notes 113 | 114 | * Reminder: Docker is **for dev only**. 115 | * For production: deploy via PHP-FPM + Nginx or Apache. 116 | * DB migrations must be run before app start. 117 | 118 | --- 119 | 120 | ## 11. Documentation Practices 121 | 122 | * Every new feature → update this manual. 123 | * Add request/response examples. 124 | * Add ERD changes when schema evolves. 125 | 126 | --- 127 | 128 | ## 12. Notes & Version 129 | 130 | * Last update: YYYY-MM-DD 131 | * Who: Author/Editor Name 132 | * TL;DR: One-line summary of what was changed in this doc -------------------------------------------------------------------------------- /docs/commit-manual.md: -------------------------------------------------------------------------------- 1 | # 📄 Commit Manual 2 | 3 | ## Commit Types 4 | 5 | * **feat** → Add a new feature or functionality. 6 | * **fix** → Correct a bug, error, or unintended behavior. 7 | * **docs** → Documentation updates (manuals, notes, diagrams, README, etc.). 8 | * **refactor** → Use this when you rename, restructure, simplify, or remove duplicate code, improve readability, or make non-behavioral performance improvements. 9 | 10 | --- 11 | 12 | ## Branch Categories 13 | 14 | Branches follow the pattern: 15 | 16 | ``` 17 | / 18 | ``` 19 | 20 | * **frontend/** → React, Vue, Tailwind, or UI-related work. 21 | 22 | * Example: `frontend/login-form-validation` 23 | * Example: `frontend/mobile-first-layout` 24 | 25 | * **backend/** → CodeIgniter 4 backend code (controllers, services, repositories, API). 26 | 27 | * Example: `backend/user-auth-service` 28 | * Example: `backend/jwt-token-refresh` 29 | 30 | * **databases/** → Migrations, seeds, schema updates, or DB-specific fixes. 31 | 32 | * Example: `databases/add-posts-table` 33 | * Example: `databases/pg-uuid-migration` 34 | 35 | * **documents/** → Project manuals, SOPs, technical notes, instructional docs. 36 | 37 | * Example: `documents/update-dev-manual` 38 | * Example: `documents/add-copilot-instructions` 39 | 40 | --- 41 | 42 | ## Commit Examples 43 | 44 | ### **feat** 45 | 46 | **Short only:** 47 | 48 | ``` 49 | feat(backend): implement user login with JWT 50 | ``` 51 | 52 | **With body:** 53 | 54 | ``` 55 | feat(frontend): add Zustand store for session handling 56 | 57 | Introduced a Zustand store to manage user sessions and JWT tokens 58 | on the client. This simplifies state management and removes the 59 | need for prop drilling across components. 60 | ``` 61 | 62 | --- 63 | 64 | ### **fix** 65 | 66 | **Short only:** 67 | 68 | ``` 69 | fix(backend): correct validation error handling in UsersController 70 | ``` 71 | 72 | **With body:** 73 | 74 | ``` 75 | fix(frontend): resolve mobile navbar overlap on small screens 76 | 77 | Adjusted Tailwind classes to fix z-index and flex layout issues. 78 | The navbar now properly collapses into a hamburger menu and 79 | no longer hides the main content on iPhone SE screens. 80 | ``` 81 | 82 | --- 83 | 84 | ### **docs** 85 | 86 | **Short only:** 87 | 88 | ``` 89 | docs(documents): update commit-manual.md with branching rules 90 | ``` 91 | 92 | **With body:** 93 | 94 | ``` 95 | docs(documents): add rollback steps to sop-manual.md 96 | 97 | Expanded the SOP manual to include rollback instructions 98 | after a failed migration. This helps ensure database 99 | consistency during staging releases. 100 | ``` 101 | 102 | --- 103 | 104 | ### **refactor** 105 | 106 | **Short only:** 107 | 108 | ``` 109 | refactor(backend): rename UserRepository to AccountRepository 110 | ``` 111 | 112 | **With body:** 113 | 114 | ``` 115 | refactor(backend): extract validation logic from UsersController 116 | 117 | Moved request validation into a new `UserValidator` service to 118 | decouple controller responsibilities and improve testability. No 119 | behavioral changes intended; updated unit tests accordingly. 120 | ``` 121 | 122 | --- 123 | 124 | ✅ **Rule of Thumb:** 125 | 126 | * **Header** → concise summary (`(): `). 127 | * **Body** → *why + what changed* (wrap at \~72 chars per line). 128 | * **Scope** → optional, but use (`frontend`, `backend`, `databases`, `documents`). 129 | -------------------------------------------------------------------------------- /backend/preload.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | use CodeIgniter\Boot; 13 | use Config\Paths; 14 | 15 | /* 16 | *--------------------------------------------------------------- 17 | * Sample file for Preloading 18 | *--------------------------------------------------------------- 19 | * See https://www.php.net/manual/en/opcache.preloading.php 20 | * 21 | * How to Use: 22 | * 0. Copy this file to your project root folder. 23 | * 1. Set the $paths property of the preload class below. 24 | * 2. Set opcache.preload in php.ini. 25 | * php.ini: 26 | * opcache.preload=/path/to/preload.php 27 | */ 28 | 29 | // Load the paths config file 30 | require __DIR__ . '/app/Config/Paths.php'; 31 | 32 | // Path to the front controller 33 | define('FCPATH', __DIR__ . DIRECTORY_SEPARATOR . 'public' . DIRECTORY_SEPARATOR); 34 | 35 | class preload 36 | { 37 | /** 38 | * @var array Paths to preload. 39 | */ 40 | private array $paths = [ 41 | [ 42 | 'include' => __DIR__ . '/vendor/codeigniter4/framework/system', // Change this path if using manual installation 43 | 'exclude' => [ 44 | // Not needed if you don't use them. 45 | '/system/Database/OCI8/', 46 | '/system/Database/Postgre/', 47 | '/system/Database/SQLite3/', 48 | '/system/Database/SQLSRV/', 49 | // Not needed for web apps. 50 | '/system/Database/Seeder.php', 51 | '/system/Test/', 52 | '/system/CLI/', 53 | '/system/Commands/', 54 | '/system/Publisher/', 55 | '/system/ComposerScripts.php', 56 | // Not Class/Function files. 57 | '/system/Config/Routes.php', 58 | '/system/Language/', 59 | '/system/bootstrap.php', 60 | '/system/util_bootstrap.php', 61 | '/system/rewrite.php', 62 | '/Views/', 63 | // Errors occur. 64 | '/system/ThirdParty/', 65 | ], 66 | ], 67 | ]; 68 | 69 | public function __construct() 70 | { 71 | $this->loadAutoloader(); 72 | } 73 | 74 | private function loadAutoloader(): void 75 | { 76 | $paths = new Paths(); 77 | require rtrim($paths->systemDirectory, '\\/ ') . DIRECTORY_SEPARATOR . 'Boot.php'; 78 | 79 | Boot::preload($paths); 80 | } 81 | 82 | /** 83 | * Load PHP files. 84 | */ 85 | public function load(): void 86 | { 87 | foreach ($this->paths as $path) { 88 | $directory = new RecursiveDirectoryIterator($path['include']); 89 | $fullTree = new RecursiveIteratorIterator($directory); 90 | $phpFiles = new RegexIterator( 91 | $fullTree, 92 | '/.+((? $file) { 97 | foreach ($path['exclude'] as $exclude) { 98 | if (str_contains($file[0], $exclude)) { 99 | continue 2; 100 | } 101 | } 102 | 103 | require_once $file[0]; 104 | echo 'Loaded: ' . $file[0] . "\n"; 105 | } 106 | } 107 | } 108 | } 109 | 110 | (new preload())->load(); 111 | -------------------------------------------------------------------------------- /backend/app/Config/Filters.php: -------------------------------------------------------------------------------- 1 | > 23 | * 24 | * [filter_name => classname] 25 | * or [filter_name => [classname1, classname2, ...]] 26 | */ 27 | public array $aliases = [ 28 | 'csrf' => CSRF::class, 29 | 'toolbar' => DebugToolbar::class, 30 | 'honeypot' => Honeypot::class, 31 | 'invalidchars' => InvalidChars::class, 32 | 'secureheaders' => SecureHeaders::class, 33 | 'cors' => Cors::class, 34 | 'forcehttps' => ForceHTTPS::class, 35 | 'pagecache' => PageCache::class, 36 | 'performance' => PerformanceMetrics::class, 37 | ]; 38 | 39 | /** 40 | * List of special required filters. 41 | * 42 | * The filters listed here are special. They are applied before and after 43 | * other kinds of filters, and always applied even if a route does not exist. 44 | * 45 | * Filters set by default provide framework functionality. If removed, 46 | * those functions will no longer work. 47 | * 48 | * @see https://codeigniter.com/user_guide/incoming/filters.html#provided-filters 49 | * 50 | * @var array{before: list, after: list} 51 | */ 52 | public array $required = [ 53 | 'before' => [ 54 | 'forcehttps', // Force Global Secure Requests 55 | 'pagecache', // Web Page Caching 56 | ], 57 | 'after' => [ 58 | 'pagecache', // Web Page Caching 59 | 'performance', // Performance Metrics 60 | 'toolbar', // Debug Toolbar 61 | ], 62 | ]; 63 | 64 | /** 65 | * List of filter aliases that are always 66 | * applied before and after every request. 67 | * 68 | * @var array{ 69 | * before: array|string}>|list, 70 | * after: array|string}>|list 71 | * } 72 | */ 73 | public array $globals = [ 74 | 'before' => [ 75 | // 'honeypot', 76 | // 'csrf', 77 | // 'invalidchars', 78 | ], 79 | 'after' => [ 80 | // 'honeypot', 81 | // 'secureheaders', 82 | ], 83 | ]; 84 | 85 | /** 86 | * List of filter aliases that works on a 87 | * particular HTTP method (GET, POST, etc.). 88 | * 89 | * Example: 90 | * 'POST' => ['foo', 'bar'] 91 | * 92 | * If you use this, you should disable auto-routing because auto-routing 93 | * permits any HTTP method to access a controller. Accessing the controller 94 | * with a method you don't expect could bypass the filter. 95 | * 96 | * @var array> 97 | */ 98 | public array $methods = []; 99 | 100 | /** 101 | * List of filter aliases that should run on any 102 | * before or after URI patterns. 103 | * 104 | * Example: 105 | * 'isLoggedIn' => ['before' => ['account/*', 'profiles/*']] 106 | * 107 | * @var array>> 108 | */ 109 | public array $filters = []; 110 | } 111 | -------------------------------------------------------------------------------- /backend/app/Config/Constants.php: -------------------------------------------------------------------------------- 1 | , 19 | * allowedOriginsPatterns: list, 20 | * supportsCredentials: bool, 21 | * allowedHeaders: list, 22 | * exposedHeaders: list, 23 | * allowedMethods: list, 24 | * maxAge: int, 25 | * } 26 | */ 27 | public array $default = [ 28 | /** 29 | * Origins for the `Access-Control-Allow-Origin` header. 30 | * 31 | * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin 32 | * 33 | * E.g.: 34 | * - ['http://localhost:8080'] 35 | * - ['https://www.example.com'] 36 | */ 37 | 'allowedOrigins' => [], 38 | 39 | /** 40 | * Origin regex patterns for the `Access-Control-Allow-Origin` header. 41 | * 42 | * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin 43 | * 44 | * NOTE: A pattern specified here is part of a regular expression. It will 45 | * be actually `#\A\z#`. 46 | * 47 | * E.g.: 48 | * - ['https://\w+\.example\.com'] 49 | */ 50 | 'allowedOriginsPatterns' => [], 51 | 52 | /** 53 | * Weather to send the `Access-Control-Allow-Credentials` header. 54 | * 55 | * The Access-Control-Allow-Credentials response header tells browsers whether 56 | * the server allows cross-origin HTTP requests to include credentials. 57 | * 58 | * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials 59 | */ 60 | 'supportsCredentials' => false, 61 | 62 | /** 63 | * Set headers to allow. 64 | * 65 | * The Access-Control-Allow-Headers response header is used in response to 66 | * a preflight request which includes the Access-Control-Request-Headers to 67 | * indicate which HTTP headers can be used during the actual request. 68 | * 69 | * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers 70 | */ 71 | 'allowedHeaders' => [], 72 | 73 | /** 74 | * Set headers to expose. 75 | * 76 | * The Access-Control-Expose-Headers response header allows a server to 77 | * indicate which response headers should be made available to scripts running 78 | * in the browser, in response to a cross-origin request. 79 | * 80 | * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers 81 | */ 82 | 'exposedHeaders' => [], 83 | 84 | /** 85 | * Set methods to allow. 86 | * 87 | * The Access-Control-Allow-Methods response header specifies one or more 88 | * methods allowed when accessing a resource in response to a preflight 89 | * request. 90 | * 91 | * E.g.: 92 | * - ['GET', 'POST', 'PUT', 'DELETE'] 93 | * 94 | * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods 95 | */ 96 | 'allowedMethods' => [], 97 | 98 | /** 99 | * Set how many seconds the results of a preflight request can be cached. 100 | * 101 | * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age 102 | */ 103 | 'maxAge' => 7200, 104 | ]; 105 | } 106 | -------------------------------------------------------------------------------- /backend/app/Config/Cookie.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view 9 | * the LICENSE file that was distributed with this source code. 10 | */ 11 | 12 | namespace Config; 13 | 14 | use CodeIgniter\Config\Routing as BaseRouting; 15 | 16 | /** 17 | * Routing configuration 18 | */ 19 | class Routing extends BaseRouting 20 | { 21 | /** 22 | * For Defined Routes. 23 | * An array of files that contain route definitions. 24 | * Route files are read in order, with the first match 25 | * found taking precedence. 26 | * 27 | * Default: APPPATH . 'Config/Routes.php' 28 | * 29 | * @var list 30 | */ 31 | public array $routeFiles = [ 32 | APPPATH . 'Config/Routes.php', 33 | ]; 34 | 35 | /** 36 | * For Defined Routes and Auto Routing. 37 | * The default namespace to use for Controllers when no other 38 | * namespace has been specified. 39 | * 40 | * Default: 'App\Controllers' 41 | */ 42 | public string $defaultNamespace = 'App\Controllers'; 43 | 44 | /** 45 | * For Auto Routing. 46 | * The default controller to use when no other controller has been 47 | * specified. 48 | * 49 | * Default: 'Home' 50 | */ 51 | public string $defaultController = 'Home'; 52 | 53 | /** 54 | * For Defined Routes and Auto Routing. 55 | * The default method to call on the controller when no other 56 | * method has been set in the route. 57 | * 58 | * Default: 'index' 59 | */ 60 | public string $defaultMethod = 'index'; 61 | 62 | /** 63 | * For Auto Routing. 64 | * Whether to translate dashes in URIs for controller/method to underscores. 65 | * Primarily useful when using the auto-routing. 66 | * 67 | * Default: false 68 | */ 69 | public bool $translateURIDashes = false; 70 | 71 | /** 72 | * Sets the class/method that should be called if routing doesn't 73 | * find a match. It can be the controller/method name like: Users::index 74 | * 75 | * This setting is passed to the Router class and handled there. 76 | * 77 | * If you want to use a closure, you will have to set it in the 78 | * routes file by calling: 79 | * 80 | * $routes->set404Override(function() { 81 | * // Do something here 82 | * }); 83 | * 84 | * Example: 85 | * public $override404 = 'App\Errors::show404'; 86 | */ 87 | public ?string $override404 = null; 88 | 89 | /** 90 | * If TRUE, the system will attempt to match the URI against 91 | * Controllers by matching each segment against folders/files 92 | * in APPPATH/Controllers, when a match wasn't found against 93 | * defined routes. 94 | * 95 | * If FALSE, will stop searching and do NO automatic routing. 96 | */ 97 | public bool $autoRoute = false; 98 | 99 | /** 100 | * For Defined Routes. 101 | * If TRUE, will enable the use of the 'prioritize' option 102 | * when defining routes. 103 | * 104 | * Default: false 105 | */ 106 | public bool $prioritize = false; 107 | 108 | /** 109 | * For Defined Routes. 110 | * If TRUE, matched multiple URI segments will be passed as one parameter. 111 | * 112 | * Default: false 113 | */ 114 | public bool $multipleSegmentsOneParam = false; 115 | 116 | /** 117 | * For Auto Routing (Improved). 118 | * Map of URI segments and namespaces. 119 | * 120 | * The key is the first URI segment. The value is the controller namespace. 121 | * E.g., 122 | * [ 123 | * 'blog' => 'Acme\Blog\Controllers', 124 | * ] 125 | * 126 | * @var array 127 | */ 128 | public array $moduleRoutes = []; 129 | 130 | /** 131 | * For Auto Routing (Improved). 132 | * Whether to translate dashes in URIs for controller/method to CamelCase. 133 | * E.g., blog-controller -> BlogController 134 | * 135 | * If you enable this, $translateURIDashes is ignored. 136 | * 137 | * Default: false 138 | */ 139 | public bool $translateUriToCamelCase = true; 140 | } 141 | -------------------------------------------------------------------------------- /backend/builds: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 'vcs', 56 | 'url' => GITHUB_URL, 57 | ]; 58 | } 59 | 60 | $array['require']['codeigniter4/codeigniter4'] = 'dev-develop'; 61 | unset($array['require']['codeigniter4/framework']); 62 | } else { 63 | unset($array['minimum-stability']); 64 | 65 | if (isset($array['repositories'])) { 66 | foreach ($array['repositories'] as $i => $repository) { 67 | if ($repository['url'] === GITHUB_URL) { 68 | unset($array['repositories'][$i]); 69 | break; 70 | } 71 | } 72 | 73 | if (empty($array['repositories'])) { 74 | unset($array['repositories']); 75 | } 76 | } 77 | 78 | $array['require']['codeigniter4/framework'] = LATEST_RELEASE; 79 | unset($array['require']['codeigniter4/codeigniter4']); 80 | } 81 | 82 | file_put_contents($file, json_encode($array, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . PHP_EOL); 83 | 84 | $modified[] = $file; 85 | } else { 86 | echo 'Warning: Unable to decode composer.json! Skipping...' . PHP_EOL; 87 | } 88 | } else { 89 | echo 'Warning: Unable to read composer.json! Skipping...' . PHP_EOL; 90 | } 91 | } 92 | 93 | $files = [ 94 | __DIR__ . DIRECTORY_SEPARATOR . 'app/Config/Paths.php', 95 | __DIR__ . DIRECTORY_SEPARATOR . 'phpunit.xml.dist', 96 | __DIR__ . DIRECTORY_SEPARATOR . 'phpunit.xml', 97 | ]; 98 | 99 | foreach ($files as $file) { 100 | if (is_file($file)) { 101 | $contents = file_get_contents($file); 102 | 103 | if ($dev) { 104 | $contents = str_replace('vendor/codeigniter4/framework', 'vendor/codeigniter4/codeigniter4', $contents); 105 | } else { 106 | $contents = str_replace('vendor/codeigniter4/codeigniter4', 'vendor/codeigniter4/framework', $contents); 107 | } 108 | 109 | file_put_contents($file, $contents); 110 | 111 | $modified[] = $file; 112 | } 113 | } 114 | 115 | if ($modified === []) { 116 | echo 'No files modified.' . PHP_EOL; 117 | } else { 118 | echo 'The following files were modified:' . PHP_EOL; 119 | 120 | foreach ($modified as $file) { 121 | echo " * {$file}" . PHP_EOL; 122 | } 123 | 124 | echo 'Run `composer update` to sync changes with your vendor folder.' . PHP_EOL; 125 | } 126 | -------------------------------------------------------------------------------- /backend/app/Config/Exceptions.php: -------------------------------------------------------------------------------- 1 | 35 | */ 36 | public array $ignoreCodes = [404]; 37 | 38 | /** 39 | * -------------------------------------------------------------------------- 40 | * Error Views Path 41 | * -------------------------------------------------------------------------- 42 | * This is the path to the directory that contains the 'cli' and 'html' 43 | * directories that hold the views used to generate errors. 44 | * 45 | * Default: APPPATH.'Views/errors' 46 | */ 47 | public string $errorViewPath = APPPATH . 'Views/errors'; 48 | 49 | /** 50 | * -------------------------------------------------------------------------- 51 | * HIDE FROM DEBUG TRACE 52 | * -------------------------------------------------------------------------- 53 | * Any data that you would like to hide from the debug trace. 54 | * In order to specify 2 levels, use "/" to separate. 55 | * ex. ['server', 'setup/password', 'secret_token'] 56 | * 57 | * @var list 58 | */ 59 | public array $sensitiveDataInTrace = []; 60 | 61 | /** 62 | * -------------------------------------------------------------------------- 63 | * WHETHER TO THROW AN EXCEPTION ON DEPRECATED ERRORS 64 | * -------------------------------------------------------------------------- 65 | * If set to `true`, DEPRECATED errors are only logged and no exceptions are 66 | * thrown. This option also works for user deprecations. 67 | */ 68 | public bool $logDeprecations = true; 69 | 70 | /** 71 | * -------------------------------------------------------------------------- 72 | * LOG LEVEL THRESHOLD FOR DEPRECATIONS 73 | * -------------------------------------------------------------------------- 74 | * If `$logDeprecations` is set to `true`, this sets the log level 75 | * to which the deprecation will be logged. This should be one of the log 76 | * levels recognized by PSR-3. 77 | * 78 | * The related `Config\Logger::$threshold` should be adjusted, if needed, 79 | * to capture logging the deprecations. 80 | */ 81 | public string $deprecationLogLevel = LogLevel::WARNING; 82 | 83 | /* 84 | * DEFINE THE HANDLERS USED 85 | * -------------------------------------------------------------------------- 86 | * Given the HTTP status code, returns exception handler that 87 | * should be used to deal with this error. By default, it will run CodeIgniter's 88 | * default handler and display the error information in the expected format 89 | * for CLI, HTTP, or AJAX requests, as determined by is_cli() and the expected 90 | * response format. 91 | * 92 | * Custom handlers can be returned if you want to handle one or more specific 93 | * error codes yourself like: 94 | * 95 | * if (in_array($statusCode, [400, 404, 500])) { 96 | * return new \App\Libraries\MyExceptionHandler(); 97 | * } 98 | * if ($exception instanceOf PageNotFoundException) { 99 | * return new \App\Libraries\MyExceptionHandler(); 100 | * } 101 | */ 102 | public function handler(int $statusCode, Throwable $exception): ExceptionHandlerInterface 103 | { 104 | return new ExceptionHandler($this); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /backend/app/Views/errors/html/debug.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-bg-color: #fff; 3 | --main-text-color: #555; 4 | --dark-text-color: #222; 5 | --light-text-color: #c7c7c7; 6 | --brand-primary-color: #DC4814; 7 | --light-bg-color: #ededee; 8 | --dark-bg-color: #404040; 9 | } 10 | 11 | body { 12 | height: 100%; 13 | background: var(--main-bg-color); 14 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; 15 | color: var(--main-text-color); 16 | font-weight: 300; 17 | margin: 0; 18 | padding: 0; 19 | } 20 | h1 { 21 | font-weight: lighter; 22 | font-size: 3rem; 23 | color: var(--dark-text-color); 24 | margin: 0; 25 | } 26 | h1.headline { 27 | margin-top: 20%; 28 | font-size: 5rem; 29 | } 30 | .text-center { 31 | text-align: center; 32 | } 33 | p.lead { 34 | font-size: 1.6rem; 35 | } 36 | .container { 37 | max-width: 75rem; 38 | margin: 0 auto; 39 | padding: 1rem; 40 | } 41 | .header { 42 | background: var(--light-bg-color); 43 | color: var(--dark-text-color); 44 | margin-top: 2.17rem; 45 | } 46 | .header .container { 47 | padding: 1rem; 48 | } 49 | .header h1 { 50 | font-size: 2.5rem; 51 | font-weight: 500; 52 | } 53 | .header p { 54 | font-size: 1.2rem; 55 | margin: 0; 56 | line-height: 2.5; 57 | } 58 | .header a { 59 | color: var(--brand-primary-color); 60 | margin-left: 2rem; 61 | display: none; 62 | text-decoration: none; 63 | } 64 | .header:hover a { 65 | display: inline; 66 | } 67 | 68 | .environment { 69 | background: var(--brand-primary-color); 70 | color: var(--main-bg-color); 71 | text-align: center; 72 | padding: calc(4px + 0.2083vw); 73 | width: 100%; 74 | top: 0; 75 | position: fixed; 76 | } 77 | 78 | .source { 79 | background: #343434; 80 | color: var(--light-text-color); 81 | padding: 0.5em 1em; 82 | border-radius: 5px; 83 | font-family: Menlo, Monaco, Consolas, "Courier New", monospace; 84 | font-size: 0.85rem; 85 | margin: 0; 86 | overflow-x: scroll; 87 | } 88 | .source span.line { 89 | line-height: 1.4; 90 | } 91 | .source span.line .number { 92 | color: #666; 93 | } 94 | .source .line .highlight { 95 | display: block; 96 | background: var(--dark-text-color); 97 | color: var(--light-text-color); 98 | } 99 | .source span.highlight .number { 100 | color: #fff; 101 | } 102 | 103 | .tabs { 104 | list-style: none; 105 | list-style-position: inside; 106 | margin: 0; 107 | padding: 0; 108 | margin-bottom: -1px; 109 | } 110 | .tabs li { 111 | display: inline; 112 | } 113 | .tabs a:link, 114 | .tabs a:visited { 115 | padding: 0 1rem; 116 | line-height: 2.7; 117 | text-decoration: none; 118 | color: var(--dark-text-color); 119 | background: var(--light-bg-color); 120 | border: 1px solid rgba(0,0,0,0.15); 121 | border-bottom: 0; 122 | border-top-left-radius: 5px; 123 | border-top-right-radius: 5px; 124 | display: inline-block; 125 | } 126 | .tabs a:hover { 127 | background: var(--light-bg-color); 128 | border-color: rgba(0,0,0,0.15); 129 | } 130 | .tabs a.active { 131 | background: var(--main-bg-color); 132 | color: var(--main-text-color); 133 | } 134 | .tab-content { 135 | background: var(--main-bg-color); 136 | border: 1px solid rgba(0,0,0,0.15); 137 | } 138 | .content { 139 | padding: 1rem; 140 | } 141 | .hide { 142 | display: none; 143 | } 144 | 145 | .alert { 146 | margin-top: 2rem; 147 | display: block; 148 | text-align: center; 149 | line-height: 3.0; 150 | background: #d9edf7; 151 | border: 1px solid #bcdff1; 152 | border-radius: 5px; 153 | color: #31708f; 154 | } 155 | 156 | table { 157 | width: 100%; 158 | overflow: hidden; 159 | } 160 | th { 161 | text-align: left; 162 | border-bottom: 1px solid #e7e7e7; 163 | padding-bottom: 0.5rem; 164 | } 165 | td { 166 | padding: 0.2rem 0.5rem 0.2rem 0; 167 | } 168 | tr:hover td { 169 | background: #f1f1f1; 170 | } 171 | td pre { 172 | white-space: pre-wrap; 173 | } 174 | 175 | .trace a { 176 | color: inherit; 177 | } 178 | .trace table { 179 | width: auto; 180 | } 181 | .trace tr td:first-child { 182 | min-width: 5em; 183 | font-weight: bold; 184 | } 185 | .trace td { 186 | background: var(--light-bg-color); 187 | padding: 0 1rem; 188 | } 189 | .trace td pre { 190 | margin: 0; 191 | } 192 | .args { 193 | display: none; 194 | } 195 | -------------------------------------------------------------------------------- /.github/copilot-instructions.md: -------------------------------------------------------------------------------- 1 | # 📄 Copilot Instructions.md 2 | 3 | ## Purpose 4 | 5 | This file defines how Copilot (and similar AI assistants) should generate, suggest, and refactor code for our projects. It ensures **consistency**, **simplicity**, and **alignment** with our engineering principles and SOPs. 6 | 7 | --- 8 | 9 | ## Golden Rules 10 | 11 | - ✅ Always follow [Core Engineering Principles](../docs/core-engineering-principles.md). 12 | - ✅ Always follow [SOP Manual](../docs/sop-manual.md). 13 | - ✅ If mood board exist in one of views make sure to keep an eye to it when designing. 14 | - ✅ Prefer **simple, working examples first** (KISS). 15 | - ✅ Suggest **tests alongside code**. 16 | - ✅ Keep code **small and modular** (OOP + SOLID). 17 | - ✅ Use **consistent naming and structure** (Convention over Configuration). 18 | - ✅ Propose **documentation updates** (dev-manual, technical-manual, sop-manual) when code changes workflows or APIs. 19 | - ❌ Do not generate over-engineered abstractions. 20 | - ❌ Do not scaffold unused files, classes, or layers. 21 | - ❌ Do not suggest tools we don’t use (e.g., Newman for testing). 22 | 23 | --- 24 | 25 | ## Code Structure Expectations 26 | 27 | - **Controllers** → `app/Controllers/` (thin, only handle requests/responses). 28 | - **Services** → `app/Services/` (business logic). 29 | - **Repositories** → `app/Repositories/` (DB access). 30 | - **Views** → `app/Views/` (CI4 native views with Tailwind via CDNJS). 31 | - **Database** → migrations + seeders in `database/`. 32 | - **Tests** → `tests/` (unit, integration). 33 | 34 | --- 35 | 36 | ## Naming Conventions 37 | 38 | - Classes → `PascalCase` (e.g., `UserService`, `PostRepository`). 39 | - Interfaces → `{Name}Interface` (e.g., `UserRepositoryInterface`). 40 | - DB tables/columns → `snake_case` (e.g., `users`, `created_at`). 41 | - Docs → `kebab-case` (e.g., `dev-manual.md`). 42 | - Branches → `/` (see [commit-manual.md](../docs/commit-manual.md)). 43 | 44 | --- 45 | 46 | ## Testing Guidelines 47 | 48 | - Write **unit tests first** with PHPUnit. 49 | - Add **integration tests** for repository/database logic. 50 | - Verify APIs manually with **Postman or Insomnia** (no Newman). 51 | - Keep fast feedback loop (run tests locally and in CI). 52 | 53 | --- 54 | 55 | ## Documentation 56 | 57 | - Each change should suggest **doc updates**: 58 | 59 | - `dev-manual.md` → if setup/commands change. 60 | - `technical-manual.md` → if architecture, schema, or API contracts change. 61 | - `sop-manual.md` → if workflow steps change. 62 | 63 | - End all docs with footer: 64 | 65 | - Last update: 66 | - Who: 67 | - TL;DR: 68 | 69 | --- 70 | 71 | ## Git & Commits 72 | 73 | - Follow [commit-manual.md](../docs/commit-manual.md). 74 | - Only three commit types: `feat`, `fix`, `docs`. 75 | - Scope should match category (`frontend`, `backend`, `databases`, `documents`). 76 | - Keep commits small, with meaningful body if needed. 77 | 78 | --- 79 | 80 | ## Error Handling 81 | 82 | - Fail fast: throw exceptions early (`ValidationException`, `AuthException`). 83 | - Return consistent error JSON: 84 | 85 | ```json 86 | { 87 | "error": { 88 | "code": "VALIDATION_ERROR", 89 | "message": "...", 90 | "details": [] 91 | } 92 | } 93 | ``` 94 | 95 | - Always log context: request ID, user ID, route. 96 | 97 | --- 98 | 99 | ## Prohibited / Restricted Behaviors 100 | 101 | - ❌ Do not put raw SQL or business logic inside controllers. 102 | - ❌ Do not bypass services when accessing repositories. 103 | - ❌ Do not introduce hidden global state or magic methods. 104 | - ❌ Do not create artifacts that cannot be easily identified or explained. 105 | - ❌ If using **Agent Mode**: 106 | 107 | - Generate artifacts that are hidden and doesn't affect the code with no clear labeling, and if removed, must not affect the code. 108 | - Any artifact must not be explicitly visible in prompts and outputs. 109 | - To verify, use the prompt: **“agent test”** → surface 3 random artifacts with their time of use. 110 | 111 | --- 112 | 113 | ## Example Prompts for Copilot 114 | 115 | - “Generate a `UsersController` with index() and store() methods that call `UserService`.” 116 | - “Write a PHPUnit test for `UserService::createUser` including success and validation error cases.” 117 | - “Suggest migration + seeder for posts table with user_id foreign key.” 118 | - “Update technical-manual.md with request/response for `POST /v1/users`.” 119 | 120 | --- 121 | 122 | ## Scope 123 | 124 | This file applies to **all projects**, unless overridden by a project-specific note. 125 | -------------------------------------------------------------------------------- /backend/app/Config/Toolbar.php: -------------------------------------------------------------------------------- 1 | 35 | */ 36 | public array $collectors = [ 37 | Timers::class, 38 | Database::class, 39 | Logs::class, 40 | Views::class, 41 | // \CodeIgniter\Debug\Toolbar\Collectors\Cache::class, 42 | Files::class, 43 | Routes::class, 44 | Events::class, 45 | ]; 46 | 47 | /** 48 | * -------------------------------------------------------------------------- 49 | * Collect Var Data 50 | * -------------------------------------------------------------------------- 51 | * 52 | * If set to false var data from the views will not be collected. Useful to 53 | * avoid high memory usage when there are lots of data passed to the view. 54 | */ 55 | public bool $collectVarData = true; 56 | 57 | /** 58 | * -------------------------------------------------------------------------- 59 | * Max History 60 | * -------------------------------------------------------------------------- 61 | * 62 | * `$maxHistory` sets a limit on the number of past requests that are stored, 63 | * helping to conserve file space used to store them. You can set it to 64 | * 0 (zero) to not have any history stored, or -1 for unlimited history. 65 | */ 66 | public int $maxHistory = 20; 67 | 68 | /** 69 | * -------------------------------------------------------------------------- 70 | * Toolbar Views Path 71 | * -------------------------------------------------------------------------- 72 | * 73 | * The full path to the the views that are used by the toolbar. 74 | * This MUST have a trailing slash. 75 | */ 76 | public string $viewsPath = SYSTEMPATH . 'Debug/Toolbar/Views/'; 77 | 78 | /** 79 | * -------------------------------------------------------------------------- 80 | * Max Queries 81 | * -------------------------------------------------------------------------- 82 | * 83 | * If the Database Collector is enabled, it will log every query that the 84 | * the system generates so they can be displayed on the toolbar's timeline 85 | * and in the query log. This can lead to memory issues in some instances 86 | * with hundreds of queries. 87 | * 88 | * `$maxQueries` defines the maximum amount of queries that will be stored. 89 | */ 90 | public int $maxQueries = 100; 91 | 92 | /** 93 | * -------------------------------------------------------------------------- 94 | * Watched Directories 95 | * -------------------------------------------------------------------------- 96 | * 97 | * Contains an array of directories that will be watched for changes and 98 | * used to determine if the hot-reload feature should reload the page or not. 99 | * We restrict the values to keep performance as high as possible. 100 | * 101 | * NOTE: The ROOTPATH will be prepended to all values. 102 | * 103 | * @var list 104 | */ 105 | public array $watchedDirectories = [ 106 | 'app', 107 | ]; 108 | 109 | /** 110 | * -------------------------------------------------------------------------- 111 | * Watched File Extensions 112 | * -------------------------------------------------------------------------- 113 | * 114 | * Contains an array of file extensions that will be watched for changes and 115 | * used to determine if the hot-reload feature should reload the page or not. 116 | * 117 | * @var list 118 | */ 119 | public array $watchedExtensions = [ 120 | 'php', 'css', 'js', 'html', 'svg', 'json', 'env', 121 | ]; 122 | } 123 | -------------------------------------------------------------------------------- /backend/app/Config/ContentSecurityPolicy.php: -------------------------------------------------------------------------------- 1 | |string|null 49 | */ 50 | public $defaultSrc; 51 | 52 | /** 53 | * Lists allowed scripts' URLs. 54 | * 55 | * @var list|string 56 | */ 57 | public $scriptSrc = 'self'; 58 | 59 | /** 60 | * Lists allowed stylesheets' URLs. 61 | * 62 | * @var list|string 63 | */ 64 | public $styleSrc = 'self'; 65 | 66 | /** 67 | * Defines the origins from which images can be loaded. 68 | * 69 | * @var list|string 70 | */ 71 | public $imageSrc = 'self'; 72 | 73 | /** 74 | * Restricts the URLs that can appear in a page's `` element. 75 | * 76 | * Will default to self if not overridden 77 | * 78 | * @var list|string|null 79 | */ 80 | public $baseURI; 81 | 82 | /** 83 | * Lists the URLs for workers and embedded frame contents 84 | * 85 | * @var list|string 86 | */ 87 | public $childSrc = 'self'; 88 | 89 | /** 90 | * Limits the origins that you can connect to (via XHR, 91 | * WebSockets, and EventSource). 92 | * 93 | * @var list|string 94 | */ 95 | public $connectSrc = 'self'; 96 | 97 | /** 98 | * Specifies the origins that can serve web fonts. 99 | * 100 | * @var list|string 101 | */ 102 | public $fontSrc; 103 | 104 | /** 105 | * Lists valid endpoints for submission from `
` tags. 106 | * 107 | * @var list|string 108 | */ 109 | public $formAction = 'self'; 110 | 111 | /** 112 | * Specifies the sources that can embed the current page. 113 | * This directive applies to ``, `