| 1 | import type { EventRow } from "../types"; |
| 2 | |
| 3 | export type EventType = "publish" | "update" | "install" | "star" | "milestone"; |
| 4 | |
| 5 | export interface NewEvent { |
| 6 | type: EventType; |
| 7 | packageId: number | null; |
| 8 | actorHandle: string; |
| 9 | summary: string; |
| 10 | now: string; |
| 11 | } |
| 12 | |
| 13 | export class EventRepo { |
| 14 | constructor(private readonly db: D1Database) {} |
| 15 | |
| 16 | async log(e: NewEvent): Promise<void> { |
| 17 | await this.db |
| 18 | .prepare( |
| 19 | `INSERT INTO events (type, package_id, actor_handle, summary, created_at) |
| 20 | VALUES (?1, ?2, ?3, ?4, ?5)`, |
| 21 | ) |
| 22 | .bind(e.type, e.packageId, e.actorHandle, e.summary.slice(0, 200), e.now) |
| 23 | .run(); |
| 24 | } |
| 25 | |
| 26 | // Most-recent social activity, joined to the package slug so the feed can link |
| 27 | // back. Raw 'install' pings are excluded — they exist only to feed the trending |
| 28 | // rank; the feed surfaces publish/update/star and install-milestone events. |
| 29 | async recent(limit: number): Promise<EventRow[]> { |
| 30 | const res = await this.db |
| 31 | .prepare( |
| 32 | `SELECT e.type AS type, p.slug AS slug, e.actor_handle AS actor_handle, |
| 33 | e.summary AS summary, e.created_at AS created_at |
| 34 | FROM events e |
| 35 | LEFT JOIN packages p ON p.id = e.package_id |
| 36 | WHERE e.type IN ('publish', 'update', 'star', 'milestone') |
| 37 | ORDER BY e.created_at DESC |
| 38 | LIMIT ?1`, |
| 39 | ) |
| 40 | .bind(limit) |
| 41 | .all<EventRow>(); |
| 42 | return res.results ?? []; |
| 43 | } |
| 44 | } |
| 45 |