Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions backend/app/routers/history.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
"""

from __future__ import annotations
from fastapi import APIRouter, HTTPException, Query
from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel, Field

from ..models import User
from ..security import get_current_user
from ..services import database

router = APIRouter()
Expand All @@ -29,8 +31,12 @@ class HistoryEntry(BaseModel):


@router.post("/", response_model=dict, status_code=201)
async def save_history(body: HistorySaveRequest):
async def save_history(
body: HistorySaveRequest,
current_user: User = Depends(get_current_user),
):
entry_id = await database.save_entry(
user_id=current_user.id,
code=body.code,
language=body.language,
score=body.score,
Expand All @@ -43,21 +49,34 @@ async def save_history(body: HistorySaveRequest):
async def get_history(
limit: int = Query(20, ge=1, le=100),
offset: int = Query(0, ge=0),
current_user: User = Depends(get_current_user),
):
return await database.get_entries(limit=limit, offset=offset)
return await database.get_entries(
user_id=current_user.id,
limit=limit,
offset=offset,
)


@router.get("/search", response_model=list[HistoryEntry])
async def search_history(
q: str = Query(..., min_length=1),
limit: int = Query(20, ge=1, le=100),
current_user: User = Depends(get_current_user),
):
return await database.search_entries(q=q, limit=limit)
return await database.search_entries(
user_id=current_user.id,
q=q,
limit=limit,
)


@router.delete("/{entry_id}", response_model=dict)
async def delete_history(entry_id: int):
deleted = await database.delete_entry(entry_id)
async def delete_history(
entry_id: int,
current_user: User = Depends(get_current_user),
):
deleted = await database.delete_entry(entry_id, user_id=current_user.id)
if not deleted:
raise HTTPException(status_code=404, detail="History entry not found.")
return {"id": entry_id, "status": "deleted"}
41 changes: 28 additions & 13 deletions backend/app/services/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ async def init_db() -> None:
await db.execute("""
CREATE TABLE IF NOT EXISTS history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER,
code_hash TEXT NOT NULL,
language TEXT NOT NULL,
score INTEGER,
Expand All @@ -28,9 +29,17 @@ async def init_db() -> None:
CREATE VIRTUAL TABLE IF NOT EXISTS fts_history
USING fts5(code_preview, content=history, content_rowid=id)
""")
columns = await db.execute("PRAGMA table_info(history)")
column_names = {row[1] for row in await columns.fetchall()}
if "user_id" not in column_names:
await db.execute("ALTER TABLE history ADD COLUMN user_id INTEGER")
await db.execute(
"CREATE INDEX IF NOT EXISTS idx_timestamp ON history(timestamp DESC)"
)
await db.execute(
"CREATE INDEX IF NOT EXISTS idx_history_user_timestamp "
"ON history(user_id, timestamp DESC)"
)
await db.commit()


Expand All @@ -39,6 +48,7 @@ def hash_code(code: str) -> str:


async def save_entry(
user_id: int,
code: str,
language: str,
score: int | None,
Expand All @@ -49,10 +59,12 @@ async def save_entry(
async with aiosqlite.connect(DB_PATH) as db:
cursor = await db.execute(
"""
INSERT INTO history (code_hash, language, score, issue_count, code_preview)
VALUES (?, ?, ?, ?, ?)
INSERT INTO history (
user_id, code_hash, language, score, issue_count, code_preview
)
VALUES (?, ?, ?, ?, ?, ?)
""",
(code_hash, language, score, issue_count, preview),
(user_id, code_hash, language, score, issue_count, preview),
)
row_id = cursor.lastrowid
await db.execute(
Expand All @@ -63,49 +75,52 @@ async def save_entry(
return row_id


async def get_entries(limit: int = 20, offset: int = 0) -> list[dict]:
async def get_entries(user_id: int, limit: int = 20, offset: int = 0) -> list[dict]:
async with aiosqlite.connect(DB_PATH) as db:
db.row_factory = aiosqlite.Row
cursor = await db.execute(
"""
SELECT id, code_hash, language, score, issue_count, timestamp, code_preview
FROM history
WHERE user_id = ?
ORDER BY timestamp DESC
LIMIT ? OFFSET ?
""",
(limit, offset),
(user_id, limit, offset),
)
rows = await cursor.fetchall()
return [dict(row) for row in rows]


async def search_entries(q: str, limit: int = 20) -> list[dict]:
async def search_entries(user_id: int, q: str, limit: int = 20) -> list[dict]:
async with aiosqlite.connect(DB_PATH) as db:
db.row_factory = aiosqlite.Row
cursor = await db.execute(
"""
SELECT h.id, h.code_hash, h.language, h.score,
h.issue_count, h.timestamp, h.code_preview
FROM history h
WHERE h.id IN (
WHERE h.user_id = ?
AND h.id IN (
SELECT rowid FROM fts_history WHERE fts_history MATCH ?
)
ORDER BY h.timestamp DESC
LIMIT ?
""",
(q, limit),
(user_id, q, limit),
)
rows = await cursor.fetchall()
return [dict(row) for row in rows]


async def delete_entry(entry_id: int) -> bool:
async def delete_entry(entry_id: int, user_id: int) -> bool:
async with aiosqlite.connect(DB_PATH) as db:
cursor = await db.execute(
"DELETE FROM history WHERE id = ?", (entry_id,)
)
await db.execute(
"DELETE FROM fts_history WHERE rowid = ?", (entry_id,)
"DELETE FROM history WHERE id = ? AND user_id = ?", (entry_id, user_id)
)
if cursor.rowcount > 0:
await db.execute(
"DELETE FROM fts_history WHERE rowid = ?", (entry_id,)
)
await db.commit()
return cursor.rowcount > 0
Loading
Loading