Full user packing in the backend and Magnetar-filled preview cards in the frontend
ci/woodpecker/push/ociImagePush Pipeline was successful
Details
ci/woodpecker/push/ociImagePush Pipeline was successful
Details
This commit is contained in:
parent
d1ca62a807
commit
81d0c678d8
|
@ -114,6 +114,26 @@ impl CalckeyModel {
|
||||||
.await?)
|
.await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_user_profile_by_id(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
) -> Result<Option<user_profile::Model>, CalckeyDbError> {
|
||||||
|
Ok(user_profile::Entity::find()
|
||||||
|
.filter(user_profile::Column::UserId.eq(id))
|
||||||
|
.one(&self.0)
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_user_security_keys_by_id(
|
||||||
|
&self,
|
||||||
|
id: &str,
|
||||||
|
) -> Result<Vec<user_security_key::Model>, CalckeyDbError> {
|
||||||
|
Ok(user_security_key::Entity::find()
|
||||||
|
.filter(user_security_key::Column::UserId.eq(id))
|
||||||
|
.all(&self.0)
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_many_users_by_id(
|
pub async fn get_many_users_by_id(
|
||||||
&self,
|
&self,
|
||||||
id: &[String],
|
id: &[String],
|
||||||
|
@ -160,6 +180,21 @@ impl CalckeyModel {
|
||||||
.await?)
|
.await?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_follow_request_status(
|
||||||
|
&self,
|
||||||
|
from: &str,
|
||||||
|
to: &str,
|
||||||
|
) -> Result<Option<follow_request::Model>, CalckeyDbError> {
|
||||||
|
Ok(follow_request::Entity::find()
|
||||||
|
.filter(
|
||||||
|
follow_request::Column::FollowerId
|
||||||
|
.eq(from)
|
||||||
|
.and(follow_request::Column::FolloweeId.eq(to)),
|
||||||
|
)
|
||||||
|
.one(&self.0)
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_block_status(
|
pub async fn get_block_status(
|
||||||
&self,
|
&self,
|
||||||
from: &str,
|
from: &str,
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use sea_orm::sea_query::{Alias, Asterisk, Expr, IntoIden, Query, SelectExpr, SimpleExpr};
|
use sea_orm::sea_query::{
|
||||||
|
Alias, Asterisk, Expr, IntoCondition, IntoIden, Query, SelectExpr, SimpleExpr,
|
||||||
|
};
|
||||||
use sea_orm::{
|
use sea_orm::{
|
||||||
ColumnTrait, DbErr, EntityName, EntityTrait, FromQueryResult, Iden, Iterable, JoinType,
|
ColumnTrait, DbErr, EntityName, EntityTrait, FromQueryResult, Iden, Iterable, JoinType,
|
||||||
QueryFilter, QueryResult, QuerySelect, QueryTrait, RelationTrait, Select,
|
QueryFilter, QueryOrder, QueryResult, QuerySelect, QueryTrait, RelationTrait, Select,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use ck::{drive_file, note, note_reaction, user};
|
use ck::{drive_file, note, note_reaction, user, user_note_pining};
|
||||||
use magnetar_sdk::types::RangeFilter;
|
use magnetar_sdk::types::RangeFilter;
|
||||||
|
|
||||||
use crate::{AliasSourceExt, CalckeyDbError, CalckeyModel};
|
use crate::{AliasSourceExt, CalckeyDbError, CalckeyModel};
|
||||||
|
@ -53,6 +55,7 @@ pub struct NoteData {
|
||||||
pub renote: Option<Box<NoteData>>,
|
pub renote: Option<Box<NoteData>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PIN: &str = "pin.";
|
||||||
const INTERACTION_REACTION: &str = "interaction.reaction.";
|
const INTERACTION_REACTION: &str = "interaction.reaction.";
|
||||||
const INTERACTION_RENOTE: &str = "interaction.renote.";
|
const INTERACTION_RENOTE: &str = "interaction.renote.";
|
||||||
const USER: &str = "user.";
|
const USER: &str = "user.";
|
||||||
|
@ -154,6 +157,7 @@ pub struct NoteResolveOptions {
|
||||||
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
|
pub with_interactions_from: Option<String>, // User ID
|
||||||
|
pub only_pins_from: Option<String>, // User ID
|
||||||
}
|
}
|
||||||
|
|
||||||
trait SelectColumnsExt {
|
trait SelectColumnsExt {
|
||||||
|
@ -262,6 +266,7 @@ impl SelectColumnsExt for Select<note::Entity> {
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
static ref ALIAS_PIN: Alias = Alias::new(PIN);
|
||||||
static ref ALIAS_INTERACTION_RENOTE: Alias = Alias::new(INTERACTION_RENOTE);
|
static ref ALIAS_INTERACTION_RENOTE: Alias = Alias::new(INTERACTION_RENOTE);
|
||||||
static ref ALIAS_INTERACTION_REACTION: Alias = Alias::new(INTERACTION_REACTION);
|
static ref ALIAS_INTERACTION_REACTION: Alias = Alias::new(INTERACTION_REACTION);
|
||||||
static ref ALIAS_USER: Alias = Alias::new(USER);
|
static ref ALIAS_USER: Alias = Alias::new(USER);
|
||||||
|
@ -281,6 +286,24 @@ lazy_static! {
|
||||||
static ref ALIAS_RENOTE_USER_BANNER: Alias = Alias::new(RENOTE_USER_BANNER);
|
static ref ALIAS_RENOTE_USER_BANNER: Alias = Alias::new(RENOTE_USER_BANNER);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn range_into_expr(filter: &RangeFilter) -> SimpleExpr {
|
||||||
|
match filter {
|
||||||
|
RangeFilter::TimeStart(start) => note::Column::CreatedAt.gte(*start),
|
||||||
|
RangeFilter::TimeRange(range) => {
|
||||||
|
note::Column::CreatedAt.between(*range.start(), *range.end())
|
||||||
|
}
|
||||||
|
RangeFilter::TimeEnd(end) => note::Column::CreatedAt.lt(*end),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ids_into_expr(ids: &Vec<String>) -> SimpleExpr {
|
||||||
|
if ids.len() == 1 {
|
||||||
|
note::Column::Id.eq(&ids[0])
|
||||||
|
} else {
|
||||||
|
note::Column::Id.is_in(ids)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl NoteResolver {
|
impl NoteResolver {
|
||||||
pub fn new(db: CalckeyModel) -> Self {
|
pub fn new(db: CalckeyModel) -> Self {
|
||||||
NoteResolver { db }
|
NoteResolver { db }
|
||||||
|
@ -292,21 +315,8 @@ impl NoteResolver {
|
||||||
) -> Result<Option<NoteData>, CalckeyDbError> {
|
) -> Result<Option<NoteData>, CalckeyDbError> {
|
||||||
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(range_into_expr);
|
||||||
RangeFilter::TimeStart(start) => note::Column::CreatedAt.gte(*start),
|
let id_filter = options.ids.as_ref().map(ids_into_expr);
|
||||||
RangeFilter::TimeRange(range) => {
|
|
||||||
note::Column::CreatedAt.between(*range.start(), *range.end())
|
|
||||||
}
|
|
||||||
RangeFilter::TimeEnd(end) => note::Column::CreatedAt.lt(*end),
|
|
||||||
});
|
|
||||||
|
|
||||||
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)
|
||||||
|
@ -319,9 +329,49 @@ impl NoteResolver {
|
||||||
Ok(notes)
|
Ok(notes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_many(
|
||||||
|
&self,
|
||||||
|
options: &NoteResolveOptions,
|
||||||
|
) -> Result<Vec<NoteData>, CalckeyDbError> {
|
||||||
|
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(range_into_expr);
|
||||||
|
let id_filter = options.ids.as_ref().map(ids_into_expr);
|
||||||
|
|
||||||
|
let notes = select
|
||||||
|
.filter(visibility_filter)
|
||||||
|
.apply_if(id_filter, Select::<note::Entity>::filter)
|
||||||
|
.apply_if(time_filter, Select::<note::Entity>::filter)
|
||||||
|
.apply_if(options.only_pins_from.as_deref(), |s, _| {
|
||||||
|
s.order_by_desc(Expr::col((
|
||||||
|
ALIAS_PIN.clone(),
|
||||||
|
user_note_pining::Column::CreatedAt,
|
||||||
|
)))
|
||||||
|
})
|
||||||
|
.into_model::<NoteData>()
|
||||||
|
.all(self.db.inner())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
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(pins_user) = options.only_pins_from.clone() {
|
||||||
|
select = select.join_as(
|
||||||
|
JoinType::InnerJoin,
|
||||||
|
note::Relation::UserNotePining
|
||||||
|
.def()
|
||||||
|
.on_condition(move |left, _right| {
|
||||||
|
Expr::col((left, note::Column::UserId))
|
||||||
|
.eq(&pins_user)
|
||||||
|
.into_condition()
|
||||||
|
}),
|
||||||
|
ALIAS_PIN.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(user_id) = &options.with_interactions_from {
|
if let Some(user_id) = &options.with_interactions_from {
|
||||||
select = select
|
select = select
|
||||||
.add_sub_select_reaction(None, INTERACTION_REACTION, user_id)
|
.add_sub_select_reaction(None, INTERACTION_REACTION, user_id)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<MkA
|
<MkA
|
||||||
v-if="url.startsWith('/')"
|
v-user-preview="{ username, host }"
|
||||||
v-user-preview="canonical"
|
|
||||||
class="mention"
|
class="mention"
|
||||||
:class="{ isMe }"
|
:class="{ isMe }"
|
||||||
:to="url"
|
:to="url"
|
||||||
|
@ -17,28 +16,12 @@
|
||||||
>
|
>
|
||||||
</span>
|
</span>
|
||||||
</MkA>
|
</MkA>
|
||||||
<a
|
|
||||||
v-else
|
|
||||||
class="mention"
|
|
||||||
:href="url"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener"
|
|
||||||
:style="{ background: bgCss }"
|
|
||||||
@click.stop
|
|
||||||
>
|
|
||||||
<span class="main">
|
|
||||||
<span class="username">@{{ username }}</span>
|
|
||||||
<span class="host">@{{ toUnicode(host) }}</span>
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { toUnicode } from "punycode";
|
import { toUnicode } from "punycode";
|
||||||
import {} from "vue";
|
|
||||||
import { host as localHost } from "@/config";
|
import { host as localHost } from "@/config";
|
||||||
import { $i } from "@/account";
|
import { $i } from "@/account";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
username: string;
|
username: string;
|
||||||
host: string;
|
host: string;
|
|
@ -19,25 +19,23 @@
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<div v-if="user != null" class="info">
|
<div v-if="user" class="info">
|
||||||
<div
|
<div
|
||||||
class="banner"
|
class="banner"
|
||||||
:style="
|
:style="
|
||||||
user.bannerUrl
|
user.banner_url
|
||||||
? `background-image: url(${user.bannerUrl})`
|
? `background-image: url(${user.banner_url})`
|
||||||
: ''
|
: ''
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-if="$i && $i.id != user.id && user.isFollowed"
|
v-if="$i && $i.id != user.id && user.follows_you"
|
||||||
class="followed"
|
class="followed"
|
||||||
>{{ i18n.ts.followsYou }}</span
|
>{{ i18n.ts.followsYou }}</span
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-if="
|
v-if="
|
||||||
$i &&
|
$i && $i.id != user.id && user.they_request_follow
|
||||||
$i.id != user.id &&
|
|
||||||
user.hasPendingFollowRequestToYou
|
|
||||||
"
|
"
|
||||||
class="followed"
|
class="followed"
|
||||||
>
|
>
|
||||||
|
@ -100,15 +98,15 @@
|
||||||
<div class="status">
|
<div class="status">
|
||||||
<div>
|
<div>
|
||||||
<p>{{ i18n.ts.notes }}</p>
|
<p>{{ i18n.ts.notes }}</p>
|
||||||
<span>{{ user.notesCount }}</span>
|
<span>{{ user.note_count }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p>{{ i18n.ts.following }}</p>
|
<p>{{ i18n.ts.following }}</p>
|
||||||
<span>{{ user.followingCount }}</span>
|
<span>{{ user.following_count }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p>{{ i18n.ts.followers }}</p>
|
<p>{{ i18n.ts.followers }}</p>
|
||||||
<span>{{ user.followersCount }}</span>
|
<span>{{ user.follower_count }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="follow-button-container">
|
<div class="follow-button-container">
|
||||||
|
@ -127,19 +125,20 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted } from "vue";
|
import { computed, onMounted } from "vue";
|
||||||
import * as Acct from "calckey-js/built/acct";
|
|
||||||
import type * as misskey from "calckey-js";
|
|
||||||
import MkFollowButton from "@/components/MkFollowButton.vue";
|
import MkFollowButton from "@/components/MkFollowButton.vue";
|
||||||
import { userPage } from "@/filters/user";
|
import { userPage } from "@/filters/user";
|
||||||
import XShowMoreButton from "@/components/MkShowMoreButton.vue";
|
import XShowMoreButton from "@/components/MkShowMoreButton.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { $i } from "@/account";
|
import { $i } from "@/account";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import { packed, endpoints } from "magnetar-common";
|
||||||
|
import { host as localHost } from "@/config";
|
||||||
|
import { toUnicode } from "punycode";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
showing: boolean;
|
showing: boolean;
|
||||||
q: string;
|
userTag: string | { username: string; host?: string };
|
||||||
source: HTMLElement;
|
source: HTMLElement;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
@ -150,40 +149,51 @@ const emit = defineEmits<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const zIndex = os.claimZIndex("middle");
|
const zIndex = os.claimZIndex("middle");
|
||||||
let user = $ref<misskey.entities.UserDetailed | null>(null);
|
let user = $ref<packed.PackUserMaybeAll | null>(null);
|
||||||
let top = $ref(0);
|
let top = $ref(0);
|
||||||
let left = $ref(0);
|
let left = $ref(0);
|
||||||
|
|
||||||
let isLong = $ref(false);
|
let isLong = computed(() => {
|
||||||
|
if (!user?.description) return;
|
||||||
|
|
||||||
|
return (
|
||||||
|
user.description.split("\n").length > 9 || user.description.length > 400
|
||||||
|
);
|
||||||
|
});
|
||||||
let collapsed = $ref(!isLong);
|
let collapsed = $ref(!isLong);
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (typeof props.q === "object") {
|
const options = { detail: true, profile: true, relation: true };
|
||||||
user = props.q;
|
|
||||||
isLong =
|
|
||||||
user.description.split("\n").length > 9 ||
|
|
||||||
user.description.length > 400;
|
|
||||||
} else {
|
|
||||||
const query = props.q.startsWith("@")
|
|
||||||
? Acct.parse(props.q.substr(1))
|
|
||||||
: { userId: props.q };
|
|
||||||
|
|
||||||
os.api("users/show", query).then((res) => {
|
debugger;
|
||||||
if (!props.showing) return;
|
if (typeof props.userTag === "object") {
|
||||||
user = res;
|
const canonical =
|
||||||
isLong =
|
!props.userTag.host || props.userTag.host === localHost
|
||||||
user.description.split("\n").length > 9 ||
|
? `${props.userTag.username}`
|
||||||
user.description.length > 400;
|
: `${props.userTag.username}@${toUnicode(props.userTag.host)}`;
|
||||||
|
|
||||||
|
os.magApi(endpoints.GetUserByAcct, options, {
|
||||||
|
user_acct: canonical,
|
||||||
|
}).then((u) => {
|
||||||
|
user = u;
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
const acctQuery = props.userTag.startsWith("@");
|
||||||
|
|
||||||
|
const apiCall = acctQuery
|
||||||
|
? os.magApi(endpoints.GetUserByAcct, options, {
|
||||||
|
user_acct: props.userTag,
|
||||||
|
})
|
||||||
|
: os.magApi(endpoints.GetUserById, options, {
|
||||||
|
user_id: props.userTag,
|
||||||
|
});
|
||||||
|
|
||||||
|
apiCall.then((u) => (user = u));
|
||||||
}
|
}
|
||||||
|
|
||||||
const rect = props.source.getBoundingClientRect();
|
const rect = props.source.getBoundingClientRect();
|
||||||
const x =
|
left = rect.left + props.source.offsetWidth / 2 - 300 / 2 + window.scrollX;
|
||||||
rect.left + props.source.offsetWidth / 2 - 300 / 2 + window.pageXOffset;
|
top = rect.top + props.source.offsetHeight + window.scrollY;
|
||||||
const y = rect.top + props.source.offsetHeight + window.pageYOffset;
|
|
||||||
|
|
||||||
top = y;
|
|
||||||
left = x;
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -5,12 +5,12 @@
|
||||||
style="margin-right: 8px"
|
style="margin-right: 8px"
|
||||||
/>
|
/>
|
||||||
{{ i18n.ts.accountMoved }}
|
{{ i18n.ts.accountMoved }}
|
||||||
<MkMention class="link" :username="acct" :host="host" />
|
<MagMention class="link" :username="acct" :host="host" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import MkMention from "./MkMention.vue";
|
import MagMention from "./MagMention.vue";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
|
|
@ -176,9 +176,7 @@
|
||||||
<I18n :src="softMuteReasonI18nSrc(muted.what)" tag="small">
|
<I18n :src="softMuteReasonI18nSrc(muted.what)" tag="small">
|
||||||
<template #name>
|
<template #name>
|
||||||
<MkA
|
<MkA
|
||||||
v-user-preview="
|
v-user-preview="note.user.id"
|
||||||
magTransMap(note, 'user', 'userId', (u) => u.id)
|
|
||||||
"
|
|
||||||
class="name"
|
class="name"
|
||||||
:to="userPage(note.user)"
|
:to="userPage(note.user)"
|
||||||
>
|
>
|
||||||
|
|
|
@ -3,7 +3,7 @@ import * as mfm from "mfm-js";
|
||||||
import type { VNode } from "vue";
|
import type { VNode } from "vue";
|
||||||
import MkUrl from "@/components/global/MkUrl.vue";
|
import MkUrl from "@/components/global/MkUrl.vue";
|
||||||
import MkLink from "@/components/MkLink.vue";
|
import MkLink from "@/components/MkLink.vue";
|
||||||
import MkMention from "@/components/MkMention.vue";
|
import MagMention from "@/components/MagMention.vue";
|
||||||
import { concat } from "@/scripts/array";
|
import { concat } from "@/scripts/array";
|
||||||
import MkFormula from "@/components/MkFormula.vue";
|
import MkFormula from "@/components/MkFormula.vue";
|
||||||
import MkCode from "@/components/MkCode.vue";
|
import MkCode from "@/components/MkCode.vue";
|
||||||
|
@ -476,15 +476,15 @@ export default defineComponent({
|
||||||
|
|
||||||
case "mention": {
|
case "mention": {
|
||||||
return [
|
return [
|
||||||
h(MkMention, {
|
h(MagMention, {
|
||||||
key: Math.random(),
|
key: Math.random(),
|
||||||
|
username: token.props.username,
|
||||||
host:
|
host:
|
||||||
(token.props.host == null &&
|
(token.props.host == null &&
|
||||||
this.author &&
|
this.author &&
|
||||||
this.author.host != null
|
this.author.host != null
|
||||||
? this.author.host
|
? this.author.host
|
||||||
: token.props.host) || host,
|
: token.props.host) || host,
|
||||||
username: token.props.username,
|
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, PropType } from "vue";
|
import { computed, defineComponent, PropType } from "vue";
|
||||||
import MkButton from "../MkButton.vue";
|
import MkButton from "../MkButton.vue";
|
||||||
import * as os from "@/os";
|
|
||||||
import { CounterVarBlock } from "@/scripts/hpml/block";
|
import { CounterVarBlock } from "@/scripts/hpml/block";
|
||||||
import { Hpml } from "@/scripts/hpml/evaluator";
|
import { Hpml } from "@/scripts/hpml/evaluator";
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { popup } from "@/os";
|
||||||
|
|
||||||
export class UserPreview {
|
export class UserPreview {
|
||||||
private el;
|
private el;
|
||||||
private user;
|
private user: string | { username: string; host?: string };
|
||||||
private showTimer;
|
private showTimer;
|
||||||
private hideTimer;
|
private hideTimer;
|
||||||
private checkTimer;
|
private checkTimer;
|
||||||
|
@ -26,11 +26,11 @@ export class UserPreview {
|
||||||
|
|
||||||
popup(
|
popup(
|
||||||
defineAsyncComponent(
|
defineAsyncComponent(
|
||||||
() => import("@/components/MkUserPreview.vue")
|
() => import("@/components/MagUserPreview.vue")
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
showing,
|
showing,
|
||||||
q: this.user,
|
userTag: this.user,
|
||||||
source: this.el,
|
source: this.el,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -127,7 +127,7 @@ export function magIsRenote(
|
||||||
note: packed.PackNoteMaybeFull | Misskey.entities.Note
|
note: packed.PackNoteMaybeFull | Misskey.entities.Note
|
||||||
): boolean {
|
): boolean {
|
||||||
return (
|
return (
|
||||||
magTransProperty(note, "renoted_note", "renote") != null &&
|
(magTransProperty(note, "renoted_note", "renote") || null) !== null &&
|
||||||
(note.text || null) === null &&
|
(note.text || null) === null &&
|
||||||
magTransProperty(note, "file_ids", "fileIds").length === 0 &&
|
magTransProperty(note, "file_ids", "fileIds").length === 0 &&
|
||||||
(note.poll || null) === null
|
(note.poll || null) === null
|
||||||
|
|
|
@ -3,8 +3,8 @@ import type { PackUserMaybeAll } from "../packed/PackUserMaybeAll";
|
||||||
import type { UserByIdReq } from "../UserByIdReq";
|
import type { UserByIdReq } from "../UserByIdReq";
|
||||||
|
|
||||||
export const GetUserByAcct = {
|
export const GetUserByAcct = {
|
||||||
endpoint: "/users/by-acct/:user_id",
|
endpoint: "/users/by-acct/:user_acct",
|
||||||
pathParams: ["user_id"] as ["user_id"],
|
pathParams: ["user_acct"] as ["user_acct"],
|
||||||
method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
|
method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
|
||||||
request: undefined as unknown as UserByIdReq,
|
request: undefined as unknown as UserByIdReq,
|
||||||
response: undefined as unknown as PackUserMaybeAll
|
response: undefined as unknown as PackUserMaybeAll
|
||||||
|
|
|
@ -76,7 +76,7 @@ pub struct GetManyUsersById;
|
||||||
|
|
||||||
#[derive(Endpoint)]
|
#[derive(Endpoint)]
|
||||||
#[endpoint(
|
#[endpoint(
|
||||||
endpoint = "/users/by-acct/:user_id",
|
endpoint = "/users/by-acct/:user_acct",
|
||||||
method = Method::GET,
|
method = Method::GET,
|
||||||
request = "UserByIdReq",
|
request = "UserByIdReq",
|
||||||
response = "PackUserMaybeAll"
|
response = "PackUserMaybeAll"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashSet;
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::types::Id;
|
use crate::types::Id;
|
||||||
|
@ -21,3 +22,19 @@ pack!(PackEmojiBase, Required<Id> as id & Required<EmojiBase> as emoji);
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[repr(transparent)]
|
#[repr(transparent)]
|
||||||
pub struct EmojiContext(pub Vec<PackEmojiBase>);
|
pub struct EmojiContext(pub Vec<PackEmojiBase>);
|
||||||
|
|
||||||
|
impl EmojiContext {
|
||||||
|
pub fn extend_from(&mut self, more: &[PackEmojiBase]) {
|
||||||
|
let existing = self
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.map(|e| e.emoji.0.shortcode.clone())
|
||||||
|
.collect::<HashSet<_>>();
|
||||||
|
|
||||||
|
self.0.extend(
|
||||||
|
more.iter()
|
||||||
|
.filter(|&e| existing.contains(&e.emoji.0.shortcode))
|
||||||
|
.cloned(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -28,10 +28,10 @@ pub enum SpeechTransform {
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct ProfileField {
|
pub struct ProfileField {
|
||||||
name: String,
|
pub name: String,
|
||||||
value: String,
|
pub value: String,
|
||||||
value_mm: Option<MmXml>,
|
pub value_mm: Option<MmXml>,
|
||||||
verified_at: Option<DateTime<Utc>>,
|
pub verified_at: Option<DateTime<Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
|
|
@ -11,8 +11,6 @@ use crate::web::auth::AuthState;
|
||||||
use axum::middleware::from_fn_with_state;
|
use axum::middleware::from_fn_with_state;
|
||||||
use axum::routing::get;
|
use axum::routing::get;
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
use serde::de::{DeserializeOwned, Error};
|
|
||||||
use serde::{Deserialize, Deserializer};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub fn create_api_router(service: Arc<MagnetarService>) -> Router {
|
pub fn create_api_router(service: Arc<MagnetarService>) -> Router {
|
||||||
|
|
|
@ -25,7 +25,7 @@ pub async fn handle_note(
|
||||||
attachments: attachments.unwrap_or_default(),
|
attachments: attachments.unwrap_or_default(),
|
||||||
with_context: context.unwrap_or_default(),
|
with_context: context.unwrap_or_default(),
|
||||||
}
|
}
|
||||||
.fetch_single(&ctx, self_user.as_deref(), &id)
|
.fetch_single(&ctx, &id)
|
||||||
.await?
|
.await?
|
||||||
.ok_or(ObjectNotFound(id))?;
|
.ok_or(ObjectNotFound(id))?;
|
||||||
|
|
||||||
|
|
|
@ -9,46 +9,32 @@ use axum::Json;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use futures_util::TryStreamExt;
|
use futures_util::TryStreamExt;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use magnetar_common::util::lenient_parse_tag;
|
use magnetar_common::util::lenient_parse_tag_decode;
|
||||||
use magnetar_sdk::endpoints::user::{
|
use magnetar_sdk::endpoints::user::{
|
||||||
GetManyUsersById, GetUserByAcct, GetUserById, GetUserSelf, ManyUsersByIdReq, UserByIdReq,
|
GetManyUsersById, GetUserByAcct, GetUserById, GetUserSelf, ManyUsersByIdReq,
|
||||||
UserSelfReq,
|
|
||||||
};
|
};
|
||||||
use magnetar_sdk::endpoints::{Req, Res};
|
use magnetar_sdk::endpoints::{Req, Res};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
pub async fn handle_user_info_self(
|
pub async fn handle_user_info_self(
|
||||||
Query(UserSelfReq {
|
Query(req): Query<Req<GetUserSelf>>,
|
||||||
detail: _,
|
|
||||||
pins: _,
|
|
||||||
profile: _,
|
|
||||||
secrets: _,
|
|
||||||
}): Query<Req<GetUserSelf>>,
|
|
||||||
State(service): State<Arc<MagnetarService>>,
|
State(service): State<Arc<MagnetarService>>,
|
||||||
AuthenticatedUser(user): AuthenticatedUser,
|
AuthenticatedUser(user): AuthenticatedUser,
|
||||||
) -> Result<Json<Res<GetUserSelf>>, ApiError> {
|
) -> Result<Json<Res<GetUserSelf>>, ApiError> {
|
||||||
// TODO: Extended properties!
|
|
||||||
|
|
||||||
let ctx = PackingContext::new(service, Some(user.clone())).await?;
|
let ctx = PackingContext::new(service, Some(user.clone())).await?;
|
||||||
let user = UserModel.base_from_existing(&ctx, user.as_ref()).await?;
|
let user = UserModel
|
||||||
Ok(Json(user.into()))
|
.self_full_from_base(&ctx, user.as_ref(), &req, None, None)
|
||||||
|
.await?;
|
||||||
|
Ok(Json(user))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_user_info(
|
pub async fn handle_user_info(
|
||||||
Path(id): Path<String>,
|
Path(id): Path<String>,
|
||||||
Query(UserByIdReq {
|
Query(req): Query<Req<GetUserById>>,
|
||||||
detail: _,
|
|
||||||
pins: _,
|
|
||||||
profile: _,
|
|
||||||
relation: _,
|
|
||||||
auth: _,
|
|
||||||
}): Query<Req<GetUserById>>,
|
|
||||||
State(service): State<Arc<MagnetarService>>,
|
State(service): State<Arc<MagnetarService>>,
|
||||||
MaybeUser(self_user): MaybeUser,
|
MaybeUser(self_user): MaybeUser,
|
||||||
) -> Result<Json<Res<GetUserById>>, ApiError> {
|
) -> Result<Json<Res<GetUserById>>, ApiError> {
|
||||||
// TODO: Extended properties!
|
|
||||||
|
|
||||||
let ctx = PackingContext::new(service.clone(), self_user).await?;
|
let ctx = PackingContext::new(service.clone(), self_user).await?;
|
||||||
let user_model = service
|
let user_model = service
|
||||||
.db
|
.db
|
||||||
|
@ -56,25 +42,19 @@ pub async fn handle_user_info(
|
||||||
.await?
|
.await?
|
||||||
.ok_or(ObjectNotFound(id))?;
|
.ok_or(ObjectNotFound(id))?;
|
||||||
|
|
||||||
let user = UserModel.base_from_existing(&ctx, &user_model).await?;
|
let user = UserModel
|
||||||
Ok(Json(user.into()))
|
.foreign_full_from_base(&ctx, &user_model, &req, None, None)
|
||||||
|
.await?;
|
||||||
|
Ok(Json(user))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_user_info_by_acct(
|
pub async fn handle_user_info_by_acct(
|
||||||
Path(tag_str): Path<String>,
|
Path(tag_str): Path<String>,
|
||||||
Query(UserByIdReq {
|
Query(req): Query<Req<GetUserByAcct>>,
|
||||||
detail: _,
|
|
||||||
pins: _,
|
|
||||||
profile: _,
|
|
||||||
relation: _,
|
|
||||||
auth: _,
|
|
||||||
}): Query<Req<GetUserByAcct>>,
|
|
||||||
State(service): State<Arc<MagnetarService>>,
|
State(service): State<Arc<MagnetarService>>,
|
||||||
MaybeUser(self_user): MaybeUser,
|
MaybeUser(self_user): MaybeUser,
|
||||||
) -> Result<Json<Res<GetUserByAcct>>, ApiError> {
|
) -> Result<Json<Res<GetUserByAcct>>, ApiError> {
|
||||||
// TODO: Extended properties!
|
let mut tag = lenient_parse_tag_decode(&tag_str)?;
|
||||||
|
|
||||||
let mut tag = lenient_parse_tag(&tag_str)?;
|
|
||||||
if matches!(&tag.host, Some(host) if host == &service.config.networking.host) {
|
if matches!(&tag.host, Some(host) if host == &service.config.networking.host) {
|
||||||
tag.host = None;
|
tag.host = None;
|
||||||
}
|
}
|
||||||
|
@ -86,8 +66,10 @@ pub async fn handle_user_info_by_acct(
|
||||||
.await?
|
.await?
|
||||||
.ok_or(ObjectNotFound(tag_str))?;
|
.ok_or(ObjectNotFound(tag_str))?;
|
||||||
|
|
||||||
let user = UserModel.base_from_existing(&ctx, &user_model).await?;
|
let user = UserModel
|
||||||
Ok(Json(user.into()))
|
.foreign_full_from_base(&ctx, &user_model, &req, None, None)
|
||||||
|
.await?;
|
||||||
|
Ok(Json(user))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_user_by_id_many(
|
pub async fn handle_user_by_id_many(
|
||||||
|
@ -109,7 +91,7 @@ pub async fn handle_user_by_id_many(
|
||||||
|
|
||||||
let futures = users
|
let futures = users
|
||||||
.iter()
|
.iter()
|
||||||
.map(|u| user_model.base_from_existing(&ctx, u))
|
.map(|u| user_model.base_from_existing(&ctx, u, None))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let users_proc = futures::stream::iter(futures)
|
let users_proc = futures::stream::iter(futures)
|
||||||
|
|
|
@ -6,7 +6,8 @@ use magnetar_sdk::types::instance::InstanceTicker;
|
||||||
use magnetar_sdk::types::note::PackNoteMaybeFull;
|
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,
|
UserAuthOverviewExt, UserBase, UserDetailExt, UserProfileExt, UserProfilePinsEx,
|
||||||
|
UserRelationExt, UserSecretsExt,
|
||||||
};
|
};
|
||||||
use magnetar_sdk::types::MmXml;
|
use magnetar_sdk::types::MmXml;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
@ -76,8 +77,7 @@ pub struct UserProfileExtSource<'a> {
|
||||||
pub user: &'a ck::user::Model,
|
pub user: &'a ck::user::Model,
|
||||||
pub profile: &'a ck::user_profile::Model,
|
pub profile: &'a ck::user_profile::Model,
|
||||||
pub profile_fields: &'a Vec<ProfileField>,
|
pub profile_fields: &'a Vec<ProfileField>,
|
||||||
pub banner_url: Option<&'a Url>,
|
pub banner: Option<&'a PackDriveFileBase>,
|
||||||
pub banner: Option<&'a ck::drive_file::Model>,
|
|
||||||
pub description_mm: Option<&'a MmXml>,
|
pub description_mm: Option<&'a MmXml>,
|
||||||
pub relation: Option<&'a UserRelationExt>,
|
pub relation: Option<&'a UserRelationExt>,
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,6 @@ impl PackType<UserProfileExtSource<'_>> for UserProfileExt {
|
||||||
user,
|
user,
|
||||||
profile,
|
profile,
|
||||||
profile_fields,
|
profile_fields,
|
||||||
banner_url,
|
|
||||||
banner,
|
banner,
|
||||||
description_mm,
|
description_mm,
|
||||||
relation,
|
relation,
|
||||||
|
@ -97,7 +96,9 @@ impl PackType<UserProfileExtSource<'_>> for UserProfileExt {
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let follow_visibility = match profile.ff_visibility {
|
let follow_visibility = match profile.ff_visibility {
|
||||||
UserProfileFfvisibilityEnum::Public => true,
|
UserProfileFfvisibilityEnum::Public => true,
|
||||||
UserProfileFfvisibilityEnum::Followers => relation.is_some_and(|r| r.follows_you),
|
UserProfileFfvisibilityEnum::Followers => {
|
||||||
|
relation.is_some_and(|r| r.follows_you) || context.is_self(user)
|
||||||
|
}
|
||||||
UserProfileFfvisibilityEnum::Private => false,
|
UserProfileFfvisibilityEnum::Private => false,
|
||||||
} || context.is_self(user);
|
} || context.is_self(user);
|
||||||
|
|
||||||
|
@ -119,8 +120,8 @@ impl PackType<UserProfileExtSource<'_>> for UserProfileExt {
|
||||||
url: profile.url.clone(),
|
url: profile.url.clone(),
|
||||||
moved_to_uri: user.moved_to_uri.clone(),
|
moved_to_uri: user.moved_to_uri.clone(),
|
||||||
also_known_as: user.also_known_as.clone(),
|
also_known_as: user.also_known_as.clone(),
|
||||||
banner_url: banner_url.map(Url::to_string),
|
banner_url: banner.and_then(|b| b.file.0.url.to_owned()),
|
||||||
banner_blurhash: banner.and_then(|b| b.blurhash.clone()),
|
banner_blurhash: banner.and_then(|b| b.file.0.blurhash.clone()),
|
||||||
has_public_reactions: profile.public_reactions,
|
has_public_reactions: profile.public_reactions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -238,3 +239,13 @@ impl PackType<UserSecretsExtSource<'_>> for UserSecretsExt {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PackType<&ck::user_profile::Model> for UserAuthOverviewExt {
|
||||||
|
fn extract(_context: &PackingContext, data: &ck::user_profile::Model) -> Self {
|
||||||
|
UserAuthOverviewExt {
|
||||||
|
has_passwordless_login: data.use_password_less_login,
|
||||||
|
has_security_keys: data.security_keys_available,
|
||||||
|
has_two_factor_enabled: data.two_factor_enabled,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ impl Default for ProcessingLimits {
|
||||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
|
||||||
enum UserRelationship {
|
enum UserRelationship {
|
||||||
Follow,
|
Follow,
|
||||||
|
FollowRequest,
|
||||||
Mute,
|
Mute,
|
||||||
Block,
|
Block,
|
||||||
RenoteMute,
|
RenoteMute,
|
||||||
|
@ -120,6 +121,12 @@ impl PackingContext {
|
||||||
.get_follower_status(&link.from, &link.to)
|
.get_follower_status(&link.from, &link.to)
|
||||||
.await?
|
.await?
|
||||||
.is_some(),
|
.is_some(),
|
||||||
|
UserRelationship::FollowRequest => self
|
||||||
|
.service
|
||||||
|
.db
|
||||||
|
.get_follow_request_status(&link.from, &link.to)
|
||||||
|
.await?
|
||||||
|
.is_some(),
|
||||||
UserRelationship::Mute => self
|
UserRelationship::Mute => self
|
||||||
.service
|
.service
|
||||||
.db
|
.db
|
||||||
|
|
|
@ -19,6 +19,8 @@ pub enum PackError {
|
||||||
#[error("Calckey database wrapper error: {0}")]
|
#[error("Calckey database wrapper error: {0}")]
|
||||||
CalckeyDbError(#[from] CalckeyDbError),
|
CalckeyDbError(#[from] CalckeyDbError),
|
||||||
#[error("Emoji cache error: {0}")]
|
#[error("Emoji cache error: {0}")]
|
||||||
|
DataError(String),
|
||||||
|
#[error("Emoji cache error: {0}")]
|
||||||
EmojiCacheError(#[from] EmojiCacheError),
|
EmojiCacheError(#[from] EmojiCacheError),
|
||||||
#[error("Instance cache error: {0}")]
|
#[error("Instance cache error: {0}")]
|
||||||
InstanceMetaCacheError(#[from] InstanceMetaCacheError),
|
InstanceMetaCacheError(#[from] InstanceMetaCacheError),
|
||||||
|
|
|
@ -7,7 +7,7 @@ use crate::model::{PackType, PackingContext, UserRelationship};
|
||||||
use compact_str::CompactString;
|
use compact_str::CompactString;
|
||||||
use either::Either;
|
use either::Either;
|
||||||
use futures_util::future::try_join_all;
|
use futures_util::future::try_join_all;
|
||||||
use futures_util::TryFutureExt;
|
use futures_util::{StreamExt, TryFutureExt, TryStreamExt};
|
||||||
use magnetar_calckey_model::ck::sea_orm_active_enums::NoteVisibilityEnum;
|
use magnetar_calckey_model::ck::sea_orm_active_enums::NoteVisibilityEnum;
|
||||||
use magnetar_calckey_model::emoji::EmojiTag;
|
use magnetar_calckey_model::emoji::EmojiTag;
|
||||||
use magnetar_calckey_model::note_model::{
|
use magnetar_calckey_model::note_model::{
|
||||||
|
@ -197,7 +197,7 @@ impl NoteModel {
|
||||||
note_data: &NoteData,
|
note_data: &NoteData,
|
||||||
) -> PackResult<PackNoteBase> {
|
) -> PackResult<PackNoteBase> {
|
||||||
let Required(ref user) = UserModel
|
let Required(ref user) = UserModel
|
||||||
.base_from_existing(ctx, ¬e_data.user)
|
.base_from_existing(ctx, ¬e_data.user, None)
|
||||||
.await?
|
.await?
|
||||||
.user;
|
.user;
|
||||||
|
|
||||||
|
@ -378,7 +378,6 @@ impl NoteModel {
|
||||||
async fn extract_poll(
|
async fn extract_poll(
|
||||||
&self,
|
&self,
|
||||||
ctx: &PackingContext,
|
ctx: &PackingContext,
|
||||||
as_user: Option<&ck::user::Model>,
|
|
||||||
note: &ck::note::Model,
|
note: &ck::note::Model,
|
||||||
) -> PackResult<Option<PackPollBase>> {
|
) -> PackResult<Option<PackPollBase>> {
|
||||||
if !note.has_poll {
|
if !note.has_poll {
|
||||||
|
@ -391,7 +390,7 @@ impl NoteModel {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
|
|
||||||
let votes = match as_user {
|
let votes = match ctx.self_user.as_deref() {
|
||||||
Some(u) => Some(poll_resolver.get_poll_votes_by(¬e.id, &u.id).await?),
|
Some(u) => Some(poll_resolver.get_poll_votes_by(¬e.id, &u.id).await?),
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
@ -406,13 +405,12 @@ impl NoteModel {
|
||||||
&self,
|
&self,
|
||||||
ctx: &PackingContext,
|
ctx: &PackingContext,
|
||||||
drive_model: &DriveModel,
|
drive_model: &DriveModel,
|
||||||
as_user: Option<&ck::user::Model>,
|
|
||||||
note_data: &NoteData,
|
note_data: &NoteData,
|
||||||
) -> PackResult<PackNoteMaybeAttachments> {
|
) -> PackResult<PackNoteMaybeAttachments> {
|
||||||
let (PackNoteBase { id, note }, attachments_pack, poll_pack) = try_join!(
|
let (PackNoteBase { id, note }, attachments_pack, poll_pack) = try_join!(
|
||||||
self.extract_base(ctx, note_data),
|
self.extract_base(ctx, note_data),
|
||||||
self.extract_attachments(ctx, drive_model, ¬e_data.note),
|
self.extract_attachments(ctx, drive_model, ¬e_data.note),
|
||||||
self.extract_poll(ctx, as_user, ¬e_data.note)
|
self.extract_poll(ctx, ¬e_data.note)
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
Ok(PackNoteMaybeAttachments::pack_from((
|
Ok(PackNoteMaybeAttachments::pack_from((
|
||||||
|
@ -431,40 +429,17 @@ impl NoteModel {
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn fetch_single(
|
async fn pack_full_single(
|
||||||
&self,
|
&self,
|
||||||
ctx: &PackingContext,
|
ctx: &PackingContext,
|
||||||
as_user: Option<&ck::user::Model>,
|
note: NoteData,
|
||||||
id: &str,
|
) -> PackResult<PackNoteMaybeFull> {
|
||||||
) -> 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: 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 {
|
|
||||||
return Ok(None);
|
|
||||||
};
|
|
||||||
|
|
||||||
let drive_model = DriveModel;
|
let drive_model = DriveModel;
|
||||||
|
|
||||||
let reply_target = async {
|
let reply_target = async {
|
||||||
match note.reply.as_ref() {
|
match note.reply.as_ref() {
|
||||||
Some(r) if self.with_context => self
|
Some(r) if self.with_context => self
|
||||||
.pack_single_attachments(ctx, &drive_model, as_user, r)
|
.pack_single_attachments(ctx, &drive_model, r)
|
||||||
.await
|
.await
|
||||||
.map(Some),
|
.map(Some),
|
||||||
_ => Ok(None),
|
_ => Ok(None),
|
||||||
|
@ -474,7 +449,7 @@ impl NoteModel {
|
||||||
let renote_target = async {
|
let renote_target = async {
|
||||||
match note.renote.as_ref() {
|
match note.renote.as_ref() {
|
||||||
Some(r) if self.with_context => self
|
Some(r) if self.with_context => self
|
||||||
.pack_single_attachments(ctx, &drive_model, as_user, r)
|
.pack_single_attachments(ctx, &drive_model, r)
|
||||||
.await
|
.await
|
||||||
.map(Some),
|
.map(Some),
|
||||||
_ => Ok(None),
|
_ => Ok(None),
|
||||||
|
@ -491,7 +466,7 @@ impl NoteModel {
|
||||||
reply_target_pack,
|
reply_target_pack,
|
||||||
renote_target_pack,
|
renote_target_pack,
|
||||||
) = try_join!(
|
) = try_join!(
|
||||||
self.pack_single_attachments(ctx, &drive_model, as_user, ¬e),
|
self.pack_single_attachments(ctx, &drive_model, ¬e),
|
||||||
reply_target,
|
reply_target,
|
||||||
renote_target
|
renote_target
|
||||||
)?;
|
)?;
|
||||||
|
@ -506,12 +481,89 @@ impl NoteModel {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(Some(PackNoteMaybeFull::pack_from((
|
Ok(PackNoteMaybeFull::pack_from((
|
||||||
id,
|
id,
|
||||||
note,
|
note,
|
||||||
user_context,
|
user_context,
|
||||||
attachment,
|
attachment,
|
||||||
Optional(detail),
|
Optional(detail),
|
||||||
))))
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn fetch_single(
|
||||||
|
&self,
|
||||||
|
ctx: &PackingContext,
|
||||||
|
id: &str,
|
||||||
|
) -> 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(
|
||||||
|
ctx.self_user.as_deref().map(ck::user::Model::get_id),
|
||||||
|
)),
|
||||||
|
time_range: None,
|
||||||
|
with_user: self.with_context,
|
||||||
|
with_reply_target: self.with_context,
|
||||||
|
with_renote_target: self.with_context,
|
||||||
|
with_interactions_from: self
|
||||||
|
.with_context
|
||||||
|
.then(|| {
|
||||||
|
ctx.self_user
|
||||||
|
.as_deref()
|
||||||
|
.map(ck::user::Model::get_id)
|
||||||
|
.map(str::to_string)
|
||||||
|
})
|
||||||
|
.flatten(),
|
||||||
|
only_pins_from: None,
|
||||||
|
})
|
||||||
|
.await?
|
||||||
|
else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Some(self.pack_full_single(ctx, note).await?))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn fetch_pins(
|
||||||
|
&self,
|
||||||
|
ctx: &PackingContext,
|
||||||
|
pin_user: &ck::user::Model,
|
||||||
|
) -> PackResult<Vec<PackNoteMaybeFull>> {
|
||||||
|
let note_resolver = ctx.service.db.get_note_resolver();
|
||||||
|
let notes = note_resolver
|
||||||
|
.get_many(&NoteResolveOptions {
|
||||||
|
ids: None,
|
||||||
|
visibility_filter: Box::new(NoteVisibilityFilterModel.new_note_visibility_filter(
|
||||||
|
ctx.self_user.as_deref().map(ck::user::Model::get_id),
|
||||||
|
)),
|
||||||
|
time_range: None,
|
||||||
|
with_user: self.with_context,
|
||||||
|
with_reply_target: self.with_context,
|
||||||
|
with_renote_target: self.with_context,
|
||||||
|
with_interactions_from: self
|
||||||
|
.with_context
|
||||||
|
.then(|| {
|
||||||
|
ctx.self_user
|
||||||
|
.as_deref()
|
||||||
|
.map(ck::user::Model::get_id)
|
||||||
|
.map(str::to_string)
|
||||||
|
})
|
||||||
|
.flatten(),
|
||||||
|
only_pins_from: Some(pin_user.id.clone()),
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let fut_iter = notes
|
||||||
|
.into_iter()
|
||||||
|
.map(|note| self.pack_full_single(ctx, note));
|
||||||
|
|
||||||
|
let processed = futures::stream::iter(fut_iter)
|
||||||
|
.buffered(10)
|
||||||
|
.err_into::<PackError>()
|
||||||
|
.try_collect::<Vec<_>>()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(processed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,35 @@
|
||||||
use crate::model::data::user::UserBaseSource;
|
use crate::model::data::user::{UserBaseSource, UserProfileExtSource};
|
||||||
use crate::model::processing::drive::DriveModel;
|
use crate::model::processing::drive::DriveModel;
|
||||||
use crate::model::processing::emoji::EmojiModel;
|
use crate::model::processing::emoji::EmojiModel;
|
||||||
use crate::model::processing::{get_mm_token_emoji, PackResult};
|
use crate::model::processing::note::NoteModel;
|
||||||
use crate::model::{PackType, PackingContext};
|
use crate::model::processing::{get_mm_token_emoji, PackError, PackResult};
|
||||||
|
use crate::model::{PackType, PackingContext, UserRelationship};
|
||||||
|
use either::Either;
|
||||||
|
use futures_util::future::OptionFuture;
|
||||||
use magnetar_calckey_model::ck;
|
use magnetar_calckey_model::ck;
|
||||||
|
use magnetar_sdk::endpoints::user::{UserByIdReq, UserSelfReq};
|
||||||
use magnetar_sdk::mmm::Token;
|
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::instance::InstanceTicker;
|
use magnetar_sdk::types::instance::InstanceTicker;
|
||||||
use magnetar_sdk::types::user::{PackUserBase, UserBase};
|
use magnetar_sdk::types::user::{
|
||||||
|
PackSecurityKeyBase, PackUserBase, PackUserMaybeAll, PackUserSelfMaybeAll, ProfileField,
|
||||||
|
SecurityKeyBase, UserAuthOverviewExt, UserBase, UserDetailExt, UserProfileExt,
|
||||||
|
UserProfilePinsEx, UserRelationExt, UserSecretsExt,
|
||||||
|
};
|
||||||
use magnetar_sdk::types::{Id, MmXml};
|
use magnetar_sdk::types::{Id, MmXml};
|
||||||
use magnetar_sdk::{mmm, Packed, Required};
|
use magnetar_sdk::{mmm, Optional, Packed, Required};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::{join, try_join};
|
||||||
use tracing::warn;
|
use tracing::warn;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct ProfileFieldRaw<'a> {
|
||||||
|
name: &'a str,
|
||||||
|
value: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct UserModel;
|
pub struct UserModel;
|
||||||
|
|
||||||
impl UserModel {
|
impl UserModel {
|
||||||
|
@ -21,6 +37,12 @@ impl UserModel {
|
||||||
mmm::Context::default().parse_ui(user.name.as_deref().unwrap_or(&user.username))
|
mmm::Context::default().parse_ui(user.name.as_deref().unwrap_or(&user.username))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn tokenize_description(&self, user: &ck::user_profile::Model) -> Option<Token> {
|
||||||
|
user.description
|
||||||
|
.as_deref()
|
||||||
|
.map(|d| mmm::Context::default().parse_inline(d))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_effective_avatar_url(
|
pub fn get_effective_avatar_url(
|
||||||
&self,
|
&self,
|
||||||
ctx: &PackingContext,
|
ctx: &PackingContext,
|
||||||
|
@ -56,15 +78,17 @@ impl UserModel {
|
||||||
&self,
|
&self,
|
||||||
ctx: &PackingContext,
|
ctx: &PackingContext,
|
||||||
user: &ck::user::Model,
|
user: &ck::user::Model,
|
||||||
|
hint_avatar_file: Option<&ck::drive_file::Model>,
|
||||||
) -> PackResult<PackUserBase> {
|
) -> PackResult<PackUserBase> {
|
||||||
let drive_file_pack = DriveModel;
|
let drive_file_pack = DriveModel;
|
||||||
let avatar = match &user.avatar_id {
|
let avatar = match (hint_avatar_file, &user.avatar_id) {
|
||||||
Some(av_id) => drive_file_pack.get_cached_base(ctx, av_id).await?,
|
(Some(avatar_file), _) => Some(drive_file_pack.pack_existing(ctx, avatar_file)),
|
||||||
None => None,
|
(None, Some(av_id)) => drive_file_pack.get_cached_base(ctx, av_id).await?,
|
||||||
|
_ => None,
|
||||||
};
|
};
|
||||||
let avatar_url = &self.get_effective_avatar_url(ctx, user, avatar.as_ref())?;
|
let avatar_url = &self.get_effective_avatar_url(ctx, user, avatar.as_ref())?;
|
||||||
|
|
||||||
let username_mm = self.tokenize_username(&user);
|
let username_mm = self.tokenize_username(user);
|
||||||
|
|
||||||
let emoji_model = EmojiModel;
|
let emoji_model = EmojiModel;
|
||||||
let shortcodes = emoji_model.deduplicate_emoji(ctx, get_mm_token_emoji(&username_mm));
|
let shortcodes = emoji_model.deduplicate_emoji(ctx, get_mm_token_emoji(&username_mm));
|
||||||
|
@ -100,4 +124,305 @@ impl UserModel {
|
||||||
Required(base),
|
Required(base),
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_profile(
|
||||||
|
&self,
|
||||||
|
ctx: &PackingContext,
|
||||||
|
user: &ck::user::Model,
|
||||||
|
) -> PackResult<ck::user_profile::Model> {
|
||||||
|
ctx.service
|
||||||
|
.db
|
||||||
|
.get_user_profile_by_id(&user.id)
|
||||||
|
.await?
|
||||||
|
.ok_or_else(|| PackError::DataError("Missing user profile".to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn profile_from_base(
|
||||||
|
&self,
|
||||||
|
ctx: &PackingContext,
|
||||||
|
user: &ck::user::Model,
|
||||||
|
profile: &ck::user_profile::Model,
|
||||||
|
relation: Option<&UserRelationExt>,
|
||||||
|
emoji_out: &mut EmojiContext,
|
||||||
|
hint_banner_file: Option<&ck::drive_file::Model>,
|
||||||
|
) -> PackResult<UserProfileExt> {
|
||||||
|
let drive_file_pack = DriveModel;
|
||||||
|
let banner = match (hint_banner_file, &user.banner_id) {
|
||||||
|
(Some(banner_file), _) => Some(drive_file_pack.pack_existing(ctx, banner_file)),
|
||||||
|
(None, Some(av_id)) => drive_file_pack.get_cached_base(ctx, av_id).await?,
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let description_mm = self.tokenize_description(&profile);
|
||||||
|
|
||||||
|
let fields = Vec::<ProfileFieldRaw>::deserialize(&profile.fields)?;
|
||||||
|
let parser = mmm::Context::new(2);
|
||||||
|
let profile_fields = fields
|
||||||
|
.into_iter()
|
||||||
|
.map(|f| {
|
||||||
|
let tok = parser.parse_profile_fields(&f.value);
|
||||||
|
|
||||||
|
ProfileField {
|
||||||
|
name: f.name.to_string(),
|
||||||
|
value: f.value.to_string(),
|
||||||
|
value_mm: mmm::to_xml_string(&tok).map(MmXml).ok(),
|
||||||
|
verified_at: None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
if let Some(desc_mm) = &description_mm {
|
||||||
|
let emoji_model = EmojiModel;
|
||||||
|
let shortcodes = emoji_model.deduplicate_emoji(ctx, get_mm_token_emoji(&desc_mm));
|
||||||
|
let emojis = emoji_model
|
||||||
|
.fetch_many_emojis(ctx, &shortcodes, user.host.as_deref())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
emoji_out.extend_from(&emojis);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(UserProfileExt::extract(
|
||||||
|
ctx,
|
||||||
|
UserProfileExtSource {
|
||||||
|
user,
|
||||||
|
profile: &profile,
|
||||||
|
profile_fields: &profile_fields,
|
||||||
|
banner: banner.as_ref(),
|
||||||
|
description_mm: description_mm
|
||||||
|
.and_then(|desc_mm| mmm::to_xml_string(&desc_mm).map(MmXml).ok())
|
||||||
|
.as_ref(),
|
||||||
|
relation,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn secrets_from_base(
|
||||||
|
&self,
|
||||||
|
ctx: &PackingContext,
|
||||||
|
user: &ck::user::Model,
|
||||||
|
profile: &ck::user_profile::Model,
|
||||||
|
) -> PackResult<UserSecretsExt> {
|
||||||
|
let secrets = ctx
|
||||||
|
.service
|
||||||
|
.db
|
||||||
|
.get_user_security_keys_by_id(&user.id)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|k| {
|
||||||
|
PackSecurityKeyBase::pack_from((
|
||||||
|
Required(Id::from(&k.id)),
|
||||||
|
Required(SecurityKeyBase {
|
||||||
|
name: k.name,
|
||||||
|
last_used_at: Some(k.last_used.into()),
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
Ok(UserSecretsExt::extract(ctx, (profile, &secrets)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn self_full_from_base(
|
||||||
|
&self,
|
||||||
|
ctx: &PackingContext,
|
||||||
|
user: &ck::user::Model,
|
||||||
|
req: &UserSelfReq,
|
||||||
|
hint_avatar_file: Option<&ck::drive_file::Model>,
|
||||||
|
hint_banner_file: Option<&ck::drive_file::Model>,
|
||||||
|
) -> PackResult<PackUserSelfMaybeAll> {
|
||||||
|
let should_fetch_profile =
|
||||||
|
req.profile.unwrap_or_default() || req.secrets.unwrap_or_default();
|
||||||
|
let profile_raw_promise =
|
||||||
|
OptionFuture::from(should_fetch_profile.then(|| self.get_profile(ctx, user)));
|
||||||
|
let (base_res, profile_res) = join!(
|
||||||
|
self.base_from_existing(ctx, user, hint_avatar_file),
|
||||||
|
profile_raw_promise
|
||||||
|
);
|
||||||
|
let mut base = base_res?;
|
||||||
|
let profile_raw = profile_res.transpose()?;
|
||||||
|
|
||||||
|
let detail = req
|
||||||
|
.detail
|
||||||
|
.unwrap_or_default()
|
||||||
|
.then(|| UserDetailExt::extract(ctx, user));
|
||||||
|
|
||||||
|
let profile = OptionFuture::from(req.profile.unwrap_or_default().then(|| {
|
||||||
|
self.profile_from_base(
|
||||||
|
ctx,
|
||||||
|
user,
|
||||||
|
profile_raw.as_ref().unwrap(),
|
||||||
|
None,
|
||||||
|
&mut base.user.0.emojis,
|
||||||
|
hint_banner_file,
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
|
||||||
|
let note_model = NoteModel {
|
||||||
|
with_context: true,
|
||||||
|
attachments: true,
|
||||||
|
};
|
||||||
|
let pins = OptionFuture::from(
|
||||||
|
req.pins
|
||||||
|
.unwrap_or_default()
|
||||||
|
.then(|| note_model.fetch_pins(ctx, user)),
|
||||||
|
);
|
||||||
|
|
||||||
|
let secrets = OptionFuture::from(
|
||||||
|
req.secrets
|
||||||
|
.unwrap_or_default()
|
||||||
|
.then(|| self.secrets_from_base(ctx, user, profile_raw.as_ref().unwrap())),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (profile_res, pins_res, secrets_res) = join!(profile, pins, secrets);
|
||||||
|
|
||||||
|
let profile_resolved = profile_res.transpose()?;
|
||||||
|
let pins_resolved = pins_res
|
||||||
|
.transpose()?
|
||||||
|
.map(|notes| UserProfilePinsEx::extract(ctx, ¬es));
|
||||||
|
let secrets_resolved = secrets_res.transpose()?;
|
||||||
|
|
||||||
|
Ok(PackUserSelfMaybeAll {
|
||||||
|
id: base.id,
|
||||||
|
user: base.user,
|
||||||
|
profile: Optional(profile_resolved),
|
||||||
|
pins: Optional(pins_resolved),
|
||||||
|
detail: Optional(detail),
|
||||||
|
secrets: Optional(secrets_resolved),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn relations_from_base(
|
||||||
|
&self,
|
||||||
|
ctx: &PackingContext,
|
||||||
|
user: &ck::user::Model,
|
||||||
|
) -> PackResult<UserRelationExt> {
|
||||||
|
let Some(me_user) = ctx.self_user.as_deref() else {
|
||||||
|
return Ok(UserRelationExt {
|
||||||
|
follows_you: false,
|
||||||
|
you_follow: false,
|
||||||
|
you_request_follow: false,
|
||||||
|
they_request_follow: false,
|
||||||
|
blocks_you: false,
|
||||||
|
you_block: false,
|
||||||
|
mute: false,
|
||||||
|
mute_renotes: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let me = Either::Right(me_user);
|
||||||
|
let them = Either::Right(user);
|
||||||
|
|
||||||
|
let (
|
||||||
|
follows_you,
|
||||||
|
you_follow,
|
||||||
|
you_request_follow,
|
||||||
|
they_request_follow,
|
||||||
|
blocks_you,
|
||||||
|
you_block,
|
||||||
|
mute,
|
||||||
|
mute_renotes,
|
||||||
|
) = try_join!(
|
||||||
|
ctx.is_relationship_between(them, me, UserRelationship::Follow),
|
||||||
|
ctx.is_relationship_between(me, them, UserRelationship::Follow),
|
||||||
|
ctx.is_relationship_between(them, me, UserRelationship::FollowRequest),
|
||||||
|
ctx.is_relationship_between(me, them, UserRelationship::FollowRequest),
|
||||||
|
ctx.is_relationship_between(them, me, UserRelationship::Block),
|
||||||
|
ctx.is_relationship_between(me, them, UserRelationship::Block),
|
||||||
|
ctx.is_relationship_between(me, them, UserRelationship::Mute),
|
||||||
|
ctx.is_relationship_between(me, them, UserRelationship::RenoteMute)
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(UserRelationExt {
|
||||||
|
follows_you,
|
||||||
|
you_follow,
|
||||||
|
you_request_follow,
|
||||||
|
they_request_follow,
|
||||||
|
blocks_you,
|
||||||
|
you_block,
|
||||||
|
mute,
|
||||||
|
mute_renotes,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn auth_overview_from_profile(
|
||||||
|
&self,
|
||||||
|
ctx: &PackingContext,
|
||||||
|
profile: &ck::user_profile::Model,
|
||||||
|
) -> PackResult<UserAuthOverviewExt> {
|
||||||
|
Ok(UserAuthOverviewExt::extract(ctx, profile))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn foreign_full_from_base(
|
||||||
|
&self,
|
||||||
|
ctx: &PackingContext,
|
||||||
|
user: &ck::user::Model,
|
||||||
|
req: &UserByIdReq,
|
||||||
|
hint_avatar_file: Option<&ck::drive_file::Model>,
|
||||||
|
hint_banner_file: Option<&ck::drive_file::Model>,
|
||||||
|
) -> PackResult<PackUserMaybeAll> {
|
||||||
|
let should_fetch_profile = req.profile.unwrap_or_default() || req.auth.unwrap_or_default();
|
||||||
|
let profile_raw_promise =
|
||||||
|
OptionFuture::from(should_fetch_profile.then(|| self.get_profile(ctx, user)));
|
||||||
|
let (base, profile) = join!(
|
||||||
|
self.base_from_existing(ctx, user, hint_avatar_file),
|
||||||
|
profile_raw_promise
|
||||||
|
);
|
||||||
|
let mut base = base?;
|
||||||
|
let profile_raw = profile.transpose()?;
|
||||||
|
|
||||||
|
let detail = req
|
||||||
|
.detail
|
||||||
|
.unwrap_or_default()
|
||||||
|
.then(|| UserDetailExt::extract(ctx, user));
|
||||||
|
|
||||||
|
let profile = OptionFuture::from(req.profile.unwrap_or_default().then(|| {
|
||||||
|
self.profile_from_base(
|
||||||
|
ctx,
|
||||||
|
user,
|
||||||
|
profile_raw.as_ref().unwrap(),
|
||||||
|
None,
|
||||||
|
&mut base.user.0.emojis,
|
||||||
|
hint_banner_file,
|
||||||
|
)
|
||||||
|
}));
|
||||||
|
|
||||||
|
let note_model = NoteModel {
|
||||||
|
with_context: true,
|
||||||
|
attachments: true,
|
||||||
|
};
|
||||||
|
let pins = OptionFuture::from(
|
||||||
|
req.pins
|
||||||
|
.unwrap_or_default()
|
||||||
|
.then(|| note_model.fetch_pins(ctx, user)),
|
||||||
|
);
|
||||||
|
let relations = OptionFuture::from(
|
||||||
|
req.relation
|
||||||
|
.unwrap_or_default()
|
||||||
|
.then(|| self.relations_from_base(ctx, user)),
|
||||||
|
);
|
||||||
|
|
||||||
|
let auth = req
|
||||||
|
.auth
|
||||||
|
.unwrap_or_default()
|
||||||
|
.then(|| self.auth_overview_from_profile(ctx, profile_raw.as_ref().unwrap()))
|
||||||
|
.transpose()?;
|
||||||
|
|
||||||
|
let (profile_res, pins_res, relations_res) = join!(profile, pins, relations);
|
||||||
|
|
||||||
|
let profile_resolved = profile_res.transpose()?;
|
||||||
|
let pins_resolved = pins_res
|
||||||
|
.transpose()?
|
||||||
|
.map(|notes| UserProfilePinsEx::extract(ctx, ¬es));
|
||||||
|
let relations_resolved = relations_res.transpose()?;
|
||||||
|
|
||||||
|
Ok(PackUserMaybeAll {
|
||||||
|
id: base.id,
|
||||||
|
user: base.user,
|
||||||
|
profile: Optional(profile_resolved),
|
||||||
|
pins: Optional(pins_resolved),
|
||||||
|
detail: Optional(detail),
|
||||||
|
relation: Optional(relations_resolved),
|
||||||
|
auth: Optional(auth),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue