added telemetry
parent
b4843108fc
commit
5a18e5728e
|
@ -293,17 +293,6 @@ dependencies = [
|
|||
"quick-xml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi 0.1.19",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
|
@ -925,19 +914,6 @@ dependencies = [
|
|||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"humantime",
|
||||
"log",
|
||||
"regex",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
|
@ -1129,6 +1105,16 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gethostname"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getopts"
|
||||
version = "0.2.21"
|
||||
|
@ -1203,15 +1189,6 @@ version = "0.14.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.2"
|
||||
|
@ -1275,12 +1252,6 @@ version = "1.0.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.27"
|
||||
|
@ -1672,7 +1643,7 @@ version = "1.16.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
||||
dependencies = [
|
||||
"hermit-abi 0.3.2",
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
|
@ -2283,11 +2254,9 @@ dependencies = [
|
|||
"diesel",
|
||||
"diesel-connection",
|
||||
"dotenv",
|
||||
"env_logger",
|
||||
"futures",
|
||||
"hmac",
|
||||
"jwt",
|
||||
"log",
|
||||
"once_cell",
|
||||
"reqwest",
|
||||
"rss",
|
||||
|
@ -2298,9 +2267,11 @@ dependencies = [
|
|||
"serde_json",
|
||||
"sha2",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-actix-web",
|
||||
"tracing-appender",
|
||||
"tracing-log",
|
||||
"tracing-bunyan-formatter",
|
||||
"tracing-log 0.2.0",
|
||||
"tracing-subscriber",
|
||||
"uuid",
|
||||
]
|
||||
|
@ -2709,15 +2680,6 @@ dependencies = [
|
|||
"utf-8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thin-slice"
|
||||
version = "0.1.1"
|
||||
|
@ -2903,11 +2865,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
|
|||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.37"
|
||||
version = "0.1.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
|
||||
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
|
@ -2951,15 +2912,44 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.31"
|
||||
name = "tracing-bunyan-formatter"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
|
||||
checksum = "b5c266b9ac83dedf0e0385ad78514949e6d89491269e7065bee51d2bb8ec7373"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"gethostname",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"time",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log 0.1.4",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-log"
|
||||
version = "0.2.0"
|
||||
|
@ -2986,7 +2976,7 @@ dependencies = [
|
|||
"thread_local",
|
||||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
"tracing-log 0.2.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3198,15 +3188,6 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
|
|
|
@ -29,8 +29,6 @@ uuid = {version = "1.2.1", features=["serde", "v4"]}
|
|||
jwt = "0.16.0"
|
||||
hmac = "0.12.1"
|
||||
sha2 = "0.10.6"
|
||||
log = "0.4.17"
|
||||
env_logger = "0.9.3"
|
||||
scraper = "0.14.0"
|
||||
actix-cors = "0.6.4"
|
||||
chrono = { version = "0.4.31", features = ["serde"] }
|
||||
|
@ -43,6 +41,8 @@ tracing-subscriber = { version = "0.3.18", features = ["registry", "env-filter"]
|
|||
tracing-log = "0.2.0"
|
||||
config = "0.14.0"
|
||||
diesel-connection = "4.1.0"
|
||||
tracing = { version = "0.1.40", features = ["log"] }
|
||||
tracing-bunyan-formatter = "0.3.9"
|
||||
|
||||
[dependencies.serde_json]
|
||||
version = "1.0.86"
|
||||
|
|
|
@ -4,6 +4,7 @@ pub mod processes;
|
|||
use crate::auth::processes::check_password;
|
||||
use crate::auth::processes::extract_header_token;
|
||||
|
||||
#[tracing::instrument(name = "Process token")]
|
||||
pub fn process_token(request: &ServiceRequest) -> Result<String, &'static str> {
|
||||
match extract_header_token(request) {
|
||||
Ok(token) => check_password(token),
|
||||
|
|
|
@ -1,21 +1,19 @@
|
|||
use super::jwt;
|
||||
use actix_web::dev::ServiceRequest;
|
||||
use secrecy::{ExposeSecret, Secret};
|
||||
|
||||
pub fn check_password(password: String) -> Result<String, &'static str> {
|
||||
match jwt::JwtToken::decode(password) {
|
||||
pub fn check_password(password: Secret<String>) -> Result<String, &'static str> {
|
||||
match jwt::JwtToken::decode(password.expose_secret().to_string()) {
|
||||
Ok(_token) => Ok(String::from("passed")),
|
||||
Err(message) => Err(message),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extract_header_token(request: &ServiceRequest) -> Result<String, &'static str> {
|
||||
log::info!("Request: {:?}", request);
|
||||
#[tracing::instrument(name = "Extract Header Token")]
|
||||
pub fn extract_header_token(request: &ServiceRequest) -> Result<Secret<String>, &'static str> {
|
||||
match request.headers().get("user-token") {
|
||||
Some(token) => match token.to_str() {
|
||||
Ok(processed_password) => {
|
||||
log::info!("Token provided: {}", processed_password);
|
||||
Ok(String::from(processed_password))
|
||||
}
|
||||
Ok(processed_password) => Ok(Secret::new(String::from(processed_password))),
|
||||
Err(_processed_password) => Err("there was an error processing token"),
|
||||
},
|
||||
None => Err("there is no token"),
|
||||
|
@ -25,6 +23,7 @@ pub fn extract_header_token(request: &ServiceRequest) -> Result<String, &'static
|
|||
#[cfg(test)]
|
||||
mod processes_test {
|
||||
use actix_web::test::TestRequest;
|
||||
use secrecy::{ExposeSecret, Secret};
|
||||
|
||||
use crate::auth::jwt::JwtToken;
|
||||
|
||||
|
@ -32,7 +31,7 @@ mod processes_test {
|
|||
|
||||
#[test]
|
||||
fn check_correct_password() {
|
||||
let password_string: String = JwtToken::encode(32);
|
||||
let password_string: Secret<String> = Secret::new(JwtToken::encode(32));
|
||||
|
||||
let result = check_password(password_string);
|
||||
|
||||
|
@ -44,7 +43,7 @@ mod processes_test {
|
|||
|
||||
#[test]
|
||||
fn incorrect_check_password() {
|
||||
let password: String = String::from("test");
|
||||
let password: Secret<String> = Secret::new(String::from("test"));
|
||||
|
||||
match check_password(password) {
|
||||
Err(message) => assert_eq!("could not decode token", message),
|
||||
|
@ -59,7 +58,7 @@ mod processes_test {
|
|||
.to_srv_request();
|
||||
|
||||
match super::extract_header_token(&request) {
|
||||
Ok(processed_password) => assert_eq!("token", processed_password),
|
||||
Ok(processed_password) => assert_eq!("token", processed_password.expose_secret()),
|
||||
_ => panic!("failed extract_header_token"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct NewFeedSchema {
|
||||
pub title: String,
|
||||
pub url: String,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct NewUserSchema {
|
||||
pub name: String,
|
||||
pub email: String,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use serde_derive::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct ReadItem {
|
||||
pub id: i32,
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct UrlJson {
|
||||
pub url: String,
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use serde_derive::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct JsonUser {
|
||||
pub user_id: i32,
|
||||
}
|
||||
|
|
|
@ -9,4 +9,5 @@ pub mod models;
|
|||
pub mod reader;
|
||||
pub mod schema;
|
||||
pub mod startup;
|
||||
pub mod telemetry;
|
||||
pub mod views;
|
||||
|
|
10
src/main.rs
10
src/main.rs
|
@ -4,12 +4,18 @@ use diesel::{
|
|||
r2d2::{ConnectionManager, Pool},
|
||||
PgConnection,
|
||||
};
|
||||
use rss_reader::{configuration::get_configuration, database::get_connection_pool, startup::run};
|
||||
use rss_reader::{
|
||||
configuration::get_configuration,
|
||||
database::get_connection_pool,
|
||||
startup::run,
|
||||
telemetry::{get_subscriber, init_subscriber},
|
||||
};
|
||||
use secrecy::ExposeSecret;
|
||||
|
||||
#[actix_rt::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
env_logger::init();
|
||||
let subscriber = get_subscriber("zero2prod".into(), "info".into(), std::io::stdout);
|
||||
init_subscriber(subscriber);
|
||||
|
||||
let configuration = get_configuration().expect("Failed to read configuration.");
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ extern crate bcrypt;
|
|||
|
||||
use bcrypt::{hash, DEFAULT_COST};
|
||||
use diesel::Insertable;
|
||||
use secrecy::{ExposeSecret, Secret};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::schema::users;
|
||||
|
@ -16,8 +17,9 @@ pub struct NewUser {
|
|||
}
|
||||
|
||||
impl NewUser {
|
||||
pub fn new(username: String, email: String, password: String) -> NewUser {
|
||||
let hashed_password: String = hash(password.as_str(), DEFAULT_COST).unwrap();
|
||||
pub fn new(username: String, email: String, password: Secret<String>) -> NewUser {
|
||||
let hashed_password: String =
|
||||
hash(password.expose_secret().as_str(), DEFAULT_COST).unwrap();
|
||||
let uuid = Uuid::new_v4();
|
||||
NewUser {
|
||||
username,
|
||||
|
|
|
@ -10,6 +10,7 @@ use crate::{
|
|||
|
||||
use super::feeds;
|
||||
|
||||
#[tracing::instrument(name = "Add new feed", skip(pool))]
|
||||
pub async fn add(
|
||||
new_feed: web::Json<NewFeedSchema>,
|
||||
pool: web::Data<Pool<ConnectionManager<PgConnection>>>,
|
||||
|
@ -24,13 +25,11 @@ pub async fn add(
|
|||
let result = feeds::get_feed(&url).await;
|
||||
match result {
|
||||
Ok(channel) => {
|
||||
log::info!("valid channel");
|
||||
if channel.items.is_empty() {
|
||||
return HttpResponse::ServiceUnavailable().await.unwrap();
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("{:?}", e);
|
||||
Err(_) => {
|
||||
return HttpResponse::NotFound().await.unwrap();
|
||||
}
|
||||
}
|
||||
|
@ -43,9 +42,6 @@ pub async fn add(
|
|||
|
||||
match insert_result {
|
||||
Ok(_) => HttpResponse::Created().await.unwrap(),
|
||||
Err(e) => {
|
||||
log::error!("{e}");
|
||||
HttpResponse::Conflict().await.unwrap()
|
||||
}
|
||||
Err(_) => HttpResponse::Conflict().await.unwrap(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@ use std::error::Error;
|
|||
|
||||
use rss::Channel;
|
||||
|
||||
#[tracing::instrument(name = "Get Channel Feed")]
|
||||
pub async fn get_feed(feed: &str) -> Result<Channel, Box<dyn Error>> {
|
||||
let content = reqwest::get(feed).await?.bytes().await?;
|
||||
let channel = Channel::read_from(&content[..])?;
|
||||
log::debug!("{:?}", channel);
|
||||
Ok(channel)
|
||||
}
|
||||
|
|
|
@ -10,11 +10,12 @@ use crate::{
|
|||
};
|
||||
use actix_web::{web, HttpRequest, Responder};
|
||||
use chrono::Local;
|
||||
use diesel::prelude::*;
|
||||
use diesel::r2d2::{ConnectionManager, Pool};
|
||||
use diesel::{prelude::*, r2d2};
|
||||
|
||||
use super::structs::article::Article;
|
||||
|
||||
#[tracing::instrument(name = "Get feeds", skip(pool))]
|
||||
pub async fn get(
|
||||
path: web::Path<JsonUser>,
|
||||
req: HttpRequest,
|
||||
|
@ -22,7 +23,6 @@ pub async fn get(
|
|||
) -> impl Responder {
|
||||
let request = req.clone();
|
||||
let req_user_id = path.user_id;
|
||||
log::info!("Received user_id: {}", req_user_id);
|
||||
// Clone the Arc containing the connection pool
|
||||
let pool_arc = pool.get_ref().clone();
|
||||
// Acquire a connection from the pool
|
||||
|
@ -35,42 +35,7 @@ pub async fn get(
|
|||
|
||||
let mut feed_aggregates: Vec<FeedAggregate> = Vec::new();
|
||||
for feed in feeds {
|
||||
let existing_item: Vec<FeedItem> = feed_item::table
|
||||
.filter(feed_id.eq(feed.id))
|
||||
.filter(read.eq(false))
|
||||
.order(id.asc())
|
||||
.load(&mut connection)
|
||||
.unwrap();
|
||||
|
||||
log::info!(
|
||||
"Load {} feed items for feed: {}",
|
||||
existing_item.len(),
|
||||
feed.url
|
||||
);
|
||||
|
||||
let article_list: Vec<Article> = existing_item
|
||||
.into_iter()
|
||||
.map(|feed_item: FeedItem| {
|
||||
let time: String = match feed_item.created_ts {
|
||||
Some(r) => r.to_string(),
|
||||
None => Local::now().naive_local().to_string(),
|
||||
};
|
||||
Article {
|
||||
title: feed_item.title,
|
||||
content: feed_item.content,
|
||||
url: feed_item.url,
|
||||
timestamp: time,
|
||||
id: feed_item.id,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
log::info!("article list with {} items generated.", article_list.len());
|
||||
|
||||
feed_aggregates.push(FeedAggregate {
|
||||
title: feed.title,
|
||||
items: article_list,
|
||||
})
|
||||
feed_aggregates.push(get_feed_aggregate(feed, &mut connection))
|
||||
}
|
||||
|
||||
let articles: Articles = Articles {
|
||||
|
@ -79,3 +44,38 @@ pub async fn get(
|
|||
|
||||
articles.respond_to(&request)
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "Get feed aggregate", skip(connection))]
|
||||
pub fn get_feed_aggregate(
|
||||
feed: Feed,
|
||||
connection: &mut r2d2::PooledConnection<ConnectionManager<PgConnection>>,
|
||||
) -> FeedAggregate {
|
||||
let existing_item: Vec<FeedItem> = feed_item::table
|
||||
.filter(feed_id.eq(feed.id))
|
||||
.filter(read.eq(false))
|
||||
.order(id.asc())
|
||||
.load(connection)
|
||||
.unwrap();
|
||||
|
||||
let article_list: Vec<Article> = existing_item
|
||||
.into_iter()
|
||||
.map(|feed_item: FeedItem| {
|
||||
let time: String = match feed_item.created_ts {
|
||||
Some(r) => r.to_string(),
|
||||
None => Local::now().naive_local().to_string(),
|
||||
};
|
||||
Article {
|
||||
title: feed_item.title,
|
||||
content: feed_item.content,
|
||||
url: feed_item.url,
|
||||
timestamp: time,
|
||||
id: feed_item.id,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
FeedAggregate {
|
||||
title: feed.title,
|
||||
items: article_list,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,36 +3,34 @@ use crate::{
|
|||
json_serialization::read_feed_item::ReadItem, models::feed_item::rss_feed_item::FeedItem,
|
||||
schema::feed_item,
|
||||
};
|
||||
use actix_web::{web, HttpRequest, HttpResponse, Responder};
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use diesel::r2d2::{ConnectionManager, Pool};
|
||||
use diesel::{ExpressionMethods, QueryDsl};
|
||||
use diesel::{PgConnection, RunQueryDsl};
|
||||
|
||||
#[tracing::instrument(name = "Mark as read", skip(pool))]
|
||||
pub async fn mark_read(
|
||||
_req: HttpRequest,
|
||||
path: web::Path<ReadItem>,
|
||||
pool: web::Data<Pool<ConnectionManager<PgConnection>>>,
|
||||
) -> impl Responder {
|
||||
) -> HttpResponse {
|
||||
let pool_arc = pool.get_ref().clone();
|
||||
let mut connection = pool_arc.get().expect("Failed to get database connection");
|
||||
|
||||
log::info!("Id: {}", path.id);
|
||||
let feed_items: Vec<FeedItem> = feed_item::table
|
||||
.filter(id.eq(path.id))
|
||||
.load::<FeedItem>(&mut connection)
|
||||
.unwrap();
|
||||
|
||||
if feed_items.len() != 1 {
|
||||
return HttpResponse::NotFound();
|
||||
return HttpResponse::NotFound().await.unwrap();
|
||||
}
|
||||
|
||||
let feed_item: &FeedItem = feed_items.first().unwrap();
|
||||
|
||||
let result: Result<usize, diesel::result::Error> = diesel::update(feed_item)
|
||||
let _result: Result<usize, diesel::result::Error> = diesel::update(feed_item)
|
||||
.set(read.eq(true))
|
||||
.execute(&mut connection);
|
||||
|
||||
log::info!("Mark as read: {:?}", result);
|
||||
|
||||
HttpResponse::Ok()
|
||||
HttpResponse::Ok().await.unwrap()
|
||||
}
|
||||
|
|
|
@ -4,15 +4,13 @@ use crate::json_serialization::{readable::Readable, url::UrlJson};
|
|||
|
||||
use super::scraper::content::do_throttled_request;
|
||||
|
||||
#[tracing::instrument(name = "Read Feed")]
|
||||
pub async fn read(_req: HttpRequest, data: web::Json<UrlJson>) -> impl Responder {
|
||||
let result = do_throttled_request(&data.url);
|
||||
|
||||
let content = match result.await {
|
||||
Ok(cont) => cont,
|
||||
Err(e) => {
|
||||
log::error!("Could not scrap url {}", data.url);
|
||||
e.to_string()
|
||||
}
|
||||
Err(e) => e.to_string(),
|
||||
};
|
||||
|
||||
Readable { content }
|
||||
|
|
|
@ -8,7 +8,7 @@ use crate::schema::{
|
|||
feed::{self, user_id},
|
||||
feed_item,
|
||||
};
|
||||
use actix_web::{web, HttpRequest, HttpResponse, Responder};
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use chrono::{DateTime, Local, NaiveDateTime};
|
||||
use dateparser::parse;
|
||||
use diesel::prelude::*;
|
||||
|
@ -16,12 +16,12 @@ use diesel::r2d2::{ConnectionManager, Pool};
|
|||
use rss::Item;
|
||||
use scraper::{Html, Selector};
|
||||
|
||||
#[tracing::instrument(name = "Get Date")]
|
||||
fn get_date(date_str: &str) -> Result<NaiveDateTime, chrono::ParseError> {
|
||||
// let format_string = "%a, %d %b %Y %H:%M:%S %z";
|
||||
let format_string = "%Y-%m-%dT%H:%M:%S%Z";
|
||||
|
||||
let result = parse(date_str).unwrap();
|
||||
log::info!("Date: {:?}", result);
|
||||
|
||||
match NaiveDateTime::parse_from_str(&result.to_string(), format_string) {
|
||||
Ok(r) => Ok(r),
|
||||
|
@ -38,9 +38,9 @@ fn get_date(date_str: &str) -> Result<NaiveDateTime, chrono::ParseError> {
|
|||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "Create Feed Item", skip(connection))]
|
||||
fn create_feed_item(item: Item, feed: &Feed, connection: &mut PgConnection) {
|
||||
let item_title = item.title.clone().unwrap();
|
||||
log::info!("Create feed item: {}", item_title);
|
||||
|
||||
let base_content: &str = match item.content() {
|
||||
Some(c) => c,
|
||||
|
@ -74,15 +74,11 @@ fn create_feed_item(item: Item, feed: &Feed, connection: &mut PgConnection) {
|
|||
.unwrap();
|
||||
|
||||
if existing_item.is_empty() {
|
||||
log::info!("{:?}", item.pub_date());
|
||||
let mut time: NaiveDateTime = Local::now().naive_local();
|
||||
if item.pub_date().is_some() {
|
||||
time = match get_date(item.pub_date().unwrap()) {
|
||||
Ok(date) => date,
|
||||
Err(err) => {
|
||||
log::error!("could not unwrap pub date: {}", err);
|
||||
time
|
||||
}
|
||||
Err(_err) => time,
|
||||
};
|
||||
}
|
||||
let new_feed_item = NewFeedItem::new(
|
||||
|
@ -92,21 +88,18 @@ fn create_feed_item(item: Item, feed: &Feed, connection: &mut PgConnection) {
|
|||
item.link.unwrap(),
|
||||
Some(time),
|
||||
);
|
||||
let insert_result = diesel::insert_into(feed_item::table)
|
||||
let _insert_result = diesel::insert_into(feed_item::table)
|
||||
.values(&new_feed_item)
|
||||
.execute(connection);
|
||||
|
||||
log::info!("Insert Result: {:?}", insert_result);
|
||||
} else {
|
||||
log::info!("Item {} already exists.", item_title);
|
||||
}
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "sync", skip(pool))]
|
||||
pub async fn sync(
|
||||
_req: HttpRequest,
|
||||
data: web::Json<JsonUser>,
|
||||
pool: web::Data<Pool<ConnectionManager<PgConnection>>>,
|
||||
) -> impl Responder {
|
||||
) -> HttpResponse {
|
||||
let pool_arc = pool.get_ref().clone();
|
||||
let mut connection = pool_arc.get().expect("Failed to get database connection");
|
||||
|
||||
|
@ -117,22 +110,18 @@ pub async fn sync(
|
|||
.load::<Feed>(&mut connection)
|
||||
.unwrap();
|
||||
|
||||
log::info!("Found {} feeds to sync.", feeds.len());
|
||||
|
||||
for feed in feeds {
|
||||
log::info!("Try to get url: {}", feed.url);
|
||||
let result = feeds::get_feed(&feed.url).await;
|
||||
|
||||
match result {
|
||||
Ok(channel) => {
|
||||
for item in channel.into_items() {
|
||||
log::info!("{:?}", item);
|
||||
create_feed_item(item, &feed, &mut connection);
|
||||
}
|
||||
}
|
||||
Err(e) => log::error!("Could not get channel {}. Error: {}", feed.url, e),
|
||||
Err(_e) => return HttpResponse::InternalServerError().await.unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
HttpResponse::Ok()
|
||||
HttpResponse::Ok().await.unwrap()
|
||||
}
|
||||
|
|
|
@ -10,25 +10,23 @@ use futures::future::{ok, Either};
|
|||
use crate::auth;
|
||||
use crate::views;
|
||||
|
||||
#[tracing::instrument(name = "Run application", skip(connection, listener))]
|
||||
pub fn run(
|
||||
listener: TcpListener,
|
||||
connection: Pool<ConnectionManager<PgConnection>>,
|
||||
) -> Result<Server, std::io::Error> {
|
||||
let wrapper = web::Data::new(connection);
|
||||
let server = HttpServer::new(move || {
|
||||
let app = App::new()
|
||||
App::new()
|
||||
.wrap_fn(|req, srv| {
|
||||
let mut passed: bool;
|
||||
let request_url: String = String::from(req.uri().path());
|
||||
|
||||
log::info!("Request Url: {}", request_url);
|
||||
if req.path().contains("/article/") {
|
||||
match auth::process_token(&req) {
|
||||
Ok(_token) => passed = true,
|
||||
Err(_message) => passed = false,
|
||||
}
|
||||
} else {
|
||||
log::warn!("No auth check done.");
|
||||
passed = true;
|
||||
}
|
||||
|
||||
|
@ -36,8 +34,6 @@ pub fn run(
|
|||
passed = true;
|
||||
}
|
||||
|
||||
log::info!("passed: {:?}", passed);
|
||||
|
||||
let end_result = match passed {
|
||||
true => Either::Left(srv.call(req)),
|
||||
false => Either::Right(ok(req.into_response(
|
||||
|
@ -47,13 +43,11 @@ pub fn run(
|
|||
|
||||
async move {
|
||||
let result = end_result.await?;
|
||||
log::info!("{} -> {}", request_url, &result.status());
|
||||
Ok(result)
|
||||
}
|
||||
})
|
||||
.app_data(wrapper.clone())
|
||||
.configure(views::views_factory);
|
||||
app
|
||||
.configure(views::views_factory)
|
||||
})
|
||||
.listen(listener)?
|
||||
.run();
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
use tracing::{dispatcher::set_global_default, Subscriber};
|
||||
use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer};
|
||||
use tracing_log::LogTracer;
|
||||
use tracing_subscriber::{fmt::MakeWriter, layer::SubscriberExt, EnvFilter, Registry};
|
||||
|
||||
pub fn get_subscriber<Sink>(
|
||||
name: String,
|
||||
env_filter: String,
|
||||
sink: Sink,
|
||||
) -> impl Subscriber + Send + Sync
|
||||
where
|
||||
Sink: for<'a> MakeWriter<'a> + Send + Sync + 'static,
|
||||
{
|
||||
let env_filter =
|
||||
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new(env_filter));
|
||||
let formatting_layer = BunyanFormattingLayer::new(name, sink);
|
||||
|
||||
Registry::default()
|
||||
.with(env_filter)
|
||||
.with(JsonStorageLayer)
|
||||
.with(formatting_layer)
|
||||
}
|
||||
|
||||
pub fn init_subscriber(subscriber: impl Subscriber + Send + Sync) {
|
||||
LogTracer::init().expect("Failed to set logger.");
|
||||
set_global_default(subscriber.into()).expect("Failed to set subscriber.");
|
||||
}
|
|
@ -25,10 +25,6 @@ pub async fn login(
|
|||
if users.is_empty() {
|
||||
return HttpResponse::NotFound().await.unwrap();
|
||||
} else if users.len() > 1 {
|
||||
log::error!(
|
||||
"multiple user have the usernam: {}",
|
||||
credentials.username.clone()
|
||||
);
|
||||
return HttpResponse::Conflict().await.unwrap();
|
||||
}
|
||||
|
||||
|
@ -36,7 +32,6 @@ pub async fn login(
|
|||
|
||||
match user.clone().verify(password) {
|
||||
true => {
|
||||
log::info!("verified password successfully for user {}", user.id);
|
||||
let token: String = JwtToken::encode(user.clone().id);
|
||||
HttpResponse::Ok()
|
||||
.insert_header(("token", token))
|
||||
|
|
|
@ -7,7 +7,9 @@ use diesel::{
|
|||
prelude::*,
|
||||
r2d2::{ConnectionManager, Pool},
|
||||
};
|
||||
use secrecy::Secret;
|
||||
|
||||
#[tracing::instrument(name = "Create new User", skip(pool))]
|
||||
pub async fn create(
|
||||
new_user: web::Json<NewUserSchema>,
|
||||
pool: web::Data<Pool<ConnectionManager<PgConnection>>>,
|
||||
|
@ -17,7 +19,7 @@ pub async fn create(
|
|||
|
||||
let name: String = new_user.name.clone();
|
||||
let email: String = new_user.email.clone();
|
||||
let new_password: String = new_user.password.clone();
|
||||
let new_password: Secret<String> = Secret::new(new_user.password.clone());
|
||||
|
||||
let new_user = NewUser::new(name, email, new_password);
|
||||
|
||||
|
|
|
@ -45,18 +45,33 @@ a,
|
|||
font-family: Georgia, 'Times New Roman', Times, serif;
|
||||
font-size: 20px;
|
||||
padding: 1em;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: left;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.feed-content p {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.feed-content h3 {
|
||||
.feed-content h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
padding: 1em;
|
||||
font-size: 21px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
.feed-content img {
|
||||
max-width: 100%;
|
||||
margin-bottom: 10px;
|
||||
/* Adjust spacing between image and text */
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
|
|
@ -67,9 +67,10 @@ const fetchData = async () => {
|
|||
'user-token': localStorage.getItem("user-token")
|
||||
}
|
||||
});
|
||||
response.data.feeds.forEach(feed => {
|
||||
feeds.value.push(...feed.items);
|
||||
});
|
||||
const sortedItems = response.data.feeds.flatMap(feed => feed.items)
|
||||
.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
|
||||
|
||||
feeds.value.push(...sortedItems);
|
||||
await nextTick();
|
||||
setupIntersectionObserver();
|
||||
} catch (error) {
|
||||
|
|
Loading…
Reference in New Issue