diff --git a/README.md b/README.md index a8d7964..076be85 100644 --- a/README.md +++ b/README.md @@ -169,6 +169,44 @@ user key: `create_ai_service_user`, `list_ai_service_users`, Full tables and an MCP example: [docs/hub/account.md](./docs/hub/account.md), [docs/api/public.md](./docs/api/public.md). +## Campaigns and triggers: the provider integration id + +The server now **requires the provider integration id** when you create an +Email, Push or SMS campaign, and on every trigger action. Pick it from +`get_email_integrations` / `get_push_integrations` / `get_sms_integrations`. +Email and Push put it inside `campaign`; SMS sends it at the top level, next to +(and different from) `databaseIntegrationId`: + +```python +norbix.hub.notifications.create_email_campaign( + campaign={"templateId": "tpl_123", "integrationId": "int_email", "source": "allUsers"} +) +norbix.hub.notifications.create_push_campaign( + campaign={"source": "allUsers", "templateId": "tpl_456", "integrationId": "int_push"} +) +norbix.hub.notifications.create_sms_campaign( + templateId="tpl_789", + integrationId="int_sms", + deliveryType="PhoneNumbers", + phoneNumbers={"recipientsSourceType": "PhoneNumbers", "phoneNumbers": ["+37060000000"]}, +) +``` + +A trigger action (`save_schema_trigger`, `save_files_trigger`, +`save_membership_trigger`, `save_payments_trigger`) takes the same required +`integrationId`, plus the optional `language` (template language) and +`initiatorId` (who the send is attributed to) on Email, Push and SMS actions. +Two related calls: + +- `norbix.hub.triggers.get_triggers_needing_attention(triggerType="Schema")` — + triggers that need a fix (for example, their provider is gone); `{"items": [...]}`. +- `norbix.hub.account.check_project_languages(project_id, languages=["en", "de"])` — + a dry run before `update_project_languages`: which templates miss a language; + `{"templates": [...]}`. Account scope. + +Details: [docs/hub/triggers.md](./docs/hub/triggers.md), +[docs/hub/sms.md](./docs/hub/sms.md), [docs/hub/push.md](./docs/hub/push.md). + ## Errors ```python @@ -365,6 +403,10 @@ uv run python scripts/generate_endpoints.py This refreshes `src/norbix_python/api/`, `hub/`, matching tests under `tests/api` and `tests/hub`, and docs under `docs/`. +Some methods are **hand-written** and marked `HAND-WRITTEN` in the source +(`hub/triggers.py`, its registration in `hub/__init__.py`, and +`account.check_project_languages`). Keep them when you regenerate. + ## Development ```bash diff --git a/docs/hub/_index.md b/docs/hub/_index.md index f2425de..bac41c1 100644 --- a/docs/hub/_index.md +++ b/docs/hub/_index.md @@ -2,7 +2,7 @@ | Module | Endpoints | | --- | ---: | -| [`account`](./account.md) | 58 | +| [`account`](./account.md) | 59 | | [`ai`](./ai.md) | 14 | | [`database`](./database.md) | 41 | | [`echo`](./echo.md) | 1 | @@ -14,4 +14,5 @@ | [`notifications`](./notifications.md) | 117 | | [`payments`](./payments.md) | 16 | | [`scheduler`](./scheduler.md) | 8 | +| [`triggers`](./triggers.md) | 1 | | [`webhooks`](./webhooks.md) | 8 | diff --git a/docs/hub/account.md b/docs/hub/account.md index 513b20b..3bd004e 100644 --- a/docs/hub/account.md +++ b/docs/hub/account.md @@ -32,6 +32,7 @@ Access with `norbix.hub.account`. | `update_project_description` | `PATCH` | `/{version}/account/projects/{projectId}/settings/description` | `account` | | `disable_project` | `PATCH` | `/{version}/account/projects/{projectId}/disable` | `account` | | `enable_project` | `PATCH` | `/{version}/account/projects/{projectId}/enable` | `account` | +| `check_project_languages` | `POST` | `/{version}/account/projects/{projectId}/settings/languages/check` | `account` | | `update_project_languages` | `PATCH` | `/{version}/account/projects/{projectId}/settings/languages` | `account` | | `update_project_url` | `PATCH` | `/{version}/account/projects/{projectId}/settings/url` | `account` | | `update_project_name` | `PATCH` | `/{version}/account/projects/{projectId}/settings/name` | `account` | diff --git a/docs/hub/push.md b/docs/hub/push.md index b8d3c80..88566de 100644 --- a/docs/hub/push.md +++ b/docs/hub/push.md @@ -21,8 +21,9 @@ field plus the audience's own fields: | rows of a database collection | `collection` | `schemaName`, `fields` (the record fields that hold the recipient), `fieldType` (`User` or `Email`), optional `roleNames`, `languages` | | raw device tokens | `devices` | `devices`: a list of `{ token, deliveryFamily }`, `deliveryFamily` one of `Ios`, `Android`, `Chrome`, `Safari`, `Expo` | -Every target also takes `templateId` (required) and the optional `integrationId`, -`language`, `notes`, `campaignTime` (Unix seconds) and `mappedTokens`. Note the +Every target also takes `templateId` and `integrationId` (both required — +`integrationId` is the push provider, from `get_push_integrations`), and the +optional `language`, `notes`, `campaignTime` (Unix seconds) and `mappedTokens`. Note the spelling: `rolesNames` on `allUsers`, but `roleNames` on `collection` — the gateway names them differently. @@ -31,6 +32,7 @@ client.hub.notifications.create_push_campaign( campaign={ "source": "allUsers", "templateId": "tpl_123", + "integrationId": "int_push", "userTags": ["beta"], } ) diff --git a/docs/hub/sms.md b/docs/hub/sms.md index 11ac3fe..ded0fdb 100644 --- a/docs/hub/sms.md +++ b/docs/hub/sms.md @@ -25,13 +25,17 @@ and the settings object repeats the audience name in `recipientsSourceType`: Every settings object also takes `campaignTime` (Unix seconds, UTC — set a future time so the campaign can be reviewed in the dashboard first), and the optional `mappedTokens` and `respectTimeZoneSettings`. The request itself takes -`templateId` (required), and the optional `language` and `databaseIntegrationId`. +`templateId` and `integrationId` (both required — `integrationId` is the SMS +provider, from `get_sms_integrations`), and the optional `language` and +`databaseIntegrationId` (the database behind a `Collection` audience — a +different id from `integrationId`). Note the spelling: `rolesNames` on `allUsers`, but `roleNames` on `collection` — the gateway names them differently. ```python client.hub.notifications.create_sms_campaign( templateId="tpl_123", + integrationId="int_sms", deliveryType="PhoneNumbers", phoneNumbers={ "recipientsSourceType": "PhoneNumbers", diff --git a/docs/hub/triggers.md b/docs/hub/triggers.md new file mode 100644 index 0000000..29aa832 --- /dev/null +++ b/docs/hub/triggers.md @@ -0,0 +1,43 @@ +# HUB · Triggers + +Access with `norbix.hub.triggers`. This module is hand-written (not generated). + +| Method | Verb | Path | Scope | +| --- | --- | --- | --- | +| `get_triggers_needing_attention` | `GET` | `/{version}/triggers/attention` | `project` | + +Pass `triggerType` — one of `Membership`, `Schema`, `Files`, `Payments`, `Ai`. +The answer is `{"items": [{"triggerId", "triggerType", "reason", "atUtc"}, ...]}`: +the triggers of that type that need a fix, for example because their provider +integration was deleted. + +```python +norbix.hub.triggers.get_triggers_needing_attention(triggerType="Schema") +``` + +## The trigger action + +Each module saves its own triggers (`database.save_schema_trigger`, +`files.save_files_trigger`, `membership.save_membership_trigger`, +`payments.save_payments_trigger`). They all send `trigger.action`, and the +server requires `integrationId` on it — the provider that does the work. Email, +Push and SMS actions also take the optional `language` (which template language +to send) and `initiatorId` (who the send is attributed to): + +```python +norbix.hub.database.save_schema_trigger( + trigger={ + "name": "welcome", + "isEnabled": True, + "action": { + "type": "Email", + "integrationId": "int_email", + "templateId": "tpl_123", + "language": "de", + "initiatorId": "user_1", + }, + } +) +``` + +Everything here is asserted in `tests/hub/test_triggers.py`. diff --git a/references/hub_dtos.py b/references/hub_dtos.py index d82a768..962afb1 100644 --- a/references/hub_dtos.py +++ b/references/hub_dtos.py @@ -1,5 +1,5 @@ """ Options: -Date: 2026-09-28 20:36:25 +Date: 2026-10-04 14:16:09 Version: 10.20 Tip: To override a DTO option, remove "#" prefix before updating BaseUrl: http://localhost:5001 @@ -211,6 +211,7 @@ class TriggerType(str, Enum): SCHEMA = 'Schema' FILES = 'Files' PAYMENTS = 'Payments' + AI = 'Ai' class TriggerActionType(str, Enum): @@ -1027,6 +1028,13 @@ class LlmProvider(str, Enum): NORBIX_HOSTED = 'NorbixHosted' +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class LlmModelOptionRequest: + id: Optional[str] = None + display_name: Optional[str] = None + + @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class LlmIntegrationRequest: @@ -1036,6 +1044,8 @@ class LlmIntegrationRequest: is_enabled: bool = False endpoint: Optional[str] = None default_model: Optional[str] = None + is_default: bool = False + models: Optional[List[LlmModelOptionRequest]] = None @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -1353,6 +1363,8 @@ class EmailCampaignDeliverySettingsDto: class TriggerActionEmailDto(TriggerActionDto): template_id: Optional[str] = None delivery_settings: Optional[EmailCampaignDeliverySettingsDto] = None + language: Optional[str] = None + initiator_id: Optional[str] = None class PushCampaignRecipientsSourceTypes(str, Enum): @@ -1377,6 +1389,8 @@ class PushCampaignDeliverySettingsDto: class TriggerActionPushDto(TriggerActionDto): template_id: Optional[str] = None delivery_settings: Optional[PushCampaignDeliverySettingsDto] = None + language: Optional[str] = None + initiator_id: Optional[str] = None @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -1430,6 +1444,8 @@ class SmsCampaignDeliverySettingsDto: class TriggerActionSmsDto(TriggerActionDto): template_id: Optional[str] = None delivery_settings: Optional[SmsCampaignDeliverySettingsDto] = None + language: Optional[str] = None + initiator_id: Optional[str] = None @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -1604,11 +1620,20 @@ class EmailToCollectionRecordsDeliverySettingsDto(EmailCampaignDeliverySettingsD languages: Optional[List[str]] = None +class PushDeviceDeliveryFamily(str, Enum): + IOS = 'Ios' + ANDROID = 'Android' + CHROME = 'Chrome' + SAFARI = 'Safari' + EXPO = 'Expo' + + @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class PushToAllUsersDeliverySettingsDto(PushCampaignDeliverySettingsDto): roles_names: Optional[List[str]] = None user_tags: Optional[List[str]] = None + platforms: Optional[List[PushDeviceDeliveryFamily]] = None @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -1621,6 +1646,7 @@ class PushToUsersDeliverySettingsDto(PushCampaignDeliverySettingsDto): @dataclass class PushToAccountUsersDeliverySettingsDto(PushCampaignDeliverySettingsDto): recipients: List[str] = field(default_factory=list) + platforms: Optional[List[PushDeviceDeliveryFamily]] = None @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -1633,14 +1659,6 @@ class PushToCollectionRecordsDeliverySettingsDto(PushCampaignDeliverySettingsDto languages: Optional[List[str]] = None -class PushDeviceDeliveryFamily(str, Enum): - IOS = 'Ios' - ANDROID = 'Android' - CHROME = 'Chrome' - SAFARI = 'Safari' - EXPO = 'Expo' - - @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class PushDeviceDeliveryTokenDto: @@ -1697,6 +1715,15 @@ class IntegrationDto(IHasViewId): requires_human_delivery_confirmation: bool = False +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class LlmModelOptionDto: + id: Optional[str] = None + display_name: Optional[str] = None + input_credit_rate: Decimal = decimal.Decimal(0) + output_credit_rate: Decimal = decimal.Decimal(0) + + @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class LlmIntegrationDto(IntegrationDto): @@ -1705,6 +1732,8 @@ class LlmIntegrationDto(IntegrationDto): default_model: Optional[str] = None is_configured: bool = False is_system_owned: bool = False + is_default: bool = False + models: List[LlmModelOptionDto] = field(default_factory=list) @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -2459,6 +2488,20 @@ class SchedulerTaskDto: updated_at_unix: Optional[int] = None +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class SchedulerTaskRequest: + type: Optional[SchedulerTaskType] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class EmailCampaignSchedulerTaskRequest(SchedulerTaskRequest): + type: Optional[SchedulerTaskType] = None + campaign: Optional[EmailCampaignRequest] = None + database_integration_id: Optional[str] = None + + @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class MongoDbAggregateDto(IHasViewId): @@ -2693,6 +2736,11 @@ class AiChatEntrySourceWireDto: artifact_id: Optional[str] = None label: Optional[str] = None step: Optional[int] = None + number: Optional[int] = None + source_kind: Optional[str] = None + source_id: Optional[str] = None + score: Optional[float] = None + cited: Optional[bool] = None @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -2863,15 +2911,6 @@ class IHasCorrelationIdRequest: correlation_id: Optional[str] = None -class SubscriptionType(str, Enum): - MANAGED_SERVICE = 'ManagedService' - LICENSE = 'License' - - -class IHasAccountId: - account_id: Optional[str] = None - - @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class CodeMashRequestBase(RequestBase, IHasProjectId, IHasEnv): @@ -2897,6 +2936,15 @@ class IHasEnv: env: Optional[str] = None +class SubscriptionType(str, Enum): + MANAGED_SERVICE = 'ManagedService' + LICENSE = 'License' + + +class IHasAccountId: + account_id: Optional[str] = None + + @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class TagDescriptionDto: @@ -2940,6 +2988,127 @@ class TagDefinitionDto(TagDefinitionBaseDto): default_delivery: Dict[str, bool] = field(default_factory=dict) +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class AiPlanModelDto: + llm_integration_id: Optional[str] = None + model: Optional[str] = None + + +class AiPlanQuotaUnit(str, Enum): + NONE = 'None' + CREDITS = 'Credits' + TOKENS = 'Tokens' + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class AiPlanFeaturesDto: + attachments: bool = False + rag: bool = False + memory: bool = False + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class AiPlanDto: + id: Optional[str] = None + name: Optional[str] = None + allowed_assistant_ids: List[str] = field(default_factory=list) + allowed_models: List[AiPlanModelDto] = field(default_factory=list) + quota_unit: Optional[AiPlanQuotaUnit] = None + monthly_quota: int = 0 + features: Optional[AiPlanFeaturesDto] = None + quota_reached_message: Optional[str] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class AiPlanRoleAssignmentDto: + role_id: Optional[str] = None + plan_id: Optional[str] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class ProjectAiAssistantRequestBase(CodeMashRequestBase): + # @ApiMember(Description="Name shown to end users. Required, at most 100 characters, unique in the project.") + name: Optional[str] = None + """ + Name shown to end users. Required, at most 100 characters, unique in the project. + """ + + + # @ApiMember(Description="First message end users see. Public. At most 2 000 characters.") + welcome_message: Optional[str] = None + """ + First message end users see. Public. At most 2 000 characters. + """ + + + # @ApiMember(Description="Instructions for the model. Never shown to end users. At most 20 000 characters.") + system_prompt: Optional[str] = None + """ + Instructions for the model. Never shown to end users. At most 20 000 characters. + """ + + + # @ApiMember(Description="Own-scope toolsets the assistant may use: own:records (the user's own database records), own:profile (their profile), own:preferences (their marketing preferences), own:knowledge (search_knowledge — the user's own and the project-wide knowledge). Any other name is refused.") + toolsets: Optional[List[str]] = None + """ + Own-scope toolsets the assistant may use: own:records (the user's own database records), own:profile (their profile), own:preferences (their marketing preferences), own:knowledge (search_knowledge — the user's own and the project-wide knowledge). Any other name is refused. + """ + + + # @ApiMember(Description="The assistant's own LLM integration id (int_…). Empty = the project's default LLM.") + llm_integration_id: Optional[str] = None + """ + The assistant's own LLM integration id (int_…). Empty = the project's default LLM. + """ + + + # @ApiMember(Description="Model name. Empty = the integration's default model.") + model: Optional[str] = None + """ + Model name. Empty = the integration's default model. + """ + + + # @ApiMember(Description="True to let the assistant remember facts about the end user across chats.") + memory_enabled: bool = False + """ + True to let the assistant remember facts about the end user across chats. + """ + + + # @ApiMember(Description="Knowledge the assistant retrieves from before each answer and cites as [n]: record (records of schemas with embed on), file (uploaded files, when the project embeds them), message (the user's earlier chats, assistants with memory). Empty = no automatic retrieval.") + rag_source_ids: Optional[List[str]] = None + """ + Knowledge the assistant retrieves from before each answer and cites as [n]: record (records of schemas with embed on), file (uploaded files, when the project embeds them), message (the user's earlier chats, assistants with memory). Empty = no automatic retrieval. + """ + + + # @ApiMember(Description="Best relevance (0–1) below which the answer says the knowledge does not match strongly. Empty = 0.5; 0 = never.") + weak_match_threshold: Optional[float] = None + """ + Best relevance (0–1) below which the answer says the knowledge does not match strongly. Empty = 0.5; 0 = never. + """ + + + # @ApiMember(Description="AI plan (quota) id. Optional.") + plan_id: Optional[str] = None + """ + AI plan (quota) id. Optional. + """ + + + # @ApiMember(Description="True to make this the project's default assistant; the previous default stops being default.") + is_default: bool = False + """ + True to make this the project's default assistant; the previous default stops being default. + """ + + @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class CursorArgs(ICursorArgs): @@ -3017,6 +3186,15 @@ class SchemaSettingsDto: description: Optional[str] = None +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class SchemaEmbedSettingsDto: + enabled: bool = False + fields: List[str] = field(default_factory=list) + embedding_integration_id: Optional[str] = None + per_user: bool = False + + @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class SchemaListColumnDto: @@ -3046,6 +3224,30 @@ class ImportColumnMappingDto: dont_import_on_error: bool = False +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class AggregateId: + value: Optional[str] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class ProjectId(AggregateId, IHasDomainEntityId): + pass + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class IntegrationId(AggregateId, IHasDomainEntityId): + pass + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class TaxonomyId(AggregateId, IHasDomainEntityId): + pass + + class EmailValidationProvider(IntEnum): ZERO_BOUNCE = 1 NEVER_BOUNCE = 2 @@ -3243,10 +3445,22 @@ class ChatScreenContextDto: view_id: Optional[str] = None +class EmbeddingProvider(str, Enum): + VOYAGE = 'Voyage' + OPEN_A_I = 'OpenAI' + + @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass -class SchedulerTaskRequest: - type: Optional[SchedulerTaskType] = None +class EmbeddingIntegrationRequest: + integration_id: Optional[str] = None + provider: Optional[EmbeddingProvider] = None + integration_name: Optional[str] = None + is_enabled: bool = False + endpoint: Optional[str] = None + model: Optional[str] = None + dimension: int = 0 + api_key: Optional[str] = None class ResourceKindDto(str, Enum): @@ -3363,6 +3577,30 @@ class PublicAuthDto: password_policy: Optional[PublicPasswordPolicyDto] = None +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class PublicAiAssistantDto: + id: Optional[str] = None + name: Optional[str] = None + welcome: Optional[str] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class PublicAiChatDto: + enabled: bool = False + assistants: List[PublicAiAssistantDto] = field(default_factory=list) + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class TriggerAttentionDto: + trigger_id: Optional[str] = None + trigger_type: Optional[TriggerType] = None + reason: Optional[str] = None + at_utc: datetime.datetime = datetime.datetime(1, 1, 1) + + @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class AccountOwnerDto: @@ -3547,6 +3785,36 @@ class AuthenticationFlowSummaryDto: password_complexity: Optional[AuthenticationFlowPasswordPolicyDto] = None +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class AiAssistantDto: + id: Optional[str] = None + name: Optional[str] = None + welcome_message: Optional[str] = None + system_prompt: Optional[str] = None + toolsets: List[str] = field(default_factory=list) + llm_integration_id: Optional[str] = None + model: Optional[str] = None + memory_enabled: bool = False + rag_source_ids: List[str] = field(default_factory=list) + weak_match_threshold: Optional[float] = None + plan_id: Optional[str] = None + is_default: bool = False + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class ProjectAiSettingsDto: + enabled: bool = False + default_llm_integration_id: Optional[str] = None + default_model: Optional[str] = None + assistants: List[AiAssistantDto] = field(default_factory=list) + embed_files: bool = False + plans: List[AiPlanDto] = field(default_factory=list) + plan_role_assignments: List[AiPlanRoleAssignmentDto] = field(default_factory=list) + default_plan_id: Optional[str] = None + + @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class TriggerDto(IHasViewId): @@ -3587,6 +3855,7 @@ class EmailDto: @dataclass class AiDto: is_enabled: bool = False + default_integration_view_ids: Dict[str, str] = field(default_factory=dict) default_integration_view_id: Optional[str] = None @@ -3751,6 +4020,7 @@ class ProjectDto(IHasViewId, IBindableContract): admin_portal_service_user_id: Optional[str] = None membership_authentication_flows: Optional[List[AuthenticationFlowSummaryDto]] = None expose_legal_to_admin_portal: bool = False + ai_chat: Optional[ProjectAiSettingsDto] = None legal_terms_markdown: Optional[str] = None legal_privacy_markdown: Optional[str] = None environments: List[str] = field(default_factory=list) @@ -3794,53 +4064,164 @@ class ProjectListItemDto: additional_regions: Optional[List[ProjectRegionDto]] = None -TViewModelProjection = TypeVar('TViewModelProjection') +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class AiCreditPackCheckoutDto: + url: Optional[str] = None + session_id: Optional[str] = None + pack: Optional[str] = None + credits: int = 0 + price_euro_cents: int = 0 + purchase_id: Optional[str] = None @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass -class PaginatedResponse(Generic[TViewModelProjection]): - items: Optional[IList[TViewModelProjection]] = None - has_more: bool = False - has_previous: bool = False - starting_after: Optional[str] = None - ending_before: Optional[str] = None +class ProjectAiPlansDto: + plans: List[AiPlanDto] = field(default_factory=list) + roles: List[AiPlanRoleAssignmentDto] = field(default_factory=list) + default_plan_id: Optional[str] = None -class AuthType(str, Enum): - SERVICE = 'Service' - EMAIL = 'Email' - USER_NAME = 'UserName' - PHONE = 'Phone' - GUEST = 'Guest' - SOCIAL = 'Social' +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class AiUserPlanAssignmentDto: + user_id: Optional[str] = None + plan_id: Optional[str] = None @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass -class AccessInformationDto: - ip: Optional[str] = None - date: Optional[datetime.datetime] = None - time_zone: Optional[str] = None +class AiUserPlansDto: + users: List[AiUserPlanAssignmentDto] = field(default_factory=list) @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass -class RegistrationDto: - registration_information: Optional[AccessInformationDto] = None +class AiUsageGroupDto: + id: Optional[str] = None + llm_input_tokens: int = 0 + llm_output_tokens: int = 0 + embedding_tokens: int = 0 + rerank_calls: int = 0 + total_tokens: int = 0 + chargeable_tokens: int = 0 + chargeable_rerank_calls: int = 0 + credits: int = 0 @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass -class LoginDto: - need_change_password_on_next_login: bool = False - last_access_information: Optional[AccessInformationDto] = None +class AiWalletLineDto: + kind: Optional[str] = None + credits: int = 0 + reference: Optional[str] = None + purchased_balance: int = 0 + included_used: Optional[int] = None + purchased_used: Optional[int] = None + at_utc: datetime.datetime = datetime.datetime(1, 1, 1) -class Gender(str, Enum): - MALE = 'Male' - FEMALE = 'Female' - OTHER = 'Other' +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class ProjectAiWalletDto: + period: Optional[str] = None + included_credits: int = 0 + purchased_credits: int = 0 + consumed_credits: int = 0 + remaining_credits: int = 0 + status: Optional[str] = None + lines: List[AiWalletLineDto] = field(default_factory=list) + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class ProjectAiUsageDto: + period: Optional[str] = None + totals: Optional[AiUsageGroupDto] = None + assistants: List[AiUsageGroupDto] = field(default_factory=list) + top_users: List[AiUsageGroupDto] = field(default_factory=list) + models: List[AiUsageGroupDto] = field(default_factory=list) + wallet: Optional[ProjectAiWalletDto] = None + + +# @Flags() +class ApplicationModule(IntEnum): + ACCOUNT = 0 + MEMBERSHIP = 1 + DATABASE = 2 + FILES = 4 + CODE = 8 + EMAIL = 16 + PUSH = 32 + PAYMENT = 64 + SCHEDULER = 128 + LOGGING = 256 + SERVER_EVENTS = 512 + AI = 1024 + SMS = 2048 + PROJECT = 4096 + COMPLIANCE = 8192 + CONTACTS = 16384 + MARKETPLACE = 32768 + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class TemplateLanguageGapDto: + module: Optional[ApplicationModule] = None + template_id: Optional[str] = None + template_name: Optional[str] = None + missing_languages: List[str] = field(default_factory=list) + + +TViewModelProjection = TypeVar('TViewModelProjection') + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class PaginatedResponse(Generic[TViewModelProjection]): + items: Optional[IList[TViewModelProjection]] = None + has_more: bool = False + has_previous: bool = False + starting_after: Optional[str] = None + ending_before: Optional[str] = None + + +class AuthType(str, Enum): + SERVICE = 'Service' + EMAIL = 'Email' + USER_NAME = 'UserName' + PHONE = 'Phone' + GUEST = 'Guest' + SOCIAL = 'Social' + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class AccessInformationDto: + ip: Optional[str] = None + date: Optional[datetime.datetime] = None + time_zone: Optional[str] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class RegistrationDto: + registration_information: Optional[AccessInformationDto] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class LoginDto: + need_change_password_on_next_login: bool = False + last_access_information: Optional[AccessInformationDto] = None + + +class Gender(str, Enum): + MALE = 'Male' + FEMALE = 'Female' + OTHER = 'Other' class MarketingBlockReason(str, Enum): @@ -4346,6 +4727,7 @@ class SchemaDto(IHasViewId): visual_schema: Optional[VisualSchemaDto] = None published_at: datetime.datetime = datetime.datetime(1, 1, 1) settings: Optional[SchemaSettingsDto] = None + embed: Optional[SchemaEmbedSettingsDto] = None triggers: Optional[List[TriggerDto]] = None @@ -4641,6 +5023,14 @@ class EmailIntegrationListProjection(IntegrationListProjection): sender_display_name: Optional[str] = None +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class EmailLinkPreferencesDto: + email_address: Optional[str] = None + unsubscribed_from_marketing: bool = False + block_reasons: List[str] = field(default_factory=list) + + class CampaignStatus(str, Enum): PENDING = 'Pending' REGISTERED = 'Registered' @@ -4801,7 +5191,7 @@ class CampaignStatsDto: @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class SmsTemplateListProjection(TemplateListProjection): - pass + languages: List[str] = field(default_factory=list) @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -4907,7 +5297,7 @@ class CodeIntegrationListProjection(IntegrationListProjection): @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class PushTemplateListProjection(TemplateListProjection): - pass + languages: List[str] = field(default_factory=list) @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -4940,6 +5330,15 @@ class PushDeviceListProjection: platform_api_level: Optional[int] = None +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class PushAudienceCountDto: + devices: int = 0 + recipients: int = 0 + skipped_user_ids: int = 0 + is_capped: bool = False + + @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class PushCampaignDto(CampaignDto): @@ -5032,6 +5431,7 @@ class TenantLogEntryDto: class AgentOnboardingSnippet: client: Optional[str] = None config: Optional[str] = None + auth: Optional[str] = None @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -5089,6 +5489,66 @@ class ChatSessionListItem: is_pinned: bool = False +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class ScaffoldStep: + order: int = 0 + kind: Optional[str] = None + title: Optional[str] = None + tool: Optional[str] = None + arguments: Optional[JsonObject] = None + check_tool: Optional[str] = None + template_ref: Optional[str] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class ScaffoldIssue: + where: Optional[str] = None + code: Optional[str] = None + message: Optional[str] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class ScaffoldStepReportWithLink: + order: int = 0 + title: Optional[str] = None + tool: Optional[str] = None + status: Optional[str] = None + id: Optional[str] = None + dashboard_url: Optional[str] = None + note: Optional[str] = None + errors: Optional[IReadOnlyList[str]] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class ScaffoldApplyReport: + completed: bool = False + project_id: Optional[str] = None + project_url: Optional[str] = None + summary: Optional[str] = None + steps: List[ScaffoldStepReportWithLink] = field(default_factory=list) + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class TemplatePreviewPartResult: + name: Optional[str] = None + rendered: Optional[str] = None + errors: Optional[IReadOnlyList[str]] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class TemplatePreview: + channel: Optional[str] = None + ok: bool = False + parts: Optional[IReadOnlyList[TemplatePreviewPartResult]] = None + errors: Optional[IReadOnlyList[str]] = None + + @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class ProjectBriefSourceWireDto: @@ -5267,12 +5727,52 @@ class WorkItemWireDto: done_conditions: List[WorkItemDoneConditionWireDto] = field(default_factory=list) +class AiTriggerType(str, Enum): + ON_CREDITS_WARNING = 'OnCreditsWarning' + ON_CREDITS_EXHAUSTED = 'OnCreditsExhausted' + ON_QUOTA_WARNING = 'OnQuotaWarning' + ON_QUOTA_EXHAUSTED = 'OnQuotaExhausted' + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class AiTriggerProjectionList(TriggerProjectionList): + type: Optional[AiTriggerType] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class AiTriggerDto(TriggerDto): + when: Optional[AiTriggerType] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class EmbeddingIntegrationDto(IntegrationDto): + provider: Optional[EmbeddingProvider] = None + model: Optional[str] = None + dimension: int = 0 + base_url: Optional[str] = None + is_configured: bool = False + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class EmbeddingIntegrationListProjection(IntegrationListProjection): + embedding_provider: Optional[EmbeddingProvider] = None + model: Optional[str] = None + dimension: int = 0 + is_configured: bool = False + + @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class LlmIntegrationListProjection(IntegrationListProjection): llm_provider: Optional[LlmProvider] = None base_url: Optional[str] = None default_model: Optional[str] = None + is_default: bool = False + models: List[LlmModelOptionDto] = field(default_factory=list) @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -5808,6 +6308,10 @@ class IHasRazorTemplateCode: pass +class IHasDomainEntityId: + view_id: Optional[str] = None + + class IHasResponsibleUserId: user_id: Optional[str] = None @@ -5948,6 +6452,7 @@ class PublicProjectConfigDto: admin_portal_enabled: bool = False branding: Optional[PublicBrandDto] = None auth: Optional[PublicAuthDto] = None + ai_chat: Optional[PublicAiChatDto] = None @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -5959,6 +6464,12 @@ class PublicLegalDocumentDto: available: bool = False +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class GetTriggersNeedingAttentionResponse(ResponseBase): + items: List[TriggerAttentionDto] = field(default_factory=list) + + @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class GetAccountProfileResponse(ResponseBase): @@ -6049,6 +6560,48 @@ class AdminPortalStructureDto: modules: List[AdminPortalModuleDto] = field(default_factory=list) +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class CreateAiCreditPackCheckoutResponse(ResponseBase): + result: Optional[AiCreditPackCheckoutDto] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class GetProjectAiPlansResponse(ResponseBase): + result: Optional[ProjectAiPlansDto] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class UpdateProjectAiPlansResponse(ResponseBase): + plan_ids: List[str] = field(default_factory=list) + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class GetProjectAiUserPlansResponse(ResponseBase): + result: Optional[AiUserPlansDto] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class GetProjectAiSettingsResponse(ResponseBase): + result: Optional[ProjectAiSettingsDto] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class GetProjectAiUsageResponse(ResponseBase): + result: Optional[ProjectAiUsageDto] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class CheckProjectLanguagesResponse(ResponseBase): + templates: List[TemplateLanguageGapDto] = field(default_factory=list) + + @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class CreateAccountResponse(IdResponse): @@ -6623,6 +7176,12 @@ class GetEmailFootersResponse(ResponseBase): list: Optional[PaginatedResponse[ListItemWithTranslationsProjection]] = None +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class GetEmailPreferencesByLinkResponse(ResponseBase): + item: Optional[EmailLinkPreferencesDto] = None + + @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class GetEmailCampaignResponse(ResponseBase): @@ -6667,12 +7226,6 @@ class PreviewEmailNotificationResponse(ResponseBase): body: Optional[str] = None -@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) -@dataclass -class GetEmailCampaignMessageResponse(ResponseBase): - email_message_entity: Optional[EmailCampaignBatchNotificationDto] = None - - @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class GetEmailCampaignMessagesResponse(ResponseBase): @@ -6772,12 +7325,6 @@ class PreviewSmsNotificationResponse(ResponseBase): body: Optional[str] = None -@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) -@dataclass -class GetSmsCampaignMessageResponse(ResponseBase): - sms_message_entity: Optional[SmsCampaignBatchNotificationDto] = None - - @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class GetSmsCampaignMessagesResponse(ResponseBase): @@ -6941,6 +7488,12 @@ class GetPushDevicesResponse(ResponseBase): list: Optional[PaginatedResponse[PushDeviceListProjection]] = None +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class GetPushCampaignAudienceCountResponse(ResponseBase): + result: Optional[PushAudienceCountDto] = None + + @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class GetPushCampaignResponse(ResponseBase): @@ -6986,12 +7539,6 @@ class PreviewPushNotificationResponse(ResponseBase): subtitle: Optional[str] = None -@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) -@dataclass -class GetPushCampaignMessageResponse(ResponseBase): - push_message_entity: Optional[PushCampaignBatchNotificationDto] = None - - @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class GetPushCampaignMessagesResponse(ResponseBase): @@ -7158,6 +7705,32 @@ class ChatTurnResponse(ResponseBase): tool_trace: Optional[List[str]] = None +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class ScaffoldProjectResponse(ResponseBase): + mode: Optional[str] = None + env: Optional[str] = None + valid: bool = False + plan: Optional[List[ScaffoldStep]] = None + defaults: Optional[List[str]] = None + issues: Optional[List[ScaffoldIssue]] = None + report: Optional[ScaffoldApplyReport] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class ValidateSchemaResponse(ResponseBase): + valid: bool = False + issues: Optional[List[ScaffoldIssue]] = None + collections: Optional[List[str]] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class RenderTemplatePreviewResponse(ResponseBase): + preview: Optional[TemplatePreview] = None + + @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class GetProjectBriefResponse(ResponseBase): @@ -7189,6 +7762,38 @@ class ExportWorkItemResponse(ResponseBase): markdown: Optional[str] = None +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class GetAiTriggersResponse(GetTriggersResponse): + list: Optional[PaginatedResponse[AiTriggerProjectionList]] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class GetAiTriggerResponse(GetTriggerResponse): + trigger: Optional[AiTriggerDto] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class GetEmbeddingIntegrationResponse(ResponseBase): + item: Optional[EmbeddingIntegrationDto] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class GetEmbeddingIntegrationsResponse(ResponseBase): + list: Optional[PaginatedResponse[EmbeddingIntegrationListProjection]] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class TestEmbeddingIntegrationResponse(ResponseBase): + dimension: Optional[int] = None + latency_ms: Optional[int] = None + total_tokens: Optional[int] = None + + @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class GetLlmIntegrationResponse(ResponseBase): @@ -7969,6 +8574,7 @@ class InternalsTypeGen: typegen_192__webhook_integration_dto: Optional[WebhookIntegrationDto] = None typegen_193__webhook_destination_dto: Optional[WebhookDestinationDto] = None typegen_194__scheduler_task_dto: Optional[SchedulerTaskDto] = None + typegen_249__email_campaign_scheduler_task_request: Optional[EmailCampaignSchedulerTaskRequest] = None typegen_195__mongo_db_aggregate_dto: Optional[MongoDbAggregateDto] = None typegen_196__marketplace_integration_dto: Optional[MarketplaceIntegrationDto] = None typegen_197__marketplace_function_dto: Optional[MarketplaceFunctionDto] = None @@ -8027,6 +8633,15 @@ class Echo(RequestBase, IReturn[EchoResponse]): pass +# @Route("/{version}/public/projects/{ProjectId}/brand/{Kind}", "GET") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class GetPublicProjectBrandAsset(RequestBase, IReturn[bytes]): + project_id: Optional[str] = None + kind: Optional[str] = None + v: Optional[str] = None + + # @Route("/{version}/public/projects/{ProjectId}/config", "GET") @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass @@ -8042,6 +8657,22 @@ class GetPublicProjectLegal(RequestBase, IReturn[PublicLegalDocumentDto]): kind: Optional[str] = None +# @Route("/{version}/triggers/attention", "GET") +# @Api(Description="Get triggers that need attention") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class GetTriggersNeedingAttention(CodeMashRequestBase, IReturn[GetTriggersNeedingAttentionResponse]): + """ + Get triggers that need attention + """ + + # @ApiMember(Description="Which triggers: Membership, Schema, Files, Payments or Ai.", IsRequired=true) + trigger_type: Optional[TriggerType] = None + """ + Which triggers: Membership, Schema, Files, Payments or Ai. + """ + + # @Route("/{version}/account/profile", "GET") @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass @@ -8465,32 +9096,80 @@ class GetProjectTokens(CodeMashRequestBase, IReturn[GetProjectTokensResponse]): membership_trigger_new_user_id: Optional[str] = None -# @Route("/{version}/account/projects/{projectId}/settings/admin-portal/service-user", "PUT") -# @Api(Description="Assigns the project's Admin Portal service user") +# @Route("/{version}/account/projects/{projectId}/admin-portal/enabled", "PUT") +# @Api(Description="Turns the project's managed Admin Portal on or off") @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass -class AssignAdminPortalServiceUserRequest(CodeMashRequestBase, IReturn[EmptyResponse]): +class SetAdminPortalEnabledRequest(CodeMashRequestBase, IReturn[EmptyResponse]): """ - Assigns the project's Admin Portal service user + Turns the project's managed Admin Portal on or off """ - # @ApiMember(Description="Id of the existing AuthType.Service user to assign as the project's Admin Portal service user.", IsRequired=true) - service_user_id: Optional[str] = None + # @ApiMember(Description="true turns the managed Admin Portal on; false turns it off.", IsRequired=true) + enabled: bool = False """ - Id of the existing AuthType.Service user to assign as the project's Admin Portal service user. + true turns the managed Admin Portal on; false turns it off. """ -# @Route("/{version}/account/projects/{projectId}/admin-portal/structure", "GET") -# @Api(Description="Reads the Admin Portal layout/structure (service user only)") +# @Route("/{version}/account/projects/{projectId}/settings/brand/expose", "PATCH") +# @Api(Description="Sets whether the project's brand is returned by the public Admin Portal config") @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass -class GetAdminPortalStructure(CodeMashRequestBase, IReturn[AdminPortalStructureDto]): +class UpdateProjectExposeBrand(CodeMashRequestBase, IReturn[EmptyResponse]): """ - Reads the Admin Portal layout/structure (service user only) + Sets whether the project's brand is returned by the public Admin Portal config """ - pass + # @ApiMember(Description="True to return the brand in the public Admin Portal config, false to hide it.") + exposed: bool = False + """ + True to return the brand in the public Admin Portal config, false to hide it. + """ + + +# @Route("/{version}/account/projects/{projectId}/settings/auth/expose", "PATCH") +# @Api(Description="Sets whether the project's sign-in methods and password policy are returned by the public Admin Portal config") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class UpdateProjectExposeAuth(CodeMashRequestBase, IReturn[EmptyResponse]): + """ + Sets whether the project's sign-in methods and password policy are returned by the public Admin Portal config + """ + + # @ApiMember(Description="True to return sign-in methods and password policy in the public Admin Portal config, false to hide them.") + exposed: bool = False + """ + True to return sign-in methods and password policy in the public Admin Portal config, false to hide them. + """ + + +# @Route("/{version}/account/projects/{projectId}/settings/admin-portal/service-user", "PUT") +# @Api(Description="Assigns the project's Admin Portal service user") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class AssignAdminPortalServiceUserRequest(CodeMashRequestBase, IReturn[EmptyResponse]): + """ + Assigns the project's Admin Portal service user + """ + + # @ApiMember(Description="Id of the existing AuthType.Service user to assign as the project's Admin Portal service user.", IsRequired=true) + service_user_id: Optional[str] = None + """ + Id of the existing AuthType.Service user to assign as the project's Admin Portal service user. + """ + + +# @Route("/{version}/account/projects/{projectId}/admin-portal/structure", "GET") +# @Api(Description="Reads the Admin Portal layout/structure (service user only)") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class GetAdminPortalStructure(CodeMashRequestBase, IReturn[AdminPortalStructureDto]): + """ + Reads the Admin Portal layout/structure (service user only) + """ + + pass # @Route("/{version}/account/projects/{projectId}/settings/admin-url", "PATCH") @@ -8509,6 +9188,210 @@ class UpdateProjectAdminUrl(CodeMashRequestBase, IReturn[EmptyResponse]): """ +# @Route("/{version}/account/projects/{projectId}/ai/credits/checkout", "POST") +# @Api(Description="Creates a Stripe Checkout session for one AI credit pack of the project and returns its URL") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class CreateAiCreditPackCheckoutRequest(CodeMashRequestBase, IReturn[CreateAiCreditPackCheckoutResponse]): + """ + Creates a Stripe Checkout session for one AI credit pack of the project and returns its URL + """ + + # @ApiMember(Description="The pack to buy: pack-10 (€10 = 1 000 credits), pack-50 (€50 = 5 000), pack-200 (€200 = 20 000). Net prices; 1 credit = €0.01.", IsRequired=true) + pack: Optional[str] = None + """ + The pack to buy: pack-10 (€10 = 1 000 credits), pack-50 (€50 = 5 000), pack-200 (€200 = 20 000). Net prices; 1 credit = €0.01. + """ + + + # @ApiMember(Description="Dashboard path the browser returns to after Stripe (aiCredits=paid or aiCredits=cancelled is added). Default: the dashboard root.") + return_url: Optional[str] = None + """ + Dashboard path the browser returns to after Stripe (aiCredits=paid or aiCredits=cancelled is added). Default: the dashboard root. + """ + + +# @Route("/{version}/account/projects/{projectId}/ai/plans", "GET") +# @Api(Description="Reads the project's end-user AI plans, the role → plan map and the default plan") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class GetProjectAiPlans(CodeMashRequestBase, IReturn[GetProjectAiPlansResponse]): + """ + Reads the project's end-user AI plans, the role → plan map and the default plan + """ + + pass + + +# @Route("/{version}/account/projects/{projectId}/ai/plans", "PUT") +# @Api(Description="Saves the project's end-user AI plans (the whole list)") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class UpdateProjectAiPlans(CodeMashRequestBase, IReturn[UpdateProjectAiPlansResponse]): + """ + Saves the project's end-user AI plans (the whole list) + """ + + # @ApiMember(Description="The complete list of plans (full replace).") + plans: Optional[List[AiPlanDto]] = None + """ + The complete list of plans (full replace). + """ + + +# @Route("/{version}/account/projects/{projectId}/ai/plans/assignments", "PUT") +# @Api(Description="Saves which end-user AI plan each project role gets, and the default plan") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class UpdateProjectAiPlanAssignments(CodeMashRequestBase, IReturn[EmptyResponse]): + """ + Saves which end-user AI plan each project role gets, and the default plan + """ + + # @ApiMember(Description="Role → plan rows, in order; the first row whose role the user has wins.") + roles: Optional[List[AiPlanRoleAssignmentDto]] = None + """ + Role → plan rows, in order; the first row whose role the user has wins. + """ + + + # @ApiMember(Description="Plan id (aip_…) of users with no per-user plan and no mapped role. Empty = no default.") + default_plan_id: Optional[str] = None + """ + Plan id (aip_…) of users with no per-user plan and no mapped role. Empty = no default. + """ + + +# @Route("/{version}/account/projects/{projectId}/ai/plans/users", "GET") +# @Api(Description="Lists the users that have their own end-user AI plan") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class GetProjectAiUserPlans(CodeMashRequestBase, IReturn[GetProjectAiUserPlansResponse]): + """ + Lists the users that have their own end-user AI plan + """ + + pass + + +# @Route("/{version}/account/projects/{projectId}/ai/settings", "GET") +# @Api(Description="Reads the project's AI chat settings for end users: on/off, default LLM and assistants") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class GetProjectAiSettings(CodeMashRequestBase, IReturn[GetProjectAiSettingsResponse]): + """ + Reads the project's AI chat settings for end users: on/off, default LLM and assistants + """ + + pass + + +# @Route("/{version}/account/projects/{projectId}/ai/settings", "PUT") +# @Api(Description="Saves the project's AI chat settings: on/off and the default LLM") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class UpdateProjectAiSettings(CodeMashRequestBase, IReturn[EmptyResponse]): + """ + Saves the project's AI chat settings: on/off and the default LLM + """ + + # @ApiMember(Description="True to show end-user AI chat in the Admin Portal, false to hide it.") + enabled: bool = False + """ + True to show end-user AI chat in the Admin Portal, false to hide it. + """ + + + # @ApiMember(Description="Default LLM integration id (int_…) for assistants without their own. Empty clears it.") + default_llm_integration_id: Optional[str] = None + """ + Default LLM integration id (int_…) for assistants without their own. Empty clears it. + """ + + + # @ApiMember(Description="Model of the default LLM. Empty = the integration's default model.") + default_model: Optional[str] = None + """ + Model of the default LLM. Empty = the integration's default model. + """ + + +# @Route("/{version}/account/projects/{projectId}/ai/knowledge", "PUT") +# @Api(Description="Saves the project's AI knowledge switches: embed uploaded files") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class UpdateProjectAiKnowledge(CodeMashRequestBase, IReturn[EmptyResponse]): + """ + Saves the project's AI knowledge switches: embed uploaded files + """ + + # @ApiMember(Description="True to put uploaded text files into the project's AI knowledge, false to stop.") + embed_files: bool = False + """ + True to put uploaded text files into the project's AI knowledge, false to stop. + """ + + +# @Route("/{version}/account/projects/{projectId}/ai/assistants", "POST") +# @Api(Description="Adds an end-user AI assistant to the project") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class CreateProjectAiAssistant(ProjectAiAssistantRequestBase, IReturn[IdResponse]): + """ + Adds an end-user AI assistant to the project + """ + + pass + + +# @Route("/{version}/account/projects/{projectId}/ai/assistants/{assistantId}", "PUT") +# @Api(Description="Updates an end-user AI assistant of the project (full replace)") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class UpdateProjectAiAssistant(ProjectAiAssistantRequestBase, IReturn[EmptyResponse]): + """ + Updates an end-user AI assistant of the project (full replace) + """ + + # @ApiMember(Description="Id of the assistant (ast_…).", IsRequired=true) + assistant_id: Optional[str] = None + """ + Id of the assistant (ast_…). + """ + + +# @Route("/{version}/account/projects/{projectId}/ai/assistants/{assistantId}", "DELETE") +# @Api(Description="Removes an end-user AI assistant from the project") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class DeleteProjectAiAssistant(CodeMashRequestBase, IReturn[EmptyResponse]): + """ + Removes an end-user AI assistant from the project + """ + + # @ApiMember(Description="Id of the assistant (ast_…).", IsRequired=true) + assistant_id: Optional[str] = None + """ + Id of the assistant (ast_…). + """ + + +# @Route("/{version}/account/projects/{projectId}/ai/usage", "GET") +# @Api(Description="Reads the project's AI usage this month: totals, per assistant, top users, per model, and the credit wallet") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class GetProjectAiUsage(CodeMashRequestBase, IReturn[GetProjectAiUsageResponse]): + """ + Reads the project's AI usage this month: totals, per assistant, top users, per model, and the credit wallet + """ + + # @ApiMember(Description="How many users to list, biggest first. 1–100, default 10.") + top: Optional[int] = None + """ + How many users to list, biggest first. 1–100, default 10. + """ + + # @Route("/{version}/account/projects/{projectId}/settings/accent-color", "PATCH") # @Api(Description="Updates project accent color") @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -8660,6 +9543,29 @@ class UpdateProjectLanguages(CodeMashRequestBase, IReturn[EmptyResponse]): """ +# @Route("/{version}/account/projects/{projectId}/settings/languages/check", "POST") +# @Api(Description="Checks which templates miss a (proposed) project language") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class CheckProjectLanguages(CodeMashRequestBase, IReturn[CheckProjectLanguagesResponse]): + """ + Checks which templates miss a (proposed) project language + """ + + # @ApiMember(Description="Proposed default language code. Omit to use the current one.") + default_language: Optional[str] = None + """ + Proposed default language code. Omit to use the current one. + """ + + + # @ApiMember(Description="Proposed complete language list. Omit to use the current one.") + languages: Optional[List[str]] = None + """ + Proposed complete language list. Omit to use the current one. + """ + + # @Route("/{version}/account/projects/{projectId}/settings/legal", "PATCH") # @Api(Description="Updates the project's public legal documents (Terms & Conditions, Privacy Policy)") @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -10632,6 +11538,29 @@ class UpdateDatabaseSchemaDraftRequest(CodeMashRequestBase, IReturn[EmptyRespons """ +# @Route("/{version}/database/schemas/{Id}/embed", "PUT") +# @Api(Description="Saves a database schema's embed setting: which records go into the project's AI knowledge") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class UpdateDatabaseSchemaEmbedRequest(CodeMashRequestBase, IReturn[EmptyResponse]): + """ + Saves a database schema's embed setting: which records go into the project's AI knowledge + """ + + # @ApiMember(Description="Schema id whose embed setting to save, from get_database_schemas.", IsRequired=true) + id: Optional[str] = None + """ + Schema id whose embed setting to save, from get_database_schemas. + """ + + + # @ApiMember(Description="The complete embed setting (full replace): enabled, fields, embeddingIntegrationId (empty = the project's default), perUser.", IsRequired=true) + embed: Optional[SchemaEmbedSettingsDto] = None + """ + The complete embed setting (full replace): enabled, fields, embeddingIntegrationId (empty = the project's default), perUser. + """ + + # @Route("/{version}/database/schemas/{Id}/list-settings", "PUT") # @Api(Description="Updates database schema records-list display settings") @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -11560,6 +12489,47 @@ class ProcessCollectionImport: env: Optional[str] = None +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class TermInserted: + project_id: Optional[ProjectId] = None + database_integration_id: Optional[IntegrationId] = None + taxonomy_id: Optional[TaxonomyId] = None + id: Optional[str] = None + document: Optional[Object] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class TermUpdated: + project_id: Optional[ProjectId] = None + database_integration_id: Optional[IntegrationId] = None + taxonomy_id: Optional[TaxonomyId] = None + id: Optional[str] = None + from_: Optional[Object] = field(metadata=config(field_name='from'), default=None) + to: Optional[Object] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class TermDeleted: + project_id: Optional[ProjectId] = None + database_integration_id: Optional[IntegrationId] = None + taxonomy_id: Optional[TaxonomyId] = None + id: Optional[str] = None + document: Optional[Object] = None + + +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class TermsDeleted: + project_id: Optional[ProjectId] = None + database_integration_id: Optional[IntegrationId] = None + taxonomy_id: Optional[TaxonomyId] = None + deleted_count: int = 0 + filter: Optional[Object] = None + + # @Route("/{version}/files/disable", "GET") @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass @@ -12450,6 +13420,22 @@ class OneClickUnsubscribeRequest(RequestBase, IReturn[EmptyResponse]): """ +# @Route("/{version}/email/preferences", "GET") +# @Api(Description="Read the marketing e-mail preferences of the person a signed unsubscribe link belongs to. No sign-in: the link is the key.") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class GetEmailPreferencesByLinkRequest(RequestBase, IReturn[GetEmailPreferencesByLinkResponse]): + """ + Read the marketing e-mail preferences of the person a signed unsubscribe link belongs to. No sign-in: the link is the key. + """ + + # @ApiMember(Description="The signed unsubscribe link token from the e-mail's Preferences or Unsubscribe link.", IsRequired=true, Name="token", ParameterType="query") + token: Optional[str] = None + """ + The signed unsubscribe link token from the e-mail's Preferences or Unsubscribe link. + """ + + # @Route("/{version}/notifications/email/campaigns", "POST") # @Api(Description="Create email campaign") @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -12461,10 +13447,10 @@ class CreateEmailCampaignRequest(CodeMashRequestBase, IReturn[IdResponse]): campaign: Optional[EmailCampaignRequest] = None database_integration_id: Optional[str] = None - # @ApiMember(Description="Optional. true sends the campaign now (or at a campaignTime sooner than the review window). Without it, a campaign created by an AI tool must be scheduled at least 24 hours ahead for role-based / all-users / collection audiences and at least 1 hour ahead for direct recipient lists, or it is rejected with CM-ERRORS-CAMPAIGNS-016. Set it only when the user explicitly asked to send now.") + # @ApiMember(Description="Optional. true sends the campaign now (or at a campaignTime sooner than the review window). Without it, a campaign created by an AI tool must be scheduled at least 24 hours ahead for role-based / all-users / collection audiences and at least 1 hour ahead for direct recipient lists, or it is rejected with CM-ERRORS-CAMPAIGNS-017. Set it only when the user explicitly asked to send now.") send_now: Optional[bool] = None """ - Optional. true sends the campaign now (or at a campaignTime sooner than the review window). Without it, a campaign created by an AI tool must be scheduled at least 24 hours ahead for role-based / all-users / collection audiences and at least 1 hour ahead for direct recipient lists, or it is rejected with CM-ERRORS-CAMPAIGNS-016. Set it only when the user explicitly asked to send now. + Optional. true sends the campaign now (or at a campaignTime sooner than the review window). Without it, a campaign created by an AI tool must be scheduled at least 24 hours ahead for role-based / all-users / collection audiences and at least 1 hour ahead for direct recipient lists, or it is rejected with CM-ERRORS-CAMPAIGNS-017. Set it only when the user explicitly asked to send now. """ @@ -12735,43 +13721,6 @@ class StopEmailCampaignRequest(CodeMashRequestBase, IReturn[EmptyResponse]): """ -# @Route("/{version}/notifications/emails/campaigns/{campaignId}/messages/{notificationId}", "GET") -# @Api(Description="Get an email campaign message") -@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) -@dataclass -class GetEmailCampaignMessage(CodeMashRequestBase, IReturn[GetEmailCampaignMessageResponse]): - """ - Get an email campaign message - """ - - # @ApiMember(Description="The email campaign id. Get it from get_all_email_campaigns.", IsRequired=true) - campaign_id: Optional[str] = None - """ - The email campaign id. Get it from get_all_email_campaigns. - """ - - - # @ApiMember(Description="The campaign batch id. Get it from get_email_campaign_batches.", IsRequired=true) - campaign_batch_id: Optional[str] = None - """ - The campaign batch id. Get it from get_email_campaign_batches. - """ - - - # @ApiMember(Description="The notification (message) id to fetch. Get it from get_email_campaign_messages.", IsRequired=true) - notification_id: Optional[str] = None - """ - The notification (message) id to fetch. Get it from get_email_campaign_messages. - """ - - - # @ApiMember(Description="Optional. Omit to use the project default database integration (resolved per environment).") - database_integration_id: Optional[str] = None - """ - Optional. Omit to use the project default database integration (resolved per environment). - """ - - # @Route("/{version}/notifications/emails/campaigns/{campaignId}/messages", "GET") # @Api(Description="Get email campaign messages") @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -13192,8 +14141,15 @@ class CreateSmsCampaignRequest(CodeMashRequestBase, IReturn[IdResponse]): """ - # @ApiMember(Description="Optional language code forcing one template translation for every recipient.") - language: Optional[str] = None + # @ApiMember(Description="SMS provider integration id the campaign sends through — pick one with get_sms_integrations (the project default unless the user named another). Required; never invent it.", IsRequired=true) + integration_id: Optional[str] = None + """ + SMS provider integration id the campaign sends through — pick one with get_sms_integrations (the project default unless the user named another). Required; never invent it. + """ + + + # @ApiMember(Description="Optional language code forcing one template translation for every recipient.") + language: Optional[str] = None """ Optional language code forcing one template translation for every recipient. """ @@ -13229,10 +14185,10 @@ class CreateSmsCampaignRequest(CodeMashRequestBase, IReturn[IdResponse]): """ - # @ApiMember(Description="Optional. true sends the campaign now (or at a campaignTime sooner than the review window). Without it, a campaign created by an AI tool must be scheduled at least 24 hours ahead for role-based / all-users / collection audiences and at least 1 hour ahead for direct recipient lists, or it is rejected with CM-ERRORS-CAMPAIGNS-016. Set it only when the user explicitly asked to send now.") + # @ApiMember(Description="Optional. true sends the campaign now (or at a campaignTime sooner than the review window). Without it, a campaign created by an AI tool must be scheduled at least 24 hours ahead for role-based / all-users / collection audiences and at least 1 hour ahead for direct recipient lists, or it is rejected with CM-ERRORS-CAMPAIGNS-017. Set it only when the user explicitly asked to send now.") send_now: Optional[bool] = None """ - Optional. true sends the campaign now (or at a campaignTime sooner than the review window). Without it, a campaign created by an AI tool must be scheduled at least 24 hours ahead for role-based / all-users / collection audiences and at least 1 hour ahead for direct recipient lists, or it is rejected with CM-ERRORS-CAMPAIGNS-016. Set it only when the user explicitly asked to send now. + Optional. true sends the campaign now (or at a campaignTime sooner than the review window). Without it, a campaign created by an AI tool must be scheduled at least 24 hours ahead for role-based / all-users / collection audiences and at least 1 hour ahead for direct recipient lists, or it is rejected with CM-ERRORS-CAMPAIGNS-017. Set it only when the user explicitly asked to send now. """ @@ -13485,43 +14441,6 @@ class StopSmsCampaignRequest(CodeMashRequestBase, IReturn[EmptyResponse]): """ -# @Route("/{version}/notifications/sms/campaigns/{campaignId}/messages/{notificationId}", "GET") -# @Api(Description="Gets campaign sms message details") -@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) -@dataclass -class GetSmsCampaignMessage(CodeMashRequestBase, IReturn[GetSmsCampaignMessageResponse]): - """ - Gets campaign sms message details - """ - - # @ApiMember(Description="The campaign id. Get it from get_sms_campaigns.", IsRequired=true) - campaign_id: Optional[str] = None - """ - The campaign id. Get it from get_sms_campaigns. - """ - - - # @ApiMember(Description="The campaign batch id. Get it from get_sms_campaign_batches.", IsRequired=true) - campaign_batch_id: Optional[str] = None - """ - The campaign batch id. Get it from get_sms_campaign_batches. - """ - - - # @ApiMember(Description="The notification (message) id. Get it from get_sms_campaign_messages.", IsRequired=true) - notification_id: Optional[str] = None - """ - The notification (message) id. Get it from get_sms_campaign_messages. - """ - - - # @ApiMember(Description="Optional. Omit to use the project default database integration (resolved per environment).") - database_integration_id: Optional[str] = None - """ - Optional. Omit to use the project default database integration (resolved per environment). - """ - - # @Route("/{version}/notifications/sms/campaigns/{campaignId}/messages", "GET") # @Api(Description="Gets the sms notifications") @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -13969,10 +14888,10 @@ class TestPushIntegration(CodeMashRequestBase, IReturn[TestEmailIntegrationRespo """ - # @ApiMember(Description="Optional delivery family for the test token (e.g. Ios, Android, Chrome, Safari, Expo). Requires TestToken when set.") + # @ApiMember(Description="Optional delivery family for the test token (ios, android, chrome or safari). Requires TestToken when set.") delivery_family: Optional[str] = None """ - Optional delivery family for the test token (e.g. Ios, Android, Chrome, Safari, Expo). Requires TestToken when set. + Optional delivery family for the test token (ios, android, chrome or safari). Requires TestToken when set. """ @@ -14060,10 +14979,10 @@ class GetPushDevices(CodeMashListPaginationRequestBase, IReturn[GetPushDevicesRe """ - # @ApiMember(Description="Optional: only devices of this platform — ios, android, chrome, safari or expo.") + # @ApiMember(Description="Optional: only devices of this platform — ios, android, chrome or safari.") platform: Optional[str] = None """ - Optional: only devices of this platform — ios, android, chrome, safari or expo. + Optional: only devices of this platform — ios, android, chrome or safari. """ @@ -14074,6 +14993,29 @@ class GetPushDevices(CodeMashListPaginationRequestBase, IReturn[GetPushDevicesRe """ +# @Route("/{version}/notifications/push/campaigns/audience-count", "POST") +# @Api(Description="Count the devices a push campaign audience would reach") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class GetPushCampaignAudienceCountRequest(CodeMashRequestBase, IReturn[GetPushCampaignAudienceCountResponse]): + """ + Count the devices a push campaign audience would reach + """ + + # @ApiMember(Description="The audience, in the same shape as CreatePushCampaignRequest.campaign. Template and send options are ignored.") + campaign: Optional[PushCampaignRequest] = None + """ + The audience, in the same shape as CreatePushCampaignRequest.campaign. Template and send options are ignored. + """ + + + # @ApiMember(Description="Optional. When omitted, the default database integration for the request's environment is used.") + database_integration_id: Optional[str] = None + """ + Optional. When omitted, the default database integration for the request's environment is used. + """ + + # @Route("/{version}/notifications/push/campaigns", "POST") # @Api(Description="Create push campaign") @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -14085,10 +15027,10 @@ class CreatePushCampaignRequest(CodeMashRequestBase, IReturn[IdResponse]): campaign: Optional[PushCampaignRequest] = None database_integration_id: Optional[str] = None - # @ApiMember(Description="Optional. true sends the campaign now (or at a campaignTime sooner than the review window). Without it, a campaign created by an AI tool must be scheduled at least 24 hours ahead for role-based / all-users / collection audiences and at least 1 hour ahead for direct recipient lists, or it is rejected with CM-ERRORS-CAMPAIGNS-016. Set it only when the user explicitly asked to send now.") + # @ApiMember(Description="Optional. true sends the campaign now (or at a campaignTime sooner than the review window). Without it, a campaign created by an AI tool must be scheduled at least 24 hours ahead for role-based / all-users / collection audiences and at least 1 hour ahead for direct recipient lists, or it is rejected with CM-ERRORS-CAMPAIGNS-017. Set it only when the user explicitly asked to send now.") send_now: Optional[bool] = None """ - Optional. true sends the campaign now (or at a campaignTime sooner than the review window). Without it, a campaign created by an AI tool must be scheduled at least 24 hours ahead for role-based / all-users / collection audiences and at least 1 hour ahead for direct recipient lists, or it is rejected with CM-ERRORS-CAMPAIGNS-016. Set it only when the user explicitly asked to send now. + Optional. true sends the campaign now (or at a campaignTime sooner than the review window). Without it, a campaign created by an AI tool must be scheduled at least 24 hours ahead for role-based / all-users / collection audiences and at least 1 hour ahead for direct recipient lists, or it is rejected with CM-ERRORS-CAMPAIGNS-017. Set it only when the user explicitly asked to send now. """ @@ -14348,43 +15290,6 @@ class StopPushCampaignRequest(CodeMashRequestBase, IReturn[EmptyResponse]): """ -# @Route("/{version}/notifications/push/campaigns/{campaignId}/messages/{notificationId}", "GET") -# @Api(Description="Gets campaign push notification details") -@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) -@dataclass -class GetPushCampaignMessage(CodeMashRequestBase, IReturn[GetPushCampaignMessageResponse]): - """ - Gets campaign push notification details - """ - - # @ApiMember(Description="The push campaign id. Get it from get_push_campaigns.") - campaign_id: Optional[str] = None - """ - The push campaign id. Get it from get_push_campaigns. - """ - - - # @ApiMember(Description="The batch id. Get it from get_push_campaign_batches.") - campaign_batch_id: Optional[str] = None - """ - The batch id. Get it from get_push_campaign_batches. - """ - - - # @ApiMember(Description="The notification id within the batch.") - notification_id: Optional[str] = None - """ - The notification id within the batch. - """ - - - # @ApiMember(Description="Optional database integration id; omit to use the project's default.") - database_integration_id: Optional[str] = None - """ - Optional database integration id; omit to use the project's default. - """ - - # @Route("/{version}/notifications/push/campaigns/{campaignId}/messages", "GET") # @Api(Description="Gets push campaign messages") @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -15157,16 +16062,113 @@ class ChatTurnRequest(RequestBase, IReturn[ChatTurnResponse]): screen_context: Optional[ChatScreenContextDto] = None +# @Route("/{version}/account/ai/scaffold", "POST") +# @Api(Description="Plans (preview) or builds (apply) a whole project from one spec.") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class ScaffoldProjectRequest(RequestBase, IReturn[ScaffoldProjectResponse]): + """ + Plans (preview) or builds (apply) a whole project from one spec. + """ + + # @ApiMember(Description="The project spec (a JSON object) — see the tool description.", IsRequired=true) + spec: Optional[str] = None + """ + The project spec (a JSON object) — see the tool description. + """ + + + # @ApiMember(Description="'preview' (writes nothing) or 'apply' (builds the project).", IsRequired=true) + mode: Optional[str] = None + """ + 'preview' (writes nothing) or 'apply' (builds the project). + """ + + + env: Optional[str] = None + + +# @Route("/{version}/account/ai/schemas/validate", "POST") +# @Api(Description="Validates a collection schema without saving it.") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class ValidateSchemaRequest(RequestBase, IReturn[ValidateSchemaResponse]): + """ + Validates a collection schema without saving it. + """ + + # @ApiMember(Description="The schema as JSON — see the tool description.", IsRequired=true) + schema_json: Optional[str] = None + """ + The schema as JSON — see the tool description. + """ + + +# @Route("/{version}/account/ai/templates/render-preview", "POST") +# @Api(Description="Renders an email, push or SMS template with sample data.") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class RenderTemplatePreviewRequest(RequestBase, IReturn[RenderTemplatePreviewResponse], IHasEnv): + """ + Renders an email, push or SMS template with sample data. + """ + + # @ApiMember(Description="The project of templateId. Not needed to render a body.") + project_id: Optional[str] = None + """ + The project of templateId. Not needed to render a body. + """ + + + env: Optional[str] = None + # @ApiMember(Description="email | push | sms", IsRequired=true) + channel: Optional[str] = None + """ + email | push | sms + """ + + + # @ApiMember(Description="An existing template id (tmpl_…). Needs projectId. Leave empty to render body instead.") + template_id: Optional[str] = None + """ + An existing template id (tmpl_…). Needs projectId. Leave empty to render body instead. + """ + + + # @ApiMember(Description="Razor template text to render when there is no templateId.") + body: Optional[str] = None + """ + Razor template text to render when there is no templateId. + """ + + + # @ApiMember(Description="Email subject / push title / SMS subject to render with body. Optional.") + subject: Optional[str] = None + """ + Email subject / push title / SMS subject to render with body. Optional. + """ + + + # @ApiMember(Description="Sample data as a JSON object, read by the template as @Model.") + sample_data: Optional[str] = None + """ + Sample data as a JSON object, read by the template as @Model. + """ + + # @Route("/{version}/account/mcp", "POST") -# @Api(Description="MCP server endpoint — JSON-RPC 2.0 over HTTP POST exposing the AI tool catalog.") +# @Route("/{version}/account/mcp", "GET") +# @Route("/{version}/account/mcp", "DELETE") +# @Api(Description="MCP server endpoint (Streamable HTTP): JSON-RPC 2.0 over POST, server stream over GET, session end over DELETE.") @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class McpRequest(IReturn[str]): """ - MCP server endpoint — JSON-RPC 2.0 over HTTP POST exposing the AI tool catalog. + MCP server endpoint (Streamable HTTP): JSON-RPC 2.0 over POST, server stream over GET, session end over DELETE. """ version: Optional[str] = None + toolsets: Optional[str] = None request_stream: Optional[bytes] = None @@ -15244,6 +16246,212 @@ class MarkNeedsYouDoneRequest(CodeMashRequestBase, IReturn[IdResponse]): """ +# @Route("/{version}/ai/triggers", "POST") +# @Api(Description="Save a trigger on an AI project event") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class SaveAiProjectTrigger(SaveTrigger, IReturn[IdResponse]): + """ + Save a trigger on an AI project event + """ + + pass + + +# @Route("/{version}/ai/triggers", "GET") +# @Api(Description="Gets the triggers on AI project events") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class GetAiProjectTriggers(GetTriggers, IReturn[GetAiTriggersResponse]): + """ + Gets the triggers on AI project events + """ + + pass + + +# @Route("/{version}/ai/triggers/{id}", "GET") +# @Api(Description="Gets one trigger on an AI project event") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class GetAiProjectTrigger(GetTrigger, IReturn[GetAiTriggerResponse]): + """ + Gets one trigger on an AI project event + """ + + pass + + +# @Route("/{version}/ai/triggers/{triggerId}/enable", "PATCH") +# @Api(Description="Enable a trigger on an AI project event") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class EnableAiProjectTrigger(EnableTrigger, IReturn[EmptyResponse]): + """ + Enable a trigger on an AI project event + """ + + pass + + +# @Route("/{version}/ai/triggers/{triggerId}/disable", "PATCH") +# @Api(Description="Disable a trigger on an AI project event") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class DisableAiProjectTrigger(DisableTrigger, IReturn[EmptyResponse]): + """ + Disable a trigger on an AI project event + """ + + pass + + +# @Route("/{version}/ai/triggers/{triggerId}", "DELETE") +# @Api(Description="Delete a trigger on an AI project event") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class DeleteAiProjectTrigger(DeleteTrigger, IReturn[EmptyResponse]): + """ + Delete a trigger on an AI project event + """ + + pass + + +# @Route("/.well-known/oauth-protected-resource", "GET") +# @Route("/.well-known/oauth-protected-resource/{Path*}", "GET") +# @Api(Description="OAuth protected-resource metadata (RFC 9728) for the Hub MCP endpoint.") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class OAuthProtectedResourceMetadataRequest(IReturn[str]): + """ + OAuth protected-resource metadata (RFC 9728) for the Hub MCP endpoint. + """ + + path: Optional[str] = None + + +# @Route("/.well-known/oauth-authorization-server", "GET") +# @Route("/.well-known/oauth-authorization-server/{Path*}", "GET") +# @Api(Description="OAuth authorization-server metadata (RFC 8414).") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class OAuthAuthorizationServerMetadataRequest(IReturn[str]): + """ + OAuth authorization-server metadata (RFC 8414). + """ + + path: Optional[str] = None + + +# @Route("/{version}/oauth/register", "POST") +# @Api(Description="Registers an MCP client for OAuth (RFC 7591). Public clients only: PKCE, no secret.") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class OAuthRegisterRequest(IReturn[str]): + """ + Registers an MCP client for OAuth (RFC 7591). Public clients only: PKCE, no secret. + """ + + version: Optional[str] = None + request_stream: Optional[bytes] = None + + +# @Route("/{version}/oauth/authorize", "GET") +# @Api(Description="OAuth authorization endpoint: sign-in hint or the consent page (HTML).") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class OAuthAuthorizeRequest(IReturn[str]): + """ + OAuth authorization endpoint: sign-in hint or the consent page (HTML). + """ + + version: Optional[str] = None + + +# @Route("/{version}/oauth/authorize", "POST") +# @Api(Description="OAuth authorization endpoint: the person's decision from the consent page.") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class OAuthAuthorizeDecisionRequest(IReturn[str]): + """ + OAuth authorization endpoint: the person's decision from the consent page. + """ + + version: Optional[str] = None + + +# @Route("/{version}/oauth/token", "POST") +# @Api(Description="OAuth token endpoint: authorization code (PKCE S256) or refresh token → access token.") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class OAuthTokenRequest(IReturn[str]): + """ + OAuth token endpoint: authorization code (PKCE S256) or refresh token → access token. + """ + + version: Optional[str] = None + + +# @Route("/{version}/oauth/revoke", "POST") +# @Api(Description="OAuth token revocation (RFC 7009): revokes the grant behind a refresh or access token.") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class OAuthRevokeRequest(IReturn[str]): + """ + OAuth token revocation (RFC 7009): revokes the grant behind a refresh or access token. + """ + + version: Optional[str] = None + + +# @Route("/{version}/ai/integrations/embeddings/{Id}", "DELETE") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class DeleteEmbeddingIntegrationRequest(CodeMashRequestBase, IReturn[EmptyResponse]): + # @ApiMember(Description="Id of the embedding integration to delete.") + id: Optional[str] = None + """ + Id of the embedding integration to delete. + """ + + +# @Route("/{version}/ai/integrations/embeddings/{Id}", "GET") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class GetEmbeddingIntegration(CodeMashRequestBase, IReturn[GetEmbeddingIntegrationResponse]): + # @ApiMember(Description="Id of the embedding integration to fetch.") + id: Optional[str] = None + """ + Id of the embedding integration to fetch. + """ + + +# @Route("/{version}/ai/integrations/embeddings", "GET") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class GetEmbeddingIntegrations(CodeMashListPaginationRequestBase, IReturn[GetEmbeddingIntegrationsResponse]): + pass + + +# @Route("/{version}/ai/integrations/embeddings", "POST") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class SaveEmbeddingIntegration(CodeMashRequestBase, IReturn[IdResponse]): + integration: Optional[EmbeddingIntegrationRequest] = None + + +# @Route("/{version}/ai/integrations/embeddings/{Id}/test", "POST") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class TestEmbeddingIntegration(CodeMashRequestBase, IReturn[TestEmbeddingIntegrationResponse]): + # @ApiMember(Description="Id of the embedding integration to test.") + id: Optional[str] = None + """ + Id of the embedding integration to test. + """ + + # @Route("/{version}/ai/integrations/llms/{Id}", "DELETE") @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass @@ -15302,6 +16510,17 @@ class SaveLlmIntegration(CodeMashRequestBase, IReturn[IdResponse]): integration: Optional[LlmIntegrationRequest] = None +# @Route("/{version}/ai/integrations/llms/{Id}/default", "PUT") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class SetLlmIntegrationAsDefaultRequest(CodeMashRequestBase, IReturn[EmptyResponse]): + # @ApiMember(Description="Id of the LLM integration to make the default.") + id: Optional[str] = None + """ + Id of the LLM integration to make the default. + """ + + # @Route("/{version}/ai/integrations/llms/test", "POST") @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass @@ -15382,6 +16601,23 @@ class TestMcpIntegration(CodeMashRequestBase, IReturn[TestLlmIntegrationResponse """ +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class IngestSourceMessage: + project_id: Optional[str] = None + env: Optional[str] = None + owner_auth_id: Optional[str] = None + source_kind: Optional[str] = None + source_id: Optional[str] = None + title: Optional[str] = None + content_type: Optional[str] = None + content: Optional[str] = None + embedding_integration_id: Optional[str] = None + removed: bool = False + metadata: Optional[Dict[str, str]] = None + owner_required: bool = False + + # @Route("/{version}/webhooks/integration", "GET") # @Api(Description="Gets the project's webhook integration") @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @@ -15507,14 +16743,14 @@ class SaveWebhookDestinationRequest(CodeMashRequestBase, IReturn[SaveWebhookDest """ -# @Route("/{version}/scheduler/disable", "GET") +# @Route("/{version}/scheduler/disable", "PUT") @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class DisableScheduler(CodeMashRequestBase, IReturn[EmptyResponse]): pass -# @Route("/{version}/scheduler/enable", "GET") +# @Route("/{version}/scheduler/enable", "PUT") @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) @dataclass class EnableScheduler(CodeMashRequestBase, IReturn[EmptyResponse]): @@ -15603,6 +16839,36 @@ class ResolveResources(CodeMashRequestBase, IReturn[ResolveResourcesResponse]): refs: Optional[IReadOnlyList[ResourceRefDto]] = None +# @Route("/{version}/membership/users/{userId}/ai-plan", "PUT") +# @Api(Description="Sets or removes one user's own end-user AI plan") +@dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) +@dataclass +class SetUserAiPlan(CodeMashRequestBase, IReturn[EmptyResponse]): + """ + Sets or removes one user's own end-user AI plan + """ + + # @ApiMember(Description="The human user id (ct_…).", IsRequired=true) + user_id: Optional[str] = None + """ + The human user id (ct_…). + """ + + + # @ApiMember(Description="The plan id (aip_…). Empty removes the user's own plan.") + plan_id: Optional[str] = None + """ + The plan id (aip_…). Empty removes the user's own plan. + """ + + + # @ApiMember(Description="Database integration id. Optional — defaults to the request environment's default integration.") + database_integration_id: Optional[str] = None + """ + Database integration id. Optional — defaults to the request environment's default integration. + """ + + # @Route("/{version}/membership/users", "POST") # @Api(Description="Create a contact") @dataclass_json(letter_case=LetterCase.CAMEL, undefined=Undefined.EXCLUDE) diff --git a/src/norbix_python/hub/__init__.py b/src/norbix_python/hub/__init__.py index 3df39d9..980c801 100644 --- a/src/norbix_python/hub/__init__.py +++ b/src/norbix_python/hub/__init__.py @@ -17,6 +17,7 @@ from .regions import AsyncRegionsModule, RegionsModule from .resources import AsyncResourcesModule, ResourcesModule from .scheduler import AsyncSchedulerModule, SchedulerModule +from .triggers import AsyncTriggersModule, TriggersModule from .webhooks import AsyncWebhooksModule, WebhooksModule @@ -38,6 +39,7 @@ def __init__(self, transport: Transport) -> None: self.regions = RegionsModule(transport) self.resources = ResourcesModule(transport) self.scheduler = SchedulerModule(transport) + self.triggers = TriggersModule(transport) # hand-written, keep on regeneration self.webhooks = WebhooksModule(transport) @@ -59,4 +61,5 @@ def __init__(self, transport: AsyncTransport) -> None: self.regions = AsyncRegionsModule(transport) self.resources = AsyncResourcesModule(transport) self.scheduler = AsyncSchedulerModule(transport) + self.triggers = AsyncTriggersModule(transport) # hand-written, keep on regeneration self.webhooks = AsyncWebhooksModule(transport) diff --git a/src/norbix_python/hub/account.py b/src/norbix_python/hub/account.py index ee05467..f9535bd 100644 --- a/src/norbix_python/hub/account.py +++ b/src/norbix_python/hub/account.py @@ -439,6 +439,26 @@ def enable_project(self, project_id: str, *, timeout: float | None = None, beare bearer_token=bearer_token, ) + # HAND-WRITTEN: not produced by scripts/generate_endpoints.py — keep on regeneration. + def check_project_languages(self, project_id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """POST /{version}/account/projects/{projectId}/settings/languages/check + + Dry run before ``update_project_languages``: lists the templates that miss + a proposed language. Pass ``defaultLanguage=...`` and/or ``languages=[...]``; + omit one to use the current value. Changes nothing. Response: + ``{"templates": [...]}``. + """ + return self._transport.send( + target="hub", + path="/{version}/account/projects/{projectId}/settings/languages/check", + method="POST", + path_params={"projectId": project_id}, + request=request, + scope="account", + timeout=timeout, + bearer_token=bearer_token, + ) + def update_project_languages(self, project_id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: """PATCH /{version}/account/projects/{projectId}/settings/languages""" return self._transport.send( @@ -1335,6 +1355,26 @@ async def enable_project(self, project_id: str, *, timeout: float | None = None, bearer_token=bearer_token, ) + # HAND-WRITTEN: not produced by scripts/generate_endpoints.py — keep on regeneration. + async def check_project_languages(self, project_id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """POST /{version}/account/projects/{projectId}/settings/languages/check + + Dry run before ``update_project_languages``: lists the templates that miss + a proposed language. Pass ``defaultLanguage=...`` and/or ``languages=[...]``; + omit one to use the current value. Changes nothing. Response: + ``{"templates": [...]}``. + """ + return await self._transport.send( + target="hub", + path="/{version}/account/projects/{projectId}/settings/languages/check", + method="POST", + path_params={"projectId": project_id}, + request=request, + scope="account", + timeout=timeout, + bearer_token=bearer_token, + ) + async def update_project_languages(self, project_id: str, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: """PATCH /{version}/account/projects/{projectId}/settings/languages""" return await self._transport.send( diff --git a/src/norbix_python/hub/triggers.py b/src/norbix_python/hub/triggers.py new file mode 100644 index 0000000..89f2e3a --- /dev/null +++ b/src/norbix_python/hub/triggers.py @@ -0,0 +1,55 @@ +from __future__ import annotations + +from typing import Any + +from ..transport import AsyncTransport, Transport + +# HAND-WRITTEN module: scripts/generate_endpoints.py (gitignored, not in the +# repo) did not produce it. Keep it — and its registration in hub/__init__.py — +# when the hub modules are regenerated. + + +class TriggersModule: + def __init__(self, transport: Transport) -> None: + self._transport = transport + + def get_triggers_needing_attention(self, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """GET /{version}/triggers/attention + + Lists the triggers of one type that need attention (for example, a + trigger whose provider integration is gone). Pass ``triggerType=...`` + (Membership, Schema, Files, Payments or Ai). Response: ``{"items": [...]}``. + """ + return self._transport.send( + target="hub", + path="/{version}/triggers/attention", + method="GET", + path_params={}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) + + +class AsyncTriggersModule: + def __init__(self, transport: AsyncTransport) -> None: + self._transport = transport + + async def get_triggers_needing_attention(self, *, timeout: float | None = None, bearer_token: str | None = None, **request: Any) -> Any: + """GET /{version}/triggers/attention + + Lists the triggers of one type that need attention (for example, a + trigger whose provider integration is gone). Pass ``triggerType=...`` + (Membership, Schema, Files, Payments or Ai). Response: ``{"items": [...]}``. + """ + return await self._transport.send( + target="hub", + path="/{version}/triggers/attention", + method="GET", + path_params={}, + request=request, + scope="project", + timeout=timeout, + bearer_token=bearer_token, + ) diff --git a/tests/hub/test_account.py b/tests/hub/test_account.py index e9e021e..24e29b2 100644 --- a/tests/hub/test_account.py +++ b/tests/hub/test_account.py @@ -702,3 +702,58 @@ def test_hub_account_set_admin_portal_enabled_request_shape() -> None: assert transport.last_request['headers']['authorization'] == 'Bearer test-token' assert transport.last_request['headers']['x-cm-projectid'] == 'test-project' + +def test_hub_account_check_project_languages_request_shape() -> None: + import json + from urllib.parse import urlparse + + client, transport = make_client(account_id='acc-1') + client.hub.account.check_project_languages( + project_id="proj-1", defaultLanguage="en", languages=["en", "de"] + ) + assert transport.last_request is not None + assert transport.last_request['method'] == 'POST' + assert urlparse(transport.last_request['url']).path == '/v2/account/projects/proj-1/settings/languages/check' + assert json.loads(transport.last_request['body']) == {"defaultLanguage": "en", "languages": ["en", "de"]} + assert transport.last_request['headers']['x-cm-accountid'] == 'acc-1' + +def test_hub_account_check_project_languages_requires_account_scope() -> None: + client = Norbix(project_id='p1', bearer_token='token') + try: + client.hub.account.check_project_languages(project_id="stub") + except NorbixError as exc: + assert exc.code == 'NORBIX_ACCOUNT_SCOPE_REQUIRED' + else: + raise AssertionError('Expected account scope error') + +def test_async_hub_account_check_project_languages_hits_the_route() -> None: + import asyncio + import json + from urllib.parse import urlparse + + import httpx + + from norbix_python import AsyncNorbix + + seen: list[httpx.Request] = [] + + def handler(request: httpx.Request) -> httpx.Response: + seen.append(request) + return httpx.Response(200, json={"templates": []}) + + async def run() -> object: + client = AsyncNorbix( + project_id='test-project', + bearer_token='test-token', + account_id='acc-1', + http_client=httpx.AsyncClient(transport=httpx.MockTransport(handler)), + ) + try: + return await client.hub.account.check_project_languages(project_id="proj-1", languages=["en"]) + finally: + await client.aclose() + + assert asyncio.run(run()) == {"templates": []} + assert [(r.method, urlparse(str(r.url)).path, json.loads(r.content)) for r in seen] == [ + ('POST', '/v2/account/projects/proj-1/settings/languages/check', {"languages": ["en"]}), + ] diff --git a/tests/hub/test_email_routes.py b/tests/hub/test_email_routes.py index b86e7d8..7f6cb00 100644 --- a/tests/hub/test_email_routes.py +++ b/tests/hub/test_email_routes.py @@ -478,3 +478,20 @@ async def run() -> None: assert len(seen) == 1 assert seen[0].headers.get("Authorization") is None + + +def test_create_email_campaign_sends_the_provider_integration_id_nested() -> None: + # The gateway requires the email provider id inside `campaign`. + import json + + client, transport = make_client() + client.hub.notifications.create_email_campaign( + campaign={"templateId": "tpl_1", "integrationId": "int_1", "source": "allUsers"} + ) + + assert transport.last_request is not None + assert transport.last_request["method"] == "POST" + assert urlparse(transport.last_request["url"]).path == "/v2/notifications/email/campaigns" + assert json.loads(transport.last_request["body"]) == { + "campaign": {"templateId": "tpl_1", "integrationId": "int_1", "source": "allUsers"} + } diff --git a/tests/hub/test_push.py b/tests/hub/test_push.py index a1ec4af..339e0db 100644 --- a/tests/hub/test_push.py +++ b/tests/hub/test_push.py @@ -270,6 +270,18 @@ def test_create_push_campaign_sends_the_target_shape(source: str, fields: dict[s assert campaign == {"source": source, "templateId": TEMPLATE_ID, **fields} +def test_create_push_campaign_sends_the_provider_integration_id_nested() -> None: + # The gateway requires the push provider id inside `campaign`. + client, transport = make_client() + client.hub.notifications.create_push_campaign( + campaign={"source": "allUsers", "templateId": TEMPLATE_ID, "integrationId": "int_1"} + ) + + assert transport.last_request is not None + body = json.loads(transport.last_request["body"]) + assert body == {"campaign": {"source": "allUsers", "templateId": TEMPLATE_ID, "integrationId": "int_1"}} + + def test_campaign_targets_match_the_gateway() -> None: assert {source.lower() for source, _ in CAMPAIGN_TARGETS} == GATEWAY_TARGETS assert len(CAMPAIGN_TARGETS) == len(GATEWAY_TARGETS) diff --git a/tests/hub/test_sms.py b/tests/hub/test_sms.py index f1f9c60..b6dd55e 100644 --- a/tests/hub/test_sms.py +++ b/tests/hub/test_sms.py @@ -318,6 +318,32 @@ def test_campaign_audiences_match_the_gateway() -> None: assert len(CAMPAIGN_AUDIENCES) == len(GATEWAY_AUDIENCES) +def test_create_sms_campaign_sends_the_provider_integration_id_top_level() -> None: + # The gateway requires the SMS provider id as a top-level `integrationId`. + # It is a different field from `databaseIntegrationId` (the database that + # holds a `Collection` audience), so both must reach the wire side by side. + client, transport = make_client() + client.hub.notifications.create_sms_campaign( + templateId=TEMPLATE_ID, + integrationId=INTEGRATION_ID, + databaseIntegrationId="db_1", + deliveryType="Collection", + collectionSettings={"schemaName": "subscribers", "fields": ["phone"], "campaignTime": CAMPAIGN_TIME}, + ) + + assert transport.last_request is not None + assert transport.last_request["method"] == "POST" + assert urlparse(transport.last_request["url"]).path == f"{BASE}/campaigns" + body = json.loads(transport.last_request["body"]) + assert body == { + "templateId": TEMPLATE_ID, + "integrationId": INTEGRATION_ID, + "databaseIntegrationId": "db_1", + "deliveryType": "Collection", + "collectionSettings": {"schemaName": "subscribers", "fields": ["phone"], "campaignTime": CAMPAIGN_TIME}, + } + + # The eight providers the gateway's save converter accepts, with the fields # each one validates. Source of truth: gateway Hub.Sms/Integrations/Save_.cs # (the switch arms of the converter, read from `provider`) and diff --git a/tests/hub/test_triggers.py b/tests/hub/test_triggers.py new file mode 100644 index 0000000..5b314a9 --- /dev/null +++ b/tests/hub/test_triggers.py @@ -0,0 +1,103 @@ +from __future__ import annotations + +import asyncio +import json +from collections.abc import Callable +from typing import Any +from urllib.parse import parse_qs, urlparse + +import httpx +import pytest + +from norbix_python import AsyncNorbix + +from ..helpers import make_client + +"""Triggers: the cross-module "needs attention" list, and the action shape that +every trigger save sends. + +The gateway requires the provider ``integrationId`` on a trigger action, and +takes an optional ``language`` (template language) and ``initiatorId`` (who the +send is attributed to). The SDK passes the request through as-is, so these +tests pin that the three fields reach the wire under ``trigger.action``. +""" + +INTEGRATION_ID = "int_1" +TEMPLATE_ID = "tpl_1" +INITIATOR_ID = "user_1" + + +def test_hub_triggers_module_surface() -> None: + client, _ = make_client() + assert callable(client.hub.triggers.get_triggers_needing_attention) + + +def test_get_triggers_needing_attention_hits_the_route_with_query_values() -> None: + client, transport = make_client() + client.hub.triggers.get_triggers_needing_attention(triggerType="Schema") + + assert transport.last_request is not None + assert transport.last_request["method"] == "GET" + url = urlparse(transport.last_request["url"]) + assert url.path == "/v2/triggers/attention" + assert parse_qs(url.query) == {"triggerType": ["Schema"]} + assert transport.last_request["body"] == "" + assert transport.last_request["headers"]["x-cm-projectid"] == "test-project" + + +def test_async_get_triggers_needing_attention_hits_the_route() -> None: + seen: list[httpx.Request] = [] + + def handler(request: httpx.Request) -> httpx.Response: + seen.append(request) + return httpx.Response(200, json={"items": []}) + + async def run() -> Any: + client = AsyncNorbix( + project_id="test-project", + bearer_token="test-token", + http_client=httpx.AsyncClient(transport=httpx.MockTransport(handler)), + ) + try: + return await client.hub.triggers.get_triggers_needing_attention(triggerType="Files") + finally: + await client.aclose() + + result = asyncio.run(run()) + + assert result == {"items": []} + assert [(r.method, urlparse(str(r.url)).path, urlparse(str(r.url)).query) for r in seen] == [ + ("GET", "/v2/triggers/attention", "triggerType=Files"), + ] + + +# name, expected path, call — every trigger save in the hub. +SaveCase = tuple[str, str, Callable[[Any, dict[str, Any]], Any]] +TRIGGER_SAVES: list[SaveCase] = [ + ("save_schema_trigger", "/v2/database/schemas/triggers", lambda c, b: c.hub.database.save_schema_trigger(**b)), + ("save_files_trigger", "/v2/files/triggers", lambda c, b: c.hub.files.save_files_trigger(**b)), + ("save_membership_trigger", "/v2/membership/triggers", lambda c, b: c.hub.membership.save_membership_trigger(**b)), + ("save_payments_trigger", "/v2/payments/triggers", lambda c, b: c.hub.payments.save_payments_trigger(**b)), +] + + +@pytest.mark.parametrize(("name", "path", "call"), TRIGGER_SAVES, ids=[c[0] for c in TRIGGER_SAVES]) +@pytest.mark.parametrize("action_type", ["Email", "Push", "Sms"]) +def test_trigger_save_sends_integration_language_and_initiator( + name: str, path: str, call: Callable[[Any, dict[str, Any]], Any], action_type: str +) -> None: + action = { + "type": action_type, + "integrationId": INTEGRATION_ID, + "templateId": TEMPLATE_ID, + "language": "de", + "initiatorId": INITIATOR_ID, + } + client, transport = make_client() + call(client, {"trigger": {"name": "welcome", "isEnabled": True, "action": action}}) + + assert transport.last_request is not None + assert transport.last_request["method"] == "POST" + assert urlparse(transport.last_request["url"]).path == path + body = json.loads(transport.last_request["body"]) + assert body == {"trigger": {"name": "welcome", "isEnabled": True, "action": action}}