feat(users): quality pass + admin UX (sub-projects 1 & 2) - #43
Merged
Conversation
Module view routes mount at prefix + "/" (e.g. /dashboard/), so links pointing to /dashboard (no slash) triggered FastAPI's redirect_slashes 307. Update menu items, Inertia links/visits, and server-side RedirectResponses to the canonical trailing-slash form so the frontend requests the route directly.
Hosting middleware no longer hardcodes the {id, name, email, roles}
shape. Modules register a principal_serializer on app.state during
register_settings; the users module owns the UserContext→dict projection.
Removes the last known module-schema leak from framework/hosting.
…slate to 404 Moves HTTP coupling out of UserService. Service raises domain exception; api_admin.py and views.py catch-and-translate at the boundary. Preserves 404 status codes and 'User not found' detail strings.
Complies with CLAUDE.md convention that service code must not call session.commit() — the per-request session auto-commits via after_flush.
…State Disambiguates from service.py (holds UserService). The dataclass at app.state.users is module state, not a 'services' collection.
Current file sat at 299 LOC (one under the cap). Split by responsibility: user/role/user_role/access_token/constants. Every test keeps its original name and body; only the containing file changes.
Filtering + sorting, confirmation flows, expanded detail page. Bulk actions deferred per scope call.
…cdce # Conflicts: # modules/dashboard/dashboard/module.py # modules/products/products/endpoints/views.py # modules/products/products/module.py # modules/users/users/endpoints/api.py
Wire status/role/verified/sort/order query params onto the Inertia admin_index view, reusing _ALLOWED_* sets from api_admin.py, and echo cleaned values into props as a filters dict.
Add status/role/verified filter selects, sortable Email/Last login/Created column headers with URL-sync, and a Created column to the admin users page. Extract filter selects to IndexFilters.tsx and roles tab to RolesTab.tsx to stay under the 300-line cap.
…e case When the Permissions module is installed via uv sync --all-packages, it joins app.state.sm.modules and the flag flips to True. Mirror the "true" case's dataclass.replace pattern to pin the expected state.
Extract filter + sort + mark_verified tests from test_service.py into test_service_admin.py; extract TestAdminListFilters + TestAdminVerify from test_api_admin.py into test_api_admin_filters.py; extract TestAdminIndexFilters + TestHasPermissionsModuleFlag from test_views.py into test_views_admin.py. 191 tests still pass.
- Wrap filters in useMemo in Index.tsx to avoid biome react-hooks violation - Inline router.reload() to trim Edit.tsx back under 300-line cap - Move IndexFilters and RolesTab helpers to components/ (prevents SM003 orphan page warning) - Apply ruff/biome auto-formatting drift across touched files
- Drop unused user_count from RolesTab (never populated by server) - Move admin-list filter allowed sets to constants.py and introduce sanitize_list_filters helper; views.py no longer imports private names from api_admin.py - Make UserService.to_list_item synchronous (no I/O) and update callers - Drop WHAT-comments in Edit.tsx and redundant docstring lines
…cdce # Conflicts: # framework/hosting/simple_module_hosting/middleware.py # modules/users/users/pages/Users/Edit.tsx
These files are imported by already-committed modules but were never added to git on origin/main, causing install-py (hatch build) to fail in CI with a "Forced include not found" error. Adding them resolves: - framework/core/simple_module_core/diagnostics/_module.py imports from _coupling.py and _js_workspace.py - modules/settings/settings/endpoints/views.py imports from _module_settings.py - scripts/new_module.py imports from _templates_js - modules/file_storage and modules/permissions pyproject.toml force-include package.json / tsconfig.json - Settings module registers a Modules.tsx page
…cdce # Conflicts: # modules/permissions/permissions/pages/Browse.tsx # modules/users/users/endpoints/views.py # modules/users/users/pages/Users/Index.tsx # modules/users/users/service.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two sub-projects against the
usersmodule, branched before origin/main picked up the BackgroundTasks / Settings / Permissions / FileStorage modules and merged forward since:Sub-project 1 — Quality pass
UserServiceraisesUserNotFoundErrorand endpoints translate to HTTP 404; service usesflush()only (framework auto-commits)._roles_payloadhelper in views; promotedPasswordHelperto module-top.services.py→state.py,UsersServices→UsersState.auth.userprojection viaPrincipalSerializerregistered onapp.state.test_models.pyby model.Sub-project 2 — Admin UX
UserListItemgainscreated_at(other fields preserved).UserService.list_usersacceptsstatus/role_name/verifiedfilters andsort/order(email,last_login_at,created_at;last_login_atalways NULLS LAST)./api/users/adminand/users/admin(view) accept the same params and echo sanitized values in Inertiafiltersprops.PATCH /api/users/admin/{id}/verify(admin mark-verified, idempotent).has_permissions_moduleprop.IndexFilters.tsxandRolesTab.tsxlive undercomponents/(outsidepages/) to avoid SM003 orphan-page warnings.sanitize_list_filtershelper inusers/constants.pyreplaces duplicated filter-validation blocks; no private imports across endpoint modules.Post-review simplifications
user_countfield fromRolesTab(never populated by server).UserService.to_list_itemsynchronous (pure mapper, no I/O).Merge from origin/main
#41(Drop CSRF token middleware): replacedfetchWithCsrf(...)with plainfetch(...)inEdit.tsxacross five call sites.middleware.py: retained this branch'sPrincipalSerializerblock, dropped the removed CSRF-token minting.Public-contract invariants
UserListItemgained onlycreated_at; no field removed.GET /api/users/adminstill acceptspage,per_page,q; shape unchanged (filters/sort are additive kwargs).PATCH /admin/{id}/{disable,enable},PUT /admin/{id}/roles,POST /admin/{id}/reset-password-linkunchanged.Users/Users/IndexandUsers/Users/Editpreserved.UserInvited,UserDisabled,RoleAssigned).Test plan
uv run pytest modules/users/tests/ -q— 191 passedmake lint— ruff / ty / biome / tsc / file-size / hardcoded-strings all greenmake doctor— 0 errors, 0 warnings fromusers/users/admin, apply each filter, toggle sort, open a user, mark verified, disable with confirmation, copy reset link, click Permissions cross-link