Files
rss-reader/src/reader/mark_read.rs
T
2026-06-12 19:22:07 +02:00

150 lines
5.0 KiB
Rust

use crate::auth::extractor::AuthUser;
use crate::error::AppError;
use crate::schema::feed_item::{id, read};
use crate::{
database::establish_connection,
json_serialization::read_feed_item::ReadItem,
models::feed_item::rss_feed_item::FeedItem,
schema::{feed, feed_item},
};
use actix_web::{web, HttpRequest, HttpResponse, Responder};
use diesel::RunQueryDsl;
use diesel::{ExpressionMethods, OptionalExtension, QueryDsl};
pub async fn mark_read(
_req: HttpRequest,
path: web::Path<ReadItem>,
auth_user: AuthUser,
) -> Result<impl Responder, AppError> {
let mut connection = establish_connection();
log::info!("Id: {}", path.id);
// Join through to `feed` so we can confirm the item belongs to the caller
// before mutating it. "Doesn't exist" and "not yours" both return 404.
let owned_item: Option<(FeedItem, i32)> = feed_item::table
.inner_join(feed::table)
.filter(id.eq(path.id))
.select((feed_item::all_columns, feed::user_id))
.first(&mut connection)
.optional()?;
let feed_item = match owned_item {
Some((feed_item, owner_id)) if owner_id == auth_user.0 => feed_item,
_ => return Ok(HttpResponse::NotFound().finish()),
};
let result = diesel::update(&feed_item)
.set(read.eq(true))
.execute(&mut connection)?;
log::info!("Mark as read: {:?}", result);
Ok(HttpResponse::Ok().finish())
}
#[cfg(test)]
mod tests {
use actix_service::Service;
use actix_web::http::StatusCode;
use actix_web::{test, web, App, HttpMessage};
use diesel::prelude::*;
use super::mark_read;
use crate::auth::extractor::AuthUser;
use crate::database::establish_connection;
use crate::models::feed_item::rss_feed_item::FeedItem;
use crate::schema::feed_item;
use crate::test_helpers::{
delete_feed, delete_feed_item, delete_user, insert_feed, insert_feed_item, insert_user,
};
#[actix_web::test]
async fn mark_read_flips_the_read_flag() {
let mut connection = establish_connection();
let user = insert_user(&mut connection, "secret");
let feed = insert_feed(&mut connection, user.id);
let item = insert_feed_item(&mut connection, feed.id, false);
let user_id = user.id;
let app = test::init_service(
App::new()
.wrap_fn(move |req, srv| {
req.extensions_mut().insert(AuthUser(user_id));
srv.call(req)
})
.route("/read/{id}", web::put().to(mark_read)),
)
.await;
let req = test::TestRequest::put()
.uri(&format!("/read/{}", item.id))
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(StatusCode::OK, resp.status());
let updated: FeedItem = feed_item::table
.find(item.id)
.first(&mut connection)
.unwrap();
assert!(updated.read);
delete_feed_item(&mut connection, item.id);
delete_feed(&mut connection, feed.id);
delete_user(&mut connection, user.id);
}
#[actix_web::test]
async fn mark_read_returns_not_found_for_unknown_id() {
let app = test::init_service(
App::new()
.wrap_fn(move |req, srv| {
req.extensions_mut().insert(AuthUser(1));
srv.call(req)
})
.route("/read/{id}", web::put().to(mark_read)),
)
.await;
let req = test::TestRequest::put().uri("/read/999999999").to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(StatusCode::NOT_FOUND, resp.status());
}
#[actix_web::test]
async fn mark_read_rejects_other_users_item() {
let mut connection = establish_connection();
let user_a = insert_user(&mut connection, "secret");
let user_b = insert_user(&mut connection, "secret");
let feed_b = insert_feed(&mut connection, user_b.id);
let item_b = insert_feed_item(&mut connection, feed_b.id, false);
let user_a_id = user_a.id;
let app = test::init_service(
App::new()
.wrap_fn(move |req, srv| {
req.extensions_mut().insert(AuthUser(user_a_id));
srv.call(req)
})
.route("/read/{id}", web::put().to(mark_read)),
)
.await;
let req = test::TestRequest::put()
.uri(&format!("/read/{}", item_b.id))
.to_request();
let resp = test::call_service(&app, req).await;
assert_eq!(StatusCode::NOT_FOUND, resp.status());
let updated: FeedItem = feed_item::table
.find(item_b.id)
.first(&mut connection)
.unwrap();
assert!(!updated.read);
delete_feed_item(&mut connection, item_b.id);
delete_feed(&mut connection, feed_b.id);
delete_user(&mut connection, user_a.id);
delete_user(&mut connection, user_b.id);
}
}