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 import logging
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
import re 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.core.events.events import shutdown_app, startup_app
from src.main import global_router from src.main import global_router
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
@ -16,10 +17,13 @@ from fastapi_jwt_auth.exceptions import AuthJWTException
# (c) LearnHouse 2022 # (c) LearnHouse 2022
######################## ########################
# Get LearnHouse Config
learnhouse_config: LearnHouseConfig = get_learnhouse_config()
# Global Config # Global Config
app = FastAPI( app = FastAPI(
title="LearnHouse", title=learnhouse_config.site_name,
description="LearnHouse is a new open-source platform tailored for learning experiences.", description=learnhouse_config.site_description,
version="0.1.0", version="0.1.0",
root_path="/" root_path="/"
) )
@ -45,7 +49,7 @@ app.add_event_handler("shutdown", shutdown_app(app))
# JWT Exception Handler # JWT Exception Handler
@app.exception_handler(AuthJWTException) @ app.exception_handler(AuthJWTException)
def authjwt_exception_handler(request: Request, exc: AuthJWTException): def authjwt_exception_handler(request: Request, exc: AuthJWTException):
return JSONResponse( return JSONResponse(
status_code=exc.status_code, # type: ignore status_code=exc.status_code, # type: ignore
@ -59,10 +63,19 @@ app.include_router(global_router)
# General Routes # General Routes
@app.get("/") @ app.get("/")
async def root(): async def root():
return {"Message": "Welcome to LearnHouse ✨"} 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") # @app.get("/initial_data")
# async def initial_data(request: Request): # 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 React from "react";
import styled from "styled-components"; import styled from "styled-components";
import { Title } from "@components/UI/Elements/Styles/Title"; 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 { deleteCourseFromBackend } from "@services/courses/courses";
import useSWR, { mutate } from "swr"; import useSWR, { mutate } from "swr";
import { swrFetcher } from "@services/utils/requests"; 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)}> <button style={{ backgroundColor: "red", border: "none" }} onClick={() => deleteCourses(course.course_id)}>
Delete <Trash size={10}></Trash> Delete <Trash size={10}></Trash>
</button> </button>
<Link href={getUriWithOrg(orgslug,"") + "/course/" + removeCoursePrefix(course.course_id)}> <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) + "/edit")}>
<button> <button>
Edit <Edit2 size={10}></Edit2> Edit <Edit2 size={10}></Edit2>
</button> </button>

View file

@ -1,3 +1,4 @@
import { getDefaultOrg, getSelfHostedOption } from "@services/config/config";
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
export const config = { export const config = {
@ -16,41 +17,34 @@ export const config = {
export default function middleware(req: NextRequest) { export default function middleware(req: NextRequest) {
const url = req.nextUrl; const url = req.nextUrl;
const isSelfHosted = getSelfHostedOption();
// Get hostname of request (e.g. demo.vercel.pub, demo.localhost:3000)
const hostname = req.headers.get("host") || "learnhouse.app"; 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. if (!isSelfHosted && currentHost === "localhost:3000" && !url.pathname.startsWith("/organizations")) {
You can also use wildcard subdomains on .vercel.app links that are associated with your Vercel team slug // Redirect to error page if not self-hosted and on localhost
in this case, our team slug is "platformize", thus *.platformize.vercel.app works. Do note that you'll const errorUrl = "/error";
still need to add "*.platformize.vercel.app" as a wildcard domain on your Vercel dashboard. */ return NextResponse.redirect(errorUrl, { status: 302 });
let currentHost = }
process.env.NODE_ENV === "production" && process.env.VERCEL === "1"
? hostname.replace(`.vercel.pub`, "").replace(`.platformize.vercel.app`, "")
: hostname.replace(`.localhost:3000`, "");
/* Editor route */
if (url.pathname.match(/^\/course\/[^/]+\/activity\/[^/]+\/edit$/)) { if (url.pathname.match(/^\/course\/[^/]+\/activity\/[^/]+\/edit$/)) {
url.pathname = `/_editor${url.pathname}`; url.pathname = `/_editor${url.pathname}`;
console.log("editor route", url.pathname);
return NextResponse.rewrite(url, { headers: { orgslug: currentHost } }); return NextResponse.rewrite(url, { headers: { orgslug: currentHost } });
} }
/* Organizations route */
if (url.pathname.startsWith("/organizations")) { if (url.pathname.startsWith("/organizations")) {
url.pathname = url.pathname.replace("/organizations", `/organizations${currentHost}`); if (!isSelfHosted) {
// remove localhost:3000 from url currentHost = "";
url.pathname = url.pathname.replace(`localhost:3000`, ""); }
url.pathname = url.pathname.replace("/organizations", `/organizations${currentHost}`).replace("localhost:3000", "");
return NextResponse.rewrite(url); 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}`; url.pathname = `/_orgs/${currentHost}${url.pathname}`;
return NextResponse.rewrite(url, { headers: { orgslug: currentHost } });
return NextResponse.rewrite(url, { headers: { olgslug: currentHost } });
} }

View file

@ -2,17 +2,33 @@ const LEARNHOUSE_API_URL = "http://localhost:1338/api/";
const LEARNHOUSE_BACKEND_URL = "http://localhost:1338/"; const LEARNHOUSE_BACKEND_URL = "http://localhost:1338/";
export const getAPIUrl = () => LEARNHOUSE_API_URL; export const getAPIUrl = () => LEARNHOUSE_API_URL;
export const getBackendUrl = () => LEARNHOUSE_BACKEND_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) => { export const getUriWithOrg = (orgslug: string, path: string) => {
const selfHosted = getSelfHostedOption();
if (selfHosted) {
return `http://localhost:3000${path}`;
}
return `http://${orgslug}.localhost:3000${path}`; return `http://${orgslug}.localhost:3000${path}`;
}; };
export const getOrgFromUri = () => { export const getOrgFromUri = () => {
const selfHosted = getSelfHostedOption();
if (selfHosted) {
getDefaultOrg();
} else {
if (typeof window !== "undefined") {
const hostname = window.location.hostname; const hostname = window.location.hostname;
// get the orgslug from the hostname return hostname.replace(".localhost:3000", "");
const orgslug = hostname.split(".")[0]; }
return orgslug; }
};
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/*"] "@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"] "exclude": ["node_modules"]
} }

View file

@ -9,3 +9,4 @@ passlib
fastapi-jwt-auth fastapi-jwt-auth
faker faker
requests 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 typing import Callable
from fastapi import FastAPI from fastapi import FastAPI
from src.core.events.database import close_database, connect_to_db 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: def startup_app(app: FastAPI) -> Callable:
@ -8,6 +9,9 @@ def startup_app(app: FastAPI) -> Callable:
# Connect to database # Connect to database
await connect_to_db(app) await connect_to_db(app)
# Create logs directory
await create_logs_dir()
return start_app 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 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 from src.routers.courses import chapters, collections, courses,activities
@ -9,7 +9,6 @@ global_router = APIRouter(prefix="/api")
# API Routes # API Routes
global_router.include_router(users.router, prefix="/users", tags=["users"]) global_router.include_router(users.router, prefix="/users", tags=["users"])
global_router.include_router(auth.router, prefix="/auth", tags=["auth"]) 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(orgs.router, prefix="/orgs", tags=["orgs"])
global_router.include_router(roles.router, prefix="/roles", tags=["roles"]) global_router.include_router(roles.router, prefix="/roles", tags=["roles"])
global_router.include_router(blocks.router, prefix="/blocks", tags=["blocks"]) global_router.include_router(blocks.router, prefix="/blocks", tags=["blocks"])

View file

@ -1,5 +1,5 @@
from fastapi import APIRouter, Depends, Request 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 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 urllib.request import Request
from fastapi import Depends, APIRouter, HTTPException, status, Request from fastapi import Depends, APIRouter, HTTPException, status, Request
from fastapi.security import OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordRequestForm
from src.dependencies.auth import * from src.security.auth import *
from src.services.users.users import * from src.services.users.users import *
from datetime import timedelta from datetime import timedelta
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse

View file

@ -1,5 +1,5 @@
from fastapi import APIRouter, Depends, UploadFile, Form, Request 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 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.imageBlock.images import create_image_block, get_image_block
from src.services.blocks.block_types.videoBlock.videoBlock import create_video_block, get_video_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 fastapi import APIRouter, Depends, UploadFile, Form, Request
from src.services.courses.activities.activities import * 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 from src.services.courses.activities.video import create_video_activity
router = APIRouter() 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.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.services.users.users import PublicUser
from src.dependencies.auth import get_current_user from src.security.auth import get_current_user
router = APIRouter() router = APIRouter()

View file

@ -1,5 +1,5 @@
from fastapi import APIRouter, Depends, Request 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.users.users import PublicUser, User
from src.services.courses.collections import Collection, create_collection, get_collection, get_collections, update_collection, delete_collection 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 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.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 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 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.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 from src.services.users.users import PublicUser, User

View file

@ -1,5 +1,5 @@
from fastapi import APIRouter, Depends, Request 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.schemas.roles import Role
from src.services.roles.roles import create_role, delete_role, read_role, update_role from src.services.roles.roles import create_role, delete_role, read_role, update_role
from src.services.users.schemas.users import PublicUser, User from src.services.users.schemas.users import PublicUser, User

View file

@ -1,7 +1,7 @@
from fastapi import Depends, FastAPI, APIRouter from fastapi import Depends, FastAPI, APIRouter
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel 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.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 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 datetime import datetime, timedelta
from src.services.users.users import * from src.services.users.users import *
from fastapi import Cookie, FastAPI 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 import AuthJWT
from fastapi_jwt_auth.exceptions import AuthJWTException from fastapi_jwt_auth.exceptions import AuthJWTException

View file

@ -1,5 +1,5 @@
from pydantic import BaseModel 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 src.services.users.schemas.users import PublicUser, User
from fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks, UploadFile, File from fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks, UploadFile, File
from uuid import uuid4 from uuid import uuid4

View file

@ -1,5 +1,5 @@
from pydantic import BaseModel 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.courses.activities.uploads.videos import upload_video
from src.services.users.users import PublicUser from src.services.users.users import PublicUser
from src.services.courses.activities.activities import ActivityInDB from src.services.courses.activities.activities import ActivityInDB

View file

@ -5,7 +5,7 @@ from uuid import uuid4
from pydantic import BaseModel from pydantic import BaseModel
from src.services.courses.courses import Course, CourseInDB from src.services.courses.courses import Course, CourseInDB
from src.services.courses.activities.activities import Activity, ActivityInDB 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 src.services.users.users import PublicUser
from fastapi import HTTPException, status, Request, Response, BackgroundTasks, UploadFile, File 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 uuid import uuid4
from pydantic import BaseModel from pydantic import BaseModel
from src.services.users.users import PublicUser, User 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 fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks
from datetime import datetime 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.activities.activities import ActivityInDB
from src.services.courses.thumbnails import upload_thumbnail from src.services.courses.thumbnails import upload_thumbnail
from src.services.users.users import PublicUser from src.services.users.users import PublicUser
from src.services.security import * from src.security.security import *
from fastapi import HTTPException, status, UploadFile from fastapi import HTTPException, status, UploadFile
from datetime import datetime 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 pydantic import BaseModel
from src.services.users.schemas.users import UserOrganization from src.services.users.schemas.users import UserOrganization
from src.services.users.users import PublicUser, User 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 fastapi import FastAPI, HTTPException, status, Request, Response, BackgroundTasks
from datetime import datetime from datetime import datetime
@ -16,12 +16,11 @@ class Organization(BaseModel):
description: str description: str
email: str email: str
slug: str slug: str
default: bool
class OrganizationInDB(Organization): class OrganizationInDB(Organization):
org_id: str org_id: str
owners: List[str]
admins: List[str]
class PublicOrganization(Organization): class PublicOrganization(Organization):
@ -75,9 +74,7 @@ async def create_org(request: Request, org_object: Organization, current_user: P
# generate org_id with uuid4 # generate org_id with uuid4
org_id = str(f"org_{uuid4()}") org_id = str(f"org_{uuid4()}")
org = OrganizationInDB(org_id=org_id, owners=[ org = OrganizationInDB(org_id=org_id, **org_object.dict())
current_user.user_id], admins=[
current_user.user_id], **org_object.dict())
org_in_db = await orgs.insert_one(org.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") status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist")
updated_org = OrganizationInDB( 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()}) 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 # get user orgs
user_orgs = await user.find_one({"user_id": user_id}) user_orgs = await user.find_one({"user_id": user_id})
org_ids : list[UserOrganization] = [] org_ids: list[UserOrganization] = []
for org in user_orgs["orgs"]: for org in user_orgs["orgs"]:
if org["org_role"] == "owner" or org["org_role"] == "editor" or org["org_role"] == "member": 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 pydantic import BaseModel
from src.services.roles.schemas.roles import Role, RoleInDB from src.services.roles.schemas.roles import Role, RoleInDB
from src.services.users.schemas.users import PublicUser, User from src.services.users.schemas.users import PublicUser, User
from src.services.security import * from src.security.security import *
from src.services.houses import House
from fastapi import HTTPException, status, Request from fastapi import HTTPException, status, Request
from datetime import datetime from datetime import datetime

View file

@ -3,7 +3,7 @@ from typing import Literal
from uuid import uuid4 from uuid import uuid4
from fastapi import HTTPException, Request, status from fastapi import HTTPException, Request, status
from src.services.roles.schemas.roles import Role 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 from src.services.users.schemas.users import PasswordChangeForm, PublicUser, User, UserOrganization, UserWithPassword, UserInDB