Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | 31x 1x 1x 2x 2x 4x 4x 5x 5x 4x 4x 3x 3x 2x 4x 5x 3x 3x 1x 3x 3x 1x 2x 2x 3x | // Data Access Layer for Social Drafts
import { eq, and, or, lt, inArray } from "drizzle-orm";
import type { DrizzleD1Database } from "drizzle-orm/d1";
import * as schema from "../db/schema";
import type { SocialDraft, NewSocialDraft } from "../db/schema";
export class SocialDraftsDal {
constructor(private db: DrizzleD1Database<typeof schema>) {}
async create(data: NewSocialDraft): Promise<SocialDraft> {
const result = await this.db
.insert(schema.socialDrafts)
.values(data)
.returning();
return result[0];
}
async getById(id: string): Promise<SocialDraft | null> {
const result = await this.db
.select()
.from(schema.socialDrafts)
.where(eq(schema.socialDrafts.id, id))
.limit(1);
return result[0] ?? null;
}
async listByPro(proId: string): Promise<{ drafts: (SocialDraft & { projectTitle: string | null })[]; pendingCount: number }> {
const drafts = await this.db
.select()
.from(schema.socialDrafts)
.where(eq(schema.socialDrafts.proId, proId))
.orderBy(schema.socialDrafts.dateCreated);
const pendingCount = drafts.filter(
(d) => d.status === "pending" || d.status === "processing",
).length;
// Fetch project titles for all drafts (avoid JOIN per D1 guidance)
const projectIds = [...new Set(drafts.map((d) => d.projectId))];
const projectTitleMap: Record<string, string> = {};
if (projectIds.length > 0) {
const projects = await this.db
.select({ id: schema.projects.id, title: schema.projects.title })
.from(schema.projects)
.where(inArray(schema.projects.id, projectIds));
for (const p of projects) {
projectTitleMap[p.id] = p.title;
}
}
return {
drafts: drafts.map((d) => ({ ...d, projectTitle: projectTitleMap[d.projectId] ?? null })),
pendingCount,
};
}
async updateStatus(id: string, status: string, jobNonce?: string): Promise<void> {
const update: Partial<typeof schema.socialDrafts.$inferInsert> = {
status: status as SocialDraft["status"],
dateUpdated: new Date(),
};
if (jobNonce !== undefined) {
update.jobNonce = jobNonce;
}
await this.db
.update(schema.socialDrafts)
.set(update)
.where(eq(schema.socialDrafts.id, id));
}
// Used by cron to find expired drafts that need cleanup
async findExpired(now: Date = new Date()): Promise<SocialDraft[]> {
return this.db
.select()
.from(schema.socialDrafts)
.where(
and(
lt(schema.socialDrafts.expiresAt, now),
or(
eq(schema.socialDrafts.status, "ready"),
// Also clean up any stuck processing/failed drafts with passed expiry
eq(schema.socialDrafts.status, "failed"),
),
),
);
}
// Clear the R2 key from a draft (used after R2 object deletion on expiry)
async clearR2Key(id: string): Promise<void> {
await this.db
.update(schema.socialDrafts)
.set({
reelR2Key: null,
dateUpdated: new Date(),
})
.where(eq(schema.socialDrafts.id, id));
}
// Cancel pending/processing drafts for a given project (used when regenerating)
async cancelPendingForProject(projectId: string): Promise<number> {
const result = await this.db
.update(schema.socialDrafts)
.set({
status: "cancelled",
dateUpdated: new Date(),
})
.where(
and(
eq(schema.socialDrafts.projectId, projectId),
or(
eq(schema.socialDrafts.status, "pending"),
eq(schema.socialDrafts.status, "processing"),
),
),
)
.returning();
return result.length;
}
// Update draft fields (used by internal callback routes)
async update(
id: string,
data: Partial<Omit<NewSocialDraft, "id" | "proId" | "projectId">>,
): Promise<void> {
await this.db
.update(schema.socialDrafts)
.set({
...data,
dateUpdated: new Date(),
})
.where(eq(schema.socialDrafts.id, id));
}
// Hard-delete a draft. Used by C2 rollback when queue.send fails AFTER
// the draft row is created — we rip the draft back out so the pro
// receives a clean 503 instead of a stuck-pending draft. FK ON DELETE
// CASCADE on social_draft_photos cleans up any photo rows that were
// already inserted.
async deleteById(id: string): Promise<void> {
await this.db
.delete(schema.socialDrafts)
.where(eq(schema.socialDrafts.id, id));
}
}
|