Ported MkAvatars to Magnetar
ci/woodpecker/push/ociImagePush Pipeline was successful
Details
ci/woodpecker/push/ociImagePush Pipeline was successful
Details
This commit is contained in:
parent
e1859c98bd
commit
4f6e6163cc
|
@ -888,6 +888,7 @@ checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
|
|||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
|
@ -1455,6 +1456,7 @@ dependencies = [
|
|||
"compact_str",
|
||||
"dotenvy",
|
||||
"either",
|
||||
"futures",
|
||||
"futures-util",
|
||||
"headers",
|
||||
"hyper",
|
||||
|
@ -1590,6 +1592,7 @@ dependencies = [
|
|||
"magnetar_sdk_macros",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"ts-rs",
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
|
|
@ -32,6 +32,7 @@ compact_str = "0.7"
|
|||
dotenvy = "0.15"
|
||||
either = "1.9"
|
||||
emojis = "0.6"
|
||||
futures = "0.3"
|
||||
futures-core = "0.3"
|
||||
futures-util = "0.3"
|
||||
headers = "0.3"
|
||||
|
@ -53,6 +54,7 @@ sea-orm = "0.12"
|
|||
sea-orm-migration = "0.12"
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
serde_urlencoded = "0.7"
|
||||
strum = "0.25"
|
||||
tera = { version = "1", default-features = false }
|
||||
thiserror = "1"
|
||||
|
@ -101,6 +103,7 @@ cfg-if = { workspace = true }
|
|||
|
||||
compact_str = { workspace = true }
|
||||
either = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
futures-util = { workspace = true }
|
||||
itertools = { workspace = true }
|
||||
lazy_static = { workspace = true }
|
||||
|
|
|
@ -114,6 +114,16 @@ impl CalckeyModel {
|
|||
.await?)
|
||||
}
|
||||
|
||||
pub async fn get_many_users_by_id(
|
||||
&self,
|
||||
id: &[String],
|
||||
) -> Result<Vec<user::Model>, CalckeyDbError> {
|
||||
Ok(user::Entity::find()
|
||||
.filter(user::Column::Id.is_in(id))
|
||||
.all(&self.0)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn get_user_by_token(
|
||||
&self,
|
||||
token: &str,
|
||||
|
|
|
@ -9,25 +9,37 @@
|
|||
<script lang="ts" setup>
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import * as os from "@/os";
|
||||
import { User } from "calckey-js/built/entities";
|
||||
import { packed, endpoints } from "magnetar-common";
|
||||
|
||||
const props = defineProps<{
|
||||
userIds: string[];
|
||||
}>();
|
||||
|
||||
const users = ref<User[]>([]);
|
||||
const users = ref<packed.PackUserBase[]>([]);
|
||||
|
||||
onMounted(async () => {
|
||||
users.value = await os.api("users/show", {
|
||||
userIds: props.userIds,
|
||||
});
|
||||
users.value = await os
|
||||
.magApi(
|
||||
endpoints.GetManyUsersById,
|
||||
{
|
||||
id: props.userIds,
|
||||
},
|
||||
{}
|
||||
)
|
||||
.then((p) => p.filter((u) => u !== null).map((u) => u!));
|
||||
|
||||
watch(
|
||||
() => props.userIds,
|
||||
async (userIds) => {
|
||||
users.value = await os.api("users/show", {
|
||||
userIds,
|
||||
});
|
||||
users.value = await os
|
||||
.magApi(
|
||||
endpoints.GetManyUsersById,
|
||||
{
|
||||
id: userIds,
|
||||
},
|
||||
{}
|
||||
)
|
||||
.then((p) => p.filter((u) => u !== null).map((u) => u!));
|
||||
}
|
||||
);
|
||||
});
|
|
@ -83,7 +83,7 @@ function onClick(ev: MouseEvent) {
|
|||
emit("click", ev);
|
||||
}
|
||||
|
||||
let color = $ref();
|
||||
let color = $ref<string>();
|
||||
|
||||
watch(
|
||||
() => magTransProperty(props.user, "avatar_blurhash", "avatarBlurhash"),
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
}}</MkA>
|
||||
</div>
|
||||
<div class="_content">
|
||||
<MkAvatars :user-ids="group.userIds" />
|
||||
<MagAvatars :user-ids="group.userIds" />
|
||||
</div>
|
||||
</div>
|
||||
</MkPagination>
|
||||
|
@ -35,7 +35,7 @@
|
|||
<div v-for="group in items" :key="group.id" class="_card">
|
||||
<div class="_title">{{ group.name }}</div>
|
||||
<div class="_content">
|
||||
<MkAvatars :user-ids="group.userIds" />
|
||||
<MagAvatars :user-ids="group.userIds" />
|
||||
</div>
|
||||
<div class="_footer">
|
||||
<MkButton danger @click="leave(group)">{{
|
||||
|
@ -52,7 +52,7 @@
|
|||
import { computed, ref } from "vue";
|
||||
import MkPagination from "@/components/MkPagination.vue";
|
||||
import MkButton from "@/components/MkButton.vue";
|
||||
import MkAvatars from "@/components/MkAvatars.vue";
|
||||
import MagAvatars from "@/components/MagAvatars.vue";
|
||||
import * as os from "@/os";
|
||||
import { definePageMetadata } from "@/scripts/page-metadata";
|
||||
import { i18n } from "@/i18n";
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
:to="`/my/lists/${list.id}`"
|
||||
>
|
||||
<div class="name">{{ list.name }}</div>
|
||||
<MkAvatars :user-ids="list.userIds" />
|
||||
<MagAvatars :user-ids="list.userIds" />
|
||||
</MkA>
|
||||
<MkButton @click="deleteAll"
|
||||
><i class="ph-trash ph-bold ph-lg"></i>
|
||||
|
@ -45,7 +45,7 @@
|
|||
import {} from "vue";
|
||||
import MkPagination from "@/components/MkPagination.vue";
|
||||
import MkButton from "@/components/MkButton.vue";
|
||||
import MkAvatars from "@/components/MkAvatars.vue";
|
||||
import MagAvatars from "@/components/MagAvatars.vue";
|
||||
import MkInfo from "@/components/MkInfo.vue";
|
||||
import * as os from "@/os";
|
||||
import { i18n } from "@/i18n";
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
><span style="margin-left: 8px">{{ column.name }}</span>
|
||||
</template>
|
||||
|
||||
<MkAvatars :userIds="userList" />
|
||||
<MagAvatars :userIds="userList" />
|
||||
</XColumn>
|
||||
</template>
|
||||
|
||||
|
@ -21,7 +21,7 @@ import * as os from "@/os";
|
|||
import { i18n } from "@/i18n";
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { User } from "calckey-js/built/entities";
|
||||
import MkAvatars from "@/components/MkAvatars.vue";
|
||||
import MagAvatars from "@/components/MagAvatars.vue";
|
||||
|
||||
const props = defineProps<{
|
||||
column: Column;
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
</div>
|
||||
<MkLoading v-else-if="fetching" />
|
||||
<div v-else class="users">
|
||||
<MkAvatars :user-ids="users" class="userAvatars" />
|
||||
<MagAvatars :user-ids="users" class="userAvatars" />
|
||||
</div>
|
||||
</div>
|
||||
</MkContainer>
|
||||
|
@ -31,7 +31,7 @@
|
|||
import { useWidgetPropsManager, Widget, WidgetComponentExpose } from "./widget";
|
||||
import { GetFormResultType } from "@/scripts/form";
|
||||
import MkContainer from "@/components/MkContainer.vue";
|
||||
import MkAvatars from "@/components/MkAvatars.vue";
|
||||
import MagAvatars from "@/components/MagAvatars.vue";
|
||||
import * as os from "@/os";
|
||||
import { useInterval } from "@/scripts/use-interval";
|
||||
import { i18n } from "@/i18n";
|
||||
|
|
|
@ -20,7 +20,7 @@ function nestedUrlSearchParams(data: any, topLevel: boolean = true): string {
|
|||
case "boolean":
|
||||
case "number":
|
||||
case "symbol":
|
||||
if (topLevel) return encodeURIComponent(data.toString()) + "=";
|
||||
if (topLevel) return encodeURIComponent(data.toString());
|
||||
|
||||
return data.toString();
|
||||
case "object":
|
||||
|
|
|
@ -3,3 +3,4 @@ export { GetTimeline } from "./types/endpoints/GetTimeline";
|
|||
export { GetUserById } from "./types/endpoints/GetUserById";
|
||||
export { GetUserByAcct } from "./types/endpoints/GetUserByAcct";
|
||||
export { GetUserSelf } from "./types/endpoints/GetUserSelf";
|
||||
export { GetManyUsersById } from "./types/endpoints/GetManyUsersById";
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export interface ManyUsersByIdReq { id: Array<string>, }
|
|
@ -0,0 +1,12 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ManyUsersByIdReq } from "../ManyUsersByIdReq";
|
||||
import type { PackUserBase } from "../packed/PackUserBase";
|
||||
|
||||
export const GetManyUsersById = {
|
||||
endpoint: "/users/lookup-many",
|
||||
pathParams: [] as [],
|
||||
method: "GET" as "GET" | "POST" | "PUT" | "DELETE" | "PATCH",
|
||||
request: undefined as unknown as ManyUsersByIdReq,
|
||||
response: undefined as unknown as Array<PackUserBase | null>
|
||||
}
|
||||
|
|
@ -13,6 +13,7 @@ chrono = { workspace = true, features = ["serde"] }
|
|||
http = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
serde_urlencoded = { workspace = true }
|
||||
|
||||
ts-rs = { workspace = true, features = ["chrono", "chrono-impl"] }
|
||||
|
||||
|
|
|
@ -4,7 +4,8 @@ use std::collections::HashSet;
|
|||
use syn::parse::Parse;
|
||||
use syn::punctuated::Punctuated;
|
||||
use syn::{
|
||||
Expr, ExprLit, ExprPath, Ident, Lit, Meta, MetaNameValue, PathArguments, Token, Type, TypePath,
|
||||
Expr, ExprLit, ExprPath, GenericArgument, Ident, Lit, Meta, MetaNameValue, PathArguments,
|
||||
Token, Type, TypePath,
|
||||
};
|
||||
|
||||
struct Field {
|
||||
|
@ -311,14 +312,52 @@ pub fn derive_endpoint(item: TokenStream) -> TokenStream {
|
|||
}
|
||||
};
|
||||
|
||||
let req_args_flat = request_args.clone().unwrap_or_default();
|
||||
let mut res_args_flat = response_args.clone().unwrap_or_default();
|
||||
|
||||
let call_name_res = if let Some(ref args) = response_args {
|
||||
let args_str = args
|
||||
let args_nested = args
|
||||
.iter()
|
||||
.map(|a| a.into_token_stream().to_string())
|
||||
.map(|a| {
|
||||
let normal = Vec::new();
|
||||
|
||||
let GenericArgument::Type(inner) = a else {
|
||||
return normal;
|
||||
};
|
||||
let Type::Path(path_inner) = inner else {
|
||||
return normal;
|
||||
};
|
||||
let Some(seg) = path_inner.path.segments.last() else {
|
||||
return normal;
|
||||
};
|
||||
let PathArguments::AngleBracketed(angle_inner) = &seg.arguments else {
|
||||
return normal;
|
||||
};
|
||||
angle_inner.args.iter().cloned().collect::<Vec<_>>()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
res_args_flat.extend(args_nested.iter().flatten().cloned());
|
||||
|
||||
let arg_tokens_nested = args_nested
|
||||
.iter()
|
||||
.zip(args)
|
||||
.map(|(args, parent)| {
|
||||
if !args.is_empty() {
|
||||
let names = args.iter().map(|a| a.to_token_stream().to_string());
|
||||
quote::quote! {
|
||||
<#parent as TS>::name_with_type_args(vec![#( #names.to_string() ),*])
|
||||
}
|
||||
} else {
|
||||
quote::quote! {
|
||||
<#parent as TS>::name()
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
quote::quote! {
|
||||
<#struct_name as Endpoint>::Response::name_with_type_args(vec![#( #args_str.to_string() ),*])
|
||||
<#struct_name as Endpoint>::Response::name_with_type_args(vec![#( #arg_tokens_nested ),*])
|
||||
}
|
||||
} else {
|
||||
quote::quote! {
|
||||
|
@ -326,9 +365,6 @@ pub fn derive_endpoint(item: TokenStream) -> TokenStream {
|
|||
}
|
||||
};
|
||||
|
||||
let req_args_flat = request_args.unwrap_or_default();
|
||||
let res_args_flat = response_args.unwrap_or_default();
|
||||
|
||||
let expanded = quote::quote! {
|
||||
impl Default for #struct_name {
|
||||
fn default() -> Self {
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use crate::endpoints::Endpoint;
|
||||
use crate::util_types::deserialize_array_urlenc;
|
||||
use http::Method;
|
||||
use magnetar_sdk_macros::Endpoint;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::types::user::{PackUserMaybeAll, PackUserSelfMaybeAll};
|
||||
use crate::types::user::{PackUserBase, PackUserMaybeAll, PackUserSelfMaybeAll};
|
||||
|
||||
// Get self
|
||||
#[derive(Serialize, Deserialize, TS)]
|
||||
|
@ -54,6 +55,23 @@ pub struct UserByIdReq {
|
|||
)]
|
||||
pub struct GetUserById;
|
||||
|
||||
// Get many users by an ID array
|
||||
#[derive(Serialize, Deserialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct ManyUsersByIdReq {
|
||||
#[serde(deserialize_with = "deserialize_array_urlenc")]
|
||||
pub id: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Endpoint)]
|
||||
#[endpoint(
|
||||
endpoint = "/users/lookup-many",
|
||||
method = Method::GET,
|
||||
request = "ManyUsersByIdReq",
|
||||
response = "Vec<Option<PackUserBase>>"
|
||||
)]
|
||||
pub struct GetManyUsersById;
|
||||
|
||||
// Get user by fedi tag
|
||||
|
||||
#[derive(Endpoint)]
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::hash::Hash;
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
|
@ -76,3 +79,20 @@ impl<const MIN: u64, const MAX: u64> TryFrom<u64> for U64Range<MIN, MAX> {
|
|||
Ok(U64Range(value))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize_array_urlenc<'de, D, T: Eq + Hash>(
|
||||
deserializer: D,
|
||||
) -> Result<Vec<T>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
T: DeserializeOwned,
|
||||
{
|
||||
let str_raw = String::deserialize(deserializer)?;
|
||||
let parts = serde_urlencoded::from_str::<Vec<(T, String)>>(&str_raw)
|
||||
.map_err(serde::de::Error::custom)?
|
||||
.into_iter()
|
||||
.map(|(k, _)| k)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(parts)
|
||||
}
|
||||
|
|
|
@ -2,19 +2,24 @@ mod note;
|
|||
mod user;
|
||||
|
||||
use crate::api_v1::note::handle_note;
|
||||
use crate::api_v1::user::{handle_user_info, handle_user_info_by_acct, handle_user_info_self};
|
||||
use crate::api_v1::user::{
|
||||
handle_user_by_id_many, handle_user_info, handle_user_info_by_acct, handle_user_info_self,
|
||||
};
|
||||
use crate::service::MagnetarService;
|
||||
use crate::web::auth;
|
||||
use crate::web::auth::AuthState;
|
||||
use axum::middleware::from_fn_with_state;
|
||||
use axum::routing::get;
|
||||
use axum::Router;
|
||||
use serde::de::{DeserializeOwned, Error};
|
||||
use serde::{Deserialize, Deserializer};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub fn create_api_router(service: Arc<MagnetarService>) -> Router {
|
||||
Router::new()
|
||||
.route("/users/@self", get(handle_user_info_self))
|
||||
.route("/users/by-acct/:id", get(handle_user_info_by_acct))
|
||||
.route("/users/lookup-many", get(handle_user_by_id_many))
|
||||
.route("/users/:id", get(handle_user_info))
|
||||
.route("/notes/:id", get(handle_note))
|
||||
.layer(from_fn_with_state(
|
||||
|
|
|
@ -1,16 +1,23 @@
|
|||
use crate::model::processing::user::UserModel;
|
||||
use crate::model::processing::PackError;
|
||||
use crate::model::PackingContext;
|
||||
use crate::service::MagnetarService;
|
||||
use crate::web::auth::{AuthenticatedUser, MaybeUser};
|
||||
use crate::web::{ApiError, ObjectNotFound};
|
||||
use crate::web::{ApiError, ArgumentOutOfRange, ObjectNotFound};
|
||||
use axum::extract::{Path, Query, State};
|
||||
use axum::Json;
|
||||
use futures::StreamExt;
|
||||
use futures_util::TryStreamExt;
|
||||
use itertools::Itertools;
|
||||
use magnetar_common::util::lenient_parse_tag;
|
||||
use magnetar_sdk::endpoints::user::{
|
||||
GetUserByAcct, GetUserById, GetUserSelf, UserByIdReq, UserSelfReq,
|
||||
GetManyUsersById, GetUserByAcct, GetUserById, GetUserSelf, ManyUsersByIdReq, UserByIdReq,
|
||||
UserSelfReq,
|
||||
};
|
||||
use magnetar_sdk::endpoints::{Req, Res};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub async fn handle_user_info_self(
|
||||
Query(UserSelfReq {
|
||||
detail: _,
|
||||
|
@ -82,3 +89,42 @@ pub async fn handle_user_info_by_acct(
|
|||
let user = UserModel.base_from_existing(&ctx, &user_model).await?;
|
||||
Ok(Json(user.into()))
|
||||
}
|
||||
|
||||
pub async fn handle_user_by_id_many(
|
||||
Query(ManyUsersByIdReq { id }): Query<Req<GetManyUsersById>>,
|
||||
State(service): State<Arc<MagnetarService>>,
|
||||
MaybeUser(user): MaybeUser,
|
||||
) -> Result<Json<Res<GetManyUsersById>>, ApiError> {
|
||||
if id.len() >= 100 {
|
||||
return Err(ArgumentOutOfRange(stringify!(id).to_string()).into());
|
||||
}
|
||||
|
||||
let users = service
|
||||
.db
|
||||
.get_many_users_by_id(&id.iter().cloned().sorted().dedup().collect::<Vec<_>>())
|
||||
.await?;
|
||||
|
||||
let ctx = PackingContext::new(service, user.clone()).await?;
|
||||
let user_model = UserModel;
|
||||
|
||||
let futures = users
|
||||
.iter()
|
||||
.map(|u| user_model.base_from_existing(&ctx, u))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let users_proc = futures::stream::iter(futures)
|
||||
.buffered(20)
|
||||
.err_into::<PackError>()
|
||||
.try_collect::<Vec<_>>()
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(|u| (u.id.0.id.clone(), u))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let users_ordered = id
|
||||
.iter()
|
||||
.map(|ident| users_proc.get(ident).cloned())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(Json(users_ordered))
|
||||
}
|
||||
|
|
|
@ -30,6 +30,8 @@ pub enum PackError {
|
|||
DeserializerError(#[from] serde_json::Error),
|
||||
#[error("URL parse error: {0}")]
|
||||
UrlParseError(#[from] url::ParseError),
|
||||
#[error("Parallel processing error: {0}")]
|
||||
JoinError(#[from] tokio::task::JoinError),
|
||||
}
|
||||
|
||||
pub type PackResult<T> = Result<T, PackError>;
|
||||
|
|
|
@ -134,3 +134,22 @@ impl From<ObjectNotFound> for ApiError {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ArgumentOutOfRange(pub String);
|
||||
|
||||
impl From<&ArgumentOutOfRange> for &str {
|
||||
fn from(_: &ArgumentOutOfRange) -> Self {
|
||||
"ArgumentOutOfRange"
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ArgumentOutOfRange> for ApiError {
|
||||
fn from(err: ArgumentOutOfRange) -> Self {
|
||||
Self {
|
||||
status: StatusCode::BAD_REQUEST,
|
||||
code: err.error_code(),
|
||||
message: format!("Argument out of range: {}", err.0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue