@fayz-ai/plugin-marketing 0.2.1 → 0.8.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/README.md CHANGED
@@ -5,6 +5,8 @@
5
5
  [![npm](https://img.shields.io/npm/v/@fayz-ai/plugin-marketing.svg)](https://www.npmjs.com/package/@fayz-ai/plugin-marketing)
6
6
  [![license](https://img.shields.io/npm/l/@fayz-ai/plugin-marketing.svg)](https://github.com/FayaLabs/fayz-sdk/blob/main/LICENSE)
7
7
 
8
+ **Status:** beta — pre-1.0. Core surface is stable; some backend facets (see `PLUGIN_PATTERNS.md`) are still landing. APIs may change before 1.0.
9
+
8
10
  Marketing analytics are always the same shape — channels, campaigns, a funnel, landing-page CVR — but what counts as a "conversion" changes per business. A salon converts a booking, a store converts an order, an agency converts a lead. `plugin-marketing` is one plugin that adapts to all of them: pick a `domain` preset (or pass an explicit conversion model + channels) and the funnel, channel performance, and cost-per-acquisition are computed generically on top of it.
9
11
 
10
12
  It ships a vertical-flavored mock today so the surface is real from the first install. Real attribution arrives through clean DI seams — an `AttributionBridge` to read conversions from CRM/agenda/orders, and a `SitesPerformanceBridge` to read landing-page numbers from your sites — so you wire reality in without rewriting the plugin. Campaigns and channels are first-class, and the whole thing exposes AI tools so your assistant can answer "which channel converts best?" out loud.
@@ -1 +1 @@
1
- {"version":3,"file":"contentSupabase.d.ts","sourceRoot":"","sources":["../../src/data/contentSupabase.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,sBAAsB,EAGvB,MAAM,gBAAgB,CAAA;AAmDvB,wBAAgB,oCAAoC,IAAI,sBAAsB,CA4M7E"}
1
+ {"version":3,"file":"contentSupabase.d.ts","sourceRoot":"","sources":["../../src/data/contentSupabase.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,sBAAsB,EAGvB,MAAM,gBAAgB,CAAA;AAoDvB,wBAAgB,oCAAoC,IAAI,sBAAsB,CA4M7E"}
@@ -0,0 +1,6 @@
1
+ export declare const T: {
2
+ readonly socialAccounts: "plg_marketing_social_accounts";
3
+ readonly contentPlans: "plg_marketing_content_plans";
4
+ readonly contentPosts: "plg_marketing_content_posts";
5
+ };
6
+ //# sourceMappingURL=tables.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tables.d.ts","sourceRoot":"","sources":["../../src/data/tables.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,CAAC;;;;CAIJ,CAAA"}
package/dist/index.cjs CHANGED
@@ -3034,6 +3034,15 @@ function createMockContentPlannerProvider() {
3034
3034
  }
3035
3035
  };
3036
3036
  }
3037
+
3038
+ // src/data/tables.ts
3039
+ var T = {
3040
+ socialAccounts: "plg_marketing_social_accounts",
3041
+ contentPlans: "plg_marketing_content_plans",
3042
+ contentPosts: "plg_marketing_content_posts"
3043
+ };
3044
+
3045
+ // src/data/contentSupabase.ts
3037
3046
  function getClient() {
3038
3047
  const supabase = core.getSupabaseClientOptional();
3039
3048
  if (!supabase) throw new Error("Supabase not initialized");
@@ -3075,7 +3084,7 @@ function createSupabaseContentPlannerProvider() {
3075
3084
  async listAccounts() {
3076
3085
  const tenantId = core.getActiveTenantId();
3077
3086
  if (!tenantId) return [];
3078
- const { data } = await getClient().from("mkt_social_accounts").select("*").eq("tenant_id", tenantId).eq("is_active", true).order("created_at", { ascending: true });
3087
+ const { data } = await getClient().from(T.socialAccounts).select("*").eq("tenant_id", tenantId).eq("is_active", true).order("created_at", { ascending: true });
3079
3088
  return (data ?? []).map(toAccount);
3080
3089
  },
3081
3090
  async saveAccount(input) {
@@ -3085,11 +3094,11 @@ function createSupabaseContentPlannerProvider() {
3085
3094
  if (input.handle !== void 0) payload.handle = input.handle;
3086
3095
  if (input.platforms !== void 0) payload.platforms = input.platforms;
3087
3096
  if (input.isActive !== void 0) payload.is_active = input.isActive;
3088
- const { data: data2, error: error2 } = await getClient().from("mkt_social_accounts").update(payload).eq("id", input.id).select("*").single();
3097
+ const { data: data2, error: error2 } = await getClient().from(T.socialAccounts).update(payload).eq("id", input.id).select("*").single();
3089
3098
  if (error2) throw error2;
3090
3099
  return toAccount(data2);
3091
3100
  }
3092
- const { data, error } = await getClient().from("mkt_social_accounts").insert({
3101
+ const { data, error } = await getClient().from(T.socialAccounts).insert({
3093
3102
  tenant_id: requireTenantId(),
3094
3103
  name: input.name,
3095
3104
  handle: input.handle ?? null,
@@ -3100,17 +3109,17 @@ function createSupabaseContentPlannerProvider() {
3100
3109
  return toAccount(data);
3101
3110
  },
3102
3111
  async deleteAccount(id) {
3103
- const { error } = await getClient().from("mkt_social_accounts").delete().eq("id", id);
3112
+ const { error } = await getClient().from(T.socialAccounts).delete().eq("id", id);
3104
3113
  if (error) throw error;
3105
3114
  },
3106
3115
  async listPlans(accountId) {
3107
3116
  const tenantId = core.getActiveTenantId();
3108
3117
  if (!tenantId) return [];
3109
- const { data } = await getClient().from("mkt_content_plans").select("*").eq("tenant_id", tenantId).eq("account_id", accountId).order("created_at", { ascending: false });
3118
+ const { data } = await getClient().from(T.contentPlans).select("*").eq("tenant_id", tenantId).eq("account_id", accountId).order("created_at", { ascending: false });
3110
3119
  return (data ?? []).map(toPlan);
3111
3120
  },
3112
3121
  async getPlan(id) {
3113
- const { data } = await getClient().from("mkt_content_plans").select("*").eq("id", id).maybeSingle();
3122
+ const { data } = await getClient().from(T.contentPlans).select("*").eq("id", id).maybeSingle();
3114
3123
  return data ? toPlan(data) : null;
3115
3124
  },
3116
3125
  async savePlan(input) {
@@ -3126,12 +3135,12 @@ function createSupabaseContentPlannerProvider() {
3126
3135
  if (input.formats !== void 0) payload.formats = input.formats;
3127
3136
  if (input.weeklyFrequency !== void 0) payload.weekly_frequency = input.weeklyFrequency;
3128
3137
  if (input.briefMd !== void 0) payload.brief_md = input.briefMd;
3129
- const { data: data2, error: error2 } = await getClient().from("mkt_content_plans").update(payload).eq("id", input.id).select("*").single();
3138
+ const { data: data2, error: error2 } = await getClient().from(T.contentPlans).update(payload).eq("id", input.id).select("*").single();
3130
3139
  if (error2) throw error2;
3131
3140
  return toPlan(data2);
3132
3141
  }
3133
3142
  if (!input.accountId) throw new Error("accountId is required");
3134
- const { data, error } = await getClient().from("mkt_content_plans").insert({
3143
+ const { data, error } = await getClient().from(T.contentPlans).insert({
3135
3144
  tenant_id: requireTenantId(),
3136
3145
  account_id: input.accountId,
3137
3146
  name: input.name ?? "Novo plano",
@@ -3149,14 +3158,14 @@ function createSupabaseContentPlannerProvider() {
3149
3158
  return toPlan(data);
3150
3159
  },
3151
3160
  async deletePlan(id) {
3152
- await getClient().from("mkt_content_plans").delete().eq("id", id);
3161
+ await getClient().from(T.contentPlans).delete().eq("id", id);
3153
3162
  },
3154
3163
  async listPosts(planId) {
3155
- const { data } = await getClient().from("mkt_content_posts").select("*").eq("plan_id", planId).order("week_number", { ascending: true }).order("position", { ascending: true });
3164
+ const { data } = await getClient().from(T.contentPosts).select("*").eq("plan_id", planId).order("week_number", { ascending: true }).order("position", { ascending: true });
3156
3165
  return (data ?? []).map(toPost);
3157
3166
  },
3158
3167
  async getPost(id) {
3159
- const { data } = await getClient().from("mkt_content_posts").select("*").eq("id", id).maybeSingle();
3168
+ const { data } = await getClient().from(T.contentPosts).select("*").eq("id", id).maybeSingle();
3160
3169
  return data ? toPost(data) : null;
3161
3170
  },
3162
3171
  async savePost(input) {
@@ -3174,15 +3183,15 @@ function createSupabaseContentPlannerProvider() {
3174
3183
  if (input.hook !== void 0) payload.hook = input.hook;
3175
3184
  if (input.cta !== void 0) payload.cta = input.cta;
3176
3185
  if (input.contentMd !== void 0) payload.content_md = input.contentMd;
3177
- const { data: data2, error: error2 } = await getClient().from("mkt_content_posts").update(payload).eq("id", input.id).select("*").single();
3186
+ const { data: data2, error: error2 } = await getClient().from(T.contentPosts).update(payload).eq("id", input.id).select("*").single();
3178
3187
  if (error2) throw error2;
3179
3188
  return toPost(data2);
3180
3189
  }
3181
3190
  if (!input.planId) throw new Error("planId is required");
3182
3191
  const week = input.weekNumber ?? 1;
3183
- const { data: maxData } = await getClient().from("mkt_content_posts").select("position").eq("plan_id", input.planId).eq("week_number", week).order("position", { ascending: false }).limit(1);
3192
+ const { data: maxData } = await getClient().from(T.contentPosts).select("position").eq("plan_id", input.planId).eq("week_number", week).order("position", { ascending: false }).limit(1);
3184
3193
  const maxPos = maxData?.[0]?.position ?? -1;
3185
- const { data, error } = await getClient().from("mkt_content_posts").insert({
3194
+ const { data, error } = await getClient().from(T.contentPosts).insert({
3186
3195
  tenant_id: requireTenantId(),
3187
3196
  plan_id: input.planId,
3188
3197
  week_number: week,
@@ -3202,7 +3211,7 @@ function createSupabaseContentPlannerProvider() {
3202
3211
  return toPost(data);
3203
3212
  },
3204
3213
  async deletePost(id) {
3205
- await getClient().from("mkt_content_posts").delete().eq("id", id);
3214
+ await getClient().from(T.contentPosts).delete().eq("id", id);
3206
3215
  }
3207
3216
  };
3208
3217
  }
@@ -3353,13 +3362,29 @@ function createContentPlannerStore(provider) {
3353
3362
  }
3354
3363
 
3355
3364
  // src/migrations/index.ts
3365
+ var MIGRATION_000_PLG_RENAME = `-- 000_plg_rename.sql \u2014 rename legacy marketing tables to plg_marketing_* for pools
3366
+ -- provisioned before the industry-pool rename. Guarded: fires only when legacy
3367
+ -- name exists and target does not, so fresh pools skip every branch.
3368
+ DO $$
3369
+ BEGIN
3370
+ IF to_regclass('public.mkt_social_accounts') IS NOT NULL AND to_regclass('public.plg_marketing_social_accounts') IS NULL THEN
3371
+ ALTER TABLE public.mkt_social_accounts RENAME TO plg_marketing_social_accounts;
3372
+ END IF;
3373
+ IF to_regclass('public.mkt_content_plans') IS NOT NULL AND to_regclass('public.plg_marketing_content_plans') IS NULL THEN
3374
+ ALTER TABLE public.mkt_content_plans RENAME TO plg_marketing_content_plans;
3375
+ END IF;
3376
+ IF to_regclass('public.mkt_content_posts') IS NOT NULL AND to_regclass('public.plg_marketing_content_posts') IS NULL THEN
3377
+ ALTER TABLE public.mkt_content_posts RENAME TO plg_marketing_content_posts;
3378
+ END IF;
3379
+ END $$;
3380
+ `;
3356
3381
  var MIGRATION_001_CONTENT_PLANNER = `-- Marketing Plugin: Content Planner
3357
- -- Prefix: mkt_
3382
+ -- Prefix: plg_marketing_
3358
3383
  -- Social accounts + content plans + posts (Notion-style markdown pages).
3359
3384
 
3360
- CREATE TABLE IF NOT EXISTS public.mkt_social_accounts (
3385
+ CREATE TABLE IF NOT EXISTS public.plg_marketing_social_accounts (
3361
3386
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
3362
- tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
3387
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
3363
3388
  name text NOT NULL,
3364
3389
  handle text,
3365
3390
  platform text NOT NULL DEFAULT 'instagram'
@@ -3367,13 +3392,13 @@ CREATE TABLE IF NOT EXISTS public.mkt_social_accounts (
3367
3392
  is_active boolean NOT NULL DEFAULT true,
3368
3393
  created_at timestamptz NOT NULL DEFAULT now()
3369
3394
  );
3370
- ALTER TABLE public.mkt_social_accounts ENABLE ROW LEVEL SECURITY;
3371
- CREATE INDEX IF NOT EXISTS idx_mkt_social_accounts_tenant ON public.mkt_social_accounts(tenant_id);
3395
+ ALTER TABLE public.plg_marketing_social_accounts ENABLE ROW LEVEL SECURITY;
3396
+ CREATE INDEX IF NOT EXISTS idx_plg_marketing_social_accounts_tenant ON public.plg_marketing_social_accounts(tenant_id);
3372
3397
 
3373
- CREATE TABLE IF NOT EXISTS public.mkt_content_plans (
3398
+ CREATE TABLE IF NOT EXISTS public.plg_marketing_content_plans (
3374
3399
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
3375
- tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
3376
- account_id uuid NOT NULL REFERENCES public.mkt_social_accounts(id) ON DELETE CASCADE,
3400
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
3401
+ account_id uuid NOT NULL REFERENCES public.plg_marketing_social_accounts(id) ON DELETE CASCADE,
3377
3402
  name text NOT NULL,
3378
3403
  status text NOT NULL DEFAULT 'active'
3379
3404
  CHECK (status IN ('draft', 'active', 'archived')),
@@ -3388,14 +3413,14 @@ CREATE TABLE IF NOT EXISTS public.mkt_content_plans (
3388
3413
  created_at timestamptz NOT NULL DEFAULT now(),
3389
3414
  updated_at timestamptz NOT NULL DEFAULT now()
3390
3415
  );
3391
- ALTER TABLE public.mkt_content_plans ENABLE ROW LEVEL SECURITY;
3392
- CREATE INDEX IF NOT EXISTS idx_mkt_content_plans_tenant ON public.mkt_content_plans(tenant_id);
3393
- CREATE INDEX IF NOT EXISTS idx_mkt_content_plans_account ON public.mkt_content_plans(account_id);
3416
+ ALTER TABLE public.plg_marketing_content_plans ENABLE ROW LEVEL SECURITY;
3417
+ CREATE INDEX IF NOT EXISTS idx_plg_marketing_content_plans_tenant ON public.plg_marketing_content_plans(tenant_id);
3418
+ CREATE INDEX IF NOT EXISTS idx_plg_marketing_content_plans_account ON public.plg_marketing_content_plans(account_id);
3394
3419
 
3395
- CREATE TABLE IF NOT EXISTS public.mkt_content_posts (
3420
+ CREATE TABLE IF NOT EXISTS public.plg_marketing_content_posts (
3396
3421
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
3397
- tenant_id uuid NOT NULL REFERENCES saas_core.tenants(id) ON DELETE CASCADE,
3398
- plan_id uuid NOT NULL REFERENCES public.mkt_content_plans(id) ON DELETE CASCADE,
3422
+ tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
3423
+ plan_id uuid NOT NULL REFERENCES public.plg_marketing_content_plans(id) ON DELETE CASCADE,
3399
3424
  week_number integer NOT NULL DEFAULT 1,
3400
3425
  position integer NOT NULL DEFAULT 0,
3401
3426
  title text NOT NULL,
@@ -3410,49 +3435,50 @@ CREATE TABLE IF NOT EXISTS public.mkt_content_posts (
3410
3435
  created_at timestamptz NOT NULL DEFAULT now(),
3411
3436
  updated_at timestamptz NOT NULL DEFAULT now()
3412
3437
  );
3413
- ALTER TABLE public.mkt_content_posts ENABLE ROW LEVEL SECURITY;
3414
- CREATE INDEX IF NOT EXISTS idx_mkt_content_posts_tenant ON public.mkt_content_posts(tenant_id);
3415
- CREATE INDEX IF NOT EXISTS idx_mkt_content_posts_plan ON public.mkt_content_posts(plan_id, week_number, position);
3416
- CREATE INDEX IF NOT EXISTS idx_mkt_content_posts_status ON public.mkt_content_posts(tenant_id, status);
3417
-
3418
- -- RLS policies for mkt_social_accounts
3419
- DROP POLICY IF EXISTS mkt_social_accounts_select ON public.mkt_social_accounts;
3420
- DROP POLICY IF EXISTS mkt_social_accounts_insert ON public.mkt_social_accounts;
3421
- DROP POLICY IF EXISTS mkt_social_accounts_update ON public.mkt_social_accounts;
3422
- DROP POLICY IF EXISTS mkt_social_accounts_delete ON public.mkt_social_accounts;
3423
- CREATE POLICY mkt_social_accounts_select ON public.mkt_social_accounts FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
3424
- CREATE POLICY mkt_social_accounts_insert ON public.mkt_social_accounts FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
3425
- CREATE POLICY mkt_social_accounts_update ON public.mkt_social_accounts FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
3426
- CREATE POLICY mkt_social_accounts_delete ON public.mkt_social_accounts FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
3427
- GRANT SELECT, INSERT, UPDATE, DELETE ON public.mkt_social_accounts TO authenticated;
3428
-
3429
- -- RLS policies for mkt_content_plans
3430
- DROP POLICY IF EXISTS mkt_content_plans_select ON public.mkt_content_plans;
3431
- DROP POLICY IF EXISTS mkt_content_plans_insert ON public.mkt_content_plans;
3432
- DROP POLICY IF EXISTS mkt_content_plans_update ON public.mkt_content_plans;
3433
- DROP POLICY IF EXISTS mkt_content_plans_delete ON public.mkt_content_plans;
3434
- CREATE POLICY mkt_content_plans_select ON public.mkt_content_plans FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
3435
- CREATE POLICY mkt_content_plans_insert ON public.mkt_content_plans FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
3436
- CREATE POLICY mkt_content_plans_update ON public.mkt_content_plans FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
3437
- CREATE POLICY mkt_content_plans_delete ON public.mkt_content_plans FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
3438
- GRANT SELECT, INSERT, UPDATE, DELETE ON public.mkt_content_plans TO authenticated;
3439
-
3440
- -- RLS policies for mkt_content_posts
3441
- DROP POLICY IF EXISTS mkt_content_posts_select ON public.mkt_content_posts;
3442
- DROP POLICY IF EXISTS mkt_content_posts_insert ON public.mkt_content_posts;
3443
- DROP POLICY IF EXISTS mkt_content_posts_update ON public.mkt_content_posts;
3444
- DROP POLICY IF EXISTS mkt_content_posts_delete ON public.mkt_content_posts;
3445
- CREATE POLICY mkt_content_posts_select ON public.mkt_content_posts FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
3446
- CREATE POLICY mkt_content_posts_insert ON public.mkt_content_posts FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
3447
- CREATE POLICY mkt_content_posts_update ON public.mkt_content_posts FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
3448
- CREATE POLICY mkt_content_posts_delete ON public.mkt_content_posts FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
3449
- GRANT SELECT, INSERT, UPDATE, DELETE ON public.mkt_content_posts TO authenticated;`;
3438
+ ALTER TABLE public.plg_marketing_content_posts ENABLE ROW LEVEL SECURITY;
3439
+ CREATE INDEX IF NOT EXISTS idx_plg_marketing_content_posts_tenant ON public.plg_marketing_content_posts(tenant_id);
3440
+ CREATE INDEX IF NOT EXISTS idx_plg_marketing_content_posts_plan ON public.plg_marketing_content_posts(plan_id, week_number, position);
3441
+ CREATE INDEX IF NOT EXISTS idx_plg_marketing_content_posts_status ON public.plg_marketing_content_posts(tenant_id, status);
3442
+
3443
+ -- RLS policies for plg_marketing_social_accounts
3444
+ DROP POLICY IF EXISTS plg_marketing_social_accounts_select ON public.plg_marketing_social_accounts;
3445
+ DROP POLICY IF EXISTS plg_marketing_social_accounts_insert ON public.plg_marketing_social_accounts;
3446
+ DROP POLICY IF EXISTS plg_marketing_social_accounts_update ON public.plg_marketing_social_accounts;
3447
+ DROP POLICY IF EXISTS plg_marketing_social_accounts_delete ON public.plg_marketing_social_accounts;
3448
+ CREATE POLICY plg_marketing_social_accounts_select ON public.plg_marketing_social_accounts FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
3449
+ CREATE POLICY plg_marketing_social_accounts_insert ON public.plg_marketing_social_accounts FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
3450
+ CREATE POLICY plg_marketing_social_accounts_update ON public.plg_marketing_social_accounts FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
3451
+ CREATE POLICY plg_marketing_social_accounts_delete ON public.plg_marketing_social_accounts FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
3452
+ GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_marketing_social_accounts TO authenticated;
3453
+
3454
+ -- RLS policies for plg_marketing_content_plans
3455
+ DROP POLICY IF EXISTS plg_marketing_content_plans_select ON public.plg_marketing_content_plans;
3456
+ DROP POLICY IF EXISTS plg_marketing_content_plans_insert ON public.plg_marketing_content_plans;
3457
+ DROP POLICY IF EXISTS plg_marketing_content_plans_update ON public.plg_marketing_content_plans;
3458
+ DROP POLICY IF EXISTS plg_marketing_content_plans_delete ON public.plg_marketing_content_plans;
3459
+ CREATE POLICY plg_marketing_content_plans_select ON public.plg_marketing_content_plans FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
3460
+ CREATE POLICY plg_marketing_content_plans_insert ON public.plg_marketing_content_plans FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
3461
+ CREATE POLICY plg_marketing_content_plans_update ON public.plg_marketing_content_plans FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
3462
+ CREATE POLICY plg_marketing_content_plans_delete ON public.plg_marketing_content_plans FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
3463
+ GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_marketing_content_plans TO authenticated;
3464
+
3465
+ -- RLS policies for plg_marketing_content_posts
3466
+ DROP POLICY IF EXISTS plg_marketing_content_posts_select ON public.plg_marketing_content_posts;
3467
+ DROP POLICY IF EXISTS plg_marketing_content_posts_insert ON public.plg_marketing_content_posts;
3468
+ DROP POLICY IF EXISTS plg_marketing_content_posts_update ON public.plg_marketing_content_posts;
3469
+ DROP POLICY IF EXISTS plg_marketing_content_posts_delete ON public.plg_marketing_content_posts;
3470
+ CREATE POLICY plg_marketing_content_posts_select ON public.plg_marketing_content_posts FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
3471
+ CREATE POLICY plg_marketing_content_posts_insert ON public.plg_marketing_content_posts FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
3472
+ CREATE POLICY plg_marketing_content_posts_update ON public.plg_marketing_content_posts FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
3473
+ CREATE POLICY plg_marketing_content_posts_delete ON public.plg_marketing_content_posts FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
3474
+ GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_marketing_content_posts TO authenticated;
3475
+ `;
3450
3476
  var MIGRATION_002_MULTI_PLATFORM = `-- Marketing Plugin: multi-platform accounts + per-post targets.
3451
3477
  -- An account is a brand/profile that publishes to MANY platforms (Instagram,
3452
3478
  -- YouTube, TikTok, ...). Posts optionally narrow the target platforms; an
3453
3479
  -- empty array means "inherit the account's platforms".
3454
3480
 
3455
- ALTER TABLE public.mkt_social_accounts
3481
+ ALTER TABLE public.plg_marketing_social_accounts
3456
3482
  ADD COLUMN IF NOT EXISTS platforms text[] NOT NULL DEFAULT '{instagram}';
3457
3483
 
3458
3484
  -- Backfill from the legacy single-platform column, then drop it (also drops
@@ -3461,26 +3487,27 @@ DO $$
3461
3487
  BEGIN
3462
3488
  IF EXISTS (
3463
3489
  SELECT 1 FROM information_schema.columns
3464
- WHERE table_schema = 'public' AND table_name = 'mkt_social_accounts' AND column_name = 'platform'
3490
+ WHERE table_schema = 'public' AND table_name = 'plg_marketing_social_accounts' AND column_name = 'platform'
3465
3491
  ) THEN
3466
- UPDATE public.mkt_social_accounts
3492
+ UPDATE public.plg_marketing_social_accounts
3467
3493
  SET platforms = ARRAY[platform]
3468
3494
  WHERE platform IS NOT NULL AND platforms = '{instagram}';
3469
- ALTER TABLE public.mkt_social_accounts DROP COLUMN platform;
3495
+ ALTER TABLE public.plg_marketing_social_accounts DROP COLUMN platform;
3470
3496
  END IF;
3471
3497
  END $$;
3472
3498
 
3473
- ALTER TABLE public.mkt_content_posts
3474
- ADD COLUMN IF NOT EXISTS platforms text[] NOT NULL DEFAULT '{}';`;
3499
+ ALTER TABLE public.plg_marketing_content_posts
3500
+ ADD COLUMN IF NOT EXISTS platforms text[] NOT NULL DEFAULT '{}';
3501
+ `;
3475
3502
  var MIGRATION_003_RECORDING_OPS = `-- Marketing Plugin: recording-day ops on posts.
3476
3503
  -- checklist: shooting checklist items [{ id, text, done }] \u2014 the on-set
3477
3504
  -- companion for recording day. media_url: uploaded asset (static posts get a
3478
3505
  -- caption + final art instead of a script).
3479
3506
 
3480
- ALTER TABLE public.mkt_content_posts
3507
+ ALTER TABLE public.plg_marketing_content_posts
3481
3508
  ADD COLUMN IF NOT EXISTS checklist jsonb NOT NULL DEFAULT '[]';
3482
3509
 
3483
- ALTER TABLE public.mkt_content_posts
3510
+ ALTER TABLE public.plg_marketing_content_posts
3484
3511
  ADD COLUMN IF NOT EXISTS media_url text;
3485
3512
 
3486
3513
  -- Public media bucket for content assets (mirrors the avatars-bucket pattern:
@@ -3489,14 +3516,15 @@ INSERT INTO storage.buckets (id, name, public)
3489
3516
  VALUES ('mkt-media', 'mkt-media', true)
3490
3517
  ON CONFLICT (id) DO NOTHING;
3491
3518
 
3492
- DROP POLICY IF EXISTS mkt_media_read ON storage.objects;
3493
- DROP POLICY IF EXISTS mkt_media_insert ON storage.objects;
3494
- DROP POLICY IF EXISTS mkt_media_update ON storage.objects;
3495
- DROP POLICY IF EXISTS mkt_media_delete ON storage.objects;
3496
- CREATE POLICY mkt_media_read ON storage.objects FOR SELECT TO public USING (bucket_id = 'mkt-media');
3497
- CREATE POLICY mkt_media_insert ON storage.objects FOR INSERT TO authenticated WITH CHECK (bucket_id = 'mkt-media');
3498
- CREATE POLICY mkt_media_update ON storage.objects FOR UPDATE TO authenticated USING (bucket_id = 'mkt-media');
3499
- CREATE POLICY mkt_media_delete ON storage.objects FOR DELETE TO authenticated USING (bucket_id = 'mkt-media');`;
3519
+ DROP POLICY IF EXISTS plg_marketing_media_read ON storage.objects;
3520
+ DROP POLICY IF EXISTS plg_marketing_media_insert ON storage.objects;
3521
+ DROP POLICY IF EXISTS plg_marketing_media_update ON storage.objects;
3522
+ DROP POLICY IF EXISTS plg_marketing_media_delete ON storage.objects;
3523
+ CREATE POLICY plg_marketing_media_read ON storage.objects FOR SELECT TO public USING (bucket_id = 'mkt-media');
3524
+ CREATE POLICY plg_marketing_media_insert ON storage.objects FOR INSERT TO authenticated WITH CHECK (bucket_id = 'mkt-media');
3525
+ CREATE POLICY plg_marketing_media_update ON storage.objects FOR UPDATE TO authenticated USING (bucket_id = 'mkt-media');
3526
+ CREATE POLICY plg_marketing_media_delete ON storage.objects FOR DELETE TO authenticated USING (bucket_id = 'mkt-media');
3527
+ `;
3500
3528
 
3501
3529
  // src/locales/en.ts
3502
3530
  var en = {
@@ -3914,6 +3942,9 @@ function createMarketingPlugin(options) {
3914
3942
  version: "1.0.0",
3915
3943
  scope: options?.scope ?? "universal",
3916
3944
  verticalId: options?.verticalId,
3945
+ // Admin content-planning surface (campaigns, content calendar) — targets the
3946
+ // saas/admin Panel world, not the ecommerce storefront/operator console.
3947
+ scaffolds: ["saas"],
3917
3948
  defaultEnabled: true,
3918
3949
  dependencies: [],
3919
3950
  declaredFeatures: [
@@ -3950,11 +3981,17 @@ function createMarketingPlugin(options) {
3950
3981
  { name: "marketing.content.post.updated", description: "A content post was updated" }
3951
3982
  ],
3952
3983
  migrations: [
3984
+ {
3985
+ id: "marketing-000-plg-rename",
3986
+ version: "1.0.0",
3987
+ sql: MIGRATION_000_PLG_RENAME,
3988
+ description: "Rename legacy mkt_* tables to plg_marketing_* in-place for pools provisioned before the industry-pool rename (guarded no-op on fresh pools)"
3989
+ },
3953
3990
  {
3954
3991
  id: "marketing-001-content-planner",
3955
3992
  version: "1.0.0",
3956
3993
  sql: MIGRATION_001_CONTENT_PLANNER,
3957
- description: "Create mkt_social_accounts, mkt_content_plans and mkt_content_posts tables"
3994
+ description: "Create plg_marketing_social_accounts, plg_marketing_content_plans and plg_marketing_content_posts"
3958
3995
  },
3959
3996
  {
3960
3997
  id: "marketing-002-multi-platform",
@@ -3966,7 +4003,7 @@ function createMarketingPlugin(options) {
3966
4003
  id: "marketing-003-recording-ops",
3967
4004
  version: "1.2.0",
3968
4005
  sql: MIGRATION_003_RECORDING_OPS,
3969
- description: "Recording-day checklist + media asset URL on posts; mkt-media storage bucket"
4006
+ description: "Recording-day checklist + media asset URL on posts; content-media storage bucket"
3970
4007
  }
3971
4008
  ],
3972
4009
  aiTools: [