Admin Feed Imports¶
This doc describes how an administrator browses external-provider sports data and imports it into Foundry through the Feed Imports admin UI.
Note on history. An earlier design proposed a dedicated provider abstraction (
IFeedProvider/FoxSportsFeedProvider), per-provider browse queries (BrowseFeedCompetitionsQueryetc.), aToggleCompetitionAutoImportCommand, and aFeedSyncTimerService. That design was never merged and none of those types exist in the codebase. The feature shipped differently: a generic, provider-agnostic admin wizard backed by the existing import infrastructure. This doc reflects what actually exists.
Overview¶
Feed Imports is an admin-only wizard that lets you drill into harvested provider data (sport -> provider -> entity) and trigger imports per entity. It has two halves:
- Browse -- read-only listings served by
LBS.Apiendpoints under/api/admin/feed-imports/*, sourced from ClickHouse harvest tables and joined against the entity-mapping crosswalk to show whether each row is already linked/imported. - Import -- an
POST /api/importjob using one of the generic per-entity importers (GenericCompetitions,GenericSeasons,GenericSportingEvents,GenericTeams,GenericParticipants,GenericVenues). These run on the standard import job queue; see Import Infrastructure Usage for the queue, executor, retry and result-tracking details.
UI structure¶
The wizard lives under src/Apps/foundry-web/src/routes/admin/feed-imports/ and is a nested
route hierarchy:
/admin/feed-imports pick a sport
/admin/feed-imports/[sport] pick a provider
/admin/feed-imports/[sport]/[provider] pick an entity area
/admin/feed-imports/[sport]/[provider]/competitions competitions list (per-row import)
.../competitions/[competitionId]/seasons seasons (per-row import)
.../competitions/[competitionId]/seasons/[seasonId]/fixtures fixtures (bulk import)
/admin/feed-imports/[sport]/[provider]/teams teams (flat, searchable)
/admin/feed-imports/[sport]/[provider]/participants participants (flat, searchable)
/admin/feed-imports/[sport]/[provider]/venues venues grouped by country
.../venues/[country] venues for a country
Sport and provider option lists come from $lib/constants/theme (allSports,
allProviders). Each entity page shows two status badges per row:
ExistenceBadge-- a tri-stateexistenceStatus(NotLinked,LinkedNotImported,Imported) telling you what is still missing before/after import.ProviderBadges-- which other providers are already mapped to the same canonical entity.
Entry points elsewhere in the app:
- Sidebar (
src/Apps/foundry-web/src/lib/components/layout/sidebar.svelte): Importers (/admin/importers) and Feed Imports (/admin/feed-imports). - Competitions list page
(
src/Apps/foundry-web/src/routes/competitions/+page.svelte): an admin-only Import from Provider button that navigates to/admin/feed-imports.
Browse flow¶
Each entity page fetches its listing through the shared helper
src/Apps/foundry-web/src/lib/components/feed-imports/api.ts (fetchJson + buildQuery),
which attaches the bearer token and calls the matching LBS.Api endpoint. For example the
competitions page requests:
The endpoints live under src/Apps/LBS.Api/Features/Admin/FeedImports/:
| Route | Endpoint |
|---|---|
GET /api/admin/feed-imports/competitions |
BrowseCompetitionsEndpoint |
GET /api/admin/feed-imports/seasons |
BrowseSeasonsEndpoint |
GET /api/admin/feed-imports/sporting-events |
BrowseSportingEventsEndpoint |
GET /api/admin/feed-imports/teams |
BrowseTeamsEndpoint |
GET /api/admin/feed-imports/participants |
BrowseParticipantsEndpoint |
GET /api/admin/feed-imports/venues |
BrowseVenuesEndpoint |
GET /api/admin/feed-imports/venues/countries |
GetVenueCountriesEndpoint |
All browse endpoints are admin-gated (this.Policies(AuthorizationPolicyDefinition.RequireAdmin))
and read from the ClickHouse harvest tables (foundry.competitions, etc.) via
IClickHouseRepository. Each row is LEFT JOINed to foundry.entity_mapping_crosswalk to
resolve a canonical id, then IEntityMappingService and the Marten document store are
consulted to compute mappedProviders and existenceStatus. Responses are paged
(FeedImportResponse<T> with Items + TotalCount).
Import flow¶
When an admin clicks an Import button, the page calls runImport(...) from
feed-imports/api.ts, which POSTs to /api/import with a generic importer type and a
GenericImportData payload, then redirects to the importer's job-history page:
// runImport('GenericCompetitions', provider, sport, { competitionProviderId })
POST /api/import
{
"importerType": "GenericCompetitions",
"priority": "High",
"importData": {
"provider": { "name": "<provider>" },
"sport": { "name": "<sport>" },
"competitionProviderId": "<providerId>" // optional FK narrowing
}
}
The contract is GenericImportData
(src/Integration/LBS.Fantasy.Integration/Importers/Generic/GenericImportData.cs):
Provider is always required; Sport is required for sport-keyed entities (venues do not
use it); and the optional CompetitionProviderId, SeasonProviderId, and Country
filters narrow the import (fixture-tree drill-down, or the venue browser).
The generic importers live in
src/Integration/LBS.Fantasy.Integration/Importers/Generic/. Each carries an
[Importer("...")] attribute (whose name matches the [DataContract(Name = "GenericImportData")])
and is gated with [RequiresModule(ModuleDefinition.SportCore)] because it depends on the
native ClickHouse repository and entity-mapping service:
| Importer type | Class |
|---|---|
GenericCompetitions |
GenericCompetitionImporter |
GenericSeasons |
GenericSeasonImporter |
GenericSportingEvents |
GenericSportingEventImporter |
GenericTeams |
GenericTeamImporter |
GenericParticipants |
GenericParticipantImporter |
GenericVenues |
GenericVenueImporter |
Each importer re-queries the relevant ClickHouse harvest table for the requested
provider/sport (optionally narrowed by the FK filters), then emits domain commands for the
import infrastructure to execute. The full lifecycle -- queueing, the
ImportProcessingBackgroundService, the ImportExecutor, transactional batching by
aggregate, retries, and ImportExecutionResult tracking -- is the standard pipeline
documented in Import Infrastructure Usage.
POST /api/import itself is handled by ImportEndpoint
(src/Domain/LBS.Domain.Infrastructure/Import/ImportEndpoint.cs): it validates the importer
type via IImporterTypeRegistry, and either runs High-priority jobs synchronously when the
queue is empty or queues them for the background worker.
Job history and available importers¶
- Available importers:
/admin/importers(src/Apps/foundry-web/src/routes/admin/importers/+page.svelte) lists every registered importer by queryingAvailableImportersthrough the SDK (AvailableImportersQuery->AvailableImportersQueryHandler, admin-only). This includes the generic importers above alongside all other importers in the system. - Job history:
/admin/importers/[type](src/Apps/foundry-web/src/routes/admin/importers/[type]/+page.svelte) shows the jobs and results for a given importer type using theAllImportJobsquery.runImportredirects here after submitting, so the admin lands on the live job status for what they just kicked off.
Adding a new provider or sport¶
Because browse and import are both provider-agnostic, onboarding a new provider/sport for an existing entity type is usually a matter of:
- Harvesting the provider's data into the relevant
foundry.*ClickHouse table (handled by the data harvesters, not this UI). - Adding the provider/sport to the option lists in
$lib/constants/theme.
No new browse endpoint or importer is needed unless you are adding a brand-new entity type.
Related docs¶
- Import Infrastructure Usage -- the queue, executor, importer attribute, retries, and result tracking.
- Event Integrators -- reacting to events for cross-aggregate workflows.