diff --git a/backend/db/scripts/migrate_dynamo_to_postgres.py b/backend/db/scripts/migrate_dynamo_to_postgres.py index b6b62eea..6c52efa9 100644 --- a/backend/db/scripts/migrate_dynamo_to_postgres.py +++ b/backend/db/scripts/migrate_dynamo_to_postgres.py @@ -41,9 +41,9 @@ - initiatives.id (UUID PK) is generated deterministically via _as_uuid(projectId / entityId) so FK lookups from donations/subscriptions remain stable across re-runs. -- DynamoDB entity type quirk: SaveEntity rewrites 'general fund' → 'initiative' - before every PutItem. Migration reverses this: - if entityType == 'initiative' → restore to 'general fund' +- DynamoDB entity type quirks — normalised to ValidInitiativeTypes canonical forms: + 'initiative' → 'general_fund' (SaveEntity rewrites 'general fund' before PutItem) + 'ostif' → 'security_audit' - Budget.AmountInCents is serialised with json tag "amount" — access as budget.get("amount"), NOT budget.get("amountInCents"). - Excluded from schema (no DynamoDB write path / computed at read-time): @@ -722,11 +722,15 @@ def migrate_initiatives(cur, entities: list, projects: list, known_users: set, u ) known_initiative_ids.add(pg_id) - # entityType quirk: SaveEntity rewrites 'general fund' → 'initiative' - # before every PutItem. Reverse it here. + # Normalise DynamoDB entityType values to the canonical forms used by + # ValidInitiativeTypes in the new backend: + # 'initiative' → 'general_fund' (SaveEntity quirk: rewrites 'general fund' before PutItem) + # 'ostif' → 'security_audit' (OSTIF is a security-audit programme) entity_type: str = e.get("entityType") or "" if entity_type == "initiative": - entity_type = "general fund" + entity_type = "general_fund" + elif entity_type == "ostif": + entity_type = "security_audit" # ── Core initiative row ────────────────────────────────────────── initiative_rows.append( @@ -843,7 +847,9 @@ def migrate_initiatives(cur, entities: list, projects: list, known_users: set, u # ── OSTIF-specific detail (ostif entity type only) ─────────────── # entity.Detail interface{} is a domain.Detail struct when non-nil. # TypeDeserializer returns it as a plain dict. - if entity_type == "ostif": + # Use the raw DynamoDB entityType here — entity_type is already + # normalised to 'security_audit' by this point. + if e.get("entityType") == "ostif": raw_detail = e.get("detail") if isinstance(raw_detail, dict) and raw_detail: ostif_detail_rows.append( diff --git a/backend/internal/infrastructure/db/initiative_repository.go b/backend/internal/infrastructure/db/initiative_repository.go index 42a38c9c..636ffedb 100644 --- a/backend/internal/infrastructure/db/initiative_repository.go +++ b/backend/internal/infrastructure/db/initiative_repository.go @@ -199,8 +199,14 @@ func (r *InitiativeRepository) List(ctx context.Context, filter models.Initiativ argN++ } if filter.InitiativeType != "" { - where += fmt.Sprintf(" AND i.initiative_type = $%d", argN) - args = append(args, filter.InitiativeType) + if filter.InitiativeType == "general_fund" { + // 'other' and 'community' are legacy types that surface under the General Fund tab. + where += fmt.Sprintf(" AND i.initiative_type = ANY($%d)", argN) + args = append(args, []string{"general_fund", "other", "community"}) + } else { + where += fmt.Sprintf(" AND i.initiative_type = $%d", argN) + args = append(args, filter.InitiativeType) + } argN++ } if filter.Status != "" { diff --git a/frontend/app/components/shared/components/initiative-card/initiative-card.config.ts b/frontend/app/components/shared/components/initiative-card/initiative-card.config.ts index 523f4046..e62a1c0f 100644 --- a/frontend/app/components/shared/components/initiative-card/initiative-card.config.ts +++ b/frontend/app/components/shared/components/initiative-card/initiative-card.config.ts @@ -34,6 +34,19 @@ export const initiativeTypeConfigMap: Record = { colorClass: 'text-positive-600', gradient: 'linear-gradient(180deg, rgba(240, 253, 244, 0.5) 0%, rgba(255, 255, 255, 0.5) 50%)', }, + // Legacy types that surface under the General Fund tab without DB renaming. + other: { + label: 'General Fund', + icon: 'hand-holding-dollar', + colorClass: 'text-positive-600', + gradient: 'linear-gradient(180deg, rgba(240, 253, 244, 0.5) 0%, rgba(255, 255, 255, 0.5) 50%)', + }, + community: { + label: 'General Fund', + icon: 'hand-holding-dollar', + colorClass: 'text-positive-600', + gradient: 'linear-gradient(180deg, rgba(240, 253, 244, 0.5) 0%, rgba(255, 255, 255, 0.5) 50%)', + }, }; export const defaultInitiativeTypeConfig: InitiativeTypeConfig = {