Self reaction and renote fetching
This commit is contained in:
parent
8e02e46be5
commit
5b9b813037
|
@ -1460,6 +1460,7 @@ dependencies = [
|
||||||
"hyper",
|
"hyper",
|
||||||
"idna",
|
"idna",
|
||||||
"itertools 0.11.0",
|
"itertools 0.11.0",
|
||||||
|
"lazy_static",
|
||||||
"lru",
|
"lru",
|
||||||
"magnetar_calckey_model",
|
"magnetar_calckey_model",
|
||||||
"magnetar_common",
|
"magnetar_common",
|
||||||
|
@ -1517,6 +1518,7 @@ dependencies = [
|
||||||
"ext_calckey_model_migration",
|
"ext_calckey_model_migration",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
"lazy_static",
|
||||||
"magnetar_common",
|
"magnetar_common",
|
||||||
"magnetar_sdk",
|
"magnetar_sdk",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
|
10
Cargo.toml
10
Cargo.toml
|
@ -39,6 +39,7 @@ http = "0.2"
|
||||||
hyper = "0.14"
|
hyper = "0.14"
|
||||||
idna = "0.4"
|
idna = "0.4"
|
||||||
itertools = "0.11"
|
itertools = "0.11"
|
||||||
|
lazy_static = "1.4"
|
||||||
lru = "0.12"
|
lru = "0.12"
|
||||||
miette = "5.9"
|
miette = "5.9"
|
||||||
nom = "7"
|
nom = "7"
|
||||||
|
@ -90,20 +91,21 @@ tower-http = { workspace = true, features = ["cors", "trace", "fs"] }
|
||||||
|
|
||||||
idna = { workspace = true }
|
idna = { workspace = true }
|
||||||
|
|
||||||
|
regex = { workspace = true }
|
||||||
|
|
||||||
tracing-subscriber = { workspace = true, features = ["env-filter"] }
|
tracing-subscriber = { workspace = true, features = ["env-filter"] }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
|
||||||
cfg-if = { workspace = true }
|
cfg-if = { workspace = true }
|
||||||
|
|
||||||
itertools = { workspace = true }
|
|
||||||
|
|
||||||
compact_str = { workspace = true }
|
compact_str = { workspace = true }
|
||||||
either = { workspace = true }
|
either = { workspace = true }
|
||||||
futures-util = { workspace = true }
|
futures-util = { workspace = true }
|
||||||
|
itertools = { workspace = true }
|
||||||
|
lazy_static = { workspace = true }
|
||||||
|
miette = { workspace = true, features = ["fancy"] }
|
||||||
strum = { workspace = true, features = ["derive"] }
|
strum = { workspace = true, features = ["derive"] }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
miette = { workspace = true, features = ["fancy"] }
|
|
||||||
regex = { workspace = true }
|
|
||||||
|
|
||||||
percent-encoding = { workspace = true }
|
percent-encoding = { workspace = true }
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ magnetar_sdk = { path = "../magnetar_sdk" }
|
||||||
dotenvy = { workspace = true}
|
dotenvy = { workspace = true}
|
||||||
futures-core = { workspace = true }
|
futures-core = { workspace = true }
|
||||||
futures-util = { workspace = true }
|
futures-util = { workspace = true }
|
||||||
|
lazy_static = { workspace = true }
|
||||||
tokio = { workspace = true, features = ["full"] }
|
tokio = { workspace = true, features = ["full"] }
|
||||||
tokio-util = { workspace = true}
|
tokio-util = { workspace = true}
|
||||||
redis = { workspace = true, features = ["tokio-comp", "json", "serde_json"]}
|
redis = { workspace = true, features = ["tokio-comp", "json", "serde_json"]}
|
||||||
|
|
|
@ -13,8 +13,8 @@ use redis::IntoConnectionInfo;
|
||||||
use sea_orm::sea_query::IntoIden;
|
use sea_orm::sea_query::IntoIden;
|
||||||
use sea_orm::ActiveValue::Set;
|
use sea_orm::ActiveValue::Set;
|
||||||
use sea_orm::{
|
use sea_orm::{
|
||||||
ColumnTrait, ConnectOptions, DatabaseConnection, DbErr, EntityTrait, QueryFilter, QueryOrder,
|
ColumnTrait, ConnectOptions, DatabaseConnection, DbErr, EntityTrait, QueryFilter, RelationDef,
|
||||||
RelationDef, RelationTrait, TransactionTrait,
|
RelationTrait, TransactionTrait,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
|
@ -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::{
|
use sea_orm::{
|
||||||
ColumnTrait, DbErr, EntityTrait, FromQueryResult, Iden, Iterable, JoinType, QueryFilter,
|
ColumnTrait, DbErr, EntityName, EntityTrait, FromQueryResult, Iden, Iterable, JoinType,
|
||||||
QueryResult, QuerySelect, QueryTrait, RelationTrait, Select,
|
QueryFilter, QueryResult, QuerySelect, QueryTrait, RelationTrait, Select,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
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 magnetar_sdk::types::RangeFilter;
|
||||||
use once_cell::unsync::Lazy;
|
|
||||||
|
|
||||||
use crate::{AliasSourceExt, CalckeyDbError, CalckeyModel};
|
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)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct NoteData {
|
pub struct NoteData {
|
||||||
pub note: note::Model,
|
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 user: user::Model,
|
||||||
pub avatar: Option<drive_file::Model>,
|
pub avatar: Option<drive_file::Model>,
|
||||||
pub banner: Option<drive_file::Model>,
|
pub banner: Option<drive_file::Model>,
|
||||||
|
@ -21,14 +54,20 @@ pub struct NoteData {
|
||||||
pub renote: Option<Box<NoteData>>,
|
pub renote: Option<Box<NoteData>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const INTERACTION_REACTION: &str = "interaction.reaction.";
|
||||||
|
const INTERACTION_RENOTE: &str = "interaction.renote.";
|
||||||
const USER: &str = "user.";
|
const USER: &str = "user.";
|
||||||
const USER_AVATAR: &str = "user.avatar.";
|
const USER_AVATAR: &str = "user.avatar.";
|
||||||
const USER_BANNER: &str = "user.banner.";
|
const USER_BANNER: &str = "user.banner.";
|
||||||
const REPLY: &str = "reply.";
|
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: &str = "reply.user.";
|
||||||
const REPLY_USER_AVATAR: &str = "reply.user.avatar.";
|
const REPLY_USER_AVATAR: &str = "reply.user.avatar.";
|
||||||
const REPLY_USER_BANNER: &str = "reply.user.banner.";
|
const REPLY_USER_BANNER: &str = "reply.user.banner.";
|
||||||
const RENOTE: &str = "renote.";
|
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: &str = "renote.user.";
|
||||||
const RENOTE_USER_AVATAR: &str = "renote.user.avatar.";
|
const RENOTE_USER_AVATAR: &str = "renote.user.avatar.";
|
||||||
const RENOTE_USER_BANNER: &str = "renote.user.banner.";
|
const RENOTE_USER_BANNER: &str = "renote.user.banner.";
|
||||||
|
@ -39,6 +78,16 @@ impl FromQueryResult for NoteData {
|
||||||
.map::<Result<_, DbErr>, _>(|r| {
|
.map::<Result<_, DbErr>, _>(|r| {
|
||||||
Ok(Box::new(NoteData {
|
Ok(Box::new(NoteData {
|
||||||
note: r,
|
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)?,
|
user: user::Model::from_query_result(res, REPLY_USER)?,
|
||||||
avatar: drive_file::Model::from_query_result_optional(res, REPLY_USER_AVATAR)?,
|
avatar: drive_file::Model::from_query_result_optional(res, REPLY_USER_AVATAR)?,
|
||||||
banner: drive_file::Model::from_query_result_optional(res, REPLY_USER_BANNER)?,
|
banner: drive_file::Model::from_query_result_optional(res, REPLY_USER_BANNER)?,
|
||||||
|
@ -52,6 +101,16 @@ impl FromQueryResult for NoteData {
|
||||||
.map::<Result<_, DbErr>, _>(|r| {
|
.map::<Result<_, DbErr>, _>(|r| {
|
||||||
Ok(Box::new(NoteData {
|
Ok(Box::new(NoteData {
|
||||||
note: r,
|
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)?,
|
user: user::Model::from_query_result(res, RENOTE_USER)?,
|
||||||
avatar: drive_file::Model::from_query_result_optional(res, RENOTE_USER_AVATAR)?,
|
avatar: drive_file::Model::from_query_result_optional(res, RENOTE_USER_AVATAR)?,
|
||||||
banner: drive_file::Model::from_query_result_optional(res, RENOTE_USER_BANNER)?,
|
banner: drive_file::Model::from_query_result_optional(res, RENOTE_USER_BANNER)?,
|
||||||
|
@ -63,6 +122,14 @@ impl FromQueryResult for NoteData {
|
||||||
|
|
||||||
Ok(NoteData {
|
Ok(NoteData {
|
||||||
note: note::Model::from_query_result(res, "")?,
|
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)?,
|
user: user::Model::from_query_result(res, USER)?,
|
||||||
avatar: drive_file::Model::from_query_result_optional(res, USER_AVATAR)?,
|
avatar: drive_file::Model::from_query_result_optional(res, USER_AVATAR)?,
|
||||||
banner: drive_file::Model::from_query_result_optional(res, USER_BANNER)?,
|
banner: drive_file::Model::from_query_result_optional(res, USER_BANNER)?,
|
||||||
|
@ -87,10 +154,25 @@ pub struct NoteResolveOptions {
|
||||||
pub with_user: bool,
|
pub with_user: bool,
|
||||||
pub with_reply_target: bool,
|
pub with_reply_target: bool,
|
||||||
pub with_renote_target: bool,
|
pub with_renote_target: bool,
|
||||||
|
pub with_interactions_from: Option<String>, // User ID
|
||||||
}
|
}
|
||||||
|
|
||||||
trait SelectColumnsExt {
|
trait SelectColumnsExt {
|
||||||
fn add_aliased_columns<T: EntityTrait>(self, alias: Option<&str>, entity: T) -> Self;
|
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> {
|
impl SelectColumnsExt for Select<note::Entity> {
|
||||||
|
@ -116,19 +198,89 @@ impl SelectColumnsExt for Select<note::Entity> {
|
||||||
|
|
||||||
self
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
const ALIAS_USER: Lazy<Alias> = Lazy::new(|| Alias::new(USER));
|
fn add_sub_select_renote(
|
||||||
const ALIAS_USER_AVATAR: Lazy<Alias> = Lazy::new(|| Alias::new(USER_AVATAR));
|
mut self: Select<note::Entity>,
|
||||||
const ALIAS_USER_BANNER: Lazy<Alias> = Lazy::new(|| Alias::new(USER_BANNER));
|
source_note_alias: Option<&str>,
|
||||||
const ALIAS_REPLY: Lazy<Alias> = Lazy::new(|| Alias::new(REPLY));
|
prefix_alias: &str,
|
||||||
const ALIAS_REPLY_USER: Lazy<Alias> = Lazy::new(|| Alias::new(REPLY_USER));
|
user_id: &str,
|
||||||
const ALIAS_REPLY_USER_AVATAR: Lazy<Alias> = Lazy::new(|| Alias::new(REPLY_USER_AVATAR));
|
) -> Select<note::Entity> {
|
||||||
const ALIAS_REPLY_USER_BANNER: Lazy<Alias> = Lazy::new(|| Alias::new(REPLY_USER_BANNER));
|
let iden = source_note_alias.unwrap_or_else(|| note::Entity.table_name());
|
||||||
const ALIAS_RENOTE: Lazy<Alias> = Lazy::new(|| Alias::new(RENOTE));
|
let note_id_col = Expr::col((Alias::new(iden), note::Column::Id));
|
||||||
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));
|
let renote_note_tbl = Alias::new(format!("{}{}", prefix_alias, "note"));
|
||||||
const ALIAS_RENOTE_USER_BANNER: Lazy<Alias> = Lazy::new(|| Alias::new(RENOTE_USER_BANNER));
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
impl NoteResolver {
|
||||||
pub fn new(db: CalckeyModel) -> Self {
|
pub fn new(db: CalckeyModel) -> Self {
|
||||||
|
@ -142,11 +294,11 @@ impl NoteResolver {
|
||||||
let select = self.resolve(options);
|
let select = self.resolve(options);
|
||||||
let visibility_filter = options.visibility_filter.with_note_and_user_tables(None);
|
let visibility_filter = options.visibility_filter.with_note_and_user_tables(None);
|
||||||
let time_filter = options.time_range.as_ref().map(|f| match f {
|
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) => {
|
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| {
|
let id_filter = options.ids.as_ref().map(|ids| {
|
||||||
|
@ -164,12 +316,19 @@ impl NoteResolver {
|
||||||
.into_model::<NoteData>()
|
.into_model::<NoteData>()
|
||||||
.one(self.db.inner())
|
.one(self.db.inner())
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(notes)
|
Ok(notes)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resolve(&self, options: &NoteResolveOptions) -> Select<note::Entity> {
|
pub fn resolve(&self, options: &NoteResolveOptions) -> Select<note::Entity> {
|
||||||
let mut select = note::Entity::find().add_aliased_columns(Some(USER), user::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 {
|
if options.with_user {
|
||||||
select = select
|
select = select
|
||||||
.add_aliased_columns(Some(USER_AVATAR), drive_file::Entity)
|
.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), note::Entity)
|
||||||
.add_aliased_columns(Some(REPLY_USER), user::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 {
|
if options.with_user {
|
||||||
select = select
|
select = select
|
||||||
.add_aliased_columns(Some(REPLY_USER_AVATAR), drive_file::Entity)
|
.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), note::Entity)
|
||||||
.add_aliased_columns(Some(RENOTE_USER), user::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 {
|
if options.with_user {
|
||||||
select = select
|
select = select
|
||||||
.add_aliased_columns(Some(RENOTE_USER_AVATAR), drive_file::Entity)
|
.add_aliased_columns(Some(RENOTE_USER_AVATAR), drive_file::Entity)
|
||||||
|
|
|
@ -74,6 +74,12 @@ pub struct NoteBase {
|
||||||
|
|
||||||
pack!(PackNoteBase, Required<Id> as id & Required<NoteBase> as note);
|
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)]
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct NoteAttachmentExt {
|
pub struct NoteAttachmentExt {
|
||||||
|
@ -82,19 +88,19 @@ pub struct NoteAttachmentExt {
|
||||||
}
|
}
|
||||||
|
|
||||||
pack!(
|
pack!(
|
||||||
PackNoteWithMaybeAttachments,
|
PackNoteMaybeAttachments,
|
||||||
Required<Id> as id & Required<NoteBase> as note & Option<NoteAttachmentExt> as attachment
|
Required<Id> as id & Required<NoteBase> as note & Option<NoteSelfContextExt> as user_context & Option<NoteAttachmentExt> as attachment
|
||||||
);
|
);
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
pub struct NoteDetailExt {
|
pub struct NoteDetailExt {
|
||||||
pub parent_note: Option<Box<PackNoteWithMaybeAttachments>>,
|
pub parent_note: Option<Box<PackNoteMaybeAttachments>>,
|
||||||
pub renoted_note: Option<Box<PackNoteWithMaybeAttachments>>,
|
pub renoted_note: Option<Box<PackNoteMaybeAttachments>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pack!(
|
pack!(
|
||||||
PackNoteMaybeFull,
|
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)]
|
#[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),
|
||||||
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ pub async fn handle_note(
|
||||||
}
|
}
|
||||||
.fetch_single(&ctx, self_user.as_deref(), &id)
|
.fetch_single(&ctx, self_user.as_deref(), &id)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| ObjectNotFound(id))?;
|
.ok_or(ObjectNotFound(id))?;
|
||||||
|
|
||||||
Ok(Json(note.into()))
|
Ok(Json(note))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use magnetar_calckey_model::ck;
|
use magnetar_calckey_model::ck;
|
||||||
|
use magnetar_calckey_model::note_model::sub_interaction_renote;
|
||||||
use magnetar_sdk::types::note::{
|
use magnetar_sdk::types::note::{
|
||||||
NoteDetailExt, PackNoteWithMaybeAttachments, PackPollBase, ReactionPair,
|
NoteDetailExt, NoteSelfContextExt, PackNoteMaybeAttachments, PackPollBase, ReactionPair,
|
||||||
};
|
};
|
||||||
use magnetar_sdk::types::user::PackUserBase;
|
use magnetar_sdk::types::user::PackUserBase;
|
||||||
use magnetar_sdk::types::{
|
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 struct NoteDetailSource<'a> {
|
||||||
pub parent_note: Option<&'a PackNoteWithMaybeAttachments>,
|
pub parent_note: Option<&'a PackNoteMaybeAttachments>,
|
||||||
pub renoted_note: Option<&'a PackNoteWithMaybeAttachments>,
|
pub renoted_note: Option<&'a PackNoteMaybeAttachments>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PackType<NoteDetailSource<'a>> for NoteDetailExt {
|
impl<'a> PackType<NoteDetailSource<'a>> for NoteDetailExt {
|
||||||
|
|
|
@ -22,8 +22,8 @@ use magnetar_sdk::mmm::Token;
|
||||||
use magnetar_sdk::types::drive::PackDriveFileBase;
|
use magnetar_sdk::types::drive::PackDriveFileBase;
|
||||||
use magnetar_sdk::types::emoji::EmojiContext;
|
use magnetar_sdk::types::emoji::EmojiContext;
|
||||||
use magnetar_sdk::types::note::{
|
use magnetar_sdk::types::note::{
|
||||||
NoteAttachmentExt, NoteBase, NoteDetailExt, PackNoteBase, PackNoteMaybeFull,
|
NoteAttachmentExt, NoteBase, NoteDetailExt, NoteSelfContextExt, PackNoteBase,
|
||||||
PackNoteWithMaybeAttachments, Reaction,
|
PackNoteMaybeAttachments, PackNoteMaybeFull, Reaction, ReactionPair,
|
||||||
};
|
};
|
||||||
use magnetar_sdk::types::{Id, MmXml};
|
use magnetar_sdk::types::{Id, MmXml};
|
||||||
use magnetar_sdk::{mmm, Packed, Required};
|
use magnetar_sdk::{mmm, Packed, Required};
|
||||||
|
@ -151,7 +151,7 @@ impl NoteVisibilityFilterModel {
|
||||||
return Ok(following || user.host.is_some() && note.user_host.is_some());
|
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 {
|
pub fn new_note_visibility_filter(&self, user: Option<&str>) -> NoteVisibilityFilterSimple {
|
||||||
|
@ -225,18 +225,28 @@ impl NoteModel {
|
||||||
let reactions_raw =
|
let reactions_raw =
|
||||||
serde_json::Map::<String, serde_json::Value>::deserialize(¬e_data.note.reactions)?
|
serde_json::Map::<String, serde_json::Value>::deserialize(¬e_data.note.reactions)?
|
||||||
.into_iter()
|
.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,
|
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>>()?;
|
.collect::<Result<Vec<_>, serde_json::Error>>()?;
|
||||||
// Pick out all successfully-parsed shortcode emojis
|
// Pick out all successfully-parsed shortcode emojis
|
||||||
let reactions_to_resolve = reactions_raw
|
let reactions_to_resolve = reactions_raw
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(code, _)| code)
|
.map(|(code, _, _)| code)
|
||||||
.map(Either::as_ref)
|
.map(Either::as_ref)
|
||||||
.filter_map(Either::right)
|
.filter_map(Either::right)
|
||||||
.filter_map(|c| match c {
|
.filter_map(|c| match c {
|
||||||
|
@ -261,9 +271,8 @@ impl NoteModel {
|
||||||
// Left reactions and the Right ones that didn't resolve to any emoji are turned back into Unknown
|
// Left reactions and the Right ones that didn't resolve to any emoji are turned back into Unknown
|
||||||
let reactions = &reactions_raw
|
let reactions = &reactions_raw
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(raw, count)| {
|
.map(|(raw, count, self_reaction)| {
|
||||||
(
|
let reaction = raw.either(
|
||||||
raw.either(
|
|
||||||
|raw| Reaction::Unknown { raw },
|
|raw| Reaction::Unknown { raw },
|
||||||
|raw| match raw {
|
|raw| match raw {
|
||||||
RawReaction::Unicode(text) => Reaction::Unicode(text),
|
RawReaction::Unicode(text) => Reaction::Unicode(text),
|
||||||
|
@ -286,9 +295,14 @@ impl NoteModel {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
),
|
);
|
||||||
count,
|
|
||||||
)
|
match self_reaction {
|
||||||
|
Some(self_reaction) => {
|
||||||
|
ReactionPair::WithContext(reaction, count, self_reaction)
|
||||||
|
}
|
||||||
|
None => ReactionPair::WithoutContext(reaction, count),
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
@ -322,54 +336,15 @@ impl NoteModel {
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn extract_reply_target_base(
|
fn extract_interaction(
|
||||||
&self,
|
&self,
|
||||||
ctx: &PackingContext,
|
ctx: &PackingContext,
|
||||||
note: &NoteData,
|
note: &NoteData,
|
||||||
) -> PackResult<Option<PackNoteBase>> {
|
) -> PackResult<Option<NoteSelfContextExt>> {
|
||||||
match note.reply.as_ref() {
|
Ok(note
|
||||||
Some(r) if self.with_context => self.extract_base(ctx, r).await.map(Some),
|
.interaction_user_renote
|
||||||
_ => Ok(None),
|
.as_ref()
|
||||||
}
|
.map(|renote_info| NoteSelfContextExt::extract(ctx, renote_info)))
|
||||||
}
|
|
||||||
|
|
||||||
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),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn extract_attachments(
|
async fn extract_attachments(
|
||||||
|
@ -393,6 +368,33 @@ impl NoteModel {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn pack_single_attachments(
|
||||||
|
&self,
|
||||||
|
ctx: &PackingContext,
|
||||||
|
drive_model: &DriveModel,
|
||||||
|
note_data: &NoteData,
|
||||||
|
) -> PackResult<PackNoteMaybeAttachments> {
|
||||||
|
let (PackNoteBase { id, note }, attachments_pack) = try_join!(
|
||||||
|
self.extract_base(ctx, note_data),
|
||||||
|
self.extract_attachments(ctx, drive_model, ¬e_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: None, // TODO
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn fetch_single(
|
pub async fn fetch_single(
|
||||||
&self,
|
&self,
|
||||||
ctx: &PackingContext,
|
ctx: &PackingContext,
|
||||||
|
@ -411,6 +413,10 @@ impl NoteModel {
|
||||||
with_user: self.with_context,
|
with_user: self.with_context,
|
||||||
with_reply_target: self.with_context,
|
with_reply_target: self.with_context,
|
||||||
with_renote_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?
|
.await?
|
||||||
else {
|
else {
|
||||||
|
@ -419,80 +425,47 @@ impl NoteModel {
|
||||||
|
|
||||||
let drive_model = DriveModel;
|
let drive_model = DriveModel;
|
||||||
|
|
||||||
let extract_reply_target = self.extract_reply_target_base(ctx, ¬e);
|
let reply_target = async {
|
||||||
let extract_renote_target = self.extract_renote_target_base(ctx, ¬e);
|
match note.reply.as_ref() {
|
||||||
let extract_attachments = self.extract_attachments(ctx, &drive_model, ¬e.note);
|
Some(r) if self.with_context => self
|
||||||
let extract_reply_attachments =
|
.pack_single_attachments(ctx, &drive_model, r)
|
||||||
self.extract_reply_target_attachemnts(ctx, &drive_model, ¬e);
|
.await
|
||||||
let extract_renote_attachments =
|
.map(Some),
|
||||||
self.extract_renote_target_attachemnts(ctx, &drive_model, ¬e);
|
_ => 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, r)
|
||||||
|
.await
|
||||||
|
.map(Some),
|
||||||
|
_ => Ok(None),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let (
|
let (
|
||||||
PackNoteBase { id, note },
|
PackNoteMaybeAttachments {
|
||||||
|
id,
|
||||||
|
note,
|
||||||
|
user_context,
|
||||||
|
attachment,
|
||||||
|
},
|
||||||
reply_target_pack,
|
reply_target_pack,
|
||||||
renote_target_pack,
|
renote_target_pack,
|
||||||
attachments_pack,
|
|
||||||
reply_attachments_pack,
|
|
||||||
renote_attachments_pack,
|
|
||||||
) = try_join!(
|
) = try_join!(
|
||||||
self.extract_base(ctx, ¬e),
|
self.pack_single_attachments(ctx, &drive_model, ¬e),
|
||||||
extract_reply_target,
|
reply_target,
|
||||||
extract_renote_target,
|
renote_target
|
||||||
extract_attachments,
|
|
||||||
extract_reply_attachments,
|
|
||||||
extract_renote_attachments
|
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let detail = self.with_context.then(|| {
|
let detail = self.with_context.then(|| {
|
||||||
NoteDetailExt::extract(
|
NoteDetailExt::extract(
|
||||||
ctx,
|
ctx,
|
||||||
NoteDetailSource {
|
NoteDetailSource {
|
||||||
parent_note: reply_target_pack
|
parent_note: reply_target_pack.as_ref(),
|
||||||
.map(|base| {
|
renoted_note: renote_target_pack.as_ref(),
|
||||||
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
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
@ -500,7 +473,8 @@ impl NoteModel {
|
||||||
Ok(Some(PackNoteMaybeFull::pack_from((
|
Ok(Some(PackNoteMaybeFull::pack_from((
|
||||||
id,
|
id,
|
||||||
note,
|
note,
|
||||||
attachments,
|
user_context,
|
||||||
|
attachment,
|
||||||
detail,
|
detail,
|
||||||
))))
|
))))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue