1
0
Fork 0
mirror of https://github.com/Nutomic/ibis.git synced 2024-11-24 11:51:09 +00:00

Use db pool

This commit is contained in:
Felix Ableitner 2024-02-27 17:49:36 +01:00
parent dd56a3f0d5
commit d22654261f
31 changed files with 211 additions and 182 deletions

21
Cargo.lock generated
View file

@ -906,6 +906,7 @@ dependencies = [
"diesel_derives", "diesel_derives",
"itoa", "itoa",
"pq-sys", "pq-sys",
"r2d2",
"uuid", "uuid",
] ]
@ -2750,6 +2751,17 @@ dependencies = [
"syn 2.0.51", "syn 2.0.51",
] ]
[[package]]
name = "r2d2"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93"
dependencies = [
"log",
"parking_lot",
"scheduled-thread-pool",
]
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.8.5" version = "0.8.5"
@ -3033,6 +3045,15 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "scheduled-thread-pool"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19"
dependencies = [
"parking_lot",
]
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.2.0" version = "1.2.0"

View file

@ -44,6 +44,7 @@ diesel = { version = "2.1.4", features = [
"postgres", "postgres",
"chrono", "chrono",
"uuid", "uuid",
"r2d2"
], optional = true } ], optional = true }
diesel-derive-newtype = { version = "2.1.0", optional = true } diesel-derive-newtype = { version = "2.1.0", optional = true }
diesel_migrations = { version = "2.1.0", optional = true } diesel_migrations = { version = "2.1.0", optional = true }

View file

@ -1,12 +1,17 @@
# Address where ibis should listen for incoming requests # Address where ibis should listen for incoming requests
bind = "127.0.0.1:8081" bind = "127.0.0.1:8081"
# Database connection url
database_url = "postgres://ibis:password@localhost:5432/ibis"
# Whether users can create new accounts # Whether users can create new accounts
registration_open = true registration_open = true
# Details about the PostgreSQL database connection
[database]
# Database connection url
connection_url = "postgres://ibis:password@localhost:5432/ibis"
# Database connection pool size
pool_size = 5
# Details of the initial admin account # Details of the initial admin account
[setup] [setup]
admin_username = "ibis" admin_username = "ibis"
@ -22,4 +27,4 @@ domain = "example.com"
allowlist = "good.com,friends.org" allowlist = "good.com,friends.org"
# Comma separated list of instances which are blocked for federation; optional # Comma separated list of instances which are blocked for federation; optional
blocklist = "evil.com,bad.org" blocklist = "evil.com,bad.org"

View file

@ -36,7 +36,7 @@ pub(in crate::backend::api) async fn create_article(
return Err(anyhow!("Title must not be empty").into()); return Err(anyhow!("Title must not be empty").into());
} }
let local_instance = DbInstance::read_local_instance(&data.db_connection)?; let local_instance = DbInstance::read_local_instance(&data)?;
let ap_id = ObjectId::parse(&format!( let ap_id = ObjectId::parse(&format!(
"{}://{}:{}/article/{}", "{}://{}:{}/article/{}",
http_protocol_str(), http_protocol_str(),
@ -51,18 +51,18 @@ pub(in crate::backend::api) async fn create_article(
instance_id: local_instance.id, instance_id: local_instance.id,
local: true, local: true,
}; };
let article = DbArticle::create(form, &data.db_connection)?; let article = DbArticle::create(form, &data)?;
let edit_data = EditArticleData { let edit_data = EditArticleData {
article_id: article.id, article_id: article.id,
new_text: create_article.text, new_text: create_article.text,
summary: create_article.summary, summary: create_article.summary,
previous_version_id: article.latest_edit_version(&data.db_connection)?, previous_version_id: article.latest_edit_version(&data)?,
resolve_conflict_id: None, resolve_conflict_id: None,
}; };
let _ = edit_article(Extension(user), data.reset_request_count(), Form(edit_data)).await?; let _ = edit_article(Extension(user), data.reset_request_count(), Form(edit_data)).await?;
let article_view = DbArticle::read_view(article.id, &data.db_connection)?; let article_view = DbArticle::read_view(article.id, &data)?;
CreateArticle::send_to_followers(article_view.article.clone(), &data).await?; CreateArticle::send_to_followers(article_view.article.clone(), &data).await?;
Ok(Json(article_view)) Ok(Json(article_view))
@ -85,9 +85,9 @@ pub(in crate::backend::api) async fn edit_article(
) -> MyResult<Json<Option<ApiConflict>>> { ) -> MyResult<Json<Option<ApiConflict>>> {
// resolve conflict if any // resolve conflict if any
if let Some(resolve_conflict_id) = edit_form.resolve_conflict_id { if let Some(resolve_conflict_id) = edit_form.resolve_conflict_id {
DbConflict::delete(resolve_conflict_id, &data.db_connection)?; DbConflict::delete(resolve_conflict_id, &data)?;
} }
let original_article = DbArticle::read_view(edit_form.article_id, &data.db_connection)?; let original_article = DbArticle::read_view(edit_form.article_id, &data)?;
if edit_form.new_text == original_article.article.text { if edit_form.new_text == original_article.article.text {
return Err(anyhow!("Edit contains no changes").into()); return Err(anyhow!("Edit contains no changes").into());
} }
@ -119,7 +119,7 @@ pub(in crate::backend::api) async fn edit_article(
generate_article_version(&original_article.edits, &edit_form.previous_version_id)?; generate_article_version(&original_article.edits, &edit_form.previous_version_id)?;
let patch = create_patch(&ancestor, &edit_form.new_text); let patch = create_patch(&ancestor, &edit_form.new_text);
let previous_version = DbEdit::read(&edit_form.previous_version_id, &data.db_connection)?; let previous_version = DbEdit::read(&edit_form.previous_version_id, &data)?;
let form = DbConflictForm { let form = DbConflictForm {
hash: EditVersion::new(&patch.to_string()), hash: EditVersion::new(&patch.to_string()),
diff: patch.to_string(), diff: patch.to_string(),
@ -128,7 +128,7 @@ pub(in crate::backend::api) async fn edit_article(
article_id: original_article.article.id, article_id: original_article.article.id,
previous_version_id: previous_version.hash, previous_version_id: previous_version.hash,
}; };
let conflict = DbConflict::create(&form, &data.db_connection)?; let conflict = DbConflict::create(&form, &data)?;
Ok(Json(conflict.to_api_conflict(&data).await?)) Ok(Json(conflict.to_api_conflict(&data).await?))
} }
} }
@ -143,13 +143,13 @@ pub(in crate::backend::api) async fn get_article(
(Some(title), None) => Ok(Json(DbArticle::read_view_title( (Some(title), None) => Ok(Json(DbArticle::read_view_title(
&title, &title,
query.domain, query.domain,
&data.db_connection, &data,
)?)), )?)),
(None, Some(id)) => { (None, Some(id)) => {
if query.domain.is_some() { if query.domain.is_some() {
return Err(anyhow!("Cant combine id and instance_domain").into()); return Err(anyhow!("Cant combine id and instance_domain").into());
} }
Ok(Json(DbArticle::read_view(id, &data.db_connection)?)) Ok(Json(DbArticle::read_view(id, &data)?))
} }
_ => Err(anyhow!("Must pass exactly one of title, id").into()), _ => Err(anyhow!("Must pass exactly one of title, id").into()),
} }
@ -161,7 +161,7 @@ pub(in crate::backend::api) async fn list_articles(
data: Data<IbisData>, data: Data<IbisData>,
) -> MyResult<Json<Vec<DbArticle>>> { ) -> MyResult<Json<Vec<DbArticle>>> {
let only_local = query.only_local.unwrap_or(false); let only_local = query.only_local.unwrap_or(false);
Ok(Json(DbArticle::read_all(only_local, &data.db_connection)?)) Ok(Json(DbArticle::read_all(only_local, &data)?))
} }
/// Fork a remote article to local instance. This is useful if there are disagreements about /// Fork a remote article to local instance. This is useful if there are disagreements about
@ -173,9 +173,9 @@ pub(in crate::backend::api) async fn fork_article(
Form(fork_form): Form<ForkArticleData>, Form(fork_form): Form<ForkArticleData>,
) -> MyResult<Json<ArticleView>> { ) -> MyResult<Json<ArticleView>> {
// TODO: lots of code duplicated from create_article(), can move it into helper // TODO: lots of code duplicated from create_article(), can move it into helper
let original_article = DbArticle::read(fork_form.article_id, &data.db_connection)?; let original_article = DbArticle::read(fork_form.article_id, &data)?;
let local_instance = DbInstance::read_local_instance(&data.db_connection)?; let local_instance = DbInstance::read_local_instance(&data)?;
let ap_id = ObjectId::parse(&format!( let ap_id = ObjectId::parse(&format!(
"{}://{}:{}/article/{}", "{}://{}:{}/article/{}",
http_protocol_str(), http_protocol_str(),
@ -190,11 +190,11 @@ pub(in crate::backend::api) async fn fork_article(
instance_id: local_instance.id, instance_id: local_instance.id,
local: true, local: true,
}; };
let article = DbArticle::create(form, &data.db_connection)?; let article = DbArticle::create(form, &data)?;
// copy edits to new article // copy edits to new article
// this could also be done in sql // this could also be done in sql
let edits = DbEdit::read_for_article(&original_article, &data.db_connection)? let edits = DbEdit::read_for_article(&original_article, &data)?
.into_iter() .into_iter()
.map(|e| e.edit) .map(|e| e.edit)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
@ -210,12 +210,12 @@ pub(in crate::backend::api) async fn fork_article(
previous_version_id: e.previous_version_id, previous_version_id: e.previous_version_id,
created: Utc::now(), created: Utc::now(),
}; };
DbEdit::create(&form, &data.db_connection)?; DbEdit::create(&form, &data)?;
} }
CreateArticle::send_to_followers(article.clone(), &data).await?; CreateArticle::send_to_followers(article.clone(), &data).await?;
Ok(Json(DbArticle::read_view(article.id, &data.db_connection)?)) Ok(Json(DbArticle::read_view(article.id, &data)?))
} }
/// Fetch a remote article, including edits collection. Allows viewing and editing. Note that new /// Fetch a remote article, including edits collection. Allows viewing and editing. Note that new
@ -226,7 +226,7 @@ pub(super) async fn resolve_article(
data: Data<IbisData>, data: Data<IbisData>,
) -> MyResult<Json<ArticleView>> { ) -> MyResult<Json<ArticleView>> {
let article: DbArticle = ObjectId::from(query.id).dereference(&data).await?; let article: DbArticle = ObjectId::from(query.id).dereference(&data).await?;
let edits = DbEdit::read_for_article(&article, &data.db_connection)?; let edits = DbEdit::read_for_article(&article, &data)?;
let latest_version = edits.last().unwrap().edit.hash.clone(); let latest_version = edits.last().unwrap().edit.hash.clone();
Ok(Json(ArticleView { Ok(Json(ArticleView {
article, article,
@ -244,6 +244,6 @@ pub(super) async fn search_article(
if query.query.is_empty() { if query.query.is_empty() {
return Err(anyhow!("Query is empty").into()); return Err(anyhow!("Query is empty").into());
} }
let article = DbArticle::search(&query.query, &data.db_connection)?; let article = DbArticle::search(&query.query, &data)?;
Ok(Json(article)) Ok(Json(article))
} }

View file

@ -27,10 +27,10 @@ pub(in crate::backend::api) async fn follow_instance(
data: Data<IbisData>, data: Data<IbisData>,
Form(query): Form<FollowInstance>, Form(query): Form<FollowInstance>,
) -> MyResult<()> { ) -> MyResult<()> {
let target = DbInstance::read(query.id, &data.db_connection)?; let target = DbInstance::read(query.id, &data)?;
let pending = !target.local; let pending = !target.local;
DbInstance::follow(&user.person, &target, pending, &data)?; DbInstance::follow(&user.person, &target, pending, &data)?;
let instance = DbInstance::read(query.id, &data.db_connection)?; let instance = DbInstance::read(query.id, &data)?;
Follow::send(user.person, &instance, &data).await?; Follow::send(user.person, &instance, &data).await?;
Ok(()) Ok(())
} }

View file

@ -74,7 +74,7 @@ async fn edit_conflicts(
Extension(user): Extension<LocalUserView>, Extension(user): Extension<LocalUserView>,
data: Data<IbisData>, data: Data<IbisData>,
) -> MyResult<Json<Vec<ApiConflict>>> { ) -> MyResult<Json<Vec<ApiConflict>>> {
let conflicts = DbConflict::list(&user.local_user, &data.db_connection)?; let conflicts = DbConflict::list(&user.local_user, &data)?;
let conflicts: Vec<ApiConflict> = try_join_all(conflicts.into_iter().map(|c| { let conflicts: Vec<ApiConflict> = try_join_all(conflicts.into_iter().map(|c| {
let data = data.reset_request_count(); let data = data.reset_request_count();
async move { c.to_api_conflict(&data).await } async move { c.to_api_conflict(&data).await }

View file

@ -3,18 +3,17 @@ use doku::Document;
use serde::Deserialize; use serde::Deserialize;
use smart_default::SmartDefault; use smart_default::SmartDefault;
use std::net::SocketAddr; use std::net::SocketAddr;
use crate::backend::error::MyResult;
#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Document, SmartDefault)] #[derive(Debug, Deserialize, PartialEq, Eq, Clone, Document, SmartDefault)]
#[serde(default)] #[serde(default)]
pub struct IbisConfig { pub struct IbisConfig {
/// Address where ibis should listen for incoming requests /// Address where ibis should listen for incoming requests
#[default("127.0.0.1:8081".parse().unwrap())] #[default("127.0.0.1:8081".parse().expect("parse config bind"))]
#[doku(as = "String", example = "127.0.0.1:8081")] #[doku(as = "String", example = "127.0.0.1:8081")]
pub bind: SocketAddr, pub bind: SocketAddr,
/// Database connection url /// Details about the PostgreSQL database connection
#[default("postgres://ibis:password@localhost:5432/ibis")] pub database: IbisConfigDatabase,
#[doku(example = "postgres://ibis:password@localhost:5432/ibis")]
pub database_url: String,
/// Whether users can create new accounts /// Whether users can create new accounts
#[default = true] #[default = true]
#[doku(example = "true")] #[doku(example = "true")]
@ -25,18 +24,30 @@ pub struct IbisConfig {
} }
impl IbisConfig { impl IbisConfig {
pub fn read() -> Self { pub fn read() -> MyResult<Self> {
let config = Config::builder() let config = Config::builder()
.add_source(config::File::with_name("config/config.toml")) .add_source(config::File::with_name("config/config.toml"))
// Cant use _ as separator due to https://github.com/mehcode/config-rs/issues/391 // Cant use _ as separator due to https://github.com/mehcode/config-rs/issues/391
.add_source(config::Environment::with_prefix("IBIS").separator("__")) .add_source(config::Environment::with_prefix("IBIS").separator("__"))
.build() .build()?;
.unwrap();
config.try_deserialize().unwrap() Ok(config.try_deserialize()?)
} }
} }
#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Document, SmartDefault)]
#[serde(default)]
pub struct IbisConfigDatabase {
/// Database connection url
#[default("postgres://ibis:password@localhost:5432/ibis")]
#[doku(example = "postgres://ibis:password@localhost:5432/ibis")]
pub connection_url: String,
/// Database connection pool size
#[default(5)]
#[doku(example = "5")]
pub pool_size: u32,
}
#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Document, SmartDefault)] #[derive(Debug, Deserialize, PartialEq, Eq, Clone, Document, SmartDefault)]
#[serde(default)] #[serde(default)]
pub struct IbisConfigSetup { pub struct IbisConfigSetup {

View file

@ -1,4 +1,5 @@
use crate::backend::database::schema::{article, edit, instance}; use crate::backend::database::schema::{article, edit, instance};
use crate::backend::database::IbisData;
use crate::backend::error::MyResult; use crate::backend::error::MyResult;
use crate::backend::federation::objects::edits_collection::DbEditCollection; use crate::backend::federation::objects::edits_collection::DbEditCollection;
use crate::common::DbEdit; use crate::common::DbEdit;
@ -7,14 +8,14 @@ use crate::common::{ArticleView, DbArticle};
use activitypub_federation::fetch::collection_id::CollectionId; use activitypub_federation::fetch::collection_id::CollectionId;
use activitypub_federation::fetch::object_id::ObjectId; use activitypub_federation::fetch::object_id::ObjectId;
use diesel::dsl::max; use diesel::dsl::max;
use diesel::pg::PgConnection;
use diesel::ExpressionMethods; use diesel::ExpressionMethods;
use diesel::{ use diesel::{
insert_into, AsChangeset, BoolExpressionMethods, Insertable, PgTextExpressionMethods, QueryDsl, insert_into, AsChangeset, BoolExpressionMethods, Insertable, PgTextExpressionMethods, QueryDsl,
RunQueryDsl, RunQueryDsl,
}; };
use std::ops::DerefMut; use std::ops::DerefMut;
use std::sync::Mutex;
#[derive(Debug, Clone, Insertable, AsChangeset)] #[derive(Debug, Clone, Insertable, AsChangeset)]
#[diesel(table_name = article, check_for_backend(diesel::pg::Pg))] #[diesel(table_name = article, check_for_backend(diesel::pg::Pg))]
@ -32,17 +33,17 @@ impl DbArticle {
Ok(CollectionId::parse(&format!("{}/edits", self.ap_id))?) Ok(CollectionId::parse(&format!("{}/edits", self.ap_id))?)
} }
pub fn create(mut form: DbArticleForm, conn: &Mutex<PgConnection>) -> MyResult<Self> { pub fn create(mut form: DbArticleForm, data: &IbisData) -> MyResult<Self> {
form.title = form.title.replace(' ', "_"); form.title = form.title.replace(' ', "_");
let mut conn = conn.lock().unwrap(); let mut conn = data.db_pool.get()?;
Ok(insert_into(article::table) Ok(insert_into(article::table)
.values(form) .values(form)
.get_result(conn.deref_mut())?) .get_result(conn.deref_mut())?)
} }
pub fn create_or_update(mut form: DbArticleForm, conn: &Mutex<PgConnection>) -> MyResult<Self> { pub fn create_or_update(mut form: DbArticleForm, data: &IbisData) -> MyResult<Self> {
form.title = form.title.replace(' ', "_"); form.title = form.title.replace(' ', "_");
let mut conn = conn.lock().unwrap(); let mut conn = data.db_pool.get()?;
Ok(insert_into(article::table) Ok(insert_into(article::table)
.values(&form) .values(&form)
.on_conflict(article::dsl::ap_id) .on_conflict(article::dsl::ap_id)
@ -51,25 +52,23 @@ impl DbArticle {
.get_result(conn.deref_mut())?) .get_result(conn.deref_mut())?)
} }
pub fn update_text(id: i32, text: &str, conn: &Mutex<PgConnection>) -> MyResult<Self> { pub fn update_text(id: i32, text: &str, data: &IbisData) -> MyResult<Self> {
let mut conn = conn.lock().unwrap(); let mut conn = data.db_pool.get()?;
Ok(diesel::update(article::dsl::article.find(id)) Ok(diesel::update(article::dsl::article.find(id))
.set(article::dsl::text.eq(text)) .set(article::dsl::text.eq(text))
.get_result::<Self>(conn.deref_mut())?) .get_result::<Self>(conn.deref_mut())?)
} }
pub fn read(id: i32, conn: &Mutex<PgConnection>) -> MyResult<Self> { pub fn read(id: i32, data: &IbisData) -> MyResult<Self> {
let mut conn = conn.lock().unwrap(); let mut conn = data.db_pool.get()?;
Ok(article::table.find(id).get_result(conn.deref_mut())?) Ok(article::table.find(id).get_result(conn.deref_mut())?)
} }
pub fn read_view(id: i32, conn: &Mutex<PgConnection>) -> MyResult<ArticleView> { pub fn read_view(id: i32, data: &IbisData) -> MyResult<ArticleView> {
let article: DbArticle = { let mut conn = data.db_pool.get()?;
let mut conn = conn.lock().unwrap(); let article: DbArticle = { article::table.find(id).get_result(conn.deref_mut())? };
article::table.find(id).get_result(conn.deref_mut())? let latest_version = article.latest_edit_version(data)?;
}; let edits = DbEdit::read_for_article(&article, data)?;
let latest_version = article.latest_edit_version(conn)?;
let edits = DbEdit::read_for_article(&article, conn)?;
Ok(ArticleView { Ok(ArticleView {
article, article,
edits, edits,
@ -80,10 +79,10 @@ impl DbArticle {
pub fn read_view_title( pub fn read_view_title(
title: &str, title: &str,
domain: Option<String>, domain: Option<String>,
conn: &Mutex<PgConnection>, data: &IbisData,
) -> MyResult<ArticleView> { ) -> MyResult<ArticleView> {
let mut conn = data.db_pool.get()?;
let article: DbArticle = { let article: DbArticle = {
let mut conn = conn.lock().unwrap();
let query = article::table let query = article::table
.inner_join(instance::table) .inner_join(instance::table)
.filter(article::dsl::title.eq(title)) .filter(article::dsl::title.eq(title))
@ -99,8 +98,8 @@ impl DbArticle {
.select(article::all_columns) .select(article::all_columns)
.get_result(conn.deref_mut())? .get_result(conn.deref_mut())?
}; };
let latest_version = article.latest_edit_version(conn)?; let latest_version = article.latest_edit_version(data)?;
let edits = DbEdit::read_for_article(&article, conn)?; let edits = DbEdit::read_for_article(&article, data)?;
Ok(ArticleView { Ok(ArticleView {
article, article,
edits, edits,
@ -108,18 +107,15 @@ impl DbArticle {
}) })
} }
pub fn read_from_ap_id( pub fn read_from_ap_id(ap_id: &ObjectId<DbArticle>, data: &IbisData) -> MyResult<Self> {
ap_id: &ObjectId<DbArticle>, let mut conn = data.db_pool.get()?;
conn: &Mutex<PgConnection>,
) -> MyResult<Self> {
let mut conn = conn.lock().unwrap();
Ok(article::table Ok(article::table
.filter(article::dsl::ap_id.eq(ap_id)) .filter(article::dsl::ap_id.eq(ap_id))
.get_result(conn.deref_mut())?) .get_result(conn.deref_mut())?)
} }
pub fn read_local_title(title: &str, conn: &Mutex<PgConnection>) -> MyResult<Self> { pub fn read_local_title(title: &str, data: &IbisData) -> MyResult<Self> {
let mut conn = conn.lock().unwrap(); let mut conn = data.db_pool.get()?;
Ok(article::table Ok(article::table
.filter(article::dsl::title.eq(title)) .filter(article::dsl::title.eq(title))
.filter(article::dsl::local.eq(true)) .filter(article::dsl::local.eq(true))
@ -127,8 +123,8 @@ impl DbArticle {
} }
/// Read all articles, ordered by most recently edited first. /// Read all articles, ordered by most recently edited first.
pub fn read_all(only_local: bool, conn: &Mutex<PgConnection>) -> MyResult<Vec<Self>> { pub fn read_all(only_local: bool, data: &IbisData) -> MyResult<Vec<Self>> {
let mut conn = conn.lock().unwrap(); let mut conn = data.db_pool.get()?;
let query = article::table let query = article::table
.inner_join(edit::table) .inner_join(edit::table)
.group_by(article::dsl::id) .group_by(article::dsl::id)
@ -137,14 +133,14 @@ impl DbArticle {
Ok(if only_local { Ok(if only_local {
query query
.filter(article::dsl::local.eq(true)) .filter(article::dsl::local.eq(true))
.get_results(conn.deref_mut())? .get_results(&mut conn)?
} else { } else {
query.get_results(conn.deref_mut())? query.get_results(&mut conn)?
}) })
} }
pub fn search(query: &str, conn: &Mutex<PgConnection>) -> MyResult<Vec<Self>> { pub fn search(query: &str, data: &IbisData) -> MyResult<Vec<Self>> {
let mut conn = conn.lock().unwrap(); let mut conn = data.db_pool.get()?;
let replaced = query let replaced = query
.replace('%', "\\%") .replace('%', "\\%")
.replace('_', "\\_") .replace('_', "\\_")
@ -159,8 +155,8 @@ impl DbArticle {
.get_results(conn.deref_mut())?) .get_results(conn.deref_mut())?)
} }
pub fn latest_edit_version(&self, conn: &Mutex<PgConnection>) -> MyResult<EditVersion> { pub fn latest_edit_version(&self, data: &IbisData) -> MyResult<EditVersion> {
let mut conn = conn.lock().unwrap(); let mut conn = data.db_pool.get()?;
let latest_version: Option<EditVersion> = edit::table let latest_version: Option<EditVersion> = edit::table
.filter(edit::dsl::article_id.eq(self.id)) .filter(edit::dsl::article_id.eq(self.id))
.order_by(edit::dsl::id.desc()) .order_by(edit::dsl::id.desc())

View file

@ -10,13 +10,13 @@ use crate::common::{ApiConflict, DbArticle};
use activitypub_federation::config::Data; use activitypub_federation::config::Data;
use diesel::ExpressionMethods; use diesel::ExpressionMethods;
use diesel::{ use diesel::{
delete, insert_into, Identifiable, Insertable, PgConnection, QueryDsl, Queryable, RunQueryDsl, delete, insert_into, Identifiable, Insertable, QueryDsl, Queryable, RunQueryDsl,
Selectable, Selectable,
}; };
use diffy::{apply, merge, Patch}; use diffy::{apply, merge, Patch};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::ops::DerefMut; use std::ops::DerefMut;
use std::sync::Mutex;
/// A local only object which represents a merge conflict. It is created /// A local only object which represents a merge conflict. It is created
/// when a local user edit conflicts with another concurrent edit. /// when a local user edit conflicts with another concurrent edit.
@ -44,33 +44,33 @@ pub struct DbConflictForm {
} }
impl DbConflict { impl DbConflict {
pub fn create(form: &DbConflictForm, conn: &Mutex<PgConnection>) -> MyResult<Self> { pub fn create(form: &DbConflictForm, data: &IbisData) -> MyResult<Self> {
let mut conn = conn.lock().unwrap(); let mut conn = data.db_pool.get()?;
Ok(insert_into(conflict::table) Ok(insert_into(conflict::table)
.values(form) .values(form)
.get_result(conn.deref_mut())?) .get_result(conn.deref_mut())?)
} }
pub fn list(local_user: &DbLocalUser, conn: &Mutex<PgConnection>) -> MyResult<Vec<Self>> { pub fn list(local_user: &DbLocalUser, data: &IbisData) -> MyResult<Vec<Self>> {
let mut conn = conn.lock().unwrap(); let mut conn = data.db_pool.get()?;
Ok(conflict::table Ok(conflict::table
.filter(conflict::dsl::creator_id.eq(local_user.id)) .filter(conflict::dsl::creator_id.eq(local_user.id))
.get_results(conn.deref_mut())?) .get_results(conn.deref_mut())?)
} }
/// Delete a merge conflict after it is resolved. /// Delete a merge conflict after it is resolved.
pub fn delete(id: i32, conn: &Mutex<PgConnection>) -> MyResult<Self> { pub fn delete(id: i32, data: &IbisData) -> MyResult<Self> {
let mut conn = conn.lock().unwrap(); let mut conn = data.db_pool.get()?;
Ok(delete(conflict::table.find(id)).get_result(conn.deref_mut())?) Ok(delete(conflict::table.find(id)).get_result(conn.deref_mut())?)
} }
pub async fn to_api_conflict(&self, data: &Data<IbisData>) -> MyResult<Option<ApiConflict>> { pub async fn to_api_conflict(&self, data: &Data<IbisData>) -> MyResult<Option<ApiConflict>> {
let article = DbArticle::read(self.article_id, &data.db_connection)?; let article = DbArticle::read(self.article_id, data)?;
// Make sure to get latest version from origin so that all conflicts can be resolved // Make sure to get latest version from origin so that all conflicts can be resolved
let original_article = article.ap_id.dereference_forced(data).await?; let original_article = article.ap_id.dereference_forced(data).await?;
// create common ancestor version // create common ancestor version
let edits = DbEdit::read_for_article(&original_article, &data.db_connection)?; let edits = DbEdit::read_for_article(&original_article, data)?;
let ancestor = generate_article_version(&edits, &self.previous_version_id)?; let ancestor = generate_article_version(&edits, &self.previous_version_id)?;
let patch = Patch::from_str(&self.diff)?; let patch = Patch::from_str(&self.diff)?;
@ -89,7 +89,7 @@ impl DbConflict {
data, data,
) )
.await?; .await?;
DbConflict::delete(self.id, &data.db_connection)?; DbConflict::delete(self.id, data)?;
Ok(None) Ok(None)
} }
Err(three_way_merge) => { Err(three_way_merge) => {
@ -100,8 +100,7 @@ impl DbConflict {
three_way_merge, three_way_merge,
summary: self.summary.clone(), summary: self.summary.clone(),
article: original_article.clone(), article: original_article.clone(),
previous_version_id: original_article previous_version_id: original_article.latest_edit_version(data)?,
.latest_edit_version(&data.db_connection)?,
})) }))
} }
} }

View file

@ -1,14 +1,15 @@
use crate::backend::database::schema::{edit, person}; use crate::backend::database::schema::{edit, person};
use crate::backend::error::MyResult; use crate::backend::error::MyResult;
use crate::backend::IbisData;
use crate::common::{DbArticle, DbEdit}; use crate::common::{DbArticle, DbEdit};
use crate::common::{EditVersion, EditView}; use crate::common::{EditVersion, EditView};
use activitypub_federation::fetch::object_id::ObjectId; use activitypub_federation::fetch::object_id::ObjectId;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use diesel::ExpressionMethods; use diesel::ExpressionMethods;
use diesel::{insert_into, AsChangeset, Insertable, PgConnection, QueryDsl, RunQueryDsl}; use diesel::{insert_into, AsChangeset, Insertable, QueryDsl, RunQueryDsl};
use diffy::create_patch; use diffy::create_patch;
use std::ops::DerefMut; use std::ops::DerefMut;
use std::sync::Mutex;
#[derive(Debug, Clone, Insertable, AsChangeset)] #[derive(Debug, Clone, Insertable, AsChangeset)]
#[diesel(table_name = edit, check_for_backend(diesel::pg::Pg))] #[diesel(table_name = edit, check_for_backend(diesel::pg::Pg))]
@ -59,8 +60,8 @@ impl DbEditForm {
} }
impl DbEdit { impl DbEdit {
pub fn create(form: &DbEditForm, conn: &Mutex<PgConnection>) -> MyResult<Self> { pub fn create(form: &DbEditForm, data: &IbisData) -> MyResult<Self> {
let mut conn = conn.lock().unwrap(); let mut conn = data.db_pool.get()?;
Ok(insert_into(edit::table) Ok(insert_into(edit::table)
.values(form) .values(form)
.on_conflict(edit::dsl::ap_id) .on_conflict(edit::dsl::ap_id)
@ -69,26 +70,23 @@ impl DbEdit {
.get_result(conn.deref_mut())?) .get_result(conn.deref_mut())?)
} }
pub fn read(version: &EditVersion, conn: &Mutex<PgConnection>) -> MyResult<Self> { pub fn read(version: &EditVersion, data: &IbisData) -> MyResult<Self> {
let mut conn = conn.lock().unwrap(); let mut conn = data.db_pool.get()?;
Ok(edit::table Ok(edit::table
.filter(edit::dsl::hash.eq(version)) .filter(edit::dsl::hash.eq(version))
.get_result(conn.deref_mut())?) .get_result(conn.deref_mut())?)
} }
pub fn read_from_ap_id(ap_id: &ObjectId<DbEdit>, conn: &Mutex<PgConnection>) -> MyResult<Self> { pub fn read_from_ap_id(ap_id: &ObjectId<DbEdit>, data: &IbisData) -> MyResult<Self> {
let mut conn = conn.lock().unwrap(); let mut conn = data.db_pool.get()?;
Ok(edit::table Ok(edit::table
.filter(edit::dsl::ap_id.eq(ap_id)) .filter(edit::dsl::ap_id.eq(ap_id))
.get_result(conn.deref_mut())?) .get_result(conn.deref_mut())?)
} }
// TODO: create internal variant which doesnt return person? // TODO: create internal variant which doesnt return person?
pub fn read_for_article( pub fn read_for_article(article: &DbArticle, data: &IbisData) -> MyResult<Vec<EditView>> {
article: &DbArticle, let mut conn = data.db_pool.get()?;
conn: &Mutex<PgConnection>,
) -> MyResult<Vec<EditView>> {
let mut conn = conn.lock().unwrap();
Ok(edit::table Ok(edit::table
.inner_join(person::table) .inner_join(person::table)
.filter(edit::article_id.eq(article.id)) .filter(edit::article_id.eq(article.id))

View file

@ -9,11 +9,11 @@ use activitypub_federation::fetch::object_id::ObjectId;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use diesel::ExpressionMethods; use diesel::ExpressionMethods;
use diesel::{ use diesel::{
insert_into, AsChangeset, Insertable, JoinOnDsl, PgConnection, QueryDsl, RunQueryDsl, insert_into, AsChangeset, Insertable, JoinOnDsl, QueryDsl, RunQueryDsl,
}; };
use std::fmt::Debug; use std::fmt::Debug;
use std::ops::DerefMut; use std::ops::DerefMut;
use std::sync::Mutex;
#[derive(Debug, Clone, Insertable, AsChangeset)] #[derive(Debug, Clone, Insertable, AsChangeset)]
#[diesel(table_name = instance, check_for_backend(diesel::pg::Pg))] #[diesel(table_name = instance, check_for_backend(diesel::pg::Pg))]
@ -30,8 +30,8 @@ pub struct DbInstanceForm {
} }
impl DbInstance { impl DbInstance {
pub fn create(form: &DbInstanceForm, conn: &Mutex<PgConnection>) -> MyResult<Self> { pub fn create(form: &DbInstanceForm, data: &IbisData) -> MyResult<Self> {
let mut conn = conn.lock().unwrap(); let mut conn = data.db_pool.get()?;
Ok(insert_into(instance::table) Ok(insert_into(instance::table)
.values(form) .values(form)
.on_conflict(instance::ap_id) .on_conflict(instance::ap_id)
@ -40,8 +40,8 @@ impl DbInstance {
.get_result(conn.deref_mut())?) .get_result(conn.deref_mut())?)
} }
pub fn read(id: i32, conn: &Mutex<PgConnection>) -> MyResult<Self> { pub fn read(id: i32, data: &IbisData) -> MyResult<Self> {
let mut conn = conn.lock().unwrap(); let mut conn = data.db_pool.get()?;
Ok(instance::table.find(id).get_result(conn.deref_mut())?) Ok(instance::table.find(id).get_result(conn.deref_mut())?)
} }
@ -49,22 +49,22 @@ impl DbInstance {
ap_id: &ObjectId<DbInstance>, ap_id: &ObjectId<DbInstance>,
data: &Data<IbisData>, data: &Data<IbisData>,
) -> MyResult<DbInstance> { ) -> MyResult<DbInstance> {
let mut conn = data.db_connection.lock().unwrap(); let mut conn = data.db_pool.get()?;
Ok(instance::table Ok(instance::table
.filter(instance::ap_id.eq(ap_id)) .filter(instance::ap_id.eq(ap_id))
.get_result(conn.deref_mut())?) .get_result(conn.deref_mut())?)
} }
pub fn read_local_instance(conn: &Mutex<PgConnection>) -> MyResult<Self> { pub fn read_local_instance(data: &IbisData) -> MyResult<Self> {
let mut conn = conn.lock().unwrap(); let mut conn = data.db_pool.get()?;
Ok(instance::table Ok(instance::table
.filter(instance::local.eq(true)) .filter(instance::local.eq(true))
.get_result(conn.deref_mut())?) .get_result(conn.deref_mut())?)
} }
pub fn read_local_view(data: &Data<IbisData>) -> MyResult<InstanceView> { pub fn read_local_view(data: &Data<IbisData>) -> MyResult<InstanceView> {
let instance = DbInstance::read_local_instance(&data.db_connection)?; let instance = DbInstance::read_local_instance(data)?;
let followers = DbInstance::read_followers(instance.id, &data.db_connection)?; let followers = DbInstance::read_followers(instance.id, data)?;
Ok(InstanceView { Ok(InstanceView {
instance, instance,
@ -80,7 +80,7 @@ impl DbInstance {
data: &Data<IbisData>, data: &Data<IbisData>,
) -> MyResult<()> { ) -> MyResult<()> {
use instance_follow::dsl::{follower_id, instance_id, pending}; use instance_follow::dsl::{follower_id, instance_id, pending};
let mut conn = data.db_connection.lock().unwrap(); let mut conn = data.db_pool.get()?;
let form = ( let form = (
instance_id.eq(instance.id), instance_id.eq(instance.id),
follower_id.eq(follower.id), follower_id.eq(follower.id),
@ -96,10 +96,10 @@ impl DbInstance {
Ok(()) Ok(())
} }
pub fn read_followers(id_: i32, conn: &Mutex<PgConnection>) -> MyResult<Vec<DbPerson>> { pub fn read_followers(id_: i32, data: &IbisData) -> MyResult<Vec<DbPerson>> {
use crate::backend::database::schema::person; use crate::backend::database::schema::person;
use instance_follow::dsl::{follower_id, instance_id}; use instance_follow::dsl::{follower_id, instance_id};
let mut conn = conn.lock().unwrap(); let mut conn = data.db_pool.get()?;
Ok(instance_follow::table Ok(instance_follow::table
.inner_join(person::table.on(follower_id.eq(person::id))) .inner_join(person::table.on(follower_id.eq(person::id)))
.filter(instance_id.eq(id_)) .filter(instance_id.eq(id_))

View file

@ -1,6 +1,8 @@
use crate::backend::config::IbisConfig; use crate::backend::config::IbisConfig;
use crate::backend::database::schema::jwt_secret; use crate::backend::database::schema::jwt_secret;
use crate::backend::error::MyResult; use crate::backend::error::MyResult;
use diesel::r2d2::ConnectionManager;
use diesel::r2d2::Pool;
use diesel::PgConnection; use diesel::PgConnection;
use diesel::{QueryDsl, RunQueryDsl}; use diesel::{QueryDsl, RunQueryDsl};
use std::ops::Deref; use std::ops::Deref;
@ -13,24 +15,15 @@ pub mod edit;
pub mod instance; pub mod instance;
pub(crate) mod schema; pub(crate) mod schema;
pub mod user; pub mod user;
pub mod version;
#[derive(Clone)] #[derive(Clone)]
pub struct IbisData { pub struct IbisData {
pub db_connection: Arc<Mutex<PgConnection>>, pub db_pool: Pool<ConnectionManager<PgConnection>>,
pub config: IbisConfig, pub config: IbisConfig,
} }
impl Deref for IbisData { pub fn read_jwt_secret(data: &IbisData) -> MyResult<String> {
type Target = Arc<Mutex<PgConnection>>; let mut conn = data.db_pool.get()?;
fn deref(&self) -> &Self::Target {
&self.db_connection
}
}
pub fn read_jwt_secret(conn: &Mutex<PgConnection>) -> MyResult<String> {
let mut conn = conn.lock().unwrap();
Ok(jwt_secret::table Ok(jwt_secret::table
.select(jwt_secret::dsl::secret) .select(jwt_secret::dsl::secret)
.first(conn.deref_mut())?) .first(conn.deref_mut())?)

View file

@ -10,11 +10,11 @@ use activitypub_federation::http_signatures::generate_actor_keypair;
use bcrypt::hash; use bcrypt::hash;
use bcrypt::DEFAULT_COST; use bcrypt::DEFAULT_COST;
use chrono::{DateTime, Local, Utc}; use chrono::{DateTime, Local, Utc};
use diesel::{insert_into, AsChangeset, Insertable, PgConnection, RunQueryDsl}; use diesel::{insert_into, AsChangeset, Insertable, RunQueryDsl};
use diesel::{ExpressionMethods, JoinOnDsl}; use diesel::{ExpressionMethods, JoinOnDsl};
use diesel::{PgTextExpressionMethods, QueryDsl}; use diesel::{PgTextExpressionMethods, QueryDsl};
use std::ops::DerefMut; use std::ops::DerefMut;
use std::sync::{Mutex, MutexGuard};
#[derive(Debug, Clone, Insertable, AsChangeset)] #[derive(Debug, Clone, Insertable, AsChangeset)]
#[diesel(table_name = local_user, check_for_backend(diesel::pg::Pg))] #[diesel(table_name = local_user, check_for_backend(diesel::pg::Pg))]
@ -37,8 +37,8 @@ pub struct DbPersonForm {
} }
impl DbPerson { impl DbPerson {
pub fn create(person_form: &DbPersonForm, conn: &Mutex<PgConnection>) -> MyResult<Self> { pub fn create(person_form: &DbPersonForm, data: &Data<IbisData>) -> MyResult<Self> {
let mut conn = conn.lock().unwrap(); let mut conn = data.db_pool.get()?;
Ok(insert_into(person::table) Ok(insert_into(person::table)
.values(person_form) .values(person_form)
.on_conflict(person::dsl::ap_id) .on_conflict(person::dsl::ap_id)
@ -48,7 +48,7 @@ impl DbPerson {
} }
pub fn read(id: i32, data: &Data<IbisData>) -> MyResult<DbPerson> { pub fn read(id: i32, data: &Data<IbisData>) -> MyResult<DbPerson> {
let mut conn = data.db_connection.lock().unwrap(); let mut conn = data.db_pool.get()?;
Ok(person::table.find(id).get_result(conn.deref_mut())?) Ok(person::table.find(id).get_result(conn.deref_mut())?)
} }
@ -58,7 +58,7 @@ impl DbPerson {
admin: bool, admin: bool,
data: &IbisData, data: &IbisData,
) -> MyResult<LocalUserView> { ) -> MyResult<LocalUserView> {
let mut conn = data.db_connection.lock().unwrap(); let mut conn = data.db_pool.get()?;
let domain = &data.config.federation.domain; let domain = &data.config.federation.domain;
let ap_id = ObjectId::parse(&format!( let ap_id = ObjectId::parse(&format!(
"{}://{domain}/user/{username}", "{}://{domain}/user/{username}",
@ -101,7 +101,7 @@ impl DbPerson {
ap_id: &ObjectId<DbPerson>, ap_id: &ObjectId<DbPerson>,
data: &Data<IbisData>, data: &Data<IbisData>,
) -> MyResult<DbPerson> { ) -> MyResult<DbPerson> {
let mut conn = data.db_connection.lock().unwrap(); let mut conn = data.db_pool.get()?;
Ok(person::table Ok(person::table
.filter(person::dsl::ap_id.eq(ap_id)) .filter(person::dsl::ap_id.eq(ap_id))
.get_result(conn.deref_mut())?) .get_result(conn.deref_mut())?)
@ -112,7 +112,7 @@ impl DbPerson {
domain: &Option<String>, domain: &Option<String>,
data: &Data<IbisData>, data: &Data<IbisData>,
) -> MyResult<DbPerson> { ) -> MyResult<DbPerson> {
let mut conn = data.db_connection.lock().unwrap(); let mut conn = data.db_pool.get()?;
let mut query = person::table let mut query = person::table
.filter(person::username.eq(username)) .filter(person::username.eq(username))
.select(person::all_columns) .select(person::all_columns)
@ -129,14 +129,14 @@ impl DbPerson {
} }
pub fn read_local_from_name(username: &str, data: &Data<IbisData>) -> MyResult<LocalUserView> { pub fn read_local_from_name(username: &str, data: &Data<IbisData>) -> MyResult<LocalUserView> {
let mut conn = data.db_connection.lock().unwrap(); let mut conn = data.db_pool.get()?;
let (person, local_user) = person::table let (person, local_user) = person::table
.inner_join(local_user::table) .inner_join(local_user::table)
.filter(person::dsl::local) .filter(person::dsl::local)
.filter(person::dsl::username.eq(username)) .filter(person::dsl::username.eq(username))
.get_result::<(DbPerson, DbLocalUser)>(conn.deref_mut())?; .get_result::<(DbPerson, DbLocalUser)>(conn.deref_mut())?;
// TODO: handle this in single query // TODO: handle this in single query
let following = Self::read_following(person.id, conn)?; let following = Self::read_following(person.id, data)?;
Ok(LocalUserView { Ok(LocalUserView {
person, person,
local_user, local_user,
@ -144,8 +144,9 @@ impl DbPerson {
}) })
} }
fn read_following(id_: i32, mut conn: MutexGuard<PgConnection>) -> MyResult<Vec<DbInstance>> { fn read_following(id_: i32, data: &Data<IbisData>) -> MyResult<Vec<DbInstance>> {
use instance_follow::dsl::{follower_id, instance_id}; use instance_follow::dsl::{follower_id, instance_id};
let mut conn = data.db_pool.get()?;
Ok(instance_follow::table Ok(instance_follow::table
.inner_join(instance::table.on(instance_id.eq(instance::dsl::id))) .inner_join(instance::table.on(instance_id.eq(instance::dsl::id)))
.filter(follower_id.eq(id_)) .filter(follower_id.eq(id_))

View file

@ -1 +0,0 @@

View file

@ -28,7 +28,7 @@ pub struct CreateArticle {
impl CreateArticle { impl CreateArticle {
pub async fn send_to_followers(article: DbArticle, data: &Data<IbisData>) -> MyResult<()> { pub async fn send_to_followers(article: DbArticle, data: &Data<IbisData>) -> MyResult<()> {
let local_instance = DbInstance::read_local_instance(&data.db_connection)?; let local_instance = DbInstance::read_local_instance(data)?;
let object = article.clone().into_json(data).await?; let object = article.clone().into_json(data).await?;
let id = generate_activity_id(&local_instance.ap_id)?; let id = generate_activity_id(&local_instance.ap_id)?;
let to = local_instance.follower_ids(data)?; let to = local_instance.follower_ids(data)?;
@ -65,7 +65,7 @@ impl ActivityHandler for CreateArticle {
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> { async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
let article = DbArticle::from_json(self.object.clone(), data).await?; let article = DbArticle::from_json(self.object.clone(), data).await?;
if article.local { if article.local {
let local_instance = DbInstance::read_local_instance(&data.db_connection)?; let local_instance = DbInstance::read_local_instance(data)?;
local_instance.send_to_followers(self, vec![], data).await?; local_instance.send_to_followers(self, vec![], data).await?;
} }
Ok(()) Ok(())

View file

@ -58,7 +58,7 @@ impl ActivityHandler for Follow {
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> { async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
let actor = self.actor.dereference(data).await?; let actor = self.actor.dereference(data).await?;
let local_instance = DbInstance::read_local_instance(&data.db_connection)?; let local_instance = DbInstance::read_local_instance(data)?;
verify_urls_match(self.object.inner(), local_instance.ap_id.inner())?; verify_urls_match(self.object.inner(), local_instance.ap_id.inner())?;
DbInstance::follow(&actor, &local_instance, false, data)?; DbInstance::follow(&actor, &local_instance, false, data)?;

View file

@ -32,9 +32,8 @@ pub async fn submit_article_update(
previous_version, previous_version,
)?; )?;
if original_article.local { if original_article.local {
let edit = DbEdit::create(&form, &data.db_connection)?; let edit = DbEdit::create(&form, data)?;
let updated_article = let updated_article = DbArticle::update_text(edit.article_id, &new_text, data)?;
DbArticle::update_text(edit.article_id, &new_text, &data.db_connection)?;
UpdateLocalArticle::send(updated_article, vec![], data).await?; UpdateLocalArticle::send(updated_article, vec![], data).await?;
} else { } else {
@ -50,7 +49,7 @@ pub async fn submit_article_update(
previous_version_id: form.previous_version_id, previous_version_id: form.previous_version_id,
created: Utc::now(), created: Utc::now(),
}; };
let instance = DbInstance::read(original_article.instance_id, &data.db_connection)?; let instance = DbInstance::read(original_article.instance_id, data)?;
UpdateRemoteArticle::send(edit, instance, data).await?; UpdateRemoteArticle::send(edit, instance, data).await?;
} }
Ok(()) Ok(())

View file

@ -33,7 +33,7 @@ impl RejectEdit {
user_instance: DbInstance, user_instance: DbInstance,
data: &Data<IbisData>, data: &Data<IbisData>,
) -> MyResult<()> { ) -> MyResult<()> {
let local_instance = DbInstance::read_local_instance(&data.db_connection)?; let local_instance = DbInstance::read_local_instance(data)?;
let id = generate_activity_id(&local_instance.ap_id)?; let id = generate_activity_id(&local_instance.ap_id)?;
let reject = RejectEdit { let reject = RejectEdit {
actor: local_instance.ap_id.clone(), actor: local_instance.ap_id.clone(),
@ -82,7 +82,7 @@ impl ActivityHandler for RejectEdit {
article_id: article.id, article_id: article.id,
previous_version_id: self.object.previous_version, previous_version_id: self.object.previous_version,
}; };
DbConflict::create(&form, &data.db_connection)?; DbConflict::create(&form, data)?;
Ok(()) Ok(())
} }
} }

View file

@ -35,7 +35,7 @@ impl UpdateLocalArticle {
data: &Data<IbisData>, data: &Data<IbisData>,
) -> MyResult<()> { ) -> MyResult<()> {
debug_assert!(article.local); debug_assert!(article.local);
let local_instance = DbInstance::read_local_instance(&data.db_connection)?; let local_instance = DbInstance::read_local_instance(data)?;
let id = generate_activity_id(&local_instance.ap_id)?; let id = generate_activity_id(&local_instance.ap_id)?;
let mut to = local_instance.follower_ids(data)?; let mut to = local_instance.follower_ids(data)?;
to.extend(extra_recipients.iter().map(|i| i.ap_id.inner().clone())); to.extend(extra_recipients.iter().map(|i| i.ap_id.inner().clone()));

View file

@ -40,7 +40,7 @@ impl UpdateRemoteArticle {
article_instance: DbInstance, article_instance: DbInstance,
data: &Data<IbisData>, data: &Data<IbisData>,
) -> MyResult<()> { ) -> MyResult<()> {
let local_instance = DbInstance::read_local_instance(&data.db_connection)?; let local_instance = DbInstance::read_local_instance(data)?;
let id = generate_activity_id(&local_instance.ap_id)?; let id = generate_activity_id(&local_instance.ap_id)?;
let update = UpdateRemoteArticle { let update = UpdateRemoteArticle {
actor: local_instance.ap_id.clone(), actor: local_instance.ap_id.clone(),
@ -74,21 +74,20 @@ impl ActivityHandler for UpdateRemoteArticle {
} }
async fn verify(&self, data: &Data<Self::DataType>) -> Result<(), Self::Error> { async fn verify(&self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
let article = DbArticle::read_from_ap_id(&self.object.object, &data.db_connection)?; let article = DbArticle::read_from_ap_id(&self.object.object, data)?;
can_edit_article(&article, false)?; can_edit_article(&article, false)?;
Ok(()) Ok(())
} }
/// Received on article origin instances /// Received on article origin instances
async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> { async fn receive(self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
let local_article = DbArticle::read_from_ap_id(&self.object.object, &data.db_connection)?; let local_article = DbArticle::read_from_ap_id(&self.object.object, data)?;
let patch = Patch::from_str(&self.object.content)?; let patch = Patch::from_str(&self.object.content)?;
match apply(&local_article.text, &patch) { match apply(&local_article.text, &patch) {
Ok(applied) => { Ok(applied) => {
let edit = DbEdit::from_json(self.object.clone(), data).await?; let edit = DbEdit::from_json(self.object.clone(), data).await?;
let article = let article = DbArticle::update_text(edit.article_id, &applied, data)?;
DbArticle::update_text(edit.article_id, &applied, &data.db_connection)?;
UpdateLocalArticle::send(article, vec![self.actor.dereference(data).await?], data) UpdateLocalArticle::send(article, vec![self.actor.dereference(data).await?], data)
.await?; .await?;
} }

View file

@ -41,19 +41,19 @@ impl Object for DbArticle {
object_id: Url, object_id: Url,
data: &Data<Self::DataType>, data: &Data<Self::DataType>,
) -> Result<Option<Self>, Self::Error> { ) -> Result<Option<Self>, Self::Error> {
let article = DbArticle::read_from_ap_id(&object_id.into(), &data.db_connection).ok(); let article = DbArticle::read_from_ap_id(&object_id.into(), data).ok();
Ok(article) Ok(article)
} }
async fn into_json(self, data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> { async fn into_json(self, data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> {
let local_instance = DbInstance::read_local_instance(&data.db_connection)?; let local_instance = DbInstance::read_local_instance(data)?;
Ok(ApubArticle { Ok(ApubArticle {
kind: Default::default(), kind: Default::default(),
id: self.ap_id.clone(), id: self.ap_id.clone(),
attributed_to: local_instance.ap_id.clone(), attributed_to: local_instance.ap_id.clone(),
to: vec![public(), local_instance.followers_url()?], to: vec![public(), local_instance.followers_url()?],
edits: self.edits_id()?, edits: self.edits_id()?,
latest_version: self.latest_edit_version(&data.db_connection)?, latest_version: self.latest_edit_version(data)?,
content: self.text, content: self.text,
name: self.title, name: self.title,
}) })
@ -77,7 +77,7 @@ impl Object for DbArticle {
local: false, local: false,
instance_id: instance.id, instance_id: instance.id,
}; };
let article = DbArticle::create_or_update(form, &data.db_connection)?; let article = DbArticle::create_or_update(form, data)?;
json.edits.dereference(&article, data).await?; json.edits.dereference(&article, data).await?;

View file

@ -38,7 +38,7 @@ impl Collection for DbArticleCollection {
owner: &Self::Owner, owner: &Self::Owner,
data: &Data<Self::DataType>, data: &Data<Self::DataType>,
) -> Result<Self::Kind, Self::Error> { ) -> Result<Self::Kind, Self::Error> {
let local_articles = DbArticle::read_all(true, &data.db_connection)?; let local_articles = DbArticle::read_all(true, data)?;
let articles = future::try_join_all( let articles = future::try_join_all(
local_articles local_articles
.into_iter() .into_iter()

View file

@ -48,7 +48,7 @@ impl Object for DbEdit {
} }
async fn into_json(self, data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> { async fn into_json(self, data: &Data<Self::DataType>) -> Result<Self::Kind, Self::Error> {
let article = DbArticle::read(self.article_id, &data.db_connection)?; let article = DbArticle::read(self.article_id, data)?;
let creator = DbPerson::read(self.creator_id, data)?; let creator = DbPerson::read(self.creator_id, data)?;
Ok(ApubEdit { Ok(ApubEdit {
kind: PatchType::Patch, kind: PatchType::Patch,
@ -85,7 +85,7 @@ impl Object for DbEdit {
previous_version_id: json.previous_version, previous_version_id: json.previous_version,
created: json.published, created: json.published,
}; };
let edit = DbEdit::create(&form, &data.db_connection)?; let edit = DbEdit::create(&form, data)?;
Ok(edit) Ok(edit)
} }
} }

View file

@ -39,7 +39,7 @@ impl Collection for DbEditCollection {
owner: &Self::Owner, owner: &Self::Owner,
data: &Data<Self::DataType>, data: &Data<Self::DataType>,
) -> Result<Self::Kind, Self::Error> { ) -> Result<Self::Kind, Self::Error> {
let article = DbArticle::read_view(owner.id, &data.db_connection)?; let article = DbArticle::read_view(owner.id, data)?;
let edits = future::try_join_all( let edits = future::try_join_all(
article article
@ -49,7 +49,7 @@ impl Collection for DbEditCollection {
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
) )
.await?; .await?;
let local_instance = DbInstance::read_local_instance(&data.db_connection)?; let local_instance = DbInstance::read_local_instance(data)?;
let collection = ApubEditCollection { let collection = ApubEditCollection {
r#type: Default::default(), r#type: Default::default(),
id: Url::from(local_instance.articles_url), id: Url::from(local_instance.articles_url),

View file

@ -38,7 +38,7 @@ impl DbInstance {
} }
pub fn follower_ids(&self, data: &Data<IbisData>) -> MyResult<Vec<Url>> { pub fn follower_ids(&self, data: &Data<IbisData>) -> MyResult<Vec<Url>> {
Ok(DbInstance::read_followers(self.id, &data.db_connection)? Ok(DbInstance::read_followers(self.id, data)?
.into_iter() .into_iter()
.map(|f| f.ap_id.into()) .map(|f| f.ap_id.into())
.collect()) .collect())
@ -55,7 +55,7 @@ impl DbInstance {
<Activity as ActivityHandler>::Error: From<activitypub_federation::error::Error>, <Activity as ActivityHandler>::Error: From<activitypub_federation::error::Error>,
<Activity as ActivityHandler>::Error: From<Error>, <Activity as ActivityHandler>::Error: From<Error>,
{ {
let mut inboxes: Vec<_> = DbInstance::read_followers(self.id, &data.db_connection)? let mut inboxes: Vec<_> = DbInstance::read_followers(self.id, data)?
.iter() .iter()
.map(|f| Url::parse(&f.inbox_url).unwrap()) .map(|f| Url::parse(&f.inbox_url).unwrap())
.collect(); .collect();
@ -119,7 +119,7 @@ impl Object for DbInstance {
last_refreshed_at: Local::now().into(), last_refreshed_at: Local::now().into(),
local: false, local: false,
}; };
let instance = DbInstance::create(&form, &data.db_connection)?; let instance = DbInstance::create(&form, data)?;
// TODO: very inefficient to sync all articles every time // TODO: very inefficient to sync all articles every time
instance.articles_url.dereference(&instance, data).await?; instance.articles_url.dereference(&instance, data).await?;
Ok(instance) Ok(instance)

View file

@ -71,7 +71,7 @@ impl Object for DbPerson {
last_refreshed_at: Local::now().into(), last_refreshed_at: Local::now().into(),
local: false, local: false,
}; };
DbPerson::create(&form, &data.db_connection) DbPerson::create(&form, data)
} }
} }

View file

@ -47,7 +47,7 @@ pub fn federation_routes() -> Router {
async fn http_get_instance( async fn http_get_instance(
data: Data<IbisData>, data: Data<IbisData>,
) -> MyResult<FederationJson<WithContext<ApubInstance>>> { ) -> MyResult<FederationJson<WithContext<ApubInstance>>> {
let local_instance = DbInstance::read_local_instance(&data.db_connection)?; let local_instance = DbInstance::read_local_instance(&data)?;
let json_instance = local_instance.into_json(&data).await?; let json_instance = local_instance.into_json(&data).await?;
Ok(FederationJson(WithContext::new_default(json_instance))) Ok(FederationJson(WithContext::new_default(json_instance)))
} }
@ -66,7 +66,7 @@ async fn http_get_person(
async fn http_get_all_articles( async fn http_get_all_articles(
data: Data<IbisData>, data: Data<IbisData>,
) -> MyResult<FederationJson<WithContext<ArticleCollection>>> { ) -> MyResult<FederationJson<WithContext<ArticleCollection>>> {
let local_instance = DbInstance::read_local_instance(&data.db_connection)?; let local_instance = DbInstance::read_local_instance(&data)?;
let collection = DbArticleCollection::read_local(&local_instance, &data).await?; let collection = DbArticleCollection::read_local(&local_instance, &data).await?;
Ok(FederationJson(WithContext::new_default(collection))) Ok(FederationJson(WithContext::new_default(collection)))
} }
@ -76,7 +76,7 @@ async fn http_get_article(
Path(title): Path<String>, Path(title): Path<String>,
data: Data<IbisData>, data: Data<IbisData>,
) -> MyResult<FederationJson<WithContext<ApubArticle>>> { ) -> MyResult<FederationJson<WithContext<ApubArticle>>> {
let article = DbArticle::read_local_title(&title, &data.db_connection)?; let article = DbArticle::read_local_title(&title, &data)?;
let json = article.into_json(&data).await?; let json = article.into_json(&data).await?;
Ok(FederationJson(WithContext::new_default(json))) Ok(FederationJson(WithContext::new_default(json)))
} }
@ -86,7 +86,7 @@ async fn http_get_article_edits(
Path(title): Path<String>, Path(title): Path<String>,
data: Data<IbisData>, data: Data<IbisData>,
) -> MyResult<FederationJson<WithContext<ApubEditCollection>>> { ) -> MyResult<FederationJson<WithContext<ApubEditCollection>>> {
let article = DbArticle::read_local_title(&title, &data.db_connection)?; let article = DbArticle::read_local_title(&title, &data)?;
let json = DbEditCollection::read_local(&article, &data).await?; let json = DbEditCollection::read_local(&article, &data).await?;
Ok(FederationJson(WithContext::new_default(json))) Ok(FederationJson(WithContext::new_default(json)))
} }

View file

@ -21,6 +21,8 @@ use axum::Server;
use axum::ServiceExt; use axum::ServiceExt;
use axum::{middleware::Next, response::Response, Router}; use axum::{middleware::Next, response::Response, Router};
use chrono::Local; use chrono::Local;
use diesel::r2d2::ConnectionManager;
use diesel::r2d2::Pool;
use diesel::Connection; use diesel::Connection;
use diesel::PgConnection; use diesel::PgConnection;
use diesel_migrations::embed_migrations; use diesel_migrations::embed_migrations;
@ -46,15 +48,17 @@ const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations");
const FEDERATION_ROUTES_PREFIX: &str = "/federation_routes"; const FEDERATION_ROUTES_PREFIX: &str = "/federation_routes";
pub async fn start(config: IbisConfig) -> MyResult<()> { pub async fn start(config: IbisConfig) -> MyResult<()> {
let db_connection = Arc::new(Mutex::new(PgConnection::establish(&config.database_url)?)); let manager = ConnectionManager::<PgConnection>::new(&config.database.connection_url);
db_connection let db_pool = Pool::builder()
.lock() .max_size(config.database.pool_size)
.unwrap() .build(manager)?;
.run_pending_migrations(MIGRATIONS)
.unwrap();
db_pool
.get()?
.run_pending_migrations(MIGRATIONS)
.expect("run migrations");
let data = IbisData { let data = IbisData {
db_connection, db_pool,
config, config,
}; };
let data = FederationConfig::builder() let data = FederationConfig::builder()
@ -66,7 +70,7 @@ pub async fn start(config: IbisConfig) -> MyResult<()> {
.await?; .await?;
// Create local instance if it doesnt exist yet // Create local instance if it doesnt exist yet
if DbInstance::read_local_instance(&data.db_connection).is_err() { if DbInstance::read_local_instance(&data).is_err() {
setup(&data.to_request_data()).await?; setup(&data.to_request_data()).await?;
} }
@ -129,7 +133,7 @@ async fn setup(data: &Data<IbisData>) -> Result<(), Error> {
last_refreshed_at: Local::now().into(), last_refreshed_at: Local::now().into(),
local: true, local: true,
}; };
let instance = DbInstance::create(&form, &data.db_connection)?; let instance = DbInstance::create(&form, data)?;
let person = DbPerson::create_local( let person = DbPerson::create_local(
data.config.setup.admin_username.clone(), data.config.setup.admin_username.clone(),
@ -149,7 +153,7 @@ async fn setup(data: &Data<IbisData>) -> Result<(), Error> {
instance_id: instance.id, instance_id: instance.id,
local: true, local: true,
}; };
let article = DbArticle::create(form, &data.db_connection)?; let article = DbArticle::create(form, data)?;
// also create an article so its included in most recently edited list // also create an article so its included in most recently edited list
submit_article_update( submit_article_update(
MAIN_PAGE_DEFAULT_TEXT.to_string(), MAIN_PAGE_DEFAULT_TEXT.to_string(),

View file

@ -57,7 +57,7 @@ fn backend_hostname() -> String {
} }
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
{ {
backend_hostname = crate::backend::config::IbisConfig::read().bind.to_string(); backend_hostname = crate::backend::config::IbisConfig::read().unwrap().bind.to_string();
} }
backend_hostname backend_hostname
} }

View file

@ -15,7 +15,7 @@ pub async fn main() -> ibis_lib::backend::error::MyResult<()> {
.filter_module("ibis", LevelFilter::Info) .filter_module("ibis", LevelFilter::Info)
.init(); .init();
let ibis_config = IbisConfig::read(); let ibis_config = IbisConfig::read()?;
ibis_lib::backend::start(ibis_config).await?; ibis_lib::backend::start(ibis_config).await?;
Ok(()) Ok(())
} }

View file

@ -1,4 +1,4 @@
use ibis_lib::backend::config::{IbisConfig, IbisConfigFederation}; use ibis_lib::backend::config::{IbisConfig, IbisConfigDatabase, IbisConfigFederation};
use ibis_lib::backend::start; use ibis_lib::backend::start;
use ibis_lib::common::RegisterUserData; use ibis_lib::common::RegisterUserData;
use ibis_lib::frontend::api::ApiClient; use ibis_lib::frontend::api::ApiClient;
@ -105,12 +105,15 @@ impl IbisInstance {
} }
async fn start(db_path: String, port: i32, username: &str) -> Self { async fn start(db_path: String, port: i32, username: &str) -> Self {
let database_url = format!("postgresql://ibis:password@/ibis?host={db_path}"); let connection_url = format!("postgresql://ibis:password@/ibis?host={db_path}");
let hostname = format!("localhost:{port}"); let hostname = format!("localhost:{port}");
let bind = format!("127.0.0.1:{port}").parse().unwrap(); let bind = format!("127.0.0.1:{port}").parse().unwrap();
let config = IbisConfig { let config = IbisConfig {
bind, bind,
database_url, database: IbisConfigDatabase {
connection_url,
..Default::default()
},
registration_open: true, registration_open: true,
federation: IbisConfigFederation { federation: IbisConfigFederation {
domain: hostname.clone(), domain: hostname.clone(),