Implementiert einen sicheren Weg zum Entfernen von Modulen aus dem System: Features: - Zeigt Modul-Informationen (Plugin + Datenbank) an - Widerruft Lizenz automatisch (optional mit --keep-license) - Löscht Datenbank-Eintrag im Permission-System (optional mit --keep-db-entry) - Warnt vor verknüpften Permissions - Zeigt nächste manuelle Schritte (Composer, Bundle, Migrations, Cache) - Bestätigung erforderlich (überspringbar mit --force) Dokumentation: - Command-Hilfe in src/Command/ModuleRemoveCommand.php - Benutzer-Anleitung in docs/PLUGIN_QUICKSTART.md - Entwickler-Referenz in CLAUDE.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
12 KiB
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
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
# 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
# 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
# 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 <email>
Code Generation
# 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
php bin/console cache:clear
APP_ENV=prod php bin/console cache:warmup
Module Management (Plugin System)
# List all installed modules with license status
php bin/console app:module:list
# Register/manage module license
php bin/console app:module:license billing # Interactive
php bin/console app:module:license billing <license-key> # Direct
php bin/console app:module:license billing --validate # Validate
php bin/console app:module:license billing --revoke # Revoke
# Remove a module safely
php bin/console app:module:remove billing # Interactive
php bin/console app:module:remove billing --force # No confirmation
php bin/console app:module:remove billing --keep-license # Keep license
php bin/console app:module:remove billing --keep-db-entry # Keep DB entry
Architecture Overview
Security System (6 Layers)
The application uses a sophisticated multi-layer security architecture:
- Authentication Layer: Symfony form login + OAuth (PocketId integration)
- Standard Roles:
ROLE_ADMIN,ROLE_USER(stored in User.roles as JSON array) - 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')
- Custom Voters: Entity-level access control (4 voters)
ModuleVoter: Routes permission checks to module systemProjectVoter: Project ownership + team member checksProjectTaskVoter: Inherits project access + admin bypassGitRepositoryVoter: Requires associated project access
- Event Listeners: Pre-persist security validation
ProjectTaskSecurityListener: Validates project tasks can only be created if user has project access
- Query Extensions: Auto-filter collections
ProjectAccessExtension: Automatically filters project collections based on user access
Permission Check Flow
// 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:
#[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 fieldsBooleanFilter: 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:
<script setup>
import { ref } from 'vue'
import CrudDataTable from '@/components/CrudDataTable.vue'
const items = ref([])
const apiEndpoint = '/api/contacts'
async function loadData() {
const response = await fetch(apiEndpoint)
items.value = await response.json()
}
</script>
PrimeVue Usage:
DataTable: Main component for entity lists (server-side pagination, filtering, sorting)Dialog: Modal forms for create/editButton,InputText,Dropdown: Form componentsChart: 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
// 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
class ContactService
{
public function __construct(
private EntityManagerInterface $em,
private EventDispatcherInterface $dispatcher
) {}
}
Entities: Doctrine attributes, define relationships
#[ORM\Entity]
#[ORM\Table(name: 'contacts')]
#[ApiResource(/* ... */)]
class Contact implements ModuleAwareInterface
{
public function getModuleName(): string { return 'contacts'; }
}
Voters: Implement granular permissions
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
// composables/usePermissions.js
export function usePermissions() {
const hasPermission = (module, action) => {
// Check user permissions
}
return { hasPermission }
}
Components: Single File Components with Composition API
<script setup>
import { ref, onMounted } from 'vue'
const props = defineProps({
apiEndpoint: String
})
const emit = defineEmits(['save', 'cancel'])
// Component logic here
</script>
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 implementationssrc/EventListener/ProjectTaskSecurityListener.php- Pre-persist validationsrc/Filter/ProjectAccessExtension.php- Query auto-filteringsrc/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 initializationassets/js/router.js- Route definitions and navigation guardswebpack.config.js- Encore configuration
Key Workflows
Adding a New Module
- Create entity with
ModuleAwareInterface - Add to Module entity fixtures/database
- Create voter (or rely on ModuleVoter)
- Configure API Platform security
- Add Vue.js view component
- Add route to router.js
- Add menu item to AppMenu.vue
Adding a New Permission-Controlled Feature
- Determine permission level (module vs. entity)
- If entity-level: create/update Voter
- Add security checks in controller/API
- Update frontend to check permissions before showing UI
- Test with different user roles
Database Schema Changes
- Modify entity attributes
php bin/console make:migration- Review generated migration
php bin/console doctrine:migrations:migratephp bin/console doctrine:schema:validateto verify- Update fixtures if needed
Common Patterns
Check Module Permission (Backend):
if (!$user->hasModulePermission('contacts', 'view')) {
throw new AccessDeniedException();
}
Check Entity Permission (Backend):
$this->denyAccessUnlessGranted('EDIT', $contact);
API Platform Security:
#[ApiResource(
security: "is_granted('ROLE_USER')",
operations: [
new Get(security: "is_granted('VIEW', object)")
]
)]
Project Access Check:
if (!$project->hasAccess($user)) {
throw new AccessDeniedException();
}
Frontend Permission Check:
import { usePermissions } from '@/composables/usePermissions'
const { hasPermission } = usePermissions()
if (hasPermission('contacts', 'create')) {
// Show create button
}
Additional Documentation
docs/LOGIN.md- Authentication system detailsdocs/PERMISSIONS.md- Modular permission systemdocs/USER-CRUD.md- User management with API Platformdocs/PROJECT_TASKS_MODULE.md- Project tasks implementation.github/copilot-instructions.md- Detailed development guidelines