feat(defects): read API — feed, per-section counts and history (T6)
GET /api/defects returns a newest-first feed joined to the project name,
bounded by limit; ?section=ID narrows to one section's history. GET
/api/defects/counts returns { sectionId: n } for the weak-point map. Adds a
(section_id, created_at) index and a db:seed script; ~2k rows query in ~20ms.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { desc } from 'drizzle-orm'
|
||||
import { desc, eq, sql } from 'drizzle-orm'
|
||||
import type { NodePgDatabase } from 'drizzle-orm/node-postgres'
|
||||
import type * as schema from '../schema'
|
||||
import { defects } from '../schema'
|
||||
import { defects, projects } from '../schema'
|
||||
|
||||
type Db = NodePgDatabase<typeof schema>
|
||||
|
||||
@@ -22,3 +22,48 @@ export async function createDefect(db: Db, input: NewDefect) {
|
||||
export async function listRecentDefects(db: Db, limit = 100) {
|
||||
return db.select().from(defects).orderBy(desc(defects.createdAt)).limit(limit)
|
||||
}
|
||||
|
||||
export interface DefectView {
|
||||
id: string
|
||||
sectionId: string
|
||||
projectId: string
|
||||
projectName: string
|
||||
verbatim: string
|
||||
reporterEmail: string
|
||||
createdAt: Date
|
||||
}
|
||||
|
||||
/**
|
||||
* Defects for the dashboard (F6), newest-first and joined to the project name.
|
||||
* The whole feed by default; one section's history when `sectionId` is given
|
||||
* (the lazy modal load). Always bounded by `limit`.
|
||||
*/
|
||||
export async function listDefects(
|
||||
db: Db,
|
||||
opts: { sectionId?: string; limit?: number } = {},
|
||||
): Promise<DefectView[]> {
|
||||
return db
|
||||
.select({
|
||||
id: defects.id,
|
||||
sectionId: defects.sectionId,
|
||||
projectId: defects.projectId,
|
||||
projectName: projects.name,
|
||||
verbatim: defects.verbatim,
|
||||
reporterEmail: defects.reporterEmail,
|
||||
createdAt: defects.createdAt,
|
||||
})
|
||||
.from(defects)
|
||||
.innerJoin(projects, eq(projects.id, defects.projectId))
|
||||
.where(opts.sectionId ? eq(defects.sectionId, opts.sectionId) : undefined)
|
||||
.orderBy(desc(defects.createdAt))
|
||||
.limit(opts.limit ?? 100)
|
||||
}
|
||||
|
||||
/** Per-section defect counts `{ sectionId: n }` for the weak-point map (F4/F6). */
|
||||
export async function countDefectsBySection(db: Db): Promise<Record<string, number>> {
|
||||
const rows = await db
|
||||
.select({ sectionId: defects.sectionId, count: sql<number>`count(*)::int` })
|
||||
.from(defects)
|
||||
.groupBy(defects.sectionId)
|
||||
return Object.fromEntries(rows.map((r) => [r.sectionId, r.count]))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user