add basic instance details page (untested)

This commit is contained in:
Felix Ableitner 2024-02-01 16:46:36 +01:00
parent 862297638f
commit f2180e5e0b
16 changed files with 91 additions and 52 deletions

View File

@ -1,6 +1,7 @@
create table instance (
id serial primary key,
ap_id varchar(255) not null unique,
description text,
inbox_url text not null,
articles_url varchar(255) not null unique,
public_key text not null,
@ -23,7 +24,8 @@ create table person (
create table local_user (
id serial primary key,
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 (

View File

@ -58,7 +58,8 @@ pub(in crate::backend::api) async fn register_user(
jar: CookieJar,
Form(form): Form<RegisterUserData>,
) -> 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 jar = jar.add(create_cookie(token, &data));
Ok((jar, Json(user)))

View File

@ -19,6 +19,7 @@ use std::sync::Mutex;
#[diesel(table_name = instance, check_for_backend(diesel::pg::Pg))]
pub struct DbInstanceForm {
pub ap_id: ObjectId<DbInstance>,
pub description: Option<String>,
pub articles_url: CollectionId<DbArticleCollection>,
pub inbox_url: String,
pub public_key: String,

View File

@ -43,6 +43,7 @@ diesel::table! {
id -> Int4,
#[max_length = 255]
ap_id -> Varchar,
description -> Nullable<Text>,
inbox_url -> Text,
#[max_length = 255]
articles_url -> Varchar,
@ -74,6 +75,7 @@ diesel::table! {
id -> Int4,
password_encrypted -> Text,
person_id -> Int4,
admin -> Bool,
}
}

View File

@ -19,6 +19,7 @@ use std::sync::Mutex;
pub struct DbLocalUserForm {
pub password_encrypted: String,
pub person_id: i32,
pub admin: bool,
}
#[derive(Debug, Clone, Insertable, AsChangeset)]
@ -52,6 +53,7 @@ impl DbPerson {
pub fn create_local(
username: String,
password: String,
admin: bool,
data: &Data<MyDataHandle>,
) -> MyResult<LocalUserView> {
let mut conn = data.db_connection.lock().unwrap();
@ -76,6 +78,7 @@ impl DbPerson {
let local_user_form = DbLocalUserForm {
password_encrypted: hash(password, DEFAULT_COST)?,
person_id: person.id,
admin,
};
let local_user = insert_into(local_user::table)

View File

@ -27,6 +27,7 @@ pub struct ApubInstance {
#[serde(rename = "type")]
kind: ServiceType,
id: ObjectId<DbInstance>,
content: Option<String>,
articles: CollectionId<DbArticleCollection>,
inbox: Url,
public_key: PublicKey,
@ -90,6 +91,7 @@ impl Object for DbInstance {
Ok(ApubInstance {
kind: Default::default(),
id: self.ap_id.clone(),
content: self.description.clone(),
articles: self.articles_url.clone(),
inbox: Url::parse(&self.inbox_url)?,
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> {
let form = DbInstanceForm {
ap_id: json.id,
description: json.content,
articles_url: json.articles,
inbox_url: json.inbox.to_string(),
public_key: json.public_key.public_key_pem,

View File

@ -65,6 +65,7 @@ pub async fn start(hostname: &str, database_url: &str) -> MyResult<()> {
let keypair = generate_actor_keypair()?;
let form = DbInstanceForm {
ap_id,
description: Some("New Ibis instance".to_string()),
articles_url,
inbox_url,
public_key: keypair.public_key,

View File

@ -133,6 +133,7 @@ pub struct DbLocalUser {
pub id: i32,
pub password_encrypted: String,
pub person_id: i32,
pub admin: bool,
}
/// Federation related data from a local or remote user.
@ -219,6 +220,7 @@ pub struct DbInstance {
pub ap_id: ObjectId<DbInstance>,
#[cfg(not(feature = "ssr"))]
pub ap_id: String,
pub description: Option<String>,
#[cfg(feature = "ssr")]
pub articles_url: CollectionId<DbArticleCollection>,
#[cfg(not(feature = "ssr"))]

View File

@ -7,13 +7,13 @@ use crate::frontend::pages::article::history::ArticleHistory;
use crate::frontend::pages::article::list::ListArticles;
use crate::frontend::pages::article::read::ReadArticle;
use crate::frontend::pages::diff::EditDiff;
use crate::frontend::pages::instance_details::InstanceDetails;
use crate::frontend::pages::login::Login;
use crate::frontend::pages::register::Register;
use crate::frontend::pages::search::Search;
use crate::frontend::pages::Page;
use leptos::{
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::*;
@ -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]
@ -71,15 +82,16 @@ pub fn App() -> impl IntoView {
<Nav />
<main>
<Routes>
<Route path={Page::Home.path()} view=ReadArticle/>
<Route path="/" view=ReadArticle/>
<Route path="/article/:title" view=ReadArticle/>
<Route path="/article/:title/edit" view=EditArticle/>
<Route path="/article/:title/history" view=ArticleHistory/>
<Route path="/article/:title/diff/:hash" view=EditDiff/>
<Route path="/article/create" view=CreateArticle/>
<Route path="/article/list" view=ListArticles/>
<Route path={Page::Login.path()} view=Login/>
<Route path={Page::Register.path()} view=Register/>
<Route path="/instance/:hostname" view=InstanceDetails/>
<Route path="/login" view=Login/>
<Route path="/register" view=Register/>
<Route path="/search" view=Search/>
</Routes>
</main>

View File

@ -4,7 +4,7 @@ use leptos::*;
use leptos_router::*;
#[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();
view! {
<Suspense>

View File

@ -8,8 +8,8 @@ use leptos_router::use_params_map;
#[component]
pub fn EditArticle() -> impl IntoView {
let params = use_params_map();
let title = params.get_untracked().get("title").cloned();
let article = article_resource(title.unwrap());
let title = move || params.get().get("title").cloned();
let article = article_resource(title);
let (text, set_text) = create_signal(String::new());
let (summary, set_summary) = create_signal(String::new());

View File

@ -6,8 +6,8 @@ use leptos_router::*;
#[component]
pub fn ArticleHistory() -> impl IntoView {
let params = use_params_map();
let title = params.get_untracked().get("title").cloned();
let article = article_resource(title.unwrap());
let title = move || params.get().get("title").cloned();
let article = article_resource(title);
view! {
<ArticleNav article=article/>

View File

@ -7,11 +7,7 @@ use markdown_it::MarkdownIt;
#[component]
pub fn ReadArticle() -> impl IntoView {
let params = use_params_map();
let title = params
.get_untracked()
.get("title")
.cloned()
.unwrap_or("Main_Page".to_string());
let title = move || params.get().get("title").cloned();
let article = article_resource(title);
view! {

View File

@ -6,7 +6,7 @@ use leptos_router::*;
#[component]
pub fn EditDiff() -> impl IntoView {
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);
view! {

View 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>
}
}

View File

@ -4,32 +4,16 @@ use leptos::{create_resource, Resource};
pub(crate) mod article;
pub(crate) mod diff;
pub mod login;
pub mod register;
pub(crate) mod instance_details;
pub(crate) mod login;
pub(crate) mod register;
pub(crate) mod search;
#[derive(Debug, Clone, Copy, Default)]
pub enum Page {
#[default]
Home,
Login,
Register,
}
impl Page {
pub fn path(&self) -> &'static str {
match self {
Self::Home => "/",
Self::Login => "/login",
Self::Register => "/register",
}
}
}
fn article_resource(title: String) -> Resource<String, ArticleView> {
create_resource(
move || title.clone(),
move |title| async move {
fn article_resource(
title: impl Fn() -> Option<String> + 'static,
) -> Resource<Option<String>, ArticleView> {
create_resource(title, move |title| async move {
let title = title.unwrap_or("Main_Page".to_string());
GlobalState::api_client()
.get_article(GetArticleData {
title: Some(title),
@ -38,6 +22,5 @@ fn article_resource(title: String) -> Resource<String, ArticleView> {
})
.await
.unwrap()
},
)
})
}