Spaces:
Running
Running
| begin; | |
| create table if not exists analytics_sync_runs ( | |
| id text primary key, | |
| workspace_id text not null, | |
| project_id text, | |
| provider text, | |
| date_from timestamptz not null, | |
| date_to timestamptz not null, | |
| timezone text not null, | |
| status text not null default 'queued' | |
| check (status in ('queued','running','succeeded','partial','failed','cancelled')), | |
| idempotency_key text not null check (char_length(idempotency_key) between 8 and 255), | |
| requested_by text, | |
| attempt_count integer not null default 0 check (attempt_count >= 0), | |
| next_attempt_at timestamptz, | |
| error_code text, | |
| error_message text, | |
| metrics_count integer not null default 0 check (metrics_count >= 0), | |
| created_at timestamptz not null default now(), | |
| started_at timestamptz, | |
| completed_at timestamptz, | |
| updated_at timestamptz not null default now(), | |
| constraint ck_analytics_sync_range check (date_to > date_from), | |
| constraint uq_analytics_sync_workspace_idempotency unique (workspace_id, idempotency_key) | |
| ); | |
| create index if not exists ix_analytics_sync_workspace_status | |
| on analytics_sync_runs(workspace_id, status); | |
| create index if not exists ix_analytics_sync_due | |
| on analytics_sync_runs(status, next_attempt_at); | |
| create table if not exists analytics_metric_snapshots ( | |
| id text primary key, | |
| workspace_id text not null, | |
| project_id text, | |
| social_post_id text references social_posts(id) on delete cascade, | |
| social_account_id text references social_accounts(id) on delete cascade, | |
| provider text not null, | |
| external_object_id text not null, | |
| metric_name text not null, | |
| metric_value double precision not null, | |
| bucket_start timestamptz not null, | |
| dimensions jsonb not null default '{}'::jsonb, | |
| source text not null default 'provider', | |
| collected_at timestamptz not null default now(), | |
| provider_updated_at timestamptz, | |
| constraint uq_analytics_metric_snapshot | |
| unique (workspace_id, provider, external_object_id, metric_name, bucket_start) | |
| ); | |
| create index if not exists ix_analytics_metric_snapshots_workspace_bucket | |
| on analytics_metric_snapshots(workspace_id, bucket_start desc); | |
| create table if not exists analytics_post_metrics ( | |
| id text primary key, | |
| workspace_id text not null, | |
| project_id text, | |
| social_post_id text not null references social_posts(id) on delete cascade, | |
| social_post_target_id text not null references social_post_targets(id) on delete cascade, | |
| social_account_id text not null references social_accounts(id) on delete cascade, | |
| provider text not null, | |
| external_post_id text, | |
| metric_date timestamptz not null, | |
| views bigint, | |
| impressions bigint, | |
| likes bigint, | |
| comments bigint, | |
| shares bigint, | |
| engagement_rate double precision, | |
| dimensions jsonb not null default '{}'::jsonb, | |
| source text not null default 'provider', | |
| collected_at timestamptz not null default now(), | |
| provider_updated_at timestamptz, | |
| constraint ck_analytics_post_nonnegative check ( | |
| (views is null or views >= 0) and | |
| (impressions is null or impressions >= 0) and | |
| (likes is null or likes >= 0) and | |
| (comments is null or comments >= 0) and | |
| (shares is null or shares >= 0) and | |
| (engagement_rate is null or engagement_rate >= 0) | |
| ), | |
| constraint uq_analytics_post_metric_bucket | |
| unique (workspace_id, social_post_target_id, metric_date) | |
| ); | |
| create index if not exists ix_analytics_post_metrics_workspace_date | |
| on analytics_post_metrics(workspace_id, metric_date desc); | |
| create index if not exists ix_analytics_post_metrics_project_date | |
| on analytics_post_metrics(workspace_id, project_id, metric_date desc); | |
| create index if not exists ix_analytics_post_metrics_provider_date | |
| on analytics_post_metrics(workspace_id, provider, metric_date desc); | |
| create table if not exists analytics_platform_metrics ( | |
| id text primary key, | |
| workspace_id text not null, | |
| project_id text, | |
| social_account_id text not null references social_accounts(id) on delete cascade, | |
| provider text not null, | |
| metric_date timestamptz not null, | |
| posts_count integer not null default 0 check (posts_count >= 0), | |
| views bigint, | |
| impressions bigint, | |
| likes bigint, | |
| comments bigint, | |
| shares bigint, | |
| engagement_rate double precision, | |
| dimensions jsonb not null default '{}'::jsonb, | |
| source text not null default 'provider', | |
| collected_at timestamptz not null default now(), | |
| constraint uq_analytics_platform_metric_bucket | |
| unique (workspace_id, provider, social_account_id, metric_date) | |
| ); | |
| create index if not exists ix_analytics_platform_metrics_workspace_date | |
| on analytics_platform_metrics(workspace_id, metric_date desc); | |
| do $$ | |
| declare table_name text; | |
| begin | |
| foreach table_name in array array[ | |
| 'analytics_sync_runs', | |
| 'analytics_metric_snapshots', | |
| 'analytics_post_metrics', | |
| 'analytics_platform_metrics' | |
| ] loop | |
| execute format('alter table %I enable row level security', table_name); | |
| execute format('alter table %I force row level security', table_name); | |
| execute format('drop policy if exists analytics_workspace_isolation on %I', table_name); | |
| execute format( | |
| 'create policy analytics_workspace_isolation on %I using (workspace_id = current_setting(''app.workspace_id'', true)) with check (workspace_id = current_setting(''app.workspace_id'', true))', | |
| table_name | |
| ); | |
| end loop; | |
| end $$; | |
| commit; | |