Connected service management front to back; migrated to formik

This commit is contained in:
2022-06-19 21:10:05 +02:00
parent e6dedf5cd8
commit fef4cac1cf
24 changed files with 454 additions and 162 deletions

View File

@ -1,17 +1,55 @@
from sqlalchemy import Column
from sqlalchemy import Column, ForeignKey
from sqlalchemy import Integer
from sqlalchemy import VARCHAR
from sqlalchemy.orm import relationship
from api.database import Base
from api.database import engine
class UserModel(Base):
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True, index=True, nullable=False)
id = Column(Integer, primary_key=True, index=True)
name = Column(VARCHAR(length=32), nullable=False, unique=True)
password_hash = Column(VARCHAR, nullable=False)
class Service(Base):
__tablename__ = "service"
id = Column(Integer, primary_key=True, index=True)
name = Column(VARCHAR, nullable=False, unique=True)
# children
loadbalancer = relationship(
"Loadbalancer", cascade="all,delete", uselist=False, backref="service"
)
class Loadbalancer(Base):
__tablename__ = "loadbalancer"
id = Column(Integer, primary_key=True, index=True)
# parent
service_id = Column(Integer, ForeignKey("service.id"))
# children
servers = relationship(
"LoadbalancerServers",
cascade="all,delete",
backref="loadbalancer",
lazy="dynamic",
)
class LoadbalancerServers(Base):
__tablename__ = "loadbalancerservers"
id = Column(Integer, primary_key=True, index=True)
address = Column(VARCHAR, nullable=False)
# parent
loadbalancer_id = Column(Integer, ForeignKey("loadbalancer.id"))
Base.metadata.create_all(engine)

View File

@ -1,24 +1,32 @@
import strawberry
from api.schema.definitions.auth import AuthResult
from api.schema.definitions.auth import login
from api.schema.definitions.auth import update_me
from api.schema.definitions.common import CommonMessage
import api.schema.definitions.auth as auth
import api.schema.permissions as permissions
import api.schema.definitions.service as service
from api.schema.extensions import extensions
from api.schema.permissions import IsAuthenticated
user_perms = {
"permission_classes": [
# permissions.IsAuthenticated
]
}
@strawberry.type
class Query:
hello: str
# service
services = strawberry.field(resolver=service.get_services, **user_perms)
@strawberry.type
class Mutation:
login: AuthResult = strawberry.field(resolver=login)
update_me: CommonMessage = strawberry.field(
resolver=update_me, permission_classes=[IsAuthenticated]
)
# auth
login = strawberry.field(resolver=auth.login)
update_me = strawberry.field(resolver=auth.update_me, **user_perms)
# service
add_service = strawberry.field(resolver=service.add_service, **user_perms)
remove_service = strawberry.field(resolver=service.remove_service, **user_perms)
schema = strawberry.Schema(query=Query, mutation=Mutation, extensions=extensions)

View File

@ -2,11 +2,10 @@ from typing import TYPE_CHECKING
import strawberry
from fastapi import Request
from sqlalchemy import true
from sqlalchemy.orm import Session
from api.hasher import argon2_hasher
from api.models import UserModel
from api.models import User as UserModel
from api.schema.definitions.common import CommonError
from api.schema.definitions.common import CommonMessage
from api.schema.definitions.user import User

View File

@ -0,0 +1,105 @@
import strawberry
import typing
import api.models as models
from sqlalchemy.orm import Session
if typing.TYPE_CHECKING:
from strawberry.types import Info
from api.schema import Query
@strawberry.input
class AddLoadbalancerInput:
servers: typing.List[str]
@strawberry.input
class AddServiceInput:
name: str
loadbalancer: AddLoadbalancerInput
@strawberry.input
class ServiceFilterInput:
id: typing.Optional[int] = None
search: typing.Optional[str] = None
@strawberry.type
class LoadbalancerServer:
id: int
address: str
@classmethod
def from_instance(cls, instance: models.LoadbalancerServers):
return cls(id=instance.id, address=instance.address)
@strawberry.type
class Loadbalancer:
servers: typing.List[LoadbalancerServer]
@classmethod
def from_instance(cls, instance: models.Loadbalancer):
if not instance:
return cls(servers=[])
return cls(
servers=[
LoadbalancerServer.from_instance(server) for server in instance.servers
]
)
@strawberry.type
class Service:
id: int
name: str
loadbalancer: Loadbalancer
@classmethod
def from_instance(cls, instance: models.Service):
return cls(
id=instance.id,
name=instance.name,
loadbalancer=Loadbalancer.from_instance(instance.loadbalancer),
)
async def get_services(
root: "Query", info: "Info", body: typing.Optional[ServiceFilterInput] = None
) -> typing.List[Service]:
db: Session = info.context["db"]
stmt = db.query(models.Service)
if body:
if body.id:
stmt = stmt.filter(models.Service.id == body.id)
elif body.search:
stmt = stmt.filter(models.Service.name.like("%" + body.search + "%"))
services = [service[0] for service in db.execute(stmt).all()]
return [Service.from_instance(service) for service in services]
async def remove_service(root: "Query", info: "Info", id: int) -> None:
db: Session = info.context["db"]
db.query(models.Service).filter(models.Service.id == id).delete()
db.commit()
return None
async def add_service(root: "Query", info: "Info", body: AddServiceInput) -> Service:
db: Session = info.context["db"]
service = models.Service(name=body.name)
if body.loadbalancer:
lb = models.Loadbalancer()
for server in body.loadbalancer.servers:
lb.servers.append(models.LoadbalancerServers(address=server))
service.loadbalancer = lb
db.add(service)
db.commit()
return Service.from_instance(service)

View File

@ -1,6 +1,6 @@
import strawberry
from api.models import UserModel
import api.models as models
@strawberry.type
@ -9,5 +9,5 @@ class User:
name: str
@classmethod
def from_instance(cls, instance: UserModel):
def from_instance(cls, instance: models.User):
return cls(id=instance.id, name=instance.name)

View File

@ -2,13 +2,28 @@ from sqlalchemy.orm import Session
from api.database import SessionLocal
from api.hasher import argon2_hasher
from api.models import UserModel
import api.models as models
def seed():
db: Session = SessionLocal()
if db.query(UserModel).count() == 0:
admin = UserModel(name="admin", password_hash=argon2_hasher.hash("admin"))
if db.query(models.User).count() == 0:
admin = models.User(name="admin", password_hash=argon2_hasher.hash("admin"))
db.add(admin)
db.commit()
if db.query(models.Service).count() == 0:
lb = models.Loadbalancer()
lb.servers.append(
models.LoadbalancerServers(address="http://192.168.2.134:8000/")
)
lb.servers.append(
models.LoadbalancerServers(address="http://192.168.2.132:80/")
)
test_service = models.Service(name="service")
test_service.loadbalancer = lb
db.add(test_service)
db.commit()

View File

@ -5,10 +5,9 @@ from datetime import timedelta
from datetime import timezone
import jwt
from fastapi import Request
from starlette.datastructures import Headers
from api.models import UserModel
import api.models as models
if typing.TYPE_CHECKING:
UserTokenData = dict[str, typing.Any]
@ -17,7 +16,7 @@ JWT_SECRET = os.getenv("JWT_SECRET", "")
def encode_user_token(
user: UserModel, expire_in: timedelta = timedelta(hours=6)
user: models.User, expire_in: timedelta = timedelta(hours=6)
) -> str:
payload = {}
payload["id"] = user.id