@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 +2 -0
- package/dist/data/contentSupabase.d.ts.map +1 -1
- package/dist/data/tables.d.ts +6 -0
- package/dist/data/tables.d.ts.map +1 -0
- package/dist/index.cjs +121 -84
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +121 -84
- package/dist/index.js.map +1 -1
- package/dist/migrations/index.d.ts +8 -3
- package/dist/migrations/index.d.ts.map +1 -1
- package/dist/schema/index.d.ts +44 -44
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema.cjs +9 -9
- package/dist/schema.cjs.map +1 -1
- package/dist/schema.js +9 -9
- package/dist/schema.js.map +1 -1
- package/package.json +8 -5
- package/src/data/contentSupabase.ts +18 -17
- package/src/data/tables.ts +7 -0
- package/src/index.ts +12 -3
- package/src/migrations/000_plg_rename.sql +15 -0
- package/src/migrations/001_content_planner.sql +48 -48
- package/src/migrations/002_multi_platform.sql +5 -5
- package/src/migrations/003_recording_ops.sql +10 -10
- package/src/migrations/index.ts +99 -74
- package/src/schema/index.ts +9 -9
package/README.md
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/@fayz-ai/plugin-marketing)
|
|
6
6
|
[](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;
|
|
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 @@
|
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
3161
|
+
await getClient().from(T.contentPlans).delete().eq("id", id);
|
|
3153
3162
|
},
|
|
3154
3163
|
async listPosts(planId) {
|
|
3155
|
-
const { data } = await getClient().from(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|
|
3382
|
+
-- Prefix: plg_marketing_
|
|
3358
3383
|
-- Social accounts + content plans + posts (Notion-style markdown pages).
|
|
3359
3384
|
|
|
3360
|
-
CREATE TABLE IF NOT EXISTS public.
|
|
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
|
|
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.
|
|
3371
|
-
CREATE INDEX IF NOT EXISTS
|
|
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.
|
|
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
|
|
3376
|
-
account_id uuid NOT NULL REFERENCES public.
|
|
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.
|
|
3392
|
-
CREATE INDEX IF NOT EXISTS
|
|
3393
|
-
CREATE INDEX IF NOT EXISTS
|
|
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.
|
|
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
|
|
3398
|
-
plan_id uuid NOT NULL REFERENCES public.
|
|
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.
|
|
3414
|
-
CREATE INDEX IF NOT EXISTS
|
|
3415
|
-
CREATE INDEX IF NOT EXISTS
|
|
3416
|
-
CREATE INDEX IF NOT EXISTS
|
|
3417
|
-
|
|
3418
|
-
-- RLS policies for
|
|
3419
|
-
DROP POLICY IF EXISTS
|
|
3420
|
-
DROP POLICY IF EXISTS
|
|
3421
|
-
DROP POLICY IF EXISTS
|
|
3422
|
-
DROP POLICY IF EXISTS
|
|
3423
|
-
CREATE POLICY
|
|
3424
|
-
CREATE POLICY
|
|
3425
|
-
CREATE POLICY
|
|
3426
|
-
CREATE POLICY
|
|
3427
|
-
GRANT SELECT, INSERT, UPDATE, DELETE ON public.
|
|
3428
|
-
|
|
3429
|
-
-- RLS policies for
|
|
3430
|
-
DROP POLICY IF EXISTS
|
|
3431
|
-
DROP POLICY IF EXISTS
|
|
3432
|
-
DROP POLICY IF EXISTS
|
|
3433
|
-
DROP POLICY IF EXISTS
|
|
3434
|
-
CREATE POLICY
|
|
3435
|
-
CREATE POLICY
|
|
3436
|
-
CREATE POLICY
|
|
3437
|
-
CREATE POLICY
|
|
3438
|
-
GRANT SELECT, INSERT, UPDATE, DELETE ON public.
|
|
3439
|
-
|
|
3440
|
-
-- RLS policies for
|
|
3441
|
-
DROP POLICY IF EXISTS
|
|
3442
|
-
DROP POLICY IF EXISTS
|
|
3443
|
-
DROP POLICY IF EXISTS
|
|
3444
|
-
DROP POLICY IF EXISTS
|
|
3445
|
-
CREATE POLICY
|
|
3446
|
-
CREATE POLICY
|
|
3447
|
-
CREATE POLICY
|
|
3448
|
-
CREATE POLICY
|
|
3449
|
-
GRANT SELECT, INSERT, UPDATE, DELETE ON public.
|
|
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.
|
|
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 = '
|
|
3490
|
+
WHERE table_schema = 'public' AND table_name = 'plg_marketing_social_accounts' AND column_name = 'platform'
|
|
3465
3491
|
) THEN
|
|
3466
|
-
UPDATE public.
|
|
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.
|
|
3495
|
+
ALTER TABLE public.plg_marketing_social_accounts DROP COLUMN platform;
|
|
3470
3496
|
END IF;
|
|
3471
3497
|
END $$;
|
|
3472
3498
|
|
|
3473
|
-
ALTER TABLE public.
|
|
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.
|
|
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.
|
|
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
|
|
3493
|
-
DROP POLICY IF EXISTS
|
|
3494
|
-
DROP POLICY IF EXISTS
|
|
3495
|
-
DROP POLICY IF EXISTS
|
|
3496
|
-
CREATE POLICY
|
|
3497
|
-
CREATE POLICY
|
|
3498
|
-
CREATE POLICY
|
|
3499
|
-
CREATE POLICY
|
|
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
|
|
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;
|
|
4006
|
+
description: "Recording-day checklist + media asset URL on posts; content-media storage bucket"
|
|
3970
4007
|
}
|
|
3971
4008
|
],
|
|
3972
4009
|
aiTools: [
|