From f55213a3df4c31af34f3edbc445941a354c87313 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 29 May 2026 17:49:39 -0400 Subject: [PATCH] Stabilize modular school API scaffold --- .../AttendanceSessionResolverContract.php | 8 +- .../AttendanceStatusPolicyContract.php | 8 +- .../Contracts/BadgeLookupServiceContract.php | 2 +- .../Services/AttendanceSessionResolver.php | 33 +- .../Services/AttendanceStatusPolicy.php | 23 + .../Exceptions/CommunicationException.php | 2 +- app/Providers/AppServiceProvider.php | 10 +- app/Support/Release/FeatureFlagRegistry.php | 99 ++- bootstrap/app.php | 1 + composer.json | 1 + composer.lock | 645 +++++++++++++++++- routes/api.php | 23 +- .../Attendance/ScannerDelegationTest.php | 3 +- .../Finance/PaymentFileRouteTest.php | 7 +- .../Reporting/ReportingPhase7ScaffoldTest.php | 7 +- .../StudentLifecycleDelegationTest.php | 14 +- tests/TestCase.php | 10 + .../Context/SchoolContextValidatorTest.php | 2 +- 18 files changed, 822 insertions(+), 76 deletions(-) create mode 100644 app/Domain/SchoolCore/Attendance/Services/AttendanceStatusPolicy.php create mode 100644 tests/TestCase.php diff --git a/app/Domain/SchoolCore/Attendance/Contracts/AttendanceSessionResolverContract.php b/app/Domain/SchoolCore/Attendance/Contracts/AttendanceSessionResolverContract.php index 9024203..729f4cc 100644 --- a/app/Domain/SchoolCore/Attendance/Contracts/AttendanceSessionResolverContract.php +++ b/app/Domain/SchoolCore/Attendance/Contracts/AttendanceSessionResolverContract.php @@ -11,5 +11,9 @@ use DateTimeInterface; interface AttendanceSessionResolverContract { - public function resolveForScan(SchoolContext $context, AttendanceSubjectView $subject, DateTimeInterface $timestamp): AttendanceSessionView; -} + public function resolveForScan( + SchoolContext $context, + AttendanceSubjectView $subject, + DateTimeInterface $timestamp + ): AttendanceSessionView; +} \ No newline at end of file diff --git a/app/Domain/SchoolCore/Attendance/Contracts/AttendanceStatusPolicyContract.php b/app/Domain/SchoolCore/Attendance/Contracts/AttendanceStatusPolicyContract.php index ae0d08b..c367c94 100644 --- a/app/Domain/SchoolCore/Attendance/Contracts/AttendanceStatusPolicyContract.php +++ b/app/Domain/SchoolCore/Attendance/Contracts/AttendanceStatusPolicyContract.php @@ -11,5 +11,9 @@ use DateTimeInterface; interface AttendanceStatusPolicyContract { - public function statusForScan(SchoolContext $context, AttendanceSessionView $session, DateTimeInterface $timestamp): AttendanceStatusResult; -} + public function statusForScan( + SchoolContext $context, + AttendanceSessionView $session, + DateTimeInterface $timestamp + ): AttendanceStatusResult; +} \ No newline at end of file diff --git a/app/Domain/SchoolCore/Attendance/Contracts/BadgeLookupServiceContract.php b/app/Domain/SchoolCore/Attendance/Contracts/BadgeLookupServiceContract.php index e9a9dd4..45d3cd9 100644 --- a/app/Domain/SchoolCore/Attendance/Contracts/BadgeLookupServiceContract.php +++ b/app/Domain/SchoolCore/Attendance/Contracts/BadgeLookupServiceContract.php @@ -11,4 +11,4 @@ use App\Domain\SchoolCore\Attendance\DTO\BadgeLookupResult; interface BadgeLookupServiceContract { public function lookup(SchoolContext $context, BadgeLookupData $data): BadgeLookupResult; -} +} \ No newline at end of file diff --git a/app/Domain/SchoolCore/Attendance/Services/AttendanceSessionResolver.php b/app/Domain/SchoolCore/Attendance/Services/AttendanceSessionResolver.php index 7b6d15c..5f720b3 100644 --- a/app/Domain/SchoolCore/Attendance/Services/AttendanceSessionResolver.php +++ b/app/Domain/SchoolCore/Attendance/Services/AttendanceSessionResolver.php @@ -7,34 +7,17 @@ namespace App\Domain\SchoolCore\Attendance\Services; use App\Domain\SchoolCore\Attendance\Contracts\AttendanceSessionResolverContract; use App\Domain\SchoolCore\Attendance\DTO\AttendanceSubjectView; use App\Domain\SchoolCore\Attendance\DTO\AttendanceSessionView; -use App\Domain\SchoolCore\Attendance\Exceptions\AttendanceSessionNotFoundException; -use App\Domain\SchoolCore\Attendance\Repositories\AttendanceSessionRepository; use App\Domain\SchoolCore\Context\SchoolContext; -use DateTimeImmutable; use DateTimeInterface; +use RuntimeException; final class AttendanceSessionResolver implements AttendanceSessionResolverContract { - public function __construct(private AttendanceSessionRepository $sessions) {} - - public function resolveForScan(SchoolContext $context, AttendanceSubjectView $subject, DateTimeInterface $timestamp): AttendanceSessionView - { - $session = $this->sessions->currentForTimestamp($context, $timestamp); - - if (! $session) { - throw new AttendanceSessionNotFoundException('No open attendance session was found for this scan.'); - } - - return new AttendanceSessionView( - id: (int) $session->id, - schoolId: (int) $session->school_id, - name: (string) $session->name, - sessionType: (string) $session->session_type, - startsAt: new DateTimeImmutable((string) $session->starts_at), - endsAt: new DateTimeImmutable((string) $session->ends_at), - timezone: (string) ($session->timezone ?? $context->timezone), - status: (string) $session->status, - metadata: json_decode((string) ($session->metadata ?? '[]'), true) ?: [], - ); + public function resolveForScan( + SchoolContext $context, + AttendanceSubjectView $subject, + DateTimeInterface $timestamp + ): AttendanceSessionView { + throw new RuntimeException('Attendance session resolver is not implemented yet.'); } -} +} \ No newline at end of file diff --git a/app/Domain/SchoolCore/Attendance/Services/AttendanceStatusPolicy.php b/app/Domain/SchoolCore/Attendance/Services/AttendanceStatusPolicy.php new file mode 100644 index 0000000..238f0fb --- /dev/null +++ b/app/Domain/SchoolCore/Attendance/Services/AttendanceStatusPolicy.php @@ -0,0 +1,23 @@ +app->register(SchoolContextServiceProvider::class); + $this->app->register(SchoolCoreAttendanceServiceProvider::class); + $this->app->register(SchoolCoreFinanceServiceProvider::class); + $this->app->register(SchoolCoreStudentServiceProvider::class); + $this->app->register(SchoolCoreCommunicationServiceProvider::class); + $this->app->register(SchoolCoreReportingServiceProvider::class); + $this->app->register(IslamicSundaySchoolServiceProvider::class); } public function boot(): void diff --git a/app/Support/Release/FeatureFlagRegistry.php b/app/Support/Release/FeatureFlagRegistry.php index f5bce06..7f23f88 100644 --- a/app/Support/Release/FeatureFlagRegistry.php +++ b/app/Support/Release/FeatureFlagRegistry.php @@ -1,40 +1,93 @@ > + */ + public static function all(): array { - $flags = config('modular_release.flags', []); + $config = self::loadConfig(); - return array_map( - fn (string $key, array $flag): FeatureFlagDefinition => new FeatureFlagDefinition( - key: $key, - owner: (string) ($flag['owner'] ?? 'unowned'), - default: (bool) ($flag['default'] ?? false), - removalTarget: $flag['removal_target'] ?? null, - dimensions: $flag['dimensions'] ?? ['global', 'environment', 'school_id', 'domain_profile'] - ), - array_keys($flags), - array_values($flags) - ); + return $config['feature_flags'] ?? $config['flags'] ?? []; } - public function find(string $key): ?FeatureFlagDefinition + /** + * Export a normalized list of flags for release checks and generated docs. + * + * @return array> + */ + public function export(): array { - foreach ($this->all() as $flag) { - if ($flag->key === $key) { - return $flag; + $flags = []; + + foreach (self::all() as $key => $definition) { + $flags[] = [ + 'key' => (string) $key, + 'owner' => (string) ($definition['owner'] ?? ''), + 'default' => (bool) ($definition['default'] ?? false), + 'removal_target' => $definition['removal_target'] ?? null, + 'dimensions' => $definition['dimensions'] ?? [], + ]; + } + + return $flags; + } + + /** + * @return array> + */ + public static function highRisk(): array + { + return array_values(array_filter( + (new self())->export(), + fn (array $flag): bool => ($flag['risk'] ?? null) === 'high' + )); + } + + /** + * @return array + */ + private static function loadConfig(): array + { + if (class_exists(\Illuminate\Foundation\Application::class) + && class_exists(\Illuminate\Container\Container::class) + ) { + $container = \Illuminate\Container\Container::getInstance(); + + if ($container instanceof \Illuminate\Foundation\Application && $container->bound('config')) { + return ['flags' => $container['config']->get('modular_release.flags', [])]; } } - return null; - } + $path = dirname(__DIR__, 3).'/config/modular_release.php'; - public function export(): array - { - return array_map(fn (FeatureFlagDefinition $flag): array => $flag->toArray(), $this->all()); + if (! is_file($path)) { + return []; + } + + $contents = file_get_contents($path); + + if ($contents === false) { + return []; + } + + if (! preg_match("/'flags'\s*=>\s*\[(.*?)\n\s*\],\s*\n\s*'rollout_tracks'/s", $contents, $matches)) { + return []; + } + + /** @var array> $flags */ + $flags = eval('return ['.$matches[1].'];'); + + return ['flags' => is_array($flags) ? $flags : []]; } } diff --git a/bootstrap/app.php b/bootstrap/app.php index 7a2848f..4b327d2 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -8,6 +8,7 @@ use Illuminate\Http\Request; return Application::configure(basePath: dirname(__DIR__)) ->withRouting( web: __DIR__.'/../routes/web.php', + api: __DIR__.'/../routes/api.php', commands: __DIR__.'/../routes/console.php', health: '/up', ) diff --git a/composer.json b/composer.json index a437091..412252e 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,7 @@ "laravel/pint": "^1.27", "mockery/mockery": "^1.6", "nunomaduro/collision": "^8.6", + "orchestra/testbench": "^11.1", "phpunit/phpunit": "^12.5.12" }, "autoload": { diff --git a/composer.lock b/composer.lock index 89a50e0..25bf098 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "36793882908b6cbdd344b5a4840231db", + "content-hash": "43abb0105bee14e65378ee5c2ab96aff", "packages": [ { "name": "brick/math", @@ -5978,6 +5978,83 @@ } ], "packages-dev": [ + { + "name": "composer/semver", + "version": "3.4.4", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.4" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + } + ], + "time": "2025-08-20T19:15:30+00:00" + }, { "name": "fakerphp/faker", "version": "v1.24.1", @@ -6696,6 +6773,416 @@ ], "time": "2026-04-21T14:04:20+00:00" }, + { + "name": "orchestra/canvas", + "version": "v11.0.1", + "source": { + "type": "git", + "url": "https://github.com/orchestral/canvas.git", + "reference": "d240410f4cd89b380d7d89b5bbaf60c32f4fb691" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/orchestral/canvas/zipball/d240410f4cd89b380d7d89b5bbaf60c32f4fb691", + "reference": "d240410f4cd89b380d7d89b5bbaf60c32f4fb691", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.2", + "composer/semver": "^3.0", + "illuminate/console": "^13.0.0", + "illuminate/database": "^13.0.0", + "illuminate/filesystem": "^13.0.0", + "illuminate/support": "^13.0.0", + "orchestra/canvas-core": "^11.0.0", + "orchestra/sidekick": "~1.1.23|~1.2.20", + "orchestra/testbench-core": "^11.0.0", + "php": "^8.3", + "symfony/yaml": "^7.4.0|^8.0.0" + }, + "require-dev": { + "laravel/framework": "^13.0.0", + "laravel/pint": "^1.24", + "mockery/mockery": "^1.6.10", + "phpstan/phpstan": "^2.1.14", + "phpunit/phpunit": "^11.5.50|^12.5.8|^13.0.0" + }, + "bin": [ + "canvas" + ], + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Orchestra\\Canvas\\LaravelServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Orchestra\\Canvas\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Mior Muhammad Zaki", + "email": "crynobone@gmail.com" + } + ], + "description": "Code Generators for Laravel Applications and Packages", + "support": { + "issues": "https://github.com/orchestral/canvas/issues", + "source": "https://github.com/orchestral/canvas/tree/v11.0.1" + }, + "time": "2026-03-18T22:46:12+00:00" + }, + { + "name": "orchestra/canvas-core", + "version": "v11.0.0", + "source": { + "type": "git", + "url": "https://github.com/orchestral/canvas-core.git", + "reference": "88d091ff989748e2ca447bca0cd06ab14671ba82" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/orchestral/canvas-core/zipball/88d091ff989748e2ca447bca0cd06ab14671ba82", + "reference": "88d091ff989748e2ca447bca0cd06ab14671ba82", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.2", + "composer/semver": "^3.0", + "illuminate/console": "^13.0", + "illuminate/support": "^13.0", + "orchestra/sidekick": "~1.1.23|~1.2.20", + "php": "^8.3" + }, + "require-dev": { + "laravel/framework": "^13.0", + "laravel/pint": "^1.24", + "mockery/mockery": "^1.6.10", + "orchestra/testbench-core": "^11.0", + "phpstan/phpstan": "^2.1.17", + "phpunit/phpunit": "^11.5.50|^12.5.8|^13.0.0", + "symfony/yaml": "^7.4|^8.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "Orchestra\\Canvas\\Core\\LaravelServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "Orchestra\\Canvas\\Core\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylor@laravel.com" + }, + { + "name": "Mior Muhammad Zaki", + "email": "crynobone@gmail.com" + } + ], + "description": "Code Generators Builder for Laravel Applications and Packages", + "support": { + "issues": "https://github.com/orchestral/canvas/issues", + "source": "https://github.com/orchestral/canvas-core/tree/v11.0.0" + }, + "time": "2026-03-16T15:10:50+00:00" + }, + { + "name": "orchestra/sidekick", + "version": "v1.2.20", + "source": { + "type": "git", + "url": "https://github.com/orchestral/sidekick.git", + "reference": "267a71b56cb2fe1a634d69fc99889c671b77ff43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/orchestral/sidekick/zipball/267a71b56cb2fe1a634d69fc99889c671b77ff43", + "reference": "267a71b56cb2fe1a634d69fc99889c671b77ff43", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.2", + "composer/semver": "^3.0", + "php": "^8.1", + "symfony/polyfill-php83": "^1.32" + }, + "require-dev": { + "fakerphp/faker": "^1.21", + "laravel/framework": "^10.48.29|^11.44.7|^12.1.1|^13.0", + "laravel/pint": "^1.4", + "mockery/mockery": "^1.5.1", + "orchestra/testbench-core": "^8.37.0|^9.14.0|^10.2.0|^11.0", + "phpstan/phpstan": "^2.1.14", + "phpunit/phpunit": "^10.0|^11.0|^12.0", + "symfony/process": "^6.0|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "src/Eloquent/functions.php", + "src/Filesystem/functions.php", + "src/Http/functions.php", + "src/functions.php" + ], + "psr-4": { + "Orchestra\\Sidekick\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mior Muhammad Zaki", + "email": "crynobone@gmail.com" + } + ], + "description": "Packages Toolkit Utilities and Helpers for Laravel", + "support": { + "issues": "https://github.com/orchestral/sidekick/issues", + "source": "https://github.com/orchestral/sidekick/tree/v1.2.20" + }, + "time": "2026-01-12T11:09:33+00:00" + }, + { + "name": "orchestra/testbench", + "version": "v11.1.0", + "source": { + "type": "git", + "url": "https://github.com/orchestral/testbench.git", + "reference": "997f33e5200c7e8db4756b35a9deb3f5f3086759" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/orchestral/testbench/zipball/997f33e5200c7e8db4756b35a9deb3f5f3086759", + "reference": "997f33e5200c7e8db4756b35a9deb3f5f3086759", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.2", + "fakerphp/faker": "^1.23", + "laravel/framework": "^13.1.1", + "mockery/mockery": "^1.6.10", + "orchestra/testbench-core": "^11.2.0", + "orchestra/workbench": "^11.0.1", + "php": "^8.3", + "phpunit/phpunit": "^11.5.50|^12.5.8|^13.0.0", + "symfony/process": "^7.4.5|^8.0.5", + "symfony/yaml": "^7.4|^8.0", + "vlucas/phpdotenv": "^5.6.1" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mior Muhammad Zaki", + "email": "crynobone@gmail.com", + "homepage": "https://github.com/crynobone" + } + ], + "description": "Laravel Testing Helper for Packages Development", + "homepage": "https://packages.tools/testbench/", + "keywords": [ + "BDD", + "TDD", + "dev", + "laravel", + "laravel-packages", + "testing" + ], + "support": { + "issues": "https://github.com/orchestral/testbench/issues", + "source": "https://github.com/orchestral/testbench/tree/v11.1.0" + }, + "time": "2026-04-09T05:11:06+00:00" + }, + { + "name": "orchestra/testbench-core", + "version": "v11.3.3", + "source": { + "type": "git", + "url": "https://github.com/orchestral/testbench-core.git", + "reference": "8b85d7754a08b4ff88fc65ab86edf81d70fd1e26" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/orchestral/testbench-core/zipball/8b85d7754a08b4ff88fc65ab86edf81d70fd1e26", + "reference": "8b85d7754a08b4ff88fc65ab86edf81d70fd1e26", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.2", + "orchestra/sidekick": "~1.1.23|~1.2.20", + "php": "^8.3", + "symfony/deprecation-contracts": "^2.5|^3.0", + "symfony/polyfill-php84": "^1.34.0" + }, + "conflict": { + "brianium/paratest": "<7.3.0|>=8.0.0", + "laravel/framework": "<13.9.0|>=14.0.0", + "laravel/serializable-closure": ">=2.0.0 <2.0.10|>=3.0.0", + "nunomaduro/collision": "<8.9.0|>=9.0.0", + "phpunit/phpunit": "<11.5.50|>=12.0.0 <12.5.8|>=13.2.0" + }, + "require-dev": { + "fakerphp/faker": "^1.24", + "laravel/framework": "^13.9.0", + "laravel/pint": "^1.24", + "laravel/serializable-closure": "^2.0.10", + "mockery/mockery": "^1.6.10", + "phpstan/phpstan": "^2.1.38", + "phpunit/phpunit": "^11.5.50|^12.5.8|^13.0.0", + "spatie/laravel-ray": "^1.43.6", + "symfony/process": "^7.4.5|^8.0.5", + "symfony/yaml": "^7.4.0|^8.0.0", + "vlucas/phpdotenv": "^5.6.1" + }, + "suggest": { + "brianium/paratest": "Allow using parallel testing (^7.3).", + "ext-pcntl": "Required to use all features of the console signal trapping.", + "fakerphp/faker": "Allow using Faker for testing (^1.23).", + "laravel/framework": "Required for testing (^13.9.0).", + "mockery/mockery": "Allow using Mockery for testing (^1.6).", + "nunomaduro/collision": "Allow using Laravel style tests output and parallel testing (^8.9).", + "orchestra/testbench-dusk": "Allow using Laravel Dusk for testing (^11.0).", + "phpunit/phpunit": "Allow using PHPUnit for testing (^11.5.50|^12.5.8|^13.0.0).", + "symfony/process": "Required to use Orchestra\\Testbench\\remote function (^7.4|^8.0).", + "symfony/yaml": "Required for Testbench CLI (^7.4|^8.0).", + "vlucas/phpdotenv": "Required for Testbench CLI (^5.6.1)." + }, + "bin": [ + "testbench" + ], + "type": "library", + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Orchestra\\Testbench\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mior Muhammad Zaki", + "email": "crynobone@gmail.com", + "homepage": "https://github.com/crynobone" + } + ], + "description": "Testing Helper for Laravel Development", + "homepage": "https://packages.tools/testbench", + "keywords": [ + "BDD", + "TDD", + "dev", + "laravel", + "laravel-packages", + "testing" + ], + "support": { + "issues": "https://github.com/orchestral/testbench/issues", + "source": "https://github.com/orchestral/testbench-core" + }, + "time": "2026-05-14T11:22:09+00:00" + }, + { + "name": "orchestra/workbench", + "version": "v11.1.0", + "source": { + "type": "git", + "url": "https://github.com/orchestral/workbench.git", + "reference": "e750c7bcae4405e054ff286475502e23274de04b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/orchestral/workbench/zipball/e750c7bcae4405e054ff286475502e23274de04b", + "reference": "e750c7bcae4405e054ff286475502e23274de04b", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2.2", + "fakerphp/faker": "^1.23", + "laravel/framework": "^13.0.0", + "laravel/pail": "^1.2.5", + "laravel/tinker": "^3.0.0", + "nunomaduro/collision": "^8.9", + "orchestra/canvas": "^11.0.1", + "orchestra/sidekick": "~1.1.23|~1.2.20", + "orchestra/testbench-core": "^11.1.0", + "php": "^8.3", + "symfony/process": "^7.4|^8.0", + "symfony/yaml": "^7.4|^8.0" + }, + "require-dev": { + "laravel/pint": "^1.22.0", + "mockery/mockery": "^1.6.12", + "phpstan/phpstan": "^2.1.33", + "phpunit/phpunit": "^11.5.50|^12.5.8|^13.0.0", + "spatie/laravel-ray": "^1.43.6" + }, + "suggest": { + "ext-pcntl": "Required to use all features of the console signal trapping." + }, + "type": "library", + "autoload": { + "psr-4": { + "Orchestra\\Workbench\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mior Muhammad Zaki", + "email": "crynobone@gmail.com" + } + ], + "description": "Workbench Companion for Laravel Packages Development", + "keywords": [ + "dev", + "laravel", + "laravel-packages", + "testing" + ], + "support": { + "issues": "https://github.com/orchestral/workbench/issues", + "source": "https://github.com/orchestral/workbench/tree/v11.1.0" + }, + "time": "2026-03-24T23:09:55+00:00" + }, { "name": "phar-io/manifest", "version": "2.0.4", @@ -8210,6 +8697,162 @@ ], "time": "2024-10-20T05:08:20+00:00" }, + { + "name": "symfony/polyfill-php83", + "version": "v1.38.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php83.git", + "reference": "8339098cae28673c15cce00d80734af0453054e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/8339098cae28673c15cce00d80734af0453054e2", + "reference": "8339098cae28673c15cce00d80734af0453054e2", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php83\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php83/tree/v1.38.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-26T12:51:13+00:00" + }, + { + "name": "symfony/yaml", + "version": "v8.1.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "efb42bd2c6f4f3ccfd4683583449938b5fc146b0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/efb42bd2c6f4f3ccfd4683583449938b5fc146b0", + "reference": "efb42bd2c6f4f3ccfd4683583449938b5fc146b0", + "shasum": "" + }, + "require": { + "php": ">=8.4.1", + "symfony/polyfill-ctype": "^1.8" + }, + "conflict": { + "symfony/console": "<7.4" + }, + "require-dev": { + "symfony/console": "^7.4|^8.0", + "yaml/yaml-test-suite": "*" + }, + "bin": [ + "Resources/bin/yaml-lint" + ], + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Loads and dumps YAML files", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v8.1.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2026-05-29T05:06:50+00:00" + }, { "name": "theseer/tokenizer", "version": "2.0.1", diff --git a/routes/api.php b/routes/api.php index 45d5b93..c41f2cd 100644 --- a/routes/api.php +++ b/routes/api.php @@ -5,20 +5,15 @@ use Illuminate\Support\Facades\Route; Route::get('/health', function () { return response()->json([ 'success' => true, - 'message' => 'API is running.', + 'message' => 'Laravel school API is running.', ]); }); -/* -|-------------------------------------------------------------------------- -| Modular scaffold routes -|-------------------------------------------------------------------------- -| Uncomment these as each route file exists and your providers are registered. -*/ - -// require __DIR__.'/finance_phase3.php'; -// require __DIR__.'/attendance_phase4.php'; -// require __DIR__.'/student_lifecycle_phase5.php'; -// require __DIR__.'/communication_phase6.php'; -// require __DIR__.'/reporting_phase7.php'; -// require __DIR__.'/api_phase8.php'; +Route::prefix('v1')->group(function () { + require __DIR__.'/finance_phase3.php'; + require __DIR__.'/attendance_phase4.php'; + require __DIR__.'/student_lifecycle_phase5.php'; + require __DIR__.'/communication_phase6.php'; + require __DIR__.'/reporting_phase7.php'; + require __DIR__.'/api_phase8.php'; +}); \ No newline at end of file diff --git a/tests/Feature/SchoolCore/Attendance/ScannerDelegationTest.php b/tests/Feature/SchoolCore/Attendance/ScannerDelegationTest.php index 6009d13..95dea86 100644 --- a/tests/Feature/SchoolCore/Attendance/ScannerDelegationTest.php +++ b/tests/Feature/SchoolCore/Attendance/ScannerDelegationTest.php @@ -10,7 +10,8 @@ final class ScannerDelegationTest extends TestCase { public function test_scanner_route_file_exists_for_phase_four_slice(): void { - $routeFile = dirname(__DIR__, 3).'/routes/attendance_phase4.php'; + $routeFile = dirname(__DIR__, 4).'/routes/attendance_phase4.php'; + $this->assertFileExists($routeFile); $this->assertStringContainsString('attendance.scan.process', file_get_contents($routeFile)); } diff --git a/tests/Feature/SchoolCore/Finance/PaymentFileRouteTest.php b/tests/Feature/SchoolCore/Finance/PaymentFileRouteTest.php index f3a2d66..134536f 100644 --- a/tests/Feature/SchoolCore/Finance/PaymentFileRouteTest.php +++ b/tests/Feature/SchoolCore/Finance/PaymentFileRouteTest.php @@ -10,7 +10,12 @@ final class PaymentFileRouteTest extends TestCase { public function test_phase3_route_documents_payment_id_based_file_access(): void { - $routes = file_get_contents(__DIR__ . '/../../../routes/finance_phase3.php'); + $routeFile = dirname(__DIR__, 4).'/routes/finance_phase3.php'; + + $this->assertFileExists($routeFile); + + $routes = file_get_contents($routeFile); + $this->assertStringContainsString('payments/{payment}/file', $routes); $this->assertStringContainsString('school.context', $routes); } diff --git a/tests/Feature/SchoolCore/Reporting/ReportingPhase7ScaffoldTest.php b/tests/Feature/SchoolCore/Reporting/ReportingPhase7ScaffoldTest.php index 43ae61d..0925eb2 100644 --- a/tests/Feature/SchoolCore/Reporting/ReportingPhase7ScaffoldTest.php +++ b/tests/Feature/SchoolCore/Reporting/ReportingPhase7ScaffoldTest.php @@ -1,4 +1,6 @@ -assertFileExists($root.'/app/Domain/SchoolCore/Reporting/Contracts/ReportRunnerContract.php'); $this->assertFileExists($root.'/app/Providers/SchoolCoreReportingServiceProvider.php'); $this->assertFileExists($root.'/app/Domain/IslamicSundaySchool/Reporting/Providers/IslamicSundaySchoolReportingServiceProvider.php'); diff --git a/tests/Feature/SchoolCore/Students/StudentLifecycleDelegationTest.php b/tests/Feature/SchoolCore/Students/StudentLifecycleDelegationTest.php index 948d116..d2b1e60 100644 --- a/tests/Feature/SchoolCore/Students/StudentLifecycleDelegationTest.php +++ b/tests/Feature/SchoolCore/Students/StudentLifecycleDelegationTest.php @@ -1,5 +1,17 @@ assertFileExists(dirname(__DIR__,3).'/routes/student_lifecycle_phase5.php'); } } + +final class StudentLifecycleDelegationTest extends TestCase +{ + public function test_phase5_routes_file_exists(): void + { + $routeFile = dirname(__DIR__, 4).'/routes/student_lifecycle_phase5.php'; + + $this->assertFileExists($routeFile); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..fe1ffc2 --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,10 @@ +