karaoke-gen 0.101.0__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/audio_search.py +4 -32
- backend/api/routes/file_upload.py +18 -83
- backend/api/routes/jobs.py +2 -2
- backend/api/routes/rate_limits.py +428 -0
- backend/api/routes/users.py +79 -19
- backend/config.py +16 -0
- backend/exceptions.py +66 -0
- backend/main.py +25 -1
- backend/services/email_validation_service.py +646 -0
- backend/services/firestore_service.py +21 -0
- backend/services/job_defaults_service.py +113 -0
- backend/services/job_manager.py +41 -2
- backend/services/rate_limit_service.py +641 -0
- backend/tests/conftest.py +7 -1
- backend/tests/test_audio_search.py +12 -8
- backend/tests/test_email_validation_service.py +298 -0
- backend/tests/test_file_upload.py +8 -6
- backend/tests/test_made_for_you.py +6 -4
- backend/tests/test_rate_limit_service.py +396 -0
- backend/tests/test_rate_limits_api.py +392 -0
- backend/workers/video_worker_orchestrator.py +26 -0
- {karaoke_gen-0.101.0.dist-info → karaoke_gen-0.103.1.dist-info}/METADATA +1 -1
- {karaoke_gen-0.101.0.dist-info → karaoke_gen-0.103.1.dist-info}/RECORD +26 -18
- {karaoke_gen-0.101.0.dist-info → karaoke_gen-0.103.1.dist-info}/WHEEL +0 -0
- {karaoke_gen-0.101.0.dist-info → karaoke_gen-0.103.1.dist-info}/entry_points.txt +0 -0
- {karaoke_gen-0.101.0.dist-info → karaoke_gen-0.103.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Unit tests for rate limits admin API endpoints.
|
|
3
|
+
|
|
4
|
+
Tests the rate limit statistics, blocklist management, and user override endpoints.
|
|
5
|
+
"""
|
|
6
|
+
import pytest
|
|
7
|
+
from unittest.mock import Mock, patch, MagicMock
|
|
8
|
+
from fastapi.testclient import TestClient
|
|
9
|
+
from fastapi import FastAPI
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
|
|
12
|
+
from backend.api.routes.rate_limits import router
|
|
13
|
+
from backend.api.dependencies import require_admin
|
|
14
|
+
from backend.services.auth_service import AuthResult, UserType
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Create a test app with the rate_limits router
|
|
18
|
+
app = FastAPI()
|
|
19
|
+
app.include_router(router, prefix="/api")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_mock_admin():
|
|
23
|
+
"""Override for require_admin dependency."""
|
|
24
|
+
return AuthResult(
|
|
25
|
+
is_valid=True,
|
|
26
|
+
user_type=UserType.ADMIN,
|
|
27
|
+
remaining_uses=-1,
|
|
28
|
+
message="Admin access granted",
|
|
29
|
+
user_email="admin@example.com",
|
|
30
|
+
is_admin=True,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Override the require_admin dependency
|
|
35
|
+
app.dependency_overrides[require_admin] = get_mock_admin
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@pytest.fixture
|
|
39
|
+
def client():
|
|
40
|
+
"""Create a test client."""
|
|
41
|
+
return TestClient(app)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@pytest.fixture
|
|
45
|
+
def mock_settings():
|
|
46
|
+
"""Create mock settings."""
|
|
47
|
+
settings = Mock()
|
|
48
|
+
settings.enable_rate_limiting = True
|
|
49
|
+
settings.rate_limit_jobs_per_day = 5
|
|
50
|
+
settings.rate_limit_youtube_uploads_per_day = 10
|
|
51
|
+
settings.rate_limit_beta_ip_per_day = 1
|
|
52
|
+
return settings
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class TestGetRateLimitStats:
|
|
56
|
+
"""Tests for GET /api/admin/rate-limits/stats endpoint."""
|
|
57
|
+
|
|
58
|
+
def test_returns_stats(self, client, mock_settings):
|
|
59
|
+
"""Test successful stats retrieval."""
|
|
60
|
+
with patch('backend.api.routes.rate_limits.get_rate_limit_service') as mock_get_rls, \
|
|
61
|
+
patch('backend.api.routes.rate_limits.get_email_validation_service') as mock_get_evs, \
|
|
62
|
+
patch('backend.api.routes.rate_limits.settings', mock_settings):
|
|
63
|
+
|
|
64
|
+
# Setup rate limit service mock
|
|
65
|
+
mock_rls = Mock()
|
|
66
|
+
mock_rls.get_youtube_uploads_today.return_value = 3
|
|
67
|
+
mock_rls.get_all_overrides.return_value = {"user1@example.com": {}}
|
|
68
|
+
mock_get_rls.return_value = mock_rls
|
|
69
|
+
|
|
70
|
+
# Setup email validation service mock
|
|
71
|
+
mock_evs = Mock()
|
|
72
|
+
mock_evs.get_blocklist_stats.return_value = {
|
|
73
|
+
"disposable_domains_count": 100,
|
|
74
|
+
"blocked_emails_count": 5,
|
|
75
|
+
"blocked_ips_count": 2,
|
|
76
|
+
"default_disposable_domains_count": 130,
|
|
77
|
+
}
|
|
78
|
+
mock_get_evs.return_value = mock_evs
|
|
79
|
+
|
|
80
|
+
response = client.get(
|
|
81
|
+
"/api/admin/rate-limits/stats",
|
|
82
|
+
headers={"Authorization": "Bearer admin-token"}
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
assert response.status_code == 200
|
|
86
|
+
data = response.json()
|
|
87
|
+
assert data["jobs_per_day_limit"] == 5
|
|
88
|
+
assert data["youtube_uploads_per_day_limit"] == 10
|
|
89
|
+
assert data["youtube_uploads_today"] == 3
|
|
90
|
+
assert data["youtube_uploads_remaining"] == 7
|
|
91
|
+
assert data["disposable_domains_count"] == 100
|
|
92
|
+
assert data["total_overrides"] == 1
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class TestGetUserRateLimitStatus:
|
|
96
|
+
"""Tests for GET /api/admin/rate-limits/users/{email} endpoint."""
|
|
97
|
+
|
|
98
|
+
def test_returns_user_status(self, client, mock_settings):
|
|
99
|
+
"""Test successful user status retrieval."""
|
|
100
|
+
with patch('backend.api.routes.rate_limits.get_rate_limit_service') as mock_get_rls, \
|
|
101
|
+
patch('backend.api.routes.rate_limits.settings', mock_settings):
|
|
102
|
+
|
|
103
|
+
mock_rls = Mock()
|
|
104
|
+
mock_rls.get_user_job_count_today.return_value = 2
|
|
105
|
+
mock_rls.get_user_override.return_value = None
|
|
106
|
+
mock_get_rls.return_value = mock_rls
|
|
107
|
+
|
|
108
|
+
response = client.get(
|
|
109
|
+
"/api/admin/rate-limits/users/user@example.com",
|
|
110
|
+
headers={"Authorization": "Bearer admin-token"}
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
assert response.status_code == 200
|
|
114
|
+
data = response.json()
|
|
115
|
+
assert data["email"] == "user@example.com"
|
|
116
|
+
assert data["jobs_today"] == 2
|
|
117
|
+
assert data["jobs_limit"] == 5
|
|
118
|
+
assert data["jobs_remaining"] == 3
|
|
119
|
+
assert data["has_bypass"] is False
|
|
120
|
+
|
|
121
|
+
def test_returns_user_with_bypass(self, client, mock_settings):
|
|
122
|
+
"""Test user with bypass override."""
|
|
123
|
+
with patch('backend.api.routes.rate_limits.get_rate_limit_service') as mock_get_rls, \
|
|
124
|
+
patch('backend.api.routes.rate_limits.settings', mock_settings):
|
|
125
|
+
|
|
126
|
+
mock_rls = Mock()
|
|
127
|
+
mock_rls.get_user_job_count_today.return_value = 10
|
|
128
|
+
mock_rls.get_user_override.return_value = {
|
|
129
|
+
"bypass_job_limit": True,
|
|
130
|
+
"reason": "VIP user"
|
|
131
|
+
}
|
|
132
|
+
mock_get_rls.return_value = mock_rls
|
|
133
|
+
|
|
134
|
+
response = client.get(
|
|
135
|
+
"/api/admin/rate-limits/users/vip@example.com",
|
|
136
|
+
headers={"Authorization": "Bearer admin-token"}
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
assert response.status_code == 200
|
|
140
|
+
data = response.json()
|
|
141
|
+
assert data["has_bypass"] is True
|
|
142
|
+
assert data["bypass_reason"] == "VIP user"
|
|
143
|
+
assert data["jobs_remaining"] == -1 # Unlimited
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class TestBlocklistEndpoints:
|
|
147
|
+
"""Tests for blocklist management endpoints."""
|
|
148
|
+
|
|
149
|
+
def test_get_blocklists(self, client):
|
|
150
|
+
"""Test getting all blocklists."""
|
|
151
|
+
with patch('backend.api.routes.rate_limits.get_email_validation_service') as mock_get_evs, \
|
|
152
|
+
patch('backend.services.firestore_service.get_firestore_client') as mock_get_db:
|
|
153
|
+
|
|
154
|
+
mock_evs = Mock()
|
|
155
|
+
mock_evs.get_blocklist_config.return_value = {
|
|
156
|
+
"disposable_domains": {"tempmail.com", "mailinator.com"},
|
|
157
|
+
"blocked_emails": {"spammer@example.com"},
|
|
158
|
+
"blocked_ips": {"192.168.1.100"},
|
|
159
|
+
}
|
|
160
|
+
mock_get_evs.return_value = mock_evs
|
|
161
|
+
|
|
162
|
+
# Mock Firestore for metadata
|
|
163
|
+
mock_db = Mock()
|
|
164
|
+
mock_doc = Mock()
|
|
165
|
+
mock_doc.exists = True
|
|
166
|
+
mock_doc.to_dict.return_value = {
|
|
167
|
+
"updated_at": datetime.now(timezone.utc),
|
|
168
|
+
"updated_by": "admin@example.com"
|
|
169
|
+
}
|
|
170
|
+
mock_db.collection.return_value.document.return_value.get.return_value = mock_doc
|
|
171
|
+
mock_get_db.return_value = mock_db
|
|
172
|
+
|
|
173
|
+
response = client.get(
|
|
174
|
+
"/api/admin/rate-limits/blocklists",
|
|
175
|
+
headers={"Authorization": "Bearer admin-token"}
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
assert response.status_code == 200
|
|
179
|
+
data = response.json()
|
|
180
|
+
assert "tempmail.com" in data["disposable_domains"]
|
|
181
|
+
assert "spammer@example.com" in data["blocked_emails"]
|
|
182
|
+
|
|
183
|
+
def test_add_disposable_domain(self, client):
|
|
184
|
+
"""Test adding a disposable domain."""
|
|
185
|
+
with patch('backend.api.routes.rate_limits.get_email_validation_service') as mock_get_evs:
|
|
186
|
+
|
|
187
|
+
mock_evs = Mock()
|
|
188
|
+
mock_evs.add_disposable_domain.return_value = True
|
|
189
|
+
mock_get_evs.return_value = mock_evs
|
|
190
|
+
|
|
191
|
+
response = client.post(
|
|
192
|
+
"/api/admin/rate-limits/blocklists/disposable-domains",
|
|
193
|
+
json={"domain": "newtemp.com"},
|
|
194
|
+
headers={"Authorization": "Bearer admin-token"}
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
assert response.status_code == 200
|
|
198
|
+
data = response.json()
|
|
199
|
+
assert data["success"] is True
|
|
200
|
+
mock_evs.add_disposable_domain.assert_called_once_with("newtemp.com", "admin@example.com")
|
|
201
|
+
|
|
202
|
+
def test_add_disposable_domain_invalid(self, client):
|
|
203
|
+
"""Test adding invalid domain."""
|
|
204
|
+
response = client.post(
|
|
205
|
+
"/api/admin/rate-limits/blocklists/disposable-domains",
|
|
206
|
+
json={"domain": "invalid"}, # No dot
|
|
207
|
+
headers={"Authorization": "Bearer admin-token"}
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
assert response.status_code == 400
|
|
211
|
+
|
|
212
|
+
def test_remove_disposable_domain(self, client):
|
|
213
|
+
"""Test removing a disposable domain."""
|
|
214
|
+
with patch('backend.api.routes.rate_limits.get_email_validation_service') as mock_get_evs:
|
|
215
|
+
|
|
216
|
+
mock_evs = Mock()
|
|
217
|
+
mock_evs.remove_disposable_domain.return_value = True
|
|
218
|
+
mock_get_evs.return_value = mock_evs
|
|
219
|
+
|
|
220
|
+
response = client.delete(
|
|
221
|
+
"/api/admin/rate-limits/blocklists/disposable-domains/tempmail.com",
|
|
222
|
+
headers={"Authorization": "Bearer admin-token"}
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
assert response.status_code == 200
|
|
226
|
+
mock_evs.remove_disposable_domain.assert_called_once()
|
|
227
|
+
|
|
228
|
+
def test_remove_disposable_domain_not_found(self, client):
|
|
229
|
+
"""Test removing non-existent domain."""
|
|
230
|
+
with patch('backend.api.routes.rate_limits.get_email_validation_service') as mock_get_evs:
|
|
231
|
+
|
|
232
|
+
mock_evs = Mock()
|
|
233
|
+
mock_evs.remove_disposable_domain.return_value = False
|
|
234
|
+
mock_get_evs.return_value = mock_evs
|
|
235
|
+
|
|
236
|
+
response = client.delete(
|
|
237
|
+
"/api/admin/rate-limits/blocklists/disposable-domains/notfound.com",
|
|
238
|
+
headers={"Authorization": "Bearer admin-token"}
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
assert response.status_code == 404
|
|
242
|
+
|
|
243
|
+
def test_add_blocked_email(self, client):
|
|
244
|
+
"""Test adding a blocked email."""
|
|
245
|
+
with patch('backend.api.routes.rate_limits.get_email_validation_service') as mock_get_evs:
|
|
246
|
+
|
|
247
|
+
mock_evs = Mock()
|
|
248
|
+
mock_evs.add_blocked_email.return_value = True
|
|
249
|
+
mock_get_evs.return_value = mock_evs
|
|
250
|
+
|
|
251
|
+
response = client.post(
|
|
252
|
+
"/api/admin/rate-limits/blocklists/blocked-emails",
|
|
253
|
+
json={"email": "spammer@example.com"},
|
|
254
|
+
headers={"Authorization": "Bearer admin-token"}
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
assert response.status_code == 200
|
|
258
|
+
|
|
259
|
+
def test_add_blocked_ip(self, client):
|
|
260
|
+
"""Test adding a blocked IP."""
|
|
261
|
+
with patch('backend.api.routes.rate_limits.get_email_validation_service') as mock_get_evs:
|
|
262
|
+
|
|
263
|
+
mock_evs = Mock()
|
|
264
|
+
mock_evs.add_blocked_ip.return_value = True
|
|
265
|
+
mock_get_evs.return_value = mock_evs
|
|
266
|
+
|
|
267
|
+
response = client.post(
|
|
268
|
+
"/api/admin/rate-limits/blocklists/blocked-ips",
|
|
269
|
+
json={"ip_address": "192.168.1.100"},
|
|
270
|
+
headers={"Authorization": "Bearer admin-token"}
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
assert response.status_code == 200
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class TestUserOverrideEndpoints:
|
|
277
|
+
"""Tests for user override management endpoints."""
|
|
278
|
+
|
|
279
|
+
def test_get_all_overrides(self, client):
|
|
280
|
+
"""Test getting all user overrides."""
|
|
281
|
+
with patch('backend.api.routes.rate_limits.get_rate_limit_service') as mock_get_rls:
|
|
282
|
+
|
|
283
|
+
mock_rls = Mock()
|
|
284
|
+
mock_rls.get_all_overrides.return_value = {
|
|
285
|
+
"vip@example.com": {
|
|
286
|
+
"bypass_job_limit": True,
|
|
287
|
+
"custom_daily_job_limit": None,
|
|
288
|
+
"reason": "VIP user",
|
|
289
|
+
"created_by": "admin@example.com",
|
|
290
|
+
"created_at": datetime.now(timezone.utc),
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
mock_get_rls.return_value = mock_rls
|
|
294
|
+
|
|
295
|
+
response = client.get(
|
|
296
|
+
"/api/admin/rate-limits/overrides",
|
|
297
|
+
headers={"Authorization": "Bearer admin-token"}
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
assert response.status_code == 200
|
|
301
|
+
data = response.json()
|
|
302
|
+
assert data["total"] == 1
|
|
303
|
+
assert len(data["overrides"]) == 1
|
|
304
|
+
assert data["overrides"][0]["email"] == "vip@example.com"
|
|
305
|
+
|
|
306
|
+
def test_set_user_override(self, client):
|
|
307
|
+
"""Test setting a user override."""
|
|
308
|
+
with patch('backend.api.routes.rate_limits.get_rate_limit_service') as mock_get_rls:
|
|
309
|
+
|
|
310
|
+
mock_rls = Mock()
|
|
311
|
+
mock_get_rls.return_value = mock_rls
|
|
312
|
+
|
|
313
|
+
response = client.put(
|
|
314
|
+
"/api/admin/rate-limits/overrides/user@example.com",
|
|
315
|
+
json={
|
|
316
|
+
"bypass_job_limit": True,
|
|
317
|
+
"reason": "Special access granted"
|
|
318
|
+
},
|
|
319
|
+
headers={"Authorization": "Bearer admin-token"}
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
assert response.status_code == 200
|
|
323
|
+
mock_rls.set_user_override.assert_called_once_with(
|
|
324
|
+
user_email="user@example.com",
|
|
325
|
+
bypass_job_limit=True,
|
|
326
|
+
custom_daily_job_limit=None,
|
|
327
|
+
reason="Special access granted",
|
|
328
|
+
admin_email="admin@example.com",
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
def test_set_user_override_with_custom_limit(self, client):
|
|
332
|
+
"""Test setting a user override with custom limit."""
|
|
333
|
+
with patch('backend.api.routes.rate_limits.get_rate_limit_service') as mock_get_rls:
|
|
334
|
+
|
|
335
|
+
mock_rls = Mock()
|
|
336
|
+
mock_get_rls.return_value = mock_rls
|
|
337
|
+
|
|
338
|
+
response = client.put(
|
|
339
|
+
"/api/admin/rate-limits/overrides/user@example.com",
|
|
340
|
+
json={
|
|
341
|
+
"bypass_job_limit": False,
|
|
342
|
+
"custom_daily_job_limit": 20,
|
|
343
|
+
"reason": "High volume user"
|
|
344
|
+
},
|
|
345
|
+
headers={"Authorization": "Bearer admin-token"}
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
assert response.status_code == 200
|
|
349
|
+
mock_rls.set_user_override.assert_called_once()
|
|
350
|
+
|
|
351
|
+
def test_set_user_override_missing_reason(self, client):
|
|
352
|
+
"""Test setting override without reason fails."""
|
|
353
|
+
response = client.put(
|
|
354
|
+
"/api/admin/rate-limits/overrides/user@example.com",
|
|
355
|
+
json={
|
|
356
|
+
"bypass_job_limit": True,
|
|
357
|
+
"reason": "ab" # Too short
|
|
358
|
+
},
|
|
359
|
+
headers={"Authorization": "Bearer admin-token"}
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
assert response.status_code == 400
|
|
363
|
+
|
|
364
|
+
def test_remove_user_override(self, client):
|
|
365
|
+
"""Test removing a user override."""
|
|
366
|
+
with patch('backend.api.routes.rate_limits.get_rate_limit_service') as mock_get_rls:
|
|
367
|
+
|
|
368
|
+
mock_rls = Mock()
|
|
369
|
+
mock_rls.remove_user_override.return_value = True
|
|
370
|
+
mock_get_rls.return_value = mock_rls
|
|
371
|
+
|
|
372
|
+
response = client.delete(
|
|
373
|
+
"/api/admin/rate-limits/overrides/user@example.com",
|
|
374
|
+
headers={"Authorization": "Bearer admin-token"}
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
assert response.status_code == 200
|
|
378
|
+
|
|
379
|
+
def test_remove_user_override_not_found(self, client):
|
|
380
|
+
"""Test removing non-existent override."""
|
|
381
|
+
with patch('backend.api.routes.rate_limits.get_rate_limit_service') as mock_get_rls:
|
|
382
|
+
|
|
383
|
+
mock_rls = Mock()
|
|
384
|
+
mock_rls.remove_user_override.return_value = False
|
|
385
|
+
mock_get_rls.return_value = mock_rls
|
|
386
|
+
|
|
387
|
+
response = client.delete(
|
|
388
|
+
"/api/admin/rate-limits/overrides/notfound@example.com",
|
|
389
|
+
headers={"Authorization": "Bearer admin-token"}
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
assert response.status_code == 404
|
|
@@ -483,6 +483,17 @@ class VideoWorkerOrchestrator:
|
|
|
483
483
|
"""Upload video to YouTube."""
|
|
484
484
|
self.job_log.info("Uploading to YouTube")
|
|
485
485
|
|
|
486
|
+
# Check YouTube upload rate limit (system-wide)
|
|
487
|
+
try:
|
|
488
|
+
from backend.services.rate_limit_service import get_rate_limit_service
|
|
489
|
+
rate_limit_service = get_rate_limit_service()
|
|
490
|
+
allowed, remaining, message = rate_limit_service.check_youtube_upload_limit()
|
|
491
|
+
if not allowed:
|
|
492
|
+
self.job_log.warning(f"YouTube upload skipped: {message}")
|
|
493
|
+
return
|
|
494
|
+
except Exception as e:
|
|
495
|
+
self.job_log.warning(f"Rate limit check failed, proceeding with upload: {e}")
|
|
496
|
+
|
|
486
497
|
# Find the best video file to upload (prefer MKV for FLAC audio, then lossless MP4)
|
|
487
498
|
video_to_upload = None
|
|
488
499
|
if self.result.final_video_mkv and os.path.isfile(self.result.final_video_mkv):
|
|
@@ -520,6 +531,21 @@ class VideoWorkerOrchestrator:
|
|
|
520
531
|
if video_url:
|
|
521
532
|
self.result.youtube_url = video_url
|
|
522
533
|
self.job_log.info(f"Uploaded to YouTube: {video_url}")
|
|
534
|
+
|
|
535
|
+
# Record the upload for rate limiting
|
|
536
|
+
try:
|
|
537
|
+
# Get user_email from job if available
|
|
538
|
+
user_email = "unknown"
|
|
539
|
+
if self.job_manager:
|
|
540
|
+
job = self.job_manager.get_job(self.config.job_id)
|
|
541
|
+
if job and job.user_email:
|
|
542
|
+
user_email = job.user_email
|
|
543
|
+
rate_limit_service.record_youtube_upload(
|
|
544
|
+
job_id=self.config.job_id,
|
|
545
|
+
user_email=user_email
|
|
546
|
+
)
|
|
547
|
+
except Exception as e:
|
|
548
|
+
self.job_log.warning(f"Failed to record YouTube upload for rate limiting: {e}")
|
|
523
549
|
else:
|
|
524
550
|
self.job_log.warning("YouTube upload did not return a URL")
|
|
525
551
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: karaoke-gen
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.103.1
|
|
4
4
|
Summary: Generate karaoke videos with synchronized lyrics. Handles the entire process from downloading audio and lyrics to creating the final video with title screens.
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -8,18 +8,20 @@ backend/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
8
8
|
backend/api/dependencies.py,sha256=-61nHBhiihUDSVMQd3VuHLP7uvKrUbm1y-j9RmV6_zc,16871
|
|
9
9
|
backend/api/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
10
|
backend/api/routes/admin.py,sha256=GI4ANzshBUfAzMdRH3M8cUBnzsCbrL1owRXmIY5HK8A,46506
|
|
11
|
-
backend/api/routes/audio_search.py,sha256=
|
|
11
|
+
backend/api/routes/audio_search.py,sha256=QxV3kIm_QYGFzrEdv0nrDIsC-X8ms-3ldOyvMwJ93RM,39267
|
|
12
12
|
backend/api/routes/auth.py,sha256=G1U2KwS3uqBJpgvg2PIe_mZOWCJKPFhhewrmKbW3Z_s,11531
|
|
13
|
-
backend/api/routes/file_upload.py,sha256=
|
|
13
|
+
backend/api/routes/file_upload.py,sha256=dGLWC7n3g9bUxwZ1UNxhW4gCEF-sogcIxYP0vByI-e4,93539
|
|
14
14
|
backend/api/routes/health.py,sha256=iZlhJpmz4vminUy_qrzkyk_1tgG1gmITz4jEWaCWJnM,14010
|
|
15
15
|
backend/api/routes/internal.py,sha256=yqzd5V8xGJQ_vTZL85vKIMwmBXf1MIp1nhder-kQEuA,15683
|
|
16
|
-
backend/api/routes/jobs.py,sha256=
|
|
16
|
+
backend/api/routes/jobs.py,sha256=FBf5_YojNMFamd51lOd3MDdevfOZefGTyMrtZ9Q8BSU,61241
|
|
17
|
+
backend/api/routes/rate_limits.py,sha256=lda7gwQdT9-U80XKsRiXkdYtB1HfpSfH5EskbB1kCQI,13806
|
|
17
18
|
backend/api/routes/review.py,sha256=BiaXZs-NXl7AVzGiwBzIhzm60fUslcQagSw4XuUByhU,28842
|
|
18
19
|
backend/api/routes/tenant.py,sha256=sM0WVXWGKJeyBNMbPDnAKOD9-SkJBmH4RkMXVWz4dlM,4038
|
|
19
20
|
backend/api/routes/themes.py,sha256=_fPZg9N2KN-yyr1YR2vAy8FIm8PqVowboQ4WEpFTYiQ,4721
|
|
20
|
-
backend/api/routes/users.py,sha256=
|
|
21
|
-
backend/config.py,sha256
|
|
22
|
-
backend/
|
|
21
|
+
backend/api/routes/users.py,sha256=9RSZyQPBF2Zx8rYMp6cJZoZ4zQ2vskcW90lFx4FSegA,55339
|
|
22
|
+
backend/config.py,sha256=Nb0-l0w59ZpWk_SXpLuqCm5ccUAU-ycEtENdv6wl74k,9293
|
|
23
|
+
backend/exceptions.py,sha256=tKu1YeAj0fmZsmGU_ZebHol-JqaGrGfauom8_DCrVys,1944
|
|
24
|
+
backend/main.py,sha256=C-eAFYUPsrO9W5y17AnVlIz_iRZiAz8XBQ4l-o1IiJY,6782
|
|
23
25
|
backend/middleware/__init__.py,sha256=usnVRHqfGW56bWJFqaIXz49pSW91uyciBfwO7ANCpIk,369
|
|
24
26
|
backend/middleware/audit_logging.py,sha256=oGdgbfH_M_3hIMAGrS5HpRvri2jjLxGFnFuA6IiU9BU,4170
|
|
25
27
|
backend/middleware/tenant.py,sha256=a-HEAhyvMUC4uFF6VptaNOO9O6YP64-ZoUitppl79PQ,6373
|
|
@@ -43,17 +45,19 @@ backend/services/credential_manager.py,sha256=VA--PwvL3gboJ21orQb7MIUiovX8n66Foh
|
|
|
43
45
|
backend/services/discord_service.py,sha256=LnEasOgaSRlmebDKsVT2yKCdWNDmskUToP3o2wPmdus,5082
|
|
44
46
|
backend/services/dropbox_service.py,sha256=R47MUKCqK2BEXXmQxMLG6zkPWw02htFoES9q9xiaYAw,10717
|
|
45
47
|
backend/services/email_service.py,sha256=gpufwXhNWmdElpWCmKrTLtm2LB8S-0Y5wcyJ3n1i6Jg,52868
|
|
48
|
+
backend/services/email_validation_service.py,sha256=xblVdzieoNTDbkB8MJLOgV_hPsHP_iwO0m36l42QmQw,19102
|
|
46
49
|
backend/services/encoding_interface.py,sha256=Y8hPskDyX9DOyAD557F3-SdHjymaBkGXcoC9E_OW7SE,15825
|
|
47
50
|
backend/services/encoding_service.py,sha256=XAX4hyL2wIHdpBCTc8yglcjVJboZxRVAjc07lUTxgFg,18025
|
|
48
|
-
backend/services/firestore_service.py,sha256
|
|
51
|
+
backend/services/firestore_service.py,sha256=SQWwIelerFOADRG_ZMVqk8zPPR0-utwWhGXwkjtaPIo,19416
|
|
49
52
|
backend/services/flacfetch_client.py,sha256=zj4VToKamR8bShXZifLvjDmUVa1HHvDhZuaw_b7g4lU,19901
|
|
50
53
|
backend/services/gce_encoding/README.md,sha256=frTFrp7-ugFAvUd_K9EgE4ud8Xu4Qf1V7BNirQn6qJk,2515
|
|
51
54
|
backend/services/gce_encoding/__init__.py,sha256=d2H6zM207NSc5afnfgkd3kwOXUu2gFBYjxBSS7tvX_s,571
|
|
52
55
|
backend/services/gce_encoding/main.py,sha256=2A0Bd3zYzbV66ikShB3avpqfNoDFBxr8Ja6c36qJhRI,22203
|
|
53
56
|
backend/services/gce_encoding/requirements.txt,sha256=-hW5ot-PnVoKkxrpY3yV6l68wx7AvzgnLYAWEgDGWNc,408
|
|
54
57
|
backend/services/gdrive_service.py,sha256=3JTXmPBuxpKopAbWDrh6YhqR5L9BLop1R7caXBnIk2A,13266
|
|
58
|
+
backend/services/job_defaults_service.py,sha256=GHWkBeEsx97K86ffAdkDvw8PunpyIFuT2_ezgEoGbBM,4446
|
|
55
59
|
backend/services/job_logging.py,sha256=theCkAlwMbqyqAbu7vCCRpVRscaIpkam3UrCs_4Yi1g,8052
|
|
56
|
-
backend/services/job_manager.py,sha256=
|
|
60
|
+
backend/services/job_manager.py,sha256=8kmGIsYUg40HO9CG_zxdLqpOgRZno39O55kAlSAeNL0,35192
|
|
57
61
|
backend/services/job_notification_service.py,sha256=ywGJUMUDqV23OSDsugOUufpXO6N_24bM9YVaPp8bOk4,9557
|
|
58
62
|
backend/services/langfuse_preloader.py,sha256=t-Rt_ThjZm4K8W3jlyLn2H3cEy7KNUlhrvOPmdg-gYM,3100
|
|
59
63
|
backend/services/local_encoding_service.py,sha256=Q6yK2-QL9tHM_pUBoxdyfT7ln2L5xMx82JB2nC5OfNQ,20680
|
|
@@ -62,6 +66,7 @@ backend/services/lyrics_cache_service.py,sha256=53EIZuDs5jIKr6QiOhIKJjE2mx1Jd1_x
|
|
|
62
66
|
backend/services/metrics.py,sha256=hlEeLG4jOR1i_QbWaNQthoko9CSpe-Zn_LHJ2huEFeY,12728
|
|
63
67
|
backend/services/nltk_preloader.py,sha256=TZFghQrpDTi6COcW_rkjL15SlzdRNvPmrdy690fq0hw,3579
|
|
64
68
|
backend/services/packaging_service.py,sha256=MUuBk0jtG3vGQhuOWbDv9u1uIMwbBC4NknLpJYFcWhs,10259
|
|
69
|
+
backend/services/rate_limit_service.py,sha256=KuLF9oWO44v-0zKYL3bFBwVq-_HejsQKbc0ojKJ1JSw,22907
|
|
65
70
|
backend/services/rclone_service.py,sha256=Q6wRAjlBiMMh9FBbbFRp4SHK1ljOQBed9zUJaJgAuhU,3646
|
|
66
71
|
backend/services/spacy_preloader.py,sha256=rpsMZB0CQWcoXjq8nyZCwJBkyjGheKaCFrfhxCOln5w,1836
|
|
67
72
|
backend/services/storage_service.py,sha256=gA7Snz1pihzYpRKZjEZx3eDwyMUBezl3ccLuBdpTwE4,8573
|
|
@@ -76,7 +81,7 @@ backend/services/worker_service.py,sha256=WRgh3kconWYmvraEIsx6eUto2frKacauKApFU1
|
|
|
76
81
|
backend/services/youtube_service.py,sha256=L2jqcWkw6qIaKh4wEfX894mHaTU0m5qtgIX6TPi6uiQ,3828
|
|
77
82
|
backend/services/youtube_upload_service.py,sha256=fvo4WxLQaKY_I75ViBMLk6vm87ym2ABHK0Fn_StYN6g,16694
|
|
78
83
|
backend/tests/__init__.py,sha256=jTjELMqIzpgHG9dcXwwkszTAAC4Wf-c93C6D1k-toZg,44
|
|
79
|
-
backend/tests/conftest.py,sha256=
|
|
84
|
+
backend/tests/conftest.py,sha256=hsUOHf3VZKX-fMorjjUY6gyTIYzdb9ICrdosYW9yAZI,8122
|
|
80
85
|
backend/tests/emulator/__init__.py,sha256=yOGPU5e0JPNYyUpy-u13jW4gEfWQs3dCJxfincptaCY,156
|
|
81
86
|
backend/tests/emulator/conftest.py,sha256=VXhoJ3NZg3uiLzSAsushQtozFS7Lt6lmjk7ym-k6V8o,3844
|
|
82
87
|
backend/tests/emulator/test_e2e_cli_backend.py,sha256=BTrWDv4sRTeOlheZ-d4R37tZsvJ2MhdFLbDb9tjbF08,41018
|
|
@@ -95,7 +100,7 @@ backend/tests/test_api_integration.py,sha256=ps4dFoW69NCKJAWvPSr410fEILPxfQNME_Y
|
|
|
95
100
|
backend/tests/test_api_routes.py,sha256=j6alBwXgtKspXxbOiDuNCCVMENk6yxdFJkakbife-rM,3193
|
|
96
101
|
backend/tests/test_audio_analysis_service.py,sha256=bKIAgyxfdbUOr7atHH-WCbZcZFkWGmGOOn7IfvRF2ms,11373
|
|
97
102
|
backend/tests/test_audio_editing_service.py,sha256=wXs82PRaxFQIji0CO1eKKYD2skHS4_VLK4aBnfj8CLE,15541
|
|
98
|
-
backend/tests/test_audio_search.py,sha256=
|
|
103
|
+
backend/tests/test_audio_search.py,sha256=VKVqh52to01AdQp1I1sAIkHIdhSOXgk82utc-oKdheg,54505
|
|
99
104
|
backend/tests/test_audio_services.py,sha256=mOF15i7IMtjM3mV-BNG0QBoMxW58cNPviEE6wka1pjQ,14607
|
|
100
105
|
backend/tests/test_auth_firestore.py,sha256=2etzuL47Pu-xOtR1CpCxi075OTFsftqneHa09yNJKEk,10063
|
|
101
106
|
backend/tests/test_config_extended.py,sha256=U2l9-wF28PmSllmRW5zNqRCNoYbBl3Nixb2SrmfGUOA,2475
|
|
@@ -105,9 +110,10 @@ backend/tests/test_discord_service.py,sha256=OU5mVzjukFtJd_H5e_HxJqSsf78B0TrNfjx
|
|
|
105
110
|
backend/tests/test_distribution_services.py,sha256=8oB2vMq5T_4rvOs6BURoLLTQiDf0kUJ-jF8dzJzDYRk,33699
|
|
106
111
|
backend/tests/test_dropbox_service.py,sha256=nDN1NjiCMS_f1bICWfe3maqbKT5FbhNuKCb1zjyjKq0,19198
|
|
107
112
|
backend/tests/test_email_service.py,sha256=u1VSTBCZl7m3GYR-vKV84tu3q7zhMuIhNA5a4OiCDlE,25290
|
|
113
|
+
backend/tests/test_email_validation_service.py,sha256=AGtusq5fUiNQz4gpiT8DXmroNPD5bcYij0R787NDooE,12472
|
|
108
114
|
backend/tests/test_emulator_integration.py,sha256=m4teGnKLBKQNe9UdFi0-0iR_unnotWs07VWgSd_atuw,10387
|
|
109
115
|
backend/tests/test_encoding_interface.py,sha256=7q9mKX4QG8-bfkxQgZkmeirK_ynfL88UTbgWS0f097U,14609
|
|
110
|
-
backend/tests/test_file_upload.py,sha256=
|
|
116
|
+
backend/tests/test_file_upload.py,sha256=seFF8viLNTEqiuChRolD5gIiMevhhkawvcXMFPcmCHw,68226
|
|
111
117
|
backend/tests/test_flacfetch_client.py,sha256=ZquMDLFVgORr9GoNfybOV9Z3eruborRgCvBRY6l-V8A,22614
|
|
112
118
|
backend/tests/test_gdrive_service.py,sha256=fh02KPaFqLEWfPaDQvSehn6r6RLq0bgt6MdXenF1kxA,19762
|
|
113
119
|
backend/tests/test_impersonation.py,sha256=vuCJaYmPOObLvFWuzxRvqadeRtFXgsbQxyOKd4TaxHg,9087
|
|
@@ -120,10 +126,12 @@ backend/tests/test_job_notification_service.py,sha256=YbXqCWbsK8O5WICItb3VCrEY8q
|
|
|
120
126
|
backend/tests/test_jobs_api.py,sha256=z3pGTawTpt75a1bQ5x4ynw3pTlNihLA6vqktTc6mBAU,11092
|
|
121
127
|
backend/tests/test_local_encoding_service.py,sha256=TbBaFOB-8Re09G-qXIQCl8mS9WlQMFNQF01V64IekyI,14638
|
|
122
128
|
backend/tests/test_local_preview_encoding_service.py,sha256=VwxpcAjDdYaVauSsrJ609U575VWRZbLsXew8BqBXpJY,20228
|
|
123
|
-
backend/tests/test_made_for_you.py,sha256=
|
|
129
|
+
backend/tests/test_made_for_you.py,sha256=Eanhp4v_dOgpQTxKQPHxHdLG4XiJ-kruVPWkR0iUnsg,87806
|
|
124
130
|
backend/tests/test_main.py,sha256=BvzcadLUV0SEUO1ViKm0djgy_TEKXm8PPvgfcCCEJAY,3348
|
|
125
131
|
backend/tests/test_models.py,sha256=buUjtm3TaiIff58SCpG0Du1Bnm-S9X8LyaW64zEhb4U,39606
|
|
126
132
|
backend/tests/test_packaging_service.py,sha256=v4Bj8R61Fx0Kng6RoTj3SRN9jmxow5qCVtTJstTWNfE,13916
|
|
133
|
+
backend/tests/test_rate_limit_service.py,sha256=1G-kskzv-5hcG_px78vZ9_J_X2Jj21s7l_wuUOWAOkY,16905
|
|
134
|
+
backend/tests/test_rate_limits_api.py,sha256=eFtdMzDMxhvI3MPhm60nRIWh-MVFCdpG0e-FwIVLn9I,14822
|
|
127
135
|
backend/tests/test_requests.py,sha256=DI2IG67hHk2rhuzUfz_QNJCLRTy1NZPLsnSdZbIfPA8,6817
|
|
128
136
|
backend/tests/test_routes_jobs.py,sha256=iqOZ93ACKpizSzLEJUx5hIo0vkKe2oIT7s7iZqQUJaE,11258
|
|
129
137
|
backend/tests/test_routes_review.py,sha256=ENo6_wFUDcyXZhRy_NBqajan9dvqV_7f1rIQ7LLLS-U,14334
|
|
@@ -159,7 +167,7 @@ backend/workers/render_video_worker.py,sha256=L6-VCxSBY6MbXBBm0-wu_DXxcIWKfW6ynT
|
|
|
159
167
|
backend/workers/screens_worker.py,sha256=vCS7j4dQRQ_tsWNhz-XH5nau7kGyUSzSp-dzyZpeJEo,21402
|
|
160
168
|
backend/workers/style_helper.py,sha256=7-PM79dAUAZHTj-bnATy43ImqNdl_00E9pm94CYg1BM,6906
|
|
161
169
|
backend/workers/video_worker.py,sha256=5r3PcYdoca16fjZtFzCwfKm-G7QjL98ZLMhw6qGVDNE,55670
|
|
162
|
-
backend/workers/video_worker_orchestrator.py,sha256=
|
|
170
|
+
backend/workers/video_worker_orchestrator.py,sha256=kmkEFUVlcWxuGXXwGmhV8A7NFfu4KfdO2LEhxSYc3RA,28779
|
|
163
171
|
backend/workers/worker_logging.py,sha256=nlbGsjDjdkv28Fm-95vKBumy6P47dGiNN5Zf3r0A480,9928
|
|
164
172
|
karaoke_gen/__init__.py,sha256=wHpDbURJxmJAMNZ0uQjISv5MIT7KD9RWYi15xlYgEhU,1351
|
|
165
173
|
karaoke_gen/audio_fetcher.py,sha256=EuMSO_wG0z0ldfQQ_xGC8kdoUCDB1Rr2_IuWIZ218lA,71380
|
|
@@ -450,8 +458,8 @@ lyrics_transcriber/transcribers/whisper.py,sha256=YcCB1ic9H6zL1GS0jD0emu8-qlcH0Q
|
|
|
450
458
|
lyrics_transcriber/types.py,sha256=UJjaxhVd2o14AG4G8ToU598p0JeYdiTFjpG38jGCoYQ,27917
|
|
451
459
|
lyrics_transcriber/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
452
460
|
lyrics_transcriber/utils/word_utils.py,sha256=-cMGpj9UV4F6IsoDKAV2i1aiqSO8eI91HMAm_igtVMk,958
|
|
453
|
-
karaoke_gen-0.
|
|
454
|
-
karaoke_gen-0.
|
|
455
|
-
karaoke_gen-0.
|
|
456
|
-
karaoke_gen-0.
|
|
457
|
-
karaoke_gen-0.
|
|
461
|
+
karaoke_gen-0.103.1.dist-info/METADATA,sha256=EMLjE5yyOq75ZCqVMPagcYppdJYukM-wmX9hgQe56Mg,23116
|
|
462
|
+
karaoke_gen-0.103.1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
463
|
+
karaoke_gen-0.103.1.dist-info/entry_points.txt,sha256=xIyLe7K84ZyjO8L0_AmNectz93QjGSs5AkApMtlAd4g,160
|
|
464
|
+
karaoke_gen-0.103.1.dist-info/licenses/LICENSE,sha256=81R_4XwMZDODHD7JcZeUR8IiCU8AD7Ajl6bmwR9tYDk,1074
|
|
465
|
+
karaoke_gen-0.103.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|