1
0
Fork 0
mirror of https://github.com/Nutomic/ibis.git synced 2025-02-04 00:31:35 +00:00

Also split up files in src/common.rs

This commit is contained in:
Felix Ableitner 2025-01-15 14:28:19 +01:00
parent 95ee090275
commit 0194ddf663
51 changed files with 504 additions and 448 deletions

View file

@ -11,25 +11,27 @@ use crate::{
utils::{error::MyResult, generate_article_version},
},
common::{
article::{
ApiConflict,
ApproveArticleForm,
ArticleView,
CreateArticleForm,
DbArticle,
DbEdit,
DeleteConflictForm,
EditArticleForm,
EditVersion,
ForkArticleForm,
GetArticleForm,
ListArticlesForm,
ProtectArticleForm,
SearchArticleForm,
},
instance::DbInstance,
user::LocalUserView,
utils::{extract_domain, http_protocol_str},
validation::can_edit_article,
ApiConflict,
ApproveArticleForm,
ArticleView,
CreateArticleForm,
DbArticle,
DbEdit,
DbInstance,
DeleteConflictForm,
EditArticleForm,
EditVersion,
ForkArticleForm,
GetArticleForm,
ListArticlesForm,
LocalUserView,
ProtectArticleForm,
ResolveObject,
SearchArticleForm,
},
};
use activitypub_federation::{config::Data, fetch::object_id::ObjectId};

View file

@ -1,11 +1,8 @@
use crate::{
backend::{database::IbisData, federation::activities::follow::Follow, utils::error::MyResult},
common::{
DbInstance,
FollowInstance,
GetInstance,
InstanceView,
LocalUserView,
instance::{DbInstance, FollowInstance, GetInstance, InstanceView},
user::LocalUserView,
ResolveObject,
SuccessResponse,
},

View file

@ -18,7 +18,11 @@ use crate::{
database::IbisData,
utils::error::MyResult,
},
common::{DbEdit, EditView, GetEditList, LocalUserView, SiteView},
common::{
article::{DbEdit, EditView, GetEditList},
instance::SiteView,
user::LocalUserView,
},
};
use activitypub_federation::config::Data;
use anyhow::anyhow;

View file

@ -5,15 +5,17 @@ use crate::{
utils::error::MyResult,
},
common::{
DbArticle,
DbPerson,
GetUserForm,
LocalUserView,
LoginUserForm,
article::DbArticle,
user::{
DbPerson,
GetUserForm,
LocalUserView,
LoginUserForm,
RegisterUserForm,
UpdateUserForm,
},
Notification,
RegisterUserForm,
SuccessResponse,
UpdateUserForm,
AUTH_COOKIE,
},
};

View file

@ -1,4 +1,4 @@
use crate::{backend::utils::error::MyResult, common::Options};
use crate::{backend::utils::error::MyResult, common::instance::Options};
use config::Config;
use doku::Document;
use serde::Deserialize;

View file

@ -8,11 +8,9 @@ use crate::{
utils::error::MyResult,
},
common::{
article::{ArticleView, DbArticle, EditVersion},
instance::DbInstance,
newtypes::{ArticleId, InstanceId},
ArticleView,
DbArticle,
DbInstance,
EditVersion,
},
};
use activitypub_federation::fetch::{collection_id::CollectionId, object_id::ObjectId};

View file

@ -5,12 +5,9 @@ use crate::{
utils::{error::MyResult, generate_article_version},
},
common::{
article::{ApiConflict, DbArticle, DbEdit, EditVersion},
newtypes::{ArticleId, ConflictId, PersonId},
ApiConflict,
DbArticle,
DbEdit,
DbPerson,
EditVersion,
user::DbPerson,
},
};
use activitypub_federation::config::Data;

View file

@ -5,11 +5,8 @@ use crate::{
IbisData,
},
common::{
article::{DbArticle, DbEdit, EditVersion, EditView},
newtypes::{ArticleId, PersonId},
DbArticle,
DbEdit,
EditVersion,
EditView,
},
};
use activitypub_federation::fetch::object_id::ObjectId;

View file

@ -10,7 +10,11 @@ use crate::{
},
utils::error::MyResult,
},
common::{newtypes::InstanceId, DbInstance, DbPerson, InstanceView},
common::{
instance::{DbInstance, InstanceView},
newtypes::InstanceId,
user::DbPerson,
},
};
use activitypub_federation::{
config::Data,

View file

@ -7,13 +7,10 @@ use crate::{
utils::{error::MyResult, generate_keypair},
},
common::{
instance::DbInstance,
newtypes::PersonId,
user::{DbLocalUser, DbPerson, LocalUserView, UpdateUserForm},
utils::http_protocol_str,
DbInstance,
DbLocalUser,
DbPerson,
LocalUserView,
UpdateUserForm,
},
};
use activitypub_federation::{config::Data, fetch::object_id::ObjectId};

View file

@ -7,7 +7,7 @@ use crate::{
generate_activity_id,
},
},
common::DbInstance,
common::instance::DbInstance,
};
use activitypub_federation::{
config::Data,

View file

@ -7,7 +7,7 @@ use crate::{
generate_activity_id,
},
},
common::{DbArticle, DbInstance},
common::{article::DbArticle, instance::DbInstance},
};
use activitypub_federation::{
config::Data,

View file

@ -5,7 +5,7 @@ use crate::{
generate_activity_id,
utils::error::{Error, MyResult},
},
common::{DbInstance, DbPerson},
common::{instance::DbInstance, user::DbPerson},
};
use activitypub_federation::{
config::Data,

View file

@ -8,11 +8,9 @@ use crate::{
utils::error::Error,
},
common::{
article::{DbArticle, DbEdit, EditVersion},
instance::DbInstance,
newtypes::{EditId, PersonId},
DbArticle,
DbEdit,
DbInstance,
EditVersion,
},
};
use activitypub_federation::config::Data;

View file

@ -10,7 +10,7 @@ use crate::{
generate_activity_id,
},
},
common::{DbInstance, EditVersion},
common::{article::EditVersion, instance::DbInstance},
};
use activitypub_federation::{
config::Data,

View file

@ -7,7 +7,7 @@ use crate::{
generate_activity_id,
},
},
common::{DbArticle, DbInstance},
common::{article::DbArticle, instance::DbInstance},
};
use activitypub_federation::{
config::Data,

View file

@ -11,7 +11,11 @@ use crate::{
generate_activity_id,
},
},
common::{validation::can_edit_article, DbArticle, DbEdit, DbInstance},
common::{
article::{DbArticle, DbEdit},
instance::DbInstance,
validation::can_edit_article,
},
};
use activitypub_federation::{
config::Data,

View file

@ -4,7 +4,10 @@ use crate::{
federation::objects::edits_collection::DbEditCollection,
utils::error::Error,
},
common::{DbArticle, DbInstance, EditVersion},
common::{
article::{DbArticle, EditVersion},
instance::DbInstance,
},
};
use activitypub_federation::{
config::Data,

View file

@ -4,7 +4,7 @@ use crate::{
federation::objects::article::ApubArticle,
utils::error::{Error, MyResult},
},
common::{utils::http_protocol_str, DbArticle},
common::{article::DbArticle, utils::http_protocol_str},
};
use activitypub_federation::{
config::Data,

View file

@ -3,7 +3,10 @@ use crate::{
database::{edit::DbEditForm, IbisData},
utils::error::Error,
},
common::{DbArticle, DbEdit, DbPerson, EditVersion},
common::{
article::{DbArticle, DbEdit, EditVersion},
user::DbPerson,
},
};
use activitypub_federation::{
config::Data,

View file

@ -1,6 +1,6 @@
use crate::{
backend::{database::IbisData, federation::objects::edit::ApubEdit, utils::error::Error},
common::{DbArticle, DbEdit},
common::article::{DbArticle, DbEdit},
};
use activitypub_federation::{
config::Data,

View file

@ -5,7 +5,7 @@ use crate::{
federation::{objects::articles_collection::DbArticleCollection, send_activity},
utils::error::{Error, MyResult},
},
common::{utils::extract_domain, DbInstance},
common::{instance::DbInstance, utils::extract_domain},
};
use activitypub_federation::{
config::Data,

View file

@ -4,7 +4,7 @@ use crate::{
database::IbisData,
utils::error::{Error, MyResult},
},
common::{utils::http_protocol_str, DbInstance},
common::{instance::DbInstance, utils::http_protocol_str},
};
use activitypub_federation::{
config::Data,

View file

@ -3,7 +3,7 @@ use crate::{
database::{user::DbPersonForm, IbisData},
utils::error::Error,
},
common::DbPerson,
common::user::DbPerson,
};
use activitypub_federation::{
config::Data,

View file

@ -21,7 +21,7 @@ use crate::{
},
utils::error::{Error, MyResult},
},
common::{DbArticle, DbInstance, DbPerson},
common::{article::DbArticle, instance::DbInstance, user::DbPerson},
};
use activitypub_federation::{
axum::{

View file

@ -9,11 +9,10 @@ use crate::{
},
},
common::{
article::{DbArticle, EditVersion},
instance::DbInstance,
user::DbPerson,
utils::http_protocol_str,
DbArticle,
DbInstance,
DbPerson,
EditVersion,
MAIN_PAGE_NAME,
},
};

View file

@ -1,6 +1,9 @@
use crate::{
backend::{database::IbisData, utils::error::MyResult},
common::{utils, DbEdit, EditVersion},
common::{
article::{DbEdit, EditVersion},
utils,
},
};
use activitypub_federation::{
config::Data,
@ -57,8 +60,8 @@ pub(super) fn generate_article_version(
mod test {
use super::*;
use crate::common::{
article::DbEdit,
newtypes::{ArticleId, EditId, PersonId},
DbEdit,
};
use activitypub_federation::fetch::object_id::ObjectId;
use chrono::Utc;

193
src/common/article.rs Normal file
View file

@ -0,0 +1,193 @@
use super::{
instance::DbInstance,
newtypes::{ArticleId, ConflictId, EditId, InstanceId, PersonId},
user::DbPerson,
};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use uuid::Uuid;
#[cfg(feature = "ssr")]
use {
crate::backend::database::schema::{article, edit},
activitypub_federation::fetch::object_id::ObjectId,
diesel::{Identifiable, Queryable, Selectable},
};
/// Should be an enum Title/Id but fails due to https://github.com/nox/serde_urlencoded/issues/66
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
pub struct GetArticleForm {
pub title: Option<String>,
pub domain: Option<String>,
pub id: Option<ArticleId>,
}
#[derive(Deserialize, Serialize, Clone, Default, Debug)]
pub struct ListArticlesForm {
pub only_local: Option<bool>,
pub instance_id: Option<InstanceId>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "ssr", derive(Queryable))]
#[cfg_attr(feature = "ssr", diesel(table_name = article, check_for_backend(diesel::pg::Pg)))]
pub struct ArticleView {
pub article: DbArticle,
pub instance: DbInstance,
pub latest_version: EditVersion,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Identifiable))]
#[cfg_attr(feature = "ssr", diesel(table_name = article, check_for_backend(diesel::pg::Pg), belongs_to(DbInstance, foreign_key = instance_id)))]
pub struct DbArticle {
pub id: ArticleId,
pub title: String,
pub text: String,
#[cfg(feature = "ssr")]
pub ap_id: ObjectId<DbArticle>,
#[cfg(not(feature = "ssr"))]
pub ap_id: String,
pub instance_id: InstanceId,
pub local: bool,
pub protected: bool,
pub approved: bool,
pub published: DateTime<Utc>,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct CreateArticleForm {
pub title: String,
pub text: String,
pub summary: String,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct EditArticleForm {
/// Id of the article to edit
pub article_id: ArticleId,
/// Full, new text of the article. A diff against `previous_version` is generated on the backend
/// side to handle conflicts.
pub new_text: String,
/// What was changed
pub summary: String,
/// The version that this edit is based on, ie [DbArticle.latest_version] or
/// [ApiConflict.previous_version]
pub previous_version_id: EditVersion,
/// If you are resolving a conflict, pass the id to delete conflict from the database
pub resolve_conflict_id: Option<ConflictId>,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct ProtectArticleForm {
pub article_id: ArticleId,
pub protected: bool,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct ForkArticleForm {
pub article_id: ArticleId,
pub new_title: String,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct ApproveArticleForm {
pub article_id: ArticleId,
pub approve: bool,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct SearchArticleForm {
pub query: String,
}
/// Represents a single change to the article.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable))]
#[cfg_attr(feature = "ssr", diesel(table_name = edit, check_for_backend(diesel::pg::Pg)))]
pub struct DbEdit {
// TODO: we could use hash as primary key, but that gives errors on forking because
// the same edit is used for multiple articles
pub id: EditId,
#[serde(skip)]
pub creator_id: PersonId,
/// UUID built from sha224 hash of diff
pub hash: EditVersion,
#[cfg(feature = "ssr")]
pub ap_id: ObjectId<DbEdit>,
#[cfg(not(feature = "ssr"))]
pub ap_id: String,
pub diff: String,
pub summary: String,
pub article_id: ArticleId,
/// First edit of an article always has `EditVersion::default()` here
pub previous_version_id: EditVersion,
pub published: DateTime<Utc>,
}
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
pub struct GetEditList {
pub article_id: Option<ArticleId>,
pub person_id: Option<PersonId>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "ssr", derive(Queryable))]
#[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))]
pub struct EditView {
pub edit: DbEdit,
pub article: DbArticle,
pub creator: DbPerson,
}
/// The version hash of a specific edit. Generated by taking an SHA256 hash of the diff
/// and using the first 16 bytes so that it fits into UUID.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "ssr", derive(diesel_derive_newtype::DieselNewType))]
pub struct EditVersion(pub(crate) Uuid);
impl EditVersion {
pub fn new(diff: &str) -> Self {
let mut sha256 = Sha256::new();
sha256.update(diff);
let hash_bytes = sha256.finalize();
let uuid =
Uuid::from_slice(&hash_bytes.as_slice()[..16]).expect("hash is correct size for uuid");
EditVersion(uuid)
}
pub fn hash(&self) -> String {
hex::encode(self.0.into_bytes())
}
}
impl Default for EditVersion {
fn default() -> Self {
EditVersion::new("")
}
}
#[derive(Deserialize, Serialize, Debug)]
pub struct DeleteConflictForm {
pub conflict_id: ConflictId,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct ApiConflict {
pub id: ConflictId,
pub hash: EditVersion,
pub three_way_merge: String,
pub summary: String,
pub article: DbArticle,
pub previous_version_id: EditVersion,
pub published: DateTime<Utc>,
}
#[test]
fn test_edit_versions() {
let default = EditVersion::default();
assert_eq!("e3b0c44298fc1c149afbf4c8996fb924", default.hash());
let version = EditVersion::new("test");
assert_eq!("9f86d081884c7d659a2feaa0c55ad015", version.hash());
}

93
src/common/instance.rs Normal file
View file

@ -0,0 +1,93 @@
use super::{
newtypes::InstanceId,
user::{DbPerson, LocalUserView},
};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use smart_default::SmartDefault;
use url::Url;
#[cfg(feature = "ssr")]
use {
crate::backend::{
database::schema::instance,
federation::objects::articles_collection::DbArticleCollection,
federation::objects::instance_collection::DbInstanceCollection,
},
activitypub_federation::fetch::{collection_id::CollectionId, object_id::ObjectId},
diesel::{Identifiable, Queryable, Selectable},
doku::Document,
};
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Identifiable))]
#[cfg_attr(feature = "ssr", diesel(table_name = instance, check_for_backend(diesel::pg::Pg)))]
pub struct DbInstance {
pub id: InstanceId,
pub domain: String,
#[cfg(feature = "ssr")]
pub ap_id: ObjectId<DbInstance>,
#[cfg(not(feature = "ssr"))]
pub ap_id: String,
pub description: Option<String>,
#[cfg(feature = "ssr")]
pub articles_url: Option<CollectionId<DbArticleCollection>>,
#[cfg(not(feature = "ssr"))]
pub articles_url: String,
pub inbox_url: String,
#[serde(skip)]
pub public_key: String,
#[serde(skip)]
pub private_key: Option<String>,
pub last_refreshed_at: DateTime<Utc>,
pub local: bool,
#[cfg(feature = "ssr")]
pub instances_url: Option<CollectionId<DbInstanceCollection>>,
}
impl DbInstance {
pub fn inbox_url(&self) -> Url {
Url::parse(&self.inbox_url).expect("can parse inbox url")
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "ssr", derive(Queryable))]
#[cfg_attr(feature = "ssr", diesel(table_name = article, check_for_backend(diesel::pg::Pg)))]
pub struct InstanceView {
pub instance: DbInstance,
pub followers: Vec<DbPerson>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, SmartDefault)]
#[serde(default)]
#[serde(deny_unknown_fields)]
#[cfg_attr(feature = "ssr", derive(Queryable, Document))]
#[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))]
pub struct Options {
/// Whether users can create new accounts
#[default = true]
#[cfg_attr(feature = "ssr", doku(example = "true"))]
pub registration_open: bool,
/// Whether admins need to approve new articles
#[default = false]
#[cfg_attr(feature = "ssr", doku(example = "false"))]
pub article_approval: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
#[cfg_attr(feature = "ssr", derive(Queryable))]
#[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))]
pub struct SiteView {
pub my_profile: Option<LocalUserView>,
pub config: Options,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct GetInstance {
pub id: Option<InstanceId>,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct FollowInstance {
pub id: InstanceId,
}

View file

@ -1,25 +1,14 @@
pub mod article;
pub mod instance;
pub mod newtypes;
pub mod user;
pub mod utils;
pub mod validation;
use article::{ApiConflict, DbArticle};
use chrono::{DateTime, Utc};
use newtypes::{ArticleId, ConflictId, EditId, InstanceId, PersonId};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use smart_default::SmartDefault;
use url::Url;
use uuid::Uuid;
#[cfg(feature = "ssr")]
use {
crate::backend::{
database::schema::{article, edit, instance, local_user, person},
federation::objects::articles_collection::DbArticleCollection,
federation::objects::instance_collection::DbInstanceCollection,
},
activitypub_federation::fetch::{collection_id::CollectionId, object_id::ObjectId},
diesel::{Identifiable, Queryable, Selectable},
doku::Document,
};
pub const MAIN_PAGE_NAME: &str = "Main_Page";
@ -28,231 +17,6 @@ pub static AUTH_COOKIE: &str = "auth";
#[derive(Clone, Debug)]
pub struct Auth(pub Option<String>);
/// Should be an enum Title/Id but fails due to https://github.com/nox/serde_urlencoded/issues/66
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
pub struct GetArticleForm {
pub title: Option<String>,
pub domain: Option<String>,
pub id: Option<ArticleId>,
}
#[derive(Deserialize, Serialize, Clone, Default, Debug)]
pub struct ListArticlesForm {
pub only_local: Option<bool>,
pub instance_id: Option<InstanceId>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "ssr", derive(Queryable))]
#[cfg_attr(feature = "ssr", diesel(table_name = article, check_for_backend(diesel::pg::Pg)))]
pub struct ArticleView {
pub article: DbArticle,
pub instance: DbInstance,
pub latest_version: EditVersion,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Identifiable))]
#[cfg_attr(feature = "ssr", diesel(table_name = article, check_for_backend(diesel::pg::Pg), belongs_to(DbInstance, foreign_key = instance_id)))]
pub struct DbArticle {
pub id: ArticleId,
pub title: String,
pub text: String,
#[cfg(feature = "ssr")]
pub ap_id: ObjectId<DbArticle>,
#[cfg(not(feature = "ssr"))]
pub ap_id: String,
pub instance_id: InstanceId,
pub local: bool,
pub protected: bool,
pub approved: bool,
pub published: DateTime<Utc>,
}
/// Represents a single change to the article.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable))]
#[cfg_attr(feature = "ssr", diesel(table_name = edit, check_for_backend(diesel::pg::Pg)))]
pub struct DbEdit {
// TODO: we could use hash as primary key, but that gives errors on forking because
// the same edit is used for multiple articles
pub id: EditId,
#[serde(skip)]
pub creator_id: PersonId,
/// UUID built from sha224 hash of diff
pub hash: EditVersion,
#[cfg(feature = "ssr")]
pub ap_id: ObjectId<DbEdit>,
#[cfg(not(feature = "ssr"))]
pub ap_id: String,
pub diff: String,
pub summary: String,
pub article_id: ArticleId,
/// First edit of an article always has `EditVersion::default()` here
pub previous_version_id: EditVersion,
pub published: DateTime<Utc>,
}
#[derive(Deserialize, Serialize, Clone, Debug, Default)]
pub struct GetEditList {
pub article_id: Option<ArticleId>,
pub person_id: Option<PersonId>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "ssr", derive(Queryable))]
#[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))]
pub struct EditView {
pub edit: DbEdit,
pub article: DbArticle,
pub creator: DbPerson,
}
/// The version hash of a specific edit. Generated by taking an SHA256 hash of the diff
/// and using the first 16 bytes so that it fits into UUID.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "ssr", derive(diesel_derive_newtype::DieselNewType))]
pub struct EditVersion(pub(crate) Uuid);
impl EditVersion {
pub fn new(diff: &str) -> Self {
let mut sha256 = Sha256::new();
sha256.update(diff);
let hash_bytes = sha256.finalize();
let uuid =
Uuid::from_slice(&hash_bytes.as_slice()[..16]).expect("hash is correct size for uuid");
EditVersion(uuid)
}
pub fn hash(&self) -> String {
hex::encode(self.0.into_bytes())
}
}
impl Default for EditVersion {
fn default() -> Self {
EditVersion::new("")
}
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct RegisterUserForm {
pub username: String,
pub password: String,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct LoginUserForm {
pub username: String,
pub password: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "ssr", derive(Queryable))]
#[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))]
pub struct LocalUserView {
pub person: DbPerson,
pub local_user: DbLocalUser,
pub following: Vec<DbInstance>,
}
/// A user with account registered on local instance.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Identifiable))]
#[cfg_attr(feature = "ssr", diesel(table_name = local_user, check_for_backend(diesel::pg::Pg)))]
pub struct DbLocalUser {
pub id: InstanceId,
#[serde(skip)]
pub password_encrypted: String,
pub person_id: PersonId,
pub admin: bool,
}
/// Federation related data from a local or remote user.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Identifiable))]
#[cfg_attr(feature = "ssr", diesel(table_name = person, check_for_backend(diesel::pg::Pg)))]
pub struct DbPerson {
pub id: PersonId,
pub username: String,
#[cfg(feature = "ssr")]
pub ap_id: ObjectId<DbPerson>,
#[cfg(not(feature = "ssr"))]
pub ap_id: String,
pub inbox_url: String,
#[serde(skip)]
pub public_key: String,
#[serde(skip)]
pub private_key: Option<String>,
#[serde(skip)]
pub last_refreshed_at: DateTime<Utc>,
pub local: bool,
pub display_name: Option<String>,
pub bio: Option<String>,
}
impl DbPerson {
pub fn inbox_url(&self) -> Url {
Url::parse(&self.inbox_url).expect("can parse inbox url")
}
}
#[derive(Deserialize, Serialize, Debug)]
pub struct CreateArticleForm {
pub title: String,
pub text: String,
pub summary: String,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct EditArticleForm {
/// Id of the article to edit
pub article_id: ArticleId,
/// Full, new text of the article. A diff against `previous_version` is generated on the backend
/// side to handle conflicts.
pub new_text: String,
/// What was changed
pub summary: String,
/// The version that this edit is based on, ie [DbArticle.latest_version] or
/// [ApiConflict.previous_version]
pub previous_version_id: EditVersion,
/// If you are resolving a conflict, pass the id to delete conflict from the database
pub resolve_conflict_id: Option<ConflictId>,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct ProtectArticleForm {
pub article_id: ArticleId,
pub protected: bool,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct ForkArticleForm {
pub article_id: ArticleId,
pub new_title: String,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct ApproveArticleForm {
pub article_id: ArticleId,
pub approve: bool,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct DeleteConflictForm {
pub conflict_id: ConflictId,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct GetInstance {
pub id: Option<InstanceId>,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct FollowInstance {
pub id: InstanceId,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct SuccessResponse {
success: bool,
@ -264,27 +28,11 @@ impl Default for SuccessResponse {
}
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct SearchArticleForm {
pub query: String,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct ResolveObject {
pub id: Url,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct ApiConflict {
pub id: ConflictId,
pub hash: EditVersion,
pub three_way_merge: String,
pub summary: String,
pub article: DbArticle,
pub previous_version_id: EditVersion,
pub published: DateTime<Utc>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Notification {
EditConflict(ApiConflict),
@ -299,89 +47,3 @@ impl Notification {
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Identifiable))]
#[cfg_attr(feature = "ssr", diesel(table_name = instance, check_for_backend(diesel::pg::Pg)))]
pub struct DbInstance {
pub id: InstanceId,
pub domain: String,
#[cfg(feature = "ssr")]
pub ap_id: ObjectId<DbInstance>,
#[cfg(not(feature = "ssr"))]
pub ap_id: String,
pub description: Option<String>,
#[cfg(feature = "ssr")]
pub articles_url: Option<CollectionId<DbArticleCollection>>,
#[cfg(not(feature = "ssr"))]
pub articles_url: String,
pub inbox_url: String,
#[serde(skip)]
pub public_key: String,
#[serde(skip)]
pub private_key: Option<String>,
pub last_refreshed_at: DateTime<Utc>,
pub local: bool,
#[cfg(feature = "ssr")]
pub instances_url: Option<CollectionId<DbInstanceCollection>>,
}
impl DbInstance {
pub fn inbox_url(&self) -> Url {
Url::parse(&self.inbox_url).expect("can parse inbox url")
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "ssr", derive(Queryable))]
#[cfg_attr(feature = "ssr", diesel(table_name = article, check_for_backend(diesel::pg::Pg)))]
pub struct InstanceView {
pub instance: DbInstance,
pub followers: Vec<DbPerson>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct GetUserForm {
pub name: String,
pub domain: Option<String>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct UpdateUserForm {
pub person_id: PersonId,
pub display_name: Option<String>,
pub bio: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, SmartDefault)]
#[serde(default)]
#[serde(deny_unknown_fields)]
#[cfg_attr(feature = "ssr", derive(Queryable, Document))]
#[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))]
pub struct Options {
/// Whether users can create new accounts
#[default = true]
#[cfg_attr(feature = "ssr", doku(example = "true"))]
pub registration_open: bool,
/// Whether admins need to approve new articles
#[default = false]
#[cfg_attr(feature = "ssr", doku(example = "false"))]
pub article_approval: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
#[cfg_attr(feature = "ssr", derive(Queryable))]
#[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))]
pub struct SiteView {
pub my_profile: Option<LocalUserView>,
pub config: Options,
}
#[test]
fn test_edit_versions() {
let default = EditVersion::default();
assert_eq!("e3b0c44298fc1c149afbf4c8996fb924", default.hash());
let version = EditVersion::new("test");
assert_eq!("9f86d081884c7d659a2feaa0c55ad015", version.hash());
}

88
src/common/user.rs Normal file
View file

@ -0,0 +1,88 @@
use super::{
instance::DbInstance,
newtypes::{InstanceId, PersonId},
};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use url::Url;
#[cfg(feature = "ssr")]
use {
crate::backend::database::schema::{local_user, person},
activitypub_federation::fetch::object_id::ObjectId,
diesel::{Identifiable, Queryable, Selectable},
};
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct RegisterUserForm {
pub username: String,
pub password: String,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct LoginUserForm {
pub username: String,
pub password: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "ssr", derive(Queryable))]
#[cfg_attr(feature = "ssr", diesel(check_for_backend(diesel::pg::Pg)))]
pub struct LocalUserView {
pub person: DbPerson,
pub local_user: DbLocalUser,
pub following: Vec<DbInstance>,
}
/// A user with account registered on local instance.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Identifiable))]
#[cfg_attr(feature = "ssr", diesel(table_name = local_user, check_for_backend(diesel::pg::Pg)))]
pub struct DbLocalUser {
pub id: InstanceId,
#[serde(skip)]
pub password_encrypted: String,
pub person_id: PersonId,
pub admin: bool,
}
/// Federation related data from a local or remote user.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "ssr", derive(Queryable, Selectable, Identifiable))]
#[cfg_attr(feature = "ssr", diesel(table_name = person, check_for_backend(diesel::pg::Pg)))]
pub struct DbPerson {
pub id: PersonId,
pub username: String,
#[cfg(feature = "ssr")]
pub ap_id: ObjectId<DbPerson>,
#[cfg(not(feature = "ssr"))]
pub ap_id: String,
pub inbox_url: String,
#[serde(skip)]
pub public_key: String,
#[serde(skip)]
pub private_key: Option<String>,
#[serde(skip)]
pub last_refreshed_at: DateTime<Utc>,
pub local: bool,
pub display_name: Option<String>,
pub bio: Option<String>,
}
impl DbPerson {
pub fn inbox_url(&self) -> Url {
Url::parse(&self.inbox_url).expect("can parse inbox url")
}
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct GetUserForm {
pub name: String,
pub domain: Option<String>,
}
#[derive(Deserialize, Serialize, Clone, Debug)]
pub struct UpdateUserForm {
pub person_id: PersonId,
pub display_name: Option<String>,
pub bio: Option<String>,
}

View file

@ -1,4 +1,4 @@
use crate::common::DbArticle;
use crate::common::article::DbArticle;
use leptos::server_fn::error::ServerFnErrorErr;
pub fn can_edit_article(article: &DbArticle, is_admin: bool) -> Result<(), ServerFnErrorErr> {

View file

@ -1,12 +1,14 @@
use crate::common::{
newtypes::{ArticleId, ConflictId},
article::*,
instance::*,
newtypes::{ArticleId, ConflictId, PersonId},
user::*,
utils::http_protocol_str,
*,
};
use http::{Method, StatusCode};
use leptos::{prelude::ServerFnError, server_fn::error::NoCustomError};
use log::error;
use newtypes::PersonId;
use serde::{Deserialize, Serialize};
use std::{fmt::Debug, sync::LazyLock};
use url::Url;

View file

@ -1,5 +1,5 @@
use crate::{
common::SiteView,
common::instance::SiteView,
frontend::{
api::CLIENT,
components::{nav::Nav, protected_route::IbisProtectedRoute},

View file

@ -1,5 +1,5 @@
use crate::{
common::{validation::can_edit_article, ArticleView},
common::{article::ArticleView, validation::can_edit_article},
frontend::{
app::{is_admin, is_logged_in},
article_path,

View file

@ -1,5 +1,5 @@
use crate::{
common::{utils::extract_domain, EditView},
common::{article::EditView, utils::extract_domain},
frontend::{article_link, render_date_time, user_link},
};
use leptos::{either::Either, prelude::*};

View file

@ -1,5 +1,8 @@
use crate::{
common::{newtypes::InstanceId, DbInstance, FollowInstance},
common::{
instance::{DbInstance, FollowInstance},
newtypes::InstanceId,
},
frontend::{
api::CLIENT,
app::{site, DefaultResource},

View file

@ -1,4 +1,4 @@
use crate::common::{utils::extract_domain, DbArticle, DbPerson};
use crate::common::{article::DbArticle, user::DbPerson, utils::extract_domain};
use chrono::{DateTime, Duration, Local, Utc};
use codee::string::FromToStringCodec;
use leptos::prelude::*;

View file

@ -1,5 +1,8 @@
use crate::{
common::{newtypes::ArticleId, ForkArticleForm, ProtectArticleForm},
common::{
article::{ForkArticleForm, ProtectArticleForm},
newtypes::ArticleId,
},
frontend::{
api::CLIENT,
app::is_admin,

View file

@ -1,5 +1,5 @@
use crate::{
common::CreateArticleForm,
common::article::CreateArticleForm,
frontend::{
api::CLIENT,
app::{is_admin, site, DefaultResource},

View file

@ -1,9 +1,7 @@
use crate::{
common::{
article::{ApiConflict, ArticleView, EditArticleForm},
newtypes::ConflictId,
ApiConflict,
ArticleView,
EditArticleForm,
Notification,
MAIN_PAGE_NAME,
},

View file

@ -1,5 +1,5 @@
use crate::{
common::ListArticlesForm,
common::article::ListArticlesForm,
frontend::{
api::CLIENT,
app::DefaultResource,

View file

@ -1,5 +1,5 @@
use crate::{
common::{utils::http_protocol_str, DbInstance, ListArticlesForm},
common::{article::ListArticlesForm, instance::DbInstance, utils::http_protocol_str},
frontend::{
api::CLIENT,
article_path,

View file

@ -1,5 +1,5 @@
use crate::{
common::LoginUserForm,
common::user::LoginUserForm,
frontend::{api::CLIENT, app::site, components::credentials::*},
};
use leptos::prelude::*;

View file

@ -1,5 +1,8 @@
use crate::{
common::{ArticleView, EditView, GetArticleForm, MAIN_PAGE_NAME},
common::{
article::{ArticleView, EditView, GetArticleForm},
MAIN_PAGE_NAME,
},
frontend::api::CLIENT,
};
use leptos::prelude::*;

View file

@ -1,5 +1,5 @@
use crate::{
common::RegisterUserForm,
common::user::RegisterUserForm,
frontend::{api::CLIENT, app::site, components::credentials::*},
};
use leptos::prelude::*;

View file

@ -1,5 +1,8 @@
use crate::{
common::{DbArticle, DbInstance, SearchArticleForm},
common::{
article::{DbArticle, SearchArticleForm},
instance::DbInstance,
},
frontend::{api::CLIENT, article_path, article_title},
};
use leptos::prelude::*;

View file

@ -1,5 +1,5 @@
use crate::{
common::UpdateUserForm,
common::user::UpdateUserForm,
frontend::{
api::CLIENT,
app::{site, DefaultResource},

View file

@ -1,5 +1,5 @@
use crate::{
common::GetUserForm,
common::user::GetUserForm,
frontend::{
api::CLIENT,
components::edit_list::EditList,

View file

@ -6,7 +6,7 @@ use ibis::{
config::{IbisConfig, IbisConfigDatabase, IbisConfigFederation},
start,
},
common::{Options, RegisterUserForm},
common::{instance::Options, user::RegisterUserForm},
frontend::api::ApiClient,
};
use reqwest::ClientBuilder;

View file

@ -5,19 +5,19 @@ mod common;
use crate::common::{TestData, TEST_ARTICLE_DEFAULT_TEXT};
use anyhow::Result;
use ibis::common::{
article::{
ArticleView,
CreateArticleForm,
EditArticleForm,
ForkArticleForm,
GetArticleForm,
ListArticlesForm,
ProtectArticleForm,
SearchArticleForm,
},
user::{GetUserForm, LoginUserForm, RegisterUserForm},
utils::extract_domain,
ArticleView,
CreateArticleForm,
EditArticleForm,
ForkArticleForm,
GetArticleForm,
GetUserForm,
ListArticlesForm,
LoginUserForm,
Notification,
ProtectArticleForm,
RegisterUserForm,
SearchArticleForm,
};
use pretty_assertions::{assert_eq, assert_ne};
use retry_future::{LinearRetryStrategy, RetryFuture, RetryPolicy};