mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
fix: collections and courses remaining bugs
This commit is contained in:
parent
4245e61df8
commit
212c50768f
8 changed files with 155 additions and 113 deletions
|
|
@ -19,9 +19,8 @@ async def authorization_verify_if_element_is_public(
|
|||
):
|
||||
element_nature = await check_element_type(element_uuid)
|
||||
# Verifies if the element is public
|
||||
if element_nature == ("courses" or "collections") and action == "read":
|
||||
if element_nature == ("courses") and action == "read":
|
||||
if element_nature == "courses":
|
||||
print("looking for course")
|
||||
statement = select(Course).where(
|
||||
Course.public == True, Course.course_uuid == element_uuid
|
||||
)
|
||||
|
|
@ -29,20 +28,29 @@ async def authorization_verify_if_element_is_public(
|
|||
if course:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="User rights : You don't have the right to perform this action",
|
||||
)
|
||||
|
||||
if element_nature == "collections" and action == "read":
|
||||
|
||||
if element_nature == "collections":
|
||||
statement = select(Collection).where(
|
||||
Collection.public == True, Collection.collection_uuid == element_uuid
|
||||
)
|
||||
collection = db_session.exec(statement).first()
|
||||
|
||||
if collection:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="User rights : You don't have the right to perform this action",
|
||||
)
|
||||
else:
|
||||
return False
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="User rights : You don't have the right to perform this action",
|
||||
)
|
||||
|
||||
|
||||
# Tested and working
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ async def check_element_type(element_id):
|
|||
"""
|
||||
Check if the element is a course, a user, a house or a collection, by checking its prefix
|
||||
"""
|
||||
print("element_id", element_id)
|
||||
if element_id.startswith("course_"):
|
||||
return "courses"
|
||||
elif element_id.startswith("user_"):
|
||||
|
|
|
|||
|
|
@ -223,7 +223,6 @@ async def rbac_check(
|
|||
res = await authorization_verify_if_element_is_public(
|
||||
request, course_uuid, action, db_session
|
||||
)
|
||||
print('res',res)
|
||||
return res
|
||||
else:
|
||||
res = await authorization_verify_based_on_roles_and_authorship(
|
||||
|
|
|
|||
|
|
@ -26,7 +26,10 @@ from fastapi import HTTPException, status, Request
|
|||
|
||||
|
||||
async def get_collection(
|
||||
request: Request, collection_uuid: str, current_user: PublicUser, db_session: Session
|
||||
request: Request,
|
||||
collection_uuid: str,
|
||||
current_user: PublicUser,
|
||||
db_session: Session,
|
||||
) -> CollectionRead:
|
||||
statement = select(Collection).where(Collection.collection_uuid == collection_uuid)
|
||||
collection = db_session.exec(statement).first()
|
||||
|
|
@ -42,11 +45,23 @@ async def get_collection(
|
|||
)
|
||||
|
||||
# get courses in collection
|
||||
statement = (
|
||||
statement_all = (
|
||||
select(Course)
|
||||
.join(CollectionCourse, Course.id == CollectionCourse.course_id)
|
||||
.distinct(Course.id)
|
||||
)
|
||||
|
||||
statement_public = (
|
||||
select(Course)
|
||||
.join(CollectionCourse, Course.id == CollectionCourse.course_id)
|
||||
.where(CollectionCourse.org_id == collection.org_id, Course.public == True)
|
||||
)
|
||||
|
||||
if current_user.id == 0:
|
||||
statement = statement_public
|
||||
else:
|
||||
statement = statement_all
|
||||
|
||||
courses = db_session.exec(statement).all()
|
||||
|
||||
collection = CollectionRead(**collection.dict(), courses=courses)
|
||||
|
|
@ -180,7 +195,10 @@ async def update_collection(
|
|||
|
||||
|
||||
async def delete_collection(
|
||||
request: Request, collection_uuid: str, current_user: PublicUser, db_session: Session
|
||||
request: Request,
|
||||
collection_uuid: str,
|
||||
current_user: PublicUser,
|
||||
db_session: Session,
|
||||
):
|
||||
statement = select(Collection).where(Collection.collection_uuid == collection_uuid)
|
||||
collection = db_session.exec(statement).first()
|
||||
|
|
@ -216,23 +234,40 @@ async def get_collections(
|
|||
page: int = 1,
|
||||
limit: int = 10,
|
||||
) -> List[CollectionRead]:
|
||||
# RBAC check
|
||||
await rbac_check(request, "collection_x", current_user, "read", db_session)
|
||||
|
||||
statement = (
|
||||
statement_public = select(Collection).where(
|
||||
Collection.org_id == org_id, Collection.public == True
|
||||
)
|
||||
statement_all = (
|
||||
select(Collection).where(Collection.org_id == org_id).distinct(Collection.id)
|
||||
)
|
||||
|
||||
if current_user.id == 0:
|
||||
statement = statement_public
|
||||
else:
|
||||
statement = statement_all
|
||||
|
||||
collections = db_session.exec(statement).all()
|
||||
|
||||
|
||||
|
||||
collections_with_courses = []
|
||||
|
||||
for collection in collections:
|
||||
statement = (
|
||||
statement_all = (
|
||||
select(Course)
|
||||
.join(CollectionCourse, Course.id == CollectionCourse.course_id)
|
||||
.distinct(Course.id)
|
||||
)
|
||||
statement_public = (
|
||||
select(Course)
|
||||
.join(CollectionCourse, Course.id == CollectionCourse.course_id)
|
||||
.where(CollectionCourse.org_id == org_id, Course.public == True)
|
||||
)
|
||||
if current_user.id == 0:
|
||||
statement = statement_public
|
||||
else:
|
||||
# RBAC check
|
||||
statement = statement_all
|
||||
|
||||
courses = db_session.exec(statement).all()
|
||||
|
||||
collection = CollectionRead(**collection.dict(), courses=courses)
|
||||
|
|
@ -256,8 +291,11 @@ async def rbac_check(
|
|||
res = await authorization_verify_if_element_is_public(
|
||||
request, collection_uuid, action, db_session
|
||||
)
|
||||
print('res',res)
|
||||
return res
|
||||
if res == False:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="User rights : You are not allowed to read this collection",
|
||||
)
|
||||
else:
|
||||
res = await authorization_verify_based_on_roles_and_authorship(
|
||||
request, current_user.id, action, collection_uuid, db_session
|
||||
|
|
@ -276,4 +314,3 @@ async def rbac_check(
|
|||
|
||||
|
||||
## 🔒 RBAC Utils ##
|
||||
|
||||
|
|
|
|||
|
|
@ -1,29 +1,27 @@
|
|||
"use client";
|
||||
import { useRouter } from "next/navigation";
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import { createCollection } from "@services/courses/collections";
|
||||
import useSWR from "swr";
|
||||
import { getAPIUrl, getUriWithOrg } from "@services/config/config";
|
||||
import { revalidateTags, swrFetcher } from "@services/utils/ts/requests";
|
||||
import { getOrganizationContextInfo } from "@services/organizations/orgs";
|
||||
import { useOrg } from "@components/Contexts/OrgContext";
|
||||
|
||||
function NewCollection(params: any) {
|
||||
const org = useOrg() as any;
|
||||
const orgslug = params.params.orgslug;
|
||||
const [name, setName] = React.useState("");
|
||||
const [org, setOrg] = React.useState({}) as any;
|
||||
const [description, setDescription] = React.useState("");
|
||||
const [selectedCourses, setSelectedCourses] = React.useState([]) as any;
|
||||
const router = useRouter();
|
||||
|
||||
const { data: courses, error: error } = useSWR(`${getAPIUrl()}courses/org_slug/${orgslug}/page/1/limit/10`, swrFetcher);
|
||||
const [isPublic, setIsPublic] = useState('true');
|
||||
|
||||
const handleVisibilityChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
setIsPublic(e.target.value);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
async function getOrg() {
|
||||
const org = await getOrganizationContextInfo(orgslug, { revalidate: 1800 });
|
||||
setOrg(org);
|
||||
}
|
||||
getOrg();
|
||||
}, []);
|
||||
|
||||
const handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setName(event.target.value);
|
||||
|
|
@ -40,78 +38,89 @@ function NewCollection(params: any) {
|
|||
name: name,
|
||||
description: description,
|
||||
courses: selectedCourses,
|
||||
public: true,
|
||||
public: isPublic,
|
||||
org_id: org.id,
|
||||
};
|
||||
await createCollection(collection);
|
||||
await revalidateTags(["collections"], orgslug);
|
||||
await revalidateTags(["collections"], org.slug);
|
||||
// reload the page
|
||||
router.refresh();
|
||||
router.prefetch(getUriWithOrg(orgslug, "/collections"));
|
||||
router.push(getUriWithOrg(orgslug, "/collections"));
|
||||
|
||||
// wait for 2s before reloading the page
|
||||
setTimeout(() => {
|
||||
router.push(getUriWithOrg(orgslug, "/collections"));
|
||||
}
|
||||
, 1000);
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-64 m-auto py-20">
|
||||
<div className="font-bold text-lg mb-4">Add new</div>
|
||||
<div className="font-bold text-lg mb-4">Add new</div>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
value={name}
|
||||
onChange={handleNameChange}
|
||||
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
value={name}
|
||||
onChange={handleNameChange}
|
||||
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
|
||||
{!courses ? (
|
||||
<select
|
||||
onChange={handleVisibilityChange}
|
||||
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
defaultValue={isPublic}
|
||||
>
|
||||
<option value="false">Private Collection</option>
|
||||
<option value="true">Public Collection </option>
|
||||
</select>
|
||||
|
||||
|
||||
{!courses ? (
|
||||
<p className="text-gray-500">Loading...</p>
|
||||
) : (
|
||||
<div>
|
||||
<div className="space-y-4 p-3">
|
||||
<p>Courses</p>
|
||||
{courses.map((course: any) => (
|
||||
<div key={course.course_uuid} className="flex items-center mb-2">
|
||||
<div key={course.course_uuid} className="flex items-center space-x-2">
|
||||
|
||||
<input
|
||||
<input
|
||||
type="checkbox"
|
||||
id={course.id}
|
||||
name={course.name}
|
||||
value={course.id}
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setSelectedCourses([...selectedCourses, course.id]);
|
||||
}
|
||||
else {
|
||||
setSelectedCourses(selectedCourses.filter((course_uuid: any) => course_uuid !== course.course_uuid));
|
||||
}
|
||||
}}
|
||||
className="text-blue-500 rounded focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
|
||||
type="checkbox"
|
||||
id={course.id}
|
||||
name={course.name}
|
||||
value={course.id}
|
||||
// id is an integer, not a string
|
||||
|
||||
onChange={(e) => {
|
||||
if (e.target.checked) {
|
||||
setSelectedCourses([...selectedCourses, course.id]);
|
||||
}
|
||||
else {
|
||||
setSelectedCourses(selectedCourses.filter((course_uuid: any) => course_uuid !== course.course_uuid));
|
||||
}
|
||||
}
|
||||
}
|
||||
className="mr-2"
|
||||
/>
|
||||
|
||||
<label htmlFor={course.course_uuid} className="text-sm">{course.name}</label>
|
||||
<label htmlFor={course.course_uuid} className="text-sm text-gray-700">{course.name}</label>
|
||||
</div>
|
||||
))}
|
||||
|
||||
</div>
|
||||
)}
|
||||
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Description"
|
||||
value={description}
|
||||
onChange={handleDescriptionChange}
|
||||
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Description"
|
||||
value={description}
|
||||
onChange={handleDescriptionChange}
|
||||
className="w-full px-4 py-2 mb-4 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
/>
|
||||
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
className="px-6 py-3 text-white bg-black rounded-lg shadow-md hover:bg-black focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSubmit}
|
||||
className="px-6 py-3 text-white bg-black rounded-lg shadow-md hover:bg-black focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -59,11 +59,11 @@ const CourseClient = (props: any) => {
|
|||
useEffect(() => {
|
||||
getLearningTags();
|
||||
}
|
||||
, [org]);
|
||||
, [org, course]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{!course ? (
|
||||
{!course && !org ? (
|
||||
<PageLoading></PageLoading>
|
||||
) : (
|
||||
<GeneralWrapperStyled>
|
||||
|
|
@ -74,8 +74,8 @@ const CourseClient = (props: any) => {
|
|||
</h1>
|
||||
</div>
|
||||
|
||||
{props.course.thumbnail_image ?
|
||||
<div className="inset-0 ring-1 ring-inset ring-black/10 rounded-lg shadow-xl relative w-auto h-[400px] bg-cover bg-center mb-4" style={{ backgroundImage: `url(${getCourseThumbnailMediaDirectory(org?.org_uuid, course.course_uuid, course.thumbnail_image)})` }}>
|
||||
{props.course?.thumbnail_image && org ?
|
||||
<div className="inset-0 ring-1 ring-inset ring-black/10 rounded-lg shadow-xl relative w-auto h-[400px] bg-cover bg-center mb-4" style={{ backgroundImage: `url(${getCourseThumbnailMediaDirectory(org?.org_uuid, course?.course_uuid, course?.thumbnail_image)})` }}>
|
||||
</div>
|
||||
:
|
||||
<div className="inset-0 ring-1 ring-inset ring-black/10 rounded-lg shadow-xl relative w-auto h-[400px] bg-cover bg-center mb-4" style={{ backgroundImage: `url('../empty_thumbnail.png')`, backgroundSize: 'auto' }}>
|
||||
|
|
@ -193,19 +193,19 @@ const CourseClient = (props: any) => {
|
|||
<div className="course_metadata_right space-y-3 w-72 antialiased flex flex-col ml-10 h-fit p-3 py-5 bg-white shadow-md shadow-gray-300/25 outline outline-1 outline-neutral-200/40 rounded-lg overflow-hidden">
|
||||
{user &&
|
||||
<div className="flex flex-col mx-auto space-y-3 px-2 py-2 items-center">
|
||||
<UserAvatar border="border-8" avatar_url={getUserAvatarMediaDirectory(course.authors[0].user_uuid,course.authors[0].avatar_image)} width={100} />
|
||||
<UserAvatar border="border-8" avatar_url={getUserAvatarMediaDirectory(course.authors[0].user_uuid, course.authors[0].avatar_image)} width={100} />
|
||||
<div className="-space-y-2 ">
|
||||
<div className="text-[12px] text-neutral-400 font-semibold">Author</div>
|
||||
<div className="text-xl font-bold text-neutral-800">
|
||||
{course.authors[0].first_name && course.authors[0].last_name && (
|
||||
<div className="flex space-x-2 items-center">
|
||||
<p>{course.authors[0].first_name + ' ' + course.authors[0].last_name}</p><span className="text-xs bg-neutral-100 p-1 px-3 rounded-full text-neutral-400 font-semibold"> @{course.authors[0].username }</span>
|
||||
</div>)}
|
||||
<div className="flex space-x-2 items-center">
|
||||
<p>{course.authors[0].first_name + ' ' + course.authors[0].last_name}</p><span className="text-xs bg-neutral-100 p-1 px-3 rounded-full text-neutral-400 font-semibold"> @{course.authors[0].username}</span>
|
||||
</div>)}
|
||||
{!course.authors[0].first_name && !course.authors[0].last_name && (
|
||||
<div className="flex space-x-2 items-center">
|
||||
<p>@{course.authors[0].username}</p>
|
||||
</div>)}
|
||||
</div>
|
||||
<div className="flex space-x-2 items-center">
|
||||
<p>@{course.authors[0].username}</p>
|
||||
</div>)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
|
@ -226,12 +226,4 @@ const CourseClient = (props: any) => {
|
|||
};
|
||||
|
||||
|
||||
const StyledBox = (props: any) => (
|
||||
<div className="p-3 pl-10 bg-white w-[100%] h-auto ring-1 ring-inset ring-gray-400/10 rounded-lg shadow-sm">
|
||||
{props.children}
|
||||
</div>
|
||||
|
||||
);
|
||||
|
||||
|
||||
export default CourseClient;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export default function Error({
|
|||
|
||||
return (
|
||||
<div>
|
||||
<ErrorUI></ErrorUI>
|
||||
<ErrorUI ></ErrorUI>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,22 +1,20 @@
|
|||
import { XCircle } from 'lucide-react'
|
||||
import React from 'react'
|
||||
|
||||
function ErrorUI() {
|
||||
return (
|
||||
<div className='flex items-center justify-center h-screen'>
|
||||
<div className='mx-auto bg-red-100 w-[800px] p-3 rounded-xl m-5 '>
|
||||
<div className='flex flex-row'>
|
||||
<div className='p-3 pr-4' >
|
||||
<svg width="35" height="35" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 2H5C4.20435 2 3.44129 2.31607 2.87868 2.87868C2.31607 3.44129 2 4.20435 2 5V15C2 15.7956 2.31607 16.5587 2.87868 17.1213C3.44129 17.6839 4.20435 18 5 18H16.59L20.29 21.71C20.3834 21.8027 20.4943 21.876 20.6161 21.9258C20.7379 21.9755 20.8684 22.0008 21 22C21.1312 22.0034 21.2613 21.976 21.38 21.92C21.5626 21.845 21.7189 21.7176 21.8293 21.5539C21.9396 21.3901 21.999 21.1974 22 21V5C22 4.20435 21.6839 3.44129 21.1213 2.87868C20.5587 2.31607 19.7956 2 19 2ZM20 18.59L17.71 16.29C17.6166 16.1973 17.5057 16.124 17.3839 16.0742C17.2621 16.0245 17.1316 15.9992 17 16H5C4.73478 16 4.48043 15.8946 4.29289 15.7071C4.10536 15.5196 4 15.2652 4 15V5C4 4.73478 4.10536 4.48043 4.29289 4.29289C4.48043 4.10536 4.73478 4 5 4H19C19.2652 4 19.5196 4.10536 19.7071 4.29289C19.8946 4.48043 20 4.73478 20 5V18.59ZM12 12C11.8022 12 11.6089 12.0586 11.4444 12.1685C11.28 12.2784 11.1518 12.4346 11.0761 12.6173C11.0004 12.8 10.9806 13.0011 11.0192 13.1951C11.0578 13.3891 11.153 13.5673 11.2929 13.7071C11.4327 13.847 11.6109 13.9422 11.8049 13.9808C11.9989 14.0194 12.2 13.9996 12.3827 13.9239C12.5654 13.8482 12.7216 13.72 12.8315 13.5556C12.9414 13.3911 13 13.1978 13 13C13 12.7348 12.8946 12.4804 12.7071 12.2929C12.5196 12.1054 12.2652 12 12 12ZM12 6C11.7348 6 11.4804 6.10536 11.2929 6.29289C11.1054 6.48043 11 6.73478 11 7V10C11 10.2652 11.1054 10.5196 11.2929 10.7071C11.4804 10.8946 11.7348 11 12 11C12.2652 11 12.5196 10.8946 12.7071 10.7071C12.8946 10.5196 13 10.2652 13 10V7C13 6.73478 12.8946 6.48043 12.7071 6.29289C12.5196 6.10536 12.2652 6 12 6Z" fill="#CC0505" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className='p-3 '>
|
||||
<h1 className='text-2xl font-bold text-red-600'>Error</h1>
|
||||
<p className='pt-0 text-md text-red-600'>Something went wrong</p>
|
||||
<div className='flex flex-row'>
|
||||
<div className='p-3 pr-4 items-center' >
|
||||
<XCircle size={40} className='text-red-600' />
|
||||
</div>
|
||||
<div className='p-3 '>
|
||||
<h1 className='text-2xl font-bold text-red-600'>Error</h1>
|
||||
<p className='pt-0 text-md text-red-600'>Something went wrong</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue