,
+ ) => {
+ event.preventDefault();
+
+ const { data, error } = await auth.client.GET("/users/github/login");
+ if (error) {
+ addErrorAlert(error);
+ } else {
+ window.open(data, "_self");
+ }
+ };
+
+ return (
+
+ );
+};
+
+const AuthProvider = () => {
return (
@@ -79,19 +135,10 @@ const AuthProvider = ({ handleGithubSubmit }: AuthProvider) => {
{/* Google */}
-
-
-
+
{/* Github */}
-
+
);
diff --git a/frontend/src/constants/env.ts b/frontend/src/constants/env.ts
index 957495af..16f7c6ab 100644
--- a/frontend/src/constants/env.ts
+++ b/frontend/src/constants/env.ts
@@ -1,3 +1,2 @@
export const BACKEND_URL =
import.meta.env.VITE_APP_BACKEND_URL || "http://127.0.0.1:8080";
-export const GOOGLE_CLIENT_ID = import.meta.env.VITE_GOOGLE_CLIENT_ID || "";
diff --git a/frontend/src/gen/api.ts b/frontend/src/gen/api.ts
index e88f4f11..3eb37db4 100644
--- a/frontend/src/gen/api.ts
+++ b/frontend/src/gen/api.ts
@@ -114,8 +114,8 @@ export interface paths {
path?: never;
cookie?: never;
};
- /** Get Users Batch Endpoint */
- get: operations["get_users_batch_endpoint_users_public_batch_get"];
+ /** Get Users Public Batch Endpoint */
+ get: operations["get_users_public_batch_endpoint_users_public_batch_get"];
put?: never;
post?: never;
delete?: never;
@@ -141,6 +141,23 @@ export interface paths {
patch?: never;
trace?: never;
};
+ "/users/public/me": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Get My Public User Info Endpoint */
+ get: operations["get_my_public_user_info_endpoint_users_public_me_get"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
"/users/public/{id}": {
parameters: {
query?: never;
@@ -209,6 +226,23 @@ export interface paths {
patch?: never;
trace?: never;
};
+ "/users/google/client-id": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Google Client Id Endpoint */
+ get: operations["google_client_id_endpoint_users_google_client_id_get"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
"/users/google/login": {
parameters: {
query?: never;
@@ -526,6 +560,11 @@ export interface components {
/** Metadata */
metadata: string;
};
+ /** ClientIdResponse */
+ ClientIdResponse: {
+ /** Client Id */
+ client_id: string;
+ };
/** DeleteTokenResponse */
DeleteTokenResponse: {
/** Message */
@@ -678,6 +717,19 @@ export interface components {
/** Token */
token: string;
};
+ /** MyUserInfoResponse */
+ MyUserInfoResponse: {
+ /** User Id */
+ user_id: string;
+ /** Email */
+ email: string;
+ /** Github Id */
+ github_id: string | null;
+ /** Google Id */
+ google_id: string | null;
+ /** Permissions */
+ permissions: "is_admin"[] | null;
+ };
/** NewListingRequest */
NewListingRequest: {
/** Name */
@@ -692,13 +744,8 @@ export interface components {
/** Listing Id */
listing_id: string;
};
- /** PublicUserInfoResponse */
- PublicUserInfoResponse: {
- /** Users */
- users: components["schemas"]["SinglePublicUserInfoResponseItem"][];
- };
- /** SinglePublicUserInfoResponseItem */
- SinglePublicUserInfoResponseItem: {
+ /** PublicUserInfoResponseItem */
+ PublicUserInfoResponseItem: {
/** Id */
id: string;
/** Email */
@@ -718,12 +765,10 @@ export interface components {
/** Bio */
bio?: string | null;
};
- /** SingleUserInfoResponseItem */
- SingleUserInfoResponseItem: {
- /** Id */
- id: string;
- /** Email */
- email: string;
+ /** PublicUsersInfoResponse */
+ PublicUsersInfoResponse: {
+ /** Users */
+ users: components["schemas"]["PublicUserInfoResponseItem"][];
};
/** UpdateArtifactRequest */
UpdateArtifactRequest: {
@@ -747,18 +792,12 @@ export interface components {
UploadArtifactResponse: {
artifact: components["schemas"]["ListArtifactsItem"];
};
- /** UserInfoResponse */
- UserInfoResponse: {
- /** User Id */
- user_id: string;
+ /** UserInfoResponseItem */
+ UserInfoResponseItem: {
+ /** Id */
+ id: string;
/** Email */
email: string;
- /** Github Id */
- github_id: string | null;
- /** Google Id */
- google_id: string | null;
- /** Permissions */
- permissions: "is_admin"[] | null;
};
/**
* UserPublic
@@ -849,7 +888,7 @@ export interface operations {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["UserInfoResponse"];
+ "application/json": components["schemas"]["MyUserInfoResponse"];
};
};
};
@@ -913,7 +952,7 @@ export interface operations {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["SingleUserInfoResponseItem"];
+ "application/json": components["schemas"]["UserInfoResponseItem"];
};
};
/** @description Validation Error */
@@ -977,7 +1016,7 @@ export interface operations {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["PublicUserInfoResponse"];
+ "application/json": components["schemas"]["PublicUsersInfoResponse"];
};
};
/** @description Validation Error */
@@ -991,7 +1030,7 @@ export interface operations {
};
};
};
- get_users_batch_endpoint_users_public_batch_get: {
+ get_users_public_batch_endpoint_users_public_batch_get: {
parameters: {
query: {
ids: string[];
@@ -1008,7 +1047,7 @@ export interface operations {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["PublicUserInfoResponse"];
+ "application/json": components["schemas"]["PublicUsersInfoResponse"];
};
};
/** @description Validation Error */
@@ -1039,7 +1078,7 @@ export interface operations {
[name: string]: unknown;
};
content: {
- "application/json": components["schemas"]["SingleUserInfoResponseItem"];
+ "application/json": components["schemas"]["UserInfoResponseItem"];
};
};
/** @description Validation Error */
@@ -1053,6 +1092,26 @@ export interface operations {
};
};
};
+ get_my_public_user_info_endpoint_users_public_me_get: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["UserPublic"];
+ };
+ };
+ };
+ };
get_public_user_info_by_id_endpoint_users_public__id__get: {
parameters: {
query?: never;
@@ -1137,6 +1196,26 @@ export interface operations {
};
};
};
+ google_client_id_endpoint_users_google_client_id_get: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["ClientIdResponse"];
+ };
+ };
+ };
+ };
google_login_endpoint_users_google_login_post: {
parameters: {
query?: never;
diff --git a/frontend/src/pages/Profile.tsx b/frontend/src/pages/Profile.tsx
index fb8517d1..a0de72b2 100644
--- a/frontend/src/pages/Profile.tsx
+++ b/frontend/src/pages/Profile.tsx
@@ -42,29 +42,35 @@ const ProfileDetails = () => {
useEffect(() => {
const fetchUser = async () => {
if (id === undefined) {
- return;
- }
-
- try {
- const { data, error } = await auth.client.GET("/users/public/{id}", {
- params: {
- path: { id },
- },
- });
+ const { data, error } = await auth.client.GET("/users/public/me");
if (error) {
addErrorAlert(error);
} else {
- setUser(data as UserResponse); // Ensure correct typing
+ setUser(data);
+ }
+ } else {
+ try {
+ const { data, error } = await auth.client.GET("/users/public/{id}", {
+ params: {
+ path: { id },
+ },
+ });
+
+ if (error) {
+ addErrorAlert(error);
+ } else {
+ setUser(data);
+ }
+ } catch (err) {
+ addErrorAlert(err);
}
- } catch (err) {
- addErrorAlert(err);
}
};
fetchUser();
}, [id]);
- return user && id ? (
+ return user ? (
) : (
diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts
index c3241695..3eaa257a 100644
--- a/frontend/src/vite-env.d.ts
+++ b/frontend/src/vite-env.d.ts
@@ -1,6 +1,5 @@
interface ImportMetaEnv {
readonly VITE_APP_BACKEND_URL: string;
- readonly VITE_GOOGLE_CLIENT_ID: string;
}
interface ImportMeta {
diff --git a/store/app/routers/auth/google.py b/store/app/routers/auth/google.py
index 8cb3bdf5..91848a78 100644
--- a/store/app/routers/auth/google.py
+++ b/store/app/routers/auth/google.py
@@ -8,6 +8,7 @@
from pydantic.main import BaseModel
from store.app.db import Crud
+from store.settings import settings
logger = logging.getLogger(__name__)
@@ -29,11 +30,20 @@ async def get_google_user_email(token: str) -> str:
return (await response.json())["email"]
+class ClientIdResponse(BaseModel):
+ client_id: str
+
+
+@google_auth_router.get("/client-id", response_model=ClientIdResponse)
+async def google_client_id_endpoint() -> ClientIdResponse:
+ return ClientIdResponse(client_id=settings.oauth.google_client_id)
+
+
class AuthResponse(BaseModel):
api_key: str
-@google_auth_router.post("/login")
+@google_auth_router.post("/login", response_model=AuthResponse)
async def google_login_endpoint(
data: GoogleLogin,
crud: Annotated[Crud, Depends(Crud.get)],
diff --git a/store/app/routers/users.py b/store/app/routers/users.py
index 1f1f1a34..d75d71f7 100644
--- a/store/app/routers/users.py
+++ b/store/app/routers/users.py
@@ -2,7 +2,7 @@
import logging
from email.utils import parseaddr as parse_email_address
-from typing import Annotated, Literal, overload
+from typing import Annotated, Literal, Self, overload
from fastapi import APIRouter, Depends, HTTPException, Query, Request, status
from fastapi.security.utils import get_authorization_scheme_param
@@ -137,7 +137,7 @@ class UserSignup(BaseModel):
password: str
-class UserInfoResponse(BaseModel):
+class MyUserInfoResponse(BaseModel):
user_id: str
email: str
github_id: str | None
@@ -145,12 +145,12 @@ class UserInfoResponse(BaseModel):
permissions: set[UserPermission] | None
-@users_router.get("/me", response_model=UserInfoResponse)
+@users_router.get("/me", response_model=MyUserInfoResponse)
async def get_user_info_endpoint(
user: Annotated[User, Depends(get_session_user_with_read_permission)],
-) -> UserInfoResponse | None:
+) -> MyUserInfoResponse | None:
try:
- return UserInfoResponse(
+ return MyUserInfoResponse(
user_id=user.id,
email=user.email,
google_id=user.google_id,
@@ -180,16 +180,23 @@ async def logout_user_endpoint(
return True
-class SingleUserInfoResponseItem(BaseModel):
+class UserInfoResponseItem(BaseModel):
id: str
email: str
+ @classmethod
+ def from_user(cls, user: User) -> Self:
+ return cls(
+ id=user.id,
+ email=user.email,
+ )
+
-class UserInfoResponse(BaseModel):
- users: list[SingleUserInfoResponseItem]
+class UsersInfoResponse(BaseModel):
+ users: list[UserInfoResponseItem]
-class SinglePublicUserInfoResponseItem(BaseModel):
+class PublicUserInfoResponseItem(BaseModel):
id: str
email: str
permissions: set[UserPermission] | None = None
@@ -200,15 +207,29 @@ class SinglePublicUserInfoResponseItem(BaseModel):
name: str | None = None
bio: str | None = None
+ @classmethod
+ def from_user(cls, user: User | UserPublic) -> Self:
+ return cls(
+ id=user.id,
+ email=user.email,
+ permissions=user.permissions,
+ created_at=user.created_at,
+ updated_at=user.updated_at,
+ first_name=user.first_name,
+ last_name=user.last_name,
+ name=user.name,
+ bio=user.bio,
+ )
+
-class PublicUserInfoResponse(BaseModel):
- users: list[SinglePublicUserInfoResponseItem]
+class PublicUsersInfoResponse(BaseModel):
+ users: list[PublicUserInfoResponseItem]
-@users_router.post("/signup", response_model=SingleUserInfoResponseItem)
+@users_router.post("/signup", response_model=UserInfoResponseItem)
async def register_user(
data: UserSignup, email_signup_crud: EmailSignUpCrud = Depends(), user_crud: UserCrud = Depends()
-) -> SingleUserInfoResponseItem:
+) -> UserInfoResponseItem:
async with email_signup_crud, user_crud:
signup_token = await email_signup_crud.get_email_signup_token(data.signup_token_id)
if not signup_token:
@@ -221,7 +242,7 @@ async def register_user(
user = await user_crud._create_user_from_email(email=signup_token.email, password=data.password)
# Delete the signup token
await email_signup_crud.delete_email_signup_token(data.signup_token_id)
- return SingleUserInfoResponseItem(id=user.id, email=user.email)
+ return UserInfoResponseItem(id=user.id, email=user.email)
class LoginRequest(BaseModel):
@@ -255,40 +276,48 @@ async def login_user(data: LoginRequest, user_crud: UserCrud = Depends()) -> Log
return LoginResponse(user_id=user.id, token=api_key.id)
-@users_router.get("/batch", response_model=PublicUserInfoResponse)
+@users_router.get("/batch", response_model=PublicUsersInfoResponse)
async def get_users_batch_endpoint(
crud: Annotated[Crud, Depends(Crud.get)],
ids: list[str] = Query(...),
-) -> PublicUserInfoResponse:
+) -> PublicUsersInfoResponse:
users = await crud.get_user_batch(ids)
- return UserInfoResponse(users=[SingleUserInfoResponseItem(user) for user in users])
+ return PublicUsersInfoResponse(users=[PublicUserInfoResponseItem.from_user(user) for user in users])
-@users_router.get("/public/batch", response_model=PublicUserInfoResponse)
+@users_router.get("/public/batch", response_model=PublicUsersInfoResponse)
async def get_users_public_batch_endpoint(
crud: Annotated[Crud, Depends(Crud.get)],
ids: list[str] = Query(...),
-) -> PublicUserInfoResponse:
+) -> PublicUsersInfoResponse:
users = await crud.get_user_batch(ids)
- return PublicUserInfoResponse(users=[SinglePublicUserInfoResponseItem(user) for user in users])
+ return PublicUsersInfoResponse(users=[PublicUserInfoResponseItem.from_user(user) for user in users])
-@users_router.get("/{id}", response_model=SingleUserInfoResponseItem)
-async def get_user_info_by_id_endpoint(id: str, crud: Annotated[Crud, Depends(Crud.get)]) -> SingleUserInfoResponseItem:
+@users_router.get("/{id}", response_model=UserInfoResponseItem)
+async def get_user_info_by_id_endpoint(id: str, crud: Annotated[Crud, Depends(Crud.get)]) -> UserInfoResponseItem:
user = await crud.get_user(id)
if user is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
- return SingleUserInfoResponseItem(user)
+ return UserInfoResponseItem.from_user(user)
+
+
+@users_router.get("/public/me", response_model=UserPublic)
+async def get_my_public_user_info_endpoint(
+ user: Annotated[User, Depends(get_session_user_with_read_permission)],
+) -> PublicUserInfoResponseItem:
+ return PublicUserInfoResponseItem.from_user(user)
@users_router.get("/public/{id}", response_model=UserPublic)
async def get_public_user_info_by_id_endpoint(
- id: str, user_crud: UserCrud = Depends()
-) -> SinglePublicUserInfoResponseItem:
+ id: str,
+ user_crud: Annotated[Crud, Depends(Crud.get)],
+) -> PublicUserInfoResponseItem:
user = await user_crud.get_user_public(id)
if user is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
- return SinglePublicUserInfoResponseItem(user)
+ return PublicUserInfoResponseItem.from_user(user)
users_router.include_router(github_auth_router, prefix="/github")