Skip to content

Commit 36e1d8c

Browse files
authored
add admin leaderboard submission listing (#477)
1 parent 371eb37 commit 36e1d8c

File tree

3 files changed

+128
-0
lines changed

3 files changed

+128
-0
lines changed

src/kernelbot/api/main.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -689,6 +689,25 @@ async def admin_delete_leaderboard(
689689
return {"status": "ok", "leaderboard": leaderboard_name, "force": force}
690690

691691

692+
@app.get("/admin/leaderboards/{leaderboard_name}/submissions")
693+
async def admin_list_leaderboard_submissions(
694+
leaderboard_name: str,
695+
_: Annotated[None, Depends(require_admin)],
696+
db_context=Depends(get_db),
697+
limit: int = 100,
698+
offset: int = 0,
699+
) -> dict:
700+
with db_context as db:
701+
submissions = db.get_leaderboard_submission_history(leaderboard_name, limit, offset)
702+
return {
703+
"status": "ok",
704+
"leaderboard": leaderboard_name,
705+
"limit": limit,
706+
"offset": offset,
707+
"submissions": submissions,
708+
}
709+
710+
692711
@app.delete("/admin/submissions/{submission_id}")
693712
async def admin_delete_submission(
694713
submission_id: int,

src/libkernelbot/leaderboard_db.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1276,6 +1276,55 @@ def get_user_submissions(
12761276
logger.exception("Error fetching user submissions for user %s", user_id, exc_info=e)
12771277
raise KernelBotError("Error fetching user submissions") from e
12781278

1279+
def get_leaderboard_submission_history(
1280+
self,
1281+
leaderboard_name: str,
1282+
limit: int = 100,
1283+
offset: int = 0,
1284+
) -> list[dict]:
1285+
"""Get all submissions for a leaderboard with admin-oriented metadata."""
1286+
limit = max(1, min(limit, 1000))
1287+
offset = max(0, offset)
1288+
1289+
try:
1290+
self.get_leaderboard_id(leaderboard_name)
1291+
self.cursor.execute(
1292+
"""
1293+
SELECT s.id, lb.name, s.file_name, s.user_id, ui.user_name,
1294+
s.submission_time, s.done
1295+
FROM leaderboard.submission s
1296+
JOIN leaderboard.leaderboard lb ON s.leaderboard_id = lb.id
1297+
LEFT JOIN leaderboard.user_info ui ON ui.id = s.user_id
1298+
WHERE lb.name = %s
1299+
ORDER BY s.submission_time DESC, s.id DESC
1300+
LIMIT %s OFFSET %s
1301+
""",
1302+
(leaderboard_name, limit, offset),
1303+
)
1304+
return [
1305+
{
1306+
"id": row[0],
1307+
"leaderboard_name": row[1],
1308+
"file_name": row[2],
1309+
"user_id": row[3],
1310+
"user_name": row[4],
1311+
"submission_time": row[5],
1312+
"done": row[6],
1313+
}
1314+
for row in self.cursor.fetchall()
1315+
]
1316+
except KernelBotError:
1317+
self.connection.rollback()
1318+
raise
1319+
except psycopg2.Error as e:
1320+
self.connection.rollback()
1321+
logger.exception(
1322+
"Error fetching submissions for leaderboard %s",
1323+
leaderboard_name,
1324+
exc_info=e,
1325+
)
1326+
raise KernelBotError("Error fetching leaderboard submissions") from e
1327+
12791328
def get_submission_by_id(self, submission_id: int) -> Optional["SubmissionItem"]:
12801329
query = """
12811330
SELECT s.leaderboard_id, lb.name, s.file_name, s.user_id,

tests/test_admin_api.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,66 @@ def test_admin_stats_with_leaderboard_name(self, test_client, mock_backend):
141141
class TestAdminSubmissions:
142142
"""Test admin submission endpoints."""
143143

144+
def test_list_leaderboard_submissions(self, test_client, mock_backend):
145+
"""GET /admin/leaderboards/{name}/submissions returns all submission metadata."""
146+
mock_backend.db.__enter__ = MagicMock(return_value=mock_backend.db)
147+
mock_backend.db.__exit__ = MagicMock(return_value=None)
148+
mock_backend.db.get_leaderboard_submission_history = MagicMock(return_value=[
149+
{
150+
"id": 123,
151+
"leaderboard_name": "test-lb",
152+
"file_name": "submission.py",
153+
"user_id": "42",
154+
"user_name": "alice",
155+
"submission_time": "2026-04-07T12:00:00Z",
156+
"done": True,
157+
},
158+
{
159+
"id": 122,
160+
"leaderboard_name": "test-lb",
161+
"file_name": "submission_old.py",
162+
"user_id": "43",
163+
"user_name": "bob",
164+
"submission_time": "2026-04-07T11:00:00Z",
165+
"done": False,
166+
},
167+
])
168+
169+
response = test_client.get(
170+
"/admin/leaderboards/test-lb/submissions?limit=50&offset=10",
171+
headers={"Authorization": "Bearer test_token"},
172+
)
173+
assert response.status_code == 200
174+
assert response.json() == {
175+
"status": "ok",
176+
"leaderboard": "test-lb",
177+
"limit": 50,
178+
"offset": 10,
179+
"submissions": [
180+
{
181+
"id": 123,
182+
"leaderboard_name": "test-lb",
183+
"file_name": "submission.py",
184+
"user_id": "42",
185+
"user_name": "alice",
186+
"submission_time": "2026-04-07T12:00:00Z",
187+
"done": True,
188+
},
189+
{
190+
"id": 122,
191+
"leaderboard_name": "test-lb",
192+
"file_name": "submission_old.py",
193+
"user_id": "43",
194+
"user_name": "bob",
195+
"submission_time": "2026-04-07T11:00:00Z",
196+
"done": False,
197+
},
198+
],
199+
}
200+
mock_backend.db.get_leaderboard_submission_history.assert_called_once_with(
201+
"test-lb", 50, 10
202+
)
203+
144204
def test_get_submission(self, test_client, mock_backend):
145205
"""GET /admin/submissions/{id} returns submission."""
146206
mock_backend.db.__enter__ = MagicMock(return_value=mock_backend.db)

0 commit comments

Comments
 (0)