# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Project Overview myCRM is a modern, modular CRM application built with Symfony 7.1 LTS, Vue.js 3, and PrimeVue. It features a sophisticated permission system, API Platform for REST APIs, and a single-page application architecture. ## Common Development Commands ### Initial Setup ```bash composer install php bin/console doctrine:database:create php bin/console doctrine:migrations:migrate php bin/console doctrine:fixtures:load # Load test data (admin@mycrm.local/admin123) npm install npm run dev ``` ### Development Workflow ```bash # Terminal 1: Backend server symfony serve -d # OR: php -S localhost:8000 -t public/ # Terminal 2: Frontend with hot reload npm run watch # Production build npm run build ``` ### Database Operations ```bash # Create new migration after entity changes php bin/console make:migration php bin/console doctrine:migrations:migrate # Validate schema matches entities php bin/console doctrine:schema:validate # Reset database (dev only) php bin/console doctrine:database:drop --force php bin/console doctrine:database:create php bin/console doctrine:migrations:migrate php bin/console doctrine:fixtures:load ``` ### Testing ```bash # Backend tests php bin/phpunit # Validate Doctrine schema php bin/console doctrine:schema:validate # Check user permissions (custom command) php bin/console app:user:permissions ``` ### Code Generation ```bash # Entity with API Platform support php bin/console make:entity # Migration after entity changes php bin/console make:migration # Security voter php bin/console make:voter # API Platform state processor/provider php bin/console make:state-processor php bin/console make:state-provider ``` ### Cache Management ```bash php bin/console cache:clear APP_ENV=prod php bin/console cache:warmup ``` ## Architecture Overview ### Security System (6 Layers) The application uses a sophisticated multi-layer security architecture: 1. **Authentication Layer**: Symfony form login + OAuth (PocketId integration) 2. **Standard Roles**: `ROLE_ADMIN`, `ROLE_USER` (stored in User.roles as JSON array) 3. **Module Permission System**: Custom fine-grained permissions - User → UserRoles (many-to-many) → Role → RolePermissions → Module - 6 permission types: view, create, edit, delete, export, manage - Checked via: `$user->hasModulePermission('contacts', 'view')` 4. **Custom Voters**: Entity-level access control (4 voters) - `ModuleVoter`: Routes permission checks to module system - `ProjectVoter`: Project ownership + team member checks - `ProjectTaskVoter`: Inherits project access + admin bypass - `GitRepositoryVoter`: Requires associated project access 5. **Event Listeners**: Pre-persist security validation - `ProjectTaskSecurityListener`: Validates project tasks can only be created if user has project access 6. **Query Extensions**: Auto-filter collections - `ProjectAccessExtension`: Automatically filters project collections based on user access ### Permission Check Flow ```php // 1. Check via Security component $this->denyAccessUnlessGranted('view', $contact); // Uses ContactVoter → ModuleVoter // 2. Direct module check if ($user->hasModulePermission('contacts', 'edit')) { } // 3. API Platform security #[ApiResource( operations: [ new Get(security: "is_granted('VIEW', object)") ] )] ``` ### Entity Relationships **Core Entities:** - **User**: Authentication + role management (dual system: Symfony roles + custom UserRoles) - **Role**: Groups of module permissions - **Module**: CRM modules (contacts, projects, tasks, etc.) - **RolePermission**: Joins Role ↔ Module with 6 boolean flags - **Contact**: Business contacts (implements ModuleAwareInterface, module='contacts') - **ContactPerson**: Linked to Contact, inherits permissions - **Project**: Project management (owner + team members, module='projects') - **ProjectTask**: Subtasks under projects (standalone tasks admin-only, module='tasks') - **GitRepository**: Linked to projects, inherits project access **Key Patterns:** - **ModuleAwareInterface**: Entities that tie to permission modules - **Project Access Model**: `$project->hasAccess($user)` returns true if owner OR team member - **Permission Inheritance**: ContactPerson inherits Contact permissions, GitRepository inherits Project permissions - **Dual Role System**: User.roles (Symfony standard array) + User.userRoles (ManyToMany with Role entity) ### API Platform Configuration **Per-Entity Security:** ```php #[ApiResource( operations: [ new GetCollection(security: "is_granted('VIEW', 'contacts')"), new Get(security: "is_granted('VIEW', object)"), new Post(security: "is_granted('CREATE', 'contacts')"), new Put(security: "is_granted('EDIT', object)"), new Delete(security: "is_granted('DELETE', object)") ] )] ``` **Serialization Groups:** - `{entity}:read`: Fields exposed in GET responses - `{entity}:write`: Fields accepted in POST/PUT - Used to control field visibility based on context **Common Filters:** - `SearchFilter`: Text search on specific fields - `BooleanFilter`: Filter by boolean fields (active, isDebtor, etc.) - `DateFilter`: Filter by date ranges - Custom filters for complex queries (e.g., ProjectTaskProjectFilter) ### Frontend Architecture (Vue.js 3) **Directory Structure:** ``` /assets/js /components - Reusable components (CrudDataTable, AppMenu, AppTopbar) /views - Page-level components (ContactManagement, ProjectManagement, Dashboard) /layout - Sakai layout components (AppLayout, AppSidebar) /composables - Composition API functions (useAuth, usePermissions) /stores - Pinia state management /api - API client wrappers /router.js - Vue Router SPA navigation /assets/styles /layout - Sakai SCSS (topbar, sidebar, menu, responsive) tailwind.css - Tailwind CSS v4 with PrimeUI plugin sakai.scss - Sakai layout imports ``` **Component Pattern:** ```vue ``` **PrimeVue Usage:** - `DataTable`: Main component for entity lists (server-side pagination, filtering, sorting) - `Dialog`: Modal forms for create/edit - `Button`, `InputText`, `Dropdown`: Form components - `Chart`: Dashboard visualizations (Chart.js integration) - Theme: Aura theme with dark mode support **Webpack Encore:** - Entry point: `assets/app.js` - Aliases: `@` → `assets/js`, `@images` → `assets/images` - Hot reload: `npm run watch` - Production build: `npm run build` ## Development Conventions ### Backend (Symfony) **Controllers**: Keep thin, delegate to services ```php // Good public function create(Request $request, ContactService $service): JsonResponse { $this->denyAccessUnlessGranted('CREATE', 'contacts'); return $this->json($service->createContact($request->toArray())); } ``` **Services**: Constructor injection, use interfaces ```php class ContactService { public function __construct( private EntityManagerInterface $em, private EventDispatcherInterface $dispatcher ) {} } ``` **Entities**: Doctrine attributes, define relationships ```php #[ORM\Entity] #[ORM\Table(name: 'contacts')] #[ApiResource(/* ... */)] class Contact implements ModuleAwareInterface { public function getModuleName(): string { return 'contacts'; } } ``` **Voters**: Implement granular permissions ```php class ContactVoter extends Voter { protected function supports(string $attribute, mixed $subject): bool { return in_array($attribute, ['VIEW', 'EDIT', 'DELETE']) && ($subject instanceof Contact || $subject === 'contacts'); } protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool { // Delegate to ModuleVoter via hasModulePermission() } } ``` ### Frontend (Vue.js) **Composables**: Extract reusable logic ```javascript // composables/usePermissions.js export function usePermissions() { const hasPermission = (module, action) => { // Check user permissions } return { hasPermission } } ``` **Components**: Single File Components with Composition API ```vue ``` ## Testing Strategy - **PHPUnit**: Backend unit and functional tests - **Doctrine Schema Validation**: Run in CI to catch schema drift - **Voter Tests**: Explicitly test permission logic - **Frontend**: Vitest/Jest for Vue components (when configured) ## Important Files **Security:** - `src/Security/Voter/` - All voter implementations - `src/EventListener/ProjectTaskSecurityListener.php` - Pre-persist validation - `src/Filter/ProjectAccessExtension.php` - Query auto-filtering - `src/Entity/User.php` - hasModulePermission() method (lines ~200-220) **API Configuration:** - Entity `#[ApiResource]` attributes - Security, operations, filters - `config/packages/api_platform.yaml` - Global API settings **Frontend Entry:** - `assets/app.js` - Vue app initialization - `assets/js/router.js` - Route definitions and navigation guards - `webpack.config.js` - Encore configuration ## Key Workflows ### Adding a New Module 1. Create entity with `ModuleAwareInterface` 2. Add to Module entity fixtures/database 3. Create voter (or rely on ModuleVoter) 4. Configure API Platform security 5. Add Vue.js view component 6. Add route to router.js 7. Add menu item to AppMenu.vue ### Adding a New Permission-Controlled Feature 1. Determine permission level (module vs. entity) 2. If entity-level: create/update Voter 3. Add security checks in controller/API 4. Update frontend to check permissions before showing UI 5. Test with different user roles ### Database Schema Changes 1. Modify entity attributes 2. `php bin/console make:migration` 3. Review generated migration 4. `php bin/console doctrine:migrations:migrate` 5. `php bin/console doctrine:schema:validate` to verify 6. Update fixtures if needed ## Common Patterns **Check Module Permission (Backend):** ```php if (!$user->hasModulePermission('contacts', 'view')) { throw new AccessDeniedException(); } ``` **Check Entity Permission (Backend):** ```php $this->denyAccessUnlessGranted('EDIT', $contact); ``` **API Platform Security:** ```php #[ApiResource( security: "is_granted('ROLE_USER')", operations: [ new Get(security: "is_granted('VIEW', object)") ] )] ``` **Project Access Check:** ```php if (!$project->hasAccess($user)) { throw new AccessDeniedException(); } ``` **Frontend Permission Check:** ```javascript import { usePermissions } from '@/composables/usePermissions' const { hasPermission } = usePermissions() if (hasPermission('contacts', 'create')) { // Show create button } ``` ## Additional Documentation - `docs/LOGIN.md` - Authentication system details - `docs/PERMISSIONS.md` - Modular permission system - `docs/USER-CRUD.md` - User management with API Platform - `docs/PROJECT_TASKS_MODULE.md` - Project tasks implementation - `.github/copilot-instructions.md` - Detailed development guidelines