Compare commits
4 Commits
85b5167805
...
3ab595abd2
Author | SHA1 | Date | |
---|---|---|---|
|
3ab595abd2 | ||
|
4c44767301 | ||
|
2b632f0a93 | ||
|
4cce902a21 |
37
src/demo.rs
37
src/demo.rs
@ -2,9 +2,12 @@ use chrono;
|
|||||||
use sqlx;
|
use sqlx;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::feeds::Feed;
|
||||||
|
|
||||||
pub async fn setup_demo_data(pool: &sqlx::SqlitePool) {
|
pub async fn setup_demo_data(pool: &sqlx::SqlitePool) {
|
||||||
// Create admin user
|
// Create admin user
|
||||||
let admin_id = Uuid::new_v4().to_string();
|
let admin_id = Uuid::new_v4();
|
||||||
|
let admin_id_str = admin_id.to_string();
|
||||||
let admin_hash = bcrypt::hash("admin", bcrypt::DEFAULT_COST).unwrap();
|
let admin_hash = bcrypt::hash("admin", bcrypt::DEFAULT_COST).unwrap();
|
||||||
let now = chrono::Utc::now().to_rfc3339();
|
let now = chrono::Utc::now().to_rfc3339();
|
||||||
|
|
||||||
@ -12,7 +15,7 @@ pub async fn setup_demo_data(pool: &sqlx::SqlitePool) {
|
|||||||
"INSERT INTO users (id, username, password_hash, email, display_name, created_at, admin)
|
"INSERT INTO users (id, username, password_hash, email, display_name, created_at, admin)
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
|
||||||
)
|
)
|
||||||
.bind(&admin_id)
|
.bind(&admin_id_str)
|
||||||
.bind("admin")
|
.bind("admin")
|
||||||
.bind(&admin_hash)
|
.bind(&admin_hash)
|
||||||
.bind(Option::<String>::None)
|
.bind(Option::<String>::None)
|
||||||
@ -24,14 +27,15 @@ pub async fn setup_demo_data(pool: &sqlx::SqlitePool) {
|
|||||||
.expect("Failed to create admin user");
|
.expect("Failed to create admin user");
|
||||||
|
|
||||||
// Create demo user
|
// Create demo user
|
||||||
let demo_id = Uuid::new_v4().to_string();
|
let demo_id = Uuid::new_v4();
|
||||||
|
let demo_id_str = demo_id.to_string();
|
||||||
let demo_hash = bcrypt::hash("demo", bcrypt::DEFAULT_COST).unwrap();
|
let demo_hash = bcrypt::hash("demo", bcrypt::DEFAULT_COST).unwrap();
|
||||||
|
|
||||||
sqlx::query(
|
sqlx::query(
|
||||||
"INSERT INTO users (id, username, password_hash, email, display_name, created_at, admin)
|
"INSERT INTO users (id, username, password_hash, email, display_name, created_at, admin)
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)",
|
||||||
)
|
)
|
||||||
.bind(&demo_id)
|
.bind(&demo_id_str)
|
||||||
.bind("demo")
|
.bind("demo")
|
||||||
.bind(&demo_hash)
|
.bind(&demo_hash)
|
||||||
.bind(Option::<String>::None)
|
.bind(Option::<String>::None)
|
||||||
@ -42,5 +46,30 @@ pub async fn setup_demo_data(pool: &sqlx::SqlitePool) {
|
|||||||
.await
|
.await
|
||||||
.expect("Failed to create demo user");
|
.expect("Failed to create demo user");
|
||||||
|
|
||||||
|
let feed = Feed::new(
|
||||||
|
"BBC News".to_string(),
|
||||||
|
"https://feeds.bbci.co.uk/news/world/us_and_canada/rss.xml"
|
||||||
|
.parse()
|
||||||
|
.unwrap(),
|
||||||
|
demo_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: This insert logic is substantially the same as Feed::write_to_database.
|
||||||
|
// Should find a way to unify these two code paths to avoid duplication.
|
||||||
|
sqlx::query(
|
||||||
|
"INSERT INTO feeds (feed_id, name, url, user_id, added_time, last_checked_time, categorization)
|
||||||
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, json(?7))",
|
||||||
|
)
|
||||||
|
.bind(feed.feed_id.to_string())
|
||||||
|
.bind(&feed.name)
|
||||||
|
.bind(feed.url.as_str())
|
||||||
|
.bind(feed.user_id.to_string())
|
||||||
|
.bind(feed.added_time.to_rfc3339())
|
||||||
|
.bind(feed.last_checked_time.to_rfc3339())
|
||||||
|
.bind("[]") // empty categorization array as JSON
|
||||||
|
.execute(pool)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create demo feed");
|
||||||
|
|
||||||
println!("Successfully set up demo data");
|
println!("Successfully set up demo data");
|
||||||
}
|
}
|
||||||
|
62
src/feeds.rs
62
src/feeds.rs
@ -12,13 +12,13 @@ use crate::Db;
|
|||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
#[serde(crate = "rocket::serde")]
|
#[serde(crate = "rocket::serde")]
|
||||||
pub struct Feed {
|
pub struct Feed {
|
||||||
feed_id: Uuid,
|
pub feed_id: Uuid,
|
||||||
name: String,
|
pub name: String,
|
||||||
url: Url,
|
pub url: Url,
|
||||||
user_id: Uuid,
|
pub user_id: Uuid,
|
||||||
added_time: chrono::DateTime<chrono::Utc>,
|
pub added_time: chrono::DateTime<chrono::Utc>,
|
||||||
last_checked_time: chrono::DateTime<chrono::Utc>,
|
pub last_checked_time: chrono::DateTime<chrono::Utc>,
|
||||||
categorization: Vec<String>,
|
pub categorization: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Feed {
|
impl Feed {
|
||||||
@ -34,6 +34,30 @@ impl Feed {
|
|||||||
categorization: Vec::new(),
|
categorization: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn write_to_database(&self, mut db: Connection<Db>) -> Result<(), sqlx::Error> {
|
||||||
|
// Convert categorization to JSON value
|
||||||
|
let categorization_json = serde::json::to_value(&self.categorization).map_err(|e| {
|
||||||
|
eprintln!("Failed to serialize categorization: {}", e);
|
||||||
|
sqlx::Error::Decode(Box::new(e))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
sqlx::query(
|
||||||
|
"INSERT INTO feeds (feed_id, name, url, user_id, added_time, last_checked_time, categorization)
|
||||||
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, json(?7))",
|
||||||
|
)
|
||||||
|
.bind(self.feed_id.to_string())
|
||||||
|
.bind(&self.name)
|
||||||
|
.bind(self.url.as_str())
|
||||||
|
.bind(self.user_id.to_string())
|
||||||
|
.bind(self.added_time.to_rfc3339())
|
||||||
|
.bind(self.last_checked_time.to_rfc3339())
|
||||||
|
.bind(&categorization_json.to_string())
|
||||||
|
.execute(&mut **db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@ -46,7 +70,7 @@ pub struct NewFeed {
|
|||||||
|
|
||||||
#[post("/feeds", data = "<new_feed>")]
|
#[post("/feeds", data = "<new_feed>")]
|
||||||
pub async fn create_feed(
|
pub async fn create_feed(
|
||||||
mut db: Connection<Db>,
|
db: Connection<Db>,
|
||||||
new_feed: Json<NewFeed>,
|
new_feed: Json<NewFeed>,
|
||||||
user: AuthenticatedUser,
|
user: AuthenticatedUser,
|
||||||
) -> Result<Json<Feed>, Status> {
|
) -> Result<Json<Feed>, Status> {
|
||||||
@ -73,27 +97,7 @@ pub async fn create_feed(
|
|||||||
let mut feed = Feed::new(name, new_feed.url, user.user_id);
|
let mut feed = Feed::new(name, new_feed.url, user.user_id);
|
||||||
feed.categorization = new_feed.categorization;
|
feed.categorization = new_feed.categorization;
|
||||||
|
|
||||||
// Convert categorization to JSON value
|
match feed.write_to_database(db).await {
|
||||||
let categorization_json = serde::json::to_value(&feed.categorization).map_err(|e| {
|
|
||||||
eprintln!("Failed to serialize categorization: {}", e);
|
|
||||||
Status::InternalServerError
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let query = sqlx::query(
|
|
||||||
"INSERT INTO feeds (feed_id, name, url, user_id, added_time, last_checked_time, categorization)
|
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, json(?7))",
|
|
||||||
)
|
|
||||||
.bind(feed.feed_id.to_string())
|
|
||||||
.bind(&feed.name)
|
|
||||||
.bind(feed.url.as_str())
|
|
||||||
.bind(feed.user_id.to_string())
|
|
||||||
.bind(feed.added_time.to_rfc3339())
|
|
||||||
.bind(feed.last_checked_time.to_rfc3339())
|
|
||||||
.bind(&categorization_json.to_string())
|
|
||||||
.execute(&mut **db)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match query {
|
|
||||||
Ok(_) => Ok(Json(feed)),
|
Ok(_) => Ok(Json(feed)),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Database error: {}", e);
|
eprintln!("Database error: {}", e);
|
||||||
|
@ -57,8 +57,8 @@ async fn index_redirect(mut db: Connection<Db>) -> Redirect {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[get("/login")]
|
#[get("/login")]
|
||||||
fn login() -> Template {
|
fn login(demo_mode: &State<bool>) -> Template {
|
||||||
Template::render("login", context! {})
|
Template::render("login", context! { demo_mode: *demo_mode })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run migrations and setup demo data if needed
|
// Run migrations and setup demo data if needed
|
||||||
@ -100,7 +100,6 @@ fn rocket() -> _ {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let figment = rocket::Config::figment().merge(("databases.rss_data.url", db_url));
|
let figment = rocket::Config::figment().merge(("databases.rss_data.url", db_url));
|
||||||
let demo = args.demo;
|
|
||||||
|
|
||||||
rocket::custom(figment)
|
rocket::custom(figment)
|
||||||
.mount(
|
.mount(
|
||||||
@ -126,7 +125,8 @@ fn rocket() -> _ {
|
|||||||
.mount("/static", FileServer::from("static"))
|
.mount("/static", FileServer::from("static"))
|
||||||
.attach(Template::fairing())
|
.attach(Template::fairing())
|
||||||
.attach(Db::init())
|
.attach(Db::init())
|
||||||
|
.manage(args.demo)
|
||||||
.attach(AdHoc::try_on_ignite("DB Setup", move |rocket| async move {
|
.attach(AdHoc::try_on_ignite("DB Setup", move |rocket| async move {
|
||||||
setup_database(demo, rocket).await
|
setup_database(args.demo, rocket).await
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -534,4 +534,22 @@ button:disabled {
|
|||||||
.feed-entry-summary {
|
.feed-entry-summary {
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-notice {
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-notice p {
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demo-notice pre {
|
||||||
|
padding: 0.1rem 0.4rem;
|
||||||
|
border-radius: 2px;
|
||||||
|
font-family: monospace;
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,15 @@
|
|||||||
<div class="login-container">
|
<div class="login-container">
|
||||||
<form class="login-form" id="loginForm">
|
<form class="login-form" id="loginForm">
|
||||||
<h1>Login</h1>
|
<h1>Login</h1>
|
||||||
|
{% if demo_mode %}
|
||||||
|
<div class="demo-notice">
|
||||||
|
<p>RSS Reader is currently running in demo mode. You can log in with these credentials:</p>
|
||||||
|
|
||||||
|
Username: <pre style="display: inline">demo</pre>, Password: <pre style="display: inline">demo</pre>
|
||||||
|
|
||||||
|
<p>Note: Any data you create will not be persisted after the application restarts.</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="username">Username</label>
|
<label for="username">Username</label>
|
||||||
<input type="text" id="username" name="username" required>
|
<input type="text" id="username" name="username" required>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user