# BitSocial — modelo de dados (confirmado em produção, 2026-08-19) Prefixo real de tabela em descomplicar.pt: `wpah_` (WP table prefix) + `bit_social_` (`Config::VAR_PREFIX`). Todas as tabelas usam `Schema::withPrefix(...)`, `timestamps()` standard (`created_at`/`updated_at`). ## Tabelas ### `wpah_bit_social_accounts` (4 linhas) | Coluna | Tipo | Notas | |---|---|---| | `id` | bigint PK | | | `custom_app_id` | bigint, nullable, FK → `custom_apps.id` | null quando usa a app proxy da BitApps | | `profile_id` | string | ID do perfil/utilizador na plataforma | | `account_id` | string | ID da conta/página na plataforma (ex.: Facebook page id) | | `account_name` | string | | | `details` | longtext (JSON) | **NUNCA seleccionar** — contém `access_token` OAuth em claro, `icon`, `category`, etc. | | `platform` | string | `facebook`, `linkedin`, `pinterest`, `tumblr`, `instagram`, `twitter`, `discord`, `threads`, `tiktok`, `bluesky`, `telegram`, `googleBusinessProfile`, … | | `account_type` | int | `Account::accountType`: `DEFAULT=1` (app proxy BitApps), `CUSTOM=2` (app própria), `AI_PLATFORM=3` | | `status` | int (bool) | `1` activo, `0` inactivo | ### `wpah_bit_social_custom_apps` (1 linha) | Coluna | Tipo | Notas | |---|---|---| | `id` | bigint PK | | | `name` | string | | | `platform` | string | | | `credential` | longtext | **NUNCA seleccionar** — segredo de app cifrado (base64 de payload AES) | | `status` | int (bool) | | ### `wpah_bit_social_schedules` (175 linhas) Cobre **dois fluxos diferentes na mesma tabela**, distinguidos por `schedule_type`: | Coluna | Tipo | Notas | |---|---|---| | `id` | bigint PK | | | `name` | string | Ex.: `"Auto Post - Post ID: 60261"` (auto-post) | | `config` | longtext (JSON) | Ver forma abaixo. Coluna aninhada — precisa de segundo `JSON.parse` no lado do cliente. | | `published_post_ids` | longtext (JSON, array de ints) | Posts já publicados por este schedule | | `repeat_schedule` | bool | | | `schedule_type` | int | `Schedule::scheduleType`: `SCHEDULE_SHARE=1` (auto-post recorrente/agendado), `DIRECT_SHARE=2` ("Share Now") | | `status` | int | `Schedule::status`: `INACTIVE=0`, `ACTIVE=1`, `COMPLETED=2`, `DRAFT=3`, `MISSED=4` | | `cron_status` | int | `INACTIVE=0`, `ACTIVE=1` | | `started_at`, `ended_at`, `last_published_at`, `next_published_at` | timestamp, nullable | | Forma real de `config` (exemplo de um schedule de auto-post): ```json { "settings": { "started_at": "2026-08-11 11:50:34" }, "post_filters": { "post_type": "post", "specific_postIds": [60261] }, "accounts": { "accountIds": [1, 4, 5, 6], "groupIds": [] }, "templates": { "facebook": { "postingType": "isFeaturedImage", "content": "{post_title}", "trimMessage": false }, "pinterest": { "postingType": "isLinkCard", "content": "{post_title}", "trimMessage": true, "isLinkCard": false }, "...": "uma entrada por plataforma, mesma forma que bit_social_templates_settings" } } ``` ### `wpah_bit_social_logs` (1825 linhas) Uma linha por tentativa de publicação (schedule × plataforma). | Coluna | Tipo | Notas | |---|---|---| | `id` | bigint PK | | | `schedule_id` | bigint, nullable, FK → `schedules.id` | | | `details` | longtext (JSON) | Ver forma abaixo | | `platform` | string | | | `status` | int (bool) | `1` sucesso, `0` falha — confirmado em `AnalyticsController::index` (`Log::where('status', true)`) | Forma de `details` em sucesso: ```json { "account_id": "164743196715367", "account_name": "Descomplicar - Agência de Aceleração Digital", "response": { "id": "164743196715367_122245726064096762", "post_supports_client_mutation_id": true }, "post_id": 60261, "post_url": "https://fb.com/164743196715367_122245726064096762" } ``` Forma de `details` em falha (o caso real mais comum, Pinterest): ```json { "account_id": "1051238806710440771", "account_name": "Blog Descomplicar", "post_id": 60261, "response": { "status": "error", "error_msg": "Must have image for create pin in Pinterest." }, "post_url": null } ``` O erro está sempre em `details.response.error_msg` nos casos observados; `extractErrorMessage()` no MCP também tenta `response.message`, `error`, `message` como fallback para plataformas com forma diferente. ### `wpah_bit_social_groups` / `wpah_bit_social_groups_accounts` (Pro, 0 linhas em produção) `groups`: `id`, `name`, `status`. `groups_accounts`: pivot `group_id` × `account_id`, cascade on delete. Um grupo pode substituir uma lista explícita de `accountIds` num `config.accounts.groupIds`. ## `wp_options` relevantes | Chave | Conteúdo | |---|---| | `bit_social_version` / `bit_social_pro_version` | string, ex. `"1.16.0"` | | `bit_social_db_version` / `bit_social_pro_db_version` | string | | `bit_social_installed` / `bit_social_pro_installed` | bool | | `bit_social_auto_post_settings` | `{isEnabled, keepLogs, taxonomies[], accounts:{accountIds[],groupIds[]}, postType[], postDelay:{every,unit}}` | | `bit_social_templates_settings` | um objecto por plataforma, mesma forma que `config.templates` num schedule | | `bit_social_pro_settings` | `{cron:{isExternalCronEnabled}}` | | `bit_social_pro_license_data` | `{key, status, expireIn}` — `key` **nunca** deve ser devolvida sem máscara | | `bit_social_secret_key` | segredo interno do plugin — nunca ler/expor | ## Análogo AnalyticsController (referência, não reimplementação 1:1) `AnalyticsController::index` só calcula `active_account_count`, `published_post_count` (logs com `status=true`) e `active_schedule_count`. `bs_get_analytics` estende com `failed_post_count` (logs com `status=0`) porque o controller original omite essa contagem apesar de ser a métrica mais accionável.