From bb090fb05f1ff859ef0c62a81eb22badf6bdc60c Mon Sep 17 00:00:00 2001 From: Zak Nesler Date: Wed, 24 Apr 2024 19:58:18 -0400 Subject: [PATCH] add repo and basic parsing --- ...5341f965207e340dd30721078f24cf0cd396d.json | 44 ------------- ...da42bbbdb5fb3c83ce9f08b7434f4c892b78b.json | 12 ---- Cargo.lock | 12 ++++ Cargo.toml | 1 + crates/blend-context/Cargo.toml | 9 +++ .../context.rs => blend-context/src/lib.rs} | 0 crates/blend-db/Cargo.toml | 1 + ...146_feeds.sql => 20240422000000_feeds.sql} | 4 +- crates/blend-db/src/lib.rs | 1 + crates/blend-db/src/model/feed.rs | 11 ++-- crates/blend-db/src/repo/feed.rs | 46 +++++++++++++ crates/blend-db/src/repo/mod.rs | 1 + crates/blend-web/Cargo.toml | 1 + crates/blend-web/src/error.rs | 3 + crates/blend-web/src/lib.rs | 4 +- crates/blend-web/src/middleware/auth.rs | 4 +- crates/blend-web/src/router/feed.rs | 64 ++++++------------- crates/blend-web/src/router/mod.rs | 5 +- crates/blend-web/src/router/user.rs | 3 +- src/main.rs | 2 +- ui/src/api/feeds.ts | 4 +- ui/src/components/demo.tsx | 24 +++---- 22 files changed, 125 insertions(+), 131 deletions(-) delete mode 100644 .sqlx/query-14f422398a6bab143a4b7204e1f5341f965207e340dd30721078f24cf0cd396d.json delete mode 100644 .sqlx/query-14ffb21f2a2d6a5f70a6a28f01eda42bbbdb5fb3c83ce9f08b7434f4c892b78b.json create mode 100644 crates/blend-context/Cargo.toml rename crates/{blend-web/src/context.rs => blend-context/src/lib.rs} (100%) rename crates/blend-db/migrations/{20240422224146_feeds.sql => 20240422000000_feeds.sql} (67%) create mode 100644 crates/blend-db/src/repo/feed.rs create mode 100644 crates/blend-db/src/repo/mod.rs diff --git a/.sqlx/query-14f422398a6bab143a4b7204e1f5341f965207e340dd30721078f24cf0cd396d.json b/.sqlx/query-14f422398a6bab143a4b7204e1f5341f965207e340dd30721078f24cf0cd396d.json deleted file mode 100644 index 4f58ffb7..00000000 --- a/.sqlx/query-14f422398a6bab143a4b7204e1f5341f965207e340dd30721078f24cf0cd396d.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "db_name": "SQLite", - "query": "SELECT * FROM feeds", - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int64" - }, - { - "name": "title", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "url", - "ordinal": 2, - "type_info": "Text" - }, - { - "name": "published_at", - "ordinal": 3, - "type_info": "Text" - }, - { - "name": "updated_at", - "ordinal": 4, - "type_info": "Text" - } - ], - "parameters": { - "Right": 0 - }, - "nullable": [ - false, - true, - true, - true, - true - ] - }, - "hash": "14f422398a6bab143a4b7204e1f5341f965207e340dd30721078f24cf0cd396d" -} diff --git a/.sqlx/query-14ffb21f2a2d6a5f70a6a28f01eda42bbbdb5fb3c83ce9f08b7434f4c892b78b.json b/.sqlx/query-14ffb21f2a2d6a5f70a6a28f01eda42bbbdb5fb3c83ce9f08b7434f4c892b78b.json deleted file mode 100644 index 489c59ea..00000000 --- a/.sqlx/query-14ffb21f2a2d6a5f70a6a28f01eda42bbbdb5fb3c83ce9f08b7434f4c892b78b.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "\n INSERT INTO feeds ( title, url )\n VALUES ( ?1, ?2 )\n ", - "describe": { - "columns": [], - "parameters": { - "Right": 2 - }, - "nullable": [] - }, - "hash": "14ffb21f2a2d6a5f70a6a28f01eda42bbbdb5fb3c83ce9f08b7434f4c892b78b" -} diff --git a/Cargo.lock b/Cargo.lock index 3f5af3ce..59f837e6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -298,6 +298,7 @@ name = "blend" version = "0.0.0" dependencies = [ "blend-config", + "blend-context", "blend-db", "blend-web", "clap", @@ -319,6 +320,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "blend-context" +version = "0.0.0" +dependencies = [ + "blend-config", + "sqlx", + "thiserror", +] + [[package]] name = "blend-crypto" version = "0.0.0" @@ -337,6 +347,7 @@ name = "blend-db" version = "0.0.0" dependencies = [ "blend-config", + "blend-context", "chrono", "futures", "serde", @@ -366,6 +377,7 @@ dependencies = [ "axum", "axum-embed", "blend-config", + "blend-context", "blend-crypto", "blend-db", "blend-parse", diff --git a/Cargo.toml b/Cargo.toml index 2d4683fe..88620a4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ edition = { workspace = true } [dependencies] blend-config = { path = "./crates/blend-config" } +blend-context = { path = "./crates/blend-context" } blend-db = { path = "./crates/blend-db" } blend-web = { path = "./crates/blend-web" } clap = { version = "4", features = ["derive"] } diff --git a/crates/blend-context/Cargo.toml b/crates/blend-context/Cargo.toml new file mode 100644 index 00000000..8c545a5d --- /dev/null +++ b/crates/blend-context/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "blend-context" +version.workspace = true +edition.workspace = true + +[dependencies] +blend-config = { path = "../blend-config" } +thiserror = { workspace = true } +sqlx = { workspace = true, features = ["sqlite"] } diff --git a/crates/blend-web/src/context.rs b/crates/blend-context/src/lib.rs similarity index 100% rename from crates/blend-web/src/context.rs rename to crates/blend-context/src/lib.rs diff --git a/crates/blend-db/Cargo.toml b/crates/blend-db/Cargo.toml index 50a179b0..96f3c636 100644 --- a/crates/blend-db/Cargo.toml +++ b/crates/blend-db/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true [dependencies] blend-config = { path = "../blend-config" } +blend-context = { path = "../blend-context" } chrono = { workspace = true, features = ["serde"] } futures = "0" serde = { workspace = true, features = ["derive"] } diff --git a/crates/blend-db/migrations/20240422224146_feeds.sql b/crates/blend-db/migrations/20240422000000_feeds.sql similarity index 67% rename from crates/blend-db/migrations/20240422224146_feeds.sql rename to crates/blend-db/migrations/20240422000000_feeds.sql index 25fbd536..7ca6c613 100644 --- a/crates/blend-db/migrations/20240422224146_feeds.sql +++ b/crates/blend-db/migrations/20240422000000_feeds.sql @@ -2,6 +2,6 @@ CREATE TABLE IF NOT EXISTS feeds ( id INTEGER PRIMARY KEY NOT NULL, title TEXT, url TEXT, - published_at TEXT, - updated_at TEXT + published_at DATETIME, + updated_at DATETIME ); diff --git a/crates/blend-db/src/lib.rs b/crates/blend-db/src/lib.rs index 90e171e9..bb432d84 100644 --- a/crates/blend-db/src/lib.rs +++ b/crates/blend-db/src/lib.rs @@ -1,3 +1,4 @@ pub mod client; pub mod error; pub mod model; +pub mod repo; diff --git a/crates/blend-db/src/model/feed.rs b/crates/blend-db/src/model/feed.rs index 2f1f3073..a7371cac 100644 --- a/crates/blend-db/src/model/feed.rs +++ b/crates/blend-db/src/model/feed.rs @@ -1,16 +1,15 @@ +use chrono::NaiveDateTime; use serde::{Deserialize, Serialize}; +use sqlx::prelude::FromRow; use ts_rs::TS; #[derive(TS)] #[ts(export, export_to = "../../../ui/src/types/bindings/feed.ts")] -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, FromRow)] pub struct Feed { pub id: i64, pub url: Option, pub title: Option, - pub published_at: Option, - pub updated_at: Option, - // published_at: Option>, - // updated_at: Option>, - // entries: Vec, + pub published_at: Option, + pub updated_at: Option, } diff --git a/crates/blend-db/src/repo/feed.rs b/crates/blend-db/src/repo/feed.rs new file mode 100644 index 00000000..f40c575f --- /dev/null +++ b/crates/blend-db/src/repo/feed.rs @@ -0,0 +1,46 @@ +use crate::{error::DbResult, model}; +use chrono::{DateTime, Utc}; + +pub struct FeedRepo { + ctx: blend_context::Context, +} + +pub struct CreateFeedParams { + pub title: Option, + pub url: Option, + pub published_at: Option>, + pub updated_at: Option>, +} + +impl FeedRepo { + pub fn new(ctx: blend_context::Context) -> Self { + Self { ctx } + } + + pub async fn get_feeds(&self) -> DbResult> { + sqlx::query_as!(model::Feed, "SELECT * FROM feeds") + .fetch_all(&self.ctx.db) + .await + .map_err(|err| err.into()) + } + + pub async fn create_feed(&self, data: CreateFeedParams) -> DbResult { + let mut conn = self.ctx.db.acquire().await?; + + let id: i64 = sqlx::query!( + r#" + INSERT INTO feeds ( title, url, published_at, updated_at ) + VALUES ( ?1, ?2, ?3, ?4 ) + "#, + data.title, + data.url, + data.published_at, + data.updated_at, + ) + .execute(&mut *conn) + .await? + .last_insert_rowid(); + + Ok(id) + } +} diff --git a/crates/blend-db/src/repo/mod.rs b/crates/blend-db/src/repo/mod.rs new file mode 100644 index 00000000..7065d2ba --- /dev/null +++ b/crates/blend-db/src/repo/mod.rs @@ -0,0 +1 @@ +pub mod feed; diff --git a/crates/blend-web/Cargo.toml b/crates/blend-web/Cargo.toml index 18180c55..0b0f3407 100644 --- a/crates/blend-web/Cargo.toml +++ b/crates/blend-web/Cargo.toml @@ -5,6 +5,7 @@ edition.workspace = true [dependencies] blend-config = { path = "../blend-config" } +blend-context = { path = "../blend-context" } blend-crypto = { path = "../blend-crypto" } blend-parse = { path = "../blend-parse" } blend-db = { path = "../blend-db" } diff --git a/crates/blend-web/src/error.rs b/crates/blend-web/src/error.rs index 83f03d6b..835af989 100644 --- a/crates/blend-web/src/error.rs +++ b/crates/blend-web/src/error.rs @@ -38,6 +38,9 @@ pub enum WebError { #[error(transparent)] CryptoError(#[from] blend_crypto::error::CryptoError), + #[error(transparent)] + DbError(#[from] blend_db::error::DbError), + #[error(transparent)] ParseError(#[from] blend_parse::error::ParseError), } diff --git a/crates/blend-web/src/lib.rs b/crates/blend-web/src/lib.rs index 5ebea4fd..436c9221 100644 --- a/crates/blend-web/src/lib.rs +++ b/crates/blend-web/src/lib.rs @@ -1,18 +1,16 @@ use self::error::WebResult; use axum::http::{header, HeaderValue, Method}; -use context::Context; use tokio::net::TcpListener; use tower_cookies::CookieManagerLayer; use tower_http::{cors, trace::TraceLayer}; -pub mod context; pub mod error; mod middleware; mod response; mod router; mod util; -pub async fn serve(ctx: Context) -> WebResult<()> { +pub async fn serve(ctx: blend_context::Context) -> WebResult<()> { tracing::info!( "Starting web server on {}:{}", ctx.blend.config.web.host, diff --git a/crates/blend-web/src/middleware/auth.rs b/crates/blend-web/src/middleware/auth.rs index 0ebfcf7d..39e61c8e 100644 --- a/crates/blend-web/src/middleware/auth.rs +++ b/crates/blend-web/src/middleware/auth.rs @@ -1,11 +1,11 @@ -use crate::{context::Context, error::WebResult, router::JWT_COOKIE}; +use crate::{error::WebResult, router::JWT_COOKIE}; use axum::{body::Body, extract::State, http::Request, middleware::Next, response::IntoResponse}; use blend_crypto::jwt; use tower_cookies::Cookies; pub async fn middleware( cookies: Cookies, - State(ctx): State, + State(ctx): State, req: Request, next: Next, ) -> WebResult { diff --git a/crates/blend-web/src/router/feed.rs b/crates/blend-web/src/router/feed.rs index 01d48bd4..482a6c9c 100644 --- a/crates/blend-web/src/router/feed.rs +++ b/crates/blend-web/src/router/feed.rs @@ -1,19 +1,18 @@ -use crate::{context::Context, error::WebResult}; +use crate::error::WebResult; use axum::{ extract::State, response::IntoResponse, routing::{get, post}, Json, Router, }; -use blend_db::model; -use serde::{Deserialize, Serialize}; +use blend_db::repo; +use serde::Deserialize; use serde_json::json; -pub fn router(ctx: Context) -> Router { +pub fn router(ctx: blend_context::Context) -> Router { Router::new() .route("/", get(index)) - .route("/create", get(create)) - .route("/parse", post(parse)) + .route("/add", post(add)) // .route_layer(middleware::from_fn_with_state( // ctx.clone(), // crate::middleware::auth::middleware, @@ -21,51 +20,30 @@ pub fn router(ctx: Context) -> Router { .with_state(ctx) } -async fn index(State(ctx): State) -> WebResult { - let feeds: Vec = sqlx::query_as!(model::Feed, "SELECT * FROM feeds") - .fetch_all(&ctx.db) - .await?; - +async fn index(State(ctx): State) -> WebResult { + let feeds = repo::feed::FeedRepo::new(ctx).get_feeds().await?; Ok(Json(json!({ "data": feeds }))) } -async fn create(State(ctx): State) -> WebResult { - let mut conn = ctx.db.acquire().await?; - - let title = "Rust Blog"; - let url = "https://blog.rust-lang.org/feed.xml"; - - // Insert the task, then obtain the ID of this row - sqlx::query!( - r#" - INSERT INTO feeds ( title, url ) - VALUES ( ?1, ?2 ) - "#, - title, - url - ) - .execute(&mut *conn) - .await? - .last_insert_rowid(); - - Ok("ok") -} - #[derive(Debug, Deserialize)] -struct ParseFeedParams { +struct AddFeedParams { url: String, } -async fn parse(Json(data): Json) -> WebResult { +async fn add( + State(ctx): State, + Json(data): Json, +) -> WebResult { let parsed = blend_parse::parse_url(&data.url).await?; - let title = parsed.title.map(|title| title.content); - - #[derive(Debug, Serialize)] - struct Response { - title: Option, - } + let feed = repo::feed::FeedRepo::new(ctx) + .create_feed(repo::feed::CreateFeedParams { + title: parsed.title.map(|title| title.content), + url: Some("https://blog.rust-lang.org/feed.xml".to_string()), + published_at: parsed.published, + updated_at: parsed.updated, + }) + .await?; - let res = Response { title }; - Ok(Json(json!({"data": res}))) + Ok(Json(json!({ "data": feed }))) } diff --git a/crates/blend-web/src/router/mod.rs b/crates/blend-web/src/router/mod.rs index 65ea9b51..3d195414 100644 --- a/crates/blend-web/src/router/mod.rs +++ b/crates/blend-web/src/router/mod.rs @@ -1,5 +1,4 @@ use super::error::WebResult; -use crate::context::Context; use axum::{response::IntoResponse, routing::get, Router}; mod feed; @@ -9,14 +8,14 @@ mod user; pub const JWT_COOKIE: &str = "blend_jwt"; // pub const CSRF_COOKIE: &str = "blend_csrf"; -pub fn router(ctx: Context) -> Router { +pub fn router(ctx: blend_context::Context) -> Router { Router::new() .with_state(ctx.clone()) .nest("/api", api_router(ctx)) .merge(ui::router()) } -pub fn api_router(ctx: Context) -> Router { +pub fn api_router(ctx: blend_context::Context) -> Router { Router::new() .route("/", get(index)) .with_state(ctx.clone()) diff --git a/crates/blend-web/src/router/user.rs b/crates/blend-web/src/router/user.rs index 6c80051c..4656c1b1 100644 --- a/crates/blend-web/src/router/user.rs +++ b/crates/blend-web/src/router/user.rs @@ -1,7 +1,6 @@ -use crate::context::Context; use axum::{middleware, response::IntoResponse, routing::get, Router}; -pub fn router(ctx: Context) -> Router { +pub fn router(ctx: blend_context::Context) -> Router { Router::new() .route("/", get(index)) .route_layer(middleware::from_fn_with_state( diff --git a/src/main.rs b/src/main.rs index 96f47137..450aea69 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,7 +31,7 @@ async fn main() -> error::BlendResult<()> { crate::args::Command::Web => { let blend = blend_config::parse(args.config)?; let db = blend_db::client::init(blend.clone()).await?; - let context = blend_web::context::Context { blend, db }; + let context = blend_context::Context { blend, db }; blend_web::serve(context).await?; } diff --git a/ui/src/api/feeds.ts b/ui/src/api/feeds.ts index ff94f6df..38c1b9ae 100644 --- a/ui/src/api/feeds.ts +++ b/ui/src/api/feeds.ts @@ -11,11 +11,11 @@ export const getFeeds = async () => { return res.data.data; }; -export const parseFeed = async (params: { url: string }) => { +export const addFeed = async (params: { url: string }) => { type Response = ApiResponse<{ title: string; }>; - const res = await axios.post(apiUrl('/feeds/parse'), params); + const res = await axios.post(apiUrl('/feeds/add'), params); return res.data.data; }; diff --git a/ui/src/components/demo.tsx b/ui/src/components/demo.tsx index 52af288a..4c36b2df 100644 --- a/ui/src/components/demo.tsx +++ b/ui/src/components/demo.tsx @@ -1,19 +1,21 @@ -import { createMutation } from '@tanstack/solid-query'; +import { createMutation, useQueryClient } from '@tanstack/solid-query'; import { type Component, Match, Switch, createSignal } from 'solid-js'; -import { parseFeed } from '../api/feeds'; +import { addFeed } from '../api/feeds'; import { Button } from './form/button'; export const Demo: Component = () => { + const queryClient = useQueryClient(); const [input, setInput] = createSignal('https://blog.rust-lang.org/feed.xml'); - const parse = createMutation(() => ({ - mutationKey: ['feed.parse'], - mutationFn: parseFeed, + const add = createMutation(() => ({ + mutationKey: ['feed.add'], + mutationFn: addFeed, })); const handleClick = async () => { if (!input()) return; - parse.mutateAsync({ url: input() }); + await add.mutateAsync({ url: input() }); + queryClient.invalidateQueries({ queryKey: ['feeds'] }); }; return ( @@ -29,16 +31,16 @@ export const Demo: Component = () => { - +

Loading...

- -

Error: {parse.error?.message}

+ +

Error: {add.error?.message}

- -
{JSON.stringify(parse.data, null, 2)}
+ +
{JSON.stringify(add.data, null, 2)}