diff --git a/.gitignore b/.gitignore index f8b367e..68bd285 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,4 @@ vendor/ node_modules/ data/*.json -.direnv/ -cypress/screenshots/ -cypress/videos/ \ No newline at end of file +.direnv/ \ No newline at end of file diff --git a/.opencode/opencode.json b/.opencode/opencode.json deleted file mode 100644 index e2342fc..0000000 --- a/.opencode/opencode.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "$schema": "https://opencode.ai/config.json", - "plugin": [ - "caveman", - "caveman-opencode-plugin@latest" - ] -} \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 5dea88b..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,7 +0,0 @@ -# Project context - -Read these on every session. Rules in them override defaults. - -@ai/shared.md -@ai/backend-context.md -@ai/frontend-context.md diff --git a/DailyGoals.drawio b/DailyGoals.drawio index aa76945..b0937cb 100644 --- a/DailyGoals.drawio +++ b/DailyGoals.drawio @@ -16,14 +16,11 @@ - - - - + @@ -31,14 +28,14 @@ - - + + - + - + diff --git a/ai/backend-context.md b/ai/backend-context.md deleted file mode 100644 index fca41f0..0000000 --- a/ai/backend-context.md +++ /dev/null @@ -1,45 +0,0 @@ -# Backend context - -> Read `ai/shared.md` first. This file only covers backend-specific rules. - -## Project Context - -**Stack:** PHP 8.5, Slim 4, PHP-DI/Slim-Bridge, PHPUnit 13, Composer. -Persistence is JSON-file based (see `Json*Repository` classes); no ORM. -**Architecture:** Domain-Driven Design. Code is organized by domain entity -under `app/` (Auth, Node, Plan, ScheduledNode, Text, User, View, -ValueObjects) into Entities, DTOs, Repositories, Use Cases, and Fakes -(in-memory repos for tests). - -## Code patterns - -- Look at similar entities (e.g. `Node`, `Text`) for reference -- Entities: constructor with properties, getters -- DTOs: simple data containers for creation (e.g. `CreateTextDto`) -- Repositories: interfaces that define data access - - Do not write unit tests for concrete repository implementations - (e.g. `JsonNodeRepository`). They are exercised by e2e tests. Use - cases are tested with fake repositories. -- Use cases: business logic with Request objects - - When throwing exceptions, add `@throws` docblock -- Fakes: in-memory implementations for testing - - Look at `tests/Fakes/` for examples - - Find/lookup methods must return a new instance of the entity, not the - stored reference -- Tests: follow existing patterns in `tests/Unit/[Entity]/UseCases/` - - In `setUp`, only use fake repositories for entities under test - construct - dependency objects directly with `new` (e.g. - `new Text(...)`) instead of creating them through their fake - repositories - -## PHP rules - -- Imports: always put `use` statements at the top of the file, never use inline - imports (e.g. `\App\Foo\Bar::class`) -- Closures: never use arrow functions (`fn () =>`) - always use regular - anonymous functions (`function () { return ...; }`) - -## Pre-commit - -Run `php-cs-fixer fix` on worked-on directories before committing (uses the -existing `.php-cs-fixer.dist.php` config). diff --git a/ai/backend_prompt_template.md b/ai/backend_prompt_template.md new file mode 100644 index 0000000..c16ef26 --- /dev/null +++ b/ai/backend_prompt_template.md @@ -0,0 +1,68 @@ +# Entity Creation Prompt Template + +Follow the existing patterns in this codebase to: +- create a new entity called [EntityName]. + +Requirements: +- The entity encapsulates [one or more Entities] +- Include [any other fields] + +Process (TDD - Test Driven Development): +1. Write a test first +2. Run the test to confirm it fails +3. Implement the code to make the test pass +4. Run the test to confirm it passes +5. Repeat for each new behavior + +Code patterns to follow: +- First, explore the codebase to understand existing entity patterns +- Look at similar entities (e.g. Node, Text, etc.) for reference +- Entities: constructor with properties, getters +- DTOs: simple data containers for creation +- Repositories: interfaces that define data access + - Do not write unit tests for concrete repository implementations + (e.g., Doctrine/persistence-backed). They are exercised by e2e + tests. Use cases are tested with fake repositories. +- Use cases: business logic with Request objects + - When throwing exceptions, add @throws docblock +- Fakes: in-memory implementations for testing + - Look at tests/Fakes/ for examples + - Find/lookup methods must return a new instance of the entity, not the stored reference +- Tests: follow existing patterns in tests/Unit/[Entity]/UseCases/ + - In setUp, only use fake repositories for entities under test - construct dependency objects directly with `new` (e.g., `new Text(....)`) instead of creating them through their fake repositories +- Lines should not exceed 80 columns, but should use up to 80 columns when possible - do not split lines unnecessarily +- Imports: always put use statements at the top of the file, never use inline imports (e.g., \App\Foo\Bar::class) +- Variable names: use explicit, descriptive names - never single-letter or abbreviated variables (e.g., use $sponsorship not $s, $event not $e) +- Never use em-dashes (—) in code, comments, commit messages, or any + written output. Use a regular hyphen (-), a colon, or rephrase + with parentheses instead. + +Git commit style: +- Subject: present tense, imperative mood (add, create, test, fix) +- Subject: lowercase, short (3-6 words) +- Match subject patterns found in git history +- Add a body when the change needs explanation beyond the subject - + e.g., why the change was made, non-obvious tradeoffs, or notable + implementation details. Skip the body for trivial/self-evident commits. +- Separate subject and body with a blank line; wrap body at ~72 columns + +Git commits: +- Tests should be committed first, before implementation +- Group related changes together in a single commit (e.g., a new class + plus its registration, or a getter plus the property it exposes). + Avoid mixing unrelated concerns in one commit. +- Keep commits small and focused - prefer many small commits over few + large ones, but don't artificially split a single logical change + across multiple commits +- Commits are for reviewing and documenting the development of code +- Don't wait to commit - commit as you go +- Run `php-cs-fixer fix` on worked on directories before committing + +Branch naming: +- Use kebab-case (e.g., presenting-track, agenda-slots) +- Use descriptive feature names +- Examples: "presenting-track", "agenda-slots", "confirm-application" +- Or use type/description: "feature/presenting-track", "fix/bug-name" +- NEVER work directly on master/main - always create and work on a branch + +Do not push anything. Make commits as you go. diff --git a/ai/frontend-context.md b/ai/frontend-context.md deleted file mode 100644 index 8757e1f..0000000 --- a/ai/frontend-context.md +++ /dev/null @@ -1,33 +0,0 @@ -# Frontend context - -> Read `ai/shared.md` first. This file only covers frontend-specific rules. - -## Project Context - -**Stack:** vanilla PHP templates in `views/templates/`, plain ES JavaScript in -`public/js/`, no framework, no build step. Cypress 15 for E2E. -**Entry point:** `public/index.php` (Slim app); page templates are rendered -via the existing templating layer. - -## Code patterns - -- Look at existing pages (`home.php`/`home.js`, `text.php`/`text.js`, - `today.php`/`today.js`) for reference before writing anything -- **Templates:** `views/templates/.php`, one file per page -- **Page JS:** `public/js/.js`, one file per page, paired with the - matching template -- **Testing:** Cypress E2E only, mirror existing `cypress/e2e/*.cy.js` style - (note: this project uses `.cy.js`, not `.cy.ts`) -- **Imports / script tags:** keep at the top of the file -- **Variable names:** explicit, descriptive (e.g. `text` not `t`) - -## Pre-commit - -No JS formatter or linter is configured yet; format manually for consistency -with surrounding files. (TODO: wire up format/lint when added.) - -## Note on commit granularity - -Frontend changes are often a template plus its page-level JS counterpart - -commit them together as a single logical unit, per the "one logical change -per commit" rule in `shared.md`. diff --git a/ai/frontend_prompt_template.md b/ai/frontend_prompt_template.md new file mode 100644 index 0000000..a865577 --- /dev/null +++ b/ai/frontend_prompt_template.md @@ -0,0 +1,52 @@ +# Frontend Prompt Template + +Follow the existing patterns in this codebase to: +- xxxxxxxx + +Requirements: +- xxxxx + +Process (TDD - Test Driven Development): +1. Write a test first +2. Run the test to confirm it fails +3. Implement the code to make the test pass +4. Run the test to confirm it passes +5. Repeat for each new behavior + +Code patterns to follow: +- First, explore the codebase to understand existing entity patterns +- Look at similar pages for reference +- Tests: follow existing patterns in cypress/e2e/ +- Lines should not exceed 80 columns, but should use up to 80 columns when possible - do not split lines unnecessarily +- Imports: always put imports at the top of the file +- Variable names: use explicit, descriptive names - never single-letter or abbreviated variables (e.g., use sponsorship not s, event not e) +- Never use em-dashes (—) in code, comments, commit messages, or any + written output. Use a regular hyphen (-), a colon, or rephrase + with parentheses instead. + +Git commit style: +- Subject: present tense, imperative mood (add, create, test, fix) +- Subject: lowercase, short (3-6 words) +- Match subject patterns found in git history +- Add a body when the change needs explanation beyond the subject - + e.g., why the change was made, non-obvious tradeoffs, or notable + implementation details. Skip the body for trivial/self-evident commits. +- Separate subject and body with a blank line; wrap body at ~72 columns + +Git commits: +- Tests should be committed first, before implementation +- Group related changes together in a single commit (e.g., a new class + plus its registration, or a getter plus the property it exposes). + Avoid mixing unrelated concerns in one commit. +- Keep commits small and focused - prefer many small commits over few + large ones, but don't artificially split a single logical change + across multiple commits +- Commits are for reviewing and documenting the development of code +- Don't wait to commit - commit as you go + +Branch naming: +- Use kebab-case (e.g., node-page text-page) +- Use descriptive feature names +- NEVER work directly on master - always create and work on a branch + +Do not push anything. Make commits as you go. diff --git a/ai/shared.md b/ai/shared.md deleted file mode 100644 index cc85dd1..0000000 --- a/ai/shared.md +++ /dev/null @@ -1,74 +0,0 @@ -# Shared rules - -Rules that apply to both backend and frontend work in this repo. Stack-specific -guides (`backend-context.md`, `frontend-context.md`) extend these. - -## Process (TDD) - -0. Before editing any file, ensure you are on a feature branch - (`git status` to confirm). If on master/main, create a branch - first. -1. Write the test first -2. Run the test to confirm it fails -3. Commit the failing test (the "tests committed first" rule in - action - the test commit precedes the implementation commit, not - merely the implementation lines) -4. Implement the code to make the test pass -5. Run the test to confirm it passes -6. Commit the implementation -7. Repeat for each new behavior - -## Code style - -- Lines should not exceed 80 columns, but should use up to 80 columns when - possible - do not split lines unnecessarily -- Variable names: use explicit, descriptive names - never single-letter or - abbreviated variables (e.g. `$text` not `$t`, `$node` not `$n`) -- Method/function/constructor parameters: do not use default values - every - call site must pass every argument explicitly. This eliminates a class of - bugs where an unintended default silently slips through (e.g. an - `isAdmin=false` or an empty `passwordHash`). Apply the same rule in tests - and fakes - if a helper accepts a value, every caller must supply it. -- First, explore the codebase to understand existing patterns - look at similar - files for reference before writing anything -- Never use em dashes (—) in code, comments, or docblocks - use hyphens (-) - instead - -## Git commit style - -- Present tense, imperative mood (add, create, wire, fix, test) -- Lowercase -- Short (3-6 words) -- Match patterns found in git history -- Do not add any section mentioning claude as a coauthor -- Add a commit body when the subject alone cannot convey the change - e.g. - non-obvious motivation, multi-file coordination, or notable complexity -- Body: wrap at ~72 columns, separated from subject by a blank line, explain - the why and any non-obvious what -- Skip the body for trivial or self-explanatory commits - -## Git commits - -- Tests should be committed first, before implementation -- One logical change per commit - a commit may span multiple files when they - form a single logical unit (e.g. a use case with its request and exception, - or a template with its page-level JS) -- Keep commits focused: not one file per commit, not unrelated work batched -- Make commits frequent - commit each meaningful logical step as you go -- Commits are for reviewing and documenting the development of code -- When the formatter or linter modifies files outside your intended - change, either `git restore` them or land them as a separate - `format ` / `lint ` commit - never bundle drive-by - formatter churn into a feature commit -- If pre-commit lint fails on code you did not touch, do not bundle - the fix - either land the unrelated fix as its own commit first, or - note the pre-existing failure and proceed - -## Branching - -- Use kebab-case (e.g. `text-page`, `scheduled-node`, `auth-flow`) -- Use descriptive feature names -- Or use type/description: `feature/text-page`, `fix/bug-name` -- NEVER work directly on master/main - always create and work on a branch - -Do not push anything. Make commits as you go. diff --git a/app/Plan/JsonPlanRepository.php b/app/Plan/JsonPlanRepository.php index 8c73d8a..8a88838 100644 --- a/app/Plan/JsonPlanRepository.php +++ b/app/Plan/JsonPlanRepository.php @@ -90,29 +90,4 @@ class JsonPlanRepository implements PlanRepository return $maxId + 1; } - - public function findByUser(User $user): array - { - $plans = array_filter( - $this->readPlans(), - function ($data) use ($user) { - return $data['userId'] === $user->getId(); - } - ); - - return array_map( - function ($data) { - $user = $this->userRepository->find($data['userId']); - if ($user === null) { - return null; - } - return new Plan( - id: $data['id'], - name: $data['name'], - user: $user, - ); - }, - $plans - ); - } } diff --git a/app/Plan/PlanRepository.php b/app/Plan/PlanRepository.php index 0585edc..a962c0f 100644 --- a/app/Plan/PlanRepository.php +++ b/app/Plan/PlanRepository.php @@ -2,14 +2,8 @@ namespace App\Plan; -use App\User\User; - interface PlanRepository { public function create(CreatePlanDto $dto): Plan; public function find(int $id): ?Plan; - /** - * @return Plan[] - */ - public function findByUser(User $user): array; } diff --git a/app/Plan/UseCases/CreatePlan.php b/app/Plan/UseCases/CreatePlan.php index 8a3cc91..b12fc50 100644 --- a/app/Plan/UseCases/CreatePlan.php +++ b/app/Plan/UseCases/CreatePlan.php @@ -64,7 +64,6 @@ class CreatePlan new CreateScheduledNodeRequest( date: $scheduledDate->format('Y-m-d'), planId: $plan->getId(), - nodeId: $node->getId(), ) ); } diff --git a/app/ScheduledNode/CreateScheduledNodeDto.php b/app/ScheduledNode/CreateScheduledNodeDto.php index 086c975..5e7a695 100644 --- a/app/ScheduledNode/CreateScheduledNodeDto.php +++ b/app/ScheduledNode/CreateScheduledNodeDto.php @@ -2,7 +2,6 @@ namespace App\ScheduledNode; -use App\Node\Node; use App\Plan\Plan; use DateTimeImmutable; @@ -11,6 +10,5 @@ class CreateScheduledNodeDto public function __construct( public DateTimeImmutable $date, public Plan $plan, - public Node $node, ) {} } diff --git a/app/ScheduledNode/JsonScheduledNodeRepository.php b/app/ScheduledNode/JsonScheduledNodeRepository.php index 1870844..bee31ea 100644 --- a/app/ScheduledNode/JsonScheduledNodeRepository.php +++ b/app/ScheduledNode/JsonScheduledNodeRepository.php @@ -2,19 +2,12 @@ namespace App\ScheduledNode; -use App\Node\NodeRepository; -use App\Plan\PlanRepository; -use App\User\User; -use DateTimeImmutable; - class JsonScheduledNodeRepository implements ScheduledNodeRepository { private string $filePath; - public function __construct( - private PlanRepository $planRepo, - private NodeRepository $nodeRepo, - ) { + public function __construct() + { $this->filePath = __DIR__ . '/../../data/scheduledNodes.json'; } @@ -27,8 +20,6 @@ class JsonScheduledNodeRepository implements ScheduledNodeRepository 'id' => $id, 'date' => $dto->date->format('Y-m-d'), 'planId' => $dto->plan->getId(), - 'nodeId' => $dto->node->getId(), - 'completed' => false, ]; $this->writeScheduledNodes($scheduledNodes); @@ -36,8 +27,6 @@ class JsonScheduledNodeRepository implements ScheduledNodeRepository id: $id, date: $dto->date, plan: $dto->plan, - node: $dto->node, - completed: false, ); } @@ -75,34 +64,4 @@ class JsonScheduledNodeRepository implements ScheduledNodeRepository return $maxId + 1; } - - public function findByUser(User $user): array - { - $allScheduledNodes = $this->readScheduledNodes(); - $planIds = array_map( - function ($plan) { - return $plan->getId(); - }, - $this->planRepo->findByUser($user) - ); - $usersScheduledNodes = array_filter( - $allScheduledNodes, - function ($node) use ($planIds) { - return in_array($node['planId'], $planIds); - } - ); - - return array_map( - function ($data) { - return new ScheduledNode( - id: $data['id'], - date: new DateTimeImmutable($data['date']), - plan: $this->planRepo->find($data['planId']), - node: $this->nodeRepo->find($data['nodeId']), - completed: $data['completed'] - ); - }, - $usersScheduledNodes - ); - } } diff --git a/app/ScheduledNode/ScheduledNode.php b/app/ScheduledNode/ScheduledNode.php index 83f29ef..7d479db 100644 --- a/app/ScheduledNode/ScheduledNode.php +++ b/app/ScheduledNode/ScheduledNode.php @@ -2,7 +2,6 @@ namespace App\ScheduledNode; -use App\Node\Node; use App\Plan\Plan; use DateTimeImmutable; @@ -12,8 +11,6 @@ class ScheduledNode private int $id, private DateTimeImmutable $date, private Plan $plan, - private Node $node, - private bool $completed, ) {} public function getId(): int @@ -30,19 +27,4 @@ class ScheduledNode { return $this->date; } - - public function getNode(): Node - { - return $this->node; - } - - public function getCompleted(): bool - { - return $this->completed; - } - - public function setCompleted(bool $complete): void - { - $this->completed = $complete; - } } diff --git a/app/ScheduledNode/ScheduledNodeController.php b/app/ScheduledNode/ScheduledNodeController.php deleted file mode 100644 index 3a71ccb..0000000 --- a/app/ScheduledNode/ScheduledNodeController.php +++ /dev/null @@ -1,73 +0,0 @@ -getAttribute('user'); - if (!$user instanceof User) { - $response->getBody()->write( - json_encode(['error' => 'unauthenticated']) - ); - return $response->withStatus(401) - ->withHeader('Content-Type', 'application/json'); - } - - $queryParams = $request->getQueryParams(); - $date = $queryParams['date'] ?? null; - if ($date === '') { - $date = null; - } - - try { - $scheduledNodes = $getTodaysSchedule->execute( - new GetTodaysScheduleRequest( - date: $date, - userId: $user->getId(), - ) - ); - } catch (BadRequestException $exception) { - $response->getBody()->write( - json_encode(['error' => $exception->getMessage()]) - ); - return $response->withStatus(400) - ->withHeader('Content-Type', 'application/json'); - } catch (DomainException $exception) { - $response->getBody()->write( - json_encode(['error' => $exception->getMessage()]) - ); - return $response->withStatus(404) - ->withHeader('Content-Type', 'application/json'); - } - - $data = array_values(array_map( - function (ScheduledNode $scheduledNode) { - return [ - 'id' => $scheduledNode->getId(), - 'date' => $scheduledNode->getDate()->format('Y-m-d'), - 'planName' => $scheduledNode->getPlan()->getName(), - 'nodeTitle' => $scheduledNode->getNode()->getTitle(), - 'completed' => $scheduledNode->getCompleted(), - ]; - }, - $scheduledNodes, - )); - - $response->getBody()->write(json_encode($data)); - return $response->withStatus(200) - ->withHeader('Content-Type', 'application/json'); - } -} diff --git a/app/ScheduledNode/ScheduledNodeRepository.php b/app/ScheduledNode/ScheduledNodeRepository.php index 055a939..b13b7c5 100644 --- a/app/ScheduledNode/ScheduledNodeRepository.php +++ b/app/ScheduledNode/ScheduledNodeRepository.php @@ -2,13 +2,7 @@ namespace App\ScheduledNode; -use App\User\User; - interface ScheduledNodeRepository { public function create(CreateScheduledNodeDto $dto): ScheduledNode; - /** - * @return ScheduledNode[] - */ - public function findByUser(User $user): array; } diff --git a/app/ScheduledNode/UseCases/CreateScheduledNode.php b/app/ScheduledNode/UseCases/CreateScheduledNode.php index 444f4d0..752b658 100644 --- a/app/ScheduledNode/UseCases/CreateScheduledNode.php +++ b/app/ScheduledNode/UseCases/CreateScheduledNode.php @@ -3,7 +3,6 @@ namespace App\ScheduledNode\UseCases; use App\Exceptions\BadRequestException; -use App\Node\NodeRepository; use App\Plan\PlanRepository; use App\ScheduledNode\ScheduledNode; use App\ScheduledNode\CreateScheduledNodeDto; @@ -16,7 +15,6 @@ class CreateScheduledNode public function __construct( private ScheduledNodeRepository $scheduledNodeRepo, private PlanRepository $planRepo, - private NodeRepository $nodeRepo, ) {} /** @@ -26,40 +24,24 @@ class CreateScheduledNode public function execute( CreateScheduledNodeRequest $request ): ScheduledNode { - $nodeId = $request->nodeId; - $planId = $request->planId; - $date = $request->date; - if ($date === null) { + if ($request->date === null) { throw new BadRequestException('date is required'); } - if ($planId === null) { + if ($request->planId === null) { throw new BadRequestException('planId is required'); } - if ($nodeId === null) { - throw new BadRequestException('nodeId is required'); - } - - $plan = $this->planRepo->find($planId); + $id = $request->planId; + $plan = $this->planRepo->find($id); if ($plan === null) { - throw new DomainException( - "Plan with id: $planId doesnt exist" - ); - } - - $node = $this->nodeRepo->find($nodeId); - if ($node === null) { - throw new DomainException( - "Node with id: $nodeId doesnt exist" - ); + throw new DomainException("Plan with id: $id doesnt exist"); } return $this->scheduledNodeRepo->create( new CreateScheduledNodeDto( - date: new DateTimeImmutable($date), + date: new DateTimeImmutable($request->date), plan: $plan, - node: $node, ) ); } diff --git a/app/ScheduledNode/UseCases/CreateScheduledNodeRequest.php b/app/ScheduledNode/UseCases/CreateScheduledNodeRequest.php index 4c0a2fd..5931cb3 100644 --- a/app/ScheduledNode/UseCases/CreateScheduledNodeRequest.php +++ b/app/ScheduledNode/UseCases/CreateScheduledNodeRequest.php @@ -7,6 +7,5 @@ class CreateScheduledNodeRequest public function __construct( public ?string $date, public ?int $planId, - public ?int $nodeId, ) {} } diff --git a/app/ScheduledNode/UseCases/GetTodaysSchedule.php b/app/ScheduledNode/UseCases/GetTodaysSchedule.php deleted file mode 100644 index 3301549..0000000 --- a/app/ScheduledNode/UseCases/GetTodaysSchedule.php +++ /dev/null @@ -1,51 +0,0 @@ -date === null) { - throw new BadRequestException('date is required'); - } - if ($request->userId === null) { - throw new BadRequestException('userId is required'); - } - $date = new DateTimeImmutable($request->date); - $userId = $request->userId; - $user = $this->userRepo->find($userId); - if ($user === null) { - throw new DomainException( - "User with id: $userId doesnt exist" - ); - } - $scheduledNodes = $this->scheduledNodeRepo->findByUser($user); - - return array_filter( - $scheduledNodes, - function (ScheduledNode $node) use ($date) { - return $node->getDate() <= $date - && $node->getCompleted() === false; - } - ); - } -} diff --git a/app/ScheduledNode/UseCases/GetTodaysScheduleRequest.php b/app/ScheduledNode/UseCases/GetTodaysScheduleRequest.php deleted file mode 100644 index ccb38b8..0000000 --- a/app/ScheduledNode/UseCases/GetTodaysScheduleRequest.php +++ /dev/null @@ -1,11 +0,0 @@ -getBody()->write($html); - - return $response; - } - public function login(Response $response): Response { $html = file_get_contents( diff --git a/bootstrap/app.php b/bootstrap/app.php index b4e9907..c05dc0f 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -11,7 +11,6 @@ use App\View\ViewController; use App\Text\TextController; use App\Node\NodeController; use App\Plan\PlanController; -use App\ScheduledNode\ScheduledNodeController; $container = require __DIR__ . '/container.php'; $app = Bridge::create($container); @@ -28,7 +27,6 @@ $app->post('/api/auth/register', [AuthController::class, 'register']); // Authenticated routes (any logged-in user) $app->group('', function (RouteCollectorProxy $group) { $group->get('/home', [ViewController::class, 'home']); - $group->get('/today', [ViewController::class, 'today']); $group->post('/api/auth/logout', [AuthController::class, 'logout']); $group->get('/api/auth/me', [AuthController::class, 'me']); @@ -45,11 +43,6 @@ $app->group('', function (RouteCollectorProxy $group) { ); $group->post('/api/plans', [PlanController::class, 'createPlan']); - - $group->get( - '/api/scheduled-nodes', - [ScheduledNodeController::class, 'getScheduledNodes'] - ); })->add(AuthMiddleware::class); // Admin-only routes diff --git a/bootstrap/container.php b/bootstrap/container.php index 02666ed..2ae6e4c 100644 --- a/bootstrap/container.php +++ b/bootstrap/container.php @@ -26,10 +26,10 @@ $container = new Container([ NodeRepository::class => DI\autowire(JsonNodeRepository::class), PlanRepository::class => DI\autowire(JsonPlanRepository::class), UserRepository::class => DI\autowire(JsonUserRepository::class), - ScheduledNodeRepository::class - => DI\autowire(JsonScheduledNodeRepository::class), - SessionRepository::class - => DI\autowire(JsonSessionRepository::class), + ScheduledNodeRepository::class => + DI\autowire(JsonScheduledNodeRepository::class), + SessionRepository::class => + DI\autowire(JsonSessionRepository::class), TokenGenerator::class => DI\autowire(RandomTokenGenerator::class), Clock::class => DI\autowire(SystemClock::class), PasswordHasher::class => DI\autowire(BcryptPasswordHasher::class), diff --git a/caveman.json b/caveman.json deleted file mode 100644 index fb19545..0000000 --- a/caveman.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "enabled": true, - "defaultMode": "full", - "features": { - "caveman": true, - "commit": , - "review": true - } -} diff --git a/cypress/e2e/today.cy.js b/cypress/e2e/today.cy.js deleted file mode 100644 index 1f24450..0000000 --- a/cypress/e2e/today.cy.js +++ /dev/null @@ -1,75 +0,0 @@ -describe('The today page', () => { - beforeEach(() => { - cy.exec('npm run db:seed') - }) - - afterEach(() => { - cy.exec('npm run db:wipe') - }) - - it('redirects to login when not authenticated', () => { - cy.visit('/today') - cy.url().should('include', '/login') - }) - - it('displays a Today heading when authenticated', () => { - cy.loginAsUser() - cy.visit('/today') - cy.get('h1').should('contain', 'Today') - }) - - it('has a list element for scheduled nodes', () => { - cy.loginAsUser() - cy.visit('/today') - cy.get('#scheduled-nodes-list').should('exist') - }) - - it('home page links to the today page', () => { - cy.loginAsUser() - cy.visit('/home') - cy.get('a[href="/today"]').should('be.visible') - }) - - it('lists scheduled nodes for today', () => { - const today = new Date() - const year = today.getFullYear() - const month = String(today.getMonth() + 1).padStart(2, '0') - const day = String(today.getDate()).padStart(2, '0') - const todayString = year + '-' + month + '-' + day - - cy.loginAsUser() - cy.request({ - method: 'POST', - url: '/api/plans', - body: { - textId: 0, - name: 'My reading plan', - dateStart: todayString, - dateEnd: todayString, - }, - }) - - cy.intercept('GET', '/api/scheduled-nodes*') - .as('getScheduledNodes') - cy.visit('/today') - cy.wait('@getScheduledNodes').then((interception) => { - expect(interception.request.url).to.include( - 'date=' + todayString - ) - }) - cy.get('#scheduled-nodes-list').should( - 'contain', - 'My reading plan' - ) - cy.get('#scheduled-nodes-list').should('contain', 'Bereishis') - }) - - it('shows an empty list when no nodes are scheduled today', () => { - cy.loginAsUser() - cy.intercept('GET', '/api/scheduled-nodes*') - .as('getScheduledNodes') - cy.visit('/today') - cy.wait('@getScheduledNodes') - cy.get('#scheduled-nodes-list li').should('have.length', 0) - }) -}) diff --git a/public/js/today.js b/public/js/today.js deleted file mode 100644 index 6f14627..0000000 --- a/public/js/today.js +++ /dev/null @@ -1,33 +0,0 @@ -document.addEventListener('DOMContentLoaded', () => { - const scheduledNodesList = document.getElementById( - 'scheduled-nodes-list' - ); - - function todayDateString() { - const today = new Date(); - const year = today.getFullYear(); - const month = String(today.getMonth() + 1).padStart(2, '0'); - const day = String(today.getDate()).padStart(2, '0'); - return year + '-' + month + '-' + day; - } - - async function loadScheduledNodes() { - const date = todayDateString(); - const response = await fetch( - '/api/scheduled-nodes?date=' + date, - { credentials: 'same-origin' } - ); - if (!response.ok) { - return; - } - const scheduledNodes = await response.json(); - scheduledNodesList.innerHTML = scheduledNodes - .map((scheduledNode) => - '
  • ' + scheduledNode.planName + ': ' + - scheduledNode.nodeTitle + '
  • ' - ) - .join(''); - } - - loadScheduledNodes(); -}); diff --git a/tests/Fakes/FakePlanRepository.php b/tests/Fakes/FakePlanRepository.php index 8c18b00..45e2e85 100644 --- a/tests/Fakes/FakePlanRepository.php +++ b/tests/Fakes/FakePlanRepository.php @@ -5,13 +5,9 @@ namespace Tests\Fakes; use App\Plan\CreatePlanDto; use App\Plan\Plan; use App\Plan\PlanRepository; -use App\User\User; class FakePlanRepository implements PlanRepository { - /** - * @var Plan[] - */ private array $existingPlans = []; public function create(CreatePlanDto $dto): Plan @@ -41,24 +37,4 @@ class FakePlanRepository implements PlanRepository } ); } - - public function findByUser(User $user): array - { - $plans = array_filter( - $this->existingPlans, - function (Plan $plan) use ($user) { - return $plan->getUser()->getId() === $user->getId(); - } - ); - return array_map( - function (Plan $plan) { - return new Plan( - id: $plan->getId(), - name: $plan->getName(), - user: $plan->getUser(), - ); - }, - $plans - ); - } } diff --git a/tests/Fakes/FakeScheduledNodeRepository.php b/tests/Fakes/FakeScheduledNodeRepository.php index 1125b51..4241813 100644 --- a/tests/Fakes/FakeScheduledNodeRepository.php +++ b/tests/Fakes/FakeScheduledNodeRepository.php @@ -5,7 +5,6 @@ namespace Tests\Fakes; use App\ScheduledNode\CreateScheduledNodeDto; use App\ScheduledNode\ScheduledNode; use App\ScheduledNode\ScheduledNodeRepository; -use App\User\User; class FakeScheduledNodeRepository implements ScheduledNodeRepository { @@ -21,27 +20,12 @@ class FakeScheduledNodeRepository implements ScheduledNodeRepository id: $id, date: $dto->date, plan: $dto->plan, - node: $dto->node, - completed: false, ); $this->existingScheduledNodes[$id] = $scheduledNode; return $scheduledNode; } - public function update(ScheduledNode $node): ScheduledNode - { - $this->existingScheduledNodes[$node->getId()] = $node; - - return new ScheduledNode( - id: $node->getId(), - date: $node->getDate(), - plan: $node->getPlan(), - node: $node->getNode(), - completed: $node->getCompleted() - ); - } - public function find(int $id): ?ScheduledNode { return array_find( @@ -61,27 +45,4 @@ class FakeScheduledNodeRepository implements ScheduledNodeRepository { return count($this->existingScheduledNodes); } - - public function findByUser(User $user): array - { - $scheduledNodes = array_filter( - $this->existingScheduledNodes, - function (ScheduledNode $node) use ($user) { - return $node->getPlan()->getUser()->getId() === $user->getId(); - } - ); - - return array_map( - function (ScheduledNode $node) { - return new ScheduledNode( - id: $node->getId(), - date: $node->getDate(), - plan: $node->getPlan(), - node: $node->getNode(), - completed: $node->getCompleted(), - ); - }, - $scheduledNodes, - ); - } } diff --git a/tests/Unit/Plan/UseCases/CreatePlanTest.php b/tests/Unit/Plan/UseCases/CreatePlanTest.php index fbdfc60..94412b7 100644 --- a/tests/Unit/Plan/UseCases/CreatePlanTest.php +++ b/tests/Unit/Plan/UseCases/CreatePlanTest.php @@ -45,7 +45,6 @@ class CreatePlanTest extends TestCase $this->createScheduledNode = new CreateScheduledNode( scheduledNodeRepo: $this->scheduledNodeRepo, planRepo: $this->planRepo, - nodeRepo: $this->nodeRepo, ); $this->textRepo->create(new CreateTextDto('testname')); $this->useCase = new CreatePlan( diff --git a/tests/Unit/ScheduledNode/UseCases/CreateScheduledNodeTest.php b/tests/Unit/ScheduledNode/UseCases/CreateScheduledNodeTest.php index 8c866fe..477a07a 100644 --- a/tests/Unit/ScheduledNode/UseCases/CreateScheduledNodeTest.php +++ b/tests/Unit/ScheduledNode/UseCases/CreateScheduledNodeTest.php @@ -3,19 +3,15 @@ namespace Tests\Unit\ScheduledNode\UseCases; use App\Exceptions\BadRequestException; -use App\Node\CreateNodeDto; -use App\Node\Node; use App\Plan\CreatePlanDto; use App\Plan\Plan; use App\ScheduledNode\ScheduledNode; use App\ScheduledNode\ScheduledNodeRepository; use App\ScheduledNode\UseCases\CreateScheduledNode; use App\ScheduledNode\UseCases\CreateScheduledNodeRequest; -use App\Text\Text; use App\User\User; use App\ValueObjects\EmailAddress; use DomainException; -use Tests\Fakes\FakeNodeRepository; use Tests\Fakes\FakePlanRepository; use Tests\Fakes\FakeScheduledNodeRepository; use PHPUnit\Framework\TestCase; @@ -26,20 +22,12 @@ class CreateScheduledNodeTest extends TestCase private FakePlanRepository $planRepo; - private FakeNodeRepository $nodeRepo; - private CreateScheduledNode $useCase; public function setUp(): void { $this->scheduledNodeRepo = new FakeScheduledNodeRepository(); $this->planRepo = new FakePlanRepository(); - $this->nodeRepo = new FakeNodeRepository(); - $this->nodeRepo->create(new CreateNodeDto( - text: new Text(0, 'text name'), - title: 'test node', - parentNode: null, - )); $this->planRepo->create(new CreatePlanDto( name: 'testplan', user: new User( @@ -52,7 +40,6 @@ class CreateScheduledNodeTest extends TestCase $this->useCase = new CreateScheduledNode( $this->scheduledNodeRepo, $this->planRepo, - $this->nodeRepo, ); } @@ -62,7 +49,6 @@ class CreateScheduledNodeTest extends TestCase new CreateScheduledNodeRequest( date: '2025-01-01', planId: 0, - nodeId: 0, ) ); $this->assertInstanceOf(ScheduledNode::class, $scheduledNode); @@ -78,24 +64,11 @@ class CreateScheduledNodeTest extends TestCase new CreateScheduledNodeRequest( date: '2025-01-01', planId: 0, - nodeId: 0 ) ); $this->assertInstanceOf(Plan::class, $scheduledNode->getPlan()); } - public function test_scheduled_node_belongs_to_node(): void - { - $scheduledNode = $this->useCase->execute( - new CreateScheduledNodeRequest( - date: '2025-01-01', - planId: 0, - nodeId: 0 - ) - ); - $this->assertInstanceOf(Node::class, $scheduledNode->getNode()); - } - public function test_nonexistant_plan_throws(): void { $this->expectException(DomainException::class); @@ -104,20 +77,6 @@ class CreateScheduledNodeTest extends TestCase new CreateScheduledNodeRequest( date: '2025-01-01', planId: 1, - nodeId: 0, - ) - ); - } - - public function test_nonexistant_node_throws(): void - { - $this->expectException(DomainException::class); - $this->expectExceptionMessage('Node with id: 1 doesnt exist'); - $this->useCase->execute( - new CreateScheduledNodeRequest( - date: '2025-01-01', - planId: 0, - nodeId: 1, ) ); } @@ -131,7 +90,6 @@ class CreateScheduledNodeTest extends TestCase new CreateScheduledNodeRequest( date: null, planId: 0, - nodeId: 0 ) ); } @@ -145,21 +103,6 @@ class CreateScheduledNodeTest extends TestCase new CreateScheduledNodeRequest( date: '2025-01-01', planId: null, - nodeId: 0, - ) - ); - } - - public function test_throws_if_node_id_is_null(): void - { - $this->expectException(BadRequestException::class); - $this->expectExceptionMessage('nodeId is required'); - - $this->useCase->execute( - new CreateScheduledNodeRequest( - date: '2025-01-01', - planId: 0, - nodeId: null, ) ); } diff --git a/tests/Unit/ScheduledNode/UseCases/GetTodaysScheduleTest.php b/tests/Unit/ScheduledNode/UseCases/GetTodaysScheduleTest.php deleted file mode 100644 index 359b632..0000000 --- a/tests/Unit/ScheduledNode/UseCases/GetTodaysScheduleTest.php +++ /dev/null @@ -1,222 +0,0 @@ -userRepo = new FakeUserRepository(); - $this->scheduledNodeRepo = new FakeScheduledNodeRepository(); - $this->planRepo = new FakePlanRepository(); - $user = $this->userRepo->create(new CreateUserDto( - email: new EmailAddress('email@email.com'), - passwordHash: 'hash', - isAdmin: false, - )); - $plan = $this->planRepo->create(new CreatePlanDto( - name: 'test plan', - user: $user, - )); - $this->scheduledNodeRepo->create(new CreateScheduledNodeDto( - date: new DateTimeImmutable('2025-01-02'), - plan: $plan, - node: new Node( - id: 0, - title: 'test node', - text: new Text(id: 0, name: 'test text'), - parentNode: null, - ), - )); - - $this->useCase = new GetTodaysSchedule( - userRepo: $this->userRepo, - scheduledNodeRepo: $this->scheduledNodeRepo, - ); - } - - public function test_returns_array_of_scheduled_nodes(): void - { - $result = $this->useCase->execute(new GetTodaysScheduleRequest( - date: '2025-01-02', - userId: 0, - )); - - $this->assertIsArray($result); - $this->assertInstanceOf(ScheduledNode::class, $result[0]); - } - - public function test_returns_all_unfinished_scheduled_nodes_up_until_today(): void - { - $this->scheduledNodeRepo->create(new CreateScheduledNodeDto( - date: new DateTimeImmutable('2025-01-01'), - plan: $this->planRepo->find(0), - node: new Node( - id: 0, - title: 'test node', - text: new Text(id: 0, name: 'test text'), - parentNode: null, - ), - )); - $result = $this->useCase->execute(new GetTodaysScheduleRequest( - date: '2025-01-02', - userId: 0, - )); - $this->assertEquals(2, count($result)); - } - - public function test_only_returns_uncompleted_nodes(): void - { - $node = $this->scheduledNodeRepo->create( - new CreateScheduledNodeDto( - date: new DateTimeImmutable('2025-01-01'), - plan: $this->planRepo->find(0), - node: new Node( - id: 0, - title: 'test node', - text: new Text(id: 0, name: 'test text'), - parentNode: null, - ), - ) - ); - $node->setCompleted(true); - $this->scheduledNodeRepo->update($node); - - $result = $this->useCase->execute(new GetTodaysScheduleRequest( - date: '2025-01-02', - userId: 0, - )); - $this->assertEquals(1, count($result)); - } - - public function test_throws_if_date_is_null(): void - { - $this->expectException(BadRequestException::class); - $this->expectExceptionMessage('date is required'); - - $this->useCase->execute(new GetTodaysScheduleRequest( - date: null, - userId: 0, - )); - } - - public function test_throws_if_user_id_is_null(): void - { - $this->expectException(BadRequestException::class); - $this->expectExceptionMessage('userId is required'); - - $this->useCase->execute(new GetTodaysScheduleRequest( - date: '2025-01-02', - userId: null, - )); - } - - public function test_nonexistant_user_throws(): void - { - $this->expectException(DomainException::class); - $this->expectExceptionMessage('User with id: 99 doesnt exist'); - - $this->useCase->execute(new GetTodaysScheduleRequest( - date: '2025-01-02', - userId: 99, - )); - } - - public function test_returns_empty_array_when_user_has_no_scheduled_nodes(): void - { - $otherUser = $this->userRepo->create(new CreateUserDto( - email: new EmailAddress('other@email.com'), - passwordHash: 'hash', - isAdmin: false, - )); - - $result = $this->useCase->execute(new GetTodaysScheduleRequest( - date: '2025-01-02', - userId: $otherUser->getId(), - )); - - $this->assertIsArray($result); - $this->assertEquals(0, count($result)); - } - - public function test_excludes_scheduled_nodes_dated_after_today(): void - { - $this->scheduledNodeRepo->create(new CreateScheduledNodeDto( - date: new DateTimeImmutable('2025-01-05'), - plan: $this->planRepo->find(0), - node: new Node( - id: 0, - title: 'future node', - text: new Text(id: 0, name: 'test text'), - parentNode: null, - ), - )); - - $result = $this->useCase->execute(new GetTodaysScheduleRequest( - date: '2025-01-02', - userId: 0, - )); - - $this->assertEquals(1, count($result)); - } - - public function test_does_not_return_other_users_scheduled_nodes(): void - { - $otherUser = $this->userRepo->create(new CreateUserDto( - email: new EmailAddress('other@email.com'), - passwordHash: 'hash', - isAdmin: false, - )); - $otherPlan = $this->planRepo->create(new CreatePlanDto( - name: 'other plan', - user: $otherUser, - )); - $this->scheduledNodeRepo->create(new CreateScheduledNodeDto( - date: new DateTimeImmutable('2025-01-02'), - plan: $otherPlan, - node: new Node( - id: 0, - title: 'other node', - text: new Text(id: 0, name: 'test text'), - parentNode: null, - ), - )); - - $result = $this->useCase->execute(new GetTodaysScheduleRequest( - date: '2025-01-02', - userId: 0, - )); - - $this->assertEquals(1, count($result)); - $resultNode = array_values($result)[0]; - $this->assertEquals( - 0, - $resultNode->getPlan()->getUser()->getId() - ); - } -} diff --git a/tests/e2e/Controllers/PlanControllerTest.php b/tests/e2e/Controllers/PlanControllerTest.php index 818651c..eb34ecd 100644 --- a/tests/e2e/Controllers/PlanControllerTest.php +++ b/tests/e2e/Controllers/PlanControllerTest.php @@ -55,7 +55,6 @@ class PlanControllerTest extends TestCase $createScheduledNode = new CreateScheduledNode( scheduledNodeRepo: $this->scheduledNodeRepo, planRepo: $this->planRepo, - nodeRepo: $this->nodeRepo, ); $this->createPlan = new CreatePlan( $this->planRepo, diff --git a/tests/e2e/Controllers/ScheduledNodeControllerTest.php b/tests/e2e/Controllers/ScheduledNodeControllerTest.php deleted file mode 100644 index 0a7c57e..0000000 --- a/tests/e2e/Controllers/ScheduledNodeControllerTest.php +++ /dev/null @@ -1,252 +0,0 @@ -userRepo = new FakeUserRepository(); - $this->planRepo = new FakePlanRepository(); - $this->scheduledNodeRepo = new FakeScheduledNodeRepository(); - - $this->user = $this->userRepo->create(new CreateUserDto( - email: new EmailAddress('test@test.com'), - passwordHash: '', - isAdmin: false, - )); - - $this->getTodaysSchedule = new GetTodaysSchedule( - userRepo: $this->userRepo, - scheduledNodeRepo: $this->scheduledNodeRepo, - ); - $this->controller = new ScheduledNodeController(); - } - - private function makeRequest( - ?string $date, - ?User $user, - ): ServerRequestInterface { - $request = new ServerRequestFactory() - ->createServerRequest( - 'GET', - 'http://localhost/api/scheduled-nodes' - ); - if ($user !== null) { - $request = $request->withAttribute('user', $user); - } - if ($date !== null) { - $request = $request->withQueryParams(['date' => $date]); - } - return $request; - } - - private function seedScheduledNode( - User $user, - string $date, - string $planName, - string $nodeTitle, - ): void { - $plan = $this->planRepo->create(new CreatePlanDto( - name: $planName, - user: $user, - )); - $this->scheduledNodeRepo->create(new CreateScheduledNodeDto( - date: new DateTimeImmutable($date), - plan: $plan, - node: new Node( - id: 0, - title: $nodeTitle, - text: new Text(id: 0, name: 'test text'), - parentNode: null, - ), - )); - } - - public function test_returns_200_with_scheduled_nodes_for_user(): void - { - $this->seedScheduledNode( - $this->user, - '2025-01-02', - 'My reading plan', - 'Bereishis', - ); - - $response = $this->controller->getScheduledNodes( - $this->makeRequest('2025-01-02', $this->user), - new Response(), - $this->getTodaysSchedule, - ); - - $this->assertEquals(200, $response->getStatusCode()); - $body = json_decode((string) $response->getBody(), true); - $this->assertIsArray($body); - $this->assertCount(1, $body); - } - - public function test_response_has_expected_fields(): void - { - $this->seedScheduledNode( - $this->user, - '2025-01-02', - 'My reading plan', - 'Bereishis', - ); - - $response = $this->controller->getScheduledNodes( - $this->makeRequest('2025-01-02', $this->user), - new Response(), - $this->getTodaysSchedule, - ); - - $body = json_decode((string) $response->getBody(), true); - $this->assertArrayHasKey('id', $body[0]); - $this->assertArrayHasKey('date', $body[0]); - $this->assertEquals('2025-01-02', $body[0]['date']); - $this->assertEquals('My reading plan', $body[0]['planName']); - $this->assertEquals('Bereishis', $body[0]['nodeTitle']); - $this->assertEquals(false, $body[0]['completed']); - } - - public function test_returns_401_when_no_user_attribute(): void - { - $response = $this->controller->getScheduledNodes( - $this->makeRequest('2025-01-02', null), - new Response(), - $this->getTodaysSchedule, - ); - - $this->assertEquals(401, $response->getStatusCode()); - } - - public function test_returns_400_when_date_query_param_missing(): void - { - $response = $this->controller->getScheduledNodes( - $this->makeRequest(null, $this->user), - new Response(), - $this->getTodaysSchedule, - ); - - $this->assertEquals(400, $response->getStatusCode()); - $body = json_decode((string) $response->getBody(), true); - $this->assertArrayHasKey('error', $body); - $this->assertEquals('date is required', $body['error']); - } - - public function test_returns_400_when_date_query_param_empty_string(): void - { - $response = $this->controller->getScheduledNodes( - $this->makeRequest('', $this->user), - new Response(), - $this->getTodaysSchedule, - ); - - $this->assertEquals(400, $response->getStatusCode()); - $body = json_decode((string) $response->getBody(), true); - $this->assertArrayHasKey('error', $body); - $this->assertEquals('date is required', $body['error']); - } - - public function test_excludes_future_scheduled_nodes(): void - { - $this->seedScheduledNode( - $this->user, - '2025-01-10', - 'My reading plan', - 'Bereishis', - ); - - $response = $this->controller->getScheduledNodes( - $this->makeRequest('2025-01-02', $this->user), - new Response(), - $this->getTodaysSchedule, - ); - - $body = json_decode((string) $response->getBody(), true); - $this->assertCount(0, $body); - } - - public function test_excludes_completed_scheduled_nodes(): void - { - $this->seedScheduledNode( - $this->user, - '2025-01-02', - 'My reading plan', - 'Bereishis', - ); - $stored = $this->scheduledNodeRepo->find(0); - $stored->setCompleted(true); - $this->scheduledNodeRepo->update($stored); - - $response = $this->controller->getScheduledNodes( - $this->makeRequest('2025-01-02', $this->user), - new Response(), - $this->getTodaysSchedule, - ); - - $body = json_decode((string) $response->getBody(), true); - $this->assertCount(0, $body); - } - - public function test_returns_empty_array_when_user_has_no_nodes(): void - { - $response = $this->controller->getScheduledNodes( - $this->makeRequest('2025-01-02', $this->user), - new Response(), - $this->getTodaysSchedule, - ); - - $this->assertEquals(200, $response->getStatusCode()); - $body = json_decode((string) $response->getBody(), true); - $this->assertEquals([], $body); - } - - public function test_returns_404_when_use_case_throws_domain_exception(): void - { - $unknownUser = new User( - id: 999, - email: new EmailAddress('ghost@test.com'), - passwordHash: '', - isAdmin: false, - ); - - $response = $this->controller->getScheduledNodes( - $this->makeRequest('2025-01-02', $unknownUser), - new Response(), - $this->getTodaysSchedule, - ); - - $this->assertEquals(404, $response->getStatusCode()); - $body = json_decode((string) $response->getBody(), true); - $this->assertArrayHasKey('error', $body); - $this->assertEquals( - 'User with id: 999 doesnt exist', - $body['error'] - ); - } -} diff --git a/views/templates/home.php b/views/templates/home.php index 258a404..4f7a096 100644 --- a/views/templates/home.php +++ b/views/templates/home.php @@ -6,7 +6,6 @@

    Home

    - Today's schedule