mirror of
https://github.com/Nutomic/ibis.git
synced 2024-11-22 08:21:09 +00:00
add basic instance details page (untested)
This commit is contained in:
parent
862297638f
commit
f2180e5e0b
16 changed files with 91 additions and 52 deletions
|
@ -1,6 +1,7 @@
|
||||||
create table instance (
|
create table instance (
|
||||||
id serial primary key,
|
id serial primary key,
|
||||||
ap_id varchar(255) not null unique,
|
ap_id varchar(255) not null unique,
|
||||||
|
description text,
|
||||||
inbox_url text not null,
|
inbox_url text not null,
|
||||||
articles_url varchar(255) not null unique,
|
articles_url varchar(255) not null unique,
|
||||||
public_key text not null,
|
public_key text not null,
|
||||||
|
@ -23,7 +24,8 @@ create table person (
|
||||||
create table local_user (
|
create table local_user (
|
||||||
id serial primary key,
|
id serial primary key,
|
||||||
password_encrypted text not null,
|
password_encrypted text not null,
|
||||||
person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL
|
person_id int REFERENCES person ON UPDATE CASCADE ON DELETE CASCADE NOT NULL,
|
||||||
|
admin bool not null
|
||||||
);
|
);
|
||||||
|
|
||||||
create table instance_follow (
|
create table instance_follow (
|
||||||
|
|
|
@ -58,7 +58,8 @@ pub(in crate::backend::api) async fn register_user(
|
||||||
jar: CookieJar,
|
jar: CookieJar,
|
||||||
Form(form): Form<RegisterUserData>,
|
Form(form): Form<RegisterUserData>,
|
||||||
) -> MyResult<(CookieJar, Json<LocalUserView>)> {
|
) -> MyResult<(CookieJar, Json<LocalUserView>)> {
|
||||||
let user = DbPerson::create_local(form.username, form.password, &data)?;
|
// TODO: make admin if its the first user account
|
||||||
|
let user = DbPerson::create_local(form.username, form.password, false, &data)?;
|
||||||
let token = generate_login_token(&user.local_user, &data)?;
|
let token = generate_login_token(&user.local_user, &data)?;
|
||||||
let jar = jar.add(create_cookie(token, &data));
|
let jar = jar.add(create_cookie(token, &data));
|
||||||
Ok((jar, Json(user)))
|
Ok((jar, Json(user)))
|
||||||
|
|
|
@ -19,6 +19,7 @@ use std::sync::Mutex;
|
||||||
#[diesel(table_name = instance, check_for_backend(diesel::pg::Pg))]
|
#[diesel(table_name = instance, check_for_backend(diesel::pg::Pg))]
|
||||||
pub struct DbInstanceForm {
|
pub struct DbInstanceForm {
|
||||||
pub ap_id: ObjectId<DbInstance>,
|
pub ap_id: ObjectId<DbInstance>,
|
||||||
|
pub description: Option<String>,
|
||||||
pub articles_url: CollectionId<DbArticleCollection>,
|
pub articles_url: CollectionId<DbArticleCollection>,
|
||||||
pub inbox_url: String,
|
pub inbox_url: String,
|
||||||
pub public_key: String,
|
pub public_key: String,
|
||||||
|
|
|
@ -43,6 +43,7 @@ diesel::table! {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
#[max_length = 255]
|
#[max_length = 255]
|
||||||
ap_id -> Varchar,
|
ap_id -> Varchar,
|
||||||
|
description -> Nullable<Text>,
|
||||||
inbox_url -> Text,
|
inbox_url -> Text,
|
||||||
#[max_length = 255]
|
#[max_length = 255]
|
||||||
articles_url -> Varchar,
|
articles_url -> Varchar,
|
||||||
|
@ -74,6 +75,7 @@ diesel::table! {
|
||||||
id -> Int4,
|
id -> Int4,
|
||||||
password_encrypted -> Text,
|
password_encrypted -> Text,
|
||||||
person_id -> Int4,
|
person_id -> Int4,
|
||||||
|
admin -> Bool,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ use std::sync::Mutex;
|
||||||
pub struct DbLocalUserForm {
|
pub struct DbLocalUserForm {
|
||||||
pub password_encrypted: String,
|
pub password_encrypted: String,
|
||||||
pub person_id: i32,
|
pub person_id: i32,
|
||||||
|
pub admin: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Insertable, AsChangeset)]
|
#[derive(Debug, Clone, Insertable, AsChangeset)]
|
||||||
|
@ -52,6 +53,7 @@ impl DbPerson {
|
||||||
pub fn create_local(
|
pub fn create_local(
|
||||||
username: String,
|
username: String,
|
||||||
password: String,
|
password: String,
|
||||||
|
admin: bool,
|
||||||
data: &Data<MyDataHandle>,
|
data: &Data<MyDataHandle>,
|
||||||
) -> MyResult<LocalUserView> {
|
) -> MyResult<LocalUserView> {
|
||||||
let mut conn = data.db_connection.lock().unwrap();
|
let mut conn = data.db_connection.lock().unwrap();
|
||||||
|
@ -76,6 +78,7 @@ impl DbPerson {
|
||||||
let local_user_form = DbLocalUserForm {
|
let local_user_form = DbLocalUserForm {
|
||||||
password_encrypted: hash(password, DEFAULT_COST)?,
|
password_encrypted: hash(password, DEFAULT_COST)?,
|
||||||
person_id: person.id,
|
person_id: person.id,
|
||||||
|
admin,
|
||||||
};
|
};
|
||||||
|
|
||||||
let local_user = insert_into(local_user::table)
|
let local_user = insert_into(local_user::table)
|
||||||
|
|
|
@ -27,6 +27,7 @@ pub struct ApubInstance {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
kind: ServiceType,
|
kind: ServiceType,
|
||||||
id: ObjectId<DbInstance>,
|
id: ObjectId<DbInstance>,
|
||||||
|
content: Option<String>,
|
||||||
articles: CollectionId<DbArticleCollection>,
|
articles: CollectionId<DbArticleCollection>,
|
||||||
inbox: Url,
|
inbox: Url,
|
||||||
public_key: PublicKey,
|
public_key: PublicKey,
|
||||||
|
@ -90,6 +91,7 @@ impl Object for DbInstance {
|
||||||
Ok(ApubInstance {
|
Ok(ApubInstance {
|
||||||
kind: Default::default(),
|
kind: Default::default(),
|
||||||
id: self.ap_id.clone(),
|
id: self.ap_id.clone(),
|
||||||
|
content: self.description.clone(),
|
||||||
articles: self.articles_url.clone(),
|
articles: self.articles_url.clone(),
|
||||||
inbox: Url::parse(&self.inbox_url)?,
|
inbox: Url::parse(&self.inbox_url)?,
|
||||||
public_key: self.public_key(),
|
public_key: self.public_key(),
|
||||||
|
@ -108,6 +110,7 @@ impl Object for DbInstance {
|
||||||
async fn from_json(json: Self::Kind, data: &Data<Self::DataType>) -> Result<Self, Self::Error> {
|
async fn from_json(json: Self::Kind, data: &Data<Self::DataType>) -> Result<Self, Self::Error> {
|
||||||
let form = DbInstanceForm {
|
let form = DbInstanceForm {
|
||||||
ap_id: json.id,
|
ap_id: json.id,
|
||||||
|
description: json.content,
|
||||||
articles_url: json.articles,
|
articles_url: json.articles,
|
||||||
inbox_url: json.inbox.to_string(),
|
inbox_url: json.inbox.to_string(),
|
||||||
public_key: json.public_key.public_key_pem,
|
public_key: json.public_key.public_key_pem,
|
||||||
|
|
|
@ -65,6 +65,7 @@ pub async fn start(hostname: &str, database_url: &str) -> MyResult<()> {
|
||||||
let keypair = generate_actor_keypair()?;
|
let keypair = generate_actor_keypair()?;
|
||||||
let form = DbInstanceForm {
|
let form = DbInstanceForm {
|
||||||
ap_id,
|
ap_id,
|
||||||
|
description: Some("New Ibis instance".to_string()),
|
||||||
articles_url,
|
articles_url,
|
||||||
inbox_url,
|
inbox_url,
|
||||||
public_key: keypair.public_key,
|
public_key: keypair.public_key,
|
||||||
|
|
|
@ -133,6 +133,7 @@ pub struct DbLocalUser {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub password_encrypted: String,
|
pub password_encrypted: String,
|
||||||
pub person_id: i32,
|
pub person_id: i32,
|
||||||
|
pub admin: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Federation related data from a local or remote user.
|
/// Federation related data from a local or remote user.
|
||||||
|
@ -219,6 +220,7 @@ pub struct DbInstance {
|
||||||
pub ap_id: ObjectId<DbInstance>,
|
pub ap_id: ObjectId<DbInstance>,
|
||||||
#[cfg(not(feature = "ssr"))]
|
#[cfg(not(feature = "ssr"))]
|
||||||
pub ap_id: String,
|
pub ap_id: String,
|
||||||
|
pub description: Option<String>,
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
pub articles_url: CollectionId<DbArticleCollection>,
|
pub articles_url: CollectionId<DbArticleCollection>,
|
||||||
#[cfg(not(feature = "ssr"))]
|
#[cfg(not(feature = "ssr"))]
|
||||||
|
|
|
@ -7,13 +7,13 @@ use crate::frontend::pages::article::history::ArticleHistory;
|
||||||
use crate::frontend::pages::article::list::ListArticles;
|
use crate::frontend::pages::article::list::ListArticles;
|
||||||
use crate::frontend::pages::article::read::ReadArticle;
|
use crate::frontend::pages::article::read::ReadArticle;
|
||||||
use crate::frontend::pages::diff::EditDiff;
|
use crate::frontend::pages::diff::EditDiff;
|
||||||
|
use crate::frontend::pages::instance_details::InstanceDetails;
|
||||||
use crate::frontend::pages::login::Login;
|
use crate::frontend::pages::login::Login;
|
||||||
use crate::frontend::pages::register::Register;
|
use crate::frontend::pages::register::Register;
|
||||||
use crate::frontend::pages::search::Search;
|
use crate::frontend::pages::search::Search;
|
||||||
use crate::frontend::pages::Page;
|
|
||||||
use leptos::{
|
use leptos::{
|
||||||
component, create_local_resource, create_rw_signal, expect_context, provide_context,
|
component, create_local_resource, create_rw_signal, expect_context, provide_context,
|
||||||
use_context, view, IntoView, RwSignal, SignalGetUntracked, SignalUpdate,
|
use_context, view, IntoView, RwSignal, SignalGet, SignalGetUntracked, SignalUpdate,
|
||||||
};
|
};
|
||||||
use leptos_meta::provide_meta_context;
|
use leptos_meta::provide_meta_context;
|
||||||
use leptos_meta::*;
|
use leptos_meta::*;
|
||||||
|
@ -47,6 +47,17 @@ impl GlobalState {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_admin() -> fn() -> bool {
|
||||||
|
move || {
|
||||||
|
use_context::<RwSignal<GlobalState>>()
|
||||||
|
.expect("global state is provided")
|
||||||
|
.get()
|
||||||
|
.my_profile
|
||||||
|
.map(|p| p.local_user.admin)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
|
@ -71,15 +82,16 @@ pub fn App() -> impl IntoView {
|
||||||
<Nav />
|
<Nav />
|
||||||
<main>
|
<main>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path={Page::Home.path()} view=ReadArticle/>
|
<Route path="/" view=ReadArticle/>
|
||||||
<Route path="/article/:title" view=ReadArticle/>
|
<Route path="/article/:title" view=ReadArticle/>
|
||||||
<Route path="/article/:title/edit" view=EditArticle/>
|
<Route path="/article/:title/edit" view=EditArticle/>
|
||||||
<Route path="/article/:title/history" view=ArticleHistory/>
|
<Route path="/article/:title/history" view=ArticleHistory/>
|
||||||
<Route path="/article/:title/diff/:hash" view=EditDiff/>
|
<Route path="/article/:title/diff/:hash" view=EditDiff/>
|
||||||
<Route path="/article/create" view=CreateArticle/>
|
<Route path="/article/create" view=CreateArticle/>
|
||||||
<Route path="/article/list" view=ListArticles/>
|
<Route path="/article/list" view=ListArticles/>
|
||||||
<Route path={Page::Login.path()} view=Login/>
|
<Route path="/instance/:hostname" view=InstanceDetails/>
|
||||||
<Route path={Page::Register.path()} view=Register/>
|
<Route path="/login" view=Login/>
|
||||||
|
<Route path="/register" view=Register/>
|
||||||
<Route path="/search" view=Search/>
|
<Route path="/search" view=Search/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -4,7 +4,7 @@ use leptos::*;
|
||||||
use leptos_router::*;
|
use leptos_router::*;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ArticleNav(article: Resource<String, ArticleView>) -> impl IntoView {
|
pub fn ArticleNav(article: Resource<Option<String>, ArticleView>) -> impl IntoView {
|
||||||
let global_state = use_context::<RwSignal<GlobalState>>().unwrap();
|
let global_state = use_context::<RwSignal<GlobalState>>().unwrap();
|
||||||
view! {
|
view! {
|
||||||
<Suspense>
|
<Suspense>
|
||||||
|
|
|
@ -8,8 +8,8 @@ use leptos_router::use_params_map;
|
||||||
#[component]
|
#[component]
|
||||||
pub fn EditArticle() -> impl IntoView {
|
pub fn EditArticle() -> impl IntoView {
|
||||||
let params = use_params_map();
|
let params = use_params_map();
|
||||||
let title = params.get_untracked().get("title").cloned();
|
let title = move || params.get().get("title").cloned();
|
||||||
let article = article_resource(title.unwrap());
|
let article = article_resource(title);
|
||||||
|
|
||||||
let (text, set_text) = create_signal(String::new());
|
let (text, set_text) = create_signal(String::new());
|
||||||
let (summary, set_summary) = create_signal(String::new());
|
let (summary, set_summary) = create_signal(String::new());
|
||||||
|
|
|
@ -6,8 +6,8 @@ use leptos_router::*;
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ArticleHistory() -> impl IntoView {
|
pub fn ArticleHistory() -> impl IntoView {
|
||||||
let params = use_params_map();
|
let params = use_params_map();
|
||||||
let title = params.get_untracked().get("title").cloned();
|
let title = move || params.get().get("title").cloned();
|
||||||
let article = article_resource(title.unwrap());
|
let article = article_resource(title);
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<ArticleNav article=article/>
|
<ArticleNav article=article/>
|
||||||
|
|
|
@ -7,11 +7,7 @@ use markdown_it::MarkdownIt;
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ReadArticle() -> impl IntoView {
|
pub fn ReadArticle() -> impl IntoView {
|
||||||
let params = use_params_map();
|
let params = use_params_map();
|
||||||
let title = params
|
let title = move || params.get().get("title").cloned();
|
||||||
.get_untracked()
|
|
||||||
.get("title")
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or("Main_Page".to_string());
|
|
||||||
let article = article_resource(title);
|
let article = article_resource(title);
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
|
|
|
@ -6,7 +6,7 @@ use leptos_router::*;
|
||||||
#[component]
|
#[component]
|
||||||
pub fn EditDiff() -> impl IntoView {
|
pub fn EditDiff() -> impl IntoView {
|
||||||
let params = use_params_map();
|
let params = use_params_map();
|
||||||
let title = params.get_untracked().get("title").cloned().unwrap();
|
let title = move || params.get().get("title").cloned();
|
||||||
let article = article_resource(title);
|
let article = article_resource(title);
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
|
|
33
src/frontend/pages/instance_details.rs
Normal file
33
src/frontend/pages/instance_details.rs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
use crate::common::DbInstance;
|
||||||
|
use crate::frontend::app::GlobalState;
|
||||||
|
use leptos::*;
|
||||||
|
use leptos_router::use_params_map;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn InstanceDetails() -> impl IntoView {
|
||||||
|
let params = use_params_map();
|
||||||
|
let hostname = move || params.get().get("hostname").cloned().unwrap();
|
||||||
|
let instance_profile = create_resource(hostname, move |hostname| async move {
|
||||||
|
let url = Url::parse(&format!("http://{hostname}")).unwrap();
|
||||||
|
GlobalState::api_client()
|
||||||
|
.resolve_instance(url)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: display list of articles from instance?
|
||||||
|
view! {
|
||||||
|
<Suspense fallback=|| view! { "Loading..." }> {
|
||||||
|
move || instance_profile.get().map(|instance: DbInstance| {
|
||||||
|
view! {
|
||||||
|
<h1>{instance.ap_id.to_string()}</h1>
|
||||||
|
<Show when=GlobalState::is_admin()>
|
||||||
|
<button text="Follow"/>
|
||||||
|
</Show>
|
||||||
|
<div>{instance.description}</div>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}</Suspense>
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,40 +4,23 @@ use leptos::{create_resource, Resource};
|
||||||
|
|
||||||
pub(crate) mod article;
|
pub(crate) mod article;
|
||||||
pub(crate) mod diff;
|
pub(crate) mod diff;
|
||||||
pub mod login;
|
pub(crate) mod instance_details;
|
||||||
pub mod register;
|
pub(crate) mod login;
|
||||||
|
pub(crate) mod register;
|
||||||
pub(crate) mod search;
|
pub(crate) mod search;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
fn article_resource(
|
||||||
pub enum Page {
|
title: impl Fn() -> Option<String> + 'static,
|
||||||
#[default]
|
) -> Resource<Option<String>, ArticleView> {
|
||||||
Home,
|
create_resource(title, move |title| async move {
|
||||||
Login,
|
let title = title.unwrap_or("Main_Page".to_string());
|
||||||
Register,
|
GlobalState::api_client()
|
||||||
}
|
.get_article(GetArticleData {
|
||||||
|
title: Some(title),
|
||||||
impl Page {
|
instance_id: None,
|
||||||
pub fn path(&self) -> &'static str {
|
id: None,
|
||||||
match self {
|
})
|
||||||
Self::Home => "/",
|
.await
|
||||||
Self::Login => "/login",
|
.unwrap()
|
||||||
Self::Register => "/register",
|
})
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn article_resource(title: String) -> Resource<String, ArticleView> {
|
|
||||||
create_resource(
|
|
||||||
move || title.clone(),
|
|
||||||
move |title| async move {
|
|
||||||
GlobalState::api_client()
|
|
||||||
.get_article(GetArticleData {
|
|
||||||
title: Some(title),
|
|
||||||
instance_id: None,
|
|
||||||
id: None,
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue