initial commit
This commit is contained in:
16
back/Dockerfile
Normal file
16
back/Dockerfile
Normal file
@@ -0,0 +1,16 @@
|
||||
FROM python:3.10
|
||||
|
||||
# make the 'app' folder the current working directory
|
||||
WORKDIR /code
|
||||
|
||||
# copy both 'package.json' and 'package-lock.json' (if available)
|
||||
COPY ./requirements.txt /code/requirements.txt
|
||||
|
||||
# install project dependencies
|
||||
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
||||
|
||||
# copy project files and folders to the current working directory (i.e. 'app' folder)
|
||||
COPY ./app /code/app
|
||||
|
||||
EXPOSE 8000
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]
|
||||
0
back/app/__init__.py
Normal file
0
back/app/__init__.py
Normal file
1
back/app/contract/__init__.py
Normal file
1
back/app/contract/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .routes import router as contract_router
|
||||
BIN
back/app/contract/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
back/app/contract/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
back/app/contract/__pycache__/models.cpython-310.pyc
Normal file
BIN
back/app/contract/__pycache__/models.cpython-310.pyc
Normal file
Binary file not shown.
BIN
back/app/contract/__pycache__/routes.cpython-310.pyc
Normal file
BIN
back/app/contract/__pycache__/routes.cpython-310.pyc
Normal file
Binary file not shown.
BIN
back/app/contract/__pycache__/schemas.cpython-310.pyc
Normal file
BIN
back/app/contract/__pycache__/schemas.cpython-310.pyc
Normal file
Binary file not shown.
40
back/app/contract/models.py
Normal file
40
back/app/contract/models.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
from enum import Enum
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from beanie import Document
|
||||
|
||||
from ..entity.models import Entity
|
||||
|
||||
|
||||
class ContractType(str, Enum):
|
||||
employment = 'employment'
|
||||
location = 'location'
|
||||
|
||||
|
||||
class ContractStatus(str, Enum):
|
||||
new = 'new'
|
||||
signed = 'signed'
|
||||
in_effect = 'in_effect'
|
||||
executed = 'executed'
|
||||
|
||||
|
||||
class Party(BaseModel):
|
||||
entity: Entity
|
||||
part: str
|
||||
|
||||
|
||||
class Clause(BaseModel):
|
||||
name: str
|
||||
body: str
|
||||
|
||||
|
||||
class Contract(Document):
|
||||
_id: str
|
||||
type: ContractType
|
||||
parties: List[Party]
|
||||
clauses: List[Clause]
|
||||
status: ContractStatus = Field(default=ContractStatus.new)
|
||||
created_at: datetime = Field(default=datetime.utcnow(), nullable=False)
|
||||
updated_at: datetime = Field(default_factory=datetime.utcnow, nullable=False)
|
||||
5
back/app/contract/routes.py
Normal file
5
back/app/contract/routes.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from ..core.routes import get_crud_router
|
||||
from .models import Contract
|
||||
from .schemas import ContractCreate, ContractRead, ContractUpdate
|
||||
|
||||
router = get_crud_router(Contract, ContractCreate, ContractRead, ContractUpdate)
|
||||
36
back/app/contract/schemas.py
Normal file
36
back/app/contract/schemas.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
|
||||
from pydantic import BaseModel, validator
|
||||
|
||||
from beanie import PydanticObjectId
|
||||
|
||||
from .models import Contract, ContractType, Clause
|
||||
from ..entity.models import Entity
|
||||
from ..core.schemas import Writer
|
||||
|
||||
|
||||
class ContractRead(Contract):
|
||||
pass
|
||||
|
||||
|
||||
class PartyCreate(BaseModel):
|
||||
entity: PydanticObjectId
|
||||
part: str
|
||||
|
||||
|
||||
class ContractCreate(Writer):
|
||||
type: ContractType
|
||||
parties: List[PartyCreate]
|
||||
clauses: List[Clause]
|
||||
|
||||
async def validate_foreign_key(self):
|
||||
for p in self.parties:
|
||||
p.entity = await Entity.get(p.entity)
|
||||
if p.entity is None:
|
||||
raise ValueError
|
||||
|
||||
|
||||
class ContractUpdate(BaseModel):
|
||||
status: str
|
||||
0
back/app/core/__init__.py
Normal file
0
back/app/core/__init__.py
Normal file
BIN
back/app/core/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
back/app/core/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
back/app/core/__pycache__/routes.cpython-310.pyc
Normal file
BIN
back/app/core/__pycache__/routes.cpython-310.pyc
Normal file
Binary file not shown.
BIN
back/app/core/__pycache__/schemas.cpython-310.pyc
Normal file
BIN
back/app/core/__pycache__/schemas.cpython-310.pyc
Normal file
Binary file not shown.
64
back/app/core/routes.py
Normal file
64
back/app/core/routes.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from beanie import PydanticObjectId
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from typing import TypeVar, List, Generic
|
||||
|
||||
|
||||
T = TypeVar('T')
|
||||
U = TypeVar('U')
|
||||
V = TypeVar('V')
|
||||
W = TypeVar('W')
|
||||
|
||||
|
||||
def get_crud_router(model, model_create, model_read, model_update):
|
||||
router = APIRouter()
|
||||
|
||||
@router.post("/", response_description="{} added to the database".format(model.__name__))
|
||||
async def create(item: model_create) -> dict:
|
||||
await item.validate_foreign_key()
|
||||
o = await model(**item.dict()).create()
|
||||
return {"message": "{} added successfully".format(model.__name__), "id": o}
|
||||
|
||||
@router.get("/{id}", response_description="{} record retrieved".format(model.__name__))
|
||||
async def read_id(id: PydanticObjectId) -> model_read:
|
||||
item = await model.get(id)
|
||||
return item
|
||||
|
||||
@router.get("/", response_description="{} records retrieved".format(model.__name__))
|
||||
async def read_list() -> List[model_read]:
|
||||
item = await model.find_all().to_list()
|
||||
return item
|
||||
|
||||
@router.put("/{id}", response_description="{} record updated".format(model.__name__))
|
||||
async def update(id: PydanticObjectId, req: model_update) -> model_read:
|
||||
req = {k: v for k, v in req.dict().items() if v is not None}
|
||||
update_query = {"$set": {
|
||||
field: value for field, value in req.items()
|
||||
}}
|
||||
|
||||
item = await model.get(id)
|
||||
if not item:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="{} record not found!".format(model.__name__)
|
||||
)
|
||||
|
||||
await item.update(update_query)
|
||||
return item
|
||||
|
||||
@router.delete("/{id}", response_description="{} record deleted from the database".format(model.__name__))
|
||||
async def delete(id: PydanticObjectId) -> dict:
|
||||
item = await model.get(id)
|
||||
|
||||
if not item:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="{} record not found!".format(model.__name__)
|
||||
)
|
||||
|
||||
await item.delete()
|
||||
return {
|
||||
"message": "{} deleted successfully".format(model.__name__)
|
||||
}
|
||||
|
||||
return router
|
||||
12
back/app/core/schemas.py
Normal file
12
back/app/core/schemas.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Reader(BaseModel):
|
||||
pass
|
||||
# class Config:
|
||||
# fields = {'id': '_id'}
|
||||
|
||||
|
||||
class Writer(BaseModel):
|
||||
async def validate_foreign_key(self):
|
||||
pass
|
||||
18
back/app/db.py
Normal file
18
back/app/db.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import motor.motor_asyncio
|
||||
|
||||
from beanie import init_beanie
|
||||
|
||||
from .user import User, AccessToken
|
||||
from .entity.models import Entity
|
||||
from .order.models import Order
|
||||
from .contract.models import Contract
|
||||
|
||||
DATABASE_URL = "mongodb://root:example@mongo:27017/"
|
||||
|
||||
|
||||
async def init_db():
|
||||
client = motor.motor_asyncio.AsyncIOMotorClient(
|
||||
DATABASE_URL, uuidRepresentation="standard"
|
||||
)
|
||||
|
||||
await init_beanie(database=client.db_name, document_models=[User, AccessToken, Entity, Order, Contract, ], )
|
||||
1
back/app/entity/__init__.py
Normal file
1
back/app/entity/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .routes import router as entity_router
|
||||
BIN
back/app/entity/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
back/app/entity/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
back/app/entity/__pycache__/models.cpython-310.pyc
Normal file
BIN
back/app/entity/__pycache__/models.cpython-310.pyc
Normal file
Binary file not shown.
BIN
back/app/entity/__pycache__/routes.cpython-310.pyc
Normal file
BIN
back/app/entity/__pycache__/routes.cpython-310.pyc
Normal file
Binary file not shown.
BIN
back/app/entity/__pycache__/schemas.cpython-310.pyc
Normal file
BIN
back/app/entity/__pycache__/schemas.cpython-310.pyc
Normal file
Binary file not shown.
20
back/app/entity/models.py
Normal file
20
back/app/entity/models.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from enum import Enum
|
||||
from datetime import datetime
|
||||
from pydantic import Field
|
||||
|
||||
from beanie import Document
|
||||
|
||||
|
||||
class EntityType(str, Enum):
|
||||
individual = 'individual'
|
||||
corporation = 'corporation'
|
||||
institution = 'institution'
|
||||
|
||||
|
||||
class Entity(Document):
|
||||
_id: str
|
||||
type: EntityType
|
||||
name: str
|
||||
address: str
|
||||
created_at: datetime = Field(default=datetime.utcnow(), nullable=False)
|
||||
updated_at: datetime = Field(default_factory=datetime.utcnow, nullable=False)
|
||||
7
back/app/entity/routes.py
Normal file
7
back/app/entity/routes.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from ..core.routes import get_crud_router
|
||||
from .models import Entity
|
||||
from .schemas import EntityCreate, EntityRead, EntityUpdate
|
||||
|
||||
router = get_crud_router(Entity, EntityCreate, EntityRead, EntityUpdate)
|
||||
|
||||
|
||||
20
back/app/entity/schemas.py
Normal file
20
back/app/entity/schemas.py
Normal file
@@ -0,0 +1,20 @@
|
||||
import uuid
|
||||
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .models import Entity, EntityType
|
||||
|
||||
|
||||
class EntityRead(Entity):
|
||||
pass
|
||||
|
||||
|
||||
class EntityCreate(BaseModel):
|
||||
type: EntityType
|
||||
name: str
|
||||
address: str
|
||||
|
||||
|
||||
class EntityUpdate(BaseModel):
|
||||
name: str
|
||||
33
back/app/main.py
Normal file
33
back/app/main.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from fastapi import FastAPI
|
||||
from fastapi import Depends, Request
|
||||
|
||||
from .contract import contract_router
|
||||
from .db import init_db
|
||||
from .user import user_router, get_auth_router
|
||||
from .entity import entity_router
|
||||
from .order import order_router
|
||||
from .template import template_router
|
||||
|
||||
app = FastAPI(root_path="/api/v1")
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
async def on_startup():
|
||||
await init_db()
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return {"message": "Hello World"}
|
||||
|
||||
|
||||
app.include_router(get_auth_router(), prefix="/auth", tags=["auth"], )
|
||||
app.include_router(user_router, prefix="/users", tags=["users"], )
|
||||
app.include_router(entity_router, prefix="/entity", tags=["entity"], )
|
||||
app.include_router(order_router, prefix="/order", tags=["order"], )
|
||||
app.include_router(template_router, prefix="/template", tags=["template"], )
|
||||
app.include_router(contract_router, prefix="/contract", tags=["contract"], )
|
||||
|
||||
if __name__ == '__main__':
|
||||
import uvicorn
|
||||
uvicorn.run("main:app", host='0.0.0.0', port=8000, reload=True)
|
||||
1
back/app/order/__init__.py
Normal file
1
back/app/order/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .routes import router as order_router
|
||||
BIN
back/app/order/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
back/app/order/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
back/app/order/__pycache__/models.cpython-310.pyc
Normal file
BIN
back/app/order/__pycache__/models.cpython-310.pyc
Normal file
Binary file not shown.
BIN
back/app/order/__pycache__/routes.cpython-310.pyc
Normal file
BIN
back/app/order/__pycache__/routes.cpython-310.pyc
Normal file
Binary file not shown.
BIN
back/app/order/__pycache__/schemas.cpython-310.pyc
Normal file
BIN
back/app/order/__pycache__/schemas.cpython-310.pyc
Normal file
Binary file not shown.
13
back/app/order/models.py
Normal file
13
back/app/order/models.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from datetime import datetime
|
||||
|
||||
from beanie import Document
|
||||
|
||||
|
||||
class Order(Document):
|
||||
id: str
|
||||
client: str
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
class Settings:
|
||||
name = "order_collection"
|
||||
5
back/app/order/routes.py
Normal file
5
back/app/order/routes.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from ..core.routes import get_crud_router
|
||||
from .models import Order
|
||||
from .schemas import OrderCreate, OrderRead, OrderUpdate
|
||||
|
||||
router = get_crud_router(Order, OrderCreate, OrderRead, OrderUpdate)
|
||||
14
back/app/order/schemas.py
Normal file
14
back/app/order/schemas.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import uuid
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class OrderRead(BaseModel):
|
||||
pass
|
||||
|
||||
|
||||
class OrderCreate(BaseModel):
|
||||
login: str
|
||||
|
||||
|
||||
class OrderUpdate(BaseModel):
|
||||
pass
|
||||
9
back/app/template/__init__.py
Normal file
9
back/app/template/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from fastapi import APIRouter
|
||||
|
||||
from .routes_contract import router as contract_router
|
||||
from .routes_clause import router as clause_router
|
||||
|
||||
template_router = APIRouter()
|
||||
|
||||
template_router.include_router(contract_router, prefix="/contract", tags=["template"], )
|
||||
template_router.include_router(clause_router, prefix="/clause", tags=["template"], )
|
||||
BIN
back/app/template/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
back/app/template/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
back/app/template/__pycache__/models.cpython-310.pyc
Normal file
BIN
back/app/template/__pycache__/models.cpython-310.pyc
Normal file
Binary file not shown.
BIN
back/app/template/__pycache__/routes_clause.cpython-310.pyc
Normal file
BIN
back/app/template/__pycache__/routes_clause.cpython-310.pyc
Normal file
Binary file not shown.
BIN
back/app/template/__pycache__/routes_contract.cpython-310.pyc
Normal file
BIN
back/app/template/__pycache__/routes_contract.cpython-310.pyc
Normal file
Binary file not shown.
BIN
back/app/template/__pycache__/schemas.cpython-310.pyc
Normal file
BIN
back/app/template/__pycache__/schemas.cpython-310.pyc
Normal file
Binary file not shown.
34
back/app/template/models.py
Normal file
34
back/app/template/models.py
Normal file
@@ -0,0 +1,34 @@
|
||||
from datetime import datetime
|
||||
from typing import List
|
||||
from enum import Enum
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from beanie import Document
|
||||
|
||||
|
||||
class ContractType(str, Enum):
|
||||
individual = 'individual'
|
||||
corporation = 'corporation'
|
||||
|
||||
|
||||
class PartyTemplate(BaseModel):
|
||||
entity_id: str
|
||||
part: str
|
||||
name: str
|
||||
address: str
|
||||
|
||||
|
||||
class ClauseTemplate(Document):
|
||||
name: str
|
||||
body: str
|
||||
created_at: datetime = Field(default=datetime.utcnow(), nullable=False)
|
||||
updated_at: datetime = Field(default_factory=datetime.utcnow, nullable=False)
|
||||
|
||||
|
||||
class ContractTemplate(Document):
|
||||
id: str
|
||||
type: ContractType
|
||||
parties: List[PartyTemplate]
|
||||
clauses: List[ClauseTemplate]
|
||||
created_at: datetime = Field(default=datetime.utcnow(), nullable=False)
|
||||
updated_at: datetime = Field(default_factory=datetime.utcnow, nullable=False)
|
||||
5
back/app/template/routes_clause.py
Normal file
5
back/app/template/routes_clause.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from ..core.routes import get_crud_router
|
||||
from .models import ClauseTemplate
|
||||
from .schemas import ClauseTemplateCreate, ClauseTemplateRead, ClauseTemplateUpdate
|
||||
|
||||
router = get_crud_router(ClauseTemplate, ClauseTemplateCreate, ClauseTemplateRead, ClauseTemplateUpdate)
|
||||
5
back/app/template/routes_contract.py
Normal file
5
back/app/template/routes_contract.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from ..core.routes import get_crud_router
|
||||
from .models import ContractTemplate
|
||||
from .schemas import ContractTemplateCreate, ContractTemplateRead, ContractTemplateUpdate
|
||||
|
||||
router = get_crud_router(ContractTemplate, ContractTemplateCreate, ContractTemplateRead, ContractTemplateUpdate)
|
||||
32
back/app/template/schemas.py
Normal file
32
back/app/template/schemas.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import uuid
|
||||
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .models import ContractTemplate, ClauseTemplate
|
||||
|
||||
|
||||
class ContractTemplateRead(ContractTemplate):
|
||||
pass
|
||||
|
||||
|
||||
class ContractTemplateCreate(BaseModel):
|
||||
name: str
|
||||
|
||||
|
||||
class ContractTemplateUpdate(BaseModel):
|
||||
name: str
|
||||
|
||||
|
||||
class ClauseTemplateRead(ClauseTemplate):
|
||||
pass
|
||||
|
||||
|
||||
class ClauseTemplateCreate(BaseModel):
|
||||
name: str
|
||||
body: str
|
||||
|
||||
|
||||
class ClauseTemplateUpdate(BaseModel):
|
||||
name: str
|
||||
body: str
|
||||
3
back/app/user/__init__.py
Normal file
3
back/app/user/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .routes import router as user_router, get_auth_router
|
||||
|
||||
from .models import User, AccessToken
|
||||
BIN
back/app/user/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
back/app/user/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
BIN
back/app/user/__pycache__/manager.cpython-310.pyc
Normal file
BIN
back/app/user/__pycache__/manager.cpython-310.pyc
Normal file
Binary file not shown.
BIN
back/app/user/__pycache__/model.cpython-310.pyc
Normal file
BIN
back/app/user/__pycache__/model.cpython-310.pyc
Normal file
Binary file not shown.
BIN
back/app/user/__pycache__/models.cpython-310.pyc
Normal file
BIN
back/app/user/__pycache__/models.cpython-310.pyc
Normal file
Binary file not shown.
BIN
back/app/user/__pycache__/routes.cpython-310.pyc
Normal file
BIN
back/app/user/__pycache__/routes.cpython-310.pyc
Normal file
Binary file not shown.
BIN
back/app/user/__pycache__/schemas.cpython-310.pyc
Normal file
BIN
back/app/user/__pycache__/schemas.cpython-310.pyc
Normal file
Binary file not shown.
71
back/app/user/manager.py
Normal file
71
back/app/user/manager.py
Normal file
@@ -0,0 +1,71 @@
|
||||
import uuid
|
||||
from typing import Any, Dict, Generic, Optional
|
||||
|
||||
from fastapi import Depends
|
||||
from fastapi_users import BaseUserManager, UUIDIDMixin, models, exceptions, schemas
|
||||
|
||||
from .models import User, get_user_db
|
||||
SECRET = "SECRET"
|
||||
|
||||
|
||||
class UserManager(UUIDIDMixin, BaseUserManager[User, uuid.UUID]):
|
||||
reset_password_token_secret = SECRET
|
||||
verification_token_secret = SECRET
|
||||
|
||||
async def get_by_email(self, user_email: str) -> models.UP:
|
||||
return await self.get_by_login(user_email)
|
||||
|
||||
async def get_by_login(self, user_login: str) -> models.UP:
|
||||
"""
|
||||
Get a user by user_login.
|
||||
|
||||
:param user_login: E-mail of the user to retrieve.
|
||||
:raises UserNotExists: The user does not exist.
|
||||
:return: A user.
|
||||
"""
|
||||
user = await self.user_db.get_by_login(user_login)
|
||||
|
||||
if user is None:
|
||||
raise exceptions.UserNotExists()
|
||||
|
||||
return user
|
||||
|
||||
async def create(
|
||||
self,
|
||||
user_create: schemas.UC,
|
||||
safe: bool = False
|
||||
) -> models.UP:
|
||||
"""
|
||||
Create a user in database.
|
||||
|
||||
Triggers the on_after_register handler on success.
|
||||
|
||||
:param user_create: The UserCreate model to create.
|
||||
:param safe: If True, sensitive values like is_superuser or is_verified
|
||||
will be ignored during the creation, defaults to False.
|
||||
:raises UserAlreadyExists: A user already exists with the same e-mail.
|
||||
:return: A new user.
|
||||
"""
|
||||
await self.validate_password(user_create.password, user_create)
|
||||
|
||||
existing_user = await self.user_db.get_by_login(user_create.login)
|
||||
if existing_user is not None:
|
||||
raise exceptions.UserAlreadyExists()
|
||||
|
||||
user_dict = (
|
||||
user_create.create_update_dict()
|
||||
if safe
|
||||
else user_create.create_update_dict_superuser()
|
||||
)
|
||||
password = user_dict.pop("password")
|
||||
user_dict["hashed_password"] = self.password_helper.hash(password)
|
||||
|
||||
created_user = await self.user_db.create(user_dict)
|
||||
|
||||
await self.on_after_register(created_user)
|
||||
|
||||
return created_user
|
||||
|
||||
|
||||
async def get_user_manager(user_db=Depends(get_user_db)):
|
||||
yield UserManager(user_db)
|
||||
40
back/app/user/models.py
Normal file
40
back/app/user/models.py
Normal file
@@ -0,0 +1,40 @@
|
||||
from typing import Optional, TypeVar
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel, Field
|
||||
from beanie import PydanticObjectId
|
||||
|
||||
from fastapi_users.db import BeanieBaseUser, BeanieUserDatabase
|
||||
from fastapi_users_db_beanie.access_token import BeanieAccessTokenDatabase, BeanieBaseAccessToken
|
||||
|
||||
from pymongo import IndexModel
|
||||
|
||||
|
||||
class AccessToken(BeanieBaseAccessToken[PydanticObjectId]):
|
||||
pass
|
||||
|
||||
|
||||
class User(BeanieBaseUser[PydanticObjectId]):
|
||||
login: str
|
||||
created_at: datetime = Field(default=datetime.utcnow(), nullable=False)
|
||||
updated_at: datetime = Field(default_factory=datetime.utcnow, nullable=False)
|
||||
|
||||
class Settings:
|
||||
indexes = [
|
||||
IndexModel("login", unique=True),
|
||||
]
|
||||
|
||||
|
||||
class UserDatabase(BeanieUserDatabase):
|
||||
async def get_by_login(self, login: str) -> Optional[TypeVar("UP_BEANIE", bound=BeanieBaseUser)]:
|
||||
"""Get a single user by email."""
|
||||
return await self.user_model.find_one(
|
||||
self.user_model.login == login
|
||||
)
|
||||
|
||||
|
||||
async def get_access_token_db():
|
||||
yield BeanieAccessTokenDatabase(AccessToken)
|
||||
|
||||
|
||||
async def get_user_db():
|
||||
yield UserDatabase(User)
|
||||
97
back/app/user/routes.py
Normal file
97
back/app/user/routes.py
Normal file
@@ -0,0 +1,97 @@
|
||||
import uuid
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import Depends, Request
|
||||
from fastapi_users import BaseUserManager, FastAPIUsers, UUIDIDMixin, models, exceptions
|
||||
from fastapi_users.authentication import BearerTransport, AuthenticationBackend
|
||||
from fastapi_users.authentication.strategy.db import AccessTokenDatabase, DatabaseStrategy
|
||||
|
||||
from beanie import PydanticObjectId
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from typing import List
|
||||
|
||||
from .models import User, AccessToken, get_user_db, get_access_token_db
|
||||
from .schemas import UserRead, UserUpdate, UserCreate
|
||||
from .manager import get_user_manager
|
||||
|
||||
|
||||
def get_database_strategy(
|
||||
access_token_db: AccessTokenDatabase[AccessToken] = Depends(get_access_token_db),
|
||||
) -> DatabaseStrategy:
|
||||
return DatabaseStrategy(access_token_db, lifetime_seconds=3600)
|
||||
|
||||
|
||||
bearer_transport = BearerTransport(tokenUrl="auth/jwt/login")
|
||||
|
||||
|
||||
auth_backend = AuthenticationBackend(
|
||||
name="db",
|
||||
transport=bearer_transport,
|
||||
get_strategy=get_database_strategy,
|
||||
)
|
||||
|
||||
|
||||
fastapi_users = FastAPIUsers[User, uuid.UUID](
|
||||
get_user_manager,
|
||||
[auth_backend],
|
||||
)
|
||||
|
||||
|
||||
def get_auth_router():
|
||||
return fastapi_users.get_auth_router(auth_backend)
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/", response_description="User added to the database")
|
||||
async def create(user: UserCreate, user_manager=Depends(get_user_manager)) -> dict:
|
||||
await user_manager.create(user, safe=True)
|
||||
return {"message": "User added successfully"}
|
||||
|
||||
|
||||
@router.get("/{id}", response_description="User record retrieved")
|
||||
async def read_id(id: PydanticObjectId) -> UserRead:
|
||||
user = await User.get(id)
|
||||
return user
|
||||
|
||||
|
||||
@router.get("/", response_model=List[UserRead], response_description="User records retrieved")
|
||||
async def read_list() -> List[UserRead]:
|
||||
users = await User.find_all().to_list()
|
||||
return users
|
||||
|
||||
|
||||
@router.put("/{id}", response_description="User record updated")
|
||||
async def update(id: PydanticObjectId, req: UserUpdate) -> UserRead:
|
||||
req = {k: v for k, v in req.dict().items() if v is not None}
|
||||
update_query = {"$set": {
|
||||
field: value for field, value in req.items()
|
||||
}}
|
||||
|
||||
user = await User.get(id)
|
||||
if not user:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="Review record not found!"
|
||||
)
|
||||
|
||||
await user.update(update_query)
|
||||
return user
|
||||
|
||||
|
||||
@router.delete("/{id}", response_description="User record deleted from the database")
|
||||
async def delete(id: PydanticObjectId) -> dict:
|
||||
record = await User.get(id)
|
||||
|
||||
if not record:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail="Review record not found!"
|
||||
)
|
||||
|
||||
await record.delete()
|
||||
return {
|
||||
"message": "Record deleted successfully"
|
||||
}
|
||||
35
back/app/user/schemas.py
Normal file
35
back/app/user/schemas.py
Normal file
@@ -0,0 +1,35 @@
|
||||
import uuid
|
||||
from typing import TypeVar
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from fastapi_users import schemas
|
||||
|
||||
from ..core.schemas import Reader
|
||||
from .models import User
|
||||
|
||||
|
||||
class UserBase(schemas.CreateUpdateDictModel):
|
||||
pass
|
||||
|
||||
|
||||
class UserRead(User):
|
||||
class Config:
|
||||
fields = {
|
||||
'_id': {'alias': 'id'},
|
||||
'hashed_password': {'exclude': True}
|
||||
}
|
||||
|
||||
|
||||
class UserCreate(UserBase):
|
||||
login: str
|
||||
password: str
|
||||
email: str
|
||||
|
||||
|
||||
class UserUpdate(UserBase):
|
||||
pass
|
||||
|
||||
|
||||
class PasswordUpdate(BaseModel):
|
||||
old_password: str
|
||||
password: str
|
||||
3
back/debug.py
Normal file
3
back/debug.py
Normal file
@@ -0,0 +1,3 @@
|
||||
if __name__ == '__main__':
|
||||
import uvicorn
|
||||
uvicorn.run("app.main:app", host='0.0.0.0', port=8000, reload=True)
|
||||
5
back/requirements.txt
Normal file
5
back/requirements.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
fastapi==0.88.0
|
||||
fastapi_users==10.2.1
|
||||
fastapi_users_db_beanie==1.1.2
|
||||
motor==3.1.1
|
||||
uvicorn
|
||||
Reference in New Issue
Block a user