Compare commits
2 Commits
661841ceef
...
f1fe81a146
| Author | SHA1 | Date | |
|---|---|---|---|
| f1fe81a146 | |||
| bc059de65b |
@@ -1,19 +1,19 @@
|
||||
import os
|
||||
from typing import Any, Optional
|
||||
from typing import Any
|
||||
|
||||
from beanie import PydanticObjectId
|
||||
from beanie import PydanticObjectId, Document
|
||||
from fastapi import Depends, Response, status
|
||||
from fastapi_users import BaseUserManager, FastAPIUsers, schemas, models
|
||||
from fastapi_users.authentication import AuthenticationBackend, BearerTransport, CookieTransport, Strategy
|
||||
from fastapi_users.authentication import AuthenticationBackend, CookieTransport, Strategy
|
||||
from fastapi_users.authentication.strategy import AccessTokenDatabase, DatabaseStrategy
|
||||
from fastapi_users_db_beanie.access_token import BeanieBaseAccessTokenDocument, BeanieAccessTokenDatabase
|
||||
from fastapi_users_db_beanie.access_token import BeanieBaseAccessToken, BeanieAccessTokenDatabase
|
||||
from fastapi_users.openapi import OpenAPIResponseType
|
||||
from httpx_oauth.clients.google import GoogleOAuth2
|
||||
from httpx_oauth.clients.discord import DiscordOAuth2
|
||||
from starlette.responses import JSONResponse, RedirectResponse
|
||||
|
||||
from hub.user import User, get_user_db
|
||||
from hub.user.schemas import UserSchema
|
||||
from hub.user.schemas import UserSchema, UserUpdateSchema
|
||||
|
||||
|
||||
SECRET = os.getenv("FASTAPI_USERS_SECRET")
|
||||
@@ -23,7 +23,7 @@ discord_oauth_client = DiscordOAuth2(os.getenv("DISCORD_CLIENT_ID"), os.getenv("
|
||||
TOKEN_LIFETIME = 3600
|
||||
|
||||
|
||||
class AccessToken(BeanieBaseAccessTokenDocument):
|
||||
class AccessToken(BeanieBaseAccessToken, Document):
|
||||
pass
|
||||
|
||||
async def get_access_token_db():
|
||||
@@ -84,10 +84,11 @@ auth_router = fastapi_users.get_auth_router(auth_backend, requires_verification=
|
||||
register_router = fastapi_users.get_register_router(UserSchema, schemas.BaseUserCreate)
|
||||
password_router = fastapi_users.get_reset_password_router()
|
||||
verification_router = fastapi_users.get_verify_router(UserSchema)
|
||||
users_router = fastapi_users.get_users_router(UserSchema, schemas.BaseUserUpdate)
|
||||
users_router = fastapi_users.get_users_router(UserSchema, UserUpdateSchema)
|
||||
|
||||
cookie_transport = CookieTransportOauth(cookie_name="rpkapiusersauth")
|
||||
auth_backend = AuthenticationBackend(name="db", transport=cookie_transport, get_strategy=get_database_strategy, )
|
||||
|
||||
google_oauth_router = fastapi_users.get_oauth_router(google_oauth_client, auth_backend, SECRET, is_verified_by_default=True)
|
||||
discord_oauth_router = fastapi_users.get_oauth_router(discord_oauth_client, auth_backend, SECRET, is_verified_by_default=True)
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ class FirmRead(BaseModel):
|
||||
name: str = Field()
|
||||
|
||||
class FirmCreate(FirmRead):
|
||||
pass
|
||||
instance: str = Field(max_length=32, min_length=3, pattern="^[0-9a-z-]+$")
|
||||
name: str = Field(max_length=32, min_length=3, pattern="^[0-9a-z-]+$")
|
||||
|
||||
class FirmUpdate(FirmRead):
|
||||
pass
|
||||
class FirmUpdate(BaseModel):
|
||||
owner: PydanticObjectId = Field()
|
||||
|
||||
@@ -4,44 +4,41 @@ from fastapi import APIRouter, Depends, HTTPException
|
||||
from hub.auth import get_current_user
|
||||
from hub.firm import Firm, FirmRead, FirmCreate, FirmUpdate
|
||||
|
||||
model = Firm
|
||||
model_read = FirmRead
|
||||
model_create = FirmCreate
|
||||
model_update = FirmUpdate
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/", response_description="{} added to the database".format(model.__name__))
|
||||
async def create(item: model_create, user=Depends(get_current_user)) -> dict:
|
||||
exists = await Firm.find_one({"name": item.name, "instance": item.instance})
|
||||
@router.post("/", response_description="{} added to the database".format(Firm.__name__))
|
||||
async def create(item: FirmCreate, user=Depends(get_current_user)) -> FirmRead:
|
||||
firm_dict = {"name": item.name, "instance": item.instance}
|
||||
exists = await Firm.find_one(firm_dict)
|
||||
if exists:
|
||||
raise HTTPException(status_code=400, detail="Firm already exists")
|
||||
|
||||
item.created_by = user.id
|
||||
item.updated_by = user.id
|
||||
item.owner = user.id
|
||||
o = await model(**item.model_dump()).create()
|
||||
return model_read(**o.model_dump())
|
||||
record = Firm(created_by=user.id, updated_by=user.id, owner=user.id, **item.model_dump())
|
||||
o = await record.create()
|
||||
user.firms.append(firm_dict)
|
||||
await user.save()
|
||||
return FirmRead(**o.model_dump())
|
||||
|
||||
@router.get("/{id}", response_description="{} record retrieved".format(model.__name__))
|
||||
async def read_id(id: PydanticObjectId, user=Depends(get_current_user)) -> model_read:
|
||||
item = await model.get(id)
|
||||
return model_read(**item.model_dump())
|
||||
@router.get("/{id}", response_description="{} record retrieved".format(Firm.__name__))
|
||||
async def read_id(id: PydanticObjectId, user=Depends(get_current_user)) -> FirmRead:
|
||||
item = await Firm.get(id)
|
||||
return FirmRead(**item.model_dump())
|
||||
|
||||
|
||||
@router.put("/{id}", response_description="{} record updated".format(model.__name__))
|
||||
async def update(id: PydanticObjectId, req: model_update, user=Depends(get_current_user)) -> model_read:
|
||||
item = await model.get(id)
|
||||
@router.put("/{id}", response_description="{} record updated".format(Firm.__name__))
|
||||
async def update(id: PydanticObjectId, req: FirmUpdate, user=Depends(get_current_user)) -> FirmRead:
|
||||
item = await Firm.get(id)
|
||||
if not item:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="{} record not found!".format(model.__name__)
|
||||
detail="{} record not found!".format(Firm.__name__)
|
||||
)
|
||||
|
||||
if item.owner != user.id:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="Insufficient credentials to modify {} record".format(model.__name__)
|
||||
detail="Insufficient credentials to modify {} record".format(Firm.__name__)
|
||||
)
|
||||
|
||||
req = {k: v for k, v in req.model_dump().items() if v is not None}
|
||||
@@ -50,23 +47,23 @@ async def update(id: PydanticObjectId, req: model_update, user=Depends(get_curre
|
||||
}}
|
||||
|
||||
await item.update(update_query)
|
||||
return model_read(**item.dict())
|
||||
return FirmRead(**item.dict())
|
||||
|
||||
@router.delete("/{id}", response_description="{} record deleted from the database".format(model.__name__))
|
||||
@router.delete("/{id}", response_description="{} record deleted from the database".format(Firm.__name__))
|
||||
async def delete(id: PydanticObjectId, user=Depends(get_current_user)) -> dict:
|
||||
item = await model.get(id)
|
||||
item = await Firm.get(id)
|
||||
if not item:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="{} record not found!".format(model.__name__)
|
||||
detail="{} record not found!".format(Firm.__name__)
|
||||
)
|
||||
if item.owner != user.id:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="Insufficient credentials delete {} record".format(model.__name__)
|
||||
detail="Insufficient credentials delete {} record".format(Firm.__name__)
|
||||
)
|
||||
|
||||
await item.delete()
|
||||
return {
|
||||
"message": "{} deleted successfully".format(model.__name__)
|
||||
"message": "{} deleted successfully".format(Firm.__name__)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
from fastapi_users_db_beanie import BaseOAuthAccount, BeanieUserDatabase, BeanieBaseUserDocument
|
||||
from beanie import Document
|
||||
from fastapi_users_db_beanie import BaseOAuthAccount, BeanieUserDatabase, BeanieBaseUser
|
||||
from pydantic import Field
|
||||
|
||||
from hub.firm import FirmRead
|
||||
from hub.user.schemas import UserSchema, UserUpdateSchema
|
||||
|
||||
|
||||
class OAuthAccount(BaseOAuthAccount):
|
||||
pass
|
||||
|
||||
class User(BeanieBaseUserDocument):
|
||||
class User(BeanieBaseUser, Document):
|
||||
oauth_accounts: list[OAuthAccount] = Field(default_factory=list)
|
||||
firms: list[FirmRead] = Field(default_factory=list)
|
||||
|
||||
class UserDatabase(BeanieUserDatabase):
|
||||
pass
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
from beanie import PydanticObjectId
|
||||
from fastapi_users.schemas import BaseUser
|
||||
from fastapi_users.schemas import BaseUser, BaseUserUpdate
|
||||
from pydantic import Field
|
||||
|
||||
from hub.firm import FirmRead
|
||||
|
||||
|
||||
class UserSchema(BaseUser[PydanticObjectId]):
|
||||
firms: list[FirmRead] = Field()
|
||||
|
||||
class UserUpdateSchema(BaseUserUpdate):
|
||||
pass
|
||||
|
||||
@@ -13,9 +13,9 @@ import routerBindings, {
|
||||
DocumentTitleHandler,
|
||||
UnsavedChangesNotifier,
|
||||
} from "@refinedev/react-router";
|
||||
import dataProvider from "@refinedev/simple-rest";
|
||||
import { BrowserRouter, Outlet, Route, Routes } from "react-router";
|
||||
import { BrowserRouter, Link, Outlet, Route, Routes } from "react-router";
|
||||
import { authProvider } from "./providers/auth-provider";
|
||||
import { dataProvider } from "./providers/data-provider";
|
||||
import { ColorModeContextProvider } from "./contexts/color-mode";
|
||||
import { Login } from "./components/auth/Login";
|
||||
import { Register } from "./components/auth/Register";
|
||||
@@ -36,7 +36,7 @@ function App() {
|
||||
<RefineSnackbarProvider>
|
||||
<Refine
|
||||
authProvider={authProvider}
|
||||
dataProvider={dataProvider("https://api.fake-rest.refine.dev")}
|
||||
dataProvider={dataProvider}
|
||||
notificationProvider={useNotificationProvider}
|
||||
routerProvider={routerBindings}
|
||||
options={{
|
||||
@@ -58,7 +58,7 @@ function App() {
|
||||
<Route path="/hub" element={ <Hub /> } />
|
||||
<Route path="/hub/create-firm" element={ <CreateFirm /> } />
|
||||
</Route>
|
||||
<Route index element={<h1>HOME</h1>} />
|
||||
<Route index element={<h1>HOME <Link to={"/login"}>Login</Link></h1>} />
|
||||
<Route path="/login" element={<Login />} />
|
||||
<Route path="/register" element={<Register />} />
|
||||
<Route path="/forgot-password" element={<ForgotPassword />} />
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import {Navigate, useSearchParams} from "react-router";
|
||||
import {AuthPage} from "@refinedev/mui";
|
||||
import GoogleIcon from "@mui/icons-material/Google";
|
||||
import DiscordIcon from "../DiscordIcon";
|
||||
import { useLogout } from "@refinedev/core";
|
||||
|
||||
export const Logout = () => {
|
||||
const { mutate: logout } = useLogout();
|
||||
|
||||
return <button onClick={() => logout()}>Logout</button>;
|
||||
return <button onClick={() => logout()} >Logout</button>;
|
||||
};
|
||||
|
||||
@@ -10,13 +10,8 @@ import { useGetIdentity } from "@refinedev/core";
|
||||
import { HamburgerMenu, RefineThemedLayoutV2HeaderProps } from "@refinedev/mui";
|
||||
import React, { useContext } from "react";
|
||||
import { ColorModeContext } from "../../contexts/color-mode";
|
||||
import {Logout} from "../auth/Logout";
|
||||
|
||||
type IUser = {
|
||||
id: number;
|
||||
email: string;
|
||||
avatar: string;
|
||||
};
|
||||
import { Logout } from "../auth/Logout";
|
||||
import { IUser } from "../../interfaces";
|
||||
|
||||
export const Header: React.FC<RefineThemedLayoutV2HeaderProps> = ({
|
||||
sticky = true,
|
||||
@@ -50,7 +45,7 @@ export const Header: React.FC<RefineThemedLayoutV2HeaderProps> = ({
|
||||
{mode === "dark" ? <LightModeOutlined /> : <DarkModeOutlined />}
|
||||
</IconButton>
|
||||
|
||||
{(user?.avatar || user?.email) && (
|
||||
{(user?.email) && (
|
||||
<Stack
|
||||
direction="row"
|
||||
gap="16px"
|
||||
@@ -70,7 +65,7 @@ export const Header: React.FC<RefineThemedLayoutV2HeaderProps> = ({
|
||||
{user?.email}
|
||||
</Typography>
|
||||
)}
|
||||
<Avatar src={user?.avatar} alt={user?.email} />
|
||||
<Avatar src={"user?.avatar"} alt={user?.email} />
|
||||
<Logout />
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
13
gui/rpk-gui/src/interfaces/index.tsx
Normal file
13
gui/rpk-gui/src/interfaces/index.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
|
||||
export type IFirm = {
|
||||
instance: string,
|
||||
name: string
|
||||
}
|
||||
|
||||
type User = {
|
||||
id: number,
|
||||
email: string,
|
||||
firms: [IFirm],
|
||||
};
|
||||
|
||||
export type IUser = User | null;
|
||||
@@ -8,10 +8,11 @@ import CrudTextWidget from "./widgets/crud-text-widget";
|
||||
import UnionEnumField from "./fields/union-enum";
|
||||
|
||||
type Props = {
|
||||
schemaName: string,
|
||||
resource: string,
|
||||
id?: string,
|
||||
//onSubmit: (data: IChangeEvent, event: FormEvent<any>) => void
|
||||
schemaName: string,
|
||||
resource: string,
|
||||
id?: string,
|
||||
//onSubmit: (data: IChangeEvent, event: FormEvent<any>) => void
|
||||
onSuccess?: (data: any) => void
|
||||
}
|
||||
|
||||
const customWidgets: RegistryWidgetsType = {
|
||||
@@ -22,12 +23,13 @@ const customFields: RegistryFieldsType = {
|
||||
AnyOfField: UnionEnumField
|
||||
}
|
||||
|
||||
export const CrudForm: React.FC<Props> = ({schemaName, resource, id}) => {
|
||||
export const CrudForm: React.FC<Props> = ({ schemaName, resource, id, onSuccess }) => {
|
||||
const { onFinish, query, formLoading } = useForm({
|
||||
resource: resource,
|
||||
action: id === undefined ? "create" : "edit",
|
||||
redirect: "show",
|
||||
id,
|
||||
onMutationSuccess: (data: any) => { if (onSuccess) { onSuccess(data) } },
|
||||
});
|
||||
|
||||
const record = query?.data?.data;
|
||||
|
||||
@@ -1,7 +1,19 @@
|
||||
import { useInvalidateAuthStore } from "@refinedev/core";
|
||||
import { CrudForm } from "../../lib/crud/components/crud-form";
|
||||
import {empty_user} from "../../providers/auth-provider";
|
||||
|
||||
export const CreateFirm = () => {
|
||||
const invalidateAuthStore = useInvalidateAuthStore()
|
||||
const refreshUser = () => {
|
||||
empty_user();
|
||||
invalidateAuthStore().then();
|
||||
}
|
||||
|
||||
return (
|
||||
<CrudForm schemaName={"FirmCreate"} resource={"firms"} />
|
||||
<CrudForm
|
||||
schemaName={"FirmCreate"}
|
||||
resource={"firms"}
|
||||
onSuccess={() => { refreshUser() }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,12 +1,38 @@
|
||||
import { Button } from "@mui/material";
|
||||
import { Link } from "react-router";
|
||||
import { useGetIdentity } from "@refinedev/core";
|
||||
|
||||
type Firm = {
|
||||
name: string,
|
||||
instance: string,
|
||||
}
|
||||
type User = {
|
||||
firms: [Firm],
|
||||
}
|
||||
|
||||
export const Hub = () => {
|
||||
const user = useGetIdentity<User>();
|
||||
|
||||
console.log(user);
|
||||
let ownFirms = [];
|
||||
let workFirms = [];
|
||||
//firms.forEach((f, index) => {
|
||||
// workFirms.push(<li>{f.instance}/{f.name}</li>)
|
||||
//})
|
||||
//{firms.map((f: Firm, index) => (
|
||||
// <li key={index}>{f.instance} / {f.name}</li>
|
||||
// ))}
|
||||
return (
|
||||
<div>
|
||||
<h1>HUB</h1>
|
||||
<p>List of managed firms</p>
|
||||
<p>List of firm you're working atx</p>
|
||||
<ul>
|
||||
<li></li>
|
||||
</ul>
|
||||
<p>List of firm you're working at</p>
|
||||
<ul>
|
||||
|
||||
</ul>
|
||||
<Link to="/hub/create-firm" ><Button >Create a new firm</Button></Link>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { AuthProvider } from "@refinedev/core";
|
||||
import {IUser} from "../interfaces";
|
||||
|
||||
const API_URL = "/api/v1";
|
||||
const LOCAL_STORAGE_USER_KEY = "rpk-gui-current-user";
|
||||
@@ -55,17 +57,24 @@ export const authProvider: AuthProvider = {
|
||||
return { success: false };
|
||||
},
|
||||
check: async () => {
|
||||
return { authenticated: Boolean(get_user()) };
|
||||
if (get_user() == null) {
|
||||
return {
|
||||
authenticated: false,
|
||||
redirectTo: "/login",
|
||||
logout: true
|
||||
}
|
||||
}
|
||||
return { authenticated: true };
|
||||
},
|
||||
getIdentity: async () => {
|
||||
getIdentity: async (): Promise<IUser> => {
|
||||
const user = get_user();
|
||||
if (user != null) {
|
||||
if (user !== null && !isEmpty(user)) {
|
||||
return user;
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_URL}/users/me`);
|
||||
if (response.status < 200 || response.status > 299) {
|
||||
return
|
||||
return null;
|
||||
}
|
||||
const user_data = await response.json();
|
||||
store_user(user_data)
|
||||
@@ -163,6 +172,10 @@ function forget_user() {
|
||||
localStorage.removeItem(LOCAL_STORAGE_USER_KEY);
|
||||
}
|
||||
|
||||
export function empty_user() {
|
||||
store_user({})
|
||||
}
|
||||
|
||||
function findGetParameter(parameterName: string) {
|
||||
let result = null, tmp = [];
|
||||
location.search.substr(1).split("&")
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
import type { DataProvider } from "@refinedev/core";
|
||||
|
||||
const API_URL = "http://localhost:8000";
|
||||
|
||||
const fetcher = async (url: string, options?: RequestInit) => {
|
||||
return fetch(url, {
|
||||
...options,
|
||||
headers: {
|
||||
...options?.headers,
|
||||
Authorization: "Bearer " + localStorage.getItem("access_token"),
|
||||
},
|
||||
});
|
||||
};
|
||||
const API_URL = "/api/v1";
|
||||
|
||||
export const dataProvider: DataProvider = {
|
||||
getOne: async ({ resource, id, meta }) => {
|
||||
const response = id !== "" ? await fetcher(`${API_URL}/${resource}/${id}`) : await fetcher(`${API_URL}/${resource}`);
|
||||
const response = id !== "" ? await fetch(`${API_URL}/${resource}/${id}`) : await fetch(`${API_URL}/${resource}`);
|
||||
if (response.status < 200 || response.status > 299) throw response;
|
||||
|
||||
const data = await response.json();
|
||||
@@ -24,7 +14,7 @@ export const dataProvider: DataProvider = {
|
||||
};
|
||||
},
|
||||
update: async ({ resource, id, variables }) => {
|
||||
const response = await fetcher(`${API_URL}/${resource}/${id}`, {
|
||||
const response = await fetch(`${API_URL}/${resource}/${id}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify(variables),
|
||||
headers: {
|
||||
@@ -58,7 +48,7 @@ export const dataProvider: DataProvider = {
|
||||
});
|
||||
}
|
||||
|
||||
const response = await fetcher(`${API_URL}/${resource}?${params.toString()}`);
|
||||
const response = await fetch(`${API_URL}/${resource}?${params.toString()}`);
|
||||
|
||||
if (response.status < 200 || response.status > 299) throw response;
|
||||
|
||||
@@ -70,7 +60,7 @@ export const dataProvider: DataProvider = {
|
||||
};
|
||||
},
|
||||
create: async ({ resource, variables }) => {
|
||||
const response = await fetcher(`${API_URL}/${resource}`, {
|
||||
const response = await fetch(`${API_URL}/${resource}`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(variables),
|
||||
headers: {
|
||||
@@ -85,7 +75,7 @@ export const dataProvider: DataProvider = {
|
||||
return { data };
|
||||
},
|
||||
deleteOne: async ({ resource, id, variables, meta }) => {
|
||||
const response = await fetcher(`${API_URL}/${resource}/${id}`,{
|
||||
const response = await fetch(`${API_URL}/${resource}/${id}`,{
|
||||
method: "DELETE",
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user