mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-18 11:59:26 +00:00
feat: course creation/del + file upload
This commit is contained in:
parent
9a8e4e4492
commit
c838b7e9cd
9 changed files with 151 additions and 21 deletions
2
app.py
2
app.py
|
|
@ -3,6 +3,7 @@ from fastapi import FastAPI
|
|||
from src.main import global_router
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from fastapi_jwt_auth.exceptions import AuthJWTException
|
||||
|
||||
########################
|
||||
|
|
@ -28,6 +29,7 @@ app.add_middleware(
|
|||
allow_headers=["*"]
|
||||
)
|
||||
|
||||
app.mount("/content", StaticFiles(directory="content"), name="content")
|
||||
|
||||
# Exception Handler
|
||||
@app.exception_handler(AuthJWTException)
|
||||
|
|
|
|||
|
|
@ -1,22 +1,24 @@
|
|||
import { useRouter } from "next/router";
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import Layout from "../../../../components/ui/layout";
|
||||
import { getAPIUrl, getBackendUrl } from "../../../../services/config";
|
||||
import { getCourse } from "../../../../services/courses";
|
||||
import { getOrganizationContextInfo } from "../../../../services/orgs";
|
||||
|
||||
const CourseIdPage = () => {
|
||||
const router = useRouter();
|
||||
const { courseid } = router.query;
|
||||
const { orgslug } = router.query;
|
||||
|
||||
const [isLoading, setIsLoading] = React.useState(true);
|
||||
const [courseInfo, setCourseInfo] = React.useState("") as any;
|
||||
const [courseInfo, setCourseInfo] = React.useState({}) as any;
|
||||
|
||||
async function fetchCourseInfo() {
|
||||
const orgid = await getOrganizationContextInfo(orgslug);
|
||||
const response = await getCourse(courseid, orgid);
|
||||
const data = await response.json();
|
||||
setCourseInfo(data);
|
||||
const course = await getCourse("course_" + courseid);
|
||||
|
||||
setCourseInfo(course);
|
||||
console.log(courseInfo);
|
||||
|
||||
setIsLoading(false);
|
||||
}
|
||||
|
||||
|
|
@ -26,7 +28,36 @@ const CourseIdPage = () => {
|
|||
}
|
||||
}, [isLoading, router.isReady]);
|
||||
|
||||
return <Layout>{isLoading ? <div>Loading...</div> : <div>{courseInfo.name}</div>}</Layout>;
|
||||
return (
|
||||
<Layout>
|
||||
{isLoading ? (
|
||||
<div>Loading...</div>
|
||||
) : (
|
||||
<div>
|
||||
<br></br>
|
||||
<h1>{courseInfo.name}</h1>
|
||||
<CourseWrapper>
|
||||
<img src={`${getBackendUrl()}content/uploads/img/${courseInfo.thumbnail}`} alt="" />
|
||||
</CourseWrapper>
|
||||
</div>
|
||||
)}
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
const CourseWrapper = styled.div`
|
||||
display: flex;
|
||||
img {
|
||||
position: absolute;
|
||||
width: 794px;
|
||||
height: 224.28px;
|
||||
|
||||
|
||||
background: url(), #d9d9d9;
|
||||
border: 1px solid rgba(255, 255, 255, 0.19);
|
||||
box-shadow: 0px 13px 33px -13px rgba(0, 0, 0, 0.42);
|
||||
border-radius: 7px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default CourseIdPage;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import React from "react";
|
|||
import { Header } from "../../../../components/ui/header";
|
||||
import Layout from "../../../../components/ui/layout";
|
||||
import { Title } from "../../../../components/ui/styles/title";
|
||||
import { getOrgCourses } from "../../../../services/courses";
|
||||
import { deleteCourseFromBackend, getOrgCourses } from "../../../../services/courses";
|
||||
import { getOrganizationContextInfo } from "../../../../services/orgs";
|
||||
|
||||
const CoursesIndexPage = () => {
|
||||
|
|
@ -22,6 +22,12 @@ const CoursesIndexPage = () => {
|
|||
setIsLoading(false);
|
||||
}
|
||||
|
||||
async function deleteCourses(course_id: any) {
|
||||
const response = await deleteCourseFromBackend(course_id);
|
||||
const newCourses = courses.filter((course: any) => course.course_id !== course_id);
|
||||
setCourses(newCourses);
|
||||
}
|
||||
|
||||
// function to remove "course_" from the course_id
|
||||
function removeCoursePrefix(course_id: string) {
|
||||
return course_id.replace("course_", "");
|
||||
|
|
@ -55,9 +61,12 @@ const CoursesIndexPage = () => {
|
|||
<div>
|
||||
{courses.map((course: any) => (
|
||||
<div key={course.course_id}>
|
||||
<Link href={"/org/" + orgslug + "/courses/" + removeCoursePrefix(course.course_id)}>
|
||||
<a><h2>{course.name}</h2></a>
|
||||
<Link href={"/org/" + orgslug + "/course/" + removeCoursePrefix(course.course_id)}>
|
||||
<a>
|
||||
<h2>{course.name}</h2>
|
||||
</a>
|
||||
</Link>
|
||||
<button onClick={() => deleteCourses(course.course_id)}>Delete</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,21 +3,67 @@ import React from "react";
|
|||
import { Header } from "../../../../../components/ui/header";
|
||||
import Layout from "../../../../../components/ui/layout";
|
||||
import { Title } from "../../../../../components/ui/styles/title";
|
||||
import { createNewCourse } from "../../../../../services/courses";
|
||||
import { getOrganizationContextInfo } from "../../../../../services/orgs";
|
||||
|
||||
const NewCoursePage = () => {
|
||||
const router = useRouter();
|
||||
const { orgslug } = router.query;
|
||||
const [name, setName] = React.useState("");
|
||||
const [description, setDescription] = React.useState("");
|
||||
const [isLoading, setIsLoading] = React.useState(false);
|
||||
const [thumbnail, setThumbnail] = React.useState(null) as any;
|
||||
const [orgId, setOrgId] = React.useState(null) as any;
|
||||
|
||||
|
||||
const getOrgMetadata = async () => {
|
||||
const org = await getOrganizationContextInfo(orgslug);
|
||||
setOrgId(org.org_id);
|
||||
}
|
||||
|
||||
const handleNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setName(event.target.value);
|
||||
};
|
||||
|
||||
const handleDescriptionChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setDescription(event.target.value);
|
||||
};
|
||||
|
||||
const handleThumbnailChange = (event: React.ChangeEvent<any>) => {
|
||||
setThumbnail(event.target.files[0]);
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: any) => {
|
||||
e.preventDefault();
|
||||
let status = await createNewCourse(orgId, { name, description }, thumbnail);
|
||||
|
||||
// TODO : wow this is terrible - fix this
|
||||
if (status.org_id == orgId) {
|
||||
router.push(`/org/${orgslug}/courses`);
|
||||
} else {
|
||||
alert("Error creating course, please see console logs");
|
||||
console.log(status);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (router.isReady) {
|
||||
getOrgMetadata();
|
||||
}
|
||||
}, [isLoading, router.isReady]);
|
||||
|
||||
|
||||
return (
|
||||
<Layout title="New course">
|
||||
<Header></Header>
|
||||
<Title>New Course </Title>
|
||||
<hr />
|
||||
Name : <input type="text" /> <br />
|
||||
Description : <input type="text" /> <br />
|
||||
Cover Photo : <input type="file" /> <br />
|
||||
Learnings (separated by ; ) : <textarea id="story" name="story" rows={5} cols={33} /> <br />
|
||||
<button>Create</button>
|
||||
Name : <input onChange={handleNameChange} type="text" /> <br />
|
||||
Description : <input onChange={handleDescriptionChange} type="text" /> <br />
|
||||
Cover Photo : <input onChange={handleThumbnailChange} type="file" /> <br />
|
||||
Learnings (empty for now) (separated by ; ) : <textarea id="story" name="story" rows={5} cols={33} /> <br />
|
||||
<button onClick={handleSubmit}>Create</button>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ const Organizations = () => {
|
|||
}
|
||||
|
||||
async function deleteOrganization(org_id:any) {
|
||||
const response = await deleteOrganizationFromBackend(org_id);
|
||||
const response = await deleteOrganizationFromBackend(org_id);
|
||||
const newOrganizations = userOrganizations.filter((org:any) => org.org_id !== org_id);
|
||||
setUserOrganizations(newOrganizations);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
const LEARNHOUSE_API_URL = "http://localhost:1338/api/";
|
||||
const LEARNHOUSE_BACKEND_URL = "http://localhost:1338/";
|
||||
|
||||
export const getAPIUrl = () => LEARNHOUSE_API_URL;
|
||||
|
||||
export const getBackendUrl = () => LEARNHOUSE_BACKEND_URL;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export async function getOrgCourses(org_id: number) {
|
|||
.catch((error) => console.log("error", error));
|
||||
}
|
||||
|
||||
export async function getCourse(org_id: any, course_id: any) {
|
||||
export async function getCourse(course_id: any) {
|
||||
const HeadersConfig = new Headers({ "Content-Type": "application/json" });
|
||||
|
||||
const requestOptions: any = {
|
||||
|
|
@ -30,3 +30,42 @@ export async function getCourse(org_id: any, course_id: any) {
|
|||
.then((result) => result.json())
|
||||
.catch((error) => console.log("error", error));
|
||||
}
|
||||
|
||||
export async function createNewCourse(org_id: string, course_body: any, thumbnail: any) {
|
||||
const HeadersConfig = new Headers();
|
||||
|
||||
// Send file thumbnail as form data
|
||||
const formData = new FormData();
|
||||
formData.append("thumbnail", thumbnail);
|
||||
formData.append("name", course_body.name);
|
||||
formData.append("description", course_body.description);
|
||||
formData.append("mini_description", "course_body.mini_description");
|
||||
formData.append("public", "true");
|
||||
|
||||
const requestOptions: any = {
|
||||
method: "POST",
|
||||
headers: HeadersConfig,
|
||||
redirect: "follow",
|
||||
credentials: "include",
|
||||
body: formData,
|
||||
};
|
||||
|
||||
return fetch(`${getAPIUrl()}courses/?org_id=${org_id}`, requestOptions)
|
||||
.then((result) => result.json())
|
||||
.catch((error) => console.log("error", error));
|
||||
}
|
||||
|
||||
export async function deleteCourseFromBackend(course_id: any) {
|
||||
const HeadersConfig = new Headers({ "Content-Type": "application/json" });
|
||||
|
||||
const requestOptions: any = {
|
||||
method: "DELETE",
|
||||
headers: HeadersConfig,
|
||||
redirect: "follow",
|
||||
credentials: "include",
|
||||
};
|
||||
|
||||
return fetch(`${getAPIUrl()}courses/${course_id}`, requestOptions)
|
||||
.then((result) => result.json())
|
||||
.catch((error) => console.log("error", error));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,11 +25,11 @@ async def api_create_course_thumbnail(course_id : str, thumbnail: UploadFile | N
|
|||
|
||||
|
||||
@router.get("/{course_id}")
|
||||
async def api_get_course(course_id: str, org_id : str, current_user: PublicUser = Depends(get_current_user)):
|
||||
async def api_get_course(course_id: str, current_user: PublicUser = Depends(get_current_user)):
|
||||
"""
|
||||
Get single Course by course_id
|
||||
"""
|
||||
return await get_course(course_id, org_id,current_user=current_user)
|
||||
return await get_course(course_id,current_user=current_user)
|
||||
|
||||
|
||||
@router.get("/{org_id}/page/{page}/limit/{limit}")
|
||||
|
|
|
|||
|
|
@ -62,11 +62,11 @@ class CourseChapterInDB(CourseChapter):
|
|||
|
||||
# Courses
|
||||
|
||||
async def get_course(course_id: str, org_id :str , current_user: PublicUser):
|
||||
async def get_course(course_id: str , current_user: PublicUser):
|
||||
await check_database()
|
||||
courses = learnhouseDB["courses"]
|
||||
|
||||
course = courses.find_one({"course_id": course_id , "org_id" : org_id})
|
||||
course = courses.find_one({"course_id": course_id})
|
||||
|
||||
# verify course rights
|
||||
await verify_rights(course_id, current_user, "read")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue