From e3d253ba92e839e012bb4a0c656c13445cc9e6d1 Mon Sep 17 00:00:00 2001 From: Stu Leak Date: Thu, 4 Dec 2025 10:49:46 -0500 Subject: [PATCH] Add updated Goondex logos --- .air.toml | 50 + .gitignore | 6 + Makefile | 64 + README.md | 9 + cmd/goondex/main.go | 192 +- config/source.yml | 6 +- docs/CLI_REFERENCE.md | 2 + internal/web/server.go | 86 +- internal/web/static/css/style.css | 17 + internal/web/static/img/logo/GOONDEX_logo.svg | 88 +- internal/web/static/img/logo/Goondex_LOGO.png | Bin 0 -> 12955 bytes .../web/static/img/logo/Goondex_LOGO2.png | Bin 0 -> 14387 bytes internal/web/static/js/app.js | 27 + internal/web/templates/dashboard.html | 68 +- internal/web/templates/performers.html | 18 +- package-lock.json | 1956 ++++++++++++++++- package.json | 7 + scripts/build.sh | 16 + scripts/env.sh | 15 + scripts/run-web.sh | 15 + scripts/run.sh | 16 + scripts/test.sh | 8 + 22 files changed, 2430 insertions(+), 236 deletions(-) create mode 100644 .air.toml create mode 100644 Makefile create mode 100644 internal/web/static/img/logo/Goondex_LOGO.png create mode 100644 internal/web/static/img/logo/Goondex_LOGO2.png create mode 100755 scripts/build.sh create mode 100755 scripts/env.sh create mode 100755 scripts/run-web.sh create mode 100755 scripts/run.sh create mode 100755 scripts/test.sh diff --git a/.air.toml b/.air.toml new file mode 100644 index 0000000..c9f5a30 --- /dev/null +++ b/.air.toml @@ -0,0 +1,50 @@ +# Air configuration for Goondex live reload +# See: https://github.com/air-verse/air + +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] + args_bin = [] + bin = "./tmp/goondex" + cmd = "go build -o ./tmp/goondex ./cmd/goondex" + delay = 1000 + exclude_dir = ["assets", "tmp", "vendor", "testdata", "docs", ".git", "node_modules"] + exclude_file = [] + exclude_regex = ["_test.go"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + # Watch all web-related files for changes + include_dir = [] + include_ext = ["go", "tpl", "tmpl", "html", "css", "js"] + include_file = [] + kill_delay = "0s" + log = "build-errors.log" + poll = false + poll_interval = 0 + post_cmd = [] + pre_cmd = [] + rerun = false + rerun_delay = 500 + send_interrupt = false + stop_on_error = false + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + main_only = false + time = true + +[misc] + clean_on_exit = false + +[screen] + clear_on_rebuild = false + keep_scroll = true diff --git a/.gitignore b/.gitignore index 0a60d04..a805100 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,12 @@ /cache/ /tmp/ +# Node modules (Bootstrap) +node_modules/ + +# Air live reload +build-errors.log + # Media & Assets (images, galleries, downloads) # Ignore all assets except logos and README /assets/* diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..747d439 --- /dev/null +++ b/Makefile @@ -0,0 +1,64 @@ +.PHONY: help dev build run clean install-deps install-air test + +# Default target +help: + @echo "Goondex Development Commands:" + @echo "" + @echo " make dev - Start development server with live reload (Air)" + @echo " make build - Build production binary" + @echo " make run - Run production binary" + @echo " make clean - Clean build artifacts" + @echo " make install-deps - Install all dependencies" + @echo " make install-air - Install Air for live reload" + @echo " make test - Run tests" + @echo "" + +# Development with Air (live reload) +dev: install-air + air + +# Build production binary +build: + @echo "Building Goondex..." + @mkdir -p bin + go build -o ./bin/goondex ./cmd/goondex + @echo "Built: ./bin/goondex" + +# Run production build +run: build + ./bin/goondex serve + +# Clean build artifacts +clean: + @echo "Cleaning build artifacts..." + rm -rf tmp/ + rm -rf bin/ + rm -f build-errors.log + @echo "Cleaned!" + +# Install all dependencies +install-deps: install-air + @echo "All dependencies installed!" + +# Install Air for live reload +install-air: + @if ! command -v air >/dev/null 2>&1; then \ + echo "Installing Air..."; \ + go install github.com/air-verse/air@latest; \ + echo "Air installed! Make sure ~/go/bin is in your PATH"; \ + else \ + echo "Air is already installed"; \ + fi + +# Run tests +test: + go test -v ./... + +# Database migrations (if needed in future) +migrate-up: + @echo "Running migrations..." + # Add migration commands here + +migrate-down: + @echo "Rolling back migrations..." + # Add rollback commands here diff --git a/README.md b/README.md index 3ed14f8..ce96dad 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ Goondex ingests metadata from external sources (ThePornDB, etc.), normalizes it, **v0.1.0-dev2** - TPDB Integration Release +> Note: TPDB import/sync commands are temporarily disabled. Use the Adult Empire commands (`goondex adultemp ...`) to build your directory for now. + ## Features (v0.1.0-dev2) - ✅ SQLite database with WAL mode for performers, studios, scenes, and tags @@ -160,6 +162,13 @@ go build -o bin/goondex ./cmd/goondex go run ./cmd/goondex performer-search "test" ``` +### Scripts + +- `source scripts/env.sh` - Pin Go caches inside the repo (recommended before building) +- `scripts/build.sh` - Build the CLI (`bin/goondex`) +- `ADDR=localhost:8788 scripts/run.sh` - Build (if needed) and start the web UI +- `scripts/test.sh` - Run `go test ./cmd/... ./internal/...` + ### Building & Rebuilding the CLI The Goondex binary is not rebuilt automatically—whenever you change Go files (especially under `cmd/goondex` or `internal/*`), rebuild before re-running commands. diff --git a/cmd/goondex/main.go b/cmd/goondex/main.go index 6879b9d..b428193 100644 --- a/cmd/goondex/main.go +++ b/cmd/goondex/main.go @@ -19,6 +19,7 @@ import ( ) const tpdbAPIKeyEnvVar = "TPDB_API_KEY" +const tpdbEnabled = false var ( dbPath string @@ -48,7 +49,7 @@ func init() { rootCmd.AddCommand(versionCmd) performerSearchCmd.Flags().Bool("show-bio", false, "Show performer bio in search results") performerGetCmd.Flags().Bool("show-bio", false, "Show performer bio") - webCmd.Flags().String("addr", "localhost:8080", "Address to listen on") + webCmd.Flags().String("addr", "localhost:8788", "Address to listen on") // Sync command flags syncCmd.PersistentFlags().Bool("force", false, "Force sync even if rate limit not met") @@ -58,8 +59,8 @@ func init() { // Import command with subcommands var importCmd = &cobra.Command{ Use: "import", - Short: "Import data from external sources (TPDB)", - Long: `Import performers, studios, and scenes from ThePornDB into your local database.`, + Short: "Import data (Adult Empire available; TPDB disabled)", + Long: `Adult Empire import commands are available. TPDB bulk import commands are temporarily disabled while we focus on Adult Empire data.`, } var importAllCmd = &cobra.Command{ @@ -151,20 +152,10 @@ func init() { // Sync command with subcommands var syncCmd = &cobra.Command{ Use: "sync", - Short: "Sync and update existing data from TPDB", - Long: `Update existing performers, studios, and scenes with latest data from ThePornDB. + Short: "Sync and update existing data (TPDB disabled for now)", + Long: `TPDB-based sync is temporarily disabled while we prioritize Adult Empire as the main source. -Rate limiting is enforced to prevent excessive API calls: -- Default minimum interval: 1 hour -- Recommended interval: 24 hours -- Use --force to override rate limiting - -Examples: - goondex sync all # Sync all entities (with 1h rate limit) - goondex sync all --interval 24h # Sync all entities (24h rate limit) - goondex sync all --force # Force sync, ignore rate limit - goondex sync performers # Sync only performers - goondex sync status # View last sync times`, +Existing sync commands are left in place for when TPDB is re-enabled, but they will currently return a disabled message.`, } func init() { @@ -445,6 +436,10 @@ func formatDuration(d time.Duration) string { } func getTPDBAPIKey() (string, error) { + if !tpdbEnabled { + return "", fmt.Errorf("TPDB integration is disabled. Use Adult Empire commands (e.g., 'goondex adultemp search-performer' and 'goondex adultemp scrape-performer ') to import data instead.") + } + apiKey := os.Getenv(tpdbAPIKeyEnvVar) if apiKey == "" { return "", fmt.Errorf("%s environment variable is not set.\n%s", tpdbAPIKeyEnvVar, apiKeySetupInstructions()) @@ -499,9 +494,9 @@ var versionCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { fmt.Println("Goondex v0.1.0-dev5") fmt.Println("Features:") - fmt.Println(" • TPDB integration with auto-import & bulk commands") fmt.Println(" • Adult Empire scraper (scenes & performers)") - fmt.Println(" • Multi-source data merging") + fmt.Println(" • TPDB commands temporarily disabled (Adult Empire-only mode)") + fmt.Println(" • Multi-source data merging (when TPDB is re-enabled)") fmt.Println(" • Grid-based web UI with GX components") fmt.Println(" • Performer/studio/scene management") }, @@ -509,7 +504,7 @@ var versionCmd = &cobra.Command{ var performerSearchCmd = &cobra.Command{ Use: "performer-search [query]", - Short: "Search for performers (auto-fetches from TPDB if not in local database)", + Short: "Search for performers (imports from Adult Empire if missing locally)", Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { query := args[0] @@ -527,55 +522,60 @@ var performerSearchCmd = &cobra.Command{ return fmt.Errorf("search failed: %w", err) } - // If no local results, try fetching from TPDB + // If no local results, pull the top Adult Empire match if len(performers) == 0 { - fmt.Printf("No local results found. Searching TPDB for '%s'...\n", query) + fmt.Printf("No local results found. Searching Adult Empire for '%s'...\n", query) - apiKey, err := getTPDBAPIKey() + scraper, err := adultemp.NewScraper() if err != nil { - fmt.Printf("⚠ %s\n", err) - return nil + return fmt.Errorf("failed to create Adult Empire scraper: %w", err) } - scraper := tpdb.NewScraper("https://api.theporndb.net", apiKey) - tpdbPerformers, err := scraper.SearchPerformers(context.Background(), query) + results, err := scraper.SearchPerformersByName(context.Background(), query) if err != nil { - fmt.Printf("⚠ TPDB search failed: %v\n", err) + fmt.Printf("⚠ Adult Empire search failed: %v\n", err) return nil } - if len(tpdbPerformers) == 0 { - fmt.Println("No performers found on TPDB either.") + if len(results) == 0 { + fmt.Println("No performers found on Adult Empire either.") return nil } - fmt.Printf("Found %d performer(s) on TPDB. Fetching full details...\n\n", len(tpdbPerformers)) + fmt.Printf("Found %d performer(s) on Adult Empire. Importing the top match...\n\n", len(results)) - // Import from TPDB with enriched data - imported := 0 - for _, p := range tpdbPerformers { - // Fetch full details for this performer - fullPerformer, err := scraper.GetPerformerByID(context.Background(), p.SourceID) - if err != nil { - fmt.Printf("⚠ Failed to fetch details for %s: %v\n", p.Name, err) - // Fall back to search result - fullPerformer = &p - } - - if err := store.Create(fullPerformer); err != nil { - fmt.Printf("⚠ Failed to import %s: %v\n", fullPerformer.Name, err) - continue - } - imported++ + // Import only the top result to avoid wrong matches; use search-performer for manual selection + top := results[0] + performerData, err := scraper.ScrapePerformerByURL(context.Background(), top.URL) + if err != nil { + fmt.Printf("⚠ Failed to fetch Adult Empire details: %v\n", err) + return nil } - // Search again to get the imported performers with their IDs + performer := scraper.ConvertPerformerToModel(performerData) + if err := store.Create(performer); err != nil { + fmt.Printf("⚠ Failed to import %s: %v\n", performer.Name, err) + } else { + fmt.Printf("✓ Imported %s from Adult Empire\n", performer.Name) + } + + // Search again to get the imported performer with their ID performers, err = store.Search(query) if err != nil { return fmt.Errorf("search failed after import: %w", err) } - fmt.Printf("✓ Imported %d performer(s)\n\n", imported) + if len(performers) == 0 { + fmt.Println("No performers matched locally even after Adult Empire import.") + return nil + } + + if len(results) > 1 { + fmt.Println("Tip: run 'goondex adultemp search-performer \"\"' to pick a different match.") + } + + imported := 1 + fmt.Printf("\n✓ Imported %d performer(s)\n\n", imported) } fmt.Printf("Found %d performer(s):\n\n", len(performers)) @@ -916,7 +916,7 @@ var performerGetCmd = &cobra.Command{ var studioSearchCmd = &cobra.Command{ Use: "studio-search [query]", - Short: "Search for studios (auto-fetches from TPDB if not in local database)", + Short: "Search for studios (local database only; TPDB disabled)", Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { query := args[0] @@ -933,49 +933,6 @@ var studioSearchCmd = &cobra.Command{ return fmt.Errorf("search failed: %w", err) } - // If no local results, try fetching from TPDB - if len(studios) == 0 { - fmt.Printf("No local results found. Searching TPDB for '%s'...\n", query) - - apiKey, err := getTPDBAPIKey() - if err != nil { - fmt.Printf("⚠ %s\n", err) - return nil - } - - scraper := tpdb.NewScraper("https://api.theporndb.net", apiKey) - tpdbStudios, err := scraper.SearchStudios(context.Background(), query) - if err != nil { - fmt.Printf("⚠ TPDB search failed: %v\n", err) - return nil - } - - if len(tpdbStudios) == 0 { - fmt.Println("No studios found on TPDB either.") - return nil - } - - fmt.Printf("Found %d studio(s) on TPDB. Importing...\n\n", len(tpdbStudios)) - - // Import from TPDB - imported := 0 - for _, s := range tpdbStudios { - if err := store.Create(&s); err != nil { - fmt.Printf("⚠ Failed to import %s: %v\n", s.Name, err) - continue - } - imported++ - } - - // Search again to get the imported studios with their IDs - studios, err = store.Search(query) - if err != nil { - return fmt.Errorf("search failed after import: %w", err) - } - - fmt.Printf("✓ Imported %d studio(s)\n\n", imported) - } - fmt.Printf("Found %d studio(s):\n\n", len(studios)) for _, s := range studios { fmt.Printf("ID: %d\n", s.ID) @@ -1079,7 +1036,7 @@ var studioGetCmd = &cobra.Command{ var sceneSearchCmd = &cobra.Command{ Use: "scene-search [query]", - Short: "Search for scenes (auto-fetches from TPDB if not in local database)", + Short: "Search for scenes (local database only; TPDB disabled)", Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { query := args[0] @@ -1096,55 +1053,6 @@ var sceneSearchCmd = &cobra.Command{ return fmt.Errorf("search failed: %w", err) } - // If no local results, try fetching from TPDB - if len(scenes) == 0 { - fmt.Printf("No local results found. Searching TPDB for '%s'...\n", query) - - apiKey, err := getTPDBAPIKey() - if err != nil { - fmt.Printf("⚠ %s\n", err) - return nil - } - - scraper := tpdb.NewScraper("https://api.theporndb.net", apiKey) - tpdbScenes, err := scraper.SearchScenes(context.Background(), query) - if err != nil { - fmt.Printf("⚠ TPDB search failed: %v\n", err) - return nil - } - - if len(tpdbScenes) == 0 { - fmt.Println("No scenes found on TPDB either.") - return nil - } - - fmt.Printf("Found %d scene(s) on TPDB. Importing (basic metadata only)...\n\n", len(tpdbScenes)) - - // Import scenes (simplified - just scene metadata, no relationships) - imported := 0 - for _, sc := range tpdbScenes { - // Clear relationships to avoid complexity in auto-import - sc.Performers = nil - sc.Tags = nil - sc.Studio = nil - sc.StudioID = nil - - if err := store.Create(&sc); err != nil { - fmt.Printf("⚠ Failed to import %s: %v\n", sc.Title, err) - continue - } - imported++ - } - - // Search again to get the imported scenes with their IDs - scenes, err = store.Search(query) - if err != nil { - return fmt.Errorf("search failed after import: %w", err) - } - - fmt.Printf("✓ Imported %d scene(s) (use 'import scene' for full metadata with relationships)\n\n", imported) - } - fmt.Printf("Found %d scene(s):\n\n", len(scenes)) for _, sc := range scenes { fmt.Printf("ID: %d\n", sc.ID) diff --git a/config/source.yml b/config/source.yml index 6249cbe..129cf4b 100644 --- a/config/source.yml +++ b/config/source.yml @@ -3,16 +3,16 @@ sources: - name: "tpdb" baseURL: "https://api.theporndb.net" apiKey: "${TPDB_API_KEY}" - enabled: true + enabled: false # Temporarily disabled (Adult Empire-only mode) priority: 1 options: includeMalePerformers: true skipMultipleMatches: true - name: "ae" - baseURL: "https://example-ae.site/api" + baseURL: "https://www.adultempire.com" apiKey: "${AE_API_KEY}" - enabled: false + enabled: true priority: 2 options: includeMalePerformers: true diff --git a/docs/CLI_REFERENCE.md b/docs/CLI_REFERENCE.md index 320937c..510abec 100644 --- a/docs/CLI_REFERENCE.md +++ b/docs/CLI_REFERENCE.md @@ -2,6 +2,8 @@ Complete command-line interface documentation for Goondex. +> TPDB import and sync commands are temporarily disabled. Use Adult Empire commands (prefixed with `goondex adultemp ...`) to search and import new data. + ## Global Flags ```bash diff --git a/internal/web/server.go b/internal/web/server.go index 2d82ae4..3ca805b 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -565,6 +565,30 @@ type APIResponse struct { Data interface{} `json:"data,omitempty"` } +const tpdbEnabled = false +const tpdbDisabledMessage = "TPDB integration is disabled. Use the Adult Empire CLI commands to import and enrich data for now." + +func tpdbAPIKey() (string, error) { + if !tpdbEnabled { + return "", fmt.Errorf(tpdbDisabledMessage) + } + + apiKey := os.Getenv("TPDB_API_KEY") + if apiKey == "" { + return "", fmt.Errorf("TPDB_API_KEY not configured") + } + + return apiKey, nil +} + +func writeTPDBError(w http.ResponseWriter, err error) bool { + if err == nil { + return false + } + json.NewEncoder(w).Encode(APIResponse{Success: false, Message: err.Error()}) + return true +} + func (s *Server) handleAPIImportPerformer(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) @@ -579,9 +603,8 @@ func (s *Server) handleAPIImportPerformer(w http.ResponseWriter, r *http.Request return } - apiKey := os.Getenv("TPDB_API_KEY") - if apiKey == "" { - json.NewEncoder(w).Encode(APIResponse{Success: false, Message: "TPDB_API_KEY not configured"}) + apiKey, err := tpdbAPIKey() + if writeTPDBError(w, err) { return } @@ -634,9 +657,8 @@ func (s *Server) handleAPIImportStudio(w http.ResponseWriter, r *http.Request) { return } - apiKey := os.Getenv("TPDB_API_KEY") - if apiKey == "" { - json.NewEncoder(w).Encode(APIResponse{Success: false, Message: "TPDB_API_KEY not configured"}) + apiKey, err := tpdbAPIKey() + if writeTPDBError(w, err) { return } @@ -684,9 +706,8 @@ func (s *Server) handleAPIImportScene(w http.ResponseWriter, r *http.Request) { return } - apiKey := os.Getenv("TPDB_API_KEY") - if apiKey == "" { - json.NewEncoder(w).Encode(APIResponse{Success: false, Message: "TPDB_API_KEY not configured"}) + apiKey, err := tpdbAPIKey() + if writeTPDBError(w, err) { return } @@ -773,9 +794,8 @@ func (s *Server) handleAPISync(w http.ResponseWriter, r *http.Request) { } json.NewDecoder(r.Body).Decode(&req) - apiKey := os.Getenv("TPDB_API_KEY") - if apiKey == "" { - json.NewEncoder(w).Encode(APIResponse{Success: false, Message: "TPDB_API_KEY not configured"}) + apiKey, err := tpdbAPIKey() + if writeTPDBError(w, err) { return } @@ -801,6 +821,10 @@ func (s *Server) handleAPISync(w http.ResponseWriter, r *http.Request) { } func (s *Server) handleAPISyncStatus(w http.ResponseWriter, r *http.Request) { + if writeTPDBError(w, fmt.Errorf(tpdbDisabledMessage)) { + return + } + scraper := tpdb.NewScraper("https://api.theporndb.net", "") service := sync.NewService(s.db, scraper) @@ -829,9 +853,8 @@ func (s *Server) handleAPIBulkImportAll(w http.ResponseWriter, r *http.Request) w.Header().Set("Content-Type", "application/json") - apiKey := os.Getenv("TPDB_API_KEY") - if apiKey == "" { - json.NewEncoder(w).Encode(APIResponse{Success: false, Message: "TPDB_API_KEY not configured"}) + apiKey, err := tpdbAPIKey() + if writeTPDBError(w, err) { return } @@ -859,9 +882,8 @@ func (s *Server) handleAPIBulkImportPerformers(w http.ResponseWriter, r *http.Re w.Header().Set("Content-Type", "application/json") - apiKey := os.Getenv("TPDB_API_KEY") - if apiKey == "" { - json.NewEncoder(w).Encode(APIResponse{Success: false, Message: "TPDB_API_KEY not configured"}) + apiKey, err := tpdbAPIKey() + if writeTPDBError(w, err) { return } @@ -889,9 +911,8 @@ func (s *Server) handleAPIBulkImportStudios(w http.ResponseWriter, r *http.Reque w.Header().Set("Content-Type", "application/json") - apiKey := os.Getenv("TPDB_API_KEY") - if apiKey == "" { - json.NewEncoder(w).Encode(APIResponse{Success: false, Message: "TPDB_API_KEY not configured"}) + apiKey, err := tpdbAPIKey() + if writeTPDBError(w, err) { return } @@ -919,9 +940,8 @@ func (s *Server) handleAPIBulkImportScenes(w http.ResponseWriter, r *http.Reques w.Header().Set("Content-Type", "application/json") - apiKey := os.Getenv("TPDB_API_KEY") - if apiKey == "" { - json.NewEncoder(w).Encode(APIResponse{Success: false, Message: "TPDB_API_KEY not configured"}) + apiKey, err := tpdbAPIKey() + if writeTPDBError(w, err) { return } @@ -947,9 +967,9 @@ func (s *Server) handleAPIBulkImportPerformersProgress(w http.ResponseWriter, r w.Header().Set("Connection", "keep-alive") w.Header().Set("Access-Control-Allow-Origin", "*") - apiKey := os.Getenv("TPDB_API_KEY") - if apiKey == "" { - fmt.Fprintf(w, "data: {\"error\": \"TPDB_API_KEY not configured\"}\n\n") + apiKey, err := tpdbAPIKey() + if err != nil { + fmt.Fprintf(w, "data: {\"error\": \"%s\"}\n\n", err.Error()) return } @@ -990,9 +1010,9 @@ func (s *Server) handleAPIBulkImportStudiosProgress(w http.ResponseWriter, r *ht w.Header().Set("Connection", "keep-alive") w.Header().Set("Access-Control-Allow-Origin", "*") - apiKey := os.Getenv("TPDB_API_KEY") - if apiKey == "" { - fmt.Fprintf(w, "data: {\"error\": \"TPDB_API_KEY not configured\"}\n\n") + apiKey, err := tpdbAPIKey() + if err != nil { + fmt.Fprintf(w, "data: {\"error\": \"%s\"}\n\n", err.Error()) return } @@ -1033,9 +1053,9 @@ func (s *Server) handleAPIBulkImportScenesProgress(w http.ResponseWriter, r *htt w.Header().Set("Connection", "keep-alive") w.Header().Set("Access-Control-Allow-Origin", "*") - apiKey := os.Getenv("TPDB_API_KEY") - if apiKey == "" { - fmt.Fprintf(w, "data: {\"error\": \"TPDB_API_KEY not configured\"}\n\n") + apiKey, err := tpdbAPIKey() + if err != nil { + fmt.Fprintf(w, "data: {\"error\": \"%s\"}\n\n", err.Error()) return } diff --git a/internal/web/static/css/style.css b/internal/web/static/css/style.css index 6354268..9b25d25 100644 --- a/internal/web/static/css/style.css +++ b/internal/web/static/css/style.css @@ -507,6 +507,23 @@ main.container { color: var(--color-brand); } +.empty-import-actions { + margin-top: 1.5rem; +} + +.empty-import-actions .action-buttons { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; + justify-content: center; + margin-bottom: 0.75rem; +} + +.empty-import-actions .hint { + font-size: 0.95rem; + color: var(--color-text-secondary); +} + /* Detail views */ .breadcrumb { margin-bottom: 1.5rem; diff --git a/internal/web/static/img/logo/GOONDEX_logo.svg b/internal/web/static/img/logo/GOONDEX_logo.svg index 1bf3bb8..be57e65 100644 --- a/internal/web/static/img/logo/GOONDEX_logo.svg +++ b/internal/web/static/img/logo/GOONDEX_logo.svg @@ -24,14 +24,15 @@ inkscape:deskcolor="#505050" inkscape:document-units="px" inkscape:zoom="2.1896304" - inkscape:cx="898.55348" - inkscape:cy="158.70258" + inkscape:cx="884.39583" + inkscape:cy="34.252356" inkscape:window-width="1920" - inkscape:window-height="1048" + inkscape:window-height="1011" inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" - inkscape:current-layer="svg1"> + inkscape:current-layer="g9" + showgrid="false"> + @@ -58,29 +73,80 @@ + d="m 89.6071,50.4885 c -23.10416,0 -39.60743,16.69024 -39.60743,39.51149 0,22.82124 16.50328,39.51151 39.60743,39.51151 22.06683,0 39.04336,-15.65212 39.04336,-37.90755 v -3.39592 h -40.6473 v 8.57996 h 30.08354 c -1.13163,13.67388 -12.63408,23.85962 -28.0997,23.85962 -17.6346,0 -30.08354,-12.82442 -30.08354,-30.64762 0,-17.72891 12.44569,-30.45956 29.89167,-30.45956 12.91947,0 23.57566,7.07122 26.68766,16.59581 h 10.75176 C 123.27385,61.70793 108.84487,50.48851 89.6071,50.4885 Z m 240.25392,1.32 v 59.3152 L 284.12556,52.28048 h -9.23996 v 75.53498 h 9.89995 V 68.50023 l 45.73537,58.84324 h 9.3359 V 51.80846 Z m 18.51061,0.47198 v 75.53499 h 26.7796 c 26.0276,0 41.1193,-15.1812 41.1193,-37.71954 0,-0.52548 -0.01,-1.04807 -0.027,-1.56558 -0.041,-1.2646 -0.1283,-2.50346 -0.2647,-3.71824 h -0.01 c -2.2059,-19.60648 -16.8839,-32.53165 -40.82,-32.53165 z m 74.6754,0 v 75.53499 h 54.5072 v -8.77182 h -44.6073 V 93.5839 h 40.4593 v -8.77179 h -40.4593 V 61.04843 h 43.7593 v -8.76795 z m 60.6582,0 26.3116,37.34349 -27.2555,38.1915 h 11.5998 l 21.9717,-30.93156 21.8797,30.93156 h 11.7878 l -27.3476,-38.47545 26.4036,-37.05954 h -11.5039 l -21.0277,29.79956 -20.8436,-29.79956 z m -125.4337,8.86387 h 17.1637 c 17.2271,0 28.4182,8.55424 30.4864,23.66776 h -23.3339 v 8.77179 h 23.5335 c 0.098,-1.12825 0.1497,-2.29113 0.1497,-3.48797 0,1.19665 -0.052,2.35989 -0.1497,3.48797 -1.4059,16.20741 -12.7883,25.27173 -30.686,25.27173 h -17.1637 z" + sodipodi:nodetypes="ssssccccsssccscccccccccccccsscccsccccccccccccccccccccccccccccsccccccscc" /> +