diff --git a/config/config.py b/config/config.py index caeead27..024728f3 100644 --- a/config/config.py +++ b/config/config.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Literal, Optional from pydantic import BaseModel import os import yaml @@ -22,6 +22,16 @@ class SecurityConfig(BaseModel): auth_jwt_secret_key: str +class S3ApiConfig(BaseModel): + bucket_name: str | None + endpoint_url: str | None + + +class ContentDeliveryConfig(BaseModel): + type: Literal["filesystem", "s3api"] + s3api: S3ApiConfig + + class HostingConfig(BaseModel): domain: str ssl: bool @@ -31,6 +41,7 @@ class HostingConfig(BaseModel): self_hosted: bool sentry_config: Optional[SentryConfig] cookie_config: CookieConfig + content_delivery: ContentDeliveryConfig class DatabaseConfig(BaseModel): @@ -116,6 +127,33 @@ def get_learnhouse_config() -> LearnHouseConfig: ).get("domain") cookie_config = CookieConfig(domain=cookies_domain) + env_content_delivery_type = os.environ.get("LEARNHOUSE_CONTENT_DELIVERY_TYPE") + content_delivery_type: str = ( + (yaml_config.get("hosting_config", {}).get("content_delivery", {}).get("type")) + or env_content_delivery_type + or "filesystem" + ) # default to filesystem + + env_bucket_name = os.environ.get("LEARNHOUSE_S3_API_BUCKET_NAME") + env_endpoint_url = os.environ.get("LEARNHOUSE_S3_API_ENDPOINT_URL") + bucket_name = ( + yaml_config.get("hosting_config", {}) + .get("content_delivery", {}) + .get("s3api", {}) + .get("bucket_name") + ) or env_bucket_name + endpoint_url = ( + yaml_config.get("hosting_config", {}) + .get("content_delivery", {}) + .get("s3api", {}) + .get("endpoint_url") + ) or env_endpoint_url + + content_delivery = ContentDeliveryConfig( + type=content_delivery_type, # type: ignore + s3api=S3ApiConfig(bucket_name=bucket_name, endpoint_url=endpoint_url), # type: ignore + ) + # Database config mongodb_connection_string = env_mongodb_connection_string or yaml_config.get( "database_config", {} @@ -158,6 +196,7 @@ def get_learnhouse_config() -> LearnHouseConfig: self_hosted=bool(self_hosted), sentry_config=sentry_config, cookie_config=cookie_config, + content_delivery=content_delivery, ) database_config = DatabaseConfig( mongodb_connection_string=mongodb_connection_string diff --git a/config/config.yaml b/config/config.yaml index b381699f..bf151243 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -17,6 +17,11 @@ hosting_config: cookies_config: domain: ".localhost" allowed_regexp: '\b((?:https?://)[^\s/$.?#].[^\s]*)\b' + content_delivery: + type: "filesystem" # "filesystem" or "s3api" + s3api: + bucket_name: "" + endpoint_url: "" database_config: mongodb_connection_string: mongodb://learnhouse:learnhouse@mongo:27017/ diff --git a/content/__init__.py b/content/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/front/app/orgs/[orgslug]/(withmenu)/collection/[collectionid]/page.tsx b/front/app/orgs/[orgslug]/(withmenu)/collection/[collectionid]/page.tsx index f96b7734..104ef05d 100644 --- a/front/app/orgs/[orgslug]/(withmenu)/collection/[collectionid]/page.tsx +++ b/front/app/orgs/[orgslug]/(withmenu)/collection/[collectionid]/page.tsx @@ -1,63 +1,64 @@ import GeneralWrapperStyled from "@components/StyledElements/Wrappers/GeneralWrapper"; import { getBackendUrl, getUriWithOrg } from "@services/config/config"; import { getCollectionByIdWithAuthHeader } from "@services/courses/collections"; +import { getCourseThumbnailMediaDirectory } from "@services/media/media"; import { getOrganizationContextInfo } from "@services/organizations/orgs"; import { Metadata } from "next"; import { cookies } from "next/headers"; import Link from "next/link"; type MetadataProps = { - params: { orgslug: string, courseid: string, collectionid: string }; - searchParams: { [key: string]: string | string[] | undefined }; + params: { orgslug: string, courseid: string, collectionid: string }; + searchParams: { [key: string]: string | string[] | undefined }; }; export async function generateMetadata( - { params }: MetadataProps, + { params }: MetadataProps, ): Promise { - const cookieStore = cookies(); - const access_token_cookie: any = cookieStore.get('access_token_cookie'); - // Get Org context information - const org = await getOrganizationContextInfo(params.orgslug, { revalidate: 1800, tags: ['organizations'] }); - const col = await getCollectionByIdWithAuthHeader(params.collectionid, access_token_cookie ? access_token_cookie.value : null, { revalidate: 0, tags: ['collections'] }); - - console.log(col) + const cookieStore = cookies(); + const access_token_cookie: any = cookieStore.get('access_token_cookie'); + // Get Org context information + const org = await getOrganizationContextInfo(params.orgslug, { revalidate: 1800, tags: ['organizations'] }); + const col = await getCollectionByIdWithAuthHeader(params.collectionid, access_token_cookie ? access_token_cookie.value : null, { revalidate: 0, tags: ['collections'] }); - return { - title: `Collection : ${col.name} — ${org.name}`, - description: `${col.description} `, - }; + console.log(col) + + return { + title: `Collection : ${col.name} — ${org.name}`, + description: `${col.description} `, + }; } -const CollectionPage = async (params : any) => { - const cookieStore = cookies(); - const access_token_cookie: any = cookieStore.get('access_token_cookie'); - const orgslug = params.params.orgslug; - const col = await getCollectionByIdWithAuthHeader(params.params.collectionid, access_token_cookie ? access_token_cookie.value : null, { revalidate: 0, tags: ['collections'] }); +const CollectionPage = async (params: any) => { + const cookieStore = cookies(); + const access_token_cookie: any = cookieStore.get('access_token_cookie'); + const orgslug = params.params.orgslug; + const col = await getCollectionByIdWithAuthHeader(params.params.collectionid, access_token_cookie ? access_token_cookie.value : null, { revalidate: 0, tags: ['collections'] }); - const removeCoursePrefix = (courseid: string) => { - return courseid.replace("course_", "") - } + const removeCoursePrefix = (courseid: string) => { + return courseid.replace("course_", "") + } - return -

Collection

-

{col.name}

-
-
- {col.courses.map((course: any) => ( -
- -
-
- -

{course.name}

+ return +

Collection

+

{col.name}

+
+
+ {col.courses.map((course: any) => ( +
+ +
- ))} + +

{course.name}

+ ))} +
-
; + ; }; export default CollectionPage; \ No newline at end of file diff --git a/front/app/orgs/[orgslug]/(withmenu)/collections/page.tsx b/front/app/orgs/[orgslug]/(withmenu)/collections/page.tsx index a933fab0..5bb3ca38 100644 --- a/front/app/orgs/[orgslug]/(withmenu)/collections/page.tsx +++ b/front/app/orgs/[orgslug]/(withmenu)/collections/page.tsx @@ -8,6 +8,7 @@ import { Metadata } from "next"; import { cookies } from "next/headers"; import Link from "next/link"; import CollectionAdminEditsArea from "./admin"; +import { getCourseThumbnailMediaDirectory } from "@services/media/media"; type MetadataProps = { params: { orgslug: string, courseid: string }; @@ -45,9 +46,9 @@ const CollectionsPage = async (params: any) => {
- - - + + +
@@ -60,7 +61,7 @@ const CollectionsPage = async (params: any) => {
{collection.courses.slice(0, 3).map((course: any) => ( - {course.name} + {course.name} ))}
diff --git a/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/activity/[activityid]/activity.tsx b/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/activity/[activityid]/activity.tsx index b2bee0a7..77d3319f 100644 --- a/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/activity/[activityid]/activity.tsx +++ b/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/activity/[activityid]/activity.tsx @@ -11,6 +11,7 @@ import ActivityIndicators from "@components/Pages/Courses/ActivityIndicators"; import GeneralWrapperStyled from "@components/StyledElements/Wrappers/GeneralWrapper"; import { useRouter } from "next/navigation"; import AuthenticatedClientElement from "@components/Security/AuthenticatedClientElement"; +import { getCourseThumbnailMediaDirectory } from "@services/media/media"; interface ActivityClientProps { activityid: string; @@ -49,7 +50,7 @@ function ActivityClient(props: ActivityClientProps) {
- +
diff --git a/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/course.tsx b/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/course.tsx index 97958e52..d1aafc72 100644 --- a/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/course.tsx +++ b/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/course.tsx @@ -12,6 +12,7 @@ import { revalidateTags } from "@services/utils/ts/requests"; import ActivityIndicators from "@components/Pages/Courses/ActivityIndicators"; import { useRouter } from "next/navigation"; import GeneralWrapperStyled from "@components/StyledElements/Wrappers/GeneralWrapper"; +import { getCourseThumbnailMediaDirectory } from "@services/media/media"; const CourseClient = (props: any) => { const courseid = props.courseid; @@ -55,7 +56,7 @@ const CourseClient = (props: any) => {
-
+
diff --git a/front/app/orgs/[orgslug]/(withmenu)/courses/courses.tsx b/front/app/orgs/[orgslug]/(withmenu)/courses/courses.tsx index e20dd860..7b860abb 100644 --- a/front/app/orgs/[orgslug]/(withmenu)/courses/courses.tsx +++ b/front/app/orgs/[orgslug]/(withmenu)/courses/courses.tsx @@ -15,6 +15,7 @@ import { useRouter } from 'next/navigation'; import GeneralWrapperStyled from '@components/StyledElements/Wrappers/GeneralWrapper'; import TypeOfContentTitle from '@components/StyledElements/Titles/TypeOfContentTitle'; import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement'; +import { getCourseThumbnailMediaDirectory } from '@services/media/media'; interface CourseProps { orgslug: string; @@ -73,7 +74,7 @@ function Courses(props: CourseProps) {
-
+
diff --git a/front/app/orgs/[orgslug]/(withmenu)/page.tsx b/front/app/orgs/[orgslug]/(withmenu)/page.tsx index 046bda78..a6f7d4b7 100644 --- a/front/app/orgs/[orgslug]/(withmenu)/page.tsx +++ b/front/app/orgs/[orgslug]/(withmenu)/page.tsx @@ -11,6 +11,7 @@ import { getOrganizationContextInfo } from '@services/organizations/orgs'; import { cookies } from 'next/headers'; import GeneralWrapperStyled from '@components/StyledElements/Wrappers/GeneralWrapper'; import TypeOfContentTitle from '@components/StyledElements/Titles/TypeOfContentTitle'; +import { getCourseThumbnailMediaDirectory } from '@services/media/media'; type MetadataProps = { params: { orgslug: string }; @@ -63,7 +64,7 @@ const OrgHomePage = async (params: any) => {
{collection.courses.slice(0, 3).map((course: any) => ( - {course.name} + {course.name} ))}
@@ -80,7 +81,7 @@ const OrgHomePage = async (params: any) => { {courses.map((course: any) => (
-
+

{course.name}

diff --git a/front/components/Objects/Editor/Extensions/Image/ImageBlockComponent.tsx b/front/components/Objects/Editor/Extensions/Image/ImageBlockComponent.tsx index 0825fa13..02fdb9a6 100644 --- a/front/components/Objects/Editor/Extensions/Image/ImageBlockComponent.tsx +++ b/front/components/Objects/Editor/Extensions/Image/ImageBlockComponent.tsx @@ -7,12 +7,14 @@ import * as AspectRatio from '@radix-ui/react-aspect-ratio'; import { AlertCircle, AlertTriangle, Image, ImagePlus, Info } from "lucide-react"; import { getImageFile, uploadNewImageFile } from "../../../../../services/blocks/Image/images"; import { getBackendUrl } from "../../../../../services/config/config"; +import { getActivityBlockMediaDirectory } from "@services/media/media"; function ImageBlockComponent(props: any) { const [image, setImage] = React.useState(null); const [isLoading, setIsLoading] = React.useState(false); const [blockObject, setblockObject] = React.useState(props.node.attrs.blockObject); const [imageSize, setImageSize] = React.useState({ width: props.node.attrs.size ? props.node.attrs.size.width : 300 }); + const fileId = blockObject ? `${blockObject.block_data.file_id}.${blockObject.block_data.file_format}` : null; const handleImageChange = (event: React.ChangeEvent) => { setImage(event.target.files[0]); @@ -30,11 +32,6 @@ function ImageBlockComponent(props: any) { }); }; - console.log(props.node.attrs); - console.log(imageSize); - - - return ( {!blockObject && ( @@ -79,10 +76,14 @@ function ImageBlockComponent(props: any) { + {blockObject.block_id} diff --git a/front/components/Objects/Editor/Extensions/PDF/PDFBlockComponent.tsx b/front/components/Objects/Editor/Extensions/PDF/PDFBlockComponent.tsx index 52c125d0..da38f04f 100644 --- a/front/components/Objects/Editor/Extensions/PDF/PDFBlockComponent.tsx +++ b/front/components/Objects/Editor/Extensions/PDF/PDFBlockComponent.tsx @@ -4,11 +4,13 @@ import styled from "styled-components"; import { AlertCircle, AlertTriangle, FileText, Image, ImagePlus, Info } from "lucide-react"; import { getPDFFile, uploadNewPDFFile } from "../../../../../services/blocks/Pdf/pdf"; import { getBackendUrl } from "../../../../../services/config/config"; +import { getActivityBlockMediaDirectory } from "@services/media/media"; function PDFBlockComponent(props: any) { const [pdf, setPDF] = React.useState(null); const [isLoading, setIsLoading] = React.useState(false); const [blockObject, setblockObject] = React.useState(props.node.attrs.blockObject); + const fileId = blockObject ? `${blockObject.block_data.file_id}.${blockObject.block_data.file_format}` : null; const handlePDFChange = (event: React.ChangeEvent) => { setPDF(event.target.files[0]); @@ -41,9 +43,11 @@ function PDFBlockComponent(props: any) { {blockObject && (