Skip to content

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 text field 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 string field (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_info column to roaster table
  • Add website column to roaster_crawl_config table
  • Add roaster_crawl_config_id column to coffee_bean table
  • 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_id column from coffee_bean table (after data migration)
  • Update indexes as needed

Phase 3: Repository Updates

3.1 CoffeeBeanRepository changes

  • Update all queries to join roasterCrawlConfig instead of direct roaster
  • Join through to roaster via roasterCrawlConfig.roaster
  • Update eager loading: cc, cc.roaster, cc.shippingRegion, etc.
  • Add visitor country filtering logic:
    WHERE (cc.shippingRegion IS NULL)  -- No shipping restrictions
       OR (sr.id = :regionId AND :countryId NOT IN exception_countries)  -- In region, not excluded
       OR (:countryId IN cc.shipsTo)  -- In custom country list
    

3.2 RoasterRepository changes

  • Keep existing queries mostly the same
  • Ensure crawl configs are properly loaded with new website field
  • 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_info field (nullable string)
  • Optionally filter crawl configs by visitor country before mapping

4.2 CoffeeBeanDTO

  • Change roaster field to use roasterCrawlConfig's roaster
  • Keep same structure for API consumers (transparent change)
  • Add website from roasterCrawlConfig (optional)

4.3 New/Updated RoasterCrawlConfigDTO

  • Include website field
  • 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 include shipping_info field
  • 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_info text in response
  • Each crawl config includes website field

6.3 Update OpenAPI/Nelmio documentation

  • Document new shipping_info field on RoasterDTO
  • Document new website field on crawl configs
  • Document visitorCountryId query parameter for both endpoints
  • Add examples showing filtered vs unfiltered responses

Phase 7: Admin Interface Updates

7.1 Roaster admin form

  • Add shipping_info textarea 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 website text 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_id column temporarily (mark deprecated)
  • If issues found, can restore old relationship
  • Remove deprecated column only after successful production deployment

Implementation Order

  1. Entity changes (shipping_info, website, new relationship)
  2. Migration creation (with comprehensive data migration)
  3. Repository query updates
  4. DTO and mapper updates
  5. API endpoint updates
  6. Admin form updates
  7. Event listener updates
  8. Test updates (all types)
  9. Run migration on test/staging
  10. Validation and rollback testing
  11. Production deployment with monitoring