Create cli binary

Just has `add-feed` command so far.
This commit is contained in:
Tyler Hallada 2023-05-09 00:08:55 -04:00
parent b2a5bf5882
commit 89fdf8f95a
9 changed files with 159 additions and 28 deletions

29
Cargo.lock generated
View File

@ -37,6 +37,34 @@ version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
[[package]]
name = "argh"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab257697eb9496bf75526f0217b5ed64636a9cfafa78b8365c71bd283fcef93e"
dependencies = [
"argh_derive",
"argh_shared",
]
[[package]]
name = "argh_derive"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b382dbd3288e053331f03399e1db106c9fb0d8562ad62cb04859ae926f324fa6"
dependencies = [
"argh_shared",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "argh_shared"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64cb94155d965e3d37ffbbe7cc5b82c3dd79dd33bd48e536f73d2cfb8d85506f"
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.68" version = "0.1.68"
@ -225,6 +253,7 @@ name = "crawlect"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"argh",
"axum", "axum",
"chrono", "chrono",
"dotenvy", "dotenvy",

View File

@ -2,18 +2,30 @@
name = "crawlect" name = "crawlect"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
default-run = "crawlect"
[lib]
name = "lib"
path = "src/lib.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
anyhow = "1" anyhow = "1"
argh = "0.1"
axum = "0.6" axum = "0.6"
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
dotenvy = "0.15" dotenvy = "0.15"
reqwest = { version = "0.11", features = ["json"] } reqwest = { version = "0.11", features = ["json"] }
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_with = "3" serde_with = "3"
sqlx = { version = "0.6", features = ["runtime-tokio-native-tls", "postgres", "macros", "migrate", "chrono"] } sqlx = { version = "0.6", features = [
"runtime-tokio-native-tls",
"postgres",
"macros",
"migrate",
"chrono",
] }
thiserror = "1" thiserror = "1"
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
tower = "0.4" tower = "0.4"

66
src/bin/cli.rs Normal file
View File

@ -0,0 +1,66 @@
use anyhow::Result;
use argh::FromArgs;
use dotenvy::dotenv;
use tracing::info;
use sqlx::postgres::PgPoolOptions;
use std::env;
use lib::models::feed::{CreateFeed, FeedType};
use lib::commands::add_feed::add_feed;
#[derive(FromArgs)]
/// CLI for crawlect
struct Args {
#[argh(subcommand)]
commands: Commands,
}
#[derive(FromArgs)]
#[argh(subcommand)]
enum Commands {
AddFeed(AddFeed),
}
#[derive(FromArgs)]
/// Add a feed to the database
#[argh(subcommand, name = "add-feed")]
struct AddFeed {
#[argh(option)]
/// title of the feed (max 255 characters)
title: String,
#[argh(option)]
/// URL of the feed (max 2048 characters)
url: String,
#[argh(option, long = "type")]
/// type of the feed ('rss' or 'atom')
feed_type: FeedType,
#[argh(option)]
/// description of the feed
description: Option<String>,
}
#[tokio::main]
pub async fn main() -> Result<()> {
dotenv().ok();
tracing_subscriber::fmt::init();
let pool = PgPoolOptions::new()
.max_connections(env::var("DATABASE_MAX_CONNECTIONS")?.parse()?)
.connect(&env::var("DATABASE_URL")?)
.await?;
let args: Args = argh::from_env();
if let Commands::AddFeed(add_feed_args) = args.commands {
add_feed(pool, CreateFeed {
title: add_feed_args.title,
url: add_feed_args.url,
feed_type: add_feed_args.feed_type,
description: add_feed_args.description,
}).await?;
}
Ok(())
}

8
src/commands/add_feed.rs Normal file
View File

@ -0,0 +1,8 @@
use sqlx::PgPool;
use crate::models::feed::{create_feed, CreateFeed, Feed};
use crate::error::Result;
pub async fn add_feed(pool: PgPool, payload: CreateFeed) -> Result<Feed> {
create_feed(pool, payload).await
}

1
src/commands/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod add_feed;

4
src/lib.rs Normal file
View File

@ -0,0 +1,4 @@
pub mod commands;
pub mod error;
pub mod handlers;
pub mod models;

View File

@ -9,9 +9,7 @@ use tower::ServiceBuilder;
use tower_http::trace::TraceLayer; use tower_http::trace::TraceLayer;
use tracing::debug; use tracing::debug;
mod error; use lib::handlers;
mod handlers;
mod models;
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {

View File

@ -1,3 +1,5 @@
use std::str::FromStr;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::PgPool; use sqlx::PgPool;
@ -13,29 +15,40 @@ pub enum FeedType {
Rss, Rss,
} }
impl FromStr for FeedType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"atom" => Ok(FeedType::Atom),
"rss" => Ok(FeedType::Rss),
_ => Err(format!("invalid feed type: {}", s)),
}
}
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct Feed { pub struct Feed {
id: i32, pub id: i32,
title: String, pub title: String,
url: String, pub url: String,
#[serde(rename = "type")] #[serde(rename = "type")]
feed_type: FeedType, pub feed_type: FeedType,
description: Option<String>, pub description: Option<String>,
created_at: NaiveDateTime, pub created_at: NaiveDateTime,
updated_at: NaiveDateTime, pub updated_at: NaiveDateTime,
deleted_at: Option<NaiveDateTime>, pub deleted_at: Option<NaiveDateTime>,
} }
#[derive(Debug, Deserialize, Validate)] #[derive(Debug, Deserialize, Validate)]
pub struct CreateFeed { pub struct CreateFeed {
#[validate(length(max = 255))] #[validate(length(max = 255))]
title: String, pub title: String,
#[validate(url)] #[validate(url)]
url: String, pub url: String,
#[serde(rename = "type")] #[serde(rename = "type")]
feed_type: FeedType, pub feed_type: FeedType,
#[validate(length(max = 524288))] #[validate(length(max = 524288))]
description: Option<String>, pub description: Option<String>,
} }
pub async fn get_feed(pool: PgPool, id: i32) -> Result<Feed> { pub async fn get_feed(pool: PgPool, id: i32) -> Result<Feed> {

View File

@ -7,26 +7,26 @@ use crate::error::{Error, Result};
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct Item { pub struct Item {
id: i32, pub id: i32,
title: String, pub title: String,
url: String, pub url: String,
description: Option<String>, pub description: Option<String>,
feed_id: i32, pub feed_id: i32,
created_at: NaiveDateTime, pub created_at: NaiveDateTime,
updated_at: NaiveDateTime, pub updated_at: NaiveDateTime,
deleted_at: Option<NaiveDateTime>, pub deleted_at: Option<NaiveDateTime>,
} }
#[derive(Debug, Deserialize, Validate)] #[derive(Debug, Deserialize, Validate)]
pub struct CreateItem { pub struct CreateItem {
#[validate(length(max = 255))] #[validate(length(max = 255))]
title: String, pub title: String,
#[validate(url)] #[validate(url)]
url: String, pub url: String,
#[validate(length(max = 524288))] #[validate(length(max = 524288))]
description: Option<String>, pub description: Option<String>,
#[validate(range(min = 1))] #[validate(range(min = 1))]
feed_id: i32, pub feed_id: i32,
} }
pub async fn get_item(pool: PgPool, id: i32) -> Result<Item> { pub async fn get_item(pool: PgPool, id: i32) -> Result<Item> {