mirror of
https://github.com/Nutomic/ibis.git
synced 2024-11-22 02:21:10 +00:00
Implement protect article function
This commit is contained in:
parent
a1e0ef4166
commit
b2b20dcd2a
26 changed files with 1500 additions and 106 deletions
1177
assets/dist/ibis.js
vendored
1177
assets/dist/ibis.js
vendored
File diff suppressed because it is too large
Load diff
BIN
assets/dist/ibis_bg.wasm
vendored
BIN
assets/dist/ibis_bg.wasm
vendored
Binary file not shown.
|
@ -43,7 +43,8 @@ create table article (
|
|||
text text not null,
|
||||
ap_id varchar(255) not null unique,
|
||||
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 (
|
||||
|
|
|
@ -9,12 +9,12 @@ use crate::backend::utils::generate_article_version;
|
|||
use crate::common::utils::extract_domain;
|
||||
use crate::common::utils::http_protocol_str;
|
||||
use crate::common::validation::can_edit_article;
|
||||
use crate::common::LocalUserView;
|
||||
use crate::common::{ApiConflict, ResolveObject};
|
||||
use crate::common::{ArticleView, DbArticle, DbEdit};
|
||||
use crate::common::{CreateArticleData, EditArticleData, EditVersion, ForkArticleData};
|
||||
use crate::common::{DbInstance, SearchArticleData};
|
||||
use crate::common::{GetArticleData, ListArticlesData};
|
||||
use crate::common::{CreateArticleForm, EditArticleForm, EditVersion, ForkArticleForm};
|
||||
use crate::common::{DbInstance, SearchArticleForm};
|
||||
use crate::common::{GetArticleForm, ListArticlesForm};
|
||||
use crate::common::{LocalUserView, ProtectArticleForm};
|
||||
use activitypub_federation::config::Data;
|
||||
use activitypub_federation::fetch::object_id::ObjectId;
|
||||
use anyhow::anyhow;
|
||||
|
@ -31,7 +31,7 @@ use diffy::create_patch;
|
|||
pub(in crate::backend::api) async fn create_article(
|
||||
Extension(user): Extension<LocalUserView>,
|
||||
data: Data<IbisData>,
|
||||
Form(create_article): Form<CreateArticleData>,
|
||||
Form(create_article): Form<CreateArticleForm>,
|
||||
) -> MyResult<Json<ArticleView>> {
|
||||
if create_article.title.is_empty() {
|
||||
return Err(anyhow!("Title must not be empty").into());
|
||||
|
@ -50,10 +50,11 @@ pub(in crate::backend::api) async fn create_article(
|
|||
ap_id,
|
||||
instance_id: local_instance.id,
|
||||
local: true,
|
||||
protected: false,
|
||||
};
|
||||
let article = DbArticle::create(form, &data)?;
|
||||
|
||||
let edit_data = EditArticleData {
|
||||
let edit_data = EditArticleForm {
|
||||
article_id: article.id,
|
||||
new_text: create_article.text,
|
||||
summary: create_article.summary,
|
||||
|
@ -81,7 +82,7 @@ pub(in crate::backend::api) async fn create_article(
|
|||
pub(in crate::backend::api) async fn edit_article(
|
||||
Extension(user): Extension<LocalUserView>,
|
||||
data: Data<IbisData>,
|
||||
Form(mut edit_form): Form<EditArticleData>,
|
||||
Form(mut edit_form): Form<EditArticleForm>,
|
||||
) -> MyResult<Json<Option<ApiConflict>>> {
|
||||
// resolve conflict if any
|
||||
if let Some(resolve_conflict_id) = edit_form.resolve_conflict_id {
|
||||
|
@ -136,7 +137,7 @@ pub(in crate::backend::api) async fn edit_article(
|
|||
/// Retrieve an article by ID. It must already be stored in the local database.
|
||||
#[debug_handler]
|
||||
pub(in crate::backend::api) async fn get_article(
|
||||
Query(query): Query<GetArticleData>,
|
||||
Query(query): Query<GetArticleForm>,
|
||||
data: Data<IbisData>,
|
||||
) -> MyResult<Json<ArticleView>> {
|
||||
match (query.title, query.id) {
|
||||
|
@ -157,7 +158,7 @@ pub(in crate::backend::api) async fn get_article(
|
|||
|
||||
#[debug_handler]
|
||||
pub(in crate::backend::api) async fn list_articles(
|
||||
Query(query): Query<ListArticlesData>,
|
||||
Query(query): Query<ListArticlesForm>,
|
||||
data: Data<IbisData>,
|
||||
) -> MyResult<Json<Vec<DbArticle>>> {
|
||||
let only_local = query.only_local.unwrap_or(false);
|
||||
|
@ -170,7 +171,7 @@ pub(in crate::backend::api) async fn list_articles(
|
|||
pub(in crate::backend::api) async fn fork_article(
|
||||
Extension(_user): Extension<LocalUserView>,
|
||||
data: Data<IbisData>,
|
||||
Form(fork_form): Form<ForkArticleData>,
|
||||
Form(fork_form): Form<ForkArticleForm>,
|
||||
) -> MyResult<Json<ArticleView>> {
|
||||
// TODO: lots of code duplicated from create_article(), can move it into helper
|
||||
let original_article = DbArticle::read(fork_form.article_id, &data)?;
|
||||
|
@ -188,6 +189,7 @@ pub(in crate::backend::api) async fn fork_article(
|
|||
ap_id,
|
||||
instance_id: local_instance.id,
|
||||
local: true,
|
||||
protected: false,
|
||||
};
|
||||
let article = DbArticle::create(form, &data)?;
|
||||
|
||||
|
@ -242,7 +244,7 @@ pub(super) async fn resolve_article(
|
|||
/// Search articles for matching title or body text.
|
||||
#[debug_handler]
|
||||
pub(super) async fn search_article(
|
||||
Query(query): Query<SearchArticleData>,
|
||||
Query(query): Query<SearchArticleForm>,
|
||||
data: Data<IbisData>,
|
||||
) -> MyResult<Json<Vec<DbArticle>>> {
|
||||
if query.query.is_empty() {
|
||||
|
@ -251,3 +253,17 @@ pub(super) async fn search_article(
|
|||
let article = DbArticle::search(&query.query, &data)?;
|
||||
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,3 +1,4 @@
|
|||
use crate::backend::api::article::protect_article;
|
||||
use crate::backend::api::article::{
|
||||
create_article, list_articles, resolve_article, search_article,
|
||||
};
|
||||
|
@ -40,6 +41,7 @@ pub fn api_routes() -> Router {
|
|||
.route("/article/list", get(list_articles))
|
||||
.route("/article/fork", post(fork_article))
|
||||
.route("/article/resolve", get(resolve_article))
|
||||
.route("/article/protect", post(protect_article))
|
||||
.route("/edit_conflicts", get(edit_conflicts))
|
||||
.route("/instance", get(get_local_instance))
|
||||
.route("/instance/follow", post(follow_instance))
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::backend::database::{read_jwt_secret, IbisData};
|
||||
use crate::backend::error::MyResult;
|
||||
use crate::common::{DbPerson, GetUserData, LocalUserView, LoginUserData, RegisterUserData};
|
||||
use crate::common::{DbPerson, GetUserForm, LocalUserView, LoginUserForm, RegisterUserForm};
|
||||
use activitypub_federation::config::Data;
|
||||
use anyhow::anyhow;
|
||||
use axum::extract::Query;
|
||||
|
@ -57,7 +57,7 @@ pub async fn validate(jwt: &str, data: &Data<IbisData>) -> MyResult<LocalUserVie
|
|||
pub(in crate::backend::api) async fn register_user(
|
||||
data: Data<IbisData>,
|
||||
jar: CookieJar,
|
||||
Form(form): Form<RegisterUserData>,
|
||||
Form(form): Form<RegisterUserForm>,
|
||||
) -> MyResult<(CookieJar, Json<LocalUserView>)> {
|
||||
if !data.config.registration_open {
|
||||
return Err(anyhow!("Registration is closed").into());
|
||||
|
@ -72,7 +72,7 @@ pub(in crate::backend::api) async fn register_user(
|
|||
pub(in crate::backend::api) async fn login_user(
|
||||
data: Data<IbisData>,
|
||||
jar: CookieJar,
|
||||
Form(form): Form<LoginUserData>,
|
||||
Form(form): Form<LoginUserForm>,
|
||||
) -> MyResult<(CookieJar, Json<LocalUserView>)> {
|
||||
let user = DbPerson::read_local_from_name(&form.username, &data)?;
|
||||
let valid = verify(&form.password, &user.local_user.password_encrypted)?;
|
||||
|
@ -126,7 +126,7 @@ pub(in crate::backend::api) async fn logout_user(
|
|||
|
||||
#[debug_handler]
|
||||
pub(in crate::backend::api) async fn get_user(
|
||||
params: Query<GetUserData>,
|
||||
params: Query<GetUserForm>,
|
||||
data: Data<IbisData>,
|
||||
) -> MyResult<Json<DbPerson>> {
|
||||
Ok(Json(DbPerson::read_from_name(
|
||||
|
|
|
@ -24,6 +24,7 @@ pub struct DbArticleForm {
|
|||
pub ap_id: ObjectId<DbArticle>,
|
||||
pub instance_id: i32,
|
||||
pub local: bool,
|
||||
pub protected: bool,
|
||||
}
|
||||
|
||||
// TODO: get rid of unnecessary methods
|
||||
|
@ -58,6 +59,13 @@ impl DbArticle {
|
|||
.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> {
|
||||
let mut conn = data.db_pool.get()?;
|
||||
Ok(article::table.find(id).get_result(conn.deref_mut())?)
|
||||
|
|
|
@ -9,6 +9,7 @@ diesel::table! {
|
|||
ap_id -> Varchar,
|
||||
instance_id -> Int4,
|
||||
local -> Bool,
|
||||
protected -> Bool,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ pub struct ApubArticle {
|
|||
latest_version: EditVersion,
|
||||
content: String,
|
||||
name: String,
|
||||
protected: bool,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
|
@ -56,6 +57,7 @@ impl Object for DbArticle {
|
|||
latest_version: self.latest_edit_version(data)?,
|
||||
content: self.text,
|
||||
name: self.title,
|
||||
protected: self.protected,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -76,6 +78,7 @@ impl Object for DbArticle {
|
|||
ap_id: json.id,
|
||||
local: false,
|
||||
instance_id: instance.id,
|
||||
protected: json.protected,
|
||||
};
|
||||
let article = DbArticle::create_or_update(form, data)?;
|
||||
|
||||
|
|
|
@ -179,6 +179,7 @@ async fn setup(data: &Data<IbisData>) -> Result<(), Error> {
|
|||
))?,
|
||||
instance_id: instance.id,
|
||||
local: true,
|
||||
protected: true,
|
||||
};
|
||||
let article = DbArticle::create(form, data)?;
|
||||
// also create an article so its included in most recently edited list
|
||||
|
|
|
@ -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
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct GetArticleData {
|
||||
pub struct GetArticleForm {
|
||||
pub title: Option<String>,
|
||||
pub domain: Option<String>,
|
||||
pub id: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct ListArticlesData {
|
||||
pub struct ListArticlesForm {
|
||||
pub only_local: Option<bool>,
|
||||
}
|
||||
|
||||
|
@ -53,6 +53,7 @@ pub struct DbArticle {
|
|||
pub ap_id: String,
|
||||
pub instance_id: i32,
|
||||
pub local: bool,
|
||||
pub protected: bool,
|
||||
}
|
||||
|
||||
/// Represents a single change to the article.
|
||||
|
@ -115,13 +116,13 @@ impl Default for EditVersion {
|
|||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct RegisterUserData {
|
||||
pub struct RegisterUserForm {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct LoginUserData {
|
||||
pub struct LoginUserForm {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
@ -175,14 +176,14 @@ impl DbPerson {
|
|||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct CreateArticleData {
|
||||
pub struct CreateArticleForm {
|
||||
pub title: String,
|
||||
pub text: String,
|
||||
pub summary: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct EditArticleData {
|
||||
pub struct EditArticleForm {
|
||||
/// Id of the article to edit
|
||||
pub article_id: i32,
|
||||
/// 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>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct ProtectArticleForm {
|
||||
pub article_id: i32,
|
||||
pub protected: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
pub struct ForkArticleData {
|
||||
pub struct ForkArticleForm {
|
||||
pub article_id: i32,
|
||||
pub new_title: String,
|
||||
}
|
||||
|
@ -209,7 +216,7 @@ pub struct FollowInstance {
|
|||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone)]
|
||||
pub struct SearchArticleData {
|
||||
pub struct SearchArticleForm {
|
||||
pub query: String,
|
||||
}
|
||||
|
||||
|
@ -269,7 +276,7 @@ pub struct InstanceView {
|
|||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||
pub struct GetUserData {
|
||||
pub struct GetUserForm {
|
||||
pub name: String,
|
||||
pub domain: Option<String>,
|
||||
}
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
use crate::common::{DbArticle, MAIN_PAGE_NAME};
|
||||
use crate::common::DbArticle;
|
||||
use anyhow::anyhow;
|
||||
use anyhow::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 {
|
||||
return Err(anyhow!("Cannot edit main page of remote instance"));
|
||||
return Err(err);
|
||||
}
|
||||
if article.local && !is_admin {
|
||||
return Err(anyhow!("Only admin can edit main page"));
|
||||
if !is_admin {
|
||||
return Err(err);
|
||||
}
|
||||
}
|
||||
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,10 +1,10 @@
|
|||
use crate::common::utils::http_protocol_str;
|
||||
use crate::common::{ApiConflict, ListArticlesData};
|
||||
use crate::common::{ArticleView, LoginUserData, RegisterUserData};
|
||||
use crate::common::{CreateArticleData, EditArticleData, ForkArticleData, LocalUserView};
|
||||
use crate::common::{DbArticle, GetArticleData};
|
||||
use crate::common::{DbInstance, FollowInstance, InstanceView, SearchArticleData};
|
||||
use crate::common::{DbPerson, GetUserData, ResolveObject};
|
||||
use crate::common::{ApiConflict, ListArticlesForm, ProtectArticleForm};
|
||||
use crate::common::{ArticleView, LoginUserForm, RegisterUserForm};
|
||||
use crate::common::{CreateArticleForm, EditArticleForm, ForkArticleForm, LocalUserView};
|
||||
use crate::common::{DbArticle, GetArticleForm};
|
||||
use crate::common::{DbInstance, FollowInstance, InstanceView, SearchArticleForm};
|
||||
use crate::common::{DbPerson, GetUserForm, ResolveObject};
|
||||
use crate::frontend::error::MyResult;
|
||||
use anyhow::anyhow;
|
||||
use reqwest::{Client, RequestBuilder, StatusCode};
|
||||
|
@ -34,15 +34,15 @@ impl ApiClient {
|
|||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
pub async fn register(&self, register_form: RegisterUserData) -> MyResult<LocalUserView> {
|
||||
pub async fn register(&self, register_form: RegisterUserForm) -> MyResult<LocalUserView> {
|
||||
let req = self
|
||||
.client
|
||||
.post(self.request_endpoint("/api/v1/account/register"))
|
||||
|
@ -50,7 +50,7 @@ impl ApiClient {
|
|||
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
|
||||
.client
|
||||
.post(self.request_endpoint("/api/v1/account/login"))
|
||||
|
@ -58,7 +58,7 @@ impl ApiClient {
|
|||
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
|
||||
.client
|
||||
.post(self.request_endpoint("/api/v1/article"))
|
||||
|
@ -68,7 +68,7 @@ impl ApiClient {
|
|||
|
||||
pub async fn edit_article_with_conflict(
|
||||
&self,
|
||||
edit_form: &EditArticleData,
|
||||
edit_form: &EditArticleForm,
|
||||
) -> MyResult<Option<ApiConflict>> {
|
||||
let req = self
|
||||
.client
|
||||
|
@ -77,11 +77,11 @@ impl ApiClient {
|
|||
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?;
|
||||
assert!(edit_res.is_none());
|
||||
|
||||
self.get_article(GetArticleData {
|
||||
self.get_article(GetArticleForm {
|
||||
title: None,
|
||||
domain: None,
|
||||
id: Some(edit_form.article_id),
|
||||
|
@ -89,7 +89,7 @@ impl ApiClient {
|
|||
.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
|
||||
}
|
||||
|
||||
|
@ -147,7 +147,7 @@ impl ApiClient {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn fork_article(&self, form: &ForkArticleData) -> MyResult<ArticleView> {
|
||||
pub async fn fork_article(&self, form: &ForkArticleForm) -> MyResult<ArticleView> {
|
||||
let req = self
|
||||
.client
|
||||
.post(self.request_endpoint("/api/v1/article/fork"))
|
||||
|
@ -155,6 +155,14 @@ impl ApiClient {
|
|||
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>> {
|
||||
let req = self
|
||||
.client
|
||||
|
@ -173,7 +181,7 @@ impl ApiClient {
|
|||
self.get_query("/api/v1/instance/resolve", Some(resolve_object))
|
||||
.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
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ pub fn ArticleNav(article: Resource<Option<String>, ArticleView>) -> impl IntoVi
|
|||
{move || article.get().map(|article| {
|
||||
let article_link = article_link(&article.article);
|
||||
let article_link_ = article_link.clone();
|
||||
let protected = article.article.protected;
|
||||
view!{
|
||||
<nav class="inner">
|
||||
<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())>
|
||||
<A href={format!("{article_link_}/actions")}>"Actions"</A>
|
||||
</Show>
|
||||
<Show when=move || protected>
|
||||
<span title="Article can only be edited by local admins">"Protected"</span>
|
||||
</Show>
|
||||
</nav>
|
||||
}})}
|
||||
</Suspense>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::common::ForkArticleData;
|
||||
use crate::common::ForkArticleForm;
|
||||
use crate::frontend::app::GlobalState;
|
||||
use crate::frontend::article_link;
|
||||
use crate::frontend::article_title;
|
||||
|
@ -15,7 +15,7 @@ pub fn ArticleActions() -> impl IntoView {
|
|||
let (fork_response, set_fork_response) = create_signal(Option::<DbArticle>::None);
|
||||
let (error, set_error) = create_signal(None::<String>);
|
||||
let fork_action = create_action(move |(article_id, new_title): &(i32, String)| {
|
||||
let params = ForkArticleData {
|
||||
let params = ForkArticleForm {
|
||||
article_id: *article_id,
|
||||
new_title: new_title.to_string(),
|
||||
};
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::common::CreateArticleData;
|
||||
use crate::common::CreateArticleForm;
|
||||
use crate::frontend::app::GlobalState;
|
||||
use leptos::*;
|
||||
use leptos_router::Redirect;
|
||||
|
@ -18,7 +18,7 @@ pub fn CreateArticle() -> impl IntoView {
|
|||
let text = text.clone();
|
||||
let summary = summary.clone();
|
||||
async move {
|
||||
let form = CreateArticleData {
|
||||
let form = CreateArticleForm {
|
||||
title,
|
||||
text,
|
||||
summary,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::common::{ApiConflict, ArticleView, EditArticleData};
|
||||
use crate::common::{ApiConflict, ArticleView, EditArticleForm};
|
||||
use crate::frontend::app::GlobalState;
|
||||
use crate::frontend::article_title;
|
||||
use crate::frontend::components::article_nav::ArticleNav;
|
||||
|
@ -65,7 +65,7 @@ pub fn EditArticle() -> impl IntoView {
|
|||
};
|
||||
async move {
|
||||
set_edit_error.update(|e| *e = None);
|
||||
let form = EditArticleData {
|
||||
let form = EditArticleForm {
|
||||
article_id: article.article.id,
|
||||
new_text,
|
||||
summary,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::common::ListArticlesData;
|
||||
use crate::common::ListArticlesForm;
|
||||
use crate::frontend::app::GlobalState;
|
||||
use crate::frontend::{article_link, article_title};
|
||||
use leptos::*;
|
||||
|
@ -11,7 +11,7 @@ pub fn ListArticles() -> impl IntoView {
|
|||
move || only_local.get(),
|
||||
|only_local| async move {
|
||||
GlobalState::api_client()
|
||||
.list_articles(ListArticlesData {
|
||||
.list_articles(ListArticlesForm {
|
||||
only_local: Some(only_local),
|
||||
})
|
||||
.await
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::common::LoginUserData;
|
||||
use crate::common::LoginUserForm;
|
||||
use crate::frontend::app::GlobalState;
|
||||
use crate::frontend::components::credentials::*;
|
||||
use leptos::*;
|
||||
|
@ -13,7 +13,7 @@ pub fn Login() -> impl IntoView {
|
|||
let login_action = create_action(move |(email, password): &(String, String)| {
|
||||
let username = email.to_string();
|
||||
let password = password.to_string();
|
||||
let credentials = LoginUserData { username, password };
|
||||
let credentials = LoginUserForm { username, password };
|
||||
async move {
|
||||
set_wait_for_response.update(|w| *w = true);
|
||||
let result = GlobalState::api_client().login(credentials).await;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::common::{ArticleView, GetArticleData, MAIN_PAGE_NAME};
|
||||
use crate::common::{ArticleView, GetArticleForm, MAIN_PAGE_NAME};
|
||||
use crate::frontend::app::GlobalState;
|
||||
use leptos::{create_resource, Resource, SignalGet};
|
||||
use leptos_router::use_params_map;
|
||||
|
@ -23,7 +23,7 @@ fn article_resource() -> Resource<Option<String>, ArticleView> {
|
|||
domain = Some(domain_.to_string());
|
||||
}
|
||||
GlobalState::api_client()
|
||||
.get_article(GetArticleData {
|
||||
.get_article(GetArticleForm {
|
||||
title: Some(title),
|
||||
domain,
|
||||
id: None,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::common::{LocalUserView, RegisterUserData};
|
||||
use crate::common::{LocalUserView, RegisterUserForm};
|
||||
use crate::frontend::app::GlobalState;
|
||||
use crate::frontend::components::credentials::*;
|
||||
use crate::frontend::error::MyResult;
|
||||
|
@ -13,7 +13,7 @@ pub fn Register() -> impl IntoView {
|
|||
let register_action = create_action(move |(email, password): &(String, String)| {
|
||||
let username = email.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);
|
||||
async move {
|
||||
set_wait_for_response.update(|w| *w = true);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::common::{DbArticle, DbInstance, SearchArticleData};
|
||||
use crate::common::{DbArticle, DbInstance, SearchArticleForm};
|
||||
use crate::frontend::app::GlobalState;
|
||||
use crate::frontend::{article_link, article_title};
|
||||
use leptos::*;
|
||||
|
@ -28,7 +28,7 @@ pub fn Search() -> impl IntoView {
|
|||
let mut search_results = SearchResults::default();
|
||||
let api_client = GlobalState::api_client();
|
||||
let url = Url::parse(&query);
|
||||
let search_data = SearchArticleData { query };
|
||||
let search_data = SearchArticleForm { query };
|
||||
let search = api_client.search(&search_data);
|
||||
|
||||
match search.await {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::common::{DbPerson, GetUserData};
|
||||
use crate::common::{DbPerson, GetUserForm};
|
||||
use crate::frontend::app::GlobalState;
|
||||
use crate::frontend::user_title;
|
||||
use leptos::*;
|
||||
|
@ -16,7 +16,7 @@ pub fn UserProfile() -> impl IntoView {
|
|||
name = title_.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()
|
||||
});
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use ibis_lib::backend::config::{IbisConfig, IbisConfigDatabase, IbisConfigFederation};
|
||||
use ibis_lib::backend::start;
|
||||
use ibis_lib::common::RegisterUserData;
|
||||
use ibis_lib::common::RegisterUserForm;
|
||||
use ibis_lib::frontend::api::ApiClient;
|
||||
use ibis_lib::frontend::error::MyResult;
|
||||
use reqwest::ClientBuilder;
|
||||
|
@ -128,7 +128,7 @@ impl IbisInstance {
|
|||
});
|
||||
// wait a moment for the backend to start
|
||||
tokio::time::sleep(Duration::from_millis(5000)).await;
|
||||
let form = RegisterUserData {
|
||||
let form = RegisterUserForm {
|
||||
username: username.to_string(),
|
||||
password: "hunter2".to_string(),
|
||||
};
|
||||
|
|
126
tests/test.rs
126
tests/test.rs
|
@ -7,10 +7,11 @@ mod common;
|
|||
use crate::common::{TestData, TEST_ARTICLE_DEFAULT_TEXT};
|
||||
use ibis_lib::common::utils::extract_domain;
|
||||
use ibis_lib::common::{
|
||||
ArticleView, EditArticleData, ForkArticleData, GetArticleData, GetUserData, ListArticlesData,
|
||||
ArticleView, EditArticleForm, ForkArticleForm, GetArticleForm, GetUserForm, ListArticlesForm,
|
||||
ProtectArticleForm,
|
||||
};
|
||||
use ibis_lib::common::{CreateArticleData, SearchArticleData};
|
||||
use ibis_lib::common::{LoginUserData, RegisterUserData};
|
||||
use ibis_lib::common::{CreateArticleForm, SearchArticleForm};
|
||||
use ibis_lib::common::{LoginUserForm, RegisterUserForm};
|
||||
use ibis_lib::frontend::error::MyResult;
|
||||
use pretty_assertions::{assert_eq, assert_ne};
|
||||
use url::Url;
|
||||
|
@ -20,7 +21,7 @@ async fn test_create_read_and_edit_local_article() -> MyResult<()> {
|
|||
let data = TestData::start().await;
|
||||
|
||||
// create article
|
||||
let create_form = CreateArticleData {
|
||||
let create_form = CreateArticleForm {
|
||||
title: "Manu_Chao".to_string(),
|
||||
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
||||
summary: "create article".to_string(),
|
||||
|
@ -30,7 +31,7 @@ async fn test_create_read_and_edit_local_article() -> MyResult<()> {
|
|||
assert!(create_res.article.local);
|
||||
|
||||
// now article can be read
|
||||
let get_article_data = GetArticleData {
|
||||
let get_article_data = GetArticleForm {
|
||||
title: Some(create_res.article.title.clone()),
|
||||
domain: None,
|
||||
id: None,
|
||||
|
@ -45,7 +46,7 @@ async fn test_create_read_and_edit_local_article() -> MyResult<()> {
|
|||
assert!(not_found.is_err());
|
||||
|
||||
// edit article
|
||||
let edit_form = EditArticleData {
|
||||
let edit_form = EditArticleForm {
|
||||
article_id: create_res.article.id,
|
||||
new_text: "Lorem Ipsum 2\n".to_string(),
|
||||
summary: "summary".to_string(),
|
||||
|
@ -57,7 +58,7 @@ async fn test_create_read_and_edit_local_article() -> MyResult<()> {
|
|||
assert_eq!(2, edit_res.edits.len());
|
||||
assert_eq!(edit_form.summary, edit_res.edits[1].edit.summary);
|
||||
|
||||
let search_form = SearchArticleData {
|
||||
let search_form = SearchArticleForm {
|
||||
query: create_form.title.clone(),
|
||||
};
|
||||
let search_res = data.alpha.search(&search_form).await?;
|
||||
|
@ -66,7 +67,7 @@ async fn test_create_read_and_edit_local_article() -> MyResult<()> {
|
|||
|
||||
let list_articles = data
|
||||
.alpha
|
||||
.list_articles(ListArticlesData {
|
||||
.list_articles(ListArticlesForm {
|
||||
only_local: Some(false),
|
||||
})
|
||||
.await?;
|
||||
|
@ -81,7 +82,7 @@ async fn test_create_duplicate_article() -> MyResult<()> {
|
|||
let data = TestData::start().await;
|
||||
|
||||
// create article
|
||||
let create_form = CreateArticleData {
|
||||
let create_form = CreateArticleForm {
|
||||
title: "Manu_Chao".to_string(),
|
||||
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
||||
summary: "create article".to_string(),
|
||||
|
@ -127,7 +128,7 @@ async fn test_synchronize_articles() -> MyResult<()> {
|
|||
let data = TestData::start().await;
|
||||
|
||||
// create article on alpha
|
||||
let create_form = CreateArticleData {
|
||||
let create_form = CreateArticleForm {
|
||||
title: "Manu_Chao".to_string(),
|
||||
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
||||
summary: "create article".to_string(),
|
||||
|
@ -138,7 +139,7 @@ async fn test_synchronize_articles() -> MyResult<()> {
|
|||
assert!(create_res.article.local);
|
||||
|
||||
// edit the article
|
||||
let edit_form = EditArticleData {
|
||||
let edit_form = EditArticleForm {
|
||||
article_id: create_res.article.id,
|
||||
new_text: "Lorem Ipsum 2\n".to_string(),
|
||||
summary: "summary".to_string(),
|
||||
|
@ -153,7 +154,7 @@ async fn test_synchronize_articles() -> MyResult<()> {
|
|||
.resolve_instance(Url::parse(&format!("http://{}", &data.alpha.hostname))?)
|
||||
.await?;
|
||||
|
||||
let mut get_article_data = GetArticleData {
|
||||
let mut get_article_data = GetArticleForm {
|
||||
title: Some(create_res.article.title),
|
||||
domain: None,
|
||||
id: None,
|
||||
|
@ -185,7 +186,7 @@ async fn test_edit_local_article() -> MyResult<()> {
|
|||
.await?;
|
||||
|
||||
// create new article
|
||||
let create_form = CreateArticleData {
|
||||
let create_form = CreateArticleForm {
|
||||
title: "Manu_Chao".to_string(),
|
||||
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
||||
summary: "create article".to_string(),
|
||||
|
@ -195,7 +196,7 @@ async fn test_edit_local_article() -> MyResult<()> {
|
|||
assert!(create_res.article.local);
|
||||
|
||||
// article should be federated to alpha
|
||||
let get_article_data = GetArticleData {
|
||||
let get_article_data = GetArticleForm {
|
||||
title: Some(create_res.article.title.to_string()),
|
||||
domain: Some(beta_instance.domain),
|
||||
id: None,
|
||||
|
@ -207,7 +208,7 @@ async fn test_edit_local_article() -> MyResult<()> {
|
|||
assert_eq!(create_res.article.text, get_res.article.text);
|
||||
|
||||
// edit the article
|
||||
let edit_form = EditArticleData {
|
||||
let edit_form = EditArticleForm {
|
||||
article_id: create_res.article.id,
|
||||
new_text: "Lorem Ipsum 2\n".to_string(),
|
||||
summary: "summary".to_string(),
|
||||
|
@ -246,7 +247,7 @@ async fn test_edit_remote_article() -> MyResult<()> {
|
|||
.await?;
|
||||
|
||||
// create new article
|
||||
let create_form = CreateArticleData {
|
||||
let create_form = CreateArticleForm {
|
||||
title: "Manu_Chao".to_string(),
|
||||
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
||||
summary: "create article".to_string(),
|
||||
|
@ -256,7 +257,7 @@ async fn test_edit_remote_article() -> MyResult<()> {
|
|||
assert!(create_res.article.local);
|
||||
|
||||
// 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()),
|
||||
domain: Some(beta_id_on_alpha.domain),
|
||||
id: None,
|
||||
|
@ -269,7 +270,7 @@ async fn test_edit_remote_article() -> MyResult<()> {
|
|||
assert_eq!(1, get_res.edits.len());
|
||||
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()),
|
||||
domain: Some(beta_id_on_gamma.domain),
|
||||
id: None,
|
||||
|
@ -281,7 +282,7 @@ async fn test_edit_remote_article() -> MyResult<()> {
|
|||
assert_eq!(create_res.article.title, get_res.article.title);
|
||||
assert_eq!(create_res.article.text, get_res.article.text);
|
||||
|
||||
let edit_form = EditArticleData {
|
||||
let edit_form = EditArticleForm {
|
||||
article_id: get_res.article.id,
|
||||
new_text: "Lorem Ipsum 2\n".to_string(),
|
||||
summary: "summary".to_string(),
|
||||
|
@ -317,7 +318,7 @@ async fn test_local_edit_conflict() -> MyResult<()> {
|
|||
let data = TestData::start().await;
|
||||
|
||||
// create new article
|
||||
let create_form = CreateArticleData {
|
||||
let create_form = CreateArticleForm {
|
||||
title: "Manu_Chao".to_string(),
|
||||
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
||||
summary: "create article".to_string(),
|
||||
|
@ -327,7 +328,7 @@ async fn test_local_edit_conflict() -> MyResult<()> {
|
|||
assert!(create_res.article.local);
|
||||
|
||||
// one user edits article
|
||||
let edit_form = EditArticleData {
|
||||
let edit_form = EditArticleForm {
|
||||
article_id: create_res.article.id,
|
||||
new_text: "Lorem Ipsum\n".to_string(),
|
||||
summary: "summary".to_string(),
|
||||
|
@ -339,7 +340,7 @@ async fn test_local_edit_conflict() -> MyResult<()> {
|
|||
assert_eq!(2, edit_res.edits.len());
|
||||
|
||||
// another user edits article, without being aware of previous edit
|
||||
let edit_form = EditArticleData {
|
||||
let edit_form = EditArticleForm {
|
||||
article_id: create_res.article.id,
|
||||
new_text: "Ipsum Lorem\n".to_string(),
|
||||
summary: "summary".to_string(),
|
||||
|
@ -357,7 +358,7 @@ async fn test_local_edit_conflict() -> MyResult<()> {
|
|||
assert_eq!(1, conflicts.len());
|
||||
assert_eq!(conflicts[0], edit_res);
|
||||
|
||||
let edit_form = EditArticleData {
|
||||
let edit_form = EditArticleForm {
|
||||
article_id: create_res.article.id,
|
||||
new_text: "Lorem Ipsum and Ipsum Lorem\n".to_string(),
|
||||
summary: "summary".to_string(),
|
||||
|
@ -383,7 +384,7 @@ async fn test_federated_edit_conflict() -> MyResult<()> {
|
|||
.await?;
|
||||
|
||||
// create new article
|
||||
let create_form = CreateArticleData {
|
||||
let create_form = CreateArticleForm {
|
||||
title: "Manu_Chao".to_string(),
|
||||
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
||||
summary: "create article".to_string(),
|
||||
|
@ -400,7 +401,7 @@ async fn test_federated_edit_conflict() -> MyResult<()> {
|
|||
assert_eq!(create_res.article.text, resolve_res.article.text);
|
||||
|
||||
// alpha edits article
|
||||
let get_article_data = GetArticleData {
|
||||
let get_article_data = GetArticleForm {
|
||||
title: Some(create_form.title.to_string()),
|
||||
domain: Some(beta_id_on_alpha.domain),
|
||||
id: None,
|
||||
|
@ -408,7 +409,7 @@ async fn test_federated_edit_conflict() -> MyResult<()> {
|
|||
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[0].edit.hash, &get_res.edits[0].edit.hash);
|
||||
let edit_form = EditArticleData {
|
||||
let edit_form = EditArticleForm {
|
||||
article_id: get_res.article.id,
|
||||
new_text: "Lorem Ipsum\n".to_string(),
|
||||
summary: "summary".to_string(),
|
||||
|
@ -427,7 +428,7 @@ async fn test_federated_edit_conflict() -> MyResult<()> {
|
|||
|
||||
// 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
|
||||
let edit_form = EditArticleData {
|
||||
let edit_form = EditArticleForm {
|
||||
article_id: resolve_res.article.id,
|
||||
new_text: "aaaa\n".to_string(),
|
||||
summary: "summary".to_string(),
|
||||
|
@ -443,7 +444,7 @@ async fn test_federated_edit_conflict() -> MyResult<()> {
|
|||
assert_eq!(1, conflicts.len());
|
||||
|
||||
// resolve the conflict
|
||||
let edit_form = EditArticleData {
|
||||
let edit_form = EditArticleForm {
|
||||
article_id: resolve_res.article.id,
|
||||
new_text: "aaaa\n".to_string(),
|
||||
summary: "summary".to_string(),
|
||||
|
@ -465,7 +466,7 @@ async fn test_overlapping_edits_no_conflict() -> MyResult<()> {
|
|||
let data = TestData::start().await;
|
||||
|
||||
// create new article
|
||||
let create_form = CreateArticleData {
|
||||
let create_form = CreateArticleForm {
|
||||
title: "Manu_Chao".to_string(),
|
||||
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
||||
summary: "create article".to_string(),
|
||||
|
@ -475,7 +476,7 @@ async fn test_overlapping_edits_no_conflict() -> MyResult<()> {
|
|||
assert!(create_res.article.local);
|
||||
|
||||
// one user edits article
|
||||
let edit_form = EditArticleData {
|
||||
let edit_form = EditArticleForm {
|
||||
article_id: create_res.article.id,
|
||||
new_text: "my\nexample\ntext\n".to_string(),
|
||||
summary: "summary".to_string(),
|
||||
|
@ -487,7 +488,7 @@ async fn test_overlapping_edits_no_conflict() -> MyResult<()> {
|
|||
assert_eq!(2, edit_res.edits.len());
|
||||
|
||||
// another user edits article, without being aware of previous edit
|
||||
let edit_form = EditArticleData {
|
||||
let edit_form = EditArticleForm {
|
||||
article_id: create_res.article.id,
|
||||
new_text: "some\nexample\narticle\n".to_string(),
|
||||
summary: "summary".to_string(),
|
||||
|
@ -508,7 +509,7 @@ async fn test_fork_article() -> MyResult<()> {
|
|||
let data = TestData::start().await;
|
||||
|
||||
// create article
|
||||
let create_form = CreateArticleData {
|
||||
let create_form = CreateArticleForm {
|
||||
title: "Manu_Chao".to_string(),
|
||||
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
||||
summary: "create article".to_string(),
|
||||
|
@ -526,7 +527,7 @@ async fn test_fork_article() -> MyResult<()> {
|
|||
assert_eq!(create_res.edits.len(), resolve_res.edits.len());
|
||||
|
||||
// fork the article to local instance
|
||||
let fork_form = ForkArticleData {
|
||||
let fork_form = ForkArticleForm {
|
||||
article_id: resolved_article.id,
|
||||
new_title: resolved_article.title.clone(),
|
||||
};
|
||||
|
@ -546,7 +547,7 @@ async fn test_fork_article() -> MyResult<()> {
|
|||
assert_eq!(forked_article.instance_id, beta_instance.instance.id);
|
||||
|
||||
// now search returns two articles for this title (original and forked)
|
||||
let search_form = SearchArticleData {
|
||||
let search_form = SearchArticleForm {
|
||||
query: create_form.title.clone(),
|
||||
};
|
||||
let search_res = data.beta.search(&search_form).await?;
|
||||
|
@ -560,20 +561,20 @@ async fn test_user_registration_login() -> MyResult<()> {
|
|||
let data = TestData::start().await;
|
||||
let username = "my_user";
|
||||
let password = "hunter2";
|
||||
let register_data = RegisterUserData {
|
||||
let register_data = RegisterUserForm {
|
||||
username: username.to_string(),
|
||||
password: password.to_string(),
|
||||
};
|
||||
data.alpha.register(register_data).await?;
|
||||
|
||||
let login_data = LoginUserData {
|
||||
let login_data = LoginUserForm {
|
||||
username: username.to_string(),
|
||||
password: "asd123".to_string(),
|
||||
};
|
||||
let invalid_login = data.alpha.login(login_data).await;
|
||||
assert!(invalid_login.is_err());
|
||||
|
||||
let login_data = LoginUserData {
|
||||
let login_data = LoginUserForm {
|
||||
username: username.to_string(),
|
||||
password: password.to_string(),
|
||||
};
|
||||
|
@ -595,7 +596,7 @@ async fn test_user_profile() -> MyResult<()> {
|
|||
let data = TestData::start().await;
|
||||
|
||||
// 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(),
|
||||
text: TEST_ARTICLE_DEFAULT_TEXT.to_string(),
|
||||
summary: "create article".to_string(),
|
||||
|
@ -607,7 +608,7 @@ async fn test_user_profile() -> MyResult<()> {
|
|||
let domain = extract_domain(&data.alpha.my_profile().await?.person.ap_id);
|
||||
|
||||
// Now we can fetch the remote user from local api
|
||||
let params = GetUserData {
|
||||
let params = GetUserForm {
|
||||
name: "alpha".to_string(),
|
||||
domain: Some(domain),
|
||||
};
|
||||
|
@ -617,3 +618,50 @@ async fn test_user_profile() -> MyResult<()> {
|
|||
|
||||
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