The Software Catalog app is a Nextcloud application that provides automated user management, group assignment, and organizational hierarchy management. The system has evolved from an event-driven architecture to a cron-based synchronization system with manual trigger capabilities to avoid racing conditions and ensure data consistency.
Location: 'lib/Service/SoftwareCatalogueService.php'
The main service handling all user and organization management logic:
- User account creation and management
- Group assignment and management
- Organization hierarchy handling
- Manager relationship establishment
- Email notifications
Key Methods:
- 'processContactgegevens()' - Main entry point for user processing
- 'processOrganization()' - Main entry point for organization processing
- 'updateUserGroups()' - Handles all group assignments
- 'ensureOrganizationBeheerder()' - Manages organizational hierarchy
- 'getUserManager()' - Retrieves user manager information
Location: 'lib/Service/OrganizationSyncService.php'
NEW: The dedicated service for organization synchronization between SoftwareCatalog objects and OpenRegister entities:
- Comprehensive organization and contact person synchronization
- User account management for contact persons
- Organization entity creation and management
- Admin user protection during status changes
- Detailed logging for all synchronization steps
Key Methods:
- 'performFullSync()' - Core synchronization logic
- 'performScheduledSync()' - Scheduled (cron) synchronization with logging
- 'performManualSync()' - Manual (API) synchronization with logging
- 'getSyncStatus()' - Synchronization status and statistics
- 'recordSyncTime()' - Track last synchronization time
Location: 'lib/Service/SettingsService.php'
Manages application configuration and schema mappings:
- Schema ID retrieval for different object types
- Register-specific configuration handling
- Settings persistence and loading
Location: 'lib/Service/EmailService.php'
Handles automated email notifications:
- Welcome emails for new organizations
- User creation notifications
- Role assignment notifications
Location: 'lib/BackgroundJob/OrganizationContactSyncJob.php'
NEW: Background job that runs every 5 minutes to ensure data consistency:
- Delegates all business logic to OrganizationSyncService
- No direct business logic - pure orchestration
- Handles job scheduling and execution
- Minimal dependencies for reliability
Key Features:
- Runs every 5 minutes (300 seconds)
- Calls 'OrganizationSyncService::performScheduledSync()'
- Comprehensive error handling and logging
- No direct database or service dependencies
Location: 'lib/EventListener/SoftwareCatalogEventListener.php'
NOTE: Event listeners are now primarily used for contact person processing, while organization synchronization uses the cron-based system.
Central event listener that processes OpenRegister object events:
- Listens to ObjectCreatedEvent, ObjectUpdatedEvent, ObjectDeletedEvent
- Routes events to appropriate service methods
- Handles different object types (contactgegevens, organization, gebruiker, contact)
Supported Events:
- 'ObjectCreatedEvent' - New object creation
- 'ObjectUpdatedEvent' - Object modifications
- 'ObjectDeletedEvent' - Object removal
- 'ObjectLockedEvent' - Object locking
- 'ObjectUnlockedEvent' - Object unlocking
- 'ObjectRevertedEvent' - Object reversion
Location: 'appinfo/routes.php'
Defines API endpoints:
- Settings management endpoints
- Schema configuration endpoints
- Load configuration endpoints
- NEW: Manual synchronization trigger endpoint
Location: 'lib/AppInfo/Application.php'
Handles application initialization:
- Event listener registration
- Service container setup
- Dependency injection configuration
- NEW: Background job registration
1. Cron Job Trigger (every 5 minutes)
↓
2. OrganizationContactSyncJob.run()
↓
3. OrganizationSyncService.performScheduledSync()
↓
4. OrganizationSyncService.performFullSync()
├── Get all organisatie objects from OpenRegister
├── For each organization:
│ ├── Ensure organization entity exists
│ ├── Get all contact persons for organization
│ ├── Process each contact person (create/update users)
│ └── Update organization entity with all usernames
└── Record sync time and log results
↓
5. Manual Trigger (API endpoint)
↓
6. SettingsController.performSync()
↓
7. OrganizationSyncService.performManualSync()
↓
8. Same core logic as scheduled sync
1. Contactgegevens Object Created/Updated
↓
2. SoftwareCatalogEventListener receives event
↓
3. Event routed to handleObjectCreated/Updated
↓
4. SoftwareCatalogueService.processContactgegevens()
↓
5. Username generation from name fields
↓
6. User account creation in Nextcloud
↓
7. Group assignment (role-based, organization, special)
↓
8. Organization beheerder check and assignment
↓
9. Manager relationship establishment
↓
10. Object updated with username
1. Organization Object Created/Updated
↓
2. SoftwareCatalogEventListener receives event (legacy)
↓
3. Event routed to handleObjectCreated/Updated
↓
4. SoftwareCatalogueService.processOrganization()
↓
5. Organization group creation (if needed)
↓
6. Group ID stored back to organization object
↓
7. Existing users linked to organization group
↓
8. NEW: Organization sync job will handle entity creation
1. User processing initiated
↓
2. updateUserGroups() called
↓
3. Role-based group assignment
├── Check user roles array
├── Create groups if needed
├── Add/remove user from groups
└── Log changes
↓
4. Organization group assignment
├── Get organization UUID from user data
├── Find organization object
├── Get organization group ID
└── Add user to organization group
↓
5. Group management (manual assignment only)
├── Create required groups if needed
├── 'ambtenaar' group available for manual assignment
└── No automatic assignment based on organization type
Problems with Event-Driven System:
- Racing conditions between multiple event listeners
- Inconsistent state when events fire out of order
- Difficult to debug and trace execution flow
- No guarantee of completion order
Benefits of Cron-Based System:
- Predictable execution every 5 minutes
- Comprehensive logging of all steps
- Manual trigger capability for immediate sync
- No racing conditions - single execution path
- Easy to test and debug
- Check register and schema configuration
- Validate required services are available
- Log configuration status
- Get all organisatie objects from specified register/schema
- Log count of objects found
- Handle retrieval errors gracefully
- For each organization object:
- Check if organization entity exists
- Create entity if missing
- Update entity if needed
- Log creation/update operations
- For each organization:
- Get all contact persons for the organization
- Process each contact person:
- Check if user account exists
- Create user account if missing
- Update user if needed
- Log user operations
- Update organization entity with all usernames
- Include admin users in the list
- Log user list changes
- Handle update errors
- Record synchronization completion time
- Log comprehensive statistics
- Handle and log any errors
- URL:
POST /apps/softwarecatalog/api/settings/sync - Authentication: Required (admin or authorized user)
- Response: JSON with sync results and statistics
1. API Request to SettingsController.performSync()
↓
2. OrganizationSyncService.performManualSync()
↓
3. Same core logic as scheduled sync
↓
4. API Response with results
The system integrates with Nextcloud's built-in user and group management:
User Management:
- 'IUserManager' - User creation and retrieval
- 'IUser' - User object manipulation
- User preferences for manager storage
Group Management:
- 'IGroupManager' - Group creation and management
- 'IGroup' - Group object manipulation
- User-group relationship management
The system depends on OpenRegister for object storage and events:
Object Storage:
- ObjectService for object persistence
- ObjectEntity for object representation
- Schema-based object validation
Event System:
- Event dispatching for object lifecycle
- Event listener registration
- Type-safe event handling
Entity System:
- Organization entities for user management
- Entity-Object relationship management
- UUID consistency across systems
The system uses schema IDs to identify object types:
// Register-specific schemas
'amef_organization_schema' => '123'
'voorzieningen_gebruiker_schema' => '456'
'voorzieningen_organisatie_schema' => '789'
'voorzieningen_contactpersoon_schema' => '101'
// Generic schemas (fallback)
'organization_schema' => '123'
'gebruiker_schema' => '456'
'contact_schema' => '789'The system supports multiple register types:
- AMEF Register: Organization schema configuration
- Voorzieningen Register: User, organization, and contact schemas
- Generic Fallback: Default schema configuration
- Group-based access control
- Manager hierarchy for authorization
- Role-based feature access
- Admin user protection during sync
- Input sanitization for group names
- Type safety for schema ID comparisons
- Graceful handling of malformed data
- UUID format validation and conversion
- Comprehensive exception catching
- Detailed error logging
- Graceful degradation on service failures
- Sync error tracking and reporting
- Batch processing of organizations
- Efficient user lookup and creation
- Minimal database operations
- Progress tracking and logging
- Parallel tool execution where possible
- Efficient group membership checking
- Cached schema ID lookups
- Batch user operations where applicable
- Optimized group queries
- Minimal object re-saves
- Appropriate log levels to avoid spam
- Contextual information for debugging
- Performance-critical path optimization
- Structured logging for analysis
- Add event handling in SoftwareCatalogEventListener
- Create processing method in SoftwareCatalogueService
- Update schema configuration in SettingsService
- Add documentation for new workflow
- Extend '_defaultGroups' array for new role-based groups
- Implement custom assignment logic in '_updateRoleBasedGroups'
- Add special handling in '_updateGemeenteGroups'
- Register additional event listeners in Application.php
- Create custom event handler methods
- Integrate with existing service methods
- Add new sync methods to OrganizationSyncService
- Extend sync statistics and reporting
- Add custom sync triggers or conditions
- User Manager API ('OCP\IUserManager')
- Group Manager API ('OCP\IGroupManager')
- Configuration API ('OCP\IConfig')
- Logger API ('Psr\Log\LoggerInterface')
- Event Dispatcher ('OCP\EventDispatcher\IEventDispatcher')
- NEW: Background Job API ('OCP\BackgroundJob\IJobList')
- NEW: Time Factory API ('OCP\AppFramework\Utility\ITimeFactory')
- OpenRegister: Provides object storage and event system
- Software Catalog Settings: UI for schema configuration
- PHP 8.1+ for typed properties and union types
- Composer autoloader for dependency management
- PSR-4 autoloading for class structure
softwarecatalog/
├── appinfo/
│ ├── routes.php # API endpoint definitions
│ └── info.xml # App metadata
├── lib/
│ ├── AppInfo/
│ │ └── Application.php # App bootstrap
│ ├── BackgroundJob/ # NEW: Background jobs
│ │ └── OrganizationContactSyncJob.php
│ ├── Controller/ # API controllers
│ ├── EventListener/ # Event handling
│ ├── Service/ # Business logic
│ │ ├── SoftwareCatalogueService.php
│ │ ├── OrganizationSyncService.php # NEW
│ │ ├── SettingsService.php
│ │ └── EmailService.php
│ └── ...
├── src/ # Frontend assets
├── docs/ # Documentation
└── vendor/ # Dependencies
- Schema Configuration: Stored in Nextcloud app config
- Register Settings: JSON files in app directory
- User Preferences: Nextcloud user preference system
- Group Memberships: Nextcloud group system
- Sync Configuration: Stored in Nextcloud app config
Manual Testing:
- Use "Sync Now" button in settings UI
- Monitor logs for detailed execution steps
- Verify organization and user creation
- Check entity-object consistency
Automated Testing:
- Background job execution testing
- API endpoint testing
- Service method unit testing
- Integration testing with OpenRegister
Key Log Patterns:
OrganizationSyncService: Starting comprehensive organization synchronizationOrganizationSyncService: Found organisatie objectsOrganizationSyncService: Processing organisatie objectOrganizationSyncService: Creating new organisation entityOrganizationSyncService: Creating user account for contact personOrganizationSyncService: Successfully updated organisation entity users
Debug Commands:
# Monitor sync logs
docker-compose exec nextcloud tail -f /var/www/html/data/nextcloud.log | grep -i "organizationsyncservice"
# Check sync status
curl -u 'admin:admin' 'http://localhost/index.php/apps/softwarecatalog/api/settings/sync-status'
# Manual sync trigger
curl -u 'admin:admin' -X POST 'http://localhost/index.php/apps/softwarecatalog/api/settings/sync'