mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: init easy backend install from cli
This commit is contained in:
parent
5c7c405e41
commit
a6742d17c1
9 changed files with 134 additions and 55 deletions
112
apps/api/cli.py
Normal file
112
apps/api/cli.py
Normal 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
45
apps/api/poetry.lock
generated
|
|
@ -3186,54 +3186,21 @@ telegram = ["requests"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typer"
|
name = "typer"
|
||||||
version = "0.12.0"
|
version = "0.12.3"
|
||||||
description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
|
description = "Typer, build great CLIs. Easy to code. Based on Python type hints."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "typer-0.12.0-py3-none-any.whl", hash = "sha256:0441a0bb8962fb4383b8537ada9f7eb2d0deda0caa2cfe7387cc221290f617e4"},
|
{file = "typer-0.12.3-py3-none-any.whl", hash = "sha256:070d7ca53f785acbccba8e7d28b08dcd88f79f1fbda035ade0aecec71ca5c914"},
|
||||||
{file = "typer-0.12.0.tar.gz", hash = "sha256:900fe786ce2d0ea44653d3c8ee4594a22a496a3104370ded770c992c5e3c542d"},
|
{file = "typer-0.12.3.tar.gz", hash = "sha256:49e73131481d804288ef62598d97a1ceef3058905aa536a1134f90891ba35482"},
|
||||||
]
|
|
||||||
|
|
||||||
[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"},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
click = ">=8.0.0"
|
click = ">=8.0.0"
|
||||||
rich = {version = ">=10.11.0", optional = true, markers = "extra == \"standard\""}
|
rich = ">=10.11.0"
|
||||||
shellingham = {version = ">=1.3.0", optional = true, markers = "extra == \"standard\""}
|
shellingham = ">=1.3.0"
|
||||||
typing-extensions = ">=3.7.4.3"
|
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]]
|
[[package]]
|
||||||
name = "typing-extensions"
|
name = "typing-extensions"
|
||||||
version = "4.10.0"
|
version = "4.10.0"
|
||||||
|
|
@ -3730,4 +3697,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.12"
|
python-versions = "^3.12"
|
||||||
content-hash = "0f7dbd7ca5511470158142c475dab8fad69ffb35fb31da437847bda67f3efbcb"
|
content-hash = "849c99445d4d0dc5fb06a50359c4e166b357f3705003c2989103a86fd388decb"
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ sentry-sdk = {extras = ["fastapi"], version = "^1.45.0"}
|
||||||
sqlmodel = "^0.0.16"
|
sqlmodel = "^0.0.16"
|
||||||
tiktoken = "^0.6.0"
|
tiktoken = "^0.6.0"
|
||||||
uvicorn = "0.29.0"
|
uvicorn = "0.29.0"
|
||||||
|
typer = "^0.12.3"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ from src.db.install import InstallRead
|
||||||
from src.core.events.database import get_db_session
|
from src.core.events.database import get_db_session
|
||||||
from src.db.organizations import OrganizationCreate
|
from src.db.organizations import OrganizationCreate
|
||||||
from src.db.users import UserCreate
|
from src.db.users import UserCreate
|
||||||
|
|
||||||
from src.services.install.install import (
|
from src.services.install.install import (
|
||||||
create_install_instance,
|
create_install_instance,
|
||||||
get_latest_install_instance,
|
get_latest_install_instance,
|
||||||
|
|
@ -43,7 +42,7 @@ async def api_get_latest_install_instance(
|
||||||
async def api_install_def_elements(
|
async def api_install_def_elements(
|
||||||
db_session=Depends(get_db_session),
|
db_session=Depends(get_db_session),
|
||||||
):
|
):
|
||||||
elements = await install_default_elements(db_session)
|
elements = install_default_elements(db_session)
|
||||||
|
|
||||||
return elements
|
return elements
|
||||||
|
|
||||||
|
|
@ -53,7 +52,7 @@ async def api_install_org(
|
||||||
org: OrganizationCreate,
|
org: OrganizationCreate,
|
||||||
db_session=Depends(get_db_session),
|
db_session=Depends(get_db_session),
|
||||||
):
|
):
|
||||||
organization = await install_create_organization(org, db_session)
|
organization = install_create_organization(org, db_session)
|
||||||
|
|
||||||
return organization
|
return organization
|
||||||
|
|
||||||
|
|
@ -64,7 +63,7 @@ async def api_install_user(
|
||||||
org_slug: str,
|
org_slug: str,
|
||||||
db_session=Depends(get_db_session),
|
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
|
return user
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ async def authenticate_user(
|
||||||
user = await security_get_user(request, db_session, email)
|
user = await security_get_user(request, db_session, email)
|
||||||
if not user:
|
if not user:
|
||||||
return False
|
return False
|
||||||
if not await security_verify_password(password, user.password):
|
if not security_verify_password(password, user.password):
|
||||||
return False
|
return False
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,11 +17,11 @@ ALGORITHM = "HS256"
|
||||||
### 🔒 Passwords Hashing ##############################################################
|
### 🔒 Passwords Hashing ##############################################################
|
||||||
|
|
||||||
|
|
||||||
async def security_hash_password(password: str):
|
def security_hash_password(password: str):
|
||||||
return pbkdf2_sha256.hash(password)
|
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)
|
return pbkdf2_sha256.verify(plain_password, hashed_password)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ async def update_install_instance(
|
||||||
|
|
||||||
|
|
||||||
# Install Default roles
|
# Install Default roles
|
||||||
async def install_default_elements(db_session: Session):
|
def install_default_elements(db_session: Session):
|
||||||
"""
|
"""
|
||||||
"""
|
"""
|
||||||
# remove all default roles
|
# remove all default roles
|
||||||
|
|
@ -300,7 +300,7 @@ async def install_default_elements(db_session: Session):
|
||||||
|
|
||||||
|
|
||||||
# Organization creation
|
# Organization creation
|
||||||
async def install_create_organization(
|
def install_create_organization(
|
||||||
org_object: OrganizationCreate, db_session: Session
|
org_object: OrganizationCreate, db_session: Session
|
||||||
):
|
):
|
||||||
org = Organization.model_validate(org_object)
|
org = Organization.model_validate(org_object)
|
||||||
|
|
@ -364,14 +364,14 @@ async def install_create_organization(
|
||||||
return org
|
return org
|
||||||
|
|
||||||
|
|
||||||
async def install_create_organization_user(
|
def install_create_organization_user(
|
||||||
user_object: UserCreate, org_slug: str, db_session: Session
|
user_object: UserCreate, org_slug: str, db_session: Session
|
||||||
):
|
):
|
||||||
user = User.model_validate(user_object)
|
user = User.model_validate(user_object)
|
||||||
|
|
||||||
# Complete the user object
|
# Complete the user object
|
||||||
user.user_uuid = f"user_{uuid4()}"
|
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.email_verified = False
|
||||||
user.creation_date = str(datetime.now())
|
user.creation_date = str(datetime.now())
|
||||||
user.update_date = str(datetime.now())
|
user.update_date = str(datetime.now())
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,7 @@ async def change_password_with_reset_code(
|
||||||
)
|
)
|
||||||
|
|
||||||
# Change password
|
# Change password
|
||||||
user.password = await security_hash_password(new_password)
|
user.password = security_hash_password(new_password)
|
||||||
db_session.add(user)
|
db_session.add(user)
|
||||||
|
|
||||||
db_session.commit()
|
db_session.commit()
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ async def create_user(
|
||||||
|
|
||||||
# Complete the user object
|
# Complete the user object
|
||||||
user.user_uuid = f"user_{uuid4()}"
|
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.email_verified = False
|
||||||
user.creation_date = str(datetime.now())
|
user.creation_date = str(datetime.now())
|
||||||
user.update_date = str(datetime.now())
|
user.update_date = str(datetime.now())
|
||||||
|
|
@ -164,7 +164,7 @@ async def create_user_without_org(
|
||||||
|
|
||||||
# Complete the user object
|
# Complete the user object
|
||||||
user.user_uuid = f"user_{uuid4()}"
|
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.email_verified = False
|
||||||
user.creation_date = str(datetime.now())
|
user.creation_date = str(datetime.now())
|
||||||
user.update_date = str(datetime.now())
|
user.update_date = str(datetime.now())
|
||||||
|
|
@ -340,13 +340,13 @@ async def update_user_password(
|
||||||
# RBAC check
|
# RBAC check
|
||||||
await rbac_check(request, current_user, "update", user.user_uuid, db_session)
|
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(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED, detail="Wrong password"
|
status_code=status.HTTP_401_UNAUTHORIZED, detail="Wrong password"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update user
|
# 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())
|
user.update_date = str(datetime.now())
|
||||||
|
|
||||||
# Update user in database
|
# Update user in database
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue