All files / dal/whatsapp messages.dal.ts

94.11% Statements 16/17
100% Branches 6/6
87.5% Functions 7/8
94.11% Lines 16/17

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              147x             7x                   2x         2x       2x         2x       20x       20x                                             3x       3x 1x     3x                       3x 3x   3x       3x      
import { and, asc, eq, gt, sql } from "drizzle-orm";
import type { DrizzleD1Database } from "drizzle-orm/d1";
import type { NewWaMessage, WaMessage } from "../../db/schema";
import * as schema from "../../db/schema";
import { statusBelow } from "./status-rank";
 
export class WaMessagesDal {
	constructor(private db: DrizzleD1Database<typeof schema>) {}
 
	async findByConversationId(
		conversationId: number,
		offset = 0,
		limit = 50,
	): Promise<WaMessage[]> {
		return this.db
			.select()
			.from(schema.waMessages)
			.where(eq(schema.waMessages.conversationId, conversationId))
			.orderBy(asc(schema.waMessages.dateCreated))
			.limit(limit)
			.offset(offset);
	}
 
	async findById(id: number): Promise<WaMessage | undefined> {
		const result = await this.db
			.select()
			.from(schema.waMessages)
			.where(eq(schema.waMessages.id, id))
			.limit(1);
		return result[0];
	}
 
	async findByWamid(wamid: string): Promise<WaMessage | undefined> {
		const result = await this.db
			.select()
			.from(schema.waMessages)
			.where(eq(schema.waMessages.wamid, wamid))
			.limit(1);
		return result[0];
	}
 
	async create(data: NewWaMessage): Promise<WaMessage> {
		const result = await this.db
			.insert(schema.waMessages)
			.values(data)
			.returning();
		return result[0];
	}
 
	/**
	 * Attach an outbound message to the prospect it was sent to.
	 *
	 * A follow-up write because the send helpers have no parameter for this
	 * column. Without the link, `recordRrmDeliveryStatus` reads a null
	 * `prospect_id` off the webhook's wamid lookup and returns `not_rrm`, so a
	 * delivery failure is stored here and surfaced nowhere.
	 */
	async linkProspect(messageId: number, prospectId: string): Promise<void> {
		await this.db
			.update(schema.waMessages)
			.set({ prospectId })
			.where(eq(schema.waMessages.id, messageId));
	}
 
	async updateStatus(
		wamid: string,
		status: string,
		errorCode?: string,
	): Promise<void> {
		const updateData: Record<string, unknown> = {
			status,
			dateUpdated: new Date(),
		};
		if (errorCode) {
			updateData.errorCode = errorCode;
		}
		// Forward-only: a late 'delivered' webhook must not regress 'read'.
		await this.db
			.update(schema.waMessages)
			.set(updateData)
			.where(
				and(
					eq(schema.waMessages.wamid, wamid),
					statusBelow(schema.waMessages.status, status),
				),
			);
	}
 
	async countToday(): Promise<number> {
		const todayStart = new Date();
		todayStart.setHours(0, 0, 0, 0);
 
		const result = await this.db
			.select({ count: sql<number>`count(*)` })
			.from(schema.waMessages)
			.where(gt(schema.waMessages.dateCreated, todayStart));
		return result[0]?.count ?? 0;
	}
}