Compare commits

...

3 commits

Author SHA1 Message Date
e3831650ca
feat: add frontend url to users 2024-05-29 21:37:21 +02:00
3fee57891d
feat: add unique index on relations 2024-05-29 21:37:09 +02:00
69cff08b5b
fix(uriproxy): users -> actors 2024-05-29 21:36:55 +02:00
14 changed files with 62 additions and 25 deletions

View file

@ -0,0 +1,31 @@
use sea_orm_migration::prelude::*;
use super::m20240524_000002_create_relations_likes_shares::Relations;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.create_index(
Index::create()
.unique()
.name("index-relations-follower-following")
.table(Relations::Table)
.col(Relations::Following)
.col(Relations::Follower)
.to_owned()
)
.await?;
Ok(())
}
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
manager
.drop_index(Index::drop().name("index-relations-follower-following").table(Relations::Table).to_owned())
.await?;
Ok(())
}
}

View file

@ -5,6 +5,7 @@ mod m20240524_000002_create_relations_likes_shares;
mod m20240524_000003_create_users_auth_and_config; mod m20240524_000003_create_users_auth_and_config;
mod m20240524_000004_create_addressing_deliveries; mod m20240524_000004_create_addressing_deliveries;
mod m20240524_000005_create_attachments_tags_mentions; mod m20240524_000005_create_attachments_tags_mentions;
mod m20240529_000001_add_relation_unique_index;
pub struct Migrator; pub struct Migrator;
@ -17,6 +18,7 @@ impl MigratorTrait for Migrator {
Box::new(m20240524_000003_create_users_auth_and_config::Migration), Box::new(m20240524_000003_create_users_auth_and_config::Migration),
Box::new(m20240524_000004_create_addressing_deliveries::Migration), Box::new(m20240524_000004_create_addressing_deliveries::Migration),
Box::new(m20240524_000005_create_attachments_tags_mentions::Migration), Box::new(m20240524_000005_create_attachments_tags_mentions::Migration),
Box::new(m20240529_000001_add_relation_unique_index::Migration),
] ]
} }
} }

View file

@ -70,6 +70,10 @@ pub async fn view(
user = user.set_following_count(None); user = user.set_following_count(None);
} }
if let Some(ref fe) = ctx.cfg().instance.frontend {
user = user.set_url(Node::link(format!("{fe}/actors/{id}")));
}
Ok(JsonLD(user.ld_context())) Ok(JsonLD(user.ld_context()))
}, },
// remote user // remote user

View file

@ -111,7 +111,7 @@ impl Context {
/// get full user id uri /// get full user id uri
pub fn uid(&self, id: &str) -> String { pub fn uid(&self, id: &str) -> String {
uriproxy::uri(self.base(), UriClass::User, id) uriproxy::uri(self.base(), UriClass::Actor, id)
} }
/// get full object id uri /// get full object id uri

View file

@ -2,7 +2,7 @@ use base64::Engine;
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum UriClass { pub enum UriClass {
User, Actor,
Object, Object,
Activity, Activity,
Context, Context,
@ -11,7 +11,7 @@ pub enum UriClass {
impl AsRef<str> for UriClass { impl AsRef<str> for UriClass {
fn as_ref(&self) -> &str { fn as_ref(&self) -> &str {
match self { match self {
Self::User => "users", Self::Actor => "actors",
Self::Object => "objects", Self::Object => "objects",
Self::Activity => "activities", Self::Activity => "activities",
Self::Context => "context", Self::Context => "context",
@ -41,7 +41,7 @@ pub fn decompose_id(full_id: &str) -> String {
full_id // https://example.org/actors/test/followers/page?offset=42 full_id // https://example.org/actors/test/followers/page?offset=42
.replace("https://", "") .replace("https://", "")
.replace("http://", "") .replace("http://", "")
.split('/') // ['example.org', 'users', 'test', 'followers', 'page?offset=42' ] .split('/') // ['example.org', 'actors', 'test', 'followers', 'page?offset=42' ]
.nth(2) // 'test' .nth(2) // 'test'
.unwrap_or("") .unwrap_or("")
.to_string() .to_string()

View file

@ -14,7 +14,7 @@ pub fn ActivityLine(activity: crate::Object) -> impl IntoView {
let actor = CACHE.get_or(&actor_id, serde_json::Value::String(actor_id.clone()).into()); let actor = CACHE.get_or(&actor_id, serde_json::Value::String(actor_id.clone()).into());
let kind = activity.activity_type().unwrap_or(apb::ActivityType::Activity); let kind = activity.activity_type().unwrap_or(apb::ActivityType::Activity);
let href = match kind { let href = match kind {
apb::ActivityType::Follow => Uri::web(U::User, &object_id), apb::ActivityType::Follow => Uri::web(U::Actor, &object_id),
// TODO for update check what's being updated // TODO for update check what's being updated
_ => Uri::web(U::Object, &object_id), _ => Uri::web(U::Object, &object_id),
}; };

View file

@ -14,7 +14,7 @@ pub fn LoginBox(
view! { view! {
<div> <div>
<div class="w-100" class:hidden=move || !auth.present() > <div class="w-100" class:hidden=move || !auth.present() >
"hi "<a href={move || Uri::web(U::User, &auth.username() )} >{move || auth.username() }</a> "hi "<a href={move || Uri::web(U::Actor, &auth.username() )} >{move || auth.username() }</a>
<input style="float:right" type="submit" value="logout" on:click=move |_| { <input style="float:right" type="submit" value="logout" on:click=move |_| {
token_tx.set(None); token_tx.set(None);
home_tl.reset(format!("{URL_BASE}/outbox/page")); home_tl.reset(format!("{URL_BASE}/outbox/page"));

View file

@ -86,7 +86,7 @@ pub fn PostBox(advanced: WriteSignal<bool>) -> impl IntoView {
mentions.get() mentions.get()
.map(|x| x.into_iter().map(|u| match CACHE.get(&u) { .map(|x| x.into_iter().map(|u| match CACHE.get(&u) {
Some(u) => view! { <span class="nowrap"><span class="emoji mr-s ml-s">"📨"</span><ActorStrip object=u /></span> }.into_view(), Some(u) => view! { <span class="nowrap"><span class="emoji mr-s ml-s">"📨"</span><ActorStrip object=u /></span> }.into_view(),
None => view! { <span class="nowrap"><span class="emoji mr-s ml-s">"📨"</span><a href={Uri::web(U::User, &u)}>{u}</a></span> }.into_view(), None => view! { <span class="nowrap"><span class="emoji mr-s ml-s">"📨"</span><a href={Uri::web(U::Actor, &u)}>{u}</a></span> }.into_view(),
}) })
.collect_view()) .collect_view())
} }

View file

@ -211,7 +211,7 @@ async fn process_activities(activities: Vec<serde_json::Value>, auth: Auth) -> V
if let Some(object_id) = activity.object().id() { if let Some(object_id) = activity.object().id() {
if !gonna_fetch.contains(&object_id) { if !gonna_fetch.contains(&object_id) {
let fetch_kind = match activity_type { let fetch_kind = match activity_type {
apb::ActivityType::Follow => U::User, apb::ActivityType::Follow => U::Actor,
_ => U::Object, _ => U::Object,
}; };
gonna_fetch.insert(object_id.clone()); gonna_fetch.insert(object_id.clone());
@ -235,20 +235,20 @@ async fn process_activities(activities: Vec<serde_json::Value>, auth: Auth) -> V
if let Some(uid) = activity.attributed_to().id() { if let Some(uid) = activity.attributed_to().id() {
if CACHE.get(&uid).is_none() && !gonna_fetch.contains(&uid) { if CACHE.get(&uid).is_none() && !gonna_fetch.contains(&uid) {
gonna_fetch.insert(uid.clone()); gonna_fetch.insert(uid.clone());
sub_tasks.push(Box::pin(fetch_and_update(U::User, uid, auth))); sub_tasks.push(Box::pin(fetch_and_update(U::Actor, uid, auth)));
} }
} }
if let Some(uid) = activity.actor().id() { if let Some(uid) = activity.actor().id() {
if CACHE.get(&uid).is_none() && !gonna_fetch.contains(&uid) { if CACHE.get(&uid).is_none() && !gonna_fetch.contains(&uid) {
gonna_fetch.insert(uid.clone()); gonna_fetch.insert(uid.clone());
sub_tasks.push(Box::pin(fetch_and_update(U::User, uid, auth))); sub_tasks.push(Box::pin(fetch_and_update(U::Actor, uid, auth)));
} }
} }
} }
for user in actors_seen { for user in actors_seen {
sub_tasks.push(Box::pin(fetch_and_update(U::User, user, auth))); sub_tasks.push(Box::pin(fetch_and_update(U::Actor, user, auth)));
} }
futures::future::join_all(sub_tasks).await; futures::future::join_all(sub_tasks).await;
@ -269,9 +269,9 @@ async fn fetch_and_update_with_user(kind: U, id: String, auth: Auth) {
if let Some(actor_id) = match kind { if let Some(actor_id) = match kind {
U::Object => obj.attributed_to().id(), U::Object => obj.attributed_to().id(),
U::Activity => obj.actor().id(), U::Activity => obj.actor().id(),
U::User | U::Context => None, U::Actor | U::Context => None,
} { } {
fetch_and_update(U::User, actor_id, auth).await; fetch_and_update(U::Actor, actor_id, auth).await;
} }
} }
} }

View file

@ -10,7 +10,7 @@ pub fn ActorStrip(object: crate::Object) -> impl IntoView {
let domain = object.id().unwrap_or_default().replace("https://", "").split('/').next().unwrap_or_default().to_string(); let domain = object.id().unwrap_or_default().replace("https://", "").split('/').next().unwrap_or_default().to_string();
let avatar = object.icon().get().map(|x| x.url().id().unwrap_or(DEFAULT_AVATAR_URL.into())).unwrap_or(DEFAULT_AVATAR_URL.into()); let avatar = object.icon().get().map(|x| x.url().id().unwrap_or(DEFAULT_AVATAR_URL.into())).unwrap_or(DEFAULT_AVATAR_URL.into());
view! { view! {
<a href={Uri::web(U::User, &actor_id)} class="clean hover"> <a href={Uri::web(U::Actor, &actor_id)} class="clean hover">
<img src={avatar} class="avatar inline mr-s" /><b>{username}</b><small>@{domain}</small> <img src={avatar} class="avatar inline mr-s" /><b>{username}</b><small>@{domain}</small>
</a> </a>
} }
@ -20,11 +20,11 @@ pub fn ActorStrip(object: crate::Object) -> impl IntoView {
pub fn ActorBanner(object: crate::Object) -> impl IntoView { pub fn ActorBanner(object: crate::Object) -> impl IntoView {
match object.as_ref() { match object.as_ref() {
serde_json::Value::String(id) => view! { serde_json::Value::String(id) => view! {
<div><b>?</b>" "<a class="clean hover" href={Uri::web(U::User, id)}>{Uri::pretty(id)}</a></div> <div><b>?</b>" "<a class="clean hover" href={Uri::web(U::Actor, id)}>{Uri::pretty(id)}</a></div>
}, },
serde_json::Value::Object(_) => { serde_json::Value::Object(_) => {
let uid = object.id().unwrap_or_default().to_string(); let uid = object.id().unwrap_or_default().to_string();
let uri = Uri::web(U::User, &uid); let uri = Uri::web(U::Actor, &uid);
let avatar_url = object.icon().get().map(|x| x.url().id().unwrap_or(DEFAULT_AVATAR_URL.into())).unwrap_or(DEFAULT_AVATAR_URL.into()); let avatar_url = object.icon().get().map(|x| x.url().id().unwrap_or(DEFAULT_AVATAR_URL.into())).unwrap_or(DEFAULT_AVATAR_URL.into());
let display_name = object.name().unwrap_or_default().to_string(); let display_name = object.name().unwrap_or_default().to_string();
let username = object.preferred_username().unwrap_or_default().to_string(); let username = object.preferred_username().unwrap_or_default().to_string();

View file

@ -84,7 +84,7 @@ pub fn DebugPage() -> impl IntoView {
" raw :: " " raw :: "
<a href={move|| Uri::web(U::Object, &text.get())} >obj</a> <a href={move|| Uri::web(U::Object, &text.get())} >obj</a>
" :: " " :: "
<a href={move|| Uri::web(U::User, &text.get())} >usr</a> <a href={move|| Uri::web(U::Actor, &text.get())} >usr</a>
" :: " " :: "
<a href=move || cached_query().0 target="_blank" rel="nofollow noreferrer">ext</a> <a href=move || cached_query().0 target="_blank" rel="nofollow noreferrer">ext</a>
" :: " " :: "

View file

@ -21,9 +21,9 @@ pub fn ObjectPage(tl: Timeline) -> impl IntoView {
let obj = Arc::new(obj); let obj = Arc::new(obj);
if let Some(author) = obj.attributed_to().id() { if let Some(author) = obj.attributed_to().id() {
if let Ok(user) = Http::fetch::<serde_json::Value>( if let Ok(user) = Http::fetch::<serde_json::Value>(
&Uri::api(U::User, &author, true), auth &Uri::api(U::Actor, &author, true), auth
).await { ).await {
CACHE.put(Uri::full(U::User, &author), Arc::new(user)); CACHE.put(Uri::full(U::Actor, &author), Arc::new(user));
} }
} }
CACHE.put(Uri::full(U::Object, &oid), obj.clone()); CACHE.put(Uri::full(U::Object, &oid), obj.clone());

View file

@ -11,7 +11,7 @@ pub fn SearchPage() -> impl IntoView {
let user = create_local_resource( let user = create_local_resource(
move || use_query_map().get().get("q").cloned().unwrap_or_default(), move || use_query_map().get().get("q").cloned().unwrap_or_default(),
move |q| { move |q| {
let user_fetch = Uri::api(U::User, &q, true); let user_fetch = Uri::api(U::Actor, &q, true);
async move { Some(Arc::new(Http::fetch::<serde_json::Value>(&user_fetch, auth).await.ok()?)) } async move { Some(Arc::new(Http::fetch::<serde_json::Value>(&user_fetch, auth).await.ok()?)) }
} }
); );

View file

@ -27,16 +27,16 @@ pub fn UserPage(tl: Timeline) -> impl IntoView {
.get("id") .get("id")
.cloned() .cloned()
.unwrap_or_default(); .unwrap_or_default();
let uid = uriproxy::uri(URL_BASE, uriproxy::UriClass::User, &id); let uid = uriproxy::uri(URL_BASE, uriproxy::UriClass::Actor, &id);
let _uid = uid.clone(); let _uid = uid.clone();
let actor = create_local_resource(move || _uid.clone(), move |id| { let actor = create_local_resource(move || _uid.clone(), move |id| {
async move { async move {
match CACHE.get(&Uri::full(U::User, &id)) { match CACHE.get(&Uri::full(U::Actor, &id)) {
Some(x) => Some(x.clone()), Some(x) => Some(x.clone()),
None => { None => {
let user : serde_json::Value = Http::fetch(&Uri::api(U::User, &id, true), auth).await.ok()?; let user : serde_json::Value = Http::fetch(&Uri::api(U::Actor, &id, true), auth).await.ok()?;
let user = Arc::new(user); let user = Arc::new(user);
CACHE.put(Uri::full(U::User, &id), user.clone()); CACHE.put(Uri::full(U::Actor, &id), user.clone());
Some(user) Some(user)
}, },
} }
@ -80,7 +80,7 @@ pub fn UserPage(tl: Timeline) -> impl IntoView {
let following = object.following_count().unwrap_or(0); let following = object.following_count().unwrap_or(0);
let followers = object.followers_count().unwrap_or(0); let followers = object.followers_count().unwrap_or(0);
let statuses = object.statuses_count().unwrap_or(0); let statuses = object.statuses_count().unwrap_or(0);
let tl_url = format!("{}/outbox/page", Uri::api(U::User, &id.clone(), false)); let tl_url = format!("{}/outbox/page", Uri::api(U::Actor, &id.clone(), false));
if !tl.next.get().starts_with(&tl_url) { if !tl.next.get().starts_with(&tl_url) {
tl.reset(tl_url); tl.reset(tl_url);
} }