Skip to content

Commit 352aab4

Browse files
committed
extend Storage Protocol to cover all SqliteStorage methods
1 parent ac7c493 commit 352aab4

2 files changed

Lines changed: 125 additions & 46 deletions

File tree

src/ghdcbot/core/interfaces.py

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from __future__ import annotations
22

33
from datetime import datetime
4-
from typing import Iterable, Protocol, Sequence
4+
from typing import Any, Iterable, Protocol, Sequence
55

6+
from ghdcbot.config.models import IdentityMapping
67
from ghdcbot.core.models import (
78
AssignmentPlan,
89
ContributionEvent,
@@ -59,6 +60,7 @@ def list_contribution_summaries(
5960
period_start: datetime,
6061
period_end: datetime,
6162
weights: dict[str, int],
63+
difficulty_weights: dict[str, int] | None = None,
6264
) -> Sequence[ContributionSummary]:
6365
"""Aggregate contribution counts and scores for the period."""
6466

@@ -74,6 +76,88 @@ def get_cursor(self, source: str) -> datetime | None:
7476
def set_cursor(self, source: str, cursor: datetime) -> None:
7577
"""Persist last sync cursor for a source."""
7678

79+
# Identity linking
80+
81+
def create_identity_claim(
82+
self,
83+
discord_user_id: str,
84+
github_user: str,
85+
verification_code: str,
86+
expires_at: datetime,
87+
*,
88+
max_age_days: int | None = None,
89+
) -> None:
90+
"""Create or refresh a pending identity claim for (discord_user_id, github_user)."""
91+
92+
def get_identity_link(self, discord_user_id: str, github_user: str) -> dict | None:
93+
"""Return identity link row for (discord_user_id, github_user), or None."""
94+
95+
def mark_identity_verified(self, discord_user_id: str, github_user: str) -> None:
96+
"""Mark an identity claim as verified."""
97+
98+
def unlink_identity(self, discord_user_id: str, cooldown_hours: int) -> dict | None:
99+
"""Unlink the verified identity for a Discord user. Returns unlink info or None."""
100+
101+
def list_verified_identity_mappings(self) -> list[IdentityMapping]:
102+
"""Return all verified identity mappings."""
103+
104+
def get_identity_links_for_discord_user(self, discord_user_id: str) -> list[dict]:
105+
"""Return all identity link rows for a Discord user (verified and pending)."""
106+
107+
def get_identity_status(
108+
self, discord_user_id: str, max_age_days: int | None = None
109+
) -> dict:
110+
"""Return current identity status dict for a Discord user."""
111+
112+
# Issue requests
113+
114+
def insert_issue_request(
115+
self,
116+
request_id: str,
117+
discord_user_id: str,
118+
github_user: str,
119+
owner: str,
120+
repo: str,
121+
issue_number: int,
122+
issue_url: str,
123+
) -> None:
124+
"""Store a new issue assignment request with status pending."""
125+
126+
def list_pending_issue_requests(self) -> list[dict]:
127+
"""Return all pending issue requests ordered by created_at ascending."""
128+
129+
def get_issue_request(self, request_id: str) -> dict | None:
130+
"""Return a single issue request by request_id, or None."""
131+
132+
def update_issue_request_status(self, request_id: str, status: str) -> None:
133+
"""Update an issue request status (pending, approved, rejected, cancelled)."""
134+
135+
# Audit log
136+
137+
def append_audit_event(self, event: dict) -> None:
138+
"""Append an audit event (append-only)."""
139+
140+
def list_audit_events(self) -> list[dict]:
141+
"""Return all audit events."""
142+
143+
# Notifications
144+
145+
def was_notification_sent(self, dedupe_key: str) -> bool:
146+
"""Check if a notification was already sent (deduplication)."""
147+
148+
def mark_notification_sent(
149+
self,
150+
dedupe_key: str,
151+
event: Any,
152+
discord_user_id: str,
153+
channel_id: str | None,
154+
target_github_user: str | None = None,
155+
) -> None:
156+
"""Record that a notification was sent (deduplication tracking)."""
157+
158+
def list_recent_notifications(self, limit: int = 1000) -> list[dict]:
159+
"""Return recent sent notifications ordered by sent_at descending."""
160+
77161

78162
class ScoreStrategy(Protocol):
79163
def compute_scores(

src/ghdcbot/engine/snapshots.py

Lines changed: 40 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from typing import Any
2222

2323
from ghdcbot.config.models import BotConfig, IdentityMapping
24+
from ghdcbot.core.interfaces import Storage
2425
from ghdcbot.core.models import ContributionEvent, ContributionSummary, Score
2526

2627
logger = logging.getLogger("Snapshots")
@@ -30,7 +31,7 @@
3031

3132

3233
def write_snapshots_to_github(
33-
storage: Any,
34+
storage: Storage,
3435
config: BotConfig,
3536
github_writer: Any,
3637
identity_mappings: list[IdentityMapping],
@@ -83,7 +84,7 @@ def write_snapshots_to_github(
8384

8485

8586
def _write_snapshots(
86-
storage: Any,
87+
storage: Storage,
8788
config: BotConfig,
8889
github_writer: Any,
8990
snapshot_config: Any,
@@ -138,23 +139,21 @@ def _write_snapshots(
138139
},
139140
)
140141
# Audit log
141-
append_audit = getattr(storage, "append_audit_event", None)
142-
if callable(append_audit):
143-
append_audit({
144-
"event_type": "snapshot_written",
145-
"context": {
146-
"org": config.github.org,
147-
"repo": f"{owner}/{repo}",
148-
"snapshot_dir": snapshot_dir,
149-
"run_id": run_id,
150-
"files_written": files_written,
151-
"timestamp": now.isoformat(),
152-
},
153-
})
142+
storage.append_audit_event({
143+
"event_type": "snapshot_written",
144+
"context": {
145+
"org": config.github.org,
146+
"repo": f"{owner}/{repo}",
147+
"snapshot_dir": snapshot_dir,
148+
"run_id": run_id,
149+
"files_written": files_written,
150+
"timestamp": now.isoformat(),
151+
},
152+
})
154153

155154

156155
def _collect_snapshot_data(
157-
storage: Any,
156+
storage: Storage,
158157
config: BotConfig,
159158
identity_mappings: list[IdentityMapping],
160159
scores: list[Score],
@@ -253,21 +252,19 @@ def _collect_snapshot_data(
253252

254253
# Issue requests snapshot
255254
issue_requests_data = []
256-
list_pending = getattr(storage, "list_pending_issue_requests", None)
257-
if callable(list_pending):
258-
pending_requests = list_pending()
259-
for req in pending_requests:
260-
issue_requests_data.append({
261-
"request_id": req.get("request_id"),
262-
"discord_user_id": req.get("discord_user_id"),
263-
"github_user": req.get("github_user"),
264-
"owner": req.get("owner"),
265-
"repo": req.get("repo"),
266-
"issue_number": req.get("issue_number"),
267-
"issue_url": req.get("issue_url"),
268-
"created_at": req.get("created_at"),
269-
"status": req.get("status"),
270-
})
255+
pending_requests = storage.list_pending_issue_requests()
256+
for req in pending_requests:
257+
issue_requests_data.append({
258+
"request_id": req.get("request_id"),
259+
"discord_user_id": req.get("discord_user_id"),
260+
"github_user": req.get("github_user"),
261+
"owner": req.get("owner"),
262+
"repo": req.get("repo"),
263+
"issue_number": req.get("issue_number"),
264+
"issue_url": req.get("issue_url"),
265+
"created_at": req.get("created_at"),
266+
"status": req.get("status"),
267+
})
271268

272269
issue_requests = {
273270
"schema_version": SCHEMA_VERSION,
@@ -279,20 +276,18 @@ def _collect_snapshot_data(
279276

280277
# Notifications snapshot (recent sent notifications)
281278
notifications_data = []
282-
list_notifications = getattr(storage, "list_recent_notifications", None)
283-
if callable(list_notifications):
284-
recent_notifications = list_notifications(limit=1000) # Last 1000 notifications
285-
for notif in recent_notifications:
286-
notifications_data.append({
287-
"dedupe_key": notif.get("dedupe_key"),
288-
"event_type": notif.get("event_type"),
289-
"github_user": notif.get("github_user"),
290-
"discord_user_id": notif.get("discord_user_id"),
291-
"repo": notif.get("repo"),
292-
"target": notif.get("target"),
293-
"channel_id": notif.get("channel_id"),
294-
"sent_at": notif.get("sent_at"),
295-
})
279+
recent_notifications = storage.list_recent_notifications(limit=1000)
280+
for notif in recent_notifications:
281+
notifications_data.append({
282+
"dedupe_key": notif.get("dedupe_key"),
283+
"event_type": notif.get("event_type"),
284+
"github_user": notif.get("github_user"),
285+
"discord_user_id": notif.get("discord_user_id"),
286+
"repo": notif.get("repo"),
287+
"target": notif.get("target"),
288+
"channel_id": notif.get("channel_id"),
289+
"sent_at": notif.get("sent_at"),
290+
})
296291

297292
notifications = {
298293
"schema_version": SCHEMA_VERSION,

0 commit comments

Comments
 (0)