Skip to content

Commit

Permalink
chore: refactor & proof 1
Browse files Browse the repository at this point in the history
  • Loading branch information
FlakM committed Dec 28, 2023
1 parent 56954d1 commit 90b4132
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 158 deletions.
8 changes: 3 additions & 5 deletions backend/src/activities/create_post.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{
error::Error,
objects::{person::DbUser, post::Note},
utils::generate_object_id,
DbPost,
FediPost,
};
use activitypub_federation::{
activity_sending::SendActivityTask,
Expand Down Expand Up @@ -53,7 +53,6 @@ impl CreatePost {
};
let create_with_context = WithContext::new_default(create);
let local_user = data.blog_user().await?;
println!(">>>> inbox: {:?}", inbox);
let sends =
SendActivityTask::prepare(&create_with_context, &local_user, vec![inbox], data).await?;
for send in sends {
Expand All @@ -77,12 +76,11 @@ impl ActivityHandler for CreatePost {
}

async fn verify(&self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
DbPost::verify(&self.object, &self.id, data).await?;
FediPost::verify(&self.object, &self.id, data).await?;
Ok(())
}

async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
DbPost::from_json(self.object, data).await?;
async fn receive(self, _data: &Data<Self::DataType>) -> Result<(), Self::Error> {
Ok(())
}
}
20 changes: 1 addition & 19 deletions backend/src/http.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
use crate::{
database::Repository,
error::Error,
objects::{
person::{DbUser, Person, PersonAcceptedActivities},
post::DbPost,
}, utils::generate_object_id,
objects::person::{DbUser, Person, PersonAcceptedActivities},
};
use activitypub_federation::{
axum::{
Expand Down Expand Up @@ -77,21 +74,6 @@ pub async fn webfinger(
)))
}

#[debug_handler]
pub async fn http_post_to_followers(data: Data<Repository>) -> Result<impl IntoResponse, Error> {
let local_user = data.blog_user().await?;
let post = DbPost {
text: "<p>Hello worl</p>".to_string(),
ap_id: generate_object_id(data.domain())?.into(),
creator: local_user.ap_id.clone(),
local: true,
};

local_user.post(post, &data).await?;

Ok(StatusCode::OK)
}

#[debug_handler]
pub async fn http_get_user_followers(
Path(name): Path<String>,
Expand Down
61 changes: 36 additions & 25 deletions backend/src/blog_posts.rs → backend/src/hugo_posts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,37 @@ use serde::{Deserialize, Serialize};
use sqlx::{sqlite::SqliteRow, Error, FromRow, Row};
use url::Url;

use crate::{objects::{person::DbUser, post::DbPost}, utils::generate_object_id};

#[derive(Serialize, Deserialize, Debug)]
use crate::{
objects::{person::DbUser, post::FediPost},
utils::generate_object_id,
};

/// Represents a blog post from the static site generator
/// This is the format of the json file that is generated by the static site generator
/// and contains the blog posts that might require publishing
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct BlogPost {
pub struct HugoBlogPost {
/// Human-readable title - it should be a short sentence - single line
title: String,
pub title: String,
/// The identifier for the post a slug from frontmatter or the filename
/// if no slug is provided.
///
/// This is used as main identifier in blog_posts database table
pub slug: String,
/// A short description of the post - it should be a single paragraph or two
description: String,
pub description: String,
/// The date the post was created
date: DateTime<Utc>,
pub date: DateTime<Utc>,
/// An image to use as the featured image for the blog post
featured_image: Option<String>,
/// List of tags for the post it will be mapped to mastodon tags
tags: Option<Vec<String>>,
pub tags: Option<Vec<String>>,
/// The URL of the post itself
url: Url,
pub url: Url,
}

impl FromRow<'_, SqliteRow> for BlogPost {
impl FromRow<'_, SqliteRow> for HugoBlogPost {
fn from_row(row: &SqliteRow) -> Result<Self, Error> {
let title: String = row.try_get("title")?;
let slug: String = row.try_get("slug")?;
Expand All @@ -49,7 +55,7 @@ impl FromRow<'_, SqliteRow> for BlogPost {
// Parse the tags if they exist
let tags = tags_str.map(|s| s.split(',').map(String::from).collect());

Ok(BlogPost {
Ok(HugoBlogPost {
title,
slug,
description,
Expand All @@ -61,9 +67,9 @@ impl FromRow<'_, SqliteRow> for BlogPost {
}
}

type BlogPosts = Vec<BlogPost>;
type BlogPosts = Vec<HugoBlogPost>;

impl BlogPost {
impl HugoBlogPost {
/// Read and deserialize the blog posts from the filesystem file
pub fn load_new_posts(path: impl AsRef<Path>) -> Result<BlogPosts, crate::Error> {
let file = File::open(path)?;
Expand All @@ -73,17 +79,12 @@ impl BlogPost {
Ok(blog_posts)
}

pub fn to_post(&self, local_user: &DbUser, domain: &str) -> Result<DbPost, crate::Error> {
let content = format!(
r#"<p>{}</p><p>{}</p><p><a href="{}">Read more</a></p>"#,
self.title, self.description, self.url
);

Ok(DbPost {
text: content,
pub fn into_post(self, local_user: &DbUser, domain: &str) -> Result<FediPost, crate::Error> {
Ok(FediPost {
ap_id: generate_object_id(domain)?.into(),
creator: local_user.ap_id.clone(),
local: true,
local: false,
blog_post: self,
})
}
}
Expand All @@ -95,7 +96,7 @@ pub struct BlogRepository {
impl BlogRepository {
/// Create a new blog post entry in the database - called when a new blog post is present in
/// json file from static site generator
pub async fn new_blog_entry(&self, blog_post: &BlogPost) -> Result<(), Error> {
pub async fn new_blog_entry(&self, blog_post: &HugoBlogPost) -> Result<(), Error> {
let tags_str = blog_post.tags.clone().map(|tags| tags.join(","));
sqlx::query(
"INSERT INTO blog_posts (title, slug, description, date, featured_image, tags, url) VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT(slug) DO UPDATE SET title = excluded.title, description = excluded.description, date = excluded.date, featured_image = excluded.featured_image, tags = excluded.tags, url = excluded.url",
Expand All @@ -116,13 +117,23 @@ impl BlogRepository {
/// Get all blog posts that have not been published yet
/// post is published when it is posted to mastodon
/// and the record is present in blog_posts_published
pub async fn get_unpublished_blog_posts(&self) -> Result<Vec<BlogPost>, Error> {
let blog_posts = sqlx::query_as::<_, BlogPost>(
pub async fn get_unpublished_blog_posts(&self) -> Result<Vec<HugoBlogPost>, Error> {
let blog_posts = sqlx::query_as::<_, HugoBlogPost>(
"SELECT * FROM blog_posts WHERE slug NOT IN (SELECT slug FROM blog_posts_published)",
)
.fetch_all(&self.db)
.await?;

Ok(blog_posts)
}

/// Mark a blog post as published
pub async fn mark_as_published(&self, blog_post: &HugoBlogPost) -> Result<(), Error> {
sqlx::query("INSERT INTO blog_posts_published (slug) VALUES (?)")
.bind(&blog_post.slug)
.execute(&self.db)
.await?;

Ok(())
}
}
54 changes: 36 additions & 18 deletions backend/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
use crate::blog_posts::BlogPost;
use crate::database::Repository;
use crate::http::http_get_user_followers;
use crate::hugo_posts::HugoBlogPost;
use crate::{
database::SqlDatabase,
http::{http_get_user, http_post_to_followers, http_post_user_inbox, webfinger},
objects::{person::DbUser, post::DbPost},
http::{http_get_user, http_post_user_inbox, webfinger},
objects::{person::DbUser, post::FediPost},
utils::generate_object_id,
};
use activitypub_federation::config::{FederationConfig, FederationMiddleware};
Expand All @@ -28,17 +28,17 @@ use tracing::{field, info, info_span, Span};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};

mod activities;
mod blog_posts;
mod database;
mod error;
#[allow(clippy::diverging_sub_expression, clippy::items_after_statements)]
mod http;
mod hugo_posts;
mod objects;
mod utils;

const DOMAIN: &str = "fedi.flakm.com";
const LOCAL_USER_NAME: &str = "blog_test2";
const BIND_ADDRESS: &str = "127.0.0.1:3000";
const DOMAIN: &str = "fedi.flakm.com";

#[tokio::main]
async fn main() -> Result<(), Error> {
Expand Down Expand Up @@ -66,8 +66,8 @@ async fn main() -> Result<(), Error> {
info!("Migrations run");

let posts_path = std::env::args().nth(1).expect("No posts file given");
let blog_repo = blog_posts::BlogRepository { db: pool.clone() };
let blog_posts = BlogPost::load_new_posts(posts_path).expect("Failed to load blog posts");
let blog_repo = hugo_posts::BlogRepository { db: pool.clone() };
let blog_posts = HugoBlogPost::load_new_posts(posts_path).expect("Failed to load blog posts");

for blog_post in blog_posts {
info!("Processing: {}", blog_post.slug);
Expand Down Expand Up @@ -102,18 +102,22 @@ async fn main() -> Result<(), Error> {
let data = config.to_request_data();
let mut to_be_published = vec![];
for post in blog_repo.get_unpublished_blog_posts().await? {
let post = post.to_post(&local_user, data.domain())?;
to_be_published.push(post.clone());
let post = post.into_post(&local_user, data.domain())?;
to_be_published.push(post);
}

tokio::spawn(async move {
let publish = async move {
// wait 5 seconds before publishing for the server to start
tokio::time::sleep(Duration::from_secs(5)).await;
tracing::info!("Publishing posts");
tracing::info!("Publishing posts...");
to_be_published.sort_by(|a, b| a.blog_post.date.cmp(&b.blog_post.date));
for post in to_be_published {
tracing::info!("Publishing post: {}", post.ap_id);
local_user.post(post, &data).await.unwrap();
tracing::info!("Publishing post: {}", post.blog_post.slug);
local_user.post(&post, &data).await.unwrap();
blog_repo.mark_as_published(&post.blog_post).await.unwrap();
}
});
Ok::<_, Error>(())
};

info!("Listen with HTTP server on {BIND_ADDRESS}");
let config = config.clone();
Expand All @@ -122,7 +126,6 @@ async fn main() -> Result<(), Error> {
.route("/:user/inbox", post(http_post_user_inbox))
.route("/:user/followers", get(http_get_user_followers))
.route("/.well-known/webfinger", get(webfinger))
.route("/followers", post(http_post_to_followers))
.layer(
TraceLayer::new_for_http()
.make_span_with(|request: &Request<_>| {
Expand Down Expand Up @@ -159,9 +162,24 @@ async fn main() -> Result<(), Error> {
.to_socket_addrs()?
.next()
.expect("Failed to lookup domain name");
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await?;
let server = async {
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.map_err(Error::from)
};

// use try_join to run the server and the publish task concurrently
let res = tokio::try_join!(server, publish);

match res {
Ok((_first, _second)) => {
// do something with the values
}
Err(err) => {
panic!("processing failed; error = {}", err);
}
};

Ok(())
}
15 changes: 11 additions & 4 deletions backend/src/objects/person.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use sqlx::{sqlite::SqliteRow, FromRow, Row};
use std::fmt::Debug;
use url::Url;

use super::post::DbPost;
use super::post::FediPost;

#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum ImageType {
Expand Down Expand Up @@ -147,9 +147,9 @@ impl DbUser {
Ok(Url::parse(&format!("{}/followers", self.ap_id.inner()))?)
}

pub async fn post(&self, post: DbPost, data: &Data<Repository>) -> Result<(), Error> {
pub async fn post(&self, post: &FediPost, data: &Data<Repository>) -> Result<(), Error> {
let id = generate_object_id(data.domain())?;
let create = CreatePost::new(post.into_json(data).await?, id.clone());
let create = CreatePost::new(post.clone().into_json(data).await?, id.clone());
let mut inboxes = vec![];

for f in data.user_followers(self).await? {
Expand All @@ -158,7 +158,14 @@ impl DbUser {
inboxes.push(mailbox);
}

tracing::info!("Sending post to {:?}", inboxes);
tracing::info!(
"Sending post to inboxes [{}]",
inboxes
.iter()
.map(|i| i.as_str())
.collect::<Vec<_>>()
.join(", ")
);
self.send(create, inboxes, data).await?;
Ok(())
}
Expand Down
Loading

0 comments on commit 90b4132

Please sign in to comment.