From 213dad6b3e049e0c761755c29a29295d314909f3 Mon Sep 17 00:00:00 2001 From: "Calum H. (IMB11)" Date: Mon, 27 Jul 2026 09:53:59 +0100 Subject: [PATCH 01/10] feat: move users page to xplat --- apps/app-frontend/src/helpers/users.ts | 46 +- apps/app-frontend/src/pages/User.vue | 97 ++ apps/app-frontend/src/pages/index.js | 3 +- apps/app-frontend/src/routes.js | 8 + apps/app/build.rs | 9 +- apps/app/src/api/users.rs | 41 +- apps/frontend/src/pages/user/[user].vue | 881 ++---------------- packages/app-lib/src/api/users.rs | 135 ++- .../src/components/user}/UserPageHeader.vue | 38 +- packages/ui/src/components/user/index.ts | 1 + packages/ui/src/layouts/index.ts | 1 + .../src/layouts/shared/user-profile/index.ts | 2 + .../layouts/shared/user-profile/layout.vue | 875 +++++++++++++++++ .../shared/user-profile/providers/index.ts | 1 + .../user-profile/providers/user-profile.ts | 21 + .../src/layouts/shared/user-profile/utils.ts | 88 ++ 16 files changed, 1395 insertions(+), 852 deletions(-) create mode 100644 apps/app-frontend/src/pages/User.vue rename {apps/frontend/src/components/ui => packages/ui/src/components/user}/UserPageHeader.vue (84%) create mode 100644 packages/ui/src/layouts/shared/user-profile/index.ts create mode 100644 packages/ui/src/layouts/shared/user-profile/layout.vue create mode 100644 packages/ui/src/layouts/shared/user-profile/providers/index.ts create mode 100644 packages/ui/src/layouts/shared/user-profile/providers/user-profile.ts create mode 100644 packages/ui/src/layouts/shared/user-profile/utils.ts diff --git a/apps/app-frontend/src/helpers/users.ts b/apps/app-frontend/src/helpers/users.ts index 042b0d4cbb..f2208e9311 100644 --- a/apps/app-frontend/src/helpers/users.ts +++ b/apps/app-frontend/src/helpers/users.ts @@ -1,11 +1,45 @@ +import type { Labrinth } from '@modrinth/api-client' import { invoke } from '@tauri-apps/api/core' -export type SearchUser = { - id: string - username: string - avatar_url: string | null +export async function search_user(query: string): Promise { + return await invoke('plugin:users|search_user', { query }) } -export async function search_user(query: string): Promise { - return await invoke('plugin:users|search_user', { query }) +export async function get_user_profile( + userId: string, +): Promise { + return await invoke('plugin:users|get_user_profile', { userId }) +} + +export async function get_user_projects( + userId: string, +): Promise { + return await invoke('plugin:users|get_user_projects', { + userId, + }) +} + +export async function get_user_organizations( + userId: string, +): Promise { + return await invoke( + 'plugin:users|get_user_organizations', + { userId }, + ) +} + +export async function get_user_collections( + userId: string, +): Promise { + return await invoke( + 'plugin:users|get_user_collections', + { userId }, + ) +} + +export async function patch_user( + userId: string, + patch: Partial>, +): Promise { + await invoke('plugin:users|patch_user', { userId, patch }) } diff --git a/apps/app-frontend/src/pages/User.vue b/apps/app-frontend/src/pages/User.vue new file mode 100644 index 0000000000..1b5a4193be --- /dev/null +++ b/apps/app-frontend/src/pages/User.vue @@ -0,0 +1,97 @@ + + + diff --git a/apps/app-frontend/src/pages/index.js b/apps/app-frontend/src/pages/index.js index 5a4294937a..b290622177 100644 --- a/apps/app-frontend/src/pages/index.js +++ b/apps/app-frontend/src/pages/index.js @@ -2,6 +2,7 @@ import Browse from './Browse.vue' import Index from './Index.vue' import Servers from './Servers.vue' import Skins from './Skins.vue' +import User from './User.vue' import Worlds from './Worlds.vue' -export { Browse, Index, Servers, Skins, Worlds } +export { Browse, Index, Servers, Skins, User, Worlds } diff --git a/apps/app-frontend/src/routes.js b/apps/app-frontend/src/routes.js index 2baa1edd06..af0131c4be 100644 --- a/apps/app-frontend/src/routes.js +++ b/apps/app-frontend/src/routes.js @@ -100,6 +100,14 @@ export default new createRouter({ breadcrumb: [{ name: 'Skin selector' }], }, }, + { + path: '/user/:user/:projectType?', + name: 'User', + component: Pages.User, + meta: { + breadcrumb: [{ name: '?User' }], + }, + }, { path: '/library', name: 'Library', diff --git a/apps/app/build.rs b/apps/app/build.rs index c7fe2d490b..eb826b13da 100644 --- a/apps/app/build.rs +++ b/apps/app/build.rs @@ -276,7 +276,14 @@ fn main() { .plugin( "users", InlinedPlugin::new() - .commands(&["search_user"]) + .commands(&[ + "search_user", + "get_user_profile", + "get_user_projects", + "get_user_organizations", + "get_user_collections", + "patch_user", + ]) .default_permission( DefaultPermissionRule::AllowAllCommands, ), diff --git a/apps/app/src/api/users.rs b/apps/app/src/api/users.rs index b4c5dba75a..8ae4474ce8 100644 --- a/apps/app/src/api/users.rs +++ b/apps/app/src/api/users.rs @@ -1,13 +1,46 @@ use crate::api::Result; +use serde_json::Value; use theseus::users::SearchUser; #[tauri::command] pub async fn search_user(query: &str) -> Result> { - Ok(theseus::users::search_user(query).await?) + Ok(theseus::users::search_user(query).await?) +} + +#[tauri::command] +pub async fn get_user_profile(user_id: &str) -> Result { + Ok(theseus::users::get_user_profile(user_id).await?) +} + +#[tauri::command] +pub async fn get_user_projects(user_id: &str) -> Result { + Ok(theseus::users::get_user_projects(user_id).await?) +} + +#[tauri::command] +pub async fn get_user_organizations(user_id: &str) -> Result { + Ok(theseus::users::get_user_organizations(user_id).await?) +} + +#[tauri::command] +pub async fn get_user_collections(user_id: &str) -> Result { + Ok(theseus::users::get_user_collections(user_id).await?) +} + +#[tauri::command] +pub async fn patch_user(user_id: &str, patch: Value) -> Result<()> { + Ok(theseus::users::patch_user(user_id, patch).await?) } pub fn init() -> tauri::plugin::TauriPlugin { - tauri::plugin::Builder::new("users") - .invoke_handler(tauri::generate_handler![search_user]) - .build() + tauri::plugin::Builder::new("users") + .invoke_handler(tauri::generate_handler![ + search_user, + get_user_profile, + get_user_projects, + get_user_organizations, + get_user_collections, + patch_user, + ]) + .build() } diff --git a/apps/frontend/src/pages/user/[user].vue b/apps/frontend/src/pages/user/[user].vue index 4ebf1e763d..897011df64 100644 --- a/apps/frontend/src/pages/user/[user].vue +++ b/apps/frontend/src/pages/user/[user].vue @@ -1,838 +1,109 @@ - - - - diff --git a/packages/app-lib/src/api/users.rs b/packages/app-lib/src/api/users.rs index 6d5f4019f6..caeddd5683 100644 --- a/packages/app-lib/src/api/users.rs +++ b/packages/app-lib/src/api/users.rs @@ -1,7 +1,8 @@ use crate::State; -use crate::util::fetch::fetch_json; +use crate::util::fetch::{fetch_advanced, fetch_json}; use reqwest::Method; use serde::{Deserialize, Serialize}; +use serde_json::Value; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct SearchUser { @@ -12,21 +13,119 @@ pub struct SearchUser { #[tracing::instrument] pub async fn search_user(query: &str) -> crate::Result> { - let state = State::get().await?; - let query = urlencoding::encode(query); - - fetch_json( - Method::GET, - &format!( - "{}users/search?query={}", - env!("MODRINTH_API_URL_V3"), - query - ), - None, - None, - Some("/v3/users/search"), - &state.api_semaphore, - &state.pool, - ) - .await + let state = State::get().await?; + let query = urlencoding::encode(query); + + fetch_json( + Method::GET, + &format!( + "{}users/search?query={}", + env!("MODRINTH_API_URL_V3"), + query + ), + None, + None, + Some("/v3/users/search"), + &state.api_semaphore, + &state.pool, + ) + .await +} + +#[tracing::instrument] +pub async fn get_user_profile(user_id: &str) -> crate::Result { + let state = State::get().await?; + let user_id = urlencoding::encode(user_id); + + fetch_json( + Method::GET, + &format!("{}user/{}", env!("MODRINTH_API_URL_V3"), user_id), + None, + None, + Some("/v3/user/:id"), + &state.api_semaphore, + &state.pool, + ) + .await +} + +#[tracing::instrument] +pub async fn get_user_projects(user_id: &str) -> crate::Result { + let state = State::get().await?; + let user_id = urlencoding::encode(user_id); + + fetch_json( + Method::GET, + &format!("{}user/{}/projects", env!("MODRINTH_API_URL"), user_id), + None, + None, + Some("/v2/user/:id/projects"), + &state.api_semaphore, + &state.pool, + ) + .await +} + +#[tracing::instrument] +pub async fn get_user_organizations(user_id: &str) -> crate::Result { + let state = State::get().await?; + let user_id = urlencoding::encode(user_id); + + fetch_json( + Method::GET, + &format!( + "{}user/{}/organizations", + env!("MODRINTH_API_URL_V3"), + user_id + ), + None, + None, + Some("/v3/user/:id/organizations"), + &state.api_semaphore, + &state.pool, + ) + .await +} + +#[tracing::instrument] +pub async fn get_user_collections(user_id: &str) -> crate::Result { + let state = State::get().await?; + let user_id = urlencoding::encode(user_id); + + fetch_json( + Method::GET, + &format!( + "{}user/{}/collections", + env!("MODRINTH_API_URL_V3"), + user_id + ), + None, + None, + Some("/v3/user/:id/collections"), + &state.api_semaphore, + &state.pool, + ) + .await +} + +#[tracing::instrument(skip(patch))] +pub async fn patch_user(user_id: &str, patch: Value) -> crate::Result<()> { + let state = State::get().await?; + let user_id = urlencoding::encode(user_id); + + fetch_advanced( + Method::PATCH, + &format!("{}user/{}", env!("MODRINTH_API_URL"), user_id), + None, + Some(patch), + None, + None, + None, + Some("/v2/user/:id"), + &state.api_semaphore, + &state.pool, + ) + .await?; + + Ok(()) } diff --git a/apps/frontend/src/components/ui/UserPageHeader.vue b/packages/ui/src/components/user/UserPageHeader.vue similarity index 84% rename from apps/frontend/src/components/ui/UserPageHeader.vue rename to packages/ui/src/components/user/UserPageHeader.vue index dfa94c475d..1392db380e 100644 --- a/apps/frontend/src/components/ui/UserPageHeader.vue +++ b/packages/ui/src/components/user/UserPageHeader.vue @@ -58,10 +58,10 @@