Migrating and Documenting Templates for Buildship V2

Sebastian Avelar →

BuildShip Developer
Low-Code/No-Code
Web Developer
BuildShip
BuildShip

Project Overview

Migrated and created new Buildship templates that leverage V2 features, including template creation, documentation, and trigger setup.

Role and Responsibilities

Migrated templates from V1 to V2 of Buildship.
Created new templates that integrated with tools like Notion, Supabase, and Firestore.
Documented templates and workflows, including visual assets like GIFs and detailed guides.
Developed and tested Triggers for various integrations.

Key Contributions

Template Creation
Designed workflows showcasing Firestore CRUD operations and other integrations.
Ensured templates were user-friendly and ready for immediate implementation.
2. Documentation Improvements
Created clear, visual instructions using GIFs and concise written steps.
Provided examples for triggers to enhance understanding.
3. Trigger Development
Developed and implemented Notion triggers, with well-structured and reusable code.
Assisted in bug identification and improvements to node instructions.

Documentation Example

Assisted with documentation for various triggers, including writing, GIF creation, and examples:

Template Examples

Read Me Documentation:
Included step-by-step examples, GIFs, and clear instructions for users to understand workflows seamlessly.
Flow Previews:
Visual representations of workflows to help users grasp the logic and structure quickly.
Inline Notes in Templates:
Added contextual notes within templates to provide additional explanations and usage tips.
Firestore CRUD Operations Template
Firestore CRUD Operations Template
Supabase CRUD Operations Template
Supabase CRUD Operations Template
Extract Structured Data using Claude Template
Extract Structured Data using Claude Template
Publish Notion Blogs Template
Publish Notion Blogs Template

Code Showcase

Here’s a sample of the Notion Trigger Code implemented for Buildship:
import {Logging} from "@google-cloud/logging";
import parser from "co-body";

// Helper function to convert Notion properties to clean data
const convertNotionProperties = (notionData) => {
const properties = notionData.properties || {};
const data = {
pageID: notionData.id,
};

Object.entries(properties).forEach(([fieldName, prop]) => {
if (prop.type === 'button') return;

switch(prop.type) {
case 'title':
data[fieldName] = prop.title[0]?.plain_text || '';
break;
case 'email':
data[fieldName] = prop.email || '';
break;
case 'select':
data[fieldName] = prop.select?.name || '';
break;
case 'status':
data[fieldName] = prop.status?.name || '';
break;
case 'rich_text':
data[fieldName] = prop.rich_text[0]?.plain_text || '';
break;
case 'number':
data[fieldName] = prop.number || 0;
break;
case 'checkbox':
data[fieldName] = prop.checkbox || false;
break;
case 'multi_select':
data[fieldName] = prop.multi_select?.map(item => item.name).join(', ') || '';
break;
case 'date':
data[fieldName] = prop.date?.start || '';
break;
case 'url':
data[fieldName] = prop.url || '';
break;
case 'phone_number':
data[fieldName] = prop.phone_number || '';
break;
}
});

return data;
};

const getAccessToken = async () => {
const response = await fetch(
"http://metadata/computeMetadata/v1/instance/service-accounts/default/token",
{
headers: { "Metadata-Flavor": "Google" },
}
);

if (!response.ok) {
throw new Error(`Failed to obtain access token: ${response.statusText}`);
}

const data = await response.json();
return data.access_token;
};

const fetchLogEntries = async (triggerId, retries = 3, delay = 1000) => {
const projectId = process.env.GCLOUD_PROJECT;
const accessToken = await getAccessToken();

const filter = `logName="projects/${projectId}/logs/buildship-node-io" AND jsonPayload.nId="${triggerId}"`;

const requestBody = {
resourceNames: [`projects/${projectId}`],
filter: filter,
pageSize: 1,
orderBy: "timestamp desc",
};

for (let attempt = 1; attempt <= retries; attempt++) {
try {
const response = await fetch(
"https://logging.googleapis.com/v2/entries:list",
{
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(requestBody),
}
);

if (!response.ok) {
const errorText = await response.text();
throw new Error(
`Error fetching log entries: ${response.statusText} - ${errorText}`
);
}

const data = await response.json();
const entries = data.entries || [];

if (entries.length > 0) {
return entries;
}

if (attempt < retries) {
await new Promise((resolve) => setTimeout(resolve, delay));
}
} catch (error) {
console.error(`Attempt ${attempt} failed due to an error:`, error);
if (attempt < retries) {
await new Promise((resolve) => setTimeout(resolve, delay));
} else {
throw new Error(
`Failed to get data after ${retries} attempts.`
);
}
}
}

throw new Error("No data found. Send a request to the API and try again.");
};

const getData = async (inputs, { trigger, workflow }) => {
try {
const entries = await fetchLogEntries(trigger.id, 5, 2000);

if (entries.length > 0 && entries[0].jsonPayload && entries[0].jsonPayload.o) {
return {
success: true,
message: "",
data: entries[0].jsonPayload.o
};
}

return {
success: false,
message: "No valid log entry found",
data: null
};
} catch (err) {
return {
success: false,
message: err?.message,
data: null
};
}
};

const onExecution = async ({ _ }, { req, logging, env, nodeReq, auth }) => {
try {
const webhookData = await parser.json(nodeReq);


const pageData = webhookData?.data
if (!pageData) {
throw new Error("No page data found in the webhook payload");
}


const processedData = convertNotionProperties(pageData);
return processedData;

} catch (error) {
console.error('Notion Trigger Error:', error);
throw error;
}
};



// Export the required functions
export default { onExecution, getData };
Partner With Sebastian
View Services

More Projects by Sebastian