@mevdragon/vidfarm-devcli 0.2.11 → 0.2.13
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/.env.example +37 -1
- package/README.md +27 -183
- package/SKILL.director.md +293 -0
- package/SKILL.platform.md +312 -0
- package/auto-create-hyperframe-templates/AUTO_CREATE_HYPERFRAME_TEMPLATES.md +275 -0
- package/auto-create-hyperframe-templates/extractor-system-prompt.md +128 -0
- package/auto-create-hyperframe-templates/input-manifest.schema.json +56 -0
- package/auto-create-hyperframe-templates/lambda-memory-estimator.md +118 -0
- package/auto-create-hyperframe-templates/production-graph.schema.json +314 -0
- package/auto-create-hyperframe-templates/runbook.md +198 -0
- package/auto-create-hyperframe-templates/scripts/create-hyperframe-template.mjs +368 -0
- package/auto-create-hyperframe-templates/template-plan.schema.json +311 -0
- package/auto-create-hyperframe-templates/template-planner-prompt.md +144 -0
- package/auto-create-templates/AUTO_CREATE_TEMPLATES.md +124 -0
- package/auto-create-templates/extractor-system-prompt.md +8 -2
- package/auto-create-templates/template-plan.schema.json +73 -2
- package/auto-create-templates/template-planner-prompt.md +63 -1
- package/dist/src/account-pages-legacy.js +361 -89
- package/dist/src/account-pages-legacy.js.map +1 -0
- package/dist/src/account-pages.js +13 -3
- package/dist/src/account-pages.js.map +1 -0
- package/dist/src/app.js +7060 -946
- package/dist/src/app.js.map +1 -0
- package/dist/src/cli.js +111 -29
- package/dist/src/cli.js.map +1 -0
- package/dist/src/composition-runtime.js +581 -0
- package/dist/src/composition-runtime.js.map +1 -0
- package/dist/src/config.js +69 -11
- package/dist/src/config.js.map +1 -0
- package/dist/src/context.js +280 -21
- package/dist/src/context.js.map +1 -0
- package/dist/src/dev-app-legacy.js +18 -17
- package/dist/src/dev-app-legacy.js.map +1 -0
- package/dist/src/dev-app.js +1 -0
- package/dist/src/dev-app.js.map +1 -0
- package/dist/src/domain.js +2 -1
- package/dist/src/domain.js.map +1 -0
- package/dist/src/editor-chat-history.js +10 -0
- package/dist/src/editor-chat-history.js.map +1 -0
- package/dist/src/editor-chat.js +133 -24
- package/dist/src/editor-chat.js.map +1 -0
- package/dist/src/frontend/flockposter-cache-store.js +10 -2
- package/dist/src/frontend/flockposter-cache-store.js.map +1 -0
- package/dist/src/frontend/homepage-client.js +6 -3
- package/dist/src/frontend/homepage-client.js.map +1 -0
- package/dist/src/frontend/homepage-shared.js +1 -0
- package/dist/src/frontend/homepage-shared.js.map +1 -0
- package/dist/src/frontend/homepage-store.js +1 -0
- package/dist/src/frontend/homepage-store.js.map +1 -0
- package/dist/src/frontend/homepage-view.js +131 -18
- package/dist/src/frontend/homepage-view.js.map +1 -0
- package/dist/src/frontend/page-runtime-client.js +5 -2
- package/dist/src/frontend/page-runtime-client.js.map +1 -0
- package/dist/src/frontend/page-runtime-store.js +1 -0
- package/dist/src/frontend/page-runtime-store.js.map +1 -0
- package/dist/src/frontend/sentry.js +42 -0
- package/dist/src/frontend/sentry.js.map +1 -0
- package/dist/src/frontend/template-editor-chat.js +1719 -213
- package/dist/src/frontend/template-editor-chat.js.map +1 -0
- package/dist/src/help-page.js +333 -0
- package/dist/src/help-page.js.map +1 -0
- package/dist/src/homepage.js +169 -47
- package/dist/src/homepage.js.map +1 -0
- package/dist/src/hyperframes/composition.js +180 -0
- package/dist/src/hyperframes/composition.js.map +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/instrument.js +30 -0
- package/dist/src/instrument.js.map +1 -0
- package/dist/src/lib/crypto.js +1 -0
- package/dist/src/lib/crypto.js.map +1 -0
- package/dist/src/lib/dev-log.js +54 -0
- package/dist/src/lib/dev-log.js.map +1 -0
- package/dist/src/lib/display-name.js +11 -0
- package/dist/src/lib/display-name.js.map +1 -0
- package/dist/src/lib/ids.js +21 -1
- package/dist/src/lib/ids.js.map +1 -0
- package/dist/src/lib/images.js +1 -0
- package/dist/src/lib/images.js.map +1 -0
- package/dist/src/lib/json.js +1 -0
- package/dist/src/lib/json.js.map +1 -0
- package/dist/src/lib/template-dna.js +1 -0
- package/dist/src/lib/template-dna.js.map +1 -0
- package/dist/src/lib/template-paths.js +1 -0
- package/dist/src/lib/template-paths.js.map +1 -0
- package/dist/src/lib/template-style-options.js +40 -2
- package/dist/src/lib/template-style-options.js.map +1 -0
- package/dist/src/lib/time.js +1 -0
- package/dist/src/lib/time.js.map +1 -0
- package/dist/src/lib/video-quality-harness.js +60 -0
- package/dist/src/lib/video-quality-harness.js.map +1 -0
- package/dist/src/page-runtime.js +1 -0
- package/dist/src/page-runtime.js.map +1 -0
- package/dist/src/page-shell.js +1501 -49
- package/dist/src/page-shell.js.map +1 -0
- package/dist/src/primitive-context.js +144 -8
- package/dist/src/primitive-context.js.map +1 -0
- package/dist/src/primitive-registry.js +1919 -78
- package/dist/src/primitive-registry.js.map +1 -0
- package/dist/src/primitive-sdk.js +1 -0
- package/dist/src/primitive-sdk.js.map +1 -0
- package/dist/src/primitives/remotion/html-image.js +3 -1
- package/dist/src/primitives/remotion/html-image.js.map +1 -0
- package/dist/src/primitives/remotion/media-slideshow.js +60 -0
- package/dist/src/primitives/remotion/media-slideshow.js.map +1 -0
- package/dist/src/react-page-shell.js +1 -0
- package/dist/src/react-page-shell.js.map +1 -0
- package/dist/src/ready-post-schedule-component.js +1 -0
- package/dist/src/ready-post-schedule-component.js.map +1 -0
- package/dist/src/registry.js +237 -24
- package/dist/src/registry.js.map +1 -0
- package/dist/src/runtime.js +3 -0
- package/dist/src/runtime.js.map +1 -0
- package/dist/src/services/api-call-history.js +4 -0
- package/dist/src/services/api-call-history.js.map +1 -0
- package/dist/src/services/auth.js +40 -50
- package/dist/src/services/auth.js.map +1 -0
- package/dist/src/services/billing.js +20 -44
- package/dist/src/services/billing.js.map +1 -0
- package/dist/src/services/chat-threads.js +10 -6
- package/dist/src/services/chat-threads.js.map +1 -0
- package/dist/src/services/fork-access.js +94 -0
- package/dist/src/services/fork-access.js.map +1 -0
- package/dist/src/services/ghostcut.js +132 -0
- package/dist/src/services/ghostcut.js.map +1 -0
- package/dist/src/services/hyperframes.js +1014 -0
- package/dist/src/services/hyperframes.js.map +1 -0
- package/dist/src/services/job-capacity.js +14 -0
- package/dist/src/services/job-capacity.js.map +1 -0
- package/dist/src/services/job-logs.js +4 -0
- package/dist/src/services/job-logs.js.map +1 -0
- package/dist/src/services/jobs.js +99 -91
- package/dist/src/services/jobs.js.map +1 -0
- package/dist/src/services/media-processing.js +743 -0
- package/dist/src/services/media-processing.js.map +1 -0
- package/dist/src/services/primitive-media-lambda.js +280 -0
- package/dist/src/services/primitive-media-lambda.js.map +1 -0
- package/dist/src/services/providers.js +1560 -178
- package/dist/src/services/providers.js.map +1 -0
- package/dist/src/services/rate-limits.js +3 -2
- package/dist/src/services/rate-limits.js.map +1 -0
- package/dist/src/services/remotion.js +495 -92
- package/dist/src/services/remotion.js.map +1 -0
- package/dist/src/services/serverless-auth.js +374 -0
- package/dist/src/services/serverless-auth.js.map +1 -0
- package/dist/src/services/serverless-jobs.js +1074 -0
- package/dist/src/services/serverless-jobs.js.map +1 -0
- package/dist/src/services/serverless-provider-keys.js +401 -0
- package/dist/src/services/serverless-provider-keys.js.map +1 -0
- package/dist/src/services/serverless-records.js +1086 -0
- package/dist/src/services/serverless-records.js.map +1 -0
- package/dist/src/services/serverless-template-configs.js +67 -0
- package/dist/src/services/serverless-template-configs.js.map +1 -0
- package/dist/src/services/storage.js +170 -35
- package/dist/src/services/storage.js.map +1 -0
- package/dist/src/services/template-certification.js +295 -3
- package/dist/src/services/template-certification.js.map +1 -0
- package/dist/src/services/template-loader.js +45 -1
- package/dist/src/services/template-loader.js.map +1 -0
- package/dist/src/services/template-runtime-bundles.js +217 -0
- package/dist/src/services/template-runtime-bundles.js.map +1 -0
- package/dist/src/services/template-sources.js +452 -87
- package/dist/src/services/template-sources.js.map +1 -0
- package/dist/src/services/video-normalization.js +2 -0
- package/dist/src/services/video-normalization.js.map +1 -0
- package/dist/src/services/webhooks.js +7 -6
- package/dist/src/services/webhooks.js.map +1 -0
- package/dist/src/template-editor-pages.js +5637 -1322
- package/dist/src/template-editor-pages.js.map +1 -0
- package/dist/src/template-editor-shell.js +1376 -170
- package/dist/src/template-editor-shell.js.map +1 -0
- package/dist/src/template-sdk.js +1 -0
- package/dist/src/template-sdk.js.map +1 -0
- package/dist/src/worker.js +11 -226
- package/dist/src/worker.js.map +1 -0
- package/package.json +47 -12
- package/public/assets/homepage-client-app.js +34 -8
- package/public/assets/page-runtime-client-app.js +44 -18
- package/readme.secret.md +89 -0
- package/templates/vidfarm_template_0000/README.md +106 -0
- package/templates/vidfarm_template_0000/SKILL.md +266 -0
- package/templates/vidfarm_template_0000/assets/Abel-Regular.ttf +0 -0
- package/templates/vidfarm_template_0000/assets/DMSerifDisplay-Regular.ttf +0 -0
- package/templates/vidfarm_template_0000/assets/Montserrat[wght].ttf +0 -0
- package/templates/vidfarm_template_0000/assets/SourceCodePro[wght].ttf +0 -0
- package/templates/vidfarm_template_0000/assets/TikTokSans-SemiBold.ttf +0 -0
- package/templates/vidfarm_template_0000/assets/Yesteryear-Regular.ttf +0 -0
- package/templates/vidfarm_template_0000/composition.json +11 -0
- package/templates/vidfarm_template_0000/src/lib/images.js +221 -0
- package/templates/vidfarm_template_0000/src/remotion/Root.js +33 -0
- package/templates/vidfarm_template_0000/src/remotion/index.js +3 -0
- package/templates/vidfarm_template_0000/src/sdk.js +3 -0
- package/templates/vidfarm_template_0000/src/style-options.js +200 -0
- package/templates/vidfarm_template_0000/src/template-dna.js +9 -0
- package/templates/vidfarm_template_0000/src/template.js +1566 -0
- package/templates/vidfarm_template_0000/template.config.json +21 -0
- package/GETTING_STARTED.developers.md +0 -87
- package/SKILL.developer.md +0 -1177
- package/dist/src/db.js +0 -2007
package/dist/src/db.js
DELETED
|
@@ -1,2007 +0,0 @@
|
|
|
1
|
-
import Database from "better-sqlite3";
|
|
2
|
-
import { config } from "./config.js";
|
|
3
|
-
import { createId } from "./lib/ids.js";
|
|
4
|
-
import { parseJson, stringifyJson } from "./lib/json.js";
|
|
5
|
-
import { nowIso } from "./lib/time.js";
|
|
6
|
-
const db = new Database(config.VIDFARM_DB_PATH);
|
|
7
|
-
db.pragma("journal_mode = WAL");
|
|
8
|
-
db.pragma("foreign_keys = ON");
|
|
9
|
-
db.exec(`
|
|
10
|
-
create table if not exists customers (
|
|
11
|
-
id text primary key,
|
|
12
|
-
email text not null unique,
|
|
13
|
-
name text,
|
|
14
|
-
default_webhook_url text,
|
|
15
|
-
is_developer integer not null default 0,
|
|
16
|
-
is_paid_plan integer not null default 0,
|
|
17
|
-
plan_tier text not null default 'starter',
|
|
18
|
-
about text,
|
|
19
|
-
groupchat_url text,
|
|
20
|
-
flockposter_api_key text,
|
|
21
|
-
password_hash text,
|
|
22
|
-
created_at text not null,
|
|
23
|
-
updated_at text not null
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
create table if not exists api_keys (
|
|
27
|
-
id text primary key,
|
|
28
|
-
customer_id text not null references customers(id) on delete cascade,
|
|
29
|
-
key_hash text not null unique,
|
|
30
|
-
raw_value text,
|
|
31
|
-
label text,
|
|
32
|
-
status text not null,
|
|
33
|
-
last_used_at text,
|
|
34
|
-
created_at text not null,
|
|
35
|
-
updated_at text not null
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
create table if not exists otp_challenges (
|
|
39
|
-
id text primary key,
|
|
40
|
-
email text not null,
|
|
41
|
-
code_hash text not null,
|
|
42
|
-
expires_at text not null,
|
|
43
|
-
consumed_at text,
|
|
44
|
-
created_at text not null
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
create table if not exists customer_provider_keys (
|
|
48
|
-
id text primary key,
|
|
49
|
-
customer_id text not null references customers(id) on delete cascade,
|
|
50
|
-
provider text not null,
|
|
51
|
-
label text,
|
|
52
|
-
encrypted_secret text not null,
|
|
53
|
-
status text not null,
|
|
54
|
-
last_used_at text,
|
|
55
|
-
cooldown_until text,
|
|
56
|
-
disabled_reason text,
|
|
57
|
-
created_at text not null,
|
|
58
|
-
updated_at text not null
|
|
59
|
-
);
|
|
60
|
-
|
|
61
|
-
create table if not exists provider_key_leases (
|
|
62
|
-
key_id text primary key references customer_provider_keys(id) on delete cascade,
|
|
63
|
-
lease_token text not null,
|
|
64
|
-
worker_id text not null,
|
|
65
|
-
job_id text not null,
|
|
66
|
-
leased_at text not null,
|
|
67
|
-
expires_at text not null
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
create table if not exists provider_key_waiters (
|
|
71
|
-
id text primary key,
|
|
72
|
-
customer_id text not null references customers(id) on delete cascade,
|
|
73
|
-
provider text not null,
|
|
74
|
-
worker_id text not null,
|
|
75
|
-
owner_id text not null,
|
|
76
|
-
priority integer not null default 100,
|
|
77
|
-
created_at text not null,
|
|
78
|
-
expires_at text not null
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
create index if not exists idx_provider_key_waiters_scope on provider_key_waiters(customer_id, provider, priority, created_at);
|
|
82
|
-
|
|
83
|
-
create table if not exists provider_key_usage_events (
|
|
84
|
-
id text primary key,
|
|
85
|
-
key_id text not null references customer_provider_keys(id) on delete cascade,
|
|
86
|
-
job_id text not null,
|
|
87
|
-
provider text not null,
|
|
88
|
-
model text,
|
|
89
|
-
event_type text not null,
|
|
90
|
-
input_tokens integer,
|
|
91
|
-
output_tokens integer,
|
|
92
|
-
cost_usd real,
|
|
93
|
-
metadata text,
|
|
94
|
-
created_at text not null
|
|
95
|
-
);
|
|
96
|
-
|
|
97
|
-
create table if not exists template_configs (
|
|
98
|
-
id text primary key,
|
|
99
|
-
customer_id text not null references customers(id) on delete cascade,
|
|
100
|
-
template_id text not null,
|
|
101
|
-
config_json text not null,
|
|
102
|
-
created_at text not null,
|
|
103
|
-
updated_at text not null,
|
|
104
|
-
unique(customer_id, template_id)
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
create table if not exists jobs (
|
|
108
|
-
id text primary key,
|
|
109
|
-
template_id text not null,
|
|
110
|
-
operation_name text not null,
|
|
111
|
-
workflow_name text not null,
|
|
112
|
-
tracer text not null,
|
|
113
|
-
status text not null,
|
|
114
|
-
customer_id text not null references customers(id) on delete cascade,
|
|
115
|
-
payload_json text not null,
|
|
116
|
-
result_json text,
|
|
117
|
-
error_json text,
|
|
118
|
-
progress real not null default 0,
|
|
119
|
-
webhook_url text,
|
|
120
|
-
parent_job_id text references jobs(id) on delete set null,
|
|
121
|
-
priority integer not null default 100,
|
|
122
|
-
attempt_count integer not null default 0,
|
|
123
|
-
max_attempts integer not null default 6,
|
|
124
|
-
run_after text not null,
|
|
125
|
-
created_at text not null,
|
|
126
|
-
updated_at text not null,
|
|
127
|
-
started_at text,
|
|
128
|
-
completed_at text
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
create index if not exists idx_jobs_runnable on jobs(status, run_after, priority, created_at);
|
|
132
|
-
|
|
133
|
-
create table if not exists artifacts (
|
|
134
|
-
id text primary key,
|
|
135
|
-
job_id text not null references jobs(id) on delete cascade,
|
|
136
|
-
customer_id text not null references customers(id) on delete cascade,
|
|
137
|
-
template_id text not null,
|
|
138
|
-
kind text not null,
|
|
139
|
-
storage_key text not null,
|
|
140
|
-
public_url text,
|
|
141
|
-
metadata_json text not null,
|
|
142
|
-
created_at text not null
|
|
143
|
-
);
|
|
144
|
-
|
|
145
|
-
drop table if exists billing_events;
|
|
146
|
-
|
|
147
|
-
create table if not exists customer_credit_grants (
|
|
148
|
-
id text primary key,
|
|
149
|
-
customer_id text not null references customers(id) on delete cascade,
|
|
150
|
-
grant_type text not null,
|
|
151
|
-
amount_usd real not null,
|
|
152
|
-
metadata_json text not null,
|
|
153
|
-
created_at text not null,
|
|
154
|
-
unique(customer_id, grant_type)
|
|
155
|
-
);
|
|
156
|
-
|
|
157
|
-
create index if not exists idx_customer_credit_grants_customer_created on customer_credit_grants(customer_id, created_at desc);
|
|
158
|
-
|
|
159
|
-
create table if not exists webhook_deliveries (
|
|
160
|
-
id text primary key,
|
|
161
|
-
job_id text not null references jobs(id) on delete cascade,
|
|
162
|
-
customer_id text not null references customers(id) on delete cascade,
|
|
163
|
-
event_type text not null,
|
|
164
|
-
destination_url text not null,
|
|
165
|
-
payload_json text not null,
|
|
166
|
-
status text not null,
|
|
167
|
-
attempt_count integer not null default 0,
|
|
168
|
-
next_attempt_at text not null,
|
|
169
|
-
last_error text,
|
|
170
|
-
created_at text not null,
|
|
171
|
-
updated_at text not null
|
|
172
|
-
);
|
|
173
|
-
|
|
174
|
-
create table if not exists user_attachments (
|
|
175
|
-
id text primary key,
|
|
176
|
-
customer_id text not null references customers(id) on delete cascade,
|
|
177
|
-
file_name text not null,
|
|
178
|
-
content_type text not null,
|
|
179
|
-
size_bytes integer not null,
|
|
180
|
-
storage_key text not null,
|
|
181
|
-
public_url text,
|
|
182
|
-
created_at text not null
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
create index if not exists idx_user_attachments_customer_created on user_attachments(customer_id, created_at desc);
|
|
186
|
-
|
|
187
|
-
create table if not exists email_channels (
|
|
188
|
-
id text primary key,
|
|
189
|
-
customer_id text not null references customers(id) on delete cascade,
|
|
190
|
-
title text not null,
|
|
191
|
-
email text not null,
|
|
192
|
-
note text,
|
|
193
|
-
avatar_url text,
|
|
194
|
-
avatar_storage_key text,
|
|
195
|
-
status text not null,
|
|
196
|
-
verification_token_hash text,
|
|
197
|
-
verification_sent_at text,
|
|
198
|
-
verified_at text,
|
|
199
|
-
created_at text not null,
|
|
200
|
-
updated_at text not null,
|
|
201
|
-
unique(customer_id, email)
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
create index if not exists idx_email_channels_customer_created on email_channels(customer_id, created_at desc);
|
|
205
|
-
create index if not exists idx_email_channels_token on email_channels(verification_token_hash);
|
|
206
|
-
|
|
207
|
-
create table if not exists ready_posts (
|
|
208
|
-
id text primary key,
|
|
209
|
-
customer_id text not null references customers(id) on delete cascade,
|
|
210
|
-
status text not null,
|
|
211
|
-
tracer text,
|
|
212
|
-
title text,
|
|
213
|
-
content text not null,
|
|
214
|
-
pinned_comment text,
|
|
215
|
-
scheduled_at text,
|
|
216
|
-
timezone text,
|
|
217
|
-
post_type text not null,
|
|
218
|
-
integration_ids_json text not null,
|
|
219
|
-
platform_targets_json text not null,
|
|
220
|
-
media_assets_json text not null,
|
|
221
|
-
renditions_json text not null,
|
|
222
|
-
flockposter_payload_json text not null,
|
|
223
|
-
field_values_json text not null,
|
|
224
|
-
share_password_hash text,
|
|
225
|
-
zip_storage_key text,
|
|
226
|
-
zip_public_url text,
|
|
227
|
-
archived_at text,
|
|
228
|
-
deleted_at text,
|
|
229
|
-
created_at text not null,
|
|
230
|
-
updated_at text not null
|
|
231
|
-
);
|
|
232
|
-
|
|
233
|
-
create index if not exists idx_ready_posts_customer_created on ready_posts(customer_id, created_at desc);
|
|
234
|
-
|
|
235
|
-
create table if not exists post_schedules (
|
|
236
|
-
id text primary key,
|
|
237
|
-
customer_id text not null references customers(id) on delete cascade,
|
|
238
|
-
ready_post_id text references ready_posts(id) on delete cascade,
|
|
239
|
-
managed_by text not null,
|
|
240
|
-
provider text not null,
|
|
241
|
-
destination_type text not null,
|
|
242
|
-
destination_id text not null,
|
|
243
|
-
scheduled_at text not null,
|
|
244
|
-
timezone text,
|
|
245
|
-
status text not null,
|
|
246
|
-
provider_status text,
|
|
247
|
-
provider_schedule_id text,
|
|
248
|
-
title text,
|
|
249
|
-
summary text,
|
|
250
|
-
url text,
|
|
251
|
-
additional_notes text,
|
|
252
|
-
provider_payload_json text not null,
|
|
253
|
-
provider_response_json text not null,
|
|
254
|
-
last_synced_at text,
|
|
255
|
-
last_error text,
|
|
256
|
-
cancelled_at text,
|
|
257
|
-
sent_at text,
|
|
258
|
-
created_at text not null,
|
|
259
|
-
updated_at text not null
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
create index if not exists idx_post_schedules_customer_scheduled on post_schedules(customer_id, scheduled_at asc);
|
|
263
|
-
create index if not exists idx_post_schedules_ready_post on post_schedules(ready_post_id, status);
|
|
264
|
-
create index if not exists idx_post_schedules_provider_id on post_schedules(customer_id, provider, provider_schedule_id);
|
|
265
|
-
|
|
266
|
-
create table if not exists chat_threads (
|
|
267
|
-
id text primary key,
|
|
268
|
-
customer_id text not null references customers(id) on delete cascade,
|
|
269
|
-
template_id text not null,
|
|
270
|
-
title text not null,
|
|
271
|
-
storage_prefix text not null,
|
|
272
|
-
message_count integer not null default 0,
|
|
273
|
-
last_message_at text,
|
|
274
|
-
hidden_from_view integer not null default 0,
|
|
275
|
-
archived_at text,
|
|
276
|
-
created_at text not null,
|
|
277
|
-
updated_at text not null
|
|
278
|
-
);
|
|
279
|
-
|
|
280
|
-
create index if not exists idx_chat_threads_customer_template_updated on chat_threads(customer_id, template_id, updated_at desc);
|
|
281
|
-
|
|
282
|
-
create table if not exists chat_thread_tracers (
|
|
283
|
-
thread_id text not null references chat_threads(id) on delete cascade,
|
|
284
|
-
tracer text not null,
|
|
285
|
-
created_at text not null,
|
|
286
|
-
primary key (thread_id, tracer)
|
|
287
|
-
);
|
|
288
|
-
|
|
289
|
-
create index if not exists idx_chat_thread_tracers_tracer on chat_thread_tracers(tracer, thread_id);
|
|
290
|
-
|
|
291
|
-
create table if not exists template_sources (
|
|
292
|
-
id text primary key,
|
|
293
|
-
template_id text not null unique,
|
|
294
|
-
slug_id text,
|
|
295
|
-
repo_url text not null,
|
|
296
|
-
branch text not null,
|
|
297
|
-
template_module_path text not null,
|
|
298
|
-
skill_path text not null,
|
|
299
|
-
install_command text not null,
|
|
300
|
-
build_command text not null,
|
|
301
|
-
status text not null,
|
|
302
|
-
created_at text not null,
|
|
303
|
-
updated_at text not null
|
|
304
|
-
);
|
|
305
|
-
|
|
306
|
-
create table if not exists template_releases (
|
|
307
|
-
id text primary key,
|
|
308
|
-
source_id text not null references template_sources(id) on delete cascade,
|
|
309
|
-
template_id text not null,
|
|
310
|
-
branch text not null,
|
|
311
|
-
commit_sha text not null,
|
|
312
|
-
checkout_path text not null,
|
|
313
|
-
skill_path text not null,
|
|
314
|
-
module_path text not null,
|
|
315
|
-
artifact_manifest_key text,
|
|
316
|
-
artifact_key text,
|
|
317
|
-
status text not null,
|
|
318
|
-
certification_report_json text,
|
|
319
|
-
activated_at text,
|
|
320
|
-
created_at text not null,
|
|
321
|
-
updated_at text not null,
|
|
322
|
-
unique(source_id, commit_sha)
|
|
323
|
-
);
|
|
324
|
-
|
|
325
|
-
create index if not exists idx_template_releases_template_status on template_releases(template_id, status, created_at);
|
|
326
|
-
`);
|
|
327
|
-
const customerColumns = db.prepare(`pragma table_info(customers)`).all();
|
|
328
|
-
if (!customerColumns.some((column) => column.name === "is_developer")) {
|
|
329
|
-
db.exec(`alter table customers add column is_developer integer not null default 0;`);
|
|
330
|
-
}
|
|
331
|
-
if (!customerColumns.some((column) => column.name === "is_paid_plan")) {
|
|
332
|
-
db.exec(`alter table customers add column is_paid_plan integer not null default 0;`);
|
|
333
|
-
}
|
|
334
|
-
if (!customerColumns.some((column) => column.name === "plan_tier")) {
|
|
335
|
-
db.exec(`alter table customers add column plan_tier text not null default 'starter';`);
|
|
336
|
-
}
|
|
337
|
-
db.exec(`
|
|
338
|
-
update customers
|
|
339
|
-
set plan_tier = 'starter'
|
|
340
|
-
where plan_tier in ('standard', 'invisible')
|
|
341
|
-
or plan_tier is null
|
|
342
|
-
or trim(plan_tier) = ''
|
|
343
|
-
`);
|
|
344
|
-
if (!customerColumns.some((column) => column.name === "password_hash")) {
|
|
345
|
-
db.exec(`alter table customers add column password_hash text;`);
|
|
346
|
-
}
|
|
347
|
-
if (!customerColumns.some((column) => column.name === "about")) {
|
|
348
|
-
db.exec(`alter table customers add column about text;`);
|
|
349
|
-
}
|
|
350
|
-
if (!customerColumns.some((column) => column.name === "groupchat_url")) {
|
|
351
|
-
db.exec(`alter table customers add column groupchat_url text;`);
|
|
352
|
-
}
|
|
353
|
-
if (!customerColumns.some((column) => column.name === "flockposter_api_key")) {
|
|
354
|
-
db.exec(`alter table customers add column flockposter_api_key text;`);
|
|
355
|
-
}
|
|
356
|
-
const apiKeyColumns = db.prepare(`pragma table_info(api_keys)`).all();
|
|
357
|
-
if (!apiKeyColumns.some((column) => column.name === "raw_value")) {
|
|
358
|
-
db.exec(`alter table api_keys add column raw_value text;`);
|
|
359
|
-
}
|
|
360
|
-
const providerKeyColumns = db.prepare(`pragma table_info(customer_provider_keys)`).all();
|
|
361
|
-
if (providerKeyColumns.some((column) => column.name === "weight")) {
|
|
362
|
-
db.exec(`alter table customer_provider_keys drop column weight;`);
|
|
363
|
-
}
|
|
364
|
-
const templateSourceColumns = db.prepare(`pragma table_info(template_sources)`).all();
|
|
365
|
-
if (!templateSourceColumns.some((column) => column.name === "slug_id")) {
|
|
366
|
-
db.exec(`alter table template_sources add column slug_id text;`);
|
|
367
|
-
}
|
|
368
|
-
db.exec(`
|
|
369
|
-
update template_sources
|
|
370
|
-
set slug_id = replace(template_id, '-', '_')
|
|
371
|
-
where slug_id is null or trim(slug_id) = '';
|
|
372
|
-
`);
|
|
373
|
-
db.exec(`create unique index if not exists idx_template_sources_slug_id on template_sources(slug_id);`);
|
|
374
|
-
db.exec(`
|
|
375
|
-
update template_sources
|
|
376
|
-
set status = 'approved'
|
|
377
|
-
where status = 'active';
|
|
378
|
-
`);
|
|
379
|
-
db.exec(`
|
|
380
|
-
update template_releases
|
|
381
|
-
set status = 'pending_approval'
|
|
382
|
-
where status = 'certified';
|
|
383
|
-
`);
|
|
384
|
-
const templateReleaseColumns = db.prepare(`pragma table_info(template_releases)`).all();
|
|
385
|
-
if (!templateReleaseColumns.some((column) => column.name === "artifact_manifest_key")) {
|
|
386
|
-
db.exec(`alter table template_releases add column artifact_manifest_key text;`);
|
|
387
|
-
}
|
|
388
|
-
if (!templateReleaseColumns.some((column) => column.name === "artifact_key")) {
|
|
389
|
-
db.exec(`alter table template_releases add column artifact_key text;`);
|
|
390
|
-
}
|
|
391
|
-
const readyPostColumns = db.prepare(`pragma table_info(ready_posts)`).all();
|
|
392
|
-
if (!readyPostColumns.some((column) => column.name === "tracer")) {
|
|
393
|
-
db.exec(`alter table ready_posts add column tracer text;`);
|
|
394
|
-
}
|
|
395
|
-
if (!readyPostColumns.some((column) => column.name === "pinned_comment")) {
|
|
396
|
-
db.exec(`alter table ready_posts add column pinned_comment text;`);
|
|
397
|
-
}
|
|
398
|
-
const postScheduleColumns = db.prepare(`pragma table_info(post_schedules)`).all();
|
|
399
|
-
if (!postScheduleColumns.some((column) => column.name === "additional_notes")) {
|
|
400
|
-
db.exec(`alter table post_schedules add column additional_notes text;`);
|
|
401
|
-
}
|
|
402
|
-
const emailChannelColumns = db.prepare(`pragma table_info(email_channels)`).all();
|
|
403
|
-
if (!emailChannelColumns.some((column) => column.name === "avatar_url")) {
|
|
404
|
-
db.exec(`alter table email_channels add column avatar_url text;`);
|
|
405
|
-
}
|
|
406
|
-
if (!emailChannelColumns.some((column) => column.name === "avatar_storage_key")) {
|
|
407
|
-
db.exec(`alter table email_channels add column avatar_storage_key text;`);
|
|
408
|
-
}
|
|
409
|
-
db.exec(`drop index if exists idx_job_events_job_time;`);
|
|
410
|
-
db.exec(`drop table if exists job_events;`);
|
|
411
|
-
function mapJob(row) {
|
|
412
|
-
return {
|
|
413
|
-
id: String(row.id),
|
|
414
|
-
templateId: String(row.template_id),
|
|
415
|
-
operationName: String(row.operation_name),
|
|
416
|
-
workflowName: String(row.workflow_name),
|
|
417
|
-
tracer: String(row.tracer),
|
|
418
|
-
status: row.status,
|
|
419
|
-
customerId: String(row.customer_id),
|
|
420
|
-
payload: parseJson(row.payload_json, {}),
|
|
421
|
-
result: parseJson(row.result_json, null),
|
|
422
|
-
error: parseJson(row.error_json, null),
|
|
423
|
-
progress: Number(row.progress),
|
|
424
|
-
webhookUrl: row.webhook_url ? String(row.webhook_url) : null,
|
|
425
|
-
parentJobId: row.parent_job_id ? String(row.parent_job_id) : null,
|
|
426
|
-
priority: Number(row.priority),
|
|
427
|
-
attemptCount: Number(row.attempt_count),
|
|
428
|
-
maxAttempts: Number(row.max_attempts),
|
|
429
|
-
runAfter: String(row.run_after),
|
|
430
|
-
createdAt: String(row.created_at),
|
|
431
|
-
updatedAt: String(row.updated_at),
|
|
432
|
-
startedAt: row.started_at ? String(row.started_at) : null,
|
|
433
|
-
completedAt: row.completed_at ? String(row.completed_at) : null
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
function mapTemplateSource(row) {
|
|
437
|
-
return {
|
|
438
|
-
id: String(row.id),
|
|
439
|
-
templateId: String(row.template_id),
|
|
440
|
-
slugId: String(row.slug_id),
|
|
441
|
-
repoUrl: String(row.repo_url),
|
|
442
|
-
branch: String(row.branch),
|
|
443
|
-
templateModulePath: String(row.template_module_path),
|
|
444
|
-
skillPath: String(row.skill_path),
|
|
445
|
-
installCommand: String(row.install_command),
|
|
446
|
-
buildCommand: String(row.build_command),
|
|
447
|
-
status: row.status,
|
|
448
|
-
createdAt: String(row.created_at),
|
|
449
|
-
updatedAt: String(row.updated_at)
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
|
-
function mapTemplateRelease(row) {
|
|
453
|
-
return {
|
|
454
|
-
id: String(row.id),
|
|
455
|
-
sourceId: String(row.source_id),
|
|
456
|
-
templateId: String(row.template_id),
|
|
457
|
-
branch: String(row.branch),
|
|
458
|
-
commitSha: String(row.commit_sha),
|
|
459
|
-
checkoutPath: String(row.checkout_path),
|
|
460
|
-
skillPath: String(row.skill_path),
|
|
461
|
-
modulePath: String(row.module_path),
|
|
462
|
-
artifactManifestKey: row.artifact_manifest_key ? String(row.artifact_manifest_key) : null,
|
|
463
|
-
artifactKey: row.artifact_key ? String(row.artifact_key) : null,
|
|
464
|
-
status: row.status,
|
|
465
|
-
certificationReport: parseJson(row.certification_report_json, null),
|
|
466
|
-
activatedAt: row.activated_at ? String(row.activated_at) : null,
|
|
467
|
-
createdAt: String(row.created_at),
|
|
468
|
-
updatedAt: String(row.updated_at)
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
function mapUserAttachment(row) {
|
|
472
|
-
return {
|
|
473
|
-
id: String(row.id),
|
|
474
|
-
customerId: String(row.customer_id),
|
|
475
|
-
fileName: String(row.file_name),
|
|
476
|
-
contentType: String(row.content_type),
|
|
477
|
-
sizeBytes: Number(row.size_bytes),
|
|
478
|
-
storageKey: String(row.storage_key),
|
|
479
|
-
publicUrl: row.public_url ? String(row.public_url) : null,
|
|
480
|
-
createdAt: String(row.created_at)
|
|
481
|
-
};
|
|
482
|
-
}
|
|
483
|
-
function mapEmailChannel(row) {
|
|
484
|
-
const status = String(row.status) === "verified" ? "verified" : "unverified";
|
|
485
|
-
return {
|
|
486
|
-
id: String(row.id),
|
|
487
|
-
customerId: String(row.customer_id),
|
|
488
|
-
title: String(row.title),
|
|
489
|
-
email: String(row.email),
|
|
490
|
-
note: row.note ? String(row.note) : null,
|
|
491
|
-
avatarUrl: row.avatar_url ? String(row.avatar_url) : null,
|
|
492
|
-
avatarStorageKey: row.avatar_storage_key ? String(row.avatar_storage_key) : null,
|
|
493
|
-
status,
|
|
494
|
-
verificationTokenHash: row.verification_token_hash ? String(row.verification_token_hash) : null,
|
|
495
|
-
verificationSentAt: row.verification_sent_at ? String(row.verification_sent_at) : null,
|
|
496
|
-
verifiedAt: row.verified_at ? String(row.verified_at) : null,
|
|
497
|
-
createdAt: String(row.created_at),
|
|
498
|
-
updatedAt: String(row.updated_at)
|
|
499
|
-
};
|
|
500
|
-
}
|
|
501
|
-
function mapArtifact(row) {
|
|
502
|
-
return {
|
|
503
|
-
id: String(row.id),
|
|
504
|
-
jobId: String(row.job_id),
|
|
505
|
-
customerId: String(row.customer_id),
|
|
506
|
-
templateId: String(row.template_id),
|
|
507
|
-
kind: String(row.kind),
|
|
508
|
-
storageKey: String(row.storage_key),
|
|
509
|
-
publicUrl: row.public_url ? String(row.public_url) : null,
|
|
510
|
-
metadata: parseJson(row.metadata_json, {}),
|
|
511
|
-
createdAt: String(row.created_at)
|
|
512
|
-
};
|
|
513
|
-
}
|
|
514
|
-
function mapReadyPost(row) {
|
|
515
|
-
return {
|
|
516
|
-
id: String(row.id),
|
|
517
|
-
customerId: String(row.customer_id),
|
|
518
|
-
status: row.status,
|
|
519
|
-
tracer: row.tracer ? String(row.tracer) : null,
|
|
520
|
-
title: row.title ? String(row.title) : null,
|
|
521
|
-
caption: String(row.content),
|
|
522
|
-
pinnedComment: row.pinned_comment ? String(row.pinned_comment) : null,
|
|
523
|
-
mediaAssets: parseJson(row.media_assets_json, []),
|
|
524
|
-
sharePasswordHash: row.share_password_hash ? String(row.share_password_hash) : null,
|
|
525
|
-
zipStorageKey: row.zip_storage_key ? String(row.zip_storage_key) : null,
|
|
526
|
-
zipPublicUrl: row.zip_public_url ? String(row.zip_public_url) : null,
|
|
527
|
-
archivedAt: row.archived_at ? String(row.archived_at) : null,
|
|
528
|
-
deletedAt: row.deleted_at ? String(row.deleted_at) : null,
|
|
529
|
-
createdAt: String(row.created_at),
|
|
530
|
-
updatedAt: String(row.updated_at)
|
|
531
|
-
};
|
|
532
|
-
}
|
|
533
|
-
function mapPostSchedule(row) {
|
|
534
|
-
return {
|
|
535
|
-
id: String(row.id),
|
|
536
|
-
customerId: String(row.customer_id),
|
|
537
|
-
readyPostId: row.ready_post_id ? String(row.ready_post_id) : null,
|
|
538
|
-
managedBy: row.managed_by,
|
|
539
|
-
provider: row.provider,
|
|
540
|
-
destinationType: row.destination_type,
|
|
541
|
-
destinationId: String(row.destination_id),
|
|
542
|
-
scheduledAt: String(row.scheduled_at),
|
|
543
|
-
timezone: row.timezone ? String(row.timezone) : null,
|
|
544
|
-
status: row.status,
|
|
545
|
-
providerStatus: row.provider_status ? String(row.provider_status) : null,
|
|
546
|
-
providerScheduleId: row.provider_schedule_id ? String(row.provider_schedule_id) : null,
|
|
547
|
-
title: row.title ? String(row.title) : null,
|
|
548
|
-
summary: row.summary ? String(row.summary) : null,
|
|
549
|
-
url: row.url ? String(row.url) : null,
|
|
550
|
-
additionalNotes: row.additional_notes ? String(row.additional_notes) : null,
|
|
551
|
-
providerPayload: parseJson(row.provider_payload_json, {}),
|
|
552
|
-
providerResponse: parseJson(row.provider_response_json, {}),
|
|
553
|
-
lastSyncedAt: row.last_synced_at ? String(row.last_synced_at) : null,
|
|
554
|
-
lastError: row.last_error ? String(row.last_error) : null,
|
|
555
|
-
cancelledAt: row.cancelled_at ? String(row.cancelled_at) : null,
|
|
556
|
-
sentAt: row.sent_at ? String(row.sent_at) : null,
|
|
557
|
-
createdAt: String(row.created_at),
|
|
558
|
-
updatedAt: String(row.updated_at)
|
|
559
|
-
};
|
|
560
|
-
}
|
|
561
|
-
function mapChatThread(row, tracers, messages = []) {
|
|
562
|
-
return {
|
|
563
|
-
id: String(row.id),
|
|
564
|
-
customerId: String(row.customer_id),
|
|
565
|
-
templateId: String(row.template_id),
|
|
566
|
-
title: String(row.title),
|
|
567
|
-
createdAt: String(row.created_at),
|
|
568
|
-
updatedAt: String(row.updated_at),
|
|
569
|
-
lastMessageAt: row.last_message_at ? String(row.last_message_at) : null,
|
|
570
|
-
messageCount: Number(row.message_count ?? 0),
|
|
571
|
-
tracers,
|
|
572
|
-
hiddenFromView: Boolean(row.hidden_from_view),
|
|
573
|
-
archivedAt: row.archived_at ? String(row.archived_at) : null,
|
|
574
|
-
storagePrefix: String(row.storage_prefix),
|
|
575
|
-
messages
|
|
576
|
-
};
|
|
577
|
-
}
|
|
578
|
-
export const database = {
|
|
579
|
-
raw: db,
|
|
580
|
-
upsertCustomer(input) {
|
|
581
|
-
const timestamp = nowIso();
|
|
582
|
-
const current = db.prepare(`select * from customers where id = ?`).get(input.id);
|
|
583
|
-
db.prepare(`
|
|
584
|
-
insert into customers (
|
|
585
|
-
id, email, name, default_webhook_url, is_developer, is_paid_plan, about, groupchat_url, flockposter_api_key, password_hash, created_at, updated_at
|
|
586
|
-
, plan_tier
|
|
587
|
-
)
|
|
588
|
-
values (
|
|
589
|
-
@id, @email, @name, @default_webhook_url, @is_developer, @is_paid_plan, @about, @groupchat_url, @flockposter_api_key, @password_hash, @created_at, @updated_at
|
|
590
|
-
, @plan_tier
|
|
591
|
-
)
|
|
592
|
-
on conflict(id) do update set
|
|
593
|
-
email = excluded.email,
|
|
594
|
-
name = excluded.name,
|
|
595
|
-
default_webhook_url = excluded.default_webhook_url,
|
|
596
|
-
is_developer = excluded.is_developer,
|
|
597
|
-
is_paid_plan = excluded.is_paid_plan,
|
|
598
|
-
plan_tier = excluded.plan_tier,
|
|
599
|
-
about = excluded.about,
|
|
600
|
-
groupchat_url = excluded.groupchat_url,
|
|
601
|
-
flockposter_api_key = excluded.flockposter_api_key,
|
|
602
|
-
password_hash = excluded.password_hash,
|
|
603
|
-
updated_at = excluded.updated_at
|
|
604
|
-
`).run({
|
|
605
|
-
id: input.id,
|
|
606
|
-
email: input.email,
|
|
607
|
-
name: input.name ?? null,
|
|
608
|
-
default_webhook_url: input.defaultWebhookUrl ?? null,
|
|
609
|
-
is_developer: input.isDeveloper === undefined ? Number(current?.is_developer ?? 0) : (input.isDeveloper ? 1 : 0),
|
|
610
|
-
is_paid_plan: input.isPaidPlan === undefined ? Number(current?.is_paid_plan ?? 0) : (input.isPaidPlan ? 1 : 0),
|
|
611
|
-
plan_tier: input.planTier === undefined ? String(current?.plan_tier ?? "starter") : input.planTier,
|
|
612
|
-
about: input.about === undefined ? (current?.about ?? null) : input.about,
|
|
613
|
-
groupchat_url: input.groupchatUrl === undefined ? (current?.groupchat_url ?? null) : input.groupchatUrl,
|
|
614
|
-
flockposter_api_key: input.flockposterApiKey === undefined ? (current?.flockposter_api_key ?? null) : input.flockposterApiKey,
|
|
615
|
-
password_hash: input.passwordHash === undefined ? (current?.password_hash ?? null) : input.passwordHash,
|
|
616
|
-
created_at: timestamp,
|
|
617
|
-
updated_at: timestamp
|
|
618
|
-
});
|
|
619
|
-
return this.getCustomerById(input.id);
|
|
620
|
-
},
|
|
621
|
-
getCustomerById(id) {
|
|
622
|
-
const row = db.prepare(`select * from customers where id = ?`).get(id);
|
|
623
|
-
if (!row) {
|
|
624
|
-
return null;
|
|
625
|
-
}
|
|
626
|
-
return {
|
|
627
|
-
id: String(row.id),
|
|
628
|
-
email: String(row.email),
|
|
629
|
-
name: row.name ? String(row.name) : null,
|
|
630
|
-
defaultWebhookUrl: row.default_webhook_url ? String(row.default_webhook_url) : null,
|
|
631
|
-
isDeveloper: Boolean(row.is_developer),
|
|
632
|
-
isPaidPlan: Boolean(row.is_paid_plan),
|
|
633
|
-
planTier: normalizeCustomerPlanTier(row.plan_tier),
|
|
634
|
-
about: row.about ? String(row.about) : null,
|
|
635
|
-
groupchatUrl: row.groupchat_url ? String(row.groupchat_url) : null,
|
|
636
|
-
flockposterApiKey: row.flockposter_api_key ? String(row.flockposter_api_key) : null,
|
|
637
|
-
createdAt: String(row.created_at),
|
|
638
|
-
updatedAt: String(row.updated_at)
|
|
639
|
-
};
|
|
640
|
-
},
|
|
641
|
-
getCustomerByEmail(email) {
|
|
642
|
-
const row = db.prepare(`select * from customers where lower(email) = lower(?)`).get(email);
|
|
643
|
-
return row ? this.getCustomerById(String(row.id)) : null;
|
|
644
|
-
},
|
|
645
|
-
getFirstPaidCustomer() {
|
|
646
|
-
const row = db.prepare(`
|
|
647
|
-
select id
|
|
648
|
-
from customers
|
|
649
|
-
where is_paid_plan = 1
|
|
650
|
-
order by created_at asc
|
|
651
|
-
limit 1
|
|
652
|
-
`).get();
|
|
653
|
-
return row?.id ? this.getCustomerById(String(row.id)) : null;
|
|
654
|
-
},
|
|
655
|
-
listPaidCustomers() {
|
|
656
|
-
const rows = db.prepare(`
|
|
657
|
-
select id
|
|
658
|
-
from customers
|
|
659
|
-
where is_paid_plan = 1
|
|
660
|
-
order by created_at asc
|
|
661
|
-
`).all();
|
|
662
|
-
return rows
|
|
663
|
-
.map((row) => this.getCustomerById(String(row.id)))
|
|
664
|
-
.filter((customer) => Boolean(customer));
|
|
665
|
-
},
|
|
666
|
-
getCustomerAuthByEmail(email) {
|
|
667
|
-
return db.prepare(`
|
|
668
|
-
select id, email, name, default_webhook_url, is_developer, is_paid_plan, password_hash, plan_tier, created_at, updated_at
|
|
669
|
-
, about, groupchat_url, flockposter_api_key
|
|
670
|
-
from customers
|
|
671
|
-
where lower(email) = lower(?)
|
|
672
|
-
limit 1
|
|
673
|
-
`).get(email);
|
|
674
|
-
},
|
|
675
|
-
updateCustomerPassword(customerId, passwordHash) {
|
|
676
|
-
db.prepare(`
|
|
677
|
-
update customers
|
|
678
|
-
set password_hash = ?, updated_at = ?
|
|
679
|
-
where id = ?
|
|
680
|
-
`).run(passwordHash, nowIso(), customerId);
|
|
681
|
-
},
|
|
682
|
-
updateCustomerPaidPlan(customerId, isPaidPlan, planTier) {
|
|
683
|
-
const existing = this.getCustomerById(customerId);
|
|
684
|
-
db.prepare(`
|
|
685
|
-
update customers
|
|
686
|
-
set is_paid_plan = ?, plan_tier = ?, updated_at = ?
|
|
687
|
-
where id = ?
|
|
688
|
-
`).run(isPaidPlan ? 1 : 0, planTier ?? existing?.planTier ?? "starter", nowIso(), customerId);
|
|
689
|
-
},
|
|
690
|
-
hasCustomerCreditGrant(customerId, grantType) {
|
|
691
|
-
const row = db.prepare(`
|
|
692
|
-
select 1
|
|
693
|
-
from customer_credit_grants
|
|
694
|
-
where customer_id = ? and grant_type = ?
|
|
695
|
-
limit 1
|
|
696
|
-
`).get(customerId, grantType);
|
|
697
|
-
return Boolean(row);
|
|
698
|
-
},
|
|
699
|
-
insertCustomerCreditGrant(record) {
|
|
700
|
-
const createdAt = nowIso();
|
|
701
|
-
db.prepare(`
|
|
702
|
-
insert into customer_credit_grants (id, customer_id, grant_type, amount_usd, metadata_json, created_at)
|
|
703
|
-
values (@id, @customer_id, @grant_type, @amount_usd, @metadata_json, @created_at)
|
|
704
|
-
`).run({
|
|
705
|
-
id: record.id,
|
|
706
|
-
customer_id: record.customerId,
|
|
707
|
-
grant_type: record.grantType,
|
|
708
|
-
amount_usd: record.amountUsd,
|
|
709
|
-
metadata_json: stringifyJson(record.metadata ?? {}),
|
|
710
|
-
created_at: createdAt
|
|
711
|
-
});
|
|
712
|
-
},
|
|
713
|
-
setCustomerAccessByEmail(input) {
|
|
714
|
-
const normalizedEmail = input.email.trim().toLowerCase();
|
|
715
|
-
const existing = this.getCustomerByEmail(normalizedEmail);
|
|
716
|
-
return this.upsertCustomer({
|
|
717
|
-
id: existing?.id ?? createId("cus"),
|
|
718
|
-
email: normalizedEmail,
|
|
719
|
-
name: input.name === undefined ? (existing?.name ?? null) : input.name,
|
|
720
|
-
defaultWebhookUrl: existing?.defaultWebhookUrl ?? null,
|
|
721
|
-
isDeveloper: input.isDeveloper === undefined ? (existing?.isDeveloper ?? false) : input.isDeveloper,
|
|
722
|
-
isPaidPlan: input.isPaidPlan === undefined ? (existing?.isPaidPlan ?? false) : input.isPaidPlan,
|
|
723
|
-
planTier: input.planTier === undefined ? (existing?.planTier ?? "starter") : input.planTier,
|
|
724
|
-
about: existing?.about ?? null,
|
|
725
|
-
groupchatUrl: existing?.groupchatUrl ?? null,
|
|
726
|
-
flockposterApiKey: existing?.flockposterApiKey ?? null
|
|
727
|
-
});
|
|
728
|
-
},
|
|
729
|
-
updateCustomerProfile(input) {
|
|
730
|
-
const existing = this.getCustomerById(input.customerId);
|
|
731
|
-
db.prepare(`
|
|
732
|
-
update customers
|
|
733
|
-
set name = ?, about = ?, groupchat_url = ?, flockposter_api_key = ?, updated_at = ?
|
|
734
|
-
where id = ?
|
|
735
|
-
`).run(input.name === undefined ? (existing?.name ?? null) : input.name, input.about === undefined ? (existing?.about ?? null) : input.about, input.groupchatUrl === undefined ? (existing?.groupchatUrl ?? null) : input.groupchatUrl, input.flockposterApiKey === undefined ? (existing?.flockposterApiKey ?? null) : input.flockposterApiKey, nowIso(), input.customerId);
|
|
736
|
-
},
|
|
737
|
-
listProviderKeys(customerId) {
|
|
738
|
-
return db.prepare(`
|
|
739
|
-
select id, provider, label, status, last_used_at, cooldown_until, disabled_reason, created_at, updated_at
|
|
740
|
-
from customer_provider_keys
|
|
741
|
-
where customer_id = ?
|
|
742
|
-
order by provider asc, created_at desc
|
|
743
|
-
`).all(customerId);
|
|
744
|
-
},
|
|
745
|
-
listProviderKeysWithSecrets(customerId) {
|
|
746
|
-
return db.prepare(`
|
|
747
|
-
select id, provider, label, encrypted_secret as secret, status, last_used_at, cooldown_until, disabled_reason, created_at, updated_at
|
|
748
|
-
from customer_provider_keys
|
|
749
|
-
where customer_id = ?
|
|
750
|
-
order by provider asc, created_at desc
|
|
751
|
-
`).all(customerId);
|
|
752
|
-
},
|
|
753
|
-
deleteProviderKey(customerId, keyId) {
|
|
754
|
-
db.prepare(`delete from customer_provider_keys where id = ? and customer_id = ?`).run(keyId, customerId);
|
|
755
|
-
},
|
|
756
|
-
createEmailChannel(record) {
|
|
757
|
-
const timestamp = nowIso();
|
|
758
|
-
db.prepare(`
|
|
759
|
-
insert into email_channels (
|
|
760
|
-
id, customer_id, title, email, note, avatar_url, avatar_storage_key, status, verification_token_hash,
|
|
761
|
-
verification_sent_at, verified_at, created_at, updated_at
|
|
762
|
-
) values (
|
|
763
|
-
@id, @customer_id, @title, @email, @note, @avatar_url, @avatar_storage_key, @status, @verification_token_hash,
|
|
764
|
-
@verification_sent_at, null, @created_at, @updated_at
|
|
765
|
-
)
|
|
766
|
-
`).run({
|
|
767
|
-
id: record.id,
|
|
768
|
-
customer_id: record.customerId,
|
|
769
|
-
title: record.title,
|
|
770
|
-
email: record.email,
|
|
771
|
-
note: record.note ?? null,
|
|
772
|
-
avatar_url: record.avatarUrl ?? null,
|
|
773
|
-
avatar_storage_key: record.avatarStorageKey ?? null,
|
|
774
|
-
status: record.status,
|
|
775
|
-
verification_token_hash: record.verificationTokenHash ?? null,
|
|
776
|
-
verification_sent_at: record.verificationSentAt ?? null,
|
|
777
|
-
created_at: timestamp,
|
|
778
|
-
updated_at: timestamp
|
|
779
|
-
});
|
|
780
|
-
return this.getEmailChannel(record.customerId, record.id);
|
|
781
|
-
},
|
|
782
|
-
listEmailChannels(customerId) {
|
|
783
|
-
const rows = db.prepare(`
|
|
784
|
-
select *
|
|
785
|
-
from email_channels
|
|
786
|
-
where customer_id = ?
|
|
787
|
-
order by created_at desc
|
|
788
|
-
`).all(customerId);
|
|
789
|
-
return rows.map(mapEmailChannel);
|
|
790
|
-
},
|
|
791
|
-
getEmailChannel(customerId, channelId) {
|
|
792
|
-
const row = db.prepare(`
|
|
793
|
-
select *
|
|
794
|
-
from email_channels
|
|
795
|
-
where customer_id = ? and id = ?
|
|
796
|
-
limit 1
|
|
797
|
-
`).get(customerId, channelId);
|
|
798
|
-
return row ? mapEmailChannel(row) : null;
|
|
799
|
-
},
|
|
800
|
-
getEmailChannelByEmail(customerId, email) {
|
|
801
|
-
const row = db.prepare(`
|
|
802
|
-
select *
|
|
803
|
-
from email_channels
|
|
804
|
-
where customer_id = ? and lower(email) = lower(?)
|
|
805
|
-
limit 1
|
|
806
|
-
`).get(customerId, email);
|
|
807
|
-
return row ? mapEmailChannel(row) : null;
|
|
808
|
-
},
|
|
809
|
-
findEmailChannelByVerificationHash(tokenHash) {
|
|
810
|
-
const row = db.prepare(`
|
|
811
|
-
select *
|
|
812
|
-
from email_channels
|
|
813
|
-
where verification_token_hash = ?
|
|
814
|
-
limit 1
|
|
815
|
-
`).get(tokenHash);
|
|
816
|
-
return row ? mapEmailChannel(row) : null;
|
|
817
|
-
},
|
|
818
|
-
updateEmailChannel(input) {
|
|
819
|
-
db.prepare(`
|
|
820
|
-
update email_channels
|
|
821
|
-
set title = ?, note = ?, updated_at = ?
|
|
822
|
-
where customer_id = ? and id = ?
|
|
823
|
-
`).run(input.title, input.note ?? null, nowIso(), input.customerId, input.channelId);
|
|
824
|
-
return this.getEmailChannel(input.customerId, input.channelId);
|
|
825
|
-
},
|
|
826
|
-
updateEmailChannelAvatar(input) {
|
|
827
|
-
db.prepare(`
|
|
828
|
-
update email_channels
|
|
829
|
-
set avatar_url = ?, avatar_storage_key = ?, updated_at = ?
|
|
830
|
-
where customer_id = ? and id = ?
|
|
831
|
-
`).run(input.avatarUrl, input.avatarStorageKey, nowIso(), input.customerId, input.channelId);
|
|
832
|
-
return this.getEmailChannel(input.customerId, input.channelId);
|
|
833
|
-
},
|
|
834
|
-
updateEmailChannelVerification(input) {
|
|
835
|
-
db.prepare(`
|
|
836
|
-
update email_channels
|
|
837
|
-
set status = 'unverified',
|
|
838
|
-
verification_token_hash = ?,
|
|
839
|
-
verification_sent_at = ?,
|
|
840
|
-
verified_at = null,
|
|
841
|
-
updated_at = ?
|
|
842
|
-
where customer_id = ? and id = ?
|
|
843
|
-
`).run(input.tokenHash, input.sentAt, nowIso(), input.customerId, input.channelId);
|
|
844
|
-
return this.getEmailChannel(input.customerId, input.channelId);
|
|
845
|
-
},
|
|
846
|
-
verifyEmailChannelByTokenHash(tokenHash) {
|
|
847
|
-
const existing = this.findEmailChannelByVerificationHash(tokenHash);
|
|
848
|
-
if (!existing) {
|
|
849
|
-
return null;
|
|
850
|
-
}
|
|
851
|
-
const timestamp = nowIso();
|
|
852
|
-
db.prepare(`
|
|
853
|
-
update email_channels
|
|
854
|
-
set status = 'verified',
|
|
855
|
-
verification_token_hash = null,
|
|
856
|
-
verified_at = ?,
|
|
857
|
-
updated_at = ?
|
|
858
|
-
where id = ?
|
|
859
|
-
`).run(timestamp, timestamp, existing.id);
|
|
860
|
-
return this.getEmailChannel(existing.customerId, existing.id);
|
|
861
|
-
},
|
|
862
|
-
deleteEmailChannel(customerId, channelId) {
|
|
863
|
-
db.prepare(`delete from email_channels where customer_id = ? and id = ?`).run(customerId, channelId);
|
|
864
|
-
},
|
|
865
|
-
insertOtpChallenge(record) {
|
|
866
|
-
db.prepare(`
|
|
867
|
-
insert into otp_challenges (id, email, code_hash, expires_at, created_at)
|
|
868
|
-
values (@id, @email, @code_hash, @expires_at, @created_at)
|
|
869
|
-
`).run({
|
|
870
|
-
id: record.id,
|
|
871
|
-
email: record.email,
|
|
872
|
-
code_hash: record.codeHash,
|
|
873
|
-
expires_at: record.expiresAt,
|
|
874
|
-
created_at: nowIso()
|
|
875
|
-
});
|
|
876
|
-
},
|
|
877
|
-
findLatestOtpChallenge(email) {
|
|
878
|
-
return db.prepare(`
|
|
879
|
-
select * from otp_challenges
|
|
880
|
-
where lower(email) = lower(?)
|
|
881
|
-
order by created_at desc
|
|
882
|
-
limit 1
|
|
883
|
-
`).get(email);
|
|
884
|
-
},
|
|
885
|
-
consumeOtpChallenge(id) {
|
|
886
|
-
db.prepare(`update otp_challenges set consumed_at = ? where id = ?`).run(nowIso(), id);
|
|
887
|
-
},
|
|
888
|
-
insertApiKey(record) {
|
|
889
|
-
const timestamp = nowIso();
|
|
890
|
-
db.prepare(`
|
|
891
|
-
insert into api_keys (id, customer_id, key_hash, raw_value, label, status, created_at, updated_at)
|
|
892
|
-
values (@id, @customer_id, @key_hash, @raw_value, @label, 'active', @created_at, @updated_at)
|
|
893
|
-
`).run({
|
|
894
|
-
id: record.id,
|
|
895
|
-
customer_id: record.customerId,
|
|
896
|
-
key_hash: record.keyHash,
|
|
897
|
-
raw_value: record.rawValue ?? null,
|
|
898
|
-
label: record.label,
|
|
899
|
-
created_at: timestamp,
|
|
900
|
-
updated_at: timestamp
|
|
901
|
-
});
|
|
902
|
-
},
|
|
903
|
-
getLatestApiKeyForCustomer(customerId) {
|
|
904
|
-
return db.prepare(`
|
|
905
|
-
select id, raw_value, label, last_used_at, created_at, updated_at
|
|
906
|
-
from api_keys
|
|
907
|
-
where customer_id = ? and status = 'active'
|
|
908
|
-
order by case when raw_value is null or trim(raw_value) = '' then 1 else 0 end asc, created_at desc
|
|
909
|
-
limit 1
|
|
910
|
-
`).get(customerId);
|
|
911
|
-
},
|
|
912
|
-
findApiKeyByHash(keyHash) {
|
|
913
|
-
return db.prepare(`
|
|
914
|
-
select a.*, c.email, c.name, c.default_webhook_url, c.is_developer, c.is_paid_plan, c.plan_tier, c.about, c.groupchat_url, c.flockposter_api_key, c.created_at, c.updated_at
|
|
915
|
-
from api_keys a
|
|
916
|
-
join customers c on c.id = a.customer_id
|
|
917
|
-
where a.key_hash = ? and a.status = 'active'
|
|
918
|
-
`).get(keyHash);
|
|
919
|
-
},
|
|
920
|
-
touchApiKey(id) {
|
|
921
|
-
db.prepare(`update api_keys set last_used_at = ?, updated_at = ? where id = ?`).run(nowIso(), nowIso(), id);
|
|
922
|
-
},
|
|
923
|
-
upsertTemplateConfig(record) {
|
|
924
|
-
const timestamp = nowIso();
|
|
925
|
-
db.prepare(`
|
|
926
|
-
insert into template_configs (id, customer_id, template_id, config_json, created_at, updated_at)
|
|
927
|
-
values (@id, @customer_id, @template_id, @config_json, @created_at, @updated_at)
|
|
928
|
-
on conflict(customer_id, template_id) do update set
|
|
929
|
-
config_json = excluded.config_json,
|
|
930
|
-
updated_at = excluded.updated_at
|
|
931
|
-
`).run({
|
|
932
|
-
id: record.id,
|
|
933
|
-
customer_id: record.customerId,
|
|
934
|
-
template_id: record.templateId,
|
|
935
|
-
config_json: stringifyJson(record.config),
|
|
936
|
-
created_at: timestamp,
|
|
937
|
-
updated_at: timestamp
|
|
938
|
-
});
|
|
939
|
-
},
|
|
940
|
-
getTemplateConfig(customerId, templateId) {
|
|
941
|
-
const row = db.prepare(`
|
|
942
|
-
select config_json from template_configs
|
|
943
|
-
where customer_id = ? and template_id = ?
|
|
944
|
-
`).get(customerId, templateId);
|
|
945
|
-
return parseJson(row?.config_json, {});
|
|
946
|
-
},
|
|
947
|
-
createJob(record) {
|
|
948
|
-
const timestamp = nowIso();
|
|
949
|
-
db.prepare(`
|
|
950
|
-
insert into jobs (
|
|
951
|
-
id, template_id, operation_name, workflow_name, tracer, status, customer_id,
|
|
952
|
-
payload_json, result_json, error_json, progress, webhook_url, parent_job_id,
|
|
953
|
-
priority, attempt_count, max_attempts, run_after, created_at, updated_at, started_at, completed_at
|
|
954
|
-
)
|
|
955
|
-
values (
|
|
956
|
-
@id, @template_id, @operation_name, @workflow_name, @tracer, @status, @customer_id,
|
|
957
|
-
@payload_json, @result_json, @error_json, @progress, @webhook_url, @parent_job_id,
|
|
958
|
-
@priority, @attempt_count, @max_attempts, @run_after, @created_at, @updated_at, @started_at, @completed_at
|
|
959
|
-
)
|
|
960
|
-
`).run({
|
|
961
|
-
id: record.id,
|
|
962
|
-
template_id: record.templateId,
|
|
963
|
-
operation_name: record.operationName,
|
|
964
|
-
workflow_name: record.workflowName,
|
|
965
|
-
tracer: record.tracer,
|
|
966
|
-
status: record.status,
|
|
967
|
-
customer_id: record.customerId,
|
|
968
|
-
payload_json: stringifyJson(record.payload),
|
|
969
|
-
result_json: stringifyJson(record.result ?? null),
|
|
970
|
-
error_json: stringifyJson(record.error ?? null),
|
|
971
|
-
progress: record.progress,
|
|
972
|
-
webhook_url: record.webhookUrl,
|
|
973
|
-
parent_job_id: record.parentJobId,
|
|
974
|
-
priority: record.priority,
|
|
975
|
-
attempt_count: record.attemptCount,
|
|
976
|
-
max_attempts: record.maxAttempts,
|
|
977
|
-
run_after: record.runAfter,
|
|
978
|
-
created_at: timestamp,
|
|
979
|
-
updated_at: timestamp,
|
|
980
|
-
started_at: record.startedAt ?? null,
|
|
981
|
-
completed_at: record.completedAt ?? null
|
|
982
|
-
});
|
|
983
|
-
return this.getJob(record.id);
|
|
984
|
-
},
|
|
985
|
-
getJob(id) {
|
|
986
|
-
const row = db.prepare(`select * from jobs where id = ?`).get(id);
|
|
987
|
-
return row ? mapJob(row) : null;
|
|
988
|
-
},
|
|
989
|
-
getJobsByIds(ids) {
|
|
990
|
-
const uniqueIds = [...new Set(ids.map((id) => String(id).trim()).filter(Boolean))];
|
|
991
|
-
if (!uniqueIds.length) {
|
|
992
|
-
return [];
|
|
993
|
-
}
|
|
994
|
-
const rows = db.prepare(`
|
|
995
|
-
select * from jobs
|
|
996
|
-
where id in (${uniqueIds.map(() => "?").join(", ")})
|
|
997
|
-
`).all(...uniqueIds);
|
|
998
|
-
return rows.map(mapJob);
|
|
999
|
-
},
|
|
1000
|
-
countJobsByStatuses(input) {
|
|
1001
|
-
const statuses = input.statuses.length ? input.statuses : ["queued"];
|
|
1002
|
-
const filters = [`status in (${statuses.map(() => "?").join(", ")})`];
|
|
1003
|
-
const values = [...statuses];
|
|
1004
|
-
if (input.customerId) {
|
|
1005
|
-
filters.push("customer_id = ?");
|
|
1006
|
-
values.push(input.customerId);
|
|
1007
|
-
}
|
|
1008
|
-
const row = db.prepare(`
|
|
1009
|
-
select count(*) as count
|
|
1010
|
-
from jobs
|
|
1011
|
-
where ${filters.join(" and ")}
|
|
1012
|
-
`).get(...values);
|
|
1013
|
-
return Number(row.count);
|
|
1014
|
-
},
|
|
1015
|
-
listRunnableJobs(limit) {
|
|
1016
|
-
const rows = db.prepare(`
|
|
1017
|
-
select * from jobs
|
|
1018
|
-
where status = 'queued' and run_after <= ?
|
|
1019
|
-
order by priority asc, created_at asc
|
|
1020
|
-
limit ?
|
|
1021
|
-
`).all(nowIso(), limit);
|
|
1022
|
-
return rows.map(mapJob);
|
|
1023
|
-
},
|
|
1024
|
-
listAvailableProviderKeyCounts() {
|
|
1025
|
-
const rows = db.prepare(`
|
|
1026
|
-
select
|
|
1027
|
-
k.customer_id,
|
|
1028
|
-
k.provider,
|
|
1029
|
-
count(*) as available_count
|
|
1030
|
-
from customer_provider_keys k
|
|
1031
|
-
left join provider_key_leases l
|
|
1032
|
-
on l.key_id = k.id
|
|
1033
|
-
and l.expires_at > ?
|
|
1034
|
-
where k.status = 'active'
|
|
1035
|
-
and (k.cooldown_until is null or k.cooldown_until <= ?)
|
|
1036
|
-
and l.key_id is null
|
|
1037
|
-
group by k.customer_id, k.provider
|
|
1038
|
-
`).all(nowIso(), nowIso());
|
|
1039
|
-
return rows.map((row) => ({
|
|
1040
|
-
customerId: row.customer_id,
|
|
1041
|
-
provider: row.provider,
|
|
1042
|
-
availableCount: Number(row.available_count),
|
|
1043
|
-
}));
|
|
1044
|
-
},
|
|
1045
|
-
markJobRunning(id) {
|
|
1046
|
-
const timestamp = nowIso();
|
|
1047
|
-
db.prepare(`
|
|
1048
|
-
update jobs
|
|
1049
|
-
set status = 'running',
|
|
1050
|
-
started_at = coalesce(started_at, ?),
|
|
1051
|
-
error_json = null,
|
|
1052
|
-
completed_at = null,
|
|
1053
|
-
updated_at = ?,
|
|
1054
|
-
attempt_count = attempt_count + 1
|
|
1055
|
-
where id = ?
|
|
1056
|
-
`).run(timestamp, timestamp, id);
|
|
1057
|
-
},
|
|
1058
|
-
updateJobStatus(input) {
|
|
1059
|
-
const current = this.getJob(input.id);
|
|
1060
|
-
db.prepare(`
|
|
1061
|
-
update jobs
|
|
1062
|
-
set status = @status,
|
|
1063
|
-
progress = @progress,
|
|
1064
|
-
result_json = @result_json,
|
|
1065
|
-
error_json = @error_json,
|
|
1066
|
-
run_after = @run_after,
|
|
1067
|
-
completed_at = @completed_at,
|
|
1068
|
-
updated_at = @updated_at
|
|
1069
|
-
where id = @id
|
|
1070
|
-
`).run({
|
|
1071
|
-
id: input.id,
|
|
1072
|
-
status: input.status,
|
|
1073
|
-
progress: input.progress ?? current?.progress ?? 0,
|
|
1074
|
-
result_json: input.result === undefined ? stringifyJson(current?.result ?? null) : stringifyJson(input.result),
|
|
1075
|
-
error_json: input.error === undefined ? stringifyJson(current?.error ?? null) : stringifyJson(input.error),
|
|
1076
|
-
run_after: input.runAfter ?? current?.runAfter ?? nowIso(),
|
|
1077
|
-
completed_at: input.completedAt ?? current?.completedAt ?? null,
|
|
1078
|
-
updated_at: nowIso()
|
|
1079
|
-
});
|
|
1080
|
-
},
|
|
1081
|
-
listJobsForCustomer(input) {
|
|
1082
|
-
const filters = ["customer_id = ?"];
|
|
1083
|
-
const values = [input.customerId];
|
|
1084
|
-
if (input.templateId) {
|
|
1085
|
-
filters.push("template_id = ?");
|
|
1086
|
-
values.push(input.templateId);
|
|
1087
|
-
}
|
|
1088
|
-
if (input.tracer) {
|
|
1089
|
-
filters.push("tracer = ?");
|
|
1090
|
-
values.push(input.tracer);
|
|
1091
|
-
}
|
|
1092
|
-
if (input.startTime) {
|
|
1093
|
-
filters.push("created_at >= ?");
|
|
1094
|
-
values.push(input.startTime);
|
|
1095
|
-
}
|
|
1096
|
-
if (input.endTime) {
|
|
1097
|
-
filters.push("created_at <= ?");
|
|
1098
|
-
values.push(input.endTime);
|
|
1099
|
-
}
|
|
1100
|
-
if (input.cursor) {
|
|
1101
|
-
filters.push("(created_at < ? or (created_at = ? and id < ?))");
|
|
1102
|
-
values.push(input.cursor.createdAt, input.cursor.createdAt, input.cursor.id);
|
|
1103
|
-
}
|
|
1104
|
-
values.push(Math.max(1, Math.min(input.limit ?? 100, 501)));
|
|
1105
|
-
const rows = db.prepare(`
|
|
1106
|
-
select * from jobs
|
|
1107
|
-
where ${filters.join(" and ")}
|
|
1108
|
-
order by created_at desc, id desc
|
|
1109
|
-
limit ?
|
|
1110
|
-
`).all(...values);
|
|
1111
|
-
return rows.map(mapJob);
|
|
1112
|
-
},
|
|
1113
|
-
renameJobsTracer(input) {
|
|
1114
|
-
const fromTracer = input.fromTracer.trim();
|
|
1115
|
-
const toTracer = input.toTracer.trim();
|
|
1116
|
-
if (!fromTracer || !toTracer || fromTracer === toTracer) {
|
|
1117
|
-
return 0;
|
|
1118
|
-
}
|
|
1119
|
-
const filters = ["customer_id = @customer_id", "tracer = @from_tracer"];
|
|
1120
|
-
if (input.templateId) {
|
|
1121
|
-
filters.push("template_id = @template_id");
|
|
1122
|
-
}
|
|
1123
|
-
const result = db.prepare(`
|
|
1124
|
-
update jobs
|
|
1125
|
-
set tracer = @to_tracer,
|
|
1126
|
-
updated_at = @updated_at
|
|
1127
|
-
where ${filters.join(" and ")}
|
|
1128
|
-
`).run({
|
|
1129
|
-
customer_id: input.customerId,
|
|
1130
|
-
template_id: input.templateId ?? null,
|
|
1131
|
-
from_tracer: fromTracer,
|
|
1132
|
-
to_tracer: toTracer,
|
|
1133
|
-
updated_at: nowIso()
|
|
1134
|
-
});
|
|
1135
|
-
return Number(result.changes || 0);
|
|
1136
|
-
},
|
|
1137
|
-
insertArtifact(record) {
|
|
1138
|
-
db.prepare(`
|
|
1139
|
-
insert into artifacts (id, job_id, customer_id, template_id, kind, storage_key, public_url, metadata_json, created_at)
|
|
1140
|
-
values (@id, @job_id, @customer_id, @template_id, @kind, @storage_key, @public_url, @metadata_json, @created_at)
|
|
1141
|
-
`).run({
|
|
1142
|
-
id: record.id,
|
|
1143
|
-
job_id: record.jobId,
|
|
1144
|
-
customer_id: record.customerId,
|
|
1145
|
-
template_id: record.templateId,
|
|
1146
|
-
kind: record.kind,
|
|
1147
|
-
storage_key: record.storageKey,
|
|
1148
|
-
public_url: record.publicUrl ?? null,
|
|
1149
|
-
metadata_json: stringifyJson(record.metadata ?? {}),
|
|
1150
|
-
created_at: nowIso()
|
|
1151
|
-
});
|
|
1152
|
-
},
|
|
1153
|
-
listArtifactsForJob(input) {
|
|
1154
|
-
const filters = ["job_id = ?", "customer_id = ?"];
|
|
1155
|
-
const values = [input.jobId, input.customerId];
|
|
1156
|
-
if (input.templateId) {
|
|
1157
|
-
filters.push("template_id = ?");
|
|
1158
|
-
values.push(input.templateId);
|
|
1159
|
-
}
|
|
1160
|
-
if (input.cursor) {
|
|
1161
|
-
filters.push("(created_at < ? or (created_at = ? and id < ?))");
|
|
1162
|
-
values.push(input.cursor.createdAt, input.cursor.createdAt, input.cursor.id);
|
|
1163
|
-
}
|
|
1164
|
-
values.push(Math.max(1, Math.min(input.limit ?? 100, 501)));
|
|
1165
|
-
const rows = db.prepare(`
|
|
1166
|
-
select *
|
|
1167
|
-
from artifacts
|
|
1168
|
-
where ${filters.join(" and ")}
|
|
1169
|
-
order by created_at desc, id desc
|
|
1170
|
-
limit ?
|
|
1171
|
-
`).all(...values);
|
|
1172
|
-
return rows.map(mapArtifact);
|
|
1173
|
-
},
|
|
1174
|
-
queueWebhookDelivery(record) {
|
|
1175
|
-
const timestamp = nowIso();
|
|
1176
|
-
db.prepare(`
|
|
1177
|
-
insert into webhook_deliveries (
|
|
1178
|
-
id, job_id, customer_id, event_type, destination_url, payload_json, status,
|
|
1179
|
-
attempt_count, next_attempt_at, created_at, updated_at
|
|
1180
|
-
) values (
|
|
1181
|
-
@id, @job_id, @customer_id, @event_type, @destination_url, @payload_json, 'pending',
|
|
1182
|
-
0, @next_attempt_at, @created_at, @updated_at
|
|
1183
|
-
)
|
|
1184
|
-
`).run({
|
|
1185
|
-
id: record.id,
|
|
1186
|
-
job_id: record.jobId,
|
|
1187
|
-
customer_id: record.customerId,
|
|
1188
|
-
event_type: record.eventType,
|
|
1189
|
-
destination_url: record.destinationUrl,
|
|
1190
|
-
payload_json: stringifyJson(record.payload),
|
|
1191
|
-
next_attempt_at: timestamp,
|
|
1192
|
-
created_at: timestamp,
|
|
1193
|
-
updated_at: timestamp
|
|
1194
|
-
});
|
|
1195
|
-
},
|
|
1196
|
-
listPendingWebhookDeliveries(limit) {
|
|
1197
|
-
return db.prepare(`
|
|
1198
|
-
select * from webhook_deliveries
|
|
1199
|
-
where status in ('pending', 'retrying') and next_attempt_at <= ?
|
|
1200
|
-
order by next_attempt_at asc
|
|
1201
|
-
limit ?
|
|
1202
|
-
`).all(nowIso(), limit);
|
|
1203
|
-
},
|
|
1204
|
-
markWebhookDelivery(record) {
|
|
1205
|
-
db.prepare(`
|
|
1206
|
-
update webhook_deliveries
|
|
1207
|
-
set status = @status,
|
|
1208
|
-
attempt_count = @attempt_count,
|
|
1209
|
-
next_attempt_at = coalesce(@next_attempt_at, next_attempt_at),
|
|
1210
|
-
last_error = @last_error,
|
|
1211
|
-
updated_at = @updated_at
|
|
1212
|
-
where id = @id
|
|
1213
|
-
`).run({
|
|
1214
|
-
id: record.id,
|
|
1215
|
-
status: record.status,
|
|
1216
|
-
attempt_count: record.attemptCount,
|
|
1217
|
-
next_attempt_at: record.nextAttemptAt ?? null,
|
|
1218
|
-
last_error: record.lastError ?? null,
|
|
1219
|
-
updated_at: nowIso()
|
|
1220
|
-
});
|
|
1221
|
-
},
|
|
1222
|
-
acquireProviderKeyLease(input) {
|
|
1223
|
-
const transaction = db.transaction(() => {
|
|
1224
|
-
db.prepare(`delete from provider_key_leases where expires_at <= ?`).run(nowIso());
|
|
1225
|
-
db.prepare(`delete from provider_key_waiters where expires_at <= ?`).run(nowIso());
|
|
1226
|
-
db.prepare(`
|
|
1227
|
-
insert into provider_key_waiters (id, customer_id, provider, worker_id, owner_id, priority, created_at, expires_at)
|
|
1228
|
-
values (?, ?, ?, ?, ?, ?, coalesce((select created_at from provider_key_waiters where id = ?), ?), ?)
|
|
1229
|
-
on conflict(id) do update set
|
|
1230
|
-
worker_id = excluded.worker_id,
|
|
1231
|
-
owner_id = excluded.owner_id,
|
|
1232
|
-
priority = excluded.priority,
|
|
1233
|
-
expires_at = excluded.expires_at
|
|
1234
|
-
`).run(input.waiterId, input.customerId, input.provider, input.workerId, input.jobId, input.priority, input.waiterId, nowIso(), input.waiterExpiresAt);
|
|
1235
|
-
const nextWaiter = db.prepare(`
|
|
1236
|
-
select id
|
|
1237
|
-
from provider_key_waiters
|
|
1238
|
-
where customer_id = ?
|
|
1239
|
-
and provider = ?
|
|
1240
|
-
and expires_at > ?
|
|
1241
|
-
order by priority asc, created_at asc
|
|
1242
|
-
limit 1
|
|
1243
|
-
`).get(input.customerId, input.provider, nowIso());
|
|
1244
|
-
if (!nextWaiter || nextWaiter.id !== input.waiterId) {
|
|
1245
|
-
return null;
|
|
1246
|
-
}
|
|
1247
|
-
const row = db.prepare(`
|
|
1248
|
-
select k.id, k.encrypted_secret
|
|
1249
|
-
from customer_provider_keys k
|
|
1250
|
-
left join provider_key_leases l on l.key_id = k.id and l.expires_at > ?
|
|
1251
|
-
where k.customer_id = ?
|
|
1252
|
-
and k.provider = ?
|
|
1253
|
-
and k.status = 'active'
|
|
1254
|
-
and (k.cooldown_until is null or k.cooldown_until <= ?)
|
|
1255
|
-
and l.key_id is null
|
|
1256
|
-
order by case when k.last_used_at is null then 0 else 1 end asc, k.last_used_at asc
|
|
1257
|
-
limit 1
|
|
1258
|
-
`).get(nowIso(), input.customerId, input.provider, nowIso());
|
|
1259
|
-
if (!row) {
|
|
1260
|
-
return null;
|
|
1261
|
-
}
|
|
1262
|
-
db.prepare(`
|
|
1263
|
-
insert into provider_key_leases (key_id, lease_token, worker_id, job_id, leased_at, expires_at)
|
|
1264
|
-
values (?, ?, ?, ?, ?, ?)
|
|
1265
|
-
`).run(row.id, input.leaseToken, input.workerId, input.jobId, nowIso(), input.expiresAt);
|
|
1266
|
-
db.prepare(`delete from provider_key_waiters where id = ?`).run(input.waiterId);
|
|
1267
|
-
return { keyId: row.id, encryptedSecret: row.encrypted_secret };
|
|
1268
|
-
});
|
|
1269
|
-
return transaction();
|
|
1270
|
-
},
|
|
1271
|
-
deleteProviderKeyWaiter(waiterId) {
|
|
1272
|
-
db.prepare(`delete from provider_key_waiters where id = ?`).run(waiterId);
|
|
1273
|
-
},
|
|
1274
|
-
releaseProviderKeyLease(input) {
|
|
1275
|
-
db.prepare(`delete from provider_key_leases where key_id = ? and lease_token = ?`).run(input.keyId, input.leaseToken);
|
|
1276
|
-
},
|
|
1277
|
-
recordProviderKeyUsage(input) {
|
|
1278
|
-
db.prepare(`
|
|
1279
|
-
insert into provider_key_usage_events (
|
|
1280
|
-
id, key_id, job_id, provider, model, event_type, input_tokens, output_tokens, cost_usd, metadata, created_at
|
|
1281
|
-
) values (
|
|
1282
|
-
@id, @key_id, @job_id, @provider, @model, @event_type, @input_tokens, @output_tokens, @cost_usd, @metadata, @created_at
|
|
1283
|
-
)
|
|
1284
|
-
`).run({
|
|
1285
|
-
id: input.id,
|
|
1286
|
-
key_id: input.keyId,
|
|
1287
|
-
job_id: input.jobId,
|
|
1288
|
-
provider: input.provider,
|
|
1289
|
-
model: input.model ?? null,
|
|
1290
|
-
event_type: input.eventType,
|
|
1291
|
-
input_tokens: input.inputTokens ?? null,
|
|
1292
|
-
output_tokens: input.outputTokens ?? null,
|
|
1293
|
-
cost_usd: input.costUsd ?? null,
|
|
1294
|
-
metadata: stringifyJson(input.metadata ?? {}),
|
|
1295
|
-
created_at: nowIso()
|
|
1296
|
-
});
|
|
1297
|
-
},
|
|
1298
|
-
touchProviderKey(keyId) {
|
|
1299
|
-
db.prepare(`update customer_provider_keys set last_used_at = ?, updated_at = ? where id = ?`).run(nowIso(), nowIso(), keyId);
|
|
1300
|
-
},
|
|
1301
|
-
setProviderKeyCooldown(keyId, cooldownUntil, status = "active", disabledReason = null) {
|
|
1302
|
-
db.prepare(`
|
|
1303
|
-
update customer_provider_keys
|
|
1304
|
-
set cooldown_until = ?, status = ?, disabled_reason = ?, updated_at = ?
|
|
1305
|
-
where id = ?
|
|
1306
|
-
`).run(cooldownUntil, status, disabledReason, nowIso(), keyId);
|
|
1307
|
-
},
|
|
1308
|
-
createProviderKey(record) {
|
|
1309
|
-
const timestamp = nowIso();
|
|
1310
|
-
db.prepare(`
|
|
1311
|
-
insert into customer_provider_keys (
|
|
1312
|
-
id, customer_id, provider, label, encrypted_secret, status, created_at, updated_at
|
|
1313
|
-
) values (
|
|
1314
|
-
@id, @customer_id, @provider, @label, @encrypted_secret, 'active', @created_at, @updated_at
|
|
1315
|
-
)
|
|
1316
|
-
`).run({
|
|
1317
|
-
id: record.id,
|
|
1318
|
-
customer_id: record.customerId,
|
|
1319
|
-
provider: record.provider,
|
|
1320
|
-
label: record.label,
|
|
1321
|
-
encrypted_secret: record.encryptedSecret,
|
|
1322
|
-
created_at: timestamp,
|
|
1323
|
-
updated_at: timestamp
|
|
1324
|
-
});
|
|
1325
|
-
},
|
|
1326
|
-
createUserAttachment(record) {
|
|
1327
|
-
db.prepare(`
|
|
1328
|
-
insert into user_attachments (
|
|
1329
|
-
id, customer_id, file_name, content_type, size_bytes, storage_key, public_url, created_at
|
|
1330
|
-
) values (
|
|
1331
|
-
@id, @customer_id, @file_name, @content_type, @size_bytes, @storage_key, @public_url, @created_at
|
|
1332
|
-
)
|
|
1333
|
-
`).run({
|
|
1334
|
-
id: record.id,
|
|
1335
|
-
customer_id: record.customerId,
|
|
1336
|
-
file_name: record.fileName,
|
|
1337
|
-
content_type: record.contentType,
|
|
1338
|
-
size_bytes: record.sizeBytes,
|
|
1339
|
-
storage_key: record.storageKey,
|
|
1340
|
-
public_url: record.publicUrl ?? null,
|
|
1341
|
-
created_at: nowIso()
|
|
1342
|
-
});
|
|
1343
|
-
},
|
|
1344
|
-
listUserAttachments(customerId) {
|
|
1345
|
-
const rows = db.prepare(`
|
|
1346
|
-
select *
|
|
1347
|
-
from user_attachments
|
|
1348
|
-
where customer_id = ?
|
|
1349
|
-
order by created_at desc
|
|
1350
|
-
`).all(customerId);
|
|
1351
|
-
return rows.map(mapUserAttachment);
|
|
1352
|
-
},
|
|
1353
|
-
getUserAttachment(customerId, attachmentId) {
|
|
1354
|
-
const row = db.prepare(`
|
|
1355
|
-
select *
|
|
1356
|
-
from user_attachments
|
|
1357
|
-
where customer_id = ? and id = ?
|
|
1358
|
-
limit 1
|
|
1359
|
-
`).get(customerId, attachmentId);
|
|
1360
|
-
return row ? mapUserAttachment(row) : null;
|
|
1361
|
-
},
|
|
1362
|
-
deleteUserAttachment(customerId, attachmentId) {
|
|
1363
|
-
db.prepare(`delete from user_attachments where customer_id = ? and id = ?`).run(customerId, attachmentId);
|
|
1364
|
-
},
|
|
1365
|
-
createReadyPost(record) {
|
|
1366
|
-
const timestamp = nowIso();
|
|
1367
|
-
db.prepare(`
|
|
1368
|
-
insert into ready_posts (
|
|
1369
|
-
id, customer_id, status, tracer, title, content, pinned_comment, scheduled_at, timezone, post_type,
|
|
1370
|
-
integration_ids_json, platform_targets_json, media_assets_json, renditions_json,
|
|
1371
|
-
flockposter_payload_json, field_values_json, share_password_hash, zip_storage_key,
|
|
1372
|
-
zip_public_url, archived_at, deleted_at, created_at, updated_at
|
|
1373
|
-
) values (
|
|
1374
|
-
@id, @customer_id, @status, @tracer, @title, @content, @pinned_comment, @scheduled_at, @timezone, @post_type,
|
|
1375
|
-
@integration_ids_json, @platform_targets_json, @media_assets_json, @renditions_json,
|
|
1376
|
-
@flockposter_payload_json, @field_values_json, @share_password_hash, @zip_storage_key,
|
|
1377
|
-
@zip_public_url, @archived_at, @deleted_at, @created_at, @updated_at
|
|
1378
|
-
)
|
|
1379
|
-
`).run({
|
|
1380
|
-
id: record.id,
|
|
1381
|
-
customer_id: record.customerId,
|
|
1382
|
-
status: record.status,
|
|
1383
|
-
tracer: record.tracer,
|
|
1384
|
-
title: record.title,
|
|
1385
|
-
content: record.caption,
|
|
1386
|
-
pinned_comment: record.pinnedComment,
|
|
1387
|
-
scheduled_at: null,
|
|
1388
|
-
timezone: null,
|
|
1389
|
-
post_type: "draft",
|
|
1390
|
-
integration_ids_json: stringifyJson([]),
|
|
1391
|
-
platform_targets_json: stringifyJson([]),
|
|
1392
|
-
media_assets_json: stringifyJson(record.mediaAssets),
|
|
1393
|
-
renditions_json: stringifyJson([]),
|
|
1394
|
-
flockposter_payload_json: stringifyJson({}),
|
|
1395
|
-
field_values_json: stringifyJson({}),
|
|
1396
|
-
share_password_hash: record.sharePasswordHash,
|
|
1397
|
-
zip_storage_key: record.zipStorageKey ?? null,
|
|
1398
|
-
zip_public_url: record.zipPublicUrl ?? null,
|
|
1399
|
-
archived_at: record.archivedAt ?? null,
|
|
1400
|
-
deleted_at: record.deletedAt ?? null,
|
|
1401
|
-
created_at: timestamp,
|
|
1402
|
-
updated_at: timestamp
|
|
1403
|
-
});
|
|
1404
|
-
return this.getReadyPost(record.id);
|
|
1405
|
-
},
|
|
1406
|
-
getReadyPost(id) {
|
|
1407
|
-
const row = db.prepare(`select * from ready_posts where id = ? limit 1`).get(id);
|
|
1408
|
-
return row ? mapReadyPost(row) : null;
|
|
1409
|
-
},
|
|
1410
|
-
listReadyPosts(customerId, input) {
|
|
1411
|
-
const filters = ["customer_id = ?", "status != 'deleted'"];
|
|
1412
|
-
const values = [customerId];
|
|
1413
|
-
if (input?.cursor) {
|
|
1414
|
-
filters.push("(created_at < ? or (created_at = ? and id < ?))");
|
|
1415
|
-
values.push(input.cursor.createdAt, input.cursor.createdAt, input.cursor.id);
|
|
1416
|
-
}
|
|
1417
|
-
const rows = db.prepare(`
|
|
1418
|
-
select *
|
|
1419
|
-
from ready_posts
|
|
1420
|
-
where ${filters.join(" and ")}
|
|
1421
|
-
order by created_at desc, id desc
|
|
1422
|
-
${input?.limit || input?.cursor ? "limit ?" : ""}
|
|
1423
|
-
`).all(...(input?.limit || input?.cursor ? [...values, Math.max(1, Math.min(input?.limit ?? 100, 501))] : values));
|
|
1424
|
-
return rows.map(mapReadyPost);
|
|
1425
|
-
},
|
|
1426
|
-
archiveReadyPost(customerId, postId) {
|
|
1427
|
-
const timestamp = nowIso();
|
|
1428
|
-
db.prepare(`
|
|
1429
|
-
update ready_posts
|
|
1430
|
-
set status = 'archived', archived_at = coalesce(archived_at, ?), updated_at = ?
|
|
1431
|
-
where id = ? and customer_id = ? and status != 'deleted'
|
|
1432
|
-
`).run(timestamp, timestamp, postId, customerId);
|
|
1433
|
-
return this.getReadyPost(postId);
|
|
1434
|
-
},
|
|
1435
|
-
deleteReadyPost(customerId, postId) {
|
|
1436
|
-
const timestamp = nowIso();
|
|
1437
|
-
db.prepare(`
|
|
1438
|
-
update ready_posts
|
|
1439
|
-
set status = 'deleted', deleted_at = coalesce(deleted_at, ?), updated_at = ?
|
|
1440
|
-
where id = ? and customer_id = ?
|
|
1441
|
-
`).run(timestamp, timestamp, postId, customerId);
|
|
1442
|
-
return this.getReadyPost(postId);
|
|
1443
|
-
},
|
|
1444
|
-
setReadyPostZip(input) {
|
|
1445
|
-
db.prepare(`
|
|
1446
|
-
update ready_posts
|
|
1447
|
-
set zip_storage_key = ?, zip_public_url = ?, updated_at = ?
|
|
1448
|
-
where id = ? and customer_id = ? and status != 'deleted'
|
|
1449
|
-
`).run(input.storageKey, input.publicUrl, nowIso(), input.postId, input.customerId);
|
|
1450
|
-
return this.getReadyPost(input.postId);
|
|
1451
|
-
},
|
|
1452
|
-
createPostSchedule(record) {
|
|
1453
|
-
const timestamp = nowIso();
|
|
1454
|
-
db.prepare(`
|
|
1455
|
-
insert into post_schedules (
|
|
1456
|
-
id, customer_id, ready_post_id, managed_by, provider, destination_type, destination_id,
|
|
1457
|
-
scheduled_at, timezone, status, provider_status, provider_schedule_id, title, summary, url,
|
|
1458
|
-
additional_notes, provider_payload_json, provider_response_json, last_synced_at, last_error, cancelled_at, sent_at,
|
|
1459
|
-
created_at, updated_at
|
|
1460
|
-
) values (
|
|
1461
|
-
@id, @customer_id, @ready_post_id, @managed_by, @provider, @destination_type, @destination_id,
|
|
1462
|
-
@scheduled_at, @timezone, @status, @provider_status, @provider_schedule_id, @title, @summary, @url,
|
|
1463
|
-
@additional_notes, @provider_payload_json, @provider_response_json, @last_synced_at, @last_error, @cancelled_at, @sent_at,
|
|
1464
|
-
@created_at, @updated_at
|
|
1465
|
-
)
|
|
1466
|
-
`).run({
|
|
1467
|
-
id: record.id,
|
|
1468
|
-
customer_id: record.customerId,
|
|
1469
|
-
ready_post_id: record.readyPostId,
|
|
1470
|
-
managed_by: record.managedBy,
|
|
1471
|
-
provider: record.provider,
|
|
1472
|
-
destination_type: record.destinationType,
|
|
1473
|
-
destination_id: record.destinationId,
|
|
1474
|
-
scheduled_at: record.scheduledAt,
|
|
1475
|
-
timezone: record.timezone,
|
|
1476
|
-
status: record.status,
|
|
1477
|
-
provider_status: record.providerStatus,
|
|
1478
|
-
provider_schedule_id: record.providerScheduleId,
|
|
1479
|
-
title: record.title,
|
|
1480
|
-
summary: record.summary,
|
|
1481
|
-
url: record.url,
|
|
1482
|
-
additional_notes: record.additionalNotes,
|
|
1483
|
-
provider_payload_json: stringifyJson(record.providerPayload),
|
|
1484
|
-
provider_response_json: stringifyJson(record.providerResponse),
|
|
1485
|
-
last_synced_at: record.lastSyncedAt,
|
|
1486
|
-
last_error: record.lastError,
|
|
1487
|
-
cancelled_at: record.cancelledAt,
|
|
1488
|
-
sent_at: record.sentAt,
|
|
1489
|
-
created_at: timestamp,
|
|
1490
|
-
updated_at: timestamp
|
|
1491
|
-
});
|
|
1492
|
-
if (record.readyPostId) {
|
|
1493
|
-
this.refreshReadyPostScheduleStatus(record.customerId, record.readyPostId);
|
|
1494
|
-
}
|
|
1495
|
-
return this.getPostSchedule(record.customerId, record.id);
|
|
1496
|
-
},
|
|
1497
|
-
getPostSchedule(customerId, scheduleId) {
|
|
1498
|
-
const row = db.prepare(`
|
|
1499
|
-
select *
|
|
1500
|
-
from post_schedules
|
|
1501
|
-
where customer_id = ? and id = ?
|
|
1502
|
-
limit 1
|
|
1503
|
-
`).get(customerId, scheduleId);
|
|
1504
|
-
return row ? mapPostSchedule(row) : null;
|
|
1505
|
-
},
|
|
1506
|
-
listPostSchedulesForPost(customerId, postId) {
|
|
1507
|
-
const rows = db.prepare(`
|
|
1508
|
-
select *
|
|
1509
|
-
from post_schedules
|
|
1510
|
-
where customer_id = ? and ready_post_id = ?
|
|
1511
|
-
order by scheduled_at asc, created_at asc
|
|
1512
|
-
`).all(customerId, postId);
|
|
1513
|
-
return rows.map(mapPostSchedule);
|
|
1514
|
-
},
|
|
1515
|
-
listPostSchedulesForCalendar(input) {
|
|
1516
|
-
const rows = db.prepare(`
|
|
1517
|
-
select *
|
|
1518
|
-
from post_schedules
|
|
1519
|
-
where customer_id = ?
|
|
1520
|
-
and scheduled_at >= ?
|
|
1521
|
-
and scheduled_at <= ?
|
|
1522
|
-
and status != 'cancelled'
|
|
1523
|
-
order by scheduled_at asc, created_at asc
|
|
1524
|
-
`).all(input.customerId, input.startDate, input.endDate);
|
|
1525
|
-
return rows.map(mapPostSchedule);
|
|
1526
|
-
},
|
|
1527
|
-
updatePostScheduleProvider(input) {
|
|
1528
|
-
db.prepare(`
|
|
1529
|
-
update post_schedules
|
|
1530
|
-
set scheduled_at = ?,
|
|
1531
|
-
timezone = ?,
|
|
1532
|
-
status = ?,
|
|
1533
|
-
provider_status = ?,
|
|
1534
|
-
provider_schedule_id = ?,
|
|
1535
|
-
provider_payload_json = ?,
|
|
1536
|
-
provider_response_json = ?,
|
|
1537
|
-
last_synced_at = ?,
|
|
1538
|
-
last_error = ?,
|
|
1539
|
-
url = coalesce(?, url),
|
|
1540
|
-
additional_notes = ?,
|
|
1541
|
-
cancelled_at = null,
|
|
1542
|
-
updated_at = ?
|
|
1543
|
-
where customer_id = ? and id = ?
|
|
1544
|
-
`).run(input.scheduledAt, input.timezone ?? null, input.status ?? "scheduled", input.providerStatus ?? null, input.providerScheduleId ?? null, stringifyJson(input.providerPayload), stringifyJson(input.providerResponse), nowIso(), input.lastError ?? null, input.url ?? null, input.additionalNotes ?? null, nowIso(), input.customerId, input.scheduleId);
|
|
1545
|
-
const updated = this.getPostSchedule(input.customerId, input.scheduleId);
|
|
1546
|
-
if (updated?.readyPostId) {
|
|
1547
|
-
this.refreshReadyPostScheduleStatus(input.customerId, updated.readyPostId);
|
|
1548
|
-
}
|
|
1549
|
-
return updated;
|
|
1550
|
-
},
|
|
1551
|
-
markPostScheduleCancelled(input) {
|
|
1552
|
-
const timestamp = nowIso();
|
|
1553
|
-
const existing = this.getPostSchedule(input.customerId, input.scheduleId);
|
|
1554
|
-
db.prepare(`
|
|
1555
|
-
update post_schedules
|
|
1556
|
-
set status = 'cancelled',
|
|
1557
|
-
provider_response_json = ?,
|
|
1558
|
-
last_synced_at = ?,
|
|
1559
|
-
last_error = ?,
|
|
1560
|
-
cancelled_at = coalesce(cancelled_at, ?),
|
|
1561
|
-
updated_at = ?
|
|
1562
|
-
where customer_id = ? and id = ?
|
|
1563
|
-
`).run(stringifyJson(input.providerResponse ?? existing?.providerResponse ?? {}), timestamp, input.lastError ?? null, timestamp, timestamp, input.customerId, input.scheduleId);
|
|
1564
|
-
if (existing?.readyPostId) {
|
|
1565
|
-
this.refreshReadyPostScheduleStatus(input.customerId, existing.readyPostId);
|
|
1566
|
-
}
|
|
1567
|
-
return this.getPostSchedule(input.customerId, input.scheduleId);
|
|
1568
|
-
},
|
|
1569
|
-
markPostScheduleFailed(input) {
|
|
1570
|
-
const existing = this.getPostSchedule(input.customerId, input.scheduleId);
|
|
1571
|
-
db.prepare(`
|
|
1572
|
-
update post_schedules
|
|
1573
|
-
set status = 'failed',
|
|
1574
|
-
provider_response_json = ?,
|
|
1575
|
-
last_synced_at = ?,
|
|
1576
|
-
last_error = ?,
|
|
1577
|
-
updated_at = ?
|
|
1578
|
-
where customer_id = ? and id = ?
|
|
1579
|
-
`).run(stringifyJson(input.providerResponse ?? existing?.providerResponse ?? {}), nowIso(), input.lastError, nowIso(), input.customerId, input.scheduleId);
|
|
1580
|
-
if (existing?.readyPostId) {
|
|
1581
|
-
this.refreshReadyPostScheduleStatus(input.customerId, existing.readyPostId);
|
|
1582
|
-
}
|
|
1583
|
-
return this.getPostSchedule(input.customerId, input.scheduleId);
|
|
1584
|
-
},
|
|
1585
|
-
upsertExternalPostSchedule(record) {
|
|
1586
|
-
const timestamp = nowIso();
|
|
1587
|
-
const existing = record.providerScheduleId
|
|
1588
|
-
? db.prepare(`
|
|
1589
|
-
select *
|
|
1590
|
-
from post_schedules
|
|
1591
|
-
where customer_id = ? and provider = ? and provider_schedule_id = ?
|
|
1592
|
-
limit 1
|
|
1593
|
-
`).get(record.customerId, record.provider, record.providerScheduleId)
|
|
1594
|
-
: undefined;
|
|
1595
|
-
if (existing) {
|
|
1596
|
-
db.prepare(`
|
|
1597
|
-
update post_schedules
|
|
1598
|
-
set destination_id = ?,
|
|
1599
|
-
scheduled_at = ?,
|
|
1600
|
-
status = ?,
|
|
1601
|
-
provider_status = ?,
|
|
1602
|
-
title = ?,
|
|
1603
|
-
summary = ?,
|
|
1604
|
-
url = ?,
|
|
1605
|
-
additional_notes = ?,
|
|
1606
|
-
provider_payload_json = ?,
|
|
1607
|
-
provider_response_json = ?,
|
|
1608
|
-
last_synced_at = ?,
|
|
1609
|
-
last_error = null,
|
|
1610
|
-
updated_at = ?
|
|
1611
|
-
where id = ?
|
|
1612
|
-
`).run(record.destinationId, record.scheduledAt, record.status, record.providerStatus, record.title, record.summary, record.url, record.additionalNotes ?? null, stringifyJson(record.providerPayload), stringifyJson(record.providerResponse), timestamp, timestamp, String(existing.id));
|
|
1613
|
-
return this.getPostSchedule(record.customerId, String(existing.id));
|
|
1614
|
-
}
|
|
1615
|
-
return this.createPostSchedule({
|
|
1616
|
-
...record,
|
|
1617
|
-
readyPostId: null,
|
|
1618
|
-
managedBy: "external",
|
|
1619
|
-
timezone: null,
|
|
1620
|
-
additionalNotes: record.additionalNotes ?? null,
|
|
1621
|
-
cancelledAt: null,
|
|
1622
|
-
sentAt: null,
|
|
1623
|
-
lastError: null
|
|
1624
|
-
});
|
|
1625
|
-
},
|
|
1626
|
-
refreshReadyPostScheduleStatus(customerId, postId) {
|
|
1627
|
-
const row = db.prepare(`
|
|
1628
|
-
select count(*) as count
|
|
1629
|
-
from post_schedules
|
|
1630
|
-
where customer_id = ? and ready_post_id = ? and status = 'scheduled'
|
|
1631
|
-
`).get(customerId, postId);
|
|
1632
|
-
const nextStatus = row && Number(row.count) > 0 ? "scheduled" : "ready";
|
|
1633
|
-
db.prepare(`
|
|
1634
|
-
update ready_posts
|
|
1635
|
-
set status = ?, updated_at = ?
|
|
1636
|
-
where customer_id = ? and id = ? and status in ('ready', 'scheduled')
|
|
1637
|
-
`).run(nextStatus, nowIso(), customerId, postId);
|
|
1638
|
-
return this.getReadyPost(postId);
|
|
1639
|
-
},
|
|
1640
|
-
createTemplateSource(record) {
|
|
1641
|
-
const timestamp = nowIso();
|
|
1642
|
-
db.prepare(`
|
|
1643
|
-
insert into template_sources (
|
|
1644
|
-
id, template_id, slug_id, repo_url, branch, template_module_path, skill_path, install_command, build_command, status, created_at, updated_at
|
|
1645
|
-
) values (
|
|
1646
|
-
@id, @template_id, @slug_id, @repo_url, @branch, @template_module_path, @skill_path, @install_command, @build_command, @status, @created_at, @updated_at
|
|
1647
|
-
)
|
|
1648
|
-
`).run({
|
|
1649
|
-
id: record.id,
|
|
1650
|
-
template_id: record.templateId,
|
|
1651
|
-
slug_id: record.slugId,
|
|
1652
|
-
repo_url: record.repoUrl,
|
|
1653
|
-
branch: record.branch,
|
|
1654
|
-
template_module_path: record.templateModulePath,
|
|
1655
|
-
skill_path: record.skillPath,
|
|
1656
|
-
install_command: record.installCommand,
|
|
1657
|
-
build_command: record.buildCommand,
|
|
1658
|
-
status: record.status,
|
|
1659
|
-
created_at: timestamp,
|
|
1660
|
-
updated_at: timestamp
|
|
1661
|
-
});
|
|
1662
|
-
return this.getTemplateSourceByTemplateId(record.templateId);
|
|
1663
|
-
},
|
|
1664
|
-
updateTemplateSource(input) {
|
|
1665
|
-
const current = this.getTemplateSource(input.id);
|
|
1666
|
-
if (!current) {
|
|
1667
|
-
throw new Error("Template source not found.");
|
|
1668
|
-
}
|
|
1669
|
-
db.prepare(`
|
|
1670
|
-
update template_sources
|
|
1671
|
-
set repo_url = @repo_url,
|
|
1672
|
-
branch = @branch,
|
|
1673
|
-
template_module_path = @template_module_path,
|
|
1674
|
-
skill_path = @skill_path,
|
|
1675
|
-
install_command = @install_command,
|
|
1676
|
-
build_command = @build_command,
|
|
1677
|
-
status = @status,
|
|
1678
|
-
updated_at = @updated_at
|
|
1679
|
-
where id = @id
|
|
1680
|
-
`).run({
|
|
1681
|
-
id: input.id,
|
|
1682
|
-
repo_url: input.repoUrl,
|
|
1683
|
-
branch: input.branch,
|
|
1684
|
-
template_module_path: input.templateModulePath,
|
|
1685
|
-
skill_path: input.skillPath,
|
|
1686
|
-
install_command: input.installCommand,
|
|
1687
|
-
build_command: input.buildCommand,
|
|
1688
|
-
status: input.status ?? current.status,
|
|
1689
|
-
updated_at: nowIso()
|
|
1690
|
-
});
|
|
1691
|
-
return this.getTemplateSource(input.id);
|
|
1692
|
-
},
|
|
1693
|
-
getTemplateSource(id) {
|
|
1694
|
-
const row = db.prepare(`select * from template_sources where id = ?`).get(id);
|
|
1695
|
-
return row ? mapTemplateSource(row) : null;
|
|
1696
|
-
},
|
|
1697
|
-
getTemplateSourceByTemplateId(templateId) {
|
|
1698
|
-
const row = db.prepare(`select * from template_sources where template_id = ?`).get(templateId);
|
|
1699
|
-
return row ? mapTemplateSource(row) : null;
|
|
1700
|
-
},
|
|
1701
|
-
getTemplateSourceBySlugId(slugId) {
|
|
1702
|
-
const row = db.prepare(`select * from template_sources where slug_id = ?`).get(slugId);
|
|
1703
|
-
return row ? mapTemplateSource(row) : null;
|
|
1704
|
-
},
|
|
1705
|
-
getTemplateSourceByLocation(repoUrl, branch, templateModulePath) {
|
|
1706
|
-
const row = db.prepare(`
|
|
1707
|
-
select *
|
|
1708
|
-
from template_sources
|
|
1709
|
-
where repo_url = ? and branch = ? and template_module_path = ?
|
|
1710
|
-
limit 1
|
|
1711
|
-
`).get(repoUrl, branch, templateModulePath);
|
|
1712
|
-
return row ? mapTemplateSource(row) : null;
|
|
1713
|
-
},
|
|
1714
|
-
listTemplateSources(input) {
|
|
1715
|
-
const filters = [];
|
|
1716
|
-
const values = [];
|
|
1717
|
-
if (input?.status) {
|
|
1718
|
-
filters.push("status = ?");
|
|
1719
|
-
values.push(input.status);
|
|
1720
|
-
}
|
|
1721
|
-
if (input?.cursor) {
|
|
1722
|
-
filters.push("(created_at < ? or (created_at = ? and id < ?))");
|
|
1723
|
-
values.push(input.cursor.createdAt, input.cursor.createdAt, input.cursor.id);
|
|
1724
|
-
}
|
|
1725
|
-
const rows = db.prepare(`
|
|
1726
|
-
select *
|
|
1727
|
-
from template_sources
|
|
1728
|
-
${filters.length ? `where ${filters.join(" and ")}` : ""}
|
|
1729
|
-
order by created_at desc, id desc
|
|
1730
|
-
${input?.limit || input?.cursor ? "limit ?" : ""}
|
|
1731
|
-
`).all(...(input?.limit || input?.cursor ? [...values, Math.max(1, Math.min(input?.limit ?? 100, 501))] : values));
|
|
1732
|
-
return rows.map(mapTemplateSource);
|
|
1733
|
-
},
|
|
1734
|
-
createTemplateRelease(record) {
|
|
1735
|
-
const timestamp = nowIso();
|
|
1736
|
-
db.prepare(`
|
|
1737
|
-
insert into template_releases (
|
|
1738
|
-
id, source_id, template_id, branch, commit_sha, checkout_path, skill_path, module_path, artifact_manifest_key, artifact_key, status,
|
|
1739
|
-
certification_report_json, activated_at, created_at, updated_at
|
|
1740
|
-
) values (
|
|
1741
|
-
@id, @source_id, @template_id, @branch, @commit_sha, @checkout_path, @skill_path, @module_path, @artifact_manifest_key, @artifact_key, @status,
|
|
1742
|
-
@certification_report_json, @activated_at, @created_at, @updated_at
|
|
1743
|
-
)
|
|
1744
|
-
on conflict(source_id, commit_sha) do update set
|
|
1745
|
-
checkout_path = excluded.checkout_path,
|
|
1746
|
-
skill_path = excluded.skill_path,
|
|
1747
|
-
module_path = excluded.module_path,
|
|
1748
|
-
artifact_manifest_key = excluded.artifact_manifest_key,
|
|
1749
|
-
artifact_key = excluded.artifact_key,
|
|
1750
|
-
status = excluded.status,
|
|
1751
|
-
certification_report_json = excluded.certification_report_json,
|
|
1752
|
-
activated_at = excluded.activated_at,
|
|
1753
|
-
updated_at = excluded.updated_at
|
|
1754
|
-
`).run({
|
|
1755
|
-
id: record.id,
|
|
1756
|
-
source_id: record.sourceId,
|
|
1757
|
-
template_id: record.templateId,
|
|
1758
|
-
branch: record.branch,
|
|
1759
|
-
commit_sha: record.commitSha,
|
|
1760
|
-
checkout_path: record.checkoutPath,
|
|
1761
|
-
skill_path: record.skillPath,
|
|
1762
|
-
module_path: record.modulePath,
|
|
1763
|
-
artifact_manifest_key: record.artifactManifestKey ?? null,
|
|
1764
|
-
artifact_key: record.artifactKey ?? null,
|
|
1765
|
-
status: record.status,
|
|
1766
|
-
certification_report_json: stringifyJson(record.certificationReport ?? null),
|
|
1767
|
-
activated_at: record.activatedAt ?? null,
|
|
1768
|
-
created_at: timestamp,
|
|
1769
|
-
updated_at: timestamp
|
|
1770
|
-
});
|
|
1771
|
-
return this.getTemplateReleaseBySourceAndCommit(record.sourceId, record.commitSha);
|
|
1772
|
-
},
|
|
1773
|
-
getTemplateRelease(id) {
|
|
1774
|
-
const row = db.prepare(`select * from template_releases where id = ?`).get(id);
|
|
1775
|
-
return row ? mapTemplateRelease(row) : null;
|
|
1776
|
-
},
|
|
1777
|
-
getTemplateReleaseBySourceAndCommit(sourceId, commitSha) {
|
|
1778
|
-
const row = db.prepare(`select * from template_releases where source_id = ? and commit_sha = ?`).get(sourceId, commitSha);
|
|
1779
|
-
return row ? mapTemplateRelease(row) : null;
|
|
1780
|
-
},
|
|
1781
|
-
getLatestTemplateReleaseForSource(sourceId) {
|
|
1782
|
-
const row = db.prepare(`
|
|
1783
|
-
select *
|
|
1784
|
-
from template_releases
|
|
1785
|
-
where source_id = ?
|
|
1786
|
-
order by created_at desc
|
|
1787
|
-
limit 1
|
|
1788
|
-
`).get(sourceId);
|
|
1789
|
-
return row ? mapTemplateRelease(row) : null;
|
|
1790
|
-
},
|
|
1791
|
-
listTemplateReleases(input) {
|
|
1792
|
-
const filters = [];
|
|
1793
|
-
const values = [];
|
|
1794
|
-
if (input?.templateId) {
|
|
1795
|
-
filters.push("template_id = ?");
|
|
1796
|
-
values.push(input.templateId);
|
|
1797
|
-
}
|
|
1798
|
-
if (input?.status) {
|
|
1799
|
-
filters.push("status = ?");
|
|
1800
|
-
values.push(input.status);
|
|
1801
|
-
}
|
|
1802
|
-
if (input?.cursor) {
|
|
1803
|
-
filters.push("(created_at < ? or (created_at = ? and id < ?))");
|
|
1804
|
-
values.push(input.cursor.createdAt, input.cursor.createdAt, input.cursor.id);
|
|
1805
|
-
}
|
|
1806
|
-
const rows = db.prepare(`
|
|
1807
|
-
select *
|
|
1808
|
-
from template_releases
|
|
1809
|
-
${filters.length ? `where ${filters.join(" and ")}` : ""}
|
|
1810
|
-
order by created_at desc, id desc
|
|
1811
|
-
${input?.limit || input?.cursor ? "limit ?" : ""}
|
|
1812
|
-
`).all(...(input?.limit || input?.cursor ? [...values, Math.max(1, Math.min(input?.limit ?? 100, 500))] : values));
|
|
1813
|
-
return rows.map(mapTemplateRelease);
|
|
1814
|
-
},
|
|
1815
|
-
getActiveTemplateReleases() {
|
|
1816
|
-
return db.prepare(`select * from template_releases where status = 'active' order by template_id asc`).all().map(mapTemplateRelease);
|
|
1817
|
-
},
|
|
1818
|
-
updateTemplateReleaseStatus(input) {
|
|
1819
|
-
const current = this.getTemplateRelease(input.id);
|
|
1820
|
-
db.prepare(`
|
|
1821
|
-
update template_releases
|
|
1822
|
-
set status = @status,
|
|
1823
|
-
certification_report_json = @certification_report_json,
|
|
1824
|
-
activated_at = @activated_at,
|
|
1825
|
-
updated_at = @updated_at
|
|
1826
|
-
where id = @id
|
|
1827
|
-
`).run({
|
|
1828
|
-
id: input.id,
|
|
1829
|
-
status: input.status,
|
|
1830
|
-
certification_report_json: input.certificationReport === undefined
|
|
1831
|
-
? stringifyJson(current?.certificationReport ?? null)
|
|
1832
|
-
: stringifyJson(input.certificationReport),
|
|
1833
|
-
activated_at: input.activatedAt === undefined ? current?.activatedAt ?? null : input.activatedAt,
|
|
1834
|
-
updated_at: nowIso()
|
|
1835
|
-
});
|
|
1836
|
-
},
|
|
1837
|
-
clearActiveTemplateReleases(templateId, fallbackStatus = "approved") {
|
|
1838
|
-
db.prepare(`
|
|
1839
|
-
update template_releases
|
|
1840
|
-
set status = case when status = 'active' then @fallback_status else status end,
|
|
1841
|
-
updated_at = @updated_at
|
|
1842
|
-
where template_id = @template_id
|
|
1843
|
-
`).run({
|
|
1844
|
-
fallback_status: fallbackStatus,
|
|
1845
|
-
updated_at: nowIso(),
|
|
1846
|
-
template_id: templateId
|
|
1847
|
-
});
|
|
1848
|
-
},
|
|
1849
|
-
upsertChatThread(input) {
|
|
1850
|
-
const timestamp = nowIso();
|
|
1851
|
-
const current = this.getChatThreadMeta(input.customerId, input.id);
|
|
1852
|
-
db.prepare(`
|
|
1853
|
-
insert into chat_threads (
|
|
1854
|
-
id, customer_id, template_id, title, storage_prefix, message_count, last_message_at,
|
|
1855
|
-
hidden_from_view, archived_at, created_at, updated_at
|
|
1856
|
-
) values (
|
|
1857
|
-
@id, @customer_id, @template_id, @title, @storage_prefix, @message_count, @last_message_at,
|
|
1858
|
-
@hidden_from_view, @archived_at, @created_at, @updated_at
|
|
1859
|
-
)
|
|
1860
|
-
on conflict(id) do update set
|
|
1861
|
-
template_id = excluded.template_id,
|
|
1862
|
-
title = excluded.title,
|
|
1863
|
-
storage_prefix = excluded.storage_prefix,
|
|
1864
|
-
message_count = excluded.message_count,
|
|
1865
|
-
last_message_at = excluded.last_message_at,
|
|
1866
|
-
hidden_from_view = excluded.hidden_from_view,
|
|
1867
|
-
archived_at = excluded.archived_at,
|
|
1868
|
-
updated_at = excluded.updated_at
|
|
1869
|
-
`).run({
|
|
1870
|
-
id: input.id,
|
|
1871
|
-
customer_id: input.customerId,
|
|
1872
|
-
template_id: input.templateId,
|
|
1873
|
-
title: input.title,
|
|
1874
|
-
storage_prefix: input.storagePrefix,
|
|
1875
|
-
message_count: input.messageCount ?? current?.messageCount ?? 0,
|
|
1876
|
-
last_message_at: input.lastMessageAt ?? current?.lastMessageAt ?? null,
|
|
1877
|
-
hidden_from_view: input.hiddenFromView === undefined ? (current?.hiddenFromView ? 1 : 0) : (input.hiddenFromView ? 1 : 0),
|
|
1878
|
-
archived_at: input.archivedAt === undefined ? (current?.archivedAt ?? null) : input.archivedAt,
|
|
1879
|
-
created_at: current?.createdAt ?? timestamp,
|
|
1880
|
-
updated_at: timestamp
|
|
1881
|
-
});
|
|
1882
|
-
if (input.tracers) {
|
|
1883
|
-
this.replaceChatThreadTracers(input.id, input.tracers);
|
|
1884
|
-
}
|
|
1885
|
-
return this.getChatThreadMeta(input.customerId, input.id);
|
|
1886
|
-
},
|
|
1887
|
-
appendChatThreadMessages(input) {
|
|
1888
|
-
const current = this.getChatThreadMeta(input.customerId, input.threadId);
|
|
1889
|
-
this.upsertChatThread({
|
|
1890
|
-
id: input.threadId,
|
|
1891
|
-
customerId: input.customerId,
|
|
1892
|
-
templateId: input.templateId,
|
|
1893
|
-
title: input.title || current?.title || "New chat",
|
|
1894
|
-
storagePrefix: input.storagePrefix,
|
|
1895
|
-
tracers: input.tracers,
|
|
1896
|
-
hiddenFromView: current?.hiddenFromView ?? false,
|
|
1897
|
-
archivedAt: current?.archivedAt ?? null,
|
|
1898
|
-
messageCount: (current?.messageCount ?? 0) + Math.max(0, input.messageCountDelta),
|
|
1899
|
-
lastMessageAt: input.lastMessageAt ?? current?.lastMessageAt ?? null
|
|
1900
|
-
});
|
|
1901
|
-
return this.getChatThreadMeta(input.customerId, input.threadId);
|
|
1902
|
-
},
|
|
1903
|
-
getChatThreadMeta(customerId, threadId) {
|
|
1904
|
-
const row = db.prepare(`
|
|
1905
|
-
select *
|
|
1906
|
-
from chat_threads
|
|
1907
|
-
where customer_id = ? and id = ?
|
|
1908
|
-
limit 1
|
|
1909
|
-
`).get(customerId, threadId);
|
|
1910
|
-
if (!row) {
|
|
1911
|
-
return null;
|
|
1912
|
-
}
|
|
1913
|
-
const tracers = db.prepare(`
|
|
1914
|
-
select tracer
|
|
1915
|
-
from chat_thread_tracers
|
|
1916
|
-
where thread_id = ?
|
|
1917
|
-
order by created_at asc, tracer asc
|
|
1918
|
-
`).all(threadId);
|
|
1919
|
-
return mapChatThread(row, tracers.map((entry) => String(entry.tracer)));
|
|
1920
|
-
},
|
|
1921
|
-
listChatThreadMetas(input) {
|
|
1922
|
-
const filters = ["customer_id = ?"];
|
|
1923
|
-
const values = [input.customerId];
|
|
1924
|
-
if (input.templateId) {
|
|
1925
|
-
filters.push("template_id = ?");
|
|
1926
|
-
values.push(input.templateId);
|
|
1927
|
-
}
|
|
1928
|
-
if (input.cursor) {
|
|
1929
|
-
filters.push("(updated_at < ? or (updated_at = ? and id < ?))");
|
|
1930
|
-
values.push(input.cursor.updatedAt, input.cursor.updatedAt, input.cursor.id);
|
|
1931
|
-
}
|
|
1932
|
-
values.push(Math.max(1, Math.min(input.limit ?? 100, 501)));
|
|
1933
|
-
const rows = db.prepare(`
|
|
1934
|
-
select *
|
|
1935
|
-
from chat_threads
|
|
1936
|
-
where ${filters.join(" and ")}
|
|
1937
|
-
order by updated_at desc, id desc
|
|
1938
|
-
limit ?
|
|
1939
|
-
`).all(...values);
|
|
1940
|
-
if (!rows.length) {
|
|
1941
|
-
return [];
|
|
1942
|
-
}
|
|
1943
|
-
const tracerRows = db.prepare(`
|
|
1944
|
-
select thread_id, tracer
|
|
1945
|
-
from chat_thread_tracers
|
|
1946
|
-
where thread_id in (${rows.map(() => "?").join(", ")})
|
|
1947
|
-
order by created_at asc, tracer asc
|
|
1948
|
-
`).all(...rows.map((row) => String(row.id)));
|
|
1949
|
-
const tracerMap = new Map();
|
|
1950
|
-
for (const row of tracerRows) {
|
|
1951
|
-
const list = tracerMap.get(String(row.thread_id)) ?? [];
|
|
1952
|
-
list.push(String(row.tracer));
|
|
1953
|
-
tracerMap.set(String(row.thread_id), list);
|
|
1954
|
-
}
|
|
1955
|
-
return rows.map((row) => mapChatThread(row, tracerMap.get(String(row.id)) ?? []));
|
|
1956
|
-
},
|
|
1957
|
-
replaceChatThreadTracers(threadId, tracers) {
|
|
1958
|
-
const normalized = [...new Set(tracers.map((value) => value.trim()).filter(Boolean))];
|
|
1959
|
-
const timestamp = nowIso();
|
|
1960
|
-
const transaction = db.transaction(() => {
|
|
1961
|
-
db.prepare(`delete from chat_thread_tracers where thread_id = ?`).run(threadId);
|
|
1962
|
-
const insert = db.prepare(`
|
|
1963
|
-
insert into chat_thread_tracers (thread_id, tracer, created_at)
|
|
1964
|
-
values (?, ?, ?)
|
|
1965
|
-
`);
|
|
1966
|
-
for (const tracer of normalized) {
|
|
1967
|
-
insert.run(threadId, tracer, timestamp);
|
|
1968
|
-
}
|
|
1969
|
-
});
|
|
1970
|
-
transaction();
|
|
1971
|
-
},
|
|
1972
|
-
updateChatThreadState(input) {
|
|
1973
|
-
const current = this.getChatThreadMeta(input.customerId, input.threadId);
|
|
1974
|
-
if (!current) {
|
|
1975
|
-
return null;
|
|
1976
|
-
}
|
|
1977
|
-
db.prepare(`
|
|
1978
|
-
update chat_threads
|
|
1979
|
-
set title = ?,
|
|
1980
|
-
hidden_from_view = ?,
|
|
1981
|
-
archived_at = ?,
|
|
1982
|
-
updated_at = ?
|
|
1983
|
-
where customer_id = ? and id = ?
|
|
1984
|
-
`).run(input.title ?? current.title, input.hiddenFromView === undefined ? (current.hiddenFromView ? 1 : 0) : (input.hiddenFromView ? 1 : 0), input.archivedAt === undefined ? current.archivedAt : input.archivedAt, nowIso(), input.customerId, input.threadId);
|
|
1985
|
-
if (input.tracers) {
|
|
1986
|
-
this.replaceChatThreadTracers(input.threadId, input.tracers);
|
|
1987
|
-
}
|
|
1988
|
-
return this.getChatThreadMeta(input.customerId, input.threadId);
|
|
1989
|
-
},
|
|
1990
|
-
deleteChatThread(customerId, threadId) {
|
|
1991
|
-
const current = this.getChatThreadMeta(customerId, threadId);
|
|
1992
|
-
if (!current) {
|
|
1993
|
-
return null;
|
|
1994
|
-
}
|
|
1995
|
-
db.prepare(`
|
|
1996
|
-
delete from chat_threads
|
|
1997
|
-
where customer_id = ? and id = ?
|
|
1998
|
-
`).run(customerId, threadId);
|
|
1999
|
-
return current;
|
|
2000
|
-
}
|
|
2001
|
-
};
|
|
2002
|
-
function normalizeCustomerPlanTier(value) {
|
|
2003
|
-
if (value === "starter" || value === "standard" || value === "agency") {
|
|
2004
|
-
return value;
|
|
2005
|
-
}
|
|
2006
|
-
return "starter";
|
|
2007
|
-
}
|