create-bw-app 0.13.3 → 0.14.1
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 -2
- package/package.json +1 -1
- package/src/app-manifest.mjs +4 -2
- package/src/constants.mjs +102 -16
- package/src/doctor.mjs +38 -0
- package/src/generator.mjs +75 -19
- package/template/base/app/(auth)/admin-invite/[invitationId]/page.tsx +6 -0
- package/template/base/app/(auth)/auth/confirmed/page.tsx +1 -0
- package/template/base/app/(auth)/auth/post-login/page.tsx +1 -0
- package/template/base/app/(auth)/auth-provider.tsx +25 -0
- package/template/base/app/(auth)/forgot-password/page.tsx +1 -0
- package/template/base/app/(auth)/invite/[invitationId]/invite-route-mount.tsx +10 -0
- package/template/base/app/(auth)/invite/[invitationId]/page.tsx +1 -0
- package/template/base/app/(auth)/layout.tsx +5 -0
- package/template/base/app/(auth)/login/page.tsx +1 -0
- package/template/base/app/(auth)/reset-password/page.tsx +1 -0
- package/template/base/app/(auth)/signup/page.tsx +1 -0
- package/template/base/app/(shell)/account/page.tsx +5 -0
- package/template/base/app/(shell)/dashboard/dashboard-live-mount.tsx +21 -0
- package/template/base/app/(shell)/dashboard/page.tsx +1 -0
- package/template/base/app/(shell)/layout.tsx +24 -0
- package/template/base/app/(shell)/shell-layout-client.tsx +134 -0
- package/template/base/app/api/account/route.ts +2 -0
- package/template/base/app/api/invitations/[invitationId]/accept/route.ts +5 -0
- package/template/base/app/api/invitations/[invitationId]/register/route.ts +5 -0
- package/template/base/app/api/invitations/[invitationId]/route.ts +5 -0
- package/template/base/app/api/invitations/_dependencies.ts +37 -0
- package/template/base/app/api/organizations/[id]/invitations/[invitationId]/route.ts +9 -0
- package/template/base/app/api/organizations/[id]/invitations/route.ts +11 -0
- package/template/base/app/api/organizations/[id]/route.ts +6 -0
- package/template/base/app/api/organizations/route.ts +6 -0
- package/template/base/app/auth/callback/route.ts +1 -0
- package/template/base/app/auth/cleanup/route.ts +1 -0
- package/template/base/app/layout.tsx +8 -2
- package/template/base/config/env.ts +98 -0
- package/template/modules/marketing/app/(shell)/marketing/page.tsx +1 -0
- package/template/modules/marketing/app/api/marketing/_handlers.ts +144 -0
- package/template/modules/marketing/app/api/marketing/analytics/campaigns/[id]/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/analytics/overview/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/analytics/segments/[id]/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/campaigns/[id]/cancel/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/campaigns/[id]/recipients/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/campaigns/[id]/retry/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/campaigns/[id]/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/campaigns/[id]/schedule/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/campaigns/[id]/send/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/campaigns/[id]/test/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/campaigns/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/segments/[id]/preview/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/segments/[id]/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/segments/preview/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/segments/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/topics/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/unsubscribe/[token]/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/webhooks/resend/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/worker/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/workflows/[id]/activate/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/workflows/[id]/nodes/[nodeId]/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/workflows/[id]/nodes/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/workflows/[id]/pause/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/workflows/[id]/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/workflows/[id]/runs/route.ts +2 -0
- package/template/modules/marketing/app/api/marketing/workflows/route.ts +2 -0
- package/template/modules/projects/app/(shell)/projects/[id]/board/page.tsx +1 -0
- package/template/modules/projects/app/(shell)/projects/[id]/page.tsx +1 -0
- package/template/modules/projects/app/(shell)/projects/[id]/tasks/page.tsx +1 -0
- package/template/modules/projects/app/(shell)/projects/layout.tsx +5 -0
- package/template/modules/projects/app/(shell)/projects/page.tsx +1 -0
- package/template/modules/projects/app/(shell)/projects/projects-live-mounts.tsx +27 -0
- package/template/modules/projects/app/(shell)/projects/projects-server-mounts.tsx +54 -0
- package/template/modules/projects/app/api/projects/[id]/activity/route.ts +2 -0
- package/template/modules/projects/app/api/projects/[id]/links/[itemId]/route.ts +2 -0
- package/template/modules/projects/app/api/projects/[id]/links/route.ts +2 -0
- package/template/modules/projects/app/api/projects/[id]/members/route.ts +2 -0
- package/template/modules/projects/app/api/projects/[id]/milestones/[itemId]/route.ts +2 -0
- package/template/modules/projects/app/api/projects/[id]/milestones/route.ts +2 -0
- package/template/modules/projects/app/api/projects/[id]/route.ts +2 -0
- package/template/modules/projects/app/api/projects/[id]/tasks/[itemId]/route.ts +2 -0
- package/template/modules/projects/app/api/projects/[id]/tasks/route.ts +2 -0
- package/template/modules/projects/app/api/projects/_handlers.ts +86 -0
- package/template/modules/projects/app/api/projects/organizations/route.ts +2 -0
- package/template/modules/projects/app/api/projects/route.ts +2 -0
- package/template/modules/projects/app/api/projects/stats/route.ts +2 -0
- package/template/supabase/README.md +3 -0
- package/template/supabase/module-registry.json +6 -0
- package/template/supabase/modules/marketing/README.md +12 -0
- package/template/supabase/modules/marketing/migrations/20260725120000_marketing_v1.sql +118 -0
- package/template/supabase/modules/marketing/migrations/20260725130000_marketing_campaigns.sql +133 -0
- package/template/supabase/modules/marketing/migrations/20260726120000_marketing_segments.sql +41 -0
- package/template/supabase/modules/marketing/migrations/20260726150000_marketing_workflows.sql +174 -0
- package/template/supabase/modules/marketing/migrations/20260726170000_marketing_engine_safety.sql +68 -0
- /package/template/modules/admin/app/{admin → (shell)/admin}/users/page.tsx +0 -0
- /package/template/modules/crm/app/{crm → (shell)/crm}/layout.tsx +0 -0
- /package/template/modules/crm/app/{crm → (shell)/crm}/page.tsx +0 -0
- /package/template/modules/crm/app/{crm → (shell)/crm}/report/page.tsx +0 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
create table if not exists public.marketing_workflows (
|
|
2
|
+
id uuid primary key default gen_random_uuid(),
|
|
3
|
+
name text not null,
|
|
4
|
+
description text,
|
|
5
|
+
trigger_type text not null
|
|
6
|
+
check (trigger_type in (
|
|
7
|
+
'contact_subscribed',
|
|
8
|
+
'crm_status_changed',
|
|
9
|
+
'project_status_changed',
|
|
10
|
+
'form_submitted'
|
|
11
|
+
)),
|
|
12
|
+
trigger_config jsonb not null default '{}'::jsonb,
|
|
13
|
+
status text not null default 'draft'
|
|
14
|
+
check (status in ('draft', 'active', 'paused')),
|
|
15
|
+
created_by_profile_id uuid references public.profiles(id),
|
|
16
|
+
created_at timestamptz not null default now(),
|
|
17
|
+
updated_at timestamptz not null default now()
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
create table if not exists public.marketing_workflow_nodes (
|
|
21
|
+
id uuid primary key default gen_random_uuid(),
|
|
22
|
+
workflow_id uuid not null
|
|
23
|
+
references public.marketing_workflows(id) on delete cascade,
|
|
24
|
+
position integer not null,
|
|
25
|
+
node_type text not null
|
|
26
|
+
check (node_type in ('send_email', 'wait', 'add_tag')),
|
|
27
|
+
config jsonb not null default '{}'::jsonb,
|
|
28
|
+
created_at timestamptz not null default now(),
|
|
29
|
+
updated_at timestamptz not null default now(),
|
|
30
|
+
unique (workflow_id, position)
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
create index if not exists marketing_workflow_nodes_workflow_position_idx
|
|
34
|
+
on public.marketing_workflow_nodes (workflow_id, position);
|
|
35
|
+
|
|
36
|
+
create table if not exists public.marketing_workflow_runs (
|
|
37
|
+
id uuid primary key default gen_random_uuid(),
|
|
38
|
+
workflow_id uuid not null
|
|
39
|
+
references public.marketing_workflows(id) on delete cascade,
|
|
40
|
+
contact_id uuid references public.crm_contacts(id) on delete cascade,
|
|
41
|
+
status text not null default 'active'
|
|
42
|
+
check (status in ('active', 'completed', 'failed', 'canceled')),
|
|
43
|
+
current_position integer not null default 0,
|
|
44
|
+
next_run_at timestamptz,
|
|
45
|
+
context jsonb,
|
|
46
|
+
started_at timestamptz not null default now(),
|
|
47
|
+
completed_at timestamptz,
|
|
48
|
+
created_at timestamptz not null default now(),
|
|
49
|
+
updated_at timestamptz not null default now(),
|
|
50
|
+
unique (workflow_id, contact_id)
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
create index if not exists marketing_workflow_runs_due_idx
|
|
54
|
+
on public.marketing_workflow_runs (status, next_run_at);
|
|
55
|
+
|
|
56
|
+
create table if not exists public.marketing_workflow_step_runs (
|
|
57
|
+
id uuid primary key default gen_random_uuid(),
|
|
58
|
+
run_id uuid not null
|
|
59
|
+
references public.marketing_workflow_runs(id) on delete cascade,
|
|
60
|
+
node_id uuid references public.marketing_workflow_nodes(id) on delete set null,
|
|
61
|
+
status text not null check (status in ('done', 'failed', 'skipped')),
|
|
62
|
+
error text,
|
|
63
|
+
executed_at timestamptz not null default now()
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
create table if not exists public.marketing_contact_tags (
|
|
67
|
+
id uuid primary key default gen_random_uuid(),
|
|
68
|
+
contact_id uuid not null references public.crm_contacts(id) on delete cascade,
|
|
69
|
+
tag text not null,
|
|
70
|
+
created_at timestamptz not null default now(),
|
|
71
|
+
unique (contact_id, tag)
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
create table if not exists public.marketing_worker_cursors (
|
|
75
|
+
key text primary key,
|
|
76
|
+
cursor timestamptz
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
alter table public.marketing_workflows enable row level security;
|
|
80
|
+
alter table public.marketing_workflow_nodes enable row level security;
|
|
81
|
+
alter table public.marketing_workflow_runs enable row level security;
|
|
82
|
+
alter table public.marketing_workflow_step_runs enable row level security;
|
|
83
|
+
alter table public.marketing_contact_tags enable row level security;
|
|
84
|
+
alter table public.marketing_worker_cursors enable row level security;
|
|
85
|
+
|
|
86
|
+
drop policy if exists "service role manages marketing workflows"
|
|
87
|
+
on public.marketing_workflows;
|
|
88
|
+
create policy "service role manages marketing workflows"
|
|
89
|
+
on public.marketing_workflows for all
|
|
90
|
+
using (auth.role() = 'service_role')
|
|
91
|
+
with check (auth.role() = 'service_role');
|
|
92
|
+
|
|
93
|
+
drop policy if exists "service role manages marketing workflow nodes"
|
|
94
|
+
on public.marketing_workflow_nodes;
|
|
95
|
+
create policy "service role manages marketing workflow nodes"
|
|
96
|
+
on public.marketing_workflow_nodes for all
|
|
97
|
+
using (auth.role() = 'service_role')
|
|
98
|
+
with check (auth.role() = 'service_role');
|
|
99
|
+
|
|
100
|
+
drop policy if exists "service role manages marketing workflow runs"
|
|
101
|
+
on public.marketing_workflow_runs;
|
|
102
|
+
create policy "service role manages marketing workflow runs"
|
|
103
|
+
on public.marketing_workflow_runs for all
|
|
104
|
+
using (auth.role() = 'service_role')
|
|
105
|
+
with check (auth.role() = 'service_role');
|
|
106
|
+
|
|
107
|
+
drop policy if exists "service role manages marketing workflow step runs"
|
|
108
|
+
on public.marketing_workflow_step_runs;
|
|
109
|
+
create policy "service role manages marketing workflow step runs"
|
|
110
|
+
on public.marketing_workflow_step_runs for all
|
|
111
|
+
using (auth.role() = 'service_role')
|
|
112
|
+
with check (auth.role() = 'service_role');
|
|
113
|
+
|
|
114
|
+
drop policy if exists "service role manages marketing contact tags"
|
|
115
|
+
on public.marketing_contact_tags;
|
|
116
|
+
create policy "service role manages marketing contact tags"
|
|
117
|
+
on public.marketing_contact_tags for all
|
|
118
|
+
using (auth.role() = 'service_role')
|
|
119
|
+
with check (auth.role() = 'service_role');
|
|
120
|
+
|
|
121
|
+
drop policy if exists "service role manages marketing worker cursors"
|
|
122
|
+
on public.marketing_worker_cursors;
|
|
123
|
+
create policy "service role manages marketing worker cursors"
|
|
124
|
+
on public.marketing_worker_cursors for all
|
|
125
|
+
using (auth.role() = 'service_role')
|
|
126
|
+
with check (auth.role() = 'service_role');
|
|
127
|
+
|
|
128
|
+
drop trigger if exists marketing_workflows_set_updated_at
|
|
129
|
+
on public.marketing_workflows;
|
|
130
|
+
create trigger marketing_workflows_set_updated_at
|
|
131
|
+
before update on public.marketing_workflows
|
|
132
|
+
for each row execute function public.set_marketing_updated_at();
|
|
133
|
+
|
|
134
|
+
drop trigger if exists marketing_workflow_nodes_set_updated_at
|
|
135
|
+
on public.marketing_workflow_nodes;
|
|
136
|
+
create trigger marketing_workflow_nodes_set_updated_at
|
|
137
|
+
before update on public.marketing_workflow_nodes
|
|
138
|
+
for each row execute function public.set_marketing_updated_at();
|
|
139
|
+
|
|
140
|
+
drop trigger if exists marketing_workflow_runs_set_updated_at
|
|
141
|
+
on public.marketing_workflow_runs;
|
|
142
|
+
create trigger marketing_workflow_runs_set_updated_at
|
|
143
|
+
before update on public.marketing_workflow_runs
|
|
144
|
+
for each row execute function public.set_marketing_updated_at();
|
|
145
|
+
|
|
146
|
+
create or replace function public.claim_marketing_workflow_runs(
|
|
147
|
+
claim_limit integer,
|
|
148
|
+
claim_time timestamptz default now()
|
|
149
|
+
)
|
|
150
|
+
returns setof public.marketing_workflow_runs
|
|
151
|
+
language sql
|
|
152
|
+
security definer
|
|
153
|
+
set search_path = public
|
|
154
|
+
as $$
|
|
155
|
+
update public.marketing_workflow_runs
|
|
156
|
+
set next_run_at = claim_time + interval '5 minutes',
|
|
157
|
+
updated_at = claim_time
|
|
158
|
+
where id in (
|
|
159
|
+
select id
|
|
160
|
+
from public.marketing_workflow_runs
|
|
161
|
+
where status = 'active'
|
|
162
|
+
and next_run_at is not null
|
|
163
|
+
and next_run_at <= claim_time
|
|
164
|
+
order by next_run_at, created_at, id
|
|
165
|
+
for update skip locked
|
|
166
|
+
limit greatest(1, least(claim_limit, 100))
|
|
167
|
+
)
|
|
168
|
+
returning *;
|
|
169
|
+
$$;
|
|
170
|
+
|
|
171
|
+
revoke all on function public.claim_marketing_workflow_runs(integer, timestamptz)
|
|
172
|
+
from public, anon, authenticated;
|
|
173
|
+
grant execute on function public.claim_marketing_workflow_runs(integer, timestamptz)
|
|
174
|
+
to service_role;
|
package/template/supabase/modules/marketing/migrations/20260726170000_marketing_engine_safety.sql
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
alter table public.marketing_worker_cursors
|
|
2
|
+
add column if not exists cursor_id uuid;
|
|
3
|
+
|
|
4
|
+
with duplicate_steps as (
|
|
5
|
+
select
|
|
6
|
+
id,
|
|
7
|
+
row_number() over (
|
|
8
|
+
partition by run_id, node_id
|
|
9
|
+
order by executed_at, id
|
|
10
|
+
) as duplicate_number
|
|
11
|
+
from public.marketing_workflow_step_runs
|
|
12
|
+
where node_id is not null
|
|
13
|
+
)
|
|
14
|
+
delete from public.marketing_workflow_step_runs
|
|
15
|
+
where id in (
|
|
16
|
+
select id
|
|
17
|
+
from duplicate_steps
|
|
18
|
+
where duplicate_number > 1
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
create unique index if not exists marketing_workflow_step_runs_run_node_idx
|
|
22
|
+
on public.marketing_workflow_step_runs (run_id, node_id)
|
|
23
|
+
where node_id is not null;
|
|
24
|
+
|
|
25
|
+
create or replace function public.claim_marketing_campaign_recipients(
|
|
26
|
+
target_campaign_id uuid,
|
|
27
|
+
claim_limit integer,
|
|
28
|
+
claim_time timestamptz default now()
|
|
29
|
+
)
|
|
30
|
+
returns setof public.marketing_campaign_recipients
|
|
31
|
+
language sql
|
|
32
|
+
security definer
|
|
33
|
+
set search_path = public
|
|
34
|
+
as $$
|
|
35
|
+
update public.marketing_campaign_recipients
|
|
36
|
+
set status = 'sending',
|
|
37
|
+
attempt_count = attempt_count + 1,
|
|
38
|
+
next_attempt_at = null,
|
|
39
|
+
updated_at = claim_time
|
|
40
|
+
where id in (
|
|
41
|
+
select id
|
|
42
|
+
from public.marketing_campaign_recipients
|
|
43
|
+
where campaign_id = target_campaign_id
|
|
44
|
+
and (
|
|
45
|
+
status = 'queued'
|
|
46
|
+
or (
|
|
47
|
+
status = 'failed'
|
|
48
|
+
and attempt_count < 3
|
|
49
|
+
and next_attempt_at is not null
|
|
50
|
+
and next_attempt_at <= claim_time
|
|
51
|
+
)
|
|
52
|
+
or (
|
|
53
|
+
status = 'sending'
|
|
54
|
+
and attempt_count < 3
|
|
55
|
+
and updated_at <= claim_time - interval '15 minutes'
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
order by created_at, id
|
|
59
|
+
for update skip locked
|
|
60
|
+
limit greatest(1, least(claim_limit, 1000))
|
|
61
|
+
)
|
|
62
|
+
returning *;
|
|
63
|
+
$$;
|
|
64
|
+
|
|
65
|
+
revoke all on function public.claim_marketing_campaign_recipients(uuid, integer, timestamptz)
|
|
66
|
+
from public, anon, authenticated;
|
|
67
|
+
grant execute on function public.claim_marketing_campaign_recipients(uuid, integer, timestamptz)
|
|
68
|
+
to service_role;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|