Compare commits

...

3 commits

6 changed files with 97 additions and 8 deletions

View file

@ -90,6 +90,21 @@ impl Query {
select select
} }
// TODO this double join is probably not the best way to query for this...
pub fn hashtags() -> Select<model::hashtag::Entity> {
let mut select =
model::hashtag::Entity::find()
.join(sea_orm::JoinType::InnerJoin, model::hashtag::Relation::Objects.def())
.join(sea_orm::JoinType::InnerJoin, model::object::Relation::Addressing.def())
.select_only();
for col in model::object::Column::iter() {
select = select.select_column_as(col, format!("{}{}", model::object::Entity.table_name(), col.to_string()));
}
select
}
pub fn notifications(user: i64, show_seen: bool) -> Select<model::notification::Entity> { pub fn notifications(user: i64, show_seen: bool) -> Select<model::notification::Entity> {
let mut select = let mut select =
model::notification::Entity::find() model::notification::Entity::find()

View file

@ -5,6 +5,7 @@ pub mod object;
pub mod activity; pub mod activity;
pub mod application; pub mod application;
pub mod auth; pub mod auth;
pub mod tags;
pub mod well_known; pub mod well_known;
use axum::{http::StatusCode, response::IntoResponse, routing::{get, patch, post, put}, Router}; use axum::{http::StatusCode, response::IntoResponse, routing::{get, patch, post, put}, Router};
@ -59,6 +60,9 @@ impl ActivityPubRouter for Router<upub::Context> {
// .route("/actors/:id/audience/page", get(ap::actor::audience::page)) // .route("/actors/:id/audience/page", get(ap::actor::audience::page))
// activities // activities
.route("/activities/:id", get(ap::activity::view)) .route("/activities/:id", get(ap::activity::view))
// hashtags
.route("/tags/:id", get(ap::tags::get))
.route("/tags/:id/page", get(ap::tags::page))
// specific object routes // specific object routes
.route("/objects/:id", get(ap::object::view)) .route("/objects/:id", get(ap::object::view))
.route("/objects/:id/replies", get(ap::object::replies::get)) .route("/objects/:id/replies", get(ap::object::replies::get))

View file

@ -0,0 +1,52 @@
use axum::extract::{Path, Query, State};
use sea_orm::{QueryFilter, QuerySelect, ColumnTrait};
use upub::{selector::{BatchFillable, RichActivity}, Context};
use crate::{activitypub::Pagination, builders::JsonLD, AuthIdentity};
pub async fn get(
State(ctx): State<Context>,
Path(id): Path<String>,
) -> crate::ApiResult<JsonLD<serde_json::Value>> {
crate::builders::collection(
&upub::url!(ctx, "/tags/{id}"),
None,
)
}
pub async fn page(
State(ctx): State<Context>,
Path(id): Path<String>,
AuthIdentity(auth): AuthIdentity,
Query(page): Query<Pagination>,
) -> crate::ApiResult<JsonLD<serde_json::Value>> {
let limit = page.batch.unwrap_or(20).min(50);
let offset = page.offset.unwrap_or(0);
let objects = upub::Query::hashtags()
.filter(auth.filter())
.filter(upub::model::hashtag::Column::Name.eq(&id))
.limit(limit)
.offset(offset)
.into_model::<RichActivity>()
.all(ctx.db())
.await?
.with_batched::<upub::model::attachment::Entity>(ctx.db())
.await?
.with_batched::<upub::model::mention::Entity>(ctx.db())
.await?
.with_batched::<upub::model::hashtag::Entity>(ctx.db())
.await?
.into_iter()
.map(|x| x.ap())
.collect();
crate::builders::collection_page(
&upub::url!(ctx, "/tags/{id}/page"),
offset,
limit,
objects,
)
}

View file

@ -5,6 +5,7 @@ pub enum UriClass {
Actor, Actor,
Object, Object,
Activity, Activity,
Hashtag,
} }
impl AsRef<str> for UriClass { impl AsRef<str> for UriClass {
@ -13,6 +14,7 @@ impl AsRef<str> for UriClass {
Self::Actor => "actors", Self::Actor => "actors",
Self::Object => "objects", Self::Object => "objects",
Self::Activity => "activities", Self::Activity => "activities",
Self::Hashtag => "tags",
} }
} }
} }

View file

@ -58,23 +58,37 @@ pub fn Object(
content = content.replace(&from, &to); content = content.replace(&from, &to);
} }
let audience_badge = object.audience().id().str() let audience_badge = object.audience().id().str()
.map(|x| view! { .map(|x| view! {
<a class="clean dim" href={Uri::web(U::Actor, &x)} rel="nofollow noreferrer"> <a class="clean dim" href={Uri::web(U::Actor, &x)}>
<span <span class="border-button ml-1" title={x.clone()}>
class="border-button"
title="this is a group: all interactions will be broadcasted to group members!"
>
<code class="color mr-s">&</code> <code class="color mr-s">&</code>
<small> <small class="mr-s">
{Uri::pretty(&x, 30)} {Uri::pretty(&x, 30)}
</small> </small>
</span> </span>
</a> </a>
}); });
let hashtag_badges = object.tag().filter_map(|x| {
if let Ok(apb::LinkType::Hashtag) = apb::Link::link_type(&x) {
let name = apb::Link::name(&x).unwrap_or_default().replace('#', "");
let href = Uri::web(U::Hashtag, &name);
Some(view! {
<a class="clean dim" href={href}>
<span class="border-button ml-1">
<code class="color mr-s">#</code>
<small class="mr-s">
{name}
</small>
</span>
</a>
})
} else {
None
}
}).collect_view();
let post_image = object.image().get().and_then(|x| x.url().id().str()).map(|x| { let post_image = object.image().get().and_then(|x| x.url().id().str()).map(|x| {
let (expand, set_expand) = create_signal(false); let (expand, set_expand) = create_signal(false);
view! { view! {
@ -140,6 +154,7 @@ pub fn Object(
</table> </table>
{post} {post}
<div class="mt-s ml-1 rev"> <div class="mt-s ml-1 rev">
{if !reply { Some(hashtag_badges) } else { None }}
{if !reply { audience_badge } else { None }} {if !reply { audience_badge } else { None }}
<ReplyButton n=comments target=oid.clone() /> <ReplyButton n=comments target=oid.clone() />
<LikeButton n=likes liked=already_liked target=oid.clone() author=author_id private=!public /> <LikeButton n=likes liked=already_liked target=oid.clone() author=author_id private=!public />

View file

@ -182,6 +182,7 @@ async fn fetch_and_update_with_user(kind: U, id: String, auth: Auth) {
U::Object => obj.attributed_to().id().str(), U::Object => obj.attributed_to().id().str(),
U::Activity => obj.actor().id().str(), U::Activity => obj.actor().id().str(),
U::Actor => None, U::Actor => None,
U::Hashtag => None,
} { } {
fetch_and_update(U::Actor, actor_id, auth).await; fetch_and_update(U::Actor, actor_id, auth).await;
} }