mirror of
https://github.com/Nutomic/ibis.git
synced 2024-11-22 01:01:09 +00:00
various changes
- only admin can edit main page - adjust config values - better text for default page
This commit is contained in:
parent
0c45fe3eba
commit
0f0f83bc3a
15 changed files with 57 additions and 32 deletions
|
@ -1,3 +1,3 @@
|
||||||
[setup]
|
[federation]
|
||||||
admin_username = "ibis"
|
# necessary for auth cookie to work
|
||||||
admin_password = "ibis"
|
domain = "127.0.0.1:8080"
|
||||||
|
|
|
@ -9,8 +9,8 @@ registration_open = true
|
||||||
|
|
||||||
# Details of the initial admin account
|
# Details of the initial admin account
|
||||||
[setup]
|
[setup]
|
||||||
admin_username = "admin"
|
admin_username = "ibis"
|
||||||
admin_password = "hunter2"
|
admin_password = "ibis"
|
||||||
|
|
||||||
[federation]
|
[federation]
|
||||||
# Domain name of the instance, mandatory for federation
|
# Domain name of the instance, mandatory for federation
|
||||||
|
|
|
@ -6,12 +6,13 @@ use crate::backend::error::MyResult;
|
||||||
use crate::backend::federation::activities::create_article::CreateArticle;
|
use crate::backend::federation::activities::create_article::CreateArticle;
|
||||||
use crate::backend::federation::activities::submit_article_update;
|
use crate::backend::federation::activities::submit_article_update;
|
||||||
use crate::backend::utils::generate_article_version;
|
use crate::backend::utils::generate_article_version;
|
||||||
|
use crate::common::validation::can_edit_article;
|
||||||
|
use crate::common::LocalUserView;
|
||||||
use crate::common::{ApiConflict, ResolveObject};
|
use crate::common::{ApiConflict, ResolveObject};
|
||||||
use crate::common::{ArticleView, DbArticle, DbEdit};
|
use crate::common::{ArticleView, DbArticle, DbEdit};
|
||||||
use crate::common::{CreateArticleData, EditArticleData, EditVersion, ForkArticleData};
|
use crate::common::{CreateArticleData, EditArticleData, EditVersion, ForkArticleData};
|
||||||
use crate::common::{DbInstance, SearchArticleData};
|
use crate::common::{DbInstance, SearchArticleData};
|
||||||
use crate::common::{GetArticleData, ListArticlesData};
|
use crate::common::{GetArticleData, ListArticlesData};
|
||||||
use crate::common::{LocalUserView, MAIN_PAGE_NAME};
|
|
||||||
use activitypub_federation::config::Data;
|
use activitypub_federation::config::Data;
|
||||||
use activitypub_federation::fetch::object_id::ObjectId;
|
use activitypub_federation::fetch::object_id::ObjectId;
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
@ -91,12 +92,7 @@ pub(in crate::backend::api) async fn edit_article(
|
||||||
if edit_form.summary.is_empty() {
|
if edit_form.summary.is_empty() {
|
||||||
return Err(anyhow!("No summary given").into());
|
return Err(anyhow!("No summary given").into());
|
||||||
}
|
}
|
||||||
if original_article.article.local
|
can_edit_article(&original_article.article, user.local_user.admin)?;
|
||||||
&& original_article.article.title == MAIN_PAGE_NAME
|
|
||||||
&& !user.local_user.admin
|
|
||||||
{
|
|
||||||
return Err(anyhow!("Only admin can edit main page").into());
|
|
||||||
}
|
|
||||||
// ensure trailing newline for clean diffs
|
// ensure trailing newline for clean diffs
|
||||||
if !edit_form.new_text.ends_with('\n') {
|
if !edit_form.new_text.ends_with('\n') {
|
||||||
edit_form.new_text.push('\n');
|
edit_form.new_text.push('\n');
|
||||||
|
|
|
@ -26,9 +26,11 @@ pub struct IbisConfig {
|
||||||
#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Document, SmartDefault)]
|
#[derive(Debug, Deserialize, PartialEq, Eq, Clone, Document, SmartDefault)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct IbisConfigSetup {
|
pub struct IbisConfigSetup {
|
||||||
#[doku(example = "admin")]
|
#[default("ibis")]
|
||||||
|
#[doku(example = "ibis")]
|
||||||
pub admin_username: String,
|
pub admin_username: String,
|
||||||
#[doku(example = "hunter2")]
|
#[default("ibis")]
|
||||||
|
#[doku(example = "ibis")]
|
||||||
pub admin_password: String,
|
pub admin_password: String,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
use crate::backend::config::IbisConfig;
|
||||||
use crate::backend::database::schema::jwt_secret;
|
use crate::backend::database::schema::jwt_secret;
|
||||||
use crate::backend::error::MyResult;
|
use crate::backend::error::MyResult;
|
||||||
use crate::config::IbisConfig;
|
|
||||||
use diesel::PgConnection;
|
use diesel::PgConnection;
|
||||||
use diesel::{QueryDsl, RunQueryDsl};
|
use diesel::{QueryDsl, RunQueryDsl};
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
|
@ -12,6 +12,7 @@ use activitypub_federation::{
|
||||||
traits::{ActivityHandler, Object},
|
traits::{ActivityHandler, Object},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::common::validation::can_edit_article;
|
||||||
use crate::common::DbArticle;
|
use crate::common::DbArticle;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -67,7 +68,9 @@ impl ActivityHandler for UpdateLocalArticle {
|
||||||
self.actor.inner()
|
self.actor.inner()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn verify(&self, _data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
async fn verify(&self, data: &Data<Self::DataType>) -> Result<(), Self::Error> {
|
||||||
|
let article = DbArticle::read_from_ap_id(&self.object.id, &data.db_connection)?;
|
||||||
|
can_edit_article(&article, false)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
use crate::backend::config::IbisConfig;
|
||||||
use crate::backend::database::IbisData;
|
use crate::backend::database::IbisData;
|
||||||
use crate::config::IbisConfig;
|
|
||||||
use activitypub_federation::activity_sending::SendActivityTask;
|
use activitypub_federation::activity_sending::SendActivityTask;
|
||||||
use activitypub_federation::config::{Data, UrlVerifier};
|
use activitypub_federation::config::{Data, UrlVerifier};
|
||||||
use activitypub_federation::error::Error as ActivityPubError;
|
use activitypub_federation::error::Error as ActivityPubError;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::backend::config::IbisConfig;
|
||||||
use crate::backend::database::article::DbArticleForm;
|
use crate::backend::database::article::DbArticleForm;
|
||||||
use crate::backend::database::instance::DbInstanceForm;
|
use crate::backend::database::instance::DbInstanceForm;
|
||||||
use crate::backend::database::IbisData;
|
use crate::backend::database::IbisData;
|
||||||
|
@ -7,7 +8,6 @@ use crate::backend::federation::routes::federation_routes;
|
||||||
use crate::backend::federation::VerifyUrlData;
|
use crate::backend::federation::VerifyUrlData;
|
||||||
use crate::backend::utils::generate_activity_id;
|
use crate::backend::utils::generate_activity_id;
|
||||||
use crate::common::{DbArticle, DbInstance, DbPerson, MAIN_PAGE_NAME};
|
use crate::common::{DbArticle, DbInstance, DbPerson, MAIN_PAGE_NAME};
|
||||||
use crate::config::IbisConfig;
|
|
||||||
use crate::frontend::app::App;
|
use crate::frontend::app::App;
|
||||||
use activitypub_federation::config::{FederationConfig, FederationMiddleware};
|
use activitypub_federation::config::{FederationConfig, FederationMiddleware};
|
||||||
use activitypub_federation::fetch::collection_id::CollectionId;
|
use activitypub_federation::fetch::collection_id::CollectionId;
|
||||||
|
@ -33,6 +33,7 @@ use tower_http::cors::CorsLayer;
|
||||||
use tower_http::services::{ServeDir, ServeFile};
|
use tower_http::services::{ServeDir, ServeFile};
|
||||||
|
|
||||||
pub mod api;
|
pub mod api;
|
||||||
|
pub mod config;
|
||||||
pub mod database;
|
pub mod database;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod federation;
|
pub mod federation;
|
||||||
|
@ -103,6 +104,11 @@ pub async fn start(config: IbisConfig) -> MyResult<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MAIN_PAGE_DEFAULT_TEXT: &str = "Welcome to Ibis, the federated Wikipedia alternative!
|
||||||
|
|
||||||
|
This main page can only be edited by the admin. Use it as an introduction for new users, \
|
||||||
|
and to list interesting articles.";
|
||||||
|
|
||||||
fn setup(data: &IbisData) -> Result<(), Error> {
|
fn setup(data: &IbisData) -> Result<(), Error> {
|
||||||
let domain = &data.config.federation.domain;
|
let domain = &data.config.federation.domain;
|
||||||
let ap_id = ObjectId::parse(&format!("http://{domain}"))?;
|
let ap_id = ObjectId::parse(&format!("http://{domain}"))?;
|
||||||
|
@ -124,7 +130,7 @@ fn setup(data: &IbisData) -> Result<(), Error> {
|
||||||
// Create the main page which is shown by default
|
// Create the main page which is shown by default
|
||||||
let form = DbArticleForm {
|
let form = DbArticleForm {
|
||||||
title: MAIN_PAGE_NAME.to_string(),
|
title: MAIN_PAGE_NAME.to_string(),
|
||||||
text: "Hello world!".to_string(),
|
text: MAIN_PAGE_DEFAULT_TEXT.to_string(),
|
||||||
ap_id: ObjectId::parse(&format!("http://{domain}/article/{MAIN_PAGE_NAME}"))?,
|
ap_id: ObjectId::parse(&format!("http://{domain}/article/{MAIN_PAGE_NAME}"))?,
|
||||||
instance_id: instance.id,
|
instance_id: instance.id,
|
||||||
local: true,
|
local: true,
|
||||||
|
|
|
@ -8,14 +8,17 @@ use rand::{distributions::Alphanumeric, thread_rng, Rng};
|
||||||
use url::{ParseError, Url};
|
use url::{ParseError, Url};
|
||||||
|
|
||||||
pub fn generate_activity_id(domain: &Url) -> Result<Url, ParseError> {
|
pub fn generate_activity_id(domain: &Url) -> Result<Url, ParseError> {
|
||||||
let port = domain.port().unwrap();
|
let port = match domain.port() {
|
||||||
|
Some(p) => format!(":{p}"),
|
||||||
|
None => String::new(),
|
||||||
|
};
|
||||||
let domain = domain.host_str().unwrap();
|
let domain = domain.host_str().unwrap();
|
||||||
let id: String = thread_rng()
|
let id: String = thread_rng()
|
||||||
.sample_iter(&Alphanumeric)
|
.sample_iter(&Alphanumeric)
|
||||||
.take(7)
|
.take(7)
|
||||||
.map(char::from)
|
.map(char::from)
|
||||||
.collect();
|
.collect();
|
||||||
Url::parse(&format!("http://{}:{}/objects/{}", domain, port, id))
|
Url::parse(&format!("http://{}{}/objects/{}", domain, port, id))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starting from empty string, apply edits until the specified version is reached. If no version is
|
/// Starting from empty string, apply edits until the specified version is reached. If no version is
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
pub mod validation;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
|
10
src/common/validation.rs
Normal file
10
src/common/validation.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
use crate::common::{DbArticle, MAIN_PAGE_NAME};
|
||||||
|
use anyhow::anyhow;
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
pub fn can_edit_article(article: &DbArticle, is_admin: bool) -> Result<()> {
|
||||||
|
if article.local && article.title == MAIN_PAGE_NAME && !is_admin {
|
||||||
|
return Err(anyhow!("Only admin can edit main page"));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::common::validation::can_edit_article;
|
||||||
use crate::common::ArticleView;
|
use crate::common::ArticleView;
|
||||||
use crate::frontend::app::GlobalState;
|
use crate::frontend::app::GlobalState;
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
|
@ -9,15 +10,18 @@ pub fn ArticleNav(article: Resource<Option<String>, ArticleView>) -> impl IntoVi
|
||||||
view! {
|
view! {
|
||||||
<Suspense>
|
<Suspense>
|
||||||
{move || article.get().map(|article| {
|
{move || article.get().map(|article| {
|
||||||
let title = article.article.title;
|
let title = article.article.title.clone();
|
||||||
view!{
|
view!{
|
||||||
<nav class="inner">
|
<nav class="inner">
|
||||||
<A href={format!("/article/{title}")}>"Read"</A>
|
<A href={format!("/article/{title}")}>"Read"</A>
|
||||||
<A href={format!("/article/{title}/history")}>"History"</A>
|
<A href={format!("/article/{title}/history")}>"History"</A>
|
||||||
<Show when=move || global_state.with(|state| state.my_profile.is_some())>
|
<Show when=move || global_state.with(|state| {
|
||||||
<A href={format!("/article/{title}/edit")}>"Edit"</A>
|
let is_admin = state.my_profile.as_ref().map(|p| p.local_user.admin).unwrap_or(false);
|
||||||
</Show>
|
state.my_profile.is_some() && can_edit_article(&article.article, is_admin).is_ok()
|
||||||
</nav>
|
})>
|
||||||
|
<A href={format!("/article/{title}/edit")}>"Edit"</A>
|
||||||
|
</Show>
|
||||||
|
</nav>
|
||||||
}})}
|
}})}
|
||||||
</Suspense>
|
</Suspense>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
pub mod backend;
|
pub mod backend;
|
||||||
pub mod common;
|
pub mod common;
|
||||||
pub mod config;
|
|
||||||
pub mod frontend;
|
pub mod frontend;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
pub async fn main() -> ibis_lib::backend::error::MyResult<()> {
|
pub async fn main() -> ibis_lib::backend::error::MyResult<()> {
|
||||||
use config::Config;
|
use config::Config;
|
||||||
use ibis_lib::config::IbisConfig;
|
use ibis_lib::backend::config::IbisConfig;
|
||||||
use log::LevelFilter;
|
use log::LevelFilter;
|
||||||
|
|
||||||
if std::env::args().collect::<Vec<_>>().get(1) == Some(&"--print-config".to_string()) {
|
if std::env::args().collect::<Vec<_>>().get(1) == Some(&"--print-config".to_string()) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
use ibis_lib::backend::config::{IbisConfig, IbisConfigFederation};
|
||||||
use ibis_lib::backend::start;
|
use ibis_lib::backend::start;
|
||||||
use ibis_lib::common::RegisterUserData;
|
use ibis_lib::common::RegisterUserData;
|
||||||
use ibis_lib::config::{IbisConfig, IbisConfigFederation};
|
|
||||||
use ibis_lib::frontend::api::ApiClient;
|
use ibis_lib::frontend::api::ApiClient;
|
||||||
use ibis_lib::frontend::error::MyResult;
|
use ibis_lib::frontend::error::MyResult;
|
||||||
use reqwest::ClientBuilder;
|
use reqwest::ClientBuilder;
|
||||||
|
|
Loading…
Reference in a new issue