Merge pull request #64 from learnhouse/feat/init-self-hosted

feat: use config files and init self hosting options
This commit is contained in:
Badr B 2023-03-26 14:21:35 +02:00 committed by GitHub
commit ecf4e4d6d9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 251 additions and 288 deletions

23
app.py
View file

@ -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):

View file

@ -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

22
config/config.yaml Normal file
View file

@ -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

View file

View file

@ -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";
@ -44,8 +44,8 @@ const CoursesIndexPage = (params: any) => {
<button style={{ backgroundColor: "red", border: "none" }} onClick={() => deleteCourses(course.course_id)}>
Delete <Trash size={10}></Trash>
</button>
<Link href={getUriWithOrg(orgslug,"") + "/course/" + removeCoursePrefix(course.course_id)}>
<Link href={getUriWithOrg(orgslug,"") + "/course/" + removeCoursePrefix(course.course_id) + "/edit"}>
<Link href={getUriWithOrg(orgslug, "/course/" + removeCoursePrefix(course.course_id))}>
<Link href={getUriWithOrg(orgslug, "/course/" + removeCoursePrefix(course.course_id) + "/edit")}>
<button>
Edit <Edit2 size={10}></Edit2>
</button>

View file

@ -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 } });
}

View file

@ -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;
}
};

View file

@ -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"]
}

View file

@ -9,3 +9,4 @@ passlib
fastapi-jwt-auth
faker
requests
pyyaml

View file

@ -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()

View file

@ -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

24
src/core/events/logs.py Normal file
View file

@ -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")

View file

@ -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"])

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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()

View file

@ -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()

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 ####################################################

View file

@ -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,9 +74,7 @@ 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())
@ -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()})
@ -152,7 +149,7 @@ async def get_orgs_by_user(request: Request, user_id: str, page: int = 1, limit:
# 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":

View file

@ -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

View file

@ -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