Make titles optional on feeds and entries
This commit is contained in:
parent
bf40b803a9
commit
9059894021
8
drop_all.psql
Normal file
8
drop_all.psql
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/* !!! THIS DROPS ALL TABLES IN THE DATABASE WHICH DELETES ALL DATA IN THE DATABASE !!!
|
||||||
|
*
|
||||||
|
* ONLY RUN IN DEVELOPMENT!
|
||||||
|
*/
|
||||||
|
DROP TABLE _sqlx_migrations CASCADE;
|
||||||
|
DROP TABLE entries CASCADE;
|
||||||
|
DROP TABLE feeds CASCADE;
|
||||||
|
DROP TYPE feed_type;
|
@ -2,7 +2,7 @@ CREATE TYPE feed_type AS ENUM ('atom', 'rss');
|
|||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "feeds" (
|
CREATE TABLE IF NOT EXISTS "feeds" (
|
||||||
"id" SERIAL PRIMARY KEY NOT NULL,
|
"id" SERIAL PRIMARY KEY NOT NULL,
|
||||||
"title" VARCHAR(255) NOT NULL,
|
"title" VARCHAR(255),
|
||||||
"url" VARCHAR(2048) NOT NULL,
|
"url" VARCHAR(2048) NOT NULL,
|
||||||
"type" feed_type NOT NULL,
|
"type" feed_type NOT NULL,
|
||||||
"description" TEXT,
|
"description" TEXT,
|
||||||
@ -15,7 +15,7 @@ CREATE UNIQUE INDEX "feeds_url" ON "feeds" ("url");
|
|||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS "entries" (
|
CREATE TABLE IF NOT EXISTS "entries" (
|
||||||
"id" SERIAL PRIMARY KEY NOT NULL,
|
"id" SERIAL PRIMARY KEY NOT NULL,
|
||||||
"title" VARCHAR(255) NOT NULL,
|
"title" VARCHAR(255),
|
||||||
"url" VARCHAR(2048) NOT NULL,
|
"url" VARCHAR(2048) NOT NULL,
|
||||||
"description" TEXT,
|
"description" TEXT,
|
||||||
"feed_id" INTEGER REFERENCES "feeds"(id) NOT NULL,
|
"feed_id" INTEGER REFERENCES "feeds"(id) NOT NULL,
|
||||||
|
@ -32,7 +32,7 @@ enum Commands {
|
|||||||
struct AddFeed {
|
struct AddFeed {
|
||||||
#[argh(option)]
|
#[argh(option)]
|
||||||
/// title of the feed (max 255 characters)
|
/// title of the feed (max 255 characters)
|
||||||
title: String,
|
title: Option<String>,
|
||||||
#[argh(option)]
|
#[argh(option)]
|
||||||
/// URL of the feed (max 2048 characters)
|
/// URL of the feed (max 2048 characters)
|
||||||
url: String,
|
url: String,
|
||||||
@ -59,7 +59,7 @@ struct DeleteFeed {
|
|||||||
struct AddEntry {
|
struct AddEntry {
|
||||||
#[argh(option)]
|
#[argh(option)]
|
||||||
/// title of the entry (max 255 characters)
|
/// title of the entry (max 255 characters)
|
||||||
title: String,
|
title: Option<String>,
|
||||||
#[argh(option)]
|
#[argh(option)]
|
||||||
/// URL of the entry (max 2048 characters)
|
/// URL of the entry (max 2048 characters)
|
||||||
url: String,
|
url: String,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use feed_rs::parser;
|
use feed_rs::parser;
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use tracing::info;
|
use tracing::{info, warn};
|
||||||
|
|
||||||
use crate::models::feed::get_feeds;
|
use crate::models::feed::get_feeds;
|
||||||
use crate::models::entry::{upsert_entries, CreateEntry};
|
use crate::models::entry::{upsert_entries, CreateEntry};
|
||||||
@ -16,18 +16,17 @@ pub async fn crawl(pool: &PgPool) -> anyhow::Result<()> {
|
|||||||
let parsed_feed = parser::parse(&bytes[..])?;
|
let parsed_feed = parser::parse(&bytes[..])?;
|
||||||
let mut payload = Vec::with_capacity(parsed_feed.entries.len());
|
let mut payload = Vec::with_capacity(parsed_feed.entries.len());
|
||||||
for entry in parsed_feed.entries {
|
for entry in parsed_feed.entries {
|
||||||
|
if let Some(link) = entry.links.get(0) {
|
||||||
let entry = CreateEntry {
|
let entry = CreateEntry {
|
||||||
title: entry
|
title: entry.title.map(|t| t.content),
|
||||||
.title
|
url: link.href.clone(),
|
||||||
.map_or_else(|| "No title".to_string(), |t| t.content),
|
|
||||||
url: entry
|
|
||||||
.links
|
|
||||||
.get(0)
|
|
||||||
.map_or_else(|| "https://example.com".to_string(), |l| l.href.clone()),
|
|
||||||
description: entry.summary.map(|s| s.content),
|
description: entry.summary.map(|s| s.content),
|
||||||
feed_id: feed.id,
|
feed_id: feed.id,
|
||||||
};
|
};
|
||||||
payload.push(entry);
|
payload.push(entry);
|
||||||
|
} else {
|
||||||
|
warn!("Feed entry has no links: {:?}", entry);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let entries = upsert_entries(pool, payload).await?;
|
let entries = upsert_entries(pool, payload).await?;
|
||||||
info!("Created {} entries for feed {}", entries.len(), feed.id);
|
info!("Created {} entries for feed {}", entries.len(), feed.id);
|
||||||
|
@ -8,7 +8,7 @@ use crate::error::{Error, Result};
|
|||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct Entry {
|
pub struct Entry {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub title: String,
|
pub title: Option<String>,
|
||||||
pub url: String,
|
pub url: String,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub feed_id: i32,
|
pub feed_id: i32,
|
||||||
@ -20,7 +20,7 @@ pub struct Entry {
|
|||||||
#[derive(Debug, Deserialize, Validate)]
|
#[derive(Debug, Deserialize, Validate)]
|
||||||
pub struct CreateEntry {
|
pub struct CreateEntry {
|
||||||
#[validate(length(max = 255))]
|
#[validate(length(max = 255))]
|
||||||
pub title: String,
|
pub title: Option<String>,
|
||||||
#[validate(url)]
|
#[validate(url)]
|
||||||
pub url: String,
|
pub url: String,
|
||||||
#[validate(length(max = 524288))]
|
#[validate(length(max = 524288))]
|
||||||
@ -73,7 +73,7 @@ pub async fn create_entry(pool: &PgPool, payload: CreateEntry) -> Result<Entry>
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_entries(pool: &PgPool, payload: Vec<Create<Entry>) -> Result<Vec<Entry>> {
|
pub async fn create_entries(pool: &PgPool, payload: Vec<CreateEntry>) -> Result<Vec<Entry>> {
|
||||||
let mut titles = Vec::with_capacity(payload.len());
|
let mut titles = Vec::with_capacity(payload.len());
|
||||||
let mut urls = Vec::with_capacity(payload.len());
|
let mut urls = Vec::with_capacity(payload.len());
|
||||||
let mut descriptions: Vec<Option<String>> = Vec::with_capacity(payload.len());
|
let mut descriptions: Vec<Option<String>> = Vec::with_capacity(payload.len());
|
||||||
@ -91,7 +91,7 @@ pub async fn create_entries(pool: &PgPool, payload: Vec<Create<Entry>) -> Result
|
|||||||
title, url, description, feed_id, created_at, updated_at
|
title, url, description, feed_id, created_at, updated_at
|
||||||
) SELECT *, now(), now() FROM UNNEST($1::text[], $2::text[], $3::text[], $4::int[])
|
) SELECT *, now(), now() FROM UNNEST($1::text[], $2::text[], $3::text[], $4::int[])
|
||||||
RETURNING *",
|
RETURNING *",
|
||||||
titles.as_slice(),
|
titles.as_slice() as &[Option<String>],
|
||||||
urls.as_slice(),
|
urls.as_slice(),
|
||||||
descriptions.as_slice() as &[Option<String>],
|
descriptions.as_slice() as &[Option<String>],
|
||||||
feed_ids.as_slice(),
|
feed_ids.as_slice(),
|
||||||
@ -127,7 +127,7 @@ pub async fn upsert_entries(pool: &PgPool, payload: Vec<CreateEntry>) -> Result<
|
|||||||
) SELECT *, now(), now() FROM UNNEST($1::text[], $2::text[], $3::text[], $4::int[])
|
) SELECT *, now(), now() FROM UNNEST($1::text[], $2::text[], $3::text[], $4::int[])
|
||||||
ON CONFLICT DO NOTHING
|
ON CONFLICT DO NOTHING
|
||||||
RETURNING *",
|
RETURNING *",
|
||||||
titles.as_slice(),
|
titles.as_slice() as &[Option<String>],
|
||||||
urls.as_slice(),
|
urls.as_slice(),
|
||||||
descriptions.as_slice() as &[Option<String>],
|
descriptions.as_slice() as &[Option<String>],
|
||||||
feed_ids.as_slice(),
|
feed_ids.as_slice(),
|
||||||
|
@ -29,7 +29,7 @@ impl FromStr for FeedType {
|
|||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct Feed {
|
pub struct Feed {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub title: String,
|
pub title: Option<String>,
|
||||||
pub url: String,
|
pub url: String,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub feed_type: FeedType,
|
pub feed_type: FeedType,
|
||||||
@ -42,7 +42,7 @@ pub struct Feed {
|
|||||||
#[derive(Debug, Deserialize, Validate)]
|
#[derive(Debug, Deserialize, Validate)]
|
||||||
pub struct CreateFeed {
|
pub struct CreateFeed {
|
||||||
#[validate(length(max = 255))]
|
#[validate(length(max = 255))]
|
||||||
pub title: String,
|
pub title: Option<String>,
|
||||||
#[validate(url)]
|
#[validate(url)]
|
||||||
pub url: String,
|
pub url: String,
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
|
Loading…
Reference in New Issue
Block a user