Uname:Linux antigravity-cli 6.8.0-31-generic #31-Ubuntu SMP PREEMPT_DYNAMIC Sat Apr 20 00:40:06 UTC 2024 x86_64

Base Dir : /var/www/moonbloom

User : wp-moonbloom


403WebShell
403Webshell
Server IP : 85.155.190.233  /  Your IP : 216.73.216.103
Web Server : nginx/1.24.0
System : Linux antigravity-cli 6.8.0-31-generic #31-Ubuntu SMP PREEMPT_DYNAMIC Sat Apr 20 00:40:06 UTC 2024 x86_64
User : wp-moonbloom ( 1001)
PHP Version : 8.3.6
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /opt/moonbloom-dashboard/sql/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /opt/moonbloom-dashboard/sql/schema.sql
-- MoonBloom Analytics Dashboard — Postgres schema
-- Auto-applied on first container start via /docker-entrypoint-initdb.d/
-- Idempotent: safe to re-run (CREATE TABLE IF NOT EXISTS / CREATE OR REPLACE VIEW).

CREATE TABLE IF NOT EXISTS ad_metrics_daily (
  date DATE NOT NULL, source TEXT NOT NULL, campaign_id TEXT NOT NULL DEFAULT '_all', campaign_name TEXT,
  impressions BIGINT DEFAULT 0, clicks BIGINT DEFAULT 0,
  spend_eur NUMERIC(12,2) DEFAULT 0, conversions NUMERIC(12,2) DEFAULT 0,
  PRIMARY KEY (date, source, campaign_id));           -- source: google_ads|pinterest_ads|etsy_ads

CREATE TABLE IF NOT EXISTS traffic_daily (
  date DATE NOT NULL, source_medium TEXT NOT NULL, channel TEXT,
  sessions BIGINT DEFAULT 0, active_users BIGINT DEFAULT 0, engaged_sessions BIGINT DEFAULT 0,
  avg_session_sec NUMERIC(10,2), PRIMARY KEY (date, source_medium));

CREATE TABLE IF NOT EXISTS etsy_orders (
  order_id TEXT PRIMARY KEY, order_date DATE, country TEXT, product TEXT,
  revenue_eur NUMERIC(12,2), etsy_fee_eur NUMERIC(12,2), profit_eur NUMERIC(12,2));

CREATE TABLE IF NOT EXISTS google_keywords_daily (
  date DATE, campaign TEXT, ad_group TEXT, keyword TEXT, match_type TEXT,
  impressions BIGINT, clicks BIGINT, spend_eur NUMERIC(12,2),
  PRIMARY KEY (date, campaign, ad_group, keyword, match_type));

CREATE TABLE IF NOT EXISTS google_search_terms_daily (
  date DATE, search_term TEXT, campaign TEXT, ad_group TEXT,
  impressions BIGINT, clicks BIGINT, spend_eur NUMERIC(12,2), conversions NUMERIC(12,2),
  PRIMARY KEY (date, search_term, campaign, ad_group));  -- ad_group in PK: same term can hit >1 ad_group/day

CREATE TABLE IF NOT EXISTS pinterest_ads_pins_daily (
  date DATE, ad_id TEXT, ad_name TEXT, campaign_name TEXT,
  impressions BIGINT, clicks BIGINT, outbound_clicks BIGINT, spend_eur NUMERIC(12,2),
  PRIMARY KEY (date, ad_id));

CREATE TABLE IF NOT EXISTS ga4_landing_daily (
  date DATE, channel TEXT, landing_page TEXT,
  sessions BIGINT, engaged_sessions BIGINT, avg_session_sec NUMERIC(10,2),
  PRIMARY KEY (date, channel, landing_page));

CREATE TABLE IF NOT EXISTS pinterest_organic_daily (
  date DATE, pin_id TEXT, pin_title TEXT,
  impressions BIGINT, saves BIGINT, pin_clicks BIGINT, outbound_clicks BIGINT,
  PRIMARY KEY (date, pin_id));

-- Hierarchy + status snapshot for both ad platforms (campaign -> ad_group -> ad).
-- One row per (date, source, level, entity) capturing that entity's status as
-- of the collector run on `date`. Pinterest rows (all 3 levels) and Google
-- Ads ad_group/ad rows are a single "current state" snapshot tagged with the
-- day the collector ran; Google Ads campaign-level rows are a free byproduct
-- of the existing per-day campaign_daily GAQL query, so they carry one row
-- per actually-collected date in the run's window (real historical status),
-- not just today. Both are valid "date" semantics for this table — see
-- v_structure_changes below, which compares per (source, level) independently
-- so this asymmetry does not cause incorrect comparisons.
CREATE TABLE IF NOT EXISTS ad_structure_daily (
  date DATE NOT NULL, source TEXT NOT NULL, level TEXT NOT NULL,
  entity_id TEXT NOT NULL, entity_name TEXT, parent_id TEXT, status TEXT,
  PRIMARY KEY (date, source, level, entity_id));
  -- source: google_ads|pinterest_ads. level: campaign|ad_group|ad.
  -- parent_id: for level=ad_group -> campaign_id; for level=ad -> ad_group_id; for level=campaign -> NULL.

CREATE TABLE IF NOT EXISTS pinterest_audience_daily (
  date DATE NOT NULL, audience_type TEXT NOT NULL, dimension TEXT NOT NULL,
  key TEXT NOT NULL, name TEXT, ratio NUMERIC(6,4), audience_size BIGINT,
  PRIMARY KEY (date, audience_type, dimension, key));
  -- audience_type: YOUR_TOTAL_AUDIENCE | YOUR_ENGAGED_AUDIENCE
  -- dimension: age | gender | device | country | metro

-- Etsy Offsite Ads has no CSV/API export at all (unlike Etsy Ads) — this is a
-- hand-maintained monthly snapshot the user types in from the Shop Manager
-- Offsite Ads page ("Dieser Monat" view). Not merged into ad_metrics_daily:
-- that table is daily-grained and drives the Overview combo chart / blended
-- Total Spend KPI, and dropping a whole month's fee onto one day would show
-- as a misleading spike. Kept as its own small, separately-displayed table.
CREATE TABLE IF NOT EXISTS etsy_offsite_ads_monthly (
  month DATE NOT NULL,                     -- first day of the calendar month this snapshot covers
  total_revenue_eur NUMERIC(12,2),         -- "Gesamtumsatz"
  orders INT,                              -- "Bestellungen" (direct block)
  new_buyers INT,                          -- "Neue Käufer:innen" (direct block)
  direct_revenue_eur NUMERIC(12,2),        -- "Direkte Einnahmen"
  fee_eur NUMERIC(12,2),                   -- "Gezahlte Anzeigengebühren" — the real out-of-pocket cost
  indirect_revenue_eur NUMERIC(12,2),      -- "Indirekte Umsätze"
  indirect_orders INT,                     -- indirect "Bestellungen"
  indirect_new_buyers INT,                 -- indirect "Neue Käufer:innen"
  indirect_fee_eur NUMERIC(12,2),          -- "Indirekte Gebühren"
  PRIMARY KEY (month));

-- ---------------------------------------------------------------------------
-- Convenience read views for Grafana panels
-- ---------------------------------------------------------------------------

-- Daily spend/clicks/impressions rolled up per human-readable channel
-- (source -> channel: google_ads -> google, pinterest_ads -> pinterest, etsy_ads -> etsy)
CREATE OR REPLACE VIEW v_spend_by_channel_daily AS
SELECT
  date,
  CASE source
    WHEN 'google_ads'    THEN 'google'
    WHEN 'pinterest_ads' THEN 'pinterest'
    WHEN 'etsy_ads'      THEN 'etsy'
    ELSE source
  END AS channel,
  SUM(spend_eur)   AS spend_eur,
  SUM(clicks)      AS clicks,
  SUM(impressions) AS impressions
FROM ad_metrics_daily
GROUP BY date, channel;

-- Single-row blended KPI summary across ad spend, site traffic, and Etsy orders
CREATE OR REPLACE VIEW v_blended_kpis AS
SELECT
  (SELECT COALESCE(SUM(spend_eur), 0)   FROM ad_metrics_daily) AS total_spend_eur,
  (SELECT COALESCE(SUM(clicks), 0)      FROM ad_metrics_daily) AS total_clicks,
  (SELECT COALESCE(SUM(impressions), 0) FROM ad_metrics_daily) AS total_impressions,
  (SELECT COALESCE(SUM(conversions), 0) FROM ad_metrics_daily) AS total_conversions,
  (SELECT COALESCE(SUM(sessions), 0)         FROM traffic_daily) AS total_sessions,
  (SELECT COALESCE(SUM(engaged_sessions), 0) FROM traffic_daily) AS total_engaged_sessions,
  (SELECT COUNT(*)                      FROM etsy_orders) AS total_orders,
  (SELECT COALESCE(SUM(revenue_eur), 0) FROM etsy_orders) AS total_revenue_eur,
  (SELECT COALESCE(SUM(profit_eur), 0)  FROM etsy_orders) AS total_profit_eur;

-- New-or-status-changed-or-disappeared entities between the latest snapshot
-- date and the immediately preceding distinct date, computed independently
-- per (source, level) so one source/level missing today's run (e.g. a
-- transient API failure) never corrupts the comparison for the others.
-- change_type: 'new' (no prior row), 'status_changed' (status differs),
-- or 'disappeared' (present in the prior snapshot, absent from the latest —
-- current_status is NULL for these rows since there is no "today" row).
CREATE OR REPLACE VIEW v_structure_changes AS
WITH latest AS (
  SELECT source, level, MAX(date) AS latest_date
  FROM ad_structure_daily
  GROUP BY source, level
),
prev AS (
  SELECT a.source, a.level, MAX(a.date) AS prev_date
  FROM ad_structure_daily a
  JOIN latest l ON a.source = l.source AND a.level = l.level AND a.date < l.latest_date
  GROUP BY a.source, a.level
),
today_rows AS (
  SELECT t.*
  FROM ad_structure_daily t
  JOIN latest l ON t.source = l.source AND t.level = l.level AND t.date = l.latest_date
),
prev_rows AS (
  SELECT p.*
  FROM ad_structure_daily p
  JOIN prev pr ON p.source = pr.source AND p.level = pr.level AND p.date = pr.prev_date
)
SELECT
  t.date, t.source, t.level, t.entity_id, t.entity_name, t.parent_id,
  t.status AS current_status,
  pr.status AS previous_status,
  CASE WHEN pr.entity_id IS NULL THEN 'new' ELSE 'status_changed' END AS change_type
FROM today_rows t
LEFT JOIN prev_rows pr
  ON t.source = pr.source AND t.level = pr.level AND t.entity_id = pr.entity_id
WHERE pr.entity_id IS NULL OR t.status IS DISTINCT FROM pr.status

UNION ALL

SELECT
  l.latest_date AS date, pr.source, pr.level, pr.entity_id, pr.entity_name, pr.parent_id,
  NULL::text AS current_status,
  pr.status AS previous_status,
  'disappeared' AS change_type
FROM prev_rows pr
JOIN latest l ON pr.source = l.source AND pr.level = l.level
LEFT JOIN today_rows t ON pr.source = t.source AND pr.level = t.level AND pr.entity_id = t.entity_id
WHERE t.entity_id IS NULL;

Youez - 2016 - github.com/yon3zu
LinuXploit