Contributor guide

July 14, 2026 · View on GitHub

GitLab Pipeline Status Build Status

Follow this steps to run/debug/develop the application on your machine.

For an environment, look at operations.md.

License and contribution terms

Keeptrack is licensed under the PolyForm Strict License 1.0.0, which by itself does not allow making changes or new works based on the software. As an exception, the licensor grants you permission to modify the software solely for the purpose of developing, testing, and submitting contributions to the official repository (https://github.com/devpro/keeptrack). Running the application locally while developing a contribution is covered by the license's personal-use permission.

By submitting a contribution in any form, you grant Bertrand THOMAS a perpetual, worldwide, irrevocable, royalty-free, sublicensable license over that contribution. This grant covers using, reproducing, modifying, distributing, and relicensing it as part of Keeptrack, under any terms, including commercial ones. You confirm that you have the right to grant this license for your contribution. If you do not agree with these terms, do not submit a contribution.

Design

The application source code is in the following .NET projects:

Project nameTechnologyProject type
BlazorAppASP.NET 10Blazor Server web application
Common.System.NET 10Library
Domain.NET 10Library
Infrastructure.MongoDb.NET 10Library
WebApiASP.NET 10Web application (REST API)
WebApi.Contracts.NET 10Library

The application is using the following .NET packages (via NuGet):

NameDescription
FirebaseAdminFirebase
MongoDB.BsonMongoDB BSON
MongoDB.DriverMongoDB .NET Driver
Scalar.AspNetCoreOpenAPI web UI

Requirements

  1. .NET 10.0 SDK

  2. MongoDB database

    Several options:

    • Local server
    cd D:/Programs/mongodb-8.2/bin
    md log
    md data
    mongod --logpath log/mongod.log --dbpath data --port 27017
    
    docker run --name mongodb -d -p 27017:27017 mongo:8.2
    

    Once it's running, create the app's indexes with mongosh, pointed at the database you're using (keeptrack_dev by default - see Web API settings below):

    mongosh "mongodb://localhost:27017/keeptrack_dev" scripts/mongodb-create-index.js
    

    scripts/mongodb-create-index.js is idempotent, so it's safe to run again after pulling changes to it or against a database that already has these indexes.

  3. IDE: Rider, Visual Studio, Visual Studio Code

Configuration

Web API settings

KeyDescription
Infrastructure:MongoDB:ConnectionStringMongoDB connection string
Infrastructure:MongoDB:DatabaseNameMongoDB database name
Tmdb:ApiKeyTMDB v3 API key, used to auto-match shows/movies to episode titles and synopses (see Reference data below)
Rawg:ApiKeyRAWG API key, used to auto-match video games to synopses/cover art/platforms (see Reference data below)
Discogs:TokenDiscogs personal access token, used to auto-match albums to synopses/cover art/genres (see Reference data below)
ReferenceData:BookProviderWhich IBookReferenceClient implementation to use for book matching (see Reference data below). Default: OpenLibrary

This values can be easily provided as environment variables (replace ":" by "__") or by configuration (json).

Template for src/WebApi/appsettings.Development.json:

{
  "AllowedOrigins": [
    "http://localhost:5207",
    "https://localhost:7042"
  ],
  "Authentication": {
    "JwtBearer": {
      "Authority": "https://securetoken.google.com/<firebase-project-id>",
      "TokenValidation": {
        "Issuer": "https://securetoken.google.com/<firebase-project-id>",
        "Audience": "<firebase-project-id>"
      }
    }
  },
  "Features": {
    "IsScalarEnabled": true,
    "IsHttpsRedirectionEnabled": false
  },
  "Infrastructure": {
    "MongoDB": {
      "ConnectionString": "mongodb://localhost:27017",
      "DatabaseName": "keeptrack_dev"
    }
  },
  "Tmdb": {
    "ApiKey": "<your-tmdb-api-key>"
  },
  "Rawg": {
    "ApiKey": "<your-rawg-api-key>"
  },
  "Discogs": {
    "Token": "<your-discogs-personal-access-token>"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "KeepTrack": "Debug"
    }
  }
}

Reference data (TMDB, Open Library, RAWG, Discogs)

Episode titles, synopses, cover art, and the "what should I watch next" experience are backed by shared reference collections, one per trackable type, each populated from a different external provider rather than typed in by hand:

TypeProviderSettingAPI key required?
TV shows / MoviesTMDB (The Movie Database)Tmdb:ApiKeyYes
BooksOpen Library(none)No
Video GamesRAWGRawg:ApiKeyYes
AlbumsDiscogsDiscogs:TokenYes (personal access token)
  1. TMDB: create a free account, then generate a v3 API key at themoviedb.org/settings/api. Set Tmdb:ApiKey (or the Tmdb__ApiKey environment variable) to that key.
  2. Open Library: nothing to configure - its search/cover-image API is free and keyless.
  3. RAWG: create a free account, then generate an API key at rawg.io/apidocs. Set Rawg:ApiKey (or Rawg__ApiKey) to that key.
  4. Discogs: create a free account, then generate a personal access token at discogs.com/settings/developers. Set Discogs:Token (or Discogs__Token) to that token.

Without a key/token for a given provider, new items of that type simply stay unresolved (no synopsis, no cover art) instead of erroring. The app degrades gracefully per type - it just won't auto-match that type until the corresponding setting is provided.

Unlike the other three, books are resolved through a provider-agnostic IBookReferenceClient interface (src/WebApi/ReferenceData/). Which book provider is active is itself a setting: ReferenceData:BookProvider (or the ReferenceData__BookProvider environment variable), defaulting to OpenLibrary. src/WebApi/Program.cs switches on this value to decide which implementation to register - OpenLibrary is the only one that ships today. To add a new book provider, implement IBookReferenceClient (a new client class alongside OpenLibraryClient.cs, plus its own settings class if it needs an API key, following RawgSettings/DiscogsSettings). Also add a matching case to that switch. Nothing else in the app needs to change, since ReferenceEnrichmentService/ReferenceDataAdminController only depend on the interface and read the active provider's key from IBookReferenceClient.ProviderKey.

Admin role

The reference-data curation page (/admin/reference-data, and its underlying api/reference-data/* admin endpoints) is restricted to users carrying a Firebase custom claim role: "admin". There's no in-app way to grant this - it's a one-off action against your own Firebase project, e.g. with the Firebase Admin SDK for Node:

const admin = require("firebase-admin");
admin.initializeApp();
admin.auth().setCustomUserClaims("<firebase-user-uid>", { role: "admin" });

The claim is embedded directly in that user's ID token on their next sign-in (existing sessions need to sign out/in again to pick it up).

Install Deno:

winget install DenoLand.Deno

Run the script:

deno run -A scripts/firebase-set-admin.js ./path/to/serviceAccount.json user@example.com

Blazor Server App settings

KeyRequiredDefault value
AllowedHostsfalse"*"
Features:IsHttpsRedirectionEnabledfalsetrue
Firebase:WebAppConfiguration:ApiKeytrue""
Firebase:WebAppConfiguration:AuthDomaintrue""
Firebase:WebAppConfiguration:ProjectIdtrue""
Firebase:ServiceAccounttrue""
Logging:LogLevel:Defaultfalse"Information"
Logging:LogLevel:Microsoft.AspNetCorefalse"Warning"
WebApi:BaseUrltrue""

Run

dotnet restore

dotnet build

# run the API (https://localhost:5011/)
dotnet run --project src/WebApi

# run the Blazor WebAssembly web app (https://localhost:5021)
dotnet run --project src/BlazorApp

Tests

The solution has two test projects:

  • test/WebApi.UnitTests has no external dependencies.
  • test/WebApi.IntegrationTests needs a running MongoDB and a Firebase test user, since it boots the real Web API and calls it like a real client would.

Run everything:

dotnet test

Run just one project:

dotnet test test/WebApi.UnitTests/WebApi.UnitTests.csproj
dotnet test test/WebApi.IntegrationTests/WebApi.IntegrationTests.csproj

Run a single test by fully qualified name (works for either project):

dotnet test --filter-method "Keeptrack.WebApi.UnitTests.Services.WatchNextServiceTest.ComputeInProgressShows_IncludesShowWithAConfirmedAiredUnwatchedNextEpisode"

--filter-method also accepts a wildcard, e.g. --filter-method "*CarResourceTest*" to run every test in a class. It's a single glob pattern, not a real filter expression: it does not support |/, alternation to combine multiple patterns in one run (that just prints the CLI help instead of running anything). So run each pattern as its own dotnet test invocation. This project's test runner is Microsoft.Testing.Platform (UseMicrosoftTestingPlatformRunner, xunit v3), which does not understand the classic VSTest --settings <file>.runsettings flag - passing it also just prints the help. Local.runsettings (below) is read automatically by Rider/Visual Studio for IDE-driven runs; for a CLI run, export the same values as environment variables instead (see "Integration tests" below).

Unit tests

No configuration needed - dotnet test test/WebApi.UnitTests/WebApi.UnitTests.csproj works as soon as the solution restores.

Integration tests

These need two things configured before they'll pass:

  1. A MongoDB instance (see Requirements above), pointed at by Infrastructure__MongoDB__ConnectionString/Infrastructure__MongoDB__DatabaseName. Use a dedicated database (e.g. keeptrack_integrationtests), not your dev database - tests create and delete real documents. Running scripts/mongodb-create-index.js against it first is recommended (keeps behavior closest to production) but not required for the tests themselves to pass. See Requirements above for the exact mongosh command (swap in keeptrack_integrationtests for the database name).
  2. A Firebase test user, since ResourceTestBase.Authenticate() performs a real Firebase sign-in to obtain a bearer token:
    • FIREBASE_APIKEY: the Firebase project's Web API key (Firebase Console → Project settings → General → Web API Key).
    • FIREBASE_USERNAME / FIREBASE_PASSWORD: the email/password of a real user created in that project (Firebase Console → Authentication → Users → Add user). Use a dedicated test account, not a personal one.
    • Authentication__JwtBearer__Authority, ..__TokenValidation__Issuer, ..__TokenValidation__Audience: all https://securetoken.google.com/<firebase-project-id>. See the Web API settings above for the Issuer/Audience split. This is what lets the API-under-test validate the token issued by that same Firebase project.

Provide all of this as environment variables (works everywhere, including CI - see .github/workflows/ci.yaml for how the pipeline supplies its own test account), for example:

export AllowedOrigins__0=http://localhost:5207
export Infrastructure__MongoDB__ConnectionString=mongodb://localhost:27017
export Infrastructure__MongoDB__DatabaseName=keeptrack_integrationtests
export Authentication__JwtBearer__Authority=https://securetoken.google.com/<firebase-project-id>
export Authentication__JwtBearer__TokenValidation__Issuer=https://securetoken.google.com/<firebase-project-id>
export Authentication__JwtBearer__TokenValidation__Audience=<firebase-project-id>
export FIREBASE_APIKEY=<web-api-key>
export FIREBASE_USERNAME=<test-user-email>
export FIREBASE_PASSWORD=<test-user-password>

dotnet test test/WebApi.IntegrationTests/WebApi.IntegrationTests.csproj

Or, for an IDE-driven workflow, put the same values in a Local.runsettings file at the repository root (gitignored - never commit it) so Rider/Visual Studio pick them up automatically for test runs:

<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
  <RunConfiguration>
    <EnvironmentVariables>
      <AllowedOrigins__0>http://localhost:5207</AllowedOrigins__0>
      <Infrastructure__MongoDB__ConnectionString>mongodb://localhost:27017</Infrastructure__MongoDB__ConnectionString>
      <Infrastructure__MongoDB__DatabaseName>keeptrack_integrationtests</Infrastructure__MongoDB__DatabaseName>
      <Authentication__JwtBearer__Authority></Authentication__JwtBearer__Authority>
      <Authentication__JwtBearer__TokenValidation__Issuer></Authentication__JwtBearer__TokenValidation__Issuer>
      <Authentication__JwtBearer__TokenValidation__Audience></Authentication__JwtBearer__TokenValidation__Audience>
      <!-- <KESTREL_WEBAPP_URL>xxxx</KESTREL_WEBAPP_URL> -->
      <FIREBASE_APIKEY>xxxx</FIREBASE_APIKEY>
      <FIREBASE_USERNAME>xxxx</FIREBASE_USERNAME>
      <FIREBASE_PASSWORD>xxxx</FIREBASE_PASSWORD>
    </EnvironmentVariables>
  </RunConfiguration>
</RunSettings>

Or in Rider, in "File | Settings | Build, Execution, Deployment | Unit Testing | Test Runner", set the same three Firebase variables directly so they apply to every test run in the IDE without a file.

Set KESTREL_WEBAPP_URL to target a specific already-running instance instead of letting the tests spin up their own.

The standard test user above now carries the role: admin custom claim (set via scripts/firebase-set-admin.js, see Admin role above). So ReferenceDataAdminResourceTest and any other admin-gated endpoint can be exercised end-to-end over HTTP with the same single test account. There's no separate non-admin test account, so there's no automated coverage of the "AdminOnly" policy actually rejecting a non-admin caller; that would need a second Firebase test user without the claim. The underlying Mongo query logic (SetReferenceIdForTitleYearAsync, FindDistinctUnresolvedTitleYearsAsync) is still covered directly against a real database in TvShowReferenceLinkingTest. This test resolves repositories from the test host's DI container instead of going over HTTP.

Container images

docker build . -t devprofr/keeptrack-blazorapp:local -f src/BlazorApp/Dockerfile
docker build . -t devprofr/keeptrack-webapi:local -f src/WebApi/Dockerfile