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.
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.
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.
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.
Add confirmation before destroying Google auth. When switching to email 2FA, check if Google 2FA is active and require explicit confirmation parameter.
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
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 settinReducer → settingReducer 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.
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.
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 string → string \| 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