forked from alemi/upub
fix: token clearing, home timeline rebuild
it's kinda ugly because were passing a ton of signals to loginbox and we use two cookies but it works. also now logging in updates your home timeline so it doesn't need a reload
This commit is contained in:
parent
267840a317
commit
39b14e058e
3 changed files with 56 additions and 47 deletions
|
@ -4,7 +4,7 @@ use apb::{Activity, ActivityMut, Base, Collection, CollectionPage};
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use leptos::{create_signal, leptos_dom::logging::console_warn, ReadSignal, Signal, SignalGet, SignalSet, WriteSignal};
|
use leptos::{create_signal, leptos_dom::logging::console_warn, ReadSignal, Signal, SignalGet, SignalSet, WriteSignal};
|
||||||
|
|
||||||
use crate::{Auth, URL_BASE};
|
use crate::URL_BASE;
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
lazy_static::lazy_static! {
|
||||||
pub static ref CACHE: ObjectCache = ObjectCache::default();
|
pub static ref CACHE: ObjectCache = ObjectCache::default();
|
||||||
|
@ -39,8 +39,8 @@ impl Uri {
|
||||||
if url.len() < 50 {
|
if url.len() < 50 {
|
||||||
url.replace("https://", "")
|
url.replace("https://", "")
|
||||||
} else {
|
} else {
|
||||||
format!("{}..", url.replace("https://", "").get(..50).unwrap_or_default().to_string())
|
format!("{}..", url.replace("https://", "").get(..50).unwrap_or_default())
|
||||||
}.replace('/', "/")
|
}.replace('/', "\u{200B}/\u{200B}")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn short(url: &str) -> String {
|
pub fn short(url: &str) -> String {
|
||||||
|
@ -103,12 +103,17 @@ impl Uri {
|
||||||
pub struct Http;
|
pub struct Http;
|
||||||
|
|
||||||
impl Http {
|
impl Http {
|
||||||
pub async fn request<T: serde::de::DeserializeOwned>(method: reqwest::Method, url: &str, data: Option<&serde_json::Value>, token: &Signal<Option<Auth>>) -> reqwest::Result<T> {
|
pub async fn request<T: serde::de::DeserializeOwned>(
|
||||||
|
method: reqwest::Method,
|
||||||
|
url: &str,
|
||||||
|
data: Option<&serde_json::Value>,
|
||||||
|
token: Signal<Option<String>>
|
||||||
|
) -> reqwest::Result<T> {
|
||||||
let mut req = reqwest::Client::new()
|
let mut req = reqwest::Client::new()
|
||||||
.request(method, url);
|
.request(method, url);
|
||||||
|
|
||||||
if let Some(auth) = token.get() {
|
if let Some(auth) = token.get() {
|
||||||
req = req.header("Authorization", format!("Bearer {}", auth.token));
|
req = req.header("Authorization", format!("Bearer {}", auth));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(data) = data {
|
if let Some(data) = data {
|
||||||
|
@ -122,11 +127,11 @@ impl Http {
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn fetch<T: serde::de::DeserializeOwned>(url: &str, token: &Signal<Option<Auth>>) -> reqwest::Result<T> {
|
pub async fn fetch<T: serde::de::DeserializeOwned>(url: &str, token: Signal<Option<String>>) -> reqwest::Result<T> {
|
||||||
Self::request(reqwest::Method::GET, url, None, token).await
|
Self::request(reqwest::Method::GET, url, None, token).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn post<T: serde::de::DeserializeOwned>(url: &str, data: &serde_json::Value, token: &Signal<Option<Auth>>) -> reqwest::Result<T> {
|
pub async fn post<T: serde::de::DeserializeOwned>(url: &str, data: &serde_json::Value, token: Signal<Option<String>>) -> reqwest::Result<T> {
|
||||||
Self::request(reqwest::Method::POST, url, Some(data), token).await
|
Self::request(reqwest::Method::POST, url, Some(data), token).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -162,10 +167,10 @@ impl Timeline {
|
||||||
self.set_next.set(feed);
|
self.set_next.set(feed);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn more(&self, auth: Signal<Option<Auth>>) -> reqwest::Result<()> {
|
pub async fn more(&self, auth: Signal<Option<String>>) -> reqwest::Result<()> {
|
||||||
let feed_url = self.next();
|
let feed_url = self.next();
|
||||||
|
|
||||||
let collection : serde_json::Value = Http::fetch(&feed_url, &auth).await?;
|
let collection : serde_json::Value = Http::fetch(&feed_url, auth).await?;
|
||||||
|
|
||||||
|
|
||||||
let activities : Vec<serde_json::Value> = collection
|
let activities : Vec<serde_json::Value> = collection
|
||||||
|
@ -202,11 +207,9 @@ impl Timeline {
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(uid) = activity.actor().id() {
|
if let Some(uid) = activity.actor().id() {
|
||||||
if CACHE.get(&uid).is_none() {
|
if CACHE.get(&uid).is_none() && !gonna_fetch.contains(&uid) {
|
||||||
if !gonna_fetch.contains(&uid) {
|
gonna_fetch.insert(uid.clone());
|
||||||
gonna_fetch.insert(uid.clone());
|
sub_tasks.push(fetch_and_update("users", uid, auth));
|
||||||
sub_tasks.push(fetch_and_update("users", uid, auth));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -223,8 +226,8 @@ impl Timeline {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_and_update(kind: &'static str, id: String, auth: Signal<Option<Auth>>) {
|
async fn fetch_and_update(kind: &'static str, id: String, auth: Signal<Option<String>>) {
|
||||||
match Http::fetch(&Uri::api(kind, &id), &auth).await {
|
match Http::fetch(&Uri::api(kind, &id), auth).await {
|
||||||
Ok(data) => CACHE.put(id, data),
|
Ok(data) => CACHE.put(id, data),
|
||||||
Err(e) => console_warn(&format!("could not fetch '{id}': {e}")),
|
Err(e) => console_warn(&format!("could not fetch '{id}': {e}")),
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,46 +26,42 @@ pub struct Auth {
|
||||||
pub trait MaybeToken {
|
pub trait MaybeToken {
|
||||||
fn present(&self) -> bool;
|
fn present(&self) -> bool;
|
||||||
fn token(&self) -> String;
|
fn token(&self) -> String;
|
||||||
fn username(&self) -> String;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MaybeToken for Option<Auth> {
|
impl MaybeToken for Option<String> {
|
||||||
fn token(&self) -> String {
|
fn token(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
None => String::new(),
|
None => String::new(),
|
||||||
Some(x) => x.token.clone(),
|
Some(x) => x.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn present(&self) -> bool {
|
fn present(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
None => false,
|
None => false,
|
||||||
Some(x) => !x.token.is_empty(),
|
Some(x) => !x.is_empty(),
|
||||||
}
|
|
||||||
}
|
|
||||||
fn username(&self) -> String {
|
|
||||||
match self {
|
|
||||||
None => "anon".to_string(),
|
|
||||||
Some(x) => x.user.split('/').last().unwrap_or_default().to_string()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn LoginBox(
|
pub fn LoginBox(
|
||||||
rx: Signal<Option<Auth>>,
|
token_tx: WriteSignal<Option<String>>,
|
||||||
tx: WriteSignal<Option<Auth>>,
|
token: Signal<Option<String>>,
|
||||||
|
username: Signal<Option<String>>,
|
||||||
|
username_tx: WriteSignal<Option<String>>,
|
||||||
|
home_tl: Timeline,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
let username_ref: NodeRef<html::Input> = create_node_ref();
|
let username_ref: NodeRef<html::Input> = create_node_ref();
|
||||||
let password_ref: NodeRef<html::Input> = create_node_ref();
|
let password_ref: NodeRef<html::Input> = create_node_ref();
|
||||||
view! {
|
view! {
|
||||||
<div>
|
<div>
|
||||||
<div class="w-100" class:hidden=move || !rx.get().present() >
|
<div class="w-100" class:hidden=move || !token.get().present() >
|
||||||
"Hello "<a href={move || Uri::web("users", &rx.get().username())} >{move || rx.get().username()}</a>
|
"Hello "<a href={move || Uri::web("users", &username.get().unwrap_or_default() )} >{move || username.get().unwrap_or_default() }</a>
|
||||||
<input style="float:right" type="submit" value="logout" on:click=move |_| {
|
<input style="float:right" type="submit" value="logout" on:click=move |_| {
|
||||||
tx.set(None);
|
token_tx.set(None);
|
||||||
} />
|
} />
|
||||||
</div>
|
</div>
|
||||||
<div class:hidden=move || rx.get().present() >
|
<div class:hidden=move || token.get().present() >
|
||||||
<input class="w-100" type="text" node_ref=username_ref placeholder="username" />
|
<input class="w-100" type="text" node_ref=username_ref placeholder="username" />
|
||||||
<input class="w-100" type="text" node_ref=password_ref placeholder="password" />
|
<input class="w-100" type="text" node_ref=password_ref placeholder="password" />
|
||||||
<input class="w-100" type="submit" value="login" on:click=move |_| {
|
<input class="w-100" type="submit" value="login" on:click=move |_| {
|
||||||
|
@ -81,7 +77,13 @@ pub fn LoginBox(
|
||||||
.json::<Auth>()
|
.json::<Auth>()
|
||||||
.await.unwrap();
|
.await.unwrap();
|
||||||
console_log(&format!("logged in until {}", auth.expires));
|
console_log(&format!("logged in until {}", auth.expires));
|
||||||
tx.set(Some(auth));
|
let username = auth.user.split('/').last().unwrap_or_default().to_string();
|
||||||
|
// reset home feed and point it to our user's inbox
|
||||||
|
home_tl.set_feed(vec![]);
|
||||||
|
home_tl.set_next(format!("{URL_BASE}/users/{}/inbox/page", username));
|
||||||
|
// update our username and token cookies
|
||||||
|
username_tx.set(Some(username));
|
||||||
|
token_tx.set(Some(auth.token));
|
||||||
});
|
});
|
||||||
} />
|
} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -91,7 +93,7 @@ pub fn LoginBox(
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn TimelineNavigation() -> impl IntoView {
|
pub fn TimelineNavigation() -> impl IntoView {
|
||||||
let auth = use_context::<Signal<Option<Auth>>>().expect("missing auth context");
|
let auth = use_context::<Signal<Option<String>>>().expect("missing auth context");
|
||||||
view! {
|
view! {
|
||||||
<a href="/web/home" >
|
<a href="/web/home" >
|
||||||
<input class="w-100"
|
<input class="w-100"
|
||||||
|
@ -114,7 +116,7 @@ pub fn TimelineNavigation() -> impl IntoView {
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn PostBox() -> impl IntoView {
|
pub fn PostBox() -> impl IntoView {
|
||||||
let auth = use_context::<Signal<Option<Auth>>>().expect("missing auth context");
|
let auth = use_context::<Signal<Option<String>>>().expect("missing auth context");
|
||||||
let summary_ref: NodeRef<html::Input> = create_node_ref();
|
let summary_ref: NodeRef<html::Input> = create_node_ref();
|
||||||
let content_ref: NodeRef<html::Textarea> = create_node_ref();
|
let content_ref: NodeRef<html::Textarea> = create_node_ref();
|
||||||
view! {
|
view! {
|
||||||
|
@ -133,7 +135,7 @@ pub fn PostBox() -> impl IntoView {
|
||||||
.set_content(Some(&content))
|
.set_content(Some(&content))
|
||||||
.set_to(apb::Node::links(vec![apb::target::PUBLIC.to_string()]))
|
.set_to(apb::Node::links(vec![apb::target::PUBLIC.to_string()]))
|
||||||
.set_cc(apb::Node::links(vec![format!("{URL_BASE}/users/test/followers")])),
|
.set_cc(apb::Node::links(vec![format!("{URL_BASE}/users/test/followers")])),
|
||||||
&auth
|
auth
|
||||||
)
|
)
|
||||||
.await.unwrap()
|
.await.unwrap()
|
||||||
})
|
})
|
||||||
|
@ -199,13 +201,13 @@ pub fn ActorBanner(object: serde_json::Value) -> impl IntoView {
|
||||||
#[component]
|
#[component]
|
||||||
pub fn UserPage() -> impl IntoView {
|
pub fn UserPage() -> impl IntoView {
|
||||||
let params = use_params_map();
|
let params = use_params_map();
|
||||||
let auth = use_context::<Signal<Option<Auth>>>().expect("missing auth context");
|
let auth = use_context::<Signal<Option<String>>>().expect("missing auth context");
|
||||||
let actor = create_local_resource(move || params.get().get("id").cloned().unwrap_or_default(), move |id| {
|
let actor = create_local_resource(move || params.get().get("id").cloned().unwrap_or_default(), move |id| {
|
||||||
async move {
|
async move {
|
||||||
match CACHE.get(&Uri::full("users", &id)) {
|
match CACHE.get(&Uri::full("users", &id)) {
|
||||||
Some(x) => Some(x.clone()),
|
Some(x) => Some(x.clone()),
|
||||||
None => {
|
None => {
|
||||||
let user : serde_json::Value = Http::fetch(&Uri::api("users", &id), &auth).await.ok()?;
|
let user : serde_json::Value = Http::fetch(&Uri::api("users", &id), auth).await.ok()?;
|
||||||
CACHE.put(Uri::full("users", &id), user.clone());
|
CACHE.put(Uri::full("users", &id), user.clone());
|
||||||
Some(user)
|
Some(user)
|
||||||
},
|
},
|
||||||
|
@ -255,13 +257,13 @@ pub fn UserPage() -> impl IntoView {
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ObjectPage() -> impl IntoView {
|
pub fn ObjectPage() -> impl IntoView {
|
||||||
let params = use_params_map();
|
let params = use_params_map();
|
||||||
let auth = use_context::<Signal<Option<Auth>>>().expect("missing auth context");
|
let auth = use_context::<Signal<Option<String>>>().expect("missing auth context");
|
||||||
let object = create_local_resource(move || params.get().get("id").cloned().unwrap_or_default(), move |oid| {
|
let object = create_local_resource(move || params.get().get("id").cloned().unwrap_or_default(), move |oid| {
|
||||||
async move {
|
async move {
|
||||||
match CACHE.get(&Uri::full("objects", &oid)) {
|
match CACHE.get(&Uri::full("objects", &oid)) {
|
||||||
Some(x) => Some(x.clone()),
|
Some(x) => Some(x.clone()),
|
||||||
None => {
|
None => {
|
||||||
let obj = Http::fetch::<serde_json::Value>(&Uri::api("objects", &oid), &auth).await.ok()?;
|
let obj = Http::fetch::<serde_json::Value>(&Uri::api("objects", &oid), auth).await.ok()?;
|
||||||
CACHE.put(Uri::full("objects", &oid), obj.clone());
|
CACHE.put(Uri::full("objects", &oid), obj.clone());
|
||||||
Some(obj)
|
Some(obj)
|
||||||
}
|
}
|
||||||
|
@ -396,7 +398,7 @@ pub fn TimelinePage(name: &'static str, tl: Timeline) -> impl IntoView {
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn TimelineFeed(tl: Timeline) -> impl IntoView {
|
pub fn TimelineFeed(tl: Timeline) -> impl IntoView {
|
||||||
let auth = use_context::<Signal<Option<Auth>>>().expect("missing auth context");
|
let auth = use_context::<Signal<Option<String>>>().expect("missing auth context");
|
||||||
view! {
|
view! {
|
||||||
<For
|
<For
|
||||||
each=move || tl.feed.get()
|
each=move || tl.feed.get()
|
||||||
|
|
|
@ -1,19 +1,20 @@
|
||||||
use leptos::{leptos_dom::logging::console_error, *};
|
use leptos::{leptos_dom::logging::console_error, *};
|
||||||
use leptos_router::*;
|
use leptos_router::*;
|
||||||
|
|
||||||
use leptos_use::{use_cookie, utils::JsonCodec};
|
use leptos_use::{use_cookie, utils::FromToStringCodec};
|
||||||
use upub_web::{
|
use upub_web::{
|
||||||
URL_BASE, context::Timeline, About, Auth, LoginBox, MaybeToken, ObjectPage, PostBox,
|
URL_BASE, context::Timeline, About, LoginBox, MaybeToken, ObjectPage, PostBox,
|
||||||
TimelinePage, TimelineNavigation, UserPage
|
TimelinePage, TimelineNavigation, UserPage
|
||||||
};
|
};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
_ = console_log::init_with_level(log::Level::Info);
|
_ = console_log::init_with_level(log::Level::Info);
|
||||||
console_error_panic_hook::set_once();
|
console_error_panic_hook::set_once();
|
||||||
let (cookie, set_cookie) = use_cookie::<Auth, JsonCodec>("token");
|
let (cookie, set_cookie) = use_cookie::<String, FromToStringCodec>("token");
|
||||||
|
let (username, set_username) = use_cookie::<String, FromToStringCodec>("username");
|
||||||
provide_context(cookie);
|
provide_context(cookie);
|
||||||
|
|
||||||
let home_tl = Timeline::new(format!("{URL_BASE}/users/{}/inbox/page", cookie.get().username()));
|
let home_tl = Timeline::new(format!("{URL_BASE}/users/{}/inbox/page", username.get().unwrap_or_default()));
|
||||||
let server_tl = Timeline::new(format!("{URL_BASE}/inbox/page"));
|
let server_tl = Timeline::new(format!("{URL_BASE}/inbox/page"));
|
||||||
|
|
||||||
spawn_local(async move {
|
spawn_local(async move {
|
||||||
|
@ -61,8 +62,11 @@ fn main() {
|
||||||
<div class="two-col" >
|
<div class="two-col" >
|
||||||
<div class="col-side" >
|
<div class="col-side" >
|
||||||
<LoginBox
|
<LoginBox
|
||||||
tx=set_cookie
|
token_tx=set_cookie
|
||||||
rx=cookie
|
token=cookie
|
||||||
|
username_tx=set_username
|
||||||
|
username=username
|
||||||
|
home_tl=home_tl
|
||||||
/>
|
/>
|
||||||
<hr class="mt-1 mb-1" />
|
<hr class="mt-1 mb-1" />
|
||||||
<TimelineNavigation />
|
<TimelineNavigation />
|
||||||
|
|
Loading…
Reference in a new issue