202 lines
6.3 KiB
JavaScript
202 lines
6.3 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Query Notion database with filters and sorts
|
|
* Usage: query-database.js <database-id> [--filter <json>] [--sort <json>] [--limit 10]
|
|
*/
|
|
|
|
const https = require('https');
|
|
|
|
const NOTION_API_KEY = process.env.NOTION_API_KEY;
|
|
const NOTION_VERSION = '2025-09-03';
|
|
|
|
if (!NOTION_API_KEY) {
|
|
console.error('Error: NOTION_API_KEY environment variable not set');
|
|
process.exit(1);
|
|
}
|
|
|
|
function notionRequest(path, method, data = null) {
|
|
return new Promise((resolve, reject) => {
|
|
const requestData = data ? JSON.stringify(data) : null;
|
|
|
|
const options = {
|
|
hostname: 'api.notion.com',
|
|
port: 443,
|
|
path: path,
|
|
method: method,
|
|
headers: {
|
|
'Authorization': `Bearer ${NOTION_API_KEY}`,
|
|
'Notion-Version': NOTION_VERSION,
|
|
'Content-Type': 'application/json'
|
|
}
|
|
};
|
|
|
|
if (requestData) {
|
|
options.headers['Content-Length'] = Buffer.byteLength(requestData);
|
|
}
|
|
|
|
const req = https.request(options, (res) => {
|
|
let body = '';
|
|
res.on('data', (chunk) => body += chunk);
|
|
res.on('end', () => {
|
|
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
resolve(JSON.parse(body));
|
|
} else {
|
|
const error = JSON.parse(body);
|
|
if (res.statusCode === 404 && error.code === 'object_not_found') {
|
|
reject(new Error('Database not found. Make sure it is shared with your integration.'));
|
|
} else if (res.statusCode === 401) {
|
|
reject(new Error('Authentication failed. Check your NOTION_API_KEY.'));
|
|
} else if (res.statusCode === 429) {
|
|
reject(new Error('Rate limit exceeded. Wait a moment and try again.'));
|
|
} else {
|
|
reject(new Error(`API error (${res.statusCode}): ${error.message || body}`));
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
req.on('error', reject);
|
|
if (requestData) {
|
|
req.write(requestData);
|
|
}
|
|
req.end();
|
|
});
|
|
}
|
|
|
|
async function queryDatabase(databaseId, filter = null, sorts = null, pageSize = 10) {
|
|
// In Notion API 2025-09-03, we need the data_source_id for queries
|
|
// Get the database first to extract data_source_id
|
|
console.error(`Fetching database info: ${databaseId}`);
|
|
const dbInfo = await notionRequest(`/v1/databases/${databaseId}`, 'GET');
|
|
|
|
const dataSourceId = dbInfo.data_sources && dbInfo.data_sources.length > 0
|
|
? dbInfo.data_sources[0].id
|
|
: databaseId; // Fallback to database_id if no data_source found
|
|
|
|
console.error(`Querying data source: ${dataSourceId}`);
|
|
|
|
const queryPayload = {
|
|
page_size: pageSize
|
|
};
|
|
|
|
if (filter) {
|
|
queryPayload.filter = filter;
|
|
console.error('Filter:', JSON.stringify(filter, null, 2));
|
|
}
|
|
|
|
if (sorts) {
|
|
queryPayload.sorts = sorts;
|
|
console.error('Sort:', JSON.stringify(sorts, null, 2));
|
|
}
|
|
|
|
const result = await notionRequest(`/v1/data_sources/${dataSourceId}/query`, 'POST', queryPayload);
|
|
|
|
return result.results.map(page => {
|
|
const properties = {};
|
|
for (const [key, value] of Object.entries(page.properties)) {
|
|
properties[key] = extractPropertyValue(value);
|
|
}
|
|
|
|
return {
|
|
id: page.id,
|
|
url: page.url,
|
|
lastEdited: page.last_edited_time,
|
|
properties: properties
|
|
};
|
|
});
|
|
}
|
|
|
|
function extractPropertyValue(property) {
|
|
switch (property.type) {
|
|
case 'title':
|
|
return property.title.map(t => t.plain_text).join('');
|
|
case 'rich_text':
|
|
return property.rich_text.map(t => t.plain_text).join('');
|
|
case 'number':
|
|
return property.number;
|
|
case 'select':
|
|
return property.select?.name || null;
|
|
case 'multi_select':
|
|
return property.multi_select.map(s => s.name);
|
|
case 'date':
|
|
return property.date ? {
|
|
start: property.date.start,
|
|
end: property.date.end
|
|
} : null;
|
|
case 'checkbox':
|
|
return property.checkbox;
|
|
case 'url':
|
|
return property.url;
|
|
case 'email':
|
|
return property.email;
|
|
case 'phone_number':
|
|
return property.phone_number;
|
|
case 'relation':
|
|
return property.relation.map(r => r.id);
|
|
default:
|
|
return property[property.type];
|
|
}
|
|
}
|
|
|
|
// Main execution
|
|
(async () => {
|
|
try {
|
|
const args = process.argv.slice(2);
|
|
|
|
if (args.length === 0 || args[0] === '--help') {
|
|
console.log('Usage: query-database.js <database-id> [options]');
|
|
console.log('');
|
|
console.log('Options:');
|
|
console.log(' --filter <json> Filter expression (JSON)');
|
|
console.log(' --sort <json> Sort expression (JSON)');
|
|
console.log(' --limit <num> Maximum results (default: 10)');
|
|
console.log('');
|
|
console.log('Examples:');
|
|
console.log(' # Get all items');
|
|
console.log(' query-database.js xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx');
|
|
console.log('');
|
|
console.log(' # Filter by status');
|
|
console.log(' query-database.js <db-id> --filter \'{"property": "Status", "select": {"equals": "Complete"}}\'');
|
|
console.log('');
|
|
console.log(' # Filter by tag (multi-select contains)');
|
|
console.log(' query-database.js <db-id> --filter \'{"property": "Tags", "multi_select": {"contains": "AI"}}\'');
|
|
console.log('');
|
|
console.log(' # Sort by date descending');
|
|
console.log(' query-database.js <db-id> --sort \'[{"property": "Date", "direction": "descending"}]\'');
|
|
console.log('');
|
|
console.log(' # Combine filter + sort');
|
|
console.log(' query-database.js <db-id> \\');
|
|
console.log(' --filter \'{"property": "Status", "select": {"equals": "Complete"}}\' \\');
|
|
console.log(' --sort \'[{"property": "Date", "direction": "descending"}]\'');
|
|
process.exit(0);
|
|
}
|
|
|
|
const databaseId = args[0];
|
|
let filter = null;
|
|
let sorts = null;
|
|
let limit = 10;
|
|
|
|
for (let i = 1; i < args.length; i++) {
|
|
if (args[i] === '--filter' && args[i + 1]) {
|
|
filter = JSON.parse(args[i + 1]);
|
|
i++;
|
|
} else if (args[i] === '--sort' && args[i + 1]) {
|
|
sorts = JSON.parse(args[i + 1]);
|
|
i++;
|
|
} else if (args[i] === '--limit' && args[i + 1]) {
|
|
limit = parseInt(args[i + 1]);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
const results = await queryDatabase(databaseId, filter, sorts, limit);
|
|
|
|
console.log(JSON.stringify(results, null, 2));
|
|
console.error(`\n✓ Found ${results.length} result(s)`);
|
|
|
|
} catch (error) {
|
|
console.error('Error:', error.message);
|
|
process.exit(1);
|
|
}
|
|
})();
|