Note by ID testing endpoint
This commit is contained in:
parent
4bbc368f92
commit
0755dac002
|
@ -1452,6 +1452,7 @@ dependencies = [
|
||||||
"cached",
|
"cached",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"compact_str",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
"either",
|
"either",
|
||||||
"headers",
|
"headers",
|
||||||
|
@ -1466,6 +1467,7 @@ dependencies = [
|
||||||
"magnetar_webfinger",
|
"magnetar_webfinger",
|
||||||
"miette",
|
"miette",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"strum",
|
"strum",
|
||||||
|
|
|
@ -99,10 +99,12 @@ cfg-if = { workspace = true }
|
||||||
|
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
|
|
||||||
|
compact_str = { workspace = true }
|
||||||
either = { workspace = true }
|
either = { workspace = true }
|
||||||
strum = { workspace = true, features = ["derive"] }
|
strum = { workspace = true, features = ["derive"] }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
miette = { workspace = true, features = ["fancy"] }
|
miette = { workspace = true, features = ["fancy"] }
|
||||||
|
regex = { workspace = true }
|
||||||
|
|
||||||
percent-encoding = { workspace = true }
|
percent-encoding = { workspace = true }
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ pub use ck;
|
||||||
use ck::*;
|
use ck::*;
|
||||||
pub use sea_orm;
|
pub use sea_orm;
|
||||||
|
|
||||||
|
use crate::note_model::NoteResolver;
|
||||||
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;
|
||||||
|
@ -286,6 +287,10 @@ impl CalckeyModel {
|
||||||
|
|
||||||
Ok(meta)
|
Ok(meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_note_resolver(&self) -> NoteResolver {
|
||||||
|
NoteResolver::new(self.clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
@ -21,17 +21,17 @@ pub struct NoteData {
|
||||||
pub renote: Option<Box<NoteData>>,
|
pub renote: Option<Box<NoteData>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
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_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_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.";
|
||||||
|
|
||||||
impl FromQueryResult for NoteData {
|
impl FromQueryResult for NoteData {
|
||||||
fn from_query_result(res: &QueryResult, _pre: &str) -> Result<Self, DbErr> {
|
fn from_query_result(res: &QueryResult, _pre: &str) -> Result<Self, DbErr> {
|
||||||
|
@ -76,16 +76,17 @@ pub struct NoteResolver {
|
||||||
db: CalckeyModel,
|
db: CalckeyModel,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait NoteVisibilityFilterFactory {
|
pub trait NoteVisibilityFilterFactory: Send + Sync {
|
||||||
fn with_note_and_user_tables(&self, note: Option<Alias>, user: Option<Alias>) -> SimpleExpr;
|
fn with_note_and_user_tables(&self, note: Option<Alias>) -> SimpleExpr;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NoteResolveOptions {
|
pub struct NoteResolveOptions {
|
||||||
visibility_filter: Box<dyn NoteVisibilityFilterFactory>,
|
pub ids: Option<Vec<String>>,
|
||||||
time_range: Option<RangeFilter>,
|
pub visibility_filter: Box<dyn NoteVisibilityFilterFactory>,
|
||||||
with_user: bool,
|
pub time_range: Option<RangeFilter>,
|
||||||
with_reply_target: bool,
|
pub with_user: bool,
|
||||||
with_renote_target: bool,
|
pub with_reply_target: bool,
|
||||||
|
pub with_renote_target: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
trait SelectColumnsExt {
|
trait SelectColumnsExt {
|
||||||
|
@ -130,14 +131,16 @@ const ALIAS_RENOTE_USER_AVATAR: Lazy<Alias> = Lazy::new(|| Alias::new(RENOTE_USE
|
||||||
const ALIAS_RENOTE_USER_BANNER: Lazy<Alias> = Lazy::new(|| Alias::new(RENOTE_USER_BANNER));
|
const ALIAS_RENOTE_USER_BANNER: Lazy<Alias> = Lazy::new(|| Alias::new(RENOTE_USER_BANNER));
|
||||||
|
|
||||||
impl NoteResolver {
|
impl NoteResolver {
|
||||||
|
pub fn new(db: CalckeyModel) -> Self {
|
||||||
|
NoteResolver { db }
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_one(
|
pub async fn get_one(
|
||||||
&self,
|
&self,
|
||||||
options: &NoteResolveOptions,
|
options: &NoteResolveOptions,
|
||||||
) -> Result<Option<NoteData>, CalckeyDbError> {
|
) -> Result<Option<NoteData>, CalckeyDbError> {
|
||||||
let select = self.resolve(options);
|
let select = self.resolve(options);
|
||||||
let visibility_filter = options
|
let visibility_filter = options.visibility_filter.with_note_and_user_tables(None);
|
||||||
.visibility_filter
|
|
||||||
.with_note_and_user_tables(None, Some(ALIAS_USER.clone()));
|
|
||||||
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.clone()),
|
||||||
RangeFilter::TimeRange(range) => {
|
RangeFilter::TimeRange(range) => {
|
||||||
|
@ -146,8 +149,17 @@ impl NoteResolver {
|
||||||
RangeFilter::TimeEnd(end) => note::Column::CreatedAt.lt(end.clone()),
|
RangeFilter::TimeEnd(end) => note::Column::CreatedAt.lt(end.clone()),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let id_filter = options.ids.as_ref().map(|ids| {
|
||||||
|
if ids.len() == 1 {
|
||||||
|
note::Column::Id.eq(&ids[0])
|
||||||
|
} else {
|
||||||
|
note::Column::Id.is_in(ids)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let notes = select
|
let notes = select
|
||||||
.filter(visibility_filter)
|
.filter(visibility_filter)
|
||||||
|
.apply_if(id_filter, Select::<note::Entity>::filter)
|
||||||
.apply_if(time_filter, Select::<note::Entity>::filter)
|
.apply_if(time_filter, Select::<note::Entity>::filter)
|
||||||
.into_model::<NoteData>()
|
.into_model::<NoteData>()
|
||||||
.one(self.db.inner())
|
.one(self.db.inner())
|
||||||
|
|
|
@ -255,6 +255,25 @@ impl Token {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn walk_speech_transform(&mut self, func: &impl Fn(&mut CompactString)) {
|
||||||
|
match self {
|
||||||
|
Token::Sequence(items) => {
|
||||||
|
items
|
||||||
|
.iter_mut()
|
||||||
|
.for_each(|tok| tok.walk_speech_transform(func));
|
||||||
|
}
|
||||||
|
Token::Small(inner)
|
||||||
|
| Token::BoldItalic(inner)
|
||||||
|
| Token::Bold(inner)
|
||||||
|
| Token::Italic(inner)
|
||||||
|
| Token::Center(inner)
|
||||||
|
| Token::Function { inner, .. }
|
||||||
|
| Token::Strikethrough(inner) => inner.walk_speech_transform(func),
|
||||||
|
Token::PlainText(text) => func(text),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn write<T: Write>(&self, writer: &mut quick_xml::Writer<T>) -> quick_xml::Result<()> {
|
fn write<T: Write>(&self, writer: &mut quick_xml::Writer<T>) -> quick_xml::Result<()> {
|
||||||
match self {
|
match self {
|
||||||
Token::PlainText(plain) => {
|
Token::PlainText(plain) => {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
pub mod note;
|
||||||
pub mod timeline;
|
pub mod timeline;
|
||||||
pub mod user;
|
pub mod user;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
use crate::endpoints::Endpoint;
|
||||||
|
use crate::types::note::PackNoteMaybeFull;
|
||||||
|
use http::Method;
|
||||||
|
use magnetar_sdk_macros::Endpoint;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use ts_rs::TS;
|
||||||
|
|
||||||
|
// Get note by id
|
||||||
|
#[derive(Serialize, Deserialize, TS)]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct NoteByIdReq {
|
||||||
|
#[serde(default)]
|
||||||
|
pub context: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub attachments: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Endpoint)]
|
||||||
|
#[endpoint(
|
||||||
|
endpoint = "/notes/:id",
|
||||||
|
method = Method::GET,
|
||||||
|
request = NoteByIdReq,
|
||||||
|
response = PackNoteMaybeFull
|
||||||
|
)]
|
||||||
|
pub struct GetNoteById;
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::endpoints::Endpoint;
|
use crate::endpoints::Endpoint;
|
||||||
use crate::types::note::{NoteListFilter, PackNoteFull};
|
use crate::types::note::{NoteListFilter, PackNoteMaybeFull};
|
||||||
use crate::util_types::U64Range;
|
use crate::util_types::U64Range;
|
||||||
use http::Method;
|
use http::Method;
|
||||||
use magnetar_sdk_macros::Endpoint;
|
use magnetar_sdk_macros::Endpoint;
|
||||||
|
@ -23,13 +23,13 @@ fn default_timeline_limit<const MIN: u64, const MAX: u64>() -> U64Range<MIN, MAX
|
||||||
#[derive(Serialize, Deserialize, TS)]
|
#[derive(Serialize, Deserialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct GetTimelineRes(pub Vec<PackNoteFull>);
|
pub struct GetTimelineRes(pub Vec<PackNoteMaybeFull>);
|
||||||
|
|
||||||
#[derive(Endpoint)]
|
#[derive(Endpoint)]
|
||||||
#[endpoint(
|
#[endpoint(
|
||||||
endpoint = "/timeline",
|
endpoint = "/timeline",
|
||||||
method = Method::GET,
|
method = Method::GET,
|
||||||
request = GetTimelineReq,
|
request = GetTimelineReq,
|
||||||
response = Vec::<PackNoteFull>,
|
response = Vec::<PackNoteMaybeFull>,
|
||||||
)]
|
)]
|
||||||
pub struct GetTimeline;
|
pub struct GetTimeline;
|
||||||
|
|
|
@ -22,7 +22,7 @@ pub struct UserSelfReq {
|
||||||
|
|
||||||
#[derive(Endpoint)]
|
#[derive(Endpoint)]
|
||||||
#[endpoint(
|
#[endpoint(
|
||||||
endpoint = "/users/@self/overview/info",
|
endpoint = "/users/@self",
|
||||||
method = Method::GET,
|
method = Method::GET,
|
||||||
request = UserSelfReq,
|
request = UserSelfReq,
|
||||||
response = PackUserSelfMaybeAll
|
response = PackUserSelfMaybeAll
|
||||||
|
@ -47,7 +47,7 @@ pub struct UserByIdReq {
|
||||||
|
|
||||||
#[derive(Endpoint)]
|
#[derive(Endpoint)]
|
||||||
#[endpoint(
|
#[endpoint(
|
||||||
endpoint = "/users/:user_id/info",
|
endpoint = "/users/:user_id",
|
||||||
method = Method::GET,
|
method = Method::GET,
|
||||||
request = UserByIdReq,
|
request = UserByIdReq,
|
||||||
response = PackUserMaybeAll
|
response = PackUserMaybeAll
|
||||||
|
|
|
@ -56,7 +56,7 @@ pub struct NoteBase {
|
||||||
pub uri: Option<String>,
|
pub uri: Option<String>,
|
||||||
pub url: Option<String>,
|
pub url: Option<String>,
|
||||||
pub text: String,
|
pub text: String,
|
||||||
pub text_mm: MmXml,
|
pub text_mm: Option<MmXml>,
|
||||||
pub visibility: NoteVisibility,
|
pub visibility: NoteVisibility,
|
||||||
pub user: Box<PackUserBase>,
|
pub user: Box<PackUserBase>,
|
||||||
pub parent_note_id: Option<String>,
|
pub parent_note_id: Option<String>,
|
||||||
|
@ -92,8 +92,8 @@ pub struct NoteDetailExt {
|
||||||
}
|
}
|
||||||
|
|
||||||
pack!(
|
pack!(
|
||||||
PackNoteFull,
|
PackNoteMaybeFull,
|
||||||
Required<Id> as id & Required<NoteBase> as note & Required<NoteAttachmentExt> as attachment & Required<NoteDetailExt> as detail
|
Required<Id> as id & Required<NoteBase> as note & Option<NoteAttachmentExt> as attachment & Option<NoteDetailExt> as detail
|
||||||
);
|
);
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
|
|
@ -5,7 +5,7 @@ use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::types::note::PackNoteFull;
|
use crate::types::note::PackNoteMaybeFull;
|
||||||
use magnetar_sdk_macros::pack;
|
use magnetar_sdk_macros::pack;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, Default, Deserialize, Serialize, TS)]
|
||||||
|
@ -88,7 +88,7 @@ pub struct UserProfileExt {
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct UserProfilePinsEx {
|
pub struct UserProfilePinsEx {
|
||||||
pub pinned_notes: Vec<PackNoteFull>,
|
pub pinned_notes: Vec<PackNoteMaybeFull>,
|
||||||
// pub pinned_page: Option<Page>,
|
// pub pinned_page: Option<Page>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
|
mod note;
|
||||||
mod user;
|
mod user;
|
||||||
|
|
||||||
|
use crate::api_v1::note::handle_note;
|
||||||
use crate::api_v1::user::{handle_user_info, handle_user_info_self};
|
use crate::api_v1::user::{handle_user_info, handle_user_info_self};
|
||||||
use crate::service::MagnetarService;
|
use crate::service::MagnetarService;
|
||||||
use crate::web::auth;
|
use crate::web::auth;
|
||||||
|
@ -13,6 +15,7 @@ pub fn create_api_router(service: Arc<MagnetarService>) -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/users/@self", get(handle_user_info_self))
|
.route("/users/@self", get(handle_user_info_self))
|
||||||
.route("/users/:id", get(handle_user_info))
|
.route("/users/:id", get(handle_user_info))
|
||||||
|
.route("/notes/:id", get(handle_note))
|
||||||
.layer(from_fn_with_state(
|
.layer(from_fn_with_state(
|
||||||
AuthState::new(service.clone()),
|
AuthState::new(service.clone()),
|
||||||
auth::auth,
|
auth::auth,
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
use axum::extract::{Path, Query, State};
|
||||||
|
use axum::{debug_handler, Json};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::model::processing::note::NoteModel;
|
||||||
|
use crate::model::PackingContext;
|
||||||
|
use magnetar_sdk::endpoints::note::{GetNoteById, NoteByIdReq};
|
||||||
|
use magnetar_sdk::endpoints::{Req, Res};
|
||||||
|
|
||||||
|
use crate::service::MagnetarService;
|
||||||
|
use crate::web::auth::MaybeUser;
|
||||||
|
use crate::web::{ApiError, ObjectNotFound};
|
||||||
|
|
||||||
|
#[debug_handler]
|
||||||
|
pub async fn handle_note(
|
||||||
|
Path(id): Path<String>,
|
||||||
|
Query(NoteByIdReq {
|
||||||
|
context,
|
||||||
|
attachments,
|
||||||
|
}): Query<Req<GetNoteById>>,
|
||||||
|
State(service): State<Arc<MagnetarService>>,
|
||||||
|
MaybeUser(self_user): MaybeUser,
|
||||||
|
) -> Result<Json<Res<GetNoteById>>, ApiError> {
|
||||||
|
let ctx = PackingContext::new(service, self_user.clone()).await?;
|
||||||
|
let note = NoteModel
|
||||||
|
.fetch_single(&ctx, self_user.as_deref(), &id, context, attachments)
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| ObjectNotFound(id))?;
|
||||||
|
|
||||||
|
Ok(Json(note.into()))
|
||||||
|
}
|
|
@ -29,7 +29,7 @@ impl PackType<&ck::note_reaction::Model> for ReactionBase {
|
||||||
pub struct NoteBaseSource<'a> {
|
pub struct NoteBaseSource<'a> {
|
||||||
pub note: &'a ck::note::Model,
|
pub note: &'a ck::note::Model,
|
||||||
pub cw_mm: Option<&'a MmXml>,
|
pub cw_mm: Option<&'a MmXml>,
|
||||||
pub text_mm: &'a MmXml,
|
pub text_mm: Option<&'a MmXml>,
|
||||||
pub reactions: &'a Vec<PackReactionBase>,
|
pub reactions: &'a Vec<PackReactionBase>,
|
||||||
pub user: &'a UserBase,
|
pub user: &'a UserBase,
|
||||||
pub emoji_context: &'a EmojiContext,
|
pub emoji_context: &'a EmojiContext,
|
||||||
|
@ -55,7 +55,7 @@ impl PackType<NoteBaseSource<'_>> for NoteBase {
|
||||||
uri: note.uri.clone(),
|
uri: note.uri.clone(),
|
||||||
url: note.url.clone(),
|
url: note.url.clone(),
|
||||||
text: note.text.clone().unwrap_or_default(),
|
text: note.text.clone().unwrap_or_default(),
|
||||||
text_mm: text_mm.clone(),
|
text_mm: text_mm.map(ToOwned::to_owned).clone(),
|
||||||
visibility: match note.visibility {
|
visibility: match note.visibility {
|
||||||
NVE::Followers => NoteVisibility::Followers,
|
NVE::Followers => NoteVisibility::Followers,
|
||||||
NVE::Hidden => NoteVisibility::Direct,
|
NVE::Hidden => NoteVisibility::Direct,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use magnetar_calckey_model::ck;
|
use magnetar_calckey_model::ck;
|
||||||
use magnetar_calckey_model::ck::sea_orm_active_enums::UserProfileFfvisibilityEnum;
|
use magnetar_calckey_model::ck::sea_orm_active_enums::UserProfileFfvisibilityEnum;
|
||||||
use magnetar_sdk::types::emoji::{EmojiContext, PackEmojiBase};
|
use magnetar_sdk::types::emoji::{EmojiContext, PackEmojiBase};
|
||||||
use magnetar_sdk::types::note::PackNoteFull;
|
use magnetar_sdk::types::note::PackNoteMaybeFull;
|
||||||
use magnetar_sdk::types::user::{
|
use magnetar_sdk::types::user::{
|
||||||
AvatarDecoration, PackSecurityKeyBase, ProfileField, SecurityKeyBase, SpeechTransform,
|
AvatarDecoration, PackSecurityKeyBase, ProfileField, SecurityKeyBase, SpeechTransform,
|
||||||
UserBase, UserDetailExt, UserProfileExt, UserProfilePinsEx, UserRelationExt, UserSecretsExt,
|
UserBase, UserDetailExt, UserProfileExt, UserProfilePinsEx, UserRelationExt, UserSecretsExt,
|
||||||
|
@ -187,8 +187,8 @@ impl PackType<UserRelationExtSource<'_>> for UserRelationExt {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PackType<&[PackNoteFull]> for UserProfilePinsEx {
|
impl PackType<&[PackNoteMaybeFull]> for UserProfilePinsEx {
|
||||||
fn extract(_context: &PackingContext, pinned_notes: &[PackNoteFull]) -> Self {
|
fn extract(_context: &PackingContext, pinned_notes: &[PackNoteMaybeFull]) -> Self {
|
||||||
UserProfilePinsEx {
|
UserProfilePinsEx {
|
||||||
pinned_notes: pinned_notes.to_owned(),
|
pinned_notes: pinned_notes.to_owned(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,37 +1,44 @@
|
||||||
use crate::model::{PackingContext, UserRelationship};
|
use crate::model::data::id::BaseId;
|
||||||
|
use crate::model::data::note::NoteBaseSource;
|
||||||
|
use crate::model::processing::emoji::EmojiModel;
|
||||||
|
use crate::model::processing::user::UserModel;
|
||||||
|
use crate::model::processing::{get_mm_token_emoji, PackResult};
|
||||||
|
use crate::model::{PackType, PackingContext, UserRelationship};
|
||||||
|
use compact_str::CompactString;
|
||||||
use either::Either;
|
use either::Either;
|
||||||
use magnetar_calckey_model::ck::sea_orm_active_enums::NoteVisibilityEnum;
|
use magnetar_calckey_model::ck::sea_orm_active_enums::NoteVisibilityEnum;
|
||||||
use magnetar_calckey_model::note_model::NoteVisibilityFilterFactory;
|
use magnetar_calckey_model::note_model::{NoteResolveOptions, NoteVisibilityFilterFactory};
|
||||||
use magnetar_calckey_model::sea_orm::prelude::Expr;
|
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::sea_query::{Alias, IntoIden, PgFunc, Query, SimpleExpr};
|
||||||
use magnetar_calckey_model::sea_orm::{ColumnTrait, IntoSimpleExpr};
|
use magnetar_calckey_model::sea_orm::{ActiveEnum, ColumnTrait, IntoSimpleExpr};
|
||||||
use magnetar_calckey_model::{ck, CalckeyDbError};
|
use magnetar_calckey_model::{ck, CalckeyDbError};
|
||||||
|
use magnetar_sdk::mmm::Token;
|
||||||
|
use magnetar_sdk::types::emoji::EmojiContext;
|
||||||
|
use magnetar_sdk::types::note::{NoteBase, PackNoteMaybeFull};
|
||||||
|
use magnetar_sdk::types::{Id, MmXml};
|
||||||
|
use magnetar_sdk::{mmm, Packed, Required};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct NoteVisibilityFilterSimple(Option<String>);
|
pub struct NoteVisibilityFilterSimple(Option<String>);
|
||||||
|
|
||||||
impl NoteVisibilityFilterFactory for NoteVisibilityFilterSimple {
|
impl NoteVisibilityFilterFactory for NoteVisibilityFilterSimple {
|
||||||
fn with_note_and_user_tables(
|
fn with_note_and_user_tables(&self, note_tbl: Option<Alias>) -> SimpleExpr {
|
||||||
&self,
|
|
||||||
note_tbl: Option<Alias>,
|
|
||||||
user_tbl: Option<Alias>,
|
|
||||||
) -> SimpleExpr {
|
|
||||||
let note_tbl_name =
|
let note_tbl_name =
|
||||||
note_tbl.map_or_else(|| ck::note::Entity.into_iden(), |a| a.into_iden());
|
note_tbl.map_or_else(|| ck::note::Entity.into_iden(), |a| a.into_iden());
|
||||||
let user_tbl_name =
|
|
||||||
user_tbl.map_or_else(|| ck::user::Entity.into_iden(), |a| a.into_iden());
|
|
||||||
|
|
||||||
let note_visibility = Expr::col((note_tbl_name.clone(), ck::note::Column::Visibility));
|
let note_visibility = Expr::col((note_tbl_name.clone(), ck::note::Column::Visibility));
|
||||||
let note_mentions = Expr::col((note_tbl_name.clone(), ck::note::Column::Mentions));
|
let note_mentions = Expr::col((note_tbl_name.clone(), ck::note::Column::Mentions));
|
||||||
let note_reply_user_id = Expr::col((note_tbl_name.clone(), ck::note::Column::ReplyUserId));
|
let note_reply_user_id = Expr::col((note_tbl_name.clone(), ck::note::Column::ReplyUserId));
|
||||||
let note_visible_user_ids =
|
let note_visible_user_ids =
|
||||||
Expr::col((note_tbl_name.clone(), ck::note::Column::VisibleUserIds));
|
Expr::col((note_tbl_name.clone(), ck::note::Column::VisibleUserIds));
|
||||||
let note_user_id = Expr::col((user_tbl_name, ck::note::Column::UserId));
|
let note_user_id = Expr::col((note_tbl_name, ck::note::Column::UserId));
|
||||||
|
|
||||||
let is_public = note_visibility
|
let is_public = note_visibility
|
||||||
.clone()
|
.clone()
|
||||||
.eq(NoteVisibilityEnum::Public)
|
.eq(NoteVisibilityEnum::Public.as_enum())
|
||||||
.or(note_visibility.clone().eq(NoteVisibilityEnum::Home));
|
.or(note_visibility
|
||||||
|
.clone()
|
||||||
|
.eq(NoteVisibilityEnum::Home.as_enum()));
|
||||||
|
|
||||||
let Some(user_id_str) = &self.0 else {
|
let Some(user_id_str) = &self.0 else {
|
||||||
return is_public;
|
return is_public;
|
||||||
|
@ -44,8 +51,10 @@ impl NoteVisibilityFilterFactory for NoteVisibilityFilterSimple {
|
||||||
let is_visible_specified = {
|
let is_visible_specified = {
|
||||||
let either_specified_or_followers = note_visibility
|
let either_specified_or_followers = note_visibility
|
||||||
.clone()
|
.clone()
|
||||||
.eq(NoteVisibilityEnum::Specified)
|
.eq(NoteVisibilityEnum::Specified.as_enum())
|
||||||
.or(note_visibility.clone().eq(NoteVisibilityEnum::Followers))
|
.or(note_visibility
|
||||||
|
.clone()
|
||||||
|
.eq(NoteVisibilityEnum::Followers.as_enum()))
|
||||||
.into_simple_expr();
|
.into_simple_expr();
|
||||||
|
|
||||||
let mentioned_or_specified = self_user_id
|
let mentioned_or_specified = self_user_id
|
||||||
|
@ -58,7 +67,7 @@ impl NoteVisibilityFilterFactory for NoteVisibilityFilterSimple {
|
||||||
|
|
||||||
let is_visible_followers = {
|
let is_visible_followers = {
|
||||||
note_visibility
|
note_visibility
|
||||||
.eq(NoteVisibilityEnum::Followers)
|
.eq(NoteVisibilityEnum::Followers.as_enum())
|
||||||
.and(
|
.and(
|
||||||
note_user_id.in_subquery(
|
note_user_id.in_subquery(
|
||||||
Query::select()
|
Query::select()
|
||||||
|
@ -135,3 +144,116 @@ impl NoteVisibilityFilterModel {
|
||||||
NoteVisibilityFilterSimple(user.map(str::to_string))
|
NoteVisibilityFilterSimple(user.map(str::to_string))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct SpeechTransformNyan;
|
||||||
|
|
||||||
|
impl SpeechTransformNyan {
|
||||||
|
fn new() -> Self {
|
||||||
|
SpeechTransformNyan
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform(&self, text: &mut CompactString) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NoteModel;
|
||||||
|
|
||||||
|
impl NoteModel {
|
||||||
|
pub fn tokenize_note_text(&self, note: &ck::note::Model) -> Option<Token> {
|
||||||
|
note.text
|
||||||
|
.as_deref()
|
||||||
|
.map(|text| mmm::Context::default().parse_full(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tokenize_note_cw(&self, note: &ck::note::Model) -> Option<Token> {
|
||||||
|
note.cw
|
||||||
|
.as_deref()
|
||||||
|
.map(|text| mmm::Context::default().parse_ui(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn fetch_single(
|
||||||
|
&self,
|
||||||
|
ctx: &PackingContext,
|
||||||
|
as_user: Option<&ck::user::Model>,
|
||||||
|
id: &str,
|
||||||
|
show_context: bool,
|
||||||
|
attachments: bool,
|
||||||
|
) -> PackResult<Option<PackNoteMaybeFull>> {
|
||||||
|
let note_resolver = ctx.service.db.get_note_resolver();
|
||||||
|
let Some(note) = note_resolver
|
||||||
|
.get_one(&NoteResolveOptions {
|
||||||
|
ids: Some(vec![id.to_owned()]),
|
||||||
|
visibility_filter: Box::new(
|
||||||
|
NoteVisibilityFilterModel
|
||||||
|
.new_note_visibility_filter(as_user.map(ck::user::Model::get_id)),
|
||||||
|
),
|
||||||
|
time_range: None,
|
||||||
|
with_user: show_context,
|
||||||
|
with_reply_target: show_context,
|
||||||
|
with_renote_target: show_context,
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
let Required(ref user) = UserModel.base_from_existing(ctx, ¬e.user).await?.user;
|
||||||
|
|
||||||
|
let cw_tok = self.tokenize_note_cw(¬e.note);
|
||||||
|
let mut text_tok = self.tokenize_note_text(¬e.note);
|
||||||
|
|
||||||
|
let mut emoji_extracted = Vec::new();
|
||||||
|
|
||||||
|
if let Some(cw_tok) = &cw_tok {
|
||||||
|
emoji_extracted.extend_from_slice(&get_mm_token_emoji(cw_tok));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(text_tok) = &mut text_tok {
|
||||||
|
emoji_extracted.extend_from_slice(&get_mm_token_emoji(text_tok));
|
||||||
|
|
||||||
|
if note.user.is_cat && note.user.speak_as_cat {
|
||||||
|
let transformer = SpeechTransformNyan::new();
|
||||||
|
text_tok.walk_speech_transform(&|text| transformer.transform(text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let emoji_model = EmojiModel;
|
||||||
|
let shortcodes = emoji_model.deduplicate_emoji(ctx, emoji_extracted);
|
||||||
|
let emojis = emoji_model
|
||||||
|
.fetch_many_emojis(ctx, &shortcodes, note.user.host.as_deref())
|
||||||
|
.await?;
|
||||||
|
let emoji_context = &EmojiContext(emojis);
|
||||||
|
|
||||||
|
// TODO: Polls, reactions, attachments, ...
|
||||||
|
|
||||||
|
let note_base = NoteBase::extract(
|
||||||
|
ctx,
|
||||||
|
NoteBaseSource {
|
||||||
|
note: ¬e.note,
|
||||||
|
cw_mm: cw_tok
|
||||||
|
.as_ref()
|
||||||
|
.map(mmm::to_xml_string)
|
||||||
|
.and_then(Result::ok)
|
||||||
|
.map(MmXml)
|
||||||
|
.as_ref(),
|
||||||
|
text_mm: text_tok
|
||||||
|
.as_ref()
|
||||||
|
.map(mmm::to_xml_string)
|
||||||
|
.and_then(Result::ok)
|
||||||
|
.map(MmXml)
|
||||||
|
.as_ref(),
|
||||||
|
reactions: &vec![],
|
||||||
|
user,
|
||||||
|
emoji_context,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(Some(PackNoteMaybeFull::pack_from((
|
||||||
|
Required(Id::from(note.note.id.clone())),
|
||||||
|
Required(note_base),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue