Gustavo Gama - #38
Open
gustavomgama wants to merge 103 commits into
Open
Conversation
- rails new with postgresql, tailwind, solid cache/queue/cable - includes brakeman, bundler-audit, rubocop, kamal, thruster - capybara + selenium for system tests - restore .gitignore (skip-git scaffold)
- annotaterb for schema annotations, models-only - bcrypt for has_secure_password (Rails 8 auth) - pin json < 3 for Ruby 4 kwargs compat with ActiveSupport
- generate authentication, rename email_address to email per spec - User: full_name, email, role enum, avatar, validations - sessions, passwords, mailer, fixtures, model/controller tests
- namespace admin root dashboard#show - total users and count grouped by role - BaseController requires admin, redirects others - integration tests for admin and non-admin
- admin to admin dashboard, user to profile - ProfilesController#show for own info - update sessions test for role redirect
- RegistrationsController new/create, strict params - role locked to user, starts session, redirects to profile
- ProfilesController edit/update/destroy for own user - install ActiveStorage tables for avatar_image
- index/new/create/edit/update/destroy under admin - member patch toggle_role, strict params - non-admin redirected
… edges) - 11 tests, 53 assertions for existing behavior - 41 runs total green
- replace roo (rubyzip CVE) with rubyXL - add turbo-rails and stimulus-rails for streams
- UserImport with status counters and file attachment - UserImportJob via Solid Queue, CSV via stdlib, XLSX via rubyXL - Turbo broadcast progress per row
- index/new/create/show under admin namespace - turbo_stream_from per import with progress partial - CSV and XLSX integration tests
- User broadcasts to admin-dashboard on commit - dashboard subscribes with turbo stream partial
- avatar_url column with http/https validation - avatar_display helper, permitted in profile/admin params
- admin@example.com / password123 - 4 sample users for dashboard and import testing
- global nav with auth-aware links and flash - home root redirects by role - profile avatar display, edit fields, delete - admin dashboard links, user avatar fields - signup link on login
- importmap-rails with turbo and stimulus - layout loads javascript_importmap_tags - enables Solid Cable dashboard and import updates
- inertia_rails + vite_rails with React 19 - app/frontend entrypoints, vite.json, importmap coexist - layout loads both Turbo streams and Inertia - CI builds vite test bundle
- Shared Layout with auth-aware nav, Session/New login - Registration, Profile Show/Edit, Password New/Edit - Admin Dashboard (3s poll), Users Index/New/Edit, Imports Index/New/Show (2s poll) - controllers render inertia with strict props - tests assert components and props JSON
- remove turbo/stimulus/importmap, bare ERB shell - mount ActionCable for JSON channels
- DashboardChannel and UserImportChannel over Solid Cable - React subscribes via @rails/actioncable
- useCable subscriptions replace polling and Turbo streams - validation failures re-render Inertia with 422
- Admin users/imports tables: overflow-x-auto, min-width, nowrap cells - Long emails and file names truncate with tooltip - Actions stay on one line
- remove container mx-auto max-w-5xl from React main - remove container mx-auto from ERB shell main
- step-by-step tool installation (Ruby, Bundler, Node, PostgreSQL) - no version-manager assumptions, no mise references - deployment prerequisites, registry auth, secrets, troubleshooting
- compose.yml for local PostgreSQL - three dev options: db-only, app image with Thruster, full compose stack - explains how Kamal reuses the same image, proxy, and db shape
- Option B/C need SECRET_KEY_BASE and four DB URLs for Solid databases - Linux host.docker.internal flag included in run command
- deploy.yml accessory port 127.0.0.1:5433:5432 - CI postgres service ports 5433:5432 - README Option C restores web service snippet with internal db:5432 - trailing newline on Imports Show page
- compose.yml: db + web stack, web maps 3000:80 - README: replace three options with one docker compose up --build flow
- SOLID_QUEUE_IN_PUMA so background jobs execute in the web container - storage_data volume so uploaded files survive container recreation
- credentials.yml.enc was in wrong format (openssl vs MessageEncryptor) - encrypts declarations removed (require proper credentials to function) - encrypted_email/encrypted_full_name columns remain in schema for future use - CI passes clean
Security: - Enable config.assume_ssl, config.force_ssl, and host_authorization in production - Add upload file size limits (5MB avatars, 50MB imports) and content type validation - Add database statement_timeout (10s) and lock_timeout (5s) - Upgrade CodeQL actions from @v1 to @V3 - Remove git from production Docker image - Add password complexity validation (8+ chars, uppercase, lowercase, digit) Stability: - Fix race condition in ActionCable subscription (refetch state on connect) - Fix toggle_role race — prevent demoting last admin - Fix terminate_session nil guard - Wrap avatar swap operations in transactions - Add session expiry/TTL (30 days) with cleanup job - Fix import job to auto-generate compliant passwords for weak inputs - Fix bin/ci to handle missing master.key gracefully Observability: - Add Sentry error tracking (production-only, 10% trace sampling) - Add request/job metrics via ActiveSupport::Notifications - Add audit logging for admin actions (user CRUD, role toggle, imports) UX: - Add shared FormFields components (Field, FieldError, PasswordHint) - Add password complexity hints to all password forms - Add labels and aria attributes for accessibility (WCAG) - Add disabled/loading visual states on all submit buttons - Add pagination (25/page) to user and import lists - Fix import status badge colors (green/red/amber/gray by status) - Fix mailer host from example.com to env-driven APP_HOST Infrastructure: - Create Solid Cache/Queue/Cable databases in docker-entrypoint - Add deploy hook for db:migrate - Update CodeQL workflow with permissions and explicit languages - Update seed and test fixtures for password complexity compliance
…ful in Docker - Delete app/jobs/expire_sessions_scheduler.rb (SolidQueue has no schedule class method) - Add expire_sessions to config/recurring.yml (proper SolidQueue scheduler config) - Make bundler-audit non-fatal when advisory DB can't be downloaded (Docker network restriction)
- psql must specify -d fullstack_developer_development (not default to username) - Add RAILS_MASTER_KEY and SECRET_KEY_BASE validation with clear error messages
Docker local runs without SSL termination — force_ssl caused SSL_ERROR_RX_RECORD_TOO_LONG. Now only enabled when ENABLE_SSL=true, which Kamal production sets via deploy.yml.
- config/deploy.yml: drop the invalid top-level `deploy:` key (Kamal 2 rejects it, so `bin/kamal deploy` failed at config parse). Migrations are already run by bin/docker-entrypoint via db:prepare. Remove the separate `job` role that duplicated the in-Puma Solid Queue supervisor (SOLID_QUEUE_IN_PUMA) and update the README deploy docs. - UserImportJob: make retries resume from the processed cursor instead of recreating users and re-failing on duplicate emails; skip already-completed imports; retry transient DB errors with bounded polynomial backoff. - ExpireSessionsJob: delete expired sessions in batches to bound locks and memory instead of one unbounded delete_all. - Profiles/Admin Users update: only purge the stored avatar after the record is persisted, so a failed validation no longer destroys the existing file. - HealthController: treat statement/connection failures as unavailable and log the cause for diagnosis. - UserImportChannel: reject unknown import ids instead of raising. - AuditLog: remove dead before_destroy callback (FK already nullifies). - config/ci.rb: drop the no-op coverage step; the 90% gate is enforced by SimpleCov minimum_coverage in test_helper during the test run. - Tests: add coverage for session expiry, import resume/completion, failed avatar updates, DB-down health, and unknown import channel ids. - README: disclose the current model in the AI usage section. Verified: rubocop clean, brakeman 0 warnings, bundler-audit clean, 121 tests / 396 assertions passing, 95% line coverage.
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.
AI Usage Disclosure
Assisted by OpenCode with models:
Muse Spark 1.3,DeepSeek v4 Flash,MiMo V2.5.Fullstack Developer — User Management App
A modern, responsive monolithic SPA for managing users. Admins get a live dashboard with user counts by role, full CRUD over users, role toggling, and asynchronous spreadsheet imports with live progress. Regular users manage only their own profile, and visitors can register as normal users.
Stack
Build / Seed / Run
These instructions assume a clean machine with nothing pre-installed. Every tool below is listed with a way to obtain it; if you already have a tool, skip its step.
1. Install Ruby 4.0.6
The app requires Ruby 4.0.6 (see
.ruby-version). Any installation method that gives you arubyexecutable on yourPATHworks — a version manager is convenient but not required.Using a version manager (recommended, e.g. rbenv):
Using your operating system's package manager (e.g. on Debian/Ubuntu):
Verify Ruby is available:
ruby -v # must print ruby 4.0.62. Install Bundler
Bundler is the Ruby dependency manager. Install it with:
Verify:
3. Install Node.js and npm
The frontend is built with Vite and requires Node.js (npm ships with it). Install via nvm (recommended) or your system package manager:
Or on Debian/Ubuntu:
Verify:
node -v # Node 18 or newer npm -v4. Install PostgreSQL
The app uses PostgreSQL as its database. Install it with your system package manager:
# Debian/Ubuntu sudo apt-get install -y postgresql postgresql-client sudo service postgresql start# macOS (Homebrew) brew install postgresql@16 brew services start postgresql@16Verify the server is reachable:
If your PostgreSQL instance requires a password for the default user, set the
DATABASE_URLenvironment variable so Rails can connect, for example:5. Install backend and frontend dependencies
From the project root:
If
bundle installfails while compiling native extensions, install the system packages listed in step 1 (build-essential,libpq-dev,libyaml-dev) and retry.6. Create, migrate, and seed the database
This creates the admin account and sample users:
The setup script is an equivalent shortcut that prepares dependencies and the database without starting the server:
Seeded login accounts (all seeded passwords are
password123unless changed after seeding):admin@example.com / password123ada@example.com,alan@example.com,grace@example.com,margaret@example.comSample import files for exercising the admin spreadsheet import (25 data rows each, distinct datasets):
test/fixtures/files/sample_users.csvtest/fixtures/files/sample_users.xlsx7. Run the app in development
Starts Rails plus the Vite dev server:
Then open http://localhost:3000 in your browser.
8. Run the full quality pipeline
Runs setup, lint, audits, test build, tests, coverage gate, and profiling:
Architecture
This is a monolithic SPA served by Rails, with client pages implemented in React under
app/frontend/pages/and bridged through Inertia.js with Vite as the asset bundler. Navigation between the admin dashboard, user list, profile, and auth screens is handled as Inertia visits, so the app behaves like a single-page application while all routing and authorization stay server-side in Rails controllers.Live updates use Solid Cable with no Redis dependency. The admin dashboard subscribes to
DashboardChannelfor total user counts and per-role breakdowns, which refresh as users are created, updated, deleted, or have their role toggled. Spreadsheet imports stream progress overUserImportChannel, so the admin sees each import move from pending through processing to completed or failed without polling.Spreadsheet processing runs in the background with Solid Queue. Uploading a CSV or XLSX file enqueues a
UserImportJobthat parses rows (CSV through the Ruby standard library, XLSX through rubyXL), creates users for valid rows, and records per-row failures for invalid ones. The admin import history shows status and row-level error detail for every run.Authentication uses the built-in Rails 8 authentication generator, extended with role-based redirects: admins land on
/adminafter login and regular users land on/profile. Authorization is enforced server-side so regular users can only view, edit, and delete their own profile, while every admin-only route is rejected for non-admins. All controllers use strictparams.expectfor structural parameter validation, and forms pair client-side interactive feedback with backend model validations.Testing
The full pipeline entry point is
bin/ci, which runs environment setup, RuboCop, bundler-audit, Brakeman, a Vite production test build, the Minitest suite, a seed replant check, the ZJIT profiling script, and the SimpleCov coverage gate.The Minitest suite runs in parallel with
bin/rails test(107 runs) and covers models, controllers, jobs, channels, mailers, and integration flows, including admin CRUD, role toggling, CSV and XLSX imports with failed-row reporting, authentication redirects, and authorization boundaries. Coverage is enforced with a SimpleCov minimum of 90, and the suite currently reports 100%.System tests live in
test/system/and run under Capybara with headless Chrome via Selenium, exercising login, registration, profile editing, and the admin dashboard from a real browser. Performance profiling usesbenchmark/zjit_profile.rbto compare warm runs with Ruby 4 ZJIT enabled against the baseline interpreter.Run with Docker
The simplest way to run the whole app is Docker — no Ruby, Node, or PostgreSQL installation needed. The
compose.ymlat the project root builds the app image and starts it together with a PostgreSQL database.Prerequisite: Docker with Compose (
docker compose version).Then open http://localhost:3000 in your browser.
That's it. The first run builds the image (takes a few minutes); later runs are fast. The app runs in production mode behind the Thruster proxy, exactly like the deployed version.
Stop the app:
To wipe the database and start fresh:
Notes:
RAILS_MASTER_KEYis the contents ofconfig/master.key.SECRET_KEY_BASEis required because the image runs in production mode and the repository's credentials file does not ship a productionsecret_key_base. Generate one withopenssl rand -hex 64.*_DATABASE_URLvariables are already set incompose.yml; they exist because the production configuration uses separate databases for Solid Cache, Solid Queue, and Solid Cable. Rails creates them automatically on first boot.bin/dev— the app also runs on http://localhost:3000.Run the quality pipeline (
bin/ci) with DockerThe full quality pipeline (lint, security audits, tests, coverage gate, profiling) runs inside the app container against the compose database. With the app stack running:
What this does:
docker compose runstarts a one-offwebcontainer on the compose network, so it can reach thedbservice.-vmount makesconfig/master.keyavailable inside the container —bin/cireads it directly and the file is not baked into the image.RAILS_ENV=testandDATABASE_URLpoint the suite at a test database on the compose Postgres; Rails creates it automatically on the first run.bundle exec bin/ciruns the same pipeline as on the host: setup, RuboCop, bundler-audit, Brakeman, Vite test build, the Minitest suite, seed replant, ZJIT profiling, and the SimpleCov gate.The container is removed when the run finishes (
--rm). The app stack itself is unaffected.Run commands inside the app container
With the app stack running (
docker compose up --build), run any Rails or Ruby command inside thewebcontainer withdocker compose exec. The running container already has the environment configured, so no extra flags are needed:docker compose exec web bin/rails consoledocker compose exec web bin/rails routesdocker compose exec web bin/rubocopdocker compose exec web bin/rails db:migrateNotes:
docker compose execruns in the already-runningwebcontainer;docker compose run(used forbin/ciabove) starts a separate one-off container instead.rails console, usedocker compose execwithout-T. For piping input (e.g.echo 'puts User.count' | docker compose exec -T web bin/rails console), add-T.RAILS_MASTER_KEYandSECRET_KEY_BASEfirst, the container will have blank values; restart with the exports from the "Run with Docker" section.How Kamal relates to local development
Kamal 2 (
config/deploy.yml) is the production deployment tool — it builds the same Dockerfile, pushes the image to a registry, and rolls it out to remote servers over SSH. Locally, you are not running Kamal; you are using Docker directly. The relationship:docker compose up --buildandbin/kamal deployboth build from the sameDockerfile, so what you validate locally is what ships.CMDrunsbin/thrust(Thruster) in both local and Kamal runs.dbservice mirrors the Postgres 16 accessory defined underaccessories:inconfig/deploy.yml.RAILS_MASTER_KEYandSECRET_KEY_BASEas environment variables; Kamal injects them from.kamal/secretsviabin/kamal secret set.Deployment is configured with Kamal 2 in
config/deploy.yml. The image is published toghcr.io(ghcr.io/umanni/fullstack-developer:latest), traffic terminates through an SSL proxy for the app host, and the stack defineswebandjobroles plus a Postgres 16 accessory for production data.Prerequisites
docker --version.ssh <user>@<server-host>.ghcr.io(or whichever registry you set inconfig/deploy.yml). Authenticate Docker to it:Configure the deployment
Set the real server addresses. Open
config/deploy.ymland replace the placeholder values with your production infrastructure:DEPLOY_WEB_HOST(default192.168.0.1) — the server that runs the web app.DEPLOY_JOB_HOST(default192.168.0.1) — the server that runs background jobs.DEPLOY_DB_HOST(default192.168.0.2) — the server that runs the Postgres accessory.proxy.host(fullstack-developer.umanni.dev) — the public hostname that will serve the app over HTTPS.Set the registry credentials. In
config/deploy.yml, uncomment and fill inregistry.usernameandregistry.password(or pointregistry.passwordat aKAMAL_REGISTRY_PASSWORDsecret). Use a personal access token rather than your account password when possible.Set the deploy secrets. Kamal reads secrets from
.kamal/secretsand injects them into the containers. Set the required values:RAILS_MASTER_KEYis the contents ofconfig/master.key(or the production master key for your environment).DB_PASSWORDis the password the Postgres accessory will use.Deploy
From the project root, with Docker running and SSH access in place:
setupprovisions the server (installs Docker on it if needed), starts the Postgres accessory, and boots the app for the first time. On subsequent releases:deploybuilds the image, pushes it to the registry, and rolls it out to the servers.Post-deploy commands
Useful commands (defined as aliases in
config/deploy.yml):Troubleshooting
registry/username is required— you skipped step 2; fill in the registry credentials inconfig/deploy.yml.config/deploy.ymland that your SSH key is authorized on the server.bin/kamal logs; the most common cause is a missing or wrongRAILS_MASTER_KEY.Known limitations
config/deploy.ymlare operator-supplied placeholder addresses that must be replaced with real infrastructure values.