Danilo Reyes 1329958b4e
Some checks failed
Test Suite / test (push) Failing after 45s
Add NixOS VM configuration and new media search endpoint
- Introduced a new NixOS VM configuration in `flake.nix` for testing purposes.
- Updated CI workflow to build the VM using the flake input for a more streamlined process.
- Added a new API endpoint `/search` in the collection API to search owned media by title, with optional filtering by media type.
- Refactored frontend components to utilize the new media search functionality, updating types and state management accordingly.
- Enhanced CSS for the map component to ensure proper rendering in various layouts.
2025-12-28 23:02:48 -06:00
2025-12-28 20:59:09 -06:00
2025-12-28 20:59:09 -06:00

Movie Map

A web application that visualizes the origin countries of your media collection from Radarr, Sonarr, and Lidarr, and tracks which foreign movies/shows you've watched.

Features

View 1: Collection Map

  • Visualizes all media in your *arr instances on a world map
  • Shows country of origin for each movie/show/artist
  • Color intensity indicates number of items per country
  • Filter by media type (movies, shows, music)
  • Pin markers show counts per country

View 2: Watched Map

  • Interactive map to track watched foreign movies and TV shows
  • Manually add watched items with country information
  • Add custom pins to mark countries
  • Visualize your personal "watched foreign media" journey

Architecture

  • Backend: FastAPI (Python) with PostgreSQL
  • Frontend: React + TypeScript + Vite + Leaflet
  • Database: PostgreSQL (via Unix socket)
  • Deployment: Nix flake with NixOS module

Development Setup

Prerequisites

  • Nix with flakes enabled
  • PostgreSQL running (accessible via socket)
  • Access to Radarr, Sonarr, Lidarr instances

Getting Started

  1. Enter the development shell:
nix develop
  1. Set up backend environment variables (create .env in backend/):
POSTGRES_SOCKET_PATH=/run/postgresql
POSTGRES_DB=jawz
POSTGRES_USER=jawz
SONARR_API_KEY=your_sonarr_api_key
RADARR_API_KEY=your_radarr_api_key
LIDARR_API_KEY=your_lidarr_api_key
PORT=8080
HOST=0.0.0.0  # Use 127.0.0.1 for localhost only

# Optional: External APIs for country data
TMDB_API_KEY=your_tmdb_api_key  # Get from https://www.themoviedb.org/settings/api
  1. (Optional) Set up frontend environment variables (create .env.local in frontend/):
# Only needed if backend port/host differs from defaults
VITE_BACKEND_HOST=127.0.0.1
VITE_BACKEND_PORT=8080
VITE_PORT=5173
  1. Start the backend (in one terminal):
cd backend
# Uses HOST and PORT from .env file (defaults to 0.0.0.0:8080)
python -m uvicorn main:app --reload --host ${HOST:-0.0.0.0} --port ${PORT:-8080}
  1. Start the frontend dev server (in another terminal):
cd frontend
npm install
# Optional: Set backend URL if different from default
# export VITE_BACKEND_HOST=127.0.0.1
# export VITE_BACKEND_PORT=8080
npm run dev
  1. Open http://localhost:5173 in your browser (or http://:5173 from another computer)

Development vs Production

Development Mode:

  • Backend runs on port 8080 (configurable)
  • Frontend dev server runs on port 5173 (configurable)
  • Frontend proxies API requests to backend
  • Both are accessible from other computers (bound to 0.0.0.0)

Production Mode (NixOS deployment):

  • Backend runs on configured port (default 8080)
  • Frontend is built and served as static files by the backend
  • No separate frontend server needed
  • Access via backend URL only

Note: The frontend dev server runs on port 5173 by default and proxies API requests to the backend. If you changed the backend port, set VITE_BACKEND_PORT environment variable to match. The frontend dev server binds to 0.0.0.0 by default, so it's accessible from other computers.

Building

Build the application using Nix:

nix build

This creates a combined package with both backend and frontend.

NixOS Deployment

1. Add the flake to your NixOS configuration

In your configuration.nix or a separate module:

{
  imports = [
    /path/to/movie-map/nixosModules.default
  ];

  services.moviemap = {
    enable = true;
    port = 8080;
    host = "0.0.0.0";  # Bind to all interfaces (use "127.0.0.1" for localhost only)
    postgresSocketPath = "/run/postgresql";
    # Secrets can be strings or file paths (for sops-nix integration)
    sonarrApiKey = "/run/secrets/sonarr-api-key";  # or "your_key_here"
    radarrApiKey = "/run/secrets/radarr-api-key";   # or "your_key_here"
    lidarrApiKey = "/run/secrets/lidarr-api-key";  # or "your_key_here"
    # Optional: admin token for sync endpoint
    adminToken = "/run/secrets/moviemap-admin-token";  # or "your_token_here"
  };
}

Or reference the flake directly:

{
  imports = [
    (builtins.getFlake "/path/to/movie-map").nixosModules.default
  ];
  
  services.moviemap = {
    enable = true;
    # ... configuration
  };
}

2. Run database migrations

Before starting the service, run migrations. You can do this either:

Option A: Run migrations manually before enabling the service

# SSH into your server
ssh server

# Enter the flake shell
cd /path/to/movie-map
nix develop

# Run migrations
cd backend
alembic upgrade head

Option B: Add a systemd service to run migrations on first start

You can add a one-shot systemd service that runs migrations before the main service starts. Add this to your NixOS configuration:

systemd.services.moviemap-migrate = {
  description = "Movie Map Database Migrations";
  serviceConfig = {
    Type = "oneshot";
    User = "moviemap";
    WorkingDirectory = "${appPackage}/backend";
    ExecStart = "${pythonEnv}/bin/alembic upgrade head";
  };
  before = [ "moviemap-backend.service" ];
  requiredBy = [ "moviemap-backend.service" ];
};

Or simply run migrations once manually, then enable the service.

3. Rebuild and enable

sudo nixos-rebuild switch

The service will be available at http://0.0.0.0:8080 (or your server's IP address). If you set host = "127.0.0.1", it will only be accessible from localhost (use a reverse proxy in that case).

Note: If you're accessing from another computer, make sure:

  1. The service is bound to 0.0.0.0 (default) or your server's IP address
  2. Your firewall allows incoming connections on the configured port
  3. If using NixOS, you may need to open the port in your firewall configuration:
    networking.firewall.allowedTCPPorts = [ 8080 ];
    

API Endpoints

Collection

  • GET /api/collection/summary?types=movie,show,music - Get collection summary by country

Watched Items

  • GET /api/watched - List all watched items
  • GET /api/watched/summary - Get watched summary by country
  • POST /api/watched - Create watched item
  • PATCH /api/watched/{id} - Update watched item
  • DELETE /api/watched/{id} - Delete watched item

Pins

  • GET /api/pins - List all manual pins
  • POST /api/pins - Create pin
  • DELETE /api/pins/{id} - Delete pin

Admin

  • POST /admin/sync - Trigger sync from all *arr instances (requires admin token if configured)
  • GET /admin/missing-countries?source_kind=sonarr&media_type=show&limit=100 - List items without country metadata
  • POST /admin/assign-country?item_id=<uuid>&country_code=US - Manually assign country to an item

Database Schema

The application creates a moviemap schema in the jawz database with the following tables:

  • source - *arr instance configuration
  • media_item - Normalized media items from *arr
  • media_country - Country associations for media items
  • watched_item - User-tracked watched items
  • manual_pin - Custom pins on the map

Country Extraction

The sync process extracts country information using multiple methods:

Automatic Extraction

  • Radarr (Movies):

    • First tries productionCountries from Radarr metadata
    • Falls back to TMDB API (requires TMDB_API_KEY env var) using tmdbId
  • Sonarr (TV Shows):

    • First tries seriesMetadata.originCountry from Sonarr metadata
    • Falls back to TMDB API (requires TMDB_API_KEY env var) using tmdbId
  • Lidarr (Music):

    • First tries country field from Lidarr metadata
    • Falls back to MusicBrainz API (no API key required) using foreignArtistId (MBID)

External API Setup

TMDB API (for Movies & TV Shows):

  1. Get a free API key from https://www.themoviedb.org/settings/api
  2. Set environment variable: TMDB_API_KEY=your_api_key_here
  3. Re-run sync to fetch country data

MusicBrainz API (for Music):

  • No API key required (uses public API)
  • Automatically used if foreignArtistId (MusicBrainz ID) is available in Lidarr

Manual Assignment

If automatic extraction fails, you can manually assign countries:

  1. View missing countries:

    curl http://127.0.0.1:8888/admin/missing-countries?source_kind=sonarr&limit=50
    
  2. Assign country manually:

    curl -X POST "http://127.0.0.1:8888/admin/assign-country?item_id=<uuid>&country_code=US"
    

Items without country information are stored but excluded from map visualization until a country is assigned.

Implementation Status

Completed Features

  • Project scaffolding (FastAPI backend + React frontend)
  • Database schema and migrations (PostgreSQL)
  • *arr sync integration (Radarr, Sonarr, Lidarr)
  • Collection Map UI (View 1) with filters
  • Watched Map UI (View 2) with manual tracking
  • NixOS module and systemd deployment
  • TMDB API integration for movies and TV shows
  • MusicBrainz API integration for music
  • Manual country assignment feature
  • Missing metadata admin view

🔄 Future Enhancements (Optional)

  • Batch country assignment UI
  • Country extraction from file paths/metadata
  • Export/import functionality
  • Statistics and analytics
  • Multi-user support

License

MIT

Description
No description provided
Readme 197 KiB
Languages
Python 55.8%
TypeScript 28.1%
Nix 6.3%
CSS 6.3%
Shell 2.9%
Other 0.6%