initial commit

This commit is contained in:
2023-01-09 13:03:16 +01:00
commit d0c0668fad
89 changed files with 12472 additions and 0 deletions

16
back/Dockerfile Normal file
View 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
View File

View File

@@ -0,0 +1 @@
from .routes import router as contract_router

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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)

View 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)

View 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

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

64
back/app/core/routes.py Normal file
View 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
View 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
View 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, ], )

View File

@@ -0,0 +1 @@
from .routes import router as entity_router

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

20
back/app/entity/models.py Normal file
View 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)

View 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)

View 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
View 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)

View File

@@ -0,0 +1 @@
from .routes import router as order_router

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

13
back/app/order/models.py Normal file
View 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
View 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
View File

@@ -0,0 +1,14 @@
import uuid
from pydantic import BaseModel
class OrderRead(BaseModel):
pass
class OrderCreate(BaseModel):
login: str
class OrderUpdate(BaseModel):
pass

View 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"], )

Binary file not shown.

Binary file not shown.

Binary file not shown.

View 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)

View 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)

View 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)

View 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

View File

@@ -0,0 +1,3 @@
from .routes import router as user_router, get_auth_router
from .models import User, AccessToken

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

71
back/app/user/manager.py Normal file
View 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
View 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
View 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
View 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
View 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
View 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