twenty-app-intake 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +308 -0
- package/manifest.json +38 -660
- package/package.json +13 -10
- package/public/cover.generated.png +0 -0
- package/src/front-components/dashboard.mjs +19 -21947
- package/src/front-components/dashboard.mjs.map +4 -4
- package/src/front-components/dashboard.tsx +118 -0
- package/src/front-components/settings-panel.mjs +19 -22103
- package/src/front-components/settings-panel.mjs.map +4 -4
- package/src/front-components/settings-panel.tsx +255 -0
- package/src/front-components/source-panel.mjs +20 -21962
- package/src/front-components/source-panel.mjs.map +4 -4
- package/src/front-components/source-panel.tsx +113 -0
- package/src/logic-functions/health.mjs.map +2 -2
- package/src/logic-functions/health.ts +26 -0
- package/src/logic-functions/register.mjs.map +2 -2
- package/src/logic-functions/register.ts +105 -0
- package/src/logic-functions/retry.mjs +3 -0
- package/src/logic-functions/retry.mjs.map +2 -2
- package/src/logic-functions/retry.ts +124 -0
- package/src/logic-functions/test-ingest.mjs.map +2 -2
- package/src/logic-functions/test-ingest.ts +75 -0
- package/src/logic-functions/webhook.mjs +3 -0
- package/src/logic-functions/webhook.mjs.map +2 -2
- package/src/logic-functions/webhook.ts +83 -0
- package/src/post-install.mjs.map +2 -2
- package/src/post-install.ts +81 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { defineFrontComponent } from 'twenty-sdk/define';
|
|
2
|
+
import React, { useEffect, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
type SourceData = {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
slug: string;
|
|
8
|
+
status: string;
|
|
9
|
+
targetObject: string;
|
|
10
|
+
webhookSecret: string | null;
|
|
11
|
+
totalIngested: number;
|
|
12
|
+
lastIngestedAt: string | null;
|
|
13
|
+
createOpportunity: boolean;
|
|
14
|
+
opportunityNameTemplate: string;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const copyIcon = `<svg width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>`;
|
|
18
|
+
|
|
19
|
+
const SourcePanel: React.FC<{ recordId?: string }> = ({ recordId }) => {
|
|
20
|
+
const [source, setSource] = useState<SourceData | null>(null);
|
|
21
|
+
const [copied, setCopied] = useState<string | null>(null);
|
|
22
|
+
const [loading, setLoading] = useState(true);
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (!recordId) { setLoading(false); return; }
|
|
26
|
+
const apiUrl = (window as any).__TWENTY_API_URL__ ?? '';
|
|
27
|
+
const token = (window as any).__TWENTY_API_TOKEN__ ?? '';
|
|
28
|
+
|
|
29
|
+
fetch(`${apiUrl}/rest/intakeSources/${recordId}`, {
|
|
30
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
31
|
+
})
|
|
32
|
+
.then(r => r.json())
|
|
33
|
+
.then(d => setSource(d.data?.intakeSource ?? null))
|
|
34
|
+
.finally(() => setLoading(false));
|
|
35
|
+
}, [recordId]);
|
|
36
|
+
|
|
37
|
+
const copy = (text: string, key: string) => {
|
|
38
|
+
navigator.clipboard.writeText(text).then(() => {
|
|
39
|
+
setCopied(key);
|
|
40
|
+
setTimeout(() => setCopied(null), 2000);
|
|
41
|
+
});
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const webhookUrl = source
|
|
45
|
+
? `${(window as any).__TWENTY_API_URL__ ?? 'https://your-crm.com'}/s/intake/${source.slug}`
|
|
46
|
+
: '';
|
|
47
|
+
|
|
48
|
+
if (loading) return <div style={{ padding: 16, color: '#6b7280', fontSize: 13 }}>Loading...</div>;
|
|
49
|
+
if (!source) return <div style={{ padding: 16, color: '#6b7280', fontSize: 13 }}>Source not found.</div>;
|
|
50
|
+
|
|
51
|
+
const Code: React.FC<{ value: string; copyKey: string }> = ({ value, copyKey }) => (
|
|
52
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 6, background: '#f9fafb', border: '1px solid #e5e7eb', borderRadius: 6, padding: '6px 10px' }}>
|
|
53
|
+
<code style={{ flex: 1, fontSize: 12, fontFamily: 'monospace', wordBreak: 'break-all', color: '#111' }}>{value}</code>
|
|
54
|
+
<button
|
|
55
|
+
onClick={() => copy(value, copyKey)}
|
|
56
|
+
style={{ border: 'none', background: 'none', cursor: 'pointer', color: copied === copyKey ? '#22c55e' : '#6b7280', padding: 2, flexShrink: 0 }}
|
|
57
|
+
title="Copy"
|
|
58
|
+
dangerouslySetInnerHTML={{ __html: copied === copyKey ? '✓' : copyIcon }}
|
|
59
|
+
/>
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div style={{ padding: '16px 20px', fontFamily: 'system-ui, sans-serif', fontSize: 13 }}>
|
|
65
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 16 }}>
|
|
66
|
+
<span style={{
|
|
67
|
+
background: source.status === 'ACTIVE' ? '#dcfce7' : '#fef3c7',
|
|
68
|
+
color: source.status === 'ACTIVE' ? '#16a34a' : '#d97706',
|
|
69
|
+
borderRadius: 4, padding: '2px 7px', fontSize: 11, fontWeight: 600,
|
|
70
|
+
}}>{source.status}</span>
|
|
71
|
+
<span style={{ color: '#6b7280' }}>→ {source.targetObject}</span>
|
|
72
|
+
<span style={{ marginLeft: 'auto', color: '#6b7280' }}>
|
|
73
|
+
{source.totalIngested} ingested
|
|
74
|
+
{source.lastIngestedAt && ` · last ${new Date(source.lastIngestedAt).toLocaleDateString()}`}
|
|
75
|
+
</span>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div style={{ marginBottom: 14 }}>
|
|
79
|
+
<div style={{ fontWeight: 500, marginBottom: 5, color: '#374151' }}>Webhook URL</div>
|
|
80
|
+
<Code value={webhookUrl} copyKey="url" />
|
|
81
|
+
<div style={{ marginTop: 4, fontSize: 11, color: '#9ca3af' }}>POST this URL from your platform to ingest leads.</div>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
{source.webhookSecret && (
|
|
85
|
+
<div style={{ marginBottom: 14 }}>
|
|
86
|
+
<div style={{ fontWeight: 500, marginBottom: 5, color: '#374151' }}>Signing Secret</div>
|
|
87
|
+
<Code value={source.webhookSecret} copyKey="secret" />
|
|
88
|
+
<div style={{ marginTop: 4, fontSize: 11, color: '#9ca3af' }}>HMAC-SHA256 sign requests with this secret. Header: <code>X-Webhook-Signature: sha256=<digest></code></div>
|
|
89
|
+
</div>
|
|
90
|
+
)}
|
|
91
|
+
|
|
92
|
+
<div style={{ marginBottom: 14 }}>
|
|
93
|
+
<div style={{ fontWeight: 500, marginBottom: 5, color: '#374151' }}>Dry-run URL</div>
|
|
94
|
+
<Code value={`${webhookUrl}/test`} copyKey="test" />
|
|
95
|
+
<div style={{ marginTop: 4, fontSize: 11, color: '#9ca3af' }}>Test any payload without writing to the CRM.</div>
|
|
96
|
+
</div>
|
|
97
|
+
|
|
98
|
+
<div style={{ borderTop: '1px solid #f3f4f6', paddingTop: 12, marginTop: 4 }}>
|
|
99
|
+
<div style={{ fontWeight: 500, marginBottom: 8, color: '#374151' }}>Quick send</div>
|
|
100
|
+
<pre style={{ background: '#f9fafb', border: '1px solid #e5e7eb', borderRadius: 6, padding: '10px 12px', fontSize: 11, overflow: 'auto', color: '#374151', margin: 0 }}>{`curl -X POST "${webhookUrl}" \\
|
|
101
|
+
-H "Content-Type: application/json" \\
|
|
102
|
+
-d '{"first_name":"Jane","email":"jane@co.com","company":"Acme"}'`}</pre>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export default defineFrontComponent({
|
|
109
|
+
universalIdentifier: 'c9d2e8f3-1b4a-4c7d-9e5f-6a7b8c0d1e2f',
|
|
110
|
+
name: 'intake-source-panel',
|
|
111
|
+
description: 'Shows the webhook URL, signing secret, and quick-send snippet for an Intake Source.',
|
|
112
|
+
component: SourcePanel,
|
|
113
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["twenty-sdk-define-stub:__twenty-sdk-define-stub__", "../../../../src/logic-functions/health.ts", "../../../../src/constants/universal-identifiers.ts"],
|
|
4
|
-
"sourcesContent": ["\n// Auto-generated stub for twenty-sdk/define injected by the SDK CLI build.\n// Real implementations would pull in zod, twenty-shared and ~1MB of code; at\n// runtime only `default.config.handler` is consumed, so tiny no-ops suffice.\nconst __defineFactoryStub = (config) => ({\n success: true,\n config,\n errors: [],\n});\n\nconst __anyHandler = {\n get(_target, prop) {\n if (prop === '__esModule') return true;\n if (prop === Symbol.toPrimitive) return () => '';\n if (typeof prop === 'symbol') return undefined;\n return new Proxy(() => undefined, __anyHandler);\n },\n apply() {\n return new Proxy(() => undefined, __anyHandler);\n },\n};\nconst __anyStub = new Proxy(() => undefined, __anyHandler);\n\nexport const createValidationResult = __defineFactoryStub;\nexport const defineAgent = __defineFactoryStub;\nexport const defineApplication = __defineFactoryStub;\nexport const defineApplicationRole = __defineFactoryStub;\nexport const defineCommandMenuItem = __defineFactoryStub;\nexport const defineConnectionProvider = __defineFactoryStub;\nexport const defineField = __defineFactoryStub;\nexport const defineFrontComponent = __defineFactoryStub;\nexport const defineLogicFunction = __defineFactoryStub;\nexport const defineNavigationMenuItem = __defineFactoryStub;\nexport const defineObject = __defineFactoryStub;\nexport const definePageLayout = __defineFactoryStub;\nexport const definePageLayoutTab = __defineFactoryStub;\nexport const definePostInstallLogicFunction = __defineFactoryStub;\nexport const definePreInstallLogicFunction = __defineFactoryStub;\nexport const defineRole = __defineFactoryStub;\nexport const defineSkill = __defineFactoryStub;\nexport const defineView = __defineFactoryStub;\nexport const AggregateOperations = __anyStub;\nexport const DateDisplayFormat = __anyStub;\nexport const FieldMetadataSettingsOnClickAction = __anyStub;\nexport const FieldType = __anyStub;\nexport const HTTPMethod = __anyStub;\nexport const NavigationMenuItemType = __anyStub;\nexport const NumberDataType = __anyStub;\nexport const ObjectRecordGroupByDateGranularity = __anyStub;\nexport const OnDeleteAction = __anyStub;\nexport const PageLayoutTabLayoutMode = __anyStub;\nexport const
|
|
5
|
-
"mappings": ";;;;AAIA,IAAM,sBAAsB,CAAC,YAAY;AAAA,EACvC,SAAS;AAAA,EACT;AAAA,EACA,QAAQ,CAAC;AACX;AAEA,IAAM,eAAe;AAAA,EACnB,IAAI,SAAS,MAAM;AACjB,QAAI,SAAS,aAAc,QAAO;AAClC,QAAI,SAAS,OAAO,YAAa,QAAO,MAAM;AAC9C,QAAI,OAAO,SAAS,SAAU,QAAO;AACrC,WAAO,IAAI,MAAM,MAAM,QAAW,YAAY;AAAA,EAChD;AAAA,EACA,QAAQ;AACN,WAAO,IAAI,MAAM,MAAM,QAAW,YAAY;AAAA,EAChD;AACF;AACA,IAAM,YAAY,IAAI,MAAM,MAAM,QAAW,YAAY;
|
|
4
|
+
"sourcesContent": ["\n// Auto-generated stub for twenty-sdk/define injected by the SDK CLI build.\n// Real implementations would pull in zod, twenty-shared and ~1MB of code; at\n// runtime only `default.config.handler` is consumed, so tiny no-ops suffice.\nconst __defineFactoryStub = (config) => ({\n success: true,\n config,\n errors: [],\n});\n\nconst __anyHandler = {\n get(_target, prop) {\n if (prop === '__esModule') return true;\n if (prop === Symbol.toPrimitive) return () => '';\n if (typeof prop === 'symbol') return undefined;\n return new Proxy(() => undefined, __anyHandler);\n },\n apply() {\n return new Proxy(() => undefined, __anyHandler);\n },\n};\nconst __anyStub = new Proxy(() => undefined, __anyHandler);\n\nexport const createValidationResult = __defineFactoryStub;\nexport const defineAgent = __defineFactoryStub;\nexport const defineApplication = __defineFactoryStub;\nexport const defineApplicationRole = __defineFactoryStub;\nexport const defineCommandMenuItem = __defineFactoryStub;\nexport const defineConnectionProvider = __defineFactoryStub;\nexport const defineField = __defineFactoryStub;\nexport const defineFrontComponent = __defineFactoryStub;\nexport const defineIndex = __defineFactoryStub;\nexport const defineLogicFunction = __defineFactoryStub;\nexport const defineNavigationMenuItem = __defineFactoryStub;\nexport const defineObject = __defineFactoryStub;\nexport const definePageLayout = __defineFactoryStub;\nexport const definePageLayoutTab = __defineFactoryStub;\nexport const definePermissionFlag = __defineFactoryStub;\nexport const definePostInstallLogicFunction = __defineFactoryStub;\nexport const definePreInstallLogicFunction = __defineFactoryStub;\nexport const defineRole = __defineFactoryStub;\nexport const defineSettingsFrontComponent = __defineFactoryStub;\nexport const defineSkill = __defineFactoryStub;\nexport const defineUninstallLogicFunction = __defineFactoryStub;\nexport const defineView = __defineFactoryStub;\nexport const defineViewField = __defineFactoryStub;\nexport const AggregateOperations = __anyStub;\nexport const DateDisplayFormat = __anyStub;\nexport const FieldMetadataSettingsOnClickAction = __anyStub;\nexport const FieldType = __anyStub;\nexport const HTTPMethod = __anyStub;\nexport const NavigationMenuItemType = __anyStub;\nexport const NumberDataType = __anyStub;\nexport const ObjectOpenRecordIn = __anyStub;\nexport const ObjectRecordGroupByDateGranularity = __anyStub;\nexport const OnDeleteAction = __anyStub;\nexport const PageLayoutTabLayoutMode = __anyStub;\nexport const PageLayoutType = __anyStub;\nexport const RelationType = __anyStub;\nexport const RowLevelPermissionPredicateGroupLogicalOperator = __anyStub;\nexport const RowLevelPermissionPredicateOperand = __anyStub;\nexport const STANDARD_OBJECT = __anyStub;\nexport const STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS = __anyStub;\nexport const STANDARD_PAGE_LAYOUT = __anyStub;\nexport const STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS = __anyStub;\nexport const SystemPermissionFlag = __anyStub;\nexport const ViewCalendarLayout = __anyStub;\nexport const ViewFilterGroupLogicalOperator = __anyStub;\nexport const ViewFilterOperand = __anyStub;\nexport const ViewKey = __anyStub;\nexport const ViewOpenRecordIn = __anyStub;\nexport const ViewSortDirection = __anyStub;\nexport const ViewType = __anyStub;\nexport const ViewVisibility = __anyStub;\nexport const canAccessFullAdminPanel = __anyStub;\nexport const canImpersonate = __anyStub;\nexport const every = __anyStub;\nexport const everyDefined = __anyStub;\nexport const everyEquals = __anyStub;\nexport const favoriteRecordIds = __anyStub;\nexport const featureFlags = __anyStub;\nexport const getFieldUniversalIdentifier = __anyStub;\nexport const getSystemRelationFieldUniversalIdentifier = __anyStub;\nexport const getSystemViewFieldUniversalIdentifier = __anyStub;\nexport const getSystemViewUniversalIdentifier = __anyStub;\nexport const hasAnySoftDeleteFilterOnView = __anyStub;\nexport const includes = __anyStub;\nexport const includesEvery = __anyStub;\nexport const isDashboardPageLayoutInEditMode = __anyStub;\nexport const isDefined = __anyStub;\nexport const isInSidePanel = __anyStub;\nexport const isLayoutCustomizationModeEnabled = __anyStub;\nexport const isNonEmptyString = __anyStub;\nexport const isSelectAll = __anyStub;\nexport const none = __anyStub;\nexport const noneDefined = __anyStub;\nexport const noneEquals = __anyStub;\nexport const numberOfSelectedRecords = __anyStub;\nexport const objectMetadataItem = __anyStub;\nexport const objectMetadataLabel = __anyStub;\nexport const objectPermissions = __anyStub;\nexport const pageType = __anyStub;\nexport const selectedRecords = __anyStub;\nexport const some = __anyStub;\nexport const someDefined = __anyStub;\nexport const someEquals = __anyStub;\nexport const someNonEmptyString = __anyStub;\nexport const targetObjectReadPermissions = __anyStub;\nexport const targetObjectWritePermissions = __anyStub;\nexport const validateFields = __anyStub;\n", "import { defineLogicFunction } from 'twenty-sdk/define';\nimport { CoreApiClient } from 'twenty-client-sdk/core';\nimport { IDS } from '../constants/universal-identifiers';\n\nconst handler = async () => {\n try {\n const client = new CoreApiClient();\n await client.query({ intakeSources: { edges: { node: { id: true } } } });\n return { statusCode: 200, body: { status: 'ok', timestamp: new Date().toISOString() } };\n } catch (e) {\n return { statusCode: 503, body: { status: 'error', message: String(e) } };\n }\n};\n\nexport default defineLogicFunction({\n universalIdentifier: IDS.HEALTH_LOGIC_FUNCTION,\n name: 'intake-health',\n description: 'Health check for the Intake app.',\n timeoutSeconds: 5,\n handler,\n httpRouteTriggerSettings: {\n path: '/intake/health',\n httpMethod: 'GET',\n isAuthRequired: false,\n },\n});\n", "export const IDS = {\n APPLICATION: '96a389dd-384a-43a1-a376-9d3cf01b6ab7',\n DEFAULT_ROLE: '3dd2cdb0-32c7-44b4-9f7b-0a9c667ec249',\n\n // Objects\n INTAKE_SOURCE_OBJECT: '8c1c227c-db7d-4834-b328-cacbe8af0bfc',\n INTAKE_LOG_OBJECT: 'b77a2439-1503-4ef6-ab8b-8f4293bc19da',\n INTAKE_FIELD_RULE_OBJECT: 'e74df817-ff25-4bfb-bdf0-651662ca327e',\n\n // IntakeSource fields\n INTAKE_SOURCE_NAME: '0deb7e4b-bb90-4c86-b903-5208b42041e2',\n INTAKE_SOURCE_SLUG: '18f7645b-2dce-4145-b1b6-9cc3332c0578',\n INTAKE_SOURCE_TARGET_OBJECT: '65f3d7f7-a601-4375-9c67-684d0044620b',\n INTAKE_SOURCE_SECRET: '4c919402-07f5-4041-ade6-a42402f1881f',\n INTAKE_SOURCE_STATUS: '6f1fa6fe-02b4-48ab-b1ee-97c600bbf483',\n INTAKE_SOURCE_CREATE_OPP: '67ae59aa-2275-4ef2-b594-137f6bfafd99',\n INTAKE_SOURCE_OPP_TEMPLATE: '3907b346-77a6-4c79-a4d7-6e1cc20bb82b',\n INTAKE_SOURCE_TOTAL_INGESTED: '69655a93-bc70-48bd-874e-aeccc63ee1a8',\n INTAKE_SOURCE_TOTAL_FAILED: '27b3a499-183e-4cc3-8e40-938f953a986d',\n INTAKE_SOURCE_LAST_INGESTED: 'e167f628-9865-468e-894a-b4b6150cec26',\n\n // IntakeLog fields\n INTAKE_LOG_SOURCE_RELATION: 'b1c623ea-7a9a-47a5-bee4-35fdd668a637',\n INTAKE_LOG_STATUS: '8b6d26d1-f89c-486c-a617-9f9f6a9ca36d',\n INTAKE_LOG_PAYLOAD_HASH: 'c43bb1ad-e78a-4c60-bd95-02475ede7f44',\n INTAKE_LOG_RECORD_ID: '77a55116-a64b-4954-8369-341022dd5bdd',\n INTAKE_LOG_RECORD_TYPE: '02154e96-5808-48ed-8838-51083f174550',\n INTAKE_LOG_FIELDS_CREATED: '2295ca2b-b559-4b38-9771-12dd8d3d76d7',\n INTAKE_LOG_FIELDS_MATCHED: '83d07a0f-5bcd-4fb1-9759-f9b04b9381d8',\n INTAKE_LOG_OVERFLOW_COUNT: '4090acb1-f48a-4957-bb91-c642f8f434a7',\n INTAKE_LOG_PROCESSING_MS: 'a9191f27-4b40-42e9-8cce-20c75900e12c',\n INTAKE_LOG_ERROR: '8d9979f1-9774-43a8-b958-d1b37bd4ecc7',\n INTAKE_LOG_PROCESSED_AT: 'a8d99fbd-722e-4987-8afe-b830ac026f0a',\n\n // IntakeFieldRule fields\n INTAKE_FIELD_RULE_SOURCE_RELATION: '396dd1f1-3873-4efa-a6bb-7b41eda14d83',\n INTAKE_FIELD_RULE_INPUT_PATTERN: '757201a0-9f64-47b9-bf3a-a793074bf84e',\n INTAKE_FIELD_RULE_CANONICAL_NAME: 'fafebc55-a2cc-4c9f-975b-050c2676659d',\n INTAKE_FIELD_RULE_FIELD_TYPE: '72d1f6d8-d63e-419e-a244-8127d7b4705f',\n INTAKE_FIELD_RULE_PRIORITY: '911df299-940b-44f9-92f7-3fd01fde01a7',\n INTAKE_FIELD_RULE_IS_ACTIVE: '6f9cd96d-aecd-437f-a7b5-d45548782771',\n\n // Logic Functions\n WEBHOOK_LOGIC_FUNCTION: '8323c257-48fe-4bb6-b1dd-166844f575c0',\n HEALTH_LOGIC_FUNCTION: '4ece4e39-286b-4820-81f4-b7beee71d092',\n REGISTER_LOGIC_FUNCTION: '56d46e18-6cae-4d99-9893-b7d70740c9db',\n\n // IntakeSource reverse relation fields\n INTAKE_SOURCE_LOGS_REVERSE: '6ecfa369-22e0-4812-bb75-4338cea8be65',\n INTAKE_SOURCE_RULES_REVERSE: '776fefaf-0eee-4ec0-93e0-d11bd80c1e20',\n\n // v0.2.0 \u2014 rawPayload + retry + post-install + UI\n INTAKE_LOG_RAW_PAYLOAD: 'b65c9e1c-f2c1-4c52-a5b3-1e1ac3139692',\n RETRY_LOGIC_FUNCTION: '90f20396-a69a-4ae4-af20-6be65c565bb5',\n POST_INSTALL_LOGIC_FUNCTION: 'e836fdd6-fd54-4aa4-ba6d-87d5a02148ef',\n LOG_RETRY_FRONT_COMPONENT: '0cb97f6d-30e2-48c8-90e1-241b82870b4d',\n FIELD_RULES_TABLE_FRONT_COMPONENT: 'c35faad9-3468-4746-9c1c-b13d73e5e09b',\n SOURCE_PAGE_LAYOUT: '8c894ba8-5c8c-45ed-b0af-1d22ede93eb9',\n SOURCE_LAYOUT_TAB_RULES: '80f5c49a-083c-4062-8bd5-ae158abb6c33',\n SOURCE_LAYOUT_TAB_RULES_WIDGET: '26a9ebb7-1d59-4b03-a018-56968f6df224',\n LOG_PAGE_LAYOUT: 'b0972802-ede5-463c-9f59-cb05f04957db',\n LOG_LAYOUT_TAB_RETRY: '6b109695-ba3a-43de-a31a-23119ccf87ba',\n LOG_LAYOUT_TAB_RETRY_WIDGET: '11727c1a-5145-46de-9742-88d07d64c72e',\n SAMPLE_RULE_PHONE: '8440da8d-8ac4-4f4b-baa2-4d62936cde35',\n SAMPLE_RULE_EMAIL: 'd123cde4-05aa-44e1-86f6-454be1d7048b',\n SAMPLE_RULE_FULLNAME: '00e49fe8-1b48-4f5b-aaf4-414d0923c712',\n\n // Views & Navigation\n SOURCES_VIEW: '4435cd43-5c13-472f-b4f9-b686c0f46285',\n LOGS_VIEW: 'a2cd0a93-9778-4ba5-99d9-b8b89c4fb39d',\n INTAKE_NAV_ITEM: 'e81bf288-5686-4713-86ae-e2947b124cab',\n} as const;\n"],
|
|
5
|
+
"mappings": ";;;;AAIA,IAAM,sBAAsB,CAAC,YAAY;AAAA,EACvC,SAAS;AAAA,EACT;AAAA,EACA,QAAQ,CAAC;AACX;AAEA,IAAM,eAAe;AAAA,EACnB,IAAI,SAAS,MAAM;AACjB,QAAI,SAAS,aAAc,QAAO;AAClC,QAAI,SAAS,OAAO,YAAa,QAAO,MAAM;AAC9C,QAAI,OAAO,SAAS,SAAU,QAAO;AACrC,WAAO,IAAI,MAAM,MAAM,QAAW,YAAY;AAAA,EAChD;AAAA,EACA,QAAQ;AACN,WAAO,IAAI,MAAM,MAAM,QAAW,YAAY;AAAA,EAChD;AACF;AACA,IAAM,YAAY,IAAI,MAAM,MAAM,QAAW,YAAY;AAWlD,IAAM,sBAAsB;;;AC/BnC,SAAS,qBAAqB;;;ACDvB,IAAM,MAAM;AAAA,EACjB,aAAa;AAAA,EACb,cAAc;AAAA;AAAA,EAGd,sBAAsB;AAAA,EACtB,mBAAmB;AAAA,EACnB,0BAA0B;AAAA;AAAA,EAG1B,oBAAoB;AAAA,EACpB,oBAAoB;AAAA,EACpB,6BAA6B;AAAA,EAC7B,sBAAsB;AAAA,EACtB,sBAAsB;AAAA,EACtB,0BAA0B;AAAA,EAC1B,4BAA4B;AAAA,EAC5B,8BAA8B;AAAA,EAC9B,4BAA4B;AAAA,EAC5B,6BAA6B;AAAA;AAAA,EAG7B,4BAA4B;AAAA,EAC5B,mBAAmB;AAAA,EACnB,yBAAyB;AAAA,EACzB,sBAAsB;AAAA,EACtB,wBAAwB;AAAA,EACxB,2BAA2B;AAAA,EAC3B,2BAA2B;AAAA,EAC3B,2BAA2B;AAAA,EAC3B,0BAA0B;AAAA,EAC1B,kBAAkB;AAAA,EAClB,yBAAyB;AAAA;AAAA,EAGzB,mCAAmC;AAAA,EACnC,iCAAiC;AAAA,EACjC,kCAAkC;AAAA,EAClC,8BAA8B;AAAA,EAC9B,4BAA4B;AAAA,EAC5B,6BAA6B;AAAA;AAAA,EAG7B,wBAAwB;AAAA,EACxB,uBAAuB;AAAA,EACvB,yBAAyB;AAAA;AAAA,EAGzB,4BAA4B;AAAA,EAC5B,6BAA6B;AAAA;AAAA,EAG7B,wBAAwB;AAAA,EACxB,sBAAsB;AAAA,EACtB,6BAA6B;AAAA,EAC7B,2BAA2B;AAAA,EAC3B,mCAAmC;AAAA,EACnC,oBAAoB;AAAA,EACpB,yBAAyB;AAAA,EACzB,gCAAgC;AAAA,EAChC,iBAAiB;AAAA,EACjB,sBAAsB;AAAA,EACtB,6BAA6B;AAAA,EAC7B,mBAAmB;AAAA,EACnB,mBAAmB;AAAA,EACnB,sBAAsB;AAAA;AAAA,EAGtB,cAAc;AAAA,EACd,WAAW;AAAA,EACX,iBAAiB;AACnB;;;ADnEA,IAAM,UAAU,YAAY;AAC1B,MAAI;AACF,UAAM,SAAS,IAAI,cAAc;AACjC,UAAM,OAAO,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,KAAK,EAAE,EAAE,EAAE,CAAC;AACvE,WAAO,EAAE,YAAY,KAAK,MAAM,EAAE,QAAQ,MAAM,YAAW,oBAAI,KAAK,GAAE,YAAY,EAAE,EAAE;AAAA,EACxF,SAAS,GAAG;AACV,WAAO,EAAE,YAAY,KAAK,MAAM,EAAE,QAAQ,SAAS,SAAS,OAAO,CAAC,EAAE,EAAE;AAAA,EAC1E;AACF;AAEA,IAAO,iBAAQ,oBAAoB;AAAA,EACjC,qBAAqB,IAAI;AAAA,EACzB,MAAM;AAAA,EACN,aAAa;AAAA,EACb,gBAAgB;AAAA,EAChB;AAAA,EACA,0BAA0B;AAAA,IACxB,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,gBAAgB;AAAA,EAClB;AACF,CAAC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineLogicFunction } from 'twenty-sdk/define';
|
|
2
|
+
import { CoreApiClient } from 'twenty-client-sdk/core';
|
|
3
|
+
import { IDS } from '../constants/universal-identifiers';
|
|
4
|
+
|
|
5
|
+
const handler = async () => {
|
|
6
|
+
try {
|
|
7
|
+
const client = new CoreApiClient();
|
|
8
|
+
await client.query({ intakeSources: { edges: { node: { id: true } } } });
|
|
9
|
+
return { statusCode: 200, body: { status: 'ok', timestamp: new Date().toISOString() } };
|
|
10
|
+
} catch (e) {
|
|
11
|
+
return { statusCode: 503, body: { status: 'error', message: String(e) } };
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export default defineLogicFunction({
|
|
16
|
+
universalIdentifier: IDS.HEALTH_LOGIC_FUNCTION,
|
|
17
|
+
name: 'intake-health',
|
|
18
|
+
description: 'Health check for the Intake app.',
|
|
19
|
+
timeoutSeconds: 5,
|
|
20
|
+
handler,
|
|
21
|
+
httpRouteTriggerSettings: {
|
|
22
|
+
path: '/intake/health',
|
|
23
|
+
httpMethod: 'GET',
|
|
24
|
+
isAuthRequired: false,
|
|
25
|
+
},
|
|
26
|
+
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["twenty-sdk-define-stub:__twenty-sdk-define-stub__", "../../../../src/logic-functions/register.ts", "../../../../src/constants/universal-identifiers.ts"],
|
|
4
|
-
"sourcesContent": ["\n// Auto-generated stub for twenty-sdk/define injected by the SDK CLI build.\n// Real implementations would pull in zod, twenty-shared and ~1MB of code; at\n// runtime only `default.config.handler` is consumed, so tiny no-ops suffice.\nconst __defineFactoryStub = (config) => ({\n success: true,\n config,\n errors: [],\n});\n\nconst __anyHandler = {\n get(_target, prop) {\n if (prop === '__esModule') return true;\n if (prop === Symbol.toPrimitive) return () => '';\n if (typeof prop === 'symbol') return undefined;\n return new Proxy(() => undefined, __anyHandler);\n },\n apply() {\n return new Proxy(() => undefined, __anyHandler);\n },\n};\nconst __anyStub = new Proxy(() => undefined, __anyHandler);\n\nexport const createValidationResult = __defineFactoryStub;\nexport const defineAgent = __defineFactoryStub;\nexport const defineApplication = __defineFactoryStub;\nexport const defineApplicationRole = __defineFactoryStub;\nexport const defineCommandMenuItem = __defineFactoryStub;\nexport const defineConnectionProvider = __defineFactoryStub;\nexport const defineField = __defineFactoryStub;\nexport const defineFrontComponent = __defineFactoryStub;\nexport const defineLogicFunction = __defineFactoryStub;\nexport const defineNavigationMenuItem = __defineFactoryStub;\nexport const defineObject = __defineFactoryStub;\nexport const definePageLayout = __defineFactoryStub;\nexport const definePageLayoutTab = __defineFactoryStub;\nexport const definePostInstallLogicFunction = __defineFactoryStub;\nexport const definePreInstallLogicFunction = __defineFactoryStub;\nexport const defineRole = __defineFactoryStub;\nexport const defineSkill = __defineFactoryStub;\nexport const defineView = __defineFactoryStub;\nexport const AggregateOperations = __anyStub;\nexport const DateDisplayFormat = __anyStub;\nexport const FieldMetadataSettingsOnClickAction = __anyStub;\nexport const FieldType = __anyStub;\nexport const HTTPMethod = __anyStub;\nexport const NavigationMenuItemType = __anyStub;\nexport const NumberDataType = __anyStub;\nexport const ObjectRecordGroupByDateGranularity = __anyStub;\nexport const OnDeleteAction = __anyStub;\nexport const PageLayoutTabLayoutMode = __anyStub;\nexport const PermissionFlag = __anyStub;\nexport const RelationType = __anyStub;\nexport const STANDARD_OBJECT = __anyStub;\nexport const STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS = __anyStub;\nexport const ViewCalendarLayout = __anyStub;\nexport const ViewFilterGroupLogicalOperator = __anyStub;\nexport const ViewFilterOperand = __anyStub;\nexport const ViewKey = __anyStub;\nexport const ViewOpenRecordIn = __anyStub;\nexport const ViewSortDirection = __anyStub;\nexport const ViewType = __anyStub;\nexport const ViewVisibility = __anyStub;\nexport const generateDefaultFieldUniversalIdentifier = __anyStub;\nexport const validateFields = __anyStub;\n", "import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define';\nimport { CoreApiClient } from 'twenty-client-sdk/core';\nimport { IDS } from '../constants/universal-identifiers';\n\nconst ALLOWED_TARGET_OBJECTS = ['PERSON', 'COMPANY', 'AUTO'] as const;\n\nconst handler = async (params: RoutePayload) => {\n const body = params.body as Record<string, unknown> | null;\n if (!body) return { statusCode: 400, body: { error: 'Missing request body' } };\n\n const name = body['name'] as string | undefined;\n const slug = body['slug'] as string | undefined;\n const targetObject = (body['targetObject'] as string | undefined) ?? 'AUTO';\n const createOpportunity = (body['createOpportunity'] as boolean | undefined) ?? true;\n const opportunityNameTemplate = (body['opportunityNameTemplate'] as string | undefined)\n ?? '{{source}} \u2014 {{firstName}} {{lastName}}';\n\n if (!name || !slug) {\n return { statusCode: 400, body: { error: 'name and slug are required' } };\n }\n if (!/^[a-z0-9-]+$/.test(slug)) {\n return { statusCode: 400, body: { error: 'slug must be lowercase letters, numbers, and hyphens only' } };\n }\n if (!ALLOWED_TARGET_OBJECTS.includes(targetObject as typeof ALLOWED_TARGET_OBJECTS[number])) {\n return { statusCode: 400, body: { error: 'targetObject must be PERSON, COMPANY, or AUTO' } };\n }\n\n const client = new CoreApiClient();\n\n // Check for existing source with same slug (idempotent registration)\n const existing = await client.query({\n intakeSources: {\n __args: { filter: { slug: { eq: slug } } },\n edges: { node: { id: true, slug: true } },\n },\n });\n\n const existingNode = existing.intakeSources?.edges?.[0]?.node;\n\n if (existingNode) {\n return {\n statusCode: 200,\n body: {\n id: existingNode.id,\n slug: existingNode.slug,\n webhookUrl: buildWebhookUrl(slug),\n message: 'Source already exists \u2014 returning existing registration.',\n },\n };\n }\n\n // Generate a signing secret\n const { webcrypto } = await import('node:crypto');\n const secretBytes = webcrypto.getRandomValues(new Uint8Array(32));\n const secret = Array.from(secretBytes).map((b) => b.toString(16).padStart(2, '0')).join('');\n\n const created = await client.mutation({\n createIntakeSource: {\n __args: {\n data: {\n name,\n slug,\n targetObject,\n webhookSecret: secret,\n status: 'ACTIVE',\n createOpportunity,\n opportunityNameTemplate,\n totalIngested: 0,\n totalFailed: 0,\n },\n },\n id: true,\n slug: true,\n },\n });\n\n return {\n statusCode: 201,\n body: {\n id: created.createIntakeSource.id,\n slug: created.createIntakeSource.slug,\n webhookUrl: buildWebhookUrl(slug),\n secret,\n message: 'Source registered. Sign all requests with X-Webhook-Signature using the secret.',\n },\n };\n};\n\nfunction buildWebhookUrl(slug: string): string {\n const baseUrl = process.env['TWENTY_API_URL'] ?? 'https://your-crm.com';\n return `${baseUrl}/s/intake/${slug}`;\n}\n\nexport default defineLogicFunction({\n universalIdentifier: IDS.REGISTER_LOGIC_FUNCTION,\n name: 'intake-register',\n description: 'Programmatically register a new Intake source and get back a webhook URL + signing secret.',\n timeoutSeconds: 10,\n handler,\n httpRouteTriggerSettings: {\n path: '/intake/sources/register',\n httpMethod: 'POST',\n isAuthRequired: true,\n },\n});\n", "export const IDS = {\n APPLICATION: '96a389dd-384a-43a1-a376-9d3cf01b6ab7',\n DEFAULT_ROLE: '3dd2cdb0-32c7-44b4-9f7b-0a9c667ec249',\n\n // Objects\n INTAKE_SOURCE_OBJECT: '8c1c227c-db7d-4834-b328-cacbe8af0bfc',\n INTAKE_LOG_OBJECT: 'b77a2439-1503-4ef6-ab8b-8f4293bc19da',\n INTAKE_FIELD_RULE_OBJECT: 'e74df817-ff25-4bfb-bdf0-651662ca327e',\n\n // IntakeSource fields\n INTAKE_SOURCE_NAME: '0deb7e4b-bb90-4c86-b903-5208b42041e2',\n INTAKE_SOURCE_SLUG: '18f7645b-2dce-4145-b1b6-9cc3332c0578',\n INTAKE_SOURCE_TARGET_OBJECT: '65f3d7f7-a601-4375-9c67-684d0044620b',\n INTAKE_SOURCE_SECRET: '4c919402-07f5-4041-ade6-a42402f1881f',\n INTAKE_SOURCE_STATUS: '6f1fa6fe-02b4-48ab-b1ee-97c600bbf483',\n INTAKE_SOURCE_CREATE_OPP: '67ae59aa-2275-4ef2-b594-137f6bfafd99',\n INTAKE_SOURCE_OPP_TEMPLATE: '3907b346-77a6-4c79-a4d7-6e1cc20bb82b',\n INTAKE_SOURCE_TOTAL_INGESTED: '69655a93-bc70-48bd-874e-aeccc63ee1a8',\n INTAKE_SOURCE_TOTAL_FAILED: '27b3a499-183e-4cc3-8e40-938f953a986d',\n INTAKE_SOURCE_LAST_INGESTED: 'e167f628-9865-468e-894a-b4b6150cec26',\n\n // IntakeLog fields\n INTAKE_LOG_SOURCE_RELATION: 'b1c623ea-7a9a-47a5-bee4-35fdd668a637',\n INTAKE_LOG_STATUS: '8b6d26d1-f89c-486c-a617-9f9f6a9ca36d',\n INTAKE_LOG_PAYLOAD_HASH: 'c43bb1ad-e78a-4c60-bd95-02475ede7f44',\n INTAKE_LOG_RECORD_ID: '77a55116-a64b-4954-8369-341022dd5bdd',\n INTAKE_LOG_RECORD_TYPE: '02154e96-5808-48ed-8838-51083f174550',\n INTAKE_LOG_FIELDS_CREATED: '2295ca2b-b559-4b38-9771-12dd8d3d76d7',\n INTAKE_LOG_FIELDS_MATCHED: '83d07a0f-5bcd-4fb1-9759-f9b04b9381d8',\n INTAKE_LOG_OVERFLOW_COUNT: '4090acb1-f48a-4957-bb91-c642f8f434a7',\n INTAKE_LOG_PROCESSING_MS: 'a9191f27-4b40-42e9-8cce-20c75900e12c',\n INTAKE_LOG_ERROR: '8d9979f1-9774-43a8-b958-d1b37bd4ecc7',\n INTAKE_LOG_PROCESSED_AT: 'a8d99fbd-722e-4987-8afe-b830ac026f0a',\n\n // IntakeFieldRule fields\n INTAKE_FIELD_RULE_SOURCE_RELATION: '396dd1f1-3873-4efa-a6bb-7b41eda14d83',\n INTAKE_FIELD_RULE_INPUT_PATTERN: '757201a0-9f64-47b9-bf3a-a793074bf84e',\n INTAKE_FIELD_RULE_CANONICAL_NAME: 'fafebc55-a2cc-4c9f-975b-050c2676659d',\n INTAKE_FIELD_RULE_FIELD_TYPE: '72d1f6d8-d63e-419e-a244-8127d7b4705f',\n INTAKE_FIELD_RULE_PRIORITY: '911df299-940b-44f9-92f7-3fd01fde01a7',\n INTAKE_FIELD_RULE_IS_ACTIVE: '6f9cd96d-aecd-437f-a7b5-d45548782771',\n\n // Logic Functions\n WEBHOOK_LOGIC_FUNCTION: '8323c257-48fe-4bb6-b1dd-166844f575c0',\n HEALTH_LOGIC_FUNCTION: '4ece4e39-286b-4820-81f4-b7beee71d092',\n REGISTER_LOGIC_FUNCTION: '56d46e18-6cae-4d99-9893-b7d70740c9db',\n\n // IntakeSource reverse relation fields\n INTAKE_SOURCE_LOGS_REVERSE: '6ecfa369-22e0-4812-bb75-4338cea8be65',\n INTAKE_SOURCE_RULES_REVERSE: '776fefaf-0eee-4ec0-93e0-d11bd80c1e20',\n\n // v0.2.0 \u2014 rawPayload + retry + post-install + UI\n INTAKE_LOG_RAW_PAYLOAD: 'b65c9e1c-f2c1-4c52-a5b3-1e1ac3139692',\n RETRY_LOGIC_FUNCTION: '90f20396-a69a-4ae4-af20-6be65c565bb5',\n POST_INSTALL_LOGIC_FUNCTION: 'e836fdd6-fd54-4aa4-ba6d-87d5a02148ef',\n LOG_RETRY_FRONT_COMPONENT: '0cb97f6d-30e2-48c8-90e1-241b82870b4d',\n FIELD_RULES_TABLE_FRONT_COMPONENT: 'c35faad9-3468-4746-9c1c-b13d73e5e09b',\n SOURCE_PAGE_LAYOUT: '8c894ba8-5c8c-45ed-b0af-1d22ede93eb9',\n SOURCE_LAYOUT_TAB_RULES: '80f5c49a-083c-4062-8bd5-ae158abb6c33',\n SOURCE_LAYOUT_TAB_RULES_WIDGET: '26a9ebb7-1d59-4b03-a018-56968f6df224',\n LOG_PAGE_LAYOUT: 'b0972802-ede5-463c-9f59-cb05f04957db',\n LOG_LAYOUT_TAB_RETRY: '6b109695-ba3a-43de-a31a-23119ccf87ba',\n LOG_LAYOUT_TAB_RETRY_WIDGET: '11727c1a-5145-46de-9742-88d07d64c72e',\n SAMPLE_RULE_PHONE: '8440da8d-8ac4-4f4b-baa2-4d62936cde35',\n SAMPLE_RULE_EMAIL: 'd123cde4-05aa-44e1-86f6-454be1d7048b',\n SAMPLE_RULE_FULLNAME: '00e49fe8-1b48-4f5b-aaf4-414d0923c712',\n\n // Views & Navigation\n SOURCES_VIEW: '4435cd43-5c13-472f-b4f9-b686c0f46285',\n LOGS_VIEW: 'a2cd0a93-9778-4ba5-99d9-b8b89c4fb39d',\n INTAKE_NAV_ITEM: 'e81bf288-5686-4713-86ae-e2947b124cab',\n} as const;\n"],
|
|
5
|
-
"mappings": ";;;;AAIA,IAAM,sBAAsB,CAAC,YAAY;AAAA,EACvC,SAAS;AAAA,EACT;AAAA,EACA,QAAQ,CAAC;AACX;AAEA,IAAM,eAAe;AAAA,EACnB,IAAI,SAAS,MAAM;AACjB,QAAI,SAAS,aAAc,QAAO;AAClC,QAAI,SAAS,OAAO,YAAa,QAAO,MAAM;AAC9C,QAAI,OAAO,SAAS,SAAU,QAAO;AACrC,WAAO,IAAI,MAAM,MAAM,QAAW,YAAY;AAAA,EAChD;AAAA,EACA,QAAQ;AACN,WAAO,IAAI,MAAM,MAAM,QAAW,YAAY;AAAA,EAChD;AACF;AACA,IAAM,YAAY,IAAI,MAAM,MAAM,QAAW,YAAY;
|
|
4
|
+
"sourcesContent": ["\n// Auto-generated stub for twenty-sdk/define injected by the SDK CLI build.\n// Real implementations would pull in zod, twenty-shared and ~1MB of code; at\n// runtime only `default.config.handler` is consumed, so tiny no-ops suffice.\nconst __defineFactoryStub = (config) => ({\n success: true,\n config,\n errors: [],\n});\n\nconst __anyHandler = {\n get(_target, prop) {\n if (prop === '__esModule') return true;\n if (prop === Symbol.toPrimitive) return () => '';\n if (typeof prop === 'symbol') return undefined;\n return new Proxy(() => undefined, __anyHandler);\n },\n apply() {\n return new Proxy(() => undefined, __anyHandler);\n },\n};\nconst __anyStub = new Proxy(() => undefined, __anyHandler);\n\nexport const createValidationResult = __defineFactoryStub;\nexport const defineAgent = __defineFactoryStub;\nexport const defineApplication = __defineFactoryStub;\nexport const defineApplicationRole = __defineFactoryStub;\nexport const defineCommandMenuItem = __defineFactoryStub;\nexport const defineConnectionProvider = __defineFactoryStub;\nexport const defineField = __defineFactoryStub;\nexport const defineFrontComponent = __defineFactoryStub;\nexport const defineIndex = __defineFactoryStub;\nexport const defineLogicFunction = __defineFactoryStub;\nexport const defineNavigationMenuItem = __defineFactoryStub;\nexport const defineObject = __defineFactoryStub;\nexport const definePageLayout = __defineFactoryStub;\nexport const definePageLayoutTab = __defineFactoryStub;\nexport const definePermissionFlag = __defineFactoryStub;\nexport const definePostInstallLogicFunction = __defineFactoryStub;\nexport const definePreInstallLogicFunction = __defineFactoryStub;\nexport const defineRole = __defineFactoryStub;\nexport const defineSettingsFrontComponent = __defineFactoryStub;\nexport const defineSkill = __defineFactoryStub;\nexport const defineUninstallLogicFunction = __defineFactoryStub;\nexport const defineView = __defineFactoryStub;\nexport const defineViewField = __defineFactoryStub;\nexport const AggregateOperations = __anyStub;\nexport const DateDisplayFormat = __anyStub;\nexport const FieldMetadataSettingsOnClickAction = __anyStub;\nexport const FieldType = __anyStub;\nexport const HTTPMethod = __anyStub;\nexport const NavigationMenuItemType = __anyStub;\nexport const NumberDataType = __anyStub;\nexport const ObjectOpenRecordIn = __anyStub;\nexport const ObjectRecordGroupByDateGranularity = __anyStub;\nexport const OnDeleteAction = __anyStub;\nexport const PageLayoutTabLayoutMode = __anyStub;\nexport const PageLayoutType = __anyStub;\nexport const RelationType = __anyStub;\nexport const RowLevelPermissionPredicateGroupLogicalOperator = __anyStub;\nexport const RowLevelPermissionPredicateOperand = __anyStub;\nexport const STANDARD_OBJECT = __anyStub;\nexport const STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS = __anyStub;\nexport const STANDARD_PAGE_LAYOUT = __anyStub;\nexport const STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS = __anyStub;\nexport const SystemPermissionFlag = __anyStub;\nexport const ViewCalendarLayout = __anyStub;\nexport const ViewFilterGroupLogicalOperator = __anyStub;\nexport const ViewFilterOperand = __anyStub;\nexport const ViewKey = __anyStub;\nexport const ViewOpenRecordIn = __anyStub;\nexport const ViewSortDirection = __anyStub;\nexport const ViewType = __anyStub;\nexport const ViewVisibility = __anyStub;\nexport const canAccessFullAdminPanel = __anyStub;\nexport const canImpersonate = __anyStub;\nexport const every = __anyStub;\nexport const everyDefined = __anyStub;\nexport const everyEquals = __anyStub;\nexport const favoriteRecordIds = __anyStub;\nexport const featureFlags = __anyStub;\nexport const getFieldUniversalIdentifier = __anyStub;\nexport const getSystemRelationFieldUniversalIdentifier = __anyStub;\nexport const getSystemViewFieldUniversalIdentifier = __anyStub;\nexport const getSystemViewUniversalIdentifier = __anyStub;\nexport const hasAnySoftDeleteFilterOnView = __anyStub;\nexport const includes = __anyStub;\nexport const includesEvery = __anyStub;\nexport const isDashboardPageLayoutInEditMode = __anyStub;\nexport const isDefined = __anyStub;\nexport const isInSidePanel = __anyStub;\nexport const isLayoutCustomizationModeEnabled = __anyStub;\nexport const isNonEmptyString = __anyStub;\nexport const isSelectAll = __anyStub;\nexport const none = __anyStub;\nexport const noneDefined = __anyStub;\nexport const noneEquals = __anyStub;\nexport const numberOfSelectedRecords = __anyStub;\nexport const objectMetadataItem = __anyStub;\nexport const objectMetadataLabel = __anyStub;\nexport const objectPermissions = __anyStub;\nexport const pageType = __anyStub;\nexport const selectedRecords = __anyStub;\nexport const some = __anyStub;\nexport const someDefined = __anyStub;\nexport const someEquals = __anyStub;\nexport const someNonEmptyString = __anyStub;\nexport const targetObjectReadPermissions = __anyStub;\nexport const targetObjectWritePermissions = __anyStub;\nexport const validateFields = __anyStub;\n", "import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define';\nimport { CoreApiClient } from 'twenty-client-sdk/core';\nimport { IDS } from '../constants/universal-identifiers';\n\nconst ALLOWED_TARGET_OBJECTS = ['PERSON', 'COMPANY', 'AUTO'] as const;\n\nconst handler = async (params: RoutePayload) => {\n const body = params.body as Record<string, unknown> | null;\n if (!body) return { statusCode: 400, body: { error: 'Missing request body' } };\n\n const name = body['name'] as string | undefined;\n const slug = body['slug'] as string | undefined;\n const targetObject = (body['targetObject'] as string | undefined) ?? 'AUTO';\n const createOpportunity = (body['createOpportunity'] as boolean | undefined) ?? true;\n const opportunityNameTemplate = (body['opportunityNameTemplate'] as string | undefined)\n ?? '{{source}} \u2014 {{firstName}} {{lastName}}';\n\n if (!name || !slug) {\n return { statusCode: 400, body: { error: 'name and slug are required' } };\n }\n if (!/^[a-z0-9-]+$/.test(slug)) {\n return { statusCode: 400, body: { error: 'slug must be lowercase letters, numbers, and hyphens only' } };\n }\n if (!ALLOWED_TARGET_OBJECTS.includes(targetObject as typeof ALLOWED_TARGET_OBJECTS[number])) {\n return { statusCode: 400, body: { error: 'targetObject must be PERSON, COMPANY, or AUTO' } };\n }\n\n const client = new CoreApiClient();\n\n // Check for existing source with same slug (idempotent registration)\n const existing = await client.query({\n intakeSources: {\n __args: { filter: { slug: { eq: slug } } },\n edges: { node: { id: true, slug: true } },\n },\n });\n\n const existingNode = existing.intakeSources?.edges?.[0]?.node;\n\n if (existingNode) {\n return {\n statusCode: 200,\n body: {\n id: existingNode.id,\n slug: existingNode.slug,\n webhookUrl: buildWebhookUrl(slug),\n message: 'Source already exists \u2014 returning existing registration.',\n },\n };\n }\n\n // Generate a signing secret\n const { webcrypto } = await import('node:crypto');\n const secretBytes = webcrypto.getRandomValues(new Uint8Array(32));\n const secret = Array.from(secretBytes).map((b) => b.toString(16).padStart(2, '0')).join('');\n\n const created = await client.mutation({\n createIntakeSource: {\n __args: {\n data: {\n name,\n slug,\n targetObject,\n webhookSecret: secret,\n status: 'ACTIVE',\n createOpportunity,\n opportunityNameTemplate,\n totalIngested: 0,\n totalFailed: 0,\n },\n },\n id: true,\n slug: true,\n },\n });\n\n return {\n statusCode: 201,\n body: {\n id: created.createIntakeSource.id,\n slug: created.createIntakeSource.slug,\n webhookUrl: buildWebhookUrl(slug),\n secret,\n message: 'Source registered. Sign all requests with X-Webhook-Signature using the secret.',\n },\n };\n};\n\nfunction buildWebhookUrl(slug: string): string {\n const baseUrl = process.env['TWENTY_API_URL'] ?? 'https://your-crm.com';\n return `${baseUrl}/s/intake/${slug}`;\n}\n\nexport default defineLogicFunction({\n universalIdentifier: IDS.REGISTER_LOGIC_FUNCTION,\n name: 'intake-register',\n description: 'Programmatically register a new Intake source and get back a webhook URL + signing secret.',\n timeoutSeconds: 10,\n handler,\n httpRouteTriggerSettings: {\n path: '/intake/sources/register',\n httpMethod: 'POST',\n isAuthRequired: true,\n },\n});\n", "export const IDS = {\n APPLICATION: '96a389dd-384a-43a1-a376-9d3cf01b6ab7',\n DEFAULT_ROLE: '3dd2cdb0-32c7-44b4-9f7b-0a9c667ec249',\n\n // Objects\n INTAKE_SOURCE_OBJECT: '8c1c227c-db7d-4834-b328-cacbe8af0bfc',\n INTAKE_LOG_OBJECT: 'b77a2439-1503-4ef6-ab8b-8f4293bc19da',\n INTAKE_FIELD_RULE_OBJECT: 'e74df817-ff25-4bfb-bdf0-651662ca327e',\n\n // IntakeSource fields\n INTAKE_SOURCE_NAME: '0deb7e4b-bb90-4c86-b903-5208b42041e2',\n INTAKE_SOURCE_SLUG: '18f7645b-2dce-4145-b1b6-9cc3332c0578',\n INTAKE_SOURCE_TARGET_OBJECT: '65f3d7f7-a601-4375-9c67-684d0044620b',\n INTAKE_SOURCE_SECRET: '4c919402-07f5-4041-ade6-a42402f1881f',\n INTAKE_SOURCE_STATUS: '6f1fa6fe-02b4-48ab-b1ee-97c600bbf483',\n INTAKE_SOURCE_CREATE_OPP: '67ae59aa-2275-4ef2-b594-137f6bfafd99',\n INTAKE_SOURCE_OPP_TEMPLATE: '3907b346-77a6-4c79-a4d7-6e1cc20bb82b',\n INTAKE_SOURCE_TOTAL_INGESTED: '69655a93-bc70-48bd-874e-aeccc63ee1a8',\n INTAKE_SOURCE_TOTAL_FAILED: '27b3a499-183e-4cc3-8e40-938f953a986d',\n INTAKE_SOURCE_LAST_INGESTED: 'e167f628-9865-468e-894a-b4b6150cec26',\n\n // IntakeLog fields\n INTAKE_LOG_SOURCE_RELATION: 'b1c623ea-7a9a-47a5-bee4-35fdd668a637',\n INTAKE_LOG_STATUS: '8b6d26d1-f89c-486c-a617-9f9f6a9ca36d',\n INTAKE_LOG_PAYLOAD_HASH: 'c43bb1ad-e78a-4c60-bd95-02475ede7f44',\n INTAKE_LOG_RECORD_ID: '77a55116-a64b-4954-8369-341022dd5bdd',\n INTAKE_LOG_RECORD_TYPE: '02154e96-5808-48ed-8838-51083f174550',\n INTAKE_LOG_FIELDS_CREATED: '2295ca2b-b559-4b38-9771-12dd8d3d76d7',\n INTAKE_LOG_FIELDS_MATCHED: '83d07a0f-5bcd-4fb1-9759-f9b04b9381d8',\n INTAKE_LOG_OVERFLOW_COUNT: '4090acb1-f48a-4957-bb91-c642f8f434a7',\n INTAKE_LOG_PROCESSING_MS: 'a9191f27-4b40-42e9-8cce-20c75900e12c',\n INTAKE_LOG_ERROR: '8d9979f1-9774-43a8-b958-d1b37bd4ecc7',\n INTAKE_LOG_PROCESSED_AT: 'a8d99fbd-722e-4987-8afe-b830ac026f0a',\n\n // IntakeFieldRule fields\n INTAKE_FIELD_RULE_SOURCE_RELATION: '396dd1f1-3873-4efa-a6bb-7b41eda14d83',\n INTAKE_FIELD_RULE_INPUT_PATTERN: '757201a0-9f64-47b9-bf3a-a793074bf84e',\n INTAKE_FIELD_RULE_CANONICAL_NAME: 'fafebc55-a2cc-4c9f-975b-050c2676659d',\n INTAKE_FIELD_RULE_FIELD_TYPE: '72d1f6d8-d63e-419e-a244-8127d7b4705f',\n INTAKE_FIELD_RULE_PRIORITY: '911df299-940b-44f9-92f7-3fd01fde01a7',\n INTAKE_FIELD_RULE_IS_ACTIVE: '6f9cd96d-aecd-437f-a7b5-d45548782771',\n\n // Logic Functions\n WEBHOOK_LOGIC_FUNCTION: '8323c257-48fe-4bb6-b1dd-166844f575c0',\n HEALTH_LOGIC_FUNCTION: '4ece4e39-286b-4820-81f4-b7beee71d092',\n REGISTER_LOGIC_FUNCTION: '56d46e18-6cae-4d99-9893-b7d70740c9db',\n\n // IntakeSource reverse relation fields\n INTAKE_SOURCE_LOGS_REVERSE: '6ecfa369-22e0-4812-bb75-4338cea8be65',\n INTAKE_SOURCE_RULES_REVERSE: '776fefaf-0eee-4ec0-93e0-d11bd80c1e20',\n\n // v0.2.0 \u2014 rawPayload + retry + post-install + UI\n INTAKE_LOG_RAW_PAYLOAD: 'b65c9e1c-f2c1-4c52-a5b3-1e1ac3139692',\n RETRY_LOGIC_FUNCTION: '90f20396-a69a-4ae4-af20-6be65c565bb5',\n POST_INSTALL_LOGIC_FUNCTION: 'e836fdd6-fd54-4aa4-ba6d-87d5a02148ef',\n LOG_RETRY_FRONT_COMPONENT: '0cb97f6d-30e2-48c8-90e1-241b82870b4d',\n FIELD_RULES_TABLE_FRONT_COMPONENT: 'c35faad9-3468-4746-9c1c-b13d73e5e09b',\n SOURCE_PAGE_LAYOUT: '8c894ba8-5c8c-45ed-b0af-1d22ede93eb9',\n SOURCE_LAYOUT_TAB_RULES: '80f5c49a-083c-4062-8bd5-ae158abb6c33',\n SOURCE_LAYOUT_TAB_RULES_WIDGET: '26a9ebb7-1d59-4b03-a018-56968f6df224',\n LOG_PAGE_LAYOUT: 'b0972802-ede5-463c-9f59-cb05f04957db',\n LOG_LAYOUT_TAB_RETRY: '6b109695-ba3a-43de-a31a-23119ccf87ba',\n LOG_LAYOUT_TAB_RETRY_WIDGET: '11727c1a-5145-46de-9742-88d07d64c72e',\n SAMPLE_RULE_PHONE: '8440da8d-8ac4-4f4b-baa2-4d62936cde35',\n SAMPLE_RULE_EMAIL: 'd123cde4-05aa-44e1-86f6-454be1d7048b',\n SAMPLE_RULE_FULLNAME: '00e49fe8-1b48-4f5b-aaf4-414d0923c712',\n\n // Views & Navigation\n SOURCES_VIEW: '4435cd43-5c13-472f-b4f9-b686c0f46285',\n LOGS_VIEW: 'a2cd0a93-9778-4ba5-99d9-b8b89c4fb39d',\n INTAKE_NAV_ITEM: 'e81bf288-5686-4713-86ae-e2947b124cab',\n} as const;\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk/define';
|
|
2
|
+
import { CoreApiClient } from 'twenty-client-sdk/core';
|
|
3
|
+
import { IDS } from '../constants/universal-identifiers';
|
|
4
|
+
|
|
5
|
+
const ALLOWED_TARGET_OBJECTS = ['PERSON', 'COMPANY', 'AUTO'] as const;
|
|
6
|
+
|
|
7
|
+
const handler = async (params: RoutePayload) => {
|
|
8
|
+
const body = params.body as Record<string, unknown> | null;
|
|
9
|
+
if (!body) return { statusCode: 400, body: { error: 'Missing request body' } };
|
|
10
|
+
|
|
11
|
+
const name = body['name'] as string | undefined;
|
|
12
|
+
const slug = body['slug'] as string | undefined;
|
|
13
|
+
const targetObject = (body['targetObject'] as string | undefined) ?? 'AUTO';
|
|
14
|
+
const createOpportunity = (body['createOpportunity'] as boolean | undefined) ?? true;
|
|
15
|
+
const opportunityNameTemplate = (body['opportunityNameTemplate'] as string | undefined)
|
|
16
|
+
?? '{{source}} — {{firstName}} {{lastName}}';
|
|
17
|
+
|
|
18
|
+
if (!name || !slug) {
|
|
19
|
+
return { statusCode: 400, body: { error: 'name and slug are required' } };
|
|
20
|
+
}
|
|
21
|
+
if (!/^[a-z0-9-]+$/.test(slug)) {
|
|
22
|
+
return { statusCode: 400, body: { error: 'slug must be lowercase letters, numbers, and hyphens only' } };
|
|
23
|
+
}
|
|
24
|
+
if (!ALLOWED_TARGET_OBJECTS.includes(targetObject as typeof ALLOWED_TARGET_OBJECTS[number])) {
|
|
25
|
+
return { statusCode: 400, body: { error: 'targetObject must be PERSON, COMPANY, or AUTO' } };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const client = new CoreApiClient();
|
|
29
|
+
|
|
30
|
+
// Check for existing source with same slug (idempotent registration)
|
|
31
|
+
const existing = await client.query({
|
|
32
|
+
intakeSources: {
|
|
33
|
+
__args: { filter: { slug: { eq: slug } } },
|
|
34
|
+
edges: { node: { id: true, slug: true } },
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const existingNode = existing.intakeSources?.edges?.[0]?.node;
|
|
39
|
+
|
|
40
|
+
if (existingNode) {
|
|
41
|
+
return {
|
|
42
|
+
statusCode: 200,
|
|
43
|
+
body: {
|
|
44
|
+
id: existingNode.id,
|
|
45
|
+
slug: existingNode.slug,
|
|
46
|
+
webhookUrl: buildWebhookUrl(slug),
|
|
47
|
+
message: 'Source already exists — returning existing registration.',
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Generate a signing secret
|
|
53
|
+
const { webcrypto } = await import('node:crypto');
|
|
54
|
+
const secretBytes = webcrypto.getRandomValues(new Uint8Array(32));
|
|
55
|
+
const secret = Array.from(secretBytes).map((b) => b.toString(16).padStart(2, '0')).join('');
|
|
56
|
+
|
|
57
|
+
const created = await client.mutation({
|
|
58
|
+
createIntakeSource: {
|
|
59
|
+
__args: {
|
|
60
|
+
data: {
|
|
61
|
+
name,
|
|
62
|
+
slug,
|
|
63
|
+
targetObject,
|
|
64
|
+
webhookSecret: secret,
|
|
65
|
+
status: 'ACTIVE',
|
|
66
|
+
createOpportunity,
|
|
67
|
+
opportunityNameTemplate,
|
|
68
|
+
totalIngested: 0,
|
|
69
|
+
totalFailed: 0,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
id: true,
|
|
73
|
+
slug: true,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
statusCode: 201,
|
|
79
|
+
body: {
|
|
80
|
+
id: created.createIntakeSource.id,
|
|
81
|
+
slug: created.createIntakeSource.slug,
|
|
82
|
+
webhookUrl: buildWebhookUrl(slug),
|
|
83
|
+
secret,
|
|
84
|
+
message: 'Source registered. Sign all requests with X-Webhook-Signature using the secret.',
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
function buildWebhookUrl(slug: string): string {
|
|
90
|
+
const baseUrl = process.env['TWENTY_API_URL'] ?? 'https://your-crm.com';
|
|
91
|
+
return `${baseUrl}/s/intake/${slug}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export default defineLogicFunction({
|
|
95
|
+
universalIdentifier: IDS.REGISTER_LOGIC_FUNCTION,
|
|
96
|
+
name: 'intake-register',
|
|
97
|
+
description: 'Programmatically register a new Intake source and get back a webhook URL + signing secret.',
|
|
98
|
+
timeoutSeconds: 10,
|
|
99
|
+
handler,
|
|
100
|
+
httpRouteTriggerSettings: {
|
|
101
|
+
path: '/intake/sources/register',
|
|
102
|
+
httpMethod: 'POST',
|
|
103
|
+
isAuthRequired: true,
|
|
104
|
+
},
|
|
105
|
+
});
|
|
@@ -136,6 +136,7 @@ var LoadSourceStage = class {
|
|
|
136
136
|
constructor(client) {
|
|
137
137
|
this.client = client;
|
|
138
138
|
}
|
|
139
|
+
client;
|
|
139
140
|
name = "load-source";
|
|
140
141
|
async execute(ctx) {
|
|
141
142
|
const result = await this.client.query({
|
|
@@ -232,6 +233,7 @@ var IdempotencyStage = class {
|
|
|
232
233
|
constructor(client) {
|
|
233
234
|
this.client = client;
|
|
234
235
|
}
|
|
236
|
+
client;
|
|
235
237
|
name = "idempotency";
|
|
236
238
|
async execute(ctx) {
|
|
237
239
|
const hash = computePayloadHash(ctx.rawPayload);
|
|
@@ -681,6 +683,7 @@ var NormalizeStage = class {
|
|
|
681
683
|
constructor(client) {
|
|
682
684
|
this.client = client;
|
|
683
685
|
}
|
|
686
|
+
client;
|
|
684
687
|
name = "normalize";
|
|
685
688
|
async execute(ctx) {
|
|
686
689
|
if (!ctx.flat) {
|