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.
Files changed (95) hide show
  1. package/README.md +2 -2
  2. package/package.json +1 -1
  3. package/src/app-manifest.mjs +4 -2
  4. package/src/constants.mjs +102 -16
  5. package/src/doctor.mjs +38 -0
  6. package/src/generator.mjs +75 -19
  7. package/template/base/app/(auth)/admin-invite/[invitationId]/page.tsx +6 -0
  8. package/template/base/app/(auth)/auth/confirmed/page.tsx +1 -0
  9. package/template/base/app/(auth)/auth/post-login/page.tsx +1 -0
  10. package/template/base/app/(auth)/auth-provider.tsx +25 -0
  11. package/template/base/app/(auth)/forgot-password/page.tsx +1 -0
  12. package/template/base/app/(auth)/invite/[invitationId]/invite-route-mount.tsx +10 -0
  13. package/template/base/app/(auth)/invite/[invitationId]/page.tsx +1 -0
  14. package/template/base/app/(auth)/layout.tsx +5 -0
  15. package/template/base/app/(auth)/login/page.tsx +1 -0
  16. package/template/base/app/(auth)/reset-password/page.tsx +1 -0
  17. package/template/base/app/(auth)/signup/page.tsx +1 -0
  18. package/template/base/app/(shell)/account/page.tsx +5 -0
  19. package/template/base/app/(shell)/dashboard/dashboard-live-mount.tsx +21 -0
  20. package/template/base/app/(shell)/dashboard/page.tsx +1 -0
  21. package/template/base/app/(shell)/layout.tsx +24 -0
  22. package/template/base/app/(shell)/shell-layout-client.tsx +134 -0
  23. package/template/base/app/api/account/route.ts +2 -0
  24. package/template/base/app/api/invitations/[invitationId]/accept/route.ts +5 -0
  25. package/template/base/app/api/invitations/[invitationId]/register/route.ts +5 -0
  26. package/template/base/app/api/invitations/[invitationId]/route.ts +5 -0
  27. package/template/base/app/api/invitations/_dependencies.ts +37 -0
  28. package/template/base/app/api/organizations/[id]/invitations/[invitationId]/route.ts +9 -0
  29. package/template/base/app/api/organizations/[id]/invitations/route.ts +11 -0
  30. package/template/base/app/api/organizations/[id]/route.ts +6 -0
  31. package/template/base/app/api/organizations/route.ts +6 -0
  32. package/template/base/app/auth/callback/route.ts +1 -0
  33. package/template/base/app/auth/cleanup/route.ts +1 -0
  34. package/template/base/app/layout.tsx +8 -2
  35. package/template/base/config/env.ts +98 -0
  36. package/template/modules/marketing/app/(shell)/marketing/page.tsx +1 -0
  37. package/template/modules/marketing/app/api/marketing/_handlers.ts +144 -0
  38. package/template/modules/marketing/app/api/marketing/analytics/campaigns/[id]/route.ts +2 -0
  39. package/template/modules/marketing/app/api/marketing/analytics/overview/route.ts +2 -0
  40. package/template/modules/marketing/app/api/marketing/analytics/segments/[id]/route.ts +2 -0
  41. package/template/modules/marketing/app/api/marketing/campaigns/[id]/cancel/route.ts +2 -0
  42. package/template/modules/marketing/app/api/marketing/campaigns/[id]/recipients/route.ts +2 -0
  43. package/template/modules/marketing/app/api/marketing/campaigns/[id]/retry/route.ts +2 -0
  44. package/template/modules/marketing/app/api/marketing/campaigns/[id]/route.ts +2 -0
  45. package/template/modules/marketing/app/api/marketing/campaigns/[id]/schedule/route.ts +2 -0
  46. package/template/modules/marketing/app/api/marketing/campaigns/[id]/send/route.ts +2 -0
  47. package/template/modules/marketing/app/api/marketing/campaigns/[id]/test/route.ts +2 -0
  48. package/template/modules/marketing/app/api/marketing/campaigns/route.ts +2 -0
  49. package/template/modules/marketing/app/api/marketing/segments/[id]/preview/route.ts +2 -0
  50. package/template/modules/marketing/app/api/marketing/segments/[id]/route.ts +2 -0
  51. package/template/modules/marketing/app/api/marketing/segments/preview/route.ts +2 -0
  52. package/template/modules/marketing/app/api/marketing/segments/route.ts +2 -0
  53. package/template/modules/marketing/app/api/marketing/topics/route.ts +2 -0
  54. package/template/modules/marketing/app/api/marketing/unsubscribe/[token]/route.ts +2 -0
  55. package/template/modules/marketing/app/api/marketing/webhooks/resend/route.ts +2 -0
  56. package/template/modules/marketing/app/api/marketing/worker/route.ts +2 -0
  57. package/template/modules/marketing/app/api/marketing/workflows/[id]/activate/route.ts +2 -0
  58. package/template/modules/marketing/app/api/marketing/workflows/[id]/nodes/[nodeId]/route.ts +2 -0
  59. package/template/modules/marketing/app/api/marketing/workflows/[id]/nodes/route.ts +2 -0
  60. package/template/modules/marketing/app/api/marketing/workflows/[id]/pause/route.ts +2 -0
  61. package/template/modules/marketing/app/api/marketing/workflows/[id]/route.ts +2 -0
  62. package/template/modules/marketing/app/api/marketing/workflows/[id]/runs/route.ts +2 -0
  63. package/template/modules/marketing/app/api/marketing/workflows/route.ts +2 -0
  64. package/template/modules/projects/app/(shell)/projects/[id]/board/page.tsx +1 -0
  65. package/template/modules/projects/app/(shell)/projects/[id]/page.tsx +1 -0
  66. package/template/modules/projects/app/(shell)/projects/[id]/tasks/page.tsx +1 -0
  67. package/template/modules/projects/app/(shell)/projects/layout.tsx +5 -0
  68. package/template/modules/projects/app/(shell)/projects/page.tsx +1 -0
  69. package/template/modules/projects/app/(shell)/projects/projects-live-mounts.tsx +27 -0
  70. package/template/modules/projects/app/(shell)/projects/projects-server-mounts.tsx +54 -0
  71. package/template/modules/projects/app/api/projects/[id]/activity/route.ts +2 -0
  72. package/template/modules/projects/app/api/projects/[id]/links/[itemId]/route.ts +2 -0
  73. package/template/modules/projects/app/api/projects/[id]/links/route.ts +2 -0
  74. package/template/modules/projects/app/api/projects/[id]/members/route.ts +2 -0
  75. package/template/modules/projects/app/api/projects/[id]/milestones/[itemId]/route.ts +2 -0
  76. package/template/modules/projects/app/api/projects/[id]/milestones/route.ts +2 -0
  77. package/template/modules/projects/app/api/projects/[id]/route.ts +2 -0
  78. package/template/modules/projects/app/api/projects/[id]/tasks/[itemId]/route.ts +2 -0
  79. package/template/modules/projects/app/api/projects/[id]/tasks/route.ts +2 -0
  80. package/template/modules/projects/app/api/projects/_handlers.ts +86 -0
  81. package/template/modules/projects/app/api/projects/organizations/route.ts +2 -0
  82. package/template/modules/projects/app/api/projects/route.ts +2 -0
  83. package/template/modules/projects/app/api/projects/stats/route.ts +2 -0
  84. package/template/supabase/README.md +3 -0
  85. package/template/supabase/module-registry.json +6 -0
  86. package/template/supabase/modules/marketing/README.md +12 -0
  87. package/template/supabase/modules/marketing/migrations/20260725120000_marketing_v1.sql +118 -0
  88. package/template/supabase/modules/marketing/migrations/20260725130000_marketing_campaigns.sql +133 -0
  89. package/template/supabase/modules/marketing/migrations/20260726120000_marketing_segments.sql +41 -0
  90. package/template/supabase/modules/marketing/migrations/20260726150000_marketing_workflows.sql +174 -0
  91. package/template/supabase/modules/marketing/migrations/20260726170000_marketing_engine_safety.sql +68 -0
  92. /package/template/modules/admin/app/{admin → (shell)/admin}/users/page.tsx +0 -0
  93. /package/template/modules/crm/app/{crm → (shell)/crm}/layout.tsx +0 -0
  94. /package/template/modules/crm/app/{crm → (shell)/crm}/page.tsx +0 -0
  95. /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;
@@ -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;