660 lines
19 KiB
JavaScript
660 lines
19 KiB
JavaScript
import Database from 'better-sqlite3';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const dbPath = path.join(__dirname, 'data.db');
|
|
|
|
export const db = new Database(dbPath);
|
|
db.pragma('journal_mode = WAL');
|
|
db.pragma('foreign_keys = ON');
|
|
|
|
// safe migrations — ignore if column already exists
|
|
for (const sql of [
|
|
"ALTER TABLE articles ADD COLUMN status TEXT DEFAULT 'published'",
|
|
"ALTER TABLE articles ADD COLUMN key_data TEXT",
|
|
]) { try { db.exec(sql) } catch {} }
|
|
|
|
db.exec(`
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
username TEXT NOT NULL UNIQUE,
|
|
password_hash TEXT NOT NULL,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS articles (
|
|
id TEXT PRIMARY KEY,
|
|
title TEXT NOT NULL,
|
|
category TEXT,
|
|
type TEXT,
|
|
author TEXT,
|
|
author_role TEXT,
|
|
author_initial TEXT,
|
|
publish_date TEXT,
|
|
pages INTEGER DEFAULT 0,
|
|
price INTEGER DEFAULT 0,
|
|
is_free INTEGER DEFAULT 1,
|
|
summary TEXT,
|
|
body TEXT,
|
|
cover_image TEXT,
|
|
tags TEXT,
|
|
featured INTEGER DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_articles_created ON articles(created_at DESC);
|
|
CREATE INDEX IF NOT EXISTS idx_articles_featured ON articles(featured);
|
|
|
|
CREATE TABLE IF NOT EXISTS risk_signals (
|
|
id TEXT PRIMARY KEY,
|
|
quote TEXT NOT NULL,
|
|
name TEXT NOT NULL,
|
|
date TEXT,
|
|
level TEXT NOT NULL CHECK (level IN ('critical','high','medium','low','opportunity')),
|
|
sort_order INTEGER DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_risks_sort ON risk_signals(sort_order, created_at);
|
|
|
|
CREATE TABLE IF NOT EXISTS events (
|
|
id TEXT PRIMARY KEY,
|
|
title_fa TEXT NOT NULL,
|
|
title_en TEXT,
|
|
date_fa TEXT,
|
|
date_en TEXT,
|
|
month_fa TEXT,
|
|
month_en TEXT,
|
|
year TEXT,
|
|
type TEXT CHECK(type IN ('conference','exhibition','seminar','international')),
|
|
location_fa TEXT,
|
|
location_en TEXT,
|
|
city_fa TEXT,
|
|
city_en TEXT,
|
|
description_fa TEXT,
|
|
description_en TEXT,
|
|
registration_open INTEGER DEFAULT 1,
|
|
sort_order INTEGER DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_events_sort ON events(sort_order, created_at DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS vision_items (
|
|
id TEXT PRIMARY KEY,
|
|
icon TEXT,
|
|
title_fa TEXT NOT NULL,
|
|
title_en TEXT,
|
|
desc_fa TEXT,
|
|
desc_en TEXT,
|
|
sort_order INTEGER DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_vision_sort ON vision_items(sort_order, created_at DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS advisory_board (
|
|
id TEXT PRIMARY KEY,
|
|
name_fa TEXT NOT NULL,
|
|
name_en TEXT,
|
|
role_fa TEXT,
|
|
role_en TEXT,
|
|
sort_order INTEGER DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_advisory_sort ON advisory_board(sort_order, created_at DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS team_members (
|
|
id TEXT PRIMARY KEY,
|
|
name_fa TEXT NOT NULL,
|
|
name_en TEXT,
|
|
role_fa TEXT,
|
|
role_en TEXT,
|
|
bio_fa TEXT,
|
|
bio_en TEXT,
|
|
expertise TEXT,
|
|
email TEXT,
|
|
initial TEXT,
|
|
photo TEXT,
|
|
report_count INTEGER DEFAULT 0,
|
|
is_expert INTEGER DEFAULT 0,
|
|
expert_room_fa TEXT,
|
|
expert_room_en TEXT,
|
|
telegram TEXT,
|
|
linkedin TEXT,
|
|
twitter TEXT,
|
|
website TEXT,
|
|
sort_order INTEGER DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_team_sort ON team_members(sort_order, created_at DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS plans (
|
|
id TEXT PRIMARY KEY,
|
|
name_fa TEXT NOT NULL,
|
|
name_en TEXT,
|
|
price INTEGER DEFAULT 0,
|
|
period_fa TEXT,
|
|
period_en TEXT,
|
|
features TEXT,
|
|
badge_fa TEXT,
|
|
badge_en TEXT,
|
|
is_featured INTEGER DEFAULT 0,
|
|
cta_fa TEXT,
|
|
cta_en TEXT,
|
|
sort_order INTEGER DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_plans_sort ON plans(sort_order);
|
|
|
|
CREATE TABLE IF NOT EXISTS radar_items (
|
|
id TEXT PRIMARY KEY,
|
|
category TEXT CHECK(category IN ('market','tech','commodity','geo','energy')),
|
|
img TEXT,
|
|
author_name TEXT,
|
|
author_avatar TEXT,
|
|
title_fa TEXT,
|
|
date_fa TEXT,
|
|
excerpt_fa TEXT,
|
|
title_en TEXT,
|
|
date_en TEXT,
|
|
excerpt_en TEXT,
|
|
sort_order INTEGER DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_radar_sort ON radar_items(sort_order, created_at DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS radar_pages (
|
|
slug TEXT PRIMARY KEY,
|
|
source_categories TEXT,
|
|
label_fa TEXT,
|
|
label_en TEXT,
|
|
latest_heading_fa TEXT,
|
|
latest_heading_en TEXT,
|
|
featured_title_fa TEXT,
|
|
featured_title_en TEXT,
|
|
featured_date_fa TEXT,
|
|
featured_date_en TEXT,
|
|
featured_img TEXT,
|
|
banner_kicker_fa TEXT,
|
|
banner_kicker_en TEXT,
|
|
banner_title_fa TEXT,
|
|
banner_title_en TEXT,
|
|
banner_desc_fa TEXT,
|
|
banner_desc_en TEXT,
|
|
banner_book_img TEXT,
|
|
sort_order INTEGER DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_radar_pages_sort ON radar_pages(sort_order);
|
|
|
|
CREATE TABLE IF NOT EXISTS banners (
|
|
id TEXT PRIMARY KEY,
|
|
overline_fa TEXT,
|
|
overline_en TEXT,
|
|
title_fa TEXT NOT NULL,
|
|
title_en TEXT,
|
|
subtitle_fa TEXT,
|
|
subtitle_en TEXT,
|
|
cta_label_fa TEXT,
|
|
cta_label_en TEXT,
|
|
cta_url TEXT,
|
|
image TEXT,
|
|
position TEXT DEFAULT 'hero',
|
|
active INTEGER DEFAULT 1,
|
|
sort_order INTEGER DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_banners_active ON banners(active, sort_order);
|
|
|
|
CREATE TABLE IF NOT EXISTS factory_reports (
|
|
id TEXT PRIMARY KEY,
|
|
img TEXT,
|
|
tag_fa TEXT,
|
|
tag_en TEXT,
|
|
title_fa TEXT NOT NULL,
|
|
title_en TEXT,
|
|
date_fa TEXT,
|
|
date_en TEXT,
|
|
excerpt_fa TEXT,
|
|
excerpt_en TEXT,
|
|
scroll_dir TEXT CHECK(scroll_dir IN ('left','right','up')) DEFAULT 'up',
|
|
sort_order INTEGER DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_factory_reports_sort ON factory_reports(sort_order, created_at DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS integrations (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
icon TEXT,
|
|
logo TEXT,
|
|
accent TEXT,
|
|
grid_col INTEGER DEFAULT 1,
|
|
grid_row INTEGER DEFAULT 1,
|
|
sort_order INTEGER DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_integrations_sort ON integrations(sort_order, created_at DESC);
|
|
|
|
CREATE TABLE IF NOT EXISTS institute_stats (
|
|
id TEXT PRIMARY KEY,
|
|
label_fa TEXT NOT NULL,
|
|
label_en TEXT,
|
|
value INTEGER DEFAULT 0,
|
|
suffix_fa TEXT,
|
|
sort_order INTEGER DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_institute_stats_sort ON institute_stats(sort_order);
|
|
|
|
CREATE TABLE IF NOT EXISTS market_prices (
|
|
id TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
value REAL DEFAULT 0,
|
|
unit TEXT,
|
|
change REAL DEFAULT 0,
|
|
change_percent REAL DEFAULT 0,
|
|
trend TEXT CHECK(trend IN ('up','down','flat')) DEFAULT 'flat',
|
|
sort_order INTEGER DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_market_prices_sort ON market_prices(sort_order);
|
|
|
|
CREATE TABLE IF NOT EXISTS market_chart_points (
|
|
id TEXT PRIMARY KEY,
|
|
series TEXT NOT NULL CHECK(series IN ('history','export','comparison')),
|
|
label TEXT NOT NULL,
|
|
value REAL DEFAULT 0,
|
|
meta TEXT,
|
|
sort_order INTEGER DEFAULT 0,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_chart_points_series ON market_chart_points(series, sort_order);
|
|
|
|
CREATE TABLE IF NOT EXISTS prices (
|
|
symbol TEXT PRIMARY KEY,
|
|
name TEXT NOT NULL,
|
|
value REAL,
|
|
unit TEXT,
|
|
change_value REAL,
|
|
change_pct REAL,
|
|
source_url TEXT,
|
|
fetched_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS members (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
name TEXT NOT NULL,
|
|
email TEXT NOT NULL UNIQUE,
|
|
password_hash TEXT NOT NULL,
|
|
phone TEXT,
|
|
avatar TEXT,
|
|
is_active INTEGER NOT NULL DEFAULT 1,
|
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_members_email ON members(email);
|
|
|
|
CREATE TABLE IF NOT EXISTS purchases (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
member_id INTEGER NOT NULL REFERENCES members(id) ON DELETE CASCADE,
|
|
article_id TEXT NOT NULL REFERENCES articles(id) ON DELETE CASCADE,
|
|
amount INTEGER NOT NULL DEFAULT 0,
|
|
purchased_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
UNIQUE(member_id, article_id)
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_purchases_member ON purchases(member_id, purchased_at DESC);
|
|
`);
|
|
|
|
/* idempotent column migrations for already-created tables */
|
|
function ensureColumn(table, col, def) {
|
|
const cols = db.prepare(`PRAGMA table_info(${table})`).all();
|
|
if (!cols.some((c) => c.name === col)) {
|
|
db.exec(`ALTER TABLE ${table} ADD COLUMN ${col} ${def}`);
|
|
}
|
|
}
|
|
ensureColumn('banners', 'overline_fa', 'TEXT');
|
|
ensureColumn('banners', 'overline_en', 'TEXT');
|
|
|
|
export function rowToPrice(row) {
|
|
if (!row) return null;
|
|
return {
|
|
symbol: row.symbol,
|
|
name: row.name,
|
|
value: row.value,
|
|
unit: row.unit,
|
|
changeValue: row.change_value,
|
|
changePct: row.change_pct,
|
|
sourceUrl: row.source_url,
|
|
fetchedAt: row.fetched_at,
|
|
};
|
|
}
|
|
|
|
export function upsertPrice(p) {
|
|
db.prepare(`
|
|
INSERT INTO prices (symbol, name, value, unit, change_value, change_pct, source_url, fetched_at)
|
|
VALUES (@symbol, @name, @value, @unit, @change_value, @change_pct, @source_url, datetime('now'))
|
|
ON CONFLICT(symbol) DO UPDATE SET
|
|
name = excluded.name,
|
|
value = excluded.value,
|
|
unit = excluded.unit,
|
|
change_value = excluded.change_value,
|
|
change_pct = excluded.change_pct,
|
|
source_url = excluded.source_url,
|
|
fetched_at = excluded.fetched_at
|
|
`).run({
|
|
symbol: p.symbol,
|
|
name: p.name,
|
|
value: p.value ?? null,
|
|
unit: p.unit ?? null,
|
|
change_value: p.changeValue ?? null,
|
|
change_pct: p.changePct ?? null,
|
|
source_url: p.sourceUrl ?? null,
|
|
});
|
|
}
|
|
|
|
export function rowToEvent(row) {
|
|
if (!row) return null;
|
|
return {
|
|
id: row.id,
|
|
titleFa: row.title_fa,
|
|
titleEn: row.title_en,
|
|
dateFa: row.date_fa,
|
|
dateEn: row.date_en,
|
|
monthFa: row.month_fa,
|
|
monthEn: row.month_en,
|
|
year: row.year,
|
|
type: row.type,
|
|
locationFa: row.location_fa,
|
|
locationEn: row.location_en,
|
|
cityFa: row.city_fa,
|
|
cityEn: row.city_en,
|
|
descriptionFa: row.description_fa,
|
|
descriptionEn: row.description_en,
|
|
registrationOpen: !!row.registration_open,
|
|
sortOrder: row.sort_order,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at,
|
|
};
|
|
}
|
|
|
|
export function rowToTeamMember(row) {
|
|
if (!row) return null;
|
|
return {
|
|
id: row.id,
|
|
nameFa: row.name_fa,
|
|
nameEn: row.name_en,
|
|
roleFa: row.role_fa,
|
|
roleEn: row.role_en,
|
|
bioFa: row.bio_fa,
|
|
bioEn: row.bio_en,
|
|
expertise: row.expertise ? JSON.parse(row.expertise) : [],
|
|
email: row.email,
|
|
initial: row.initial,
|
|
photo: row.photo,
|
|
reportCount: row.report_count,
|
|
isExpert: !!row.is_expert,
|
|
expertRoomFa: row.expert_room_fa,
|
|
expertRoomEn: row.expert_room_en,
|
|
telegram: row.telegram,
|
|
linkedin: row.linkedin,
|
|
twitter: row.twitter,
|
|
website: row.website,
|
|
sortOrder: row.sort_order,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at,
|
|
};
|
|
}
|
|
|
|
export function rowToPlan(row) {
|
|
if (!row) return null;
|
|
return {
|
|
id: row.id,
|
|
nameFa: row.name_fa,
|
|
nameEn: row.name_en,
|
|
price: row.price,
|
|
periodFa: row.period_fa,
|
|
periodEn: row.period_en,
|
|
features: row.features ? JSON.parse(row.features) : [],
|
|
badgeFa: row.badge_fa,
|
|
badgeEn: row.badge_en,
|
|
isFeatured: !!row.is_featured,
|
|
ctaFa: row.cta_fa,
|
|
ctaEn: row.cta_en,
|
|
sortOrder: row.sort_order,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at,
|
|
};
|
|
}
|
|
|
|
export function rowToRiskSignal(row) {
|
|
if (!row) return null;
|
|
return {
|
|
id: row.id,
|
|
quote: row.quote,
|
|
name: row.name,
|
|
date: row.date,
|
|
level: row.level,
|
|
sortOrder: row.sort_order,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at,
|
|
};
|
|
}
|
|
|
|
export function rowToFactoryReport(row) {
|
|
if (!row) return null;
|
|
return {
|
|
id: row.id,
|
|
img: row.img,
|
|
tagFa: row.tag_fa,
|
|
tagEn: row.tag_en,
|
|
titleFa: row.title_fa,
|
|
titleEn: row.title_en,
|
|
dateFa: row.date_fa,
|
|
dateEn: row.date_en,
|
|
excerptFa: row.excerpt_fa,
|
|
excerptEn: row.excerpt_en,
|
|
scrollDir: row.scroll_dir,
|
|
sortOrder: row.sort_order,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at,
|
|
};
|
|
}
|
|
|
|
export function rowToIntegration(row) {
|
|
if (!row) return null;
|
|
return {
|
|
id: row.id,
|
|
name: row.name,
|
|
icon: row.icon,
|
|
logo: row.logo,
|
|
accent: row.accent,
|
|
gridCol: row.grid_col,
|
|
gridRow: row.grid_row,
|
|
sortOrder: row.sort_order,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at,
|
|
};
|
|
}
|
|
|
|
export function rowToInstituteStat(row) {
|
|
if (!row) return null;
|
|
return {
|
|
id: row.id,
|
|
labelFa: row.label_fa,
|
|
labelEn: row.label_en,
|
|
value: row.value,
|
|
suffixFa: row.suffix_fa,
|
|
sortOrder: row.sort_order,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at,
|
|
};
|
|
}
|
|
|
|
export function rowToMarketPrice(row) {
|
|
if (!row) return null;
|
|
return {
|
|
id: row.id,
|
|
name: row.name,
|
|
value: row.value,
|
|
unit: row.unit,
|
|
change: row.change,
|
|
changePercent: row.change_percent,
|
|
trend: row.trend,
|
|
sortOrder: row.sort_order,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at,
|
|
};
|
|
}
|
|
|
|
export function rowToMarketChartPoint(row) {
|
|
if (!row) return null;
|
|
return {
|
|
id: row.id,
|
|
series: row.series,
|
|
label: row.label,
|
|
value: row.value,
|
|
meta: row.meta,
|
|
sortOrder: row.sort_order,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at,
|
|
};
|
|
}
|
|
|
|
export function rowToVisionItem(row) {
|
|
if (!row) return null;
|
|
return {
|
|
id: row.id,
|
|
icon: row.icon,
|
|
titleFa: row.title_fa,
|
|
titleEn: row.title_en,
|
|
descFa: row.desc_fa,
|
|
descEn: row.desc_en,
|
|
sortOrder: row.sort_order,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at,
|
|
};
|
|
}
|
|
|
|
export function rowToAdvisoryMember(row) {
|
|
if (!row) return null;
|
|
return {
|
|
id: row.id,
|
|
nameFa: row.name_fa,
|
|
nameEn: row.name_en,
|
|
roleFa: row.role_fa,
|
|
roleEn: row.role_en,
|
|
sortOrder: row.sort_order,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at,
|
|
};
|
|
}
|
|
|
|
export function rowToRadarItem(row) {
|
|
if (!row) return null;
|
|
return {
|
|
id: row.id,
|
|
category: row.category,
|
|
img: row.img,
|
|
authorName: row.author_name,
|
|
authorAvatar: row.author_avatar,
|
|
titleFa: row.title_fa,
|
|
dateFa: row.date_fa,
|
|
excerptFa: row.excerpt_fa,
|
|
titleEn: row.title_en,
|
|
dateEn: row.date_en,
|
|
excerptEn: row.excerpt_en,
|
|
sortOrder: row.sort_order,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at,
|
|
};
|
|
}
|
|
|
|
export function rowToRadarPage(row) {
|
|
if (!row) return null;
|
|
return {
|
|
slug: row.slug,
|
|
sourceCategories: row.source_categories ? JSON.parse(row.source_categories) : [],
|
|
labelFa: row.label_fa,
|
|
labelEn: row.label_en,
|
|
latestHeadingFa: row.latest_heading_fa,
|
|
latestHeadingEn: row.latest_heading_en,
|
|
featuredTitleFa: row.featured_title_fa,
|
|
featuredTitleEn: row.featured_title_en,
|
|
featuredDateFa: row.featured_date_fa,
|
|
featuredDateEn: row.featured_date_en,
|
|
featuredImg: row.featured_img,
|
|
bannerKickerFa: row.banner_kicker_fa,
|
|
bannerKickerEn: row.banner_kicker_en,
|
|
bannerTitleFa: row.banner_title_fa,
|
|
bannerTitleEn: row.banner_title_en,
|
|
bannerDescFa: row.banner_desc_fa,
|
|
bannerDescEn: row.banner_desc_en,
|
|
bannerBookImg: row.banner_book_img,
|
|
sortOrder: row.sort_order,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at,
|
|
};
|
|
}
|
|
|
|
export function rowToBanner(row) {
|
|
if (!row) return null;
|
|
return {
|
|
id: row.id,
|
|
overlineFa: row.overline_fa,
|
|
overlineEn: row.overline_en,
|
|
titleFa: row.title_fa,
|
|
titleEn: row.title_en,
|
|
subtitleFa: row.subtitle_fa,
|
|
subtitleEn: row.subtitle_en,
|
|
ctaLabelFa: row.cta_label_fa,
|
|
ctaLabelEn: row.cta_label_en,
|
|
ctaUrl: row.cta_url,
|
|
image: row.image,
|
|
position: row.position,
|
|
active: !!row.active,
|
|
sortOrder: row.sort_order,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at,
|
|
};
|
|
}
|
|
|
|
export function rowToArticle(row) {
|
|
if (!row) return null;
|
|
return {
|
|
id: row.id,
|
|
title: row.title,
|
|
category: row.category,
|
|
type: row.type,
|
|
author: row.author,
|
|
authorRole: row.author_role,
|
|
authorInitial: row.author_initial,
|
|
publishDate: row.publish_date,
|
|
pages: row.pages,
|
|
price: row.price,
|
|
isFree: !!row.is_free,
|
|
summary: row.summary,
|
|
body: row.body,
|
|
coverImage: row.cover_image,
|
|
tags: row.tags ? JSON.parse(row.tags) : [],
|
|
featured: !!row.featured,
|
|
createdAt: row.created_at,
|
|
updatedAt: row.updated_at,
|
|
};
|
|
}
|