-
Notifications
You must be signed in to change notification settings - Fork 0
feat: PR-5 - Person API endpoints with auth and permissions (Issue #50) #64
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Implemented process-specific KEK files to eliminate race conditions
in parallel test execution.
Root cause:
- All tests shared the same KEK file (storage/app/keys/kek.key)
- Parallel processes overwrote each other's KEK files
- Led to 'Failed to unwrap idx_key' errors (~1-10% failure rate)
Solution:
- Added TenantKey::setKekPath() to support custom KEK paths
- Modified getKekPath() to use static $kekPath property when set
- Updated TenantKeyTest and PersonTest to use process-specific paths:
storage/app/keys/kek-test-{pid}.key
- Added cleanup in afterEach hooks
Testing:
- Verified 10 consecutive successful parallel test runs (79/79 pass)
- Tests run in 7-8s with 2 parallel processes
- PHPStan Level 9: Clean
- Pint PSR-12: Compliant
Fixes #62
TenantKey::generateKek() already handles directory creation, so the manual mkdir in PersonTest.php beforeEach is unnecessary. This simplifies the code and reduces duplication. Addresses Copilot review comment on PR #63.
Implements JSON API endpoints for Person resource with full auth/authz.
## Core Implementation
**PersonController** (93 LOC):
- POST /api/v1/tenants/{tenant}/persons - Create/update Person
- GET /api/v1/tenants/{tenant}/persons/by-email - Search by email
- Uses PersonRepository for business logic
- Returns sanitized JSON (no _enc/_idx exposure)
**StorePersonRequest** (58 LOC):
- Validates email_plain (required, email format)
- phone_plain and note_enc are optional
- Custom error messages
**Routes** (routes/api.php):
- Nested under auth:sanctum middleware
- SetTenant middleware for tenant context
- Spatie Permission middleware (person.write, person.read)
## Middleware Setup
**bootstrap/app.php**:
- Registered 'permission' and 'role' middleware aliases
- Required for Spatie Permission integration
**User Model**:
- Added HasRoles trait from Spatie Permission
- Enables permission assignment in tests
## Testing
**PersonApiTest** (288 LOC, 16 tests):
- ✅ Authentication (401 when not authenticated)
- ✅ Authorization (403 without permissions)
- ✅ Validation (422 for invalid data)
- ✅ Success cases (201 create, 200 search)
- ✅ Tenant isolation (404 for wrong tenant)
- ✅ Security (no _enc/_idx in responses)
- ✅ Case-insensitive search
- All 95 tests passing (237 assertions)
## Database Changes
**Migration update**:
- Made phone_enc and phone_idx nullable
- Allows creating Person with only email
## Quality Gates
- ✅ **Tests**: 95 passed, 237 assertions
- ✅ **PHPStan**: Level 9, 0 errors
- ✅ **Pint**: PSR-12 compliant
- ✅ **LOC**: ~439 new lines (under 600 limit)
- ✅ **Parallel tests**: Working perfectly
## API Examples
**Create Person:**
```bash
POST /api/v1/tenants/1/persons
Authorization: Bearer {token}
{
"email_plain": "user@example.com",
"phone_plain": "+49 123 456789",
"note_enc": "Optional note"
}
# Returns: {"id": 1, "tenant_id": 1, "created_at": "...", "updated_at": "..."}
```
**Search by Email:**
```bash
GET /api/v1/tenants/1/persons/by-email?email=user@example.com
Authorization: Bearer {token}
# Returns: {"id": 1, "tenant_id": 1, "created_at": "...", "updated_at": "..."}
```
## Related
- Issue #50 (PR-5: API Endpoints)
- Follows: PR-4 (Sanctum Auth), PR-3 (SetTenant Middleware)
- Prepares for: PR-6 (Rotation & Maintenance)
## Notes
- Permissions are team-scoped (tenant_id) via Spatie configuration
- Encrypted fields and blind indexes never exposed in responses
- Search is case-insensitive (normalized at blind index level)
- Phone is optional (can create Person with email only)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR implements Person API endpoints with parallel test execution support. The main focus is adding two new REST API endpoints for managing Person resources (create and search by email) and ensuring test isolation when running tests in parallel.
Key changes:
- Added two Person API endpoints:
POST /v1/tenants/{tenant}/personsandGET /v1/tenants/{tenant}/persons/by-email - Implemented process-specific KEK file paths in tests to enable parallel test execution without file conflicts
- Made
phone_encandphone_idxcolumns nullable in the database migration
Reviewed Changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| app/Http/Controllers/PersonController.php | New controller with store() and byEmail() endpoints for Person resource management |
| app/Http/Requests/StorePersonRequest.php | Form request class with validation rules for Person creation |
| routes/api.php | Registered Person endpoints under tenant-scoped routes with permission middleware |
| app/Models/User.php | Added HasRoles trait to enable Spatie permission functionality |
| app/Models/TenantKey.php | Added setKekPath() method to allow test-specific KEK file paths |
| bootstrap/app.php | Registered permission and role middleware aliases |
| tests/Feature/PersonApiTest.php | Comprehensive API tests covering authentication, authorization, validation, and tenant isolation |
| tests/Feature/TenantKeyTest.php | Updated to use process-specific KEK paths for parallel execution |
| tests/Feature/PersonTest.php | Updated to use process-specific KEK paths for parallel execution |
| database/migrations/2025_11_01_210213_change_bytea_columns_to_varchar.php | Made phone fields nullable |
| phpstan.neon | Added PHPStan ignores for test dynamic properties and API test patterns |
…rces Addresses Copilot PR review comments for Issue #50 PR-5. ## DRY Violations Fixed **1. KEK Path Construction (3x duplication):** - Centralized getTestKekPath() in tests/Pest.php - Removed duplicate functions from PersonTest, TenantKeyTest, PersonApiTest - Added cleanupTestKekFile() helper for consistent cleanup **2. Permission Assignment (middleware duplication):** - Moved givePermissionWithTenant() to tests/Pest.php - Used across PersonApiTest for proper Spatie Permission team context - Single source of truth for tenant-scoped permission logic **3. API Response Structure (2x duplication in Controller):** - Created PersonResource following Laravel best practices - Replaced manual JSON assembly in store() and byEmail() - Disabled resource wrapping with $wrap = null for clean responses - Consistent response format across all Person endpoints ## Changes **New:** - app/Http/Resources/PersonResource.php (45 LOC) **Modified:** - tests/Pest.php (+33 LOC) - Added 3 global test helpers - app/Http/Controllers/PersonController.php (-6 LOC) - Uses PersonResource - tests/Feature/PersonApiTest.php (-13 LOC) - Uses central helpers - tests/Feature/PersonTest.php (-5 LOC) - Uses getTestKekPath() - tests/Feature/TenantKeyTest.php (-10 LOC) - Uses cleanupTestKekFile() ## Benefits - ✅ Single source of truth for test KEK paths - ✅ Consistent permission assignment across tests - ✅ Laravel Resource pattern for API responses - ✅ ~44 LOC reduction through refactoring - ✅ Easier maintenance and updates ## Quality - ✅ Tests: 95 passed (237 assertions) - ✅ PHPStan: Level 9, 0 errors - ✅ Pint: PSR-12 compliant
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.
Resolved conflicts in PersonTest.php and TenantKeyTest.php. Kept our branch version (HEAD) which uses centralized test helpers from tests/Pest.php instead of local duplicate functions. This maintains DRY principles established in the refactoring commit.
Addresses Copilot review comment on PR #64. The manual setPermissionsTeamId() call on line 60 was redundant since givePermissionWithTenant() helper already handles setting and resetting the team ID context. This eliminates duplication and makes the code cleaner.
🎯 Overview
Implements RESTful Person API endpoints following Issue #50 PR-5 acceptance criteria. Full TDD approach with comprehensive test coverage, authentication, authorization, and tenant isolation.
Fixes #50 (PR-5)
📋 Endpoints
POST
/api/v1/tenants/{tenant}/personsperson.write{ "email_plain": "user@example.com", "phone_plain": "+49 123 456789", // optional "note_enc": "Optional note" // optional }201 Created{ "id": 1, "tenant_id": 1, "created_at": "2025-11-02T...", "updated_at": "2025-11-02T..." }GET
/api/v1/tenants/{tenant}/persons/by-email?email=...person.read200 OKor404 Not Found🔐 Security & Architecture
Middleware Stack:
Features:
SetTenantmiddlewareStorePersonRequestphone_enc,phone_idx)🧪 Testing
PersonApiTest - 16 comprehensive tests:
_enc/_idxexposureTest Helper:
Sets Spatie Permission team context for proper tenant-scoped permissions.
📊 Quality Metrics
📁 Changes
New Files
app/Http/Controllers/PersonController.php(93 LOC)app/Http/Requests/StorePersonRequest.php(58 LOC)tests/Feature/PersonApiTest.php(288 LOC)Modified Files
routes/api.php- Added Person routes with middlewarebootstrap/app.php- Registered Spatie Permission middleware aliasesapp/Models/User.php- AddedHasRolestraitdatabase/migrations/2025_11_01_210213_change_bytea_columns_to_varchar.php- Madephone_enc/phone_idxnullablephpstan.neon- Added ignores for PEST test patterns🔗 Dependencies
Requires:
SetTenantmiddleware (feat: PR-3 - Tenant Middleware & RBAC Wiring #55)PersonRepositorywith blind index searchPersonObserverPrepares for:
🎓 Design Decisions
Permission Helper Function: Created
givePermissionWithTenant()to properly set Spatie Permission team context, avoidingtenant_id NOT NULLconstraint violations.PHPDoc Type Hints: Added
@var int $tenantIdannotations in controller to satisfy PHPStan Level 9 with mixed route parameters.PHPStan Ignores: Added patterns for PEST dynamic properties (
$this->tenant,$this->user) to avoid false positives.Nullable Phone: Made
phone_encandphone_idxnullable since phone is an optional field.Sanitized Responses: PersonController returns only safe fields (id, tenant_id, timestamps) - encrypted fields and blind indexes are hidden via model
$hiddenproperty.✅ Issue #50 PR-5 Acceptance Criteria
[auth:api, SetTenant]permission:person.write/read_enc/_idxexposure🧑💻 Review Checklist
Ready for review! 🚀