16 Commits

Author SHA1 Message Date
15bc323ee4 test: add client services 2025-06-10 17:06:19 +05:00
958f00069f test 2025-06-10 17:05:35 +05:00
d7a5109d8e feat: add user store 2025-06-09 17:47:45 +05:00
8122f1878a feat: add zustand 2025-06-09 17:47:26 +05:00
9d2aef5671 feat: add user and auth types 2025-06-09 17:00:27 +05:00
34be97996e feat: add axios instance 2025-06-09 17:00:01 +05:00
e0dca78ef3 feat: add login page 2025-06-09 16:59:31 +05:00
0bba6e7f54 feat: add axios 2025-06-09 16:58:47 +05:00
21216a6ad5 feat: generate types from openapi 2025-06-09 14:32:44 +05:00
2bf0f20e73 Merge pull request 'VORKOUT-7 : fix' (#10) from VORKOUT-7fix into master
Reviewed-on: #10
Reviewed-by: Vladislav Syrochkin <vlad.dev@heado.ru>
2025-06-09 13:06:37 +05:00
abd87b46b3 refactor(tables): change enum, str to StrEnum 2025-06-09 13:03:33 +05:00
787dc0e8f8 refactor: refactor api project with ruff 2025-06-09 12:12:48 +05:00
2e4e9d1113 feat: add bearer schema to all endpoints with auth 2025-06-09 12:10:40 +05:00
c1d315d6e9 feat: add bearer schema and get_current_user function 2025-06-09 11:51:13 +05:00
TheNoxium
a31758192d fix: model 2025-06-09 02:10:46 +05:00
8965365afc Merge pull request 'VORKOUT-7' (#9) from VORKOUT-7 into master
Reviewed-on: #9
Reviewed-by: cyrussmeat <dr.cyrill@gmail.com>
2025-06-05 16:29:14 +05:00
40 changed files with 1321 additions and 230 deletions

View File

@@ -73,6 +73,7 @@ if __name__ == "__main__":
log_level="info",
)
app.add_middleware(MiddlewareAccessTokenValidadtion)
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
@@ -80,5 +81,3 @@ app.add_middleware(
allow_methods=["GET", "POST", "OPTIONS", "DELETE", "PUT"],
allow_headers=["*"],
)
app.add_middleware(MiddlewareAccessTokenValidadtion)

View File

@@ -18,7 +18,8 @@ class DbCredentialsSchema(BaseModel):
class DefaultSettings(BaseSettings):
ENV: str = environ.get("ENV", "local")
PATH_PREFIX: str = environ.get("PATH_PREFIX", "/api/v1")
APP_HOST: str = environ.get("APP_HOST", "http://127.0.0.1")
# APP_HOST: str = environ.get("APP_HOST", "http://127.0.0.1")
APP_HOST: str = environ.get("APP_HOST", "http://localhost")
APP_PORT: int = int(environ.get("APP_PORT", 8000))
APP_ID: uuid.UUID = environ.get("APP_ID", uuid.uuid4())
LOGS_STORAGE_PATH: str = environ.get("LOGS_STORAGE_PATH", "storage/logs")

View File

@@ -18,17 +18,19 @@ async def get_user_accaunt_page(connection: AsyncConnection, page, limit) -> Opt
Получает список ползовелей заданных значениями page, limit.
"""
first_user = page*limit-(limit)
first_user = page * limit - (limit)
query = (
select(account_table.c.id,
account_table.c.name,
account_table.c.login,
account_table.c.email,
account_table.c.bind_tenant_id,
account_table.c.role,
account_table.c.created_at,
account_table.c.status)
select(
account_table.c.id,
account_table.c.name,
account_table.c.login,
account_table.c.email,
account_table.c.bind_tenant_id,
account_table.c.role,
account_table.c.created_at,
account_table.c.status,
)
.order_by(account_table.c.id)
.offset(first_user)
.limit(limit)
@@ -45,11 +47,7 @@ async def get_user_accaunt_page(connection: AsyncConnection, page, limit) -> Opt
validated_users = all_user_adapter.validate_python(users_data)
return AllUserResponse(
users=validated_users,
amount_count=total_count,
amount_pages=total_pages
)
return AllUserResponse(users=validated_users, amount_count=total_count, amount_pages=total_pages)
async def get_user_by_id(connection: AsyncConnection, id: int) -> Optional[User]:

View File

@@ -50,13 +50,12 @@ async def get_user(connection: AsyncConnection, login: str) -> Optional[User]:
return user, password
async def upgrade_old_refresh_token(connection: AsyncConnection, user, refresh_token) -> Optional[User]:
async def upgrade_old_refresh_token(connection: AsyncConnection, refresh_token) -> Optional[User]:
new_status = KeyStatus.EXPIRED
update_query = (
update(account_keyring_table)
.where(
account_table.c.id == user.id,
account_keyring_table.c.status == KeyStatus.ACTIVE,
account_keyring_table.c.key_type == KeyType.REFRESH_TOKEN,
account_keyring_table.c.key_value == refresh_token,

View File

@@ -10,8 +10,6 @@ from api.db.tables.account import account_keyring_table
from api.schemas.account.account_keyring import AccountKeyring
async def get_key_by_id(connection: AsyncConnection, key_id: str) -> Optional[AccountKeyring]:
"""
Получает key по key_id.

View File

@@ -1,3 +1,5 @@
import enum
from sqlalchemy import Table, Column, String, Enum as SQLAEnum, JSON, ForeignKey, DateTime, Index
from sqlalchemy.sql import func
@@ -7,17 +9,18 @@ from api.db.sql_types import UnsignedInt
from api.db import metadata
class AccountRole(str,Enum):
OWNER = 'OWNER'
ADMIN = 'ADMIN'
EDITOR = 'EDITOR'
VIEWER = 'VIEWER'
class AccountRole(enum.StrEnum):
OWNER = "OWNER"
ADMIN = "ADMIN"
EDITOR = "EDITOR"
VIEWER = "VIEWER"
class AccountStatus(str,Enum):
ACTIVE = 'ACTIVE'
DISABLED = 'DISABLED'
BLOCKED = 'BLOCKED'
DELETED = 'DELETED'
class AccountStatus(enum.StrEnum):
ACTIVE = "ACTIVE"
DISABLED = "DISABLED"
BLOCKED = "BLOCKED"
DELETED = "DELETED"
account_table = Table(
@@ -38,13 +41,14 @@ account_table = Table(
)
class KeyType(str,Enum):
class KeyType(enum.StrEnum):
PASSWORD = "PASSWORD"
ACCESS_TOKEN = "ACCESS_TOKEN"
REFRESH_TOKEN = "REFRESH_TOKEN"
API_KEY = "API_KEY"
class KeyStatus(str,Enum):
class KeyStatus(enum.StrEnum):
ACTIVE = "ACTIVE"
EXPIRED = "EXPIRED"
DELETED = "DELETED"

View File

@@ -1,3 +1,5 @@
import enum
from sqlalchemy import Table, Column, Integer, String, Enum as SQLAEnum, JSON, ForeignKey, DateTime, Index
from sqlalchemy.sql import func
from enum import Enum, auto
@@ -7,15 +9,15 @@ from api.db.sql_types import UnsignedInt
from api.db import metadata
class EventState(str, Enum):
AUTO = auto()
DESCRIPTED = auto()
class EventState(enum.StrEnum):
AUTO = "AUTO"
DESCRIPTED = "DESCRIPTED"
class EventStatus(str, Enum):
ACTIVE = auto()
DISABLED = auto()
DELETED = auto()
class EventStatus(enum.StrEnum):
ACTIVE = "ACTIVE"
DISABLED = "DISABLED"
DELETED = "DELETED"
list_events_table = Table(

View File

@@ -1,3 +1,5 @@
import enum
from sqlalchemy import (
Table,
Column,
@@ -19,11 +21,11 @@ from api.db.sql_types import UnsignedInt
from api.db import metadata
class ProcessStatus(str, Enum):
ACTIVE = auto()
STOPPING = auto()
STOPPED = auto()
DELETED = auto()
class ProcessStatus(enum.StrEnum):
ACTIVE = "ACTIVE"
STOPPING = "STOPPING"
STOPPED = "STOPPED"
DELETED = "DELETED"
process_schema_table = Table(
@@ -57,10 +59,10 @@ process_version_archive_table = Table(
)
class NodeStatus(str, Enum):
ACTIVE = auto()
DISABLED = auto()
DELETED = auto()
class NodeStatus(enum.StrEnum):
ACTIVE = "ACTIVE"
DISABLED = "DISABLED"
DELETED = "DELETED"
class NodeType(Enum):
@@ -83,11 +85,11 @@ ps_node_table = Table(
)
class NodeLinkStatus(str, Enum):
ACTIVE = auto()
STOPPING = auto()
STOPPED = auto()
DELETED = auto()
class NodeLinkStatus(enum.StrEnum):
ACTIVE = "ACTIVE"
STOPPING = "STOPPING"
STOPPED = "STOPPED"
DELETED = "DELETED"
node_link_table = Table(

View File

@@ -2,7 +2,6 @@ from fastapi import (
APIRouter,
Depends,
HTTPException,
Request,
status,
)
@@ -11,49 +10,51 @@ from sqlalchemy.ext.asyncio import AsyncConnection
from api.db.connection.session import get_connection_dep
from api.db.logic.account import get_user_by_id, update_user_by_id, create_user,get_user_by_login,get_user_accaunt_page
from api.db.logic.account import (
get_user_by_id,
update_user_by_id,
create_user,
get_user_by_login,
get_user_accaunt_page,
)
from api.schemas.account.account import User
from api.db.tables.account import AccountStatus
from api.schemas.endpoints.account import UserUpdate,AllUserResponse
from api.schemas.base import bearer_schema
from api.schemas.endpoints.account import UserUpdate, AllUserResponse
from api.services.auth import get_current_user
from api.services.user_role_validation import db_user_role_validation
from api.services.update_data_validation import update_user_data_changes
api_router = APIRouter(
prefix="/account",
tags=["User accountModel"],
)
@api_router.get("",response_model=AllUserResponse)
@api_router.get("", dependencies=[Depends(bearer_schema)], response_model=AllUserResponse)
async def get_all_account(
request: Request,
page: int ,
limit: int ,
connection: AsyncConnection = Depends(get_connection_dep)
):
current_user = request.state.current_user
page: int = 1,
limit: int = 10,
connection: AsyncConnection = Depends(get_connection_dep),
current_user=Depends(get_current_user),
):
authorize_user = await db_user_role_validation(connection, current_user)
user_list = await get_user_accaunt_page(connection,page,limit)
user_list = await get_user_accaunt_page(connection, page, limit)
if user_list is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Accounts not found")
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Accounts not found")
return user_list
@api_router.get("/{user_id}", response_model=User)
async def get_account(user_id: int, request: Request, connection: AsyncConnection = Depends(get_connection_dep)):
current_user = request.state.current_user
@api_router.get("/{user_id}", dependencies=[Depends(bearer_schema)], response_model=User)
async def get_account(
user_id: int, connection: AsyncConnection = Depends(get_connection_dep), current_user=Depends(get_current_user)
):
authorize_user = await db_user_role_validation(connection, current_user)
user = await get_user_by_id(connection, user_id)
@@ -64,23 +65,16 @@ async def get_account(user_id: int, request: Request, connection: AsyncConnectio
return user
@api_router.post("", response_model=User)
@api_router.post("", dependencies=[Depends(bearer_schema)], response_model=User)
async def create_account(
user: UserUpdate,
request: Request,
connection: AsyncConnection = Depends(get_connection_dep)
):
current_user = request.state.current_user
user: UserUpdate, connection: AsyncConnection = Depends(get_connection_dep), current_user=Depends(get_current_user)
):
authorize_user = await db_user_role_validation(connection, current_user)
user_validation = await get_user_by_login(connection, user.login)
if user_validation is None:
await create_user(connection,user,authorize_user.id)
await create_user(connection, user, authorize_user.id)
user_new = await get_user_by_login(connection, user.login)
return user_new
@@ -90,12 +84,13 @@ async def create_account(
)
@api_router.put("/{user_id}", response_model=User)
@api_router.put("/{user_id}", dependencies=[Depends(bearer_schema)], response_model=User)
async def update_account(
user_id: int, request: Request, user_update: UserUpdate, connection: AsyncConnection = Depends(get_connection_dep)
user_id: int,
user_update: UserUpdate,
connection: AsyncConnection = Depends(get_connection_dep),
current_user=Depends(get_current_user),
):
current_user = request.state.current_user
authorize_user = await db_user_role_validation(connection, current_user)
user = await get_user_by_id(connection, user_id)
@@ -111,21 +106,15 @@ async def update_account(
await update_user_by_id(connection, update_values, user)
user = await get_user_by_id(connection, user_id)
return user
@api_router.delete("/{user_id}", response_model=User)
@api_router.delete("/{user_id}", dependencies=[Depends(bearer_schema)], response_model=User)
async def delete_account(
user_id: int,
request: Request,
connection: AsyncConnection = Depends(get_connection_dep)
):
current_user = request.state.current_user
user_id: int, connection: AsyncConnection = Depends(get_connection_dep), current_user=Depends(get_current_user)
):
authorize_user = await db_user_role_validation(connection, current_user)
user = await get_user_by_id(connection, user_id)
@@ -139,10 +128,8 @@ async def delete_account(
if update_values is None:
return user
await update_user_by_id(connection, update_values, user)
user = await get_user_by_id(connection, user_id)
return user

View File

@@ -1,5 +1,6 @@
from datetime import datetime, timedelta, timezone
import jwt
from fastapi import (
APIRouter,
Depends,
@@ -8,7 +9,6 @@ from fastapi import (
Response,
status,
)
from loguru import logger
from fastapi_jwt_auth import AuthJWT
@@ -30,11 +30,21 @@ api_router = APIRouter(
)
def get_login_from_jwt(token: str):
payload = jwt.decode(
token,
get_settings().SECRET_KEY,
algorithms=[get_settings().ALGORITHM],
)
return payload.get("sub")
class Settings(BaseModel):
authjwt_secret_key: str = get_settings().SECRET_KEY
# Configure application to store and get JWT from cookies
authjwt_token_location: set = {"headers", "cookies"}
authjwt_cookie_domain: str = get_settings().DOMAIN
authjwt_refresh_cookie_name: str = "refresh_token_cookie"
# Only allow JWT cookies to be sent over https
authjwt_cookie_secure: bool = get_settings().ENV == "prod"
@@ -68,7 +78,8 @@ async def login_for_access_token(
# headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=get_settings().ACCESS_TOKEN_EXPIRE_MINUTES)
# access_token_expires = timedelta(minutes=get_settings().ACCESS_TOKEN_EXPIRE_MINUTES)
access_token_expires = timedelta(seconds=5)
refresh_token_expires = timedelta(days=get_settings().REFRESH_TOKEN_EXPIRE_DAYS)
@@ -86,31 +97,21 @@ async def login_for_access_token(
return Access(access_token=access_token)
@api_router.post("/refresh",response_model=Access)
@api_router.post("/refresh", response_model=Access)
async def refresh(
request: Request, connection: AsyncConnection = Depends(get_connection_dep), Authorize: AuthJWT = Depends()
request: Request,
connection: AsyncConnection = Depends(get_connection_dep),
Authorize: AuthJWT = Depends(),
):
refresh_token = request.cookies.get("refresh_token_cookie")
# print("Refresh Token:", refresh_token)
if not refresh_token:
raise HTTPException(status_code=401, detail="Refresh token is missing")
try:
Authorize.jwt_refresh_token_required()
current_user = Authorize.get_jwt_subject()
except Exception as e:
await upgrade_old_refresh_token(connection, current_user, refresh_token)
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid refresh token",
)
access_token_expires = timedelta(minutes=get_settings().ACCESS_TOKEN_EXPIRE_MINUTES)
Authorize.jwt_refresh_token_required(refresh_token)
current_user = Authorize.get_jwt_subject()
# try:
# access_token_expires = timedelta(minutes=get_settings().ACCESS_TOKEN_EXPIRE_MINUTES)
access_token_expires = timedelta(seconds=5)
new_access_token = Authorize.create_access_token(subject=current_user, expires_time=access_token_expires)
return Access(access_token=new_access_token)

View File

@@ -4,7 +4,6 @@ from fastapi import (
Depends,
Form,
HTTPException,
Request,
Response,
status,
)
@@ -14,14 +13,15 @@ from sqlalchemy.ext.asyncio import AsyncConnection
from api.db.connection.session import get_connection_dep
from api.db.logic.keyring import get_key_by_id,create_key,update_key_by_id
from api.db.logic.keyring import get_key_by_id, create_key, update_key_by_id
from api.db.tables.account import KeyStatus
from api.schemas.base import bearer_schema
from api.schemas.endpoints.account_keyring import AccountKeyringUpdate
from api.schemas.account.account_keyring import AccountKeyring
from api.services.auth import get_current_user
from api.services.user_role_validation import db_user_role_validation
from api.services.update_data_validation import update_key_data_changes
@@ -33,15 +33,10 @@ api_router = APIRouter(
)
@api_router.get("/{user_id}/{key_id}", response_model=AccountKeyring)
@api_router.get("/{user_id}/{key_id}", dependencies=[Depends(bearer_schema)], response_model=AccountKeyring)
async def get_keyring(
key_id: str,
request: Request,
connection: AsyncConnection = Depends(get_connection_dep)
):
current_user = request.state.current_user
key_id: str, connection: AsyncConnection = Depends(get_connection_dep), current_user=Depends(get_current_user)
):
authorize_user = await db_user_role_validation(connection, current_user)
keyring = await get_key_by_id(connection, key_id)
@@ -52,22 +47,24 @@ async def get_keyring(
return keyring
@api_router.post("/{user_id}/{key_id}", response_model=AccountKeyring)
@api_router.post("/{user_id}/{key_id}", dependencies=[Depends(bearer_schema)], response_model=AccountKeyring)
async def create_keyring(
user_id: int,
key_id: str,
request: Request,
key: AccountKeyringUpdate,
connection: AsyncConnection = Depends(get_connection_dep),
current_user=Depends(get_current_user),
):
current_user = request.state.current_user
authorize_user = await db_user_role_validation(connection, current_user)
keyring = await get_key_by_id(connection, key_id)
if keyring is None:
keyring_new = await create_key(connection,key, key_id, )
keyring_new = await create_key(
connection,
key,
key_id,
)
return keyring_new
else:
@@ -76,16 +73,14 @@ async def create_keyring(
)
@api_router.put("/{user_id}/{key_id}", response_model=AccountKeyring)
@api_router.put("/{user_id}/{key_id}", dependencies=[Depends(bearer_schema)], response_model=AccountKeyring)
async def update_keyring(
user_id: int,
key_id: str,
request: Request,
keyring_update: AccountKeyringUpdate,
connection: AsyncConnection = Depends(get_connection_dep),
current_user=Depends(get_current_user),
):
current_user = request.state.current_user
authorize_user = await db_user_role_validation(connection, current_user)
keyring = await get_key_by_id(connection, key_id)
@@ -101,17 +96,18 @@ async def update_keyring(
await update_key_by_id(connection, update_values, keyring)
keyring = await get_key_by_id(connection, key_id)
return keyring
@api_router.delete("/{user_id}/{key_id}", response_model=AccountKeyring)
async def delete_keyring(
user_id: int, key_id: str, request: Request, connection: AsyncConnection = Depends(get_connection_dep)
):
current_user = request.state.current_user
@api_router.delete("/{user_id}/{key_id}", dependencies=[Depends(bearer_schema)], response_model=AccountKeyring)
async def delete_keyring(
user_id: int,
key_id: str,
connection: AsyncConnection = Depends(get_connection_dep),
current_user=Depends(get_current_user),
):
authorize_user = await db_user_role_validation(connection, current_user)
keyring = await get_key_by_id(connection, key_id)
@@ -127,7 +123,6 @@ async def delete_keyring(
await update_key_by_id(connection, update_values, keyring)
keyring = await get_key_by_id(connection, key_id)
return keyring

View File

@@ -13,7 +13,9 @@ from fastapi import (
from sqlalchemy.ext.asyncio import AsyncConnection
from api.db.connection.session import get_connection_dep
from api.db.logic.account import get_user_by_id, update_user_by_id,get_user_by_login
from api.db.logic.account import get_user_by_id, update_user_by_id, get_user_by_login
from api.schemas.base import bearer_schema
from api.services.auth import get_current_user
from api.services.update_data_validation import update_user_data_changes
from api.schemas.endpoints.account import UserUpdate
@@ -26,14 +28,10 @@ api_router = APIRouter(
)
@api_router.get("",response_model=User)
@api_router.get("", dependencies=[Depends(bearer_schema)], response_model=User)
async def get_profile(
request: Request,
connection: AsyncConnection = Depends(get_connection_dep),
connection: AsyncConnection = Depends(get_connection_dep), current_user=Depends(get_current_user)
):
# Извлекаем текущего пользователя из request.state
current_user = request.state.current_user
user = await get_user_by_login(connection, current_user)
if user is None:
@@ -42,14 +40,12 @@ async def get_profile(
return user
@api_router.put("",response_model=User)
@api_router.put("", dependencies=[Depends(bearer_schema)], response_model=User)
async def update_profile(
request: Request,
user_updata: UserUpdate,
connection: AsyncConnection = Depends(get_connection_dep),
current_user=Depends(get_current_user),
):
current_user = request.state.current_user
user = await get_user_by_login(connection, current_user)
if user is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Account not found")

View File

@@ -1,11 +1,13 @@
import datetime
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, EmailStr, Field
from api.db.tables.account import AccountRole,AccountStatus
from pydantic import EmailStr, Field
from api.db.tables.account import AccountRole, AccountStatus
from api.schemas.base import Base
class User(BaseModel):
class User(Base):
id: Optional[int] = None
name: str = Field(..., max_length=100)
login: str = Field(..., max_length=100)

View File

@@ -1,11 +1,13 @@
import datetime
from typing import Optional
from pydantic import BaseModel, Field
from pydantic import Field
from datetime import datetime
from api.db.tables.account import KeyType,KeyStatus
from api.db.tables.account import KeyType, KeyStatus
from api.schemas.base import Base
class AccountKeyring(BaseModel):
class AccountKeyring(Base):
owner_id: int
key_type: KeyType
key_id: Optional[str] = Field(None, max_length=40)

View File

@@ -1,7 +1,11 @@
from fastapi.security import HTTPBearer
from pydantic import BaseModel, ConfigDict
from pydantic.alias_generators import to_camel
bearer_schema = HTTPBearer() # схема для авторизации в swagger
class Base(BaseModel):
model_config = ConfigDict(
from_attributes=True,

View File

@@ -1,12 +1,13 @@
from typing import Optional, List
from datetime import datetime
from pydantic import BaseModel, EmailStr, Field, TypeAdapter
from pydantic import EmailStr, Field, TypeAdapter
from api.db.tables.account import AccountRole,AccountStatus
from api.db.tables.account import AccountRole, AccountStatus
from api.schemas.base import Base
class UserUpdate(BaseModel):
class UserUpdate(Base):
id: Optional[int] = None
name: Optional[str] = Field(None, max_length=100)
login: Optional[str] = Field(None, max_length=100)
@@ -18,7 +19,8 @@ class UserUpdate(BaseModel):
created_at: Optional[datetime] = None
status: Optional[AccountStatus] = None
class AllUser(BaseModel):
class AllUser(Base):
id: int
name: str
login: str
@@ -29,9 +31,10 @@ class AllUser(BaseModel):
status: AccountStatus
class AllUserResponse(BaseModel):
class AllUserResponse(Base):
users: List[AllUser]
amount_count: int
amount_pages: int
all_user_adapter = TypeAdapter(List[AllUser])

View File

@@ -1,11 +1,13 @@
import datetime
from typing import Optional
from pydantic import BaseModel, Field
from pydantic import Field
from datetime import datetime
from api.db.tables.account import KeyType,KeyStatus
from api.db.tables.account import KeyType, KeyStatus
from api.schemas.base import Base
class AccountKeyringUpdate(BaseModel):
class AccountKeyringUpdate(Base):
owner_id: Optional[int] = None
key_type: Optional[KeyType] = None
key_id: Optional[str] = Field(None, max_length=40)

View File

@@ -1,14 +1,16 @@
from api.schemas.base import BaseModel
from api.schemas.base import Base
# Таблица для получения информации из запроса
class Auth(BaseModel):
class Auth(Base):
login: str
password: str
class Refresh(BaseModel,):
class Refresh(Base):
refresh_token: str
class Access(BaseModel):
class Access(Base):
access_token: str

View File

@@ -3,7 +3,7 @@ from typing import Dict, Any
from datetime import datetime
from enum import Enum
from api.schemas.base import BaseModel
from api.schemas.base import Base
class State(Enum):
@@ -17,7 +17,7 @@ class Status(Enum):
DELETED = "Deleted"
class ListEvent(BaseModel):
class ListEvent(Base):
id: int
name: str = Field(..., max_length=40)
title: str = Field(..., max_length=64)

View File

@@ -1,4 +1,4 @@
from pydantic import Field, conint
from pydantic import Field
from typing import Dict, Any
from datetime import datetime
from enum import Enum

View File

@@ -1,3 +1,4 @@
from fastapi import Request, HTTPException
from typing import Optional
from sqlalchemy.ext.asyncio import AsyncConnection
from api.db.logic.auth import get_user
@@ -9,11 +10,17 @@ from api.db.tables.account import AccountStatus
from api.utils.hasher import Hasher
async def get_current_user(request: Request) -> Optional[User]:
if not hasattr(request.state, "current_user"):
return HTTPException(status_code=401, detail="Unauthorized")
return request.state.current_user
async def authenticate_user(connection: AsyncConnection, username: str, password: str) -> Optional[User]:
sql_user, sql_password = await get_user(connection, username)
if not sql_user or sql_user.status != AccountStatus.ACTIVE :
return None
if not sql_user or sql_user.status != AccountStatus.ACTIVE:
return None
hasher = Hasher()
if not hasher.verify_data(password, sql_password.key_value):
return None

View File

@@ -22,40 +22,38 @@ class MiddlewareAccessTokenValidadtion(BaseHTTPMiddleware):
self.excluded_routes = [
re.compile(r"^" + re.escape(self.prefix) + r"/auth/refresh/?$"),
re.compile(r"^" + re.escape(self.prefix) + r"/auth/?$"),
re.compile(r"^" + r"/swagger"),
re.compile(r"^" + r"/openapi"),
]
async def dispatch(self, request: Request, call_next):
if request.method in ["GET", "POST", "PUT", "DELETE"]:
if any(pattern.match(request.url.path) for pattern in self.excluded_routes):
return await call_next(request)
else:
auth_header = request.headers.get("Authorization")
if not auth_header:
return JSONResponse(
status_code=status.HTTP_401_UNAUTHORIZED,
content={"detail": "Missing authorization header."},
headers={"WWW-Authenticate": "Bearer"},
)
if request.method not in ["GET", "POST", "PUT", "DELETE"]:
return JSONResponse(
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
content={"detail": "Method not allowed"},
)
token = auth_header.split(" ")[1]
Authorize = AuthJWT(request)
if any(pattern.match(request.url.path) for pattern in self.excluded_routes):
return await call_next(request)
try:
current_user = Authorize.get_jwt_subject()
request.state.current_user = current_user
return await call_next(request)
auth_header = request.headers.get("Authorization")
if not auth_header:
return JSONResponse(
status_code=status.HTTP_401_UNAUTHORIZED,
content={"detail": "Missing authorization header."},
headers={"WWW-Authenticate": "Bearer"},
)
except Exception:
return JSONResponse(
status_code=status.HTTP_401_UNAUTHORIZED,
content={"detail": "The access token is invalid or expired."},
headers={"WWW-Authenticate": "Bearer"},
)
try:
token = auth_header.split(" ")[1]
Authorize = AuthJWT(request)
current_user = Authorize.get_jwt_subject()
request.state.current_user = current_user
except Exception:
return JSONResponse(
status_code=status.HTTP_401_UNAUTHORIZED,
content={"detail": "The access token is invalid or expired."},
headers={"WWW-Authenticate": "Bearer"},
)
# async with get_connection() as connection:
# authorize_user = await get_user_login(connection, current_user)
# print(authorize_user)
# if authorize_user is None :
# return JSONResponse(
# status_code=status.HTTP_404_NOT_FOUND ,
# detail="User not found.")
return await call_next(request)

View File

@@ -1,9 +1,9 @@
from enum import Enum
from typing import Optional
from api.schemas.endpoints.account import UserUpdate
from api.db.tables.account import KeyType,KeyStatus
from api.db.tables.account import KeyType, KeyStatus
from api.schemas.endpoints.account_keyring import AccountKeyringUpdate
from api.db.tables.account import AccountRole,AccountStatus
from api.db.tables.account import AccountRole, AccountStatus
def update_user_data_changes(update_data: UserUpdate, user) -> Optional[dict]:

View File

@@ -6,12 +6,8 @@ from api.db.logic.account import get_user_by_login
from api.db.tables.account import AccountRole
async def db_user_role_validation(connection, current_user):
authorize_user = await get_user_by_login(connection, current_user)
if authorize_user.role not in {AccountRole.OWNER, AccountRole.ADMIN}:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You do not have enough permissions")
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="You do not have enough permissions")
return authorize_user

View File

@@ -1,12 +1,12 @@
{
"name": "client",
"version": "0.0.1",
"version": "0.0.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "client",
"version": "0.0.1",
"version": "0.0.2",
"dependencies": {
"@ant-design/icons": "^5.6.1",
"@testing-library/dom": "^10.4.0",
@@ -18,6 +18,8 @@
"@types/react": "^19.0.11",
"@types/react-dom": "^19.0.4",
"antd": "^5.24.7",
"axios": "^1.9.0",
"axios-retry": "^4.5.0",
"i18next": "^25.0.1",
"i18next-browser-languagedetector": "^8.0.5",
"react": "^18.3.1",
@@ -26,7 +28,8 @@
"react-router-dom": "^7.5.0",
"react-scripts": "5.0.1",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
"web-vitals": "^2.1.4",
"zustand": "^5.0.5"
}
},
"node_modules/@adobe/css-tools": {
@@ -5262,6 +5265,45 @@
"node": ">=4"
}
},
"node_modules/axios": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/axios-retry": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-4.5.0.tgz",
"integrity": "sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ==",
"license": "Apache-2.0",
"dependencies": {
"is-retry-allowed": "^2.2.0"
},
"peerDependencies": {
"axios": "0.x || 1.x"
}
},
"node_modules/axios/node_modules/form-data": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz",
"integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/axobject-query": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
@@ -10063,6 +10105,18 @@
"node": ">=0.10.0"
}
},
"node_modules/is-retry-allowed": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz",
"integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-root": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz",
@@ -14085,6 +14139,12 @@
"node": ">= 0.10"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/psl": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
@@ -18743,6 +18803,35 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zustand": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.5.tgz",
"integrity": "sha512-mILtRfKW9xM47hqxGIxCv12gXusoY/xTSHBYApXozR0HmQv299whhBeeAcRy+KrPPybzosvJBCOmVjq6x12fCg==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
},
"peerDependencies": {
"@types/react": ">=18.0.0",
"immer": ">=9.0.6",
"react": ">=18.0.0",
"use-sync-external-store": ">=1.2.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
},
"use-sync-external-store": {
"optional": true
}
}
}
}
}

View File

@@ -13,6 +13,8 @@
"@types/react": "^19.0.11",
"@types/react-dom": "^19.0.4",
"antd": "^5.24.7",
"axios": "^1.9.0",
"axios-retry": "^4.5.0",
"i18next": "^25.0.1",
"i18next-browser-languagedetector": "^8.0.5",
"react": "^18.3.1",
@@ -21,7 +23,8 @@
"react-router-dom": "^7.5.0",
"react-scripts": "5.0.1",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4"
"web-vitals": "^2.1.4",
"zustand": "^5.0.5"
},
"scripts": {
"start": "react-scripts start",

View File

@@ -0,0 +1,3 @@
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M52.0626 73.9965L52.0348 76.7513C51.9791 79.687 49.6 82.0452 46.6644 82.0452C43.8957 82.0452 41.6139 79.9444 41.3357 77.2383L41.3217 77.1965C41.3217 76.953 41.1269 76.7583 40.8835 76.7513C40.8835 76.7513 36.7235 76.1391 35.5548 75.8052C34.7757 75.5826 34.7061 73.9965 35.9722 73.9965H52.0696H52.0626ZM76.0904 73.9965L76.1183 76.7513C76.1739 79.687 78.5531 82.0452 81.4887 82.0452C84.2574 82.0452 86.5391 79.9444 86.8174 77.2383L86.8313 77.1965C86.8313 76.953 87.0261 76.7583 87.2696 76.7513C87.2696 76.7513 91.4296 76.1391 92.5983 75.8052C93.3774 75.5826 93.447 73.9965 92.1809 73.9965H76.0835H76.0904ZM58.233 127.979V103.123C54.6643 105.468 51.0957 105.085 50.4557 105.023V126.804C53.0157 127.353 55.6104 127.75 58.233 127.979ZM69.8157 103.123V128C72.4383 127.777 75.0331 127.388 77.593 126.845V105.023C76.953 105.085 73.3774 105.468 69.8157 103.123ZM127.993 64.1183C127.993 109.913 86.88 124.139 85.3704 124.668V96.2157H79.5339C72.6957 96.2157 67.1583 90.5809 67.1583 83.6313V73.9965H60.9948V83.6313C60.9948 90.5809 55.4574 96.2157 48.6191 96.2157H42.6783V124.591C26.6435 118.908 0 99.9861 0 64.1183C0 24.9252 32.4244 0 64 0C103.847 0 128 32.487 128 64.1183H127.993ZM56.5774 70.5948C56.5774 65.5443 52.5565 61.4539 47.5896 61.4539H35.4504C30.4835 61.4539 26.4626 65.5443 26.4626 70.5948V81.5652C26.4626 86.6226 30.4835 90.7061 35.4504 90.7061H47.5896C52.5565 90.7061 56.5774 86.6157 56.5774 81.5652V70.5948ZM79.5339 55.9444C79.5339 55.9444 87.8609 55.9652 93.1687 55.9861V43.4713C93.1687 33.6765 85.2452 25.7391 75.4644 25.7391H52.6539C42.88 25.7391 34.9496 33.6765 34.9496 43.4713V55.9861C40.2504 55.9722 48.6122 55.9444 48.6122 55.9444C55.2417 55.9444 60.633 61.2383 60.96 67.8957H67.1861C67.5131 61.2383 72.9044 55.9444 79.5339 55.9444ZM92.7026 90.6991C97.6696 90.6991 101.69 86.6087 101.69 81.5583V70.5878C101.69 65.5374 97.6696 61.447 92.7026 61.447H80.5635C75.5965 61.447 71.5757 65.5374 71.5757 70.5878V81.5583C71.5757 86.6087 75.5965 90.6991 80.5635 90.6991H92.7026ZM120.188 64.1113C120.188 33.0713 95.7009 7.81914 64 7.81914C32.2991 7.81914 7.81218 33.0713 7.81218 64.1113C7.81218 84.487 18.6783 102.372 34.9078 112.257V96.2087H31.9165C25.0852 96.2087 19.5409 90.5739 19.5409 83.6244V74.087H18.3165C17.5931 74.087 17.0017 73.5374 17.0017 72.8626V69.1478C17.0017 68.473 17.5931 67.9235 18.3165 67.9235H19.5617C19.7565 62.8035 22.8522 58.5461 27.2765 56.8V41.2313C27.2765 28.8348 37.3078 18.7896 49.6765 18.7896H78.4696C90.8383 18.7896 100.87 28.8348 100.87 41.2313V56.793C105.301 58.5322 108.41 62.7965 108.605 67.9235H109.85C110.574 67.9235 111.165 68.473 111.165 69.1478V72.8626C111.165 73.5374 110.574 74.087 109.85 74.087H108.626V83.6244C108.626 90.5739 103.089 96.2087 96.2505 96.2087H93.1548V112.383C109.503 102.525 120.188 84.5774 120.188 64.1113Z" fill="#333333"/>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -2,12 +2,13 @@ import React from 'react';
import { Route, Routes } from 'react-router-dom';
import MainLayout from './pages/MainLayout';
import ProtectedRoute from './pages/ProtectedRoute';
import LoginPage from './pages/LoginPage';
function App() {
return (
<div className="App">
<Routes>
<Route path="/login" element={<div>login</div>} />
<Route path="/login" element={<LoginPage />} />
<Route element={<ProtectedRoute />}>
<Route path="*" element={<MainLayout />}></Route>
</Route>

88
client/src/api/api.ts Normal file
View File

@@ -0,0 +1,88 @@
import axios from 'axios';
import { Access, Auth } from '../types/auth';
import { User } from '../types/user';
import { AuthService } from '../services/auth';
import axiosRetry from 'axios-retry';
const baseURL = `${process.env.REACT_APP_HTTP_PROTOCOL}://${process.env.REACT_APP_API_URL}/api/v1`;
const base = axios.create({
baseURL,
withCredentials: true,
headers: {
accepts: 'application/json',
},
});
base.interceptors.request.use((config) => {
const token = localStorage.getItem('accessToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// axiosRetry(base, {
// retries: 3,
// retryDelay: (retryCount: number) => {
// console.log(`retry attempt: ${retryCount}`);
// return retryCount * 2000;
// },
// retryCondition: async (error: any) => {
// if (error.code === 'ERR_CANCELED') {
// return true;
// }
// return false;
// },
// });
base.interceptors.response.use(
(response) => {
return response;
},
async function (error) {
console.log('error', error);
const originalRequest = error.response.config;
console.log('originalRequest._retry', originalRequest);
const urlTokens = error?.request?.responseURL.split('/');
const url = urlTokens[urlTokens.length - 1];
console.log('url', url);
if (
error.response.status === 401 &&
!(originalRequest?._retry != null) &&
url !== 'login' &&
url !== 'refresh' &&
url !== 'logout'
) {
originalRequest._retry = true;
const res = await AuthService.refresh().catch(async () => {
await AuthService.logout();
});
console.log('res', res);
return await base(originalRequest);
}
return await Promise.reject(error);
}
);
const api = {
// auth
async login(auth: Auth): Promise<Access> {
console.log(auth);
const response = await base.post<Access>('/auth', auth);
return response.data;
},
async refreshToken(): Promise<Access> {
const response = await base.post<Access>('/auth/refresh');
return response.data;
},
// user
async getProfile(): Promise<User> {
const response = await base.get<User>('/profile');
return response.data;
},
};
export default api;

View File

@@ -0,0 +1,103 @@
import React from 'react';
import { Form, Input, Button, Typography } from 'antd';
import {
EyeInvisibleOutlined,
EyeTwoTone,
UserOutlined,
} from '@ant-design/icons';
import { AuthService } from '../services/auth';
import { Auth } from '../types/auth';
import { useNavigate } from 'react-router-dom';
const { Text, Link } = Typography;
export default function LoginPage() {
const navigate = useNavigate();
const onFinish = async (values: any) => {
await AuthService.login(values as Auth);
navigate('/');
};
return (
<div
style={{
height: '100vh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<div
style={{
width: '100%',
maxWidth: 472,
padding: 24,
background: '#fff',
textAlign: 'center',
}}
>
<div style={{ marginBottom: 32 }}>
<img
src="./icons/logo.svg"
alt="logo"
style={{ width: 128, height: 128, marginBottom: 16 }}
/>
</div>
<Form name="login" onFinish={onFinish} layout="vertical">
<Form.Item
name="login"
rules={[{ required: true, message: 'Введите login' }]}
>
<Input size="large" placeholder="Логин" prefix={<UserOutlined />} />
</Form.Item>
<Form.Item
name="password"
rules={[{ required: true, message: 'Введите пароль' }]}
>
<Input.Password
size="large"
placeholder="Пароль"
iconRender={(visible) =>
visible ? <EyeTwoTone /> : <EyeInvisibleOutlined />
}
/>
</Form.Item>
<Form.Item>
<Button
type="primary"
htmlType="submit"
block
size="large"
style={{
backgroundColor: '#C2DA3D',
borderColor: '#C2DA3D',
color: '#000',
}}
>
Войти
</Button>
</Form.Item>
<Text type="secondary" style={{ fontSize: 12 }}>
Нажимая кнопку Войти, Вы полностью принимаете{' '}
<Link href="/offer" target="_blank">
Публичную оферту
</Link>{' '}
и{' '}
<Link href="/privacy" target="_blank">
Политику обработки персональных данных
</Link>
</Text>
</Form>
<div style={{ marginTop: 256 }}>
<Link href="/forgot-password">Забыли пароль?</Link>
</div>
</div>
</div>
);
}

View File

@@ -9,6 +9,8 @@ import RunningProcessesPage from './RunningProcessesPage';
import AccountsPage from './AccountsPage';
import EventsListPage from './EventsListPage';
import ConfigurationPage from './ConfigurationPage';
import { useSetUserSelector } from '../store/user';
import { UserService } from '../services/user';
export default function MainLayout() {
const navigate = useNavigate();
@@ -19,6 +21,8 @@ export default function MainLayout() {
const [width, setWidth] = useState<number | string>('15%');
const [collapsedWidth, setCollapsedWidth] = useState(50);
const setUser = useSetUserSelector()
const calculateWidths = () => {
const windowWidth = window.innerWidth;
const expanded = Math.min(Math.max(windowWidth * 0.15, 180), 240);
@@ -54,6 +58,21 @@ export default function MainLayout() {
navigate(key);
}
useEffect(() => {
const token = localStorage.getItem('accessToken');
if (!token) {
navigate('/login');
} else {
if (localStorage.getItem('user')) {
setUser(JSON.parse(localStorage.getItem('user') as string))
} else {
UserService.getProfile().then((user) => {
setUser(user);
});
}
}
}, [])
return (
<Layout style={{ minHeight: '100vh' }}>
<Sider

View File

@@ -1,8 +1,19 @@
/* eslint-disable react-hooks/exhaustive-deps */
// ProtectedRoute.js
import { Outlet } from 'react-router-dom';
import React from 'react';
import { Outlet, useNavigate } from 'react-router-dom';
import React, { useEffect } from 'react';
import { useUserSelector } from '../store/user';
const ProtectedRoute = (): React.JSX.Element => {
const user = useUserSelector();
const navigate = useNavigate();
useEffect(() => {
if (user.id === null) {
navigate('/login');
}
}, [user]);
return <Outlet />;
};
export default ProtectedRoute;

View File

@@ -0,0 +1,22 @@
import api from '../api/api';
import { useUserStore } from '../store/user';
import { Auth } from '../types/auth';
export class AuthService {
static async login(auth: Auth) {
const token = await api.login(auth);
console.log(token)
localStorage.setItem('accessToken', token.accessToken);
}
static async logout() {
useUserStore.getState().removeUser();
localStorage.removeItem('userInfo');
localStorage.removeItem('accessToken');
}
static async refresh() {
const token = await api.refreshToken();
localStorage.setItem('accessToken', token.accessToken);
}
}

View File

@@ -0,0 +1,10 @@
import api from '../api/api';
import { User } from '../types/user';
export class UserService {
static async getProfile(): Promise<User> {
const user = api.getProfile();
return user;
}
}

38
client/src/store/user.ts Normal file
View File

@@ -0,0 +1,38 @@
import { create } from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { User } from '../types/user';
const userInfo = localStorage.getItem('userInfo');
type UserStoreState = {
user: User;
loading: boolean;
};
type UserStoreActions = {
setUser: (user: User) => void;
removeUser: () => void;
};
type UserStore = UserStoreState & UserStoreActions;
export const useUserStore = create<UserStore>()(
devtools(
persist(
(set, get) => ({
user: userInfo != null ? JSON.parse(userInfo) : ({} as User),
loading: false,
setUser: (user: User) => set({ user }),
removeUser: () => set({ user: {} as User }),
}),
{ name: 'userInfo' }
)
)
);
export const useUserSelector = () => {
return useUserStore((state) => state.user);
};
export const useSetUserSelector = () => {
return useUserStore((state) => state.setUser);
};

4
client/src/types/auth.ts Normal file
View File

@@ -0,0 +1,4 @@
import { components } from './openapi-types';
export type Auth = components['schemas']['Auth'];
export type Access = components['schemas']['Access'];

View File

@@ -0,0 +1,692 @@
/**
* This file was auto-generated by openapi-typescript.
* Do not make direct changes to the file.
*/
export interface paths {
"/api/v1/auth": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/**
* Login For Access Token
* @description Авторизирует, выставляет токены в куки.
*/
post: operations["login_for_access_token_api_v1_auth_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/auth/refresh": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Refresh */
post: operations["refresh_api_v1_auth_refresh_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/profile": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** Get Profile */
get: operations["get_profile_api_v1_profile_get"];
/** Update Profile */
put: operations["update_profile_api_v1_profile_put"];
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/account": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** Get All Account */
get: operations["get_all_account_api_v1_account_get"];
put?: never;
/** Create Account */
post: operations["create_account_api_v1_account_post"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/account/{user_id}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** Get Account */
get: operations["get_account_api_v1_account__user_id__get"];
/** Update Account */
put: operations["update_account_api_v1_account__user_id__put"];
post?: never;
/** Delete Account */
delete: operations["delete_account_api_v1_account__user_id__delete"];
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/v1/keyring/{user_id}/{key_id}": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** Get Keyring */
get: operations["get_keyring_api_v1_keyring__user_id___key_id__get"];
/** Update Keyring */
put: operations["update_keyring_api_v1_keyring__user_id___key_id__put"];
/** Create Keyring */
post: operations["create_keyring_api_v1_keyring__user_id___key_id__post"];
/** Delete Keyring */
delete: operations["delete_keyring_api_v1_keyring__user_id___key_id__delete"];
options?: never;
head?: never;
patch?: never;
trace?: never;
};
}
export type webhooks = Record<string, never>;
export interface components {
schemas: {
/** Access */
Access: {
/** Accesstoken */
accessToken: string;
};
/** AccountKeyring */
AccountKeyring: {
/** Ownerid */
ownerId: number;
keyType: components["schemas"]["KeyType"];
/** Keyid */
keyId?: string | null;
/** Keyvalue */
keyValue: string;
/**
* Createdat
* Format: date-time
*/
createdAt: string;
/** Expiry */
expiry?: string | null;
status: components["schemas"]["KeyStatus"];
};
/** AccountKeyringUpdate */
AccountKeyringUpdate: {
/** Ownerid */
ownerId?: number | null;
keyType?: components["schemas"]["KeyType"] | null;
/** Keyid */
keyId?: string | null;
/** Keyvalue */
keyValue?: string | null;
/** Createdat */
createdAt?: string | null;
/** Expiry */
expiry?: string | null;
status?: components["schemas"]["KeyStatus"] | null;
};
/**
* AccountRole
* @enum {string}
*/
AccountRole: "OWNER" | "ADMIN" | "EDITOR" | "VIEWER";
/**
* AccountStatus
* @enum {string}
*/
AccountStatus: "ACTIVE" | "DISABLED" | "BLOCKED" | "DELETED";
/** AllUser */
AllUser: {
/** Id */
id: number;
/** Name */
name: string;
/** Login */
login: string;
/** Email */
email?: string | null;
/** Bindtenantid */
bindTenantId?: string | null;
role: components["schemas"]["AccountRole"];
/**
* Createdat
* Format: date-time
*/
createdAt: string;
status: components["schemas"]["AccountStatus"];
};
/** AllUserResponse */
AllUserResponse: {
/** Users */
users: components["schemas"]["AllUser"][];
/** Amountcount */
amountCount: number;
/** Amountpages */
amountPages: number;
};
/** Auth */
Auth: {
/** Login */
login: string;
/** Password */
password: string;
};
/** HTTPValidationError */
HTTPValidationError: {
/** Detail */
detail?: components["schemas"]["ValidationError"][];
};
/**
* KeyStatus
* @enum {string}
*/
KeyStatus: "ACTIVE" | "EXPIRED" | "DELETED";
/**
* KeyType
* @enum {string}
*/
KeyType: "PASSWORD" | "ACCESS_TOKEN" | "REFRESH_TOKEN" | "API_KEY";
/** User */
User: {
/** Id */
id?: number | null;
/** Name */
name: string;
/** Login */
login: string;
/** Email */
email?: string | null;
/** Bindtenantid */
bindTenantId?: string | null;
role: components["schemas"]["AccountRole"];
/** Meta */
meta: {
[key: string]: unknown;
};
/** Creatorid */
creatorId?: number | null;
/**
* Createdat
* Format: date-time
*/
createdAt: string;
status: components["schemas"]["AccountStatus"];
};
/** UserUpdate */
UserUpdate: {
/** Id */
id?: number | null;
/** Name */
name?: string | null;
/** Login */
login?: string | null;
/** Email */
email?: string | null;
/** Bindtenantid */
bindTenantId?: string | null;
role?: components["schemas"]["AccountRole"] | null;
/** Meta */
meta?: {
[key: string]: unknown;
} | null;
/** Creatorid */
creatorId?: number | null;
/** Createdat */
createdAt?: string | null;
status?: components["schemas"]["AccountStatus"] | null;
};
/** ValidationError */
ValidationError: {
/** Location */
loc: (string | number)[];
/** Message */
msg: string;
/** Error Type */
type: string;
};
};
responses: never;
parameters: never;
requestBodies: never;
headers: never;
pathItems: never;
}
export type $defs = Record<string, never>;
export interface operations {
login_for_access_token_api_v1_auth_post: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["Auth"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["Access"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
refresh_api_v1_auth_refresh_post: {
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"]["Access"];
};
};
};
};
get_profile_api_v1_profile_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"]["User"];
};
};
};
};
update_profile_api_v1_profile_put: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["UserUpdate"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["User"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
get_all_account_api_v1_account_get: {
parameters: {
query?: {
page?: number;
limit?: number;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AllUserResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
create_account_api_v1_account_post: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["UserUpdate"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["User"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
get_account_api_v1_account__user_id__get: {
parameters: {
query?: never;
header?: never;
path: {
user_id: number;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["User"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
update_account_api_v1_account__user_id__put: {
parameters: {
query?: never;
header?: never;
path: {
user_id: number;
};
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["UserUpdate"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["User"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
delete_account_api_v1_account__user_id__delete: {
parameters: {
query?: never;
header?: never;
path: {
user_id: number;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["User"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
get_keyring_api_v1_keyring__user_id___key_id__get: {
parameters: {
query?: never;
header?: never;
path: {
key_id: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AccountKeyring"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
update_keyring_api_v1_keyring__user_id___key_id__put: {
parameters: {
query?: never;
header?: never;
path: {
user_id: number;
key_id: string;
};
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["AccountKeyringUpdate"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AccountKeyring"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
create_keyring_api_v1_keyring__user_id___key_id__post: {
parameters: {
query?: never;
header?: never;
path: {
user_id: number;
key_id: string;
};
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["AccountKeyringUpdate"];
};
};
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AccountKeyring"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
delete_keyring_api_v1_keyring__user_id___key_id__delete: {
parameters: {
query?: never;
header?: never;
path: {
user_id: number;
key_id: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AccountKeyring"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
}

3
client/src/types/user.ts Normal file
View File

@@ -0,0 +1,3 @@
import { components } from "./openapi-types"
export type User = components["schemas"]["User"];

6
package-lock.json generated Normal file
View File

@@ -0,0 +1,6 @@
{
"name": "connect",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

1
package.json Normal file
View File

@@ -0,0 +1 @@
{}