diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..597bf48a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,32 @@ +# Repository Guidelines + +## Project Structure & Module Organization +- `app/` holds the Rails application: controllers and views under feature-oriented folders, `services/` for importers and background workflows, and `policies/` for Pundit authorization. +- `app/javascript/` contains Stimulus controllers (`controllers/`), map widgets (`maps/`), and Tailwind/Turbo setup in `application.js`. +- `lib/` stores reusable support code and rake tasks, while `config/` tracks environment settings, credentials, and initializers. +- `db/` carries schema migrations and data migrations; `spec/` provides RSpec coverage; `e2e/` hosts Playwright scenarios; `docker/` bundles deployment compose files. + +## Build, Test, and Development Commands +- `bundle exec rails db:prepare` initializes or migrates the PostgreSQL database. +- `bundle exec bin/dev` starts the Rails app plus JS bundler via Foreman using `Procfile.dev` (set `PROMETHEUS_EXPORTER_ENABLED=true` to use the Prometheus profile). +- `bundle exec sidekiq` runs background jobs locally alongside the web server. +- `docker compose -f docker/docker-compose.yml up` brings up the containerized stack for end-to-end smoke checks. + +## Coding Style & Naming Conventions +- Follow default Ruby style with two-space indentation and snake_case filenames; run `bin/rubocop` before pushing. +- JavaScript modules in `app/javascript/` use ES modules and Stimulus naming (`*_controller.js`); keep exports camelCase and limit files to a single controller. +- Tailwind classes power the UI; co-locate shared styles under `app/javascript/styles/` rather than inline overrides. + +## Testing Guidelines +- Use `bundle exec rspec` for unit and feature specs; mirror production behavior by tagging jobs or services with factories in `spec/support`. +- End-to-end flows live in `e2e/`; execute `npx playwright test` (set `BASE_URL` if the server runs on a non-default port). +- Commit failing scenarios together with the fix, and prefer descriptive `it "..."` strings that capture user intent. + +## Commit & Pull Request Guidelines +- Write concise, imperative commit titles (e.g., `Add family sharing policy`); group related changes rather than omnibus commits. +- Target pull requests at the `dev` branch, describe the motivation, reference GitHub issues when applicable, and attach screenshots for UI-facing changes. +- Confirm CI, lint, and test status before requesting review; call out migrations or data tasks in the PR checklist. + +## Environment & Configuration Tips +- Copy `.env.example` to `.env` or rely on Docker secrets to supply API keys, map tokens, and mail credentials. +- Regenerate credentials with `bin/rails credentials:edit` when altering secrets, and avoid committing any generated `.env` or `credentials.yml.enc` changes. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d1470f1e..dc0d96fe 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,7 @@ ## How to contribute to Dawarich +Refer to [Repository Guidelines](AGENTS.md) for structure, tooling, and workflow expectations before submitting changes. + #### **Did you find a bug?** * **Ensure the bug was not already reported** by searching on GitHub under [Issues](https://github.com/Freika/dawarich/issues). diff --git a/app/controllers/users/registrations_controller.rb b/app/controllers/users/registrations_controller.rb index c3926413..dd264c00 100644 --- a/app/controllers/users/registrations_controller.rb +++ b/app/controllers/users/registrations_controller.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class Users::RegistrationsController < Devise::RegistrationsController - before_action :check_registration_allowed, only: [:new, :create] - before_action :set_invitation, only: [:new, :create] + before_action :set_invitation, only: %i[new create] + before_action :check_registration_allowed, only: %i[new create] def new build_resource({}) @@ -43,10 +43,11 @@ class Users::RegistrationsController < Devise::RegistrationsController private def check_registration_allowed - return true if DawarichSettings.self_hosted? - return true if valid_invitation_token? + return unless DawarichSettings.self_hosted? + return if valid_invitation_token? - redirect_to root_path, alert: 'Registration is not available. Please contact your administrator for access.' + redirect_to root_path, + alert: 'Registration is not available. Please contact your administrator for access.' end def set_invitation @@ -56,10 +57,7 @@ class Users::RegistrationsController < Devise::RegistrationsController end def valid_invitation_token? - return false unless invitation_token.present? - - invitation = FamilyInvitation.find_by(token: invitation_token) - invitation&.can_be_accepted? + @invitation&.can_be_accepted? end def invitation_token diff --git a/spec/requests/family/invitations_spec.rb b/spec/requests/family/invitations_spec.rb index 6e840c56..fd9e1c19 100644 --- a/spec/requests/family/invitations_spec.rb +++ b/spec/requests/family/invitations_spec.rb @@ -13,7 +13,7 @@ RSpec.describe 'Family::Invitations', type: :request do .to_return(status: 200, body: '[{"name": "1.0.0"}]', headers: {}) end - describe 'GET /families/:family_id/invitations' do + describe 'GET /family/invitations' do before { sign_in user } it 'shows pending invitations' do @@ -81,7 +81,7 @@ RSpec.describe 'Family::Invitations', type: :request do end end - describe 'POST /families/:family_id/invitations' do + describe 'POST /family/invitations' do before { sign_in user } context 'with valid email' do @@ -161,7 +161,7 @@ RSpec.describe 'Family::Invitations', type: :request do end end - describe 'POST /families/:family_id/invitations/:id/accept' do + describe 'POST /family/invitations/:id/accept' do let(:invitee) { create(:user) } let(:invitee_invitation) { create(:family_invitation, family: family, invited_by: user, email: invitee.email) } @@ -236,7 +236,7 @@ RSpec.describe 'Family::Invitations', type: :request do end end - describe 'DELETE /families/:family_id/invitations/:id' do + describe 'DELETE /family/invitations/:id' do before { sign_in user } it 'cancels the invitation' do diff --git a/spec/requests/family/memberships_spec.rb b/spec/requests/family/memberships_spec.rb index 4bee77cc..c276147b 100644 --- a/spec/requests/family/memberships_spec.rb +++ b/spec/requests/family/memberships_spec.rb @@ -15,7 +15,7 @@ RSpec.describe 'Family::Memberships', type: :request do sign_in user end - describe 'DELETE /families/:family_id/members/:id' do + describe 'DELETE /family/members/:id' do context 'when removing a regular member' do it 'removes the member from the family' do expect do @@ -69,7 +69,7 @@ RSpec.describe 'Family::Memberships', type: :request do let(:other_membership) { create(:family_membership, family: other_family) } it 'returns not found' do - delete "/families/#{family.id}/members/#{other_membership.id}" + delete "/family/members/#{other_membership.id}" expect(response).to have_http_status(:not_found) end end @@ -118,7 +118,7 @@ RSpec.describe 'Family::Memberships', type: :request do delete "/family/members/#{member_membership.id}" # Verify removal - expect(response).to redirect_to(family_path(family)) + expect(response).to redirect_to(family_path) expect(family.reload.members).to include(user) expect(family.members).not_to include(member_user) expect(member_user.reload.family).to be_nil @@ -130,10 +130,10 @@ RSpec.describe 'Family::Memberships', type: :request do expect(user.family_owner?).to be true # Try to remove owner - delete "/families/#{family.id}/members/#{owner_membership.id}" + delete "/family/members/#{owner_membership.id}" # Verify prevention - expect(response).to redirect_to(family_path(family)) + expect(response).to redirect_to(family_path) expect(family.reload.members).to include(user, member_user) expect(user.reload.family).to eq(family) end @@ -151,7 +151,7 @@ RSpec.describe 'Family::Memberships', type: :request do delete "/family/members/#{owner_membership.id}" end.not_to change(FamilyMembership, :count) - expect(response).to redirect_to(family_path(family)) + expect(response).to redirect_to(family_path) expect(user.reload.family).to eq(family) expect(family.reload).to be_present end diff --git a/spec/requests/family_workflows_spec.rb b/spec/requests/family_workflows_spec.rb index 3acade0c..b4c1e576 100644 --- a/spec/requests/family_workflows_spec.rb +++ b/spec/requests/family_workflows_spec.rb @@ -128,7 +128,7 @@ RSpec.describe 'Family Workflows', type: :request do # User3 accepts invitation to Family 1 sign_in user3 post "/family/invitations/#{invitation1.token}/accept" - expect(response).to redirect_to(family_path(user3.reload.family)) + expect(response).to redirect_to(family_path) expect(user3.family).to eq(family1) # User3 tries to accept invitation to Family 2 @@ -245,7 +245,7 @@ RSpec.describe 'Family Workflows', type: :request do expect(flash[:alert]).to include('not authorized') # Member cannot delete family - delete "/families/#{family.id}" + delete "/family" expect(response).to have_http_status(:see_other) expect(flash[:alert]).to include('not authorized') diff --git a/spec/requests/users/registrations_spec.rb b/spec/requests/users/registrations_spec.rb index 18d19b6b..add2d1aa 100644 --- a/spec/requests/users/registrations_spec.rb +++ b/spec/requests/users/registrations_spec.rb @@ -87,7 +87,7 @@ RSpec.describe 'Users::Registrations', type: :request do it 'redirects to family page after successful registration' do post user_registration_path, params: request_params - expect(response).to redirect_to(family_path(family)) + expect(response).to redirect_to(family_path) end it 'displays success message with family name' do @@ -187,7 +187,7 @@ RSpec.describe 'Users::Registrations', type: :request do } end.to change(User, :count).by(1) - expect(response).to redirect_to(family_path(family)) + expect(response).to redirect_to(family_path) end end @@ -320,4 +320,4 @@ RSpec.describe 'Users::Registrations', type: :request do end end end -end \ No newline at end of file +end