Skip to content

Wilbert Ribeiro - Fullstack-Developer - #42

Open
wilbert wants to merge 90 commits into
umanni:masterfrom
wilbert:master
Open

wilbert wants to merge 90 commits into
umanni:masterfrom
wilbert:master

Conversation

@wilbert

@wilbert wilbert commented Sep 14, 2026

Copy link
Copy Markdown

User Management App — Rails 8.1 + Inertia/React submission

Implements the full challenge: a responsive user-management app on Ruby 4.0.6 / Rails 8.1, with React 19 + Inertia.js via Vite Rails, Tailwind CSS 4, and the native Rails 8 stack — built-in authentication, Solid Queue for background imports, Solid Cable for live updates, Solid Cache. No Redis, no Devise.

Branch: wilbert-ribeiro-devmaster. Built across six feature PRs (feat/front, feat/react-files, feat/admin-dashboard, feat/spreadsheet-import, feat/layout, feat/deployment); this PR merges the result.

Use cases delivered

Admin

  • Redirected to the admin dashboard after login.
  • Dashboard shows total users and users grouped by role, pushed over Action Cable — no polling, no reload.
  • Full CRUD over users, with search, role filter, sorting, and pagination (Pagy).
  • Toggle a user's role from the list or the detail page.
  • Upload a .csv / .xlsx spreadsheet; users are created by a background job.
  • Live per-row progress, status, and error report while the import runs.

User

  • Redirected to their own profile after login; can view, edit, and delete only their own account.

Visitor

  • Self-registration as a regular member, rate-limited.

Architecture

Domain logic is kept out of controllers, which stay thin and only render Inertia responses.

Layer Location Responsibility
Queries app/queries/ UserSearch (filter/sort/paginate), Dashboard::Stats
Serializers app/serializers/ Explicit prop shapes for React — no model leakage
Policies app/policies/ ApplicationPolicy / UserPolicy, plain objects, no gem
Import pipeline app/imports/ RowSetCsvRowSet / SpreadsheetRowSet, UserRow, UserImporter, ProgressBroadcaster
Broadcasters app/broadcasters/ Dashboard::Broadcaster, with a suppression switch for bulk writes
Jobs app/jobs/ ProcessImportJob, Dashboard::BroadcastJob

Notable choices:

  • Import format is abstracted behind RowSet. CSV and XLSX both yield normalised hashes, so UserImporter never knows which it got. Adding a format is one subclass.
  • Broadcast suppression. A bulk import would otherwise fire one dashboard broadcast per created user; DashboardBroadcasts.suppressed? silences the per-record callbacks and the job broadcasts once at the end.
  • Last-admin protection is enforced in the model (before_destroy + an update validation), not only in the UI, so it holds for imports and the console too.
  • email_address is encrypted with Active Record deterministic encryption, keeping uniqueness and lookups working.
  • Strict validation on both sides: model validations plus a shared client-side rule set (app/javascript/lib/validation.ts + useLiveValidation) that gives interactive feedback without duplicating server truth.

Frontend

  • React 19 + TypeScript, tsc -b in CI, no any escape hatches in the page tree.
  • Tailwind CSS 4 via the Vite plugin, responsive down to mobile, with a PWA manifest and icons.
  • useDashboardStream and the import page subscribe to Action Cable channels for real-time state.
  • useLocaleFormat / useHydrated keep server- and client-rendered timestamps consistent so SSR does not mismatch on hydration.

SSR (extra credit). Inertia SSR is wired end to end: bin/vite dev answers Rails on /__inertia_ssr in development, and assets:precompile emits public/vite-ssr/ssr.js, kept alive in production by the inertia_ssr Puma plugin. It degrades to client rendering if neither is present, and is off in the test env by default. Covered by spec/system/server_side_rendering_spec.rb.

Testing

  • 54 spec files, RSpec, run in parallel with parallel_tests.
  • Unit (models, queries, policies, serializers, importers, jobs, channels), request specs for every controller, and system specs driven by Capybara + Playwright.
  • SimpleCov enforces 90% line / 80% branch minimum, with per-worker result sets merged across parallel runs.
  • CI (.github/workflows/ci.yml) runs RuboCop (rubocop-rails-omakase + rubocop-rspec), Brakeman, bundler-audit, tsc, Prettier, and the parallel suite against Postgres 17, then uploads the coverage report. CodeQL runs separately.

Deployment

  • Multi-stage Dockerfile serving through Thruster.
  • docker-compose.yml for local development — docker compose build && docker compose up is the whole setup; the Solid Queue worker runs inside Puma so imports work with no extra process. Full walkthrough in DEVELOPMENT_SETUP.md.
  • Kamal 2 config (config/deploy.yml) with web and job roles, kamal-proxy + Let's Encrypt, a postgres:17 accessory, and secrets mapped through .kamal/secrets with no values committed.

⚠️ The Kamal deploy was not verified against a real server. I had no VPS available, and the local Multipass rehearsal did not fit in the ~20 GB free on my machine. KAMAL_DISCLOUSURE.md documents exactly what is configured, what is unverified, and the remaining steps (placeholders to replace, RAILS_ENV: production, DNS, secrets).

Ruby 4 JIT profiling

bin/jit-profile benchmarks this app's own hot paths (serialization, import parsing, row validation, a full page render) under the interpreter, YJIT, and Ruby 4's new ZJIT, each in a fresh process. Findings: YJIT is 1.53×–2.12× faster than the interpreter, ZJIT 1.07×–1.20×, so production runs YJIT — switchable with one env var (RUBY_JIT). Method and numbers in PERFORMANCE.md.

AI disclosure

Per the AI policy: GitHub Copilot for inline completion and Claude Opus 5 for planning, debugging, documentation, and PR reviews. Scope detailed in AI_DISCLOSURE.md.

Reviewing locally

# Create .env with RAILS_MASTER_KEY, SECRET_KEY_BASE, RAILS_ENV, RACK_ENV
# (exact contents in DEVELOPMENT_SETUP.md §3)
docker compose build
docker compose up
docker compose exec web bin/rails db:seed

App at http://localhost:3000 — admin admin@umanni.test / password123, plus 25 seeded members.

🤖 Generated with Claude Code

- Created 422.html for handling "Unprocessable Entity" errors with a user-friendly message and SVG icon.
- Created 500.html for handling "Internal Server Error" with a corresponding message and SVG icon.
- Added icon.png and icon.svg for branding and visual representation on error pages.
- Included a robots.txt file to manage web crawler access.
- Added placeholder .keep files in various directories to ensure they are tracked by version control.
- Added package.json with dependencies for React, Inertia.js, Tailwind CSS, and Vite.
- Created rails_helper.rb and spec_helper.rb for RSpec configuration.
- Added TypeScript configuration files: tsconfig.app.json, tsconfig.node.json, and tsconfig.json.
- Set up Vite configuration for React, Inertia.js, and Tailwind CSS integration.
This commit renames config/database.yml to config/database.yml.sample to keep environment-specific database settings out of version control. The sample file can still be copied and customized locally without committing sensitive or machine-specific values.
…reate database.yml for development and production
Feat/front - Authentication/Authorization/Frontend in Ruby side
- Added `useLiveValidation` hook for real-time form validation in various forms.
- Introduced validation rules for user registration and import forms.
- Enhanced error handling and user feedback for form fields.
- Refactored components to improve readability and maintainability.
- Updated application layout for better structure and styling.
- Removed unused Redis service from Docker configuration.
- Improved CI configuration for better test coverage and performance.
- Added system tests for client-side validation to ensure functionality.
Add .nvmrc (Node 26.8.2) and update Dockerfile/DEVELOPMENT_SETUP to match. Modernize GitHub Actions: upgrade actions/checkout, actions/setup-node and actions/upload-artifact to v7 and use .nvmrc for node-version in CI. Refresh CodeQL workflow to newer action versions, add required permissions, use a language matrix and build-mode none, and simplify steps. These changes align local, Docker and CI Node versions and bring workflows up-to-date for more reliable analysis and caching.
Introduce server-side rendering and related infra, safe client locale formatting, and JIT profiling tooling.

Highlights:
- Vite SSR: enable ssr build/output and SSR entry (app/javascript/ssr/ssr.ts, vite.config.ts, config/vite.json).
- Runtime: keep node binary in final image and wire inertia_ssr plugin (Dockerfile, .dockerignore, config/puma.rb, config/initializers/inertia_rails.rb). Updated docs (DEVELOPMENT_SETUP.md).
- Locale-safe hydration: useHydrated/useLocaleFormat hooks + LocalTime component and update pages/components to format numbers/dates without hydration mismatches.
- JIT tooling: bin/jit-profile and lib/ruby_jit + jit_profile suite (benchmark, driver, probe, workloads, report) with specs and SSR test helpers.
- Enable INERTIA_SSR and default RUBY_JIT=yjit in config/deploy.yml.

Adds tests and system spec coverage for SSR and JIT helpers.
This change tidies up Ruby and spec formatting across the app, including quote style, multiline block layout, and metadata ordering in system specs. It also modernizes a few RSpec expectations to newer matcher idioms and keeps Action Cable and rate-limit configuration formatting consistent with the project’s linting rules.
This commit updates the RuboCop config for controller specs, normalizes multiline `rate_limit` blocks, and refactors several specs to use clearer assertions and explicit subject/setup patterns. It also renames the shared-data controller spec and splits the import flow/system examples to better isolate queued and completed behaviors.
Wilbert ribeiro Fullstack-Developer implementation
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant