mirror of
https://github.com/Nutomic/ibis.git
synced 2024-12-01 18:41:09 +00:00
Merge branch 'master' into ci-release
This commit is contained in:
commit
f7c5d0d28c
57 changed files with 910 additions and 522 deletions
4
.rustfmt.toml
Normal file
4
.rustfmt.toml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
edition = "2021"
|
||||||
|
imports_layout = "HorizontalVertical"
|
||||||
|
imports_granularity = "Crate"
|
||||||
|
group_imports = "One"
|
|
@ -4,13 +4,14 @@ variables:
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
cargo_fmt:
|
cargo_fmt:
|
||||||
image: *rust_image
|
image: rustlang/rust:nightly
|
||||||
environment:
|
environment:
|
||||||
# store cargo data in repo folder so that it gets cached between steps
|
# store cargo data in repo folder so that it gets cached between steps
|
||||||
CARGO_HOME: .cargo_home
|
CARGO_HOME: .cargo_home
|
||||||
commands:
|
commands:
|
||||||
- rustup component add rustfmt
|
- rustup component add rustfmt
|
||||||
- cargo fmt -- --check
|
- cargo +nightly fmt -- --check
|
||||||
|
|
||||||
|
|
||||||
check_config_defaults_updated:
|
check_config_defaults_updated:
|
||||||
image: *rust_image
|
image: *rust_image
|
||||||
|
|
|
@ -43,7 +43,8 @@ create table article (
|
||||||
text text not null,
|
text text not null,
|
||||||
ap_id varchar(255) not null unique,
|
ap_id varchar(255) not null unique,
|
||||||
instance_id int REFERENCES instance ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
instance_id int REFERENCES instance ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||||
local bool not null
|
local bool not null,
|
||||||
|
protected bool not null
|
||||||
);
|
);
|
||||||
|
|
||||||
create table edit (
|
create table edit (
|
||||||
|
|
|
@ -1,27 +1,38 @@
|
||||||
use crate::backend::database::article::DbArticleForm;
|
use crate::{
|
||||||
use crate::backend::database::conflict::{DbConflict, DbConflictForm};
|
backend::{
|
||||||
use crate::backend::database::edit::DbEditForm;
|
database::{
|
||||||
use crate::backend::database::IbisData;
|
article::DbArticleForm,
|
||||||
use crate::backend::error::MyResult;
|
conflict::{DbConflict, DbConflictForm},
|
||||||
use crate::backend::federation::activities::create_article::CreateArticle;
|
edit::DbEditForm,
|
||||||
use crate::backend::federation::activities::submit_article_update;
|
IbisData,
|
||||||
use crate::backend::utils::generate_article_version;
|
},
|
||||||
use crate::common::utils::extract_domain;
|
error::MyResult,
|
||||||
use crate::common::utils::http_protocol_str;
|
federation::activities::{create_article::CreateArticle, submit_article_update},
|
||||||
use crate::common::validation::can_edit_article;
|
utils::generate_article_version,
|
||||||
use crate::common::LocalUserView;
|
},
|
||||||
use crate::common::{ApiConflict, ResolveObject};
|
common::{
|
||||||
use crate::common::{ArticleView, DbArticle, DbEdit};
|
utils::{extract_domain, http_protocol_str},
|
||||||
use crate::common::{CreateArticleData, EditArticleData, EditVersion, ForkArticleData};
|
validation::can_edit_article,
|
||||||
use crate::common::{DbInstance, SearchArticleData};
|
ApiConflict,
|
||||||
use crate::common::{GetArticleData, ListArticlesData};
|
ArticleView,
|
||||||
use activitypub_federation::config::Data;
|
CreateArticleForm,
|
||||||
use activitypub_federation::fetch::object_id::ObjectId;
|
DbArticle,
|
||||||
|
DbEdit,
|
||||||
|
DbInstance,
|
||||||
|
EditArticleForm,
|
||||||
|
EditVersion,
|
||||||
|
ForkArticleForm,
|
||||||
|
GetArticleForm,
|
||||||
|
ListArticlesForm,
|
||||||
|
LocalUserView,
|
||||||
|
ProtectArticleForm,
|
||||||
|
ResolveObject,
|
||||||
|
SearchArticleForm,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use activitypub_federation::{config::Data, fetch::object_id::ObjectId};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use axum::extract::Query;
|
use axum::{extract::Query, Extension, Form, Json};
|
||||||
use axum::Extension;
|
|
||||||
use axum::Form;
|
|
||||||
use axum::Json;
|
|
||||||
use axum_macros::debug_handler;
|
use axum_macros::debug_handler;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use diffy::create_patch;
|
use diffy::create_patch;
|
||||||
|
@ -31,7 +42,7 @@ use diffy::create_patch;
|
||||||
pub(in crate::backend::api) async fn create_article(
|
pub(in crate::backend::api) async fn create_article(
|
||||||
Extension(user): Extension<LocalUserView>,
|
Extension(user): Extension<LocalUserView>,
|
||||||
data: Data<IbisData>,
|
data: Data<IbisData>,
|
||||||
Form(create_article): Form<CreateArticleData>,
|
Form(create_article): Form<CreateArticleForm>,
|
||||||
) -> MyResult<Json<ArticleView>> {
|
) -> MyResult<Json<ArticleView>> {
|
||||||
if create_article.title.is_empty() {
|
if create_article.title.is_empty() {
|
||||||
return Err(anyhow!("Title must not be empty").into());
|
return Err(anyhow!("Title must not be empty").into());
|
||||||
|
@ -50,10 +61,11 @@ pub(in crate::backend::api) async fn create_article(
|
||||||
ap_id,
|
ap_id,
|
||||||
instance_id: local_instance.id,
|
instance_id: local_instance.id,
|
||||||
local: true,
|
local: true,
|
||||||
|
protected: false,
|
||||||
};
|
};
|
||||||
let article = DbArticle::create(form, &data)?;
|
let article = DbArticle::create(form, &data)?;
|
||||||
|
|
||||||
let edit_data = EditArticleData {
|
let edit_data = EditArticleForm {
|
||||||
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,
|
||||||
|
@ -81,7 +93,7 @@ pub(in crate::backend::api) async fn create_article(
|
||||||
pub(in crate::backend::api) async fn edit_article(
|
pub(in crate::backend::api) async fn edit_article(
|
||||||
Extension(user): Extension<LocalUserView>,
|
Extension(user): Extension<LocalUserView>,
|
||||||
data: Data<IbisData>,
|
data: Data<IbisData>,
|
||||||
Form(mut edit_form): Form<EditArticleData>,
|
Form(mut edit_form): Form<EditArticleForm>,
|
||||||
) -> 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 {
|
||||||
|
@ -136,7 +148,7 @@ pub(in crate::backend::api) async fn edit_article(
|
||||||
/// Retrieve an article by ID. It must already be stored in the local database.
|
/// Retrieve an article by ID. It must already be stored in the local database.
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
pub(in crate::backend::api) async fn get_article(
|
pub(in crate::backend::api) async fn get_article(
|
||||||
Query(query): Query<GetArticleData>,
|
Query(query): Query<GetArticleForm>,
|
||||||
data: Data<IbisData>,
|
data: Data<IbisData>,
|
||||||
) -> MyResult<Json<ArticleView>> {
|
) -> MyResult<Json<ArticleView>> {
|
||||||
match (query.title, query.id) {
|
match (query.title, query.id) {
|
||||||
|
@ -157,7 +169,7 @@ pub(in crate::backend::api) async fn get_article(
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
pub(in crate::backend::api) async fn list_articles(
|
pub(in crate::backend::api) async fn list_articles(
|
||||||
Query(query): Query<ListArticlesData>,
|
Query(query): Query<ListArticlesForm>,
|
||||||
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);
|
||||||
|
@ -170,7 +182,7 @@ pub(in crate::backend::api) async fn list_articles(
|
||||||
pub(in crate::backend::api) async fn fork_article(
|
pub(in crate::backend::api) async fn fork_article(
|
||||||
Extension(_user): Extension<LocalUserView>,
|
Extension(_user): Extension<LocalUserView>,
|
||||||
data: Data<IbisData>,
|
data: Data<IbisData>,
|
||||||
Form(fork_form): Form<ForkArticleData>,
|
Form(fork_form): Form<ForkArticleForm>,
|
||||||
) -> 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)?;
|
let original_article = DbArticle::read(fork_form.article_id, &data)?;
|
||||||
|
@ -188,6 +200,7 @@ pub(in crate::backend::api) async fn fork_article(
|
||||||
ap_id,
|
ap_id,
|
||||||
instance_id: local_instance.id,
|
instance_id: local_instance.id,
|
||||||
local: true,
|
local: true,
|
||||||
|
protected: false,
|
||||||
};
|
};
|
||||||
let article = DbArticle::create(form, &data)?;
|
let article = DbArticle::create(form, &data)?;
|
||||||
|
|
||||||
|
@ -242,7 +255,7 @@ pub(super) async fn resolve_article(
|
||||||
/// Search articles for matching title or body text.
|
/// Search articles for matching title or body text.
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
pub(super) async fn search_article(
|
pub(super) async fn search_article(
|
||||||
Query(query): Query<SearchArticleData>,
|
Query(query): Query<SearchArticleForm>,
|
||||||
data: Data<IbisData>,
|
data: Data<IbisData>,
|
||||||
) -> MyResult<Json<Vec<DbArticle>>> {
|
) -> MyResult<Json<Vec<DbArticle>>> {
|
||||||
if query.query.is_empty() {
|
if query.query.is_empty() {
|
||||||
|
@ -251,3 +264,17 @@ pub(super) async fn search_article(
|
||||||
let article = DbArticle::search(&query.query, &data)?;
|
let article = DbArticle::search(&query.query, &data)?;
|
||||||
Ok(Json(article))
|
Ok(Json(article))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[debug_handler]
|
||||||
|
pub(in crate::backend::api) async fn protect_article(
|
||||||
|
Extension(user): Extension<LocalUserView>,
|
||||||
|
data: Data<IbisData>,
|
||||||
|
Form(lock_params): Form<ProtectArticleForm>,
|
||||||
|
) -> MyResult<Json<DbArticle>> {
|
||||||
|
if !user.local_user.admin {
|
||||||
|
return Err(anyhow!("Only admin can lock articles").into());
|
||||||
|
}
|
||||||
|
let article =
|
||||||
|
DbArticle::update_protected(lock_params.article_id, lock_params.protected, &data)?;
|
||||||
|
Ok(Json(article))
|
||||||
|
}
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
use crate::backend::database::IbisData;
|
use crate::{
|
||||||
use crate::backend::error::MyResult;
|
backend::{database::IbisData, error::MyResult, federation::activities::follow::Follow},
|
||||||
use crate::backend::federation::activities::follow::Follow;
|
common::{DbInstance, FollowInstance, InstanceView, LocalUserView, ResolveObject},
|
||||||
use crate::common::{DbInstance, InstanceView, ResolveObject};
|
};
|
||||||
use crate::common::{FollowInstance, LocalUserView};
|
use activitypub_federation::{config::Data, fetch::object_id::ObjectId};
|
||||||
use activitypub_federation::config::Data;
|
use axum::{extract::Query, Extension, Form, Json};
|
||||||
use activitypub_federation::fetch::object_id::ObjectId;
|
|
||||||
use axum::extract::Query;
|
|
||||||
use axum::Extension;
|
|
||||||
use axum::{Form, Json};
|
|
||||||
use axum_macros::debug_handler;
|
use axum_macros::debug_handler;
|
||||||
|
|
||||||
/// Retrieve the local instance info.
|
/// Retrieve the local instance info.
|
||||||
|
|
|
@ -1,28 +1,42 @@
|
||||||
use crate::backend::api::article::{
|
use crate::{
|
||||||
create_article, list_articles, resolve_article, search_article,
|
backend::{
|
||||||
|
api::{
|
||||||
|
article::{
|
||||||
|
create_article,
|
||||||
|
edit_article,
|
||||||
|
fork_article,
|
||||||
|
get_article,
|
||||||
|
list_articles,
|
||||||
|
protect_article,
|
||||||
|
resolve_article,
|
||||||
|
search_article,
|
||||||
|
},
|
||||||
|
instance::{follow_instance, get_local_instance, resolve_instance},
|
||||||
|
user::{
|
||||||
|
get_user,
|
||||||
|
login_user,
|
||||||
|
logout_user,
|
||||||
|
my_profile,
|
||||||
|
register_user,
|
||||||
|
validate,
|
||||||
|
AUTH_COOKIE,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
database::{conflict::DbConflict, IbisData},
|
||||||
|
error::MyResult,
|
||||||
|
},
|
||||||
|
common::{ApiConflict, LocalUserView},
|
||||||
};
|
};
|
||||||
use crate::backend::api::article::{edit_article, fork_article, get_article};
|
|
||||||
use crate::backend::api::instance::get_local_instance;
|
|
||||||
use crate::backend::api::instance::{follow_instance, resolve_instance};
|
|
||||||
use crate::backend::api::user::validate;
|
|
||||||
use crate::backend::api::user::{get_user, register_user};
|
|
||||||
use crate::backend::api::user::{login_user, logout_user};
|
|
||||||
use crate::backend::api::user::{my_profile, AUTH_COOKIE};
|
|
||||||
use crate::backend::database::conflict::DbConflict;
|
|
||||||
use crate::backend::database::IbisData;
|
|
||||||
use crate::backend::error::MyResult;
|
|
||||||
use crate::common::ApiConflict;
|
|
||||||
use crate::common::LocalUserView;
|
|
||||||
use activitypub_federation::config::Data;
|
use activitypub_federation::config::Data;
|
||||||
use axum::routing::{get, post};
|
|
||||||
use axum::{
|
use axum::{
|
||||||
http::Request,
|
http::{Request, StatusCode},
|
||||||
http::StatusCode,
|
|
||||||
middleware::{self, Next},
|
middleware::{self, Next},
|
||||||
response::Response,
|
response::Response,
|
||||||
|
routing::{get, post},
|
||||||
Extension,
|
Extension,
|
||||||
|
Json,
|
||||||
|
Router,
|
||||||
};
|
};
|
||||||
use axum::{Json, Router};
|
|
||||||
use axum_extra::extract::CookieJar;
|
use axum_extra::extract::CookieJar;
|
||||||
use axum_macros::debug_handler;
|
use axum_macros::debug_handler;
|
||||||
use futures::future::try_join_all;
|
use futures::future::try_join_all;
|
||||||
|
@ -40,6 +54,7 @@ pub fn api_routes() -> Router {
|
||||||
.route("/article/list", get(list_articles))
|
.route("/article/list", get(list_articles))
|
||||||
.route("/article/fork", post(fork_article))
|
.route("/article/fork", post(fork_article))
|
||||||
.route("/article/resolve", get(resolve_article))
|
.route("/article/resolve", get(resolve_article))
|
||||||
|
.route("/article/protect", post(protect_article))
|
||||||
.route("/edit_conflicts", get(edit_conflicts))
|
.route("/edit_conflicts", get(edit_conflicts))
|
||||||
.route("/instance", get(get_local_instance))
|
.route("/instance", get(get_local_instance))
|
||||||
.route("/instance/follow", post(follow_instance))
|
.route("/instance/follow", post(follow_instance))
|
||||||
|
|
|
@ -1,18 +1,26 @@
|
||||||
use crate::backend::database::{read_jwt_secret, IbisData};
|
use crate::{
|
||||||
use crate::backend::error::MyResult;
|
backend::{
|
||||||
use crate::common::{DbPerson, GetUserData, LocalUserView, LoginUserData, RegisterUserData};
|
database::{read_jwt_secret, IbisData},
|
||||||
|
error::MyResult,
|
||||||
|
},
|
||||||
|
common::{DbPerson, GetUserForm, LocalUserView, LoginUserForm, RegisterUserForm},
|
||||||
|
};
|
||||||
use activitypub_federation::config::Data;
|
use activitypub_federation::config::Data;
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use axum::extract::Query;
|
use axum::{extract::Query, Form, Json};
|
||||||
use axum::{Form, Json};
|
|
||||||
use axum_extra::extract::cookie::{Cookie, CookieJar, Expiration, SameSite};
|
use axum_extra::extract::cookie::{Cookie, CookieJar, Expiration, SameSite};
|
||||||
use axum_macros::debug_handler;
|
use axum_macros::debug_handler;
|
||||||
use bcrypt::verify;
|
use bcrypt::verify;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use jsonwebtoken::DecodingKey;
|
use jsonwebtoken::{
|
||||||
use jsonwebtoken::Validation;
|
decode,
|
||||||
use jsonwebtoken::{decode, get_current_timestamp};
|
encode,
|
||||||
use jsonwebtoken::{encode, EncodingKey, Header};
|
get_current_timestamp,
|
||||||
|
DecodingKey,
|
||||||
|
EncodingKey,
|
||||||
|
Header,
|
||||||
|
Validation,
|
||||||
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use time::{Duration, OffsetDateTime};
|
use time::{Duration, OffsetDateTime};
|
||||||
|
|
||||||
|
@ -57,7 +65,7 @@ pub async fn validate(jwt: &str, data: &Data<IbisData>) -> MyResult<LocalUserVie
|
||||||
pub(in crate::backend::api) async fn register_user(
|
pub(in crate::backend::api) async fn register_user(
|
||||||
data: Data<IbisData>,
|
data: Data<IbisData>,
|
||||||
jar: CookieJar,
|
jar: CookieJar,
|
||||||
Form(form): Form<RegisterUserData>,
|
Form(form): Form<RegisterUserForm>,
|
||||||
) -> MyResult<(CookieJar, Json<LocalUserView>)> {
|
) -> MyResult<(CookieJar, Json<LocalUserView>)> {
|
||||||
if !data.config.registration_open {
|
if !data.config.registration_open {
|
||||||
return Err(anyhow!("Registration is closed").into());
|
return Err(anyhow!("Registration is closed").into());
|
||||||
|
@ -72,7 +80,7 @@ pub(in crate::backend::api) async fn register_user(
|
||||||
pub(in crate::backend::api) async fn login_user(
|
pub(in crate::backend::api) async fn login_user(
|
||||||
data: Data<IbisData>,
|
data: Data<IbisData>,
|
||||||
jar: CookieJar,
|
jar: CookieJar,
|
||||||
Form(form): Form<LoginUserData>,
|
Form(form): Form<LoginUserForm>,
|
||||||
) -> MyResult<(CookieJar, Json<LocalUserView>)> {
|
) -> MyResult<(CookieJar, Json<LocalUserView>)> {
|
||||||
let user = DbPerson::read_local_from_name(&form.username, &data)?;
|
let user = DbPerson::read_local_from_name(&form.username, &data)?;
|
||||||
let valid = verify(&form.password, &user.local_user.password_encrypted)?;
|
let valid = verify(&form.password, &user.local_user.password_encrypted)?;
|
||||||
|
@ -126,7 +134,7 @@ pub(in crate::backend::api) async fn logout_user(
|
||||||
|
|
||||||
#[debug_handler]
|
#[debug_handler]
|
||||||
pub(in crate::backend::api) async fn get_user(
|
pub(in crate::backend::api) async fn get_user(
|
||||||
params: Query<GetUserData>,
|
params: Query<GetUserForm>,
|
||||||
data: Data<IbisData>,
|
data: Data<IbisData>,
|
||||||
) -> MyResult<Json<DbPerson>> {
|
) -> MyResult<Json<DbPerson>> {
|
||||||
Ok(Json(DbPerson::read_from_name(
|
Ok(Json(DbPerson::read_from_name(
|
||||||
|
|
|
@ -1,17 +1,24 @@
|
||||||
use crate::backend::database::schema::{article, edit, instance};
|
use crate::{
|
||||||
use crate::backend::database::IbisData;
|
backend::{
|
||||||
use crate::backend::error::MyResult;
|
database::{
|
||||||
use crate::backend::federation::objects::edits_collection::DbEditCollection;
|
schema::{article, edit, instance},
|
||||||
use crate::common::DbEdit;
|
IbisData,
|
||||||
use crate::common::EditVersion;
|
},
|
||||||
use crate::common::{ArticleView, DbArticle};
|
error::MyResult,
|
||||||
use activitypub_federation::fetch::collection_id::CollectionId;
|
federation::objects::edits_collection::DbEditCollection,
|
||||||
use activitypub_federation::fetch::object_id::ObjectId;
|
},
|
||||||
use diesel::dsl::max;
|
common::{ArticleView, DbArticle, DbEdit, EditVersion},
|
||||||
|
};
|
||||||
use diesel::ExpressionMethods;
|
use activitypub_federation::fetch::{collection_id::CollectionId, object_id::ObjectId};
|
||||||
use diesel::{
|
use diesel::{
|
||||||
insert_into, AsChangeset, BoolExpressionMethods, Insertable, PgTextExpressionMethods, QueryDsl,
|
dsl::max,
|
||||||
|
insert_into,
|
||||||
|
AsChangeset,
|
||||||
|
BoolExpressionMethods,
|
||||||
|
ExpressionMethods,
|
||||||
|
Insertable,
|
||||||
|
PgTextExpressionMethods,
|
||||||
|
QueryDsl,
|
||||||
RunQueryDsl,
|
RunQueryDsl,
|
||||||
};
|
};
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
|
@ -24,6 +31,7 @@ pub struct DbArticleForm {
|
||||||
pub ap_id: ObjectId<DbArticle>,
|
pub ap_id: ObjectId<DbArticle>,
|
||||||
pub instance_id: i32,
|
pub instance_id: i32,
|
||||||
pub local: bool,
|
pub local: bool,
|
||||||
|
pub protected: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: get rid of unnecessary methods
|
// TODO: get rid of unnecessary methods
|
||||||
|
@ -58,6 +66,13 @@ impl DbArticle {
|
||||||
.get_result::<Self>(conn.deref_mut())?)
|
.get_result::<Self>(conn.deref_mut())?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn update_protected(id: i32, locked: bool, data: &IbisData) -> MyResult<Self> {
|
||||||
|
let mut conn = data.db_pool.get()?;
|
||||||
|
Ok(diesel::update(article::dsl::article.find(id))
|
||||||
|
.set(article::dsl::protected.eq(locked))
|
||||||
|
.get_result::<Self>(conn.deref_mut())?)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read(id: i32, data: &IbisData) -> MyResult<Self> {
|
pub fn read(id: i32, data: &IbisData) -> MyResult<Self> {
|
||||||
let mut conn = data.db_pool.get()?;
|
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())?)
|
||||||
|
|
|
@ -1,16 +1,23 @@
|
||||||
use crate::backend::database::schema::conflict;
|
use crate::{
|
||||||
use crate::backend::database::IbisData;
|
backend::{
|
||||||
use crate::backend::error::MyResult;
|
database::{schema::conflict, IbisData},
|
||||||
use crate::backend::federation::activities::submit_article_update;
|
error::MyResult,
|
||||||
use crate::backend::utils::generate_article_version;
|
federation::activities::submit_article_update,
|
||||||
use crate::common::DbEdit;
|
utils::generate_article_version,
|
||||||
use crate::common::DbLocalUser;
|
},
|
||||||
use crate::common::EditVersion;
|
common::{ApiConflict, DbArticle, DbEdit, DbLocalUser, EditVersion},
|
||||||
use crate::common::{ApiConflict, DbArticle};
|
};
|
||||||
use activitypub_federation::config::Data;
|
use activitypub_federation::config::Data;
|
||||||
use diesel::ExpressionMethods;
|
|
||||||
use diesel::{
|
use diesel::{
|
||||||
delete, insert_into, Identifiable, Insertable, QueryDsl, Queryable, RunQueryDsl, Selectable,
|
delete,
|
||||||
|
insert_into,
|
||||||
|
ExpressionMethods,
|
||||||
|
Identifiable,
|
||||||
|
Insertable,
|
||||||
|
QueryDsl,
|
||||||
|
Queryable,
|
||||||
|
RunQueryDsl,
|
||||||
|
Selectable,
|
||||||
};
|
};
|
||||||
use diffy::{apply, merge, Patch};
|
use diffy::{apply, merge, Patch};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
use crate::backend::database::schema::{edit, person};
|
use crate::{
|
||||||
use crate::backend::error::MyResult;
|
backend::{
|
||||||
use crate::backend::IbisData;
|
database::schema::{edit, person},
|
||||||
use crate::common::{DbArticle, DbEdit};
|
error::MyResult,
|
||||||
use crate::common::{EditVersion, EditView};
|
IbisData,
|
||||||
|
},
|
||||||
|
common::{DbArticle, DbEdit, 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::{insert_into, AsChangeset, ExpressionMethods, Insertable, 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;
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,29 @@
|
||||||
use crate::backend::database::schema::{instance, instance_follow};
|
use crate::{
|
||||||
use crate::backend::database::IbisData;
|
backend::{
|
||||||
use crate::backend::error::MyResult;
|
database::{
|
||||||
use crate::backend::federation::objects::articles_collection::DbArticleCollection;
|
schema::{instance, instance_follow},
|
||||||
use crate::common::{DbInstance, DbPerson, InstanceView};
|
IbisData,
|
||||||
use activitypub_federation::config::Data;
|
},
|
||||||
use activitypub_federation::fetch::collection_id::CollectionId;
|
error::MyResult,
|
||||||
use activitypub_federation::fetch::object_id::ObjectId;
|
federation::objects::articles_collection::DbArticleCollection,
|
||||||
|
},
|
||||||
|
common::{DbInstance, DbPerson, InstanceView},
|
||||||
|
};
|
||||||
|
use activitypub_federation::{
|
||||||
|
config::Data,
|
||||||
|
fetch::{collection_id::CollectionId, object_id::ObjectId},
|
||||||
|
};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use diesel::ExpressionMethods;
|
use diesel::{
|
||||||
use diesel::{insert_into, AsChangeset, Insertable, JoinOnDsl, QueryDsl, RunQueryDsl};
|
insert_into,
|
||||||
use std::fmt::Debug;
|
AsChangeset,
|
||||||
use std::ops::DerefMut;
|
ExpressionMethods,
|
||||||
|
Insertable,
|
||||||
|
JoinOnDsl,
|
||||||
|
QueryDsl,
|
||||||
|
RunQueryDsl,
|
||||||
|
};
|
||||||
|
use std::{fmt::Debug, ops::DerefMut};
|
||||||
|
|
||||||
#[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))]
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
use crate::backend::config::IbisConfig;
|
use crate::backend::{config::IbisConfig, database::schema::jwt_secret, error::MyResult};
|
||||||
use crate::backend::database::schema::jwt_secret;
|
use diesel::{
|
||||||
use crate::backend::error::MyResult;
|
r2d2::{ConnectionManager, Pool},
|
||||||
use diesel::r2d2::ConnectionManager;
|
PgConnection,
|
||||||
use diesel::r2d2::Pool;
|
QueryDsl,
|
||||||
use diesel::PgConnection;
|
RunQueryDsl,
|
||||||
use diesel::{QueryDsl, RunQueryDsl};
|
};
|
||||||
|
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
|
|
||||||
pub mod article;
|
pub mod article;
|
||||||
|
|
|
@ -9,6 +9,7 @@ diesel::table! {
|
||||||
ap_id -> Varchar,
|
ap_id -> Varchar,
|
||||||
instance_id -> Int4,
|
instance_id -> Int4,
|
||||||
local -> Bool,
|
local -> Bool,
|
||||||
|
protected -> Bool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,30 @@
|
||||||
use crate::backend::database::schema::{instance, instance_follow};
|
use crate::{
|
||||||
use crate::backend::database::schema::{local_user, person};
|
backend::{
|
||||||
use crate::backend::database::IbisData;
|
database::{
|
||||||
use crate::backend::error::MyResult;
|
schema::{instance, instance_follow, local_user, person},
|
||||||
use crate::common::utils::http_protocol_str;
|
IbisData,
|
||||||
use crate::common::{DbInstance, DbLocalUser, DbPerson, LocalUserView};
|
},
|
||||||
use activitypub_federation::config::Data;
|
error::MyResult,
|
||||||
use activitypub_federation::fetch::object_id::ObjectId;
|
},
|
||||||
use activitypub_federation::http_signatures::generate_actor_keypair;
|
common::{utils::http_protocol_str, DbInstance, DbLocalUser, DbPerson, LocalUserView},
|
||||||
use bcrypt::hash;
|
};
|
||||||
use bcrypt::DEFAULT_COST;
|
use activitypub_federation::{
|
||||||
|
config::Data,
|
||||||
|
fetch::object_id::ObjectId,
|
||||||
|
http_signatures::generate_actor_keypair,
|
||||||
|
};
|
||||||
|
use bcrypt::{hash, DEFAULT_COST};
|
||||||
use chrono::{DateTime, Local, Utc};
|
use chrono::{DateTime, Local, Utc};
|
||||||
use diesel::{insert_into, AsChangeset, Insertable, RunQueryDsl};
|
use diesel::{
|
||||||
use diesel::{ExpressionMethods, JoinOnDsl};
|
insert_into,
|
||||||
use diesel::{PgTextExpressionMethods, QueryDsl};
|
AsChangeset,
|
||||||
|
ExpressionMethods,
|
||||||
|
Insertable,
|
||||||
|
JoinOnDsl,
|
||||||
|
PgTextExpressionMethods,
|
||||||
|
QueryDsl,
|
||||||
|
RunQueryDsl,
|
||||||
|
};
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Insertable, AsChangeset)]
|
#[derive(Debug, Clone, Insertable, AsChangeset)]
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
use crate::backend::error::MyResult;
|
use crate::{
|
||||||
use crate::backend::federation::send_activity;
|
backend::{
|
||||||
use crate::backend::utils::generate_activity_id;
|
database::IbisData,
|
||||||
use crate::backend::{database::IbisData, federation::activities::follow::Follow};
|
error::MyResult,
|
||||||
use crate::common::DbInstance;
|
federation::{activities::follow::Follow, send_activity},
|
||||||
use activitypub_federation::traits::Actor;
|
utils::generate_activity_id,
|
||||||
|
},
|
||||||
|
common::DbInstance,
|
||||||
|
};
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
config::Data, fetch::object_id::ObjectId, kinds::activity::AcceptType, traits::ActivityHandler,
|
config::Data,
|
||||||
|
fetch::object_id::ObjectId,
|
||||||
|
kinds::activity::AcceptType,
|
||||||
|
traits::{ActivityHandler, Actor},
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
use crate::backend::database::IbisData;
|
use crate::{
|
||||||
use crate::backend::error::MyResult;
|
backend::{
|
||||||
use crate::backend::federation::objects::article::ApubArticle;
|
database::IbisData,
|
||||||
use crate::backend::utils::generate_activity_id;
|
error::MyResult,
|
||||||
use crate::common::DbArticle;
|
federation::objects::article::ApubArticle,
|
||||||
use crate::common::DbInstance;
|
utils::generate_activity_id,
|
||||||
use activitypub_federation::kinds::activity::CreateType;
|
},
|
||||||
|
common::{DbArticle, DbInstance},
|
||||||
|
};
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
config::Data,
|
config::Data,
|
||||||
fetch::object_id::ObjectId,
|
fetch::object_id::ObjectId,
|
||||||
|
kinds::activity::CreateType,
|
||||||
protocol::helpers::deserialize_one_or_many,
|
protocol::helpers::deserialize_one_or_many,
|
||||||
traits::{ActivityHandler, Object},
|
traits::{ActivityHandler, Object},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
use crate::backend::error::MyResult;
|
use crate::{
|
||||||
use crate::backend::federation::send_activity;
|
backend::{
|
||||||
use crate::backend::{
|
database::IbisData,
|
||||||
database::IbisData, federation::activities::accept::Accept, generate_activity_id,
|
error::MyResult,
|
||||||
|
federation::{activities::accept::Accept, send_activity},
|
||||||
|
generate_activity_id,
|
||||||
|
},
|
||||||
|
common::{DbInstance, DbPerson},
|
||||||
};
|
};
|
||||||
use crate::common::DbInstance;
|
|
||||||
use crate::common::DbPerson;
|
|
||||||
use activitypub_federation::protocol::verification::verify_urls_match;
|
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
config::Data,
|
config::Data,
|
||||||
fetch::object_id::ObjectId,
|
fetch::object_id::ObjectId,
|
||||||
kinds::activity::FollowType,
|
kinds::activity::FollowType,
|
||||||
|
protocol::verification::verify_urls_match,
|
||||||
traits::{ActivityHandler, Actor},
|
traits::{ActivityHandler, Actor},
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
use crate::backend::database::edit::DbEditForm;
|
use crate::{
|
||||||
use crate::backend::database::IbisData;
|
backend::{
|
||||||
use crate::backend::error::Error;
|
database::{edit::DbEditForm, IbisData},
|
||||||
use crate::backend::federation::activities::update_local_article::UpdateLocalArticle;
|
error::Error,
|
||||||
use crate::backend::federation::activities::update_remote_article::UpdateRemoteArticle;
|
federation::activities::{
|
||||||
use crate::common::DbInstance;
|
update_local_article::UpdateLocalArticle,
|
||||||
use crate::common::EditVersion;
|
update_remote_article::UpdateRemoteArticle,
|
||||||
use crate::common::{DbArticle, DbEdit};
|
},
|
||||||
|
},
|
||||||
|
common::{DbArticle, DbEdit, DbInstance, EditVersion},
|
||||||
|
};
|
||||||
use activitypub_federation::config::Data;
|
use activitypub_federation::config::Data;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,22 @@
|
||||||
use crate::backend::database::conflict::{DbConflict, DbConflictForm};
|
use crate::{
|
||||||
use crate::backend::database::IbisData;
|
backend::{
|
||||||
use crate::backend::error::MyResult;
|
database::{
|
||||||
use crate::backend::federation::objects::edit::ApubEdit;
|
conflict::{DbConflict, DbConflictForm},
|
||||||
use crate::backend::utils::generate_activity_id;
|
IbisData,
|
||||||
use crate::common::DbInstance;
|
},
|
||||||
use crate::common::EditVersion;
|
error::MyResult,
|
||||||
use activitypub_federation::kinds::activity::RejectType;
|
federation::{objects::edit::ApubEdit, send_activity},
|
||||||
|
utils::generate_activity_id,
|
||||||
|
},
|
||||||
|
common::{DbInstance, EditVersion},
|
||||||
|
};
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
config::Data, fetch::object_id::ObjectId, protocol::helpers::deserialize_one_or_many,
|
config::Data,
|
||||||
|
fetch::object_id::ObjectId,
|
||||||
|
kinds::activity::RejectType,
|
||||||
|
protocol::helpers::deserialize_one_or_many,
|
||||||
traits::ActivityHandler,
|
traits::ActivityHandler,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::backend::federation::send_activity;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
use crate::backend::database::IbisData;
|
use crate::{
|
||||||
use crate::backend::error::MyResult;
|
backend::{
|
||||||
use crate::backend::federation::objects::article::ApubArticle;
|
database::IbisData,
|
||||||
|
error::MyResult,
|
||||||
use crate::backend::utils::generate_activity_id;
|
federation::objects::article::ApubArticle,
|
||||||
use crate::common::DbArticle;
|
utils::generate_activity_id,
|
||||||
use crate::common::DbInstance;
|
},
|
||||||
use activitypub_federation::kinds::activity::UpdateType;
|
common::{DbArticle, DbInstance},
|
||||||
|
};
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
config::Data,
|
config::Data,
|
||||||
fetch::object_id::ObjectId,
|
fetch::object_id::ObjectId,
|
||||||
|
kinds::activity::UpdateType,
|
||||||
protocol::helpers::deserialize_one_or_many,
|
protocol::helpers::deserialize_one_or_many,
|
||||||
traits::{ActivityHandler, Object},
|
traits::{ActivityHandler, Object},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
use crate::backend::database::IbisData;
|
use crate::{
|
||||||
use crate::backend::error::MyResult;
|
backend::{
|
||||||
|
database::IbisData,
|
||||||
use crate::backend::federation::activities::reject::RejectEdit;
|
error::MyResult,
|
||||||
use crate::backend::federation::activities::update_local_article::UpdateLocalArticle;
|
federation::{
|
||||||
use crate::backend::federation::objects::edit::ApubEdit;
|
activities::{reject::RejectEdit, update_local_article::UpdateLocalArticle},
|
||||||
use crate::backend::federation::send_activity;
|
objects::edit::ApubEdit,
|
||||||
use crate::backend::utils::generate_activity_id;
|
send_activity,
|
||||||
use crate::common::validation::can_edit_article;
|
},
|
||||||
use crate::common::DbArticle;
|
utils::generate_activity_id,
|
||||||
use crate::common::DbEdit;
|
},
|
||||||
use crate::common::DbInstance;
|
common::{validation::can_edit_article, DbArticle, DbEdit, DbInstance},
|
||||||
use activitypub_federation::kinds::activity::UpdateType;
|
};
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
config::Data,
|
config::Data,
|
||||||
fetch::object_id::ObjectId,
|
fetch::object_id::ObjectId,
|
||||||
|
kinds::activity::UpdateType,
|
||||||
protocol::helpers::deserialize_one_or_many,
|
protocol::helpers::deserialize_one_or_many,
|
||||||
traits::{ActivityHandler, Object},
|
traits::{ActivityHandler, Object},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use crate::backend::config::IbisConfig;
|
use crate::backend::{config::IbisConfig, database::IbisData};
|
||||||
use crate::backend::database::IbisData;
|
use activitypub_federation::{
|
||||||
use activitypub_federation::activity_queue::queue_activity;
|
activity_queue::queue_activity,
|
||||||
use activitypub_federation::config::{Data, UrlVerifier};
|
config::{Data, UrlVerifier},
|
||||||
use activitypub_federation::error::Error as ActivityPubError;
|
error::Error as ActivityPubError,
|
||||||
use activitypub_federation::protocol::context::WithContext;
|
protocol::context::WithContext,
|
||||||
use activitypub_federation::traits::{ActivityHandler, Actor};
|
traits::{ActivityHandler, Actor},
|
||||||
|
};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
use crate::backend::database::article::DbArticleForm;
|
use crate::{
|
||||||
use crate::backend::database::IbisData;
|
backend::{
|
||||||
use crate::backend::error::Error;
|
database::{article::DbArticleForm, IbisData},
|
||||||
use crate::backend::federation::objects::edits_collection::DbEditCollection;
|
error::Error,
|
||||||
use crate::common::DbArticle;
|
federation::objects::edits_collection::DbEditCollection,
|
||||||
use crate::common::DbInstance;
|
},
|
||||||
use crate::common::EditVersion;
|
common::{DbArticle, DbInstance, EditVersion},
|
||||||
use activitypub_federation::config::Data;
|
};
|
||||||
use activitypub_federation::fetch::collection_id::CollectionId;
|
|
||||||
use activitypub_federation::kinds::object::ArticleType;
|
|
||||||
use activitypub_federation::kinds::public;
|
|
||||||
use activitypub_federation::protocol::verification::verify_domains_match;
|
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
fetch::object_id::ObjectId, protocol::helpers::deserialize_one_or_many, traits::Object,
|
config::Data,
|
||||||
|
fetch::{collection_id::CollectionId, object_id::ObjectId},
|
||||||
|
kinds::{object::ArticleType, public},
|
||||||
|
protocol::{helpers::deserialize_one_or_many, verification::verify_domains_match},
|
||||||
|
traits::Object,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -29,6 +29,7 @@ pub struct ApubArticle {
|
||||||
latest_version: EditVersion,
|
latest_version: EditVersion,
|
||||||
content: String,
|
content: String,
|
||||||
name: String,
|
name: String,
|
||||||
|
protected: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
#[async_trait::async_trait]
|
||||||
|
@ -56,6 +57,7 @@ impl Object for DbArticle {
|
||||||
latest_version: self.latest_edit_version(data)?,
|
latest_version: self.latest_edit_version(data)?,
|
||||||
content: self.text,
|
content: self.text,
|
||||||
name: self.title,
|
name: self.title,
|
||||||
|
protected: self.protected,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,6 +78,7 @@ impl Object for DbArticle {
|
||||||
ap_id: json.id,
|
ap_id: json.id,
|
||||||
local: false,
|
local: false,
|
||||||
instance_id: instance.id,
|
instance_id: instance.id,
|
||||||
|
protected: json.protected,
|
||||||
};
|
};
|
||||||
let article = DbArticle::create_or_update(form, data)?;
|
let article = DbArticle::create_or_update(form, data)?;
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,14 @@
|
||||||
use crate::backend::database::IbisData;
|
use crate::{
|
||||||
use crate::backend::error::Error;
|
backend::{database::IbisData, error::Error, federation::objects::article::ApubArticle},
|
||||||
use crate::backend::federation::objects::article::ApubArticle;
|
common::{DbArticle, DbInstance},
|
||||||
use crate::common::DbInstance;
|
};
|
||||||
|
|
||||||
use crate::common::DbArticle;
|
|
||||||
use activitypub_federation::kinds::collection::CollectionType;
|
|
||||||
use activitypub_federation::protocol::verification::verify_domains_match;
|
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
config::Data,
|
config::Data,
|
||||||
|
kinds::collection::CollectionType,
|
||||||
|
protocol::verification::verify_domains_match,
|
||||||
traits::{Collection, Object},
|
traits::{Collection, Object},
|
||||||
};
|
};
|
||||||
use futures::future;
|
use futures::{future, future::try_join_all};
|
||||||
use futures::future::try_join_all;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
use crate::backend::database::edit::DbEditForm;
|
use crate::{
|
||||||
use crate::backend::database::IbisData;
|
backend::{
|
||||||
use crate::backend::error::Error;
|
database::{edit::DbEditForm, IbisData},
|
||||||
use crate::common::DbPerson;
|
error::Error,
|
||||||
use crate::common::EditVersion;
|
},
|
||||||
use crate::common::{DbArticle, DbEdit};
|
common::{DbArticle, DbEdit, DbPerson, EditVersion},
|
||||||
use activitypub_federation::config::Data;
|
};
|
||||||
use activitypub_federation::fetch::object_id::ObjectId;
|
use activitypub_federation::{
|
||||||
use activitypub_federation::protocol::verification::verify_domains_match;
|
config::Data,
|
||||||
use activitypub_federation::traits::Object;
|
fetch::object_id::ObjectId,
|
||||||
|
protocol::verification::verify_domains_match,
|
||||||
|
traits::Object,
|
||||||
|
};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
use crate::backend::database::IbisData;
|
use crate::{
|
||||||
use crate::backend::error::Error;
|
backend::{database::IbisData, error::Error, federation::objects::edit::ApubEdit},
|
||||||
use crate::backend::federation::objects::edit::ApubEdit;
|
common::{DbArticle, DbEdit, DbInstance},
|
||||||
use crate::common::DbArticle;
|
};
|
||||||
|
|
||||||
use crate::common::DbEdit;
|
|
||||||
use crate::common::DbInstance;
|
|
||||||
use activitypub_federation::kinds::collection::OrderedCollectionType;
|
|
||||||
use activitypub_federation::protocol::verification::verify_domains_match;
|
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
config::Data,
|
config::Data,
|
||||||
|
kinds::collection::OrderedCollectionType,
|
||||||
|
protocol::verification::verify_domains_match,
|
||||||
traits::{Collection, Object},
|
traits::{Collection, Object},
|
||||||
};
|
};
|
||||||
use futures::future;
|
use futures::{future, future::try_join_all};
|
||||||
use futures::future::try_join_all;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,17 @@
|
||||||
use crate::backend::database::instance::DbInstanceForm;
|
use crate::{
|
||||||
use crate::backend::database::IbisData;
|
backend::{
|
||||||
use crate::backend::error::Error;
|
database::{instance::DbInstanceForm, IbisData},
|
||||||
use crate::backend::error::MyResult;
|
error::{Error, MyResult},
|
||||||
use crate::backend::federation::objects::articles_collection::DbArticleCollection;
|
federation::{objects::articles_collection::DbArticleCollection, send_activity},
|
||||||
use crate::backend::federation::send_activity;
|
},
|
||||||
use crate::common::utils::extract_domain;
|
common::{utils::extract_domain, DbInstance},
|
||||||
use crate::common::DbInstance;
|
};
|
||||||
use activitypub_federation::fetch::collection_id::CollectionId;
|
|
||||||
use activitypub_federation::kinds::actor::ServiceType;
|
|
||||||
use activitypub_federation::traits::ActivityHandler;
|
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
config::Data,
|
config::Data,
|
||||||
fetch::object_id::ObjectId,
|
fetch::{collection_id::CollectionId, object_id::ObjectId},
|
||||||
|
kinds::actor::ServiceType,
|
||||||
protocol::{public_key::PublicKey, verification::verify_domains_match},
|
protocol::{public_key::PublicKey, verification::verify_domains_match},
|
||||||
traits::{Actor, Object},
|
traits::{ActivityHandler, Actor, Object},
|
||||||
};
|
};
|
||||||
use chrono::{DateTime, Local, Utc};
|
use chrono::{DateTime, Local, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
use crate::backend::database::user::DbPersonForm;
|
use crate::{
|
||||||
use crate::backend::database::IbisData;
|
backend::{
|
||||||
use crate::backend::error::Error;
|
database::{user::DbPersonForm, IbisData},
|
||||||
use crate::common::DbPerson;
|
error::Error,
|
||||||
use activitypub_federation::kinds::actor::PersonType;
|
},
|
||||||
|
common::DbPerson,
|
||||||
|
};
|
||||||
use activitypub_federation::{
|
use activitypub_federation::{
|
||||||
config::Data,
|
config::Data,
|
||||||
fetch::object_id::ObjectId,
|
fetch::object_id::ObjectId,
|
||||||
|
kinds::actor::PersonType,
|
||||||
protocol::{public_key::PublicKey, verification::verify_domains_match},
|
protocol::{public_key::PublicKey, verification::verify_domains_match},
|
||||||
traits::{Actor, Object},
|
traits::{Actor, Object},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,33 +1,42 @@
|
||||||
use crate::backend::database::IbisData;
|
use crate::{
|
||||||
use crate::backend::error::Error;
|
backend::{
|
||||||
use crate::backend::error::MyResult;
|
database::IbisData,
|
||||||
use crate::backend::federation::activities::accept::Accept;
|
error::{Error, MyResult},
|
||||||
use crate::backend::federation::activities::create_article::CreateArticle;
|
federation::{
|
||||||
use crate::backend::federation::activities::follow::Follow;
|
activities::{
|
||||||
use crate::backend::federation::activities::reject::RejectEdit;
|
accept::Accept,
|
||||||
use crate::backend::federation::activities::update_local_article::UpdateLocalArticle;
|
create_article::CreateArticle,
|
||||||
use crate::backend::federation::activities::update_remote_article::UpdateRemoteArticle;
|
follow::Follow,
|
||||||
use crate::backend::federation::objects::article::ApubArticle;
|
reject::RejectEdit,
|
||||||
use crate::backend::federation::objects::articles_collection::{
|
update_local_article::UpdateLocalArticle,
|
||||||
ArticleCollection, DbArticleCollection,
|
update_remote_article::UpdateRemoteArticle,
|
||||||
|
},
|
||||||
|
objects::{
|
||||||
|
article::ApubArticle,
|
||||||
|
articles_collection::{ArticleCollection, DbArticleCollection},
|
||||||
|
edits_collection::{ApubEditCollection, DbEditCollection},
|
||||||
|
instance::ApubInstance,
|
||||||
|
user::ApubUser,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
common::{DbArticle, DbInstance, DbPerson},
|
||||||
|
};
|
||||||
|
use activitypub_federation::{
|
||||||
|
axum::{
|
||||||
|
inbox::{receive_activity, ActivityData},
|
||||||
|
json::FederationJson,
|
||||||
|
},
|
||||||
|
config::Data,
|
||||||
|
protocol::context::WithContext,
|
||||||
|
traits::{ActivityHandler, Actor, Collection, Object},
|
||||||
|
};
|
||||||
|
use axum::{
|
||||||
|
extract::Path,
|
||||||
|
response::IntoResponse,
|
||||||
|
routing::{get, post},
|
||||||
|
Router,
|
||||||
};
|
};
|
||||||
use crate::backend::federation::objects::edits_collection::{ApubEditCollection, DbEditCollection};
|
|
||||||
use crate::backend::federation::objects::instance::ApubInstance;
|
|
||||||
use crate::backend::federation::objects::user::ApubUser;
|
|
||||||
use crate::common::DbArticle;
|
|
||||||
use crate::common::DbInstance;
|
|
||||||
use crate::common::DbPerson;
|
|
||||||
use activitypub_federation::axum::inbox::{receive_activity, ActivityData};
|
|
||||||
use activitypub_federation::axum::json::FederationJson;
|
|
||||||
use activitypub_federation::config::Data;
|
|
||||||
use activitypub_federation::protocol::context::WithContext;
|
|
||||||
use activitypub_federation::traits::Actor;
|
|
||||||
use activitypub_federation::traits::Object;
|
|
||||||
use activitypub_federation::traits::{ActivityHandler, Collection};
|
|
||||||
use axum::extract::Path;
|
|
||||||
use axum::response::IntoResponse;
|
|
||||||
use axum::routing::{get, post};
|
|
||||||
use axum::Router;
|
|
||||||
use axum_macros::debug_handler;
|
use axum_macros::debug_handler;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
|
@ -1,38 +1,45 @@
|
||||||
use crate::backend::config::IbisConfig;
|
use crate::{
|
||||||
use crate::backend::database::article::DbArticleForm;
|
backend::{
|
||||||
use crate::backend::database::instance::DbInstanceForm;
|
config::IbisConfig,
|
||||||
use crate::backend::database::IbisData;
|
database::{article::DbArticleForm, instance::DbInstanceForm, IbisData},
|
||||||
use crate::backend::error::Error;
|
error::{Error, MyResult},
|
||||||
use crate::backend::error::MyResult;
|
federation::{activities::submit_article_update, routes::federation_routes, VerifyUrlData},
|
||||||
use crate::backend::federation::activities::submit_article_update;
|
utils::generate_activity_id,
|
||||||
use crate::backend::federation::routes::federation_routes;
|
},
|
||||||
use crate::backend::federation::VerifyUrlData;
|
common::{
|
||||||
use crate::backend::utils::generate_activity_id;
|
utils::http_protocol_str,
|
||||||
use crate::common::utils::http_protocol_str;
|
DbArticle,
|
||||||
use crate::common::{DbArticle, DbInstance, DbPerson, EditVersion, MAIN_PAGE_NAME};
|
DbInstance,
|
||||||
use crate::frontend::app::App;
|
DbPerson,
|
||||||
use activitypub_federation::config::{Data, FederationConfig, FederationMiddleware};
|
EditVersion,
|
||||||
use activitypub_federation::fetch::collection_id::CollectionId;
|
MAIN_PAGE_NAME,
|
||||||
use activitypub_federation::fetch::object_id::ObjectId;
|
},
|
||||||
use activitypub_federation::http_signatures::generate_actor_keypair;
|
frontend::app::App,
|
||||||
|
};
|
||||||
|
use activitypub_federation::{
|
||||||
|
config::{Data, FederationConfig, FederationMiddleware},
|
||||||
|
fetch::{collection_id::CollectionId, object_id::ObjectId},
|
||||||
|
http_signatures::generate_actor_keypair,
|
||||||
|
};
|
||||||
use api::api_routes;
|
use api::api_routes;
|
||||||
use axum::debug_handler;
|
use axum::{
|
||||||
use axum::headers::HeaderMap;
|
debug_handler,
|
||||||
use axum::http::{HeaderValue, Request};
|
headers::HeaderMap,
|
||||||
use axum::response::IntoResponse;
|
http::{HeaderValue, Request},
|
||||||
use axum::routing::get;
|
middleware::Next,
|
||||||
use axum::Server;
|
response::{IntoResponse, Response},
|
||||||
use axum::ServiceExt;
|
routing::get,
|
||||||
use axum::{middleware::Next, response::Response, Router};
|
Router,
|
||||||
|
Server,
|
||||||
|
ServiceExt,
|
||||||
|
};
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use diesel::r2d2::ConnectionManager;
|
use diesel::{
|
||||||
use diesel::r2d2::Pool;
|
r2d2::{ConnectionManager, Pool},
|
||||||
use diesel::PgConnection;
|
PgConnection,
|
||||||
use diesel_migrations::embed_migrations;
|
};
|
||||||
use diesel_migrations::EmbeddedMigrations;
|
use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness};
|
||||||
use diesel_migrations::MigrationHarness;
|
use leptos::{leptos_config::get_config_from_str, *};
|
||||||
use leptos::leptos_config::get_config_from_str;
|
|
||||||
use leptos::*;
|
|
||||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||||
use log::info;
|
use log::info;
|
||||||
use tower::Layer;
|
use tower::Layer;
|
||||||
|
@ -179,6 +186,7 @@ async fn setup(data: &Data<IbisData>) -> Result<(), Error> {
|
||||||
))?,
|
))?,
|
||||||
instance_id: instance.id,
|
instance_id: instance.id,
|
||||||
local: true,
|
local: true,
|
||||||
|
protected: true,
|
||||||
};
|
};
|
||||||
let article = DbArticle::create(form, data)?;
|
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
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
use crate::backend::error::MyResult;
|
use crate::{
|
||||||
use crate::common::EditView;
|
backend::error::MyResult,
|
||||||
use crate::common::{utils, EditVersion};
|
common::{utils, utils::extract_domain, EditVersion, EditView},
|
||||||
use activitypub_federation::fetch::object_id::ObjectId;
|
};
|
||||||
use activitypub_federation::traits::Object;
|
use activitypub_federation::{fetch::object_id::ObjectId, traits::Object};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use diffy::{apply, Patch};
|
use diffy::{apply, Patch};
|
||||||
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
|
||||||
use crate::common::utils::extract_domain;
|
|
||||||
use url::{ParseError, Url};
|
use url::{ParseError, Url};
|
||||||
|
|
||||||
pub fn generate_activity_id<T>(for_url: &ObjectId<T>) -> Result<Url, ParseError>
|
pub fn generate_activity_id<T>(for_url: &ObjectId<T>) -> Result<Url, ParseError>
|
||||||
|
|
|
@ -20,14 +20,14 @@ pub const MAIN_PAGE_NAME: &str = "Main_Page";
|
||||||
|
|
||||||
/// Should be an enum Title/Id but fails due to https://github.com/nox/serde_urlencoded/issues/66
|
/// Should be an enum Title/Id but fails due to https://github.com/nox/serde_urlencoded/issues/66
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
pub struct GetArticleData {
|
pub struct GetArticleForm {
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
pub domain: Option<String>,
|
pub domain: Option<String>,
|
||||||
pub id: Option<i32>,
|
pub id: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone)]
|
#[derive(Deserialize, Serialize, Clone)]
|
||||||
pub struct ListArticlesData {
|
pub struct ListArticlesForm {
|
||||||
pub only_local: Option<bool>,
|
pub only_local: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +53,7 @@ pub struct DbArticle {
|
||||||
pub ap_id: String,
|
pub ap_id: String,
|
||||||
pub instance_id: i32,
|
pub instance_id: i32,
|
||||||
pub local: bool,
|
pub local: bool,
|
||||||
|
pub protected: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Represents a single change to the article.
|
/// Represents a single change to the article.
|
||||||
|
@ -115,13 +116,13 @@ impl Default for EditVersion {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone)]
|
#[derive(Deserialize, Serialize, Clone)]
|
||||||
pub struct RegisterUserData {
|
pub struct RegisterUserForm {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub struct LoginUserData {
|
pub struct LoginUserForm {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
|
@ -175,14 +176,14 @@ impl DbPerson {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub struct CreateArticleData {
|
pub struct CreateArticleForm {
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub text: String,
|
pub text: String,
|
||||||
pub summary: String,
|
pub summary: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
pub struct EditArticleData {
|
pub struct EditArticleForm {
|
||||||
/// Id of the article to edit
|
/// Id of the article to edit
|
||||||
pub article_id: i32,
|
pub article_id: i32,
|
||||||
/// Full, new text of the article. A diff against `previous_version` is generated on the backend
|
/// Full, new text of the article. A diff against `previous_version` is generated on the backend
|
||||||
|
@ -197,8 +198,14 @@ pub struct EditArticleData {
|
||||||
pub resolve_conflict_id: Option<i32>,
|
pub resolve_conflict_id: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct ProtectArticleForm {
|
||||||
|
pub article_id: i32,
|
||||||
|
pub protected: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub struct ForkArticleData {
|
pub struct ForkArticleForm {
|
||||||
pub article_id: i32,
|
pub article_id: i32,
|
||||||
pub new_title: String,
|
pub new_title: String,
|
||||||
}
|
}
|
||||||
|
@ -209,7 +216,7 @@ pub struct FollowInstance {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone)]
|
#[derive(Deserialize, Serialize, Clone)]
|
||||||
pub struct SearchArticleData {
|
pub struct SearchArticleForm {
|
||||||
pub query: String,
|
pub query: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,7 +276,7 @@ pub struct InstanceView {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
pub struct GetUserData {
|
pub struct GetUserForm {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub domain: Option<String>,
|
pub domain: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use crate::common::{DbArticle, MAIN_PAGE_NAME};
|
use crate::common::DbArticle;
|
||||||
use anyhow::anyhow;
|
use anyhow::{anyhow, Result};
|
||||||
use anyhow::Result;
|
|
||||||
|
|
||||||
pub fn can_edit_article(article: &DbArticle, is_admin: bool) -> Result<()> {
|
pub fn can_edit_article(article: &DbArticle, is_admin: bool) -> Result<()> {
|
||||||
if article.title == MAIN_PAGE_NAME {
|
let err = anyhow!("Article is protected, only admins on origin instance can edit");
|
||||||
|
if article.protected {
|
||||||
if !article.local {
|
if !article.local {
|
||||||
return Err(anyhow!("Cannot edit main page of remote instance"));
|
return Err(err);
|
||||||
}
|
}
|
||||||
if article.local && !is_admin {
|
if !is_admin {
|
||||||
return Err(anyhow!("Only admin can edit main page"));
|
return Err(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
117
src/database/schema.rs
Normal file
117
src/database/schema.rs
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
// @generated automatically by Diesel CLI.
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
article (id) {
|
||||||
|
id -> Int4,
|
||||||
|
title -> Text,
|
||||||
|
text -> Text,
|
||||||
|
#[max_length = 255]
|
||||||
|
ap_id -> Varchar,
|
||||||
|
instance_id -> Int4,
|
||||||
|
local -> Bool,
|
||||||
|
protected -> Bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
conflict (id) {
|
||||||
|
id -> Int4,
|
||||||
|
hash -> Uuid,
|
||||||
|
diff -> Text,
|
||||||
|
summary -> Text,
|
||||||
|
creator_id -> Int4,
|
||||||
|
article_id -> Int4,
|
||||||
|
previous_version_id -> Uuid,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
edit (id) {
|
||||||
|
id -> Int4,
|
||||||
|
creator_id -> Int4,
|
||||||
|
hash -> Uuid,
|
||||||
|
#[max_length = 255]
|
||||||
|
ap_id -> Varchar,
|
||||||
|
diff -> Text,
|
||||||
|
summary -> Text,
|
||||||
|
article_id -> Int4,
|
||||||
|
previous_version_id -> Uuid,
|
||||||
|
created -> Timestamptz,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
instance (id) {
|
||||||
|
id -> Int4,
|
||||||
|
domain -> Text,
|
||||||
|
#[max_length = 255]
|
||||||
|
ap_id -> Varchar,
|
||||||
|
description -> Nullable<Text>,
|
||||||
|
inbox_url -> Text,
|
||||||
|
#[max_length = 255]
|
||||||
|
articles_url -> Varchar,
|
||||||
|
public_key -> Text,
|
||||||
|
private_key -> Nullable<Text>,
|
||||||
|
last_refreshed_at -> Timestamptz,
|
||||||
|
local -> Bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
instance_follow (id) {
|
||||||
|
id -> Int4,
|
||||||
|
instance_id -> Int4,
|
||||||
|
follower_id -> Int4,
|
||||||
|
pending -> Bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
jwt_secret (id) {
|
||||||
|
id -> Int4,
|
||||||
|
secret -> Varchar,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
local_user (id) {
|
||||||
|
id -> Int4,
|
||||||
|
password_encrypted -> Text,
|
||||||
|
person_id -> Int4,
|
||||||
|
admin -> Bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
person (id) {
|
||||||
|
id -> Int4,
|
||||||
|
username -> Text,
|
||||||
|
#[max_length = 255]
|
||||||
|
ap_id -> Varchar,
|
||||||
|
inbox_url -> Text,
|
||||||
|
public_key -> Text,
|
||||||
|
private_key -> Nullable<Text>,
|
||||||
|
last_refreshed_at -> Timestamptz,
|
||||||
|
local -> Bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::joinable!(article -> instance (instance_id));
|
||||||
|
diesel::joinable!(conflict -> article (article_id));
|
||||||
|
diesel::joinable!(conflict -> local_user (creator_id));
|
||||||
|
diesel::joinable!(edit -> article (article_id));
|
||||||
|
diesel::joinable!(edit -> person (creator_id));
|
||||||
|
diesel::joinable!(instance_follow -> instance (instance_id));
|
||||||
|
diesel::joinable!(instance_follow -> person (follower_id));
|
||||||
|
diesel::joinable!(local_user -> person (person_id));
|
||||||
|
|
||||||
|
diesel::allow_tables_to_appear_in_same_query!(
|
||||||
|
article,
|
||||||
|
conflict,
|
||||||
|
edit,
|
||||||
|
instance,
|
||||||
|
instance_follow,
|
||||||
|
jwt_secret,
|
||||||
|
local_user,
|
||||||
|
person,
|
||||||
|
);
|
|
@ -1,11 +1,28 @@
|
||||||
use crate::common::utils::http_protocol_str;
|
use crate::{
|
||||||
use crate::common::{ApiConflict, ListArticlesData};
|
common::{
|
||||||
use crate::common::{ArticleView, LoginUserData, RegisterUserData};
|
utils::http_protocol_str,
|
||||||
use crate::common::{CreateArticleData, EditArticleData, ForkArticleData, LocalUserView};
|
ApiConflict,
|
||||||
use crate::common::{DbArticle, GetArticleData};
|
ArticleView,
|
||||||
use crate::common::{DbInstance, FollowInstance, InstanceView, SearchArticleData};
|
CreateArticleForm,
|
||||||
use crate::common::{DbPerson, GetUserData, ResolveObject};
|
DbArticle,
|
||||||
use crate::frontend::error::MyResult;
|
DbInstance,
|
||||||
|
DbPerson,
|
||||||
|
EditArticleForm,
|
||||||
|
FollowInstance,
|
||||||
|
ForkArticleForm,
|
||||||
|
GetArticleForm,
|
||||||
|
GetUserForm,
|
||||||
|
InstanceView,
|
||||||
|
ListArticlesForm,
|
||||||
|
LocalUserView,
|
||||||
|
LoginUserForm,
|
||||||
|
ProtectArticleForm,
|
||||||
|
RegisterUserForm,
|
||||||
|
ResolveObject,
|
||||||
|
SearchArticleForm,
|
||||||
|
},
|
||||||
|
frontend::error::MyResult,
|
||||||
|
};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use reqwest::{Client, RequestBuilder, StatusCode};
|
use reqwest::{Client, RequestBuilder, StatusCode};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -34,15 +51,15 @@ impl ApiClient {
|
||||||
handle_json_res::<T>(req).await
|
handle_json_res::<T>(req).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_article(&self, data: GetArticleData) -> MyResult<ArticleView> {
|
pub async fn get_article(&self, data: GetArticleForm) -> MyResult<ArticleView> {
|
||||||
self.get_query("/api/v1/article", Some(data)).await
|
self.get_query("/api/v1/article", Some(data)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list_articles(&self, data: ListArticlesData) -> MyResult<Vec<DbArticle>> {
|
pub async fn list_articles(&self, data: ListArticlesForm) -> MyResult<Vec<DbArticle>> {
|
||||||
self.get_query("/api/v1/article/list", Some(data)).await
|
self.get_query("/api/v1/article/list", Some(data)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn register(&self, register_form: RegisterUserData) -> MyResult<LocalUserView> {
|
pub async fn register(&self, register_form: RegisterUserForm) -> MyResult<LocalUserView> {
|
||||||
let req = self
|
let req = self
|
||||||
.client
|
.client
|
||||||
.post(self.request_endpoint("/api/v1/account/register"))
|
.post(self.request_endpoint("/api/v1/account/register"))
|
||||||
|
@ -50,7 +67,7 @@ impl ApiClient {
|
||||||
handle_json_res::<LocalUserView>(req).await
|
handle_json_res::<LocalUserView>(req).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn login(&self, login_form: LoginUserData) -> MyResult<LocalUserView> {
|
pub async fn login(&self, login_form: LoginUserForm) -> MyResult<LocalUserView> {
|
||||||
let req = self
|
let req = self
|
||||||
.client
|
.client
|
||||||
.post(self.request_endpoint("/api/v1/account/login"))
|
.post(self.request_endpoint("/api/v1/account/login"))
|
||||||
|
@ -58,7 +75,7 @@ impl ApiClient {
|
||||||
handle_json_res::<LocalUserView>(req).await
|
handle_json_res::<LocalUserView>(req).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn create_article(&self, data: &CreateArticleData) -> MyResult<ArticleView> {
|
pub async fn create_article(&self, data: &CreateArticleForm) -> MyResult<ArticleView> {
|
||||||
let req = self
|
let req = self
|
||||||
.client
|
.client
|
||||||
.post(self.request_endpoint("/api/v1/article"))
|
.post(self.request_endpoint("/api/v1/article"))
|
||||||
|
@ -68,7 +85,7 @@ impl ApiClient {
|
||||||
|
|
||||||
pub async fn edit_article_with_conflict(
|
pub async fn edit_article_with_conflict(
|
||||||
&self,
|
&self,
|
||||||
edit_form: &EditArticleData,
|
edit_form: &EditArticleForm,
|
||||||
) -> MyResult<Option<ApiConflict>> {
|
) -> MyResult<Option<ApiConflict>> {
|
||||||
let req = self
|
let req = self
|
||||||
.client
|
.client
|
||||||
|
@ -77,11 +94,11 @@ impl ApiClient {
|
||||||
handle_json_res(req).await
|
handle_json_res(req).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn edit_article(&self, edit_form: &EditArticleData) -> MyResult<ArticleView> {
|
pub async fn edit_article(&self, edit_form: &EditArticleForm) -> MyResult<ArticleView> {
|
||||||
let edit_res = self.edit_article_with_conflict(edit_form).await?;
|
let edit_res = self.edit_article_with_conflict(edit_form).await?;
|
||||||
assert!(edit_res.is_none());
|
assert!(edit_res.is_none());
|
||||||
|
|
||||||
self.get_article(GetArticleData {
|
self.get_article(GetArticleForm {
|
||||||
title: None,
|
title: None,
|
||||||
domain: None,
|
domain: None,
|
||||||
id: Some(edit_form.article_id),
|
id: Some(edit_form.article_id),
|
||||||
|
@ -89,7 +106,7 @@ impl ApiClient {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn search(&self, search_form: &SearchArticleData) -> MyResult<Vec<DbArticle>> {
|
pub async fn search(&self, search_form: &SearchArticleForm) -> MyResult<Vec<DbArticle>> {
|
||||||
self.get_query("/api/v1/search", Some(search_form)).await
|
self.get_query("/api/v1/search", Some(search_form)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -147,7 +164,7 @@ impl ApiClient {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn fork_article(&self, form: &ForkArticleData) -> MyResult<ArticleView> {
|
pub async fn fork_article(&self, form: &ForkArticleForm) -> MyResult<ArticleView> {
|
||||||
let req = self
|
let req = self
|
||||||
.client
|
.client
|
||||||
.post(self.request_endpoint("/api/v1/article/fork"))
|
.post(self.request_endpoint("/api/v1/article/fork"))
|
||||||
|
@ -155,6 +172,14 @@ impl ApiClient {
|
||||||
Ok(handle_json_res(req).await.unwrap())
|
Ok(handle_json_res(req).await.unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn protect_article(&self, params: &ProtectArticleForm) -> MyResult<DbArticle> {
|
||||||
|
let req = self
|
||||||
|
.client
|
||||||
|
.post(self.request_endpoint("/api/v1/article/protect"))
|
||||||
|
.form(params);
|
||||||
|
handle_json_res(req).await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_conflicts(&self) -> MyResult<Vec<ApiConflict>> {
|
pub async fn get_conflicts(&self) -> MyResult<Vec<ApiConflict>> {
|
||||||
let req = self
|
let req = self
|
||||||
.client
|
.client
|
||||||
|
@ -173,7 +198,7 @@ impl ApiClient {
|
||||||
self.get_query("/api/v1/instance/resolve", Some(resolve_object))
|
self.get_query("/api/v1/instance/resolve", Some(resolve_object))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
pub async fn get_user(&self, data: GetUserData) -> MyResult<DbPerson> {
|
pub async fn get_user(&self, data: GetUserForm) -> MyResult<DbPerson> {
|
||||||
self.get_query("/api/v1/user", Some(data)).await
|
self.get_query("/api/v1/user", Some(data)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,29 +1,44 @@
|
||||||
use crate::common::LocalUserView;
|
use crate::{
|
||||||
use crate::frontend::api::ApiClient;
|
common::LocalUserView,
|
||||||
use crate::frontend::backend_hostname;
|
frontend::{
|
||||||
use crate::frontend::components::nav::Nav;
|
api::ApiClient,
|
||||||
use crate::frontend::pages::article::actions::ArticleActions;
|
backend_hostname,
|
||||||
use crate::frontend::pages::article::create::CreateArticle;
|
components::nav::Nav,
|
||||||
use crate::frontend::pages::article::edit::EditArticle;
|
pages::{
|
||||||
use crate::frontend::pages::article::history::ArticleHistory;
|
article::{
|
||||||
use crate::frontend::pages::article::list::ListArticles;
|
actions::ArticleActions,
|
||||||
use crate::frontend::pages::article::read::ReadArticle;
|
create::CreateArticle,
|
||||||
use crate::frontend::pages::conflicts::Conflicts;
|
edit::EditArticle,
|
||||||
use crate::frontend::pages::diff::EditDiff;
|
history::ArticleHistory,
|
||||||
use crate::frontend::pages::instance_details::InstanceDetails;
|
list::ListArticles,
|
||||||
use crate::frontend::pages::login::Login;
|
read::ReadArticle,
|
||||||
use crate::frontend::pages::register::Register;
|
},
|
||||||
use crate::frontend::pages::search::Search;
|
conflicts::Conflicts,
|
||||||
use crate::frontend::pages::user_profile::UserProfile;
|
diff::EditDiff,
|
||||||
use leptos::{
|
instance_details::InstanceDetails,
|
||||||
component, create_local_resource, create_rw_signal, expect_context, provide_context,
|
login::Login,
|
||||||
use_context, view, IntoView, RwSignal, SignalGet, SignalGetUntracked, SignalUpdate,
|
register::Register,
|
||||||
|
search::Search,
|
||||||
|
user_profile::UserProfile,
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
use leptos_meta::provide_meta_context;
|
use leptos::{
|
||||||
use leptos_meta::*;
|
component,
|
||||||
use leptos_router::Route;
|
create_local_resource,
|
||||||
use leptos_router::Router;
|
create_rw_signal,
|
||||||
use leptos_router::Routes;
|
expect_context,
|
||||||
|
provide_context,
|
||||||
|
use_context,
|
||||||
|
view,
|
||||||
|
IntoView,
|
||||||
|
RwSignal,
|
||||||
|
SignalGet,
|
||||||
|
SignalGetUntracked,
|
||||||
|
SignalUpdate,
|
||||||
|
};
|
||||||
|
use leptos_meta::{provide_meta_context, *};
|
||||||
|
use leptos_router::{Route, Router, Routes};
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
|
|
||||||
// https://book.leptos.dev/15_global_state.html
|
// https://book.leptos.dev/15_global_state.html
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::common::validation::can_edit_article;
|
use crate::{
|
||||||
use crate::common::ArticleView;
|
common::{validation::can_edit_article, ArticleView},
|
||||||
use crate::frontend::app::GlobalState;
|
frontend::{app::GlobalState, article_link},
|
||||||
use crate::frontend::article_link;
|
};
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_router::*;
|
use leptos_router::*;
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ pub fn ArticleNav(article: Resource<Option<String>, ArticleView>) -> impl IntoVi
|
||||||
{move || article.get().map(|article| {
|
{move || article.get().map(|article| {
|
||||||
let article_link = article_link(&article.article);
|
let article_link = article_link(&article.article);
|
||||||
let article_link_ = article_link.clone();
|
let article_link_ = article_link.clone();
|
||||||
|
let protected = article.article.protected;
|
||||||
view!{
|
view!{
|
||||||
<nav class="inner">
|
<nav class="inner">
|
||||||
<A href=article_link.clone()>"Read"</A>
|
<A href=article_link.clone()>"Read"</A>
|
||||||
|
@ -26,6 +27,9 @@ pub fn ArticleNav(article: Resource<Option<String>, ArticleView>) -> impl IntoVi
|
||||||
<Show when=move || global_state.with(|state| state.my_profile.is_some())>
|
<Show when=move || global_state.with(|state| state.my_profile.is_some())>
|
||||||
<A href={format!("{article_link_}/actions")}>"Actions"</A>
|
<A href={format!("{article_link_}/actions")}>"Actions"</A>
|
||||||
</Show>
|
</Show>
|
||||||
|
<Show when=move || protected>
|
||||||
|
<span title="Article can only be edited by local admins">"Protected"</span>
|
||||||
|
</Show>
|
||||||
</nav>
|
</nav>
|
||||||
}})}
|
}})}
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
use crate::frontend::app::GlobalState;
|
use crate::frontend::app::GlobalState;
|
||||||
use leptos::*;
|
use leptos::{component, use_context, view, IntoView, RwSignal, SignalWith, *};
|
||||||
use leptos::{component, use_context, view, IntoView, RwSignal, SignalWith};
|
|
||||||
use leptos_router::*;
|
use leptos_router::*;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
use crate::frontend::backend_hostname;
|
use crate::frontend::backend_hostname;
|
||||||
use markdown_it::parser::inline::{InlineRule, InlineState};
|
use markdown_it::{
|
||||||
use markdown_it::{MarkdownIt, Node, NodeValue, Renderer};
|
parser::inline::{InlineRule, InlineState},
|
||||||
|
MarkdownIt,
|
||||||
|
Node,
|
||||||
|
NodeValue,
|
||||||
|
Renderer,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn markdown_parser() -> MarkdownIt {
|
pub fn markdown_parser() -> MarkdownIt {
|
||||||
let mut parser = MarkdownIt::new();
|
let mut parser = MarkdownIt::new();
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::common::utils::extract_domain;
|
use crate::common::{utils::extract_domain, DbArticle, DbPerson};
|
||||||
use crate::common::{DbArticle, DbPerson};
|
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
|
|
||||||
pub mod api;
|
pub mod api;
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
use crate::common::ForkArticleData;
|
use crate::{
|
||||||
use crate::frontend::app::GlobalState;
|
common::ForkArticleForm,
|
||||||
use crate::frontend::article_link;
|
frontend::{
|
||||||
use crate::frontend::article_title;
|
app::GlobalState,
|
||||||
use crate::frontend::components::article_nav::ArticleNav;
|
article_link,
|
||||||
use crate::frontend::pages::article_resource;
|
article_title,
|
||||||
use crate::frontend::DbArticle;
|
components::article_nav::ArticleNav,
|
||||||
|
pages::article_resource,
|
||||||
|
DbArticle,
|
||||||
|
},
|
||||||
|
};
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_router::Redirect;
|
use leptos_router::Redirect;
|
||||||
|
|
||||||
|
@ -15,7 +19,7 @@ pub fn ArticleActions() -> impl IntoView {
|
||||||
let (fork_response, set_fork_response) = create_signal(Option::<DbArticle>::None);
|
let (fork_response, set_fork_response) = create_signal(Option::<DbArticle>::None);
|
||||||
let (error, set_error) = create_signal(None::<String>);
|
let (error, set_error) = create_signal(None::<String>);
|
||||||
let fork_action = create_action(move |(article_id, new_title): &(i32, String)| {
|
let fork_action = create_action(move |(article_id, new_title): &(i32, String)| {
|
||||||
let params = ForkArticleData {
|
let params = ForkArticleForm {
|
||||||
article_id: *article_id,
|
article_id: *article_id,
|
||||||
new_title: new_title.to_string(),
|
new_title: new_title.to_string(),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use crate::common::CreateArticleData;
|
use crate::{common::CreateArticleForm, frontend::app::GlobalState};
|
||||||
use crate::frontend::app::GlobalState;
|
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_router::Redirect;
|
use leptos_router::Redirect;
|
||||||
|
|
||||||
|
@ -18,7 +17,7 @@ pub fn CreateArticle() -> impl IntoView {
|
||||||
let text = text.clone();
|
let text = text.clone();
|
||||||
let summary = summary.clone();
|
let summary = summary.clone();
|
||||||
async move {
|
async move {
|
||||||
let form = CreateArticleData {
|
let form = CreateArticleForm {
|
||||||
title,
|
title,
|
||||||
text,
|
text,
|
||||||
summary,
|
summary,
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
use crate::common::{ApiConflict, ArticleView, EditArticleData};
|
use crate::{
|
||||||
use crate::frontend::app::GlobalState;
|
common::{ApiConflict, ArticleView, EditArticleForm},
|
||||||
use crate::frontend::article_title;
|
frontend::{
|
||||||
use crate::frontend::components::article_nav::ArticleNav;
|
app::GlobalState,
|
||||||
use crate::frontend::pages::article_resource;
|
article_title,
|
||||||
|
components::article_nav::ArticleNav,
|
||||||
|
pages::article_resource,
|
||||||
|
},
|
||||||
|
};
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_router::use_params_map;
|
use leptos_router::use_params_map;
|
||||||
|
|
||||||
|
@ -65,7 +69,7 @@ pub fn EditArticle() -> impl IntoView {
|
||||||
};
|
};
|
||||||
async move {
|
async move {
|
||||||
set_edit_error.update(|e| *e = None);
|
set_edit_error.update(|e| *e = None);
|
||||||
let form = EditArticleData {
|
let form = EditArticleForm {
|
||||||
article_id: article.article.id,
|
article_id: article.article.id,
|
||||||
new_text,
|
new_text,
|
||||||
summary,
|
summary,
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
use crate::frontend::components::article_nav::ArticleNav;
|
use crate::frontend::{
|
||||||
use crate::frontend::pages::article_resource;
|
article_title,
|
||||||
use crate::frontend::{article_title, user_link};
|
components::article_nav::ArticleNav,
|
||||||
|
pages::article_resource,
|
||||||
|
user_link,
|
||||||
|
};
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::common::ListArticlesData;
|
use crate::{
|
||||||
use crate::frontend::app::GlobalState;
|
common::ListArticlesForm,
|
||||||
use crate::frontend::{article_link, article_title};
|
frontend::{app::GlobalState, article_link, article_title},
|
||||||
|
};
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use web_sys::wasm_bindgen::JsCast;
|
use web_sys::wasm_bindgen::JsCast;
|
||||||
|
|
||||||
|
@ -11,7 +12,7 @@ pub fn ListArticles() -> impl IntoView {
|
||||||
move || only_local.get(),
|
move || only_local.get(),
|
||||||
|only_local| async move {
|
|only_local| async move {
|
||||||
GlobalState::api_client()
|
GlobalState::api_client()
|
||||||
.list_articles(ListArticlesData {
|
.list_articles(ListArticlesForm {
|
||||||
only_local: Some(only_local),
|
only_local: Some(only_local),
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
use crate::frontend::article_title;
|
use crate::frontend::{
|
||||||
use crate::frontend::components::article_nav::ArticleNav;
|
article_title,
|
||||||
use crate::frontend::markdown::markdown_parser;
|
components::article_nav::ArticleNav,
|
||||||
use crate::frontend::pages::article_resource;
|
markdown::markdown_parser,
|
||||||
|
pages::article_resource,
|
||||||
|
};
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
use crate::frontend::app::GlobalState;
|
use crate::frontend::{app::GlobalState, article_link, article_title};
|
||||||
use crate::frontend::article_link;
|
|
||||||
use crate::frontend::article_title;
|
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
use crate::frontend::components::article_nav::ArticleNav;
|
use crate::frontend::{components::article_nav::ArticleNav, pages::article_resource, user_link};
|
||||||
use crate::frontend::pages::article_resource;
|
|
||||||
use crate::frontend::user_link;
|
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_router::*;
|
use leptos_router::*;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::common::utils::http_protocol_str;
|
use crate::{
|
||||||
use crate::common::{DbInstance, FollowInstance};
|
common::{utils::http_protocol_str, DbInstance, FollowInstance},
|
||||||
use crate::frontend::app::GlobalState;
|
frontend::app::GlobalState,
|
||||||
|
};
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_router::use_params_map;
|
use leptos_router::use_params_map;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::common::LoginUserData;
|
use crate::{
|
||||||
use crate::frontend::app::GlobalState;
|
common::LoginUserForm,
|
||||||
use crate::frontend::components::credentials::*;
|
frontend::{app::GlobalState, components::credentials::*},
|
||||||
|
};
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_router::Redirect;
|
use leptos_router::Redirect;
|
||||||
|
|
||||||
|
@ -13,7 +14,7 @@ pub fn Login() -> impl IntoView {
|
||||||
let login_action = create_action(move |(email, password): &(String, String)| {
|
let login_action = create_action(move |(email, password): &(String, String)| {
|
||||||
let username = email.to_string();
|
let username = email.to_string();
|
||||||
let password = password.to_string();
|
let password = password.to_string();
|
||||||
let credentials = LoginUserData { username, password };
|
let credentials = LoginUserForm { username, password };
|
||||||
async move {
|
async move {
|
||||||
set_wait_for_response.update(|w| *w = true);
|
set_wait_for_response.update(|w| *w = true);
|
||||||
let result = GlobalState::api_client().login(credentials).await;
|
let result = GlobalState::api_client().login(credentials).await;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
use crate::common::{ArticleView, GetArticleData, MAIN_PAGE_NAME};
|
use crate::{
|
||||||
use crate::frontend::app::GlobalState;
|
common::{ArticleView, GetArticleForm, MAIN_PAGE_NAME},
|
||||||
|
frontend::app::GlobalState,
|
||||||
|
};
|
||||||
use leptos::{create_resource, Resource, SignalGet};
|
use leptos::{create_resource, Resource, SignalGet};
|
||||||
use leptos_router::use_params_map;
|
use leptos_router::use_params_map;
|
||||||
|
|
||||||
|
@ -23,7 +25,7 @@ fn article_resource() -> Resource<Option<String>, ArticleView> {
|
||||||
domain = Some(domain_.to_string());
|
domain = Some(domain_.to_string());
|
||||||
}
|
}
|
||||||
GlobalState::api_client()
|
GlobalState::api_client()
|
||||||
.get_article(GetArticleData {
|
.get_article(GetArticleForm {
|
||||||
title: Some(title),
|
title: Some(title),
|
||||||
domain,
|
domain,
|
||||||
id: None,
|
id: None,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::common::{LocalUserView, RegisterUserData};
|
use crate::{
|
||||||
use crate::frontend::app::GlobalState;
|
common::{LocalUserView, RegisterUserForm},
|
||||||
use crate::frontend::components::credentials::*;
|
frontend::{app::GlobalState, components::credentials::*, error::MyResult},
|
||||||
use crate::frontend::error::MyResult;
|
};
|
||||||
use leptos::{logging::log, *};
|
use leptos::{logging::log, *};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
|
@ -13,7 +13,7 @@ pub fn Register() -> impl IntoView {
|
||||||
let register_action = create_action(move |(email, password): &(String, String)| {
|
let register_action = create_action(move |(email, password): &(String, String)| {
|
||||||
let username = email.to_string();
|
let username = email.to_string();
|
||||||
let password = password.to_string();
|
let password = password.to_string();
|
||||||
let credentials = RegisterUserData { username, password };
|
let credentials = RegisterUserForm { username, password };
|
||||||
log!("Try to register new account for {}", credentials.username);
|
log!("Try to register new account for {}", credentials.username);
|
||||||
async move {
|
async move {
|
||||||
set_wait_for_response.update(|w| *w = true);
|
set_wait_for_response.update(|w| *w = true);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::common::{DbArticle, DbInstance, SearchArticleData};
|
use crate::{
|
||||||
use crate::frontend::app::GlobalState;
|
common::{DbArticle, DbInstance, SearchArticleForm},
|
||||||
use crate::frontend::{article_link, article_title};
|
frontend::{app::GlobalState, article_link, article_title},
|
||||||
|
};
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_router::use_query_map;
|
use leptos_router::use_query_map;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -28,7 +29,7 @@ pub fn Search() -> impl IntoView {
|
||||||
let mut search_results = SearchResults::default();
|
let mut search_results = SearchResults::default();
|
||||||
let api_client = GlobalState::api_client();
|
let api_client = GlobalState::api_client();
|
||||||
let url = Url::parse(&query);
|
let url = Url::parse(&query);
|
||||||
let search_data = SearchArticleData { query };
|
let search_data = SearchArticleForm { query };
|
||||||
let search = api_client.search(&search_data);
|
let search = api_client.search(&search_data);
|
||||||
|
|
||||||
match search.await {
|
match search.await {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::common::{DbPerson, GetUserData};
|
use crate::{
|
||||||
use crate::frontend::app::GlobalState;
|
common::{DbPerson, GetUserForm},
|
||||||
use crate::frontend::user_title;
|
frontend::{app::GlobalState, user_title},
|
||||||
|
};
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use leptos_router::use_params_map;
|
use leptos_router::use_params_map;
|
||||||
|
|
||||||
|
@ -16,7 +17,7 @@ pub fn UserProfile() -> impl IntoView {
|
||||||
name = title_.to_string();
|
name = title_.to_string();
|
||||||
domain = Some(domain_.to_string());
|
domain = Some(domain_.to_string());
|
||||||
}
|
}
|
||||||
let params = GetUserData { name, domain };
|
let params = GetUserForm { name, domain };
|
||||||
GlobalState::api_client().get_user(params).await.unwrap()
|
GlobalState::api_client().get_user(params).await.unwrap()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -23,8 +23,7 @@ pub async fn main() -> ibis_lib::backend::error::MyResult<()> {
|
||||||
#[cfg(not(feature = "ssr"))]
|
#[cfg(not(feature = "ssr"))]
|
||||||
fn main() {
|
fn main() {
|
||||||
use ibis_lib::frontend::app::App;
|
use ibis_lib::frontend::app::App;
|
||||||
use leptos::mount_to_body;
|
use leptos::{mount_to_body, view};
|
||||||
use leptos::view;
|
|
||||||
|
|
||||||
_ = console_log::init_with_level(log::Level::Debug);
|
_ = console_log::init_with_level(log::Level::Debug);
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
|
|
|
@ -1,19 +1,26 @@
|
||||||
#![allow(clippy::unwrap_used)]
|
#![allow(clippy::unwrap_used)]
|
||||||
|
|
||||||
use ibis_lib::backend::config::{IbisConfig, IbisConfigDatabase, IbisConfigFederation};
|
use ibis_lib::{
|
||||||
use ibis_lib::backend::start;
|
backend::{
|
||||||
use ibis_lib::common::RegisterUserData;
|
config::{IbisConfig, IbisConfigDatabase, IbisConfigFederation},
|
||||||
use ibis_lib::frontend::api::ApiClient;
|
start,
|
||||||
use ibis_lib::frontend::error::MyResult;
|
},
|
||||||
|
common::RegisterUserForm,
|
||||||
|
frontend::{api::ApiClient, error::MyResult},
|
||||||
|
};
|
||||||
use reqwest::ClientBuilder;
|
use reqwest::ClientBuilder;
|
||||||
use std::env::current_dir;
|
use std::{
|
||||||
use std::fs::{create_dir_all, remove_dir_all};
|
env::current_dir,
|
||||||
use std::ops::Deref;
|
fs::{create_dir_all, remove_dir_all},
|
||||||
use std::process::{Command, Stdio};
|
ops::Deref,
|
||||||
use std::sync::atomic::{AtomicI32, Ordering};
|
process::{Command, Stdio},
|
||||||
use std::sync::Once;
|
sync::{
|
||||||
use std::thread::{sleep, spawn};
|
atomic::{AtomicI32, Ordering},
|
||||||
use std::time::Duration;
|
Once,
|
||||||
|
},
|
||||||
|
thread::{sleep, spawn},
|
||||||
|
time::Duration,
|
||||||
|
};
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
use tracing::log::LevelFilter;
|
use tracing::log::LevelFilter;
|
||||||
|
|
||||||
|
@ -128,7 +135,7 @@ impl IbisInstance {
|
||||||
});
|
});
|
||||||
// wait a moment for the backend to start
|
// wait a moment for the backend to start
|
||||||
tokio::time::sleep(Duration::from_millis(5000)).await;
|
tokio::time::sleep(Duration::from_millis(5000)).await;
|
||||||
let form = RegisterUserData {
|
let form = RegisterUserForm {
|
||||||
username: username.to_string(),
|
username: username.to_string(),
|
||||||
password: "hunter2".to_string(),
|
password: "hunter2".to_string(),
|
||||||
};
|
};
|
||||||
|
|
141
tests/test.rs
141
tests/test.rs
|
@ -5,13 +5,23 @@ extern crate ibis_lib;
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
use crate::common::{TestData, TEST_ARTICLE_DEFAULT_TEXT};
|
use crate::common::{TestData, TEST_ARTICLE_DEFAULT_TEXT};
|
||||||
use ibis_lib::common::utils::extract_domain;
|
use ibis_lib::{
|
||||||
use ibis_lib::common::{
|
common::{
|
||||||
ArticleView, EditArticleData, ForkArticleData, GetArticleData, GetUserData, ListArticlesData,
|
utils::extract_domain,
|
||||||
|
ArticleView,
|
||||||
|
CreateArticleForm,
|
||||||
|
EditArticleForm,
|
||||||
|
ForkArticleForm,
|
||||||
|
GetArticleForm,
|
||||||
|
GetUserForm,
|
||||||
|
ListArticlesForm,
|
||||||
|
LoginUserForm,
|
||||||
|
ProtectArticleForm,
|
||||||
|
RegisterUserForm,
|
||||||
|
SearchArticleForm,
|
||||||
|
},
|
||||||
|
frontend::error::MyResult,
|
||||||
};
|
};
|
||||||
use ibis_lib::common::{CreateArticleData, SearchArticleData};
|
|
||||||
use ibis_lib::common::{LoginUserData, RegisterUserData};
|
|
||||||
use ibis_lib::frontend::error::MyResult;
|
|
||||||
use pretty_assertions::{assert_eq, assert_ne};
|
use pretty_assertions::{assert_eq, assert_ne};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
@ -20,7 +30,7 @@ async fn test_create_read_and_edit_local_article() -> MyResult<()> {
|
||||||
let data = TestData::start().await;
|
let data = TestData::start().await;
|
||||||
|
|
||||||
// create article
|
// create article
|
||||||
let create_form = CreateArticleData {
|
let create_form = CreateArticleForm {
|
||||||
title: "Manu_Chao".to_string(),
|
title: "Manu_Chao".to_string(),
|
||||||
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
||||||
summary: "create article".to_string(),
|
summary: "create article".to_string(),
|
||||||
|
@ -30,7 +40,7 @@ async fn test_create_read_and_edit_local_article() -> MyResult<()> {
|
||||||
assert!(create_res.article.local);
|
assert!(create_res.article.local);
|
||||||
|
|
||||||
// now article can be read
|
// now article can be read
|
||||||
let get_article_data = GetArticleData {
|
let get_article_data = GetArticleForm {
|
||||||
title: Some(create_res.article.title.clone()),
|
title: Some(create_res.article.title.clone()),
|
||||||
domain: None,
|
domain: None,
|
||||||
id: None,
|
id: None,
|
||||||
|
@ -45,7 +55,7 @@ async fn test_create_read_and_edit_local_article() -> MyResult<()> {
|
||||||
assert!(not_found.is_err());
|
assert!(not_found.is_err());
|
||||||
|
|
||||||
// edit article
|
// edit article
|
||||||
let edit_form = EditArticleData {
|
let edit_form = EditArticleForm {
|
||||||
article_id: create_res.article.id,
|
article_id: create_res.article.id,
|
||||||
new_text: "Lorem Ipsum 2\n".to_string(),
|
new_text: "Lorem Ipsum 2\n".to_string(),
|
||||||
summary: "summary".to_string(),
|
summary: "summary".to_string(),
|
||||||
|
@ -57,7 +67,7 @@ async fn test_create_read_and_edit_local_article() -> MyResult<()> {
|
||||||
assert_eq!(2, edit_res.edits.len());
|
assert_eq!(2, edit_res.edits.len());
|
||||||
assert_eq!(edit_form.summary, edit_res.edits[1].edit.summary);
|
assert_eq!(edit_form.summary, edit_res.edits[1].edit.summary);
|
||||||
|
|
||||||
let search_form = SearchArticleData {
|
let search_form = SearchArticleForm {
|
||||||
query: create_form.title.clone(),
|
query: create_form.title.clone(),
|
||||||
};
|
};
|
||||||
let search_res = data.alpha.search(&search_form).await?;
|
let search_res = data.alpha.search(&search_form).await?;
|
||||||
|
@ -66,7 +76,7 @@ async fn test_create_read_and_edit_local_article() -> MyResult<()> {
|
||||||
|
|
||||||
let list_articles = data
|
let list_articles = data
|
||||||
.alpha
|
.alpha
|
||||||
.list_articles(ListArticlesData {
|
.list_articles(ListArticlesForm {
|
||||||
only_local: Some(false),
|
only_local: Some(false),
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -81,7 +91,7 @@ async fn test_create_duplicate_article() -> MyResult<()> {
|
||||||
let data = TestData::start().await;
|
let data = TestData::start().await;
|
||||||
|
|
||||||
// create article
|
// create article
|
||||||
let create_form = CreateArticleData {
|
let create_form = CreateArticleForm {
|
||||||
title: "Manu_Chao".to_string(),
|
title: "Manu_Chao".to_string(),
|
||||||
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
||||||
summary: "create article".to_string(),
|
summary: "create article".to_string(),
|
||||||
|
@ -127,7 +137,7 @@ async fn test_synchronize_articles() -> MyResult<()> {
|
||||||
let data = TestData::start().await;
|
let data = TestData::start().await;
|
||||||
|
|
||||||
// create article on alpha
|
// create article on alpha
|
||||||
let create_form = CreateArticleData {
|
let create_form = CreateArticleForm {
|
||||||
title: "Manu_Chao".to_string(),
|
title: "Manu_Chao".to_string(),
|
||||||
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
||||||
summary: "create article".to_string(),
|
summary: "create article".to_string(),
|
||||||
|
@ -138,7 +148,7 @@ async fn test_synchronize_articles() -> MyResult<()> {
|
||||||
assert!(create_res.article.local);
|
assert!(create_res.article.local);
|
||||||
|
|
||||||
// edit the article
|
// edit the article
|
||||||
let edit_form = EditArticleData {
|
let edit_form = EditArticleForm {
|
||||||
article_id: create_res.article.id,
|
article_id: create_res.article.id,
|
||||||
new_text: "Lorem Ipsum 2\n".to_string(),
|
new_text: "Lorem Ipsum 2\n".to_string(),
|
||||||
summary: "summary".to_string(),
|
summary: "summary".to_string(),
|
||||||
|
@ -153,7 +163,7 @@ async fn test_synchronize_articles() -> MyResult<()> {
|
||||||
.resolve_instance(Url::parse(&format!("http://{}", &data.alpha.hostname))?)
|
.resolve_instance(Url::parse(&format!("http://{}", &data.alpha.hostname))?)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut get_article_data = GetArticleData {
|
let mut get_article_data = GetArticleForm {
|
||||||
title: Some(create_res.article.title),
|
title: Some(create_res.article.title),
|
||||||
domain: None,
|
domain: None,
|
||||||
id: None,
|
id: None,
|
||||||
|
@ -185,7 +195,7 @@ async fn test_edit_local_article() -> MyResult<()> {
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// create new article
|
// create new article
|
||||||
let create_form = CreateArticleData {
|
let create_form = CreateArticleForm {
|
||||||
title: "Manu_Chao".to_string(),
|
title: "Manu_Chao".to_string(),
|
||||||
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
||||||
summary: "create article".to_string(),
|
summary: "create article".to_string(),
|
||||||
|
@ -195,7 +205,7 @@ async fn test_edit_local_article() -> MyResult<()> {
|
||||||
assert!(create_res.article.local);
|
assert!(create_res.article.local);
|
||||||
|
|
||||||
// article should be federated to alpha
|
// article should be federated to alpha
|
||||||
let get_article_data = GetArticleData {
|
let get_article_data = GetArticleForm {
|
||||||
title: Some(create_res.article.title.to_string()),
|
title: Some(create_res.article.title.to_string()),
|
||||||
domain: Some(beta_instance.domain),
|
domain: Some(beta_instance.domain),
|
||||||
id: None,
|
id: None,
|
||||||
|
@ -207,7 +217,7 @@ async fn test_edit_local_article() -> MyResult<()> {
|
||||||
assert_eq!(create_res.article.text, get_res.article.text);
|
assert_eq!(create_res.article.text, get_res.article.text);
|
||||||
|
|
||||||
// edit the article
|
// edit the article
|
||||||
let edit_form = EditArticleData {
|
let edit_form = EditArticleForm {
|
||||||
article_id: create_res.article.id,
|
article_id: create_res.article.id,
|
||||||
new_text: "Lorem Ipsum 2\n".to_string(),
|
new_text: "Lorem Ipsum 2\n".to_string(),
|
||||||
summary: "summary".to_string(),
|
summary: "summary".to_string(),
|
||||||
|
@ -246,7 +256,7 @@ async fn test_edit_remote_article() -> MyResult<()> {
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// create new article
|
// create new article
|
||||||
let create_form = CreateArticleData {
|
let create_form = CreateArticleForm {
|
||||||
title: "Manu_Chao".to_string(),
|
title: "Manu_Chao".to_string(),
|
||||||
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
||||||
summary: "create article".to_string(),
|
summary: "create article".to_string(),
|
||||||
|
@ -256,7 +266,7 @@ async fn test_edit_remote_article() -> MyResult<()> {
|
||||||
assert!(create_res.article.local);
|
assert!(create_res.article.local);
|
||||||
|
|
||||||
// article should be federated to alpha and gamma
|
// article should be federated to alpha and gamma
|
||||||
let get_article_data_alpha = GetArticleData {
|
let get_article_data_alpha = GetArticleForm {
|
||||||
title: Some(create_res.article.title.to_string()),
|
title: Some(create_res.article.title.to_string()),
|
||||||
domain: Some(beta_id_on_alpha.domain),
|
domain: Some(beta_id_on_alpha.domain),
|
||||||
id: None,
|
id: None,
|
||||||
|
@ -269,7 +279,7 @@ async fn test_edit_remote_article() -> MyResult<()> {
|
||||||
assert_eq!(1, get_res.edits.len());
|
assert_eq!(1, get_res.edits.len());
|
||||||
assert!(!get_res.article.local);
|
assert!(!get_res.article.local);
|
||||||
|
|
||||||
let get_article_data_gamma = GetArticleData {
|
let get_article_data_gamma = GetArticleForm {
|
||||||
title: Some(create_res.article.title.to_string()),
|
title: Some(create_res.article.title.to_string()),
|
||||||
domain: Some(beta_id_on_gamma.domain),
|
domain: Some(beta_id_on_gamma.domain),
|
||||||
id: None,
|
id: None,
|
||||||
|
@ -281,7 +291,7 @@ async fn test_edit_remote_article() -> MyResult<()> {
|
||||||
assert_eq!(create_res.article.title, get_res.article.title);
|
assert_eq!(create_res.article.title, get_res.article.title);
|
||||||
assert_eq!(create_res.article.text, get_res.article.text);
|
assert_eq!(create_res.article.text, get_res.article.text);
|
||||||
|
|
||||||
let edit_form = EditArticleData {
|
let edit_form = EditArticleForm {
|
||||||
article_id: get_res.article.id,
|
article_id: get_res.article.id,
|
||||||
new_text: "Lorem Ipsum 2\n".to_string(),
|
new_text: "Lorem Ipsum 2\n".to_string(),
|
||||||
summary: "summary".to_string(),
|
summary: "summary".to_string(),
|
||||||
|
@ -317,7 +327,7 @@ async fn test_local_edit_conflict() -> MyResult<()> {
|
||||||
let data = TestData::start().await;
|
let data = TestData::start().await;
|
||||||
|
|
||||||
// create new article
|
// create new article
|
||||||
let create_form = CreateArticleData {
|
let create_form = CreateArticleForm {
|
||||||
title: "Manu_Chao".to_string(),
|
title: "Manu_Chao".to_string(),
|
||||||
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
||||||
summary: "create article".to_string(),
|
summary: "create article".to_string(),
|
||||||
|
@ -327,7 +337,7 @@ async fn test_local_edit_conflict() -> MyResult<()> {
|
||||||
assert!(create_res.article.local);
|
assert!(create_res.article.local);
|
||||||
|
|
||||||
// one user edits article
|
// one user edits article
|
||||||
let edit_form = EditArticleData {
|
let edit_form = EditArticleForm {
|
||||||
article_id: create_res.article.id,
|
article_id: create_res.article.id,
|
||||||
new_text: "Lorem Ipsum\n".to_string(),
|
new_text: "Lorem Ipsum\n".to_string(),
|
||||||
summary: "summary".to_string(),
|
summary: "summary".to_string(),
|
||||||
|
@ -339,7 +349,7 @@ async fn test_local_edit_conflict() -> MyResult<()> {
|
||||||
assert_eq!(2, edit_res.edits.len());
|
assert_eq!(2, edit_res.edits.len());
|
||||||
|
|
||||||
// another user edits article, without being aware of previous edit
|
// another user edits article, without being aware of previous edit
|
||||||
let edit_form = EditArticleData {
|
let edit_form = EditArticleForm {
|
||||||
article_id: create_res.article.id,
|
article_id: create_res.article.id,
|
||||||
new_text: "Ipsum Lorem\n".to_string(),
|
new_text: "Ipsum Lorem\n".to_string(),
|
||||||
summary: "summary".to_string(),
|
summary: "summary".to_string(),
|
||||||
|
@ -357,7 +367,7 @@ async fn test_local_edit_conflict() -> MyResult<()> {
|
||||||
assert_eq!(1, conflicts.len());
|
assert_eq!(1, conflicts.len());
|
||||||
assert_eq!(conflicts[0], edit_res);
|
assert_eq!(conflicts[0], edit_res);
|
||||||
|
|
||||||
let edit_form = EditArticleData {
|
let edit_form = EditArticleForm {
|
||||||
article_id: create_res.article.id,
|
article_id: create_res.article.id,
|
||||||
new_text: "Lorem Ipsum and Ipsum Lorem\n".to_string(),
|
new_text: "Lorem Ipsum and Ipsum Lorem\n".to_string(),
|
||||||
summary: "summary".to_string(),
|
summary: "summary".to_string(),
|
||||||
|
@ -383,7 +393,7 @@ async fn test_federated_edit_conflict() -> MyResult<()> {
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// create new article
|
// create new article
|
||||||
let create_form = CreateArticleData {
|
let create_form = CreateArticleForm {
|
||||||
title: "Manu_Chao".to_string(),
|
title: "Manu_Chao".to_string(),
|
||||||
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
||||||
summary: "create article".to_string(),
|
summary: "create article".to_string(),
|
||||||
|
@ -400,7 +410,7 @@ async fn test_federated_edit_conflict() -> MyResult<()> {
|
||||||
assert_eq!(create_res.article.text, resolve_res.article.text);
|
assert_eq!(create_res.article.text, resolve_res.article.text);
|
||||||
|
|
||||||
// alpha edits article
|
// alpha edits article
|
||||||
let get_article_data = GetArticleData {
|
let get_article_data = GetArticleForm {
|
||||||
title: Some(create_form.title.to_string()),
|
title: Some(create_form.title.to_string()),
|
||||||
domain: Some(beta_id_on_alpha.domain),
|
domain: Some(beta_id_on_alpha.domain),
|
||||||
id: None,
|
id: None,
|
||||||
|
@ -408,7 +418,7 @@ async fn test_federated_edit_conflict() -> MyResult<()> {
|
||||||
let get_res = data.alpha.get_article(get_article_data).await?;
|
let get_res = data.alpha.get_article(get_article_data).await?;
|
||||||
assert_eq!(&create_res.edits.len(), &get_res.edits.len());
|
assert_eq!(&create_res.edits.len(), &get_res.edits.len());
|
||||||
assert_eq!(&create_res.edits[0].edit.hash, &get_res.edits[0].edit.hash);
|
assert_eq!(&create_res.edits[0].edit.hash, &get_res.edits[0].edit.hash);
|
||||||
let edit_form = EditArticleData {
|
let edit_form = EditArticleForm {
|
||||||
article_id: get_res.article.id,
|
article_id: get_res.article.id,
|
||||||
new_text: "Lorem Ipsum\n".to_string(),
|
new_text: "Lorem Ipsum\n".to_string(),
|
||||||
summary: "summary".to_string(),
|
summary: "summary".to_string(),
|
||||||
|
@ -427,7 +437,7 @@ async fn test_federated_edit_conflict() -> MyResult<()> {
|
||||||
|
|
||||||
// gamma also edits, as its not the latest version there is a conflict. local version should
|
// gamma also edits, as its not the latest version there is a conflict. local version should
|
||||||
// not be updated with this conflicting version, instead user needs to handle the conflict
|
// not be updated with this conflicting version, instead user needs to handle the conflict
|
||||||
let edit_form = EditArticleData {
|
let edit_form = EditArticleForm {
|
||||||
article_id: resolve_res.article.id,
|
article_id: resolve_res.article.id,
|
||||||
new_text: "aaaa\n".to_string(),
|
new_text: "aaaa\n".to_string(),
|
||||||
summary: "summary".to_string(),
|
summary: "summary".to_string(),
|
||||||
|
@ -443,7 +453,7 @@ async fn test_federated_edit_conflict() -> MyResult<()> {
|
||||||
assert_eq!(1, conflicts.len());
|
assert_eq!(1, conflicts.len());
|
||||||
|
|
||||||
// resolve the conflict
|
// resolve the conflict
|
||||||
let edit_form = EditArticleData {
|
let edit_form = EditArticleForm {
|
||||||
article_id: resolve_res.article.id,
|
article_id: resolve_res.article.id,
|
||||||
new_text: "aaaa\n".to_string(),
|
new_text: "aaaa\n".to_string(),
|
||||||
summary: "summary".to_string(),
|
summary: "summary".to_string(),
|
||||||
|
@ -465,7 +475,7 @@ async fn test_overlapping_edits_no_conflict() -> MyResult<()> {
|
||||||
let data = TestData::start().await;
|
let data = TestData::start().await;
|
||||||
|
|
||||||
// create new article
|
// create new article
|
||||||
let create_form = CreateArticleData {
|
let create_form = CreateArticleForm {
|
||||||
title: "Manu_Chao".to_string(),
|
title: "Manu_Chao".to_string(),
|
||||||
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
||||||
summary: "create article".to_string(),
|
summary: "create article".to_string(),
|
||||||
|
@ -475,7 +485,7 @@ async fn test_overlapping_edits_no_conflict() -> MyResult<()> {
|
||||||
assert!(create_res.article.local);
|
assert!(create_res.article.local);
|
||||||
|
|
||||||
// one user edits article
|
// one user edits article
|
||||||
let edit_form = EditArticleData {
|
let edit_form = EditArticleForm {
|
||||||
article_id: create_res.article.id,
|
article_id: create_res.article.id,
|
||||||
new_text: "my\nexample\ntext\n".to_string(),
|
new_text: "my\nexample\ntext\n".to_string(),
|
||||||
summary: "summary".to_string(),
|
summary: "summary".to_string(),
|
||||||
|
@ -487,7 +497,7 @@ async fn test_overlapping_edits_no_conflict() -> MyResult<()> {
|
||||||
assert_eq!(2, edit_res.edits.len());
|
assert_eq!(2, edit_res.edits.len());
|
||||||
|
|
||||||
// another user edits article, without being aware of previous edit
|
// another user edits article, without being aware of previous edit
|
||||||
let edit_form = EditArticleData {
|
let edit_form = EditArticleForm {
|
||||||
article_id: create_res.article.id,
|
article_id: create_res.article.id,
|
||||||
new_text: "some\nexample\narticle\n".to_string(),
|
new_text: "some\nexample\narticle\n".to_string(),
|
||||||
summary: "summary".to_string(),
|
summary: "summary".to_string(),
|
||||||
|
@ -508,7 +518,7 @@ async fn test_fork_article() -> MyResult<()> {
|
||||||
let data = TestData::start().await;
|
let data = TestData::start().await;
|
||||||
|
|
||||||
// create article
|
// create article
|
||||||
let create_form = CreateArticleData {
|
let create_form = CreateArticleForm {
|
||||||
title: "Manu_Chao".to_string(),
|
title: "Manu_Chao".to_string(),
|
||||||
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
||||||
summary: "create article".to_string(),
|
summary: "create article".to_string(),
|
||||||
|
@ -526,7 +536,7 @@ async fn test_fork_article() -> MyResult<()> {
|
||||||
assert_eq!(create_res.edits.len(), resolve_res.edits.len());
|
assert_eq!(create_res.edits.len(), resolve_res.edits.len());
|
||||||
|
|
||||||
// fork the article to local instance
|
// fork the article to local instance
|
||||||
let fork_form = ForkArticleData {
|
let fork_form = ForkArticleForm {
|
||||||
article_id: resolved_article.id,
|
article_id: resolved_article.id,
|
||||||
new_title: resolved_article.title.clone(),
|
new_title: resolved_article.title.clone(),
|
||||||
};
|
};
|
||||||
|
@ -546,7 +556,7 @@ async fn test_fork_article() -> MyResult<()> {
|
||||||
assert_eq!(forked_article.instance_id, beta_instance.instance.id);
|
assert_eq!(forked_article.instance_id, beta_instance.instance.id);
|
||||||
|
|
||||||
// now search returns two articles for this title (original and forked)
|
// now search returns two articles for this title (original and forked)
|
||||||
let search_form = SearchArticleData {
|
let search_form = SearchArticleForm {
|
||||||
query: create_form.title.clone(),
|
query: create_form.title.clone(),
|
||||||
};
|
};
|
||||||
let search_res = data.beta.search(&search_form).await?;
|
let search_res = data.beta.search(&search_form).await?;
|
||||||
|
@ -560,20 +570,20 @@ async fn test_user_registration_login() -> MyResult<()> {
|
||||||
let data = TestData::start().await;
|
let data = TestData::start().await;
|
||||||
let username = "my_user";
|
let username = "my_user";
|
||||||
let password = "hunter2";
|
let password = "hunter2";
|
||||||
let register_data = RegisterUserData {
|
let register_data = RegisterUserForm {
|
||||||
username: username.to_string(),
|
username: username.to_string(),
|
||||||
password: password.to_string(),
|
password: password.to_string(),
|
||||||
};
|
};
|
||||||
data.alpha.register(register_data).await?;
|
data.alpha.register(register_data).await?;
|
||||||
|
|
||||||
let login_data = LoginUserData {
|
let login_data = LoginUserForm {
|
||||||
username: username.to_string(),
|
username: username.to_string(),
|
||||||
password: "asd123".to_string(),
|
password: "asd123".to_string(),
|
||||||
};
|
};
|
||||||
let invalid_login = data.alpha.login(login_data).await;
|
let invalid_login = data.alpha.login(login_data).await;
|
||||||
assert!(invalid_login.is_err());
|
assert!(invalid_login.is_err());
|
||||||
|
|
||||||
let login_data = LoginUserData {
|
let login_data = LoginUserForm {
|
||||||
username: username.to_string(),
|
username: username.to_string(),
|
||||||
password: password.to_string(),
|
password: password.to_string(),
|
||||||
};
|
};
|
||||||
|
@ -595,7 +605,7 @@ async fn test_user_profile() -> MyResult<()> {
|
||||||
let data = TestData::start().await;
|
let data = TestData::start().await;
|
||||||
|
|
||||||
// Create an article and federate it, in order to federate the user who created it
|
// Create an article and federate it, in order to federate the user who created it
|
||||||
let create_form = CreateArticleData {
|
let create_form = CreateArticleForm {
|
||||||
title: "Manu_Chao".to_string(),
|
title: "Manu_Chao".to_string(),
|
||||||
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
||||||
summary: "create article".to_string(),
|
summary: "create article".to_string(),
|
||||||
|
@ -607,7 +617,7 @@ async fn test_user_profile() -> MyResult<()> {
|
||||||
let domain = extract_domain(&data.alpha.my_profile().await?.person.ap_id);
|
let domain = extract_domain(&data.alpha.my_profile().await?.person.ap_id);
|
||||||
|
|
||||||
// Now we can fetch the remote user from local api
|
// Now we can fetch the remote user from local api
|
||||||
let params = GetUserData {
|
let params = GetUserForm {
|
||||||
name: "alpha".to_string(),
|
name: "alpha".to_string(),
|
||||||
domain: Some(domain),
|
domain: Some(domain),
|
||||||
};
|
};
|
||||||
|
@ -617,3 +627,50 @@ async fn test_user_profile() -> MyResult<()> {
|
||||||
|
|
||||||
data.stop()
|
data.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_lock_article() -> MyResult<()> {
|
||||||
|
let data = TestData::start().await;
|
||||||
|
|
||||||
|
// create article
|
||||||
|
let create_form = CreateArticleForm {
|
||||||
|
title: "Manu_Chao".to_string(),
|
||||||
|
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
||||||
|
summary: "create article".to_string(),
|
||||||
|
};
|
||||||
|
let create_res = data.alpha.create_article(&create_form).await?;
|
||||||
|
assert!(!create_res.article.protected);
|
||||||
|
|
||||||
|
// lock from normal user fails
|
||||||
|
let lock_form = ProtectArticleForm {
|
||||||
|
article_id: create_res.article.id,
|
||||||
|
protected: true,
|
||||||
|
};
|
||||||
|
let lock_res = data.alpha.protect_article(&lock_form).await;
|
||||||
|
assert!(lock_res.is_err());
|
||||||
|
|
||||||
|
// login as admin to lock article
|
||||||
|
let form = LoginUserForm {
|
||||||
|
username: "ibis".to_string(),
|
||||||
|
password: "ibis".to_string(),
|
||||||
|
};
|
||||||
|
data.alpha.login(form).await?;
|
||||||
|
let lock_res = data.alpha.protect_article(&lock_form).await?;
|
||||||
|
assert!(lock_res.protected);
|
||||||
|
|
||||||
|
let resolve_res: ArticleView = data
|
||||||
|
.gamma
|
||||||
|
.resolve_article(create_res.article.ap_id.inner().clone())
|
||||||
|
.await?;
|
||||||
|
let edit_form = EditArticleForm {
|
||||||
|
article_id: resolve_res.article.id,
|
||||||
|
new_text: "test".to_string(),
|
||||||
|
summary: "test".to_string(),
|
||||||
|
previous_version_id: resolve_res.latest_version,
|
||||||
|
resolve_conflict_id: None,
|
||||||
|
};
|
||||||
|
let edit_res = data.gamma.edit_article(&edit_form).await;
|
||||||
|
assert!(edit_res.is_err());
|
||||||
|
|
||||||
|
data.stop()
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue