Commit Graph

2 Commits

Author SHA1 Message Date
Vasyka 3911012c65 feat(i18n): mega-wrap 494 raw RO strings + 269 lang entries + 161 human RU/163 EN
Broad sweep across the whole codebase:
- Blade views (39 files, 315 wraps): tag-text and title/placeholder/alt
  attributes wrapped with {{ __() }}. Excludes scripts, styles, @php,
  @verbatim, {{ }}, {!! !!}, comments to avoid touching interpolations.
- PHP (54 files, 179 wraps): array 'key' => 'RO value' patterns and
  list items with diacritics wrapped with __(). Reverted __() inside
  const arrays (PHP disallows non-constant expressions).
- Added 269 new keys to lang/{ru,en}.json (identity fallback for
  unknowns → 161 human RU + 163 EN translations added for the most
  common enums, stages, roles, statuses, payment methods, vehicle
  categories, warehouse, portal, form actions.

Missing translations fall back to RO so the UI never breaks. All 306
tests pass; view cache compiles cleanly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-07-15 05:53:22 +00:00
Vasyka d9180e16b3 feat: P2 RBAC defers — REST API + invitation workflow
Closes the P2 items from /tmp/service/new/01-TZ-rbac §4.1 §4.2.

== User invitation workflow ==

New columns on users: invited_at, invited_by_id (FK self), accepted_at,
invitation_token (sha256 hash, indexed). Migration is idempotent.

User::sendInvitation($invitedBy = auth()->user())
  - generates 64-char random token
  - stores sha256(token) in invitation_token column (never plaintext)
  - marks invited_at = now(), status = inactive
  - queues UserInvitationMail to the user's email with the signed accept URL
  - returns the raw token (for tests / API consumers)

User::findByInvitationToken($rawToken) hashes + lookups.
User::acceptInvitation($password) sets password (hashed cast), clears
invitation_token, marks accepted_at + email_verified_at, status = active.

Web routes (no auth — token IS the credential):
  GET  /invitations/{token}  → password-set form
  POST /invitations/{token}  → validates min:8 + confirmed, accepts

Tokens expire after 7 days (checked against invited_at). Expired and
invalid tokens render dedicated views (invitations/expired.blade.php,
invitations/invalid.blade.php) instead of generic 404 — so the user
knows to ask for a resend.

UserInvitationMail uses Filament's existing markdown layout; subject
includes the tenant display_name.

== REST API ==

Twenty new endpoints under /api/v1/ (Sanctum auth + tenant scoping
via the existing EnsureTokenMatchesTenant middleware). All gated by
ADMIN_USERS_* / ADMIN_ROLES_MANAGE permissions; mechanic-level token
gets 403.

Users:
  GET    /users                                  — paginated + role/status/q filters
  GET    /users/{u}                              — eager-loads roles + overrides + invitedBy
  POST   /users                                  — creates inactive user + sends invitation
  PATCH  /users/{u}                              — update name/email/role/status
  DELETE /users/{u}                              — soft delete
  POST   /users/{u}/activate
  POST   /users/{u}/deactivate                   — also revokes all sessions
  POST   /users/{u}/resend-invitation
  POST   /users/{u}/force-password-reset         — re-sends invitation
  GET    /users/{u}/sessions                     — list active sessions (from sessions table)
  DELETE /users/{u}/sessions                     — revoke all
  DELETE /users/{u}/sessions/{sessionId}         — revoke one
  GET    /users/{u}/roles                        — assigned roles
  POST   /users/{u}/roles                        — assign role
  DELETE /users/{u}/roles/{role}                 — remove role
  GET    /users/{u}/permissions                  — effective: role perms + grants - active denies
  POST   /users/{u}/permission-overrides         — add grant/deny (with optional expires_at)
  DELETE /users/{u}/permission-overrides/{perm}

Roles:
  apiResource roles                              — index/show/store/update/destroy
                                                   (system roles guarded against rename/delete)
  GET    /roles/{r}/permissions
  PUT    /roles/{r}/permissions                  — bulk sync
  GET    /permissions                            — catalog: flat list + grouped + labels + role labels

Authorization is uniform: every controller method calls $this->authorize()
which throws 403 if canDo(perm) is false. canDo() already honors the
overrides + admin bypass + audit log from earlier commits, so the API
behaves identically to the Filament UI.

== Tests ==

InvitationFlowTest (8): token generation + sha256 storage + queued mail,
findByInvitationToken happy/sad path, accept sets password + activates,
GET form renders, POST accepts + redirects, invalid token view,
backdated invited_at → expired view, password too short → validation error.

RbacApiTest (12): admin can list users, mechanic 403, create user
queues invitation, assign+remove role round-trip, effective permissions
endpoint subtracts active denies, add+remove override via API,
role index returns 7 system roles with permission counts (51 for owner),
role sync permissions, system role destroy rejected with 422,
permission catalog endpoint returns all 51 + grouped + labels,
revoke all sessions deletes only target user's rows.

Suite: 234 passed (659 assertions). Was 214.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-04 22:36:44 +00:00