Merge branch 'development'
ci/woodpecker/push/ociImagePush Pipeline was successful Details

This commit is contained in:
Natty 2023-11-02 00:22:30 +01:00
commit a34829c63d
Signed by: natty
GPG Key ID: BF6CB659ADEE60EC
14 changed files with 451 additions and 187 deletions

2
Cargo.lock generated
View File

@ -1460,6 +1460,7 @@ dependencies = [
"hyper",
"idna",
"itertools 0.11.0",
"lazy_static",
"lru",
"magnetar_calckey_model",
"magnetar_common",
@ -1517,6 +1518,7 @@ dependencies = [
"ext_calckey_model_migration",
"futures-core",
"futures-util",
"lazy_static",
"magnetar_common",
"magnetar_sdk",
"once_cell",

View File

@ -39,6 +39,7 @@ http = "0.2"
hyper = "0.14"
idna = "0.4"
itertools = "0.11"
lazy_static = "1.4"
lru = "0.12"
miette = "5.9"
nom = "7"
@ -90,20 +91,21 @@ tower-http = { workspace = true, features = ["cors", "trace", "fs"] }
idna = { workspace = true }
regex = { workspace = true }
tracing-subscriber = { workspace = true, features = ["env-filter"] }
tracing = { workspace = true }
cfg-if = { workspace = true }
itertools = { workspace = true }
compact_str = { workspace = true }
either = { workspace = true }
futures-util = { workspace = true }
itertools = { workspace = true }
lazy_static = { workspace = true }
miette = { workspace = true, features = ["fancy"] }
strum = { workspace = true, features = ["derive"] }
thiserror = { workspace = true }
miette = { workspace = true, features = ["fancy"] }
regex = { workspace = true }
percent-encoding = { workspace = true }

View File

@ -16,6 +16,7 @@ magnetar_sdk = { path = "../magnetar_sdk" }
dotenvy = { workspace = true}
futures-core = { workspace = true }
futures-util = { workspace = true }
lazy_static = { workspace = true }
tokio = { workspace = true, features = ["full"] }
tokio-util = { workspace = true}
redis = { workspace = true, features = ["tokio-comp", "json", "serde_json"]}

View File

@ -1,5 +1,6 @@
pub mod emoji;
pub mod note_model;
pub mod poll;
pub use ck;
use ck::*;
@ -13,8 +14,8 @@ use redis::IntoConnectionInfo;
use sea_orm::sea_query::IntoIden;
use sea_orm::ActiveValue::Set;
use sea_orm::{
ColumnTrait, ConnectOptions, DatabaseConnection, DbErr, EntityTrait, QueryFilter, QueryOrder,
RelationDef, RelationTrait, TransactionTrait,
ColumnTrait, ConnectOptions, DatabaseConnection, DbErr, EntityTrait, QueryFilter, RelationDef,
RelationTrait, TransactionTrait,
};
use serde::{Deserialize, Serialize};
use std::future::Future;

View File

@ -1,19 +1,52 @@
use sea_orm::sea_query::{Alias, Expr, IntoIden, SelectExpr, SimpleExpr};
use lazy_static::lazy_static;
use sea_orm::sea_query::{Alias, Asterisk, Expr, IntoIden, Query, SelectExpr, SimpleExpr};
use sea_orm::{
ColumnTrait, DbErr, EntityTrait, FromQueryResult, Iden, Iterable, JoinType, QueryFilter,
QueryResult, QuerySelect, QueryTrait, RelationTrait, Select,
ColumnTrait, DbErr, EntityName, EntityTrait, FromQueryResult, Iden, Iterable, JoinType,
QueryFilter, QueryResult, QuerySelect, QueryTrait, RelationTrait, Select,
};
use serde::{Deserialize, Serialize};
use tracing::info;
use ck::{drive_file, note, user};
use ck::{drive_file, note, note_reaction, user};
use magnetar_sdk::types::RangeFilter;
use once_cell::unsync::Lazy;
use crate::{AliasSourceExt, CalckeyDbError, CalckeyModel};
pub mod sub_interaction_renote {
use sea_orm::{DeriveColumn, EnumIter, FromQueryResult};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, FromQueryResult, Serialize, Deserialize)]
pub struct Model {
pub renotes: Option<i64>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
pub enum Column {
Renotes,
}
}
pub mod sub_interaction_reaction {
use sea_orm::{DeriveColumn, EnumIter, FromQueryResult};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, PartialEq, Eq, FromQueryResult, Serialize, Deserialize)]
pub struct Model {
pub reaction_name: Option<String>,
}
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
pub enum Column {
ReactionName,
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NoteData {
pub note: note::Model,
pub interaction_user_renote: Option<sub_interaction_renote::Model>,
pub interaction_user_reaction: Option<sub_interaction_reaction::Model>,
pub user: user::Model,
pub avatar: Option<drive_file::Model>,
pub banner: Option<drive_file::Model>,
@ -21,14 +54,20 @@ pub struct NoteData {
pub renote: Option<Box<NoteData>>,
}
const INTERACTION_REACTION: &str = "interaction.reaction.";
const INTERACTION_RENOTE: &str = "interaction.renote.";
const USER: &str = "user.";
const USER_AVATAR: &str = "user.avatar.";
const USER_BANNER: &str = "user.banner.";
const REPLY: &str = "reply.";
const REPLY_INTERACTION_REACTION: &str = "reply.interaction.reaction.";
const REPLY_INTERACTION_RENOTE: &str = "reply.interaction.renote.";
const REPLY_USER: &str = "reply.user.";
const REPLY_USER_AVATAR: &str = "reply.user.avatar.";
const REPLY_USER_BANNER: &str = "reply.user.banner.";
const RENOTE: &str = "renote.";
const RENOTE_INTERACTION_REACTION: &str = "renote.interaction.reaction.";
const RENOTE_INTERACTION_RENOTE: &str = "renote.interaction.renote.";
const RENOTE_USER: &str = "renote.user.";
const RENOTE_USER_AVATAR: &str = "renote.user.avatar.";
const RENOTE_USER_BANNER: &str = "renote.user.banner.";
@ -39,6 +78,16 @@ impl FromQueryResult for NoteData {
.map::<Result<_, DbErr>, _>(|r| {
Ok(Box::new(NoteData {
note: r,
interaction_user_renote:
sub_interaction_renote::Model::from_query_result_optional(
res,
REPLY_INTERACTION_RENOTE,
)?,
interaction_user_reaction:
sub_interaction_reaction::Model::from_query_result_optional(
res,
REPLY_INTERACTION_REACTION,
)?,
user: user::Model::from_query_result(res, REPLY_USER)?,
avatar: drive_file::Model::from_query_result_optional(res, REPLY_USER_AVATAR)?,
banner: drive_file::Model::from_query_result_optional(res, REPLY_USER_BANNER)?,
@ -52,6 +101,16 @@ impl FromQueryResult for NoteData {
.map::<Result<_, DbErr>, _>(|r| {
Ok(Box::new(NoteData {
note: r,
interaction_user_renote:
sub_interaction_renote::Model::from_query_result_optional(
res,
RENOTE_INTERACTION_RENOTE,
)?,
interaction_user_reaction:
sub_interaction_reaction::Model::from_query_result_optional(
res,
RENOTE_INTERACTION_REACTION,
)?,
user: user::Model::from_query_result(res, RENOTE_USER)?,
avatar: drive_file::Model::from_query_result_optional(res, RENOTE_USER_AVATAR)?,
banner: drive_file::Model::from_query_result_optional(res, RENOTE_USER_BANNER)?,
@ -63,6 +122,14 @@ impl FromQueryResult for NoteData {
Ok(NoteData {
note: note::Model::from_query_result(res, "")?,
interaction_user_renote: sub_interaction_renote::Model::from_query_result_optional(
res,
INTERACTION_RENOTE,
)?,
interaction_user_reaction: sub_interaction_reaction::Model::from_query_result_optional(
res,
INTERACTION_REACTION,
)?,
user: user::Model::from_query_result(res, USER)?,
avatar: drive_file::Model::from_query_result_optional(res, USER_AVATAR)?,
banner: drive_file::Model::from_query_result_optional(res, USER_BANNER)?,
@ -87,10 +154,25 @@ pub struct NoteResolveOptions {
pub with_user: bool,
pub with_reply_target: bool,
pub with_renote_target: bool,
pub with_interactions_from: Option<String>, // User ID
}
trait SelectColumnsExt {
fn add_aliased_columns<T: EntityTrait>(self, alias: Option<&str>, entity: T) -> Self;
fn add_sub_select_reaction(
self,
source_note_alias: Option<&str>,
alias: &str,
user_id: &str,
) -> Select<note::Entity>;
fn add_sub_select_renote(
self,
source_note_alias: Option<&str>,
alias: &str,
user_id: &str,
) -> Select<note::Entity>;
}
impl SelectColumnsExt for Select<note::Entity> {
@ -116,19 +198,89 @@ impl SelectColumnsExt for Select<note::Entity> {
self
}
fn add_sub_select_reaction(
mut self: Select<note::Entity>,
source_note_alias: Option<&str>,
prefix_alias: &str,
user_id: &str,
) -> Select<note::Entity> {
let iden = source_note_alias.unwrap_or_else(|| note::Entity.table_name());
let note_id_col = Expr::col((Alias::new(iden), note::Column::Id));
let column = sub_interaction_reaction::Column::ReactionName;
let alias = format!("{}{}", prefix_alias, column.to_string());
let sub_query = Query::select()
.expr(SelectExpr {
expr: Expr::col(note_reaction::Column::Reaction).into(),
alias: Some(Alias::new(alias).into_iden()),
window: None,
})
.from(note_reaction::Entity)
.cond_where(Expr::col(note_reaction::Column::NoteId).eq(note_id_col))
.and_where(Expr::col(note_reaction::Column::UserId).eq(user_id))
.take()
.into_sub_query_statement();
QuerySelect::query(&mut self).expr(SimpleExpr::SubQuery(None, Box::new(sub_query)));
self
}
fn add_sub_select_renote(
mut self: Select<note::Entity>,
source_note_alias: Option<&str>,
prefix_alias: &str,
user_id: &str,
) -> Select<note::Entity> {
let iden = source_note_alias.unwrap_or_else(|| note::Entity.table_name());
let note_id_col = Expr::col((Alias::new(iden), note::Column::Id));
let renote_note_tbl = Alias::new(format!("{}{}", prefix_alias, "note"));
let column = sub_interaction_renote::Column::Renotes;
let alias = format!("{}{}", prefix_alias, column.to_string());
let sub_query = Query::select()
.expr(SelectExpr {
expr: Expr::count(Expr::col(Asterisk)),
alias: Some(Alias::new(alias).into_iden()),
window: None,
})
.from_as(note::Entity, renote_note_tbl.clone())
.cond_where(
Expr::col((renote_note_tbl.clone(), note::Column::RenoteId)).eq(note_id_col),
)
.and_where(Expr::col((renote_note_tbl.clone(), note::Column::UserId)).eq(user_id))
.take()
.into_sub_query_statement();
QuerySelect::query(&mut self).expr(SimpleExpr::SubQuery(None, Box::new(sub_query)));
self
}
}
const ALIAS_USER: Lazy<Alias> = Lazy::new(|| Alias::new(USER));
const ALIAS_USER_AVATAR: Lazy<Alias> = Lazy::new(|| Alias::new(USER_AVATAR));
const ALIAS_USER_BANNER: Lazy<Alias> = Lazy::new(|| Alias::new(USER_BANNER));
const ALIAS_REPLY: Lazy<Alias> = Lazy::new(|| Alias::new(REPLY));
const ALIAS_REPLY_USER: Lazy<Alias> = Lazy::new(|| Alias::new(REPLY_USER));
const ALIAS_REPLY_USER_AVATAR: Lazy<Alias> = Lazy::new(|| Alias::new(REPLY_USER_AVATAR));
const ALIAS_REPLY_USER_BANNER: Lazy<Alias> = Lazy::new(|| Alias::new(REPLY_USER_BANNER));
const ALIAS_RENOTE: Lazy<Alias> = Lazy::new(|| Alias::new(RENOTE));
const ALIAS_RENOTE_USER: Lazy<Alias> = Lazy::new(|| Alias::new(RENOTE_USER));
const ALIAS_RENOTE_USER_AVATAR: Lazy<Alias> = Lazy::new(|| Alias::new(RENOTE_USER_AVATAR));
const ALIAS_RENOTE_USER_BANNER: Lazy<Alias> = Lazy::new(|| Alias::new(RENOTE_USER_BANNER));
lazy_static! {
static ref ALIAS_INTERACTION_RENOTE: Alias = Alias::new(INTERACTION_RENOTE);
static ref ALIAS_INTERACTION_REACTION: Alias = Alias::new(INTERACTION_REACTION);
static ref ALIAS_USER: Alias = Alias::new(USER);
static ref ALIAS_USER_AVATAR: Alias = Alias::new(USER_AVATAR);
static ref ALIAS_USER_BANNER: Alias = Alias::new(USER_BANNER);
static ref ALIAS_REPLY: Alias = Alias::new(REPLY);
static ref ALIAS_REPLY_INTERACTION_RENOTE: Alias = Alias::new(REPLY_INTERACTION_RENOTE);
static ref ALIAS_REPLY_INTERACTION_REACTION: Alias = Alias::new(REPLY_INTERACTION_REACTION);
static ref ALIAS_REPLY_USER: Alias = Alias::new(REPLY_USER);
static ref ALIAS_REPLY_USER_AVATAR: Alias = Alias::new(REPLY_USER_AVATAR);
static ref ALIAS_REPLY_USER_BANNER: Alias = Alias::new(REPLY_USER_BANNER);
static ref ALIAS_RENOTE: Alias = Alias::new(RENOTE);
static ref ALIAS_RENOTE_INTERACTION_RENOTE: Alias = Alias::new(RENOTE_INTERACTION_RENOTE);
static ref ALIAS_RENOTE_INTERACTION_REACTION: Alias = Alias::new(RENOTE_INTERACTION_REACTION);
static ref ALIAS_RENOTE_USER: Alias = Alias::new(RENOTE_USER);
static ref ALIAS_RENOTE_USER_AVATAR: Alias = Alias::new(RENOTE_USER_AVATAR);
static ref ALIAS_RENOTE_USER_BANNER: Alias = Alias::new(RENOTE_USER_BANNER);
}
impl NoteResolver {
pub fn new(db: CalckeyModel) -> Self {
@ -142,11 +294,11 @@ impl NoteResolver {
let select = self.resolve(options);
let visibility_filter = options.visibility_filter.with_note_and_user_tables(None);
let time_filter = options.time_range.as_ref().map(|f| match f {
RangeFilter::TimeStart(start) => note::Column::CreatedAt.gte(start.clone()),
RangeFilter::TimeStart(start) => note::Column::CreatedAt.gte(*start),
RangeFilter::TimeRange(range) => {
note::Column::CreatedAt.between(range.start().clone(), range.end().clone())
note::Column::CreatedAt.between(*range.start(), *range.end())
}
RangeFilter::TimeEnd(end) => note::Column::CreatedAt.lt(end.clone()),
RangeFilter::TimeEnd(end) => note::Column::CreatedAt.lt(*end),
});
let id_filter = options.ids.as_ref().map(|ids| {
@ -164,12 +316,19 @@ impl NoteResolver {
.into_model::<NoteData>()
.one(self.db.inner())
.await?;
Ok(notes)
}
pub fn resolve(&self, options: &NoteResolveOptions) -> Select<note::Entity> {
let mut select = note::Entity::find().add_aliased_columns(Some(USER), user::Entity);
if let Some(user_id) = &options.with_interactions_from {
select = select
.add_sub_select_reaction(None, INTERACTION_REACTION, user_id)
.add_sub_select_renote(None, INTERACTION_RENOTE, user_id);
}
if options.with_user {
select = select
.add_aliased_columns(Some(USER_AVATAR), drive_file::Entity)
@ -181,6 +340,12 @@ impl NoteResolver {
.add_aliased_columns(Some(REPLY), note::Entity)
.add_aliased_columns(Some(REPLY_USER), user::Entity);
if let Some(user_id) = &options.with_interactions_from {
select = select
.add_sub_select_reaction(Some(REPLY), REPLY_INTERACTION_REACTION, user_id)
.add_sub_select_renote(Some(REPLY), REPLY_INTERACTION_RENOTE, user_id);
}
if options.with_user {
select = select
.add_aliased_columns(Some(REPLY_USER_AVATAR), drive_file::Entity)
@ -193,6 +358,12 @@ impl NoteResolver {
.add_aliased_columns(Some(RENOTE), note::Entity)
.add_aliased_columns(Some(RENOTE_USER), user::Entity);
if let Some(user_id) = &options.with_interactions_from {
select = select
.add_sub_select_reaction(Some(RENOTE), RENOTE_INTERACTION_REACTION, user_id)
.add_sub_select_renote(Some(RENOTE), RENOTE_INTERACTION_RENOTE, user_id);
}
if options.with_user {
select = select
.add_aliased_columns(Some(RENOTE_USER_AVATAR), drive_file::Entity)

View File

@ -0,0 +1,33 @@
use crate::{CalckeyDbError, CalckeyModel};
use ck::{poll, poll_vote};
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
pub struct PollResolver(CalckeyModel);
impl PollResolver {
pub fn new(db: CalckeyModel) -> Self {
PollResolver(db)
}
pub async fn get_poll(&self, note_id: &str) -> Result<Option<poll::Model>, CalckeyDbError> {
Ok(poll::Entity::find()
.filter(poll::Column::NoteId.eq(note_id))
.one(self.0.inner())
.await?)
}
pub async fn get_poll_votes_by(
&self,
note_id: &str,
user_id: &str,
) -> Result<Vec<poll_vote::Model>, CalckeyDbError> {
Ok(poll_vote::Entity::find()
.filter(
poll_vote::Column::NoteId
.eq(note_id)
.and(poll_vote::Column::UserId.eq(user_id)),
)
.all(self.0.inner())
.await?)
}
}

View File

@ -33,13 +33,13 @@ pub enum NoteVisibility {
pub struct PollChoice {
pub title: String,
pub votes_count: u64,
pub voted: bool,
pub voted: Option<bool>,
}
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
pub struct PollBase {
pub expires_at: DateTime<Utc>,
pub expires_at: Option<DateTime<Utc>>,
pub expired: bool,
pub multiple_choice: bool,
pub options: Vec<PollChoice>,
@ -74,6 +74,12 @@ pub struct NoteBase {
pack!(PackNoteBase, Required<Id> as id & Required<NoteBase> as note);
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
pub struct NoteSelfContextExt {
pub self_renote_count: Option<u64>,
}
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[ts(export)]
pub struct NoteAttachmentExt {
@ -82,19 +88,19 @@ pub struct NoteAttachmentExt {
}
pack!(
PackNoteWithMaybeAttachments,
Required<Id> as id & Required<NoteBase> as note & Option<NoteAttachmentExt> as attachment
PackNoteMaybeAttachments,
Required<Id> as id & Required<NoteBase> as note & Option<NoteSelfContextExt> as user_context & Option<NoteAttachmentExt> as attachment
);
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
pub struct NoteDetailExt {
pub parent_note: Option<Box<PackNoteWithMaybeAttachments>>,
pub renoted_note: Option<Box<PackNoteWithMaybeAttachments>>,
pub parent_note: Option<Box<PackNoteMaybeAttachments>>,
pub renoted_note: Option<Box<PackNoteMaybeAttachments>>,
}
pack!(
PackNoteMaybeFull,
Required<Id> as id & Required<NoteBase> as note & Option<NoteAttachmentExt> as attachment & Option<NoteDetailExt> as detail
Required<Id> as id & Required<NoteBase> as note & Option<NoteSelfContextExt> as user_context & Option<NoteAttachmentExt> as attachment & Option<NoteDetailExt> as detail
);
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
@ -111,4 +117,9 @@ pub enum Reaction {
},
}
pub type ReactionPair = (Reaction, usize);
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
#[serde(untagged)]
pub enum ReactionPair {
WithoutContext(Reaction, u64),
WithContext(Reaction, u64, bool),
}

View File

@ -27,7 +27,7 @@ pub async fn handle_note(
}
.fetch_single(&ctx, self_user.as_deref(), &id)
.await?
.ok_or_else(|| ObjectNotFound(id))?;
.ok_or(ObjectNotFound(id))?;
Ok(Json(note.into()))
Ok(Json(note))
}

View File

@ -17,3 +17,9 @@ macro_rules! impl_id {
impl_id!(ck::emoji::Model);
impl_id!(ck::user::Model);
impl_id!(ck::note::Model);
impl BaseId for ck::poll::Model {
fn get_id(&self) -> &str {
&self.note_id
}
}

View File

@ -2,4 +2,5 @@ pub mod drive;
pub mod emoji;
pub mod id;
pub mod note;
pub mod poll;
pub mod user;

View File

@ -1,6 +1,7 @@
use magnetar_calckey_model::ck;
use magnetar_calckey_model::note_model::sub_interaction_renote;
use magnetar_sdk::types::note::{
NoteDetailExt, PackNoteWithMaybeAttachments, PackPollBase, ReactionPair,
NoteDetailExt, NoteSelfContextExt, PackNoteMaybeAttachments, PackPollBase, ReactionPair,
};
use magnetar_sdk::types::user::PackUserBase;
use magnetar_sdk::types::{
@ -70,9 +71,17 @@ impl PackType<NoteBaseSource<'_>> for NoteBase {
}
}
impl PackType<&sub_interaction_renote::Model> for NoteSelfContextExt {
fn extract(_context: &PackingContext, data: &sub_interaction_renote::Model) -> Self {
NoteSelfContextExt {
self_renote_count: data.renotes.map(|v| v as u64),
}
}
}
pub struct NoteDetailSource<'a> {
pub parent_note: Option<&'a PackNoteWithMaybeAttachments>,
pub renoted_note: Option<&'a PackNoteWithMaybeAttachments>,
pub parent_note: Option<&'a PackNoteMaybeAttachments>,
pub renoted_note: Option<&'a PackNoteMaybeAttachments>,
}
impl<'a> PackType<NoteDetailSource<'a>> for NoteDetailExt {

27
src/model/data/poll.rs Normal file
View File

@ -0,0 +1,27 @@
use crate::model::{PackType, PackingContext};
use chrono::{DateTime, Utc};
use magnetar_calckey_model::ck;
use magnetar_sdk::types::note::{PollBase, PollChoice};
pub type PollPackInput<'a> = (&'a ck::poll::Model, Option<&'a [ck::poll_vote::Model]>);
impl PackType<PollPackInput<'_>> for PollBase {
fn extract(_context: &PackingContext, (poll, votes): PollPackInput<'_>) -> Self {
PollBase {
expires_at: poll.expires_at.map(DateTime::<Utc>::from),
expired: poll.expires_at.is_some_and(|e| e < Utc::now()),
multiple_choice: poll.multiple,
options: poll
.choices
.iter()
.zip(poll.votes.iter())
.enumerate()
.map(|(i, (name, count))| PollChoice {
title: name.to_string(),
votes_count: *count as u64,
voted: votes.map(|v| v.iter().any(|v| v.choice as usize == i)),
})
.collect(),
}
}
}

View File

@ -85,11 +85,7 @@ impl PackingContext {
return Ok(false);
};
self.is_relationship_between(
Either::Right(self_user.into()),
Either::Right(user.into()),
rel_type,
)
self.is_relationship_between(Either::Right(self_user), Either::Right(user), rel_type)
.await
}

View File

@ -13,6 +13,7 @@ use magnetar_calckey_model::emoji::EmojiTag;
use magnetar_calckey_model::note_model::{
NoteData, NoteResolveOptions, NoteVisibilityFilterFactory,
};
use magnetar_calckey_model::poll::PollResolver;
use magnetar_calckey_model::sea_orm::prelude::Expr;
use magnetar_calckey_model::sea_orm::sea_query::{Alias, IntoIden, PgFunc, Query, SimpleExpr};
use magnetar_calckey_model::sea_orm::{ActiveEnum, ColumnTrait, IntoSimpleExpr};
@ -22,8 +23,8 @@ use magnetar_sdk::mmm::Token;
use magnetar_sdk::types::drive::PackDriveFileBase;
use magnetar_sdk::types::emoji::EmojiContext;
use magnetar_sdk::types::note::{
NoteAttachmentExt, NoteBase, NoteDetailExt, PackNoteBase, PackNoteMaybeFull,
PackNoteWithMaybeAttachments, Reaction,
NoteAttachmentExt, NoteBase, NoteDetailExt, NoteSelfContextExt, PackNoteBase,
PackNoteMaybeAttachments, PackNoteMaybeFull, PackPollBase, PollBase, Reaction, ReactionPair,
};
use magnetar_sdk::types::{Id, MmXml};
use magnetar_sdk::{mmm, Packed, Required};
@ -151,7 +152,7 @@ impl NoteVisibilityFilterModel {
return Ok(following || user.host.is_some() && note.user_host.is_some());
}
return Ok(false);
Ok(false)
}
pub fn new_note_visibility_filter(&self, user: Option<&str>) -> NoteVisibilityFilterSimple {
@ -225,18 +226,28 @@ impl NoteModel {
let reactions_raw =
serde_json::Map::<String, serde_json::Value>::deserialize(&note_data.note.reactions)?
.into_iter()
.map(|(code, count)| {
.map(|(ref code, count)| {
let reaction = parse_reaction(code)
.map_or_else(|| Either::Left(code.to_string()), Either::Right);
(
parse_reaction(&code).map_or_else(|| Either::Left(code), Either::Right),
reaction,
count,
note_data
.interaction_user_reaction
.as_ref()
.and_then(|r| r.reaction_name.as_deref())
.map(|r| r == code),
)
})
.map(|(code, count)| Ok((code, usize::deserialize(count)?)))
.map(|(code, count, self_reacted)| {
Ok((code, u64::deserialize(count)?, self_reacted))
})
.collect::<Result<Vec<_>, serde_json::Error>>()?;
// Pick out all successfully-parsed shortcode emojis
let reactions_to_resolve = reactions_raw
.iter()
.map(|(code, _)| code)
.map(|(code, _, _)| code)
.map(Either::as_ref)
.filter_map(Either::right)
.filter_map(|c| match c {
@ -261,9 +272,8 @@ impl NoteModel {
// Left reactions and the Right ones that didn't resolve to any emoji are turned back into Unknown
let reactions = &reactions_raw
.into_iter()
.map(|(raw, count)| {
(
raw.either(
.map(|(raw, count, self_reaction)| {
let reaction = raw.either(
|raw| Reaction::Unknown { raw },
|raw| match raw {
RawReaction::Unicode(text) => Reaction::Unicode(text),
@ -286,9 +296,14 @@ impl NoteModel {
},
),
},
),
count,
)
);
match self_reaction {
Some(self_reaction) => {
ReactionPair::WithContext(reaction, count, self_reaction)
}
None => ReactionPair::WithoutContext(reaction, count),
}
})
.collect::<Vec<_>>();
@ -322,54 +337,15 @@ impl NoteModel {
)))
}
async fn extract_reply_target_base(
fn extract_interaction(
&self,
ctx: &PackingContext,
note: &NoteData,
) -> PackResult<Option<PackNoteBase>> {
match note.reply.as_ref() {
Some(r) if self.with_context => self.extract_base(ctx, r).await.map(Some),
_ => Ok(None),
}
}
async fn extract_renote_target_base(
&self,
ctx: &PackingContext,
note: &NoteData,
) -> PackResult<Option<PackNoteBase>> {
match note.renote.as_ref() {
Some(r) if self.with_context => self.extract_base(ctx, r).await.map(Some),
_ => Ok(None),
}
}
async fn extract_reply_target_attachemnts(
&self,
ctx: &PackingContext,
drive_model: &DriveModel,
note: &NoteData,
) -> PackResult<Option<Vec<PackDriveFileBase>>> {
match note.reply.as_ref() {
Some(r) if self.with_context => {
self.extract_attachments(ctx, drive_model, &r.note).await
}
_ => Ok(None),
}
}
async fn extract_renote_target_attachemnts(
&self,
ctx: &PackingContext,
drive_model: &DriveModel,
note: &NoteData,
) -> PackResult<Option<Vec<PackDriveFileBase>>> {
match note.renote.as_ref() {
Some(r) if self.with_context => {
self.extract_attachments(ctx, drive_model, &r.note).await
}
_ => Ok(None),
}
) -> PackResult<Option<NoteSelfContextExt>> {
Ok(note
.interaction_user_renote
.as_ref()
.map(|renote_info| NoteSelfContextExt::extract(ctx, renote_info)))
}
async fn extract_attachments(
@ -393,6 +369,62 @@ impl NoteModel {
Ok(None)
}
async fn extract_poll(
&self,
ctx: &PackingContext,
as_user: Option<&ck::user::Model>,
note: &ck::note::Model,
) -> PackResult<Option<PackPollBase>> {
if !note.has_poll {
return Ok(None);
}
let poll_resolver = PollResolver::new(ctx.service.db.clone());
let Some(poll) = poll_resolver.get_poll(&note.id).await? else {
return Ok(None);
};
let votes = match as_user {
Some(u) => Some(poll_resolver.get_poll_votes_by(&note.id, &u.id).await?),
None => None,
};
Ok(Some(PackPollBase::pack_from((
Required(Id::from(poll.get_id())),
Required(PollBase::extract(ctx, (&poll, votes.as_deref()))),
))))
}
async fn pack_single_attachments(
&self,
ctx: &PackingContext,
drive_model: &DriveModel,
as_user: Option<&ck::user::Model>,
note_data: &NoteData,
) -> PackResult<PackNoteMaybeAttachments> {
let (PackNoteBase { id, note }, attachments_pack, poll_pack) = try_join!(
self.extract_base(ctx, note_data),
self.extract_attachments(ctx, drive_model, &note_data.note),
self.extract_poll(ctx, as_user, &note_data.note)
)?;
Ok(PackNoteMaybeAttachments::pack_from((
id,
note,
self.extract_interaction(ctx, note_data)?,
attachments_pack.map(|attachments| {
NoteAttachmentExt::extract(
ctx,
NoteAttachmentSource {
attachments: &attachments,
poll: poll_pack.as_ref(),
},
)
}),
)))
}
pub async fn fetch_single(
&self,
ctx: &PackingContext,
@ -411,6 +443,10 @@ impl NoteModel {
with_user: self.with_context,
with_reply_target: self.with_context,
with_renote_target: self.with_context,
with_interactions_from: self
.with_context
.then(|| as_user.map(ck::user::Model::get_id).map(str::to_string))
.flatten(),
})
.await?
else {
@ -419,80 +455,47 @@ impl NoteModel {
let drive_model = DriveModel;
let extract_reply_target = self.extract_reply_target_base(ctx, &note);
let extract_renote_target = self.extract_renote_target_base(ctx, &note);
let extract_attachments = self.extract_attachments(ctx, &drive_model, &note.note);
let extract_reply_attachments =
self.extract_reply_target_attachemnts(ctx, &drive_model, &note);
let extract_renote_attachments =
self.extract_renote_target_attachemnts(ctx, &drive_model, &note);
let reply_target = async {
match note.reply.as_ref() {
Some(r) if self.with_context => self
.pack_single_attachments(ctx, &drive_model, as_user, r)
.await
.map(Some),
_ => Ok(None),
}
};
// TODO: Polls, ...
let renote_target = async {
match note.renote.as_ref() {
Some(r) if self.with_context => self
.pack_single_attachments(ctx, &drive_model, as_user, r)
.await
.map(Some),
_ => Ok(None),
}
};
let (
PackNoteBase { id, note },
PackNoteMaybeAttachments {
id,
note,
user_context,
attachment,
},
reply_target_pack,
renote_target_pack,
attachments_pack,
reply_attachments_pack,
renote_attachments_pack,
) = try_join!(
self.extract_base(ctx, &note),
extract_reply_target,
extract_renote_target,
extract_attachments,
extract_reply_attachments,
extract_renote_attachments
self.pack_single_attachments(ctx, &drive_model, as_user, &note),
reply_target,
renote_target
)?;
let detail = self.with_context.then(|| {
NoteDetailExt::extract(
ctx,
NoteDetailSource {
parent_note: reply_target_pack
.map(|base| {
PackNoteWithMaybeAttachments::pack_from((
base.id,
base.note,
reply_attachments_pack.map(|attachments| {
NoteAttachmentExt::extract(
ctx,
NoteAttachmentSource {
attachments: &attachments,
poll: None, // TODO
},
)
}),
))
})
.as_ref(),
renoted_note: renote_target_pack
.map(|base| {
PackNoteWithMaybeAttachments::pack_from((
base.id,
base.note,
renote_attachments_pack.map(|attachments| {
NoteAttachmentExt::extract(
ctx,
NoteAttachmentSource {
attachments: &attachments,
poll: None, // TODO
},
)
}),
))
})
.as_ref(),
},
)
});
let attachments = attachments_pack.map(|attachments| {
NoteAttachmentExt::extract(
ctx,
NoteAttachmentSource {
attachments: &attachments,
poll: None, // TODO
parent_note: reply_target_pack.as_ref(),
renoted_note: renote_target_pack.as_ref(),
},
)
});
@ -500,7 +503,8 @@ impl NoteModel {
Ok(Some(PackNoteMaybeFull::pack_from((
id,
note,
attachments,
user_context,
attachment,
detail,
))))
}