diff --git a/app.py b/app.py index a09626a9..c4f356a2 100644 --- a/app.py +++ b/app.py @@ -8,7 +8,7 @@ from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from fastapi.staticfiles import StaticFiles from fastapi_jwt_auth.exceptions import AuthJWTException -from src.services.mocks.initial import create_initial_data +# from src.services.mocks.initial import create_initial_data ######################## # Pre-Alpha Version 0.1.0 @@ -64,8 +64,8 @@ async def root(): return {"Message": "Welcome to LearnHouse ✨"} -@app.get("/initial_data") -async def initial_data(request: Request): +# @app.get("/initial_data") +# async def initial_data(request: Request): - await create_initial_data(request) - return {"Message": "Initial data created 🤖"} +# await create_initial_data(request) +# return {"Message": "Initial data created 🤖"} diff --git a/front/app/_orgs/[orgslug]/(withmenu)/courses/new/page.tsx b/front/app/_orgs/[orgslug]/(withmenu)/courses/new/page.tsx index a05ad43c..a9f755f9 100644 --- a/front/app/_orgs/[orgslug]/(withmenu)/courses/new/page.tsx +++ b/front/app/_orgs/[orgslug]/(withmenu)/courses/new/page.tsx @@ -1,11 +1,11 @@ "use client"; import { useRouter } from "next/navigation"; import React from "react"; -import { Header } from "../../../../../components/UI/Header"; -import Layout from "../../../../../components/UI/Layout"; -import { Title } from "../../../../../components/UI/Elements/Styles/Title"; -import { createNewCourse } from "../../../../../services/courses/courses"; -import { getOrganizationContextInfo } from "../../../../../services/orgs"; +import { Header } from "@components/UI/Header"; +import Layout from "@components/UI/Layout"; +import { Title } from "@components/UI/Elements/Styles/Title"; +import { createNewCourse } from "@services/courses/courses"; +import { getOrganizationContextInfo } from "@services/orgs"; const NewCoursePage = (params: any) => { const router = useRouter(); diff --git a/src/dependencies/auth.py b/src/dependencies/auth.py index 92777f4f..1e3f0c02 100644 --- a/src/dependencies/auth.py +++ b/src/dependencies/auth.py @@ -4,7 +4,7 @@ from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from passlib.context import CryptContext from jose import JWTError, jwt from datetime import datetime, timedelta -from src.services.users import * +from src.services.users.users import * from fastapi import Cookie, FastAPI from src.services.security import * from fastapi_jwt_auth import AuthJWT diff --git a/src/routers/auth.py b/src/routers/auth.py index d08ef3ff..9c8bf905 100644 --- a/src/routers/auth.py +++ b/src/routers/auth.py @@ -2,7 +2,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.services.users 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 b9b68707..9103e587 100644 --- a/src/routers/blocks.py +++ b/src/routers/blocks.py @@ -5,7 +5,7 @@ from src.services.blocks.imageBlock.images import create_image_file, get_image_f from src.services.blocks.videoBlock.videos import create_video_file, get_video_file from src.services.blocks.pdfBlock.documents import create_document_file, get_document_file from src.services.blocks.quizBlock.quizBlock import create_quiz_block, get_quiz_block_answers, get_quiz_block_options, quizBlock -from src.services.users import PublicUser +from src.services.users.users import PublicUser router = APIRouter() diff --git a/src/routers/courses/chapters.py b/src/routers/courses/chapters.py index a00b7b83..57d42fec 100644 --- a/src/routers/courses/chapters.py +++ b/src/routers/courses/chapters.py @@ -1,7 +1,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 import PublicUser +from src.services.users.users import PublicUser from src.dependencies.auth import get_current_user router = APIRouter() diff --git a/src/routers/courses/collections.py b/src/routers/courses/collections.py index 284b7cad..26293524 100644 --- a/src/routers/courses/collections.py +++ b/src/routers/courses/collections.py @@ -1,6 +1,6 @@ from fastapi import APIRouter, Depends, Request from src.dependencies.auth import get_current_user -from src.services.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 diff --git a/src/routers/courses/courses.py b/src/routers/courses/courses.py index 340cb735..b0b3d8d9 100644 --- a/src/routers/courses/courses.py +++ b/src/routers/courses/courses.py @@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends, UploadFile, Form, Request from src.dependencies.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 import PublicUser +from src.services.users.users import PublicUser router = APIRouter() diff --git a/src/routers/houses.py b/src/routers/houses.py index 164d5417..8827402f 100644 --- a/src/routers/houses.py +++ b/src/routers/houses.py @@ -2,7 +2,7 @@ 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 import PublicUser, User +from src.services.users.users import PublicUser, User router = APIRouter() diff --git a/src/routers/orgs.py b/src/routers/orgs.py index 465a953b..e571195e 100644 --- a/src/routers/orgs.py +++ b/src/routers/orgs.py @@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends, Request from src.dependencies.auth import get_current_user from src.services.orgs import Organization, create_org, delete_org, get_organization, get_organization_by_slug, get_orgs_by_user, update_org -from src.services.users import PublicUser, User +from src.services.users.users import PublicUser, User router = APIRouter() diff --git a/src/routers/roles.py b/src/routers/roles.py index 5efebe00..903125e3 100644 --- a/src/routers/roles.py +++ b/src/routers/roles.py @@ -1,14 +1,15 @@ from fastapi import APIRouter, Depends, Request from src.dependencies.auth import get_current_user -from src.services.roles import Role, create_role, delete_role, get_role, get_roles, update_role -from src.services.users import PublicUser, 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 router = APIRouter() @router.post("/") -async def api_create_role(request: Request,role_object: Role, current_user: PublicUser = Depends(get_current_user)): +async def api_create_role(request: Request, role_object: Role, current_user: PublicUser = Depends(get_current_user)): """ Create new role """ @@ -16,31 +17,23 @@ async def api_create_role(request: Request,role_object: Role, current_user: Publ @router.get("/{role_id}") -async def api_get_role(request: Request,role_id: str): +async def api_get_role(request: Request, role_id: str, current_user: PublicUser = Depends(get_current_user)): """ Get single role by role_id """ - return await get_role(request, role_id) - - -@router.get("/page/{page}/limit/{limit}") -async def api_get_role_by(request: Request,page: int, limit: int): - """ - Get roles by page and limit - """ - return await get_roles(request, page, limit) + return await read_role(request, role_id, current_user) @router.put("/{role_id}") -async def api_update_role(request: Request,role_object: Role, role_id: str, current_user: PublicUser = Depends(get_current_user)): +async def api_update_role(request: Request, role_object: Role, role_id: str, current_user: PublicUser = Depends(get_current_user)): """ Update role by role_id """ - return await update_role(request, role_object, role_id, current_user) + return await update_role(request, role_id, role_object, current_user) @router.delete("/{role_id}") -async def api_delete_role(request: Request,role_id: str, current_user: PublicUser = Depends(get_current_user)): +async def api_delete_role(request: Request, role_id: str, current_user: PublicUser = Depends(get_current_user)): """ Delete role by ID """ diff --git a/src/routers/users.py b/src/routers/users.py index 6a600bd5..8d79b7d0 100644 --- a/src/routers/users.py +++ b/src/routers/users.py @@ -2,7 +2,9 @@ from fastapi import Depends, FastAPI, APIRouter from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from pydantic import BaseModel from src.dependencies.auth import * -from src.services.users 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 + @@ -25,13 +27,6 @@ async def api_get_current_user_metadata(request: Request,current_user: User = De return await get_profile_metadata(request , current_user.dict()) -@router.get("/username/{username}") -async def api_get_user_by_username(request: Request, username: str): - """ - Get single user by username - """ - return await get_user(request, username) - @router.get("/user_id/{user_id}") async def api_get_user_by_userid(request: Request,user_id: str): @@ -42,32 +37,32 @@ async def api_get_user_by_userid(request: Request,user_id: str): @router.post("/") -async def api_create_user(request: Request,user_object: UserWithPassword): +async def api_create_user(request: Request,user_object: UserWithPassword, org_id: str ): """ Create new user """ - return await create_user(request, user_object) + return await create_user(request, None, user_object, org_id) @router.delete("/user_id/{user_id}") -async def api_delete_user(request: Request, user_id: str): +async def api_delete_user(request: Request, user_id: str, current_user: PublicUser = Depends(get_current_user)): """ Delete user by ID """ - return await delete_user(request, user_id) + return await delete_user(request, current_user, user_id) @router.put("/user_id/{user_id}") -async def api_update_user(request: Request, user_object: User, user_id: str): +async def api_update_user(request: Request, user_object: User, user_id: str, current_user: PublicUser = Depends(get_current_user)): """ Update user by ID """ - return await update_user(request, user_id, user_object) + return await update_user(request, user_id, user_object, current_user) @router.put("/password/user_id/{user_id}") -async def api_update_user_password(request: Request, user_id: str , passwordChangeForm : PasswordChangeForm): +async def api_update_user_password(request: Request, user_id: str , passwordChangeForm : PasswordChangeForm, current_user: PublicUser = Depends(get_current_user)): """ Update user password by ID """ - return await update_user_password(request, user_id, passwordChangeForm) + return await update_user_password(request,current_user, user_id, passwordChangeForm) diff --git a/src/services/activity.py b/src/services/activity.py index ae2ece70..88d85d17 100644 --- a/src/services/activity.py +++ b/src/services/activity.py @@ -7,7 +7,7 @@ from fastapi import HTTPException, Request, status from pydantic import BaseModel from src.services.courses.chapters import get_coursechapters_meta -from src.services.users import PublicUser +from src.services.users.users import PublicUser #### Classes #################################################### diff --git a/src/services/blocks/imageBlock/images.py b/src/services/blocks/imageBlock/images.py index be8104b9..0ceba3bf 100644 --- a/src/services/blocks/imageBlock/images.py +++ b/src/services/blocks/imageBlock/images.py @@ -4,7 +4,7 @@ from fastapi import HTTPException, status, UploadFile, Request from fastapi.responses import StreamingResponse import os -from src.services.users import PublicUser +from src.services.users.users import PublicUser class PhotoFile(BaseModel): diff --git a/src/services/blocks/pdfBlock/documents.py b/src/services/blocks/pdfBlock/documents.py index 0f061ecd..eeb861ea 100644 --- a/src/services/blocks/pdfBlock/documents.py +++ b/src/services/blocks/pdfBlock/documents.py @@ -4,7 +4,7 @@ from fastapi import HTTPException, status, UploadFile, Request from fastapi.responses import StreamingResponse import os -from src.services.users import PublicUser +from src.services.users.users import PublicUser class DocumentFile(BaseModel): diff --git a/src/services/blocks/quizBlock/quizBlock.py b/src/services/blocks/quizBlock/quizBlock.py index d1f0cecb..1d5e43fa 100644 --- a/src/services/blocks/quizBlock/quizBlock.py +++ b/src/services/blocks/quizBlock/quizBlock.py @@ -3,7 +3,7 @@ from uuid import uuid4 from fastapi import Request from pydantic import BaseModel from src.services.blocks.blocks import Block -from src.services.users import PublicUser +from src.services.users.users import PublicUser class option(BaseModel): diff --git a/src/services/blocks/videoBlock/videos.py b/src/services/blocks/videoBlock/videos.py index b703013f..768d6adb 100644 --- a/src/services/blocks/videoBlock/videos.py +++ b/src/services/blocks/videoBlock/videos.py @@ -4,7 +4,7 @@ import os from fastapi import HTTPException, status, UploadFile,Request from fastapi.responses import StreamingResponse -from src.services.users import PublicUser +from src.services.users.users import PublicUser class VideoFile(BaseModel): diff --git a/src/services/courses/chapters.py b/src/services/courses/chapters.py index bda0aeff..21f31ea1 100644 --- a/src/services/courses/chapters.py +++ b/src/services/courses/chapters.py @@ -6,7 +6,7 @@ from pydantic import BaseModel from src.services.courses.courses import Course, CourseInDB from src.services.courses.lectures.lectures import Lecture, LectureInDB from src.services.security import verify_user_rights_with_roles -from src.services.users import PublicUser +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 3996c91b..63eca6f7 100644 --- a/src/services/courses/collections.py +++ b/src/services/courses/collections.py @@ -2,7 +2,7 @@ import json from typing import List from uuid import uuid4 from pydantic import BaseModel -from src.services.users import PublicUser, User +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 diff --git a/src/services/courses/courses.py b/src/services/courses/courses.py index 265dcf85..a2e84b4d 100644 --- a/src/services/courses/courses.py +++ b/src/services/courses/courses.py @@ -4,7 +4,7 @@ from uuid import uuid4 from pydantic import BaseModel from src.services.courses.lectures.lectures import LectureInDB from src.services.courses.thumbnails import upload_thumbnail -from src.services.users import PublicUser +from src.services.users.users import PublicUser from src.services.security import * from fastapi import HTTPException, status, UploadFile from datetime import datetime diff --git a/src/services/courses/lectures/lectures.py b/src/services/courses/lectures/lectures.py index 51d58fb4..eea0f72f 100644 --- a/src/services/courses/lectures/lectures.py +++ b/src/services/courses/lectures/lectures.py @@ -1,6 +1,6 @@ from pydantic import BaseModel from src.services.security import verify_user_rights_with_roles -from src.services.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 uuid import uuid4 from datetime import datetime diff --git a/src/services/courses/lectures/uploads/videos.py b/src/services/courses/lectures/uploads/videos.py index 78106531..8d503100 100644 --- a/src/services/courses/lectures/uploads/videos.py +++ b/src/services/courses/lectures/uploads/videos.py @@ -13,7 +13,6 @@ async def upload_video(video_file, lecture_id): f.close() except Exception as e: - print(e) return {"message": "There was an error uploading the file"} finally: video_file.file.close() diff --git a/src/services/courses/lectures/video.py b/src/services/courses/lectures/video.py index b2c0dd0b..66726291 100644 --- a/src/services/courses/lectures/video.py +++ b/src/services/courses/lectures/video.py @@ -1,7 +1,7 @@ from pydantic import BaseModel from src.services.security import verify_user_rights_with_roles from src.services.courses.lectures.uploads.videos import upload_video -from src.services.users import PublicUser +from src.services.users.users import PublicUser from src.services.courses.lectures.lectures import LectureInDB from fastapi import HTTPException, status, UploadFile, Request from uuid import uuid4 @@ -48,9 +48,7 @@ async def create_video_lecture(request: Request,name: str, coursechapter_id: st # upload video if video_file: - print("uploading video") # get videofile format - await upload_video(video_file, lecture_id) # todo : choose whether to update the chapter or not diff --git a/src/services/courses/thumbnails.py b/src/services/courses/thumbnails.py index 1306038d..cef0d41c 100644 --- a/src/services/courses/thumbnails.py +++ b/src/services/courses/thumbnails.py @@ -9,7 +9,6 @@ async def upload_thumbnail(thumbnail_file, name_in_disk): f.close() except Exception as e: - print(e) return {"message": "There was an error uploading the file"} finally: thumbnail_file.file.close() \ No newline at end of file diff --git a/src/services/houses.py b/src/services/houses.py index 9acd1b96..5023302b 100644 --- a/src/services/houses.py +++ b/src/services/houses.py @@ -2,7 +2,7 @@ import json from typing import List from uuid import uuid4 from pydantic import BaseModel -from src.services.users import PublicUser, User +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 diff --git a/src/services/mocks/initial.py b/src/services/mocks/initial.py index dfbed6da..6785f344 100644 --- a/src/services/mocks/initial.py +++ b/src/services/mocks/initial.py @@ -1,186 +1,186 @@ -import requests -from datetime import datetime -from fileinput import filename -from pprint import pprint -from uuid import uuid4 -from fastapi import File, UploadFile, Request -from src.services.courses.chapters import CourseChapter, create_coursechapter -from src.services.courses.lectures.lectures import Lecture, create_lecture -from src.services.courses.thumbnails import upload_thumbnail -from src.services.users import PublicUser, User, UserInDB, UserWithPassword +# import requests +# from datetime import datetime +# from fileinput import filename +# from pprint import pprint +# from uuid import uuid4 +# from fastapi import File, UploadFile, Request +# from src.services.courses.chapters import CourseChapter, create_coursechapter +# from src.services.courses.lectures.lectures import Lecture, create_lecture +# from src.services.courses.thumbnails import upload_thumbnail +# from src.services.users.users import PublicUser, User, UserInDB, UserWithPassword -from src.services.orgs import OrganizationInDB, Organization, create_org -from src.services.roles import Permission, Elements, RolePolicy, create_role -from src.services.users import create_user -from src.services.courses.courses import Course, CourseInDB, create_course -from src.services.roles import Role -from faker import Faker +# from src.services.orgs import OrganizationInDB, Organization, create_org +# from src.services.roles.schemas.roles import Permission, Elements, RolePolicy, create_role +# from src.services.users.users import create_user +# from src.services.courses.courses import Course, CourseInDB, create_course +# from src.services.roles.roles import Role +# from faker import Faker -async def create_initial_data(request: Request): - fake = Faker(['en_US']) - fake_multilang = Faker( - ['en_US', 'de_DE', 'ja_JP', 'es_ES', 'it_IT', 'pt_BR', 'ar_PS']) +# async def create_initial_data(request: Request): +# fake = Faker(['en_US']) +# fake_multilang = Faker( +# ['en_US', 'de_DE', 'ja_JP', 'es_ES', 'it_IT', 'pt_BR', 'ar_PS']) - # Create users - ######################################## +# # Create users +# ######################################## - database_users = request.app.db["users"] - await database_users.delete_many({}) +# database_users = request.app.db["users"] +# await database_users.delete_many({}) - users = [] - admin_user = UserWithPassword( - username=f"admin", - email=f"admin@admin.admin", - password="admin", - user_type="isOwner", - ) +# users = [] +# admin_user = UserWithPassword( +# username=f"admin", +# email=f"admin@admin.admin", +# password="admin", +# user_type="isOwner", +# ) - admin_user = await create_user(request, admin_user) +# admin_user = await create_user(request, admin_user) - for i in range(0, 20): - user = UserWithPassword( - username=fake.simple_profile()['username'], - email=fake.email(), - password=fake.password(), - user_type="isOwner", - full_name=fake.name(), - ) - users.append(user) +# for i in range(0, 20): +# user = UserWithPassword( +# username=fake.simple_profile()['username'], +# email=fake.email(), +# password=fake.password(), +# user_type="isOwner", +# full_name=fake.name(), +# ) +# users.append(user) - for user in users: - await create_user(request, user) +# for user in users: +# await create_user(request, user) - # find admin user - users = request.app.db["users"] - admin_user = await users.find_one({"username": "admin"}) +# # find admin user +# users = request.app.db["users"] +# admin_user = await users.find_one({"username": "admin"}) - if admin_user: - admin_user = UserInDB(**admin_user) - current_user = PublicUser(**admin_user.dict()) - else: - raise Exception("Admin user not found") +# if admin_user: +# admin_user = UserInDB(**admin_user) +# current_user = PublicUser(**admin_user.dict()) +# else: +# raise Exception("Admin user not found") - # Create organizations - ######################################## +# # Create organizations +# ######################################## - database_orgs = request.app.db["organizations"] - await database_orgs.delete_many({}) +# database_orgs = request.app.db["organizations"] +# await database_orgs.delete_many({}) - organizations = [] - for i in range(0, 5): - company = fake.company() - # remove whitespace and special characters and make lowercase - slug = ''.join(e for e in company if e.isalnum()).lower() - org = Organization( - name=company, - description=fake.unique.text(), - email=fake.unique.email(), - slug=slug, - ) - organizations.append(org) - await create_org(request, org, current_user) +# organizations = [] +# for i in range(0, 5): +# company = fake.company() +# # remove whitespace and special characters and make lowercase +# slug = ''.join(e for e in company if e.isalnum()).lower() +# org = Organization( +# name=company, +# description=fake.unique.text(), +# email=fake.unique.email(), +# slug=slug, +# ) +# organizations.append(org) +# await create_org(request, org, current_user) - # Create roles - ######################################## +# # Create roles +# ######################################## - database_roles = request.app.db["roles"] - await database_roles.delete_many({}) +# database_roles = request.app.db["roles"] +# await database_roles.delete_many({}) - roles = [] - admin_role = Role( - name="admin", - description="admin", - policies=[RolePolicy(permissions=Permission( - action_create=True, - action_read=True, - action_update=True, - action_delete=True, - ), - elements=Elements( - courses=["*"], - users=["*"], - houses=["*"], - collections=["*"], - organizations=["*"], - coursechapters=["*"], - lectures=["*"], - ))], - linked_users=[admin_user.user_id], - ) - roles.append(admin_role) +# roles = [] +# admin_role = Role( +# name="admin", +# description="admin", +# policies=[RolePolicy(permissions=Permission( +# action_create=True, +# action_read=True, +# action_update=True, +# action_delete=True, +# ), +# elements=Elements( +# courses=["*"], +# users=["*"], +# houses=["*"], +# collections=["*"], +# organizations=["*"], +# coursechapters=["*"], +# lectures=["*"], +# ))], +# linked_users=[admin_user.user_id], +# ) +# roles.append(admin_role) - await create_role(request, admin_role, current_user) +# await create_role(request, admin_role, current_user) - # Generate Courses and CourseChapters - ######################################## +# # Generate Courses and CourseChapters +# ######################################## - database_courses = request.app.db["courses"] - database_chapters = request.app.db["coursechapters"] - await database_courses.delete_many({}) - await database_chapters.delete_many({}) +# database_courses = request.app.db["courses"] +# database_chapters = request.app.db["coursechapters"] +# await database_courses.delete_many({}) +# await database_chapters.delete_many({}) - courses = [] - orgs = request.app.db["organizations"] +# courses = [] +# orgs = request.app.db["organizations"] - if await orgs.count_documents({}) > 0: - for org in await orgs.find().to_list(length=100): - for i in range(0, 5): +# if await orgs.count_documents({}) > 0: +# for org in await orgs.find().to_list(length=100): +# for i in range(0, 5): - # get image in BinaryIO format from unsplash and save it to disk - image = requests.get( - "https://source.unsplash.com/random/800x600") - with open("thumbnail.jpg", "wb") as f: - f.write(image.content) +# # get image in BinaryIO format from unsplash and save it to disk +# image = requests.get( +# "https://source.unsplash.com/random/800x600") +# with open("thumbnail.jpg", "wb") as f: +# f.write(image.content) - course_id = f"course_{uuid4()}" - course = CourseInDB( - name=fake_multilang.unique.sentence(), - description=fake_multilang.unique.text(), - mini_description=fake_multilang.unique.text(), - thumbnail="thumbnail", - org_id=org['org_id'], - learnings=[fake_multilang.unique.sentence() - for i in range(0, 5)], - public=True, - chapters=[], - course_id=course_id, - creationDate=str(datetime.now()), - updateDate=str(datetime.now()), - authors=[current_user.user_id], - ) +# course_id = f"course_{uuid4()}" +# course = CourseInDB( +# name=fake_multilang.unique.sentence(), +# description=fake_multilang.unique.text(), +# mini_description=fake_multilang.unique.text(), +# thumbnail="thumbnail", +# org_id=org['org_id'], +# learnings=[fake_multilang.unique.sentence() +# for i in range(0, 5)], +# public=True, +# chapters=[], +# course_id=course_id, +# creationDate=str(datetime.now()), +# updateDate=str(datetime.now()), +# authors=[current_user.user_id], +# ) - courses = request.app.db["courses"] - name_in_disk = f"test_mock{course_id}.jpeg" +# courses = request.app.db["courses"] +# name_in_disk = f"test_mock{course_id}.jpeg" - image = requests.get( - "https://source.unsplash.com/random/800x600") - with open(f"content/uploads/img/{name_in_disk}", "wb") as f: - f.write(image.content) +# image = requests.get( +# "https://source.unsplash.com/random/800x600") +# with open(f"content/uploads/img/{name_in_disk}", "wb") as f: +# f.write(image.content) - course.thumbnail = name_in_disk +# course.thumbnail = name_in_disk - course = CourseInDB(**course.dict()) - course_in_db = await courses.insert_one(course.dict()) +# course = CourseInDB(**course.dict()) +# course_in_db = await courses.insert_one(course.dict()) - # create chapters - for i in range(0, 5): - coursechapter = CourseChapter( - name=fake_multilang.unique.sentence(), - description=fake_multilang.unique.text(), - lectures=[], - ) - coursechapter = await create_coursechapter(request,coursechapter, course_id, current_user) - pprint(coursechapter) - if coursechapter: - # create lectures - for i in range(0, 5): - lecture = Lecture( - name=fake_multilang.unique.sentence(), - type="dynamic", - content={}, - ) - lecture = await create_lecture(request,lecture, coursechapter['coursechapter_id'], current_user) +# # create chapters +# for i in range(0, 5): +# coursechapter = CourseChapter( +# name=fake_multilang.unique.sentence(), +# description=fake_multilang.unique.text(), +# lectures=[], +# ) +# coursechapter = await create_coursechapter(request,coursechapter, course_id, current_user) +# pprint(coursechapter) +# if coursechapter: +# # create lectures +# for i in range(0, 5): +# lecture = Lecture( +# name=fake_multilang.unique.sentence(), +# type="dynamic", +# content={}, +# ) +# lecture = await create_lecture(request,lecture, coursechapter['coursechapter_id'], current_user) diff --git a/src/services/orgs.py b/src/services/orgs.py index 1e45853d..98f516bf 100644 --- a/src/services/orgs.py +++ b/src/services/orgs.py @@ -2,7 +2,7 @@ import json from typing import List from uuid import uuid4 from pydantic import BaseModel -from src.services.users import PublicUser, User +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 diff --git a/src/services/roles.py b/src/services/roles.py deleted file mode 100644 index 2423392d..00000000 --- a/src/services/roles.py +++ /dev/null @@ -1,167 +0,0 @@ -import json -from typing import List -from uuid import uuid4 -from pydantic import BaseModel -from src.services.users import PublicUser, User -from src.services.security import * -from src.services.houses import House -from fastapi import HTTPException, status, Request -from datetime import datetime - -#### Classes #################################################### - - -class Permission(BaseModel): - action_create: bool - action_read: bool - action_update: bool - action_delete: bool - - -class Elements(BaseModel): - courses: List[str] - users: List[str] - houses: List[str] - collections: List[str] - organizations: List[str] - coursechapters: List[str] - lectures : List[str] - - -class RolePolicy(BaseModel): - permissions: Permission - elements: Elements - -class Role(BaseModel): - name: str - description: str - policies: List[RolePolicy] - linked_users: List[str] - -class RoleInDB(Role): - role_id: str - creationDate: str - updateDate: str - -#### Classes #################################################### - - -async def get_role(request: Request,role_id: str): - roles = request.app.db["roles"] - - role = await roles.find_one({"role_id": role_id}) - - if not role: - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="Role does not exist") - - role = Role(**role) - return role - - -async def create_role(request: Request,role_object: Role, current_user: PublicUser): - roles = request.app.db["roles"] - - # find if house already exists using name - isRoleAvailable = await roles.find_one({"name": role_object.name}) - - if isRoleAvailable: - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="Role name already exists") - - await verify_user_permissions_on_roles(request, "create", current_user) - - # generate house_id with uuid4 - role_id = str(f"role_{uuid4()}") - - role = RoleInDB(role_id=role_id, creationDate=str(datetime.now()), - updateDate=str(datetime.now()), **role_object.dict()) - - role_in_db = await roles.insert_one(role.dict()) - - if not role_in_db: - raise HTTPException( - status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database") - - return role.dict() - - -async def update_role(request: Request,role_object: Role, role_id: str, current_user: PublicUser): - - # verify house rights - await verify_user_permissions_on_roles(request, "update", current_user) - - roles = request.app.db["roles"] - - role = await roles.find_one({"role_id": role_id}) - - if not role: - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="Role does not exist") - - updated_role = RoleInDB( - role_id=role_id, updateDate=str(datetime.now()), creationDate=role["creationDate"], **role_object.dict()) - - await roles.update_one({"role_id": role_id}, {"$set": updated_role.dict()}) - - return RoleInDB(**updated_role.dict()) - - -async def delete_role(request: Request,role_id: str, current_user: PublicUser): - - # verify house rights - await verify_user_permissions_on_roles(request, "delete", current_user) - - roles = request.app.db["roles"] - - role = await roles.find_one({"role_id": role_id}) - - if not role: - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="Role does not exist") - - isDeleted = await roles.delete_one({"role_id": role_id}) - - if isDeleted: - return {"detail": "Role deleted"} - else: - raise HTTPException( - status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database") - - -async def get_roles(request: Request,page: int = 1, limit: int = 10): - roles = request.app.db["roles"] - - # get all roles from database - all_roles = roles.find().sort("name", 1).skip(10 * (page - 1)).limit(limit) - - return [json.loads(json.dumps(role, default=str)) for role in await all_roles.to_list(length=limit)] - - -#### Security #################################################### - -async def verify_user_permissions_on_roles(request: Request,action: str, current_user: PublicUser): - users = request.app.db["users"] - - user = await users.find_one({"user_id": current_user.user_id}) - - if not user: - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="User does not exist") - - isOwner = "owner" in user["user_type"] - isEditor = "editor" in user["user_type"] - - # TODO: verify for all actions. - if action == "delete": - if isEditor: - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, detail="You do not have rights to this Role") - - if not isOwner and not isEditor: - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, detail="You do not have rights to this Role") - - return True - -#### Security #################################################### diff --git a/src/services/roles/__init__.py b/src/services/roles/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/services/roles/roles.py b/src/services/roles/roles.py new file mode 100644 index 00000000..cae2585c --- /dev/null +++ b/src/services/roles/roles.py @@ -0,0 +1,112 @@ +import json +from typing import List, Literal +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 fastapi import HTTPException, status, Request +from datetime import datetime + + +async def create_role(request: Request, role_object: Role, current_user: PublicUser): + roles = request.app.db["roles"] + + await verify_user_permissions_on_roles(request, current_user, "create", None) + + + # create the role object in the database and return the object + role_id = "role_" + str(uuid4()) + + role = RoleInDB( + role_id=role_id, + created_at=str(datetime.now()), + updated_at=str(datetime.now()), + **role_object.dict() + ) + + await roles.insert_one(role.dict()) + + return role + +async def read_role(request: Request, role_id: str, current_user: PublicUser): + roles = request.app.db["roles"] + + await verify_user_permissions_on_roles(request, current_user, "read", role_id) + + role = RoleInDB(**await roles.find_one({"role_id": role_id})) + + return role + +async def update_role(request: Request, role_id: str, role_object: Role, current_user: PublicUser): + roles = request.app.db["roles"] + + await verify_user_permissions_on_roles(request, current_user, "update", role_id) + + role_object.updated_at = datetime.now() + + # Update the role object in the database and return the object + updated_role = RoleInDB(**await roles.find_one_and_update({"role_id": role_id}, {"$set": role_object.dict()}, return_document=True)) + + return updated_role + +async def delete_role(request: Request, role_id: str, current_user: PublicUser): + roles = request.app.db["roles"] + + await verify_user_permissions_on_roles(request, current_user, "delete", role_id) + + # Delete the role object in the database and return the object + deleted_role = RoleInDB(**await roles.find_one_and_delete({"role_id": role_id})) + + return deleted_role + +#### Security #################################################### + +async def verify_user_permissions_on_roles(request: Request, current_user: PublicUser, action: Literal["create", "read", "update", "delete"], role_id: str | None): + users = request.app.db["users"] + roles = request.app.db["roles"] + + # If current user is not authenticated + + if not current_user: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, detail="Roles : Not authenticated") + + if action == "create": + if "owner" in [org.org_role for org in current_user.orgs]: + return True + + if role_id is not None: + role = RoleInDB(**await roles.find_one({"role_id": role_id})) + + if action == "read": + if "owner" in [org.org_role for org in current_user.orgs]: + return True + + for org in current_user.orgs: + if org.org_id == role.org_id: + return True + + if action == "update": + for org in current_user.orgs: + # If the user is an owner of the organization + if org.org_id == role.org_id: + if org.org_role == "owner" or org.org_role == "editor": + return True + # Can't update a global role + if role.org_id == "*": + return False + + if action == "delete": + for org in current_user.orgs: + # If the user is an owner of the organization + if org.org_id == role.org_id: + if org.org_role == "owner": + return True + # Can't delete a global role + if role.org_id == "*": + return False + + +#### Security #################################################### diff --git a/src/services/roles/schemas/__init__.py b/src/services/roles/schemas/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/services/roles/schemas/roles.py b/src/services/roles/schemas/roles.py new file mode 100644 index 00000000..cf3f5f74 --- /dev/null +++ b/src/services/roles/schemas/roles.py @@ -0,0 +1,41 @@ +from typing import List, Literal +from pydantic import BaseModel + + +# Database Models + +class Permission(BaseModel): + action_create: bool + action_read: bool + action_update: bool + action_delete: bool + + def __getitem__(self, item): + return getattr(self, item) + + +class Elements(BaseModel): + courses: Permission + users: Permission + houses: Permission + collections: Permission + organizations: Permission + coursechapters: Permission + lectures: Permission + + def __getitem__(self, item): + return getattr(self, item) + + +class Role(BaseModel): + name: str + description: str + elements : Elements + org_id: str | Literal["*"] + + +class RoleInDB(Role): + role_id: str + created_at: str + updated_at: str + diff --git a/src/services/security.py b/src/services/security.py index 0b5efafc..419a1f23 100644 --- a/src/services/security.py +++ b/src/services/security.py @@ -1,6 +1,10 @@ +from pprint import pprint from fastapi import HTTPException, status, Request from passlib.context import CryptContext from passlib.hash import pbkdf2_sha256 +from src.services.roles.schemas.roles import RoleInDB + +from src.services.users.schemas.users import User, UserInDB ### 🔒 JWT ############################################################## @@ -27,34 +31,41 @@ async def security_verify_password(plain_password: str, hashed_password: str): ### 🔒 Roles checking ############################################################## -async def verify_user_rights_with_roles(request: Request,action: str, user_id: str, element_id: str): +async def verify_user_rights_with_roles(request: Request, action: str, user_id: str, element_id: str): """ Check if the user has the right to perform the action on the element """ roles = request.app.db["roles"] + users = request.app.db["users"] - # find data where user_id is in linked_users or * is in linked_users - user_roles_cursor = roles.find({"$or": [{"linked_users": user_id}, {"linked_users": "*"}]}) + # Check if the user is an admin + user: UserInDB = UserInDB(**await users.find_one({"user_id": user_id})) - + # Organization roles verification + for org in user.orgs: + # TODO: Check if the org_id (user) is the same as the org_id (element) - user_roles = [] + # Check if user is owner or reader of the organization + if org.org_role == ("owner" or "editor"): + return True - # Info: permission actions are: read, create, delete, update + # If the user is not an owner or a editor, check if he has a role + # Get user roles + user_roles = user.roles - for role in await user_roles_cursor.to_list(length=100): - user_roles.append(role) + # TODO: Check if the org_id of the role is the same as the org_id of the element using find - for role in user_roles: - for policy in role['policies']: - element = policy["elements"][await check_element_type(element_id)] - permission_state = policy["permissions"][f'action_{action}'] + # Check if user has the right role - ## - if ("*" in element or element_id in element) and (permission_state is True): - return True - else: - return False + element_type = await check_element_type(element_id) + for role_id in user_roles: + role = RoleInDB(**await roles.find_one({"role_id": role_id})) + if role.elements[element_type][f"action_{action}"]: + return True + + # If no role is found, raise an error + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, detail="You don't have the right to perform this action") async def check_element_type(element_id): diff --git a/src/services/users.py b/src/services/users.py index ccc84ca8..6adde4cc 100644 --- a/src/services/users.py +++ b/src/services/users.py @@ -17,6 +17,8 @@ class User(BaseModel): verified: bool | None = False user_type: str | None = None bio: str | None = None + orgs: list + class UserWithPassword(User): password: str @@ -24,9 +26,11 @@ class UserWithPassword(User): class PublicUser(User): user_id: str + orgs: list creationDate: str updateDate: str + class PasswordChangeForm(BaseModel): old_password: str new_password: str @@ -86,7 +90,6 @@ async def get_profile_metadata(request: Request, user): user_roles_list = [] for role in await user_roles.to_list(length=100): - print(role) user_roles_list.append(Role(**role)) return { @@ -142,7 +145,6 @@ async def update_user(request: Request, user_id: str, user_object: User): raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="User does not exist") - # TODO : fix this # okay if username is not changed @@ -151,9 +153,8 @@ async def update_user(request: Request, user_id: str, user_object: User): else: if isUsernameAvailable: - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="Username already used") - + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="Username already used") updated_user = {"$set": user_object.dict()} users.update_one({"user_id": user_id}, updated_user) diff --git a/src/services/users/__init__.py b/src/services/users/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/services/users/schemas/__init__.py b/src/services/users/schemas/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/services/users/schemas/users.py b/src/services/users/schemas/users.py new file mode 100644 index 00000000..2f093cce --- /dev/null +++ b/src/services/users/schemas/users.py @@ -0,0 +1,48 @@ +from typing import Literal +from pydantic import BaseModel + + +class UserOrganization(BaseModel): + org_id: str + org_role: Literal['owner', 'editor', 'member'] + + +class User(BaseModel): + username: str + email: str + full_name: str | None = None + avatar_url: str | None = None + bio: str | None = None + + + +class UserWithPassword(User): + password: str + + +class UserInDB(User): + user_id: str + password: str + verified: bool | None = False + disabled: bool | None = False + orgs: list[UserOrganization] = [] + roles: list[str] = [] + creation_date: str + update_date: str + + + + +class PublicUser(User): + user_id: str + orgs: list[UserOrganization] = [] + roles: list[str] = [] + creation_date: str + update_date: str + + +# Forms #################################################### + +class PasswordChangeForm(BaseModel): + old_password: str + new_password: str diff --git a/src/services/users/users.py b/src/services/users/users.py new file mode 100644 index 00000000..c2e1fe06 --- /dev/null +++ b/src/services/users/users.py @@ -0,0 +1,232 @@ +from datetime import datetime +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.services.users.schemas.users import PasswordChangeForm, PublicUser, User, UserOrganization, UserWithPassword, UserInDB + + +async def create_user(request: Request, current_user: PublicUser | None, user_object: UserWithPassword, org_id: str): + users = request.app.db["users"] + + isUserAvailable = await users.find_one({"username": user_object.username}) + + if isUserAvailable: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="Username already exists") + + # Generate user_id with uuid4 + user_id = str(f"user_{uuid4()}") + + # Check if the requesting user is authenticated + if current_user is not None: + # Verify rights + await verify_user_rights_on_user(request, current_user, "create", user_id) + + # Set the username & hash the password + user_object.username = user_object.username.lower() + user_object.password = await security_hash_password(user_object.password) + + # Create initial orgs list with the org_id passed in + orgs = [UserOrganization(org_id=org_id, org_role="member")] + + # Give role + roles = ["role_1"] + + # Create the user + user = UserInDB(user_id=user_id, creation_date=str(datetime.now()), + update_date=str(datetime.now()), orgs=orgs, roles=roles, **user_object.dict()) + + # Insert the user into the database + await users.insert_one(user.dict()) + + return User(**user.dict()) + + +async def read_user(request: Request, current_user: PublicUser, user_id: str): + users = request.app.db["users"] + + # Check if the user exists + isUserExists = await users.find_one({"user_id": user_id}) + + # Verify rights + await verify_user_rights_on_user(request, current_user, "read", user_id) + + # If the user does not exist, raise an error + if not isUserExists: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="User does not exist") + + return User(**isUserExists) + + +async def update_user(request: Request, user_id: str, user_object: User,current_user: PublicUser): + users = request.app.db["users"] + + # Verify rights + await verify_user_rights_on_user(request, current_user, "update", user_id) + + isUserExists = await users.find_one({"user_id": user_id}) + isUsernameAvailable = await users.find_one({"username": user_object.username}) + + if not isUserExists: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="User does not exist") + + # okay if username is not changed + if isUserExists["username"] == user_object.username: + user_object.username = user_object.username.lower() + + else: + if isUsernameAvailable: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="Username already used") + + updated_user = {"$set": user_object.dict()} + users.update_one({"user_id": user_id}, updated_user) + + return User(**user_object.dict()) + + + +async def update_user_password(request: Request, current_user: PublicUser, user_id: str, password_change_form: PasswordChangeForm): + users = request.app.db["users"] + + isUserExists = await users.find_one({"user_id": user_id}) + + # Verify rights + await verify_user_rights_on_user(request, current_user, "update", user_id) + + if not isUserExists: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="User does not exist") + + if not await security_verify_password(password_change_form.old_password, isUserExists["password"]): + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, detail="Wrong password") + + new_password = await security_hash_password(password_change_form.new_password) + + updated_user = {"$set": {"password": new_password}} + await users.update_one({"user_id": user_id}, updated_user) + + return {"detail": "Password updated"} + + +async def delete_user(request: Request, current_user: PublicUser, user_id: str): + users = request.app.db["users"] + + isUserExists = await users.find_one({"user_id": user_id}) + + # Verify is user has permission to delete the user + await verify_user_rights_on_user(request, current_user, "delete", user_id) + + if not isUserExists: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="User does not exist") + + await users.delete_one({"user_id": user_id}) + + return {"detail": "User deleted"} + + +# Utils & Security functions + +async def security_get_user(request: Request, email: str): + users = request.app.db["users"] + + + user = await users.find_one({"email": email}) + + if not user: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="User with Email does not exist") + + return UserInDB(**user) + +async def get_userid_by_username(request: Request, username: str): + users = request.app.db["users"] + + user = await users.find_one({"username": username}) + + if not user: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="User does not exist") + + return user["user_id"] + +async def get_user_by_userid(request: Request, user_id: str): + users = request.app.db["users"] + + user = await users.find_one({"user_id": user_id}) + + if not user: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="User does not exist") + + user = User(**user) + return user + +async def get_profile_metadata(request: Request, user): + users = request.app.db["users"] + roles = request.app.db["roles"] + + user = await users.find_one({"user_id": user['user_id']}) + + if not user: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, detail="User does not exist") + + + + return { + "user_object": PublicUser(**user), + "roles": "random" + } + + +# Verification of the user's permissions on the roles + +async def verify_user_rights_on_user(request: Request, current_user: PublicUser, action: Literal["create", "read", "update", "delete"], user_id: str): + users = request.app.db["users"] + user = UserInDB(**await users.find_one({"user_id": user_id})) + + if action == "create": + return True + + if action == "read": + if current_user.user_id == user_id: + return True + + for org in current_user.orgs: + if org.org_id in [org.org_id for org in user.orgs]: + return True + + return False + + if action == "update": + if current_user.user_id == user_id: + return True + + for org in current_user.orgs: + if org.org_id in [org.org_id for org in user.orgs]: + + if org.org_role == "owner": + return True + + # TODO: Verify user roles on the org + + return False + + if action == "delete": + if current_user.user_id == user_id: + return True + + for org in current_user.orgs: + if org.org_id in [org.org_id for org in user.orgs]: + + if org.org_role == "owner": + return True + + # TODO: Verify user roles on the org