Align legacy post snapshots with bDS2.
This commit is contained in:
@@ -79,75 +79,6 @@ pub fn update_post_status(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn clear_post_content(conn: &DbConnection, id: &str, updated_at: i64) -> QueryResult<()> {
|
||||
conn.with(|c| {
|
||||
diesel::update(posts::table.filter(posts::id.eq(id)))
|
||||
.set((
|
||||
posts::content.eq(None::<String>),
|
||||
posts::updated_at.eq(updated_at),
|
||||
))
|
||||
.execute(c)
|
||||
.map(|_| ())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_post_file_path(
|
||||
conn: &DbConnection,
|
||||
id: &str,
|
||||
file_path: &str,
|
||||
updated_at: i64,
|
||||
) -> QueryResult<()> {
|
||||
conn.with(|c| {
|
||||
diesel::update(posts::table.filter(posts::id.eq(id)))
|
||||
.set((
|
||||
posts::file_path.eq(file_path),
|
||||
posts::updated_at.eq(updated_at),
|
||||
))
|
||||
.execute(c)
|
||||
.map(|_| ())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_post_checksum(conn: &DbConnection, id: &str, checksum: Option<&str>) -> QueryResult<()> {
|
||||
conn.with(|c| {
|
||||
diesel::update(posts::table.filter(posts::id.eq(id)))
|
||||
.set(posts::checksum.eq(checksum))
|
||||
.execute(c)
|
||||
.map(|_| ())
|
||||
})
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::too_many_arguments,
|
||||
reason = "arguments mirror the published snapshot columns"
|
||||
)]
|
||||
pub fn set_published_snapshot(
|
||||
conn: &DbConnection,
|
||||
id: &str,
|
||||
title: &str,
|
||||
content: &str,
|
||||
tags: &str,
|
||||
categories: &str,
|
||||
excerpt: Option<&str>,
|
||||
published_at: i64,
|
||||
updated_at: i64,
|
||||
) -> QueryResult<()> {
|
||||
conn.with(|c| {
|
||||
diesel::update(posts::table.filter(posts::id.eq(id)))
|
||||
.set((
|
||||
posts::published_title.eq(title),
|
||||
posts::published_content.eq(content),
|
||||
posts::published_tags.eq(tags),
|
||||
posts::published_categories.eq(categories),
|
||||
posts::published_excerpt.eq(excerpt),
|
||||
posts::published_at.eq(published_at),
|
||||
posts::updated_at.eq(updated_at),
|
||||
))
|
||||
.execute(c)
|
||||
.map(|_| ())
|
||||
})
|
||||
}
|
||||
|
||||
pub fn delete_post(conn: &DbConnection, id: &str) -> QueryResult<()> {
|
||||
conn.with(|c| {
|
||||
diesel::delete(posts::table.filter(posts::id.eq(id)))
|
||||
@@ -545,46 +476,6 @@ mod tests {
|
||||
assert!(get_post_by_id(db.conn(), "x1").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clear_content_sets_null() {
|
||||
let db = setup();
|
||||
insert_post(db.conn(), &make_post("x1", "hello")).unwrap();
|
||||
clear_post_content(db.conn(), "x1", 5000).unwrap();
|
||||
let fetched = get_post_by_id(db.conn(), "x1").unwrap();
|
||||
assert!(fetched.content.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_file_path_updates() {
|
||||
let db = setup();
|
||||
insert_post(db.conn(), &make_post("x1", "hello")).unwrap();
|
||||
set_post_file_path(db.conn(), "x1", "posts/2024/01/hello.md", 5000).unwrap();
|
||||
let fetched = get_post_by_id(db.conn(), "x1").unwrap();
|
||||
assert_eq!(fetched.file_path, "posts/2024/01/hello.md");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn published_snapshot() {
|
||||
let db = setup();
|
||||
insert_post(db.conn(), &make_post("x1", "hello")).unwrap();
|
||||
set_published_snapshot(
|
||||
db.conn(),
|
||||
"x1",
|
||||
"Pub Title",
|
||||
"Pub Body",
|
||||
"[\"rust\"]",
|
||||
"[\"tech\"]",
|
||||
Some("Pub Excerpt"),
|
||||
3000,
|
||||
3000,
|
||||
)
|
||||
.unwrap();
|
||||
let fetched = get_post_by_id(db.conn(), "x1").unwrap();
|
||||
assert_eq!(fetched.published_title.as_deref(), Some("Pub Title"));
|
||||
assert_eq!(fetched.published_content.as_deref(), Some("Pub Body"));
|
||||
assert_eq!(fetched.published_at, Some(3000));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete_removes_post() {
|
||||
let db = setup();
|
||||
|
||||
@@ -327,37 +327,9 @@ fn publish_post_in_savepoint(
|
||||
}
|
||||
}
|
||||
|
||||
// Set published snapshot fields
|
||||
let tags_json = serde_json::to_string(&post.tags).unwrap_or_else(|_| "[]".into());
|
||||
let cats_json = serde_json::to_string(&post.categories).unwrap_or_else(|_| "[]".into());
|
||||
post.published_title = Some(post.title.clone());
|
||||
post.published_content = Some(body.clone());
|
||||
post.published_tags = Some(tags_json.clone());
|
||||
post.published_categories = Some(cats_json.clone());
|
||||
post.published_excerpt = post.excerpt.clone();
|
||||
|
||||
qp::set_published_snapshot(
|
||||
conn,
|
||||
&post_id,
|
||||
&post.title,
|
||||
&body,
|
||||
&tags_json,
|
||||
&cats_json,
|
||||
post.excerpt.as_deref(),
|
||||
published_at,
|
||||
now,
|
||||
)?;
|
||||
|
||||
// Set file_path and checksum in DB
|
||||
qp::set_post_file_path(conn, &post_id, &post.file_path, now)?;
|
||||
qp::set_post_checksum(conn, &post_id, post.checksum.as_deref())?;
|
||||
|
||||
// Clear content in DB
|
||||
qp::clear_post_content(conn, &post_id, now)?;
|
||||
// Persist the published record without touching the legacy published_* columns.
|
||||
post.content = None;
|
||||
|
||||
// Set status = Published
|
||||
qp::update_post_status(conn, &post_id, &PostStatus::Published, now)?;
|
||||
qp::update_post(conn, &post)?;
|
||||
|
||||
// Publish all translations
|
||||
let translations = qt::list_post_translations_by_post(conn, &post_id)?;
|
||||
@@ -1638,6 +1610,53 @@ mod tests {
|
||||
assert_eq!(updated.content, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn published_updates_ignore_legacy_snapshot_values() {
|
||||
let (db, dir) = setup();
|
||||
let mut post = create_published_post(&db, &dir, "Published", "canonical body");
|
||||
post.published_title = Some("Different Legacy Title".into());
|
||||
post.published_content = Some("replacement body".into());
|
||||
post.published_tags = Some("[\"legacy\"]".into());
|
||||
qp::update_post(db.conn(), &post).unwrap();
|
||||
|
||||
let identical = update_post(
|
||||
db.conn(),
|
||||
dir.path(),
|
||||
&post.id,
|
||||
Some("Published"),
|
||||
None,
|
||||
None,
|
||||
Some("canonical body"),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(identical.status, PostStatus::Published);
|
||||
|
||||
let changed = update_post(
|
||||
db.conn(),
|
||||
dir.path(),
|
||||
&post.id,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some("replacement body"),
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(changed.status, PostStatus::Draft);
|
||||
assert_eq!(changed.content.as_deref(), Some("replacement body"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn published_title_change_reopens_draft_with_file_body() {
|
||||
let (db, dir) = setup();
|
||||
@@ -1809,9 +1828,12 @@ mod tests {
|
||||
// published_at should be set
|
||||
assert!(from_db.published_at.is_some());
|
||||
|
||||
// Published snapshot fields should be set
|
||||
assert_eq!(from_db.published_title.as_deref(), Some("Publish Me"));
|
||||
assert!(from_db.published_content.is_some());
|
||||
// Legacy published snapshot columns stay empty, matching bDS2.
|
||||
assert!(from_db.published_title.is_none());
|
||||
assert!(from_db.published_content.is_none());
|
||||
assert!(from_db.published_tags.is_none());
|
||||
assert!(from_db.published_categories.is_none());
|
||||
assert!(from_db.published_excerpt.is_none());
|
||||
|
||||
// File should exist on disk
|
||||
let abs_path = dir.path().join(&from_db.file_path);
|
||||
@@ -1823,6 +1845,39 @@ mod tests {
|
||||
assert!(file_content.contains("Publish Me"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn publish_post_preserves_legacy_snapshot_values_without_using_them() {
|
||||
let (db, dir) = setup();
|
||||
let mut post = create_post(
|
||||
db.conn(),
|
||||
dir.path(),
|
||||
"p1",
|
||||
"Publish Me",
|
||||
Some("body"),
|
||||
vec![],
|
||||
vec![],
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
.unwrap();
|
||||
post.published_title = Some("Legacy Title".into());
|
||||
post.published_content = Some("Legacy Body".into());
|
||||
post.published_tags = Some("[\"legacy\"]".into());
|
||||
post.published_categories = Some("[\"old\"]".into());
|
||||
post.published_excerpt = Some("Legacy Excerpt".into());
|
||||
qp::update_post(db.conn(), &post).unwrap();
|
||||
|
||||
publish_post(db.conn(), dir.path(), &post.id).unwrap();
|
||||
|
||||
let published = qp::get_post_by_id(db.conn(), &post.id).unwrap();
|
||||
assert_eq!(published.published_title, post.published_title);
|
||||
assert_eq!(published.published_content, post.published_content);
|
||||
assert_eq!(published.published_tags, post.published_tags);
|
||||
assert_eq!(published.published_categories, post.published_categories);
|
||||
assert_eq!(published.published_excerpt, post.published_excerpt);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn publish_replaces_divergent_post_path_and_ignores_missing_old_file() {
|
||||
let (db, dir) = setup();
|
||||
@@ -1988,7 +2043,7 @@ mod tests {
|
||||
let discarded = discard_post_draft(db.conn(), dir.path(), &post.id).unwrap();
|
||||
assert_eq!(discarded.status, PostStatus::Published);
|
||||
assert_eq!(discarded.title, published.title);
|
||||
assert_eq!(discarded.excerpt, published.published_excerpt);
|
||||
assert_eq!(discarded.excerpt, published.excerpt);
|
||||
assert_eq!(discarded.tags, vec!["one"]);
|
||||
assert_eq!(discarded.categories, vec!["cat"]);
|
||||
assert_eq!(discarded.content, None);
|
||||
|
||||
@@ -598,16 +598,13 @@ pub fn analyze_wxr(
|
||||
}
|
||||
|
||||
fn existing_post_body(post: &Post, data_dir: &Path) -> Option<String> {
|
||||
post.content
|
||||
.clone()
|
||||
.or_else(|| post.published_content.clone())
|
||||
.or_else(|| {
|
||||
(!post.file_path.is_empty())
|
||||
.then(|| fs::read_to_string(data_dir.join(&post.file_path)).ok())
|
||||
.flatten()
|
||||
.and_then(|raw| crate::util::frontmatter::read_post_file(&raw).ok())
|
||||
.map(|(_, body)| body)
|
||||
})
|
||||
post.content.clone().or_else(|| {
|
||||
(!post.file_path.is_empty())
|
||||
.then(|| fs::read_to_string(data_dir.join(&post.file_path)).ok())
|
||||
.flatten()
|
||||
.and_then(|raw| crate::util::frontmatter::read_post_file(&raw).ok())
|
||||
.map(|(_, body)| body)
|
||||
})
|
||||
}
|
||||
|
||||
fn analyze_post(
|
||||
@@ -1363,7 +1360,7 @@ fn import_post_item(
|
||||
qp::update_post(conn, &imported)?;
|
||||
if item.source_status.as_deref() == Some("publish") {
|
||||
imported = post::publish_post(conn, data_dir, &imported.id)?;
|
||||
let body = imported.published_content.clone().unwrap_or_default();
|
||||
let body = content.to_string();
|
||||
imported.created_at = item.created_at.unwrap_or(imported.created_at);
|
||||
imported.updated_at = item.updated_at.unwrap_or(imported.created_at);
|
||||
imported.published_at = item.published_at.or(Some(imported.created_at));
|
||||
|
||||
@@ -96,7 +96,7 @@ pub struct Post {
|
||||
serialize_as = crate::db::types::DbStringList
|
||||
)]
|
||||
pub categories: Vec<String>,
|
||||
// Published snapshot fields (used for diff detection)
|
||||
// Legacy bDS2-compatible columns. Publishing and lifecycle logic leave them untouched.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub published_title: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
|
||||
@@ -635,8 +635,12 @@ fn overwrite_resolution_preserves_existing_post_and_media_identity() {
|
||||
bds_core::db::queries::post::get_post_by_id(db.conn(), &existing_post.id).unwrap();
|
||||
assert_eq!(overwritten_post.id, existing_post.id);
|
||||
assert_eq!(overwritten_post.status, PostStatus::Published);
|
||||
assert!(overwritten_post.published_content.is_none());
|
||||
let (_, overwritten_body) =
|
||||
read_post_file(&fs::read_to_string(dir.path().join(&overwritten_post.file_path)).unwrap())
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
overwritten_post.published_content.as_deref(),
|
||||
Some(overwritten_body.as_str()),
|
||||
report.posts[0].content.as_deref()
|
||||
);
|
||||
let overwritten_media =
|
||||
|
||||
@@ -107,9 +107,9 @@ entity Post {
|
||||
updated_at: Timestamp
|
||||
published_at: Timestamp?
|
||||
|
||||
-- Published snapshot: copy of title/content/tags/categories/excerpt as of
|
||||
-- the last publish. Used by changes_affect_published_content to decide when
|
||||
-- an edit reopens a published post to draft (see ReopenPublishedPost).
|
||||
-- Legacy bDS2-compatible columns. Publishing does not populate them and
|
||||
-- application behaviour does not depend on them; imported non-null values
|
||||
-- remain passive database data.
|
||||
published_title: String?
|
||||
published_content: String?
|
||||
published_tags: String?
|
||||
@@ -200,6 +200,9 @@ rule ReopenPublishedPost {
|
||||
when: UpdatePostRequested(post, changes)
|
||||
requires: post.status = published
|
||||
requires: changes_affect_published_content(changes)
|
||||
-- Compares supplied metadata with the current post fields and supplied
|
||||
-- content with the canonical file body. Legacy published_* columns are
|
||||
-- not consulted because bDS2 does not populate them.
|
||||
ensures: post.status = draft
|
||||
}
|
||||
|
||||
|
||||
@@ -62,7 +62,8 @@ entity Post {
|
||||
language: String? -- ISO 639-1 code
|
||||
do_not_translate: Boolean
|
||||
|
||||
-- Published snapshot columns (written on publish for diff detection)
|
||||
-- Legacy compatibility columns. bDS2 and RuDS leave them unchanged on
|
||||
-- publish and do not use them to determine lifecycle behaviour.
|
||||
published_title: String?
|
||||
published_content: String?
|
||||
published_tags: String?
|
||||
|
||||
Reference in New Issue
Block a user