|
35 | 35 | EntityCandidate, |
36 | 36 | EntityMention, |
37 | 37 | IngestionRun, |
| 38 | + IntakeAllowlist, |
| 39 | + NewsletterIntake, |
38 | 40 | Project, |
39 | 41 | ProjectConfig, |
40 | 42 | ReviewQueue, |
41 | 43 | SkillResult, |
42 | 44 | SourceConfig, |
43 | 45 | TopicCentroidSnapshot, |
44 | 46 | UserFeedback, |
| 47 | + generate_project_intake_token, |
45 | 48 | ) |
46 | 49 | from core.serializers import ( |
| 50 | + BlueskyCredentialsSerializer, |
47 | 51 | ContentSerializer, |
48 | 52 | EntityAuthoritySnapshotSerializer, |
49 | 53 | EntityCandidateMergeSerializer, |
50 | 54 | EntityCandidateSerializer, |
51 | 55 | EntityMentionSummarySerializer, |
52 | 56 | EntitySerializer, |
53 | 57 | IngestionRunSerializer, |
| 58 | + IntakeAllowlistSerializer, |
| 59 | + NewsletterIntakeSerializer, |
54 | 60 | ProjectConfigSerializer, |
55 | 61 | ProjectSerializer, |
56 | 62 | ReviewQueueSerializer, |
|
116 | 122 | response_only=True, |
117 | 123 | ) |
118 | 124 |
|
| 125 | +BLUESKY_CREDENTIALS_RESPONSE_EXAMPLE = OpenApiExample( |
| 126 | + "Bluesky Credentials Response", |
| 127 | + value={ |
| 128 | + "id": 1, |
| 129 | + "project": 1, |
| 130 | + "handle": "aiweekly.bsky.social", |
| 131 | + "pds_url": "", |
| 132 | + "is_active": True, |
| 133 | + "has_stored_credential": True, |
| 134 | + "last_verified_at": "2026-04-26T13:00:00Z", |
| 135 | + "last_error": "", |
| 136 | + "created_at": "2026-04-26T12:30:00Z", |
| 137 | + "updated_at": "2026-04-26T13:00:00Z", |
| 138 | + }, |
| 139 | + response_only=True, |
| 140 | +) |
| 141 | + |
119 | 142 | SOURCE_CONFIG_CREATE_REQUEST_EXAMPLE = OpenApiExample( |
120 | 143 | "Create RSS Source Request", |
121 | 144 | value={ |
@@ -609,6 +632,26 @@ def get_queryset(self): |
609 | 632 |
|
610 | 633 | return self.queryset.filter(group__user=self.request.user).distinct() |
611 | 634 |
|
| 635 | + @extend_schema( |
| 636 | + summary="Rotate newsletter intake token", |
| 637 | + description=( |
| 638 | + "Generate a fresh project-specific newsletter intake token and return the " |
| 639 | + "updated project payload." |
| 640 | + ), |
| 641 | + tags=["Project Management"], |
| 642 | + request=None, |
| 643 | + responses={200: ProjectSerializer, 403: AUTHENTICATION_REQUIRED_RESPONSE}, |
| 644 | + ) |
| 645 | + @action(detail=True, methods=["post"], url_path="rotate-intake-token") |
| 646 | + def rotate_intake_token(self, request, *args, **kwargs): |
| 647 | + """Generate a fresh intake token for the selected project.""" |
| 648 | + |
| 649 | + project = self.get_object() |
| 650 | + project.intake_token = generate_project_intake_token() |
| 651 | + project.save(update_fields=["intake_token"]) |
| 652 | + serializer = self.get_serializer(project) |
| 653 | + return Response(serializer.data) |
| 654 | + |
612 | 655 | @extend_schema( |
613 | 656 | summary="Verify Bluesky credentials", |
614 | 657 | description=( |
@@ -1020,6 +1063,75 @@ class IngestionRunViewSet(ProjectOwnedQuerysetMixin, viewsets.ModelViewSet): |
1020 | 1063 | queryset = IngestionRun.objects.select_related("project") |
1021 | 1064 |
|
1022 | 1065 |
|
| 1066 | +@document_project_owned_viewset( |
| 1067 | + resource_plural="Bluesky credentials", |
| 1068 | + resource_singular="Bluesky credentials", |
| 1069 | + create_description=( |
| 1070 | + "Create Bluesky credentials for the selected project. The app password is " |
| 1071 | + "accepted write-only and is never returned in API responses." |
| 1072 | + ), |
| 1073 | + tag="Ingestion", |
| 1074 | + action_overrides=build_crud_action_overrides( |
| 1075 | + BlueskyCredentialsSerializer, |
| 1076 | + resource_plural="Bluesky credentials for the selected project", |
| 1077 | + resource_singular="Bluesky credentials", |
| 1078 | + retrieve_examples=[BLUESKY_CREDENTIALS_RESPONSE_EXAMPLE], |
| 1079 | + ), |
| 1080 | +) |
| 1081 | +class BlueskyCredentialsViewSet(ProjectOwnedQuerysetMixin, viewsets.ModelViewSet): |
| 1082 | + """Manage project-scoped Bluesky credentials.""" |
| 1083 | + |
| 1084 | + serializer_class = BlueskyCredentialsSerializer |
| 1085 | + queryset = BlueskyCredentials.objects.select_related("project") |
| 1086 | + |
| 1087 | + def get_queryset(self): |
| 1088 | + """Restrict credentials to the selected project and current user.""" |
| 1089 | + |
| 1090 | + return super().get_queryset().order_by("-updated_at") |
| 1091 | + |
| 1092 | + |
| 1093 | +@document_project_owned_viewset( |
| 1094 | + resource_plural="intake allowlist entries", |
| 1095 | + resource_singular="intake allowlist entry", |
| 1096 | + create_description=( |
| 1097 | + "Create a new confirmed or pending sender allowlist entry for the selected " |
| 1098 | + "project's newsletter intake workflow." |
| 1099 | + ), |
| 1100 | + tag="Ingestion", |
| 1101 | + action_overrides=build_crud_action_overrides( |
| 1102 | + IntakeAllowlistSerializer, |
| 1103 | + resource_plural="intake allowlist entries for the selected project", |
| 1104 | + resource_singular="intake allowlist entry", |
| 1105 | + ), |
| 1106 | +) |
| 1107 | +class IntakeAllowlistViewSet(ProjectOwnedQuerysetMixin, viewsets.ModelViewSet): |
| 1108 | + """Manage newsletter sender allowlist entries for a project.""" |
| 1109 | + |
| 1110 | + serializer_class = IntakeAllowlistSerializer |
| 1111 | + queryset = IntakeAllowlist.objects.select_related("project") |
| 1112 | + |
| 1113 | + |
| 1114 | +@document_project_owned_viewset( |
| 1115 | + resource_plural="newsletter intake entries", |
| 1116 | + resource_singular="newsletter intake entry", |
| 1117 | + create_description=( |
| 1118 | + "Newsletter intake entries are created by inbound email processing and are " |
| 1119 | + "exposed read-only for audit and troubleshooting." |
| 1120 | + ), |
| 1121 | + tag="Ingestion", |
| 1122 | + action_overrides=build_crud_action_overrides( |
| 1123 | + NewsletterIntakeSerializer, |
| 1124 | + resource_plural="newsletter intake entries for the selected project", |
| 1125 | + resource_singular="newsletter intake entry", |
| 1126 | + ), |
| 1127 | +) |
| 1128 | +class NewsletterIntakeViewSet(ProjectOwnedQuerysetMixin, viewsets.ReadOnlyModelViewSet): |
| 1129 | + """Inspect inbound newsletter history for a project.""" |
| 1130 | + |
| 1131 | + serializer_class = NewsletterIntakeSerializer |
| 1132 | + queryset = NewsletterIntake.objects.select_related("project") |
| 1133 | + |
| 1134 | + |
1023 | 1135 | @document_project_owned_viewset( |
1024 | 1136 | resource_plural="source configurations", |
1025 | 1137 | resource_singular="source configuration", |
|
0 commit comments