karaoke-gen 0.99.3__py3-none-any.whl → 0.103.1__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.
- backend/api/routes/admin.py +512 -1
- backend/api/routes/audio_search.py +17 -34
- backend/api/routes/file_upload.py +60 -84
- backend/api/routes/internal.py +6 -0
- backend/api/routes/jobs.py +11 -3
- backend/api/routes/rate_limits.py +428 -0
- backend/api/routes/review.py +13 -6
- backend/api/routes/tenant.py +120 -0
- backend/api/routes/users.py +229 -247
- backend/config.py +16 -0
- backend/exceptions.py +66 -0
- backend/main.py +30 -1
- backend/middleware/__init__.py +7 -1
- backend/middleware/tenant.py +192 -0
- backend/models/job.py +19 -3
- backend/models/tenant.py +208 -0
- backend/models/user.py +18 -0
- backend/services/email_service.py +253 -6
- backend/services/email_validation_service.py +646 -0
- backend/services/firestore_service.py +27 -0
- backend/services/job_defaults_service.py +113 -0
- backend/services/job_manager.py +73 -3
- backend/services/rate_limit_service.py +641 -0
- backend/services/stripe_service.py +61 -35
- backend/services/tenant_service.py +285 -0
- backend/services/user_service.py +85 -7
- backend/tests/conftest.py +7 -1
- backend/tests/emulator/test_made_for_you_integration.py +167 -0
- backend/tests/test_admin_job_files.py +337 -0
- backend/tests/test_admin_job_reset.py +384 -0
- backend/tests/test_admin_job_update.py +326 -0
- backend/tests/test_audio_search.py +12 -8
- backend/tests/test_email_service.py +233 -0
- backend/tests/test_email_validation_service.py +298 -0
- backend/tests/test_file_upload.py +8 -6
- backend/tests/test_impersonation.py +223 -0
- backend/tests/test_job_creation_regression.py +4 -0
- backend/tests/test_job_manager.py +146 -1
- backend/tests/test_made_for_you.py +2088 -0
- backend/tests/test_models.py +139 -0
- backend/tests/test_rate_limit_service.py +396 -0
- backend/tests/test_rate_limits_api.py +392 -0
- backend/tests/test_tenant_api.py +350 -0
- backend/tests/test_tenant_middleware.py +345 -0
- backend/tests/test_tenant_models.py +406 -0
- backend/tests/test_tenant_service.py +418 -0
- backend/workers/video_worker.py +8 -3
- backend/workers/video_worker_orchestrator.py +26 -0
- {karaoke_gen-0.99.3.dist-info → karaoke_gen-0.103.1.dist-info}/METADATA +1 -1
- {karaoke_gen-0.99.3.dist-info → karaoke_gen-0.103.1.dist-info}/RECORD +55 -33
- lyrics_transcriber/frontend/src/api.ts +13 -5
- lyrics_transcriber/frontend/src/components/PreviewVideoSection.tsx +90 -57
- {karaoke_gen-0.99.3.dist-info → karaoke_gen-0.103.1.dist-info}/WHEEL +0 -0
- {karaoke_gen-0.99.3.dist-info → karaoke_gen-0.103.1.dist-info}/entry_points.txt +0 -0
- {karaoke_gen-0.99.3.dist-info → karaoke_gen-0.103.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -15,7 +15,6 @@ import json
|
|
|
15
15
|
import logging
|
|
16
16
|
import tempfile
|
|
17
17
|
import os
|
|
18
|
-
from dataclasses import dataclass
|
|
19
18
|
from fastapi import APIRouter, UploadFile, File, Form, HTTPException, BackgroundTasks, Request, Body, Depends
|
|
20
19
|
from pathlib import Path
|
|
21
20
|
from typing import Optional, List, Dict, Any, Tuple
|
|
@@ -29,11 +28,17 @@ from backend.services.storage_service import StorageService
|
|
|
29
28
|
from backend.services.worker_service import get_worker_service
|
|
30
29
|
from backend.services.credential_manager import get_credential_manager, CredentialStatus
|
|
31
30
|
from backend.services.theme_service import get_theme_service
|
|
31
|
+
from backend.services.job_defaults_service import (
|
|
32
|
+
get_effective_distribution_settings,
|
|
33
|
+
resolve_cdg_txt_defaults,
|
|
34
|
+
EffectiveDistributionSettings,
|
|
35
|
+
)
|
|
32
36
|
from backend.config import get_settings
|
|
33
37
|
from backend.version import VERSION
|
|
34
38
|
from backend.services.metrics import metrics
|
|
35
39
|
from backend.api.dependencies import require_auth
|
|
36
40
|
from backend.services.auth_service import UserType, AuthResult
|
|
41
|
+
from backend.middleware.tenant import get_tenant_config_from_request
|
|
37
42
|
|
|
38
43
|
logger = logging.getLogger(__name__)
|
|
39
44
|
router = APIRouter(tags=["jobs"])
|
|
@@ -263,75 +268,6 @@ async def _trigger_audio_worker_only(job_id: str) -> None:
|
|
|
263
268
|
await worker_service.trigger_audio_worker(job_id)
|
|
264
269
|
|
|
265
270
|
|
|
266
|
-
def _resolve_cdg_txt_defaults(
|
|
267
|
-
theme_id: Optional[str],
|
|
268
|
-
enable_cdg: Optional[bool],
|
|
269
|
-
enable_txt: Optional[bool]
|
|
270
|
-
) -> Tuple[bool, bool]:
|
|
271
|
-
"""
|
|
272
|
-
Resolve CDG/TXT settings based on theme and explicit settings.
|
|
273
|
-
|
|
274
|
-
When a theme is selected, CDG and TXT are enabled by default.
|
|
275
|
-
Explicit True/False values always override the default.
|
|
276
|
-
|
|
277
|
-
Args:
|
|
278
|
-
theme_id: Theme identifier (if any)
|
|
279
|
-
enable_cdg: Explicit CDG setting (None means use default)
|
|
280
|
-
enable_txt: Explicit TXT setting (None means use default)
|
|
281
|
-
|
|
282
|
-
Returns:
|
|
283
|
-
Tuple of (resolved_enable_cdg, resolved_enable_txt)
|
|
284
|
-
"""
|
|
285
|
-
# Default based on whether theme is set
|
|
286
|
-
default_enabled = theme_id is not None
|
|
287
|
-
|
|
288
|
-
# Resolve with explicit override taking precedence
|
|
289
|
-
resolved_cdg = enable_cdg if enable_cdg is not None else default_enabled
|
|
290
|
-
resolved_txt = enable_txt if enable_txt is not None else default_enabled
|
|
291
|
-
|
|
292
|
-
return resolved_cdg, resolved_txt
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
@dataclass
|
|
296
|
-
class EffectiveDistributionSettings:
|
|
297
|
-
"""Settings with defaults applied from environment variables."""
|
|
298
|
-
dropbox_path: Optional[str]
|
|
299
|
-
gdrive_folder_id: Optional[str]
|
|
300
|
-
discord_webhook_url: Optional[str]
|
|
301
|
-
brand_prefix: Optional[str]
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
def _get_effective_distribution_settings(
|
|
305
|
-
dropbox_path: Optional[str] = None,
|
|
306
|
-
gdrive_folder_id: Optional[str] = None,
|
|
307
|
-
discord_webhook_url: Optional[str] = None,
|
|
308
|
-
brand_prefix: Optional[str] = None,
|
|
309
|
-
) -> EffectiveDistributionSettings:
|
|
310
|
-
"""
|
|
311
|
-
Get distribution settings with defaults applied from environment variables.
|
|
312
|
-
|
|
313
|
-
This ensures consistent handling of defaults across all job creation endpoints.
|
|
314
|
-
Each parameter, if not provided (None), falls back to the corresponding
|
|
315
|
-
environment variable configured in settings.
|
|
316
|
-
|
|
317
|
-
Args:
|
|
318
|
-
dropbox_path: Explicit Dropbox path or None for default
|
|
319
|
-
gdrive_folder_id: Explicit Google Drive folder ID or None for default
|
|
320
|
-
discord_webhook_url: Explicit Discord webhook URL or None for default
|
|
321
|
-
brand_prefix: Explicit brand prefix or None for default
|
|
322
|
-
|
|
323
|
-
Returns:
|
|
324
|
-
EffectiveDistributionSettings with defaults applied
|
|
325
|
-
"""
|
|
326
|
-
settings = get_settings()
|
|
327
|
-
return EffectiveDistributionSettings(
|
|
328
|
-
dropbox_path=dropbox_path or settings.default_dropbox_path,
|
|
329
|
-
gdrive_folder_id=gdrive_folder_id or settings.default_gdrive_folder_id,
|
|
330
|
-
discord_webhook_url=discord_webhook_url or settings.default_discord_webhook_url,
|
|
331
|
-
brand_prefix=brand_prefix or settings.default_brand_prefix,
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
|
|
335
271
|
def _prepare_theme_for_job(
|
|
336
272
|
job_id: str,
|
|
337
273
|
theme_id: str,
|
|
@@ -446,6 +382,14 @@ async def upload_and_create_job(
|
|
|
446
382
|
The style_params JSON can reference the uploaded images/fonts by their original
|
|
447
383
|
filenames, and the backend will update the paths to GCS locations.
|
|
448
384
|
"""
|
|
385
|
+
# Check tenant feature flag
|
|
386
|
+
tenant_config = get_tenant_config_from_request(request)
|
|
387
|
+
if tenant_config and not tenant_config.features.file_upload:
|
|
388
|
+
raise HTTPException(
|
|
389
|
+
status_code=403,
|
|
390
|
+
detail="File upload is not available for this portal"
|
|
391
|
+
)
|
|
392
|
+
|
|
449
393
|
try:
|
|
450
394
|
# Validate main audio file type
|
|
451
395
|
file_ext = Path(file.filename).suffix.lower()
|
|
@@ -494,7 +438,7 @@ async def upload_and_create_job(
|
|
|
494
438
|
)
|
|
495
439
|
|
|
496
440
|
# Apply default distribution settings from environment if not provided
|
|
497
|
-
dist =
|
|
441
|
+
dist = get_effective_distribution_settings(
|
|
498
442
|
dropbox_path=dropbox_path,
|
|
499
443
|
gdrive_folder_id=gdrive_folder_id,
|
|
500
444
|
discord_webhook_url=discord_webhook_url,
|
|
@@ -572,7 +516,7 @@ async def upload_and_create_job(
|
|
|
572
516
|
logger.info(f"Applying default theme: {effective_theme_id}")
|
|
573
517
|
|
|
574
518
|
# Resolve CDG/TXT defaults based on theme
|
|
575
|
-
resolved_cdg, resolved_txt =
|
|
519
|
+
resolved_cdg, resolved_txt = resolve_cdg_txt_defaults(
|
|
576
520
|
effective_theme_id, enable_cdg, enable_txt
|
|
577
521
|
)
|
|
578
522
|
|
|
@@ -632,10 +576,12 @@ async def upload_and_create_job(
|
|
|
632
576
|
request_metadata=request_metadata,
|
|
633
577
|
# Non-interactive mode
|
|
634
578
|
non_interactive=non_interactive,
|
|
579
|
+
# Tenant scoping
|
|
580
|
+
tenant_id=tenant_config.id if tenant_config else None,
|
|
635
581
|
)
|
|
636
|
-
job = job_manager.create_job(job_create)
|
|
582
|
+
job = job_manager.create_job(job_create, is_admin=auth_result.is_admin)
|
|
637
583
|
job_id = job.job_id
|
|
638
|
-
|
|
584
|
+
|
|
639
585
|
# Record job creation metric
|
|
640
586
|
metrics.record_job_created(job_id, source="upload")
|
|
641
587
|
|
|
@@ -1029,6 +975,14 @@ async def create_job_with_upload_urls(
|
|
|
1029
975
|
- Works with any HTTP client (no HTTP/2 required)
|
|
1030
976
|
- Resumable uploads possible with GCS
|
|
1031
977
|
"""
|
|
978
|
+
# Check tenant feature flag
|
|
979
|
+
tenant_config = get_tenant_config_from_request(request)
|
|
980
|
+
if tenant_config and not tenant_config.features.file_upload:
|
|
981
|
+
raise HTTPException(
|
|
982
|
+
status_code=403,
|
|
983
|
+
detail="File upload is not available for this portal"
|
|
984
|
+
)
|
|
985
|
+
|
|
1032
986
|
try:
|
|
1033
987
|
# Validate files list
|
|
1034
988
|
if not body.files:
|
|
@@ -1058,7 +1012,7 @@ async def create_job_with_upload_urls(
|
|
|
1058
1012
|
)
|
|
1059
1013
|
|
|
1060
1014
|
# Apply default distribution settings from environment if not provided
|
|
1061
|
-
dist =
|
|
1015
|
+
dist = get_effective_distribution_settings(
|
|
1062
1016
|
dropbox_path=body.dropbox_path,
|
|
1063
1017
|
gdrive_folder_id=body.gdrive_folder_id,
|
|
1064
1018
|
discord_webhook_url=body.discord_webhook_url,
|
|
@@ -1116,7 +1070,7 @@ async def create_job_with_upload_urls(
|
|
|
1116
1070
|
logger.info(f"Applying default theme: {effective_theme_id}")
|
|
1117
1071
|
|
|
1118
1072
|
# Resolve CDG/TXT defaults based on theme
|
|
1119
|
-
resolved_cdg, resolved_txt =
|
|
1073
|
+
resolved_cdg, resolved_txt = resolve_cdg_txt_defaults(
|
|
1120
1074
|
effective_theme_id, body.enable_cdg, body.enable_txt
|
|
1121
1075
|
)
|
|
1122
1076
|
|
|
@@ -1153,8 +1107,10 @@ async def create_job_with_upload_urls(
|
|
|
1153
1107
|
other_stems_models=body.other_stems_models,
|
|
1154
1108
|
request_metadata=request_metadata,
|
|
1155
1109
|
non_interactive=body.non_interactive,
|
|
1110
|
+
# Tenant scoping
|
|
1111
|
+
tenant_id=tenant_config.id if tenant_config else None,
|
|
1156
1112
|
)
|
|
1157
|
-
job = job_manager.create_job(job_create)
|
|
1113
|
+
job = job_manager.create_job(job_create, is_admin=auth_result.is_admin)
|
|
1158
1114
|
job_id = job.job_id
|
|
1159
1115
|
|
|
1160
1116
|
# Record job creation metric
|
|
@@ -1481,6 +1437,14 @@ async def create_job_from_url(
|
|
|
1481
1437
|
Note: YouTube rate limiting may cause occasional download failures.
|
|
1482
1438
|
The backend will retry automatically.
|
|
1483
1439
|
"""
|
|
1440
|
+
# Check tenant feature flag
|
|
1441
|
+
tenant_config = get_tenant_config_from_request(request)
|
|
1442
|
+
if tenant_config and not tenant_config.features.youtube_url:
|
|
1443
|
+
raise HTTPException(
|
|
1444
|
+
status_code=403,
|
|
1445
|
+
detail="URL-based job creation is not available for this portal"
|
|
1446
|
+
)
|
|
1447
|
+
|
|
1484
1448
|
try:
|
|
1485
1449
|
# Validate URL
|
|
1486
1450
|
if not _validate_url(body.url):
|
|
@@ -1490,7 +1454,7 @@ async def create_job_from_url(
|
|
|
1490
1454
|
)
|
|
1491
1455
|
|
|
1492
1456
|
# Apply default distribution settings from environment if not provided
|
|
1493
|
-
dist =
|
|
1457
|
+
dist = get_effective_distribution_settings(
|
|
1494
1458
|
dropbox_path=body.dropbox_path,
|
|
1495
1459
|
gdrive_folder_id=body.gdrive_folder_id,
|
|
1496
1460
|
discord_webhook_url=body.discord_webhook_url,
|
|
@@ -1549,7 +1513,7 @@ async def create_job_from_url(
|
|
|
1549
1513
|
logger.info(f"Applying default theme: {effective_theme_id}")
|
|
1550
1514
|
|
|
1551
1515
|
# Resolve CDG/TXT defaults based on theme
|
|
1552
|
-
resolved_cdg, resolved_txt =
|
|
1516
|
+
resolved_cdg, resolved_txt = resolve_cdg_txt_defaults(
|
|
1553
1517
|
effective_theme_id, body.enable_cdg, body.enable_txt
|
|
1554
1518
|
)
|
|
1555
1519
|
|
|
@@ -1583,8 +1547,10 @@ async def create_job_from_url(
|
|
|
1583
1547
|
other_stems_models=body.other_stems_models,
|
|
1584
1548
|
request_metadata=request_metadata,
|
|
1585
1549
|
non_interactive=body.non_interactive,
|
|
1550
|
+
# Tenant scoping
|
|
1551
|
+
tenant_id=tenant_config.id if tenant_config else None,
|
|
1586
1552
|
)
|
|
1587
|
-
job = job_manager.create_job(job_create)
|
|
1553
|
+
job = job_manager.create_job(job_create, is_admin=auth_result.is_admin)
|
|
1588
1554
|
job_id = job.job_id
|
|
1589
1555
|
|
|
1590
1556
|
# Record job creation metric
|
|
@@ -1604,7 +1570,7 @@ async def create_job_from_url(
|
|
|
1604
1570
|
update_data['youtube_description_template'] = youtube_desc
|
|
1605
1571
|
job_manager.update_job(job_id, update_data)
|
|
1606
1572
|
logger.info(f"Applied theme '{effective_theme_id}' to job {job_id}")
|
|
1607
|
-
|
|
1573
|
+
|
|
1608
1574
|
logger.info(f"Created URL-based job {job_id} for URL: {body.url}")
|
|
1609
1575
|
if artist:
|
|
1610
1576
|
logger.info(f" Artist: {artist}")
|
|
@@ -1703,6 +1669,14 @@ async def create_finalise_only_job(
|
|
|
1703
1669
|
|
|
1704
1670
|
The endpoint returns signed URLs for uploading all the prep files.
|
|
1705
1671
|
"""
|
|
1672
|
+
# Check tenant feature flag - finalise-only requires file upload capability
|
|
1673
|
+
tenant_config = get_tenant_config_from_request(request)
|
|
1674
|
+
if tenant_config and not tenant_config.features.file_upload:
|
|
1675
|
+
raise HTTPException(
|
|
1676
|
+
status_code=403,
|
|
1677
|
+
detail="File upload is not available for this portal"
|
|
1678
|
+
)
|
|
1679
|
+
|
|
1706
1680
|
try:
|
|
1707
1681
|
# Validate files list
|
|
1708
1682
|
if not body.files:
|
|
@@ -1750,7 +1724,7 @@ async def create_finalise_only_job(
|
|
|
1750
1724
|
)
|
|
1751
1725
|
|
|
1752
1726
|
# Apply default distribution settings
|
|
1753
|
-
dist =
|
|
1727
|
+
dist = get_effective_distribution_settings(
|
|
1754
1728
|
dropbox_path=body.dropbox_path,
|
|
1755
1729
|
gdrive_folder_id=body.gdrive_folder_id,
|
|
1756
1730
|
discord_webhook_url=body.discord_webhook_url,
|
|
@@ -1805,7 +1779,7 @@ async def create_finalise_only_job(
|
|
|
1805
1779
|
logger.info(f"Applying default theme: {effective_theme_id}")
|
|
1806
1780
|
|
|
1807
1781
|
# Resolve CDG/TXT defaults based on theme
|
|
1808
|
-
resolved_cdg, resolved_txt =
|
|
1782
|
+
resolved_cdg, resolved_txt = resolve_cdg_txt_defaults(
|
|
1809
1783
|
effective_theme_id, body.enable_cdg, body.enable_txt
|
|
1810
1784
|
)
|
|
1811
1785
|
|
|
@@ -1834,8 +1808,10 @@ async def create_finalise_only_job(
|
|
|
1834
1808
|
finalise_only=True,
|
|
1835
1809
|
keep_brand_code=body.keep_brand_code,
|
|
1836
1810
|
request_metadata=request_metadata,
|
|
1811
|
+
# Tenant scoping
|
|
1812
|
+
tenant_id=tenant_config.id if tenant_config else None,
|
|
1837
1813
|
)
|
|
1838
|
-
job = job_manager.create_job(job_create)
|
|
1814
|
+
job = job_manager.create_job(job_create, is_admin=auth_result.is_admin)
|
|
1839
1815
|
job_id = job.job_id
|
|
1840
1816
|
|
|
1841
1817
|
# Record job creation metric
|
backend/api/routes/internal.py
CHANGED
|
@@ -371,6 +371,12 @@ async def check_idle_reminder(
|
|
|
371
371
|
add_span_event("already_sent")
|
|
372
372
|
return {"status": "already_sent", "job_id": job_id, "message": "Reminder already sent"}
|
|
373
373
|
|
|
374
|
+
# Skip reminders for made-for-you jobs (admin handles these directly, no intermediate customer emails)
|
|
375
|
+
if getattr(job, 'made_for_you', False):
|
|
376
|
+
logger.info(f"[job:{job_id}] Made-for-you job, skipping customer reminder (admin handles)")
|
|
377
|
+
add_span_event("made_for_you_skip")
|
|
378
|
+
return {"status": "skipped", "job_id": job_id, "message": "Made-for-you job - admin handles directly"}
|
|
379
|
+
|
|
374
380
|
# Check if user has an email
|
|
375
381
|
if not job.user_email:
|
|
376
382
|
logger.warning(f"[job:{job_id}] No user email, cannot send reminder")
|
backend/api/routes/jobs.py
CHANGED
|
@@ -11,7 +11,7 @@ import asyncio
|
|
|
11
11
|
import logging
|
|
12
12
|
import httpx
|
|
13
13
|
from typing import List, Optional, Dict, Any, Tuple
|
|
14
|
-
from fastapi import APIRouter, HTTPException, BackgroundTasks, Depends
|
|
14
|
+
from fastapi import APIRouter, HTTPException, BackgroundTasks, Depends, Request
|
|
15
15
|
|
|
16
16
|
from backend.models.job import Job, JobCreate, JobResponse, JobStatus
|
|
17
17
|
from backend.models.requests import (
|
|
@@ -30,6 +30,7 @@ from backend.config import get_settings
|
|
|
30
30
|
from backend.api.dependencies import require_admin, require_auth, require_instrumental_auth
|
|
31
31
|
from backend.services.auth_service import UserType, AuthResult
|
|
32
32
|
from backend.services.metrics import metrics
|
|
33
|
+
from backend.middleware.tenant import get_tenant_from_request
|
|
33
34
|
from backend.utils.test_data import is_test_email
|
|
34
35
|
|
|
35
36
|
|
|
@@ -117,8 +118,8 @@ async def create_job(
|
|
|
117
118
|
webhook_url=request.webhook_url,
|
|
118
119
|
user_email=user_email
|
|
119
120
|
)
|
|
120
|
-
job = job_manager.create_job(job_create)
|
|
121
|
-
|
|
121
|
+
job = job_manager.create_job(job_create, is_admin=auth_result.is_admin)
|
|
122
|
+
|
|
122
123
|
# Record job creation metric
|
|
123
124
|
metrics.record_job_created(job.job_id, source="url")
|
|
124
125
|
|
|
@@ -190,6 +191,7 @@ def _check_job_ownership(job: Job, auth_result: AuthResult) -> bool:
|
|
|
190
191
|
|
|
191
192
|
@router.get("", response_model=List[Job])
|
|
192
193
|
async def list_jobs(
|
|
194
|
+
request: Request,
|
|
193
195
|
status: Optional[JobStatus] = None,
|
|
194
196
|
environment: Optional[str] = None,
|
|
195
197
|
client_id: Optional[str] = None,
|
|
@@ -203,6 +205,7 @@ async def list_jobs(
|
|
|
203
205
|
List jobs with optional filters.
|
|
204
206
|
|
|
205
207
|
Regular users only see their own jobs. Admins see all jobs.
|
|
208
|
+
Users on tenant portals only see jobs from their tenant.
|
|
206
209
|
|
|
207
210
|
Args:
|
|
208
211
|
status: Filter by job status (pending, complete, failed, etc.)
|
|
@@ -247,6 +250,10 @@ async def list_jobs(
|
|
|
247
250
|
logger.warning("Non-admin auth without user_email, returning empty job list")
|
|
248
251
|
return []
|
|
249
252
|
|
|
253
|
+
# Get tenant_id from request for portal scoping
|
|
254
|
+
# Tenant users only see jobs from their tenant
|
|
255
|
+
tenant_id = get_tenant_from_request(request)
|
|
256
|
+
|
|
250
257
|
jobs = job_manager.list_jobs(
|
|
251
258
|
status=status,
|
|
252
259
|
environment=environment,
|
|
@@ -254,6 +261,7 @@ async def list_jobs(
|
|
|
254
261
|
created_after=created_after_dt,
|
|
255
262
|
created_before=created_before_dt,
|
|
256
263
|
user_email=user_email_filter,
|
|
264
|
+
tenant_id=tenant_id,
|
|
257
265
|
limit=limit
|
|
258
266
|
)
|
|
259
267
|
|