feat: add custom organization logo feature

This commit is contained in:
swve 2023-06-19 00:19:02 +02:00
parent 8c058db5c6
commit 91cb5740ef
16 changed files with 396 additions and 140 deletions

View file

@ -28,7 +28,8 @@ const Organizations = () => {
const handleSubmit = async (e: any) => {
e.preventDefault();
console.log({ name, description, email });
const status = await createNewOrganization({ name, description, email, slug , default: false });
let logo = ''
const status = await createNewOrganization({ name, description, email, logo, slug, default: false });
alert(JSON.stringify(status));
};

View file

@ -1,23 +1,51 @@
"use client";
import React from 'react'
import React, { useState } from 'react'
import { Field, Form, Formik } from 'formik';
import { updateOrganization } from '@services/settings/org';
import { updateOrganization, uploadOrganizationLogo } from '@services/settings/org';
import { UploadCloud } from 'lucide-react';
import { revalidateTags } from '@services/utils/ts/requests';
interface OrganizationValues {
name: string;
description: string;
slug: string;
logo: string;
email: string;
}
function OrganizationClient(props: any) {
const [selectedFile, setSelectedFile] = useState<File | null>(null);
// ...
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files && event.target.files.length > 0) {
const file = event.target.files[0];
setSelectedFile(file);
}
};
const uploadLogo = async () => {
if (selectedFile) {
let org_id = org.org_id;
await uploadOrganizationLogo(org_id, selectedFile);
setSelectedFile(null); // Reset the selected file
revalidateTags(['organizations']);
// reload the page
// terrible hack, it will fixed later
window.location.reload();
}
};
const org = props.org;
let orgValues: OrganizationValues = {
name: org.name,
description: org.description,
slug: org.slug,
logo: org.logo,
email: org.email
}
@ -26,6 +54,7 @@ function OrganizationClient(props: any) {
await updateOrganization(org_id, values);
}
return (
<div>
<h1 className='text-3xl font-bold'>Organization Settings</h1>
@ -62,6 +91,28 @@ function OrganizationClient(props: any) {
name="description"
/>
<label className="block mb-2 font-bold" htmlFor="slug">
Logo
</label>
<div className="flex items-center justify-center w-full ">
<input
className="w-full px-4 py-2 mr-1 border rounded-lg bg-gray-200 cursor-not-allowed"
type="file"
name="logo"
onChange={handleFileChange}
/>
<button
type="button"
onClick={uploadLogo}
disabled={isSubmitting || selectedFile === null}
className="px-6 py-3 text-white bg-gray-500 rounded-lg hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-black"
>
<UploadCloud size={24} />
</button>
</div>
<label className="block mb-2 font-bold" htmlFor="slug">
Slug
</label>

View file

@ -3,16 +3,17 @@ import React from "react";
import learnhouseLogo from "public/learnhouse_logo.png";
import Link from "next/link";
import Image from "next/image";
import { getUriWithOrg } from "@services/config/config";
import { getBackendUrl, getUriWithOrg } from "@services/config/config";
import { getOrganizationContextInfo, getOrganizationContextInfoNoAsync } from "@services/organizations/orgs";
import ClientComponentSkeleton from "@components/UI/Utils/ClientComp";
import { HeaderProfileBox } from "@components/Security/HeaderProfileBox";
export const Menu = (props: any) => {
export const Menu = async (props: any) => {
const orgslug = props.orgslug;
const org = getOrganizationContextInfoNoAsync(orgslug, { revalidate: 1800, tags: ['organizations'] });
const org = await getOrganizationContextInfo(orgslug, { revalidate: 1800, tags: ['organizations'] });
console.log(org);
return (
<>
<div className="h-[60px]"></div>
@ -20,8 +21,16 @@ export const Menu = (props: any) => {
<div className="logo flex ">
<Link href={getUriWithOrg(orgslug, "/")}>
<div className="flex w-auto h-9 rounded-md items-center m-auto justify-center" >
<LearnHouseLogo></LearnHouseLogo>
{org?.logo ? (
<img
src={`${getBackendUrl()}content/uploads/logos/${org?.logo}`}
alt="Learnhouse"
style={{ width: "auto", height: "100%" }}
className="rounded-md"
/>
) : (
<LearnHouseLogo></LearnHouseLogo>
)}
</div>
</Link>
</div>

View file

@ -25,7 +25,7 @@ export default function middleware(req: NextRequest) {
// Organizations & Global settings
if (pathname.startsWith("/organizations")) {
return NextResponse.rewrite(new URL("/organizations", req.url));
return NextResponse.rewrite(new URL(pathname, req.url));
}
// Dynamic Pages Editor

View file

@ -1,5 +1,5 @@
import { getAPIUrl } from "@services/config/config";
import { RequestBody, errorHandling } from "@services/utils/ts/requests";
import { RequestBody, errorHandling, RequestBodyForm } from "@services/utils/ts/requests";
/*
This file includes only POST, PUT, DELETE requests
@ -11,3 +11,12 @@ export async function updateOrganization(org_id: string, data: any) {
const res = await errorHandling(result);
return res;
}
export async function uploadOrganizationLogo(org_id: string, logo_file: any) {
// Send file thumbnail as form data
const formData = new FormData();
formData.append("logo_file", logo_file);
const result: any = await fetch(`${getAPIUrl()}orgs/` + org_id + "/logo", RequestBodyForm("PUT", formData, null));
const res = await errorHandling(result);
return res;
}

View file

@ -1,7 +1,7 @@
from fastapi import APIRouter, Depends, Request
from fastapi import APIRouter, Depends, Request, UploadFile
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.orgs import Organization, create_org, delete_org, get_organization, get_organization_by_slug, get_orgs_by_user, update_org, update_org_logo
from src.services.users.users import PublicUser, User
@ -31,6 +31,12 @@ async def api_get_org_by_slug(request: Request, org_slug: str, current_user: Use
"""
return await get_organization_by_slug(request, org_slug)
@router.put("/{org_id}/logo")
async def api_update_org_logo(request: Request, org_id: str, logo_file:UploadFile, current_user: PublicUser = Depends(get_current_user)):
"""
Get single Org by Slug
"""
return await update_org_logo(request=request,logo_file=logo_file, org_id=org_id, current_user=current_user)
@router.get("/user/page/{page}/limit/{limit}")
async def api_user_orgs(request: Request, page: int, limit: int, current_user: PublicUser = Depends(get_current_user)):

View file

@ -3,7 +3,7 @@ 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 UserInDB
from src.services.users.schemas.users import UserInDB, UserRolesInOrganization
### 🔒 JWT ##############################################################
@ -108,7 +108,7 @@ async def check_element_type(element_id):
status_code=status.HTTP_409_CONFLICT, detail="Issue verifying element nature")
async def check_user_role_org_with_element_org(request: Request, element_id: str, roles_list: list[str]):
async def check_user_role_org_with_element_org(request: Request, element_id: str, roles_list: list[UserRolesInOrganization]):
element_type = await check_element_type(element_id)
element = request.app.db[element_type]

View file

@ -49,6 +49,5 @@ async def upload_file_and_return_file_object(request: Request, file: UploadFile,
f.write(file_binary)
f.close()
# TODO: do some error handling here
return uploadable_file

View file

@ -46,6 +46,7 @@ class CourseChapterInDB(CourseChapter):
creationDate: str
updateDate: str
#### Classes ####################################################
# TODO : Add courses photo & cover upload and delete
@ -55,6 +56,7 @@ class CourseChapterInDB(CourseChapter):
# CRUD
####################################################
async def get_course(request: Request, course_id: str, current_user: PublicUser):
courses = request.app.db["courses"]
@ -65,7 +67,8 @@ async def get_course(request: Request, course_id: str, current_user: PublicUser)
if not course:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist")
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist"
)
course = Course(**course)
return course
@ -83,10 +86,12 @@ async def get_course_meta(request: Request, course_id: str, current_user: Public
if not course:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist")
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist"
)
coursechapters = await courses.find_one({"course_id": course_id}, {
"chapters_content": 1, "_id": 0})
coursechapters = await courses.find_one(
{"course_id": course_id}, {"chapters_content": 1, "_id": 0}
)
# activities
coursechapter_activityIds_global = []
@ -103,42 +108,66 @@ async def get_course_meta(request: Request, course_id: str, current_user: Public
coursechapter_activityIds_global.append(activity)
chapters[coursechapter.coursechapter_id] = {
"id": coursechapter.coursechapter_id, "name": coursechapter.name, "activityIds": coursechapter_activityIds
"id": coursechapter.coursechapter_id,
"name": coursechapter.name,
"activityIds": coursechapter_activityIds,
}
# activities
activities_list = {}
for activity in await activities.find({"activity_id": {"$in": coursechapter_activityIds_global}}).to_list(length=100):
for activity in await activities.find(
{"activity_id": {"$in": coursechapter_activityIds_global}}
).to_list(length=100):
activity = ActivityInDB(**activity)
activities_list[activity.activity_id] = {
"id": activity.activity_id, "name": activity.name, "type": activity.type, "content": activity.content
"id": activity.activity_id,
"name": activity.name,
"type": activity.type,
"content": activity.content,
}
chapters_list_with_activities = []
for chapter in chapters:
chapters_list_with_activities.append(
{"id": chapters[chapter]["id"], "name": chapters[chapter]["name"], "activities": [activities_list[activity] for activity in chapters[chapter]["activityIds"]]})
{
"id": chapters[chapter]["id"],
"name": chapters[chapter]["name"],
"activities": [
activities_list[activity]
for activity in chapters[chapter]["activityIds"]
],
}
)
course = CourseInDB(**course)
# Get activity by user
trail = await trails.find_one(
{"courses.course_id": course_id, "user_id": current_user.user_id})
{"courses.course_id": course_id, "user_id": current_user.user_id}
)
print(trail)
if trail:
# get only the course where course_id == course_id
trail_course = next(
(course for course in trail["courses"] if course["course_id"] == course_id), None)
(course for course in trail["courses"] if course["course_id"] == course_id),
None,
)
else:
trail_course = ""
return {
"course": course,
"chapters": chapters_list_with_activities,
"trail": trail_course
"trail": trail_course,
}
async def create_course(request: Request, course_object: Course, org_id: str, current_user: PublicUser, thumbnail_file: UploadFile | None = None):
async def create_course(
request: Request,
course_object: Course,
org_id: str,
current_user: PublicUser,
thumbnail_file: UploadFile | None = None,
):
courses = request.app.db["courses"]
# generate course_id with uuid4
@ -147,27 +176,42 @@ async def create_course(request: Request, course_object: Course, org_id: str, cu
# TODO(fix) : the implementation here is clearly not the best one (this entire function)
course_object.org_id = org_id
course_object.chapters_content = []
await verify_user_rights_with_roles(request, "create", current_user.user_id, course_id, org_id)
await verify_user_rights_with_roles(
request, "create", current_user.user_id, course_id, org_id
)
if thumbnail_file:
name_in_disk = f"{course_id}_thumbnail_{uuid4()}.{thumbnail_file.filename.split('.')[-1]}"
if thumbnail_file and thumbnail_file.filename:
name_in_disk = (
f"{course_id}_thumbnail_{uuid4()}.{thumbnail_file.filename.split('.')[-1]}"
)
await upload_thumbnail(thumbnail_file, name_in_disk)
course_object.thumbnail = name_in_disk
course = CourseInDB(course_id=course_id, authors=[
current_user.user_id], creationDate=str(datetime.now()), updateDate=str(datetime.now()), **course_object.dict())
course = CourseInDB(
course_id=course_id,
authors=[current_user.user_id],
creationDate=str(datetime.now()),
updateDate=str(datetime.now()),
**course_object.dict(),
)
course_in_db = await courses.insert_one(course.dict())
if not course_in_db:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database")
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Unavailable database",
)
return course.dict()
async def update_course_thumbnail(request: Request, course_id: str, current_user: PublicUser, thumbnail_file: UploadFile | None = None):
async def update_course_thumbnail(
request: Request,
course_id: str,
current_user: PublicUser,
thumbnail_file: UploadFile | None = None,
):
# verify course rights
await verify_rights(request, course_id, current_user, "update")
@ -178,26 +222,34 @@ async def update_course_thumbnail(request: Request, course_id: str, current_user
if course:
creationDate = course["creationDate"]
authors = course["authors"]
if thumbnail_file:
if thumbnail_file and thumbnail_file.filename:
name_in_disk = f"{course_id}_thumbnail_{uuid4()}.{thumbnail_file.filename.split('.')[-1]}"
course = Course(**course).copy(update={"thumbnail": name_in_disk})
await upload_thumbnail(thumbnail_file, name_in_disk)
updated_course = CourseInDB(course_id=course_id, creationDate=creationDate,
authors=authors, updateDate=str(datetime.now()), **course.dict())
updated_course = CourseInDB(
course_id=course_id,
creationDate=creationDate,
authors=authors,
updateDate=str(datetime.now()),
**course.dict(),
)
await courses.update_one({"course_id": course_id}, {
"$set": updated_course.dict()})
await courses.update_one(
{"course_id": course_id}, {"$set": updated_course.dict()}
)
return CourseInDB(**updated_course.dict())
else:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist")
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist"
)
async def update_course(request: Request, course_object: Course, course_id: str, current_user: PublicUser):
async def update_course(
request: Request, course_object: Course, course_id: str, current_user: PublicUser
):
# verify course rights
await verify_rights(request, course_id, current_user, "update")
@ -213,20 +265,26 @@ async def update_course(request: Request, course_object: Course, course_id: str,
datetime_object = datetime.now()
updated_course = CourseInDB(
course_id=course_id, creationDate=creationDate, authors=authors, updateDate=str(datetime_object), **course_object.dict())
course_id=course_id,
creationDate=creationDate,
authors=authors,
updateDate=str(datetime_object),
**course_object.dict(),
)
await courses.update_one({"course_id": course_id}, {
"$set": updated_course.dict()})
await courses.update_one(
{"course_id": course_id}, {"$set": updated_course.dict()}
)
return CourseInDB(**updated_course.dict())
else:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist")
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist"
)
async def delete_course(request: Request, course_id: str, current_user: PublicUser):
# verify course rights
await verify_rights(request, course_id, current_user, "delete")
@ -236,7 +294,8 @@ async def delete_course(request: Request, course_id: str, current_user: PublicUs
if not course:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist")
status_code=status.HTTP_409_CONFLICT, detail="Course does not exist"
)
isDeleted = await courses.delete_one({"course_id": course_id})
@ -244,24 +303,38 @@ async def delete_course(request: Request, course_id: str, current_user: PublicUs
return {"detail": "Course deleted"}
else:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database")
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Unavailable database",
)
####################################################
# Misc
####################################################
async def get_courses(request: Request, page: int = 1, limit: int = 10, org_id: str | None = None):
async def get_courses(
request: Request, page: int = 1, limit: int = 10, org_id: str | None = None
):
courses = request.app.db["courses"]
# TODO : Get only courses that user is admin/has roles of
# get all courses from database
all_courses = courses.find({"org_id": org_id}).sort(
"name", 1).skip(10 * (page - 1)).limit(limit)
all_courses = (
courses.find({"org_id": org_id})
.sort("name", 1)
.skip(10 * (page - 1))
.limit(limit)
)
return [json.loads(json.dumps(course, default=str)) for course in await all_courses.to_list(length=100)]
return [
json.loads(json.dumps(course, default=str))
for course in await all_courses.to_list(length=100)
]
async def get_courses_orgslug(request: Request, page: int = 1, limit: int = 10, org_slug: str | None = None):
async def get_courses_orgslug(
request: Request, page: int = 1, limit: int = 10, org_slug: str | None = None
):
courses = request.app.db["courses"]
orgs = request.app.db["organizations"]
# TODO : Get only courses that user is admin/has roles of
@ -271,37 +344,61 @@ async def get_courses_orgslug(request: Request, page: int = 1, limit: int = 10,
if not org:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist")
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist"
)
# get all courses from database
all_courses = courses.find({"org_id": org['org_id']}).sort(
"name", 1).skip(10 * (page - 1)).limit(limit)
all_courses = (
courses.find({"org_id": org["org_id"]})
.sort("name", 1)
.skip(10 * (page - 1))
.limit(limit)
)
return [json.loads(json.dumps(course, default=str)) for course in await all_courses.to_list(length=100)]
return [
json.loads(json.dumps(course, default=str))
for course in await all_courses.to_list(length=100)
]
#### Security ####################################################
async def verify_rights(request: Request, course_id: str, current_user: PublicUser | AnonymousUser, action: str):
async def verify_rights(
request: Request,
course_id: str,
current_user: PublicUser | AnonymousUser,
action: str,
):
courses = request.app.db["courses"]
course = await courses.find_one({"course_id": course_id})
if current_user.user_id == "anonymous" and course["public"] is True and action == "read":
if (
current_user.user_id == "anonymous"
and course["public"] is True
and action == "read"
):
return True
if not course:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Course/CourseChapter does not exist")
status_code=status.HTTP_409_CONFLICT,
detail="Course/CourseChapter does not exist",
)
hasRoleRights = await verify_user_rights_with_roles(request, action, current_user.user_id, course_id, course["org_id"])
hasRoleRights = await verify_user_rights_with_roles(
request, action, current_user.user_id, course_id, course["org_id"]
)
isAuthor = current_user.user_id in course["authors"]
if not hasRoleRights and not isAuthor:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="Roles/Ownership : Insufficient rights to perform this action")
status_code=status.HTTP_403_FORBIDDEN,
detail="Roles/Ownership : Insufficient rights to perform this action",
)
return True
#### Security ####################################################

View file

@ -9,7 +9,7 @@ from src.services.courses.chapters import CourseChapter, create_coursechapter
from src.services.courses.activities.activities import Activity, create_activity
from src.services.users.users import PublicUser, UserInDB
from src.services.orgs import Organization, create_org
from src.services.orgs.orgs import Organization, create_org
from src.services.roles.schemas.roles import Permission, Elements, RoleInDB
from src.services.courses.courses import CourseInDB
from faker import Faker
@ -133,6 +133,7 @@ async def create_initial_data(request: Request):
description=fake.unique.text(),
email=fake.unique.email(),
slug=slug,
logo="",
default=False
)
organizations.append(org)

View file

View file

@ -0,0 +1,22 @@
import os
from uuid import uuid4
async def upload_org_logo(logo_file):
contents = logo_file.file.read()
name_in_disk = f"{uuid4()}.{logo_file.filename.split('.')[-1]}"
try:
if not os.path.exists("content/uploads/logos"):
os.makedirs("content/uploads/logos")
with open(f"content/uploads/logos/{name_in_disk}", "wb") as f:
f.write(contents)
f.close()
except Exception:
return {"message": "There was an error uploading the file"}
finally:
logo_file.file.close()
return name_in_disk

View file

@ -1,40 +1,16 @@
import json
from typing import Optional
from uuid import uuid4
from click import Option
from pydantic import BaseModel
from src.services.orgs.logos import upload_org_logo
from src.services.orgs.schemas.orgs import (
Organization,
OrganizationInDB,
PublicOrganization,
)
from src.services.users.schemas.users import UserOrganization
from src.services.users.users import PublicUser
from src.security.security import *
from fastapi import HTTPException, status, Request
#### Classes ####################################################
class Organization(BaseModel):
name: str
description: str
email: str
slug: str
default: Optional[bool]
class OrganizationInDB(Organization):
org_id: str
class PublicOrganization(Organization):
name: str
description: str
email: str
slug: str
org_id: str
def __getitem__(self, item):
return getattr(self, item)
#### Classes ####################################################
from fastapi import HTTPException, UploadFile, status, Request
async def get_organization(request: Request, org_id: str):
@ -44,7 +20,8 @@ async def get_organization(request: Request, org_id: str):
if not org:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist")
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist"
)
org = PublicOrganization(**org)
return org
@ -57,13 +34,16 @@ async def get_organization_by_slug(request: Request, org_slug: str):
if not org:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist")
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist"
)
org = PublicOrganization(**org)
return org
async def create_org(request: Request, org_object: Organization, current_user: PublicUser):
async def create_org(
request: Request, org_object: Organization, current_user: PublicUser
):
orgs = request.app.db["organizations"]
user = request.app.db["users"]
@ -72,7 +52,9 @@ async def create_org(request: Request, org_object: Organization, current_user: P
if isOrgAvailable:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Organization slug already exists")
status_code=status.HTTP_409_CONFLICT,
detail="Organization slug already exists",
)
# generate org_id with uuid4
org_id = str(f"org_{uuid4()}")
@ -82,25 +64,33 @@ async def create_org(request: Request, org_object: Organization, current_user: P
org_in_db = await orgs.insert_one(org.dict())
user_organization: UserOrganization = UserOrganization(
org_id=org_id, org_role="owner")
org_id=org_id, org_role="owner"
)
# add org to user
await user.update_one({"user_id": current_user.user_id}, {
"$addToSet": {"orgs": user_organization.dict()}})
# add role admin to org
await user.update_one({"user_id": current_user.user_id}, {
"$addToSet": {"roles": {"org_id": org_id, "role_id": "role_admin"}}})
await user.update_one(
{"user_id": current_user.user_id},
{"$addToSet": {"orgs": user_organization.dict()}},
)
# add role admin to org
await user.update_one(
{"user_id": current_user.user_id},
{"$addToSet": {"roles": {"org_id": org_id, "role_id": "role_admin"}}},
)
if not org_in_db:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database")
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Unavailable database",
)
return org.dict()
async def update_org(request: Request, org_object: Organization, org_id: str, current_user: PublicUser):
async def update_org(
request: Request, org_object: Organization, org_id: str, current_user: PublicUser
):
# verify org rights
await verify_org_rights(request, org_id, current_user, "update")
@ -108,21 +98,38 @@ async def update_org(request: Request, org_object: Organization, org_id: str, cu
org = await orgs.find_one({"org_id": org_id})
if not org:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist")
updated_org = OrganizationInDB(
org_id=org_id, **org_object.dict())
updated_org = OrganizationInDB(org_id=org_id, **org_object.dict())
# update org
await orgs.update_one({"org_id": org_id}, {"$set": updated_org.dict()})
return Organization(**updated_org.dict())
return updated_org.dict()
async def update_org_logo(
request: Request, logo_file: UploadFile, org_id: str, current_user: PublicUser
):
# verify org rights
await verify_org_rights(request, org_id, current_user, "update")
orgs = request.app.db["organizations"]
org = await orgs.find_one({"org_id": org_id})
name_in_disk = await upload_org_logo(logo_file)
# update org
org = await orgs.update_one({"org_id": org_id}, {"$set": {"logo": name_in_disk}})
return {"detail": "Logo updated"}
async def delete_org(request: Request, org_id: str, current_user: PublicUser):
await verify_org_rights(request, org_id, current_user, "delete")
orgs = request.app.db["organizations"]
@ -131,7 +138,8 @@ async def delete_org(request: Request, org_id: str, current_user: PublicUser):
if not org:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist")
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist"
)
isDeleted = await orgs.delete_one({"org_id": org_id})
@ -143,56 +151,80 @@ async def delete_org(request: Request, org_id: str, current_user: PublicUser):
return {"detail": "Org deleted"}
else:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail="Unavailable database")
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="Unavailable database",
)
async def get_orgs_by_user(request: Request, user_id: str, page: int = 1, limit: int = 10):
async def get_orgs_by_user(
request: Request, user_id: str, page: int = 1, limit: int = 10
):
orgs = request.app.db["organizations"]
user = request.app.db["users"]
if user_id == "anonymous":
# raise error
# raise error
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="User not logged in")
status_code=status.HTTP_409_CONFLICT, detail="User not logged in"
)
# get user orgs
user_orgs = await user.find_one({"user_id": user_id})
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":
if (
org["org_role"] == "owner"
or org["org_role"] == "editor"
or org["org_role"] == "member"
):
org_ids.append(org["org_id"])
# find all orgs where org_id is in org_ids array
all_orgs = orgs.find({"org_id": {"$in": org_ids}}).sort(
"name", 1).skip(10 * (page - 1)).limit(100)
all_orgs = (
orgs.find({"org_id": {"$in": org_ids}})
.sort("name", 1)
.skip(10 * (page - 1))
.limit(100)
)
return [json.loads(json.dumps(org, default=str)) for org in await all_orgs.to_list(length=100)]
return [
json.loads(json.dumps(org, default=str))
for org in await all_orgs.to_list(length=100)
]
#### Security ####################################################
async def verify_org_rights(request: Request, org_id: str, current_user: PublicUser, action: str,):
async def verify_org_rights(
request: Request,
org_id: str,
current_user: PublicUser,
action: str,
):
orgs = request.app.db["organizations"]
org = await orgs.find_one({"org_id": org_id})
if not org:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist")
status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist"
)
# check if is owner of org
# todo check if is admin of org
hasRoleRights = await verify_user_rights_with_roles(
request, action, current_user.user_id, org_id, org_id
)
hasRoleRights = await verify_user_rights_with_roles(request, action, current_user.user_id, org_id, org_id)
# if not hasRoleRights and not isOwner:
# raise HTTPException(
# status_code=status.HTTP_403_FORBIDDEN, detail="You do not have rights to this organization")
if not hasRoleRights:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You do not have rights to this organization",
)
return True
#### Security ####################################################

View file

View file

@ -0,0 +1,29 @@
from typing import Optional
from pydantic import BaseModel
from src.security.security import *
#### Classes ####################################################
class Organization(BaseModel):
name: str
description: str
email: str
slug: str
logo: Optional[str]
default: Optional[bool] = False
class OrganizationInDB(Organization):
org_id: str
class PublicOrganization(Organization):
name: str
description: str
email: str
slug: str
org_id: str
def __getitem__(self, item):
return getattr(self, item)

View file

@ -4,7 +4,7 @@ from uuid import uuid4
from fastapi import HTTPException, Request, status
from pydantic import BaseModel
from src.services.courses.chapters import get_coursechapters_meta
from src.services.orgs import PublicOrganization
from src.services.orgs.orgs import PublicOrganization
from src.services.users.users import PublicUser