Skip to content

Settings & Configuration Sprint Plan — Cross-Functional War Room Output

Context: 85 issues from Settings & Configuration Deep-Dive Generated: 2026-04-07 | Last Updated: 2026-04-11 | Method: Cross-Functional War Room (PM + Engineer + Designer) Post-Update Note (Apr 11b): FE-H3 (keepPreviousData → placeholderData), FE-M7 (settings non-blocking in dashboard layout + toast fallback), FE-M6 (getSettingByGroup → selectSettingByGroup Redux selector rename), FE-M3 (key-splitting handles dotless & multi-dot keys across 4 locations), FE-M4 (getSettingByGroup error return []{}), FE-L4 (phantom redux-persist middleware removed), FE-M2 (dev-mode delete mock removed), FE-L2 (useBackupCodes response unwrap + consumer updated) applied. Sprint 1: 4 resolved. Sprint 2: 4 additional resolved. Backlog: 1 resolved. Total resolved: 20/85. Participants: PM (business impact), Engineer (effort/risk), Designer (UX impact) Related: Deep-Dive Post-Update Note (Apr 11): FE-C1 (useSetupTwoFactor → useMutation), FE-C2 (useBackupCodes → useMutation), FE-C3 (useVerifyOTP default endpoint), FE-H1 (errors returned → thrown), FE-H2 (invalidateQueries v5 syntax) applied. Ship blockers: 12 remaining, 4 resolved. Sprint 1: 1 resolved. Total resolved: 12/85. Post-Update Note (Apr 7): Codebase re-verified April 7. UserSetting backup codes refactored (crypto.randomBytes + timingSafeEqual now used). Several frontend fixes landed. 7 issues resolved, 3 modified. Sprint items updated after comparative analysis.


Comparative Analysis Scorecard (Apr 7)

Scored every sprint item against the current codebase after re-verification:

Phase Items Resolved Still Needed Modified Notes
Ship Blockers 16 4 12 0 FE-C1, FE-C2, FE-C3, FE-H1 resolved (Apr 11)
Sprint 1 16 4 10 2 FE-H2 resolved (Apr 11); FE-H3, FE-M7, FE-M6 resolved (Apr 11b); US-H1 modified (exported but unwired), US-H4 needs re-verify
Sprint 2 19 6 13 0 FE-M1 (revalidate) resolved, FE-M5 (error msg) resolved, FE-M3/M4/L4/M2 resolved (Apr 11b)
Backlog 25 6 19 1 US-C5, US-C1, US-H3 resolved (backup codes refactored); FE-L1 not found; setting-field dropdown now dynamic; FE-L2 resolved (Apr 11b)
Total 85 20 54 3 FE-C1/C2/C3/H1/H2 fixed Apr 11; FE-H3/M7/M6/M3/M4/L4/M2/L2 fixed Apr 11b; backup code refactoring was main earlier improvement

Triage Summary

Bucket Criteria Count Effort
Ship Blocker Cannot deploy. Security exploit, data loss, or core feature broken. 12 (4 resolved) ~38 min
Sprint 1 Settings system doesn't work for non-super_admin. Validation, dead code, response bugs. 16 (4 resolved) ~6.9 hrs
Sprint 2 Data model cleanup, seed robustness, frontend state architecture, testing. 19 ~13 hrs
Backlog Low-impact cosmetics, dead code, minor inconsistencies. 20 Ongoing

Ship Blockers (Pre-Release Gate)

Timeline: ~75 minutes, 1 developer Exit criteria: All 2FA secrets hidden. AppConfig accessible to admins. No data loss on delete. Core mutations work.

Order Issue Description File Change Effort
1 US-C2 Set twoFactorSecret to select: false UserSetting.ts:44 Add select: false to column decorator (also tempTwoFactorSecret) 1 min
2 US-H2 Remove TOTP secret from 2FA setup response user_setting.service.ts:173,185 Remove secret: secret.base32 from return objects 2 min
3 US-C4 Add permission check to getSettingById user_setting.service.ts:407 Add await checkPermissionAndThrow("user-setting.view", "view user settings"); 2 min
4 AC-C1 Fix AppConfig permission naming to app_config.* app_config.service.ts:42,71 Change create_app_config.createapp_config.create, update_app_config.updateapp_config.update 5 min
5 AC-C2 Seed AppConfig permissions seed/data/permission.ts Add app_config.create, app_config.read, app_config.update, app_config.delete 5 min
6 SET-C2 Seed setting.bulkUpdate permission seed/data/permission.ts Add setting.bulkUpdate to permission seed data 2 min
7 AC-C3 Fix hard delete → soft delete in AppConfig app_config.service.ts:117 Change configRepo.delete(id)configRepo.softDelete(id) 1 min
8 AC-C5 Strip encrypted values from API responses app_config.service.ts:19-20,64,107 Exclude value field when isEncrypted: true, or decrypt before return 10 min
9 AC-H2 Add graceful error for missing MASTER_KEY helper/encryption.ts:6 Wrap Buffer.from(process.env.MASTER_KEY!, 'hex') in try-catch or early env validation 5 min
10 SET-C3 Add unique constraint on Setting key column entity/Setting.ts Add @Index({ unique: true }) decorator on key column 5 min
11 SET-H4 Fix createSetting error type setting.service.ts:47 Change throw new Error(...)throw new ApiError(httpStatus.CONFLICT, ...) 2 min
12 FE-C1 Convert useSetupTwoFactor from useQuery to useMutation api/settings/account-setting.js:16-25 RESOLVED (Apr 11)useMutation applied, consumer setup-two-factor-verification.jsx updated to trigger mutate() via useEffect on modal open 10 min
13 FE-C2 Convert useBackupCodes from useQuery to useMutation api/settings/account-setting.js:83-89 RESOLVED (Apr 11)useMutation applied, consumer two-factor-backup.jsx updated to use .mutate() 10 min
14 FE-C3 Fix useVerifyOTP default endpoint api/settings/account-setting.js:56 RESOLVED (Apr 11) — Default case now throws Error("Unknown OTP action") instead of calling non-existent /2fa/verify 2 min
15 FE-H1 Fix errors returned instead of thrown api/settings/account-setting.js:7-9,19-22 RESOLVED (Apr 11) — Removed try/catch from useAccountSettings and useSetupTwoFactor queryFns; errors now propagate to React Query 5 min
16 New Fix email OTP generation security user_setting.service.ts:159 Replace Math.random()crypto.randomInt(100000, 999999) in startEmail2FASetup 2 min

Verification Checklist

After completing all 16 ship blockers:

  • GET /user-settings/:id without user-setting.view permission returns 403
  • twoFactorSecret is NOT included in any API response (check /user-settings/me, /2fa/google/setup, /2fa/google/verify)
  • POST /app-configs with app_config.create permission succeeds (non-super_admin)
  • DELETE /app-configs/:id soft-deletes (row still in DB with deletedAt)
  • GET /app-configs does NOT return encrypted ciphertext values
  • App starts without MASTER_KEY env var without crashing (graceful error)
  • POST /settings with duplicate key returns 409 (not 500)
  • yarn seed creates all app_config.* and setting.bulkUpdate permissions
  • 2FA setup modal does NOT fire PUT on mount — only on explicit user action — RESOLVED Apr 11 (FE-C1)
  • Backup codes do NOT regenerate on component re-render — RESOLVED Apr 11 (FE-C2)
  • OTP verification with invalid action shows proper error (not 404) — RESOLVED Apr 11 (FE-C3)

Sprint 1: Make Settings Actually Work (Week 1)

Timeline: ~6.9 hours, 1 developer (3 FE items resolved Apr 11) North Star: Non-super_admin admin can manage all settings. Validation protects all endpoints. No dead code. Exit criteria: Log in as "admin" role, verify Site Settings CRUD, AppConfig CRUD, 2FA flow all work. No 500 errors.

Backend — Setting Module (3.5 hrs)

# Issue(s) Description File(s) Effort
S1-1 SET-C5 Create Setting validation schemas. Add Zod schemas for create/update/bulkUpdate. Wire validateRequest middleware to POST/PATCH routes. Validate key, display_name, value, type, group, order. setting.validation.ts (REWRITE), setting.routes.ts 1 hr
S1-2 SET-C4, SET-H2 Remove dead code. Delete generateBackupCodes (lines 22-32), verifyBackupCode (lines 170-188), unused imports (Not, hashedPassword, verifyPassword, bcrypt, crypto). setting.service.ts 15 min
S1-3 SET-H1 Remove backupCodes from Setting entity. Create DB migration to drop column. Remove from seed data. entity/Setting.ts, seed/data/setting.ts, migration 30 min
S1-4 SET-M1 Fix updateSettingById response. Change message from "created" to "updated", status from 201 to 200. setting.controller.ts:76-81 5 min
S1-5 SET-M2 Fix bulkUpdateSettings response. Change data: "updates"data: updates (return actual data). setting.controller.ts:91 5 min
S1-6 SET-M4 Fix LIKE-pattern injection in prefix. Escape % and _ wildcards in user-provided prefix before passing to Like(). setting.service.ts:163 15 min
S1-7 SET-M5 Add transaction to bulkUpdateSettings. Wrap Promise.all in queryRunner.startTransaction() to prevent partial updates on failure. setting.service.ts:85-108 30 min
S1-8 SET-H5 Add file-size limit to multer. Set limits: { fileSize: 5 * 1024 * 1024 } (5MB) on upload middleware. setting.routes.ts:14,16 10 min
S1-9 SET-H3 Add setting.view permission check to read methods OR remove from seed. Since GET routes are intentionally public for SSR, document this decision and remove unused setting.view from seed. seed/data/permission.ts, docs 15 min

Backend — AppConfig Module (1.5 hrs)

# Issue(s) Description File(s) Effort
S1-10 AC-M2 Add decryption to read operations. When isEncrypted: true, call decrypt() before returning in loadAllConfigs and getConfigById. app_config.service.ts:19-20,64 45 min
S1-11 AC-H1 Fix silent encryption default. Change isEncrypted default to false. Require explicit isEncrypted: true when storing secrets. entity/AppConfig.ts:26 5 min
S1-12 AC-C4 Uncomment getConfigByKey permission check OR add internal-only annotation. Since StripeService calls this internally, create getConfigByKeyInternal() without check, keep external one with check. app_config.service.ts:34 30 min
S1-13 AC-M3 Add max length to key validation. Add .max(150) to key field in Zod schema. app_config.validation.ts:7 5 min

Backend — UserSetting Module (1 hr)

# Issue(s) Description File(s) Effort
S1-14 US-H1 (MODIFIED) Wire email 2FA verification flow. startEmail2FASetup is now exported but no route calls it. Add PUT /2fa/email/setup route handler that calls startEmail2FASetup, then requires OTP verification before enabling email 2FA. user_setting.routes.ts, user_setting.controller.ts 45 min
S1-15 US-H4 Fix inconsistent TOTP window. Align setup verification and login verification to same window value (recommend window: 1). user_setting.service.ts:205, TwoFactorPolicy.ts:46 10 min
S1-16 US-M5 Add confirmation before destroying Google auth. When switching to email 2FA, check if Google 2FA is active and require explicit confirmation parameter. user_setting.service.ts:114 30 min

Frontend (2 hrs)

# Issue(s) Description File(s) Effort
S1-17 FE-H2 Fix invalidateQueries v4 → v5 syntax. api/settings/account-setting.js:75-76 RESOLVED (Apr 11) — Updated to { queryKey: [...] } syntax
S1-18 FE-H3 Fix deprecated keepPreviousData. api/settings/index.js:25,192 RESOLVED (Apr 11)placeholderData: keepPreviousData applied, import added from @tanstack/react-query
S1-19 FE-M7 Handle settings API failure in dashboard layout. layout.js (dashboard) RESOLVED (Apr 11) — Settings removed from loading gate (non-blocking), fallback title with nullish coalescing, toast on error via react-hot-toast
S1-20 FE-M6 Fix getSettingByGroup name collision. store/slices/settingSlice.js:22, consumers RESOLVED (Apr 11) — Redux selector renamed to selectSettingByGroup, updated in layout.js, footer-credit.jsx, theme-control.jsx

Sprint 1 Acceptance Test

1. yarn seed                           → Seeds roles, permissions (incl. app_config.*, setting.bulkUpdate)
2. Login as super_admin                → Create "admin" role with setting.*, app_config.* permissions
3. Create user with "admin" role       → User created
4. Login as new admin user             → Dashboard loads (no infinite loading on settings failure)
5. Navigate to Site Settings           → Settings grouped by tabs, CRUD works
6. Create a new setting                → Validation enforced (key required, no arbitrary fields)
7. Bulk-update settings                → Succeeds, returns actual updated data (not string "updates")
8. Update a single setting             → Returns "updated" message with 200 status
9. Create duplicate setting key        → Returns 409 Conflict (not 500)
10. AppConfig CRUD as admin            → All operations work (create, read, update, delete)
11. AppConfig encrypted values         → Values decrypted in responses
12. 2FA setup                          → No auto-fire on page load, only on user action
13. Backup codes                       → Generated only on explicit click, not on re-render
14. No console errors                  → Clean

Sprint 2: Clean Foundation (Week 2-3)

Timeline: ~13 hours, 1-2 developers North Star: Clean data model, robust seeding, unified frontend state, first integration test. Exit criteria: Dead code removed. Seed incremental. State architecture simplified. Integration test passes.

Data Model & Seed (3.5 hrs)

# Issue(s) Description Effort
S2-1 SET-L3 Add createdAt/updatedAt timestamps to Setting entity (match AppConfig/UserSetting). Create migration. 30 min
S2-2 SET-L4 Add index on group column for Setting entity. Key already gets unique index from ship blocker. 15 min
S2-3 SET-M9 Fix skip-if-any-exist seeding logic. Make incremental — upsert missing settings, don't skip all if any exist. 1 hr
S2-4 SET-M6, SET-M7 Fix seed data typos: "SASS Boilerplate" → "SaaS Boilerplate", "Headquaters" → "Headquarters". 5 min
S2-5 SET-M8 Replace 6 hardcoded Cloudinary URLs with placeholder paths or configurable defaults. 15 min
S2-6 SET-L5 Remove admin.test_5 / test_dropdown test data from production seed. 5 min
S2-7 SET-L6 Fix duplicate order values within same group in seed data. 10 min
S2-8 US-M1 Remove or wire dead startEmail2FASetup if S1-14 didn't fully resolve it. Clean up any remaining dead 2FA code. 30 min
S2-9 AC-H3 Fix updateAppConfig duplicate key check — exclude current record from uniqueness check (use Not(id) in query). 30 min

Security Hardening (2 hrs)

# Issue(s) Description Effort
S2-10 US-M3 Fix "Sass Boilerplate" → "SaaS Boilerplate" in authenticator app name. 5 min
S2-11 US-M4 Remove unused TwoFactorProvider.SMS enum value. 5 min
S2-12 US-M6 Remove unnecessary @Index() on twoFactorSecret column. 5 min
S2-13 AC-L3 Add audit logging for AppConfig secret management CRUD operations. 1 hr
S2-14 AC-L4 Add max length on AppConfig value field in Zod validation. 5 min
S2-15 AC-M4 Fix double space in error message "Config not found". 1 min
S2-16 AC-M1 Add pagination to loadAllConfigs (ordering + limit). 30 min

Frontend (3 hrs)

# Issue(s) Description Effort
S2-17 FE-M3 Fix key-splitting logic to handle keys without dots. Add fallback for undefined field name. RESOLVED (Apr 11b) — Applied to 4 locations: api/settings/index.js (2x) and api/public/settings.js (2x). Uses item.group fallback when key has no dot; also fixes multi-dot key data loss (e.g., admin.meta.title).
S2-18 FE-M4 Standardize getSettingByGroup error return type — change [] to {} for consistency. RESOLVED (Apr 11b) — Both return [] (HTTP error + catch) changed to return {} in api/public/settings.js. getAllSettings already returned {}.
S2-19 FE-L3 Fix settinReducersettingReducer typo in Redux store. 5 min
S2-20 FE-L4 Remove phantom redux-persist ignored actions (library not installed). RESOLVED (Apr 11b) — Removed entire middleware override from store/index.js; redux-persist not in package.json, the ignoredActions: ["persist/PERSIST", "persist/REHYDRATE"] was dead config.
S2-21 FE-M2 Remove dev-mode mock that swallows real delete errors in useDeleteSetting. RESOLVED (Apr 11b) — Removed NODE_ENV === "development" branch that returned { message: "... (mock)" } on any caught error. Errors now propagate consistently with useUpdateSetting/useCreateSetting.
S2-22 Architecture Evaluate Redux removal for settings. React Query is the primary cache; Redux settingSlice is a redundant mirror. Simplify to React Query only, refactor consumers to use useSettings() hook directly. 2 hrs

Testing (2.5 hrs)

# Description Effort
S2-23 Set up test framework (Jest + ts-jest for backend). Create first integration tests: Setting CRUD with permission checks, AppConfig encryption roundtrip, UserSetting 2FA setup flow. 2.5 hrs

Documentation (1 hr)

# Description Effort
S2-24 Update database.md for Setting, AppConfig, UserSetting entities — column changes, new indexes, migration history. 30 min
S2-25 Document the three-system architecture decision (Setting vs AppConfig vs UserSetting), public vs auth access patterns, and frontend caching strategy. 30 min

Backlog (Ongoing)

Fix opportunistically when touching adjacent files. No dedicated sprint time needed.

Issue(s) Description Fix When
SET-L1 Remove unused authorId variable in setting.controller.ts:15 Touching controller
SET-L2 Fix file save after DB write inconsistency (save file first, then DB) Touching controller
SET-L7 Remove unused DataSource import in seed file Touching seed
SET-L8 Remove commented-out old implementation in service:138-145 Touching service
SET-L9 Document unconventional /:prefix/prefix route pattern Touching routes
SET-M10 setting.view — remove from seed if public GET decision is documented (S1-9) Done in Sprint 1
AC-L1 Fix AppconfigValidation casing → AppConfigValidation Touching validation
AC-L2 Remove stale // Implementation here placeholder comment Touching service
US-L1 Fix inconsistent response shape in getMySetting (has setting vs no setting) Touching service
US-L2 Fix TypeScript type stringstring \| null for nullable columns Touching entity
US-L3 Remove unnecessary subscription relation load in verifyBackupCode Touching service
US-L4 Remove unused authGuard import in routes Touching routes
US-L5 Remove unused checkPermissionAndThrow import in controller Touching controller
US-L6 Remove unused get from http import in service Touching service
US-L7 Remove unused IUserSettingPayload interface in service Touching service
US-M2 Fix generateBackupCodes(count) to actually use count param Touching service
FE-L2 Fix useBackupCodes returns res.data instead of res.data.data RESOLVED (Apr 11b)account-setting.js:85 unwraps once more; two-factor-backup.jsx consumer updated (4 access sites) to use data directly instead of data?.data
FE-L5 Remove dead commented-out code in public/settings.js:26 Touching file
SET-M3 getImageSizeByKey references non-existent key admin.image_size — seed or remove Touching service
Circular Dep Break UserSetting.serviceAuth.controller circular dependency Architecture refactor

Key Decisions Log

Decision PM Engineer Designer Outcome
Fix order for ship blockers Security first (2FA secrets, AppConfig access) Agrees — mostly 1-line fixes 2FA setup auto-firing most disruptive to users Security → data loss → UX
AppConfig permission naming Standardize immediately app_config.* convention matches codebase Invisible to users Sprint 0 (ship blocker)
Remove backupCodes from Setting entity Sprint 1 — needs migration Sprint 1 — dead column, no risk Invisible to users Sprint 1
Create Setting validation schemas Ship blocker — no input validation is critical Sprint 1 — needs proper Zod schema design Validation errors need UX consideration Sprint 1 (moved from ship blocker due to effort)
Remove Redux settings mirror Sprint 2 — needs careful refactor Sprint 2 — consumers need migration Invisible if done right Sprint 2
Write integration tests Sprint 2 — after architecture stabilizes Sprint 2 — needs test framework setup N/A Sprint 2
Public GET routes for settings Keep public — SSR pages depend on it Document the decision, remove unused setting.view Public = faster page loads Keep public, document
isEncrypted default Sprint 1 — silent encryption is confusing Sprint 1 — change to false, require explicit N/A Sprint 1

Sprint Board Visualization

+-----------------------------------------------------------------+
|                    SHIP BLOCKERS (pre-release)                   |
|                    16 items . ~38 min remaining . 1 developer    |
|                    STATUS: 4/16 resolved (Apr 11)                |
|                                                                  |
|  [SEC] US-C2 US-H2 US-C4 AC-C5 SET-C3 +emailOTP               |
|  [BUG] AC-C3 SET-H4 ~~FE-C1~~ ~~FE-C2~~ ~~FE-C3~~ ~~FE-H1~~ |
|  [PRE] AC-C1(naming) AC-C2(seed) SET-C2(seed) AC-H2(env)      |
|                                                                  |
|  EXIT: All secrets hidden. AppConfig accessible. Core works.     |
+-----------------------------+-----------------------------------+
                              v
+-----------------------------------------------------------------+
|                    SPRINT 1 -- "Settings Work" (week 1)          |
|                    20 items . ~8 hrs . 1 developer               |
|                    STATUS: 4/20 resolved. 2 items MODIFIED.      |
|                                                                  |
|  [BE-SET] SET-C5(validation) SET-C4+H2(dead code) SET-H1       |
|           SET-M1 SET-M2 SET-M4 SET-M5 SET-H5 SET-H3            |
|  [BE-AC]  AC-M2(decrypt) AC-H1(default) AC-C4 AC-M3            |
|  [BE-US]  US-H1(modified) US-H4 US-M5                          |
|  [FE]     ~~FE-H2~~ ~~FE-H3~~ ~~FE-M7~~ ~~FE-M6~~            |
|                                                                  |
|  EXIT: Non-super_admin admin can manage all settings.            |
+-----------------------------+-----------------------------------+
                              v
+-----------------------------------------------------------------+
|                    SPRINT 2 -- "Clean Foundation" (week 2-3)     |
|                    25 items . ~13 hrs . 1-2 developers           |
|                    STATUS: 6/25 resolved (FE-M1/M5/M3/M4/L4/M2) |
|                                                                  |
|  [DATA]  SET-L3 SET-L4 SET-M9 SET-M6 SET-M7 SET-M8             |
|          SET-L5 SET-L6 US-M1 AC-H3                              |
|  [SEC]   US-M3 US-M4 US-M6 AC-L3 AC-L4 AC-M4 AC-M1            |
|  [FE]    ~~M3~~ ~~M4~~ FE-L3 ~~L4~~ ~~M2~~ +Redux rm         |
|  [TEST]  Integration tests (most important item)                |
|  [DOCS]  database.md + architecture docs                        |
|                                                                  |
|  EXIT: Clean model. Incremental seed. Tests pass. Docs match.   |
+-----------------------------+-----------------------------------+
                              v
+-----------------------------------------------------------------+
|                    BACKLOG (ongoing)                              |
|                    20 items . fix when touching adjacent files    |
|                                                                  |
|  SET-L1..L9, AC-L1..L2, US-L1..L7, US-M2, FE-L2, FE-L5       |
|  + circular dependency refactor                                  |
+-----------------------------------------------------------------+

Total Effort Estimate

Phase Items Effort Developers Timeline
Ship Blockers 12 remaining (4 resolved) ~38 min 1 Day 1 (before any deployment)
Sprint 1 16 remaining (4 resolved) ~6.9 hrs 1 Week 1
Sprint 2 21 remaining (4 resolved) ~12 hrs 1-2 Week 2-3
Backlog 19 remaining (1 resolved Apr 11b) ~4 hrs 1 Ongoing
Total 68 remaining ~23 hrs ~3 weeks

20 issues resolved since deep-dive (7 prior + FE-C1/FE-C2/FE-C3/FE-H1/FE-H2 on Apr 11 + FE-H3/FE-M7/FE-M6/FE-M3/FE-M4/FE-L4/FE-M2/FE-L2 on Apr 11b). 3 items modified. Net: 85 → 68 actionable.

3 issues resolved by Sprint 1 deletions (SET-C4 dead code, SET-H2 imports, SET-M10 via S1-9) — not counted separately.


Generated by BMAD Cross-Functional War Room workflow — Settings & Configuration, 2026-04-07 Cross-reference: Deep-Dive: Settings & Configuration