Files
RuDS/crates/bds-core/src/engine/post_media.rs

217 lines
7.1 KiB
Rust

use std::path::Path;
use crate::db::DbConnection as Connection;
use uuid::Uuid;
use crate::db::queries::media as qm;
use crate::db::queries::post as qp;
use crate::db::queries::post_media as qpm;
use crate::engine::EngineResult;
use crate::model::{Media, Post, PostMedia};
use crate::util::now_unix_ms;
/// Link a media item to a post and sync the media sidecar.
pub fn link_media_to_post(
conn: &Connection,
data_dir: &Path,
project_id: &str,
post_id: &str,
media_id: &str,
sort_order: i32,
) -> EngineResult<PostMedia> {
let id = Uuid::new_v4().to_string();
let now = now_unix_ms();
let pm = PostMedia {
id,
project_id: project_id.to_string(),
post_id: post_id.to_string(),
media_id: media_id.to_string(),
sort_order,
created_at: now,
};
qpm::link_media(conn, &pm)?;
crate::engine::media::sync_media_sidecar(conn, data_dir, media_id)?;
Ok(pm)
}
/// Unlink a media item from a post and sync the media sidecar.
pub fn unlink_media_from_post(
conn: &Connection,
data_dir: &Path,
post_id: &str,
media_id: &str,
) -> EngineResult<()> {
qpm::unlink_media(conn, post_id, media_id)?;
crate::engine::media::sync_media_sidecar(conn, data_dir, media_id)?;
Ok(())
}
/// Reorder media items for a post. `media_ids` contains the new ordering;
/// each entry gets sort_order = its index.
pub fn reorder_post_media(
conn: &Connection,
post_id: &str,
media_ids: &[String],
) -> EngineResult<()> {
for (i, media_id) in media_ids.iter().enumerate() {
qpm::update_sort_order(conn, post_id, media_id, i as i32)?;
}
Ok(())
}
/// List media items currently linked to a post.
pub fn list_media_for_post(conn: &Connection, post_id: &str) -> EngineResult<Vec<Media>> {
let links = qpm::list_post_media_by_post(conn, post_id)?;
let mut media = Vec::with_capacity(links.len());
for link in links {
if let Ok(item) = qm::get_media_by_id(conn, &link.media_id) {
media.push(item);
}
}
Ok(media)
}
/// List posts currently linked to a media item.
pub fn list_posts_for_media(conn: &Connection, media_id: &str) -> EngineResult<Vec<Post>> {
let links = qpm::list_post_media_by_media(conn, media_id)?;
let mut posts = Vec::with_capacity(links.len());
for link in links {
if let Ok(post) = qp::get_post_by_id(conn, &link.post_id) {
posts.push(post);
}
}
Ok(posts)
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
use crate::db::Database;
use crate::db::queries::media::{insert_media, make_test_media};
use crate::db::queries::post::{insert_post, make_test_post};
use crate::db::queries::post_media::list_post_media_by_post;
use crate::db::queries::project::{insert_project, make_test_project};
use crate::util::sidecar::MediaSidecar;
use crate::util::sidecar::read_sidecar;
fn setup() -> (Database, TempDir) {
let db = Database::open_in_memory().unwrap();
db.migrate().unwrap();
insert_project(db.conn(), &make_test_project("p1", "blog")).unwrap();
let dir = TempDir::new().unwrap();
insert_post(db.conn(), &make_test_post("post1", "p1", "test")).unwrap();
(db, dir)
}
/// Insert a media item whose sidecar_path is inside the temp dir.
fn insert_test_media(db: &Database, dir: &Path, id: &str) {
let sidecar_rel = format!("media/{id}.jpg.meta");
let mut media = make_test_media(id, "p1");
media.file_path = format!("media/{id}.jpg");
media.sidecar_path = sidecar_rel.clone();
insert_media(db.conn(), &media).unwrap();
// Write an initial sidecar so the directory exists
let initial = MediaSidecar::from_media(&media, &[]);
let abs_sidecar = dir.join(&sidecar_rel);
fs::create_dir_all(abs_sidecar.parent().unwrap()).unwrap();
fs::write(&abs_sidecar, initial.to_string()).unwrap();
}
fn read_linked_ids(dir: &Path, sidecar_rel: &str) -> Vec<String> {
let content = fs::read_to_string(dir.join(sidecar_rel)).unwrap();
let sc = read_sidecar(&content).unwrap();
sc.linked_post_ids
}
#[test]
fn link_and_verify_sidecar() {
let (db, dir) = setup();
insert_test_media(&db, dir.path(), "m1");
link_media_to_post(db.conn(), dir.path(), "p1", "post1", "m1", 0).unwrap();
let ids = read_linked_ids(dir.path(), "media/m1.jpg.meta");
assert_eq!(ids, vec!["post1"]);
}
#[test]
fn unlink_removes_from_sidecar() {
let (db, dir) = setup();
insert_test_media(&db, dir.path(), "m1");
link_media_to_post(db.conn(), dir.path(), "p1", "post1", "m1", 0).unwrap();
unlink_media_from_post(db.conn(), dir.path(), "post1", "m1").unwrap();
let ids = read_linked_ids(dir.path(), "media/m1.jpg.meta");
assert!(ids.is_empty());
}
#[test]
fn reorder_updates_sort_order() {
let (db, dir) = setup();
insert_test_media(&db, dir.path(), "m1");
insert_test_media(&db, dir.path(), "m2");
link_media_to_post(db.conn(), dir.path(), "p1", "post1", "m1", 0).unwrap();
link_media_to_post(db.conn(), dir.path(), "p1", "post1", "m2", 1).unwrap();
// Reverse the order: m2 first, m1 second
reorder_post_media(db.conn(), "post1", &["m2".to_string(), "m1".to_string()]).unwrap();
let list = list_post_media_by_post(db.conn(), "post1").unwrap();
assert_eq!(list.len(), 2);
assert_eq!(list[0].media_id, "m2");
assert_eq!(list[0].sort_order, 0);
assert_eq!(list[1].media_id, "m1");
assert_eq!(list[1].sort_order, 1);
}
#[test]
fn multiple_posts_linked() {
let (db, dir) = setup();
insert_test_media(&db, dir.path(), "m1");
let mut post = make_test_post("post2", "p1", "test2");
post.created_at = 2000;
post.updated_at = 2000;
insert_post(db.conn(), &post).unwrap();
link_media_to_post(db.conn(), dir.path(), "p1", "post1", "m1", 0).unwrap();
link_media_to_post(db.conn(), dir.path(), "p1", "post2", "m1", 0).unwrap();
let ids = read_linked_ids(dir.path(), "media/m1.jpg.meta");
assert_eq!(ids.len(), 2);
assert!(ids.contains(&"post1".to_string()));
assert!(ids.contains(&"post2".to_string()));
}
#[test]
fn list_media_for_post_returns_resolved_media() {
let (db, dir) = setup();
insert_test_media(&db, dir.path(), "m1");
link_media_to_post(db.conn(), dir.path(), "p1", "post1", "m1", 0).unwrap();
let media = list_media_for_post(db.conn(), "post1").unwrap();
assert_eq!(media.len(), 1);
assert_eq!(media[0].id, "m1");
}
#[test]
fn list_posts_for_media_returns_resolved_posts() {
let (db, dir) = setup();
insert_test_media(&db, dir.path(), "m1");
link_media_to_post(db.conn(), dir.path(), "p1", "post1", "m1", 0).unwrap();
let posts = list_posts_for_media(db.conn(), "m1").unwrap();
assert_eq!(posts.len(), 1);
assert_eq!(posts[0].id, "post1");
}
}