From 38288e8a57e410f26efd13303d6d56007652353e Mon Sep 17 00:00:00 2001 From: swve Date: Mon, 20 Nov 2023 22:38:49 +0100 Subject: [PATCH] feat: init install + cleanup code --- apps/api/src/core/events/database.py | 17 +- apps/api/src/db/activities.py | 5 +- apps/api/src/db/chapter_activities.py | 1 - apps/api/src/db/chapters.py | 2 +- apps/api/src/db/course_chapters.py | 1 - apps/api/src/db/install.py | 31 ++ apps/api/src/db/roles.py | 42 +- apps/api/src/db/trails.py | 4 +- apps/api/src/routers/auth.py | 1 - apps/api/src/routers/courses/activities.py | 1 - apps/api/src/routers/courses/collections.py | 1 - apps/api/src/routers/courses/courses.py | 2 +- apps/api/src/routers/dev.py | 9 +- apps/api/src/routers/install/install.py | 47 +- apps/api/src/routers/roles.py | 1 - .../services/courses/activities/activities.py | 14 +- .../src/services/courses/activities/pdf.py | 1 - .../src/services/courses/activities/video.py | 3 - apps/api/src/services/courses/chapters.py | 8 - apps/api/src/services/courses/collections.py | 8 +- apps/api/src/services/courses/courses.py | 4 +- apps/api/src/services/dev/mocks/__init__.py | 0 apps/api/src/services/dev/mocks/initial.py | 213 -------- apps/api/src/services/install/install.py | 458 ++++++++---------- apps/api/src/services/orgs/orgs.py | 9 +- apps/api/src/services/roles/roles.py | 3 +- apps/api/src/services/trail/trail.py | 5 +- apps/api/src/services/users/users.py | 2 +- 28 files changed, 310 insertions(+), 583 deletions(-) create mode 100644 apps/api/src/db/install.py delete mode 100644 apps/api/src/services/dev/mocks/__init__.py delete mode 100644 apps/api/src/services/dev/mocks/initial.py diff --git a/apps/api/src/core/events/database.py b/apps/api/src/core/events/database.py index 04bc0e3d..deb88120 100644 --- a/apps/api/src/core/events/database.py +++ b/apps/api/src/core/events/database.py @@ -1,23 +1,8 @@ import logging from fastapi import FastAPI import motor.motor_asyncio -from sqlmodel import Field, SQLModel, Session, create_engine +from sqlmodel import SQLModel, Session, create_engine -from src.db import ( - user_organizations, - users, - roles, - organization_settings, - organizations, - courses, - course_authors, - chapters, - activities, - course_chapters, - chapter_activities, - collections, - blocks, -) engine = create_engine( "postgresql://learnhouse:learnhouse@db:5432/learnhouse", echo=True diff --git a/apps/api/src/db/activities.py b/apps/api/src/db/activities.py index 29d460cc..27c06bac 100644 --- a/apps/api/src/db/activities.py +++ b/apps/api/src/db/activities.py @@ -1,7 +1,6 @@ -from typing import Literal, Optional -from click import Option +from typing import Optional from sqlalchemy import JSON, Column -from sqlmodel import Field, Session, SQLModel, create_engine, select +from sqlmodel import Field, SQLModel from enum import Enum diff --git a/apps/api/src/db/chapter_activities.py b/apps/api/src/db/chapter_activities.py index 1e31b7c4..b567e0de 100644 --- a/apps/api/src/db/chapter_activities.py +++ b/apps/api/src/db/chapter_activities.py @@ -1,6 +1,5 @@ from typing import Optional from sqlmodel import Field, SQLModel -from enum import Enum class ChapterActivity(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) diff --git a/apps/api/src/db/chapters.py b/apps/api/src/db/chapters.py index 7b80f564..ce5c19d1 100644 --- a/apps/api/src/db/chapters.py +++ b/apps/api/src/db/chapters.py @@ -1,7 +1,7 @@ from typing import List, Optional from pydantic import BaseModel from sqlmodel import Field, SQLModel -from src.db.activities import Activity, ActivityRead, ActivityUpdate +from src.db.activities import ActivityRead class ChapterBase(SQLModel): diff --git a/apps/api/src/db/course_chapters.py b/apps/api/src/db/course_chapters.py index 600eef0c..1d9f0990 100644 --- a/apps/api/src/db/course_chapters.py +++ b/apps/api/src/db/course_chapters.py @@ -1,6 +1,5 @@ from typing import Optional from sqlmodel import Field, SQLModel -from enum import Enum class CourseChapter(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) diff --git a/apps/api/src/db/install.py b/apps/api/src/db/install.py new file mode 100644 index 00000000..92624b17 --- /dev/null +++ b/apps/api/src/db/install.py @@ -0,0 +1,31 @@ +from typing import Optional +from sqlalchemy import JSON, Column +from sqlmodel import Field, SQLModel + + +class InstallBase(SQLModel): + step: int = Field(default=0) + data: dict = Field(default={}, sa_column=Column(JSON)) + + +class Install(InstallBase, table=True): + id: Optional[int] = Field(default=None, primary_key=True) + install_uuid: str = Field(default=None) + creation_date: str = "" + update_date: str = "" + + +class InstallCreate(InstallBase): + pass + + +class InstallUpdate(InstallBase): + pass + + +class InstallRead(InstallBase): + id: Optional[int] = Field(default=None, primary_key=True) + install_uuid: str = Field(default=None) + creation_date: str + update_date: str + pass diff --git a/apps/api/src/db/roles.py b/apps/api/src/db/roles.py index 10f1e917..70d52637 100644 --- a/apps/api/src/db/roles.py +++ b/apps/api/src/db/roles.py @@ -1,19 +1,46 @@ from enum import Enum -from typing import Optional +from typing import Optional, Union +from pydantic import BaseModel from sqlalchemy import JSON, Column from sqlmodel import Field, SQLModel +# Rights +class Permission(BaseModel): + action_create: bool + action_read: bool + action_update: bool + action_delete: bool + + def __getitem__(self, item): + return getattr(self, item) + + +class Rights(BaseModel): + courses: Permission + users: Permission + collections: Permission + organizations: Permission + coursechapters: Permission + activities: Permission + + def __getitem__(self, item): + return getattr(self, item) + + +# Database Models + + class RoleTypeEnum(str, Enum): - TYPE_ORGANIZATION = "TYPE_ORGANIZATION" - TYPE_ORGANIZATION_API_TOKEN = "TYPE_ORGANIZATION_API_TOKEN" - TYPE_GLOBAL = "TYPE_GLOBAL" + TYPE_ORGANIZATION = "TYPE_ORGANIZATION" # Organization roles are associated with an organization, they are used to define the rights of a user in an organization + TYPE_ORGANIZATION_API_TOKEN = "TYPE_ORGANIZATION_API_TOKEN" # Organization API Token roles are associated with an organization, they are used to define the rights of an API Token in an organization + TYPE_GLOBAL = "TYPE_GLOBAL" # Global roles are not associated with an organization, they are used to define the default rights of a user class RoleBase(SQLModel): name: str description: Optional[str] - rights: dict = Field(default={}, sa_column=Column(JSON)) + rights: Optional[Union[Rights,dict]] = Field(default={}, sa_column=Column(JSON)) class Role(RoleBase, table=True): @@ -26,11 +53,12 @@ class Role(RoleBase, table=True): class RoleCreate(RoleBase): - org_id: int = Field(default=None, foreign_key="organization.id") + org_id: Optional[int] = Field(default=None, foreign_key="organization.id") + class RoleUpdate(SQLModel): role_id: int = Field(default=None, foreign_key="role.id") name: Optional[str] description: Optional[str] - rights: Optional[dict] = Field(default={}, sa_column=Column(JSON)) + rights: Optional[Union[Rights,dict]] = Field(default={}, sa_column=Column(JSON)) diff --git a/apps/api/src/db/trails.py b/apps/api/src/db/trails.py index 94c19f31..c830bbd2 100644 --- a/apps/api/src/db/trails.py +++ b/apps/api/src/db/trails.py @@ -1,10 +1,8 @@ from typing import Optional from pydantic import BaseModel from sqlmodel import Field, SQLModel -from enum import Enum -from src.db.trail_runs import TrailRun, TrailRunRead +from src.db.trail_runs import TrailRunRead -from src.db.trail_steps import TrailStep class TrailBase(SQLModel): diff --git a/apps/api/src/routers/auth.py b/apps/api/src/routers/auth.py index 589782e0..307ad70b 100644 --- a/apps/api/src/routers/auth.py +++ b/apps/api/src/routers/auth.py @@ -5,7 +5,6 @@ from src.db.users import UserRead from src.core.events.database import get_db_session from config.config import get_learnhouse_config from src.security.auth import AuthJWT, authenticate_user -from src.services.users.users import PublicUser router = APIRouter() diff --git a/apps/api/src/routers/courses/activities.py b/apps/api/src/routers/courses/activities.py index 6b62128c..963d14f9 100644 --- a/apps/api/src/routers/courses/activities.py +++ b/apps/api/src/routers/courses/activities.py @@ -3,7 +3,6 @@ from src.db.activities import ActivityCreate, ActivityUpdate from src.db.users import PublicUser from src.core.events.database import get_db_session from src.services.courses.activities.activities import ( - Activity, create_activity, get_activity, get_activities, diff --git a/apps/api/src/routers/courses/collections.py b/apps/api/src/routers/courses/collections.py index d691401f..99ab1d2d 100644 --- a/apps/api/src/routers/courses/collections.py +++ b/apps/api/src/routers/courses/collections.py @@ -4,7 +4,6 @@ from src.db.collections import CollectionCreate, CollectionUpdate from src.security.auth import get_current_user from src.services.users.users import PublicUser from src.services.courses.collections import ( - Collection, create_collection, get_collection, get_collections, diff --git a/apps/api/src/routers/courses/courses.py b/apps/api/src/routers/courses/courses.py index b496f376..f9bc33ea 100644 --- a/apps/api/src/routers/courses/courses.py +++ b/apps/api/src/routers/courses/courses.py @@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends, UploadFile, Form, Request from sqlmodel import Session from src.core.events.database import get_db_session from src.db.users import PublicUser -from src.db.courses import Course, CourseCreate, CourseUpdate +from src.db.courses import CourseCreate, CourseUpdate from src.security.auth import get_current_user from src.services.courses.courses import ( diff --git a/apps/api/src/routers/dev.py b/apps/api/src/routers/dev.py index 318154e8..4fd3d2d5 100644 --- a/apps/api/src/routers/dev.py +++ b/apps/api/src/routers/dev.py @@ -1,6 +1,5 @@ -from fastapi import APIRouter, Request +from fastapi import APIRouter from config.config import get_learnhouse_config -from src.services.dev.mocks.initial import create_initial_data router = APIRouter() @@ -10,9 +9,3 @@ router = APIRouter() async def config(): config = get_learnhouse_config() return config.dict() - - -@router.get("/mock/initial") -async def initial_data(request: Request): - await create_initial_data(request) - return {"Message": "Initial data created 🤖"} diff --git a/apps/api/src/routers/install/install.py b/apps/api/src/routers/install/install.py index 52f6d5f2..dae19f3a 100644 --- a/apps/api/src/routers/install/install.py +++ b/apps/api/src/routers/install/install.py @@ -1,70 +1,71 @@ -from fastapi import APIRouter, Request +from fastapi import APIRouter, Depends, Request +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, - create_sample_data, get_latest_install_instance, install_create_organization, install_create_organization_user, install_default_elements, update_install_instance, ) -from src.services.orgs.schemas.orgs import Organization -from src.services.users.schemas.users import UserWithPassword router = APIRouter() @router.post("/start") -async def api_create_install_instance(request: Request, data: dict): +async def api_create_install_instance( + request: Request, data: dict, db_session=Depends(get_db_session), +): # create install - install = await create_install_instance(request, data) + install = await create_install_instance(request, data, db_session) return install @router.get("/latest") -async def api_get_latest_install_instance(request: Request): +async def api_get_latest_install_instance(request: Request, db_session=Depends(get_db_session),): # get latest created install - install = await get_latest_install_instance(request) + install = await get_latest_install_instance(request, db_session=db_session) return install @router.post("/default_elements") -async def api_install_def_elements(request: Request): - elements = await install_default_elements(request, {}) +async def api_install_def_elements(request: Request, db_session=Depends(get_db_session),): + elements = await install_default_elements(request, {}, db_session) return elements @router.post("/org") -async def api_install_org(request: Request, org: Organization): - organization = await install_create_organization(request, org) +async def api_install_org( + request: Request, org: OrganizationCreate, db_session=Depends(get_db_session), +): + organization = await install_create_organization(request, org, db_session) return organization @router.post("/user") -async def api_install_user(request: Request, data: UserWithPassword, org_slug: str): - user = await install_create_organization_user(request, data, org_slug) +async def api_install_user( + request: Request, data: UserCreate, org_slug: str, db_session=Depends(get_db_session), +): + user = await install_create_organization_user(request, data, org_slug, db_session) return user -@router.post("/sample") -async def api_install_user_sample(request: Request, username: str, org_slug: str): - sample = await create_sample_data(org_slug, username, request) - - return sample - - @router.post("/update") -async def api_update_install_instance(request: Request, data: dict, step: int): +async def api_update_install_instance( + request: Request, data: dict, step: int, db_session=Depends(get_db_session), +): request.app.db["installs"] # get latest created install - install = await update_install_instance(request, data, step) + install = await update_install_instance(request, data, step, db_session) return install diff --git a/apps/api/src/routers/roles.py b/apps/api/src/routers/roles.py index 61573d81..114cc255 100644 --- a/apps/api/src/routers/roles.py +++ b/apps/api/src/routers/roles.py @@ -3,7 +3,6 @@ from sqlmodel import Session from src.core.events.database import get_db_session from src.db.roles import RoleCreate, RoleUpdate from src.security.auth import get_current_user -from src.services.roles.schemas.roles import Role from src.services.roles.roles import create_role, delete_role, read_role, update_role from src.services.users.schemas.users import PublicUser diff --git a/apps/api/src/services/courses/activities/activities.py b/apps/api/src/services/courses/activities/activities.py index 3035d84b..9531ca4c 100644 --- a/apps/api/src/services/courses/activities/activities.py +++ b/apps/api/src/services/courses/activities/activities.py @@ -1,19 +1,9 @@ -import stat -from typing import Literal -from pydantic import BaseModel from sqlmodel import Session, select -from src.db.chapters import Chapter from src.db.organizations import Organization -from src import db from src.db.activities import ActivityCreate, Activity, ActivityRead, ActivityUpdate from src.db.chapter_activities import ChapterActivity -from src.security.rbac.rbac import ( - authorization_verify_based_on_roles, - authorization_verify_if_element_is_public, - authorization_verify_if_user_is_anon, -) -from src.db.users import AnonymousUser, PublicUser -from fastapi import HTTPException, status, Request +from src.db.users import PublicUser +from fastapi import HTTPException, Request from uuid import uuid4 from datetime import datetime diff --git a/apps/api/src/services/courses/activities/pdf.py b/apps/api/src/services/courses/activities/pdf.py index 9011c8a5..5dae7d02 100644 --- a/apps/api/src/services/courses/activities/pdf.py +++ b/apps/api/src/services/courses/activities/pdf.py @@ -9,7 +9,6 @@ from src.db.activities import ( from src.db.chapter_activities import ChapterActivity from src.db.course_chapters import CourseChapter from src.db.users import PublicUser -from src.security.rbac.rbac import authorization_verify_based_on_roles from src.services.courses.activities.uploads.pdfs import upload_pdf from fastapi import HTTPException, status, UploadFile, Request from uuid import uuid4 diff --git a/apps/api/src/services/courses/activities/video.py b/apps/api/src/services/courses/activities/video.py index 924a68ad..70babb80 100644 --- a/apps/api/src/services/courses/activities/video.py +++ b/apps/api/src/services/courses/activities/video.py @@ -7,9 +7,6 @@ from src.db.activities import Activity, ActivityRead, ActivitySubTypeEnum, Activ from src.db.chapter_activities import ChapterActivity from src.db.course_chapters import CourseChapter from src.db.users import PublicUser -from src.security.rbac.rbac import ( - authorization_verify_based_on_roles, -) from src.services.courses.activities.uploads.videos import upload_video from fastapi import HTTPException, status, UploadFile, Request from uuid import uuid4 diff --git a/apps/api/src/services/courses/chapters.py b/apps/api/src/services/courses/chapters.py index 09403480..f8ad98d4 100644 --- a/apps/api/src/services/courses/chapters.py +++ b/apps/api/src/services/courses/chapters.py @@ -2,7 +2,6 @@ from datetime import datetime from typing import List from uuid import uuid4 from sqlmodel import Session, select -from src import db from src.db.course_chapters import CourseChapter from src.db.activities import Activity, ActivityRead from src.db.chapter_activities import ChapterActivity @@ -14,13 +13,6 @@ from src.db.chapters import ( ChapterUpdateOrder, DepreceatedChaptersRead, ) -from src.security.auth import non_public_endpoint -from src.security.rbac.rbac import ( - authorization_verify_based_on_roles, - authorization_verify_based_on_roles_and_authorship, - authorization_verify_if_element_is_public, - authorization_verify_if_user_is_anon, -) from src.services.courses.courses import Course from src.services.users.users import PublicUser from fastapi import HTTPException, status, Request diff --git a/apps/api/src/services/courses/collections.py b/apps/api/src/services/courses/collections.py index 38269e53..73e911b0 100644 --- a/apps/api/src/services/courses/collections.py +++ b/apps/api/src/services/courses/collections.py @@ -1,8 +1,6 @@ from datetime import datetime -from gc import collect -from typing import List, Literal +from typing import List from uuid import uuid4 -from pydantic import BaseModel from sqlmodel import Session, select from src.db.collections import ( Collection, @@ -12,10 +10,6 @@ from src.db.collections import ( ) from src.db.collections_courses import CollectionCourse from src.db.courses import Course -from src.security.rbac.rbac import ( - authorization_verify_based_on_roles_and_authorship, - authorization_verify_if_user_is_anon, -) from src.services.users.users import PublicUser from fastapi import HTTPException, status, Request from typing import List diff --git a/apps/api/src/services/courses/courses.py b/apps/api/src/services/courses/courses.py index 95a90426..cc69524f 100644 --- a/apps/api/src/services/courses/courses.py +++ b/apps/api/src/services/courses/courses.py @@ -1,13 +1,11 @@ import json -from typing import List, Literal, Optional +from typing import Literal from uuid import uuid4 -from pydantic import BaseModel from sqlmodel import Session, select from src.db.course_authors import CourseAuthor, CourseAuthorshipEnum from src.db.users import PublicUser, AnonymousUser from src.db.courses import Course, CourseCreate, CourseRead, CourseUpdate from src.security.rbac.rbac import ( - authorization_verify_based_on_roles, authorization_verify_based_on_roles_and_authorship, authorization_verify_if_element_is_public, authorization_verify_if_user_is_anon, diff --git a/apps/api/src/services/dev/mocks/__init__.py b/apps/api/src/services/dev/mocks/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/api/src/services/dev/mocks/initial.py b/apps/api/src/services/dev/mocks/initial.py deleted file mode 100644 index 86973158..00000000 --- a/apps/api/src/services/dev/mocks/initial.py +++ /dev/null @@ -1,213 +0,0 @@ -import os -import requests -from datetime import datetime -from uuid import uuid4 -from fastapi import Request -from src.services.users.schemas.users import UserInDB -from src.security.security import security_hash_password -from src.services.courses.activities.activities import Activity, create_activity -from src.services.users.users import PublicUser - -from src.services.orgs.orgs import Organization, create_org -from src.services.roles.schemas.roles import Permission, Elements, RoleInDB -from faker import Faker - - -async def create_initial_data(request: Request): - fake = Faker(['en_US']) - fake_multilang = Faker( - ['en_US', 'de_DE', 'ja_JP', 'es_ES', 'it_IT', 'pt_BR', 'ar_PS']) - - - # Create users - ######################################## - - database_users = request.app.db["users"] - await database_users.delete_many({}) - - users = [] - admin_user = UserInDB( - user_id="user_admin", - creation_date=str(datetime.now()), - update_date=str(datetime.now()), - roles= [], - orgs=[], - username="admin", - email="admin@admin.admin", - password=str(await security_hash_password("admin")), - ) - - await database_users.insert_one(admin_user.dict()) - - # find admin user - users = request.app.db["users"] - admin_user = await users.find_one({"username": "admin"}) - - if admin_user: - admin_user = UserInDB(**admin_user) - current_user = PublicUser(**admin_user.dict()) - else: - raise Exception("Admin user not found") - # Create roles - ######################################## - - database_roles = request.app.db["roles"] - await database_roles.delete_many({}) - - - roles = [] - admin_role = RoleInDB( - name="Admin", - description="Admin", - elements=Elements( - courses=Permission( - action_create=True, - action_read=True, - action_update=True, - action_delete=True, - ), - users=Permission( - action_create=True, - action_read=True, - action_update=True, - action_delete=True, - ), - houses=Permission( - action_create=True, - action_read=True, - action_update=True, - action_delete=True, - ), - collections=Permission( - action_create=True, - action_read=True, - action_update=True, - action_delete=True, - ), - organizations=Permission( - action_create=True, - action_read=True, - action_update=True, - action_delete=True, - ), - coursechapters=Permission( - action_create=True, - action_read=True, - action_update=True, - action_delete=True, - ), - activities=Permission( - action_create=True, - action_read=True, - action_update=True, - action_delete=True, - ), - ), - org_id="org_test", - role_id="role_admin", - created_at=str(datetime.now()), - updated_at=str(datetime.now()), - ) - - roles.append(admin_role) - - for role in roles: - database_roles.insert_one(role.dict()) - - - # Create organizations - ######################################## - - database_orgs = request.app.db["organizations"] - await database_orgs.delete_many({}) - - organizations = [] - for i in range(0, 2): - company = fake.company() - # remove whitespace and special characters and make lowercase - slug = ''.join(e for e in company if e.isalnum()).lower() - # org = Organization( - # name=company, - # description=fake.unique.text(), - # email=fake.unique.email(), - # slug=slug, - # logo="", - # default=False - # ) - # organizations.append(org) - # await create_org(request, org, current_user) - - - # Generate Courses and CourseChapters - ######################################## - - database_courses = request.app.db["courses"] - await database_courses.delete_many({}) - - courses = [] - orgs = request.app.db["organizations"] - - if await orgs.count_documents({}) > 0: - for org in await orgs.find().to_list(length=100): - for i in range(0, 5): - - # get image in BinaryIO format from unsplash and save it to disk - image = requests.get( - "https://source.unsplash.com/random/800x600") - with open("thumbnail.jpg", "wb") as f: - f.write(image.content) - - course_id = f"course_{uuid4()}" - # course = CourseInDB( - # name=fake_multilang.unique.sentence(), - # description=fake_multilang.unique.text(), - # mini_description=fake_multilang.unique.text(), - # thumbnail="thumbnail", - # org_id=org['org_id'], - # learnings=[fake_multilang.unique.sentence() - # for i in range(0, 5)], - # public=True, - # chapters=[], - # course_id=course_id, - # creationDate=str(datetime.now()), - # updateDate=str(datetime.now()), - # authors=[current_user.user_id], - # chapters_content=[], - # ) - - courses = request.app.db["courses"] - name_in_disk = f"test_mock{course_id}.jpeg" - - image = requests.get( - "https://source.unsplash.com/random/800x600/?img=1") - - # check if folder exists and create it if not - if not os.path.exists("content/uploads/img"): - - os.makedirs("content/uploads/img") - - with open(f"content/uploads/img/{name_in_disk}", "wb") as f: - f.write(image.content) - - # course.thumbnail = name_in_disk - - # course = CourseInDB(**course.dict()) - # await courses.insert_one(course.dict()) - - # create chapters - # for i in range(0, 5): - # coursechapter = CourseChapter( - # name=fake_multilang.unique.sentence(), - # description=fake_multilang.unique.text(), - # activities=[], - # ) - # coursechapter = await create_coursechapter(request,coursechapter, course_id, current_user) - # if coursechapter: - # # create activities - # for i in range(0, 5): - # activity = Activity( - # name=fake_multilang.unique.sentence(), - # type="dynamic", - # content={}, - # ) - # activity = await create_activity(request,activity, "org_test", coursechapter['coursechapter_id'], current_user) diff --git a/apps/api/src/services/install/install.py b/apps/api/src/services/install/install.py index 152ef745..9c08c3fb 100644 --- a/apps/api/src/services/install/install.py +++ b/apps/api/src/services/install/install.py @@ -1,33 +1,15 @@ from datetime import datetime from uuid import uuid4 -from fastapi import HTTPException, Request, status -from pydantic import BaseModel -import requests +from fastapi import HTTPException, Request +from sqlalchemy import desc +from sqlmodel import Session, select +from src.db.install import Install +from src.db.organizations import Organization, OrganizationCreate +from src.db.roles import Permission, Rights, Role, RoleTypeEnum +from src.db.user_organizations import UserOrganization +from src.db.users import User, UserCreate, UserRead from config.config import get_learnhouse_config from src.security.security import security_hash_password -from src.services.courses.activities.activities import Activity, create_activity - -from src.services.orgs.schemas.orgs import Organization, OrganizationInDB -from faker import Faker - - -from src.services.roles.schemas.roles import Elements, Permission, RoleInDB -from src.services.users.schemas.users import ( - PublicUser, - User, - UserInDB, - UserOrganization, - UserRolesInOrganization, - UserWithPassword, -) - - -class InstallInstance(BaseModel): - install_id: str - created_date: str - updated_date: str - step: int - data: dict async def isInstallModeEnabled(): @@ -42,37 +24,29 @@ async def isInstallModeEnabled(): ) -async def create_install_instance(request: Request, data: dict): - installs = request.app.db["installs"] +async def create_install_instance(request: Request, data: dict, db_session: Session): + install = Install.from_orm(data) - # get install_id - install_id = str(f"install_{uuid4()}") - created_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - updated_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - step = 1 + # complete install instance + install.install_uuid = str(f"install_{uuid4()}") + install.update_date = str(datetime.now()) + install.creation_date = str(datetime.now()) - # create install - install = InstallInstance( - install_id=install_id, - created_date=created_date, - updated_date=updated_date, - step=step, - data=data, - ) + # insert install instance + db_session.add(install) - # insert install - installs.insert_one(install.dict()) + # commit changes + db_session.commit() + + # refresh install instance + db_session.refresh(install) return install -async def get_latest_install_instance(request: Request): - installs = request.app.db["installs"] - - # get latest created install instance using find_one - install = await installs.find_one( - sort=[("created_date", -1)], limit=1, projection={"_id": 0} - ) +async def get_latest_install_instance(request: Request, db_session: Session): + statement = select(Install).order_by(desc(Install.creation_date)).limit(1) + install = db_session.exec(statement).first() if install is None: raise HTTPException( @@ -80,37 +54,31 @@ async def get_latest_install_instance(request: Request): detail="No install instance found", ) - else: - install = InstallInstance(**install) - - return install + return install -async def update_install_instance(request: Request, data: dict, step: int): - installs = request.app.db["installs"] - - # get latest created install - install = await installs.find_one( - sort=[("created_date", -1)], limit=1, projection={"_id": 0} - ) +async def update_install_instance( + request: Request, data: dict, step: int, db_session: Session +): + statement = select(Install).order_by(desc(Install.creation_date)).limit(1) + install = db_session.exec(statement).first() if install is None: - return None - - else: - # update install - install["data"] = data - install["step"] = step - install["updated_date"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - # update install - await installs.update_one( - {"install_id": install["install_id"]}, {"$set": install} + raise HTTPException( + status_code=404, + detail="No install instance found", ) - install = InstallInstance(**install) + install.step = step + install.data = data - return install + # commit changes + db_session.commit() + + # refresh install instance + db_session.refresh(install) + + return install ############################################################################################################ @@ -119,24 +87,34 @@ async def update_install_instance(request: Request, data: dict, step: int): # Install Default roles -async def install_default_elements(request: Request, data: dict): - roles = request.app.db["roles"] +async def install_default_elements(request: Request, data: dict, db_session: Session): + # remove all default roles + statement = select(Role).where(Role.role_type == RoleTypeEnum.TYPE_GLOBAL) + roles = db_session.exec(statement).all() - # check if default roles ADMIN_ROLE and USER_ROLE already exist - admin_role = await roles.find_one({"role_id": "role_admin"}) - user_role = await roles.find_one({"role_id": "role_member"}) + for role in roles: + db_session.delete(role) - if admin_role is not None or user_role is not None: + db_session.commit() + + # Check if default roles already exist + statement = select(Role).where(Role.role_type == RoleTypeEnum.TYPE_GLOBAL) + roles = db_session.exec(statement).all() + + if roles and len(roles) == 3: raise HTTPException( - status_code=400, + status_code=409, detail="Default roles already exist", ) - # get default roles - ADMIN_ROLE = RoleInDB( - name="Admin Role", - description="This role grants all permissions to the user", - elements=Elements( + # Create default roles + role_global_admin = Role( + name="Admin", + description="Standard Admin Role", + id=1, + role_type=RoleTypeEnum.TYPE_GLOBAL, + role_uuid="role_global_admin", + rights=Rights( courses=Permission( action_create=True, action_read=True, @@ -149,12 +127,6 @@ async def install_default_elements(request: Request, data: dict): action_update=True, action_delete=True, ), - houses=Permission( - action_create=True, - action_read=True, - action_update=True, - action_delete=True, - ), collections=Permission( action_create=True, action_read=True, @@ -180,16 +152,65 @@ async def install_default_elements(request: Request, data: dict): action_delete=True, ), ), - org_id="*", - role_id="role_admin", - created_at=str(datetime.now()), - updated_at=str(datetime.now()), + creation_date=str(datetime.now()), + update_date=str(datetime.now()), ) - USER_ROLE = RoleInDB( - name="Member Role", - description="This role grants read-only permissions to the user", - elements=Elements( + role_global_maintainer = Role( + name="Maintainer", + description="Standard Maintainer Role", + id=2, + role_type=RoleTypeEnum.TYPE_GLOBAL, + role_uuid="role_global_maintainer", + rights=Rights( + courses=Permission( + action_create=True, + action_read=True, + action_update=True, + action_delete=True, + ), + users=Permission( + action_create=True, + action_read=True, + action_update=True, + action_delete=True, + ), + collections=Permission( + action_create=True, + action_read=True, + action_update=True, + action_delete=True, + ), + organizations=Permission( + action_create=True, + action_read=True, + action_update=True, + action_delete=True, + ), + coursechapters=Permission( + action_create=True, + action_read=True, + action_update=True, + action_delete=True, + ), + activities=Permission( + action_create=True, + action_read=True, + action_update=True, + action_delete=True, + ), + ), + creation_date=str(datetime.now()), + update_date=str(datetime.now()), + ) + + role_global_user = Role( + name="User", + description="Standard User Role", + role_type=RoleTypeEnum.TYPE_GLOBAL, + role_uuid="role_global_user", + id=3, + rights=Rights( courses=Permission( action_create=False, action_read=True, @@ -197,13 +218,7 @@ async def install_default_elements(request: Request, data: dict): action_delete=False, ), users=Permission( - action_create=False, - action_read=True, - action_update=False, - action_delete=False, - ), - houses=Permission( - action_create=False, + action_create=True, action_read=True, action_update=False, action_delete=False, @@ -233,185 +248,122 @@ async def install_default_elements(request: Request, data: dict): action_delete=False, ), ), - org_id="*", - role_id="role_member", - created_at=str(datetime.now()), - updated_at=str(datetime.now()), + creation_date=str(datetime.now()), + update_date=str(datetime.now()), ) - try: - # insert default roles - await roles.insert_many([USER_ROLE.dict(), ADMIN_ROLE.dict()]) - return True + # Serialize rights to JSON + role_global_admin.rights = role_global_admin.rights.dict() # type: ignore + role_global_maintainer.rights = role_global_maintainer.rights.dict() # type: ignore + role_global_user.rights = role_global_user.rights.dict() # type: ignore - except Exception: - raise HTTPException( - status_code=400, - detail="Error while inserting default roles", - ) + # Insert roles in DB + db_session.add(role_global_admin) + db_session.add(role_global_maintainer) + db_session.add(role_global_user) + + # commit changes + db_session.commit() + + # refresh roles + db_session.refresh(role_global_admin) + + return True # Organization creation async def install_create_organization( - request: Request, - org_object: Organization, + request: Request, org_object: OrganizationCreate, db_session: Session ): - orgs = request.app.db["organizations"] - request.app.db["users"] + org = Organization.from_orm(org_object) - # find if org already exists using name + # Complete the org object + org.org_uuid = f"org_{uuid4()}" + org.creation_date = str(datetime.now()) + org.update_date = str(datetime.now()) - isOrgAvailable = await orgs.find_one({"slug": org_object.slug.lower()}) + db_session.add(org) + db_session.commit() + db_session.refresh(org) - if isOrgAvailable: - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, - detail="Organization slug already exists", - ) - - # generate org_id with uuid4 - org_id = str(f"org_{uuid4()}") - - org = OrganizationInDB(org_id=org_id, **org_object.dict()) - - org_in_db = await orgs.insert_one(org.dict()) - - if not org_in_db: - raise HTTPException( - status_code=status.HTTP_503_SERVICE_UNAVAILABLE, - detail="Unavailable database", - ) - - return org.dict() + return org async def install_create_organization_user( - request: Request, user_object: UserWithPassword, org_slug: str + request: Request, user_object: UserCreate, org_slug: str, db_session: Session ): - users = request.app.db["users"] + user = User.from_orm(user_object) - isUsernameAvailable = await users.find_one({"username": user_object.username}) - isEmailAvailable = await users.find_one({"email": user_object.email}) + # Complete the user object + user.user_uuid = f"user_{uuid4()}" + user.password = await security_hash_password(user_object.password) + user.email_verified = False + user.creation_date = str(datetime.now()) + user.update_date = str(datetime.now()) - if isUsernameAvailable: + # Verifications + + # Check if Organization exists + statement = select(Organization).where(Organization.slug == org_slug) + org = db_session.exec(statement) + + if not org.first(): raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="Username already exists" + status_code=400, + detail="Organization does not exist", ) - if isEmailAvailable: + # Username + statement = select(User).where(User.username == user.username) + result = db_session.exec(statement) + + if result.first(): raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="Email already exists" + status_code=400, + detail="Username already exists", ) - # Generate user_id with uuid4 - user_id = str(f"user_{uuid4()}") + # Email + statement = select(User).where(User.email == user.email) + result = db_session.exec(statement) - # Set the username & hash the password - user_object.username = user_object.username.lower() - user_object.password = await security_hash_password(user_object.password) - - # Get org_id from org_slug - orgs = request.app.db["organizations"] - - # Check if the org exists - isOrgExists = await orgs.find_one({"slug": org_slug}) - - # If the org does not exist, raise an error - if not isOrgExists: + if result.first(): raise HTTPException( - status_code=status.HTTP_409_CONFLICT, - detail="You are trying to create a user in an organization that does not exist", + status_code=400, + detail="Email already exists", ) - org_id = isOrgExists["org_id"] + # Exclude unset values + user_data = user.dict(exclude_unset=True) + for key, value in user_data.items(): + setattr(user, key, value) - # Create initial orgs list with the org_id passed in - orgs = [UserOrganization(org_id=org_id, org_role="owner")] + # Add user to database + db_session.add(user) + db_session.commit() + db_session.refresh(user) - # Give role - roles = [UserRolesInOrganization(role_id="role_admin", org_id=org_id)] + - # Create the user - user = UserInDB( - user_id=user_id, + # get org id + statement = select(Organization).where(Organization.slug == org_slug) + org = db_session.exec(statement) + org = org.first() + org_id = org.id if org else 0 + + # Link user and organization + user_organization = UserOrganization( + user_id=user.id if user.id else 0, + org_id=org_id or 0, + role_id=1, creation_date=str(datetime.now()), update_date=str(datetime.now()), - orgs=orgs, - roles=roles, - **user_object.dict(), ) - # Insert the user into the database - await users.insert_one(user.dict()) + db_session.add(user_organization) + db_session.commit() + db_session.refresh(user_organization) - return User(**user.dict()) + user = UserRead.from_orm(user) - -async def create_sample_data(org_slug: str, username: str, request: Request): - Faker(["en_US"]) - fake_multilang = Faker( - ["en_US", "de_DE", "ja_JP", "es_ES", "it_IT", "pt_BR", "ar_PS"] - ) - - users = request.app.db["users"] - orgs = request.app.db["organizations"] - user = await users.find_one({"username": username}) - org = await orgs.find_one({"slug": org_slug.lower()}) - user_id = user["user_id"] - org_id = org["org_id"] - - current_user = PublicUser(**user) - - for i in range(0, 5): - # get image in BinaryIO format from unsplash and save it to disk - image = requests.get("https://source.unsplash.com/random/800x600") - with open("thumbnail.jpg", "wb") as f: - f.write(image.content) - - # course_id = f"course_{uuid4()}" - # course = CourseInDB( - # name=fake_multilang.unique.sentence(), - # description=fake_multilang.unique.text(), - # mini_description=fake_multilang.unique.text(), - # thumbnail="thumbnail", - # org_id=org_id, - # learnings=[fake_multilang.unique.sentence() for i in range(0, 5)], - # public=True, - # chapters=[], - # course_id=course_id, - # creationDate=str(datetime.now()), - # updateDate=str(datetime.now()), - # authors=[user_id], - # chapters_content=[], - # ) - - # courses = request.app.db["courses"] - - # course = CourseInDB(**course.dict()) - # await courses.insert_one(course.dict()) - - # # create chapters - # for i in range(0, 5): - # coursechapter = CourseChapter( - # name=fake_multilang.unique.sentence(), - # description=fake_multilang.unique.text(), - # activities=[], - # ) - # coursechapter = await create_coursechapter( - # request, coursechapter, course_id, current_user - # ) - # if coursechapter: - # # create activities - # for i in range(0, 5): - # activity = Activity( - # name=fake_multilang.unique.sentence(), - # type="dynamic", - # content={}, - # ) - # activity = await create_activity( - # request, - # activity, - # org_id, - # coursechapter["coursechapter_id"], - # current_user, - # ) + return user diff --git a/apps/api/src/services/orgs/orgs.py b/apps/api/src/services/orgs/orgs.py index 87f4e9f7..54182f0c 100644 --- a/apps/api/src/services/orgs/orgs.py +++ b/apps/api/src/services/orgs/orgs.py @@ -1,10 +1,7 @@ from datetime import datetime -import json -from operator import or_ -from typing import Literal from uuid import uuid4 from sqlmodel import Session, select -from src.db.users import UserRead, PublicUser +from src.db.users import PublicUser from src.db.user_organizations import UserOrganization from src.db.organizations import ( Organization, @@ -12,10 +9,6 @@ from src.db.organizations import ( OrganizationRead, OrganizationUpdate, ) -from src.security.rbac.rbac import ( - authorization_verify_based_on_roles, - authorization_verify_if_user_is_anon, -) from src.services.orgs.logos import upload_org_logo from fastapi import HTTPException, UploadFile, status, Request diff --git a/apps/api/src/services/roles/roles.py b/apps/api/src/services/roles/roles.py index 8e3aff3a..3da8f089 100644 --- a/apps/api/src/services/roles/roles.py +++ b/apps/api/src/services/roles/roles.py @@ -1,9 +1,8 @@ from uuid import uuid4 from sqlmodel import Session, select from src.db.roles import Role, RoleCreate, RoleUpdate -from src.security.rbac.rbac import authorization_verify_if_user_is_anon from src.services.users.schemas.users import PublicUser -from fastapi import HTTPException, status, Request +from fastapi import HTTPException, Request from datetime import datetime diff --git a/apps/api/src/services/trail/trail.py b/apps/api/src/services/trail/trail.py index 92a5ae03..7c4d5b58 100644 --- a/apps/api/src/services/trail/trail.py +++ b/apps/api/src/services/trail/trail.py @@ -1,12 +1,9 @@ from datetime import datetime -import stat -from typing import List, Literal, Optional from uuid import uuid4 from fastapi import HTTPException, Request, status -from pydantic import BaseModel from sqlmodel import Session, select from src.db.courses import Course -from src.db.trail_runs import TrailRun, TrailRunCreate, TrailRunRead +from src.db.trail_runs import TrailRun, TrailRunRead from src.db.trail_steps import TrailStep from src.db.trails import Trail, TrailCreate, TrailRead from src.db.users import PublicUser diff --git a/apps/api/src/services/users/users.py b/apps/api/src/services/users/users.py index 553a632a..55bbab6d 100644 --- a/apps/api/src/services/users/users.py +++ b/apps/api/src/services/users/users.py @@ -77,7 +77,7 @@ async def create_user( user_organization = UserOrganization( user_id=user.id if user.id else 0, org_id=int(org_id), - role_id=1, + role_id=3, creation_date=str(datetime.now()), update_date=str(datetime.now()), )