Skip to main content
Glama
storyline-notion-sync.js15.6 kB
// Notion Integration for Storyline Database Management import { Client } from '@notionhq/client'; import { z } from 'zod'; import { NOTION_CONFIG, NOTION_DATABASE_NAMES, validateNotionConfig } from '../config/notion-config.js'; class StorylineNotionSync { constructor() { this.notion = null; this.databaseIds = { storylines: null, scenes: null, characterDevelopment: null, storyArcs: null }; } async getNotionClient() { if (this.notion) return this.notion; // Get Notion connection using the same pattern as character pipeline let connectionSettings; const hostname = process.env.REPLIT_CONNECTORS_HOSTNAME; const xReplitToken = process.env.REPL_IDENTITY ? 'repl ' + process.env.REPL_IDENTITY : process.env.WEB_REPL_RENEWAL ? 'depl ' + process.env.WEB_REPL_RENEWAL : null; if (!xReplitToken) { throw new Error('X_REPLIT_TOKEN not found for repl/depl'); } connectionSettings = await fetch( 'https://' + hostname + '/api/v2/connection?include_secrets=true&connector_names=notion', { headers: { 'Accept': 'application/json', 'X_REPLIT_TOKEN': xReplitToken } } ).then(res => res.json()).then(data => data.items?.[0]); const accessToken = connectionSettings?.settings?.access_token || connectionSettings.settings?.oauth?.credentials?.access_token; if (!connectionSettings || !accessToken) { throw new Error('Notion not connected'); } this.notion = new Client({ auth: accessToken }); return this.notion; } // Create Notion databases for storylines if they don't exist async ensureStorylineDatabases() { const notion = await this.getNotionClient(); // Validate configuration first const validation = validateNotionConfig(); if (!validation.isValid) { console.log('⚠️ Notion configuration incomplete:', validation.issues.join(', ')); return null; } try { // Check if databases already exist by searching if (NOTION_CONFIG.STORYLINES_DB) { try { await notion.databases.retrieve({ database_id: NOTION_CONFIG.STORYLINES_DB }); this.databaseIds.storylines = NOTION_CONFIG.STORYLINES_DB; console.log('✅ Using existing Storylines database'); } catch (error) { console.log('⚠️ Configured Storylines database ID invalid, will create new one'); } } // Create Storylines database if not exists if (!this.databaseIds.storylines) { const parentPageId = NOTION_CONFIG.STORYLINES_PARENT_PAGE || NOTION_CONFIG.DEFAULT_PARENT_PAGE; const storylinesDb = await notion.databases.create({ parent: { type: "page_id", page_id: parentPageId }, title: [{ type: "text", text: { content: NOTION_DATABASE_NAMES.STORYLINES } }], properties: { "Title": { title: {} }, "Description": { rich_text: {} }, "Genre": { select: { options: [ { name: "Comedy", color: "yellow" }, { name: "Drama", color: "red" }, { name: "Adventure", color: "green" }, { name: "Educational", color: "blue" }, { name: "Fantasy", color: "purple" } ] }}, "Status": { select: { options: [ { name: "Draft", color: "gray" }, { name: "In Production", color: "orange" }, { name: "Completed", color: "green" }, { name: "On Hold", color: "red" } ] }}, "Total Scenes": { number: {} }, "Total Duration": { number: {} }, "Created": { created_time: {} }, "Last Updated": { last_edited_time: {} } } }); this.databaseIds.storylines = storylinesDb.id; console.log(`✅ Created Storylines database: ${storylinesDb.id}`); } // Create Scenes database if not exists if (NOTION_CONFIG.SCENES_DB) { try { await notion.databases.retrieve({ database_id: NOTION_CONFIG.SCENES_DB }); this.databaseIds.scenes = NOTION_CONFIG.SCENES_DB; console.log('✅ Using existing Scenes database'); } catch (error) { console.log('⚠️ Configured Scenes database ID invalid, will create new one'); } } if (!this.databaseIds.scenes) { const parentPageId = NOTION_CONFIG.SCENES_PARENT_PAGE || NOTION_CONFIG.DEFAULT_PARENT_PAGE; const scenesDb = await notion.databases.create({ parent: { type: "page_id", page_id: parentPageId }, title: [{ type: "text", text: { content: NOTION_DATABASE_NAMES.SCENES } }], properties: { "Scene Title": { title: {} }, "Storyline": { relation: { database_id: this.databaseIds.storylines } }, "Scene Number": { number: {} }, "Description": { rich_text: {} }, "Dialogue": { rich_text: {} }, "Visual Direction": { rich_text: {} }, "Scene Type": { select: { options: [ { name: "Dialogue", color: "blue" }, { name: "Action", color: "red" }, { name: "Montage", color: "green" }, { name: "Transition", color: "gray" } ] }}, "Duration": { number: {} }, "Status": { select: { options: [ { name: "Draft", color: "gray" }, { name: "Ready", color: "yellow" }, { name: "In Production", color: "orange" }, { name: "Complete", color: "green" } ] }}, "Characters": { multi_select: {} } } }); this.databaseIds.scenes = scenesDb.id; console.log(`✅ Created Scenes database: ${scenesDb.id}`); } // Create Character Development database if not exists if (NOTION_CONFIG.CHARACTER_DEVELOPMENT_DB) { try { await notion.databases.retrieve({ database_id: NOTION_CONFIG.CHARACTER_DEVELOPMENT_DB }); this.databaseIds.characterDevelopment = NOTION_CONFIG.CHARACTER_DEVELOPMENT_DB; console.log('✅ Using existing Character Development database'); } catch (error) { console.log('⚠️ Configured Character Development database ID invalid, will create new one'); } } if (!this.databaseIds.characterDevelopment) { const parentPageId = NOTION_CONFIG.CHARACTER_DEV_PARENT_PAGE || NOTION_CONFIG.DEFAULT_PARENT_PAGE; const devDb = await notion.databases.create({ parent: { type: "page_id", page_id: parentPageId }, title: [{ type: "text", text: { content: NOTION_DATABASE_NAMES.CHARACTER_DEVELOPMENT } }], properties: { "Development Event": { title: {} }, "Character": { relation: { database_id: NOTION_CONFIG.CHARACTERS_MASTER_DB } }, "Storyline": { relation: { database_id: this.databaseIds.storylines } }, "Scene": { relation: { database_id: this.databaseIds.scenes } }, "Development Type": { select: { options: [ { name: "Growth", color: "green" }, { name: "Realization", color: "yellow" }, { name: "Conflict", color: "red" }, { name: "Resolution", color: "blue" }, { name: "Backstory", color: "purple" } ] }}, "Description": { rich_text: {} }, "Emotional Impact": { number: {} }, "Significance": { select: { options: [ { name: "Minor", color: "gray" }, { name: "Major", color: "orange" }, { name: "Pivotal", color: "red" } ] }}, "Created": { created_time: {} } } }); this.databaseIds.characterDevelopment = devDb.id; console.log(`✅ Created Character Development database: ${devDb.id}`); } console.log('✅ Notion storyline databases ensured successfully'); return this.databaseIds; } catch (error) { console.error('❌ Error creating Notion databases:', error); throw error; } } // Sync storyline from PostgreSQL to Notion async syncStorylineToNotion(storylineData) { const notion = await this.getNotionClient(); try { const page = await notion.pages.create({ parent: { database_id: this.databaseIds.storylines }, properties: { "Title": { title: [{ type: "text", text: { content: storylineData.title } }] }, "Description": { rich_text: [{ type: "text", text: { content: storylineData.description || '' } }] }, "Genre": { select: { name: storylineData.genre } }, "Status": { select: { name: storylineData.status || 'Draft' } }, "Total Scenes": { number: storylineData.total_scenes || 0 }, "Total Duration": { number: storylineData.total_duration || 0 } } }); return { success: true, notion_page_id: page.id, message: `Storyline "${storylineData.title}" synced to Notion` }; } catch (error) { return { success: false, error: error.message, message: `Failed to sync storyline to Notion: ${error.message}` }; } } // Sync scene from PostgreSQL to Notion async syncSceneToNotion(sceneData, storylineNotionId) { const notion = await this.getNotionClient(); try { const page = await notion.pages.create({ parent: { database_id: this.databaseIds.scenes }, properties: { "Scene Title": { title: [{ type: "text", text: { content: sceneData.title } }] }, "Storyline": { relation: [{ id: storylineNotionId }] }, "Scene Number": { number: sceneData.scene_number }, "Description": { rich_text: [{ type: "text", text: { content: sceneData.description || '' } }] }, "Dialogue": { rich_text: [{ type: "text", text: { content: sceneData.dialogue || '' } }] }, "Visual Direction": { rich_text: [{ type: "text", text: { content: sceneData.visual_direction || '' } }] }, "Scene Type": { select: { name: sceneData.scene_type || 'Dialogue' } }, "Duration": { number: sceneData.duration || 0 }, "Status": { select: { name: sceneData.status || 'Draft' } } } }); return { success: true, notion_page_id: page.id, message: `Scene "${sceneData.title}" synced to Notion` }; } catch (error) { return { success: false, error: error.message, message: `Failed to sync scene to Notion: ${error.message}` }; } } // Sync character development to Notion async syncCharacterDevelopmentToNotion(devData, characterNotionId, storylineNotionId, sceneNotionId) { const notion = await this.getNotionClient(); try { const page = await notion.pages.create({ parent: { database_id: this.databaseIds.characterDevelopment }, properties: { "Development Event": { title: [{ type: "text", text: { content: `${devData.development_type}: ${devData.description.substring(0, 100)}...` } }] }, "Character": { relation: [{ id: characterNotionId }] }, "Storyline": { relation: [{ id: storylineNotionId }] }, "Scene": { relation: [{ id: sceneNotionId }] }, "Development Type": { select: { name: devData.development_type } }, "Description": { rich_text: [{ type: "text", text: { content: devData.description } }] }, "Emotional Impact": { number: devData.emotional_impact || 5 }, "Significance": { select: { name: devData.significance || 'Minor' } } } }); return { success: true, notion_page_id: page.id, message: `Character development event synced to Notion` }; } catch (error) { return { success: false, error: error.message, message: `Failed to sync character development to Notion: ${error.message}` }; } } // Get storyline data from Notion async getStorylineFromNotion(notionPageId) { const notion = await this.getNotionClient(); try { const page = await notion.pages.retrieve({ page_id: notionPageId }); const properties = page.properties; return { success: true, storyline: { title: properties.Title?.title?.[0]?.text?.content || '', description: properties.Description?.rich_text?.[0]?.text?.content || '', genre: properties.Genre?.select?.name || '', status: properties.Status?.select?.name || 'Draft', total_scenes: properties['Total Scenes']?.number || 0, total_duration: properties['Total Duration']?.number || 0, notion_page_id: notionPageId } }; } catch (error) { return { success: false, error: error.message, message: `Failed to retrieve storyline from Notion: ${error.message}` }; } } } export { StorylineNotionSync };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/bermingham85/mcp-puppet-pipeline'

If you have feedback or need assistance with the MCP directory API, please join our Discord server