feat: added notifications collections

This commit is contained in:
əlemi 2024-06-28 01:06:31 +02:00
parent 727e977c4e
commit bb79ca7728
Signed by: alemi
GPG key ID: A4895B84D311642C
6 changed files with 93 additions and 5 deletions

View file

@ -169,6 +169,14 @@ impl Node<serde_json::Value> {
)
}
pub fn maybe_array(values: Vec<serde_json::Value>) -> Self {
if values.is_empty() {
Node::Empty
} else {
Node::array(values)
}
}
#[cfg(feature = "fetch")]
pub async fn fetch(&mut self) -> reqwest::Result<&mut Self> {
if let Node::Link(link) = self {

View file

@ -44,7 +44,7 @@ pub trait Actor : Object {
#[cfg(feature = "activitypub-fe")]
fn followed_by_me(&self) -> Field<bool> { Err(FieldErr("followedByMe")) }
#[cfg(feature = "activitypub-fe")]
fn feed(&self) -> Node<Self::Collection> { Node::Empty }
fn notifications(&self) -> Node<Self::Collection> { Node::Empty }
#[cfg(feature = "activitypub-counters")]
fn followers_count(&self) -> Field<u64> { Err(FieldErr("followersCount")) }
@ -99,7 +99,7 @@ pub trait ActorMut : ObjectMut {
#[cfg(feature = "activitypub-fe")]
fn set_followed_by_me(self, val: Option<bool>) -> Self;
#[cfg(feature = "activitypub-fe")]
fn set_feed(self, val: Node<Self::Collection>) -> Self;
fn set_notifications(self, val: Node<Self::Collection>) -> Self;
#[cfg(feature = "activitypub-counters")]
fn set_followers_count(self, val: Option<u64>) -> Self;
@ -155,7 +155,7 @@ impl Actor for serde_json::Value {
#[cfg(feature = "activitypub-fe")]
crate::getter! { followedByMe -> bool }
#[cfg(feature = "activitypub-fe")]
crate::getter! { feed -> node Self::Collection }
crate::getter! { notifications -> node Self::Collection }
#[cfg(feature = "activitypub-counters")]
crate::getter! { followingCount -> u64 }
@ -206,7 +206,7 @@ impl ActorMut for serde_json::Value {
#[cfg(feature = "activitypub-fe")]
crate::setter! { followedByMe -> bool }
#[cfg(feature = "activitypub-fe")]
crate::setter! { feed -> node Self::Collection }
crate::setter! { notifications -> node Self::Collection }
#[cfg(feature = "activitypub-counters")]
crate::setter! { followingCount -> u64 }

View file

@ -85,6 +85,26 @@ impl Query {
select
}
pub fn notifications(user: i64, show_seen: bool) -> Select<model::notification::Entity> {
let mut select =
model::notification::Entity::find()
.join(sea_orm::JoinType::InnerJoin, model::notification::Relation::Activities.def())
.order_by_desc(model::notification::Column::Published)
.filter(model::notification::Column::Actor.eq(user));
if !show_seen {
select = select.filter(model::notification::Column::Seen.eq(false));
}
select = select.select_only();
for column in model::activity::Column::iter() {
select = select.select_column(column);
}
select
}
pub fn notify(activity: i64, actor: i64) -> Insert<model::notification::ActiveModel> {
model::notification::Entity::insert(
model::notification::ActiveModel {

View file

@ -1,6 +1,7 @@
pub mod inbox;
pub mod outbox;
pub mod following;
pub mod notifications;
use axum::extract::{Path, Query, State};
@ -68,7 +69,7 @@ pub async fn view(
));
if auth.is(&uid) {
user = user.set_feed(Node::link(upub::url!(ctx, "/actors/{id}/feed")));
user = user.set_notifications(Node::link(upub::url!(ctx, "/actors/{id}/notifications")));
}
if !auth.is(&uid) && !cfg.show_followers_count {

View file

@ -0,0 +1,57 @@
use axum::extract::{Path, Query, State};
use sea_orm::{PaginatorTrait, QuerySelect};
use upub::Context;
use crate::{activitypub::Pagination, builders::JsonLD, AuthIdentity, Identity};
pub async fn get(
State(ctx): State<Context>,
Path(id): Path<String>,
AuthIdentity(auth): AuthIdentity,
) -> crate::ApiResult<JsonLD<serde_json::Value>> {
let Identity::Local { id: uid, internal } = &auth else {
// notifications are only for local users
return Err(crate::ApiError::forbidden());
};
if uid != &ctx.uid(&id) {
return Err(crate::ApiError::forbidden());
}
let count = upub::Query::notifications(*internal, false)
.count(ctx.db())
.await?;
crate::builders::collection(&upub::url!(ctx, "/actors/{id}/notifications"), Some(count))
}
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 Identity::Local { id: uid, internal } = &auth else {
// notifications are only for local users
return Err(crate::ApiError::forbidden());
};
if uid != &ctx.uid(&id) {
return Err(crate::ApiError::forbidden());
}
let limit = page.batch.unwrap_or(20).min(50);
let offset = page.offset.unwrap_or(0);
let activities = upub::Query::notifications(*internal, false)
.limit(limit)
.offset(offset)
.into_model::<upub::model::activity::Model>()
.all(ctx.db())
.await?
.into_iter()
.map(|x| x.ap())
.collect();
crate::builders::collection_page(&upub::url!(ctx, "/actors/{id}/notifications/page"), offset, limit, activities)
}

View file

@ -49,6 +49,8 @@ impl ActivityPubRouter for Router<upub::Context> {
.route("/actors/:id/outbox", post(ap::actor::outbox::post))
.route("/actors/:id/outbox", get(ap::actor::outbox::get))
.route("/actors/:id/outbox/page", get(ap::actor::outbox::page))
.route("/actors/:id/notifications", get(ap::actor::notifications::get))
.route("/actors/:id/notifications/page", get(ap::actor::notifications::page))
.route("/actors/:id/followers", get(ap::actor::following::get::<false>))
.route("/actors/:id/followers/page", get(ap::actor::following::page::<false>))
.route("/actors/:id/following", get(ap::actor::following::get::<true>))