feat: add cloudflare workers deployment
This commit is contained in:
@@ -0,0 +1,303 @@
|
||||
import { nanoid } from "nanoid";
|
||||
|
||||
import type {
|
||||
ConfirmedFactCard,
|
||||
ImageInput,
|
||||
OptimizedArticle,
|
||||
PublishPlatform,
|
||||
QaReport,
|
||||
} from "../domain/types";
|
||||
import type { AppRepository } from "./repository";
|
||||
import type { ArticleJob, BrandTemplate } from "./repositories";
|
||||
|
||||
type JsonObject = Record<string, unknown>;
|
||||
|
||||
interface BrandTemplateRow {
|
||||
id: string;
|
||||
brand_name: string;
|
||||
company_full_name: string;
|
||||
company_short_names: string;
|
||||
product_names: string;
|
||||
target_industries: string;
|
||||
target_audience: string;
|
||||
verified_claims: string;
|
||||
forbidden_claims: string;
|
||||
tone_rules: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
interface ArticleJobRow {
|
||||
id: string;
|
||||
brand_template_id: string | null;
|
||||
source_title: string;
|
||||
source_body: string;
|
||||
image_inputs: string;
|
||||
publish_platform: PublishPlatform;
|
||||
user_instructions: string;
|
||||
status: string;
|
||||
export_paths: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
interface FactCardRow {
|
||||
job_id: string;
|
||||
fact_card: string;
|
||||
}
|
||||
|
||||
interface OptimizedArticleRow {
|
||||
job_id: string;
|
||||
revision: number;
|
||||
article: string;
|
||||
}
|
||||
|
||||
interface QaReportRow {
|
||||
job_id: string;
|
||||
revision: number;
|
||||
report: string;
|
||||
}
|
||||
|
||||
function nowIso() {
|
||||
return new Date().toISOString();
|
||||
}
|
||||
|
||||
function serialize(value: unknown) {
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
function parseJson<T>(value: string): T {
|
||||
return JSON.parse(value) as T;
|
||||
}
|
||||
|
||||
function toBrandTemplate(row: BrandTemplateRow): BrandTemplate {
|
||||
return {
|
||||
...row,
|
||||
company_short_names: parseJson<string[]>(row.company_short_names),
|
||||
product_names: parseJson<string[]>(row.product_names),
|
||||
target_industries: parseJson<string[]>(row.target_industries),
|
||||
target_audience: parseJson<string[]>(row.target_audience),
|
||||
verified_claims: parseJson<string[]>(row.verified_claims),
|
||||
forbidden_claims: parseJson<string[]>(row.forbidden_claims),
|
||||
tone_rules: parseJson<JsonObject>(row.tone_rules),
|
||||
};
|
||||
}
|
||||
|
||||
function toArticleJob(row: ArticleJobRow): ArticleJob {
|
||||
return {
|
||||
...row,
|
||||
image_inputs: parseJson<ImageInput[]>(row.image_inputs),
|
||||
export_paths: parseJson<Record<string, string>>(row.export_paths),
|
||||
};
|
||||
}
|
||||
|
||||
export function createD1Repository(db: D1Database): AppRepository {
|
||||
return {
|
||||
async createBrandTemplate(input) {
|
||||
const createdAt = nowIso();
|
||||
const template: BrandTemplate = {
|
||||
id: `brand_${nanoid(10)}`,
|
||||
...input,
|
||||
created_at: createdAt,
|
||||
updated_at: createdAt,
|
||||
};
|
||||
|
||||
await db
|
||||
.prepare(
|
||||
`insert into brand_templates (
|
||||
id, brand_name, company_full_name, company_short_names, product_names,
|
||||
target_industries, target_audience, verified_claims, forbidden_claims,
|
||||
tone_rules, created_at, updated_at
|
||||
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
)
|
||||
.bind(
|
||||
template.id,
|
||||
template.brand_name,
|
||||
template.company_full_name,
|
||||
serialize(template.company_short_names),
|
||||
serialize(template.product_names),
|
||||
serialize(template.target_industries),
|
||||
serialize(template.target_audience),
|
||||
serialize(template.verified_claims),
|
||||
serialize(template.forbidden_claims),
|
||||
serialize(template.tone_rules),
|
||||
template.created_at,
|
||||
template.updated_at,
|
||||
)
|
||||
.run();
|
||||
|
||||
return template;
|
||||
},
|
||||
async listBrandTemplates() {
|
||||
const result = await db
|
||||
.prepare("select * from brand_templates order by updated_at desc")
|
||||
.all<BrandTemplateRow>();
|
||||
return result.results.map(toBrandTemplate);
|
||||
},
|
||||
async getBrandTemplate(id) {
|
||||
const row = await db
|
||||
.prepare("select * from brand_templates where id = ?")
|
||||
.bind(id)
|
||||
.first<BrandTemplateRow>();
|
||||
return row ? toBrandTemplate(row) : null;
|
||||
},
|
||||
async createArticleJob(input) {
|
||||
const createdAt = nowIso();
|
||||
const job: ArticleJob = {
|
||||
id: `job_${nanoid(10)}`,
|
||||
brand_template_id: input.brand_template_id ?? null,
|
||||
source_title: input.source_title,
|
||||
source_body: input.source_body,
|
||||
image_inputs: input.image_inputs,
|
||||
publish_platform: input.publish_platform,
|
||||
user_instructions: input.user_instructions,
|
||||
status: "draft",
|
||||
export_paths: {},
|
||||
created_at: createdAt,
|
||||
updated_at: createdAt,
|
||||
};
|
||||
|
||||
await db
|
||||
.prepare(
|
||||
`insert into article_jobs (
|
||||
id, brand_template_id, source_title, source_body, image_inputs,
|
||||
publish_platform, user_instructions, status, export_paths, created_at, updated_at
|
||||
) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
)
|
||||
.bind(
|
||||
job.id,
|
||||
job.brand_template_id,
|
||||
job.source_title,
|
||||
job.source_body,
|
||||
serialize(job.image_inputs),
|
||||
job.publish_platform,
|
||||
job.user_instructions,
|
||||
job.status,
|
||||
serialize(job.export_paths),
|
||||
job.created_at,
|
||||
job.updated_at,
|
||||
)
|
||||
.run();
|
||||
|
||||
return job;
|
||||
},
|
||||
async getArticleJob(id) {
|
||||
const row = await db
|
||||
.prepare("select * from article_jobs where id = ?")
|
||||
.bind(id)
|
||||
.first<ArticleJobRow>();
|
||||
return row ? toArticleJob(row) : null;
|
||||
},
|
||||
async updateArticleJob(id, changes) {
|
||||
const existing = await this.getArticleJob(id);
|
||||
if (!existing) return null;
|
||||
const updated = {
|
||||
brand_template_id: changes.brand_template_id ?? existing.brand_template_id,
|
||||
status: changes.status ?? existing.status,
|
||||
export_paths: changes.export_paths ?? existing.export_paths,
|
||||
updated_at: nowIso(),
|
||||
};
|
||||
await db
|
||||
.prepare(
|
||||
`update article_jobs set
|
||||
brand_template_id = ?,
|
||||
status = ?,
|
||||
export_paths = ?,
|
||||
updated_at = ?
|
||||
where id = ?`,
|
||||
)
|
||||
.bind(
|
||||
updated.brand_template_id,
|
||||
updated.status,
|
||||
serialize(updated.export_paths),
|
||||
updated.updated_at,
|
||||
id,
|
||||
)
|
||||
.run();
|
||||
return this.getArticleJob(id);
|
||||
},
|
||||
async saveFactCard(jobId, factCard) {
|
||||
const timestamp = nowIso();
|
||||
await db
|
||||
.prepare(
|
||||
`insert into fact_cards (
|
||||
job_id, source, fact_card, confirmed_by_user, created_at, updated_at
|
||||
) values (?, ?, ?, ?, ?, ?)
|
||||
on conflict(job_id) do update set
|
||||
fact_card = excluded.fact_card,
|
||||
confirmed_by_user = excluded.confirmed_by_user,
|
||||
updated_at = excluded.updated_at`,
|
||||
)
|
||||
.bind(
|
||||
jobId,
|
||||
"auto_extract_then_user_confirmed",
|
||||
serialize(factCard),
|
||||
factCard.confirmed_by_user ? 1 : 0,
|
||||
timestamp,
|
||||
timestamp,
|
||||
)
|
||||
.run();
|
||||
return { job_id: jobId, ...factCard };
|
||||
},
|
||||
async getFactCard(jobId) {
|
||||
const row = await db
|
||||
.prepare("select job_id, fact_card from fact_cards where job_id = ?")
|
||||
.bind(jobId)
|
||||
.first<FactCardRow>();
|
||||
return row
|
||||
? { job_id: row.job_id, ...parseJson<ConfirmedFactCard>(row.fact_card) }
|
||||
: null;
|
||||
},
|
||||
async saveOptimizedArticle(jobId, article) {
|
||||
const latest = await this.getLatestOptimizedArticle(jobId);
|
||||
const revision = (latest?.revision ?? 0) + 1;
|
||||
const saved = { ...article, job_id: jobId, revision };
|
||||
await db
|
||||
.prepare(
|
||||
`insert into optimized_articles (job_id, revision, article, created_at)
|
||||
values (?, ?, ?, ?)`,
|
||||
)
|
||||
.bind(jobId, revision, serialize(saved), nowIso())
|
||||
.run();
|
||||
return saved;
|
||||
},
|
||||
async getLatestOptimizedArticle(jobId) {
|
||||
const row = await db
|
||||
.prepare(
|
||||
`select job_id, revision, article
|
||||
from optimized_articles
|
||||
where job_id = ?
|
||||
order by revision desc
|
||||
limit 1`,
|
||||
)
|
||||
.bind(jobId)
|
||||
.first<OptimizedArticleRow>();
|
||||
return row ? parseJson<OptimizedArticle>(row.article) : null;
|
||||
},
|
||||
async saveQaReport(jobId, revision, report) {
|
||||
const saved = { ...report, job_id: jobId, revision };
|
||||
await db
|
||||
.prepare(
|
||||
`insert into qa_reports (job_id, revision, report, created_at)
|
||||
values (?, ?, ?, ?)
|
||||
on conflict(job_id, revision) do update set report = excluded.report`,
|
||||
)
|
||||
.bind(jobId, revision, serialize(saved), nowIso())
|
||||
.run();
|
||||
return saved;
|
||||
},
|
||||
async getLatestQaReport(jobId) {
|
||||
const row = await db
|
||||
.prepare(
|
||||
`select job_id, revision, report
|
||||
from qa_reports
|
||||
where job_id = ?
|
||||
order by revision desc
|
||||
limit 1`,
|
||||
)
|
||||
.bind(jobId)
|
||||
.first<QaReportRow>();
|
||||
return row ? parseJson<QaReport>(row.report) : null;
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user