forked from alemi/upub
feat: way smarter way to represent remote ids
base64 em basically. big commit because touches basically everything!!
This commit is contained in:
parent
27073138ae
commit
af3a3fbbb8
32 changed files with 182 additions and 147 deletions
9
Cargo.lock
generated
9
Cargo.lock
generated
|
@ -4489,6 +4489,7 @@ dependencies = [
|
||||||
"tower-http",
|
"tower-http",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
|
"uriproxy",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -4515,9 +4516,17 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"uriproxy",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uriproxy"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["apb", "web", "mdhtml"]
|
members = ["apb", "web", "mdhtml", "uriproxy"]
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "upub"
|
name = "upub"
|
||||||
|
@ -28,6 +28,7 @@ serde_default = "0.1"
|
||||||
serde-inline-default = "0.2"
|
serde-inline-default = "0.2"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
mdhtml = { path = "mdhtml", features = ["markdown"] }
|
mdhtml = { path = "mdhtml", features = ["markdown"] }
|
||||||
|
uriproxy = { path = "uriproxy" }
|
||||||
jrd = "0.1"
|
jrd = "0.1"
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
use sea_orm::{ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter, QueryOrder};
|
use sea_orm::{ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter, QueryOrder};
|
||||||
|
|
||||||
pub async fn relay(ctx: crate::server::Context, actor: String, accept: bool) -> crate::Result<()> {
|
pub async fn relay(ctx: crate::server::Context, actor: String, accept: bool) -> crate::Result<()> {
|
||||||
let aid = ctx.aid(uuid::Uuid::new_v4().to_string());
|
let aid = ctx.aid(&uuid::Uuid::new_v4().to_string());
|
||||||
|
|
||||||
let mut activity_model = crate::model::activity::Model {
|
let mut activity_model = crate::model::activity::Model {
|
||||||
id: aid.clone(),
|
id: aid.clone(),
|
||||||
activity_type: apb::ActivityType::Follow,
|
activity_type: apb::ActivityType::Follow,
|
||||||
actor: ctx.base(),
|
actor: ctx.base().to_string(),
|
||||||
object: Some(actor.clone()),
|
object: Some(actor.clone()),
|
||||||
target: None,
|
target: None,
|
||||||
published: chrono::Utc::now(),
|
published: chrono::Utc::now(),
|
||||||
|
@ -32,7 +32,7 @@ pub async fn relay(ctx: crate::server::Context, actor: String, accept: bool) ->
|
||||||
crate::model::activity::Entity::insert(activity_model.into_active_model())
|
crate::model::activity::Entity::insert(activity_model.into_active_model())
|
||||||
.exec(ctx.db()).await?;
|
.exec(ctx.db()).await?;
|
||||||
|
|
||||||
ctx.dispatch(&ctx.base(), vec![actor, apb::target::PUBLIC.to_string()], &aid, None).await?;
|
ctx.dispatch(ctx.base(), vec![actor, apb::target::PUBLIC.to_string()], &aid, None).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ pub async fn view(
|
||||||
AuthIdentity(auth): AuthIdentity,
|
AuthIdentity(auth): AuthIdentity,
|
||||||
Query(query): Query<TryFetch>,
|
Query(query): Query<TryFetch>,
|
||||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
) -> crate::Result<JsonLD<serde_json::Value>> {
|
||||||
let aid = ctx.uri("activities", id);
|
let aid = ctx.aid(&id);
|
||||||
if auth.is_local() && query.fetch && !ctx.is_local(&aid) {
|
if auth.is_local() && query.fetch && !ctx.is_local(&aid) {
|
||||||
let obj = ctx.fetch_activity(&aid).await?;
|
let obj = ctx.fetch_activity(&aid).await?;
|
||||||
if obj.id != aid {
|
if obj.id != aid {
|
||||||
|
|
|
@ -58,7 +58,7 @@ pub async fn proxy_get(
|
||||||
Method::GET,
|
Method::GET,
|
||||||
&query.id,
|
&query.id,
|
||||||
None,
|
None,
|
||||||
&ctx.base(),
|
ctx.base(),
|
||||||
&ctx.app().private_key,
|
&ctx.app().private_key,
|
||||||
&format!("{}+proxy", ctx.domain()),
|
&format!("{}+proxy", ctx.domain()),
|
||||||
)
|
)
|
||||||
|
@ -82,7 +82,7 @@ pub async fn proxy_form(
|
||||||
Method::GET,
|
Method::GET,
|
||||||
&query.id,
|
&query.id,
|
||||||
None,
|
None,
|
||||||
&ctx.base(),
|
ctx.base(),
|
||||||
&ctx.app().private_key,
|
&ctx.app().private_key,
|
||||||
&format!("{}+proxy", ctx.domain()),
|
&format!("{}+proxy", ctx.domain()),
|
||||||
)
|
)
|
||||||
|
|
|
@ -84,5 +84,5 @@ pub async fn register(
|
||||||
registration.banner_url
|
registration.banner_url
|
||||||
).await?;
|
).await?;
|
||||||
|
|
||||||
Ok(Json(ctx.uid(registration.username)))
|
Ok(Json(ctx.uid(®istration.username)))
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ pub async fn get(
|
||||||
AuthIdentity(auth): AuthIdentity,
|
AuthIdentity(auth): AuthIdentity,
|
||||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
) -> crate::Result<JsonLD<serde_json::Value>> {
|
||||||
let local_context_id = url!(ctx, "/context/{id}");
|
let local_context_id = url!(ctx, "/context/{id}");
|
||||||
let context = ctx.uri("context", id);
|
let context = ctx.context_id(&id);
|
||||||
|
|
||||||
let count = model::addressing::Entity::find_addressed(auth.my_id())
|
let count = model::addressing::Entity::find_addressed(auth.my_id())
|
||||||
.filter(auth.filter_condition())
|
.filter(auth.filter_condition())
|
||||||
|
|
|
@ -14,7 +14,7 @@ pub async fn view(
|
||||||
AuthIdentity(auth): AuthIdentity,
|
AuthIdentity(auth): AuthIdentity,
|
||||||
Query(query): Query<TryFetch>,
|
Query(query): Query<TryFetch>,
|
||||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
) -> crate::Result<JsonLD<serde_json::Value>> {
|
||||||
let oid = ctx.uri("objects", id);
|
let oid = ctx.oid(&id);
|
||||||
if auth.is_local() && query.fetch && !ctx.is_local(&oid) {
|
if auth.is_local() && query.fetch && !ctx.is_local(&oid) {
|
||||||
let obj = ctx.fetch_object(&oid).await?;
|
let obj = ctx.fetch_object(&oid).await?;
|
||||||
// some implementations serve statuses on different urls than their AP id
|
// some implementations serve statuses on different urls than their AP id
|
||||||
|
|
|
@ -10,7 +10,7 @@ pub async fn get(
|
||||||
Query(q): Query<TryFetch>,
|
Query(q): Query<TryFetch>,
|
||||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
) -> crate::Result<JsonLD<serde_json::Value>> {
|
||||||
let replies_id = url!(ctx, "/objects/{id}/replies");
|
let replies_id = url!(ctx, "/objects/{id}/replies");
|
||||||
let oid = ctx.uri("objects", id);
|
let oid = ctx.oid(&id);
|
||||||
|
|
||||||
if auth.is_local() && q.fetch {
|
if auth.is_local() && q.fetch {
|
||||||
ctx.fetch_thread(&oid).await?;
|
ctx.fetch_thread(&oid).await?;
|
||||||
|
@ -32,7 +32,7 @@ pub async fn page(
|
||||||
AuthIdentity(auth): AuthIdentity,
|
AuthIdentity(auth): AuthIdentity,
|
||||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
) -> crate::Result<JsonLD<serde_json::Value>> {
|
||||||
let page_id = url!(ctx, "/objects/{id}/replies/page");
|
let page_id = url!(ctx, "/objects/{id}/replies/page");
|
||||||
let oid = ctx.uri("objects", id);
|
let oid = ctx.oid(&id);
|
||||||
|
|
||||||
crate::server::builders::paginate(
|
crate::server::builders::paginate(
|
||||||
page_id,
|
page_id,
|
||||||
|
|
|
@ -11,7 +11,7 @@ pub async fn get<const OUTGOING: bool>(
|
||||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
) -> crate::Result<JsonLD<serde_json::Value>> {
|
||||||
let follow___ = if OUTGOING { "following" } else { "followers" };
|
let follow___ = if OUTGOING { "following" } else { "followers" };
|
||||||
let count = model::relation::Entity::find()
|
let count = model::relation::Entity::find()
|
||||||
.filter(if OUTGOING { Follower } else { Following }.eq(ctx.uid(id.clone())))
|
.filter(if OUTGOING { Follower } else { Following }.eq(ctx.uid(&id)))
|
||||||
.count(ctx.db()).await.unwrap_or_else(|e| {
|
.count(ctx.db()).await.unwrap_or_else(|e| {
|
||||||
tracing::error!("failed counting {follow___} for {id}: {e}");
|
tracing::error!("failed counting {follow___} for {id}: {e}");
|
||||||
0
|
0
|
||||||
|
@ -30,7 +30,7 @@ pub async fn page<const OUTGOING: bool>(
|
||||||
let offset = page.offset.unwrap_or(0);
|
let offset = page.offset.unwrap_or(0);
|
||||||
|
|
||||||
let following = model::relation::Entity::find()
|
let following = model::relation::Entity::find()
|
||||||
.filter(if OUTGOING { Follower } else { Following }.eq(ctx.uid(id.clone())))
|
.filter(if OUTGOING { Follower } else { Following }.eq(ctx.uid(&id)))
|
||||||
.select_only()
|
.select_only()
|
||||||
.select_column(if OUTGOING { Following } else { Follower })
|
.select_column(if OUTGOING { Following } else { Follower })
|
||||||
.limit(limit)
|
.limit(limit)
|
||||||
|
|
|
@ -11,7 +11,7 @@ pub async fn get(
|
||||||
match auth {
|
match auth {
|
||||||
Identity::Anonymous => Err(StatusCode::FORBIDDEN.into()),
|
Identity::Anonymous => Err(StatusCode::FORBIDDEN.into()),
|
||||||
Identity::Remote(_) => Err(StatusCode::FORBIDDEN.into()),
|
Identity::Remote(_) => Err(StatusCode::FORBIDDEN.into()),
|
||||||
Identity::Local(user) => if ctx.uid(id.clone()) == user {
|
Identity::Local(user) => if ctx.uid(&id) == user {
|
||||||
crate::server::builders::collection(&url!(ctx, "/users/{id}/inbox"), None)
|
crate::server::builders::collection(&url!(ctx, "/users/{id}/inbox"), None)
|
||||||
} else {
|
} else {
|
||||||
Err(StatusCode::FORBIDDEN.into())
|
Err(StatusCode::FORBIDDEN.into())
|
||||||
|
@ -29,7 +29,7 @@ pub async fn page(
|
||||||
// local inbox is only for local users
|
// local inbox is only for local users
|
||||||
return Err(UpubError::forbidden());
|
return Err(UpubError::forbidden());
|
||||||
};
|
};
|
||||||
if uid != &ctx.uid(id.clone()) {
|
if uid != &ctx.uid(&id) {
|
||||||
return Err(UpubError::forbidden());
|
return Err(UpubError::forbidden());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ pub async fn view(
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
Query(query): Query<TryFetch>,
|
Query(query): Query<TryFetch>,
|
||||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
) -> crate::Result<JsonLD<serde_json::Value>> {
|
||||||
let mut uid = ctx.uri("users", id.clone());
|
let mut uid = ctx.uid(&id);
|
||||||
if auth.is_local() {
|
if auth.is_local() {
|
||||||
if id.starts_with('@') {
|
if id.starts_with('@') {
|
||||||
if let Some((user, host)) = id.replacen('@', "", 1).split_once('@') {
|
if let Some((user, host)) = id.replacen('@', "", 1).split_once('@') {
|
||||||
|
|
|
@ -17,11 +17,7 @@ pub async fn page(
|
||||||
Query(page): Query<Pagination>,
|
Query(page): Query<Pagination>,
|
||||||
AuthIdentity(auth): AuthIdentity,
|
AuthIdentity(auth): AuthIdentity,
|
||||||
) -> crate::Result<JsonLD<serde_json::Value>> {
|
) -> crate::Result<JsonLD<serde_json::Value>> {
|
||||||
let uid = if id.starts_with('+') {
|
let uid = ctx.uid(&id);
|
||||||
format!("https://{}", id.replacen('+', "", 1).replace('@', "/"))
|
|
||||||
} else {
|
|
||||||
ctx.uid(id.clone())
|
|
||||||
};
|
|
||||||
crate::server::builders::paginate(
|
crate::server::builders::paginate(
|
||||||
url!(ctx, "/users/{id}/outbox/page"),
|
url!(ctx, "/users/{id}/outbox/page"),
|
||||||
Condition::all()
|
Condition::all()
|
||||||
|
@ -47,7 +43,7 @@ pub async fn post(
|
||||||
match auth {
|
match auth {
|
||||||
Identity::Anonymous => Err(StatusCode::UNAUTHORIZED.into()),
|
Identity::Anonymous => Err(StatusCode::UNAUTHORIZED.into()),
|
||||||
Identity::Remote(_) => Err(StatusCode::NOT_IMPLEMENTED.into()),
|
Identity::Remote(_) => Err(StatusCode::NOT_IMPLEMENTED.into()),
|
||||||
Identity::Local(uid) => if ctx.uid(id.clone()) == uid {
|
Identity::Local(uid) => if ctx.uid(&id) == uid {
|
||||||
tracing::debug!("processing new local activity: {}", serde_json::to_string(&activity).unwrap_or_default());
|
tracing::debug!("processing new local activity: {}", serde_json::to_string(&activity).unwrap_or_default());
|
||||||
match activity.base_type() {
|
match activity.base_type() {
|
||||||
None => Err(StatusCode::BAD_REQUEST.into()),
|
None => Err(StatusCode::BAD_REQUEST.into()),
|
||||||
|
|
|
@ -105,12 +105,12 @@ pub async fn webfinger(State(ctx): State<Context>, Query(query): Query<Webfinger
|
||||||
if user == ctx.domain() && domain == ctx.domain() {
|
if user == ctx.domain() && domain == ctx.domain() {
|
||||||
return Ok(JsonRD(JsonResourceDescriptor {
|
return Ok(JsonRD(JsonResourceDescriptor {
|
||||||
subject: format!("acct:{user}@{domain}"),
|
subject: format!("acct:{user}@{domain}"),
|
||||||
aliases: vec![ctx.base()],
|
aliases: vec![ctx.base().to_string()],
|
||||||
links: vec![
|
links: vec![
|
||||||
JsonResourceDescriptorLink {
|
JsonResourceDescriptorLink {
|
||||||
rel: "self".to_string(),
|
rel: "self".to_string(),
|
||||||
link_type: Some("application/ld+json".to_string()),
|
link_type: Some("application/ld+json".to_string()),
|
||||||
href: Some(ctx.base()),
|
href: Some(ctx.base().to_string()),
|
||||||
properties: jrd::Map::default(),
|
properties: jrd::Map::default(),
|
||||||
titles: jrd::Map::default(),
|
titles: jrd::Map::default(),
|
||||||
},
|
},
|
||||||
|
@ -119,7 +119,7 @@ pub async fn webfinger(State(ctx): State<Context>, Query(query): Query<Webfinger
|
||||||
properties: jrd::Map::default(),
|
properties: jrd::Map::default(),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
let uid = ctx.uid(user.to_string());
|
let uid = ctx.uid(user);
|
||||||
match model::user::Entity::find_by_id(uid)
|
match model::user::Entity::find_by_id(uid)
|
||||||
.one(ctx.db())
|
.one(ctx.db())
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -9,7 +9,7 @@ pub async fn view(
|
||||||
AuthIdentity(_auth): AuthIdentity,
|
AuthIdentity(_auth): AuthIdentity,
|
||||||
Path(id): Path<String>
|
Path(id): Path<String>
|
||||||
) -> Result<Json<Account>, StatusCode> {
|
) -> Result<Json<Account>, StatusCode> {
|
||||||
match model::user::Entity::find_by_id(ctx.uid(id))
|
match model::user::Entity::find_by_id(ctx.uid(&id))
|
||||||
.find_also_related(model::config::Entity)
|
.find_also_related(model::config::Entity)
|
||||||
.one(ctx.db())
|
.one(ctx.db())
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -25,7 +25,7 @@ impl Administrable for super::Context {
|
||||||
banner_url: Option<String>,
|
banner_url: Option<String>,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
let key = openssl::rsa::Rsa::generate(2048).unwrap();
|
let key = openssl::rsa::Rsa::generate(2048).unwrap();
|
||||||
let ap_id = self.uid(username.clone());
|
let ap_id = self.uid(&username);
|
||||||
let db = self.db();
|
let db = self.db();
|
||||||
let domain = self.domain().to_string();
|
let domain = self.domain().to_string();
|
||||||
let user_model = crate::model::user::Model {
|
let user_model = crate::model::user::Model {
|
||||||
|
|
|
@ -4,6 +4,7 @@ use openssl::rsa::Rsa;
|
||||||
use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect, SelectColumns, Set};
|
use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QueryFilter, QuerySelect, SelectColumns, Set};
|
||||||
|
|
||||||
use crate::{config::Config, model, server::fetcher::Fetcher};
|
use crate::{config::Config, model, server::fetcher::Fetcher};
|
||||||
|
use uriproxy::UriClass;
|
||||||
|
|
||||||
use super::dispatcher::Dispatcher;
|
use super::dispatcher::Dispatcher;
|
||||||
|
|
||||||
|
@ -15,6 +16,7 @@ struct ContextInner {
|
||||||
config: Config,
|
config: Config,
|
||||||
domain: String,
|
domain: String,
|
||||||
protocol: String,
|
protocol: String,
|
||||||
|
base_url: String,
|
||||||
dispatcher: Dispatcher,
|
dispatcher: Dispatcher,
|
||||||
// TODO keep these pre-parsed
|
// TODO keep these pre-parsed
|
||||||
app: model::application::Model,
|
app: model::application::Model,
|
||||||
|
@ -72,6 +74,7 @@ impl Context {
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(Context(Arc::new(ContextInner {
|
Ok(Context(Arc::new(ContextInner {
|
||||||
|
base_url: format!("{}{}", protocol, domain),
|
||||||
db, domain, protocol, app, dispatcher, config,
|
db, domain, protocol, app, dispatcher, config,
|
||||||
relays: BTreeSet::from_iter(relays.into_iter()),
|
relays: BTreeSet::from_iter(relays.into_iter()),
|
||||||
})))
|
})))
|
||||||
|
@ -97,53 +100,38 @@ impl Context {
|
||||||
&self.0.protocol
|
&self.0.protocol
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn base(&self) -> String {
|
pub fn base(&self) -> &str {
|
||||||
format!("{}{}", self.0.protocol, self.0.domain)
|
&self.0.base_url
|
||||||
}
|
|
||||||
|
|
||||||
pub fn uri(&self, entity: &str, id: String) -> String {
|
|
||||||
if id.starts_with("http") { // ready-to-use id
|
|
||||||
id
|
|
||||||
} else if id.starts_with('+') { // compacted id
|
|
||||||
// TODO theres already 2 edge cases, i really need to get rid of this
|
|
||||||
id
|
|
||||||
.replace('@', "/")
|
|
||||||
.replace("///", "/@/") // omg wordpress PLEASE AAAAAAAAAAAAAAAAAAAA
|
|
||||||
.replace("//", "/@") // oops my method sucks!! TODO
|
|
||||||
.replacen('+', "https://", 1)
|
|
||||||
.replace(' ', "%20") // omg wordpress
|
|
||||||
} else { // bare local id
|
|
||||||
format!("{}{}/{}/{}", self.0.protocol, self.0.domain, entity, id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// get bare id, usually an uuid but unspecified
|
|
||||||
pub fn id(&self, uri: &str) -> String {
|
|
||||||
if uri.starts_with(&self.0.domain) {
|
|
||||||
uri.split('/').last().unwrap_or("").to_string()
|
|
||||||
} else {
|
|
||||||
uri
|
|
||||||
.replace("https://", "+")
|
|
||||||
.replace("http://", "+")
|
|
||||||
.replace('/', "@")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get full user id uri
|
/// get full user id uri
|
||||||
pub fn uid(&self, id: String) -> String {
|
pub fn uid(&self, id: &str) -> String {
|
||||||
self.uri("users", id)
|
uriproxy::uri(self.base(), UriClass::User, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get full object id uri
|
/// get full object id uri
|
||||||
pub fn oid(&self, id: String) -> String {
|
pub fn oid(&self, id: &str) -> String {
|
||||||
self.uri("objects", id)
|
uriproxy::uri(self.base(), UriClass::Object, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// get full activity id uri
|
/// get full activity id uri
|
||||||
pub fn aid(&self, id: String) -> String {
|
pub fn aid(&self, id: &str) -> String {
|
||||||
self.uri("activities", id)
|
uriproxy::uri(self.base(), UriClass::Activity, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO remove this!!
|
||||||
|
pub fn context_id(&self, id: &str) -> String {
|
||||||
|
uriproxy::uri(self.base(), UriClass::Context, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// get bare id, which is uuid for local stuff and ~{uri|base64} for remote stuff
|
||||||
|
pub fn id(&self, full_id: &str) -> String {
|
||||||
|
if self.is_local(full_id) {
|
||||||
|
uriproxy::decompose_id(full_id)
|
||||||
|
} else {
|
||||||
|
uriproxy::compact_id(full_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn server(id: &str) -> String {
|
pub fn server(id: &str) -> String {
|
||||||
id
|
id
|
||||||
|
@ -156,8 +144,7 @@ impl Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_local(&self, id: &str) -> bool {
|
pub fn is_local(&self, id: &str) -> bool {
|
||||||
// TODO consider precalculating once this format!
|
id.starts_with(self.base())
|
||||||
id.starts_with(&format!("{}{}", self.0.protocol, self.0.domain))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn expand_addressing(&self, targets: Vec<String>) -> crate::Result<Vec<String>> {
|
pub async fn expand_addressing(&self, targets: Vec<String>) -> crate::Result<Vec<String>> {
|
||||||
|
|
|
@ -15,8 +15,8 @@ impl apb::server::Outbox for Context {
|
||||||
|
|
||||||
async fn create_note(&self, uid: String, object: serde_json::Value) -> crate::Result<String> {
|
async fn create_note(&self, uid: String, object: serde_json::Value) -> crate::Result<String> {
|
||||||
let raw_oid = uuid::Uuid::new_v4().to_string();
|
let raw_oid = uuid::Uuid::new_v4().to_string();
|
||||||
let oid = self.oid(raw_oid.clone());
|
let oid = self.oid(&raw_oid);
|
||||||
let aid = self.aid(uuid::Uuid::new_v4().to_string());
|
let aid = self.aid(&uuid::Uuid::new_v4().to_string());
|
||||||
let activity_targets = object.addressed();
|
let activity_targets = object.addressed();
|
||||||
let object_model = self.insert_object(
|
let object_model = self.insert_object(
|
||||||
object
|
object
|
||||||
|
@ -24,7 +24,7 @@ impl apb::server::Outbox for Context {
|
||||||
.set_attributed_to(Node::link(uid.clone()))
|
.set_attributed_to(Node::link(uid.clone()))
|
||||||
.set_published(Some(chrono::Utc::now()))
|
.set_published(Some(chrono::Utc::now()))
|
||||||
.set_url(Node::maybe_link(self.cfg().instance.frontend.as_ref().map(|x| format!("{x}/objects/{raw_oid}")))),
|
.set_url(Node::maybe_link(self.cfg().instance.frontend.as_ref().map(|x| format!("{x}/objects/{raw_oid}")))),
|
||||||
Some(self.base()),
|
Some(self.base().to_string()),
|
||||||
).await?;
|
).await?;
|
||||||
|
|
||||||
let activity_model = model::activity::Model {
|
let activity_model = model::activity::Model {
|
||||||
|
@ -54,8 +54,8 @@ impl apb::server::Outbox for Context {
|
||||||
};
|
};
|
||||||
|
|
||||||
let raw_oid = uuid::Uuid::new_v4().to_string();
|
let raw_oid = uuid::Uuid::new_v4().to_string();
|
||||||
let oid = self.oid(raw_oid.clone());
|
let oid = self.oid(&raw_oid);
|
||||||
let aid = self.aid(uuid::Uuid::new_v4().to_string());
|
let aid = self.aid(&uuid::Uuid::new_v4().to_string());
|
||||||
let activity_targets = activity.addressed();
|
let activity_targets = activity.addressed();
|
||||||
|
|
||||||
self.insert_object(
|
self.insert_object(
|
||||||
|
@ -67,7 +67,7 @@ impl apb::server::Outbox for Context {
|
||||||
.set_bto(activity.bto())
|
.set_bto(activity.bto())
|
||||||
.set_cc(activity.cc())
|
.set_cc(activity.cc())
|
||||||
.set_bcc(activity.bcc()),
|
.set_bcc(activity.bcc()),
|
||||||
Some(self.base()),
|
Some(self.base().to_string()),
|
||||||
).await?;
|
).await?;
|
||||||
|
|
||||||
let activity_model = model::activity::Model::new(
|
let activity_model = model::activity::Model::new(
|
||||||
|
@ -86,7 +86,7 @@ impl apb::server::Outbox for Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn like(&self, uid: String, activity: serde_json::Value) -> crate::Result<String> {
|
async fn like(&self, uid: String, activity: serde_json::Value) -> crate::Result<String> {
|
||||||
let aid = self.aid(uuid::Uuid::new_v4().to_string());
|
let aid = self.aid(&uuid::Uuid::new_v4().to_string());
|
||||||
let activity_targets = activity.addressed();
|
let activity_targets = activity.addressed();
|
||||||
let oid = activity.object().id().ok_or_else(UpubError::bad_request)?;
|
let oid = activity.object().id().ok_or_else(UpubError::bad_request)?;
|
||||||
self.fetch_object(&oid).await?;
|
self.fetch_object(&oid).await?;
|
||||||
|
@ -118,7 +118,7 @@ impl apb::server::Outbox for Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn follow(&self, uid: String, activity: serde_json::Value) -> crate::Result<String> {
|
async fn follow(&self, uid: String, activity: serde_json::Value) -> crate::Result<String> {
|
||||||
let aid = self.aid(uuid::Uuid::new_v4().to_string());
|
let aid = self.aid(&uuid::Uuid::new_v4().to_string());
|
||||||
let activity_targets = activity.addressed();
|
let activity_targets = activity.addressed();
|
||||||
if activity.object().id().is_none() {
|
if activity.object().id().is_none() {
|
||||||
return Err(UpubError::bad_request());
|
return Err(UpubError::bad_request());
|
||||||
|
@ -139,7 +139,7 @@ impl apb::server::Outbox for Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn accept(&self, uid: String, activity: serde_json::Value) -> crate::Result<String> {
|
async fn accept(&self, uid: String, activity: serde_json::Value) -> crate::Result<String> {
|
||||||
let aid = self.aid(uuid::Uuid::new_v4().to_string());
|
let aid = self.aid(&uuid::Uuid::new_v4().to_string());
|
||||||
let activity_targets = activity.addressed();
|
let activity_targets = activity.addressed();
|
||||||
if activity.object().id().is_none() {
|
if activity.object().id().is_none() {
|
||||||
return Err(UpubError::bad_request());
|
return Err(UpubError::bad_request());
|
||||||
|
@ -192,7 +192,7 @@ impl apb::server::Outbox for Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn undo(&self, uid: String, activity: serde_json::Value) -> crate::Result<String> {
|
async fn undo(&self, uid: String, activity: serde_json::Value) -> crate::Result<String> {
|
||||||
let aid = self.aid(uuid::Uuid::new_v4().to_string());
|
let aid = self.aid(&uuid::Uuid::new_v4().to_string());
|
||||||
let activity_targets = activity.addressed();
|
let activity_targets = activity.addressed();
|
||||||
let old_aid = activity.object().id().ok_or_else(UpubError::bad_request)?;
|
let old_aid = activity.object().id().ok_or_else(UpubError::bad_request)?;
|
||||||
let old_activity = model::activity::Entity::find_by_id(old_aid)
|
let old_activity = model::activity::Entity::find_by_id(old_aid)
|
||||||
|
@ -235,7 +235,7 @@ impl apb::server::Outbox for Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete(&self, uid: String, activity: serde_json::Value) -> crate::Result<String> {
|
async fn delete(&self, uid: String, activity: serde_json::Value) -> crate::Result<String> {
|
||||||
let aid = self.aid(uuid::Uuid::new_v4().to_string());
|
let aid = self.aid(&uuid::Uuid::new_v4().to_string());
|
||||||
let oid = activity.object().id().ok_or_else(UpubError::bad_request)?;
|
let oid = activity.object().id().ok_or_else(UpubError::bad_request)?;
|
||||||
|
|
||||||
let object = model::object::Entity::find_by_id(&oid)
|
let object = model::object::Entity::find_by_id(&oid)
|
||||||
|
@ -275,7 +275,7 @@ impl apb::server::Outbox for Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update(&self, uid: String, activity: serde_json::Value) -> crate::Result<String> {
|
async fn update(&self, uid: String, activity: serde_json::Value) -> crate::Result<String> {
|
||||||
let aid = self.aid(uuid::Uuid::new_v4().to_string());
|
let aid = self.aid(&uuid::Uuid::new_v4().to_string());
|
||||||
let object_node = activity.object().extract().ok_or_else(UpubError::bad_request)?;
|
let object_node = activity.object().extract().ok_or_else(UpubError::bad_request)?;
|
||||||
|
|
||||||
match object_node.object_type() {
|
match object_node.object_type() {
|
||||||
|
@ -364,7 +364,7 @@ impl apb::server::Outbox for Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn announce(&self, uid: String, activity: serde_json::Value) -> crate::Result<String> {
|
async fn announce(&self, uid: String, activity: serde_json::Value) -> crate::Result<String> {
|
||||||
let aid = self.aid(uuid::Uuid::new_v4().to_string());
|
let aid = self.aid(&uuid::Uuid::new_v4().to_string());
|
||||||
let activity_targets = activity.addressed();
|
let activity_targets = activity.addressed();
|
||||||
let oid = activity.object().id().ok_or_else(UpubError::bad_request)?;
|
let oid = activity.object().id().ok_or_else(UpubError::bad_request)?;
|
||||||
self.fetch_object(&oid).await?;
|
self.fetch_object(&oid).await?;
|
||||||
|
|
14
uriproxy/Cargo.toml
Normal file
14
uriproxy/Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "uriproxy"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = [ "alemi <me@alemi.dev>" ]
|
||||||
|
description = "internal upub crate to handle remote uris"
|
||||||
|
license = "MIT"
|
||||||
|
keywords = ["upub", "uri", "base64"]
|
||||||
|
repository = "https://moonlit.technology/alemi/upub"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
base64 = "0.22"
|
47
uriproxy/src/lib.rs
Normal file
47
uriproxy/src/lib.rs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
use base64::Engine;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum UriClass {
|
||||||
|
User,
|
||||||
|
Object,
|
||||||
|
Activity,
|
||||||
|
Context,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsRef<str> for UriClass {
|
||||||
|
fn as_ref(&self) -> &str {
|
||||||
|
match self {
|
||||||
|
Self::User => "users",
|
||||||
|
Self::Object => "objects",
|
||||||
|
Self::Activity => "activities",
|
||||||
|
Self::Context => "context",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// unpack uri in id if valid, otherwise compose full uri with "{base}/{entity}/{id}"
|
||||||
|
pub fn uri(base: &str, entity: UriClass, id: &str) -> String {
|
||||||
|
if id.starts_with('~') { // ready-to-use base64-encoded id
|
||||||
|
if let Ok(bytes) = base64::prelude::BASE64_STANDARD.decode(id) {
|
||||||
|
if let Ok(uri) = std::str::from_utf8(&bytes) {
|
||||||
|
return uri.to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
format!("{}/{}/{}", base, entity.as_ref(), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// decompose local id constructed by uri() fn
|
||||||
|
pub fn decompose_id(full_id: &str) -> String {
|
||||||
|
full_id // https://example.org/users/test/followers/page?offset=42
|
||||||
|
.split('/') // ['https:', '', 'example.org', 'users', 'test', 'followers', 'page?offset=42' ]
|
||||||
|
.nth(4) // 'test'
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// encode with base64 remote url and prefix it with ~
|
||||||
|
pub fn compact_id(uri: &str) -> String {
|
||||||
|
let encoded = base64::prelude::BASE64_STANDARD.encode(uri.as_bytes());
|
||||||
|
format!("~{encoded}")
|
||||||
|
}
|
|
@ -27,6 +27,7 @@ leptos_router = { version = "0.6", features = ["csr"] }
|
||||||
leptos-use = { version = "0.10", features = ["serde"] }
|
leptos-use = { version = "0.10", features = ["serde"] }
|
||||||
reqwest = { version = "0.12", features = ["json"] }
|
reqwest = { version = "0.12", features = ["json"] }
|
||||||
apb = { path = "../apb", features = ["unstructured", "activitypub-fe", "activitypub-counters", "litepub"] }
|
apb = { path = "../apb", features = ["unstructured", "activitypub-fe", "activitypub-counters", "litepub"] }
|
||||||
|
uriproxy = { path = "../uriproxy/" }
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
|
|
@ -11,9 +11,9 @@ 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(FetchKind::User, &object_id),
|
apb::ActivityType::Follow => Uri::web(U::User, &object_id),
|
||||||
// TODO for update check what's being updated
|
// TODO for update check what's being updated
|
||||||
_ => Uri::web(FetchKind::Object, &object_id),
|
_ => Uri::web(U::Object, &object_id),
|
||||||
};
|
};
|
||||||
view! {
|
view! {
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -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(FetchKind::User, &auth.username() )} >{move || auth.username() }</a>
|
"hi "<a href={move || Uri::web(U::User, &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"));
|
||||||
|
|
|
@ -176,10 +176,10 @@ pub fn Object(object: crate::Object) -> impl IntoView {
|
||||||
<td><ActorBanner object=author /></td>
|
<td><ActorBanner object=author /></td>
|
||||||
<td class="rev" >
|
<td class="rev" >
|
||||||
{object.in_reply_to().id().map(|reply| view! {
|
{object.in_reply_to().id().map(|reply| view! {
|
||||||
<small><i><a class="clean" href={Uri::web(FetchKind::Object, &reply)} title={reply}>reply</a></i></small>
|
<small><i><a class="clean" href={Uri::web(U::Object, &reply)} title={reply}>reply</a></i></small>
|
||||||
})}
|
})}
|
||||||
<PrivacyMarker addressed=addressed />
|
<PrivacyMarker addressed=addressed />
|
||||||
<a class="clean hover ml-s" href={Uri::web(FetchKind::Object, object.id().unwrap_or_default())}>
|
<a class="clean hover ml-s" href={Uri::web(U::Object, object.id().unwrap_or_default())}>
|
||||||
<DateTime t=object.published() />
|
<DateTime t=object.published() />
|
||||||
</a>
|
</a>
|
||||||
<sup><small><a class="clean ml-s" href={external_url} target="_blank">"↗"</a></small></sup>
|
<sup><small><a class="clean ml-s" href={external_url} target="_blank">"↗"</a></small></sup>
|
||||||
|
|
|
@ -187,8 +187,8 @@ 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 => FetchKind::User,
|
apb::ActivityType::Follow => U::User,
|
||||||
_ => FetchKind::Object,
|
_ => U::Object,
|
||||||
};
|
};
|
||||||
gonna_fetch.insert(object_id.clone());
|
gonna_fetch.insert(object_id.clone());
|
||||||
sub_tasks.push(Box::pin(fetch_and_update_with_user(fetch_kind, object_id, auth)));
|
sub_tasks.push(Box::pin(fetch_and_update_with_user(fetch_kind, object_id, auth)));
|
||||||
|
@ -211,20 +211,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(FetchKind::User, uid, auth)));
|
sub_tasks.push(Box::pin(fetch_and_update(U::User, 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(FetchKind::User, uid, auth)));
|
sub_tasks.push(Box::pin(fetch_and_update(U::User, uid, auth)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for user in actors_seen {
|
for user in actors_seen {
|
||||||
sub_tasks.push(Box::pin(fetch_and_update(FetchKind::User, user, auth)));
|
sub_tasks.push(Box::pin(fetch_and_update(U::User, user, auth)));
|
||||||
}
|
}
|
||||||
|
|
||||||
futures::future::join_all(sub_tasks).await;
|
futures::future::join_all(sub_tasks).await;
|
||||||
|
@ -232,22 +232,22 @@ async fn process_activities(activities: Vec<serde_json::Value>, auth: Auth) -> V
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_and_update(kind: FetchKind, id: String, auth: Auth) {
|
async fn fetch_and_update(kind: U, id: String, auth: Auth) {
|
||||||
match Http::fetch(&Uri::api(kind, &id, false), auth).await {
|
match Http::fetch(&Uri::api(kind, &id, false), auth).await {
|
||||||
Ok(data) => CACHE.put(id, Arc::new(data)),
|
Ok(data) => CACHE.put(id, Arc::new(data)),
|
||||||
Err(e) => console_warn(&format!("could not fetch '{id}': {e}")),
|
Err(e) => console_warn(&format!("could not fetch '{id}': {e}")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_and_update_with_user(kind: FetchKind, id: String, auth: Auth) {
|
async fn fetch_and_update_with_user(kind: U, id: String, auth: Auth) {
|
||||||
fetch_and_update(kind.clone(), id.clone(), auth).await;
|
fetch_and_update(kind, id.clone(), auth).await;
|
||||||
if let Some(obj) = CACHE.get(&id) {
|
if let Some(obj) = CACHE.get(&id) {
|
||||||
if let Some(actor_id) = match kind {
|
if let Some(actor_id) = match kind {
|
||||||
FetchKind::Object => obj.attributed_to().id(),
|
U::Object => obj.attributed_to().id(),
|
||||||
FetchKind::Activity => obj.actor().id(),
|
U::Activity => obj.actor().id(),
|
||||||
FetchKind::User | FetchKind::Context => None,
|
U::User | U::Context => None,
|
||||||
} {
|
} {
|
||||||
fetch_and_update(FetchKind::User, actor_id, auth).await;
|
fetch_and_update(U::User, actor_id, auth).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(FetchKind::User, &actor_id)} class="clean hover">
|
<a href={Uri::web(U::User, &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(FetchKind::User, id)}>{Uri::pretty(id)}</a></div>
|
<div><b>?</b>" "<a class="clean hover" href={Uri::web(U::User, 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(FetchKind::User, &uid);
|
let uri = Uri::web(U::User, &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();
|
||||||
|
|
|
@ -17,6 +17,7 @@ pub const DEFAULT_AVATAR_URL: &str = "https://cdn.alemi.dev/social/gradient.png"
|
||||||
pub const NAME: &str = "μ";
|
pub const NAME: &str = "μ";
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use uriproxy::UriClass;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -42,7 +43,7 @@ impl ObjectCache {
|
||||||
self.0.insert(k, v);
|
self.0.insert(k, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn fetch(&self, k: &str, kind: FetchKind) -> reqwest::Result<Object> {
|
pub async fn fetch(&self, k: &str, kind: UriClass) -> reqwest::Result<Object> {
|
||||||
match self.get(k) {
|
match self.get(k) {
|
||||||
Some(x) => Ok(x),
|
Some(x) => Ok(x),
|
||||||
None => {
|
None => {
|
||||||
|
@ -58,25 +59,6 @@ impl ObjectCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum FetchKind {
|
|
||||||
User,
|
|
||||||
Object,
|
|
||||||
Activity,
|
|
||||||
Context,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AsRef<str> for FetchKind {
|
|
||||||
fn as_ref(&self) -> &str {
|
|
||||||
match self {
|
|
||||||
Self::User => "users",
|
|
||||||
Self::Object => "objects",
|
|
||||||
Self::Activity => "activities",
|
|
||||||
Self::Context => "context",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Http;
|
pub struct Http;
|
||||||
|
|
||||||
impl Http {
|
impl Http {
|
||||||
|
@ -121,28 +103,24 @@ impl Http {
|
||||||
pub struct Uri;
|
pub struct Uri;
|
||||||
|
|
||||||
impl Uri {
|
impl Uri {
|
||||||
pub fn full(kind: FetchKind, id: &str) -> String {
|
pub fn full(kind: UriClass, id: &str) -> String {
|
||||||
let kind = kind.as_ref();
|
uriproxy::uri(URL_BASE, kind, id)
|
||||||
if id.starts_with('+') {
|
|
||||||
id.replace('+', "https://").replace('@', "/")
|
|
||||||
} else {
|
|
||||||
format!("{URL_BASE}/{kind}/{id}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pretty(url: &str) -> String {
|
pub fn pretty(url: &str) -> String {
|
||||||
|
let bare = url.replace("https://", "");
|
||||||
if url.len() < 50 {
|
if url.len() < 50 {
|
||||||
url.replace("https://", "")
|
bare
|
||||||
} else {
|
} else {
|
||||||
format!("{}..", url.replace("https://", "").get(..50).unwrap_or_default())
|
format!("{}..", bare.get(..50).unwrap_or_default())
|
||||||
}.replace('/', "\u{200B}/\u{200B}")
|
}.replace('/', "\u{200B}/\u{200B}")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn short(url: &str) -> String {
|
pub fn short(url: &str) -> String {
|
||||||
if url.starts_with(URL_BASE) {
|
if url.starts_with(URL_BASE) {
|
||||||
url.split('/').last().unwrap_or_default().to_string()
|
uriproxy::decompose_id(url)
|
||||||
} else {
|
} else {
|
||||||
url.replace("https://", "+").replace('/', "@")
|
uriproxy::compact_id(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +132,7 @@ impl Uri {
|
||||||
/// - https://other.domain.net/unexpected/path/root
|
/// - https://other.domain.net/unexpected/path/root
|
||||||
/// - +other.domain.net@users@root
|
/// - +other.domain.net@users@root
|
||||||
/// - root
|
/// - root
|
||||||
pub fn web(kind: FetchKind, url: &str) -> String {
|
pub fn web(kind: UriClass, url: &str) -> String {
|
||||||
let kind = kind.as_ref();
|
let kind = kind.as_ref();
|
||||||
format!("/web/{kind}/{}", Self::short(url))
|
format!("/web/{kind}/{}", Self::short(url))
|
||||||
}
|
}
|
||||||
|
@ -167,7 +145,7 @@ impl Uri {
|
||||||
/// - https://other.domain.net/unexpected/path/root
|
/// - https://other.domain.net/unexpected/path/root
|
||||||
/// - +other.domain.net@users@root
|
/// - +other.domain.net@users@root
|
||||||
/// - root
|
/// - root
|
||||||
pub fn api(kind: FetchKind, url: &str, fetch: bool) -> String {
|
pub fn api(kind: UriClass, url: &str, fetch: bool) -> String {
|
||||||
let kind = kind.as_ref();
|
let kind = kind.as_ref();
|
||||||
format!("{URL_BASE}/{kind}/{}{}", Self::short(url), if fetch { "?fetch=true" } else { "" })
|
format!("{URL_BASE}/{kind}/{}{}", Self::short(url), if fetch { "?fetch=true" } else { "" })
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,11 +33,11 @@ pub fn DebugPage() -> impl IntoView {
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<small><a
|
<small><a
|
||||||
href={move|| Uri::web(FetchKind::Object, &query.get())}
|
href={move|| Uri::web(U::Object, &query.get())}
|
||||||
>obj</a>
|
>obj</a>
|
||||||
" "
|
" "
|
||||||
<a
|
<a
|
||||||
href={move|| Uri::web(FetchKind::User, &query.get())}
|
href={move|| Uri::web(U::User, &query.get())}
|
||||||
>usr</a></small>
|
>usr</a></small>
|
||||||
</td>
|
</td>
|
||||||
<td class="w-100"><input class="w-100" type="text" on:input=move|ev| set_query.set(event_target_value(&ev)) placeholder="AP id" /></td>
|
<td class="w-100"><input class="w-100" type="text" on:input=move|ev| set_query.set(event_target_value(&ev)) placeholder="AP id" /></td>
|
||||||
|
|
|
@ -21,19 +21,19 @@ pub fn ObjectPage(tl: Timeline) -> impl IntoView {
|
||||||
}
|
}
|
||||||
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(FetchKind::Object, &oid)) {
|
match CACHE.get(&Uri::full(U::Object, &oid)) {
|
||||||
Some(x) => Some(x.clone()),
|
Some(x) => Some(x.clone()),
|
||||||
None => {
|
None => {
|
||||||
let obj = Http::fetch::<serde_json::Value>(&Uri::api(FetchKind::Object, &oid, true), auth).await.ok()?;
|
let obj = Http::fetch::<serde_json::Value>(&Uri::api(U::Object, &oid, true), auth).await.ok()?;
|
||||||
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(FetchKind::User, &author, true), auth
|
&Uri::api(U::User, &author, true), auth
|
||||||
).await {
|
).await {
|
||||||
CACHE.put(Uri::full(FetchKind::User, &author), Arc::new(user));
|
CACHE.put(Uri::full(U::User, &author), Arc::new(user));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CACHE.put(Uri::full(FetchKind::Object, &oid), obj.clone());
|
CACHE.put(Uri::full(U::Object, &oid), obj.clone());
|
||||||
Some(obj)
|
Some(obj)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ pub fn ObjectPage(tl: Timeline) -> impl IntoView {
|
||||||
},
|
},
|
||||||
Some(Some(o)) => {
|
Some(Some(o)) => {
|
||||||
let object = o.clone();
|
let object = o.clone();
|
||||||
let tl_url = format!("{}/page", Uri::api(FetchKind::Context, &o.context().id().unwrap_or_default(), false));
|
let tl_url = format!("{}/page", Uri::api(U::Context, &o.context().id().unwrap_or_default(), 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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(FetchKind::User, &q, true);
|
let user_fetch = Uri::api(U::User, &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()?)) }
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -19,7 +19,7 @@ pub fn SearchPage() -> impl IntoView {
|
||||||
let object = create_local_resource(
|
let object = 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 object_fetch = Uri::api(FetchKind::Object, &q, true);
|
let object_fetch = Uri::api(U::Object, &q, true);
|
||||||
async move { Some(Arc::new(Http::fetch::<serde_json::Value>(&object_fetch, auth).await.ok()?)) }
|
async move { Some(Arc::new(Http::fetch::<serde_json::Value>(&object_fetch, auth).await.ok()?)) }
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
|
@ -36,12 +36,12 @@ pub fn UserPage(tl: Timeline) -> impl IntoView {
|
||||||
}
|
}
|
||||||
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(FetchKind::User, &id)) {
|
match CACHE.get(&Uri::full(U::User, &id)) {
|
||||||
Some(x) => Some(x.clone()),
|
Some(x) => Some(x.clone()),
|
||||||
None => {
|
None => {
|
||||||
let user : serde_json::Value = Http::fetch(&Uri::api(FetchKind::User, &id, true), auth).await.ok()?;
|
let user : serde_json::Value = Http::fetch(&Uri::api(U::User, &id, true), auth).await.ok()?;
|
||||||
let user = Arc::new(user);
|
let user = Arc::new(user);
|
||||||
CACHE.put(Uri::full(FetchKind::User, &id), user.clone());
|
CACHE.put(Uri::full(U::User, &id), user.clone());
|
||||||
Some(user)
|
Some(user)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,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(FetchKind::User, &id.clone(), false));
|
let tl_url = format!("{}/outbox/page", Uri::api(U::User, &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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
Http, Uri, FetchKind,
|
Http, Uri,
|
||||||
CACHE, URL_BASE,
|
CACHE, URL_BASE,
|
||||||
auth::{Auth, AuthToken},
|
auth::{Auth, AuthToken},
|
||||||
page::*,
|
page::*,
|
||||||
components::*,
|
components::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub use uriproxy::UriClass as U;
|
||||||
|
|
Loading…
Reference in a new issue