250 lines
7.4 KiB
JavaScript
250 lines
7.4 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* setup-tables.js — Creates a new "Digital Hub" base in NocoDB
|
|
* with all 12 tables, fields, and links.
|
|
* Run once: node setup-tables.js
|
|
*/
|
|
|
|
require('dotenv').config({ path: __dirname + '/.env' });
|
|
|
|
const NOCODB_URL = process.env.NOCODB_URL || 'http://localhost:8090';
|
|
const NOCODB_TOKEN = process.env.NOCODB_TOKEN;
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
async function request(method, url, body) {
|
|
const opts = {
|
|
method,
|
|
headers: { 'xc-token': NOCODB_TOKEN, 'Content-Type': 'application/json' },
|
|
};
|
|
if (body) opts.body = JSON.stringify(body);
|
|
const res = await fetch(url, opts);
|
|
if (!res.ok) {
|
|
const text = await res.text();
|
|
throw new Error(`${method} ${url} → ${res.status}: ${text}`);
|
|
}
|
|
const text = await res.text();
|
|
return text ? JSON.parse(text) : {};
|
|
}
|
|
|
|
async function createBase() {
|
|
console.log('Creating "Digital Hub" base...');
|
|
const data = await request('POST', `${NOCODB_URL}/api/v2/meta/bases/`, {
|
|
title: 'Digital Hub',
|
|
type: 'database',
|
|
});
|
|
console.log(` Base created: ${data.id}`);
|
|
return data.id;
|
|
}
|
|
|
|
async function createTable(baseId, title, columns) {
|
|
console.log(` Creating table: ${title}`);
|
|
const data = await request('POST', `${NOCODB_URL}/api/v2/meta/bases/${baseId}/tables`, {
|
|
title,
|
|
columns,
|
|
});
|
|
console.log(` → ${data.id}`);
|
|
return data;
|
|
}
|
|
|
|
async function createLinkColumn(tableId, title, relatedTableId, type = 'hm') {
|
|
console.log(` Linking: ${title}`);
|
|
await request('POST', `${NOCODB_URL}/api/v2/meta/tables/${tableId}/columns`, {
|
|
title,
|
|
uidt: 'Links',
|
|
parentId: tableId,
|
|
childId: relatedTableId,
|
|
type,
|
|
});
|
|
}
|
|
|
|
// Field type helpers
|
|
const text = (title) => ({ title, uidt: 'SingleLineText' });
|
|
const longText = (title) => ({ title, uidt: 'LongText' });
|
|
const email = (title) => ({ title, uidt: 'Email' });
|
|
const num = (title) => ({ title, uidt: 'Number' });
|
|
const decimal = (title) => ({ title, uidt: 'Decimal' });
|
|
const checkbox = (title) => ({ title, uidt: 'Checkbox' });
|
|
const date = (title) => ({ title, uidt: 'Date' });
|
|
const dateTime = (title) => ({ title, uidt: 'DateTime' });
|
|
const singleSelect = (title, options) => ({
|
|
title,
|
|
uidt: 'SingleSelect',
|
|
dtxp: options.map(o => `'${o}'`).join(','),
|
|
});
|
|
|
|
async function main() {
|
|
try {
|
|
// 1. Create base
|
|
const baseId = await createBase();
|
|
|
|
// 2. Create tables (without links first)
|
|
const users = await createTable(baseId, 'Users', [
|
|
text('name'),
|
|
email('email'),
|
|
singleSelect('role', ['superadmin', 'manager', 'contributor']),
|
|
text('team_role'),
|
|
longText('brands'),
|
|
text('phone'),
|
|
text('avatar'),
|
|
checkbox('tutorial_completed'),
|
|
]);
|
|
|
|
const brands = await createTable(baseId, 'Brands', [
|
|
text('name'),
|
|
text('name_ar'),
|
|
num('priority'),
|
|
text('color'),
|
|
text('icon'),
|
|
text('category'),
|
|
]);
|
|
|
|
const campaigns = await createTable(baseId, 'Campaigns', [
|
|
text('name'),
|
|
longText('description'),
|
|
date('start_date'),
|
|
date('end_date'),
|
|
singleSelect('status', ['planning', 'active', 'paused', 'completed', 'cancelled']),
|
|
text('color'),
|
|
decimal('budget'),
|
|
longText('goals'),
|
|
longText('platforms'),
|
|
decimal('budget_spent'),
|
|
decimal('revenue'),
|
|
num('impressions'),
|
|
num('clicks'),
|
|
num('conversions'),
|
|
decimal('cost_per_click'),
|
|
longText('notes'),
|
|
num('brand_id'),
|
|
num('created_by_user_id'),
|
|
]);
|
|
|
|
const campaignTracks = await createTable(baseId, 'CampaignTracks', [
|
|
text('name'),
|
|
singleSelect('type', ['organic_social', 'paid_social', 'paid_search', 'email', 'seo', 'influencer', 'event', 'other']),
|
|
text('platform'),
|
|
decimal('budget_allocated'),
|
|
decimal('budget_spent'),
|
|
decimal('revenue'),
|
|
num('impressions'),
|
|
num('clicks'),
|
|
num('conversions'),
|
|
longText('notes'),
|
|
singleSelect('status', ['planned', 'active', 'paused', 'completed']),
|
|
num('campaign_id'),
|
|
]);
|
|
|
|
const campaignAssignments = await createTable(baseId, 'CampaignAssignments', [
|
|
dateTime('assigned_at'),
|
|
num('campaign_id'),
|
|
num('member_id'),
|
|
num('assigner_id'),
|
|
]);
|
|
|
|
const projects = await createTable(baseId, 'Projects', [
|
|
text('name'),
|
|
longText('description'),
|
|
singleSelect('status', ['active', 'paused', 'completed', 'cancelled']),
|
|
singleSelect('priority', ['low', 'medium', 'high', 'urgent']),
|
|
date('start_date'),
|
|
date('due_date'),
|
|
num('brand_id'),
|
|
num('owner_id'),
|
|
num('created_by_user_id'),
|
|
]);
|
|
|
|
const tasks = await createTable(baseId, 'Tasks', [
|
|
text('title'),
|
|
longText('description'),
|
|
singleSelect('status', ['todo', 'in_progress', 'done']),
|
|
singleSelect('priority', ['low', 'medium', 'high', 'urgent']),
|
|
date('start_date'),
|
|
date('due_date'),
|
|
checkbox('is_personal'),
|
|
dateTime('completed_at'),
|
|
num('project_id'),
|
|
num('assigned_to_id'),
|
|
num('created_by_user_id'),
|
|
]);
|
|
|
|
const posts = await createTable(baseId, 'Posts', [
|
|
text('title'),
|
|
longText('description'),
|
|
singleSelect('status', ['draft', 'in_review', 'approved', 'scheduled', 'published', 'rejected']),
|
|
text('platform'),
|
|
longText('platforms'),
|
|
text('content_type'),
|
|
dateTime('scheduled_date'),
|
|
dateTime('published_date'),
|
|
longText('notes'),
|
|
longText('publication_links'),
|
|
num('brand_id'),
|
|
num('assigned_to_id'),
|
|
num('campaign_id'),
|
|
num('track_id'),
|
|
num('created_by_user_id'),
|
|
]);
|
|
|
|
const assets = await createTable(baseId, 'Assets', [
|
|
text('filename'),
|
|
text('original_name'),
|
|
text('mime_type'),
|
|
num('size'),
|
|
longText('tags'),
|
|
text('folder'),
|
|
num('brand_id'),
|
|
num('campaign_id'),
|
|
num('uploader_id'),
|
|
]);
|
|
|
|
const postAttachments = await createTable(baseId, 'PostAttachments', [
|
|
text('filename'),
|
|
text('original_name'),
|
|
text('mime_type'),
|
|
num('size'),
|
|
text('url'),
|
|
num('post_id'),
|
|
]);
|
|
|
|
const comments = await createTable(baseId, 'Comments', [
|
|
text('entity_type'),
|
|
num('entity_id'),
|
|
longText('content'),
|
|
num('user_id'),
|
|
]);
|
|
|
|
const budgetEntries = await createTable(baseId, 'BudgetEntries', [
|
|
text('label'),
|
|
decimal('amount'),
|
|
text('source'),
|
|
text('category'),
|
|
date('date_received'),
|
|
longText('notes'),
|
|
num('campaign_id'),
|
|
]);
|
|
|
|
// 3. All relationships are now handled via plain Number FK columns
|
|
// (brand_id, owner_id, campaign_id, etc.) defined directly in each table above.
|
|
// No NocoDB link columns are needed.
|
|
|
|
// 4. Save base ID to .env
|
|
const envPath = path.join(__dirname, '.env');
|
|
let envContent = fs.readFileSync(envPath, 'utf8');
|
|
envContent = envContent.replace(/^NOCODB_BASE_ID=.*$/m, `NOCODB_BASE_ID=${baseId}`);
|
|
fs.writeFileSync(envPath, envContent);
|
|
|
|
console.log(`\n✅ Done! Base ID ${baseId} saved to .env`);
|
|
console.log('Tables created:');
|
|
const tables = [users, brands, campaigns, campaignTracks, campaignAssignments, projects, tasks, posts, assets, postAttachments, comments, budgetEntries];
|
|
for (const t of tables) {
|
|
console.log(` ${t.title}: ${t.id}`);
|
|
}
|
|
} catch (err) {
|
|
console.error('Setup failed:', err);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
main();
|