diff --git a/app.py b/app.py
index c4f356a2..58907580 100644
--- a/app.py
+++ b/app.py
@@ -1,7 +1,8 @@
+import asyncio
import logging
from fastapi import FastAPI, Request
import re
-from src.core.config.config import Settings, get_settings
+from config.config import LearnHouseConfig, get_learnhouse_config
from src.core.events.events import shutdown_app, startup_app
from src.main import global_router
from fastapi.middleware.cors import CORSMiddleware
@@ -16,10 +17,13 @@ from fastapi_jwt_auth.exceptions import AuthJWTException
# (c) LearnHouse 2022
########################
+# Get LearnHouse Config
+learnhouse_config: LearnHouseConfig = get_learnhouse_config()
+
# Global Config
app = FastAPI(
- title="LearnHouse",
- description="LearnHouse is a new open-source platform tailored for learning experiences.",
+ title=learnhouse_config.site_name,
+ description=learnhouse_config.site_description,
version="0.1.0",
root_path="/"
)
@@ -45,7 +49,7 @@ app.add_event_handler("shutdown", shutdown_app(app))
# JWT Exception Handler
-@app.exception_handler(AuthJWTException)
+@ app.exception_handler(AuthJWTException)
def authjwt_exception_handler(request: Request, exc: AuthJWTException):
return JSONResponse(
status_code=exc.status_code, # type: ignore
@@ -59,10 +63,19 @@ app.include_router(global_router)
# General Routes
-@app.get("/")
+@ app.get("/")
async def root():
return {"Message": "Welcome to LearnHouse ✨"}
+# Get config
+
+
+@ app.get("/config")
+async def config():
+ logging.info("Getting config")
+ config = get_learnhouse_config()
+ return config.dict()
+
# @app.get("/initial_data")
# async def initial_data(request: Request):
diff --git a/config/config.py b/config/config.py
index e69de29b..e94ae341 100644
--- a/config/config.py
+++ b/config/config.py
@@ -0,0 +1,111 @@
+from pydantic import BaseModel
+import os
+import yaml
+
+
+class HostingConfig(BaseModel):
+ domain: str
+ port: int
+ ssl: bool
+ use_default_org: bool
+ allowed_origins: list
+ allowed_regexp: str
+ self_hosted: bool
+
+
+class DatabaseConfig(BaseModel):
+ host: str
+ port: int
+ user: str
+ password: str
+ database_name: str
+
+
+class LearnHouseConfig(BaseModel):
+ site_name: str
+ site_description: str
+ contact_email: str
+ hosting_config: HostingConfig
+ database_config: DatabaseConfig
+
+
+def get_learnhouse_config() -> LearnHouseConfig:
+
+ # Get the YAML file
+ yaml_path = os.path.join(os.path.dirname(__file__), 'config.yaml')
+
+ # Load the YAML file
+ with open(yaml_path, 'r') as f:
+ yaml_config = yaml.safe_load(f)
+
+ # Check if environment variables are defined
+ env_site_name = os.environ.get('LEARNHOUSE_SITE_NAME')
+ env_site_description = os.environ.get('LEARNHOUSE_SITE_DESCRIPTION')
+ env_contact_email = os.environ.get('LEARNHOUSE_CONTACT_EMAIL')
+ env_domain = os.environ.get('LEARNHOUSE_DOMAIN')
+ env_port = os.environ.get('LEARNHOUSE_PORT')
+ env_ssl = os.environ.get('LEARNHOUSE_SSL')
+ env_use_default_org = os.environ.get('LEARNHOUSE_USE_DEFAULT_ORG')
+ env_allowed_origins = os.environ.get('LEARNHOUSE_ALLOWED_ORIGINS')
+ env_allowed_regexp = os.environ.get('LEARNHOUSE_ALLOWED_REGEXP')
+ env_self_hosted = os.environ.get('LEARNHOUSE_SELF_HOSTED')
+ env_host = os.environ.get('LEARNHOUSE_DB_HOST')
+ env_db_port = os.environ.get('LEARNHOUSE_DB_PORT')
+ env_user = os.environ.get('LEARNHOUSE_DB_USER')
+ env_password = os.environ.get('LEARNHOUSE_DB_PASSWORD')
+ env_database_name = os.environ.get('LEARNHOUSE_DB_NAME')
+
+ # Fill in values with YAML file if they are not provided
+ site_name = env_site_name or yaml_config.get('site_name')
+ site_description = env_site_description or yaml_config.get(
+ 'site_description')
+ contact_email = env_contact_email or yaml_config.get('contact_email')
+
+ domain = env_domain or yaml_config.get('hosting_config', {}).get('domain')
+ port = env_port or yaml_config.get('hosting_config', {}).get('port')
+ ssl = env_ssl or yaml_config.get('hosting_config', {}).get('ssl')
+ use_default_org = env_use_default_org or yaml_config.get(
+ 'hosting_config', {}).get('use_default_org')
+ allowed_origins = env_allowed_origins or yaml_config.get(
+ 'hosting_config', {}).get('allowed_origins')
+ allowed_regexp = env_allowed_regexp or yaml_config.get(
+ 'hosting_config', {}).get('allowed_regexp')
+ self_hosted = env_self_hosted or yaml_config.get(
+ 'hosting_config', {}).get('self_hosted')
+
+ host = env_host or yaml_config.get('database_config', {}).get('host')
+ db_port = env_db_port or yaml_config.get('database_config', {}).get('port')
+ user = env_user or yaml_config.get('database_config', {}).get('user')
+ password = env_password or yaml_config.get(
+ 'database_config', {}).get('password')
+ database_name = env_database_name or yaml_config.get(
+ 'database_config', {}).get('database_name')
+
+ # Create HostingConfig and DatabaseConfig objects
+ hosting_config = HostingConfig(
+ domain=domain,
+ port=int(port),
+ ssl=bool(ssl),
+ use_default_org=bool(use_default_org),
+ allowed_origins=list(allowed_origins),
+ allowed_regexp=allowed_regexp,
+ self_hosted=bool(self_hosted)
+ )
+ database_config = DatabaseConfig(
+ host=host,
+ port=int(db_port),
+ user=user,
+ password=password,
+ database_name=database_name
+ )
+
+ # Create LearnHouseConfig object
+ config = LearnHouseConfig(
+ site_name=site_name,
+ site_description=site_description,
+ contact_email=contact_email,
+ hosting_config=hosting_config,
+ database_config=database_config
+ )
+
+ return config
diff --git a/config/config.yaml b/config/config.yaml
new file mode 100644
index 00000000..65532180
--- /dev/null
+++ b/config/config.yaml
@@ -0,0 +1,22 @@
+site_name: LearnHouse
+site_description: LearnHouse is an open-source platform tailored for learning experiences.
+contact_email: hi@learnhouse.app
+
+hosting_config:
+ domain: learnhouse.app
+ port: 443
+ ssl: true
+ use_default_org: false
+ default_org: learnhouse
+ allowed_origins:
+ - https://learnhouse.app
+ - https://learnhouse.io
+ allowed_regexp: "^https://(.*\\.)?learnhouse\\.app$"
+ self_hosted: false
+
+database_config:
+ host: db.mongo
+ port: 5432
+ user: myuser
+ password: mypassword
+ database_name: mydatabase
diff --git a/config/config.yml b/config/config.yml
deleted file mode 100644
index e69de29b..00000000
diff --git a/front/app/_orgs/[orgslug]/(withmenu)/courses/page.tsx b/front/app/_orgs/[orgslug]/(withmenu)/courses/page.tsx
index ae143509..86706ad1 100644
--- a/front/app/_orgs/[orgslug]/(withmenu)/courses/page.tsx
+++ b/front/app/_orgs/[orgslug]/(withmenu)/courses/page.tsx
@@ -4,7 +4,7 @@ import { useRouter } from "next/navigation";
import React from "react";
import styled from "styled-components";
import { Title } from "@components/UI/Elements/Styles/Title";
-import { getAPIUrl, getBackendUrl, getUriWithOrg } from "@services/config/config";
+import { getAPIUrl, getBackendUrl, getSelfHostedOption, getUriWithOrg } from "@services/config/config";
import { deleteCourseFromBackend } from "@services/courses/courses";
import useSWR, { mutate } from "swr";
import { swrFetcher } from "@services/utils/requests";
@@ -25,7 +25,7 @@ const CoursesIndexPage = (params: any) => {
function removeCoursePrefix(course_id: string) {
return course_id.replace("course_", "");
}
-
+
return (
<>
@@ -44,8 +44,8 @@ const CoursesIndexPage = (params: any) => {
-
-
+
+
diff --git a/front/middleware.ts b/front/middleware.ts
index 8093b6ca..56fd95cd 100644
--- a/front/middleware.ts
+++ b/front/middleware.ts
@@ -1,3 +1,4 @@
+import { getDefaultOrg, getSelfHostedOption } from "@services/config/config";
import { NextRequest, NextResponse } from "next/server";
export const config = {
@@ -16,41 +17,34 @@ export const config = {
export default function middleware(req: NextRequest) {
const url = req.nextUrl;
-
- // Get hostname of request (e.g. demo.vercel.pub, demo.localhost:3000)
+ const isSelfHosted = getSelfHostedOption();
const hostname = req.headers.get("host") || "learnhouse.app";
+ let currentHost = hostname.replace(".localhost:3000", "");
- /* You have to replace ".vercel.pub" with your own domain if you deploy this example under your domain.
- You can also use wildcard subdomains on .vercel.app links that are associated with your Vercel team slug
- in this case, our team slug is "platformize", thus *.platformize.vercel.app works. Do note that you'll
- still need to add "*.platformize.vercel.app" as a wildcard domain on your Vercel dashboard. */
- let currentHost =
- process.env.NODE_ENV === "production" && process.env.VERCEL === "1"
- ? hostname.replace(`.vercel.pub`, "").replace(`.platformize.vercel.app`, "")
- : hostname.replace(`.localhost:3000`, "");
+ if (!isSelfHosted && currentHost === "localhost:3000" && !url.pathname.startsWith("/organizations")) {
+ // Redirect to error page if not self-hosted and on localhost
+ const errorUrl = "/error";
+ return NextResponse.redirect(errorUrl, { status: 302 });
+ }
- /* Editor route */
if (url.pathname.match(/^\/course\/[^/]+\/activity\/[^/]+\/edit$/)) {
url.pathname = `/_editor${url.pathname}`;
- console.log("editor route", url.pathname);
-
return NextResponse.rewrite(url, { headers: { orgslug: currentHost } });
}
- /* Organizations route */
if (url.pathname.startsWith("/organizations")) {
- url.pathname = url.pathname.replace("/organizations", `/organizations${currentHost}`);
- // remove localhost:3000 from url
- url.pathname = url.pathname.replace(`localhost:3000`, "");
-
-
+ if (!isSelfHosted) {
+ currentHost = "";
+ }
+ url.pathname = url.pathname.replace("/organizations", `/organizations${currentHost}`).replace("localhost:3000", "");
+
return NextResponse.rewrite(url);
}
- console.log("currentHost", url);
+ if (isSelfHosted) {
+ currentHost = getDefaultOrg() || currentHost;
+ }
- // rewrite everything else to `/_sites/[site] dynamic route
url.pathname = `/_orgs/${currentHost}${url.pathname}`;
-
- return NextResponse.rewrite(url, { headers: { olgslug: currentHost } });
+ return NextResponse.rewrite(url, { headers: { orgslug: currentHost } });
}
diff --git a/front/services/config/config.ts b/front/services/config/config.ts
index caec07fe..b8352c0d 100644
--- a/front/services/config/config.ts
+++ b/front/services/config/config.ts
@@ -2,17 +2,33 @@ const LEARNHOUSE_API_URL = "http://localhost:1338/api/";
const LEARNHOUSE_BACKEND_URL = "http://localhost:1338/";
export const getAPIUrl = () => LEARNHOUSE_API_URL;
-
export const getBackendUrl = () => LEARNHOUSE_BACKEND_URL;
+export const getSelfHostedOption = () => (process.env.NEXT_PUBLIC_LEARNHOUSE_SELF_HOSTED === "true" ? true : false);
export const getUriWithOrg = (orgslug: string, path: string) => {
+ const selfHosted = getSelfHostedOption();
+
+ if (selfHosted) {
+ return `http://localhost:3000${path}`;
+ }
return `http://${orgslug}.localhost:3000${path}`;
};
export const getOrgFromUri = () => {
- const hostname = window.location.hostname;
- // get the orgslug from the hostname
- const orgslug = hostname.split(".")[0];
- return orgslug;
-
+ const selfHosted = getSelfHostedOption();
+ if (selfHosted) {
+ getDefaultOrg();
+ } else {
+ if (typeof window !== "undefined") {
+ const hostname = window.location.hostname;
+ return hostname.replace(".localhost:3000", "");
+ }
+ }
+};
+
+export const getDefaultOrg = () => {
+ const selfHosted = getSelfHostedOption();
+ if (selfHosted) {
+ return process.env.NEXT_PUBLIC_LEARNHOUSE_DEFAULT_ORG;
+ }
};
diff --git a/front/tsconfig.json b/front/tsconfig.json
index 31ffef7d..e8fc70c6 100644
--- a/front/tsconfig.json
+++ b/front/tsconfig.json
@@ -29,7 +29,7 @@
"@editor/*": ["components/Editor/*"]
}
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx","**/**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
diff --git a/requirements.txt b/requirements.txt
index d1f1bd3a..b6abfd01 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,4 +8,5 @@ python-jose
passlib
fastapi-jwt-auth
faker
-requests
\ No newline at end of file
+requests
+pyyaml
\ No newline at end of file
diff --git a/src/core/config/config.py b/src/core/config/config.py
deleted file mode 100644
index 99b429d5..00000000
--- a/src/core/config/config.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from fastapi import FastAPI
-
-class Settings(FastAPI):
- title="LearnHousse",
- description="LearnHouse is a new open-source platform tailored for learning experiences.",
- version="0.1.0",
- root_path="/"
- docs_url="/docs"
-
-async def get_settings() -> Settings:
- return Settings()
\ No newline at end of file
diff --git a/src/core/events/events.py b/src/core/events/events.py
index daa8cc01..27dd830b 100644
--- a/src/core/events/events.py
+++ b/src/core/events/events.py
@@ -1,6 +1,7 @@
from typing import Callable
from fastapi import FastAPI
from src.core.events.database import close_database, connect_to_db
+from src.core.events.logs import create_logs_dir
def startup_app(app: FastAPI) -> Callable:
@@ -8,6 +9,9 @@ def startup_app(app: FastAPI) -> Callable:
# Connect to database
await connect_to_db(app)
+ # Create logs directory
+ await create_logs_dir()
+
return start_app
diff --git a/src/core/events/logs.py b/src/core/events/logs.py
new file mode 100644
index 00000000..08550073
--- /dev/null
+++ b/src/core/events/logs.py
@@ -0,0 +1,24 @@
+import logging
+import os
+
+
+async def create_logs_dir():
+ if not os.path.exists("logs"):
+ os.mkdir("logs")
+
+# Initiate logging
+async def init_logging():
+ await create_logs_dir()
+
+ # Logging
+ logging.basicConfig(
+ level=logging.INFO,
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
+ datefmt="%d-%b-%y %H:%M:%S",
+ handlers=[
+ logging.FileHandler("logs/learnhouse.log"),
+ logging.StreamHandler()
+ ]
+ )
+
+ logging.info("Logging initiated")
diff --git a/src/dependencies/__init__.py b/src/dependencies/__init__.py
deleted file mode 100644
index e69de29b..00000000
diff --git a/src/main.py b/src/main.py
index 3e9f6942..f85d6cbb 100644
--- a/src/main.py
+++ b/src/main.py
@@ -1,5 +1,5 @@
from fastapi import APIRouter
-from src.routers import activity, blocks, users, auth, houses, orgs, roles
+from src.routers import activity, blocks, users, auth, orgs, roles
from src.routers.courses import chapters, collections, courses,activities
@@ -9,7 +9,6 @@ global_router = APIRouter(prefix="/api")
# API Routes
global_router.include_router(users.router, prefix="/users", tags=["users"])
global_router.include_router(auth.router, prefix="/auth", tags=["auth"])
-global_router.include_router(houses.router, prefix="/houses", tags=["houses"])
global_router.include_router(orgs.router, prefix="/orgs", tags=["orgs"])
global_router.include_router(roles.router, prefix="/roles", tags=["roles"])
global_router.include_router(blocks.router, prefix="/blocks", tags=["blocks"])
diff --git a/src/routers/activity.py b/src/routers/activity.py
index 91479bef..6ea6e04c 100644
--- a/src/routers/activity.py
+++ b/src/routers/activity.py
@@ -1,5 +1,5 @@
from fastapi import APIRouter, Depends, Request
-from src.dependencies.auth import get_current_user
+from src.security.auth import get_current_user
from src.services.activity import Activity, add_activity_to_activity, close_activity, create_activity, get_user_activities, get_user_activities_orgslug
diff --git a/src/routers/auth.py b/src/routers/auth.py
index 9c8bf905..27dd78cf 100644
--- a/src/routers/auth.py
+++ b/src/routers/auth.py
@@ -1,7 +1,7 @@
from urllib.request import Request
from fastapi import Depends, APIRouter, HTTPException, status, Request
from fastapi.security import OAuth2PasswordRequestForm
-from src.dependencies.auth import *
+from src.security.auth import *
from src.services.users.users import *
from datetime import timedelta
from fastapi.responses import JSONResponse
diff --git a/src/routers/blocks.py b/src/routers/blocks.py
index 6cf26d63..6d90bb32 100644
--- a/src/routers/blocks.py
+++ b/src/routers/blocks.py
@@ -1,5 +1,5 @@
from fastapi import APIRouter, Depends, UploadFile, Form, Request
-from src.dependencies.auth import get_current_user
+from src.security.auth import get_current_user
from fastapi import HTTPException, status, UploadFile
from src.services.blocks.block_types.imageBlock.images import create_image_block, get_image_block
from src.services.blocks.block_types.videoBlock.videoBlock import create_video_block, get_video_block
diff --git a/src/routers/courses/activities.py b/src/routers/courses/activities.py
index f77e1225..c0cc5f8b 100644
--- a/src/routers/courses/activities.py
+++ b/src/routers/courses/activities.py
@@ -1,6 +1,6 @@
from fastapi import APIRouter, Depends, UploadFile, Form, Request
from src.services.courses.activities.activities import *
-from src.dependencies.auth import get_current_user
+from src.security.auth import get_current_user
from src.services.courses.activities.video import create_video_activity
router = APIRouter()
diff --git a/src/routers/courses/chapters.py b/src/routers/courses/chapters.py
index 57d42fec..aa024cae 100644
--- a/src/routers/courses/chapters.py
+++ b/src/routers/courses/chapters.py
@@ -2,7 +2,7 @@ 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.users import PublicUser
-from src.dependencies.auth import get_current_user
+from src.security.auth import get_current_user
router = APIRouter()
diff --git a/src/routers/courses/collections.py b/src/routers/courses/collections.py
index 26293524..fe5f8f17 100644
--- a/src/routers/courses/collections.py
+++ b/src/routers/courses/collections.py
@@ -1,5 +1,5 @@
from fastapi import APIRouter, Depends, Request
-from src.dependencies.auth import get_current_user
+from src.security.auth import get_current_user
from src.services.users.users import PublicUser, User
from src.services.courses.collections import Collection, create_collection, get_collection, get_collections, update_collection, delete_collection
diff --git a/src/routers/courses/courses.py b/src/routers/courses/courses.py
index f440d8e5..6bd0d20e 100644
--- a/src/routers/courses/courses.py
+++ b/src/routers/courses/courses.py
@@ -1,5 +1,5 @@
from fastapi import APIRouter, Depends, UploadFile, Form, Request
-from src.dependencies.auth import get_current_user
+from src.security.auth import get_current_user
from src.services.courses.courses import Course, create_course, get_course, get_course_meta, get_courses, get_courses_orgslug, update_course, delete_course, update_course_thumbnail
from src.services.users.users import PublicUser
diff --git a/src/routers/houses.py b/src/routers/houses.py
deleted file mode 100644
index 8827402f..00000000
--- a/src/routers/houses.py
+++ /dev/null
@@ -1,49 +0,0 @@
-from fastapi import APIRouter, Depends, Request
-from src.dependencies.auth import get_current_user
-
-from src.services.houses import House, HouseInDB, create_house, get_house, get_houses, update_house, delete_house
-from src.services.users.users import PublicUser, User
-
-
-router = APIRouter()
-
-
-@router.post("/")
-async def api_create_house(request: Request,house_object: House, current_user: PublicUser = Depends(get_current_user)):
- """
- Create new house
- """
- return await create_house(request, house_object, current_user)
-
-
-@router.get("/{house_id}")
-async def api_get_house(request: Request,house_id: str, current_user: PublicUser = Depends(get_current_user)):
- """
- Get single House by house_id
- """
- return await get_house(request, house_id, current_user=current_user)
-
-
-@router.get("/page/{page}/limit/{limit}")
-async def api_get_house_by(request: Request,page: int, limit: int):
- """
- Get houses by page and limit
- """
- return await get_houses(request, page, limit)
-
-
-@router.put("/{house_id}")
-async def api_update_house(request: Request,house_object: House, house_id: str, current_user: PublicUser = Depends(get_current_user)):
- """
- Update House by house_id
- """
- return await update_house(request, house_object, house_id, current_user)
-
-
-@router.delete("/{house_id}")
-async def api_delete_house(request: Request,house_id: str, current_user: PublicUser = Depends(get_current_user)):
- """
- Delete House by ID
- """
-
- return await delete_house(request, house_id, current_user)
diff --git a/src/routers/orgs.py b/src/routers/orgs.py
index e571195e..579c0c02 100644
--- a/src/routers/orgs.py
+++ b/src/routers/orgs.py
@@ -1,6 +1,6 @@
from fastapi import APIRouter, Depends, Request
-from src.dependencies.auth import get_current_user
+from src.security.auth import get_current_user
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.users import PublicUser, User
diff --git a/src/routers/roles.py b/src/routers/roles.py
index 903125e3..e784324d 100644
--- a/src/routers/roles.py
+++ b/src/routers/roles.py
@@ -1,5 +1,5 @@
from fastapi import APIRouter, Depends, Request
-from src.dependencies.auth import get_current_user
+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, User
diff --git a/src/routers/users.py b/src/routers/users.py
index 8d79b7d0..3a17567c 100644
--- a/src/routers/users.py
+++ b/src/routers/users.py
@@ -1,7 +1,7 @@
from fastapi import Depends, FastAPI, APIRouter
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel
-from src.dependencies.auth import *
+from src.security.auth import *
from src.services.users.schemas.users import PasswordChangeForm, PublicUser, User, UserWithPassword
from src.services.users.users import create_user, delete_user, get_profile_metadata, get_user_by_userid, read_user, update_user, update_user_password
diff --git a/src/core/config/__init__.py b/src/security/__init__.py
similarity index 100%
rename from src/core/config/__init__.py
rename to src/security/__init__.py
diff --git a/src/dependencies/auth.py b/src/security/auth.py
similarity index 98%
rename from src/dependencies/auth.py
rename to src/security/auth.py
index 1e3f0c02..60a1487a 100644
--- a/src/dependencies/auth.py
+++ b/src/security/auth.py
@@ -6,7 +6,7 @@ from jose import JWTError, jwt
from datetime import datetime, timedelta
from src.services.users.users import *
from fastapi import Cookie, FastAPI
-from src.services.security import *
+from src.security.security import *
from fastapi_jwt_auth import AuthJWT
from fastapi_jwt_auth.exceptions import AuthJWTException
diff --git a/src/services/security.py b/src/security/security.py
similarity index 100%
rename from src/services/security.py
rename to src/security/security.py
diff --git a/src/services/courses/activities/activities.py b/src/services/courses/activities/activities.py
index b3205925..1adf49b6 100644
--- a/src/services/courses/activities/activities.py
+++ b/src/services/courses/activities/activities.py
@@ -1,5 +1,5 @@
from pydantic import BaseModel
-from src.services.security import verify_user_rights_with_roles
+from src.security.security import verify_user_rights_with_roles
from src.services.users.schemas.users import PublicUser, User
from fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks, UploadFile, File
from uuid import uuid4
diff --git a/src/services/courses/activities/video.py b/src/services/courses/activities/video.py
index d921585f..f4ac26aa 100644
--- a/src/services/courses/activities/video.py
+++ b/src/services/courses/activities/video.py
@@ -1,5 +1,5 @@
from pydantic import BaseModel
-from src.services.security import verify_user_rights_with_roles
+from src.security.security import verify_user_rights_with_roles
from src.services.courses.activities.uploads.videos import upload_video
from src.services.users.users import PublicUser
from src.services.courses.activities.activities import ActivityInDB
diff --git a/src/services/courses/chapters.py b/src/services/courses/chapters.py
index 460a0d97..a2a4e1c1 100644
--- a/src/services/courses/chapters.py
+++ b/src/services/courses/chapters.py
@@ -5,7 +5,7 @@ from uuid import uuid4
from pydantic import BaseModel
from src.services.courses.courses import Course, CourseInDB
from src.services.courses.activities.activities import Activity, ActivityInDB
-from src.services.security import verify_user_rights_with_roles
+from src.security.security import verify_user_rights_with_roles
from src.services.users.users import PublicUser
from fastapi import HTTPException, status, Request, Response, BackgroundTasks, UploadFile, File
diff --git a/src/services/courses/collections.py b/src/services/courses/collections.py
index 63eca6f7..387d910d 100644
--- a/src/services/courses/collections.py
+++ b/src/services/courses/collections.py
@@ -3,7 +3,7 @@ from typing import List
from uuid import uuid4
from pydantic import BaseModel
from src.services.users.users import PublicUser, User
-from src.services.security import *
+from src.security.security import *
from fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks
from datetime import datetime
diff --git a/src/services/courses/courses.py b/src/services/courses/courses.py
index a2560cd4..5e568904 100644
--- a/src/services/courses/courses.py
+++ b/src/services/courses/courses.py
@@ -5,7 +5,7 @@ from pydantic import BaseModel
from src.services.courses.activities.activities import ActivityInDB
from src.services.courses.thumbnails import upload_thumbnail
from src.services.users.users import PublicUser
-from src.services.security import *
+from src.security.security import *
from fastapi import HTTPException, status, UploadFile
from datetime import datetime
diff --git a/src/services/houses.py b/src/services/houses.py
deleted file mode 100644
index 5023302b..00000000
--- a/src/services/houses.py
+++ /dev/null
@@ -1,157 +0,0 @@
-import json
-from typing import List
-from uuid import uuid4
-from pydantic import BaseModel
-from src.services.users.users import PublicUser, User
-from src.services.security import *
-from fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks
-from datetime import datetime
-
-#### Classes ####################################################
-
-
-class House(BaseModel):
- name: str
- photo: str
- description: str
- email: str
- org: str
-
-
-class HouseInDB(House):
- house_id: str
- owners: List[str]
- admins: List[str]
-
-#### Classes ####################################################
-
-# TODO : Add house photo upload and delete
-
-async def get_house(request: Request, house_id: str, current_user: PublicUser):
- houses = request.app.db["houses"]
-
- house = houses.find_one({"house_id": house_id})
-
- # verify house rights
- await verify_house_rights(request,house_id, current_user, "read")
-
- if not house:
- raise HTTPException(
- status_code=status.HTTP_409_CONFLICT, detail="House does not exist")
-
- house = House(**house)
- return house
-
-
-async def create_house(request: Request,house_object: House, current_user: PublicUser):
- houses = request.app.db["houses"]
-
- # find if house already exists using name
- isHouseAvailable = houses.find_one({"name": house_object.name})
-
- if isHouseAvailable:
- raise HTTPException(
- status_code=status.HTTP_409_CONFLICT, detail="House name already exists")
-
- # generate house_id with uuid4
- house_id = str(f"house_{uuid4()}")
-
- hasRoleRights = await verify_user_rights_with_roles(request, "create", current_user.user_id, house_id)
-
- if not hasRoleRights:
- raise HTTPException(
- status_code=status.HTTP_409_CONFLICT, detail="Roles : Insufficient rights to perform this action")
-
- house = HouseInDB(house_id=house_id, owners=[
- current_user.user_id], admins=[
- current_user.user_id], **house_object.dict())
-
- house_in_db = houses.insert_one(house.dict())
-
- if not house_in_db:
- raise HTTPException(
- status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database")
-
- return house.dict()
-
-
-async def update_house(request: Request,house_object: House, house_id: str, current_user: PublicUser):
-
- # verify house rights
- await verify_house_rights(request,house_id, current_user, "update")
-
- houses = request.app.db["houses"]
-
- house = houses.find_one({"house_id": house_id})
-
- if house:
- # get owner value from house object database
- owners = house["owners"]
- admins = house["admins"]
-
- updated_house = HouseInDB(
- house_id=house_id, owners=owners, admins=admins, **house_object.dict())
-
- houses.update_one({"house_id": house_id}, {"$set": updated_house.dict()})
-
- return HouseInDB(**updated_house.dict())
-
- else:
- raise HTTPException(
- status_code=status.HTTP_409_CONFLICT, detail="House does not exist")
-
-
-
-
-async def delete_house(request: Request,house_id: str, current_user: PublicUser):
-
- # verify house rights
- await verify_house_rights(request,house_id, current_user, "delete")
-
- houses = request.app.db["houses"]
-
- house = houses.find_one({"house_id": house_id})
-
- if not house:
- raise HTTPException(
- status_code=status.HTTP_409_CONFLICT, detail="House does not exist")
-
- isDeleted = houses.delete_one({"house_id": house_id})
-
- if isDeleted:
- return {"detail": "House deleted"}
- else:
- raise HTTPException(
- status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database")
-
-
-async def get_houses(request: Request,page: int = 1, limit: int = 10):
- houses = request.app.db["houses"]
- # TODO : Get only houses that user is admin/has roles of
- # get all houses from database
- all_houses = houses.find().sort("name", 1).skip(10 * (page - 1)).limit(limit)
-
- return [json.loads(json.dumps(house, default=str)) for house in await all_houses.to_list(length=limit)]
-
-
-#### Security ####################################################
-
-async def verify_house_rights(request: Request,house_id: str, current_user: PublicUser, action: str):
- houses = request.app.db["houses"]
-
- house = houses.find_one({"house_id": house_id})
-
- if not house:
- raise HTTPException(
- status_code=status.HTTP_409_CONFLICT, detail="House does not exist")
-
- hasRoleRights = await verify_user_rights_with_roles(request,action, current_user.user_id, house_id)
- isOwner = current_user.user_id in house["owners"]
-
- if not hasRoleRights and not isOwner:
- raise HTTPException(
- status_code=status.HTTP_403_FORBIDDEN, detail="Roles/Ownership : Insufficient rights to perform this action")
-
- return True
-
-#### Security ####################################################
diff --git a/src/services/orgs.py b/src/services/orgs.py
index d5576c98..28fd642a 100644
--- a/src/services/orgs.py
+++ b/src/services/orgs.py
@@ -4,7 +4,7 @@ from uuid import uuid4
from pydantic import BaseModel
from src.services.users.schemas.users import UserOrganization
from src.services.users.users import PublicUser, User
-from src.services.security import *
+from src.security.security import *
from fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks
from datetime import datetime
@@ -16,12 +16,11 @@ class Organization(BaseModel):
description: str
email: str
slug: str
+ default: bool
class OrganizationInDB(Organization):
org_id: str
- owners: List[str]
- admins: List[str]
class PublicOrganization(Organization):
@@ -75,15 +74,13 @@ async def create_org(request: Request, org_object: Organization, current_user: P
# generate org_id with uuid4
org_id = str(f"org_{uuid4()}")
- org = OrganizationInDB(org_id=org_id, owners=[
- current_user.user_id], admins=[
- current_user.user_id], **org_object.dict())
+ org = OrganizationInDB(org_id=org_id, **org_object.dict())
org_in_db = await orgs.insert_one(org.dict())
user_organization: UserOrganization = UserOrganization(
org_id=org_id, org_role="owner")
-
+
# add org to user
await user.update_one({"user_id": current_user.user_id}, {
"$addToSet": {"orgs": user_organization.dict()}})
@@ -113,7 +110,7 @@ async def update_org(request: Request, org_object: Organization, org_id: str, cu
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist")
updated_org = OrganizationInDB(
- org_id=org_id, owners=owners, admins=admins, **org_object.dict())
+ org_id=org_id, **org_object.dict())
await orgs.update_one({"org_id": org_id}, {"$set": updated_org.dict()})
@@ -149,10 +146,10 @@ async def get_orgs_by_user(request: Request, user_id: str, page: int = 1, limit:
orgs = request.app.db["organizations"]
user = request.app.db["users"]
- # get user orgs
+ # get user orgs
user_orgs = await user.find_one({"user_id": user_id})
- org_ids : list[UserOrganization] = []
+ org_ids: list[UserOrganization] = []
for org in user_orgs["orgs"]:
if org["org_role"] == "owner" or org["org_role"] == "editor" or org["org_role"] == "member":
diff --git a/src/services/roles/roles.py b/src/services/roles/roles.py
index cae2585c..3893ff54 100644
--- a/src/services/roles/roles.py
+++ b/src/services/roles/roles.py
@@ -4,8 +4,7 @@ from uuid import uuid4
from pydantic import BaseModel
from src.services.roles.schemas.roles import Role, RoleInDB
from src.services.users.schemas.users import PublicUser, User
-from src.services.security import *
-from src.services.houses import House
+from src.security.security import *
from fastapi import HTTPException, status, Request
from datetime import datetime
diff --git a/src/services/users/users.py b/src/services/users/users.py
index 7a4fdc6d..e64adcc4 100644
--- a/src/services/users/users.py
+++ b/src/services/users/users.py
@@ -3,7 +3,7 @@ from typing import Literal
from uuid import uuid4
from fastapi import HTTPException, Request, status
from src.services.roles.schemas.roles import Role
-from src.services.security import security_hash_password, security_verify_password
+from src.security.security import security_hash_password, security_verify_password
from src.services.users.schemas.users import PasswordChangeForm, PublicUser, User, UserOrganization, UserWithPassword, UserInDB