Skip to content

Commit c61d1a1

Browse files
committed
feat: support read/unread state tracking and setting
1 parent 1d5e228 commit c61d1a1

File tree

10 files changed

+312
-58
lines changed

10 files changed

+312
-58
lines changed

backend/lfreader_server/app.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737

3838
from .storage import Storage
3939
from .config import Config
40-
from .models import AppState, AppStatus, QueryEntriesArgs, FetchFeedsArgs, ArchiveFeedsArgs, CleanFeedsArgs, DeleteFeedsArgs, UpdateFeedsArgs
40+
from .models import AppState, AppStatus, QueryEntriesArgs, FetchFeedsArgs, ArchiveFeedsArgs, CleanFeedsArgs, DeleteFeedsArgs, UpdateFeedsArgs, UpdateEntriesArgs
4141

4242

4343
try:
@@ -142,9 +142,28 @@ async def feed_action_api(
142142
storage.update_feeds(args.feeds)
143143
state.update()
144144
case _:
145-
raise HTTPException(status_code=400, detail=f"Invalid action: {args.action}")
145+
raise HTTPException(status_code=400, detail=f"Invalid feed action: {args.action}")
146146
return {}
147147

148+
149+
"""
150+
Entry Action API
151+
"""
152+
@app.post("/entries")
153+
async def entry_action_api(
154+
args: UpdateEntriesArgs
155+
):
156+
match args.action:
157+
case "update":
158+
storage.update_entries(args.entries)
159+
state.update()
160+
case _:
161+
raise HTTPException(status_code=400, detail=f"Invalid entry action: {args.action}")
162+
return {}
163+
164+
165+
166+
148167
## Error handlers
149168

150169
# use plaintext for HTTP and validation exceptions

backend/lfreader_server/models.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,20 @@ class UpdateFeedsArgs(BaseModel):
8888
action: Literal["update"]
8989
feeds: list[FeedInfo]
9090

91+
92+
### Entry Action API (tagged union)
93+
94+
class EntryInfo(BaseModel):
95+
feed_url: str
96+
entry_id: str
97+
user_data: dict
98+
99+
# allow using subscript to access
100+
def __getitem__(self, item):
101+
return getattr(self, item)
102+
103+
104+
class UpdateEntriesArgs(BaseModel):
105+
action: Literal["update"]
106+
entries: list[EntryInfo]
107+

backend/lfreader_server/storage.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
from .archive import Archiver
3636
from .config import Config
3737
from .utils import async_map, sql_update_field
38-
from .models import QueryEntry, FeedInfo
38+
from .models import QueryEntry, FeedInfo, EntryInfo
3939

4040
# for logging
4141
def entry_title(e):
@@ -484,6 +484,15 @@ def get_entries(
484484
) -> list[dict]:
485485
return self.get_entries_cursor(feed_urls, entries, offset, limit, columns).fetchall()
486486

487+
def update_entries(self, entries: list[EntryInfo]):
488+
cur = self.db.cursor()
489+
for e in entries:
490+
cur.execute(
491+
"UPDATE entries SET user_data = ? WHERE feed_url = ? AND id = ?",
492+
(pack_data(e.user_data), e.feed_url, e.entry_id)
493+
)
494+
self.db.commit()
495+
487496
def update_feeds(self, feeds: list[FeedInfo]):
488497
cur = self.db.cursor()
489498
for f in feeds:

frontend/package-lock.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"@preact/signals": "^2.0.4",
1919
"@types/katex": "^0.16.7",
2020
"highlight.js": "^11.11.1",
21+
"immutable": "^5.1.1",
2122
"js-base64": "^3.7.7",
2223
"katex": "^0.16.22",
2324
"luxon": "^3.6.1",

frontend/src/components/Entry.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@
1616

1717
import { computed, signal } from "@preact/signals";
1818
import { createRef } from "preact";
19-
import { appState, computedState, lookupEntry, lookupFeed } from "../store/state";
20-
import { textCategories, Enclosure, Feed, getEntryTitle, getFeedTitle, toEntryId } from "../store/feed";
19+
import { appState, computedState, lookupEntryUrl, lookupFeed } from "../store/state";
20+
import { textCategories, Enclosure, getEntryTitle, getFeedTitle, toEntryId } from "../store/feed";
2121
import Icon from "@mdi/react";
2222
import renderMathInElement from "katex/contrib/auto-render";
2323
import hljs from "highlight.js";
@@ -118,7 +118,7 @@ export default function Entry() {
118118
element.querySelectorAll("a").forEach((el: HTMLElement) => {
119119
try {
120120
const url = new URL(el.getAttribute("href") || "");
121-
const e = lookupEntry(url.origin);
121+
const e = lookupEntryUrl(url.origin);
122122
if (e) {
123123
// replace external link with internal link
124124
el.setAttribute("href", `/?${new URLSearchParams({

0 commit comments

Comments
 (0)