karaoke-gen 0.96.0__py3-none-any.whl → 0.101.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. backend/api/routes/admin.py +696 -92
  2. backend/api/routes/audio_search.py +29 -8
  3. backend/api/routes/file_upload.py +99 -22
  4. backend/api/routes/health.py +65 -0
  5. backend/api/routes/internal.py +6 -0
  6. backend/api/routes/jobs.py +28 -1
  7. backend/api/routes/review.py +13 -6
  8. backend/api/routes/tenant.py +120 -0
  9. backend/api/routes/users.py +472 -51
  10. backend/main.py +31 -2
  11. backend/middleware/__init__.py +7 -1
  12. backend/middleware/tenant.py +192 -0
  13. backend/models/job.py +19 -3
  14. backend/models/tenant.py +208 -0
  15. backend/models/user.py +18 -0
  16. backend/services/email_service.py +253 -6
  17. backend/services/encoding_service.py +128 -31
  18. backend/services/firestore_service.py +6 -0
  19. backend/services/job_manager.py +44 -2
  20. backend/services/langfuse_preloader.py +98 -0
  21. backend/services/nltk_preloader.py +122 -0
  22. backend/services/spacy_preloader.py +65 -0
  23. backend/services/stripe_service.py +133 -11
  24. backend/services/tenant_service.py +285 -0
  25. backend/services/user_service.py +85 -7
  26. backend/tests/emulator/conftest.py +22 -1
  27. backend/tests/emulator/test_made_for_you_integration.py +167 -0
  28. backend/tests/test_admin_job_files.py +337 -0
  29. backend/tests/test_admin_job_reset.py +384 -0
  30. backend/tests/test_admin_job_update.py +326 -0
  31. backend/tests/test_email_service.py +233 -0
  32. backend/tests/test_impersonation.py +223 -0
  33. backend/tests/test_job_creation_regression.py +4 -0
  34. backend/tests/test_job_manager.py +171 -9
  35. backend/tests/test_jobs_api.py +11 -1
  36. backend/tests/test_made_for_you.py +2086 -0
  37. backend/tests/test_models.py +139 -0
  38. backend/tests/test_spacy_preloader.py +119 -0
  39. backend/tests/test_tenant_api.py +350 -0
  40. backend/tests/test_tenant_middleware.py +345 -0
  41. backend/tests/test_tenant_models.py +406 -0
  42. backend/tests/test_tenant_service.py +418 -0
  43. backend/utils/test_data.py +27 -0
  44. backend/workers/screens_worker.py +16 -6
  45. backend/workers/video_worker.py +8 -3
  46. {karaoke_gen-0.96.0.dist-info → karaoke_gen-0.101.0.dist-info}/METADATA +1 -1
  47. {karaoke_gen-0.96.0.dist-info → karaoke_gen-0.101.0.dist-info}/RECORD +58 -39
  48. lyrics_transcriber/correction/agentic/agent.py +17 -6
  49. lyrics_transcriber/correction/agentic/providers/langchain_bridge.py +96 -43
  50. lyrics_transcriber/correction/agentic/providers/model_factory.py +27 -6
  51. lyrics_transcriber/correction/anchor_sequence.py +151 -37
  52. lyrics_transcriber/correction/handlers/syllables_match.py +44 -2
  53. lyrics_transcriber/correction/phrase_analyzer.py +18 -0
  54. lyrics_transcriber/frontend/src/api.ts +13 -5
  55. lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +90 -57
  56. {karaoke_gen-0.96.0.dist-info → karaoke_gen-0.101.0.dist-info}/WHEEL +0 -0
  57. {karaoke_gen-0.96.0.dist-info → karaoke_gen-0.101.0.dist-info}/entry_points.txt +0 -0
  58. {karaoke_gen-0.96.0.dist-info → karaoke_gen-0.101.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,120 @@
1
+ """
2
+ Tenant API routes for white-label portal configuration.
3
+
4
+ These endpoints are public (no auth required) since the frontend needs
5
+ to fetch tenant branding before the user logs in.
6
+ """
7
+
8
+ import logging
9
+ from typing import Optional
10
+
11
+ from fastapi import APIRouter, Query, Request
12
+
13
+ from backend.models.tenant import TenantConfigResponse, TenantPublicConfig
14
+ from backend.services.tenant_service import get_tenant_service
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ router = APIRouter(prefix="/api/tenant", tags=["tenant"])
19
+
20
+
21
+ @router.get("/config", response_model=TenantConfigResponse)
22
+ async def get_tenant_config(
23
+ request: Request,
24
+ tenant: Optional[str] = Query(
25
+ None,
26
+ description="Tenant ID override (for development). If not provided, detected from subdomain.",
27
+ ),
28
+ ):
29
+ """
30
+ Get tenant configuration for the current portal.
31
+
32
+ This endpoint detects the tenant from:
33
+ 1. Query parameter `tenant` (for development/testing)
34
+ 2. X-Tenant-ID header (set by frontend)
35
+ 3. Host header subdomain
36
+
37
+ Returns the public tenant configuration (branding, features, defaults)
38
+ or indicates this is the default Nomad Karaoke portal.
39
+ """
40
+ tenant_service = get_tenant_service()
41
+
42
+ # Priority 1: Query parameter (for dev/testing)
43
+ tenant_id = tenant
44
+
45
+ # Priority 2: X-Tenant-ID header
46
+ if not tenant_id:
47
+ tenant_id = request.headers.get("X-Tenant-ID")
48
+
49
+ # Priority 3: Detect from Host header
50
+ if not tenant_id:
51
+ host = request.headers.get("Host", "")
52
+ # Check if this is a tenant subdomain
53
+ # Pattern: {tenant}.nomadkaraoke.com or {tenant}.gen.nomadkaraoke.com
54
+ if host and "nomadkaraoke.com" in host.lower():
55
+ parts = host.lower().split(".")
56
+ # Skip known non-tenant subdomains
57
+ if parts[0] not in ["gen", "api", "www", "buy", "admin"]:
58
+ potential_tenant = parts[0]
59
+ if tenant_service.tenant_exists(potential_tenant):
60
+ tenant_id = potential_tenant
61
+
62
+ # If no tenant detected, return default config
63
+ if not tenant_id:
64
+ logger.debug("No tenant detected, returning default config")
65
+ return TenantConfigResponse(tenant=None, is_default=True)
66
+
67
+ # Load tenant config
68
+ public_config = tenant_service.get_public_config(tenant_id)
69
+
70
+ if not public_config:
71
+ logger.warning(f"Tenant not found: {tenant_id}")
72
+ return TenantConfigResponse(tenant=None, is_default=True)
73
+
74
+ if not public_config.is_active:
75
+ logger.warning(f"Tenant is inactive: {tenant_id}")
76
+ return TenantConfigResponse(tenant=None, is_default=True)
77
+
78
+ logger.info(f"Returning config for tenant: {tenant_id}")
79
+ return TenantConfigResponse(tenant=public_config, is_default=False)
80
+
81
+
82
+ @router.get("/config/{tenant_id}", response_model=TenantConfigResponse)
83
+ async def get_tenant_config_by_id(tenant_id: str):
84
+ """
85
+ Get tenant configuration by explicit tenant ID.
86
+
87
+ This is useful for admin tools or debugging.
88
+ """
89
+ tenant_service = get_tenant_service()
90
+ public_config = tenant_service.get_public_config(tenant_id)
91
+
92
+ if not public_config:
93
+ return TenantConfigResponse(tenant=None, is_default=True)
94
+
95
+ # Check if tenant is active
96
+ if not public_config.is_active:
97
+ logger.warning(f"Tenant is inactive: {tenant_id}")
98
+ return TenantConfigResponse(tenant=None, is_default=True)
99
+
100
+ return TenantConfigResponse(tenant=public_config, is_default=False)
101
+
102
+
103
+ @router.get("/asset/{tenant_id}/{asset_name}")
104
+ async def get_tenant_asset(tenant_id: str, asset_name: str):
105
+ """
106
+ Get a signed URL for a tenant asset (logo, favicon, etc.).
107
+
108
+ This redirects to the signed GCS URL for the asset.
109
+ """
110
+ from fastapi.responses import RedirectResponse
111
+
112
+ tenant_service = get_tenant_service()
113
+ url = tenant_service.get_asset_url(tenant_id, asset_name)
114
+
115
+ if not url:
116
+ from fastapi import HTTPException
117
+
118
+ raise HTTPException(status_code=404, detail="Asset not found")
119
+
120
+ return RedirectResponse(url=url)