(issueNumbers: number[])
| 566 | // ─── Status Refresh ──────────────────────────────────────────────────────── |
| 567 | |
| 568 | export async function refreshFeedbackStatus(issueNumbers: number[]): Promise<FeedbackStatusItem[]> { |
| 569 | if (!_workerBaseUrl || issueNumbers.length === 0) return []; |
| 570 | |
| 571 | const params = issueNumbers.join(","); |
| 572 | let response: Response; |
| 573 | try { |
| 574 | response = await fetch(`${_workerBaseUrl}/api/feedback/status?issues=${params}`); |
| 575 | } catch (error) { |
| 576 | appendStructuredLog("feedback.status_refresh.failed", { error: formatLogArg(error) }, "warn"); |
| 577 | return []; |
| 578 | } |
| 579 | |
| 580 | if (!response.ok) { |
| 581 | appendStructuredLog("feedback.status_refresh.failed", { status: response.status }, "warn"); |
| 582 | return []; |
| 583 | } |
| 584 | |
| 585 | const items = (await response.json()) as FeedbackStatusItem[]; |
| 586 | appendStructuredLog("feedback.status_refresh.success", { count: items.length }); |
| 587 | |
| 588 | // Update local DB |
| 589 | const db = await getDB(); |
| 590 | const returnedNumbers = new Set(items.map((i) => i.number)); |
| 591 | |
| 592 | for (const item of items) { |
| 593 | const existingRows = await db.select<{ |
| 594 | comment_count?: number | null; |
| 595 | has_new_reply?: number | null; |
| 596 | }>("SELECT comment_count, has_new_reply FROM feedback WHERE issue_number = ?", [item.number]); |
| 597 | const existing = existingRows[0]; |
| 598 | const previousCommentCount = existing?.comment_count ?? 0; |
| 599 | const commentCount = item.commentCount ?? (item.hasNewComment ? 1 : 0); |
| 600 | const hasNewReply = Boolean(existing?.has_new_reply) || commentCount > previousCommentCount; |
| 601 | |
| 602 | await db.execute( |
| 603 | "UPDATE feedback SET status = ?, has_new_reply = ?, comment_count = ?, updated_at = ? WHERE issue_number = ?", |
| 604 | [item.state, hasNewReply ? 1 : 0, commentCount, Date.now(), item.number], |
| 605 | ); |
| 606 | } |
| 607 | |
| 608 | // Remove issues not returned by the API (likely deleted on GitHub) |
| 609 | // Only delete records older than 5 minutes to avoid race condition with newly submitted feedback |
| 610 | const safeDeleteThreshold = Date.now() - 5 * 60 * 1000; |
| 611 | for (const num of issueNumbers) { |
| 612 | if (!returnedNumbers.has(num)) { |
| 613 | await db.execute("DELETE FROM feedback WHERE issue_number = ? AND created_at < ?", [ |
| 614 | num, |
| 615 | safeDeleteThreshold, |
| 616 | ]); |
| 617 | } |
| 618 | } |
| 619 | |
| 620 | return items; |
| 621 | } |
| 622 | |
| 623 | // ─── Device Info Collection ──────────────────────────────────────────────── |
| 624 |
no test coverage detected