|
4 | 4 | from django.conf import settings |
5 | 5 | from django.urls import reverse |
6 | 6 |
|
7 | | -from mailing.models import CampaignStatus, EmailValidationStatus, RecipientListType, SubscriptionStatus |
| 7 | +from mailing.models import ( |
| 8 | + CampaignStatus, |
| 9 | + EmailValidationStatus, |
| 10 | + RecipientListImportJobStatus, |
| 11 | + RecipientListType, |
| 12 | + SubscriptionStatus, |
| 13 | +) |
8 | 14 |
|
9 | 15 | DEMO_API_KEYS = [ |
10 | 16 | { |
|
59 | 65 | "mailing:api_recipient_list_member": "/api/recipient-lists/{list_key}/members/{source_object_key}", |
60 | 66 | "mailing:api_recipient_list_bulk_upsert": "/api/recipient-lists/{list_key}/members/bulk-upsert", |
61 | 67 | "mailing:api_recipient_list_reconcile": "/api/recipient-lists/{list_key}/members/reconcile", |
| 68 | + "mailing:api_recipient_list_imports": "/api/recipient-lists/{list_key}/imports", |
| 69 | + "mailing:api_recipient_list_import": "/api/recipient-lists/{list_key}/imports/{job_id}", |
62 | 70 | "mailing:api_recipient_list_transactional_send": "/api/recipient-lists/{list_key}/transactional-send", |
63 | 71 | "mailing:api_transient_recipient_list_transactional_send": ( |
64 | 72 | "/api/transient-recipient-lists/transactional-send" |
@@ -804,6 +812,16 @@ def endpoint_groups(): |
804 | 812 | "/api/recipient-lists/{list_key}/members/reconcile", |
805 | 813 | "Replace list membership from a current source snapshot.", |
806 | 814 | ), |
| 815 | + ( |
| 816 | + "POST", |
| 817 | + "/api/recipient-lists/{list_key}/imports", |
| 818 | + "Create a pending JSONL import job from a fetchable URL.", |
| 819 | + ), |
| 820 | + ( |
| 821 | + "GET", |
| 822 | + "/api/recipient-lists/{list_key}/imports/{job_id}", |
| 823 | + "Get recipient-list import job status.", |
| 824 | + ), |
807 | 825 | ( |
808 | 826 | "POST", |
809 | 827 | "/api/recipient-lists/{list_key}/transactional-send", |
@@ -889,6 +907,14 @@ def route_path_map(): |
889 | 907 | "mailing:api_recipient_list_reconcile", |
890 | 908 | args=["ml-zoomcamp-2026"], |
891 | 909 | ), |
| 910 | + API_DOC_PATHS["mailing:api_recipient_list_imports"]: reverse( |
| 911 | + "mailing:api_recipient_list_imports", |
| 912 | + args=["ml-zoomcamp-2026"], |
| 913 | + ), |
| 914 | + API_DOC_PATHS["mailing:api_recipient_list_import"]: reverse( |
| 915 | + "mailing:api_recipient_list_import", |
| 916 | + args=["ml-zoomcamp-2026", 101], |
| 917 | + ), |
892 | 918 | API_DOC_PATHS["mailing:api_recipient_list_transactional_send"]: reverse( |
893 | 919 | "mailing:api_recipient_list_transactional_send", |
894 | 920 | args=["ml-zoomcamp-2026:@e:@homework:homework-1"], |
@@ -955,6 +981,7 @@ def bearer_responses(success, *, accepted=False): |
955 | 981 | MESSAGE_ID_PARAM = {"name": "message_id", "in": "path", "required": True, "schema": {"type": "integer"}} |
956 | 982 | TAG_SLUG_PARAM = {"name": "tag_slug", "in": "path", "required": True, "schema": {"type": "string"}} |
957 | 983 | LIST_KEY_PARAM = {"name": "list_key", "in": "path", "required": True, "schema": {"type": "string"}} |
| 984 | +JOB_ID_PARAM = {"name": "job_id", "in": "path", "required": True, "schema": {"type": "integer"}} |
958 | 985 | CAMPAIGN_EXTERNAL_KEY_PARAM = { |
959 | 986 | "name": "external_key", |
960 | 987 | "in": "path", |
@@ -1366,6 +1393,36 @@ def bearer_responses(success, *, accepted=False): |
1366 | 1393 | ), |
1367 | 1394 | } |
1368 | 1395 | }, |
| 1396 | + "/api/recipient-lists/{list_key}/imports": { |
| 1397 | + "post": { |
| 1398 | + "tags": ["Recipient Lists"], |
| 1399 | + "summary": "Create recipient list import job", |
| 1400 | + "description": "Creates a pending job that fetches JSONL member rows from a client-owned URL. Process the job asynchronously, then poll the job status before dependent sends.", |
| 1401 | + "security": [{"BearerAuth": []}], |
| 1402 | + "parameters": [LIST_KEY_PARAM], |
| 1403 | + "requestBody": json_body("#/components/schemas/RecipientListImportRequest"), |
| 1404 | + "responses": bearer_responses( |
| 1405 | + json_response("Recipient list import job", "#/components/schemas/RecipientListImportJobResponse"), |
| 1406 | + accepted=True, |
| 1407 | + ), |
| 1408 | + } |
| 1409 | + }, |
| 1410 | + "/api/recipient-lists/{list_key}/imports/{job_id}": { |
| 1411 | + "get": { |
| 1412 | + "tags": ["Recipient Lists"], |
| 1413 | + "summary": "Get recipient list import job", |
| 1414 | + "security": [{"BearerAuth": []}], |
| 1415 | + "parameters": [ |
| 1416 | + LIST_KEY_PARAM, |
| 1417 | + JOB_ID_PARAM, |
| 1418 | + {"name": "audience", "in": "query", "required": True, "schema": {"type": "string"}}, |
| 1419 | + {"name": "client", "in": "query", "required": True, "schema": {"type": "string"}}, |
| 1420 | + ], |
| 1421 | + "responses": bearer_responses( |
| 1422 | + json_response("Recipient list import job", "#/components/schemas/RecipientListImportJobResponse") |
| 1423 | + ), |
| 1424 | + } |
| 1425 | + }, |
1369 | 1426 | "/api/recipient-lists/{list_key}/transactional-send": { |
1370 | 1427 | "post": { |
1371 | 1428 | "tags": ["Recipient Lists"], |
@@ -1823,6 +1880,10 @@ def bearer_responses(success, *, accepted=False): |
1823 | 1880 | "properties": {"deleted_count": {"type": "integer"}}, |
1824 | 1881 | }, |
1825 | 1882 | "RecipientListType": {"type": "string", "enum": [choice.value for choice in RecipientListType]}, |
| 1883 | + "RecipientListImportJobStatus": { |
| 1884 | + "type": "string", |
| 1885 | + "enum": [choice.value for choice in RecipientListImportJobStatus], |
| 1886 | + }, |
1826 | 1887 | "RecipientListInput": { |
1827 | 1888 | "type": "object", |
1828 | 1889 | "properties": { |
@@ -1887,6 +1948,21 @@ def bearer_responses(success, *, accepted=False): |
1887 | 1948 | }, |
1888 | 1949 | ] |
1889 | 1950 | }, |
| 1951 | + "RecipientListImportRequest": { |
| 1952 | + "allOf": [ |
| 1953 | + {"$ref": "#/components/schemas/ScopedMutationRequest"}, |
| 1954 | + { |
| 1955 | + "type": "object", |
| 1956 | + "required": ["source_url"], |
| 1957 | + "properties": { |
| 1958 | + "source_url": {"type": "string", "format": "uri"}, |
| 1959 | + "idempotency_key": {"type": "string", "maxLength": 255}, |
| 1960 | + "list": {"$ref": "#/components/schemas/RecipientListInput"}, |
| 1961 | + "remove_absent": {"type": "boolean"}, |
| 1962 | + }, |
| 1963 | + }, |
| 1964 | + ] |
| 1965 | + }, |
1890 | 1966 | "RecipientListTransactionalSendRequest": { |
1891 | 1967 | "allOf": [ |
1892 | 1968 | {"$ref": "#/components/schemas/ScopedMutationRequest"}, |
@@ -2268,6 +2344,45 @@ def bearer_responses(success, *, accepted=False): |
2268 | 2344 | "removed_count": {"type": "integer"}, |
2269 | 2345 | }, |
2270 | 2346 | }, |
| 2347 | + "RecipientListImportJob": { |
| 2348 | + "type": "object", |
| 2349 | + "properties": { |
| 2350 | + "id": {"type": "integer"}, |
| 2351 | + "list_key": {"type": "string"}, |
| 2352 | + "audience": {"type": "string"}, |
| 2353 | + "client": {"type": "string"}, |
| 2354 | + "source_url": {"type": "string", "format": "uri"}, |
| 2355 | + "idempotency_key": {"type": "string"}, |
| 2356 | + "status": {"$ref": "#/components/schemas/RecipientListImportJobStatus"}, |
| 2357 | + "list": {"type": "object"}, |
| 2358 | + "remove_absent": {"type": "boolean"}, |
| 2359 | + "row_count": {"type": "integer"}, |
| 2360 | + "created_count": {"type": "integer"}, |
| 2361 | + "updated_count": {"type": "integer"}, |
| 2362 | + "removed_count": {"type": "integer"}, |
| 2363 | + "failed_count": {"type": "integer"}, |
| 2364 | + "failed_rows": {"type": "array", "items": {"type": "object"}}, |
| 2365 | + "content_sha256": {"type": "string"}, |
| 2366 | + "error": {"type": "string"}, |
| 2367 | + "started_at": {"type": ["string", "null"], "format": "date-time"}, |
| 2368 | + "completed_at": {"type": ["string", "null"], "format": "date-time"}, |
| 2369 | + "created_at": {"type": "string", "format": "date-time"}, |
| 2370 | + "updated_at": {"type": "string", "format": "date-time"}, |
| 2371 | + "recipient_list": { |
| 2372 | + "anyOf": [ |
| 2373 | + {"$ref": "#/components/schemas/RecipientList"}, |
| 2374 | + {"type": "null"}, |
| 2375 | + ] |
| 2376 | + }, |
| 2377 | + }, |
| 2378 | + }, |
| 2379 | + "RecipientListImportJobResponse": { |
| 2380 | + "type": "object", |
| 2381 | + "properties": { |
| 2382 | + "import_job": {"$ref": "#/components/schemas/RecipientListImportJob"}, |
| 2383 | + "created": {"type": "boolean"}, |
| 2384 | + }, |
| 2385 | + }, |
2271 | 2386 | "RecipientListTransactionalSendResponse": { |
2272 | 2387 | "type": "object", |
2273 | 2388 | "properties": { |
|
0 commit comments