update on timeline on portfolio view + some corrections
This commit is contained in:
249
server/setup-tables.js
Normal file
249
server/setup-tables.js
Normal file
@@ -0,0 +1,249 @@
|
||||
#!/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();
|
||||
Reference in New Issue
Block a user