mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-18 20:09:25 +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 src.main import global_router
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
from fastapi_jwt_auth.exceptions import AuthJWTException
|
from fastapi_jwt_auth.exceptions import AuthJWTException
|
||||||
|
|
||||||
########################
|
########################
|
||||||
|
|
@ -28,6 +29,7 @@ app.add_middleware(
|
||||||
allow_headers=["*"]
|
allow_headers=["*"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
app.mount("/content", StaticFiles(directory="content"), name="content")
|
||||||
|
|
||||||
# Exception Handler
|
# Exception Handler
|
||||||
@app.exception_handler(AuthJWTException)
|
@app.exception_handler(AuthJWTException)
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,24 @@
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
import Layout from "../../../../components/ui/layout";
|
import Layout from "../../../../components/ui/layout";
|
||||||
|
import { getAPIUrl, getBackendUrl } from "../../../../services/config";
|
||||||
import { getCourse } from "../../../../services/courses";
|
import { getCourse } from "../../../../services/courses";
|
||||||
import { getOrganizationContextInfo } from "../../../../services/orgs";
|
import { getOrganizationContextInfo } from "../../../../services/orgs";
|
||||||
|
|
||||||
const CourseIdPage = () => {
|
const CourseIdPage = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { courseid } = router.query;
|
const { courseid } = router.query;
|
||||||
const { orgslug } = router.query;
|
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = React.useState(true);
|
const [isLoading, setIsLoading] = React.useState(true);
|
||||||
const [courseInfo, setCourseInfo] = React.useState("") as any;
|
const [courseInfo, setCourseInfo] = React.useState({}) as any;
|
||||||
|
|
||||||
async function fetchCourseInfo() {
|
async function fetchCourseInfo() {
|
||||||
const orgid = await getOrganizationContextInfo(orgslug);
|
const course = await getCourse("course_" + courseid);
|
||||||
const response = await getCourse(courseid, orgid);
|
|
||||||
const data = await response.json();
|
setCourseInfo(course);
|
||||||
setCourseInfo(data);
|
console.log(courseInfo);
|
||||||
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -26,7 +28,36 @@ const CourseIdPage = () => {
|
||||||
}
|
}
|
||||||
}, [isLoading, router.isReady]);
|
}, [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;
|
export default CourseIdPage;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import React from "react";
|
||||||
import { Header } from "../../../../components/ui/header";
|
import { Header } from "../../../../components/ui/header";
|
||||||
import Layout from "../../../../components/ui/layout";
|
import Layout from "../../../../components/ui/layout";
|
||||||
import { Title } from "../../../../components/ui/styles/title";
|
import { Title } from "../../../../components/ui/styles/title";
|
||||||
import { getOrgCourses } from "../../../../services/courses";
|
import { deleteCourseFromBackend, getOrgCourses } from "../../../../services/courses";
|
||||||
import { getOrganizationContextInfo } from "../../../../services/orgs";
|
import { getOrganizationContextInfo } from "../../../../services/orgs";
|
||||||
|
|
||||||
const CoursesIndexPage = () => {
|
const CoursesIndexPage = () => {
|
||||||
|
|
@ -22,6 +22,12 @@ const CoursesIndexPage = () => {
|
||||||
setIsLoading(false);
|
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 to remove "course_" from the course_id
|
||||||
function removeCoursePrefix(course_id: string) {
|
function removeCoursePrefix(course_id: string) {
|
||||||
return course_id.replace("course_", "");
|
return course_id.replace("course_", "");
|
||||||
|
|
@ -55,9 +61,12 @@ const CoursesIndexPage = () => {
|
||||||
<div>
|
<div>
|
||||||
{courses.map((course: any) => (
|
{courses.map((course: any) => (
|
||||||
<div key={course.course_id}>
|
<div key={course.course_id}>
|
||||||
<Link href={"/org/" + orgslug + "/courses/" + removeCoursePrefix(course.course_id)}>
|
<Link href={"/org/" + orgslug + "/course/" + removeCoursePrefix(course.course_id)}>
|
||||||
<a><h2>{course.name}</h2></a>
|
<a>
|
||||||
|
<h2>{course.name}</h2>
|
||||||
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
|
<button onClick={() => deleteCourses(course.course_id)}>Delete</button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,21 +3,67 @@ import React from "react";
|
||||||
import { Header } from "../../../../../components/ui/header";
|
import { Header } from "../../../../../components/ui/header";
|
||||||
import Layout from "../../../../../components/ui/layout";
|
import Layout from "../../../../../components/ui/layout";
|
||||||
import { Title } from "../../../../../components/ui/styles/title";
|
import { Title } from "../../../../../components/ui/styles/title";
|
||||||
|
import { createNewCourse } from "../../../../../services/courses";
|
||||||
|
import { getOrganizationContextInfo } from "../../../../../services/orgs";
|
||||||
|
|
||||||
const NewCoursePage = () => {
|
const NewCoursePage = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { orgslug } = router.query;
|
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 (
|
return (
|
||||||
<Layout title="New course">
|
<Layout title="New course">
|
||||||
<Header></Header>
|
<Header></Header>
|
||||||
<Title>New Course </Title>
|
<Title>New Course </Title>
|
||||||
<hr />
|
<hr />
|
||||||
Name : <input type="text" /> <br />
|
Name : <input onChange={handleNameChange} type="text" /> <br />
|
||||||
Description : <input type="text" /> <br />
|
Description : <input onChange={handleDescriptionChange} type="text" /> <br />
|
||||||
Cover Photo : <input type="file" /> <br />
|
Cover Photo : <input onChange={handleThumbnailChange} type="file" /> <br />
|
||||||
Learnings (separated by ; ) : <textarea id="story" name="story" rows={5} cols={33} /> <br />
|
Learnings (empty for now) (separated by ; ) : <textarea id="story" name="story" rows={5} cols={33} /> <br />
|
||||||
<button>Create</button>
|
<button onClick={handleSubmit}>Create</button>
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ const Organizations = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteOrganization(org_id:any) {
|
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);
|
const newOrganizations = userOrganizations.filter((org:any) => org.org_id !== org_id);
|
||||||
setUserOrganizations(newOrganizations);
|
setUserOrganizations(newOrganizations);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
const LEARNHOUSE_API_URL = "http://localhost:1338/api/";
|
const LEARNHOUSE_API_URL = "http://localhost:1338/api/";
|
||||||
|
const LEARNHOUSE_BACKEND_URL = "http://localhost:1338/";
|
||||||
|
|
||||||
export const getAPIUrl = () => LEARNHOUSE_API_URL;
|
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));
|
.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 HeadersConfig = new Headers({ "Content-Type": "application/json" });
|
||||||
|
|
||||||
const requestOptions: any = {
|
const requestOptions: any = {
|
||||||
|
|
@ -30,3 +30,42 @@ export async function getCourse(org_id: any, course_id: any) {
|
||||||
.then((result) => result.json())
|
.then((result) => result.json())
|
||||||
.catch((error) => console.log("error", error));
|
.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}")
|
@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
|
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}")
|
@router.get("/{org_id}/page/{page}/limit/{limit}")
|
||||||
|
|
|
||||||
|
|
@ -62,11 +62,11 @@ class CourseChapterInDB(CourseChapter):
|
||||||
|
|
||||||
# Courses
|
# 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()
|
await check_database()
|
||||||
courses = learnhouseDB["courses"]
|
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
|
# verify course rights
|
||||||
await verify_rights(course_id, current_user, "read")
|
await verify_rights(course_id, current_user, "read")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue