feat: course creation/del + file upload

This commit is contained in:
swve 2022-10-15 16:55:19 +02:00
parent 9a8e4e4492
commit c838b7e9cd
9 changed files with 151 additions and 21 deletions

2
app.py
View file

@ -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)

View file

@ -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;

View file

@ -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>

View file

@ -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>
);
};

View file

@ -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);
}

View file

@ -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;

View file

@ -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));
}

View file

@ -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}")

View file

@ -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")