diff --git a/app.py b/app.py index 2f66b922..d2fb50fb 100644 --- a/app.py +++ b/app.py @@ -1,3 +1,4 @@ +import logging from urllib.request import Request from fastapi import FastAPI from src.main import global_router @@ -6,6 +7,7 @@ from fastapi.responses import JSONResponse from fastapi.staticfiles import StaticFiles from fastapi_jwt_auth.exceptions import AuthJWTException from src.services.mocks.initial import create_initial_data +import pymongo ######################## # Pre-Alpha Version 0.1.0 @@ -24,17 +26,37 @@ app = FastAPI( app.add_middleware( CORSMiddleware, - allow_origins=["http://localhost:3000"], + allow_origins=["http://localhost:3000", "http://localhost:3001"], allow_methods=["*"], allow_credentials=True, allow_headers=["*"] ) +# Static Files app.mount("/content", StaticFiles(directory="content"), name="content") -# Exception Handler + +# Lifecycle Events +@app.on_event("startup") +def startup_event(): + logging.info("Starting LearnHouse...") + # Database Connection + logging.info("Connecting to database...") + try: + app.mongodb_client = pymongo.MongoClient("mongodb://localhost:27017/") # type: ignore + app.db = app.mongodb_client["learnhouse"] # type: ignore + logging.info("Connected to database!") + except Exception as e: + logging.error("Failed to connect to database!") + logging.error(e) + +@app.on_event("shutdown") +def shutdown_event(): + app.mongodb_client.close() # type: ignore + logging.info("LearnHouse has been shut down.") +# JWT Exception Handler @app.exception_handler(AuthJWTException) def authjwt_exception_handler(request: Request, exc: AuthJWTException): return JSONResponse( @@ -42,10 +64,10 @@ def authjwt_exception_handler(request: Request, exc: AuthJWTException): content={"detail": exc.message} # type: ignore ) - +# Global Routes app.include_router(global_router) - +# General Routes @app.get("/") async def root(): return {"Message": "Welcome to LearnHouse ✨"} diff --git a/docker-compose.yml b/docker-compose.yml index 6396cad6..18e3ba17 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,7 +9,7 @@ services: frontend: build: ./front ports: - - "3000:3000" + - "3001:3000" volumes: - ./front:/usr/learnhouse/front mongo: diff --git a/front/app/organizations/new.tsx b/front/app/organizations/new/page.tsx similarity index 86% rename from front/app/organizations/new.tsx rename to front/app/organizations/new/page.tsx index 81dc0086..ece50646 100644 --- a/front/app/organizations/new.tsx +++ b/front/app/organizations/new/page.tsx @@ -1,7 +1,8 @@ +"use client"; import React from "react"; -import Layout from "../../components/UI/Layout"; -import { Title } from "../../components/UI/Elements/Styles/Title"; -import { createNewOrganization } from "../../services/orgs"; +import Layout from "../../../components/UI/Layout"; +import { Title } from "../../../components/UI/Elements/Styles/Title"; +import { createNewOrganization } from "../../../services/orgs"; const Organizations = () => { const [name, setName] = React.useState(""); diff --git a/requirements.txt b/requirements.txt index b0098a02..6881d583 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ -fastapi==0.78.0 +fastapi==0.89.1 pydantic>=1.8.0,<2.0.0 -uvicorn>=0.15.0,<0.16.0 +uvicorn==0.20.0 pymongo==4.1.1 python-multipart python-jose diff --git a/src/core/__init__.py b/src/core/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/core/config/__init__.py b/src/core/config/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/core/config/config.py b/src/core/config/config.py new file mode 100644 index 00000000..e69de29b diff --git a/src/routers/courses/chapters.py b/src/routers/courses/chapters.py index 2bd25eaa..8a3beda9 100644 --- a/src/routers/courses/chapters.py +++ b/src/routers/courses/chapters.py @@ -1,4 +1,4 @@ -from fastapi import APIRouter, Depends, UploadFile, Form +from fastapi import APIRouter, Depends, Request, UploadFile, Form from src.services.courses.chapters import CourseChapter, CourseChapterMetaData, create_coursechapter, delete_coursechapter, get_coursechapter, get_coursechapters, get_coursechapters_meta, update_coursechapter, update_coursechapters_meta from src.services.users import PublicUser diff --git a/src/routers/orgs.py b/src/routers/orgs.py index 58fd8fdc..465a953b 100644 --- a/src/routers/orgs.py +++ b/src/routers/orgs.py @@ -1,6 +1,7 @@ -from fastapi import APIRouter, Depends + +from fastapi import APIRouter, Depends, Request from src.dependencies.auth import get_current_user -from src.services.orgs import Organization, create_org, delete_org, get_organization, get_organization_by_slug, get_orgs, get_orgs_by_user, update_org +from src.services.orgs import Organization, create_org, delete_org, get_organization, get_organization_by_slug, get_orgs_by_user, update_org from src.services.users import PublicUser, User @@ -8,55 +9,49 @@ router = APIRouter() @router.post("/") -async def api_create_org(org_object: Organization, current_user: PublicUser = Depends(get_current_user)): +async def api_create_org(request: Request, org_object: Organization, current_user: PublicUser = Depends(get_current_user)): """ Create new organization """ - return await create_org(org_object, current_user) + return await create_org(request, org_object, current_user) @router.get("/{org_id}") -async def api_get_org(org_id: str, current_user: PublicUser = Depends(get_current_user)): +async def api_get_org(request: Request, org_id: str, current_user: PublicUser = Depends(get_current_user)): """ Get single Org by ID """ - return await get_organization(org_id) + return await get_organization(request, org_id) + @router.get("/slug/{org_slug}") -async def api_get_org_by_slug(org_slug: str, current_user: User = Depends(get_current_user)): +async def api_get_org_by_slug(request: Request, org_slug: str, current_user: User = Depends(get_current_user)): """ Get single Org by Slug """ - return await get_organization_by_slug(org_slug) + return await get_organization_by_slug(request, org_slug) -@router.get("/page/{page}/limit/{limit}") -async def api_get_org_by(page: int, limit: int): - """ - Get orgs by page and limit - """ - return await get_orgs(page, limit) - @router.get("/user/page/{page}/limit/{limit}") -async def api_user_orgs(page: int, limit: int, current_user: PublicUser = Depends(get_current_user)): +async def api_user_orgs(request: Request, page: int, limit: int, current_user: PublicUser = Depends(get_current_user)): """ Get orgs by page and limit by user """ - return await get_orgs_by_user(current_user.user_id, page, limit) + return await get_orgs_by_user(request, current_user.user_id, page, limit) @router.put("/{org_id}") -async def api_update_org(org_object: Organization, org_id: str, current_user: PublicUser = Depends(get_current_user)): +async def api_update_org(request: Request, org_object: Organization, org_id: str, current_user: PublicUser = Depends(get_current_user)): """ Update Org by ID """ - return await update_org(org_object, org_id, current_user) + return await update_org(request, org_object, org_id, current_user) @router.delete("/{org_id}") -async def api_delete_org(org_id: str, current_user: PublicUser = Depends(get_current_user)): +async def api_delete_org(request: Request, org_id: str, current_user: PublicUser = Depends(get_current_user)): """ Delete Org by ID """ - return await delete_org(org_id, current_user) + return await delete_org(request, org_id, current_user) diff --git a/src/services/houses.py b/src/services/houses.py index 957ac35a..9464dbb4 100644 --- a/src/services/houses.py +++ b/src/services/houses.py @@ -3,7 +3,7 @@ from typing import List from uuid import uuid4 from pydantic import BaseModel from src.services.users import PublicUser, User -from src.services.database import create_config_collection, check_database, create_database, learnhouseDB, learnhouseDB +from src.services.database import check_database, create_database, learnhouseDB, learnhouseDB from src.services.security import * from fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks from datetime import datetime diff --git a/src/services/orgs.py b/src/services/orgs.py index bb37d8dd..d9287429 100644 --- a/src/services/orgs.py +++ b/src/services/orgs.py @@ -3,7 +3,7 @@ from typing import List from uuid import uuid4 from pydantic import BaseModel from src.services.users import PublicUser, User -from src.services.database import create_config_collection, check_database, create_database, learnhouseDB, learnhouseDB +from src.services.database import check_database, create_database, learnhouseDB, learnhouseDB from src.services.security import * from fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks from datetime import datetime @@ -15,14 +15,15 @@ class Organization(BaseModel): name: str description: str email: str - slug :str + slug: str class OrganizationInDB(Organization): org_id: str owners: List[str] admins: List[str] - + + class PublicOrganization(Organization): name: str description: str @@ -34,9 +35,8 @@ class PublicOrganization(Organization): #### Classes #################################################### -async def get_organization(org_id: str): - await check_database() - orgs = learnhouseDB["organizations"] +async def get_organization(request: Request, org_id: str): + orgs = request.app.db["organizations"] org = orgs.find_one({"org_id": org_id}) @@ -47,9 +47,9 @@ async def get_organization(org_id: str): org = PublicOrganization(**org) return org -async def get_organization_by_slug(org_slug: str): - await check_database() - orgs = learnhouseDB["organizations"] + +async def get_organization_by_slug(request: Request, org_slug: str): + orgs = request.app.db["organizations"] org = orgs.find_one({"slug": org_slug}) @@ -61,9 +61,8 @@ async def get_organization_by_slug(org_slug: str): return org -async def create_org(org_object: Organization, current_user: PublicUser): - await check_database() - orgs = learnhouseDB["organizations"] +async def create_org(request: Request, org_object: Organization, current_user: PublicUser): + orgs = request.app.db["organizations"] # find if org already exists using name isOrgAvailable = orgs.find_one({"slug": org_object.slug}) @@ -88,13 +87,12 @@ async def create_org(org_object: Organization, current_user: PublicUser): return org.dict() -async def update_org(org_object: Organization, org_id: str, current_user: PublicUser): - await check_database() +async def update_org(request: Request, org_object: Organization, org_id: str, current_user: PublicUser): # verify org rights - await verify_org_rights(org_id, current_user,"update") + await verify_org_rights(request, org_id, current_user, "update") - orgs = learnhouseDB["organizations"] + orgs = request.app.db["organizations"] org = orgs.find_one({"org_id": org_id}) @@ -114,12 +112,12 @@ async def update_org(org_object: Organization, org_id: str, current_user: Public return Organization(**updated_org.dict()) -async def delete_org(org_id: str, current_user: PublicUser): +async def delete_org(request: Request, org_id: str, current_user: PublicUser): await check_database() - await verify_org_rights(org_id, current_user,"delete") + await verify_org_rights(request, org_id, current_user, "delete") - orgs = learnhouseDB["organizations"] + orgs = request.app.db["organizations"] org = orgs.find_one({"org_id": org_id}) @@ -136,33 +134,21 @@ async def delete_org(org_id: str, current_user: PublicUser): status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database") -async def get_orgs(page: int = 1, limit: int = 10): - ## TODO : Deprecated - await check_database() - orgs = learnhouseDB["organizations"] +async def get_orgs_by_user(request: Request, user_id: str, page: int = 1, limit: int = 10): + orgs = request.app.db["organizations"] - # get all orgs from database - all_orgs = orgs.find().sort("name", 1).skip(10 * (page - 1)).limit(limit) - - return [json.loads(json.dumps(org, default=str)) for org in all_orgs] - - -async def get_orgs_by_user(user_id: str, page: int = 1, limit: int = 10): - await check_database() - orgs = learnhouseDB["organizations"] print(user_id) - # find all orgs where user_id is in owners or admins arrays - all_orgs = orgs.find({"$or": [{"owners": user_id}, {"admins": user_id}]}).sort("name", 1).skip(10 * (page - 1)).limit(limit) - + # find all orgs where user_id is in owners or admins arrays + all_orgs = orgs.find({"$or": [{"owners": user_id}, {"admins": user_id}]}).sort( + "name", 1).skip(10 * (page - 1)).limit(limit) + return [json.loads(json.dumps(org, default=str)) for org in all_orgs] - #### Security #################################################### -async def verify_org_rights(org_id: str, current_user: PublicUser, action: str,): - await check_database() - orgs = learnhouseDB["organizations"] +async def verify_org_rights(request: Request, org_id: str, current_user: PublicUser, action: str,): + orgs = request.app.db["organizations"] org = orgs.find_one({"org_id": org_id})