From 6e1e699083e07cdc6e14914c7fb0e541a3f16075 Mon Sep 17 00:00:00 2001 From: Luc <luc@lucemans.nl> Date: Thu, 19 Dec 2024 01:27:39 +0100 Subject: [PATCH] Introduce user settings and subtexts --- engine/src/models/user/userentry.rs | 6 ++++ engine/src/routes/users/mod.rs | 23 +++++++++++++- web/src/api/schema.gen.ts | 38 +++++++++++++++++++++++ web/src/api/user/index.ts | 12 +++++++ web/src/index.css | 4 +++ web/src/routes/settings/_layout/build.tsx | 20 ++++++++++-- web/src/routes/settings/_layout/users.tsx | 28 ++++++++++++++++- 7 files changed, 127 insertions(+), 4 deletions(-) diff --git a/engine/src/models/user/userentry.rs b/engine/src/models/user/userentry.rs index c3ddad2..b326b9d 100644 --- a/engine/src/models/user/userentry.rs +++ b/engine/src/models/user/userentry.rs @@ -21,6 +21,12 @@ impl UserEntry { self.oauth_sub == "$$SYSTEM$$" } + pub async fn find_all(database: &Database) -> Result<Vec<UserEntry>, sqlx::Error> { + query_as!(UserEntry, "SELECT user_id, oauth_sub, oauth_data::text::json as \"oauth_data!: Json<Userinfo>\", nickname, created_at, updated_at FROM users") + .fetch_all(&database.pool) + .await + } + pub async fn upsert( oauth_userinfo: &Userinfo, nickname: Option<String>, diff --git a/engine/src/routes/users/mod.rs b/engine/src/routes/users/mod.rs index 71d7d4f..4ace889 100644 --- a/engine/src/routes/users/mod.rs +++ b/engine/src/routes/users/mod.rs @@ -6,7 +6,7 @@ use reqwest::StatusCode; use super::{error::HttpError, ApiTags}; use crate::{ - auth::middleware::AuthUser, + auth::{middleware::AuthUser, permissions::Action}, models::user::{user::User, userentry::UserEntry}, state::AppState, }; @@ -17,6 +17,27 @@ pub struct UserApi; #[OpenApi] impl UserApi { + /// /user + /// + /// List all users + #[oai(path = "/user", method = "get", tag = "ApiTags::User")] + pub async fn users( + &self, + user: AuthUser, + state: Data<&Arc<AppState>>, + ) -> Result<Json<Vec<User>>> { + user.check_policy("user", "", Action::Read).await?; + + Ok(Json( + UserEntry::find_all(&state.database) + .await + .map_err(HttpError::from)? + .into_iter() + .map(|user| user.into()) + .collect::<Vec<User>>(), + )) + } + /// /user/:user_id /// /// Get a User by `user_id` diff --git a/web/src/api/schema.gen.ts b/web/src/api/schema.gen.ts index fb32993..7f2aad1 100644 --- a/web/src/api/schema.gen.ts +++ b/web/src/api/schema.gen.ts @@ -1327,6 +1327,44 @@ export type paths = { patch?: never; trace?: never; }; + "/user": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * /user + * @description List all users + */ + get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json; charset=utf-8": components["schemas"]["User"][]; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/user/{user_id}": { parameters: { query?: never; diff --git a/web/src/api/user/index.ts b/web/src/api/user/index.ts index a7c4bc8..55772aa 100644 --- a/web/src/api/user/index.ts +++ b/web/src/api/user/index.ts @@ -20,3 +20,15 @@ export const getUserById = (user_id: number) => }); export const useUserById = (user_id: number) => useQuery(getUserById(user_id)); + +export const getUsers = () => + queryOptions({ + queryKey: ['users'], + queryFn: async () => { + const response = await apiRequest('/user', 'get', {}); + + return response.data; + }, + }); + +export const useUsers = () => useQuery(getUsers()); diff --git a/web/src/index.css b/web/src/index.css index 26376d7..5afac88 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -96,6 +96,10 @@ a { all: unset; } +.link { + @apply text-blue-500 hover:cursor-pointer hover:underline; +} + .pre { @apply bg-neutral-100 rounded-md p-2; } diff --git a/web/src/routes/settings/_layout/build.tsx b/web/src/routes/settings/_layout/build.tsx index 4fa3be4..dea2284 100644 --- a/web/src/routes/settings/_layout/build.tsx +++ b/web/src/routes/settings/_layout/build.tsx @@ -1,4 +1,5 @@ -import { createFileRoute } from '@tanstack/react-router'; +import { createFileRoute, Link } from '@tanstack/react-router'; +import { FiHeart } from 'react-icons/fi'; import { BuildDetails } from '@/components/settings/BuildDetails'; @@ -12,5 +13,20 @@ export const Route = createFileRoute('/settings/_layout/build')({ }); function RouteComponent() { - return <BuildDetails />; + return ( + <> + <BuildDetails /> + <p className="text-sm text-gray-500 px-4 flex items-center gap-1"> + We thank you for using open-source software.{' '} + <Link + href="https://v3x.company" + className="link" + target="_blank" + > + V3X Labs + </Link>{' '} + <FiHeart className="text-xs" /> + </p> + </> + ); } diff --git a/web/src/routes/settings/_layout/users.tsx b/web/src/routes/settings/_layout/users.tsx index 5219108..ca855fd 100644 --- a/web/src/routes/settings/_layout/users.tsx +++ b/web/src/routes/settings/_layout/users.tsx @@ -1,5 +1,8 @@ import { createFileRoute } from '@tanstack/react-router'; +import { useUsers } from '@/api/user'; +import { UserProfile } from '@/components/UserProfile'; + export const Route = createFileRoute('/settings/_layout/users')({ component: RouteComponent, context() { @@ -10,5 +13,28 @@ export const Route = createFileRoute('/settings/_layout/users')({ }); function RouteComponent() { - return <div>Hello "/settings/users"!</div>; + const { data } = useUsers(); + + return ( + <> + <ul className="space-y-2"> + {data + ?.filter((user) => user.user_id !== 1) + .map((user) => ( + <li key={user.user_id}> + <div className="card no-padding p-2"> + <UserProfile + user_id={user.user_id} + variant="full" + /> + </div> + </li> + ))} + </ul> + <p className="text-sm text-gray-500 px-4"> + With OAuth configured users will get automatically added here as + soon as they log in the for the first time. + </p> + </> + ); }