Notification fetching base

This commit is contained in:
Natty 2024-01-15 01:46:08 +01:00
parent 3c16574d03
commit 475c33e57e
Signed by: natty
GPG Key ID: BF6CB659ADEE60EC
27 changed files with 354 additions and 18 deletions

1
Cargo.lock generated
View File

@ -1617,6 +1617,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"serde_urlencoded", "serde_urlencoded",
"strum",
"ts-rs", "ts-rs",
"unicode-segmentation", "unicode-segmentation",
] ]

View File

@ -1,6 +1,7 @@
pub mod emoji; pub mod emoji;
pub mod model_ext; pub mod model_ext;
pub mod note_model; pub mod note_model;
pub mod notification_model;
pub mod poll; pub mod poll;
pub mod user_model; pub mod user_model;
@ -10,6 +11,7 @@ pub use sea_orm;
use user_model::UserResolver; use user_model::UserResolver;
use crate::note_model::NoteResolver; use crate::note_model::NoteResolver;
use crate::notification_model::NotificationResolver;
use chrono::Utc; use chrono::Utc;
use ext_calckey_model_migration::{Migrator, MigratorTrait}; use ext_calckey_model_migration::{Migrator, MigratorTrait};
use futures_util::StreamExt; use futures_util::StreamExt;
@ -296,6 +298,14 @@ impl CalckeyModel {
Ok(meta) Ok(meta)
} }
pub fn get_notification_resolver(&self) -> NotificationResolver {
NotificationResolver::new(
self.clone(),
self.get_user_resolver(),
self.get_note_resolver(),
)
}
pub fn get_note_resolver(&self) -> NoteResolver { pub fn get_note_resolver(&self) -> NoteResolver {
NoteResolver::new(self.clone(), self.get_user_resolver()) NoteResolver::new(self.clone(), self.get_user_resolver())
} }

View File

@ -25,6 +25,7 @@ const USER: &str = "user.";
const REPLY: &str = "reply."; const REPLY: &str = "reply.";
const RENOTE: &str = "renote."; const RENOTE: &str = "renote.";
#[derive(Clone)]
pub struct NoteResolver { pub struct NoteResolver {
db: CalckeyModel, db: CalckeyModel,
user_resolver: UserResolver, user_resolver: UserResolver,
@ -207,7 +208,7 @@ impl NoteResolver {
*/ */
fn attach_note( pub fn attach_note(
&self, &self,
q: &mut SelectStatement, q: &mut SelectStatement,
note_tbl: &MagIden, note_tbl: &MagIden,

View File

@ -0,0 +1,162 @@
use crate::model_ext::{
AliasColumnExt, AliasSourceExt, AliasSuffixExt, CursorPaginationExt, EntityPrefixExt, MagIden,
ModelPagination, SelectColumnsExt,
};
use crate::note_model::data::NoteData;
use crate::note_model::{NoteResolveOptions, NoteResolver};
use crate::user_model::{UserData, UserResolveOptions, UserResolver};
use crate::{CalckeyDbError, CalckeyModel};
use chrono::{DateTime, Utc};
use ck::{access_token, notification, user};
use ext_calckey_model_migration::{JoinType, SelectStatement};
use magnetar_sdk::types::SpanFilter;
use sea_orm::Iden;
use sea_orm::{DbErr, EntityTrait, FromQueryResult, QueryFilter, QueryResult, QuerySelect};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NotificationData {
pub notification: notification::Model,
pub access_token: Option<access_token::Model>,
pub notifier: Option<UserData>,
pub notification_note: Option<NoteData>,
}
impl FromQueryResult for NotificationData {
fn from_query_result(res: &QueryResult, prefix: &str) -> Result<Self, DbErr> {
let prefix = if prefix.is_empty() {
notification::Entity.base_prefix()
} else {
MagIden::alias(prefix)
};
Ok(NotificationData {
notification: notification::Model::from_query_result(res, &prefix.to_string())?,
access_token: access_token::Model::from_query_result_optional(
res,
&prefix.join_str_as_str(ACCESS_TOKEN),
)?,
notification_note: NoteData::from_query_result_optional(
res,
&prefix.join_str_as_str(NOTIFICATION_NOTE),
)?,
notifier: UserData::from_query_result_optional(res, &prefix.join_str_as_str(NOTIFIER))?,
})
}
}
impl ModelPagination for NotificationData {
fn id(&self) -> &str {
&self.notification.id
}
fn time(&self) -> DateTime<Utc> {
self.notification.created_at.into()
}
}
const NOTIFICATION: &str = "notification.";
const NOTIFIER: &str = "notifier.";
const NOTIFICATION_NOTE: &str = "note.";
const ACCESS_TOKEN: &str = "access_token.";
pub struct NotificationResolveOptions {}
#[derive(Clone)]
pub struct NotificationResolver {
db: CalckeyModel,
note_resolver: NoteResolver,
user_resolver: UserResolver,
}
impl NotificationResolver {
pub(crate) fn new(
db: CalckeyModel,
user_resolver: UserResolver,
note_resolver: NoteResolver,
) -> Self {
Self {
db,
note_resolver,
user_resolver,
}
}
pub fn resolve(
&self,
q: &mut SelectStatement,
notification_tbl: &MagIden,
note_options: &NoteResolveOptions,
user_options: &UserResolveOptions,
note_resolver: &NoteResolver,
user_resolver: &UserResolver,
) {
let notifier_tbl = notification_tbl.join_str(NOTIFIER);
q.add_aliased_columns::<user::Entity>(&notifier_tbl);
q.join_columns(
JoinType::LeftJoin,
notification::Relation::User2.with_from_alias(notification_tbl),
&notifier_tbl,
);
user_resolver.resolve(q, &notifier_tbl, &user_options);
let token_tbl = notification_tbl.join_str(ACCESS_TOKEN);
q.add_aliased_columns::<access_token::Entity>(&token_tbl);
q.join_columns(
JoinType::LeftJoin,
notification::Relation::AccessToken.with_from_alias(notification_tbl),
&token_tbl,
);
let note_tbl = notification_tbl.join_str(NOTIFICATION_NOTE);
q.join_columns(
JoinType::LeftJoin,
notification::Relation::Note.with_from_alias(notification_tbl),
&note_tbl,
);
note_resolver.attach_note(q, &note_tbl, 1, 1, note_options, &self.user_resolver);
}
pub async fn get(
&self,
note_options: &NoteResolveOptions,
user_options: &UserResolveOptions,
user_id: &str,
pagination: &SpanFilter,
prev: &mut Option<SpanFilter>,
next: &mut Option<SpanFilter>,
limit: u64,
) -> Result<Vec<NotificationData>, CalckeyDbError> {
let notification_tbl = notification::Entity.base_prefix();
let mut select = notification::Entity::find();
let query = QuerySelect::query(&mut select);
self.resolve(
query,
&notification_tbl,
note_options,
user_options,
&self.note_resolver,
&self.user_resolver,
);
let notifications = select
.filter(
notification_tbl
.col(notification::Column::NotifieeId)
.eq(user_id),
)
.get_paginated_model::<NotificationData, _, _>(
&self.db.0,
(notification::Column::CreatedAt, notification::Column::Id),
pagination,
prev,
next,
limit,
)
.await?;
Ok(notifications)
}
}

View File

@ -115,6 +115,7 @@ const PROFILE: &str = "profile.";
const AVATAR: &str = "avatar."; const AVATAR: &str = "avatar.";
const BANNER: &str = "banner."; const BANNER: &str = "banner.";
#[derive(Clone)]
pub struct UserResolver { pub struct UserResolver {
db: CalckeyModel, db: CalckeyModel,
} }

View File

@ -14,3 +14,14 @@ export { PackUserBase } from "./types/packed/PackUserBase";
export { PackUserMaybeAll } from "./types/packed/PackUserMaybeAll"; export { PackUserMaybeAll } from "./types/packed/PackUserMaybeAll";
export { PackUserSelf } from "./types/packed/PackUserSelf"; export { PackUserSelf } from "./types/packed/PackUserSelf";
export { PackUserSelfMaybeAll } from "./types/packed/PackUserSelfMaybeAll"; export { PackUserSelfMaybeAll } from "./types/packed/PackUserSelfMaybeAll";
export { PackNotificationFollow } from "./types/packed/PackNotificationFollow";
export { PackNotificationFollowRequestReceived } from "./types/packed/PackNotificationFollowRequestReceived";
export { PackNotificationFollowRequestAccepted } from "./types/packed/PackNotificationFollowRequestAccepted";
export { PackNotificationMention } from "./types/packed/PackNotificationMention";
export { PackNotificationReply } from "./types/packed/PackNotificationReply";
export { PackNotificationRenote } from "./types/packed/PackNotificationRenote";
export { PackNotificationReaction } from "./types/packed/PackNotificationReaction";
export { PackNotificationQuote } from "./types/packed/PackNotificationQuote";
export { PackNotificationPollEnd } from "./types/packed/PackNotificationPollEnd";
export { PackNotificationApp } from "./types/packed/PackNotificationApp";
export { PackNotification } from "./types/PackNotification";

View File

@ -49,3 +49,8 @@ export { NoFilter } from "./types/NoFilter";
export { TimelineType } from "./types/TimelineType"; export { TimelineType } from "./types/TimelineType";
export { Empty } from "./types/Empty"; export { Empty } from "./types/Empty";
export { PaginationShape } from "./types/PaginationShape"; export { PaginationShape } from "./types/PaginationShape";
export { NotificationBase } from "./types/NotificationBase";
export { NotificationAppExt } from "./types/NotificationAppExt";
export { NotificationNoteExt } from "./types/NotificationNoteExt";
export { NotificationReactionExt } from "./types/NotificationReactionExt";
export { NotificationUserExt } from "./types/NotificationUserExt";

View File

@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export interface NotificationAppExt { body: string, header: string, icon: string, }

View File

@ -0,0 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export interface NotificationBase { id: string, created_at: string, is_read: boolean, }

View File

@ -0,0 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PackNoteMaybeFull } from "./packed/PackNoteMaybeFull";
export interface NotificationNoteExt { note: PackNoteMaybeFull, }

View File

@ -0,0 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Reaction } from "./Reaction";
export interface NotificationReactionExt { reaction: Reaction, }

View File

@ -1,3 +1,3 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type NotificationType = "Follow" | "Mention" | "Reply" | "Renote" | "Quote" | "Reaction" | "PollVote" | "PollEnded" | "FollowRequest" | "FollowRequestAccepted" | "App"; export type NotificationType = "Follow" | "FollowRequestReceived" | "FollowRequestAccepted" | "Mention" | "Reply" | "Renote" | "Reaction" | "Quote" | "PollEnd" | "App";

View File

@ -0,0 +1,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PackUserBase } from "./packed/PackUserBase";
export interface NotificationUserExt { user: PackUserBase, }

View File

@ -0,0 +1,13 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { PackNotificationApp } from "./packed/PackNotificationApp";
import type { PackNotificationFollow } from "./packed/PackNotificationFollow";
import type { PackNotificationFollowRequestAccepted } from "./packed/PackNotificationFollowRequestAccepted";
import type { PackNotificationFollowRequestReceived } from "./packed/PackNotificationFollowRequestReceived";
import type { PackNotificationMention } from "./packed/PackNotificationMention";
import type { PackNotificationPollEnd } from "./packed/PackNotificationPollEnd";
import type { PackNotificationQuote } from "./packed/PackNotificationQuote";
import type { PackNotificationReaction } from "./packed/PackNotificationReaction";
import type { PackNotificationRenote } from "./packed/PackNotificationRenote";
import type { PackNotificationReply } from "./packed/PackNotificationReply";
export type PackNotification = { "type": "Follow" } & PackNotificationFollow | { "type": "FollowRequestReceived" } & PackNotificationFollowRequestReceived | { "type": "FollowRequestAccepted" } & PackNotificationFollowRequestAccepted | { "type": "Mention" } & PackNotificationMention | { "type": "Reply" } & PackNotificationReply | { "type": "Renote" } & PackNotificationRenote | { "type": "Reaction" } & PackNotificationReaction | { "type": "Quote" } & PackNotificationQuote | { "type": "PollEnd" } & PackNotificationPollEnd | { "type": "App" } & PackNotificationApp;

View File

@ -0,0 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Id } from "../Id";
import type { NotificationAppExt } from "../NotificationAppExt";
export type PackNotificationApp = Id & NotificationAppExt;

View File

@ -0,0 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Id } from "../Id";
import type { NotificationUserExt } from "../NotificationUserExt";
export type PackNotificationFollow = Id & NotificationUserExt;

View File

@ -0,0 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Id } from "../Id";
import type { NotificationUserExt } from "../NotificationUserExt";
export type PackNotificationFollowRequestAccepted = Id & NotificationUserExt;

View File

@ -0,0 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Id } from "../Id";
import type { NotificationUserExt } from "../NotificationUserExt";
export type PackNotificationFollowRequestReceived = Id & NotificationUserExt;

View File

@ -0,0 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Id } from "../Id";
import type { NotificationNoteExt } from "../NotificationNoteExt";
export type PackNotificationMention = Id & NotificationNoteExt;

View File

@ -0,0 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Id } from "../Id";
import type { NotificationNoteExt } from "../NotificationNoteExt";
export type PackNotificationPollEnd = Id & NotificationNoteExt;

View File

@ -0,0 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Id } from "../Id";
import type { NotificationNoteExt } from "../NotificationNoteExt";
export type PackNotificationQuote = Id & NotificationNoteExt;

View File

@ -0,0 +1,6 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Id } from "../Id";
import type { NotificationReactionExt } from "../NotificationReactionExt";
import type { NotificationUserExt } from "../NotificationUserExt";
export type PackNotificationReaction = Id & NotificationReactionExt & NotificationUserExt;

View File

@ -0,0 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Id } from "../Id";
import type { NotificationNoteExt } from "../NotificationNoteExt";
export type PackNotificationRenote = Id & NotificationNoteExt;

View File

@ -0,0 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { Id } from "../Id";
import type { NotificationNoteExt } from "../NotificationNoteExt";
export type PackNotificationReply = Id & NotificationNoteExt;

View File

@ -15,6 +15,8 @@ serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true } serde_json = { workspace = true }
serde_urlencoded = { workspace = true } serde_urlencoded = { workspace = true }
strum = { workspace = true, features = ["derive"] }
ts-rs = { workspace = true, features = ["chrono", "chrono-impl"] } ts-rs = { workspace = true, features = ["chrono", "chrono-impl"] }
unicode-segmentation = { workspace = true } unicode-segmentation = { workspace = true }

View File

@ -2,9 +2,11 @@ pub mod drive;
pub mod emoji; pub mod emoji;
pub mod instance; pub mod instance;
pub mod note; pub mod note;
pub mod notification;
pub mod timeline; pub mod timeline;
pub mod user; pub mod user;
use crate::types::notification::NotificationType;
use crate::util_types::U64Range; use crate::util_types::U64Range;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use serde::de::Error; use serde::de::Error;
@ -213,22 +215,6 @@ impl<T: AsRef<str>> From<T> for Id {
#[repr(transparent)] #[repr(transparent)]
pub struct MmXml(pub String); pub struct MmXml(pub String);
#[derive(Copy, Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
pub enum NotificationType {
Follow,
Mention,
Reply,
Renote,
Quote,
Reaction,
PollVote,
PollEnded,
FollowRequest,
FollowRequestAccepted,
App,
}
#[derive(Clone, Debug, Deserialize, Serialize, TS)] #[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)] #[ts(export)]
pub struct NotificationSettings { pub struct NotificationSettings {

View File

@ -0,0 +1,75 @@
use crate::types::note::{
PackNoteMaybeFull, Reaction, ReactionShortcode, ReactionUnicode, ReactionUnknown,
};
use crate::types::user::PackUserBase;
use crate::types::Id;
use crate::{Packed, Required};
use chrono::{DateTime, Utc};
use magnetar_sdk_macros::pack;
use serde::{Deserialize, Serialize};
use strum::EnumDiscriminants;
use ts_rs::TS;
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
pub struct NotificationBase {
pub id: String,
pub created_at: DateTime<Utc>,
pub is_read: bool,
}
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
pub struct NotificationNoteExt {
pub note: PackNoteMaybeFull,
}
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
pub struct NotificationUserExt {
pub user: PackUserBase,
}
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
pub struct NotificationReactionExt {
pub reaction: Reaction,
}
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
pub struct NotificationAppExt {
pub body: String,
pub header: String,
pub icon: String,
}
pack!(PackNotificationFollow, Required<Id> as id & Required<NotificationUserExt> as follower);
pack!(PackNotificationFollowRequestReceived, Required<Id> as id & Required<NotificationUserExt> as follower);
pack!(PackNotificationFollowRequestAccepted, Required<Id> as id & Required<NotificationUserExt> as follower);
pack!(PackNotificationMention, Required<Id> as id & Required<NotificationNoteExt> as note);
pack!(PackNotificationReply, Required<Id> as id & Required<NotificationNoteExt> as note);
pack!(PackNotificationRenote, Required<Id> as id & Required<NotificationNoteExt> as note);
pack!(PackNotificationReaction, Required<Id> as id & Required<NotificationReactionExt> as reaction & Required<NotificationUserExt> as user);
pack!(PackNotificationQuote, Required<Id> as id & Required<NotificationNoteExt> as note);
pack!(PackNotificationPollEnd, Required<Id> as id & Required<NotificationNoteExt> as note);
pack!(PackNotificationApp, Required<Id> as id & Required<NotificationAppExt> as custom);
#[derive(Clone, Debug, Deserialize, Serialize, TS, EnumDiscriminants)]
#[strum_discriminants(name(NotificationType))]
#[strum_discriminants(ts(export))]
#[strum_discriminants(derive(Deserialize, Serialize, TS))]
#[ts(export)]
#[serde(tag = "type")]
pub enum PackNotification {
Follow(PackNotificationFollow),
FollowRequestReceived(PackNotificationFollowRequestReceived),
FollowRequestAccepted(PackNotificationFollowRequestAccepted),
Mention(PackNotificationMention),
Reply(PackNotificationReply),
Renote(PackNotificationRenote),
Reaction(PackNotificationReaction),
Quote(PackNotificationQuote),
PollEnd(PackNotificationPollEnd),
App(PackNotificationApp),
}