Restructure Roaster/RoasterCrawlConfig Architecture¶
Overview¶
Restructure the relationship between CoffeeBean, Roaster, and RoasterCrawlConfig to better reflect the reality that: 1. Beans are sourced from specific crawl configurations (websites) 2. Different crawl configs may have different websites and shipping rules 3. Roasters need user-facing shipping information text
Phase 1: Entity Changes¶
1.1 Add shipping_info to Roaster entity¶
- Add nullable
textfield for user-entered shipping display text - Examples: "Ships worldwide except EU", "Ships to USA & Canada only"
- Add to admin form as textarea with help text
- Add validation (max length ~500 chars)
1.2 Add website to RoasterCrawlConfig entity¶
- Add nullable
stringfield (max 255 chars) - Store the specific website URL for this crawl config
- Different regions may have different websites
- Add to admin form with URL validation
1.3 Change CoffeeBean relationship¶
- Current:
roaster(ManyToOne to Roaster, required) - New:
roasterCrawlConfig(ManyToOne to RoasterCrawlConfig, required) - Keep roaster accessible via
$bean->getRoasterCrawlConfig()->getRoaster() - Update all entity methods, annotations, and validation
Phase 2: Database Migration¶
2.1 Create migration¶
- Add
shipping_infocolumn toroastertable - Add
websitecolumn toroaster_crawl_configtable - Add
roaster_crawl_config_idcolumn tocoffee_beantable - Create foreign key constraint for new relationship
2.2 Data migration strategy¶
- For each existing CoffeeBean with a Roaster:
- Find or create a "default" RoasterCrawlConfig for that Roaster
- If Roaster has only 1 active RoasterCrawlConfig, use that
- If multiple configs exist, use the first active one (or create new one)
- Update CoffeeBean to point to RoasterCrawlConfig
- For beans with CrawlUrl: link to existing RoasterCrawlConfig from CrawlUrl
- Handle edge cases: beans without roaster, inactive configs, etc.
2.3 Drop old relationship¶
- Remove
roaster_idcolumn fromcoffee_beantable (after data migration) - Update indexes as needed
Phase 3: Repository Updates¶
3.1 CoffeeBeanRepository changes¶
- Update all queries to join
roasterCrawlConfiginstead of directroaster - Join through to
roasterviaroasterCrawlConfig.roaster - Update eager loading:
cc,cc.roaster,cc.shippingRegion, etc. - Add visitor country filtering logic:
3.2 RoasterRepository changes¶
- Keep existing queries mostly the same
- Ensure crawl configs are properly loaded with new
websitefield - Add filtering by visitor country to return only matching configs
3.3 RoasterCrawlConfigRepository changes¶
- Add method to find default config for a roaster
- Add method to find config by roaster + visitor country
Phase 4: DTO Updates¶
4.1 RoasterDTO¶
- Add
shipping_infofield (nullable string) - Optionally filter crawl configs by visitor country before mapping
4.2 CoffeeBeanDTO¶
- Change
roasterfield to use roasterCrawlConfig's roaster - Keep same structure for API consumers (transparent change)
- Add
websitefrom roasterCrawlConfig (optional)
4.3 New/Updated RoasterCrawlConfigDTO¶
- Include
websitefield - Include shipping region/country data as currently structured
Phase 5: Mapper Updates¶
5.1 EntityToDtoMapper changes¶
- Update
mapCoffeeBeanToDto()to access roaster via crawl config - Update
mapRoasterToDto()to includeshipping_infofield - Ensure crawl config website is included where needed
- Keep existing shipping mapping logic (already optimized)
Phase 6: API Endpoint Updates¶
6.1 Add visitor country filter to Coffee Beans¶
/api/coffee-beans?visitorCountryId={uuid}- Filter beans where roasterCrawlConfig ships to that country
- Use optimized query with shipping region checks
- Return standard CoffeeBeanDTO array
6.2 Update Roaster detail endpoint¶
/api/roasters/{id}- return all crawl configs/api/roasters/{id}?visitorCountryId={uuid}- return only matching configs- Include new
shipping_infotext in response - Each crawl config includes
websitefield
6.3 Update OpenAPI/Nelmio documentation¶
- Document new
shipping_infofield on RoasterDTO - Document new
websitefield on crawl configs - Document
visitorCountryIdquery parameter for both endpoints - Add examples showing filtered vs unfiltered responses
Phase 7: Admin Interface Updates¶
7.1 Roaster admin form¶
- Add
shipping_infotextarea field - Help text: "User-facing shipping information (e.g., 'Ships worldwide except EU')"
- Max length validation and character counter
7.2 RoasterCrawlConfig admin form¶
- Add
websitetext field with URL validation - Help text: "Website URL for this crawl configuration"
- Ensure this displays in list view
7.3 CoffeeBean admin form¶
- Change roaster selector to roasterCrawlConfig selector
- Grouped by roaster name for easier selection
- Show roaster name + website in dropdown
- Update any roaster-related validation
Phase 8: Event Listener Updates¶
8.1 CoffeeBeanListener¶
- Update any logic that accesses roaster directly
- Change to access via
getRoasterCrawlConfig()->getRoaster() - Ensure slug generation still works correctly
8.2 Cache invalidation¶
- Ensure roaster cache tags invalidate correctly
- Add cache invalidation when crawl config changes affect beans
- Test cache warming with new structure
Phase 9: Test Updates¶
9.1 Unit tests¶
- Update CoffeeBeanTest for new relationship
- Update RoasterTest to include shipping_info
- Update RoasterCrawlConfigTest to include website
- Test entity getters/setters for new fields
9.2 Repository tests¶
- Test visitor country filtering logic thoroughly
- Test edge cases: worldwide shipping, no shipping, exceptions
- Test performance with new joins
9.3 Integration/API tests¶
- Test
/api/coffee-beans?visitorCountryId=...filtering - Test
/api/roasters/{id}?visitorCountryId=...filtering - Test that shipping_info appears in responses
- Test that website appears in crawl config data
9.4 Migration tests¶
- Test migration up/down
- Verify data integrity after migration
- Test edge cases in data migration logic
Phase 10: Data Integrity & Rollback Plan¶
10.1 Pre-migration validation¶
- Count beans without roaster (should be 0)
- Count roasters without crawl configs
- Identify any orphaned data
10.2 Post-migration validation¶
- Verify all beans have roasterCrawlConfig
- Verify no beans lost their roaster connection
- Spot-check API responses for correctness
10.3 Rollback strategy¶
- Keep old
roaster_idcolumn temporarily (mark deprecated) - If issues found, can restore old relationship
- Remove deprecated column only after successful production deployment
Implementation Order¶
- Entity changes (shipping_info, website, new relationship)
- Migration creation (with comprehensive data migration)
- Repository query updates
- DTO and mapper updates
- API endpoint updates
- Admin form updates
- Event listener updates
- Test updates (all types)
- Run migration on test/staging
- Validation and rollback testing
- Production deployment with monitoring