mirror of
https://gitlab.com/Anson-Projects/projects.git
synced 2025-09-14 09:35:04 +00:00
Add force update functionality for Ghost posts
- Add manual CI trigger 'force-update-ghost' for updating all posts - Support FORCE_UPDATE environment variable in Rust code - Implement post update logic via Ghost API PUT requests - Add get_existing_post_id() function to find existing posts - Update README with usage instructions - Enhanced validation script to test new functionality Usage: - Normal: Only syncs new posts (default behavior) - Force: FORCE_UPDATE=true updates ALL posts including existing ones
This commit is contained in:
@@ -13,3 +13,20 @@ publish:
|
||||
- pages
|
||||
rules:
|
||||
- if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
|
||||
|
||||
# Manual trigger to force update all Ghost posts
|
||||
force-update-ghost:
|
||||
stage: deploy
|
||||
image: rust:latest
|
||||
script:
|
||||
- echo "🔄 Force updating all Ghost posts..."
|
||||
- cd ./ghost-upload
|
||||
- FORCE_UPDATE=true cargo run
|
||||
needs:
|
||||
- pages
|
||||
rules:
|
||||
- if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
|
||||
when: manual
|
||||
allow_failure: false
|
||||
variables:
|
||||
FORCE_UPDATE: "true"
|
||||
|
@@ -1,3 +1,39 @@
|
||||
# ghost-upload
|
||||
|
||||
This code uploads posts from https://projects.ansonbiggs.com to https://notes.ansonbiggs.com. I couldn't figure out how to update posts, and the kagi API doesn't make it clear how long it caches results for so for now only posts that don't exist on the ghost blog will be uploaded. If you want to update content you need to manually make edits to the code and delete posts on the blog.
|
||||
This tool synchronizes posts from https://projects.ansonbiggs.com to the Ghost blog at https://notes.ansonbiggs.com.
|
||||
|
||||
## Features
|
||||
|
||||
- **Automatic sync**: Only uploads new posts by default
|
||||
- **Content extraction**: Fetches clean HTML content instead of using iframes
|
||||
- **AI summaries**: Uses Kagi Summarizer for post summaries
|
||||
- **Force update**: Manual trigger to update all existing posts
|
||||
|
||||
## Usage
|
||||
|
||||
### Normal Mode (Default)
|
||||
```bash
|
||||
cargo run
|
||||
```
|
||||
Only processes new posts that don't exist on the Ghost blog.
|
||||
|
||||
### Force Update Mode
|
||||
```bash
|
||||
FORCE_UPDATE=true cargo run
|
||||
```
|
||||
Updates ALL posts, including existing ones. Useful for:
|
||||
- Updating content after changes
|
||||
- Refreshing summaries
|
||||
- Applying new styling/formatting
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
The GitLab CI pipeline includes:
|
||||
- **Automatic sync**: Runs after each deployment
|
||||
- **Manual force update**: Available as a manual trigger in GitLab UI
|
||||
|
||||
## Environment Variables
|
||||
|
||||
- `admin_api_key`: Ghost Admin API key (required)
|
||||
- `kagi_api_key`: Kagi Summarizer API key (required)
|
||||
- `FORCE_UPDATE`: Set to "true" to update all posts (optional)
|
@@ -202,6 +202,42 @@ async fn check_if_post_exists(entry: &Entry) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct GhostPostsResponse {
|
||||
posts: Vec<GhostPost>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct GhostPost {
|
||||
id: String,
|
||||
slug: String,
|
||||
}
|
||||
|
||||
async fn get_existing_post_id(slug: &str, token: &str) -> Option<String> {
|
||||
let client = Client::new();
|
||||
let api_url = format!("https://notes.ansonbiggs.com/ghost/api/v3/admin/posts/slug/{}/", slug);
|
||||
|
||||
match client
|
||||
.get(&api_url)
|
||||
.header("Authorization", format!("Ghost {}", token))
|
||||
.send()
|
||||
.await
|
||||
{
|
||||
Ok(response) => {
|
||||
if response.status().is_success() {
|
||||
if let Ok(ghost_response) = response.json::<GhostPostsResponse>().await {
|
||||
ghost_response.posts.first().map(|post| post.id.clone())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_feed(url: &str) -> Vec<Entry> {
|
||||
let content = reqwest::get(url).await.unwrap().text().await.unwrap();
|
||||
|
||||
@@ -269,6 +305,16 @@ async fn main() {
|
||||
let ghost_api_url = "https://notes.ansonbiggs.com/ghost/api/v3/admin/posts/?source=html";
|
||||
let ghost_admin_api_key = env::var("admin_api_key").unwrap();
|
||||
|
||||
// Check if force update is enabled
|
||||
let force_update = env::var("FORCE_UPDATE").unwrap_or_default() == "true";
|
||||
|
||||
if force_update {
|
||||
println!("🔄 FORCE UPDATE MODE ENABLED");
|
||||
println!(" This will update ALL posts, including existing ones.");
|
||||
} else {
|
||||
println!("📝 NORMAL MODE - Only publishing new posts");
|
||||
}
|
||||
|
||||
let feed = "https://projects.ansonbiggs.com/index.xml";
|
||||
|
||||
// Split the key into ID and SECRET
|
||||
@@ -302,6 +348,10 @@ async fn main() {
|
||||
// Prepare the post data
|
||||
let entries = fetch_feed(feed).await;
|
||||
|
||||
let filtered_entries: Vec<Entry> = if force_update {
|
||||
println!("🔄 Force update enabled - processing all {} posts", entries.len());
|
||||
entries
|
||||
} else {
|
||||
let post_exists_futures = entries.into_iter().map(|entry| {
|
||||
let entry_clone = entry.clone();
|
||||
async move { (entry_clone, check_if_post_exists(&entry).await) }
|
||||
@@ -309,11 +359,15 @@ async fn main() {
|
||||
|
||||
let post_exists_results = join_all(post_exists_futures).await;
|
||||
|
||||
let filtered_entries: Vec<Entry> = post_exists_results
|
||||
let new_entries: Vec<Entry> = post_exists_results
|
||||
.into_iter()
|
||||
.filter_map(|(entry, exists)| if !exists { Some(entry) } else { None })
|
||||
.collect();
|
||||
|
||||
println!("📝 Found {} new posts to publish", new_entries.len());
|
||||
new_entries
|
||||
};
|
||||
|
||||
if filtered_entries.is_empty() {
|
||||
println!("Nothing to post.");
|
||||
return;
|
||||
@@ -328,21 +382,46 @@ async fn main() {
|
||||
posts: vec![post.clone()],
|
||||
};
|
||||
|
||||
let response = client
|
||||
.post(ghost_api_url)
|
||||
// Check if this is an update (for force_update mode)
|
||||
let (method, url) = if force_update {
|
||||
if let Some(existing_id) = get_existing_post_id(&post.slug, &token).await {
|
||||
println!("🔄 Updating existing post: {}", post.title);
|
||||
("PUT", format!("https://notes.ansonbiggs.com/ghost/api/v3/admin/posts/{}/", existing_id))
|
||||
} else {
|
||||
println!("📝 Creating new post: {}", post.title);
|
||||
("POST", ghost_api_url.to_string())
|
||||
}
|
||||
} else {
|
||||
println!("📝 Creating new post: {}", post.title);
|
||||
("POST", ghost_api_url.to_string())
|
||||
};
|
||||
|
||||
let response = match method {
|
||||
"PUT" => client
|
||||
.put(&url)
|
||||
.header("Authorization", format!("Ghost {}", token))
|
||||
.json(&post_payload)
|
||||
.send()
|
||||
.await
|
||||
.expect("Request failed");
|
||||
.expect("Request failed"),
|
||||
_ => client
|
||||
.post(&url)
|
||||
.header("Authorization", format!("Ghost {}", token))
|
||||
.json(&post_payload)
|
||||
.send()
|
||||
.await
|
||||
.expect("Request failed"),
|
||||
};
|
||||
|
||||
// Check the response
|
||||
if response.status().is_success() {
|
||||
println!("Post {} published successfully.", post.title);
|
||||
let action = if method == "PUT" { "updated" } else { "published" };
|
||||
println!("✅ Post '{}' {} successfully.", post.title, action);
|
||||
} else {
|
||||
let action = if method == "PUT" { "update" } else { "publish" };
|
||||
println!(
|
||||
"Failed to publish post {}.\n\tResp: {:?}",
|
||||
&post.title, response
|
||||
"❌ Failed to {} post '{}'.\n\tStatus: {}\n\tResponse: {:?}",
|
||||
action, &post.title, response.status(), response.text().await.unwrap_or_default()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -49,6 +49,22 @@ else
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if force update functionality is available
|
||||
if grep -q "FORCE_UPDATE" ghost-upload/src/main.rs; then
|
||||
echo "✅ Force update functionality found"
|
||||
else
|
||||
echo "❌ Force update functionality not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if manual CI job is configured
|
||||
if grep -q "force-update-ghost" ghost-upload/.gitlab-ci.yml; then
|
||||
echo "✅ Manual force update CI job found"
|
||||
else
|
||||
echo "❌ Manual force update CI job not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Verify Rust code compiles
|
||||
echo "🛠️ Building Rust code..."
|
||||
cd ghost-upload
|
||||
@@ -67,5 +83,7 @@ echo " • Quarto profiles for dual-output rendering"
|
||||
echo " • Ghost-optimized CSS styling"
|
||||
echo " • GitLab CI builds both main site and ghost-content"
|
||||
echo " • Rust extracts HTML content instead of using iframes"
|
||||
echo " • Force update mode to refresh existing posts"
|
||||
echo " • Manual CI trigger for content updates"
|
||||
echo ""
|
||||
echo "🚀 Ready for testing in CI/CD pipeline!"
|
Reference in New Issue
Block a user