Files
twenty/packages/twenty-apps/community/rollup-engine/scripts/setup-company-rollup-fields.mjs
T
Félix MalfaitandGitHub d85785e380 Twenty self hosting app (#15987)
App to manage telemetry/billing (twenty for twenty!)
2025-11-21 14:10:51 +01:00

139 lines
4.3 KiB
JavaScript

import { config as loadEnv } from 'dotenv';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const envCandidates = [
process.env.TWENTY_ENV_PATH ? path.resolve(process.env.TWENTY_ENV_PATH) : null,
path.resolve(__dirname, '..', '.env'),
path.resolve(__dirname, '..', '..', '..', '..', '..', '.env'),
].filter(Boolean);
envCandidates.forEach((candidate) => {
if (!candidate) {
return;
}
const result = loadEnv({ path: candidate });
if (!result.error && process.env.TWENTY_API_KEY) {
return;
}
});
const METADATA_BASE_URL = (
process.env.TWENTY_METADATA_BASE_URL ??
(process.env.TWENTY_API_BASE_URL
? `${process.env.TWENTY_API_BASE_URL.replace(/\/+$/, '')}/metadata`
: null) ??
'http://localhost:3000/rest/metadata'
).replace(/\/+$/, '');
const API_KEY = process.env.TWENTY_API_KEY;
if (!API_KEY) {
console.error('TWENTY_API_KEY is required in the environment to call the Metadata API.');
process.exit(1);
}
const COMPANY_ROLLUP_FIELDS = [
{ name: 'totalPipelineAmount', label: 'Total Pipeline Amount', type: 'CURRENCY' },
{ name: 'totalOpportunityCount', label: 'Total Opportunity Count', type: 'NUMBER' },
{ name: 'wonPipelineAmount', label: 'Won Pipeline Amount', type: 'CURRENCY' },
{ name: 'wonOpportunityCount', label: 'Won Opportunity Count', type: 'NUMBER' },
{ name: 'openPipelineAmount', label: 'Open Pipeline Amount', type: 'CURRENCY' },
{ name: 'openOpportunityCount', label: 'Open Opportunity Count', type: 'NUMBER' },
{ name: 'lastOpportunityCloseDate', label: 'Last Opportunity Close Date', type: 'DATE' },
];
const metadataRequest = async (method, endpoint, body) => {
const url = `${METADATA_BASE_URL}${endpoint}`;
const headers = {
Authorization: `Bearer ${API_KEY}`,
};
const init = { method, headers };
if (body !== undefined) {
headers['Content-Type'] = 'application/json';
init.body = JSON.stringify(body);
}
const response = await fetch(url, init);
const text = await response.text();
let parsed = {};
if (text) {
try {
parsed = JSON.parse(text);
} catch (error) {
throw new Error(`Failed to parse JSON response from ${url}: ${text}`);
}
}
if (!response.ok) {
const messages = parsed?.messages || parsed?.message;
const error = new Error(
`Metadata API ${method} ${endpoint} failed: ${messages || response.statusText}`,
);
error.response = parsed;
throw error;
}
return parsed;
};
const findObjectId = async (nameSingular) => {
const response = await metadataRequest('GET', '/objects');
const objects =
(response?.data && Array.isArray(response.data.objects) ? response.data.objects : []) || [];
const match = objects.find((entry) => entry.nameSingular === nameSingular);
if (!match) {
throw new Error(`Unable to find object with nameSingular="${nameSingular}" via Metadata API`);
}
return match.id;
};
const createField = async (objectMetadataId, field) => {
try {
const payload = {
objectMetadataId,
name: field.name,
label: field.label,
type: field.type,
};
await metadataRequest('POST', '/fields', payload);
console.log(`✔ Created ${field.name}`);
} catch (error) {
const messages = error.response?.messages;
const messageList = Array.isArray(messages)
? messages
: typeof messages === 'string'
? [messages]
: [];
if (messageList.some((msg) => msg.includes('already exists'))) {
console.log(`→ ${field.name} already exists; skipping`);
return;
}
throw error;
}
};
const main = async () => {
console.log(`Using metadata base URL: ${METADATA_BASE_URL}`);
const companyObjectId = await findObjectId('company');
for (const field of COMPANY_ROLLUP_FIELDS) {
// Metadata API prefers camelCase names; script assumes labels may contain spaces.
await createField(companyObjectId, field);
}
console.log('Company rollup fields ensured.');
};
main().catch((error) => {
console.error('Failed to provision rollup fields via Metadata API.');
console.error(error instanceof Error ? error.message : error);
if (error instanceof Error && error.stack) {
console.error(error.stack);
}
process.exit(1);
});