@fayz-ai/plugin-marketing 0.9.1 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +225 -167
- package/dist/index.js.map +1 -1
- package/dist/integrations/ranklayer/connectorDef.d.ts +1 -1
- package/dist/integrations/ranklayer/connectorDef.d.ts.map +1 -1
- package/dist/integrations/ranklayer/data/supabase.d.ts +24 -6
- package/dist/integrations/ranklayer/data/supabase.d.ts.map +1 -1
- package/dist/integrations/ranklayer/outbound.d.ts +16 -0
- package/dist/integrations/ranklayer/outbound.d.ts.map +1 -0
- package/dist/integrations/ranklayer/settings.d.ts +12 -0
- package/dist/integrations/ranklayer/settings.d.ts.map +1 -0
- package/dist/integrations/ranklayer/types.d.ts +0 -1
- package/dist/integrations/ranklayer/types.d.ts.map +1 -1
- package/dist/migrations/index.d.ts +1 -1
- package/dist/migrations/index.d.ts.map +1 -1
- package/package.json +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ import { type MarketingCurrency } from './lib/format';
|
|
|
6
6
|
import { type MarketingDomain, type MarketingDomainModules } from './lib/presets';
|
|
7
7
|
import type { AcquisitionChannel, ConversionModel } from './types';
|
|
8
8
|
export interface MarketingPluginOptions {
|
|
9
|
+
/** Where the module's sub-nav lives. MarketingPage already reads it. */
|
|
10
|
+
moduleNav?: 'rail' | 'tabs' | 'sidebar';
|
|
9
11
|
/** Vertical preset for conversion model + channels + module defaults. */
|
|
10
12
|
domain?: MarketingDomain;
|
|
11
13
|
/** Override the conversion model (else taken from the domain preset). */
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAO/F,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,WAAW,CAAA;AAClB,OAAO,KAAK,EACV,iBAAiB,EACjB,qBAAqB,EACrB,sBAAsB,EACvB,MAAM,cAAc,CAAA;AAIrB,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAOjE,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAA;AACvE,OAAO,EAAqB,KAAK,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,eAAe,CAAA;AACpG,OAAO,KAAK,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAclE,MAAM,WAAW,sBAAsB;IACrC,yEAAyE;IACzE,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB,yEAAyE;IACzE,UAAU,CAAC,EAAE,eAAe,CAAA;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,EAAE,kBAAkB,EAAE,CAAA;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAA;IACzC,QAAQ,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAA;IACrC,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAA;IACjC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAA;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,YAAY,CAAC,EAAE,qBAAqB,CAAA;IACpC,6EAA6E;IAC7E,cAAc,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,sBAAsB,CAAA;KAAE,CAAA;IAC1D,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,iBAAiB,EAAE,CAAA;IACxC,4DAA4D;IAC5D,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC,oEAAoE;IACpE,WAAW,CAAC,EAAE,sBAAsB,CAAA;IACpC;6DACyD;IACzD,QAAQ,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CACxE;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAO/F,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,WAAW,CAAA;AAClB,OAAO,KAAK,EACV,iBAAiB,EACjB,qBAAqB,EACrB,sBAAsB,EACvB,MAAM,cAAc,CAAA;AAIrB,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAA;AAOjE,OAAO,EAAoB,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAA;AACvE,OAAO,EAAqB,KAAK,eAAe,EAAE,KAAK,sBAAsB,EAAE,MAAM,eAAe,CAAA;AACpG,OAAO,KAAK,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAclE,MAAM,WAAW,sBAAsB;IACrC,wEAAwE;IACxE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;IACvC,yEAAyE;IACzE,MAAM,CAAC,EAAE,eAAe,CAAA;IACxB,yEAAyE;IACzE,UAAU,CAAC,EAAE,eAAe,CAAA;IAC5B,uEAAuE;IACvE,QAAQ,CAAC,EAAE,kBAAkB,EAAE,CAAA;IAC/B,OAAO,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAA;IACzC,QAAQ,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAA;IACrC,MAAM,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAA;IACjC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAA;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB,YAAY,CAAC,EAAE,qBAAqB,CAAA;IACpC,6EAA6E;IAC7E,cAAc,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,sBAAsB,CAAA;KAAE,CAAA;IAC1D,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,iBAAiB,EAAE,CAAA;IACxC,4DAA4D;IAC5D,iBAAiB,CAAC,EAAE,iBAAiB,CAAA;IACrC,oEAAoE;IACpE,WAAW,CAAC,EAAE,sBAAsB,CAAA;IACpC;6DACyD;IACzD,QAAQ,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CACxE;AA+CD,wBAAgB,qBAAqB,CAAC,OAAO,CAAC,EAAE,sBAAsB,GAAG,cAAc,CA0TtF;AAED,YAAY,EACV,qBAAqB,EACrB,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,cAAc,CAAA;AACrB,YAAY,EACV,kBAAkB,EAClB,eAAe,EACf,QAAQ,EACR,kBAAkB,EAClB,eAAe,GAChB,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,iBAAiB,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAA;AACvE,OAAO,EAAE,2BAA2B,EAAE,MAAM,aAAa,CAAA;AACzD,OAAO,EAAE,+BAA+B,EAAE,MAAM,iBAAiB,CAAA;AACjE,YAAY,EACV,sBAAsB,EACtB,WAAW,EACX,WAAW,EACX,aAAa,EACb,UAAU,EACV,UAAU,EACV,oBAAoB,EACpB,oBAAoB,EACpB,sBAAsB,GACvB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,gCAAgC,EAAE,MAAM,oBAAoB,CAAA;AACrE,OAAO,EAAE,oCAAoC,EAAE,MAAM,wBAAwB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React9, { useState, useEffect, useMemo, useCallback } from 'react';
|
|
2
|
-
import { getActiveTenantId, registerTranslations, createSafeDataProvider, getSupabaseClientOptional, useTranslation, useDataChanged, resolveDataProvider } from '@fayz-ai/core';
|
|
3
|
-
import { createPluginContext, PluginSettingsPanel, useModuleNavigation, createViewRouter, ModuleActionBar, SettingsGroup, SelectRow, ToggleRow, PermissionGate, useLimitGuard, useOrganizationStore, CrudFormPage, invalidateLimit, CrudListView } from '@fayz-ai/admin';
|
|
2
|
+
import { connectionSettings, connectionStatus, settingsStatus, createConnectionStore, getActiveTenantId, registerTranslations, createSafeDataProvider, getSupabaseClientOptional, useTranslation, useDataChanged, resolveDataProvider } from '@fayz-ai/core';
|
|
3
|
+
import { createPluginContext, fayzConnectorOAuth, PluginSettingsPanel, useModuleNavigation, createViewRouter, ModuleActionBar, SettingsGroup, SelectRow, ToggleRow, PermissionGate, useLimitGuard, useOrganizationStore, CrudFormPage, invalidateLimit, CrudListView } from '@fayz-ai/admin';
|
|
4
4
|
import { defineKpiWidget, defineCustomWidget, defineTableWidget, ModulePage, DataTable, SubpageHeader, Button, Skeleton, ConfirmDialog, ContentSplit, ContentSplitTrigger, Input, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, MarkdownEditor, DashboardCanvas, SegmentedControl, cn, Modal, ModalContent, ModalHeader, ModalTitle, ModalBody, ModalFooter, Badge, SelectSeparator, Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetBody, SheetFooter, Checkbox, toast, KpiCard as KpiCard$1, Card, CardHeader, CardTitle, CardContent, TableWidget } from '@fayz-ai/ui';
|
|
5
5
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
6
|
-
import {
|
|
6
|
+
import { History, CheckCircle2, AlertCircle, ChevronRight, Users, Target, Percent, DollarSign, Trash2, Plus, ExternalLink, Sparkles, Pencil, Layers, MousePointerClick, NotebookPen, ArrowUpRight, ArrowDownRight, Settings2, Columns3, CalendarDays, List, ChevronLeft, Loader2, ImagePlus, X, Bike, Utensils, DoorOpen, MessageCircle, Instagram, Mail, Globe, Megaphone, Search, Link2, AtSign, Pin, Twitter, Linkedin, Facebook, Youtube, Music2, Repeat2, Heart, BarChart2, Share2, ThumbsUp, Send, MoreHorizontal, Bookmark, GripVertical, Image, Trophy } from 'lucide-react';
|
|
7
7
|
import { createStore } from 'zustand/vanilla';
|
|
8
8
|
|
|
9
9
|
// src/index.ts
|
|
@@ -2740,7 +2740,7 @@ function buildNav(config, view, navigate) {
|
|
|
2740
2740
|
}
|
|
2741
2741
|
return items;
|
|
2742
2742
|
}
|
|
2743
|
-
function MarketingPage({ config, provider: provider2, store, contentProvider, contentStore }) {
|
|
2743
|
+
function MarketingPage({ config, provider: provider2, store: store3, contentProvider, contentStore }) {
|
|
2744
2744
|
const t = useTranslation();
|
|
2745
2745
|
const { view, direction, navigate } = useModuleNavigation("/marketing", {
|
|
2746
2746
|
overview: 0,
|
|
@@ -2759,11 +2759,11 @@ function MarketingPage({ config, provider: provider2, store, contentProvider, co
|
|
|
2759
2759
|
"blog-cat": 1
|
|
2760
2760
|
}, "overview");
|
|
2761
2761
|
React9.useEffect(() => {
|
|
2762
|
-
void
|
|
2763
|
-
}, [
|
|
2762
|
+
void store3.getState().load();
|
|
2763
|
+
}, [store3]);
|
|
2764
2764
|
useDataChanged({ table: "plg_marketing_campaigns" }, () => {
|
|
2765
|
-
void
|
|
2766
|
-
}, [
|
|
2765
|
+
void store3.getState().refresh();
|
|
2766
|
+
}, [store3]);
|
|
2767
2767
|
const isOverview = view === "overview";
|
|
2768
2768
|
const nav = buildNav(config, view, navigate);
|
|
2769
2769
|
const blogPostEntity = React9.useMemo(() => buildBlogPostEntity(), []);
|
|
@@ -2787,7 +2787,7 @@ function MarketingPage({ config, provider: provider2, store, contentProvider, co
|
|
|
2787
2787
|
{ id: "blog-cat", render: ({ id }) => /* @__PURE__ */ jsx(BlogEntityForm, { entity: blogCategoryEntity, editId: id, parentLabel: config.labels.blogCategories, onDone: () => navigate("blog") }) },
|
|
2788
2788
|
{ id: "overview", render: () => /* @__PURE__ */ jsx(OverviewView, {}) }
|
|
2789
2789
|
], "overview");
|
|
2790
|
-
return /* @__PURE__ */ jsx(MarketingContextProvider, { config, provider: provider2, store, children: /* @__PURE__ */ jsx(ContentPlannerContextProvider, { config, provider: contentProvider, store: contentStore, children: /* @__PURE__ */ jsx(
|
|
2790
|
+
return /* @__PURE__ */ jsx(MarketingContextProvider, { config, provider: provider2, store: store3, children: /* @__PURE__ */ jsx(ContentPlannerContextProvider, { config, provider: contentProvider, store: contentStore, children: /* @__PURE__ */ jsx(
|
|
2791
2791
|
ModulePage,
|
|
2792
2792
|
{
|
|
2793
2793
|
title: config.labels.pageTitle,
|
|
@@ -4593,57 +4593,6 @@ CREATE POLICY plg_marketing_media_insert ON storage.objects FOR INSERT TO authen
|
|
|
4593
4593
|
CREATE POLICY plg_marketing_media_update ON storage.objects FOR UPDATE TO authenticated USING (bucket_id = 'mkt-media');
|
|
4594
4594
|
CREATE POLICY plg_marketing_media_delete ON storage.objects FOR DELETE TO authenticated USING (bucket_id = 'mkt-media');
|
|
4595
4595
|
`;
|
|
4596
|
-
var MIGRATION_004_RANKLAYER = `-- Marketing Plugin: RankLayer integration (SEO content publishing).
|
|
4597
|
-
-- Control-plane state for the RankLayer connector rendered in Marketing \u2192
|
|
4598
|
-
-- Integra\xE7\xF5es. Prefix: plg_marketing_ranklayer_. Scaffold: the real sync
|
|
4599
|
-
-- (edge function + RankLayer API calls) lands via an external PR \u2014 see
|
|
4600
|
-
-- src/integrations/ranklayer/RANKLAYER.md.
|
|
4601
|
-
|
|
4602
|
-
CREATE TABLE IF NOT EXISTS public.plg_marketing_ranklayer_integrations (
|
|
4603
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
4604
|
-
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
4605
|
-
api_key text,
|
|
4606
|
-
site_domain text,
|
|
4607
|
-
active boolean NOT NULL DEFAULT true,
|
|
4608
|
-
last_sync_at timestamptz,
|
|
4609
|
-
created_at timestamptz NOT NULL DEFAULT now(),
|
|
4610
|
-
updated_at timestamptz NOT NULL DEFAULT now()
|
|
4611
|
-
);
|
|
4612
|
-
ALTER TABLE public.plg_marketing_ranklayer_integrations ENABLE ROW LEVEL SECURITY;
|
|
4613
|
-
CREATE UNIQUE INDEX IF NOT EXISTS uq_plg_marketing_ranklayer_integrations_tenant
|
|
4614
|
-
ON public.plg_marketing_ranklayer_integrations(tenant_id);
|
|
4615
|
-
|
|
4616
|
-
CREATE TABLE IF NOT EXISTS public.plg_marketing_ranklayer_sync_log (
|
|
4617
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
4618
|
-
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
4619
|
-
integration_id uuid REFERENCES public.plg_marketing_ranklayer_integrations(id) ON DELETE CASCADE,
|
|
4620
|
-
status text NOT NULL DEFAULT 'success' CHECK (status IN ('success', 'partial', 'error')),
|
|
4621
|
-
message text,
|
|
4622
|
-
fetched integer NOT NULL DEFAULT 0,
|
|
4623
|
-
written integer NOT NULL DEFAULT 0,
|
|
4624
|
-
created_at timestamptz NOT NULL DEFAULT now()
|
|
4625
|
-
);
|
|
4626
|
-
ALTER TABLE public.plg_marketing_ranklayer_sync_log ENABLE ROW LEVEL SECURITY;
|
|
4627
|
-
CREATE INDEX IF NOT EXISTS idx_plg_marketing_ranklayer_sync_log_tenant
|
|
4628
|
-
ON public.plg_marketing_ranklayer_sync_log(tenant_id, created_at DESC);
|
|
4629
|
-
|
|
4630
|
-
-- RLS: authenticated tenant CRUD
|
|
4631
|
-
DROP POLICY IF EXISTS plg_marketing_ranklayer_integrations_select ON public.plg_marketing_ranklayer_integrations;
|
|
4632
|
-
DROP POLICY IF EXISTS plg_marketing_ranklayer_integrations_insert ON public.plg_marketing_ranklayer_integrations;
|
|
4633
|
-
DROP POLICY IF EXISTS plg_marketing_ranklayer_integrations_update ON public.plg_marketing_ranklayer_integrations;
|
|
4634
|
-
DROP POLICY IF EXISTS plg_marketing_ranklayer_integrations_delete ON public.plg_marketing_ranklayer_integrations;
|
|
4635
|
-
CREATE POLICY plg_marketing_ranklayer_integrations_select ON public.plg_marketing_ranklayer_integrations FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
4636
|
-
CREATE POLICY plg_marketing_ranklayer_integrations_insert ON public.plg_marketing_ranklayer_integrations FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
4637
|
-
CREATE POLICY plg_marketing_ranklayer_integrations_update ON public.plg_marketing_ranklayer_integrations FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
4638
|
-
CREATE POLICY plg_marketing_ranklayer_integrations_delete ON public.plg_marketing_ranklayer_integrations FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
4639
|
-
GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_marketing_ranklayer_integrations TO authenticated;
|
|
4640
|
-
|
|
4641
|
-
DROP POLICY IF EXISTS plg_marketing_ranklayer_sync_log_select ON public.plg_marketing_ranklayer_sync_log;
|
|
4642
|
-
DROP POLICY IF EXISTS plg_marketing_ranklayer_sync_log_insert ON public.plg_marketing_ranklayer_sync_log;
|
|
4643
|
-
CREATE POLICY plg_marketing_ranklayer_sync_log_select ON public.plg_marketing_ranklayer_sync_log FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
4644
|
-
CREATE POLICY plg_marketing_ranklayer_sync_log_insert ON public.plg_marketing_ranklayer_sync_log FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
4645
|
-
GRANT SELECT, INSERT ON public.plg_marketing_ranklayer_sync_log TO authenticated;
|
|
4646
|
-
`;
|
|
4647
4596
|
var MIGRATION_005_ANALYTICS_BASE = `-- Marketing Plugin: analytics base tables (channels + campaigns).
|
|
4648
4597
|
-- Prefix: plg_marketing_
|
|
4649
4598
|
-- Channels are tenant-owned rows lazily seeded from the app's domain preset
|
|
@@ -5007,75 +4956,122 @@ WHERE p.kind = 'lead'
|
|
|
5007
4956
|
AND COALESCE(p.is_active, true);
|
|
5008
4957
|
GRANT SELECT ON public.v_marketing_lead_tag_events TO authenticated;
|
|
5009
4958
|
`;
|
|
5010
|
-
var
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5014
|
-
|
|
5015
|
-
|
|
4959
|
+
var MIGRATION_010_RANKLAYER_ONTO_CONNECTIONS = `-- ============================================================================
|
|
4960
|
+
-- Marketing 010 \u2014 RankLayer stops owning tables.
|
|
4961
|
+
--
|
|
4962
|
+
-- Retires plg_marketing_ranklayer_integrations and plg_marketing_ranklayer_sync_log.
|
|
4963
|
+
-- Both were the generic shape under a private name: tenant, active, one config
|
|
4964
|
+
-- field, last sync \u2014 plus a run history beside it. They are now rows of
|
|
4965
|
+
-- public.plg_connections / public.plg_sync_runs (packages/db 029), reached
|
|
4966
|
+
-- through createConnectionStore('ranklayer'). See FAY-1376.
|
|
4967
|
+
--
|
|
4968
|
+
-- Where each column went:
|
|
4969
|
+
-- integrations.site_domain \u2192 plg_connections.settings->>'siteDomain'
|
|
4970
|
+
-- integrations.active \u2192 plg_connections.active
|
|
4971
|
+
-- integrations.last_sync_at \u2192 plg_connections.last_sync_at
|
|
4972
|
+
-- UNIQUE (tenant_id) \u2192 UNIQUE (tenant_id, connector_id, instance_key),
|
|
4973
|
+
-- with instance_key = '' (RankLayer publishes to
|
|
4974
|
+
-- one site per tenant; a second site is a second
|
|
4975
|
+
-- instance_key the day it is asked for)
|
|
4976
|
+
-- sync_log.created_at \u2192 plg_sync_runs.started_at
|
|
4977
|
+
-- sync_log.status \u2192 plg_sync_runs.status
|
|
4978
|
+
-- sync_log.fetched/written \u2192 plg_sync_runs.fetched/written
|
|
4979
|
+
-- sync_log.message \u2192 plg_sync_runs.error on a failed run, and
|
|
4980
|
+
-- plg_sync_runs.stats->>'message' otherwise
|
|
4981
|
+
-- sync_log.integration_id \u2192 plg_sync_runs.connection_id
|
|
4982
|
+
--
|
|
4983
|
+
-- 004 (which created the pair) and 009 (which dropped its api_key column) are
|
|
4984
|
+
-- deleted rather than edited: their ids stay in the ledger of any pool that ran
|
|
4985
|
+
-- them, and this migration is what removes what they left behind. Nothing is in
|
|
4986
|
+
-- production, so this is a drop, not a data migration.
|
|
4987
|
+
--
|
|
4988
|
+
-- One thing is not merely moved: 004 granted INSERT on the sync log to
|
|
4989
|
+
-- \`authenticated\`, so any member of the tenant could forge publication history.
|
|
4990
|
+
-- plg_sync_runs carries no such grant \u2014 runs are written server-side. The
|
|
4991
|
+
-- exposure closes by moving.
|
|
4992
|
+
-- ============================================================================
|
|
4993
|
+
|
|
4994
|
+
DROP TABLE IF EXISTS public.plg_marketing_ranklayer_sync_log CASCADE;
|
|
4995
|
+
DROP TABLE IF EXISTS public.plg_marketing_ranklayer_integrations CASCADE;
|
|
4996
|
+
`;
|
|
4997
|
+
|
|
4998
|
+
// src/integrations/ranklayer/settings.ts
|
|
4999
|
+
var RANKLAYER_SETTINGS = [
|
|
5000
|
+
{
|
|
5001
|
+
key: "siteDomain",
|
|
5002
|
+
label: "Dom\xEDnio do site",
|
|
5003
|
+
description: "O site em que a RankLayer publica os artigos deste blog.",
|
|
5004
|
+
type: "text",
|
|
5005
|
+
placeholder: "exemplo.com.br",
|
|
5006
|
+
requiredToOperate: true
|
|
5007
|
+
}
|
|
5008
|
+
];
|
|
5009
|
+
|
|
5010
|
+
// src/integrations/ranklayer/data/supabase.ts
|
|
5011
|
+
var RANKLAYER_CONNECTOR_ID = "ranklayer";
|
|
5012
|
+
var INSTANCE_KEY = "";
|
|
5013
|
+
function ranklayerConnectionStore() {
|
|
5014
|
+
return createConnectionStore(RANKLAYER_CONNECTOR_ID, {
|
|
5015
|
+
settings: RANKLAYER_SETTINGS,
|
|
5016
|
+
instanceKey: INSTANCE_KEY
|
|
5017
|
+
});
|
|
5016
5018
|
}
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5019
|
+
var store = ranklayerConnectionStore;
|
|
5020
|
+
function toIntegration(connection) {
|
|
5021
|
+
return {
|
|
5022
|
+
id: connection.id ?? "",
|
|
5023
|
+
siteDomain: connection.settings?.siteDomain || void 0,
|
|
5024
|
+
active: connection.active,
|
|
5025
|
+
lastSyncAt: connection.lastSyncAt ?? void 0
|
|
5026
|
+
};
|
|
5021
5027
|
}
|
|
5022
|
-
function
|
|
5028
|
+
function toLogEntry(run) {
|
|
5023
5029
|
return {
|
|
5024
|
-
id:
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5030
|
+
id: run.id ?? "",
|
|
5031
|
+
createdAt: run.startedAt ?? "",
|
|
5032
|
+
status: run.status === "success" ? "success" : "error",
|
|
5033
|
+
message: run.error ?? run.message ?? void 0,
|
|
5034
|
+
fetched: run.fetched ?? 0,
|
|
5035
|
+
written: run.written ?? 0
|
|
5029
5036
|
};
|
|
5030
5037
|
}
|
|
5031
5038
|
function createRankLayerProvider() {
|
|
5032
5039
|
return {
|
|
5033
5040
|
async getIntegration() {
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5037
|
-
|
|
5041
|
+
try {
|
|
5042
|
+
const connection = await store().get();
|
|
5043
|
+
return connection ? toIntegration(connection) : null;
|
|
5044
|
+
} catch {
|
|
5045
|
+
return null;
|
|
5046
|
+
}
|
|
5038
5047
|
},
|
|
5039
5048
|
async saveIntegration(values) {
|
|
5040
|
-
const
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
active: true,
|
|
5046
|
-
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
5047
|
-
};
|
|
5048
|
-
const { data, error } = await getClient3().from(INTEGRATIONS).upsert(payload, { onConflict: "tenant_id" }).select("*").single();
|
|
5049
|
-
if (error) throw error;
|
|
5050
|
-
return toIntegration(data);
|
|
5049
|
+
const saved = await store().save({
|
|
5050
|
+
settings: { siteDomain: values.siteDomain ?? null },
|
|
5051
|
+
active: true
|
|
5052
|
+
});
|
|
5053
|
+
return toIntegration(saved);
|
|
5051
5054
|
},
|
|
5055
|
+
/**
|
|
5056
|
+
* Soft, where the old table hard-deleted the row. The publication history
|
|
5057
|
+
* survives a disconnect now — it is attributed to the connection, and a
|
|
5058
|
+
* tenant that reconnects next week should still see what was published.
|
|
5059
|
+
*/
|
|
5052
5060
|
async disconnect() {
|
|
5053
|
-
|
|
5054
|
-
if (!tenantId) return;
|
|
5055
|
-
await getClient3().from(INTEGRATIONS).delete().eq("tenant_id", tenantId);
|
|
5061
|
+
await store().disconnect();
|
|
5056
5062
|
},
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5063
|
+
async getSyncLog() {
|
|
5064
|
+
try {
|
|
5065
|
+
const runs = await store().listRuns({ limit: 20 });
|
|
5066
|
+
return runs.map(toLogEntry);
|
|
5067
|
+
} catch {
|
|
5068
|
+
return [];
|
|
5062
5069
|
}
|
|
5063
|
-
return { ok: true, message: "Chave aceita (valida\xE7\xE3o real pendente \u2014 integra\xE7\xE3o em prepara\xE7\xE3o)." };
|
|
5064
|
-
},
|
|
5065
|
-
async getSyncLog(integrationId) {
|
|
5066
|
-
const { data } = await getClient3().from(SYNC_LOG).select("*").eq("integration_id", integrationId).order("created_at", { ascending: false }).limit(20);
|
|
5067
|
-
return (data ?? []).map((r) => ({
|
|
5068
|
-
id: r.id,
|
|
5069
|
-
createdAt: r.created_at,
|
|
5070
|
-
status: r.status,
|
|
5071
|
-
message: r.message ?? void 0,
|
|
5072
|
-
fetched: r.fetched ?? 0,
|
|
5073
|
-
written: r.written ?? 0
|
|
5074
|
-
}));
|
|
5075
5070
|
}
|
|
5076
5071
|
};
|
|
5077
5072
|
}
|
|
5078
5073
|
var provider = createRankLayerProvider();
|
|
5074
|
+
var store2 = ranklayerConnectionStore();
|
|
5079
5075
|
function RankLayerExtraPanel() {
|
|
5080
5076
|
const [integration, setIntegration] = useState(null);
|
|
5081
5077
|
const [log, setLog] = useState([]);
|
|
@@ -5087,7 +5083,10 @@ function RankLayerExtraPanel() {
|
|
|
5087
5083
|
const found = await provider.getIntegration();
|
|
5088
5084
|
if (cancelled) return;
|
|
5089
5085
|
setIntegration(found);
|
|
5090
|
-
if (found)
|
|
5086
|
+
if (found) {
|
|
5087
|
+
const rows = await provider.getSyncLog();
|
|
5088
|
+
if (!cancelled) setLog(rows);
|
|
5089
|
+
}
|
|
5091
5090
|
} catch {
|
|
5092
5091
|
} finally {
|
|
5093
5092
|
if (!cancelled) setLoaded(true);
|
|
@@ -5097,35 +5096,27 @@ function RankLayerExtraPanel() {
|
|
|
5097
5096
|
cancelled = true;
|
|
5098
5097
|
};
|
|
5099
5098
|
}, []);
|
|
5100
|
-
if (!loaded || !integration) return null;
|
|
5099
|
+
if (!loaded || !integration?.active) return null;
|
|
5101
5100
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-4 border-t pt-4", children: [
|
|
5102
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-
|
|
5103
|
-
/* @__PURE__ */ jsx(
|
|
5104
|
-
/* @__PURE__ */
|
|
5105
|
-
/* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold", children: "Sincroniza\xE7\xE3o de conte\xFAdo \u2014 em breve" }),
|
|
5106
|
-
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
5107
|
-
"A publica\xE7\xE3o autom\xE1tica de p\xE1ginas e artigos de SEO com a RankLayer est\xE1 em prepara\xE7\xE3o.",
|
|
5108
|
-
integration.siteDomain ? ` Dom\xEDnio configurado: ${integration.siteDomain}.` : "",
|
|
5109
|
-
" Assim que a integra\xE7\xE3o estiver ativa, o hist\xF3rico de sincroniza\xE7\xF5es aparece aqui."
|
|
5110
|
-
] })
|
|
5111
|
-
] })
|
|
5101
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
5102
|
+
/* @__PURE__ */ jsx(History, { className: "h-4 w-4 text-muted-foreground" }),
|
|
5103
|
+
/* @__PURE__ */ jsx("h4", { className: "text-sm font-semibold", children: "Hist\xF3rico de publica\xE7\xF5es" })
|
|
5112
5104
|
] }),
|
|
5113
|
-
log.length
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5105
|
+
log.length === 0 ? /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
5106
|
+
"Nenhuma publica\xE7\xE3o recebida at\xE9 agora. Quem envia \xE9 a RankLayer",
|
|
5107
|
+
integration.siteDomain ? ` (site ${integration.siteDomain})` : "",
|
|
5108
|
+
": a cada envio, uma linha aparece aqui com a data e quantos artigos foram gravados."
|
|
5109
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "divide-y rounded-md border text-sm", children: log.map((r) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-3 py-2", title: r.message, children: [
|
|
5110
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: new Date(r.createdAt).toLocaleString("pt-BR") }),
|
|
5111
|
+
/* @__PURE__ */ jsxs("span", { className: "ml-auto text-xs", children: [
|
|
5112
|
+
r.written,
|
|
5113
|
+
"/",
|
|
5114
|
+
r.fetched,
|
|
5115
|
+
" publicadas"
|
|
5117
5116
|
] }),
|
|
5118
|
-
/* @__PURE__ */ jsx(
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
r.written,
|
|
5122
|
-
"/",
|
|
5123
|
-
r.fetched,
|
|
5124
|
-
" publicadas"
|
|
5125
|
-
] }),
|
|
5126
|
-
r.status === "success" ? /* @__PURE__ */ jsx(CheckCircle2, { className: "h-3.5 w-3.5 text-success" }) : /* @__PURE__ */ jsx(AlertCircle, { className: "h-3.5 w-3.5 text-warning" })
|
|
5127
|
-
] }, r.id)) })
|
|
5128
|
-
] })
|
|
5117
|
+
r.status === "success" ? /* @__PURE__ */ jsx(CheckCircle2, { className: "h-3.5 w-3.5 text-success" }) : /* @__PURE__ */ jsx(AlertCircle, { className: "h-3.5 w-3.5 text-warning" })
|
|
5118
|
+
] }, r.id)) }),
|
|
5119
|
+
/* @__PURE__ */ jsx("p", { className: "text-[11px] text-muted-foreground", children: "A chave que a RankLayer usa para publicar aqui \xE9 emitida na plataforma Fayz e n\xE3o fica guardada neste aplicativo." })
|
|
5129
5120
|
] });
|
|
5130
5121
|
}
|
|
5131
5122
|
var RANKLAYER_MARK = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIzMiIgaGVpZ2h0PSIzMiIgdmlld0JveD0iMCAwIDMyIDMyIj48cmVjdCB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHJ4PSI3IiBmaWxsPSIjMUYyNDMxIi8+PHBvbHlnb24gcG9pbnRzPSIxNiw1LjYgMjYuNCwxMS42IDE2LDE3LjYgNS42LDExLjYiIGZpbGw9IiNGRkZGRkYiIHN0cm9rZT0iI0ZGRkZGRiIgc3Ryb2tlLXdpZHRoPSIxLjI1IiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PGcgb3BhY2l0eT0iMC41NSI+PHBvbHlnb24gcG9pbnRzPSI1LjYsMTYuNCAxNiwyMi40IDI2LjQsMTYuNCAyNi40LDE4LjIgMTYsMjQuMiA1LjYsMTguMiIgZmlsbD0iI0ZGRkZGRiIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjEuMjUiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48L2c+PGcgb3BhY2l0eT0iMC4yOCI+PHBvbHlnb24gcG9pbnRzPSI1LjYsMjEuMiAxNiwyNy4yIDI2LjQsMjEuMiAyNi40LDIzIDE2LDI5IDUuNiwyMyIgZmlsbD0iI0ZGRkZGRiIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjEuMjUiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48L2c+PC9zdmc+";
|
|
@@ -5136,22 +5127,84 @@ var ranklayerConnectorDef = {
|
|
|
5136
5127
|
description: "Publica\xE7\xE3o de conte\xFAdo e p\xE1ginas de SEO por servi\xE7o/bairro para ser encontrado no Google e em assistentes de IA.",
|
|
5137
5128
|
icon: "Search",
|
|
5138
5129
|
logoUrl: RANKLAYER_MARK,
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5130
|
+
// OAuth, not a pasted key: what the tenant is doing is linking their RankLayer
|
|
5131
|
+
// account, and a key typed into this app would be a second copy of a secret
|
|
5132
|
+
// the platform already holds — the exact shape migration 009 deleted.
|
|
5133
|
+
authKind: "oauth",
|
|
5134
|
+
// The catalogue files RankLayer under "Analytics & Marketing"
|
|
5135
|
+
// (docs/site/data/integrations.json).
|
|
5136
|
+
category: "analytics",
|
|
5137
|
+
// Inbound because RankLayer reaches us: it writes the articles in. The edit
|
|
5138
|
+
// that goes back is delivered by the Fayz ingress, not by this app.
|
|
5139
|
+
direction: "inbound",
|
|
5140
|
+
docsUrl: "https://ranklayer.app",
|
|
5141
|
+
// The write is grounded: RankLayer's server calls `blog_upsert_external_post`
|
|
5142
|
+
// (plugin-blog migrations §external), which INSERTs/UPDATEs plg_blog_posts and
|
|
5143
|
+
// refuses a credential not scoped to exactly one tenant.
|
|
5144
|
+
//
|
|
5145
|
+
// The read is grounded only as far as the managed automation below — sending
|
|
5146
|
+
// an edit back means reading the article. That push is declared and its
|
|
5147
|
+
// anti-loop guard exists (./outbound.ts `shouldPushToRankLayer`), but nothing
|
|
5148
|
+
// calls it: the ingress that would is the same one that does not exist. Kept
|
|
5149
|
+
// rather than dropped because the automation IS declared, and a permission
|
|
5150
|
+
// list that contradicts the automation next to it is the defect this whole
|
|
5151
|
+
// declaration exists to prevent.
|
|
5152
|
+
permissions: [
|
|
5153
|
+
{
|
|
5154
|
+
kind: "data",
|
|
5155
|
+
resource: "blog_post",
|
|
5156
|
+
actions: ["read", "write"],
|
|
5157
|
+
reason: "Publicar aqui os artigos que a RankLayer escreve, e ler os artigos daqui para mandar de volta as edi\xE7\xF5es que voc\xEA fizer."
|
|
5158
|
+
},
|
|
5159
|
+
{
|
|
5160
|
+
kind: "event",
|
|
5161
|
+
resource: "blog.post.*",
|
|
5162
|
+
actions: ["listen"],
|
|
5163
|
+
reason: "Saber quando um artigo \xE9 criado, editado, publicado ou apagado aqui."
|
|
5150
5164
|
}
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5165
|
+
],
|
|
5166
|
+
/**
|
|
5167
|
+
* No form. The connect step authorizes and nothing else; the site domain is
|
|
5168
|
+
* configuration and lives on the Configurações tab (./settings.ts).
|
|
5169
|
+
*
|
|
5170
|
+
* TODO(ranklayer): the consent screen only opens once the platform has a
|
|
5171
|
+
* `ranklayer` OAuth provider registered — client id/secret and the authorize
|
|
5172
|
+
* and token URLs, plus our redirect URI allow-listed on RankLayer's side. Until
|
|
5173
|
+
* then Conectar answers `config_missing` and the tenant is told, in as many
|
|
5174
|
+
* words, that this is not something they can fix. Nothing in this repository
|
|
5175
|
+
* changes when it lands.
|
|
5176
|
+
*/
|
|
5177
|
+
startOAuth: fayzConnectorOAuth(RANKLAYER_CONNECTOR_ID),
|
|
5178
|
+
settings: RANKLAYER_SETTINGS,
|
|
5179
|
+
subscribesTo: ["blog.post.*"],
|
|
5180
|
+
managedAutomations: [
|
|
5181
|
+
{ eventKey: "blog.post.updated", label: "Enviar para a RankLayer as edi\xE7\xF5es feitas no artigo aqui" }
|
|
5182
|
+
],
|
|
5183
|
+
// Read from the row the list already batched — one request for N connectors.
|
|
5184
|
+
...connectionStatus(
|
|
5185
|
+
RANKLAYER_CONNECTOR_ID,
|
|
5186
|
+
(connection) => {
|
|
5187
|
+
const found = connection ? toIntegration(connection) : null;
|
|
5188
|
+
return {
|
|
5189
|
+
connected: !!found?.active,
|
|
5190
|
+
detail: found?.siteDomain,
|
|
5191
|
+
plane: "sync",
|
|
5192
|
+
// "Conectado, falta configurar" while the domain is empty — which is
|
|
5193
|
+
// true, and is the difference between not ready and broken.
|
|
5194
|
+
...settingsStatus(RANKLAYER_SETTINGS, connection)
|
|
5195
|
+
};
|
|
5196
|
+
},
|
|
5197
|
+
{ store: () => store2 }
|
|
5198
|
+
),
|
|
5199
|
+
...connectionSettings(store2),
|
|
5200
|
+
/**
|
|
5201
|
+
* Called with no values: the credential is not ours to see, and the platform's
|
|
5202
|
+
* redirect route does not write this pool's `plg_connections` (FAY-1388) — the
|
|
5203
|
+
* app writes its own row, or the tenant authorizes, comes back, and reads "not
|
|
5204
|
+
* connected" forever. Bling shipped with the other belief and looped.
|
|
5205
|
+
*/
|
|
5206
|
+
saveConnection: async () => {
|
|
5207
|
+
await store2.save({ active: true });
|
|
5155
5208
|
},
|
|
5156
5209
|
disconnect: () => provider.disconnect(),
|
|
5157
5210
|
ExtraPanel: RankLayerExtraPanel
|
|
@@ -5573,7 +5626,8 @@ function resolveConfig(options) {
|
|
|
5573
5626
|
channels: options?.channels ?? preset.channels,
|
|
5574
5627
|
modules: { ...preset.modules, ...options?.modules },
|
|
5575
5628
|
currency: { ...DEFAULT_CURRENCY, ...options?.currency },
|
|
5576
|
-
labels: { ...DEFAULT_LABELS, ...options?.labels }
|
|
5629
|
+
labels: { ...DEFAULT_LABELS, ...options?.labels },
|
|
5630
|
+
moduleNav: options?.moduleNav
|
|
5577
5631
|
};
|
|
5578
5632
|
return { config, domain };
|
|
5579
5633
|
}
|
|
@@ -5593,8 +5647,8 @@ function createMarketingPlugin(options) {
|
|
|
5593
5647
|
registerTranslations(marketingLocales);
|
|
5594
5648
|
const { config, domain } = resolveConfig(options);
|
|
5595
5649
|
const provider2 = options?.dataProvider ?? createSafeProvider(config, domain, options);
|
|
5596
|
-
const
|
|
5597
|
-
const dashboardWidgets = createMarketingDashboardWidgets({ config, provider: provider2, store });
|
|
5650
|
+
const store3 = createMarketingStore(provider2);
|
|
5651
|
+
const dashboardWidgets = createMarketingDashboardWidgets({ config, provider: provider2, store: store3 });
|
|
5598
5652
|
const contentProvider = options?.contentPlanner?.dataProvider ?? createSafeDataProvider(
|
|
5599
5653
|
() => createSupabaseContentPlannerProvider(),
|
|
5600
5654
|
() => createMockContentPlannerProvider()
|
|
@@ -5602,8 +5656,8 @@ function createMarketingPlugin(options) {
|
|
|
5602
5656
|
const contentStore = createContentPlannerStore(contentProvider);
|
|
5603
5657
|
const PageComponent = () => React9.createElement(
|
|
5604
5658
|
MarketingContextProvider,
|
|
5605
|
-
{ config, provider: provider2, store },
|
|
5606
|
-
React9.createElement(MarketingPage, { config, provider: provider2, store, contentProvider, contentStore })
|
|
5659
|
+
{ config, provider: provider2, store: store3 },
|
|
5660
|
+
React9.createElement(MarketingPage, { config, provider: provider2, store: store3, contentProvider, contentStore })
|
|
5607
5661
|
);
|
|
5608
5662
|
PageComponent.displayName = "MarketingPage";
|
|
5609
5663
|
const marketingRegistries = [
|
|
@@ -5612,7 +5666,7 @@ function createMarketingPlugin(options) {
|
|
|
5612
5666
|
];
|
|
5613
5667
|
const SettingsComponent = () => React9.createElement(
|
|
5614
5668
|
MarketingContextProvider,
|
|
5615
|
-
{ config, provider: provider2, store },
|
|
5669
|
+
{ config, provider: provider2, store: store3 },
|
|
5616
5670
|
React9.createElement(PluginSettingsPanel, {
|
|
5617
5671
|
title: config.labels.settings ?? config.labels.pageTitle,
|
|
5618
5672
|
subtitle: config.labels.pageSubtitle,
|
|
@@ -5783,12 +5837,6 @@ function createMarketingPlugin(options) {
|
|
|
5783
5837
|
sql: MIGRATION_003_RECORDING_OPS,
|
|
5784
5838
|
description: "Recording-day checklist + media asset URL on posts; content-media storage bucket"
|
|
5785
5839
|
},
|
|
5786
|
-
{
|
|
5787
|
-
id: "marketing-004-ranklayer",
|
|
5788
|
-
version: "1.3.0",
|
|
5789
|
-
sql: MIGRATION_004_RANKLAYER,
|
|
5790
|
-
description: "RankLayer connector state: plg_marketing_ranklayer_integrations + _sync_log (tenant RLS)"
|
|
5791
|
-
},
|
|
5792
5840
|
{
|
|
5793
5841
|
id: "marketing-005-analytics-base",
|
|
5794
5842
|
version: "1.4.0",
|
|
@@ -5812,10 +5860,17 @@ function createMarketingPlugin(options) {
|
|
|
5812
5860
|
version: "1.5.0",
|
|
5813
5861
|
sql: MIGRATION_008_LANDING_PAGES,
|
|
5814
5862
|
description: "Sites + landing pages (site + route + lead tag) with tenant RLS, v_marketing_landing_pages and v_marketing_lead_tag_events"
|
|
5863
|
+
},
|
|
5864
|
+
{
|
|
5865
|
+
id: "marketing-010-ranklayer-onto-connections",
|
|
5866
|
+
version: "1.6.0",
|
|
5867
|
+
sql: MIGRATION_010_RANKLAYER_ONTO_CONNECTIONS,
|
|
5868
|
+
description: "Retire plg_marketing_ranklayer_integrations + _sync_log \u2014 the connection and its publication history are rows of plg_connections/plg_sync_runs (packages/db 029)"
|
|
5815
5869
|
}
|
|
5816
5870
|
],
|
|
5817
|
-
// RankLayer SEO connector — renders in the Integrações settings tab.
|
|
5818
|
-
//
|
|
5871
|
+
// RankLayer SEO connector — renders in the Integrações settings tab. A push
|
|
5872
|
+
// connector: RankLayer writes posts in and subscribes to blog.post.* to hear
|
|
5873
|
+
// the edits back. See src/integrations/ranklayer/RANKLAYER.md.
|
|
5819
5874
|
connectors: [ranklayerConnectorDef],
|
|
5820
5875
|
aiTools: [
|
|
5821
5876
|
{
|
|
@@ -5890,6 +5945,9 @@ function createMarketingPlugin(options) {
|
|
|
5890
5945
|
permission: { feature: "marketing", action: "read" }
|
|
5891
5946
|
}
|
|
5892
5947
|
],
|
|
5948
|
+
// Google Meu Negócio sits on the same shelf as RankLayer above: being
|
|
5949
|
+
// found locally, not being measured.
|
|
5950
|
+
integrations: ["google-my-business"],
|
|
5893
5951
|
locales: marketingLocales
|
|
5894
5952
|
};
|
|
5895
5953
|
}
|