diff --git a/.env.example b/.env.example index f0d8f371a7a..c3b8c94d8f7 100644 --- a/.env.example +++ b/.env.example @@ -312,6 +312,10 @@ VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" # DISABLE_IMPORT_FROM_SERVER=false +# When enabled, the request caching settings (cache_enabled, cache_ttl, +# cache_event_logging) become visible in the admin settings panel. +# ENABLE_REQUEST_CACHING=false + ################################################################### # Payment integration (requires SE) # ################################################################### diff --git a/app/Http/Controllers/Admin/SettingsController.php b/app/Http/Controllers/Admin/SettingsController.php index 19ed5c1ac55..b98eb583266 100644 --- a/app/Http/Controllers/Admin/SettingsController.php +++ b/app/Http/Controllers/Admin/SettingsController.php @@ -45,6 +45,7 @@ public function getAll(GetAllConfigsRequest $request, DockerVersionInfo $docker_ $editable_configs = ConfigCategory::with([ 'configs' => fn ($query) => $query ->when(config('features.hide-lychee-SE', false) === true, fn ($q) => $q->where('cat', '!=', 'lychee SE')) + ->when(config('features.enable-request-caching') === false, fn ($q) => $q->where('cat', '!=', 'Mod Cache')) ->when($docker_info->isDocker(), fn ($q) => $q->where('not_on_docker', '!=', true)) ->when(!$request->verify()->is_supporter() && !$request->configs()->getValueAsBool('enable_se_preview'), fn ($q) => $q->where('level', '=', 0)) ->when(!$request->verify()->is_pro(), fn ($q) => $q->where('level', '<', 2)) diff --git a/config/features.php b/config/features.php index 703d6641592..8b1e02bd5ef 100644 --- a/config/features.php +++ b/config/features.php @@ -230,4 +230,17 @@ | Set USE_FOPEN_FOR_URL_IMPORTS=true in .env to use fopen instead of curl for URL imports. */ 'use_fopen_for_url_imports' => (bool) env('USE_FOPEN_FOR_URL_IMPORTS', false), + + /* + |-------------------------------------------------------------------------- + | Enable Request Caching + |-------------------------------------------------------------------------- + | + | When enabled, admins can configure Redis-backed response caching from + | the settings panel (cache_enabled, cache_ttl, cache_event_logging). + | Disabled by default — set ENABLE_REQUEST_CACHING=true to activate. + | Note: caching is only active when cache_enabled is also set to 1 in + | the database settings. + */ + 'enable-request-caching' => (bool) env('ENABLE_REQUEST_CACHING', false), ]; \ No newline at end of file diff --git a/database/migrations/2026_05_18_000001_disable_request_caching.php b/database/migrations/2026_05_18_000001_disable_request_caching.php new file mode 100644 index 00000000000..8753b55f6d4 --- /dev/null +++ b/database/migrations/2026_05_18_000001_disable_request_caching.php @@ -0,0 +1,37 @@ +where('key', 'cache_enabled') + ->update(['value' => '0']); + } + + public function down(): void + { + // Intentional no-op: previous value is not known. + } +}; diff --git a/docs/specs/4-architecture/features/040-disable-request-caching/plan.md b/docs/specs/4-architecture/features/040-disable-request-caching/plan.md new file mode 100644 index 00000000000..6d3d8922b5d --- /dev/null +++ b/docs/specs/4-architecture/features/040-disable-request-caching/plan.md @@ -0,0 +1,170 @@ +# Feature Plan 040 – Disable Request Caching + +_Linked specification:_ `docs/specs/4-architecture/features/040-disable-request-caching/spec.md` +_Status:_ Draft +_Last updated:_ 2026-05-18 + +> Guardrail: Keep this plan traceable back to the governing spec. Reference FR/NFR/Scenario IDs from `spec.md` where relevant, log any new high- or medium-impact questions in [docs/specs/4-architecture/open-questions.md](docs/specs/4-architecture/open-questions.md), and assume clarifications are resolved only when the spec's normative sections (requirements/NFR/behaviour/telemetry) and, where applicable, ADRs under `docs/specs/5-decisions/` have been updated. + +## Vision & Success Criteria + +Redis-backed request caching is disabled by default on all installations. Operators who wish to use it must explicitly opt in by setting `ENABLE_REQUEST_CACHING=true` in `.env`, which makes the caching settings visible in the admin UI. Success is measured by: + +- All existing installations have `cache_enabled = 0` after migration. +- `GET /api/v2/Settings` omits the `Mod Cache` category when `ENABLE_REQUEST_CACHING` is not set. +- PHPStan 0 errors, php-cs-fixer clean, all tests pass. + +## Scope Alignment + +- **In scope:** + - Database migration to force `cache_enabled = '0'` (FR-040-01). + - New `enable-request-caching` key in `config/features.php` tied to `ENABLE_REQUEST_CACHING` env variable (FR-040-02). + - `SettingsController::getAll` filter to hide `Mod Cache` configs when flag is off (FR-040-03). + - `.env.example` update (FR-040-04). + - Feature test covering S-040-03 and S-040-05. + - Quality gates (NFR-040-01 to NFR-040-04). + - Confirmation that `General.vue` and `InitConfig` require **no changes** (S-040-07 / S-040-08 — handled automatically by the API-level filter). + +- **Out of scope:** + - Removing or refactoring the caching middleware. + - Runtime UI toggle for enabling caching. + - Redis configuration changes. + - Changes to `InitConfig`, `LycheeStateStore`, or `General.vue` — not required because the existing `v-if="cache_enabled !== undefined"` guard in `General.vue` already hides the toggle when the config is absent from the API response. + +## Dependencies & Interfaces + +- `database/migrations/` — migration naming convention (`YYYY_MM_DD_HHMMSS_.php`). +- `App\Models\Extensions\BaseConfigMigration` — not used here; this migration uses a plain `Migration` with a direct `DB::table` update. +- `config/features.php` — existing feature-flag file; pattern already in use for `hide-lychee-SE`, `webshop`, `webhook`, etc. +- `App\Http\Controllers\Admin\SettingsController::getAll` — query builder chain that filters configs; pattern already used for `hide-lychee-SE` and `not_on_docker`. +- `tests/Feature_v2/` — existing PHPUnit feature test suite using `BaseApiWithDataTest`. + +## Assumptions & Risks + +- **Assumptions:** + - The `configs` table and the `cache_enabled` key are always present when the new migration runs (introduced in `2024_12_28_190150_caching_config.php`). + - The `Mod Cache` category string is stable and used only for caching-related configs. +- **Risks / Mitigations:** + - If `cache_enabled` key is missing (e.g., very old or incomplete installation), the migration's `DB::table` update will silently affect 0 rows — which is safe. + - Tests that currently rely on `cache_enabled = 1` being the stored default could fail; check existing test suite for such assumptions before committing. + +## Implementation Drift Gate + +After each increment, run: + +```bash +vendor/bin/php-cs-fixer fix +php artisan test +make phpstan +``` + +Record results in this plan's Scenario Tracking table. + +## Increment Map + +### I1 – Database Migration (≤30 min) + +- _Goal:_ Force `cache_enabled = '0'` for all rows in `configs` (FR-040-01, S-040-01, S-040-02). +- _Preconditions:_ None. +- _Steps:_ + 1. Write the test first: assert `configs` row `cache_enabled` equals `'0'` after the test suite runs (covered implicitly by the full migration run in tests). + 2. Create `database/migrations/2026_05_18_000001_disable_request_caching.php` extending `Migration`. + 3. `up()`: `DB::table('configs')->where('key', 'cache_enabled')->update(['value' => '0']);` + 4. `down()`: no-op (value is not restored — migration is one-directional per FR-040-01 failure path). + 5. Verify test suite passes, PHPStan clean. +- _Commands:_ `php artisan test`, `make phpstan`, `vendor/bin/php-cs-fixer fix` +- _Exit:_ Migration file present and parseable; `php artisan migrate` completes without error; test suite passes. + +### I2 – Feature Flag in `config/features.php` (≤20 min) + +- _Goal:_ Expose `enable-request-caching` feature flag (FR-040-02, S-040-03, S-040-04, S-040-05). +- _Preconditions:_ I1 complete. +- _Steps:_ + 1. Add a new entry in `config/features.php`: + ```php + 'enable-request-caching' => (bool) env('ENABLE_REQUEST_CACHING', false), + ``` + 2. Add `ENABLE_REQUEST_CACHING=false` with a descriptive comment to `.env.example` (FR-040-04). + 3. Run `php artisan config:clear` locally and verify `config('features.enable-request-caching')` defaults to `false`. +- _Commands:_ `vendor/bin/php-cs-fixer fix`, `make phpstan` +- _Exit:_ `config/features.php` updated; `.env.example` updated; PHPStan 0 errors. + +### I3 – Settings Controller Filter (≤30 min) + +- _Goal:_ Hide `Mod Cache` configs from admin settings when feature flag is off (FR-040-03, S-040-03, S-040-04, S-040-05, S-040-07, S-040-08). +- _Preconditions:_ I2 complete. +- _Steps:_ + 1. In `SettingsController::getAll`, add the filter to the `configs` query chain (after the existing `not_on_docker` filter): + ```php + ->when(config('features.enable-request-caching') === false, fn ($q) => $q->where('cat', '!=', 'Mod Cache')) + ``` + 2. **No changes to `General.vue` or `InitConfig` are required.** `General.vue` already guards the cache toggle with `v-if="cache_enabled !== undefined"`. The `load()` function populates `cache_enabled` via `configurations.find(config => config.key === 'cache_enabled')`. When the API omits the `Mod Cache` category, `cache_enabled.value` is `undefined` and the toggle is automatically hidden. Adding an explicit `is_request_caching_enabled` property to `InitConfig` and `LycheeStateStore` would be redundant. + 3. Run full test suite to verify no regressions. +- _Commands:_ `vendor/bin/php-cs-fixer fix`, `php artisan test`, `make phpstan` +- _Exit:_ Controller updated; tests pass; `General.vue` toggle hidden (S-040-07) confirmed indirectly by REST test. + +### I4 – Feature Tests (≤40 min) + +- _Goal:_ Provide explicit test coverage for S-040-03 and S-040-05. +- _Preconditions:_ I3 complete. +- _Steps:_ + 1. Locate or create a `Feature_v2` test class for the Settings endpoint (extending `BaseApiWithDataTest`). + 2. Add test method for S-040-03: with default config (flag off), assert `GET /api/v2/Settings` response does not contain a category named `Mod Cache`. + 3. Add test method for S-040-05: with `config(['features.enable-request-caching' => true])`, assert `GET /api/v2/Settings` response contains a category named `Mod Cache` with the expected config keys. + 4. Run test suite, confirm new tests are green. +- _Commands:_ `php artisan test --filter=Settings`, `make phpstan`, `vendor/bin/php-cs-fixer fix` +- _Exit:_ New tests pass; no other tests broken. + +### I5 – Quality Gates & Documentation (≤20 min) + +- _Goal:_ Ensure full pipeline passes and roadmap/session docs are updated. +- _Preconditions:_ I4 complete. +- _Steps:_ + 1. Run complete quality gate: `vendor/bin/php-cs-fixer fix`, `php artisan test`, `make phpstan`. + 2. Update `roadmap.md`: move Feature 040 from Active to Completed. + 3. Update `_current-session.md` with session summary for Feature 040. +- _Commands:_ `vendor/bin/php-cs-fixer fix`, `php artisan test`, `make phpstan` +- _Exit:_ All gates green; docs updated. + +## Scenario Tracking + +| Scenario ID | Increment / Task reference | Notes | +|-------------|---------------------------|-------| +| S-040-01 | I1 / T-040-01 | Covered by migration `up()` and test suite re-run. | +| S-040-02 | I1 / T-040-01 | Idempotent `UPDATE` with no-op when already `'0'`. | +| S-040-03 | I3, I4 / T-040-04, T-040-05 | Controller filter; explicit feature test. | +| S-040-04 | I3, I4 / T-040-04, T-040-05 | Same filter path as S-040-03. | +| S-040-05 | I3, I4 / T-040-04, T-040-06 | Controller filter absent when flag is `true`; explicit feature test. | +| S-040-06 | I1 / T-040-01 | `down()` is a no-op; migrate:rollback safe. | +| S-040-07 | I3 / T-040-04 | `General.vue` hides toggle automatically — `v-if="cache_enabled !== undefined"` is `false` when config absent from API response. No changes to `General.vue` or `InitConfig` required. | +| S-040-08 | I3 / T-040-04 | `General.vue` shows toggle when config present in API response. | + +## Analysis Gate + +_To be completed before coding begins._ + +- [ ] All four FRs are unambiguous and traceable to tasks. +- [ ] All six scenarios map to at least one increment/task. +- [ ] No open questions logged in `open-questions.md` for Feature 040. +- [ ] Estimated total effort ≤ 140 min (fits within session). + +## Exit Criteria + +- [ ] Migration `2026_05_18_000001_disable_request_caching.php` created and applied. +- [ ] `config/features.php` contains `enable-request-caching` key. +- [ ] `.env.example` documents `ENABLE_REQUEST_CACHING=false`. +- [ ] `SettingsController::getAll` filters `Mod Cache` configs when flag is off. +- [ ] Feature tests for S-040-03 and S-040-05 pass. +- [ ] `vendor/bin/php-cs-fixer fix` exits 0. +- [ ] `php artisan test` exits 0. +- [ ] `make phpstan` exits 0. +- [ ] `roadmap.md` updated. + +## Follow-ups / Backlog + +- Consider adding a diagnostic warning when `ENABLE_REQUEST_CACHING=true` but Redis is not configured as the cache driver. +- If the caching feature is later removed entirely, the `Mod Cache` config category and all three config rows can be dropped via a follow-up migration. + +--- + +*Last updated: 2026-05-18* diff --git a/docs/specs/4-architecture/features/040-disable-request-caching/spec.md b/docs/specs/4-architecture/features/040-disable-request-caching/spec.md new file mode 100644 index 00000000000..7d38445c4b5 --- /dev/null +++ b/docs/specs/4-architecture/features/040-disable-request-caching/spec.md @@ -0,0 +1,174 @@ +# Feature 040 – Disable Request Caching + +| Field | Value | +|-------|-------| +| Status | Planning | +| Last updated | 2026-05-18 | +| Owners | LycheeOrg | +| Linked plan | `docs/specs/4-architecture/features/040-disable-request-caching/plan.md` | +| Linked tasks | `docs/specs/4-architecture/features/040-disable-request-caching/tasks.md` | +| Roadmap entry | #040 | + +> Guardrail: This specification is the single normative source of truth for the feature. Track high- and medium-impact questions in [docs/specs/4-architecture/open-questions.md](docs/specs/4-architecture/open-questions.md), encode resolved answers directly in the Requirements/NFR/Behaviour/UI/Telemetry sections below (no per-feature `## Clarifications` sections), and use ADRs under `docs/specs/5-decisions/` for architecturally significant clarifications (referencing their IDs from the relevant spec sections). + +## Overview + +The Redis-backed request caching feature (`cache_enabled`) has been identified as something that should be **off by default and hidden from the settings UI** unless an operator explicitly opts in via an environment variable. This feature (1) introduces a database migration that forces `cache_enabled = 0` regardless of its current value, and (2) adds a `ENABLE_REQUEST_CACHING` environment variable to `config/features.php` (default `false`) that controls whether the three caching-related config rows (`cache_enabled`, `cache_ttl`, `cache_event_logging`) are visible in the admin settings UI. When `ENABLE_REQUEST_CACHING=false` the settings panel never exposes these options to the admin, preventing accidental re-activation. + +## Goals + +1. Ensure all existing installations have `cache_enabled` forced to `0` after running migrations. +2. Provide a clear operator-level opt-in (`ENABLE_REQUEST_CACHING=true` in `.env`) that makes the caching settings visible in the admin UI. +3. Keep the actual caching middleware (`ResponseCache`, `AlbumRouteCacheRefresher`) intact — the behaviour is gated by `cache_enabled`; this feature simply ensures it is off unless deliberately enabled by the operator. +4. Update `.env.example` to document the new environment variable. +5. Ensure the quality gate (PHPStan, php-cs-fixer, tests) passes with no regressions. + +## Non-Goals + +- Removing or refactoring the caching middleware or route-cache infrastructure. +- Providing a UI toggle to enable/disable request caching at runtime. +- Changing the default cache driver or Redis configuration. +- Adding a new property to `InitConfig` / `LycheeStateStore` to propagate the feature flag to the frontend (see Appendix — this is not required because the existing `v-if` guard in `General.vue` already handles hiding). + +## Functional Requirements + +| ID | Requirement | Success path | Validation path | Failure path | Telemetry & traces | Source | +|----|-------------|--------------|-----------------|--------------|--------------------|--------| +| FR-040-01 | A database migration sets the `cache_enabled` config value to `'0'` unconditionally. | After running `php artisan migrate`, `DB::table('configs')->where('key','cache_enabled')->value('value')` returns `'0'`. | Migration rollback (`down`) reverts nothing (the value is not restored; the migration is one-directional). | If the `configs` table or key does not exist the migration should exit gracefully without error. | None. | Problem statement: "set the value to 0 no matter the current value". | +| FR-040-02 | `config/features.php` exposes a new key `enable-request-caching` sourced from `env('ENABLE_REQUEST_CACHING', false)`. | `config('features.enable-request-caching')` returns `false` unless `.env` contains `ENABLE_REQUEST_CACHING=true`. | Unit test or feature test reads the resolved config value. | N/A — env defaults are always available. | None. | Problem statement: "link it to an env variable in .env ENABLE_REQUEST_CACHING (default to false)". | +| FR-040-03 | The admin settings controller (`SettingsController::getAll`) filters out all configs with `cat = 'Mod Cache'` when `config('features.enable-request-caching')` is `false`. | When `ENABLE_REQUEST_CACHING` is unset or `false`, the API response for `GET /api/v2/Settings` contains no configs whose `cat` is `'Mod Cache'`. | Feature test asserts the category is absent in the response body. | If the feature flag is `true`, all three cache config rows (`cache_enabled`, `cache_ttl`, `cache_event_logging`) appear normally. | None. | Problem statement: "disable the config visibility in the setting". | +| FR-040-04 | `.env.example` is updated to document `ENABLE_REQUEST_CACHING=false` with a descriptive comment. | `.env.example` contains the key and a comment explaining the flag. | Code review / doc review. | N/A. | None. | Coding convention: keep `.env.example` in sync with new env variables. | + +## Non-Functional Requirements + +| ID | Requirement | Driver | Measurement | Dependencies | Source | +|----|-------------|--------|-------------|--------------|--------| +| NFR-040-01 | Migration must be idempotent: running it twice must not throw an error. | Deployment safety. | Test environment running `php artisan migrate` twice without failure. | Laravel migration system. | Standard practice. | +| NFR-040-02 | PHPStan level 6 must report 0 errors after changes. | Code quality gate. | `make phpstan` exits 0. | `phpstan.neon` baseline. | Coding conventions. | +| NFR-040-03 | `php-cs-fixer` must report 0 violations after changes. | Code style gate. | `vendor/bin/php-cs-fixer fix --dry-run` exits 0. | `.php-cs-fixer.php`. | Coding conventions. | +| NFR-040-04 | All existing tests must continue to pass. | Regression safety. | `php artisan test` exits 0. | SQLite test database. | Coding conventions. | + +## Branch & Scenario Matrix + +| Scenario ID | Description / Expected outcome | +|-------------|--------------------------------| +| S-040-01 | **Migration – value forced off.** An installation where `cache_enabled = '1'` runs `php artisan migrate`. After migration `cache_enabled = '0'`. | +| S-040-02 | **Migration – already off.** An installation where `cache_enabled = '0'` runs `php artisan migrate`. Migration completes without error; value remains `'0'`. | +| S-040-03 | **Feature flag default off – settings hidden.** `ENABLE_REQUEST_CACHING` not set (default). `GET /api/v2/Settings` response contains no `Mod Cache` category. | +| S-040-04 | **Feature flag explicitly false – settings hidden.** `.env` has `ENABLE_REQUEST_CACHING=false`. Same outcome as S-040-03. | +| S-040-05 | **Feature flag true – settings visible.** `.env` has `ENABLE_REQUEST_CACHING=true`. `GET /api/v2/Settings` response contains the `Mod Cache` category with `cache_enabled`, `cache_ttl`, `cache_event_logging` rows. | +| S-040-06 | **Migration rollback.** Running `php artisan migrate:rollback` on the new migration completes without error (no value restoration). | +| S-040-07 | **Vue toggle hidden when flag off.** `ENABLE_REQUEST_CACHING` is unset or `false`. In `resources/js/components/settings/General.vue`, the `BoolField` for `cache_enabled` is not rendered because `cache_enabled.value` is `undefined` (config not in API response). No changes to `General.vue` or `InitConfig` are required — the existing `v-if="cache_enabled !== undefined"` guard handles this automatically. | +| S-040-08 | **Vue toggle visible when flag on.** `ENABLE_REQUEST_CACHING=true`. The `GET /api/v2/Settings` response includes `cache_enabled`; `load()` finds it; `v-if="cache_enabled !== undefined"` is `true`; the toggle renders. | + +## Test Strategy + +- **Migration:** Covered implicitly by the full test suite (SQLite re-runs all migrations). No additional migration-specific test needed beyond confirming the test suite passes. +- **Feature flag (config):** Verified by the existing settings feature tests (`tests/Feature_v2/` covering `GET /api/v2/Settings`) when run with default env (flag off) and with the flag forced on via `config(['features.enable-request-caching' => true])`. +- **REST (settings list):** Add or extend a `Feature_v2` test that asserts: + - With flag `false`: response body does not include any config with `cat = 'Mod Cache'`. + - With flag `true`: response body includes at least one config with `cat = 'Mod Cache'`. +- **Vue (`General.vue`):** No code changes and no additional tests required. The existing `v-if="cache_enabled !== undefined"` guard in the `` element already hides the toggle when `cache_enabled` is absent from the API response. The `load()` function in `General.vue` populates `cache_enabled` via `configurations.find(config => config.key === 'cache_enabled')`; when the config is not in the API payload the reactive ref stays `undefined` and the toggle is not rendered. This behaviour is indirectly verified by the REST tests above. +- **`InitConfig` / `LycheeStateStore`:** No changes required. The frontend does not need an explicit feature-flag property because the conditional rendering is already driven by whether the config exists in the API payload. +- **Core / CLI / Docs:** No changes required. + +## Interface & Contract Catalogue + +### Domain Objects + +_None introduced._ + +### API Routes / Services + +| ID | Transport | Description | Notes | +|----|-----------|-------------|-------| +| API-040-01 | REST GET /api/v2/Settings | Returns all visible config categories and their rows. | Affected by FR-040-03: the `Mod Cache` category is omitted when `ENABLE_REQUEST_CACHING=false`. | + +### CLI Commands / Flags + +_None introduced._ + +### Telemetry Events + +_None introduced._ + +### Fixtures & Sample Data + +_None introduced._ + +### UI States + +| ID | State | Trigger / Expected outcome | +|----|-------|---------------------------| +| UI-040-01 | `Mod Cache` category absent from settings panel | `ENABLE_REQUEST_CACHING` is unset or `false`. Admin opens Settings; no caching section visible. Mechanism: backend API filter → `cache_enabled` absent from response → `v-if="cache_enabled !== undefined"` is `false` in `General.vue`. | +| UI-040-02 | `Mod Cache` category present in settings panel | `ENABLE_REQUEST_CACHING=true`. Admin opens Settings; caching section with three rows visible. Mechanism: filter inactive → `cache_enabled` config included in response → `v-if="cache_enabled !== undefined"` is `true`. | + +## Telemetry & Observability + +No new telemetry events. The existing `cache_event_logging` config row remains unchanged. + +## Documentation Deliverables + +- Update `.env.example` to include `ENABLE_REQUEST_CACHING=false` with a comment. +- Update `roadmap.md` (Active Features table → Completed once done). +- Update `_current-session.md`. + +## Fixtures & Sample Data + +None. + +## Spec DSL + +```yaml +routes: + - id: API-040-01 + method: GET + path: /api/v2/Settings +ui_states: + - id: UI-040-01 + description: Mod Cache category absent (flag off) + - id: UI-040-02 + description: Mod Cache category visible (flag on) +``` + +## Appendix + +### Affected files (anticipated) + +| File | Change | +|------|--------| +| `database/migrations/2026_05_18_000001_disable_request_caching.php` | New migration: set `cache_enabled = '0'`. | +| `config/features.php` | Add `enable-request-caching` entry sourced from `ENABLE_REQUEST_CACHING`. | +| `app/Http/Controllers/Admin/SettingsController.php` | Add `->when(config('features.enable-request-caching') === false, fn ($q) => $q->where('cat', '!=', 'Mod Cache'))` filter. | +| `.env.example` | Add `ENABLE_REQUEST_CACHING=false` with comment. | +| `tests/Feature_v2/Settings/...` | Add/extend test for S-040-03 and S-040-05. | + +### `General.vue` and `InitConfig` — no changes required + +`resources/js/components/settings/General.vue` already guards the cache toggle with: + +```html + +``` + +The reactive ref is populated in `load()`: + +```ts +cache_enabled.value = configurations.find((config) => config.key === "cache_enabled"); +``` + +When `SettingsController::getAll` filters out the `Mod Cache` category (FR-040-03), `cache_enabled` is absent from the API response. `configurations.find(...)` returns `undefined`, so `cache_enabled.value` remains `undefined`, and the `v-if` guard keeps the toggle hidden. This end-to-end chain means: + +1. **No changes to `General.vue`** — the `v-if` guard is already in place. +2. **No changes to `InitConfig`** — the frontend does not need a dedicated `is_request_caching_enabled` property. Propagating the flag via `InitConfig` → `LycheeStateStore` would introduce an unnecessary data path when the API-level filter already provides the correct signal. + +This decision is captured as S-040-07 and S-040-08 in the Branch & Scenario Matrix. + +--- + +*Last updated: 2026-05-18* diff --git a/docs/specs/4-architecture/features/040-disable-request-caching/tasks.md b/docs/specs/4-architecture/features/040-disable-request-caching/tasks.md new file mode 100644 index 00000000000..f3ecc5349f0 --- /dev/null +++ b/docs/specs/4-architecture/features/040-disable-request-caching/tasks.md @@ -0,0 +1,108 @@ +# Feature 040 Tasks – Disable Request Caching + +_Status: Draft_ +_Last updated: 2026-05-18_ + +> Keep this checklist aligned with the feature plan increments. Stage tests before implementation, record verification commands beside each task, and prefer bite-sized entries (≤90 minutes). +> **Mark tasks `[x]` immediately** after each one passes verification—do not batch completions. Update the roadmap status when all tasks are done. +> When referencing requirements, keep feature IDs (`FR-`), non-goal IDs, and scenario IDs (`S-040-`) inside the same parentheses immediately after the task title. +> When new high- or medium-impact questions arise during execution, add them to [docs/specs/4-architecture/open-questions.md](docs/specs/4-architecture/open-questions.md) instead of informal notes, and treat a task as fully resolved only once the governing spec sections and, when required, ADRs reflect the clarified behaviour. + +## Checklist + +### I1 – Database Migration + +- [x] T-040-01 – Create migration to force `cache_enabled = '0'` (FR-040-01, S-040-01, S-040-02, S-040-06). + _Intent:_ Create `database/migrations/2026_05_18_000001_disable_request_caching.php` extending `Illuminate\Database\Migrations\Migration`. The `up()` method updates `configs` where `key = 'cache_enabled'` to `value = '0'`. The `down()` method is intentionally a no-op (migration is one-directional — the old value is not restored). + _Verification commands:_ + - `php artisan test` — full suite must pass (migration is applied to SQLite test DB on every run). + - `make phpstan` — 0 errors. + - `vendor/bin/php-cs-fixer fix` — 0 violations (run with `--dry-run` to check; fix without `--dry-run` to apply). + _Notes:_ Use `DB::table('configs')->where('key', 'cache_enabled')->update(['value' => '0'])` in `up()`. No `BaseConfigMigration` inheritance needed since this is an update, not an insert. License header required (see coding conventions). + +### I2 – Feature Flag + +- [x] T-040-02 – Add `enable-request-caching` feature flag to `config/features.php` (FR-040-02, S-040-03, S-040-04, S-040-05). + _Intent:_ Append a new entry to `config/features.php` following the existing pattern: + ```php + 'enable-request-caching' => (bool) env('ENABLE_REQUEST_CACHING', false), + ``` + Include a block comment following the style of neighbouring entries, explaining that setting `ENABLE_REQUEST_CACHING=true` makes the caching-related settings visible in the admin UI. + _Verification commands:_ + - `make phpstan` — 0 errors. + - `vendor/bin/php-cs-fixer fix` — 0 violations. + _Notes:_ Default is `false`; no `.env` change required for the safe default. + +- [x] T-040-03 – Update `.env.example` to document `ENABLE_REQUEST_CACHING` (FR-040-04). + _Intent:_ Add `ENABLE_REQUEST_CACHING=false` with a descriptive comment to `.env.example`, near the other feature-flag entries (e.g., near `WEBHOOK_ENABLED`, `WEBSHOP_ENABLED`, or `HIDE_LYCHEE_SE_CONFIG`). + _Verification commands:_ + - Manual review: confirm key and comment are present. + _Notes:_ No functional impact; documentation only. + +### I3 – Settings Controller Filter + +- [x] T-040-04 – Filter `Mod Cache` configs out of settings response when `ENABLE_REQUEST_CACHING` is off (FR-040-03, S-040-03, S-040-04, S-040-05, S-040-07, S-040-08). + _Intent:_ In `app/Http/Controllers/Admin/SettingsController::getAll`, add a `->when(...)` filter to the `configs` eager-load query chain. Specifically, after the existing `->when($docker_info->isDocker(), ...)` clause, add: + ```php + ->when(config('features.enable-request-caching') === false, fn ($q) => $q->where('cat', '!=', 'Mod Cache')) + ``` + This mirrors the existing `hide-lychee-SE` pattern (`->when(config('features.hide-lychee-SE', false) === true, fn ($q) => $q->where('cat', '!=', 'lychee SE'))`). + + **`General.vue` and `InitConfig` — no code changes required.** `resources/js/components/settings/General.vue` already guards the cache toggle with `v-if="cache_enabled !== undefined"`. The `load()` function populates `cache_enabled` via `configurations.find(config => config.key === 'cache_enabled')`. When this task's filter removes `Mod Cache` from the API response, `cache_enabled.value` is `undefined` and the toggle is not rendered — automatically satisfying S-040-07. No new `InitConfig` property or `LycheeStateStore` field is needed. + _Verification commands:_ + - `php artisan test` — full suite must pass. + - `make phpstan` — 0 errors. + - `vendor/bin/php-cs-fixer fix` — 0 violations. + _Notes:_ This is a one-line change to the existing query chain. The Vue hiding behaviour (S-040-07) is confirmed indirectly by the REST-level tests in T-040-05 / T-040-06. + +### I4 – Feature Tests + +- [x] T-040-05 – Write feature test: `Mod Cache` category absent when flag is off (S-040-03, S-040-04). + _Intent:_ In `tests/Feature_v2/` (extending `BaseApiWithDataTest`), add a test method that: + 1. Ensures `config('features.enable-request-caching')` is `false` (default). + 2. Calls `GET /api/v2/Settings` as admin. + 3. Asserts the response JSON does not contain any item with a `title` or `key` of `Mod Cache` (or that the entire category is absent). + _Verification commands:_ + - `php artisan test --filter=` — new test must pass green. + - `make phpstan` — 0 errors. + - `vendor/bin/php-cs-fixer fix` — 0 violations. + _Notes:_ Locate the existing settings test file in `tests/Feature_v2/` to find the correct base class and endpoint path. + +- [x] T-040-06 – Write feature test: `Mod Cache` category present when flag is on (S-040-05). + _Intent:_ Add a companion test method in the same test class that: + 1. Forces `config(['features.enable-request-caching' => true])`. + 2. Calls `GET /api/v2/Settings` as admin. + 3. Asserts the response JSON contains a category with at least one config whose `key` is `cache_enabled`. + _Verification commands:_ + - `php artisan test --filter=` — new test must pass green. + - `make phpstan` — 0 errors. + - `vendor/bin/php-cs-fixer fix` — 0 violations. + _Notes:_ Use `$this->withConfig(['features.enable-request-caching' => true])` or `config([...])` override pattern already used elsewhere in the test suite. + +### I5 – Quality Gates & Documentation + +- [x] T-040-07 – Run full quality gate (NFR-040-01 to NFR-040-04). + _Intent:_ Execute the complete quality gate sequence and confirm all checks pass. + _Verification commands:_ + - `vendor/bin/php-cs-fixer fix` + - `php artisan test` + - `make phpstan` + _Notes:_ All three must exit 0 before proceeding. + +- [ ] T-040-08 – Update `roadmap.md`: move Feature 040 from Active to Completed. + _Intent:_ Add Feature 040 row to the Completed table with today's date and a one-line summary. Remove it from Active Features. Update the "Last updated" footer. + _Verification commands:_ Manual review. + _Notes:_ Follow the pattern of completed rows (e.g., Feature 037). + +- [ ] T-040-09 – Update `_current-session.md` with Feature 040 summary. + _Intent:_ Replace the Feature 037 session context with a Feature 040 summary covering what was implemented and confirming all tasks complete. + _Verification commands:_ Manual review. + _Notes:_ Keep the session doc as the single live snapshot per the session conventions. + +## Notes / TODOs + +- If `GET /api/v2/Settings` route or response shape differs from assumed, update T-040-05/T-040-06 assertions accordingly. +- `Mod Cache` is the exact category name used in the migration `2024_12_28_190150_caching_config.php` and is stable. +- The `down()` no-op in T-040-01 is intentional per FR-040-01 failure path: once the caching is disabled, we do not restore the previous value on rollback. +- **`General.vue` requires no changes.** The toggle `` is already guarded. When the SettingsController filter (T-040-04) removes `Mod Cache` from the API response, `cache_enabled.value` stays `undefined` and the toggle is hidden automatically. This was confirmed by code inspection (see spec Appendix). +- **`InitConfig` and `LycheeStateStore` require no changes.** No new `is_request_caching_enabled` property is needed — the API-level filter is the sole signal. diff --git a/docs/specs/4-architecture/roadmap.md b/docs/specs/4-architecture/roadmap.md index 2199164ffc1..810d6da620a 100644 --- a/docs/specs/4-architecture/roadmap.md +++ b/docs/specs/4-architecture/roadmap.md @@ -6,6 +6,7 @@ High-level planning document for Lychee features and architectural initiatives. | Feature ID | Name | Status | Priority | Assignee | Started | Updated | Progress | |------------|------|--------|----------|----------|---------|---------|----------| +| 040 | Disable Request Caching | Planning | P2 | LycheeOrg | 2026-05-18 | 2026-05-18 | Spec, plan, tasks drafted. 9 tasks across 5 increments (I1 migration, I2 feature flag + .env.example, I3 controller filter, I4 feature tests, I5 quality gates). No open questions. Ready to begin T-040-01. | ## Paused Features @@ -110,4 +111,4 @@ features/ --- -*Last updated: 2026-04-22 (Feature 037 completed — Admin Dashboard & `/admin/` URL Reorg)* +*Last updated: 2026-05-18 (Feature 040 planned — Disable Request Caching)* diff --git a/docs/specs/_current-session.md b/docs/specs/_current-session.md index 7ea251aa51b..2b3cca4d04c 100644 --- a/docs/specs/_current-session.md +++ b/docs/specs/_current-session.md @@ -1,73 +1,61 @@ # Current Session -_Last updated: 2026-04-22_ +_Last updated: 2026-05-18_ ## Active Features -**Feature 037 – Admin Dashboard & `/admin/` URL Reorganisation** -- Status: Ready for Implementation (spec + plan + tasks complete) +**Feature 040 – Disable Request Caching** +- Status: Planning (spec + plan + tasks complete) - Priority: P2 - License: Open -- Started: 2026-04-22 +- Started: 2026-05-18 - Dependencies: None ## Session Summary -User requested a new admin dashboard page listing admin tools with a cacheable stats overview, a toggleable admin setting that replaces the left-menu "Admin" submenu, and migration of the major admin/maintenance pages (plus matching Vue views) under `/admin/`. Diagnostics and Logs keep their existing URLs. +User requested Feature 040: disable the Redis request-caching functionality. Two deliverables: +1. A database migration that forces `cache_enabled = '0'` regardless of its current value. +2. A feature flag `ENABLE_REQUEST_CACHING` (default `false`) in `config/features.php` that controls visibility of the `Mod Cache` config category in the admin settings UI. -### Feature 037: Admin Dashboard & `/admin/` URL Reorg +### Feature 040: Disable Request Caching -**Status:** spec.md + plan.md + tasks.md complete; ready to begin T-037-01. +**Status:** spec.md + plan.md + tasks.md complete; ready to begin T-040-01. -**Locked decisions (via Q-037-01 … Q-037-08):** -- **Scope (Q-037-01):** 9 admin-only pages move under `/admin/`: Settings, Users, User Groups, Purchasables, Contact Messages, Webhooks, Moderation, Maintenance, Jobs. Diagnostics (`/diagnostics`) and Logs (`/Logs`) unchanged; Clockwork stays external. -- **Stats overview (Q-037-02):** 7 metrics (photos, albums, users, storage bytes, queued jobs, failed-24h jobs, last-successful-job timestamp) via `GET /api/v2/Admin/Stats`, cached with `Cache::remember('admin.stats', 300, …)`. Dashboard "Refresh" action busts cache (`?force=1`). -- **Toggle (Q-037-03):** Config key `use_admin_dashboard`, boolean, default `1`. When ON: left-menu "Admin" collapses to one link → `/admin`; when OFF: legacy nested submenu. -- **Views (Q-037-04):** Mirror URL scope. Nine moved views live under `resources/js/views/admin/`; Diagnostics.vue stays at top level. New `AdminDashboard.vue` added. -- **Label (Q-037-05):** "Admin Dashboard" — i18n key `admin-dashboard.title`, route name `admin-dashboard`. -- **Settings category (Q-037-06):** Config row uses `cat = 'config'` (operator override of the recommended `access_permissions`). -- **URL policy (Q-037-07):** Greenfield — no redirects from old paths (aligns with AGENTS.md). -- **Partial-admin handling (Q-037-08):** Collapsed "Admin" link appears for anyone with `canSeeAdmin` (union of 5 capability flags). Dashboard tiles are gated per individual capability; the stats block and `GET /api/v2/Admin/Stats` are gated on `settings.can_edit` (full admin only). Partial-admins (e.g., User-Groups-only, logs-only) land on a dashboard showing only their authorised tiles and no stats section; calling the stats endpoint returns 403. +**No open questions.** All requirements are clear from the problem statement. -**Plan increments (7 × ≤90 min, 30 tasks total):** -- **I1 – Config migration for `use_admin_dashboard`** (T-037-01..03): new migration row `cat = config`, default `1`; surface through `LycheeStateStore`. -- **I2 – `AdminStatsOverview` DTO + `AdminStatsService` + cache** (T-037-04..06): pure PHP DTO, 7 metrics, 5-min TTL, `force` bypass, partial-error path does **not** cache. -- **I3 – REST endpoint + Feature_v2 tests** (T-037-07..10): `GET /api/v2/Admin/Stats` gated on `SettingsPolicy::CAN_EDIT`, telemetry `admin.stats.fetch` + `admin.stats.refresh`. -- **I4 – Router + view relocation** (T-037-11..15): router Vitest (RED) → move 9 views to `resources/js/views/admin/`, rewrite 9 paths under `/admin/`, old paths 404. -- **I5 – `AdminDashboard.vue`** (T-037-16..20): `useAdminTiles` composable, PrimeVue tile grid + stats block (full admin only) + Refresh, Vitest covers full admin / partial admin / refresh / errors / keyboard nav. -- **I6 – Left-menu composable branch + 22-locale parity** (T-037-21..24): toggle branch in `useLeftMenu`, new i18n keys authored in `en.json` then propagated to 21 other locales. -- **I7 – Quality gates + docs + roadmap** (T-037-25..30): OpenAPI snapshot, knowledge-map, operator how-to, full pipeline, roadmap closeout. +**Plan increments (5 × ≤40 min, 9 tasks total):** +- **I1 – Migration** (T-040-01): force `cache_enabled = '0'` via `DB::table('configs')` update; `down()` no-op. +- **I2 – Feature flag** (T-040-02, T-040-03): add `enable-request-caching` to `config/features.php` sourced from `ENABLE_REQUEST_CACHING` env var (default false); update `.env.example`. +- **I3 – Controller filter** (T-040-04): `SettingsController::getAll` filters out `Mod Cache` category when flag is off. +- **I4 – Feature tests** (T-040-05, T-040-06): two `Feature_v2` tests — one asserting the category is hidden (flag off), one asserting it is visible (flag on). +- **I5 – Quality gates + docs** (T-040-07, T-040-08, T-040-09): full pipeline green; roadmap and session docs updated. **Key artefacts produced:** -- Spec: [docs/specs/4-architecture/features/037-admin-dashboard/spec.md](docs/specs/4-architecture/features/037-admin-dashboard/spec.md) -- Plan: [docs/specs/4-architecture/features/037-admin-dashboard/plan.md](docs/specs/4-architecture/features/037-admin-dashboard/plan.md) -- Tasks: [docs/specs/4-architecture/features/037-admin-dashboard/tasks.md](docs/specs/4-architecture/features/037-admin-dashboard/tasks.md) -- Open-questions log updated (Q-037-01 … Q-037-08 all marked resolved) -- Roadmap row: Ready for Implementation - -**Key scenarios (S-037-01 … S-037-18):** see spec Branch & Scenario Matrix; the plan's Scenario Tracking table maps each to the owning increment/test. S-037-16..18 cover partial-admin paths (collapsed menu visibility, 403 on stats endpoint, legacy submenu with single capability). +- Spec: [docs/specs/4-architecture/features/040-disable-request-caching/spec.md](docs/specs/4-architecture/features/040-disable-request-caching/spec.md) +- Plan: [docs/specs/4-architecture/features/040-disable-request-caching/plan.md](docs/specs/4-architecture/features/040-disable-request-caching/plan.md) +- Tasks: [docs/specs/4-architecture/features/040-disable-request-caching/tasks.md](docs/specs/4-architecture/features/040-disable-request-caching/tasks.md) +- Roadmap row added to Active Features. ## Next Steps -1. Run the analysis-gate checklist on the agreed spec/plan/tasks bundle before coding. -2. Start implementation at **T-037-01** (config migration) following tests-before-code ordering. -3. After each task passes verification, tick the box in `tasks.md` immediately (do not batch completions). -4. On completion of I7, move the roadmap row from "Active" to "Completed" and archive the session block. +1. Run the analysis gate checklist before coding. +2. Start implementation at **T-040-01** (migration) following tests-before-code ordering. +3. After each task passes verification, tick the box in `tasks.md` immediately. +4. On completion of I5, move the roadmap row from "Active" to "Completed". ## Open Questions -None for Feature 037 (Q-037-01 … Q-037-08 all resolved). +None for Feature 040. ## References -**Feature 037:** -- Spec: [037-admin-dashboard/spec.md](docs/specs/4-architecture/features/037-admin-dashboard/spec.md) -- Plan: [037-admin-dashboard/plan.md](docs/specs/4-architecture/features/037-admin-dashboard/plan.md) -- Tasks: [037-admin-dashboard/tasks.md](docs/specs/4-architecture/features/037-admin-dashboard/tasks.md) -- Open questions (resolved): [open-questions.md](docs/specs/4-architecture/open-questions.md) -- Admin controllers: [app/Http/Controllers/Admin/](app/Http/Controllers/Admin/) -- Left-menu composable: [resources/js/composables/contextMenus/leftMenu.ts](resources/js/composables/contextMenus/leftMenu.ts) -- Vue router: [resources/js/router/routes.ts](resources/js/router/routes.ts) +**Feature 040:** +- Spec: [040-disable-request-caching/spec.md](docs/specs/4-architecture/features/040-disable-request-caching/spec.md) +- Plan: [040-disable-request-caching/plan.md](docs/specs/4-architecture/features/040-disable-request-caching/plan.md) +- Tasks: [040-disable-request-caching/tasks.md](docs/specs/4-architecture/features/040-disable-request-caching/tasks.md) +- Existing caching migration: [database/migrations/2024_12_28_190150_caching_config.php](database/migrations/2024_12_28_190150_caching_config.php) +- Features config: [config/features.php](config/features.php) +- Settings controller: [app/Http/Controllers/Admin/SettingsController.php](app/Http/Controllers/Admin/SettingsController.php) **Common:** - Roadmap: [roadmap.md](docs/specs/4-architecture/roadmap.md) @@ -77,4 +65,5 @@ None for Feature 037 (Q-037-01 … Q-037-08 all resolved). **Session Context for Handoff:** -Feature 037 spec, plan, and tasks are complete (30 tasks across 7 increments, all ≤90 min, tests-before-code). All 8 open questions resolved. Next author to pick up: run the analysis gate, then begin T-037-01 (config migration for `use_admin_dashboard`). I4 (router/views) can begin in parallel with I2/I3 once I1 is merged. +Feature 040 spec, plan, and tasks are complete (9 tasks across 5 increments, all ≤40 min, tests-before-code). No open questions. Next author to pick up: run the analysis gate, then begin T-040-01 (migration to force `cache_enabled = '0'`). All increments are sequential with no blocking dependencies. + diff --git a/tests/Feature_v2/Settings/GetAllSettingsTest.php b/tests/Feature_v2/Settings/GetAllSettingsTest.php index c9d33df0d46..dbbc7ba526b 100644 --- a/tests/Feature_v2/Settings/GetAllSettingsTest.php +++ b/tests/Feature_v2/Settings/GetAllSettingsTest.php @@ -69,6 +69,9 @@ public function testGetAllSettingsAdmin(): void ], ]); + // Mod Cache must be hidden by default (ENABLE_REQUEST_CACHING defaults to false). + $response->assertJsonMissing(['cat' => 'Mod Cache']); + $response = $this->actingAs($this->admin)->getJson('Settings::init'); $this->assertOk($response); $response->assertJson([ @@ -80,4 +83,22 @@ public function testGetAllSettingsAdmin(): void $response = $this->actingAs($this->admin)->getJson('Settings::getLanguages'); $this->assertOk($response); } + + public function testModCacheVisibleWhenFeatureEnabled(): void + { + config(['features.enable-request-caching' => true]); + + $response = $this->actingAs($this->admin)->getJson('Settings'); + $this->assertOk($response); + $response->assertJsonFragment(['cat' => 'Mod Cache']); + } + + public function testModCacheHiddenWhenFeatureDisabled(): void + { + config(['features.enable-request-caching' => false]); + + $response = $this->actingAs($this->admin)->getJson('Settings'); + $this->assertOk($response); + $response->assertJsonMissing(['cat' => 'Mod Cache']); + } } \ No newline at end of file