Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
zaknesler committed Nov 1, 2024
1 parent 2978f01 commit 34f7889
Show file tree
Hide file tree
Showing 13 changed files with 380 additions and 339 deletions.
584 changes: 273 additions & 311 deletions Cargo.lock

Large diffs are not rendered by default.

Binary file modified bun.lockb
100644 → 100755
Binary file not shown.
10 changes: 10 additions & 0 deletions crates/blend-db/src/repo/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,16 @@ impl EntryRepo {
.map_err(|err| err.into())
}

pub async fn update_all_entries_as_read(&self) -> DbResult<bool> {
let rows_affected = sqlx::query("UPDATE entries SET read_at = ?1 WHERE read_at IS NULL")
.bind(Utc::now())
.execute(&self.db)
.await?
.rows_affected();

Ok(rows_affected > 0)
}

pub async fn update_entry_as_read(&self, entry_uuid: &uuid::Uuid) -> DbResult<bool> {
let rows_affected = sqlx::query("UPDATE entries SET read_at = ?1 WHERE uuid = ?2")
.bind(Utc::now())
Expand Down
11 changes: 11 additions & 0 deletions crates/blend-db/src/repo/feed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,15 @@ impl FeedRepo {

Ok(rows_affected > 0)
}

pub async fn update_feed_as_read(&self, feed_uuid: &uuid::Uuid) -> DbResult<bool> {
let rows_affected = sqlx::query("UPDATE entries SET read_at = ?1 WHERE feed_uuid = ?2")
.bind(Utc::now())
.bind(feed_uuid)
.execute(&self.db)
.await?
.rows_affected();

Ok(rows_affected > 0)
}
}
7 changes: 7 additions & 0 deletions crates/blend-web/src/router/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use uuid::Uuid;
pub fn router(ctx: crate::Context) -> Router {
Router::new()
.route("/", get(index))
.route("/read", post(update_all_read))
.route("/:entry_uuid", get(view))
.route("/:entry_uuid/read", post(update_read))
.route("/:entry_uuid/unread", post(update_unread))
Expand Down Expand Up @@ -49,6 +50,12 @@ async fn view(
Ok(Json(json!({ "data": entry })))
}

async fn update_all_read(State(ctx): State<crate::Context>) -> WebResult<impl IntoResponse> {
let success = repo::entry::EntryRepo::new(ctx.db).update_all_entries_as_read().await?;

Ok(Json(json!({ "success": success })))
}

async fn update_read(
State(ctx): State<crate::Context>,
Path(params): Path<ViewEntryParams>,
Expand Down
24 changes: 17 additions & 7 deletions crates/blend-web/src/router/feed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use axum::{
routing::{get, post},
Json, Router,
};
use blend_db::repo;
use blend_db::repo::{self, feed::FeedRepo};
use serde::Deserialize;
use serde_json::json;
use typeshare::typeshare;
Expand All @@ -20,13 +20,14 @@ pub fn router(ctx: crate::Context) -> Router {
.route("/refresh", post(refresh_feeds))
.route("/stats", get(stats))
.route("/:uuid", get(view))
.route("/:uuid/read", post(update_read))
.route("/:uuid/refresh", post(refresh_feed))
.route_layer(from_fn_with_state(ctx.clone(), crate::middleware::auth))
.with_state(ctx)
}

async fn index(State(ctx): State<crate::Context>) -> WebResult<impl IntoResponse> {
let feeds = repo::feed::FeedRepo::new(ctx.db).get_feeds().await?;
let feeds = FeedRepo::new(ctx.db).get_feeds().await?;
Ok(Json(json!({ "data": feeds })))
}

Expand All @@ -44,7 +45,7 @@ async fn create(
data.validate()?;

let parsed = blend_feed::parse_feed(&data.url).await?;
let feed = repo::feed::FeedRepo::new(ctx.db)
let feed = FeedRepo::new(ctx.db)
.create_feed(repo::feed::CreateFeedParams {
id: parsed.id,
title: parsed.title.unwrap_or_else(|| data.url.clone()),
Expand All @@ -64,7 +65,7 @@ async fn create(
}

async fn stats(State(ctx): State<crate::Context>) -> WebResult<impl IntoResponse> {
let stats = repo::feed::FeedRepo::new(ctx.db).get_stats().await?;
let stats = FeedRepo::new(ctx.db).get_stats().await?;

Ok(Json(json!({ "data": stats })))
}
Expand All @@ -78,19 +79,28 @@ async fn view(
State(ctx): State<crate::Context>,
Path(params): Path<ViewFeedParams>,
) -> WebResult<impl IntoResponse> {
let feed = repo::feed::FeedRepo::new(ctx.db)
let feed = FeedRepo::new(ctx.db)
.get_feed(params.uuid)
.await?
.ok_or_else(|| WebError::NotFoundError)?;

Ok(Json(json!({ "data": feed })))
}

async fn update_read(
State(ctx): State<crate::Context>,
Path(params): Path<ViewFeedParams>,
) -> WebResult<impl IntoResponse> {
let success = FeedRepo::new(ctx.db).update_feed_as_read(&params.uuid).await?;

Ok(Json(json!({ "success": success })))
}

async fn refresh_feed(
State(ctx): State<crate::Context>,
Path(params): Path<ViewFeedParams>,
) -> WebResult<impl IntoResponse> {
let feed = repo::feed::FeedRepo::new(ctx.db)
let feed = FeedRepo::new(ctx.db)
.get_feed(params.uuid)
.await?
.ok_or_else(|| WebError::NotFoundError)?;
Expand All @@ -108,7 +118,7 @@ async fn refresh_feed(
}

async fn refresh_feeds(State(ctx): State<crate::Context>) -> WebResult<impl IntoResponse> {
let feeds = repo::feed::FeedRepo::new(ctx.db).get_feeds().await?;
let feeds = FeedRepo::new(ctx.db).get_feeds().await?;

let notifier = ctx.notif_tx.lock().await;
let dispatcher = ctx.job_tx.lock().await;
Expand Down
34 changes: 17 additions & 17 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,39 +20,39 @@
"typeshare:generate": "typeshare ./ --lang typescript --output-file ./ui/src/types/bindings.ts && biome check --write ./ui/src/types/bindings.ts"
},
"dependencies": {
"@kobalte/core": "^0.13.5",
"@kobalte/core": "^0.13.7",
"@kobalte/tailwindcss": "^0.9.0",
"@solid-primitives/active-element": "^2.0.20",
"@solid-primitives/bounds": "^0.0.122",
"@solid-primitives/keyboard": "^1.2.8",
"@solid-primitives/resize-observer": "^2.0.26",
"@solid-primitives/scheduled": "^1.4.3",
"@solid-primitives/scroll": "^2.0.23",
"@solidjs/router": "^0.14.3",
"@tanstack/solid-query": "^5.52.2",
"@tanstack/solid-query-devtools": "^5.52.2",
"@solidjs/router": "^0.14.10",
"@tanstack/solid-query": "^5.59.16",
"@tanstack/solid-query-devtools": "^5.59.16",
"class-variance-authority": "^0.7.0",
"partysocket": "^1.0.2",
"solid-icons": "^1.1.0",
"solid-js": "^1.8.21",
"solid-js": "^1.9.3",
"solid-transition-group": "^0.2.3",
"wretch": "^2.9.0"
"wretch": "^2.10.0"
},
"devDependencies": {
"@biomejs/biome": "1.8.3",
"@tailwindcss/forms": "^0.5.7",
"@tailwindcss/typography": "^0.5.14",
"@total-typescript/ts-reset": "^0.6.0",
"@vitest/coverage-v8": "^2.0.5",
"@biomejs/biome": "1.9.4",
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"@total-typescript/ts-reset": "^0.6.1",
"@vitest/coverage-v8": "^2.1.3",
"autoprefixer": "^10.4.20",
"jsdom": "^25.0.0",
"postcss": "^8.4.41",
"jsdom": "^25.0.1",
"postcss": "^8.4.47",
"solid-devtools": "^0.30.1",
"tailwindcss": "^3.4.10",
"typescript": "^5.5.4",
"vite": "^5.4.2",
"tailwindcss": "^3.4.14",
"typescript": "^5.6.3",
"vite": "^5.4.10",
"vite-plugin-solid": "^2.10.2",
"vite-tsconfig-paths": "^5.0.1",
"vitest": "^2.0.5"
"vitest": "^2.1.3"
}
}
2 changes: 2 additions & 0 deletions ui/src/api/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export const getEntry = async (entry_uuid: string, signal: AbortSignal) =>
.json<ApiResponse<Entry>>()
.then(res => res.data);

export const updateAllEntriesAsRead = async () => wretch(apiUrl('/entries/read')).post().json<ApiSuccessResponse>();

export const updateEntryAsRead = async (entry_uuid: string) =>
wretch(apiUrl(`/entries/${entry_uuid}/read`))
.post()
Expand Down
5 changes: 5 additions & 0 deletions ui/src/api/feeds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,8 @@ export const refreshFeed = async (uuid: string) =>
wretch(apiUrl(`/feeds/${uuid}/refresh`))
.post()
.json<ApiSuccessResponse>();

export const updateFeedAsRead = async (uuid: string) =>
wretch(apiUrl(`/feeds/${uuid}/read`))
.post()
.json<ApiSuccessResponse>();
6 changes: 4 additions & 2 deletions ui/src/components/feed/feed-info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { HiOutlineCheck } from 'solid-icons/hi';
import { type Component, Match, Switch, createSignal } from 'solid-js';
import { getFeed } from '~/api/feeds';
import { QUERY_KEYS } from '~/constants/query';
import { useFeedRead } from '~/hooks/queries/use-feed-read';
import { FeedMenu } from '../menus/menu-feed';
import { IconButton } from '../ui/button/icon-button';
import { FeedHeader } from './feed-header';
Expand All @@ -12,6 +13,7 @@ type FeedInfoProps = {
};

export const FeedInfo: Component<FeedInfoProps> = props => {
const markFeedAsRead = useFeedRead();
const feed = createQuery(() => ({
queryKey: [QUERY_KEYS.FEEDS_VIEW, props.uuid],
queryFn: () => getFeed(props.uuid),
Expand All @@ -33,14 +35,14 @@ export const FeedInfo: Component<FeedInfoProps> = props => {

<Match when={feed.isSuccess}>
<div class="flex w-full items-start gap-2">
<FeedHeader title={feed.data?.title_display || feed.data?.title} />
<FeedHeader title={feed.data!.title_display || feed.data!.title} />

<IconButton
disabled
icon={HiOutlineCheck}
tooltip="Mark feed as read"
class="size-8 rounded-lg text-gray-500 md:size-6 md:rounded-md"
iconClass="size-5 md:size-4"
onClick={() => markFeedAsRead(feed.data!.uuid)}
/>

<FeedMenu
Expand Down
10 changes: 8 additions & 2 deletions ui/src/components/feed/feed-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Transition } from 'solid-transition-group';
import * as feedClasses from '~/constants/ui/feed';
import { useNotifications } from '~/contexts/notification-context';
import { useQueryState } from '~/contexts/query-state-context';
import { useFeedRead } from '~/hooks/queries/use-feed-read';
import { useFeedsStats } from '~/hooks/queries/use-feeds-stats';
import { useRefreshFeed } from '~/hooks/queries/use-refresh-feed';
import { useRefreshFeeds } from '~/hooks/queries/use-refresh-feeds';
Expand All @@ -33,6 +34,7 @@ export const FeedItem: Component<FeedItemProps> = props => {

const stats = useFeedsStats();
const refreshFeed = useRefreshFeed();
const markFeedAsRead = useFeedRead();
const notifications = useNotifications();

const getPath = createMemo(() => `/feeds/${props.feed.uuid}`);
Expand Down Expand Up @@ -62,11 +64,15 @@ export const FeedItem: Component<FeedItemProps> = props => {
/>
)}
>
<ContextMenu.Item label="Mark feed as read" disabled icon={HiOutlineCheck} />
<ContextMenu.Item
label="Mark feed as read"
icon={HiOutlineCheck}
onClick={() => markFeedAsRead(props.feed.uuid)}
/>
<ContextMenu.Item
label="Refresh feed"
onClick={() => refreshFeed(props.feed.uuid)}
icon={HiOutlineArrowPath}
onClick={() => refreshFeed(props.feed.uuid)}
iconClass={isRefreshing() && 'animate-spin'}
disabled={isRefreshing()}
/>
Expand Down
2 changes: 2 additions & 0 deletions ui/src/constants/query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ export const QUERY_KEYS = {
FEEDS_CREATE: 'feeds.create',
FEEDS_VIEW: 'feeds.view',
FEEDS_VIEW_REFRESH: 'feeds.view.refresh',
FEEDS_VIEW_READ: 'feeds.view.read',
FEEDS_STATS: 'feeds.stats',

// Entries
ENTRIES_INDEX: 'entries.index',
ENTRIES_INDEX_READ: 'entries.index.read',
ENTRIES_VIEW: 'entries.view',
ENTRIES_VIEW_READ: 'entries.view.read',
ENTRIES_VIEW_UNREAD: 'entries.view.unread',
Expand Down
24 changes: 24 additions & 0 deletions ui/src/hooks/queries/use-feed-read.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { createMutation, useQueryClient } from '@tanstack/solid-query';
import { updateFeedAsRead } from '~/api/feeds';
import { QUERY_KEYS } from '~/constants/query';
import { useQueryState } from '~/contexts/query-state-context';
import { useInvalidateStats } from '~/hooks/queries/use-invalidate-stats';

export const useFeedRead = () => {
const state = useQueryState();
const queryClient = useQueryClient();
const invalidateStats = useInvalidateStats();

const markAsRead = createMutation(() => ({
mutationKey: [QUERY_KEYS.FEEDS_VIEW_READ],
mutationFn: updateFeedAsRead,
}));

const invalidate = (feed_uuid: string) => {
invalidateStats();
queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.FEEDS_VIEW, feed_uuid] });
queryClient.invalidateQueries({ queryKey: [QUERY_KEYS.ENTRIES_INDEX, undefined, state.getView()] });
};

return (feed_uuid: string) => markAsRead.mutateAsync(feed_uuid).then(() => invalidate(feed_uuid));
};

0 comments on commit 34f7889

Please sign in to comment.