Skip to content

Latest commit

 

History

History
186 lines (144 loc) · 12.2 KB

File metadata and controls

186 lines (144 loc) · 12.2 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project Overview

Hi.Events is an open-source event management and ticketing platform with a Laravel backend and React frontend, using Domain-Driven Design (DDD).

Key Commands

Backend (Laravel)

Commands must be executed in the backend docker container:

cd docker/development

docker compose -f docker-compose.dev.yml exec backend php artisan migrate
docker compose -f docker-compose.dev.yml exec backend php artisan generate-domain-objects
docker compose -f docker-compose.dev.yml exec backend php artisan test
docker compose -f docker-compose.dev.yml exec backend php artisan test --filter=TestName
docker compose -f docker-compose.dev.yml exec backend php artisan test --testsuite=Unit
docker compose -f docker-compose.dev.yml exec backend ./vendor/bin/pint --test

Frontend (React + Vite) - SSR Only

cd frontend
yarn install
yarn dev:ssr              # Development server
yarn build                # SSR build
yarn messages:extract     # Extract translatable strings
yarn messages:compile     # Compile translations
npx tsc --noEmit          # TypeScript validation

Docker Development

cd docker/development
./start-dev.sh                     # Unsigned SSL certs
./start-dev.sh --certs=signed      # Signed certs with mkcert

API smoke-testing (after backend changes)

Unit tests miss wiring bugs — exercise changed endpoints against the dev stack. Base URL https://localhost:8443/api (self-signed — curl -sk). Endpoints are defined in backend/routes/api.php.

# Verified SUPERADMIN account + organizer + LIVE single/recurring events + products
# (paid has waitlist) + promo + affiliate; prints ids and a Bearer token:
docker compose -f docker-compose.dev.yml exec backend php artisan dev:bootstrap

# Manual token (`token` field → `Authorization: Bearer <token>`):
curl -sk -X POST https://localhost:8443/api/auth/login -H "Content-Type: application/json" -d '{"email":"<email>","password":"<password>"}'

Gotchas:

  • Product upsert requires product_type (TICKET/GENERAL), type (FREE/PAID/DONATION/TIERED) and product_category_id
  • Promo create requires applicable_product_ids: [] (empty = all products)
  • Public order complete requires email_confirmation on the order and on each product entry
  • Publishing needs accounts.account_verified_at; /admin/* needs role SUPERADMIN (dev:bootstrap handles both)
  • Emails land in Mailpit at http://localhost:8025 (/api/v1/search?query=to:<addr>)

Development Guidelines

Comments — hard rule for all code (backend, frontend, SCSS)

  • DON'T add explanatory comments. The code must speak for itself.
  • This includes "why" comments justifying a design choice ("X is intentionally omitted because…", "matches the rest of the section rhythm…", "Views live on the per-event-per-day table…", "Online events satisfy the where requirement…"). If you'd write that, rename a variable / extract a function / restructure the code instead, or just leave it implicit.
  • Functional annotations are fine: PHPDoc @throws / @return / @param, // TODO(handle:owner) linked to a tracked task, schema comments inside SQL migrations that future migrations depend on.
  • Never restate what the next line does. If a reviewer can read the diff and understand it, the comment is noise.
  • If you're tempted to leave a comment "for the next agent", don't — write it as a CLAUDE.md note instead.

Backend

Architecture Flow

  • Request flow: Action → Handler → Domain Service → Repository
  • Handlers can use repositories directly when a service would be overkill
  • No Eloquent in handlers or services — Eloquent belongs in repositories only
  • Favour composition over inheritance
  • Keep code clean, but don't be dogmatic about it

General Standards

  • ALWAYS wrap all translatable strings in __() helper
  • Domain Objects are auto-generated via php artisan generate-domain-objects - never edit manually
  • Always create unit tests for new features in backend/tests/Unit/
  • DON'T add comments — see the comments rule above. No exceptions for "this seems useful context".
  • NEVER leave dead code. Code that has no production callers — unused methods, unused DTO fields, unused constants, columns that are written but never read, classes only called from tests — must be deleted, not left "for future use". This applies to both backend and frontend. If you add a method speculatively, wire it to a real caller in the same change or remove it. The same rule applies after refactors: if something becomes unreferenced, it goes. Confirm with grep before claiming a method or class is reachable.
  • ALWAYS sanitize user-provided content with HtmlPurifierService before storing, especially content rendered as HTML

DTOs

  • Use Spatie Laravel Data package for all new DTOs
  • ALWAYS extend BaseDataObject, not BaseDTO
  • ALWAYS favor DTOs over arrays when returning multiple values from services

HTTP Actions

  • Always extend BaseAction.php
  • ALWAYS use BaseAction response methods: resourceResponse(), jsonResponse(), errorResponse(), deletedResponse(), etc. Never use response()->json() or new JsonResponse() directly
  • Always use isActionAuthorized for non-public endpoints
  • DON'T create actions handling multiple entity types with optional parameters - create separate, focused actions instead
  • DO use base classes to share common validation and logic

Exception Handling

  • DON'T use generic exceptions like InvalidArgumentException and RuntimeException
  • DO use custom exceptions (e.g., EmailTemplateValidationException, ResourceConflictException)
  • DO catch custom exceptions in actions and convert to ValidationException::withMessages() or appropriate error responses

Repository Pattern

  • Favour existing repository methods over creating bespoke ones. E.g., use findFirstWhere(['event_id' => $eventId]) instead of creating findByEventId
  • Bespoke repository methods must wrap their query in runQuery() — it is the single point that resets $this->model/$this->eagerLoads after each call
  • increment()/decrement() are findOrFail-based and throw for soft-deleted rows; use the where-based incrementEach()/decrementEach() when the target row may have been deleted (e.g. a promo code deleted after orders used it)

Mail & side effects

  • BaseMail is queued and afterCommit() — a mail sent inside a DB transaction that rolls back is silently discarded. Chain ->beforeCommit() on the mailable when the send must survive a deliberate rollback (e.g. refund-and-reject webhook paths)
  • Promo usage, products.sales_volume and affiliate sales counters increment only when an order completes — any decrement must be gated on isOrderCompleted() (or equivalent) to stay symmetric

Database & Migrations

  • DO use auto-incrementing integer IDs ($table->id()), not UUIDs
  • Use anonymous class syntax for migrations

Enums

  • Status enums go in backend/app/DomainObjects/Status/
  • Other enums go in backend/app/DomainObjects/Enums/

Testing

  • DON'T use RefreshDatabase - use DatabaseTransactions instead
  • Unit tests extend Laravel's TestCase, not PHPUnit's TestCase
  • Use Mockery for mocking
  • Unit suite (tests/Unit/) is for pure isolation tests — no DB, no HTTP, no real container resolution. If a test uses DatabaseTransactions, hits the DB (raw DB:: calls, factories that persist, repository methods that query), or boots significant framework state, it's an integration test and belongs in tests/Feature/ (mirror the path, e.g. tests/Feature/Repository/Eloquent/). Running --testsuite=Unit must stay fast and DB-free.
  • Tests run against a dedicated hievents_test database, configured via backend/.env.testing and enforced by phpunit.xml. The local docker-compose creates this database automatically via docker/development/pgsql-init/. If your existing pgsql volume predates this script, create the DB once with: docker compose -f docker-compose.dev.yml exec pgsql psql -U username -d backend -c 'CREATE DATABASE hievents_test OWNER username;'
  • Database name must end in _test. Enforced globally by a final guard in tests/TestCase.php::guardAgainstNonTestDatabase() which runs on every test that boots Laravel — no per-test opt-in needed and no way to bypass.

Frontend

General Standards

  • This is a SSR app - ensure safe usage of window and document objects
  • Favour using existing components over creating new ones
  • DON'T include unnecessary React imports
  • ALWAYS add translations when adding new user-facing strings - use Lingui t function or Trans component
  • IMMEDIATELY after adding translatable strings, add translations for all supported languages (use /translations skill for the workflow)

Data Fetching

  • Use React Query for all API interactions
  • Query example: frontend/src/queries/useGetCapacityAssignment.ts
  • Mutation example: frontend/src/mutations/useCreateAffiliate.ts

UI & Styling

  • Use Mantine UI components for UI elements
  • Prefer SCSS modules over Mantine layout components for layout styling

E2E Tests

  • There is a Playwright E2E suite in e2e/ (see e2e/README.md). It runs the real stack (Laravel + SSR frontend + Postgres + Redis + Mailpit) in Docker.
  • To test uncommitted changes, run specs against the dev stack — the hermetic e2e stack bakes source into images and docker compose up never rebuilds them. From e2e/:
    E2E_BASE_URL=https://localhost:8443 MAILPIT_URL=http://localhost:8025 E2E_SAAS_MODE=true npx playwright test <spec>
    E2E_SAAS_MODE=true is required (the dev stack requires email verification; the fixture only confirms via Mailpit in SaaS mode), a queue worker must be running to deliver the verification emails, and superadmin-dependent specs need a one-time php artisan dev:bootstrap --email=superadmin@e2e.test --password='SuperAdminPass123!'. See "Against the running dev stack" in e2e/README.md.
  • When you add or meaningfully change a user-facing flow, add or update an E2E spec for it where practical. Follow the existing pattern: arrange data via the API/factory, drive only the flow under test through the UI with a thin page object, and assert on real page content (the created/edited item appears), not just a URL change. Tag fast, load-bearing checks with @smoke.
  • Not everything needs E2E — reserve it for real user journeys (create/edit/complete flows). Pure logic belongs in backend unit/feature tests instead.

Test IDs (E2E)

  • Add a data-testid to interactive elements the E2E suite needs to drive — primarily buttons (open-modal triggers, submit/save), menu items, and custom widgets with no accessible label (e.g. CustomSelect, which takes a dataTestId prop that lands on its target and options). This is not required for every element: text inputs with a unique <label> are found by role/label instead, so don't add IDs there.
  • Convention: kebab-case <feature>-<element>, e.g. promo-code-create-button, webhook-submit-button, product-edit-menu-item. For CustomSelect, options are auto-derived as <dataTestId>-option-<value>.
  • Only add IDs for elements a test actually interacts with; don't blanket-annotate new UI.

Error Handling

  • DON'T use showNotification from @mantine/notifications
  • DO use showSuccess, showError from frontend/src/utilites/notifications.tsx
  • DO use useFormErrorResponseHandler from frontend/src/hooks/useFormErrorResponseHandler.tsx for validation errors
  • DO handle errors in parent components, not in reusable components

Git Commit Guidelines

  • NEVER add Co-Authored-By: Claude or any AI attribution lines to commit messages.

Development Workflows

Database Changes (in Backend Container)

  1. Create migration: php artisan make:migration create_XXX_table
  2. Run migration: php artisan migrate
  3. Regenerate Domain Objects: php artisan generate-domain-objects

Before Finalizing Changes

  1. Frontend: cd frontend && npx tsc --noEmit
  2. Backend: docker compose -f docker-compose.dev.yml exec backend php artisan test --testsuite=Unit