Compare commits
76 Commits
VORKOUT-13
...
master
Author | SHA1 | Date | |
---|---|---|---|
e6589a0950 | |||
a0fcc324fa | |||
|
2a35386c1d | ||
a936500101 | |||
4c0beb24f9 | |||
ad1369c3e3 | |||
5958f29ba8 | |||
784be40369 | |||
ad312d4ff8 | |||
ba65f36696 | |||
ad0a4837fc | |||
1eadd834e3 | |||
8f3fde623f | |||
edfd2c1159 | |||
3d8ee4835d | |||
9c9201f130 | |||
0eed0b0f20 | |||
7127d88524 | |||
692461e266 | |||
22064d2b52 | |||
febac9659f | |||
6c0a6ac1d4 | |||
a7e813b447 | |||
919758ab69 | |||
53bf173373 | |||
8f5dd07bf5 | |||
448e4264a5 | |||
e5dfdc3464 | |||
aae56a8c73 | |||
7c2c4071cc | |||
18bb79262c | |||
a3ee18f6fd | |||
71ab39a03c | |||
5d39065a7f | |||
92ff1d3d0a | |||
5ed8ca9251 | |||
8ac329e76e | |||
c68b512902 | |||
70aaaeabf1 | |||
92203351ff | |||
ee92428ec3 | |||
c87581c9e2 | |||
79cb434ebd | |||
5def1a9bb1 | |||
d55a99aafd | |||
599bf22bda | |||
e114f963ab | |||
d7a5109d8e | |||
8122f1878a | |||
9d2aef5671 | |||
34be97996e | |||
e0dca78ef3 | |||
0bba6e7f54 | |||
21216a6ad5 | |||
2bf0f20e73 | |||
abd87b46b3 | |||
787dc0e8f8 | |||
2e4e9d1113 | |||
c1d315d6e9 | |||
|
a31758192d | ||
8965365afc | |||
|
c68286f7cc | ||
|
58cc23f79b | ||
|
48d52bf903 | ||
|
66c60cfc59 | ||
|
c60d19262e | ||
|
31236d558f | ||
|
5094b84675 | ||
7e1fe6f5c4 | |||
|
320c13183f | ||
|
98a4692247 | ||
8613cbdaac | |||
5f981e8ce1 | |||
|
96dbc744d7 | ||
|
e47e449a36 | ||
|
c3c421f66f |
24
Makefile
24
Makefile
@ -12,36 +12,35 @@ services:
|
||||
|
||||
start-api:
|
||||
cd api && \
|
||||
source .venv/bin/activate && \
|
||||
python -m ${API_APPLICATION_NAME}
|
||||
poetry run python -m ${API_APPLICATION_NAME}
|
||||
|
||||
start-client:
|
||||
cd client && \
|
||||
npm start
|
||||
npm run dev
|
||||
|
||||
migrate:
|
||||
cd api && \
|
||||
source .venv/bin/activate && \
|
||||
cd $(API_APPLICATION_NAME)/db && \
|
||||
PYTHONPATH='../..' ALEMBIC_MIGRATIONS=True alembic upgrade $(args)
|
||||
PYTHONPATH='../..' ALEMBIC_MIGRATIONS=True poetry run alembic upgrade $(args)
|
||||
|
||||
downgrade:
|
||||
cd api && \
|
||||
source .venv/bin/activate && \
|
||||
cd $(API_APPLICATION_NAME)/db && \
|
||||
PYTHONPATH='../..' alembic downgrade -1
|
||||
PYTHONPATH='../..' poetry run alembic downgrade -1
|
||||
|
||||
revision:
|
||||
cd api && \
|
||||
source .venv/bin/activate && \
|
||||
cd $(API_APPLICATION_NAME)/db && \
|
||||
PYTHONPATH='../..' ALEMBIC_MIGRATIONS=True alembic revision --autogenerate
|
||||
PYTHONPATH='../..' ALEMBIC_MIGRATIONS=True poetry run alembic revision --autogenerate
|
||||
|
||||
venv-api:
|
||||
cd api && \
|
||||
poetry env activate \
|
||||
poetry install
|
||||
|
||||
venv-client:
|
||||
cd client && \
|
||||
npm install
|
||||
|
||||
install:
|
||||
make migrate head && \
|
||||
cd api && \
|
||||
@ -58,3 +57,8 @@ format-api:
|
||||
check-api:
|
||||
cd api && \
|
||||
poetry run ruff format . --check
|
||||
|
||||
regenerate-openapi-local:
|
||||
cd client \
|
||||
rm src/types/openapi-types.ts \
|
||||
npx openapi-typescript http://localhost:8000/openapi -o src/types/openapi-types.ts
|
||||
|
50
README.md
50
README.md
@ -1 +1,49 @@
|
||||
Vorkout/connect
|
||||
# Vorkout/connect
|
||||
|
||||
### Makefile cheat sheet
|
||||
|
||||
```Makefile
|
||||
Dev:
|
||||
venv-api create python virtual environment
|
||||
venv-client install node modules
|
||||
install Migrate database and initialize project
|
||||
|
||||
Application Api:
|
||||
start-api Run api server
|
||||
|
||||
Application Client:
|
||||
start-client Run client server
|
||||
|
||||
Prod:
|
||||
...
|
||||
|
||||
Code:
|
||||
check-api Check api code with ruff
|
||||
format-api Reformat api code with ruff
|
||||
|
||||
Help:
|
||||
...
|
||||
|
||||
Testing:
|
||||
...
|
||||
```
|
||||
|
||||
### Запуск в режиме разработки
|
||||
|
||||
Для запуска в режиме разработки нужно
|
||||
|
||||
1. Устрановить среду для clint и api
|
||||
2. Запустить в докере или локально необходимые сервисы (базуб брокер и redis) `make services`
|
||||
3. Для миграции и создания первого пользователя необходимо запустить `make install`
|
||||
3. Запустить api `make start-api`
|
||||
4. Запустить client `make start-client`
|
||||
|
||||
### Миграции алембик
|
||||
|
||||
1. Стоит внимательно учитывать, адрес какой базы стоит в настройках alembic - локальной или продакшн. Посмотреть это можно в файле [env.py](connect/api/api/db/alembic/env.py). Конфиг для локальной базы
|
||||
```python
|
||||
config.set_main_option(
|
||||
"sqlalchemy.url",
|
||||
f"mysql+pymysql://root:hackme@localhost:3306/connect_test",
|
||||
)
|
||||
```
|
||||
|
@ -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)
|
||||
|
38
api/api/db/alembic/versions/93106fbe7d83_.py
Normal file
38
api/api/db/alembic/versions/93106fbe7d83_.py
Normal file
@ -0,0 +1,38 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: 93106fbe7d83
|
||||
Revises: f1b06efacec0
|
||||
Create Date: 2025-06-26 16:36:02.270706
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import mysql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '93106fbe7d83'
|
||||
down_revision: Union[str, None] = 'f1b06efacec0'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Upgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.alter_column('account_keyring', 'key_value',
|
||||
existing_type=mysql.VARCHAR(length=255),
|
||||
type_=sa.String(length=512),
|
||||
existing_nullable=False)
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Downgrade schema."""
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.alter_column('account_keyring', 'key_value',
|
||||
existing_type=sa.String(length=512),
|
||||
type_=mysql.VARCHAR(length=255),
|
||||
existing_nullable=False)
|
||||
# ### end Alembic commands ###
|
@ -1,42 +1,75 @@
|
||||
import math
|
||||
from datetime import datetime, timezone
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from sqlalchemy import insert, select
|
||||
from sqlalchemy import func, insert, select
|
||||
from sqlalchemy.ext.asyncio import AsyncConnection
|
||||
from enum import Enum
|
||||
|
||||
from api.db.tables.account import account_table
|
||||
|
||||
from api.schemas.account.account import User
|
||||
from api.schemas.endpoints.account import UserUpdate, Role, Status
|
||||
from api.schemas.endpoints.account import all_user_adapter, AllUser, AllUserResponse, UserCreate
|
||||
|
||||
|
||||
async def get_user_id(connection: AsyncConnection, id: int) -> Optional[User]:
|
||||
async def get_user_accaunt_page(connection: AsyncConnection, page, limit) -> Optional[AllUserResponse]:
|
||||
"""
|
||||
Получает список ползовелей заданных значениями page, 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,
|
||||
)
|
||||
.order_by(account_table.c.id)
|
||||
.offset(first_user)
|
||||
.limit(limit)
|
||||
)
|
||||
|
||||
count_query = select(func.count()).select_from(account_table)
|
||||
|
||||
result = await connection.execute(query)
|
||||
count_result = await connection.execute(count_query)
|
||||
|
||||
users_data = result.mappings().all()
|
||||
total_count = count_result.scalar()
|
||||
total_pages = math.ceil(total_count / limit)
|
||||
|
||||
validated_users = all_user_adapter.validate_python(users_data)
|
||||
|
||||
return AllUserResponse(
|
||||
users=validated_users,
|
||||
amount_count=total_count,
|
||||
amount_pages=total_pages,
|
||||
current_page=page,
|
||||
limit=limit,
|
||||
)
|
||||
|
||||
|
||||
async def get_user_by_id(connection: AsyncConnection, user_id: int) -> Optional[AllUser]:
|
||||
"""
|
||||
Получает юзера по id.
|
||||
"""
|
||||
query = select(account_table).where(account_table.c.id == id)
|
||||
query = select(account_table).where(account_table.c.id == user_id)
|
||||
|
||||
user_db_cursor = await connection.execute(query)
|
||||
user_db = user_db_cursor.one_or_none()
|
||||
user = user_db_cursor.mappings().one_or_none()
|
||||
|
||||
if not user_db:
|
||||
if not user:
|
||||
return None
|
||||
|
||||
user_data = {
|
||||
column.name: (
|
||||
getattr(user_db, column.name).name
|
||||
if isinstance(getattr(user_db, column.name), Enum)
|
||||
else getattr(user_db, column.name)
|
||||
)
|
||||
for column in account_table.columns
|
||||
}
|
||||
|
||||
return User.model_validate(user_data)
|
||||
return AllUser.model_validate(user)
|
||||
|
||||
|
||||
async def get_user_login(connection: AsyncConnection, login: str) -> Optional[User]:
|
||||
async def get_user_by_login(connection: AsyncConnection, login: str) -> Optional[User]:
|
||||
"""
|
||||
Получает юзера по login.
|
||||
"""
|
||||
@ -60,7 +93,7 @@ async def get_user_login(connection: AsyncConnection, login: str) -> Optional[Us
|
||||
return User.model_validate(user_data)
|
||||
|
||||
|
||||
async def update_user_id(connection: AsyncConnection, update_values, user) -> Optional[User]:
|
||||
async def update_user_by_id(connection: AsyncConnection, update_values, user) -> Optional[User]:
|
||||
"""
|
||||
Вносит изменеия в нужное поле таблицы account_table.
|
||||
"""
|
||||
@ -69,7 +102,7 @@ async def update_user_id(connection: AsyncConnection, update_values, user) -> Op
|
||||
await connection.commit()
|
||||
|
||||
|
||||
async def create_user(connection: AsyncConnection, user: User, creator_id: int) -> Optional[User]:
|
||||
async def create_user(connection: AsyncConnection, user: UserCreate, creator_id: int) -> Optional[AllUser]:
|
||||
"""
|
||||
Создает нове поле в таблице account_table.
|
||||
"""
|
||||
@ -79,14 +112,15 @@ async def create_user(connection: AsyncConnection, user: User, creator_id: int)
|
||||
email=user.email,
|
||||
bind_tenant_id=user.bind_tenant_id,
|
||||
role=user.role.value,
|
||||
meta=user.meta,
|
||||
meta=user.meta or {},
|
||||
creator_id=creator_id,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
status=user.status.value,
|
||||
)
|
||||
|
||||
await connection.execute(query)
|
||||
res = await connection.execute(query)
|
||||
|
||||
await connection.commit()
|
||||
new_user = await get_user_by_id(connection, res.lastrowid)
|
||||
|
||||
return user
|
||||
return new_user
|
||||
|
@ -8,13 +8,14 @@ from api.db.tables.account import account_table, account_keyring_table, KeyType,
|
||||
|
||||
from api.schemas.account.account import User
|
||||
from api.schemas.account.account_keyring import AccountKeyring
|
||||
from api.schemas.endpoints.account import AllUser
|
||||
|
||||
from api.utils.key_id_gen import KeyIdGenerator
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
|
||||
async def get_user(connection: AsyncConnection, login: str) -> Optional[User]:
|
||||
async def get_user(connection: AsyncConnection, login: str) -> tuple[Optional[AllUser], Optional[AccountKeyring]]:
|
||||
query = (
|
||||
select(account_table, account_keyring_table)
|
||||
.join(account_keyring_table, account_table.c.id == account_keyring_table.c.owner_id)
|
||||
@ -45,18 +46,17 @@ async def get_user(connection: AsyncConnection, login: str) -> Optional[User]:
|
||||
for column in account_keyring_table.columns
|
||||
}
|
||||
|
||||
user = User.model_validate(user_data)
|
||||
user = AllUser.model_validate(user_data)
|
||||
password = AccountKeyring.model_validate(password_data)
|
||||
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,
|
||||
|
@ -1,17 +1,17 @@
|
||||
from typing import Optional
|
||||
from datetime import datetime, timezone
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
from sqlalchemy import insert, select
|
||||
from sqlalchemy import insert, select, update
|
||||
from sqlalchemy.dialects.mysql import insert as mysql_insert
|
||||
from sqlalchemy.ext.asyncio import AsyncConnection
|
||||
|
||||
from api.db.tables.account import account_keyring_table
|
||||
|
||||
from api.db.tables.account import account_keyring_table, KeyStatus, KeyType
|
||||
from api.schemas.account.account_keyring import AccountKeyring
|
||||
from api.schemas.endpoints.account_keyring import AccountKeyringUpdate, StatusKey, TypeKey
|
||||
from api.utils.hasher import hasher
|
||||
|
||||
|
||||
async def get_key_id(connection: AsyncConnection, key_id: str) -> Optional[AccountKeyring]:
|
||||
async def get_key_by_id(connection: AsyncConnection, key_id: str) -> Optional[AccountKeyring]:
|
||||
"""
|
||||
Получает key по key_id.
|
||||
"""
|
||||
@ -35,7 +35,7 @@ async def get_key_id(connection: AsyncConnection, key_id: str) -> Optional[Accou
|
||||
return AccountKeyring.model_validate(user_data)
|
||||
|
||||
|
||||
async def update_key_id(connection: AsyncConnection, update_values, key) -> Optional[AccountKeyring]:
|
||||
async def update_key_by_id(connection: AsyncConnection, update_values, key) -> Optional[AccountKeyring]:
|
||||
"""
|
||||
Вносит изменеия в нужное поле таблицы account_keyring_table.
|
||||
"""
|
||||
@ -68,3 +68,37 @@ async def create_key(connection: AsyncConnection, key: AccountKeyring, key_id: i
|
||||
await connection.commit()
|
||||
|
||||
return key
|
||||
|
||||
|
||||
async def create_password_key(connection: AsyncConnection, password: str | None, owner_id: int):
|
||||
if password is None:
|
||||
password = hasher.generate_password()
|
||||
hashed_password = hasher.hash_data(password)
|
||||
stmt = mysql_insert(account_keyring_table).values(
|
||||
owner_id=owner_id,
|
||||
key_type=KeyType.PASSWORD.value,
|
||||
key_id="PASSWORD",
|
||||
key_value=hashed_password,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
expiry=datetime.now(timezone.utc) + timedelta(days=365),
|
||||
status=KeyStatus.ACTIVE,
|
||||
)
|
||||
stmt.on_duplicate_key_update(key_value=hashed_password)
|
||||
await connection.execute(stmt)
|
||||
await connection.commit()
|
||||
|
||||
|
||||
async def update_password_key(connection: AsyncConnection, owner_id: int, password: str):
|
||||
stmt = select(account_keyring_table).where(account_keyring_table.c.owner_id == owner_id)
|
||||
result = await connection.execute(stmt)
|
||||
keyring = result.one_or_none()
|
||||
if not keyring:
|
||||
await create_password_key(connection, password, owner_id)
|
||||
else:
|
||||
stmt = (
|
||||
update(account_keyring_table)
|
||||
.values(key_value=hasher.hash_data(password), expiry=datetime.now(timezone.utc) + timedelta(days=365))
|
||||
.where(account_keyring_table.c.owner_id == owner_id)
|
||||
)
|
||||
await connection.execute(stmt)
|
||||
await connection.commit()
|
||||
|
@ -1,24 +1,26 @@
|
||||
import enum
|
||||
|
||||
from sqlalchemy import Table, Column, String, Enum as SQLAEnum, JSON, ForeignKey, DateTime, Index
|
||||
from sqlalchemy.sql import func
|
||||
|
||||
from enum import Enum, auto
|
||||
from enum import Enum
|
||||
|
||||
from api.db.sql_types import UnsignedInt
|
||||
from api.db import metadata
|
||||
|
||||
|
||||
class AccountRole(str, Enum):
|
||||
OWNER = auto()
|
||||
ADMIN = auto()
|
||||
EDITOR = auto()
|
||||
VIEWER = auto()
|
||||
class AccountRole(enum.StrEnum):
|
||||
OWNER = "OWNER"
|
||||
ADMIN = "ADMIN"
|
||||
EDITOR = "EDITOR"
|
||||
VIEWER = "VIEWER"
|
||||
|
||||
|
||||
class AccountStatus(str, Enum):
|
||||
ACTIVE = auto()
|
||||
DISABLED = auto()
|
||||
BLOCKED = auto()
|
||||
DELETED = auto()
|
||||
class AccountStatus(enum.StrEnum):
|
||||
ACTIVE = "ACTIVE"
|
||||
DISABLED = "DISABLED"
|
||||
BLOCKED = "BLOCKED"
|
||||
DELETED = "DELETED"
|
||||
|
||||
|
||||
account_table = Table(
|
||||
@ -39,17 +41,17 @@ account_table = Table(
|
||||
)
|
||||
|
||||
|
||||
class KeyType(str, Enum):
|
||||
PASSWORD = auto()
|
||||
ACCESS_TOKEN = auto()
|
||||
REFRESH_TOKEN = auto()
|
||||
API_KEY = auto()
|
||||
class KeyType(enum.StrEnum):
|
||||
PASSWORD = "PASSWORD"
|
||||
ACCESS_TOKEN = "ACCESS_TOKEN"
|
||||
REFRESH_TOKEN = "REFRESH_TOKEN"
|
||||
API_KEY = "API_KEY"
|
||||
|
||||
|
||||
class KeyStatus(str, Enum):
|
||||
ACTIVE = auto()
|
||||
EXPIRED = auto()
|
||||
DELETED = auto()
|
||||
class KeyStatus(enum.StrEnum):
|
||||
ACTIVE = "ACTIVE"
|
||||
EXPIRED = "EXPIRED"
|
||||
DELETED = "DELETED"
|
||||
|
||||
|
||||
account_keyring_table = Table(
|
||||
@ -58,7 +60,7 @@ account_keyring_table = Table(
|
||||
Column("owner_id", UnsignedInt, ForeignKey("account.id"), primary_key=True, nullable=False),
|
||||
Column("key_type", SQLAEnum(KeyType), primary_key=True, nullable=False),
|
||||
Column("key_id", String(40), primary_key=True, default=None),
|
||||
Column("key_value", String(255), nullable=False),
|
||||
Column("key_value", String(512), nullable=False),
|
||||
Column("created_at", DateTime(timezone=True), server_default=func.now()),
|
||||
Column("expiry", DateTime(timezone=True), nullable=True),
|
||||
Column("status", SQLAEnum(KeyStatus), nullable=False),
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -1,28 +1,27 @@
|
||||
from fastapi import (
|
||||
APIRouter,
|
||||
Body,
|
||||
Depends,
|
||||
Form,
|
||||
HTTPException,
|
||||
Request,
|
||||
Response,
|
||||
status,
|
||||
)
|
||||
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncConnection
|
||||
|
||||
from api.db.connection.session import get_connection_dep
|
||||
|
||||
from api.db.logic.account import get_user_id, update_user_id, create_user, get_user_login
|
||||
|
||||
from api.schemas.account.account import User, Status
|
||||
from api.schemas.endpoints.account import UserUpdate
|
||||
|
||||
|
||||
from api.services.user_role_validation import db_user_role_validation
|
||||
from api.db.logic.account import (
|
||||
create_user,
|
||||
get_user_accaunt_page,
|
||||
get_user_by_id,
|
||||
get_user_by_login,
|
||||
update_user_by_id,
|
||||
)
|
||||
from api.db.logic.keyring import create_password_key, update_password_key
|
||||
from api.db.tables.account import AccountStatus
|
||||
from api.schemas.account.account import User
|
||||
from api.schemas.base import bearer_schema
|
||||
from api.schemas.endpoints.account import AllUser, AllUserResponse, UserCreate, UserUpdate
|
||||
from api.services.auth import get_current_user
|
||||
from api.services.update_data_validation import update_user_data_changes
|
||||
|
||||
from api.services.user_role_validation import db_user_role_validation
|
||||
|
||||
api_router = APIRouter(
|
||||
prefix="/account",
|
||||
@ -30,12 +29,32 @@ api_router = APIRouter(
|
||||
)
|
||||
|
||||
|
||||
@api_router.get("/{user_id}")
|
||||
async def get_account(user_id: int, request: Request, connection: AsyncConnection = Depends(get_connection_dep)):
|
||||
current_user = request.state.current_user
|
||||
@api_router.get("", dependencies=[Depends(bearer_schema)], response_model=AllUserResponse)
|
||||
async def get_all_account(
|
||||
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 = await get_user_id(connection, user_id)
|
||||
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")
|
||||
|
||||
return user_list
|
||||
|
||||
|
||||
@api_router.get("/{user_id}", dependencies=[Depends(bearer_schema)], response_model=UserUpdate)
|
||||
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)
|
||||
|
||||
if user is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Account not found")
|
||||
@ -43,70 +62,77 @@ async def get_account(user_id: int, request: Request, connection: AsyncConnectio
|
||||
return user
|
||||
|
||||
|
||||
@api_router.post("")
|
||||
async def create_account(user: UserUpdate, request: Request, connection: AsyncConnection = Depends(get_connection_dep)):
|
||||
current_user = request.state.current_user
|
||||
|
||||
@api_router.post("", dependencies=[Depends(bearer_schema)], response_model=AllUser)
|
||||
async def create_account(
|
||||
user: UserCreate,
|
||||
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_login(connection, user.login)
|
||||
user_validation = await get_user_by_login(connection, user.login)
|
||||
|
||||
if user_validation is None:
|
||||
await create_user(connection, user, authorize_user.id)
|
||||
user_new = await get_user_login(connection, user.login)
|
||||
return user_new
|
||||
|
||||
new_user = await create_user(connection, user, authorize_user.id)
|
||||
await create_password_key(connection, user.password, new_user.id)
|
||||
return new_user
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST, detail="An account with this information already exists."
|
||||
)
|
||||
|
||||
|
||||
@api_router.put("/{user_id}")
|
||||
@api_router.put("/{user_id}", dependencies=[Depends(bearer_schema)], response_model=UserUpdate)
|
||||
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_id(connection, user_id)
|
||||
user = await get_user_by_id(connection, user_id)
|
||||
if user is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Account not found")
|
||||
|
||||
if user_update.password is not None:
|
||||
await update_password_key(connection, user.id, user_update.password)
|
||||
|
||||
update_values = update_user_data_changes(user_update, user)
|
||||
|
||||
if update_values is None:
|
||||
return user
|
||||
|
||||
user_update_data = User.model_validate({**user.model_dump(), **update_values})
|
||||
user_update_data = UserUpdate.model_validate({**user.model_dump(), **update_values})
|
||||
|
||||
await update_user_id(connection, update_values, user)
|
||||
await update_user_by_id(connection, update_values, user)
|
||||
|
||||
user = await get_user_id(connection, user_id)
|
||||
user = await get_user_by_id(connection, user_id)
|
||||
|
||||
return user
|
||||
|
||||
|
||||
@api_router.delete("/{user_id}")
|
||||
async def delete_account(user_id: int, request: Request, connection: AsyncConnection = Depends(get_connection_dep)):
|
||||
current_user = request.state.current_user
|
||||
|
||||
@api_router.delete("/{user_id}", dependencies=[Depends(bearer_schema)], response_model=User)
|
||||
async def delete_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_id(connection, user_id)
|
||||
user = await get_user_by_id(connection, user_id)
|
||||
if user is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Account not found")
|
||||
|
||||
user_update = UserUpdate(status=Status.DELETED.value)
|
||||
user_update = UserUpdate(status=AccountStatus.DELETED.value)
|
||||
|
||||
update_values = update_user_data_changes(user_update, user)
|
||||
|
||||
if update_values is None:
|
||||
return user
|
||||
|
||||
await update_user_id(connection, update_values, user)
|
||||
await update_user_by_id(connection, update_values, user)
|
||||
|
||||
user = await get_user_id(connection, user_id)
|
||||
user = await get_user_by_id(connection, user_id)
|
||||
|
||||
return user
|
||||
|
@ -2,13 +2,11 @@ from datetime import datetime, timedelta, timezone
|
||||
|
||||
from fastapi import (
|
||||
APIRouter,
|
||||
Body,
|
||||
Depends,
|
||||
Form,
|
||||
HTTPException,
|
||||
Request,
|
||||
Response,
|
||||
status,
|
||||
Request,
|
||||
)
|
||||
|
||||
from loguru import logger
|
||||
@ -24,7 +22,7 @@ from api.services.auth import authenticate_user
|
||||
|
||||
from api.db.logic.auth import add_new_refresh_token, upgrade_old_refresh_token
|
||||
|
||||
from api.schemas.endpoints.auth import Auth, AccessToken
|
||||
from api.schemas.endpoints.auth import Auth, Tokens
|
||||
|
||||
api_router = APIRouter(
|
||||
prefix="/auth",
|
||||
@ -35,7 +33,7 @@ api_router = APIRouter(
|
||||
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_token_location: set = {"headers"}
|
||||
authjwt_cookie_domain: str = get_settings().DOMAIN
|
||||
|
||||
# Only allow JWT cookies to be sent over https
|
||||
@ -50,7 +48,7 @@ def get_config():
|
||||
return Settings()
|
||||
|
||||
|
||||
@api_router.post("")
|
||||
@api_router.post("", response_model=Tokens)
|
||||
async def login_for_access_token(
|
||||
user: Auth,
|
||||
response: Response,
|
||||
@ -71,7 +69,6 @@ async def login_for_access_token(
|
||||
)
|
||||
|
||||
access_token_expires = timedelta(minutes=get_settings().ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
|
||||
refresh_token_expires = timedelta(days=get_settings().REFRESH_TOKEN_EXPIRE_DAYS)
|
||||
|
||||
logger.debug(f"refresh_token_expires {refresh_token_expires}")
|
||||
@ -83,35 +80,27 @@ async def login_for_access_token(
|
||||
|
||||
await add_new_refresh_token(connection, refresh_token, refresh_token_expires_time, user)
|
||||
|
||||
Authorize.set_refresh_cookies(refresh_token)
|
||||
|
||||
return AccessToken(access_token=access_token)
|
||||
return Tokens(access_token=access_token, refresh_token=refresh_token)
|
||||
|
||||
|
||||
@api_router.post("/refresh")
|
||||
@api_router.post("/refresh", response_model=Tokens)
|
||||
async def refresh(
|
||||
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")
|
||||
|
||||
request: Request,
|
||||
connection: AsyncConnection = Depends(get_connection_dep),
|
||||
Authorize: AuthJWT = Depends(),
|
||||
) -> Tokens:
|
||||
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)
|
||||
|
||||
except Exception:
|
||||
refresh_token = request.headers.get("Authorization").split(" ")[1]
|
||||
await upgrade_old_refresh_token(connection, 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)
|
||||
|
||||
new_access_token = Authorize.create_access_token(subject=current_user, expires_time=access_token_expires)
|
||||
|
||||
return AccessToken(access_token=new_access_token)
|
||||
return Tokens(access_token=new_access_token)
|
||||
|
@ -4,7 +4,6 @@ from fastapi import (
|
||||
Depends,
|
||||
Form,
|
||||
HTTPException,
|
||||
Request,
|
||||
Response,
|
||||
status,
|
||||
)
|
||||
@ -14,13 +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_id, create_key, update_key_id
|
||||
from api.db.logic.keyring import get_key_by_id, create_key, update_key_by_id
|
||||
|
||||
|
||||
from api.schemas.account.account import Status
|
||||
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
|
||||
@ -32,13 +33,13 @@ api_router = APIRouter(
|
||||
)
|
||||
|
||||
|
||||
@api_router.get("/{user_id}/{key_id}")
|
||||
async def get_keyring(key_id: str, request: Request, connection: AsyncConnection = Depends(get_connection_dep)):
|
||||
current_user = request.state.current_user
|
||||
|
||||
@api_router.get("/{user_id}/{key_id}", dependencies=[Depends(bearer_schema)], response_model=AccountKeyring)
|
||||
async def get_keyring(
|
||||
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_id(connection, key_id)
|
||||
keyring = await get_key_by_id(connection, key_id)
|
||||
|
||||
if keyring is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Key not found")
|
||||
@ -46,27 +47,25 @@ async def get_keyring(key_id: str, request: Request, connection: AsyncConnection
|
||||
return keyring
|
||||
|
||||
|
||||
@api_router.post("/{user_id}/{key_id}")
|
||||
@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_id(connection, key_id)
|
||||
keyring = await get_key_by_id(connection, key_id)
|
||||
|
||||
if keyring is None:
|
||||
user_new = await create_key(
|
||||
keyring_new = await create_key(
|
||||
connection,
|
||||
key,
|
||||
key_id,
|
||||
)
|
||||
return user_new
|
||||
return keyring_new
|
||||
|
||||
else:
|
||||
raise HTTPException(
|
||||
@ -74,19 +73,17 @@ async def create_keyring(
|
||||
)
|
||||
|
||||
|
||||
@api_router.put("/{user_id}/{key_id}")
|
||||
@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_id(connection, key_id)
|
||||
keyring = await get_key_by_id(connection, key_id)
|
||||
if keyring is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="keyring not found")
|
||||
|
||||
@ -97,34 +94,35 @@ async def update_keyring(
|
||||
|
||||
keyring_update_data = AccountKeyring.model_validate({**keyring.model_dump(), **update_values})
|
||||
|
||||
await update_key_id(connection, update_values, keyring)
|
||||
await update_key_by_id(connection, update_values, keyring)
|
||||
|
||||
keyring = await get_key_id(connection, key_id)
|
||||
keyring = await get_key_by_id(connection, key_id)
|
||||
|
||||
return keyring
|
||||
|
||||
|
||||
@api_router.delete("/{user_id}/{key_id}")
|
||||
@api_router.delete("/{user_id}/{key_id}", dependencies=[Depends(bearer_schema)], response_model=AccountKeyring)
|
||||
async def delete_keyring(
|
||||
user_id: int, key_id: str, request: Request, connection: AsyncConnection = Depends(get_connection_dep)
|
||||
user_id: int,
|
||||
key_id: str,
|
||||
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_id(connection, key_id)
|
||||
keyring = await get_key_by_id(connection, key_id)
|
||||
if keyring is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="keyring not found")
|
||||
|
||||
keyring_update = AccountKeyringUpdate(status=Status.DELETED.value)
|
||||
keyring_update = AccountKeyringUpdate(status=KeyStatus.DELETED.value)
|
||||
|
||||
update_values = update_key_data_changes(keyring_update, keyring)
|
||||
|
||||
if update_values is None:
|
||||
return keyring
|
||||
|
||||
await update_key_id(connection, update_values, keyring)
|
||||
await update_key_by_id(connection, update_values, keyring)
|
||||
|
||||
keyring = await get_key_id(connection, key_id)
|
||||
keyring = await get_key_by_id(connection, key_id)
|
||||
|
||||
return keyring
|
||||
|
@ -13,10 +13,13 @@ 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_id, update_user_id, get_user_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
|
||||
from api.schemas.account.account import User
|
||||
|
||||
|
||||
api_router = APIRouter(
|
||||
@ -25,15 +28,11 @@ api_router = APIRouter(
|
||||
)
|
||||
|
||||
|
||||
@api_router.get("")
|
||||
@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_login(connection, 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")
|
||||
@ -41,15 +40,13 @@ async def get_profile(
|
||||
return user
|
||||
|
||||
|
||||
@api_router.put("")
|
||||
@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_login(connection, 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")
|
||||
|
||||
@ -59,9 +56,9 @@ async def update_profile(
|
||||
if update_values is None:
|
||||
return user
|
||||
|
||||
await update_user_id(connection, update_values, user)
|
||||
await update_user_by_id(connection, update_values, user)
|
||||
|
||||
user = await get_user_id(connection, user.id)
|
||||
user = await get_user_by_id(connection, user.id)
|
||||
|
||||
return user
|
||||
else:
|
||||
|
@ -1,37 +1,20 @@
|
||||
import datetime
|
||||
from enum import Enum
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from pydantic import EmailStr, Field
|
||||
from api.db.tables.account import AccountRole, AccountStatus
|
||||
|
||||
from api.schemas.base import Base
|
||||
|
||||
|
||||
# Модель для хранения информации из запроса
|
||||
|
||||
|
||||
class Role(Enum):
|
||||
OWNER = "OWNER"
|
||||
ADMIN = "ADMIN"
|
||||
EDITOR = "EDITOR"
|
||||
VIEWER = "VIEWER"
|
||||
|
||||
|
||||
class Status(Enum):
|
||||
ACTIVE = "ACTIVE"
|
||||
DISABLED = "DISABLED"
|
||||
BLOCKED = "BLOCKED"
|
||||
DELETED = "DELETED"
|
||||
|
||||
|
||||
class User(Base):
|
||||
id: Optional[int] = None
|
||||
name: str = Field(..., max_length=100)
|
||||
login: str = Field(..., max_length=100)
|
||||
email: Optional[EmailStr] = Field(None, max_length=100) # Электронная почта (может быть None)
|
||||
bind_tenant_id: Optional[str] = Field(None, max_length=40)
|
||||
role: Role
|
||||
role: AccountRole
|
||||
meta: dict
|
||||
creator_id: Optional[int] = None
|
||||
created_at: datetime
|
||||
status: Status
|
||||
status: AccountStatus
|
||||
|
@ -1,33 +1,17 @@
|
||||
import datetime
|
||||
from enum import Enum
|
||||
from typing import Optional, Dict
|
||||
from typing import Optional
|
||||
from pydantic import Field
|
||||
from datetime import datetime
|
||||
from api.db.tables.account import KeyType, KeyStatus
|
||||
|
||||
from api.schemas.base import Base
|
||||
|
||||
|
||||
# Модель для хранения информации из запроса
|
||||
|
||||
|
||||
class TypeKey(Enum):
|
||||
PASSWORD = "PASSWORD"
|
||||
ACCESS_TOKEN = "ACCESS_TOKEN"
|
||||
REFRESH_TOKEN = "REFRESH_TOKEN"
|
||||
API_KEY = "API_KEY"
|
||||
|
||||
|
||||
class StatusKey(Enum):
|
||||
ACTIVE = "ACTIVE"
|
||||
EXPIRED = "EXPIRED"
|
||||
DELETED = "DELETED"
|
||||
|
||||
|
||||
class AccountKeyring(Base):
|
||||
owner_id: int
|
||||
key_type: TypeKey # Используем тот же KeyType
|
||||
key_id: Optional[str] = Field(None, max_length=40) # Изменено на None как default
|
||||
key_type: KeyType
|
||||
key_id: Optional[str] = Field(None, max_length=40)
|
||||
key_value: str = Field(..., max_length=255)
|
||||
created_at: datetime
|
||||
expiry: Optional[datetime] = None
|
||||
status: StatusKey
|
||||
status: KeyStatus
|
||||
|
@ -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,
|
||||
|
@ -1,36 +1,54 @@
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
from pydantic import EmailStr, Field
|
||||
from typing import List, Optional
|
||||
|
||||
from pydantic import EmailStr, Field, TypeAdapter
|
||||
|
||||
from api.db.tables.account import AccountRole, AccountStatus
|
||||
from api.schemas.base import Base
|
||||
|
||||
|
||||
# Таблица для получения информации из запроса
|
||||
|
||||
|
||||
class Role(Enum):
|
||||
OWNER = "OWNER"
|
||||
ADMIN = "ADMIN"
|
||||
EDITOR = "EDITOR"
|
||||
VIEWER = "VIEWER"
|
||||
|
||||
|
||||
class Status(Enum):
|
||||
ACTIVE = "ACTIVE"
|
||||
DISABLED = "DISABLED"
|
||||
BLOCKED = "BLOCKED"
|
||||
DELETED = "DELETED"
|
||||
|
||||
|
||||
class UserUpdate(Base):
|
||||
id: Optional[int] = None
|
||||
name: Optional[str] = Field(None, max_length=100)
|
||||
login: Optional[str] = Field(None, max_length=100)
|
||||
email: Optional[EmailStr] = None
|
||||
password: Optional[str] = None
|
||||
bind_tenant_id: Optional[str] = Field(None, max_length=40)
|
||||
role: Optional[Role] = None
|
||||
role: Optional[AccountRole] = None
|
||||
meta: Optional[dict] = None
|
||||
creator_id: Optional[int] = None
|
||||
created_at: Optional[datetime] = None
|
||||
status: Optional[Status] = None
|
||||
status: Optional[AccountStatus] = None
|
||||
|
||||
|
||||
class UserCreate(Base):
|
||||
name: str = Field(max_length=100)
|
||||
login: str = Field(max_length=100)
|
||||
email: Optional[EmailStr] = None
|
||||
password: Optional[str] = None
|
||||
bind_tenant_id: Optional[str] = Field(None, max_length=40)
|
||||
role: AccountRole
|
||||
meta: Optional[dict] = None
|
||||
status: AccountStatus
|
||||
|
||||
|
||||
class AllUser(Base):
|
||||
id: int
|
||||
name: str
|
||||
login: str
|
||||
email: Optional[EmailStr] = None
|
||||
bind_tenant_id: Optional[str] = None
|
||||
role: AccountRole
|
||||
created_at: datetime
|
||||
status: AccountStatus
|
||||
|
||||
|
||||
class AllUserResponse(Base):
|
||||
users: List[AllUser]
|
||||
amount_count: int
|
||||
amount_pages: int
|
||||
current_page: int
|
||||
limit: int
|
||||
|
||||
|
||||
all_user_adapter = TypeAdapter(List[AllUser])
|
||||
|
@ -1,33 +1,17 @@
|
||||
import datetime
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
from pydantic import Field
|
||||
from datetime import datetime
|
||||
from api.db.tables.account import KeyType, KeyStatus
|
||||
|
||||
from api.schemas.base import Base
|
||||
|
||||
|
||||
# Таблица для получения информации из запроса
|
||||
|
||||
|
||||
class TypeKey(Enum):
|
||||
PASSWORD = "PASSWORD"
|
||||
ACCESS_TOKEN = "ACCESS_TOKEN"
|
||||
REFRESH_TOKEN = "REFRESH_TOKEN"
|
||||
API_KEY = "API_KEY"
|
||||
|
||||
|
||||
class StatusKey(Enum):
|
||||
ACTIVE = "ACTIVE"
|
||||
EXPIRED = "EXPIRED"
|
||||
DELETED = "DELETED"
|
||||
|
||||
|
||||
class AccountKeyringUpdate(Base):
|
||||
owner_id: Optional[int] = None
|
||||
key_type: Optional[TypeKey] = None
|
||||
key_type: Optional[KeyType] = None
|
||||
key_id: Optional[str] = Field(None, max_length=40)
|
||||
key_value: Optional[str] = Field(None, max_length=255)
|
||||
created_at: Optional[datetime] = None
|
||||
expiry: Optional[datetime] = None
|
||||
status: Optional[StatusKey] = None
|
||||
status: Optional[KeyStatus] = None
|
||||
|
@ -1,6 +1,5 @@
|
||||
from api.schemas.base import Base
|
||||
|
||||
|
||||
# Таблица для получения информации из запроса
|
||||
|
||||
|
||||
@ -9,9 +8,6 @@ class Auth(Base):
|
||||
password: str
|
||||
|
||||
|
||||
class AccessToken(Base):
|
||||
class Tokens(Base):
|
||||
access_token: str
|
||||
|
||||
|
||||
class Refresh(Base):
|
||||
refresh_token: str
|
||||
refresh_token: str | None = None
|
||||
|
@ -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
|
||||
|
@ -1,18 +1,25 @@
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import HTTPException, Request
|
||||
from sqlalchemy.ext.asyncio import AsyncConnection
|
||||
|
||||
from api.db.logic.auth import get_user
|
||||
|
||||
# # from backend.schemas.users.token import TokenData
|
||||
from api.schemas.account.account import User, Status
|
||||
from api.utils.hasher import Hasher
|
||||
from api.db.tables.account import AccountStatus
|
||||
from api.schemas.endpoints.account import AllUser
|
||||
from api.utils.hasher import hasher
|
||||
|
||||
|
||||
async def authenticate_user(connection: AsyncConnection, username: str, password: str) -> Optional[User]:
|
||||
async def get_current_user(request: Request) -> str | HTTPException:
|
||||
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[AllUser]:
|
||||
sql_user, sql_password = await get_user(connection, username)
|
||||
|
||||
if not sql_user or sql_user.status != Status.ACTIVE:
|
||||
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
|
||||
return sql_user
|
||||
|
@ -1,3 +1,4 @@
|
||||
from fastapi_jwt_auth import AuthJWT
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
from fastapi import (
|
||||
Request,
|
||||
@ -11,9 +12,6 @@ import re
|
||||
from re import escape
|
||||
|
||||
|
||||
from fastapi_jwt_auth import AuthJWT
|
||||
|
||||
|
||||
class MiddlewareAccessTokenValidadtion(BaseHTTPMiddleware):
|
||||
def __init__(self, app):
|
||||
super().__init__(app)
|
||||
@ -22,40 +20,34 @@ 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"},
|
||||
)
|
||||
|
||||
token = auth_header.split(" ")[1]
|
||||
Authorize = AuthJWT(request)
|
||||
|
||||
try:
|
||||
current_user = Authorize.get_jwt_subject()
|
||||
request.state.current_user = current_user
|
||||
return await call_next(request)
|
||||
|
||||
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.")
|
||||
if request.method not in ["GET", "POST", "PUT", "DELETE"]:
|
||||
return JSONResponse(
|
||||
status_code=status.HTTP_405_METHOD_NOT_ALLOWED,
|
||||
content={"detail": "Method not allowed"},
|
||||
)
|
||||
if any(pattern.match(request.url.path) for pattern in self.excluded_routes):
|
||||
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"},
|
||||
)
|
||||
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"},
|
||||
)
|
||||
return await call_next(request)
|
||||
|
@ -1,7 +1,9 @@
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
from api.schemas.endpoints.account import UserUpdate, Role, Status
|
||||
from api.schemas.endpoints.account_keyring import AccountKeyringUpdate, StatusKey, TypeKey
|
||||
from api.schemas.endpoints.account import UserUpdate
|
||||
from api.db.tables.account import KeyType, KeyStatus
|
||||
from api.schemas.endpoints.account_keyring import AccountKeyringUpdate
|
||||
from api.db.tables.account import AccountRole, AccountStatus
|
||||
|
||||
|
||||
def update_user_data_changes(update_data: UserUpdate, user) -> Optional[dict]:
|
||||
@ -18,7 +20,7 @@ def update_user_data_changes(update_data: UserUpdate, user) -> Optional[dict]:
|
||||
if value is None:
|
||||
continue
|
||||
|
||||
if isinstance(value, (Role, Status)):
|
||||
if isinstance(value, (AccountRole, AccountStatus)):
|
||||
update_values[field] = value.value
|
||||
else:
|
||||
update_values[field] = value
|
||||
@ -52,7 +54,7 @@ def update_key_data_changes(update_data: AccountKeyringUpdate, key) -> Optional[
|
||||
if value is None:
|
||||
continue
|
||||
|
||||
if isinstance(value, (TypeKey, StatusKey)):
|
||||
if isinstance(value, (KeyType, KeyStatus)):
|
||||
update_values[field] = value.value
|
||||
else:
|
||||
update_values[field] = value
|
||||
|
@ -2,12 +2,12 @@ from fastapi import (
|
||||
HTTPException,
|
||||
status,
|
||||
)
|
||||
from api.db.logic.account import get_user_login
|
||||
from api.schemas.account.account import Role, Status
|
||||
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_login(connection, current_user)
|
||||
if authorize_user.role not in {Role.OWNER, Role.ADMIN}:
|
||||
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")
|
||||
return authorize_user
|
||||
|
@ -1,4 +1,6 @@
|
||||
import hashlib
|
||||
import secrets
|
||||
|
||||
|
||||
# Хешер для работы с паролем.
|
||||
|
||||
@ -14,3 +16,10 @@ class Hasher:
|
||||
def verify_data(self, password: str, hashed: str) -> bool:
|
||||
# Проверяет пароль путем сравнения его хеша с сохраненным хешем.
|
||||
return self.hash_data(password) == hashed
|
||||
|
||||
@staticmethod
|
||||
def generate_password() -> str:
|
||||
return secrets.token_urlsafe(20)
|
||||
|
||||
|
||||
hasher = Hasher()
|
||||
|
@ -1,32 +1,23 @@
|
||||
import os
|
||||
import asyncio
|
||||
import hashlib
|
||||
import secrets
|
||||
import os
|
||||
|
||||
from api.db.connection.session import get_connection
|
||||
from api.db.tables.account import account_table, account_keyring_table, AccountRole, KeyType, KeyStatus
|
||||
from api.db.tables.account import account_keyring_table, account_table, AccountRole, KeyStatus, KeyType
|
||||
from api.utils.hasher import hasher
|
||||
from api.utils.key_id_gen import KeyIdGenerator
|
||||
|
||||
INIT_LOCK_FILE = "../init.lock"
|
||||
DEFAULT_LOGIN = "vorkout"
|
||||
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
return hashlib.sha256(password.encode()).hexdigest()
|
||||
|
||||
|
||||
def generate_password() -> str:
|
||||
return secrets.token_urlsafe(20)
|
||||
|
||||
|
||||
async def init():
|
||||
if os.path.exists(INIT_LOCK_FILE):
|
||||
print("Sorry, service is already initialized")
|
||||
return
|
||||
|
||||
async with get_connection() as conn:
|
||||
password = generate_password()
|
||||
hashed_password = hash_password(password)
|
||||
password = hasher.generate_password()
|
||||
hashed_password = hasher.hash_data(password)
|
||||
|
||||
create_user_query = account_table.insert().values(
|
||||
name=DEFAULT_LOGIN,
|
||||
|
565
api/poetry.lock
generated
565
api/poetry.lock
generated
@ -106,6 +106,18 @@ doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)",
|
||||
test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""]
|
||||
trio = ["trio (>=0.26.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2025.4.26"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "certifi-2025.4.26-py3-none-any.whl", hash = "sha256:30350364dfe371162649852c63336a15c70c6510c2ad5015b21c2345311805f3"},
|
||||
{file = "certifi-2025.4.26.tar.gz", hash = "sha256:0a816057ea3cdefcef70270d2c515e4506bbc954f417fa5ade2021213bb8f0c6"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cffi"
|
||||
version = "1.17.1"
|
||||
@ -338,14 +350,40 @@ files = [
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
email-validator = {version = ">=2.0.0", optional = true, markers = "extra == \"standard\""}
|
||||
fastapi-cli = {version = ">=0.0.5", extras = ["standard"], optional = true, markers = "extra == \"standard\""}
|
||||
httpx = {version = ">=0.23.0", optional = true, markers = "extra == \"standard\""}
|
||||
jinja2 = {version = ">=3.1.5", optional = true, markers = "extra == \"standard\""}
|
||||
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
|
||||
python-multipart = {version = ">=0.0.18", optional = true, markers = "extra == \"standard\""}
|
||||
starlette = ">=0.40.0,<0.47.0"
|
||||
typing-extensions = ">=4.8.0"
|
||||
uvicorn = {version = ">=0.12.0", extras = ["standard"], optional = true, markers = "extra == \"standard\""}
|
||||
|
||||
[package.extras]
|
||||
all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
|
||||
standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi-cli"
|
||||
version = "0.0.7"
|
||||
description = "Run and manage FastAPI apps from the command line with FastAPI CLI. 🚀"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "fastapi_cli-0.0.7-py3-none-any.whl", hash = "sha256:d549368ff584b2804336c61f192d86ddea080c11255f375959627911944804f4"},
|
||||
{file = "fastapi_cli-0.0.7.tar.gz", hash = "sha256:02b3b65956f526412515907a0793c9094abd4bfb5457b389f645b0ea6ba3605e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
rich-toolkit = ">=0.11.1"
|
||||
typer = ">=0.12.3"
|
||||
uvicorn = {version = ">=0.15.0", extras = ["standard"]}
|
||||
|
||||
[package.extras]
|
||||
standard = ["uvicorn[standard] (>=0.15.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi-jwt-auth"
|
||||
version = "0.5.0"
|
||||
@ -472,6 +510,109 @@ files = [
|
||||
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "1.0.8"
|
||||
description = "A minimal low-level HTTP client."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "httpcore-1.0.8-py3-none-any.whl", hash = "sha256:5254cf149bcb5f75e9d1b2b9f729ea4a4b883d1ad7379fc632b727cec23674be"},
|
||||
{file = "httpcore-1.0.8.tar.gz", hash = "sha256:86e94505ed24ea06514883fd44d2bc02d90e77e7979c8eb71b90f41d364a1bad"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
certifi = "*"
|
||||
h11 = ">=0.13,<0.15"
|
||||
|
||||
[package.extras]
|
||||
asyncio = ["anyio (>=4.0,<5.0)"]
|
||||
http2 = ["h2 (>=3,<5)"]
|
||||
socks = ["socksio (==1.*)"]
|
||||
trio = ["trio (>=0.22.0,<1.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "httptools"
|
||||
version = "0.6.4"
|
||||
description = "A collection of framework independent HTTP protocol utils."
|
||||
optional = false
|
||||
python-versions = ">=3.8.0"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "httptools-0.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3c73ce323711a6ffb0d247dcd5a550b8babf0f757e86a52558fe5b86d6fefcc0"},
|
||||
{file = "httptools-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:345c288418f0944a6fe67be8e6afa9262b18c7626c3ef3c28adc5eabc06a68da"},
|
||||
{file = "httptools-0.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deee0e3343f98ee8047e9f4c5bc7cedbf69f5734454a94c38ee829fb2d5fa3c1"},
|
||||
{file = "httptools-0.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca80b7485c76f768a3bc83ea58373f8db7b015551117375e4918e2aa77ea9b50"},
|
||||
{file = "httptools-0.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:90d96a385fa941283ebd231464045187a31ad932ebfa541be8edf5b3c2328959"},
|
||||
{file = "httptools-0.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:59e724f8b332319e2875efd360e61ac07f33b492889284a3e05e6d13746876f4"},
|
||||
{file = "httptools-0.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:c26f313951f6e26147833fc923f78f95604bbec812a43e5ee37f26dc9e5a686c"},
|
||||
{file = "httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069"},
|
||||
{file = "httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a"},
|
||||
{file = "httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975"},
|
||||
{file = "httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636"},
|
||||
{file = "httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721"},
|
||||
{file = "httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988"},
|
||||
{file = "httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17"},
|
||||
{file = "httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2"},
|
||||
{file = "httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44"},
|
||||
{file = "httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1"},
|
||||
{file = "httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2"},
|
||||
{file = "httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81"},
|
||||
{file = "httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f"},
|
||||
{file = "httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970"},
|
||||
{file = "httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660"},
|
||||
{file = "httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083"},
|
||||
{file = "httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3"},
|
||||
{file = "httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071"},
|
||||
{file = "httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5"},
|
||||
{file = "httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0"},
|
||||
{file = "httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8"},
|
||||
{file = "httptools-0.6.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d3f0d369e7ffbe59c4b6116a44d6a8eb4783aae027f2c0b366cf0aa964185dba"},
|
||||
{file = "httptools-0.6.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:94978a49b8f4569ad607cd4946b759d90b285e39c0d4640c6b36ca7a3ddf2efc"},
|
||||
{file = "httptools-0.6.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40dc6a8e399e15ea525305a2ddba998b0af5caa2566bcd79dcbe8948181eeaff"},
|
||||
{file = "httptools-0.6.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab9ba8dcf59de5181f6be44a77458e45a578fc99c31510b8c65b7d5acc3cf490"},
|
||||
{file = "httptools-0.6.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:fc411e1c0a7dcd2f902c7c48cf079947a7e65b5485dea9decb82b9105ca71a43"},
|
||||
{file = "httptools-0.6.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:d54efd20338ac52ba31e7da78e4a72570cf729fac82bc31ff9199bedf1dc7440"},
|
||||
{file = "httptools-0.6.4-cp38-cp38-win_amd64.whl", hash = "sha256:df959752a0c2748a65ab5387d08287abf6779ae9165916fe053e68ae1fbdc47f"},
|
||||
{file = "httptools-0.6.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:85797e37e8eeaa5439d33e556662cc370e474445d5fab24dcadc65a8ffb04003"},
|
||||
{file = "httptools-0.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:db353d22843cf1028f43c3651581e4bb49374d85692a85f95f7b9a130e1b2cab"},
|
||||
{file = "httptools-0.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ffd262a73d7c28424252381a5b854c19d9de5f56f075445d33919a637e3547"},
|
||||
{file = "httptools-0.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703c346571fa50d2e9856a37d7cd9435a25e7fd15e236c397bf224afaa355fe9"},
|
||||
{file = "httptools-0.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aafe0f1918ed07b67c1e838f950b1c1fabc683030477e60b335649b8020e1076"},
|
||||
{file = "httptools-0.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0e563e54979e97b6d13f1bbc05a96109923e76b901f786a5eae36e99c01237bd"},
|
||||
{file = "httptools-0.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:b799de31416ecc589ad79dd85a0b2657a8fe39327944998dea368c1d4c9e55e6"},
|
||||
{file = "httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
test = ["Cython (>=0.29.24)"]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.28.1"
|
||||
description = "The next generation HTTP client."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"},
|
||||
{file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
anyio = "*"
|
||||
certifi = "*"
|
||||
httpcore = "==1.*"
|
||||
idna = "*"
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""]
|
||||
cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
|
||||
http2 = ["h2 (>=3,<5)"]
|
||||
socks = ["socksio (==1.*)"]
|
||||
zstd = ["zstandard (>=0.18.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.10"
|
||||
@ -487,6 +628,24 @@ files = [
|
||||
[package.extras]
|
||||
all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "jinja2"
|
||||
version = "3.1.6"
|
||||
description = "A very fast and expressive template engine."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"},
|
||||
{file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
MarkupSafe = ">=2.0"
|
||||
|
||||
[package.extras]
|
||||
i18n = ["Babel (>=2.7)"]
|
||||
|
||||
[[package]]
|
||||
name = "loguru"
|
||||
version = "0.7.3"
|
||||
@ -526,6 +685,31 @@ babel = ["Babel"]
|
||||
lingua = ["lingua"]
|
||||
testing = ["pytest"]
|
||||
|
||||
[[package]]
|
||||
name = "markdown-it-py"
|
||||
version = "3.0.0"
|
||||
description = "Python port of markdown-it. Markdown parsing, done right!"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"},
|
||||
{file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
mdurl = ">=0.1,<1.0"
|
||||
|
||||
[package.extras]
|
||||
benchmarking = ["psutil", "pytest", "pytest-benchmark"]
|
||||
code-style = ["pre-commit (>=3.0,<4.0)"]
|
||||
compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"]
|
||||
linkify = ["linkify-it-py (>=1,<3)"]
|
||||
plugins = ["mdit-py-plugins"]
|
||||
profiling = ["gprof2dot"]
|
||||
rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"]
|
||||
testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"]
|
||||
|
||||
[[package]]
|
||||
name = "markupsafe"
|
||||
version = "3.0.2"
|
||||
@ -597,6 +781,18 @@ files = [
|
||||
{file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mdurl"
|
||||
version = "0.1.2"
|
||||
description = "Markdown URL utilities"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"},
|
||||
{file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multidict"
|
||||
version = "6.2.0"
|
||||
@ -992,6 +1188,21 @@ azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0
|
||||
toml = ["tomli (>=2.0.1)"]
|
||||
yaml = ["pyyaml (>=6.0.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.19.1"
|
||||
description = "Pygments is a syntax highlighting package written in Python."
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"},
|
||||
{file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
windows-terminal = ["colorama (>=0.4.6)"]
|
||||
|
||||
[[package]]
|
||||
name = "pyjwt"
|
||||
version = "1.7.1"
|
||||
@ -1052,6 +1263,105 @@ files = [
|
||||
{file = "python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "6.0.2"
|
||||
description = "YAML parser and emitter for Python"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"},
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"},
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"},
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"},
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"},
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"},
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"},
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"},
|
||||
{file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"},
|
||||
{file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"},
|
||||
{file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"},
|
||||
{file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"},
|
||||
{file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"},
|
||||
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"},
|
||||
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"},
|
||||
{file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"},
|
||||
{file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"},
|
||||
{file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"},
|
||||
{file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"},
|
||||
{file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"},
|
||||
{file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "14.0.0"
|
||||
description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
|
||||
optional = false
|
||||
python-versions = ">=3.8.0"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0"},
|
||||
{file = "rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
markdown-it-py = ">=2.2.0"
|
||||
pygments = ">=2.13.0,<3.0.0"
|
||||
|
||||
[package.extras]
|
||||
jupyter = ["ipywidgets (>=7.5.1,<9)"]
|
||||
|
||||
[[package]]
|
||||
name = "rich-toolkit"
|
||||
version = "0.14.7"
|
||||
description = "Rich toolkit for building command-line applications"
|
||||
optional = false
|
||||
python-versions = ">=3.8"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "rich_toolkit-0.14.7-py3-none-any.whl", hash = "sha256:def05cc6e0f1176d6263b6a26648f16a62c4563b277ca2f8538683acdba1e0da"},
|
||||
{file = "rich_toolkit-0.14.7.tar.gz", hash = "sha256:6cca5a68850cc5778915f528eb785662c27ba3b4b2624612cce8340fa9701c5e"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=8.1.7"
|
||||
rich = ">=13.7.1"
|
||||
typing-extensions = ">=4.12.2"
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.11.10"
|
||||
@ -1080,6 +1390,18 @@ files = [
|
||||
{file = "ruff-0.11.10.tar.gz", hash = "sha256:d522fb204b4959909ecac47da02830daec102eeb100fb50ea9554818d47a5fa6"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shellingham"
|
||||
version = "1.5.4"
|
||||
description = "Tool to Detect Surrounding Shell"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"},
|
||||
{file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.3.1"
|
||||
@ -1208,6 +1530,24 @@ anyio = ">=3.6.2,<5"
|
||||
[package.extras]
|
||||
full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"]
|
||||
|
||||
[[package]]
|
||||
name = "typer"
|
||||
version = "0.16.0"
|
||||
description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "typer-0.16.0-py3-none-any.whl", hash = "sha256:1f79bed11d4d02d4310e3c1b7ba594183bcedb0ac73b27a9e5f28f6fb5b98855"},
|
||||
{file = "typer-0.16.0.tar.gz", hash = "sha256:af377ffaee1dbe37ae9440cb4e8f11686ea5ce4e9bae01b84ae7c63b87f1dd3b"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=8.0.0"
|
||||
rich = ">=10.11.0"
|
||||
shellingham = ">=1.3.0"
|
||||
typing-extensions = ">=3.7.4.3"
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.12.2"
|
||||
@ -1249,11 +1589,234 @@ files = [
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=7.0"
|
||||
colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""}
|
||||
h11 = ">=0.8"
|
||||
httptools = {version = ">=0.6.3", optional = true, markers = "extra == \"standard\""}
|
||||
python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
|
||||
pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""}
|
||||
uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\" and extra == \"standard\""}
|
||||
watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
|
||||
websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""}
|
||||
|
||||
[package.extras]
|
||||
standard = ["colorama (>=0.4) ; sys_platform == \"win32\"", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1) ; sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\"", "watchfiles (>=0.13)", "websockets (>=10.4)"]
|
||||
|
||||
[[package]]
|
||||
name = "uvloop"
|
||||
version = "0.21.0"
|
||||
description = "Fast implementation of asyncio event loop on top of libuv"
|
||||
optional = false
|
||||
python-versions = ">=3.8.0"
|
||||
groups = ["main"]
|
||||
markers = "sys_platform != \"win32\" and sys_platform != \"cygwin\" and platform_python_implementation != \"PyPy\""
|
||||
files = [
|
||||
{file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ec7e6b09a6fdded42403182ab6b832b71f4edaf7f37a9a0e371a01db5f0cb45f"},
|
||||
{file = "uvloop-0.21.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:196274f2adb9689a289ad7d65700d37df0c0930fd8e4e743fa4834e850d7719d"},
|
||||
{file = "uvloop-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f38b2e090258d051d68a5b14d1da7203a3c3677321cf32a95a6f4db4dd8b6f26"},
|
||||
{file = "uvloop-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c43e0f13022b998eb9b973b5e97200c8b90823454d4bc06ab33829e09fb9bb"},
|
||||
{file = "uvloop-0.21.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:10d66943def5fcb6e7b37310eb6b5639fd2ccbc38df1177262b0640c3ca68c1f"},
|
||||
{file = "uvloop-0.21.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:67dd654b8ca23aed0a8e99010b4c34aca62f4b7fce88f39d452ed7622c94845c"},
|
||||
{file = "uvloop-0.21.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8"},
|
||||
{file = "uvloop-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0"},
|
||||
{file = "uvloop-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e"},
|
||||
{file = "uvloop-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb"},
|
||||
{file = "uvloop-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6"},
|
||||
{file = "uvloop-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d"},
|
||||
{file = "uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c"},
|
||||
{file = "uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2"},
|
||||
{file = "uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d"},
|
||||
{file = "uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc"},
|
||||
{file = "uvloop-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb"},
|
||||
{file = "uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f"},
|
||||
{file = "uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281"},
|
||||
{file = "uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af"},
|
||||
{file = "uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6"},
|
||||
{file = "uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816"},
|
||||
{file = "uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc"},
|
||||
{file = "uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553"},
|
||||
{file = "uvloop-0.21.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:17df489689befc72c39a08359efac29bbee8eee5209650d4b9f34df73d22e414"},
|
||||
{file = "uvloop-0.21.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bc09f0ff191e61c2d592a752423c767b4ebb2986daa9ed62908e2b1b9a9ae206"},
|
||||
{file = "uvloop-0.21.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0ce1b49560b1d2d8a2977e3ba4afb2414fb46b86a1b64056bc4ab929efdafbe"},
|
||||
{file = "uvloop-0.21.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e678ad6fe52af2c58d2ae3c73dc85524ba8abe637f134bf3564ed07f555c5e79"},
|
||||
{file = "uvloop-0.21.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:460def4412e473896ef179a1671b40c039c7012184b627898eea5072ef6f017a"},
|
||||
{file = "uvloop-0.21.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:10da8046cc4a8f12c91a1c39d1dd1585c41162a15caaef165c2174db9ef18bdc"},
|
||||
{file = "uvloop-0.21.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c097078b8031190c934ed0ebfee8cc5f9ba9642e6eb88322b9958b649750f72b"},
|
||||
{file = "uvloop-0.21.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:46923b0b5ee7fc0020bef24afe7836cb068f5050ca04caf6b487c513dc1a20b2"},
|
||||
{file = "uvloop-0.21.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53e420a3afe22cdcf2a0f4846e377d16e718bc70103d7088a4f7623567ba5fb0"},
|
||||
{file = "uvloop-0.21.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88cb67cdbc0e483da00af0b2c3cdad4b7c61ceb1ee0f33fe00e09c81e3a6cb75"},
|
||||
{file = "uvloop-0.21.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:221f4f2a1f46032b403bf3be628011caf75428ee3cc204a22addf96f586b19fd"},
|
||||
{file = "uvloop-0.21.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2d1f581393673ce119355d56da84fe1dd9d2bb8b3d13ce792524e1607139feff"},
|
||||
{file = "uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3"},
|
||||
]
|
||||
|
||||
[package.extras]
|
||||
dev = ["Cython (>=3.0,<4.0)", "setuptools (>=60)"]
|
||||
docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
|
||||
test = ["aiohttp (>=3.10.5)", "flake8 (>=5.0,<6.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=23.0.0,<23.1.0)", "pycodestyle (>=2.9.0,<2.10.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "watchfiles"
|
||||
version = "1.0.5"
|
||||
description = "Simple, modern and high performance file watching and code reload in python."
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "watchfiles-1.0.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:5c40fe7dd9e5f81e0847b1ea64e1f5dd79dd61afbedb57759df06767ac719b40"},
|
||||
{file = "watchfiles-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8c0db396e6003d99bb2d7232c957b5f0b5634bbd1b24e381a5afcc880f7373fb"},
|
||||
{file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b551d4fb482fc57d852b4541f911ba28957d051c8776e79c3b4a51eb5e2a1b11"},
|
||||
{file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:830aa432ba5c491d52a15b51526c29e4a4b92bf4f92253787f9726fe01519487"},
|
||||
{file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a16512051a822a416b0d477d5f8c0e67b67c1a20d9acecb0aafa3aa4d6e7d256"},
|
||||
{file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bfe0cbc787770e52a96c6fda6726ace75be7f840cb327e1b08d7d54eadc3bc85"},
|
||||
{file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d363152c5e16b29d66cbde8fa614f9e313e6f94a8204eaab268db52231fe5358"},
|
||||
{file = "watchfiles-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ee32c9a9bee4d0b7bd7cbeb53cb185cf0b622ac761efaa2eba84006c3b3a614"},
|
||||
{file = "watchfiles-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29c7fd632ccaf5517c16a5188e36f6612d6472ccf55382db6c7fe3fcccb7f59f"},
|
||||
{file = "watchfiles-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8e637810586e6fe380c8bc1b3910accd7f1d3a9a7262c8a78d4c8fb3ba6a2b3d"},
|
||||
{file = "watchfiles-1.0.5-cp310-cp310-win32.whl", hash = "sha256:cd47d063fbeabd4c6cae1d4bcaa38f0902f8dc5ed168072874ea11d0c7afc1ff"},
|
||||
{file = "watchfiles-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:86c0df05b47a79d80351cd179893f2f9c1b1cae49d96e8b3290c7f4bd0ca0a92"},
|
||||
{file = "watchfiles-1.0.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:237f9be419e977a0f8f6b2e7b0475ababe78ff1ab06822df95d914a945eac827"},
|
||||
{file = "watchfiles-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e0da39ff917af8b27a4bdc5a97ac577552a38aac0d260a859c1517ea3dc1a7c4"},
|
||||
{file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cfcb3952350e95603f232a7a15f6c5f86c5375e46f0bd4ae70d43e3e063c13d"},
|
||||
{file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:68b2dddba7a4e6151384e252a5632efcaa9bc5d1c4b567f3cb621306b2ca9f63"},
|
||||
{file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95cf944fcfc394c5f9de794ce581914900f82ff1f855326f25ebcf24d5397418"},
|
||||
{file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecf6cd9f83d7c023b1aba15d13f705ca7b7d38675c121f3cc4a6e25bd0857ee9"},
|
||||
{file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:852de68acd6212cd6d33edf21e6f9e56e5d98c6add46f48244bd479d97c967c6"},
|
||||
{file = "watchfiles-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5730f3aa35e646103b53389d5bc77edfbf578ab6dab2e005142b5b80a35ef25"},
|
||||
{file = "watchfiles-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:18b3bd29954bc4abeeb4e9d9cf0b30227f0f206c86657674f544cb032296acd5"},
|
||||
{file = "watchfiles-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ba5552a1b07c8edbf197055bc9d518b8f0d98a1c6a73a293bc0726dce068ed01"},
|
||||
{file = "watchfiles-1.0.5-cp311-cp311-win32.whl", hash = "sha256:2f1fefb2e90e89959447bc0420fddd1e76f625784340d64a2f7d5983ef9ad246"},
|
||||
{file = "watchfiles-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:b6e76ceb1dd18c8e29c73f47d41866972e891fc4cc7ba014f487def72c1cf096"},
|
||||
{file = "watchfiles-1.0.5-cp311-cp311-win_arm64.whl", hash = "sha256:266710eb6fddc1f5e51843c70e3bebfb0f5e77cf4f27129278c70554104d19ed"},
|
||||
{file = "watchfiles-1.0.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b5eb568c2aa6018e26da9e6c86f3ec3fd958cee7f0311b35c2630fa4217d17f2"},
|
||||
{file = "watchfiles-1.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0a04059f4923ce4e856b4b4e5e783a70f49d9663d22a4c3b3298165996d1377f"},
|
||||
{file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e380c89983ce6e6fe2dd1e1921b9952fb4e6da882931abd1824c092ed495dec"},
|
||||
{file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fe43139b2c0fdc4a14d4f8d5b5d967f7a2777fd3d38ecf5b1ec669b0d7e43c21"},
|
||||
{file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee0822ce1b8a14fe5a066f93edd20aada932acfe348bede8aa2149f1a4489512"},
|
||||
{file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a0dbcb1c2d8f2ab6e0a81c6699b236932bd264d4cef1ac475858d16c403de74d"},
|
||||
{file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a2014a2b18ad3ca53b1f6c23f8cd94a18ce930c1837bd891262c182640eb40a6"},
|
||||
{file = "watchfiles-1.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10f6ae86d5cb647bf58f9f655fcf577f713915a5d69057a0371bc257e2553234"},
|
||||
{file = "watchfiles-1.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1a7bac2bde1d661fb31f4d4e8e539e178774b76db3c2c17c4bb3e960a5de07a2"},
|
||||
{file = "watchfiles-1.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ab626da2fc1ac277bbf752446470b367f84b50295264d2d313e28dc4405d663"},
|
||||
{file = "watchfiles-1.0.5-cp312-cp312-win32.whl", hash = "sha256:9f4571a783914feda92018ef3901dab8caf5b029325b5fe4558c074582815249"},
|
||||
{file = "watchfiles-1.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:360a398c3a19672cf93527f7e8d8b60d8275119c5d900f2e184d32483117a705"},
|
||||
{file = "watchfiles-1.0.5-cp312-cp312-win_arm64.whl", hash = "sha256:1a2902ede862969077b97523987c38db28abbe09fb19866e711485d9fbf0d417"},
|
||||
{file = "watchfiles-1.0.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:0b289572c33a0deae62daa57e44a25b99b783e5f7aed81b314232b3d3c81a11d"},
|
||||
{file = "watchfiles-1.0.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a056c2f692d65bf1e99c41045e3bdcaea3cb9e6b5a53dcaf60a5f3bd95fc9763"},
|
||||
{file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9dca99744991fc9850d18015c4f0438865414e50069670f5f7eee08340d8b40"},
|
||||
{file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:894342d61d355446d02cd3988a7326af344143eb33a2fd5d38482a92072d9563"},
|
||||
{file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab44e1580924d1ffd7b3938e02716d5ad190441965138b4aa1d1f31ea0877f04"},
|
||||
{file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d6f9367b132078b2ceb8d066ff6c93a970a18c3029cea37bfd7b2d3dd2e5db8f"},
|
||||
{file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2e55a9b162e06e3f862fb61e399fe9f05d908d019d87bf5b496a04ef18a970a"},
|
||||
{file = "watchfiles-1.0.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0125f91f70e0732a9f8ee01e49515c35d38ba48db507a50c5bdcad9503af5827"},
|
||||
{file = "watchfiles-1.0.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:13bb21f8ba3248386337c9fa51c528868e6c34a707f729ab041c846d52a0c69a"},
|
||||
{file = "watchfiles-1.0.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:839ebd0df4a18c5b3c1b890145b5a3f5f64063c2a0d02b13c76d78fe5de34936"},
|
||||
{file = "watchfiles-1.0.5-cp313-cp313-win32.whl", hash = "sha256:4a8ec1e4e16e2d5bafc9ba82f7aaecfeec990ca7cd27e84fb6f191804ed2fcfc"},
|
||||
{file = "watchfiles-1.0.5-cp313-cp313-win_amd64.whl", hash = "sha256:f436601594f15bf406518af922a89dcaab416568edb6f65c4e5bbbad1ea45c11"},
|
||||
{file = "watchfiles-1.0.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:2cfb371be97d4db374cba381b9f911dd35bb5f4c58faa7b8b7106c8853e5d225"},
|
||||
{file = "watchfiles-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a3904d88955fda461ea2531fcf6ef73584ca921415d5cfa44457a225f4a42bc1"},
|
||||
{file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2b7a21715fb12274a71d335cff6c71fe7f676b293d322722fe708a9ec81d91f5"},
|
||||
{file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dfd6ae1c385ab481766b3c61c44aca2b3cd775f6f7c0fa93d979ddec853d29d5"},
|
||||
{file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b659576b950865fdad31fa491d31d37cf78b27113a7671d39f919828587b429b"},
|
||||
{file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1909e0a9cd95251b15bff4261de5dd7550885bd172e3536824bf1cf6b121e200"},
|
||||
{file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:832ccc221927c860e7286c55c9b6ebcc0265d5e072f49c7f6456c7798d2b39aa"},
|
||||
{file = "watchfiles-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85fbb6102b3296926d0c62cfc9347f6237fb9400aecd0ba6bbda94cae15f2b3b"},
|
||||
{file = "watchfiles-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:15ac96dd567ad6c71c71f7b2c658cb22b7734901546cd50a475128ab557593ca"},
|
||||
{file = "watchfiles-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4b6227351e11c57ae997d222e13f5b6f1f0700d84b8c52304e8675d33a808382"},
|
||||
{file = "watchfiles-1.0.5-cp39-cp39-win32.whl", hash = "sha256:974866e0db748ebf1eccab17862bc0f0303807ed9cda465d1324625b81293a18"},
|
||||
{file = "watchfiles-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:9848b21ae152fe79c10dd0197304ada8f7b586d3ebc3f27f43c506e5a52a863c"},
|
||||
{file = "watchfiles-1.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f59b870db1f1ae5a9ac28245707d955c8721dd6565e7f411024fa374b5362d1d"},
|
||||
{file = "watchfiles-1.0.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9475b0093767e1475095f2aeb1d219fb9664081d403d1dff81342df8cd707034"},
|
||||
{file = "watchfiles-1.0.5-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc533aa50664ebd6c628b2f30591956519462f5d27f951ed03d6c82b2dfd9965"},
|
||||
{file = "watchfiles-1.0.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fed1cd825158dcaae36acce7b2db33dcbfd12b30c34317a88b8ed80f0541cc57"},
|
||||
{file = "watchfiles-1.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:554389562c29c2c182e3908b149095051f81d28c2fec79ad6c8997d7d63e0009"},
|
||||
{file = "watchfiles-1.0.5-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a74add8d7727e6404d5dc4dcd7fac65d4d82f95928bbee0cf5414c900e86773e"},
|
||||
{file = "watchfiles-1.0.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb1489f25b051a89fae574505cc26360c8e95e227a9500182a7fe0afcc500ce0"},
|
||||
{file = "watchfiles-1.0.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0901429650652d3f0da90bad42bdafc1f9143ff3605633c455c999a2d786cac"},
|
||||
{file = "watchfiles-1.0.5.tar.gz", hash = "sha256:b7529b5dcc114679d43827d8c35a07c493ad6f083633d573d81c660abc5979e9"},
|
||||
]
|
||||
|
||||
[package.dependencies]
|
||||
anyio = ">=3.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "websockets"
|
||||
version = "15.0.1"
|
||||
description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
|
||||
optional = false
|
||||
python-versions = ">=3.9"
|
||||
groups = ["main"]
|
||||
files = [
|
||||
{file = "websockets-15.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d63efaa0cd96cf0c5fe4d581521d9fa87744540d4bc999ae6e08595a1014b45b"},
|
||||
{file = "websockets-15.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac60e3b188ec7574cb761b08d50fcedf9d77f1530352db4eef1707fe9dee7205"},
|
||||
{file = "websockets-15.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5756779642579d902eed757b21b0164cd6fe338506a8083eb58af5c372e39d9a"},
|
||||
{file = "websockets-15.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fdfe3e2a29e4db3659dbd5bbf04560cea53dd9610273917799f1cde46aa725e"},
|
||||
{file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c2529b320eb9e35af0fa3016c187dffb84a3ecc572bcee7c3ce302bfeba52bf"},
|
||||
{file = "websockets-15.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac1e5c9054fe23226fb11e05a6e630837f074174c4c2f0fe442996112a6de4fb"},
|
||||
{file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5df592cd503496351d6dc14f7cdad49f268d8e618f80dce0cd5a36b93c3fc08d"},
|
||||
{file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a34631031a8f05657e8e90903e656959234f3a04552259458aac0b0f9ae6fd9"},
|
||||
{file = "websockets-15.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3d00075aa65772e7ce9e990cab3ff1de702aa09be3940d1dc88d5abf1ab8a09c"},
|
||||
{file = "websockets-15.0.1-cp310-cp310-win32.whl", hash = "sha256:1234d4ef35db82f5446dca8e35a7da7964d02c127b095e172e54397fb6a6c256"},
|
||||
{file = "websockets-15.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:39c1fec2c11dc8d89bba6b2bf1556af381611a173ac2b511cf7231622058af41"},
|
||||
{file = "websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431"},
|
||||
{file = "websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57"},
|
||||
{file = "websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905"},
|
||||
{file = "websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562"},
|
||||
{file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792"},
|
||||
{file = "websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413"},
|
||||
{file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8"},
|
||||
{file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3"},
|
||||
{file = "websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf"},
|
||||
{file = "websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85"},
|
||||
{file = "websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065"},
|
||||
{file = "websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3"},
|
||||
{file = "websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665"},
|
||||
{file = "websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2"},
|
||||
{file = "websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215"},
|
||||
{file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5"},
|
||||
{file = "websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65"},
|
||||
{file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe"},
|
||||
{file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4"},
|
||||
{file = "websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597"},
|
||||
{file = "websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9"},
|
||||
{file = "websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7"},
|
||||
{file = "websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931"},
|
||||
{file = "websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675"},
|
||||
{file = "websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151"},
|
||||
{file = "websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22"},
|
||||
{file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f"},
|
||||
{file = "websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8"},
|
||||
{file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375"},
|
||||
{file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d"},
|
||||
{file = "websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4"},
|
||||
{file = "websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa"},
|
||||
{file = "websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561"},
|
||||
{file = "websockets-15.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f4c04ead5aed67c8a1a20491d54cdfba5884507a48dd798ecaf13c74c4489f5"},
|
||||
{file = "websockets-15.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abdc0c6c8c648b4805c5eacd131910d2a7f6455dfd3becab248ef108e89ab16a"},
|
||||
{file = "websockets-15.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a625e06551975f4b7ea7102bc43895b90742746797e2e14b70ed61c43a90f09b"},
|
||||
{file = "websockets-15.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d591f8de75824cbb7acad4e05d2d710484f15f29d4a915092675ad3456f11770"},
|
||||
{file = "websockets-15.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47819cea040f31d670cc8d324bb6435c6f133b8c7a19ec3d61634e62f8d8f9eb"},
|
||||
{file = "websockets-15.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac017dd64572e5c3bd01939121e4d16cf30e5d7e110a119399cf3133b63ad054"},
|
||||
{file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4a9fac8e469d04ce6c25bb2610dc535235bd4aa14996b4e6dbebf5e007eba5ee"},
|
||||
{file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:363c6f671b761efcb30608d24925a382497c12c506b51661883c3e22337265ed"},
|
||||
{file = "websockets-15.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2034693ad3097d5355bfdacfffcbd3ef5694f9718ab7f29c29689a9eae841880"},
|
||||
{file = "websockets-15.0.1-cp39-cp39-win32.whl", hash = "sha256:3b1ac0d3e594bf121308112697cf4b32be538fb1444468fb0a6ae4feebc83411"},
|
||||
{file = "websockets-15.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b7643a03db5c95c799b89b31c036d5f27eeb4d259c798e878d6937d71832b1e4"},
|
||||
{file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0c9e74d766f2818bb95f84c25be4dea09841ac0f734d1966f415e4edfc4ef1c3"},
|
||||
{file = "websockets-15.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1009ee0c7739c08a0cd59de430d6de452a55e42d6b522de7aa15e6f67db0b8e1"},
|
||||
{file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76d1f20b1c7a2fa82367e04982e708723ba0e7b8d43aa643d3dcd404d74f1475"},
|
||||
{file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f29d80eb9a9263b8d109135351caf568cc3f80b9928bccde535c235de55c22d9"},
|
||||
{file = "websockets-15.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b359ed09954d7c18bbc1680f380c7301f92c60bf924171629c5db97febb12f04"},
|
||||
{file = "websockets-15.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:cad21560da69f4ce7658ca2cb83138fb4cf695a2ba3e475e0559e05991aa8122"},
|
||||
{file = "websockets-15.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7f493881579c90fc262d9cdbaa05a6b54b3811c2f300766748db79f098db9940"},
|
||||
{file = "websockets-15.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:47b099e1f4fbc95b701b6e85768e1fcdaf1630f3cbe4765fa216596f12310e2e"},
|
||||
{file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67f2b6de947f8c757db2db9c71527933ad0019737ec374a8a6be9a956786aaf9"},
|
||||
{file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d08eb4c2b7d6c41da6ca0600c077e93f5adcfd979cd777d747e9ee624556da4b"},
|
||||
{file = "websockets-15.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b826973a4a2ae47ba357e4e82fa44a463b8f168e1ca775ac64521442b19e87f"},
|
||||
{file = "websockets-15.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:21c1fa28a6a7e3cbdc171c694398b6df4744613ce9b36b1a498e816787e28123"},
|
||||
{file = "websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f"},
|
||||
{file = "websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee"},
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "win32-setctime"
|
||||
version = "1.2.0"
|
||||
@ -1370,4 +1933,4 @@ propcache = ">=0.2.0"
|
||||
[metadata]
|
||||
lock-version = "2.1"
|
||||
python-versions = ">=3.11,<4.0"
|
||||
content-hash = "22129fa3f5a2cc1190af6c7645f3dd8b4ab0f873b120987cd2e06772863e7dc8"
|
||||
content-hash = "5ed129fde2c5d7b3518fbb2fe2ce79f0bad2aa1060304d45a7bc26d35f7ab46b"
|
||||
|
@ -1,17 +1,15 @@
|
||||
[project]
|
||||
name = "api"
|
||||
version = "0.0.2"
|
||||
version = "0.0.5"
|
||||
description = ""
|
||||
authors = [
|
||||
{name = "Vladislav",email = "vlad.dev@heado.ru"}
|
||||
]
|
||||
authors = [{ name = "Vladislav", email = "vlad.dev@heado.ru" }]
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11,<4.0"
|
||||
dependencies = [
|
||||
"sqlalchemy[pymysql,aiomysql] (>=2.0.39,<3.0.0)",
|
||||
"alembic (>=1.15.1,<2.0.0)",
|
||||
"aio-pika (>=9.5.5,<10.0.0)",
|
||||
"fastapi[standart] (>=0.115.11,<0.116.0)",
|
||||
"fastapi[standard] (>=0.115.11,<0.116.0)",
|
||||
"uvicorn (>=0.34.0,<0.35.0)",
|
||||
"loguru (>=0.7.3,<0.8.0)",
|
||||
"pydantic-settings (>=2.8.1,<3.0.0)",
|
||||
|
@ -1,5 +1,4 @@
|
||||
REACT_APP_WEBSOCKET_PROTOCOL=ws
|
||||
REACT_APP_HTTP_PROTOCOL=http
|
||||
REACT_APP_API_URL=localhost:8000
|
||||
REACT_APP_URL=localhost:3000
|
||||
BROWSER=none
|
||||
VITE_APP_WEBSOCKET_PROTOCOL=ws
|
||||
VITE_APP_HTTP_PROTOCOL=http
|
||||
VITE_APP_API_URL=localhost:8000
|
||||
VITE_APP_URL=localhost:3000
|
||||
|
17
client/index.html
Normal file
17
client/index.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="description" content="Web site created using Vite" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" href="/logo192.png" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<title>VORKOUT</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
17497
client/package-lock.json
generated
17497
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "client",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.5",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@ant-design/icons": "^5.6.1",
|
||||
@ -9,25 +9,24 @@
|
||||
"@testing-library/react": "^16.2.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@types/jest": "^27.5.2",
|
||||
"@types/node": "^16.18.126",
|
||||
"@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",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-i18next": "^15.5.1",
|
||||
"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",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
@ -46,5 +45,13 @@
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
|
||||
"@types/node": "^20.19.1",
|
||||
"@vitejs/plugin-react": "^4.5.2",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.3.5",
|
||||
"vite-plugin-node-polyfills": "^0.23.0"
|
||||
}
|
||||
}
|
||||
|
BIN
client/public/favicon.ico
Normal file
BIN
client/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
3
client/public/icons/logo.svg
Normal file
3
client/public/icons/logo.svg
Normal 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 |
@ -1,43 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>VORKOUT</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
@ -1,13 +1,25 @@
|
||||
import React from 'react';
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import React, { useEffect } from 'react';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import MainLayout from './pages/MainLayout';
|
||||
import { useSetUserSelector } from './store/userStore';
|
||||
import LoginPage from './pages/LoginPage';
|
||||
import ProtectedRoute from './pages/ProtectedRoute';
|
||||
import MainLayout from './pages/MainLayout';
|
||||
|
||||
function App() {
|
||||
const setUser = useSetUserSelector();
|
||||
|
||||
useEffect(() => {
|
||||
const storedUser = localStorage.getItem('user');
|
||||
if (storedUser) {
|
||||
setUser(JSON.parse(storedUser));
|
||||
}
|
||||
}, []);
|
||||
|
||||
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>
|
||||
|
130
client/src/api/api.ts
Normal file
130
client/src/api/api.ts
Normal file
@ -0,0 +1,130 @@
|
||||
import axios from 'axios';
|
||||
import axiosRetry from 'axios-retry';
|
||||
import { Auth, Tokens } from '@/types/auth';
|
||||
import { useAuthStore } from '@/store/authStore';
|
||||
import { AuthService } from '@/services/authService';
|
||||
import { User, UserCreate, UserUpdate } from '@/types/user';
|
||||
|
||||
const baseURL = `${import.meta.env.VITE_APP_HTTP_PROTOCOL}://${
|
||||
import.meta.env.VITE_APP_API_URL
|
||||
}/api/v1`;
|
||||
|
||||
const base = axios.create({
|
||||
baseURL,
|
||||
withCredentials: true,
|
||||
headers: {
|
||||
accepts: 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
base.interceptors.request.use((config) => {
|
||||
if (config.url === '/auth/refresh') {
|
||||
return config;
|
||||
}
|
||||
const token = useAuthStore.getState().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) {
|
||||
if (!error.response) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
console.log('error', error);
|
||||
const originalRequest = error.response.config;
|
||||
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;
|
||||
try {
|
||||
await AuthService.refresh();
|
||||
return base(originalRequest);
|
||||
} catch (error) {
|
||||
await AuthService.logout();
|
||||
return new Promise(() => {});
|
||||
}
|
||||
}
|
||||
return await Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
const api = {
|
||||
// auth
|
||||
async login(auth: Auth): Promise<Tokens> {
|
||||
const response = await base.post<Tokens>('/auth', auth);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async refreshToken(): Promise<Tokens> {
|
||||
const token = localStorage.getItem('refreshToken');
|
||||
const response = await base.post<Tokens>(
|
||||
'/auth/refresh',
|
||||
{},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// user
|
||||
async getProfile(): Promise<User> {
|
||||
const response = await base.get<User>('/profile');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async getUsers(page: number, limit: number): Promise<any> {
|
||||
const response = await base.get<User[]>(
|
||||
`/account?page=${page}&limit=${limit}`
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async getUserById(userId: number): Promise<User> {
|
||||
const response = await base.get<User>(`/account/${userId}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async createUser(user: UserCreate): Promise<User> {
|
||||
const response = await base.post<User>('/account', user);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async updateUser(userId: number, user: UserUpdate): Promise<User> {
|
||||
const response = await base.put<User>(`/account/${userId}`, user);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// keyrings
|
||||
};
|
||||
|
||||
export default api;
|
@ -2,12 +2,16 @@ import { Drawer } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Avatar, Typography } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useUserSelector } from '@/store/userStore';
|
||||
|
||||
interface ContentDrawerProps {
|
||||
open: boolean;
|
||||
closeDrawer: () => void;
|
||||
children: React.ReactNode;
|
||||
type: 'create' | 'edit';
|
||||
login?: string;
|
||||
name?: string;
|
||||
email?: string | null;
|
||||
}
|
||||
|
||||
export default function ContentDrawer({
|
||||
@ -15,7 +19,11 @@ export default function ContentDrawer({
|
||||
closeDrawer,
|
||||
children,
|
||||
type,
|
||||
login,
|
||||
name,
|
||||
email,
|
||||
}: ContentDrawerProps) {
|
||||
const user = useUserSelector();
|
||||
const { t } = useTranslation();
|
||||
const [width, setWidth] = useState<number | string>('30%');
|
||||
|
||||
@ -30,6 +38,7 @@ export default function ContentDrawer({
|
||||
window.addEventListener('resize', calculateWidths);
|
||||
return () => window.removeEventListener('resize', calculateWidths);
|
||||
}, []);
|
||||
console.log(login, user?.login, login === user?.login);
|
||||
|
||||
const editDrawerTitle = (
|
||||
<div
|
||||
@ -59,16 +68,21 @@ export default function ContentDrawer({
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, flex: 1 }}>
|
||||
<Avatar
|
||||
src="https://cdn-icons-png.flaticon.com/512/219/219986.png"
|
||||
src={
|
||||
login ? `https://gamma.heado.ru/go/ava?name=${login}` : undefined
|
||||
}
|
||||
size={40}
|
||||
style={{ flexShrink: 0 }}
|
||||
/>
|
||||
<div>
|
||||
<Typography.Text strong style={{ display: 'block' }}>
|
||||
Александр Александров
|
||||
<Typography.Text
|
||||
strong
|
||||
style={{ display: 'block', fontSize: '20px' }}
|
||||
>
|
||||
{name} {login === user?.login ? t('you') : ''}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="secondary" style={{ fontSize: 14 }}>
|
||||
alexandralex@vorkout.ru
|
||||
{email}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
@ -152,7 +166,7 @@ export default function ContentDrawer({
|
||||
placement="right"
|
||||
open={open}
|
||||
width={width}
|
||||
destroyOnClose={true}
|
||||
destroyOnHidden={true}
|
||||
closable={false}
|
||||
>
|
||||
{children}
|
||||
|
@ -1,5 +1,9 @@
|
||||
import { useUserSelector } from '@/store/userStore';
|
||||
import { Avatar } from 'antd';
|
||||
import Title from 'antd/es/typography/Title';
|
||||
import { useState } from 'react';
|
||||
import ContentDrawer from './ContentDrawer';
|
||||
import UserEdit from './UserEdit';
|
||||
|
||||
interface HeaderProps {
|
||||
title: string;
|
||||
@ -7,6 +11,13 @@ interface HeaderProps {
|
||||
}
|
||||
|
||||
export default function Header({ title, additionalContent }: HeaderProps) {
|
||||
const [openEdit, setOpenEdit] = useState(false);
|
||||
|
||||
const showEditDrawer = () => setOpenEdit(true);
|
||||
const closeEditDrawer = () => {
|
||||
setOpenEdit(false);
|
||||
};
|
||||
const user = useUserSelector();
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@ -43,13 +54,24 @@ export default function Header({ title, additionalContent }: HeaderProps) {
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
onClick={showEditDrawer}
|
||||
>
|
||||
<Avatar
|
||||
size={25.77}
|
||||
src={`https://cdn-icons-png.flaticon.com/512/219/219986.png`}
|
||||
src={`https://gamma.heado.ru/go/ava?name=${user?.login}`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ContentDrawer
|
||||
login={user?.login}
|
||||
name={user?.name}
|
||||
email={user?.email}
|
||||
open={openEdit}
|
||||
closeDrawer={closeEditDrawer}
|
||||
type="edit"
|
||||
>
|
||||
{user?.id && <UserEdit closeDrawer={closeEditDrawer} userId={user?.id} />}
|
||||
</ContentDrawer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { useUserSelector } from '@/store/userStore';
|
||||
import { Divider, Menu, Tooltip } from 'antd';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -13,6 +14,7 @@ export default function SiderMenu({
|
||||
selectedKey,
|
||||
hangleMenuClick,
|
||||
}: SiderMenuProps) {
|
||||
const user = useUserSelector();
|
||||
const { t } = useTranslation();
|
||||
const collapseStyle = collapsed
|
||||
? { fontSize: '12px' }
|
||||
@ -74,15 +76,17 @@ export default function SiderMenu({
|
||||
label: t('settings'),
|
||||
className: 'no-expand-icon',
|
||||
children: [
|
||||
{
|
||||
key: '/accounts',
|
||||
label: !collapsed ? (
|
||||
<Tooltip title={t('accounts')}>{t('accounts')}</Tooltip>
|
||||
) : (
|
||||
t('accounts')
|
||||
),
|
||||
style: collapseStyle,
|
||||
},
|
||||
user && (user.role === 'OWNER' || user.role === 'ADMIN')
|
||||
? {
|
||||
key: '/accounts',
|
||||
label: !collapsed ? (
|
||||
<Tooltip title={t('accounts')}>{t('accounts')}</Tooltip>
|
||||
) : (
|
||||
t('accounts')
|
||||
),
|
||||
style: collapseStyle,
|
||||
}
|
||||
: undefined,
|
||||
{
|
||||
key: '/events-list',
|
||||
label: !collapsed ? (
|
||||
|
@ -8,13 +8,19 @@ import {
|
||||
UploadFile,
|
||||
GetProp,
|
||||
UploadProps,
|
||||
} from 'antd';
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
message,
|
||||
Spin,
|
||||
} from "antd";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useUserSelector } from "@/store/userStore";
|
||||
import { UserCreate as NewUserCreate } from "@/types/user";
|
||||
import { UserService } from "@/services/userService";
|
||||
import { LoadingOutlined } from "@ant-design/icons";
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0];
|
||||
type FileType = Parameters<GetProp<UploadProps, "beforeUpload">>[0];
|
||||
|
||||
const getBase64 = (file: FileType): Promise<string> =>
|
||||
new Promise((resolve, reject) => {
|
||||
@ -24,10 +30,17 @@ const getBase64 = (file: FileType): Promise<string> =>
|
||||
reader.onerror = (error) => reject(error);
|
||||
});
|
||||
|
||||
export default function UserCreate() {
|
||||
interface UserCreateProps {
|
||||
closeDrawer: () => void;
|
||||
getUsers: () => Promise<void>;
|
||||
}
|
||||
|
||||
export default function UserCreate({ closeDrawer, getUsers }: UserCreateProps) {
|
||||
const user = useUserSelector();
|
||||
const { t } = useTranslation();
|
||||
const [previewOpen, setPreviewOpen] = useState(false);
|
||||
const [previewImage, setPreviewImage] = useState('');
|
||||
const [previewImage, setPreviewImage] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const [fileList, setFileList] = useState<UploadFile[]>([]);
|
||||
|
||||
@ -40,40 +53,49 @@ export default function UserCreate() {
|
||||
setPreviewOpen(true);
|
||||
};
|
||||
|
||||
const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) =>
|
||||
const handleChange: UploadProps["onChange"] = ({ fileList: newFileList }) =>
|
||||
setFileList(newFileList);
|
||||
|
||||
const onFinish = async (values: NewUserCreate) => {
|
||||
setLoading(true);
|
||||
await UserService.createUser(values);
|
||||
await getUsers();
|
||||
closeDrawer();
|
||||
setLoading(false);
|
||||
message.info(t("createdAccountMessage"), 4);
|
||||
};
|
||||
|
||||
const customUploadButton = (
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
height: '102px',
|
||||
width: '102px',
|
||||
backgroundColor: '#E2E2E2',
|
||||
borderRadius: '50%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: "102px",
|
||||
width: "102px",
|
||||
backgroundColor: "#E2E2E2",
|
||||
borderRadius: "50%",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
marginBottom: 8,
|
||||
marginTop: 30,
|
||||
cursor: 'pointer',
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<img
|
||||
src="./icons/drawer/add_photo_alternate.svg"
|
||||
alt="add_photo_alternate"
|
||||
style={{ height: '18px', width: '18px' }}
|
||||
style={{ height: "18px", width: "18px" }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<span style={{ fontSize: '14px', color: '#8c8c8c' }}>
|
||||
{t('selectPhoto')}
|
||||
<span style={{ fontSize: "14px", color: "#8c8c8c" }}>
|
||||
{t("selectPhoto")}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const photoToUpload = (
|
||||
<div style={{ height: '102px' }}>
|
||||
<div style={{ height: "102px" }}>
|
||||
<Upload
|
||||
listType="picture-circle"
|
||||
fileList={fileList}
|
||||
@ -85,11 +107,11 @@ export default function UserCreate() {
|
||||
</Upload>
|
||||
{previewImage && (
|
||||
<Image
|
||||
wrapperStyle={{ display: 'none' }}
|
||||
wrapperStyle={{ display: "none" }}
|
||||
preview={{
|
||||
visible: previewOpen,
|
||||
onVisibleChange: (visible) => setPreviewOpen(visible),
|
||||
afterOpenChange: (visible) => !visible && setPreviewImage(''),
|
||||
afterOpenChange: (visible) => !visible && setPreviewImage(""),
|
||||
}}
|
||||
src={previewImage}
|
||||
/>
|
||||
@ -100,24 +122,24 @@ export default function UserCreate() {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginBottom: '36px',
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
marginBottom: "36px",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
{photoToUpload}
|
||||
@ -126,83 +148,85 @@ export default function UserCreate() {
|
||||
<Form
|
||||
name="user-edit-form"
|
||||
layout="vertical"
|
||||
// onFinish={onFinish}
|
||||
onFinish={onFinish}
|
||||
initialValues={{
|
||||
name: '',
|
||||
login: '',
|
||||
password: '',
|
||||
email: '',
|
||||
tenant: '',
|
||||
role: '',
|
||||
status: '',
|
||||
name: "",
|
||||
login: "",
|
||||
password: "",
|
||||
email: "",
|
||||
bindTenantId: "",
|
||||
role: "",
|
||||
status: "",
|
||||
}}
|
||||
style={{ flex: 1, display: 'flex', flexDirection: 'column' }}
|
||||
style={{ flex: 1, display: "flex", flexDirection: "column" }}
|
||||
>
|
||||
<Form.Item
|
||||
label={t('name')}
|
||||
label={t("name")}
|
||||
name="name"
|
||||
rules={[{ required: true, message: t('nameMessage') }]}
|
||||
rules={[{ required: true, message: t("nameMessage") }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t('login')}
|
||||
label={t("login")}
|
||||
name="login"
|
||||
rules={[{ required: true, message: t('loginMessage') }]}
|
||||
rules={[{ required: true, message: t("loginMessage") }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t('password')}
|
||||
label={t("password")}
|
||||
name="password"
|
||||
rules={[{ required: true, message: t('passwordMessage') }]}
|
||||
rules={[{ message: t("passwordMessage") }]}
|
||||
>
|
||||
<Input.Password />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t('email')}
|
||||
label={t("email")}
|
||||
name="email"
|
||||
rules={[
|
||||
{ required: true, message: t('emailMessage') },
|
||||
{ type: 'email', message: t('emailErrorMessage') },
|
||||
{ message: t("emailMessage") },
|
||||
{ type: "email", message: t("emailErrorMessage") },
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t('tenant')}
|
||||
name="tenant"
|
||||
rules={[{ required: true, message: t('tenantMessage') }]}
|
||||
label={t("tenant")}
|
||||
name="bindTenantId"
|
||||
rules={[{ message: t("tenantMessage") }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t('role')}
|
||||
label={t("role")}
|
||||
name="role"
|
||||
rules={[{ required: true, message: t('roleMessage') }]}
|
||||
rules={[{ required: true, message: t("roleMessage") }]}
|
||||
>
|
||||
<Select placeholder={t('roleMessage')}>
|
||||
<Option value="Директор магазина">Директор магазина</Option>
|
||||
<Option value="Менеджер">Менеджер</Option>
|
||||
<Option value="Кассир">Кассир</Option>
|
||||
<Select placeholder={t("roleMessage")}>
|
||||
{user && user.role === "OWNER" ? (
|
||||
<Option value="ADMIN">{t("ADMIN")}</Option>
|
||||
) : undefined}
|
||||
<Option value="EDITOR">{t("EDITOR")}</Option>
|
||||
<Option value="VIEWER">{t("VIEWER")}</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t('status')}
|
||||
label={t("status")}
|
||||
name="status"
|
||||
rules={[{ required: true, message: t('statusMessage') }]}
|
||||
rules={[{ required: true, message: t("statusMessage") }]}
|
||||
>
|
||||
<Select placeholder={t('statusMessage')}>
|
||||
<Option value="ACTIVE">Активен</Option>
|
||||
<Option value="DISABLED">Неактивен</Option>
|
||||
<Option value="BLOCKED">Заблокирован</Option>
|
||||
<Option value="DELETED">Удален</Option>
|
||||
<Select placeholder={t("statusMessage")}>
|
||||
<Option value="ACTIVE">{t("ACTIVE")}</Option>
|
||||
<Option value="DISABLED">{t("DISABLED")}</Option>
|
||||
<Option value="BLOCKED">{t("BLOCKED")}</Option>
|
||||
<Option value="DELETED">{t("DELETED")}</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
@ -213,14 +237,23 @@ export default function UserCreate() {
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
block
|
||||
style={{ color: '#000' }}
|
||||
style={{ color: "#000" }}
|
||||
>
|
||||
<img
|
||||
src="/icons/drawer/reg.svg"
|
||||
alt="save"
|
||||
style={{ height: '18px', width: '18px' }}
|
||||
/>{' '}
|
||||
{t('addAccount')}
|
||||
{loading ? (
|
||||
<>
|
||||
<Spin indicator={<LoadingOutlined spin />} size="small"></Spin>{" "}
|
||||
{t("saving")}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<img
|
||||
src="/icons/drawer/reg.svg"
|
||||
alt="save"
|
||||
style={{ height: "18px", width: "18px" }}
|
||||
/>{" "}
|
||||
{t("addAccount")}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
@ -1,47 +1,101 @@
|
||||
import { Button, Form, Input, Select } from 'antd';
|
||||
import { UserService } from '@/services/userService';
|
||||
import { useUserSelector } from '@/store/userStore';
|
||||
import { UserUpdate } from '@/types/user';
|
||||
import { LoadingOutlined } from '@ant-design/icons';
|
||||
import { Button, Form, Input, message, Select, Spin } from 'antd';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
export default function UserEdit() {
|
||||
interface UserEditProps {
|
||||
userId?: number;
|
||||
closeDrawer: () => void;
|
||||
}
|
||||
|
||||
export default function UserEdit({ userId, closeDrawer }: UserEditProps) {
|
||||
const currentUser = useUserSelector();
|
||||
const [form] = Form.useForm();
|
||||
const { t } = useTranslation();
|
||||
const [user, setUser] = useState<UserUpdate>({
|
||||
id: 0,
|
||||
name: '',
|
||||
login: '',
|
||||
email: '',
|
||||
password: '',
|
||||
bindTenantId: '',
|
||||
role: 'VIEWER',
|
||||
meta: {},
|
||||
createdAt: '',
|
||||
status: 'ACTIVE',
|
||||
});
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
async function getUser() {
|
||||
if (typeof userId === 'undefined') {
|
||||
return;
|
||||
}
|
||||
const user = await UserService.getUserById(userId);
|
||||
setUser(user);
|
||||
form.setFieldsValue({ ...user });
|
||||
}
|
||||
|
||||
getUser();
|
||||
}, []);
|
||||
|
||||
const onFinish = async (values: UserUpdate) => {
|
||||
setLoading(true);
|
||||
let updatedUser: Partial<UserUpdate> = {};
|
||||
|
||||
(Object.keys(values) as Array<keyof UserUpdate>).forEach((key) => {
|
||||
if (values[key] !== user[key]) {
|
||||
updatedUser = { ...updatedUser, [key]: values[key] };
|
||||
}
|
||||
});
|
||||
|
||||
if (Object.keys(updatedUser).length > 0) {
|
||||
console.log('updateUser', userId, updatedUser);
|
||||
await UserService.updateUser(userId!, updatedUser);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
message.info(t('editAccountMessage'), 4);
|
||||
closeDrawer();
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', height: '100%' }}>
|
||||
<Form
|
||||
form={form}
|
||||
name="user-edit-form"
|
||||
layout="vertical"
|
||||
// onFinish={onFinish}
|
||||
initialValues={{
|
||||
name: 'Александр Александров',
|
||||
login: 'alexandralex@vorkout.ru',
|
||||
password: 'jKUUl776GHd',
|
||||
email: 'alexandralex@vorkout.ru',
|
||||
tenant: 'text',
|
||||
role: 'Директор магазина',
|
||||
status: 'Активен',
|
||||
}}
|
||||
onFinish={onFinish}
|
||||
initialValues={{ ...user }}
|
||||
style={{ flex: 1, display: 'flex', flexDirection: 'column' }}
|
||||
>
|
||||
<Form.Item
|
||||
label={t('name')}
|
||||
name="name"
|
||||
rules={[{ required: true, message: t('nameMessage') }]}
|
||||
rules={[{ message: t('nameMessage') }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t('login')}
|
||||
name="login"
|
||||
rules={[{ required: true, message: t('loginMessage') }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
{user?.id === currentUser?.id ? undefined : (
|
||||
<Form.Item
|
||||
label={t('login')}
|
||||
name="login"
|
||||
rules={[{ message: t('loginMessage') }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<Form.Item
|
||||
label={t('password')}
|
||||
name="password"
|
||||
rules={[{ required: true, message: t('passwordMessage') }]}
|
||||
rules={[{ message: t('passwordMessage') }]}
|
||||
>
|
||||
<Input.Password />
|
||||
</Form.Item>
|
||||
@ -59,23 +113,27 @@ export default function UserEdit() {
|
||||
|
||||
<Form.Item
|
||||
label={t('tenant')}
|
||||
name="tenant"
|
||||
name="bindTenantId"
|
||||
rules={[{ required: true, message: t('tenantMessage') }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t('role')}
|
||||
name="role"
|
||||
rules={[{ required: true, message: t('roleMessage') }]}
|
||||
>
|
||||
<Select placeholder={t('roleMessage')}>
|
||||
<Option value="Директор магазина">Директор магазина</Option>
|
||||
<Option value="Менеджер">Менеджер</Option>
|
||||
<Option value="Кассир">Кассир</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
{user?.id === currentUser?.id ? undefined : (
|
||||
<Form.Item
|
||||
label={t('role')}
|
||||
name="role"
|
||||
rules={[{ required: true, message: t('roleMessage') }]}
|
||||
>
|
||||
<Select placeholder={t('roleMessage')}>
|
||||
{currentUser && currentUser.role === 'OWNER' ? (
|
||||
<Option value="ADMIN">{t('ADMIN')}</Option>
|
||||
) : undefined}
|
||||
<Option value="EDITOR">{t('EDITOR')}</Option>
|
||||
<Option value="VIEWER">{t('VIEWER')}</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<Form.Item
|
||||
label={t('status')}
|
||||
@ -83,10 +141,10 @@ export default function UserEdit() {
|
||||
rules={[{ required: true, message: t('statusMessage') }]}
|
||||
>
|
||||
<Select placeholder={t('statusMessage')}>
|
||||
<Option value="ACTIVE">Активен</Option>
|
||||
<Option value="DISABLED">Неактивен</Option>
|
||||
<Option value="BLOCKED">Заблокирован</Option>
|
||||
<Option value="DELETED">Удален</Option>
|
||||
<Option value="ACTIVE">{t('ACTIVE')}</Option>
|
||||
<Option value="DISABLED">{t('DISABLED')}</Option>
|
||||
<Option value="BLOCKED">{t('BLOCKED')}</Option>
|
||||
<Option value="DELETED">{t('DELETED')}</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
|
||||
@ -99,12 +157,21 @@ export default function UserEdit() {
|
||||
block
|
||||
style={{ color: '#000' }}
|
||||
>
|
||||
<img
|
||||
src="/icons/drawer/save.svg"
|
||||
alt="save"
|
||||
style={{ height: '18px', width: '18px' }}
|
||||
/>{' '}
|
||||
{t('save')}
|
||||
{loading ? (
|
||||
<>
|
||||
<Spin indicator={<LoadingOutlined spin />} size="small"></Spin>{' '}
|
||||
{t('saving')}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<img
|
||||
src="/icons/drawer/save.svg"
|
||||
alt="save"
|
||||
style={{ height: '18px', width: '18px' }}
|
||||
/>{' '}
|
||||
{t('save')}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
@ -1,8 +1,9 @@
|
||||
import './i18n';
|
||||
import '@/config/i18n';
|
||||
import { ConfigProvider } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
|
||||
import { theme } from './customTheme';
|
||||
import { theme } from '@/config/customTheme';
|
||||
|
||||
import en from 'antd/locale/en_US';
|
||||
import ru from 'antd/locale/ru_RU';
|
||||
@ -18,7 +19,7 @@ export default function AppWrapper({ children }: any) {
|
||||
|
||||
return (
|
||||
<ConfigProvider locale={antdLocales[currentLang]} theme={theme}>
|
||||
{children}
|
||||
<BrowserRouter>{children}</BrowserRouter>
|
||||
</ConfigProvider>
|
||||
);
|
||||
}
|
||||
|
@ -37,6 +37,20 @@ i18n
|
||||
addAccount: 'Add account',
|
||||
save: 'Save changes',
|
||||
newAccount: 'New account',
|
||||
ACTIVE: 'Active',
|
||||
DISABLED: 'Disabled',
|
||||
BLOCKED: 'Blocked',
|
||||
DELETED: 'Deleted',
|
||||
OWNER: 'Owner',
|
||||
ADMIN: 'Admin',
|
||||
EDITOR: 'Editor',
|
||||
VIEWER: 'Viewer',
|
||||
nameLogin: 'Name, login',
|
||||
createdAt: 'Created',
|
||||
saving: 'Saving...',
|
||||
createdAccountMessage: 'User successfully created!',
|
||||
editAccountMessage: 'User successfully updated!',
|
||||
you: '(You)',
|
||||
},
|
||||
},
|
||||
ru: {
|
||||
@ -66,6 +80,20 @@ i18n
|
||||
addAccount: 'Добавить аккаунт',
|
||||
save: 'Сохранить изменения',
|
||||
newAccount: 'Новая учетная запись',
|
||||
ACTIVE: 'Активен',
|
||||
DISABLED: 'Выключен',
|
||||
BLOCKED: 'Заблокирован',
|
||||
DELETED: 'Удален',
|
||||
OWNER: 'Владелец',
|
||||
ADMIN: 'Админ',
|
||||
EDITOR: 'Редактор',
|
||||
VIEWER: 'Наблюдатель',
|
||||
nameLogin: 'Имя, Логин',
|
||||
createdAt: 'Создано',
|
||||
saving: 'Сохранение...',
|
||||
createdAccountMessage: 'Пользователь успешно создан!',
|
||||
editAccountMessage: 'Пользователь успешно обновлен!',
|
||||
you: '(Вы)',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
12
client/src/env.d.ts
vendored
Normal file
12
client/src/env.d.ts
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly VITE_APP_WEBSOCKET_PROTOCOL: string;
|
||||
readonly VITE_APP_HTTP_PROTOCOL: string;
|
||||
readonly VITE_APP_API_URL: string;
|
||||
readonly VITE_APP_URL: string;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import AppWrapper from './config/AppWrapper';
|
||||
import '@/index.css';
|
||||
import App from '@/App';
|
||||
import AppWrapper from '@/config/AppWrapper';
|
||||
|
||||
const root = ReactDOM.createRoot(
|
||||
document.getElementById('root') as HTMLElement
|
||||
@ -11,8 +10,6 @@ const root = ReactDOM.createRoot(
|
||||
|
||||
root.render(
|
||||
<AppWrapper>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
</BrowserRouter>
|
||||
<App />
|
||||
</AppWrapper>
|
||||
);
|
@ -1,36 +1,219 @@
|
||||
import Header from '../components/Header';
|
||||
import { useState } from 'react';
|
||||
import ContentDrawer from '../components/ContentDrawer';
|
||||
import UserCreate from '../components/UserCreate';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { AccountStatus, AllUser, AllUserResponse } from "@/types/user";
|
||||
import Header from "@/components/Header";
|
||||
import ContentDrawer from "@/components/ContentDrawer";
|
||||
import UserCreate from "@/components/UserCreate";
|
||||
import { Avatar, Table } from "antd";
|
||||
import { TableProps } from "antd/lib";
|
||||
import { UserService } from "@/services/userService";
|
||||
import UserEdit from "@/components/UserEdit";
|
||||
import { useSearchParams } from "react-router-dom";
|
||||
|
||||
export default function AccountsPage() {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [openCreate, setOpenCreate] = useState(false);
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
|
||||
const showDrawer = () => setOpen(true);
|
||||
const closeDrawer = () => setOpen(false);
|
||||
const [activeAccount, setActiveAccount] = useState<
|
||||
{ login: string; id: number; name: string; email: string } | undefined
|
||||
>(undefined);
|
||||
|
||||
const showCreateDrawer = () => setOpenCreate(true);
|
||||
const closeCreateDrawer = () => {
|
||||
setActiveAccount(undefined);
|
||||
setOpenCreate(false);
|
||||
};
|
||||
const [openEdit, setOpenEdit] = useState(false);
|
||||
|
||||
const showEditDrawer = () => setOpenEdit(true);
|
||||
const closeEditDrawer = () => {
|
||||
setActiveAccount(undefined);
|
||||
setOpenEdit(false);
|
||||
};
|
||||
|
||||
const [accounts, setAccounts] = useState<AllUserResponse>({
|
||||
amountCount: 0,
|
||||
amountPages: 0,
|
||||
users: [],
|
||||
currentPage: 1,
|
||||
limit: 10,
|
||||
});
|
||||
|
||||
async function getUsers() {
|
||||
const page = Number(searchParams.get("page") || "1");
|
||||
const limit = Number(searchParams.get("limit") || "10");
|
||||
setSearchParams({
|
||||
page: page.toString(),
|
||||
limit: limit.toString(),
|
||||
});
|
||||
const data = await UserService.getUsers(page, limit);
|
||||
console.log("searchParams", searchParams);
|
||||
setAccounts(data);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getUsers();
|
||||
}, []);
|
||||
|
||||
const statusColor = {
|
||||
ACTIVE: "#27AE60",
|
||||
DISABLED: "#606060",
|
||||
BLOCKED: "#FF0000",
|
||||
DELETED: "#B30000",
|
||||
};
|
||||
|
||||
const columns: TableProps<AllUser>["columns"] = [
|
||||
{
|
||||
title: "#",
|
||||
dataIndex: "id",
|
||||
key: "id",
|
||||
},
|
||||
{
|
||||
title: t("nameLogin"),
|
||||
dataIndex: "nameLogin",
|
||||
key: "nameLogin",
|
||||
render: (text, record) => (
|
||||
<div
|
||||
onClick={() => {
|
||||
setActiveAccount({
|
||||
login: record.login,
|
||||
id: record.id,
|
||||
name: record.name,
|
||||
email: record.email || "",
|
||||
});
|
||||
showEditDrawer();
|
||||
}}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: "16px",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: "32px",
|
||||
width: "32px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<Avatar
|
||||
size={32}
|
||||
src={`https://gamma.heado.ru/go/ava?name=${record.login}`}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||
<div>{record.name}</div>
|
||||
<div style={{ color: "#606060" }}>{record.login}</div>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "E-mail",
|
||||
dataIndex: "email",
|
||||
key: "email",
|
||||
},
|
||||
{
|
||||
title: t("tenant"),
|
||||
dataIndex: "bindTenantId",
|
||||
key: "tenant",
|
||||
},
|
||||
{
|
||||
title: t("role"),
|
||||
dataIndex: "role",
|
||||
key: "role",
|
||||
render: (text) => <div>{t(text)}</div>,
|
||||
},
|
||||
{
|
||||
title: t("createdAt"),
|
||||
dataIndex: "createdAt",
|
||||
key: "createdAt",
|
||||
render: (text) => (
|
||||
<div>
|
||||
{new Date(text).toLocaleString("ru", {
|
||||
year: "2-digit",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
})}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("status"),
|
||||
dataIndex: "status",
|
||||
key: "status",
|
||||
render: (text) => (
|
||||
<div style={{ color: statusColor[text as AccountStatus] }}>
|
||||
{t(text)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const onTableChange: TableProps<AllUser>["onChange"] = (pagination) => {
|
||||
console.log(pagination);
|
||||
UserService.getUsers(
|
||||
pagination.current as number,
|
||||
pagination.pageSize
|
||||
).then((data) => {
|
||||
setAccounts(data);
|
||||
setSearchParams({
|
||||
page: data.currentPage.toString(),
|
||||
limit: data.limit.toString(),
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header
|
||||
title={t('accounts')}
|
||||
title={t("accounts")}
|
||||
additionalContent={
|
||||
<img
|
||||
src="./icons/header/add_2.svg"
|
||||
alt="add"
|
||||
style={{
|
||||
height: '18px',
|
||||
width: '18px',
|
||||
cursor: 'pointer',
|
||||
height: "18px",
|
||||
width: "18px",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
onClick={showDrawer}
|
||||
onClick={showCreateDrawer}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Table
|
||||
size="small"
|
||||
onChange={onTableChange}
|
||||
columns={columns}
|
||||
dataSource={accounts.users}
|
||||
pagination={{
|
||||
pageSize: accounts.limit,
|
||||
current: accounts.currentPage,
|
||||
total: accounts.amountCount,
|
||||
}}
|
||||
rowKey={"id"}
|
||||
/>
|
||||
|
||||
<ContentDrawer open={open} closeDrawer={closeDrawer} type="create">
|
||||
<UserCreate />
|
||||
<ContentDrawer
|
||||
open={openCreate}
|
||||
closeDrawer={closeCreateDrawer}
|
||||
type="create"
|
||||
>
|
||||
<UserCreate getUsers={getUsers} closeDrawer={closeCreateDrawer} />
|
||||
</ContentDrawer>
|
||||
<ContentDrawer
|
||||
login={activeAccount?.login}
|
||||
name={activeAccount?.name}
|
||||
email={activeAccount?.email}
|
||||
open={openEdit}
|
||||
closeDrawer={closeEditDrawer}
|
||||
type="edit"
|
||||
>
|
||||
<UserEdit userId={activeAccount?.id} closeDrawer={closeEditDrawer} />
|
||||
</ContentDrawer>
|
||||
</>
|
||||
);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import Header from '@/components/Header';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Header from '../components/Header';
|
||||
|
||||
export default function ConfigurationPage() {
|
||||
const { t } = useTranslation();
|
||||
|
@ -1,5 +1,5 @@
|
||||
import Header from '@/components/Header';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Header from '../components/Header';
|
||||
|
||||
export default function EventsListPage() {
|
||||
const { t } = useTranslation();
|
||||
|
107
client/src/pages/LoginPage.tsx
Normal file
107
client/src/pages/LoginPage.tsx
Normal file
@ -0,0 +1,107 @@
|
||||
import React from 'react';
|
||||
import { Form, Input, Button, Typography, message } from 'antd';
|
||||
import {
|
||||
EyeInvisibleOutlined,
|
||||
EyeTwoTone,
|
||||
UserOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { AuthService } from '@/services/authService';
|
||||
import { Auth } from '@/types/auth';
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
@ -2,8 +2,8 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Layout } from 'antd';
|
||||
import Sider from 'antd/es/layout/Sider';
|
||||
import SiderMenu from '../components/SiderMenu';
|
||||
import { Route, Routes, useLocation, useNavigate } from 'react-router-dom';
|
||||
import SiderMenu from '@/components/SiderMenu';
|
||||
import ProcessDiagramPage from './ProcessDiagramPage';
|
||||
import RunningProcessesPage from './RunningProcessesPage';
|
||||
import AccountsPage from './AccountsPage';
|
||||
|
@ -1,5 +1,5 @@
|
||||
import Header from '@/components/Header';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Header from '../components/Header';
|
||||
|
||||
export default function ProcessDiagramPage() {
|
||||
const { t } = useTranslation();
|
||||
|
@ -1,8 +1,18 @@
|
||||
// ProtectedRoute.js
|
||||
import { Outlet } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import { Outlet, useNavigate } from 'react-router-dom';
|
||||
import React, { useEffect } from 'react';
|
||||
import { useUserSelector } from '@/store/userStore';
|
||||
|
||||
const ProtectedRoute = (): React.JSX.Element => {
|
||||
const navigate = useNavigate();
|
||||
const user = useUserSelector();
|
||||
|
||||
useEffect(() => {
|
||||
if (!user?.id) {
|
||||
navigate('/login');
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
return <Outlet />;
|
||||
};
|
||||
export default ProtectedRoute;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import Header from '@/components/Header';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Header from '../components/Header';
|
||||
|
||||
export default function RunningProcessesPage() {
|
||||
const { t } = useTranslation();
|
||||
|
30
client/src/services/authService.ts
Normal file
30
client/src/services/authService.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import api from "@/api/api";
|
||||
import { useAuthStore } from "@/store/authStore";
|
||||
import { Auth } from "@/types/auth";
|
||||
import { UserService } from "./userService";
|
||||
import { useUserStore } from "@/store/userStore";
|
||||
|
||||
export class AuthService {
|
||||
static async login(auth: Auth) {
|
||||
const token = await api.login(auth);
|
||||
useAuthStore.getState().setAccessToken(token.accessToken);
|
||||
localStorage.setItem('refreshToken', token.refreshToken as string);
|
||||
await UserService.getProfile().then((user) => {
|
||||
useUserStore.getState().setUser(user);
|
||||
});
|
||||
}
|
||||
|
||||
static async logout() {
|
||||
console.log('logout');
|
||||
useUserStore.getState().setUser(null);
|
||||
useAuthStore.getState().setAccessToken(null);
|
||||
localStorage.removeItem('userInfo');
|
||||
localStorage.removeItem('refreshToken');
|
||||
}
|
||||
|
||||
static async refresh() {
|
||||
console.log('refresh');
|
||||
const token = await api.refreshToken();
|
||||
useAuthStore.getState().setAccessToken(token.accessToken);
|
||||
}
|
||||
}
|
38
client/src/services/userService.ts
Normal file
38
client/src/services/userService.ts
Normal file
@ -0,0 +1,38 @@
|
||||
import api from '@/api/api';
|
||||
import { AllUserResponse, User, UserCreate, UserUpdate } from '@/types/user';
|
||||
|
||||
export class UserService {
|
||||
static async getProfile(): Promise<User> {
|
||||
console.log('getProfile');
|
||||
const user = api.getProfile();
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
static async getUsers(
|
||||
page: number = 1,
|
||||
limit: number = 10
|
||||
): Promise<AllUserResponse> {
|
||||
console.log('getUsers');
|
||||
const allUsers = api.getUsers(page, limit);
|
||||
return allUsers;
|
||||
}
|
||||
|
||||
static async getUserById(userId: number): Promise<User> {
|
||||
console.log('getUserById');
|
||||
const user = api.getUserById(userId);
|
||||
return user;
|
||||
}
|
||||
|
||||
static async createUser(user: UserCreate): Promise<User> {
|
||||
console.log('createUser');
|
||||
const createdUser = api.createUser(user);
|
||||
return createdUser;
|
||||
}
|
||||
|
||||
static async updateUser(userId: number, user: UserUpdate): Promise<User> {
|
||||
console.log('updateUser');
|
||||
const updatedUser = api.updateUser(userId, user);
|
||||
return updatedUser;
|
||||
}
|
||||
}
|
18
client/src/store/authStore.ts
Normal file
18
client/src/store/authStore.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { create } from 'zustand';
|
||||
import { devtools } from 'zustand/middleware';
|
||||
|
||||
type AuthState = {
|
||||
accessToken: string | null;
|
||||
setAccessToken: (token: string | null) => void;
|
||||
};
|
||||
|
||||
export const useAuthStore = create<AuthState>()(
|
||||
devtools((set) => ({
|
||||
accessToken: null,
|
||||
setAccessToken: (token) => set({ accessToken: token }),
|
||||
}))
|
||||
);
|
||||
|
||||
export const useAuthSelector = () => {
|
||||
return useAuthStore((state) => state.accessToken);
|
||||
};
|
36
client/src/store/userStore.ts
Normal file
36
client/src/store/userStore.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import { User } from '@/types/user';
|
||||
import { create } from 'zustand';
|
||||
import { devtools, persist } from 'zustand/middleware';
|
||||
|
||||
const userInfo = localStorage.getItem('userInfo');
|
||||
|
||||
type UserStoreState = {
|
||||
user: User | null;
|
||||
loading: boolean;
|
||||
};
|
||||
|
||||
type UserStoreActions = {
|
||||
setUser: (user: User | null) => 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 | null) => set({ 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
4
client/src/types/auth.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { components } from './openapi-types';
|
||||
|
||||
export type Auth = components['schemas']['Auth'];
|
||||
export type Tokens = components['schemas']['Tokens'];
|
719
client/src/types/openapi-types.ts
Normal file
719
client/src/types/openapi-types.ts
Normal file
@ -0,0 +1,719 @@
|
||||
/**
|
||||
* 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: {
|
||||
/** 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;
|
||||
/** Currentpage */
|
||||
currentPage: number;
|
||||
/** Limit */
|
||||
limit: 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";
|
||||
/** Tokens */
|
||||
Tokens: {
|
||||
/** Accesstoken */
|
||||
accessToken: string;
|
||||
/** Refreshtoken */
|
||||
refreshToken?: string | null;
|
||||
};
|
||||
/** 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"];
|
||||
};
|
||||
/** UserCreate */
|
||||
UserCreate: {
|
||||
/** Name */
|
||||
name?: string | null;
|
||||
/** Login */
|
||||
login?: string | null;
|
||||
/** Email */
|
||||
email?: string | null;
|
||||
/** Password */
|
||||
password?: string | null;
|
||||
/** Bindtenantid */
|
||||
bindTenantId?: string | null;
|
||||
role?: components["schemas"]["AccountRole"] | null;
|
||||
/** Meta */
|
||||
meta?: {
|
||||
[key: string]: unknown;
|
||||
} | null;
|
||||
status?: components["schemas"]["AccountStatus"] | null;
|
||||
};
|
||||
/** UserUpdate */
|
||||
UserUpdate: {
|
||||
/** Id */
|
||||
id?: number | null;
|
||||
/** Name */
|
||||
name?: string | null;
|
||||
/** Login */
|
||||
login?: string | null;
|
||||
/** Email */
|
||||
email?: string | null;
|
||||
/** Password */
|
||||
password?: 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"]["Tokens"];
|
||||
};
|
||||
};
|
||||
/** @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"]["Tokens"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
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"]["UserCreate"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description Successful Response */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["AllUser"];
|
||||
};
|
||||
};
|
||||
/** @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"]["UserUpdate"];
|
||||
};
|
||||
};
|
||||
/** @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"]["UserUpdate"];
|
||||
};
|
||||
};
|
||||
/** @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"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
9
client/src/types/user.ts
Normal file
9
client/src/types/user.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { components } from './openapi-types';
|
||||
|
||||
export type User = components['schemas']['User'];
|
||||
export type AllUserResponse = components['schemas']['AllUserResponse'];
|
||||
export type AllUser = components['schemas']['AllUser'];
|
||||
export type AccountStatus = components['schemas']['AccountStatus'];
|
||||
export type AccountRole = components['schemas']['AccountRole'];
|
||||
export type UserUpdate = components['schemas']['UserUpdate'];
|
||||
export type UserCreate = components['schemas']['UserCreate'];
|
@ -1,11 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
@ -18,9 +14,11 @@
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
"jsx": "react-jsx",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
"include": ["src"]
|
||||
}
|
||||
|
23
client/vite.config.ts
Normal file
23
client/vite.config.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import path from 'path';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
port: 3000,
|
||||
open: false,
|
||||
},
|
||||
build: {
|
||||
outDir: 'build',
|
||||
},
|
||||
preview: {
|
||||
port: 3000,
|
||||
open: false,
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
},
|
||||
},
|
||||
});
|
@ -32,11 +32,11 @@ startretries=5
|
||||
|
||||
[program:client]
|
||||
environment=
|
||||
REACT_APP_WEBSOCKET_PROTOCOL=ws,
|
||||
REACT_APP_HTTP_PROTOCOL=http,
|
||||
REACT_APP_API_URL=localhost:8000,
|
||||
REACT_APP_URL=localhost:3000
|
||||
command=bash -c 'cd client; npm run build; serve -s build'
|
||||
VITE_APP_WEBSOCKET_PROTOCOL=ws,
|
||||
VITE_APP_HTTP_PROTOCOL=http,
|
||||
VITE_APP_API_URL=localhost:8000,
|
||||
VITE_APP_URL=localhost:3000
|
||||
command=bash -c 'cd client; npm run build; npm run preview'
|
||||
numprocs=1
|
||||
process_name=node-%(process_num)d
|
||||
stdout_logfile=client.out.log
|
||||
|
Loading…
Reference in New Issue
Block a user