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 = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
|
@ -1455,6 +1456,7 @@ dependencies = [
|
||||||
"compact_str",
|
"compact_str",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
"either",
|
"either",
|
||||||
|
"futures",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"headers",
|
"headers",
|
||||||
"hyper",
|
"hyper",
|
||||||
|
@ -1590,6 +1592,7 @@ dependencies = [
|
||||||
"magnetar_sdk_macros",
|
"magnetar_sdk_macros",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"serde_urlencoded",
|
||||||
"ts-rs",
|
"ts-rs",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
|
@ -32,6 +32,7 @@ compact_str = "0.7"
|
||||||
dotenvy = "0.15"
|
dotenvy = "0.15"
|
||||||
either = "1.9"
|
either = "1.9"
|
||||||
emojis = "0.6"
|
emojis = "0.6"
|
||||||
|
futures = "0.3"
|
||||||
futures-core = "0.3"
|
futures-core = "0.3"
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
headers = "0.3"
|
headers = "0.3"
|
||||||
|
@ -53,6 +54,7 @@ sea-orm = "0.12"
|
||||||
sea-orm-migration = "0.12"
|
sea-orm-migration = "0.12"
|
||||||
serde = "1"
|
serde = "1"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
serde_urlencoded = "0.7"
|
||||||
strum = "0.25"
|
strum = "0.25"
|
||||||
tera = { version = "1", default-features = false }
|
tera = { version = "1", default-features = false }
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
|
@ -101,6 +103,7 @@ cfg-if = { workspace = true }
|
||||||
|
|
||||||
compact_str = { workspace = true }
|
compact_str = { workspace = true }
|
||||||
either = { workspace = true }
|
either = { workspace = true }
|
||||||
|
futures = { workspace = true }
|
||||||
futures-util = { workspace = true }
|
futures-util = { workspace = true }
|
||||||
itertools = { workspace = true }
|
itertools = { workspace = true }
|
||||||
lazy_static = { workspace = true }
|
lazy_static = { workspace = true }
|
||||||
|
|
|
@ -114,6 +114,16 @@ impl CalckeyModel {
|
||||||
.await?)
|
.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(
|
pub async fn get_user_by_token(
|
||||||
&self,
|
&self,
|
||||||
token: &str,
|
token: &str,
|
||||||
|
|
|
@ -9,25 +9,37 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref, watch } from "vue";
|
import { onMounted, ref, watch } from "vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { User } from "calckey-js/built/entities";
|
import { packed, endpoints } from "magnetar-common";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
userIds: string[];
|
userIds: string[];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const users = ref<User[]>([]);
|
const users = ref<packed.PackUserBase[]>([]);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
users.value = await os.api("users/show", {
|
users.value = await os
|
||||||
userIds: props.userIds,
|
.magApi(
|
||||||
});
|
endpoints.GetManyUsersById,
|
||||||
|
{
|
||||||
|
id: props.userIds,
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
.then((p) => p.filter((u) => u !== null).map((u) => u!));
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.userIds,
|
() => props.userIds,
|
||||||
async (userIds) => {
|
async (userIds) => {
|
||||||
users.value = await os.api("users/show", {
|
users.value = await os
|
||||||
userIds,
|
.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);
|
emit("click", ev);
|
||||||
}
|
}
|
||||||
|
|
||||||
let color = $ref();
|
let color = $ref<string>();
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => magTransProperty(props.user, "avatar_blurhash", "avatarBlurhash"),
|
() => magTransProperty(props.user, "avatar_blurhash", "avatarBlurhash"),
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
}}</MkA>
|
}}</MkA>
|
||||||
</div>
|
</div>
|
||||||
<div class="_content">
|
<div class="_content">
|
||||||
<MkAvatars :user-ids="group.userIds" />
|
<MagAvatars :user-ids="group.userIds" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MkPagination>
|
</MkPagination>
|
||||||
|
@ -35,7 +35,7 @@
|
||||||
<div v-for="group in items" :key="group.id" class="_card">
|
<div v-for="group in items" :key="group.id" class="_card">
|
||||||
<div class="_title">{{ group.name }}</div>
|
<div class="_title">{{ group.name }}</div>
|
||||||
<div class="_content">
|
<div class="_content">
|
||||||
<MkAvatars :user-ids="group.userIds" />
|
<MagAvatars :user-ids="group.userIds" />
|
||||||
</div>
|
</div>
|
||||||
<div class="_footer">
|
<div class="_footer">
|
||||||
<MkButton danger @click="leave(group)">{{
|
<MkButton danger @click="leave(group)">{{
|
||||||
|
@ -52,7 +52,7 @@
|
||||||
import { computed, ref } from "vue";
|
import { computed, ref } from "vue";
|
||||||
import MkPagination from "@/components/MkPagination.vue";
|
import MkPagination from "@/components/MkPagination.vue";
|
||||||
import MkButton from "@/components/MkButton.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 * as os from "@/os";
|
||||||
import { definePageMetadata } from "@/scripts/page-metadata";
|
import { definePageMetadata } from "@/scripts/page-metadata";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
:to="`/my/lists/${list.id}`"
|
:to="`/my/lists/${list.id}`"
|
||||||
>
|
>
|
||||||
<div class="name">{{ list.name }}</div>
|
<div class="name">{{ list.name }}</div>
|
||||||
<MkAvatars :user-ids="list.userIds" />
|
<MagAvatars :user-ids="list.userIds" />
|
||||||
</MkA>
|
</MkA>
|
||||||
<MkButton @click="deleteAll"
|
<MkButton @click="deleteAll"
|
||||||
><i class="ph-trash ph-bold ph-lg"></i>
|
><i class="ph-trash ph-bold ph-lg"></i>
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
import {} from "vue";
|
import {} from "vue";
|
||||||
import MkPagination from "@/components/MkPagination.vue";
|
import MkPagination from "@/components/MkPagination.vue";
|
||||||
import MkButton from "@/components/MkButton.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 MkInfo from "@/components/MkInfo.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
><span style="margin-left: 8px">{{ column.name }}</span>
|
><span style="margin-left: 8px">{{ column.name }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<MkAvatars :userIds="userList" />
|
<MagAvatars :userIds="userList" />
|
||||||
</XColumn>
|
</XColumn>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ import * as os from "@/os";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
import { onMounted, ref, watch } from "vue";
|
import { onMounted, ref, watch } from "vue";
|
||||||
import { User } from "calckey-js/built/entities";
|
import { User } from "calckey-js/built/entities";
|
||||||
import MkAvatars from "@/components/MkAvatars.vue";
|
import MagAvatars from "@/components/MagAvatars.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
column: Column;
|
column: Column;
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
</div>
|
</div>
|
||||||
<MkLoading v-else-if="fetching" />
|
<MkLoading v-else-if="fetching" />
|
||||||
<div v-else class="users">
|
<div v-else class="users">
|
||||||
<MkAvatars :user-ids="users" class="userAvatars" />
|
<MagAvatars :user-ids="users" class="userAvatars" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MkContainer>
|
</MkContainer>
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
import { useWidgetPropsManager, Widget, WidgetComponentExpose } from "./widget";
|
import { useWidgetPropsManager, Widget, WidgetComponentExpose } from "./widget";
|
||||||
import { GetFormResultType } from "@/scripts/form";
|
import { GetFormResultType } from "@/scripts/form";
|
||||||
import MkContainer from "@/components/MkContainer.vue";
|
import MkContainer from "@/components/MkContainer.vue";
|
||||||
import MkAvatars from "@/components/MkAvatars.vue";
|
import MagAvatars from "@/components/MagAvatars.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { useInterval } from "@/scripts/use-interval";
|
import { useInterval } from "@/scripts/use-interval";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
|
|
@ -20,7 +20,7 @@ function nestedUrlSearchParams(data: any, topLevel: boolean = true): string {
|
||||||
case "boolean":
|
case "boolean":
|
||||||
case "number":
|
case "number":
|
||||||
case "symbol":
|
case "symbol":
|
||||||
if (topLevel) return encodeURIComponent(data.toString()) + "=";
|
if (topLevel) return encodeURIComponent(data.toString());
|
||||||
|
|
||||||
return data.toString();
|
return data.toString();
|
||||||
case "object":
|
case "object":
|
||||||
|
|
|
@ -3,3 +3,4 @@ export { GetTimeline } from "./types/endpoints/GetTimeline";
|
||||||
export { GetUserById } from "./types/endpoints/GetUserById";
|
export { GetUserById } from "./types/endpoints/GetUserById";
|
||||||
export { GetUserByAcct } from "./types/endpoints/GetUserByAcct";
|
export { GetUserByAcct } from "./types/endpoints/GetUserByAcct";
|
||||||
export { GetUserSelf } from "./types/endpoints/GetUserSelf";
|
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 }
|
http = { workspace = true }
|
||||||
serde = { workspace = true, features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
|
serde_urlencoded = { workspace = true }
|
||||||
|
|
||||||
ts-rs = { workspace = true, features = ["chrono", "chrono-impl"] }
|
ts-rs = { workspace = true, features = ["chrono", "chrono-impl"] }
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,8 @@ use std::collections::HashSet;
|
||||||
use syn::parse::Parse;
|
use syn::parse::Parse;
|
||||||
use syn::punctuated::Punctuated;
|
use syn::punctuated::Punctuated;
|
||||||
use syn::{
|
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 {
|
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 call_name_res = if let Some(ref args) = response_args {
|
||||||
let args_str = args
|
let args_nested = args
|
||||||
.iter()
|
.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<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
quote::quote! {
|
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 {
|
} else {
|
||||||
quote::quote! {
|
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! {
|
let expanded = quote::quote! {
|
||||||
impl Default for #struct_name {
|
impl Default for #struct_name {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use crate::endpoints::Endpoint;
|
use crate::endpoints::Endpoint;
|
||||||
|
use crate::util_types::deserialize_array_urlenc;
|
||||||
use http::Method;
|
use http::Method;
|
||||||
use magnetar_sdk_macros::Endpoint;
|
use magnetar_sdk_macros::Endpoint;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ts_rs::TS;
|
use ts_rs::TS;
|
||||||
|
|
||||||
use crate::types::user::{PackUserMaybeAll, PackUserSelfMaybeAll};
|
use crate::types::user::{PackUserBase, PackUserMaybeAll, PackUserSelfMaybeAll};
|
||||||
|
|
||||||
// Get self
|
// Get self
|
||||||
#[derive(Serialize, Deserialize, TS)]
|
#[derive(Serialize, Deserialize, TS)]
|
||||||
|
@ -54,6 +55,23 @@ pub struct UserByIdReq {
|
||||||
)]
|
)]
|
||||||
pub struct GetUserById;
|
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
|
// Get user by fedi tag
|
||||||
|
|
||||||
#[derive(Endpoint)]
|
#[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;
|
use ts_rs::TS;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[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))
|
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;
|
mod user;
|
||||||
|
|
||||||
use crate::api_v1::note::handle_note;
|
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::service::MagnetarService;
|
||||||
use crate::web::auth;
|
use crate::web::auth;
|
||||||
use crate::web::auth::AuthState;
|
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 {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/users/@self", get(handle_user_info_self))
|
.route("/users/@self", get(handle_user_info_self))
|
||||||
.route("/users/by-acct/:id", get(handle_user_info_by_acct))
|
.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("/users/:id", get(handle_user_info))
|
||||||
.route("/notes/:id", get(handle_note))
|
.route("/notes/:id", get(handle_note))
|
||||||
.layer(from_fn_with_state(
|
.layer(from_fn_with_state(
|
||||||
|
|
|
@ -1,16 +1,23 @@
|
||||||
use crate::model::processing::user::UserModel;
|
use crate::model::processing::user::UserModel;
|
||||||
|
use crate::model::processing::PackError;
|
||||||
use crate::model::PackingContext;
|
use crate::model::PackingContext;
|
||||||
use crate::service::MagnetarService;
|
use crate::service::MagnetarService;
|
||||||
use crate::web::auth::{AuthenticatedUser, MaybeUser};
|
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::extract::{Path, Query, State};
|
||||||
use axum::Json;
|
use axum::Json;
|
||||||
|
use futures::StreamExt;
|
||||||
|
use futures_util::TryStreamExt;
|
||||||
|
use itertools::Itertools;
|
||||||
use magnetar_common::util::lenient_parse_tag;
|
use magnetar_common::util::lenient_parse_tag;
|
||||||
use magnetar_sdk::endpoints::user::{
|
use magnetar_sdk::endpoints::user::{
|
||||||
GetUserByAcct, GetUserById, GetUserSelf, UserByIdReq, UserSelfReq,
|
GetManyUsersById, GetUserByAcct, GetUserById, GetUserSelf, ManyUsersByIdReq, UserByIdReq,
|
||||||
|
UserSelfReq,
|
||||||
};
|
};
|
||||||
use magnetar_sdk::endpoints::{Req, Res};
|
use magnetar_sdk::endpoints::{Req, Res};
|
||||||
|
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(UserSelfReq {
|
||||||
detail: _,
|
detail: _,
|
||||||
|
@ -82,3 +89,42 @@ pub async fn handle_user_info_by_acct(
|
||||||
let user = UserModel.base_from_existing(&ctx, &user_model).await?;
|
let user = UserModel.base_from_existing(&ctx, &user_model).await?;
|
||||||
Ok(Json(user.into()))
|
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),
|
DeserializerError(#[from] serde_json::Error),
|
||||||
#[error("URL parse error: {0}")]
|
#[error("URL parse error: {0}")]
|
||||||
UrlParseError(#[from] url::ParseError),
|
UrlParseError(#[from] url::ParseError),
|
||||||
|
#[error("Parallel processing error: {0}")]
|
||||||
|
JoinError(#[from] tokio::task::JoinError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type PackResult<T> = Result<T, PackError>;
|
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