feat: init easy backend install from cli

This commit is contained in:
swve 2024-04-19 19:00:49 +02:00
parent 5c7c405e41
commit a6742d17c1
9 changed files with 134 additions and 55 deletions

112
apps/api/cli.py Normal file
View file

@ -0,0 +1,112 @@
from typing import Annotated
from pydantic import EmailStr
from sqlalchemy import create_engine
from sqlmodel import SQLModel, Session
import typer
from config.config import get_learnhouse_config
from src.db.organizations import OrganizationCreate
from src.db.users import UserCreate
from src.services.install.install import (
install_create_organization,
install_create_organization_user,
install_default_elements,
)
cli = typer.Typer()
@cli.command()
def install(
short: Annotated[bool, typer.Option(help="Install with predefined values")] = False
):
# Get the database session
learnhouse_config = get_learnhouse_config()
engine = create_engine(
learnhouse_config.database_config.sql_connection_string, echo=False, pool_pre_ping=True # type: ignore
)
SQLModel.metadata.create_all(engine)
db_session = Session(engine)
if short:
# Install the default elements
print("Installing default elements...")
install_default_elements(db_session)
print("Default elements installed ✅")
# Create the Organization
print("Creating default organization...")
org = OrganizationCreate(
name="Default Organization",
description="Default Organization",
slug="default",
email="",
logo_image="",
)
install_create_organization(org, db_session)
print("Default organization created ✅")
# Create Organization User
print("Creating default organization user...")
user = UserCreate(
username="admin", email=EmailStr("admin@school.io"), password="adminsecret"
)
install_create_organization_user(user, "default", db_session)
print("Default organization user created ✅")
# Show the user how to login
print("Installation completed ✅")
print("")
print("Login with the following credentials:")
print("email: admin@school.io")
print("password: adminsecret")
else:
# Install the default elements
print("Installing default elements...")
install_default_elements(db_session)
print("Default elements installed ✅")
# Create the Organization
print("Creating your organization...")
orgname = typer.prompt("What's shall we call your organization?")
slug = typer.prompt(
"What's the slug for your organization? (e.g. school, acme)"
)
org = OrganizationCreate(
name=orgname,
description="Default Organization",
slug=slug.lower(),
email="",
logo_image="",
)
install_create_organization(org, db_session)
print(orgname + " Organization created ✅")
# Create Organization User
print("Creating your organization user...")
username = typer.prompt("What's the username for the user?")
email = typer.prompt("What's the email for the user?")
password = typer.prompt("What's the password for the user?", hide_input=True)
user = UserCreate(username=username, email=EmailStr(email), password=password)
install_create_organization_user(user, slug, db_session)
print(username + " user created ✅")
# Show the user how to login
print("Installation completed ✅")
print("")
print("Login with the following credentials:")
print("email: " + email)
print("password: The password you entered")
@cli.command()
def main():
cli()
if __name__ == "__main__":
cli()

45
apps/api/poetry.lock generated
View file

@ -3186,54 +3186,21 @@ telegram = ["requests"]
[[package]]
name = "typer"
version = "0.12.0"
version = "0.12.3"
description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
optional = false
python-versions = ">=3.7"
files = [
{file = "typer-0.12.0-py3-none-any.whl", hash = "sha256:0441a0bb8962fb4383b8537ada9f7eb2d0deda0caa2cfe7387cc221290f617e4"},
{file = "typer-0.12.0.tar.gz", hash = "sha256:900fe786ce2d0ea44653d3c8ee4594a22a496a3104370ded770c992c5e3c542d"},
]
[package.dependencies]
typer-cli = "0.12.0"
typer-slim = {version = "0.12.0", extras = ["standard"]}
[[package]]
name = "typer-cli"
version = "0.12.0"
description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
optional = false
python-versions = ">=3.7"
files = [
{file = "typer_cli-0.12.0-py3-none-any.whl", hash = "sha256:7b7e2dd49f59974bb5a869747045d5444b17bffb851e006cd424f602d3578104"},
{file = "typer_cli-0.12.0.tar.gz", hash = "sha256:603ed3d5a278827bd497e4dc73a39bb714b230371c8724090b0de2abdcdd9f6e"},
]
[package.dependencies]
typer-slim = {version = "0.12.0", extras = ["standard"]}
[[package]]
name = "typer-slim"
version = "0.12.0"
description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
optional = false
python-versions = ">=3.7"
files = [
{file = "typer_slim-0.12.0-py3-none-any.whl", hash = "sha256:ddd7042b29a32140528caa415750bcae54113ba0c32270ca11a6f64069ddadf9"},
{file = "typer_slim-0.12.0.tar.gz", hash = "sha256:3e8a3f17286b173d76dca0fd4e02651c9a2ce1467b3754876b1ac4bd72572beb"},
{file = "typer-0.12.3-py3-none-any.whl", hash = "sha256:070d7ca53f785acbccba8e7d28b08dcd88f79f1fbda035ade0aecec71ca5c914"},
{file = "typer-0.12.3.tar.gz", hash = "sha256:49e73131481d804288ef62598d97a1ceef3058905aa536a1134f90891ba35482"},
]
[package.dependencies]
click = ">=8.0.0"
rich = {version = ">=10.11.0", optional = true, markers = "extra == \"standard\""}
shellingham = {version = ">=1.3.0", optional = true, markers = "extra == \"standard\""}
rich = ">=10.11.0"
shellingham = ">=1.3.0"
typing-extensions = ">=3.7.4.3"
[package.extras]
all = ["rich (>=10.11.0)", "shellingham (>=1.3.0)"]
standard = ["rich (>=10.11.0)", "shellingham (>=1.3.0)"]
[[package]]
name = "typing-extensions"
version = "4.10.0"
@ -3730,4 +3697,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
[metadata]
lock-version = "2.0"
python-versions = "^3.12"
content-hash = "0f7dbd7ca5511470158142c475dab8fad69ffb35fb31da437847bda67f3efbcb"
content-hash = "849c99445d4d0dc5fb06a50359c4e166b357f3705003c2989103a86fd388decb"

View file

@ -37,6 +37,7 @@ sentry-sdk = {extras = ["fastapi"], version = "^1.45.0"}
sqlmodel = "^0.0.16"
tiktoken = "^0.6.0"
uvicorn = "0.29.0"
typer = "^0.12.3"
[build-system]
build-backend = "poetry.core.masonry.api"

View file

@ -3,7 +3,6 @@ from src.db.install import InstallRead
from src.core.events.database import get_db_session
from src.db.organizations import OrganizationCreate
from src.db.users import UserCreate
from src.services.install.install import (
create_install_instance,
get_latest_install_instance,
@ -43,7 +42,7 @@ async def api_get_latest_install_instance(
async def api_install_def_elements(
db_session=Depends(get_db_session),
):
elements = await install_default_elements(db_session)
elements = install_default_elements(db_session)
return elements
@ -53,7 +52,7 @@ async def api_install_org(
org: OrganizationCreate,
db_session=Depends(get_db_session),
):
organization = await install_create_organization(org, db_session)
organization = install_create_organization(org, db_session)
return organization
@ -64,7 +63,7 @@ async def api_install_user(
org_slug: str,
db_session=Depends(get_db_session),
):
user = await install_create_organization_user(data, org_slug, db_session)
user = install_create_organization_user(data, org_slug, db_session)
return user

View file

@ -59,7 +59,7 @@ async def authenticate_user(
user = await security_get_user(request, db_session, email)
if not user:
return False
if not await security_verify_password(password, user.password):
if not security_verify_password(password, user.password):
return False
return user

View file

@ -17,11 +17,11 @@ ALGORITHM = "HS256"
### 🔒 Passwords Hashing ##############################################################
async def security_hash_password(password: str):
def security_hash_password(password: str):
return pbkdf2_sha256.hash(password)
async def security_verify_password(plain_password: str, hashed_password: str):
def security_verify_password(plain_password: str, hashed_password: str):
return pbkdf2_sha256.verify(plain_password, hashed_password)

View file

@ -95,7 +95,7 @@ async def update_install_instance(
# Install Default roles
async def install_default_elements(db_session: Session):
def install_default_elements(db_session: Session):
"""
"""
# remove all default roles
@ -300,7 +300,7 @@ async def install_default_elements(db_session: Session):
# Organization creation
async def install_create_organization(
def install_create_organization(
org_object: OrganizationCreate, db_session: Session
):
org = Organization.model_validate(org_object)
@ -364,14 +364,14 @@ async def install_create_organization(
return org
async def install_create_organization_user(
def install_create_organization_user(
user_object: UserCreate, org_slug: str, db_session: Session
):
user = User.model_validate(user_object)
# Complete the user object
user.user_uuid = f"user_{uuid4()}"
user.password = await security_hash_password(user_object.password)
user.password = security_hash_password(user_object.password)
user.email_verified = False
user.creation_date = str(datetime.now())
user.update_date = str(datetime.now())

View file

@ -190,7 +190,7 @@ async def change_password_with_reset_code(
)
# Change password
user.password = await security_hash_password(new_password)
user.password = security_hash_password(new_password)
db_session.add(user)
db_session.commit()

View file

@ -44,7 +44,7 @@ async def create_user(
# Complete the user object
user.user_uuid = f"user_{uuid4()}"
user.password = await security_hash_password(user_object.password)
user.password = security_hash_password(user_object.password)
user.email_verified = False
user.creation_date = str(datetime.now())
user.update_date = str(datetime.now())
@ -164,7 +164,7 @@ async def create_user_without_org(
# Complete the user object
user.user_uuid = f"user_{uuid4()}"
user.password = await security_hash_password(user_object.password)
user.password = security_hash_password(user_object.password)
user.email_verified = False
user.creation_date = str(datetime.now())
user.update_date = str(datetime.now())
@ -340,13 +340,13 @@ async def update_user_password(
# RBAC check
await rbac_check(request, current_user, "update", user.user_uuid, db_session)
if not await security_verify_password(form.old_password, user.password):
if not security_verify_password(form.old_password, user.password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED, detail="Wrong password"
)
# Update user
user.password = await security_hash_password(form.new_password)
user.password = security_hash_password(form.new_password)
user.update_date = str(datetime.now())
# Update user in database