diff --git a/app.py b/app.py index da8c358c..9dce8265 100644 --- a/app.py +++ b/app.py @@ -1,15 +1,13 @@ -import logging from fastapi import FastAPI, Request from config.config import LearnHouseConfig, get_learnhouse_config from src.core.events.events import shutdown_app, startup_app -from src.main import global_router +from src.router import v1_router from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from fastapi.staticfiles import StaticFiles from fastapi_jwt_auth.exceptions import AuthJWTException from fastapi.middleware.gzip import GZipMiddleware -from src.services.mocks.initial import create_initial_data # from src.services.mocks.initial import create_initial_data @@ -62,7 +60,7 @@ def authjwt_exception_handler(request: Request, exc: AuthJWTException): # Global Routes -app.include_router(global_router) +app.include_router(v1_router) # General Routes @@ -71,18 +69,4 @@ app.include_router(global_router) async def root(): return {"Message": "Welcome to LearnHouse ✨"} -# Get config - -@ app.get("/config") -async def config(): - logging.info("Getting config") - config = get_learnhouse_config() - return config.dict() - - -@app.get("/initial_data") -async def initial_data(request: Request): - - await create_initial_data(request) - return {"Message": "Initial data created 🤖"} diff --git a/config/config.py b/config/config.py index cad437a3..caeead27 100644 --- a/config/config.py +++ b/config/config.py @@ -14,6 +14,14 @@ class CookieConfig(BaseModel): domain: str +class GeneralConfig(BaseModel): + development_mode: bool + + +class SecurityConfig(BaseModel): + auth_jwt_secret_key: str + + class HostingConfig(BaseModel): domain: str ssl: bool @@ -33,8 +41,10 @@ class LearnHouseConfig(BaseModel): site_name: str site_description: str contact_email: str + general_config: GeneralConfig hosting_config: HostingConfig database_config: DatabaseConfig + security_config: SecurityConfig def get_learnhouse_config() -> LearnHouseConfig: @@ -44,7 +54,19 @@ def get_learnhouse_config() -> LearnHouseConfig: # Load the YAML file with open(yaml_path, "r") as f: yaml_config = yaml.safe_load(f) - + + # General Config + env_development_mode = os.environ.get("LEARNHOUSE_DEVELOPMENT_MODE") + development_mode = env_development_mode or yaml_config.get("general", {}).get( + "development_mode" + ) + + # Security Config + env_auth_jwt_secret_key = os.environ.get("LEARNHOUSE_AUTH_JWT_SECRET_KEY") + auth_jwt_secret_key = env_auth_jwt_secret_key or yaml_config.get( + "security", {} + ).get("auth_jwt_secret_key") + # Check if environment variables are defined env_site_name = os.environ.get("LEARNHOUSE_SITE_NAME") env_site_description = os.environ.get("LEARNHOUSE_SITE_DESCRIPTION") @@ -89,10 +111,10 @@ def get_learnhouse_config() -> LearnHouseConfig: "self_hosted" ) - cookies_domain = env_cookie_domain or yaml_config.get("hosting_config", {}).get("cookies_config", {}).get("domain") - cookie_config = CookieConfig( - domain=cookies_domain - ) + cookies_domain = env_cookie_domain or yaml_config.get("hosting_config", {}).get( + "cookies_config", {} + ).get("domain") + cookie_config = CookieConfig(domain=cookies_domain) # Database config mongodb_connection_string = env_mongodb_connection_string or yaml_config.get( @@ -146,8 +168,10 @@ def get_learnhouse_config() -> LearnHouseConfig: site_name=site_name, site_description=site_description, contact_email=contact_email, + general_config=GeneralConfig(development_mode=bool(development_mode)), hosting_config=hosting_config, database_config=database_config, + security_config=SecurityConfig(auth_jwt_secret_key=auth_jwt_secret_key), ) return config diff --git a/config/config.yaml b/config/config.yaml index c9bb893c..b381699f 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -2,6 +2,12 @@ site_name: LearnHouse site_description: LearnHouse is an open-source platform tailored for learning experiences. contact_email: hi@learnhouse.app +general: + development_mode: true + +security: + auth_jwt_secret_key: secret + hosting_config: domain: learnhouse.app ssl: true diff --git a/front/.eslintrc b/front/.eslintrc new file mode 100644 index 00000000..af133940 --- /dev/null +++ b/front/.eslintrc @@ -0,0 +1,8 @@ +{ + "extends": "next", + "rules": { + "react/no-unescaped-entities": "off", + "@next/next/no-page-custom-font": "off", + "@next/next/no-img-element": "off" + } +} diff --git a/front/.eslintrc.json b/front/.eslintrc.json deleted file mode 100644 index bffb357a..00000000 --- a/front/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "next/core-web-vitals" -} diff --git a/front/app/orgs/[orgslug]/(withmenu)/collections/admin.tsx b/front/app/orgs/[orgslug]/(withmenu)/collections/admin.tsx index 2af0c9bc..e32eb804 100644 --- a/front/app/orgs/[orgslug]/(withmenu)/collections/admin.tsx +++ b/front/app/orgs/[orgslug]/(withmenu)/collections/admin.tsx @@ -14,13 +14,13 @@ const CollectionAdminEditsArea = (props: any) => { const deleteCollectionUI = async (collectionId: number) => { await deleteCollection(collectionId); - revalidateTags(["collections"]); + revalidateTags(["collections"], props.orgslug); // reload the page router.refresh(); router.push(getUriWithOrg(props.orgslug, "/collections")); // refresh page (FIX for Next.js BUG) - window.location.reload(); + //window.location.reload(); } return ( diff --git a/front/app/orgs/[orgslug]/(withmenu)/collections/new/page.tsx b/front/app/orgs/[orgslug]/(withmenu)/collections/new/page.tsx index a440348b..41f05ff0 100644 --- a/front/app/orgs/[orgslug]/(withmenu)/collections/new/page.tsx +++ b/front/app/orgs/[orgslug]/(withmenu)/collections/new/page.tsx @@ -43,13 +43,11 @@ function NewCollection(params: any) { org_id: org.org_id, }; await createCollection(collection); - revalidateTags(["collections"]); + revalidateTags(["collections"], orgslug); router.prefetch(getUriWithOrg(orgslug, "/collections")); router.push(getUriWithOrg(orgslug, "/collections")); router.refresh(); - // refresh page (FIX for Next.js BUG) - window.location.reload(); }; 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 e9f94228..b2bee0a7 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 @@ -10,6 +10,7 @@ import DocumentPdfActivity from "@components/Pages/Activities/DocumentPdf/Docume 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"; interface ActivityClientProps { activityid: string; @@ -64,8 +65,10 @@ function ActivityClient(props: ActivityClientProps) {

{activity.name}

- + + +
diff --git a/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/course.tsx b/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/course.tsx index adf51412..97958e52 100644 --- a/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/course.tsx +++ b/front/app/orgs/[orgslug]/(withmenu)/course/[courseid]/course.tsx @@ -22,7 +22,7 @@ const CourseClient = (props: any) => { async function startCourseUI() { // Create activity await startCourse("course_" + courseid, orgslug); - revalidateTags(['courses']); + revalidateTags(['courses'], orgslug); router.refresh(); // refresh page (FIX for Next.js BUG) @@ -33,7 +33,7 @@ const CourseClient = (props: any) => { // Close activity let activity = await removeCourse("course_" + courseid, orgslug); // Mutate course - revalidateTags(['courses']); + revalidateTags(['courses'], orgslug); router.refresh(); // refresh page (FIX for Next.js BUG) diff --git a/front/app/orgs/[orgslug]/(withmenu)/courses/courses.tsx b/front/app/orgs/[orgslug]/(withmenu)/courses/courses.tsx index 1257e34b..e20dd860 100644 --- a/front/app/orgs/[orgslug]/(withmenu)/courses/courses.tsx +++ b/front/app/orgs/[orgslug]/(withmenu)/courses/courses.tsx @@ -34,7 +34,7 @@ function Courses(props: CourseProps) { async function deleteCourses(course_id: any) { await deleteCourseFromBackend(course_id); - revalidateTags(['courses']); + revalidateTags(['courses'], orgslug); router.refresh(); } diff --git a/front/app/orgs/[orgslug]/(withmenu)/courses/page.tsx b/front/app/orgs/[orgslug]/(withmenu)/courses/page.tsx index fb3238f3..027e7719 100644 --- a/front/app/orgs/[orgslug]/(withmenu)/courses/page.tsx +++ b/front/app/orgs/[orgslug]/(withmenu)/courses/page.tsx @@ -1,9 +1,10 @@ import React from "react"; import Courses from "./courses"; -import { getOrgCourses } from "@services/courses/courses"; +import { getOrgCoursesWithAuthHeader } from "@services/courses/courses"; import { Metadata } from "next"; import { getOrganizationContextInfo } from "@services/organizations/orgs"; +import { cookies } from "next/headers"; type MetadataProps = { params: { orgslug: string }; @@ -24,7 +25,9 @@ export async function generateMetadata( const CoursesPage = async (params: any) => { const orgslug = params.params.orgslug; - const courses = await getOrgCourses(orgslug, { revalidate: 0, tags: ['courses'] }); + const cookieStore = cookies(); + const access_token_cookie: any = cookieStore.get('access_token_cookie'); + const courses = await getOrgCoursesWithAuthHeader(orgslug, { revalidate: 0, tags: ['courses'] }, access_token_cookie ? access_token_cookie.value : null); return (
diff --git a/front/app/orgs/[orgslug]/login/page.tsx b/front/app/orgs/[orgslug]/login/page.tsx index aaad485c..08aa23a7 100644 --- a/front/app/orgs/[orgslug]/login/page.tsx +++ b/front/app/orgs/[orgslug]/login/page.tsx @@ -10,8 +10,8 @@ import Toast from '@components/StyledElements/Toast/Toast'; import { toast } from 'react-hot-toast'; const Login = () => { - const [email, setEmail] = React.useState("admin@admin.admin"); - const [password, setPassword] = React.useState("admin"); + const [email, setEmail] = React.useState(""); + const [password, setPassword] = React.useState(""); const [isSubmitting, setIsSubmitting] = useState(false); const router = useRouter(); @@ -44,13 +44,13 @@ const Login = () => { return (
- + Email - Please provide an email + Please provide an email @@ -59,7 +59,7 @@ const Login = () => { Password - Please provide a password + Please provide a password diff --git a/front/app/orgs/[orgslug]/settings/layout.tsx b/front/app/orgs/[orgslug]/settings/layout.tsx index 10402e16..3e2ff305 100644 --- a/front/app/orgs/[orgslug]/settings/layout.tsx +++ b/front/app/orgs/[orgslug]/settings/layout.tsx @@ -6,38 +6,45 @@ import LearnHouseWhiteLogo from '@public/learnhouse_text_white.png'; import AuthProvider, { AuthContext } from '@components/Security/AuthProvider'; import Avvvatars from 'avvvatars-react'; import Image from 'next/image'; +import AuthenticatedClientElement from '@components/Security/AuthenticatedClientElement'; +import { getOrganizationContextInfo } from '@services/organizations/orgs'; -function SettingsLayout({ children, params }: { children: React.ReactNode, params: any }) { +async function SettingsLayout({ children, params }: { children: React.ReactNode, params: any }) { const auth: any = React.useContext(AuthContext); + const orgslug = params.orgslug; + + let org = await getOrganizationContextInfo(orgslug, {}); return ( - <> - -
- - - - Learnhouse logo - {auth.isAuthenticated && ( - - )} - - - Account -
    -
  • Profile
  • -
  • Passwords
  • -
- Organization -
    -
  • General
  • -
-
-
- - {children} - -
+ <> + +
+ + + + Learnhouse logo + {auth.isAuthenticated && ( + + )} + + + Account +
    +
  • Profile
  • +
  • Passwords
  • +
+ + Organization +
    +
  • General
  • +
+
+
+
+ + {children} + +
) } @@ -59,7 +66,7 @@ const LeftWrapper = styled('div', { const LeftTopArea = styled('div', { display: 'flex', marginLeft: '20px', - + alignItems: 'center', img: { @@ -72,7 +79,7 @@ const LeftTopArea = styled('div', { placeContent: 'center', } - + }) const LeftMenuWrapper = styled('div', { diff --git a/front/app/orgs/[orgslug]/settings/organization/general/organization.tsx b/front/app/orgs/[orgslug]/settings/organization/general/organization.tsx index 69830d16..116c919b 100644 --- a/front/app/orgs/[orgslug]/settings/organization/general/organization.tsx +++ b/front/app/orgs/[orgslug]/settings/organization/general/organization.tsx @@ -33,12 +33,9 @@ function OrganizationClient(props: any) { let org_id = org.org_id; await uploadOrganizationLogo(org_id, selectedFile); setSelectedFile(null); // Reset the selected file - revalidateTags(['organizations']); + revalidateTags(['organizations'], org.slug); router.refresh(); - // refresh page (FIX for Next.js BUG) - window.location.reload(); - } }; @@ -55,6 +52,10 @@ function OrganizationClient(props: any) { const updateOrg = async (values: OrganizationValues) => { let org_id = org.org_id; await updateOrganization(org_id, values); + + // Mutate the org + revalidateTags(['organizations'], org.slug); + router.refresh(); } diff --git a/front/components/Objects/Editor/Editor.tsx b/front/components/Objects/Editor/Editor.tsx index a9b77fb8..9bcd84d2 100644 --- a/front/components/Objects/Editor/Editor.tsx +++ b/front/components/Objects/Editor/Editor.tsx @@ -348,6 +348,42 @@ export const EditorContentWrapper = styled.div` // disable chrome outline .ProseMirror { + + h1 { + font-size: 30px; + font-weight: 600; + margin-top: 10px; + margin-bottom: 10px; + } + + h2 { + font-size: 25px; + font-weight: 600; + margin-top: 10px; + margin-bottom: 10px; + } + + h3 { + font-size: 20px; + font-weight: 600; + margin-top: 10px; + margin-bottom: 10px; + } + + h4 { + font-size: 18px; + font-weight: 600; + margin-top: 10px; + margin-bottom: 10px; + } + + h5 { + font-size: 16px; + font-weight: 600; + margin-top: 10px; + margin-bottom: 10px; + } + padding-left: 20px; padding-right: 20px; padding-bottom: 20px; diff --git a/front/components/Objects/Loaders/PageLoading.tsx b/front/components/Objects/Loaders/PageLoading.tsx index 809b2b80..9954b338 100644 --- a/front/components/Objects/Loaders/PageLoading.tsx +++ b/front/components/Objects/Loaders/PageLoading.tsx @@ -1,8 +1,24 @@ -import React from 'react' +'use client'; +import { motion } from "framer-motion"; + +const variants = { + hidden: { opacity: 0, x: 0, y: 0 }, + enter: { opacity: 1, x: 0, y: 0 }, + exit: { opacity: 0, x: 0, y: 0 }, + }; function PageLoading() { + return ( -
+ +
@@ -10,6 +26,7 @@ function PageLoading() {
+
) } diff --git a/front/components/Objects/Modals/Course/Create/CreateCourse.tsx b/front/components/Objects/Modals/Course/Create/CreateCourse.tsx index 5e7c0fc2..e9870b9f 100644 --- a/front/components/Objects/Modals/Course/Create/CreateCourse.tsx +++ b/front/components/Objects/Modals/Course/Create/CreateCourse.tsx @@ -43,7 +43,7 @@ function CreateCourseModal({ closeModal, orgslug }: any) { e.preventDefault(); setIsSubmitting(true); let status = await createNewCourse(orgId, { name, description }, thumbnail); - revalidateTags(['courses']); + revalidateTags(['courses'], orgslug); setIsSubmitting(false); if (status.org_id == orgId) { diff --git a/front/components/Pages/Trail/TrailCourseElement.tsx b/front/components/Pages/Trail/TrailCourseElement.tsx index dd32c5c8..c2d9ec6a 100644 --- a/front/components/Pages/Trail/TrailCourseElement.tsx +++ b/front/components/Pages/Trail/TrailCourseElement.tsx @@ -18,7 +18,7 @@ function TrailCourseElement(props: TrailCourseElementProps) { // Close activity let activity = await removeCourse(course_id, props.orgslug); // Mutate course - revalidateTags(['courses']); + revalidateTags(['courses'], props.orgslug); // Mutate mutate(`${getAPIUrl()}trail/org_slug/${props.orgslug}/trail`); diff --git a/front/components/Security/AuthenticatedClientElement.tsx b/front/components/Security/AuthenticatedClientElement.tsx index 8e442352..6ced1f88 100644 --- a/front/components/Security/AuthenticatedClientElement.tsx +++ b/front/components/Security/AuthenticatedClientElement.tsx @@ -9,7 +9,7 @@ interface AuthenticatedClientElementProps { } -function AuthenticatedClientElement(props: AuthenticatedClientElementProps) { +export const AuthenticatedClientElement = (props: AuthenticatedClientElementProps) => { const auth: any = React.useContext(AuthContext); // Available roles diff --git a/front/components/StyledElements/Wrappers/GeneralWrapper.tsx b/front/components/StyledElements/Wrappers/GeneralWrapper.tsx index e6eb908c..5b42ad8e 100644 --- a/front/components/StyledElements/Wrappers/GeneralWrapper.tsx +++ b/front/components/StyledElements/Wrappers/GeneralWrapper.tsx @@ -3,6 +3,7 @@ function GeneralWrapperStyled({ children }: { children: React.ReactNode }) { return (
{children}
) } diff --git a/front/package-lock.json b/front/package-lock.json index 8f46da95..fa7645e5 100644 --- a/front/package-lock.json +++ b/front/package-lock.json @@ -51,7 +51,7 @@ "@types/styled-components": "^5.1.26", "@types/uuid": "^9.0.0", "autoprefixer": "^10.4.14", - "eslint": "8.23.1", + "eslint": "^8.43.0", "eslint-config-next": "^13.0.6", "postcss": "^8.4.23", "tailwindcss": "^3.3.2", @@ -1930,16 +1930,40 @@ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.2.tgz", - "integrity": "sha512-AXYd23w1S/bv3fTs3Lz0vjiYemS08jWkI3hYyS9I1ry+0f+Yjs1wm+sU0BS8qDOPrBIkp4qHYC16I8uVtpLajQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", + "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.15.0", + "espree": "^9.5.2", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -1953,6 +1977,15 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@eslint/js": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.43.0.tgz", + "integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/@floating-ui/core": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz", @@ -1980,29 +2013,19 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", - "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==", + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", - "minimatch": "^3.0.4" + "minimatch": "^3.0.5" }, "engines": { "node": ">=10.10.0" } }, - "node_modules/@humanwhocodes/gitignore-to-minimatch": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", - "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -3770,9 +3793,9 @@ } }, "node_modules/acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", + "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -4663,39 +4686,40 @@ } }, "node_modules/eslint": { - "version": "8.23.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.23.1.tgz", - "integrity": "sha512-w7C1IXCc6fNqjpuYd0yPlcTKKmHlHHktRkzmBPZ+7cvNBQuiNjx0xaMTjAJGCafJhQkrFJooREv0CtrVzmHwqg==", + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.43.0.tgz", + "integrity": "sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==", "dev": true, "dependencies": { - "@eslint/eslintrc": "^1.3.2", - "@humanwhocodes/config-array": "^0.10.4", - "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.3", + "@eslint/js": "8.43.0", + "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.5.2", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", - "glob-parent": "^6.0.1", - "globals": "^13.15.0", - "globby": "^11.1.0", - "grapheme-splitter": "^1.0.4", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "js-sdsl": "^4.1.4", + "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", @@ -4703,7 +4727,6 @@ "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.1", - "regexpp": "^3.2.0", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" @@ -5014,9 +5037,9 @@ } }, "node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -5024,53 +5047,32 @@ }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^2.0.0" - }, - "engines": { - "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" }, "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=5" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/espree": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", - "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", + "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", "dev": true, "dependencies": { "acorn": "^8.8.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -5080,9 +5082,9 @@ } }, "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "dependencies": { "estraverse": "^5.1.0" @@ -5475,9 +5477,9 @@ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, "node_modules/globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -5546,10 +5548,10 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, "node_modules/has": { @@ -5888,6 +5890,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-reference": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", @@ -6001,12 +6012,6 @@ "jiti": "bin/jiti.js" } }, - "node_modules/js-sdsl": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.4.tgz", - "integrity": "sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw==", - "dev": true - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -7005,9 +7010,9 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", "dev": true, "engines": { "node": ">=6" @@ -7350,18 +7355,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, "node_modules/regexpu-core": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", @@ -9895,16 +9888,31 @@ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", "integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" }, + "@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.3.0" + } + }, + "@eslint-community/regexpp": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "dev": true + }, "@eslint/eslintrc": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.2.tgz", - "integrity": "sha512-AXYd23w1S/bv3fTs3Lz0vjiYemS08jWkI3hYyS9I1ry+0f+Yjs1wm+sU0BS8qDOPrBIkp4qHYC16I8uVtpLajQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", + "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.4.0", - "globals": "^13.15.0", + "espree": "^9.5.2", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -9912,6 +9920,12 @@ "strip-json-comments": "^3.1.1" } }, + "@eslint/js": { + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.43.0.tgz", + "integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==", + "dev": true + }, "@floating-ui/core": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz", @@ -9935,22 +9949,16 @@ } }, "@humanwhocodes/config-array": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", - "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==", + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", "dev": true, "requires": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", - "minimatch": "^3.0.4" + "minimatch": "^3.0.5" } }, - "@humanwhocodes/gitignore-to-minimatch": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", - "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", - "dev": true - }, "@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -11206,9 +11214,9 @@ } }, "acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", + "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", "dev": true }, "acorn-jsx": { @@ -11820,39 +11828,40 @@ "dev": true }, "eslint": { - "version": "8.23.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.23.1.tgz", - "integrity": "sha512-w7C1IXCc6fNqjpuYd0yPlcTKKmHlHHktRkzmBPZ+7cvNBQuiNjx0xaMTjAJGCafJhQkrFJooREv0CtrVzmHwqg==", + "version": "8.43.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.43.0.tgz", + "integrity": "sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==", "dev": true, "requires": { - "@eslint/eslintrc": "^1.3.2", - "@humanwhocodes/config-array": "^0.10.4", - "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.3", + "@eslint/js": "8.43.0", + "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.4.0", - "esquery": "^1.4.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.5.2", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", - "glob-parent": "^6.0.1", - "globals": "^13.15.0", - "globby": "^11.1.0", - "grapheme-splitter": "^1.0.4", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", "ignore": "^5.2.0", "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "js-sdsl": "^4.1.4", + "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", @@ -11860,7 +11869,6 @@ "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.1", - "regexpp": "^3.2.0", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" @@ -12094,53 +12102,36 @@ "requires": {} }, "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", "dev": true, "requires": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true - } - } - }, "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", "dev": true }, "espree": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", - "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", + "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", "dev": true, "requires": { "acorn": "^8.8.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.1" } }, "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, "requires": { "estraverse": "^5.1.0" @@ -12441,9 +12432,9 @@ "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" }, "globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -12495,10 +12486,10 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, - "grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, "has": { @@ -12724,6 +12715,12 @@ "has-tostringtag": "^1.0.0" } }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, "is-reference": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", @@ -12803,12 +12800,6 @@ "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==", "dev": true }, - "js-sdsl": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.4.tgz", - "integrity": "sha512-Y2/yD55y5jteOAmY50JbUZYwk3CP3wnLPEZnlR1w9oKhITrBEtAxwuWKebFf8hMrPMgbYwFoWK/lH2sBkErELw==", - "dev": true - }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -13533,9 +13524,9 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", "dev": true }, "queue-microtask": { @@ -13767,12 +13758,6 @@ "functions-have-names": "^1.2.2" } }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, "regexpu-core": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", diff --git a/front/package.json b/front/package.json index f39cbba5..c5b09c2d 100644 --- a/front/package.json +++ b/front/package.json @@ -52,7 +52,7 @@ "@types/styled-components": "^5.1.26", "@types/uuid": "^9.0.0", "autoprefixer": "^10.4.14", - "eslint": "8.23.1", + "eslint": "^8.43.0", "eslint-config-next": "^13.0.6", "postcss": "^8.4.23", "tailwindcss": "^3.3.2", diff --git a/front/services/courses/courses.ts b/front/services/courses/courses.ts index 62e29b3f..3cf1f13c 100644 --- a/front/services/courses/courses.ts +++ b/front/services/courses/courses.ts @@ -9,7 +9,6 @@ import { RequestBody, RequestBodyForm, RequestBodyWithAuthHeader, errorHandling export async function getOrgCourses(org_id: number, next: any) { const result: any = await fetch(`${getAPIUrl()}courses/org_slug/${org_id}/page/1/limit/10`, RequestBody("GET", null, next)); const res = await errorHandling(result); - return res; } diff --git a/front/services/utils/ts/requests.ts b/front/services/utils/ts/requests.ts index e5b9bf5a..b0b49390 100644 --- a/front/services/utils/ts/requests.ts +++ b/front/services/utils/ts/requests.ts @@ -1,6 +1,6 @@ import { AppRouterInstance } from "next/dist/shared/lib/app-router-context"; import { denyAccessToUser } from "../react/middlewares/views"; -import { LEARNHOUSE_DOMAIN, LEARNHOUSE_HTTP_PROTOCOL } from "@services/config/config"; +import { getUriWithOrg, LEARNHOUSE_DOMAIN, LEARNHOUSE_HTTP_PROTOCOL } from "@services/config/config"; export const RequestBody = (method: string, data: any, next: any) => { let HeadersConfig = new Headers({ "Content-Type": "application/json" }); @@ -78,15 +78,16 @@ export const swrFetcher = async (url: string, body: any, router?: AppRouterInsta export const errorHandling = (res: any) => { if (!res.ok) { - const error: any = new Error(`${res.status}: ${res.statusText}`, {}); + const error: any = new Error(`${res.statusText}`); error.status = res.status; throw error; } return res.json(); }; -export const revalidateTags = (tags: string[]) => { +export const revalidateTags = (tags: string[], orgslug: string) => { + const url = getUriWithOrg(orgslug, ""); tags.forEach((tag) => { - fetch(`${LEARNHOUSE_HTTP_PROTOCOL}${LEARNHOUSE_DOMAIN}/api/revalidate?tag=${tag}`); + fetch(`${url}/api/revalidate?tag=${tag}`); }); }; diff --git a/src/main.py b/src/main.py deleted file mode 100644 index add8842f..00000000 --- a/src/main.py +++ /dev/null @@ -1,21 +0,0 @@ -from fastapi import APIRouter -from src.routers import blocks, trail, users, auth, orgs, roles -from src.routers.courses import chapters, collections, courses,activities - - -global_router = APIRouter(prefix="/api") - - -# API Routes -global_router.include_router(users.router, prefix="/users", tags=["users"]) -global_router.include_router(auth.router, prefix="/auth", tags=["auth"]) -global_router.include_router(orgs.router, prefix="/orgs", tags=["orgs"]) -global_router.include_router(roles.router, prefix="/roles", tags=["roles"]) -global_router.include_router(blocks.router, prefix="/blocks", tags=["blocks"]) -global_router.include_router(courses.router, prefix="/courses", tags=["courses"]) -global_router.include_router(chapters.router, prefix="/chapters", tags=["chapters"]) -global_router.include_router(activities.router, prefix="/activities", tags=["activities"]) -global_router.include_router(collections.router, prefix="/collections", tags=["collections"]) -global_router.include_router(trail.router, prefix="/trail", tags=["trail"]) - - diff --git a/src/router.py b/src/router.py new file mode 100644 index 00000000..66f8b633 --- /dev/null +++ b/src/router.py @@ -0,0 +1,25 @@ +from fastapi import APIRouter, Depends +from src.routers import blocks, dev, trail, users, auth, orgs, roles +from src.routers.courses import chapters, collections, courses, activities +from src.services.dev.dev import isDevModeEnabled + + +v1_router = APIRouter(prefix="/api/v1") + + +# API Routes +v1_router.include_router(users.router, prefix="/users", tags=["users"]) +v1_router.include_router(auth.router, prefix="/auth", tags=["auth"]) +v1_router.include_router(orgs.router, prefix="/orgs", tags=["orgs"]) +v1_router.include_router(roles.router, prefix="/roles", tags=["roles"]) +v1_router.include_router(blocks.router, prefix="/blocks", tags=["blocks"]) +v1_router.include_router(courses.router, prefix="/courses", tags=["courses"]) +v1_router.include_router(chapters.router, prefix="/chapters", tags=["chapters"]) +v1_router.include_router(activities.router, prefix="/activities", tags=["activities"]) +v1_router.include_router( collections.router, prefix="/collections", tags=["collections"]) +v1_router.include_router(trail.router, prefix="/trail", tags=["trail"]) + +# Dev Routes +v1_router.include_router( + dev.router, prefix="/dev", tags=["dev"], dependencies=[Depends(isDevModeEnabled)] +) diff --git a/src/routers/auth.py b/src/routers/auth.py index 0af18380..2cc82973 100644 --- a/src/routers/auth.py +++ b/src/routers/auth.py @@ -1,5 +1,6 @@ from fastapi import Depends, APIRouter, HTTPException, Response, status, Request from fastapi.security import OAuth2PasswordRequestForm +from config.config import get_learnhouse_config from src.security.auth import AuthJWT, authenticate_user from src.services.users.users import PublicUser @@ -41,7 +42,7 @@ async def login( refresh_token = Authorize.create_refresh_token(subject=form_data.username) Authorize.set_refresh_cookies(refresh_token) # set cookies using fastapi - response.set_cookie(key="access_token_cookie", value=access_token, httponly=False) + response.set_cookie(key="access_token_cookie", value=access_token, httponly=False, domain=get_learnhouse_config().hosting_config.cookie_config.domain) user = PublicUser(**user.dict()) result = { diff --git a/src/routers/courses/courses.py b/src/routers/courses/courses.py index bb6252e4..4aed4c99 100644 --- a/src/routers/courses/courses.py +++ b/src/routers/courses/courses.py @@ -1,7 +1,7 @@ from fastapi import APIRouter, Depends, UploadFile, Form, Request from src.security.auth import get_current_user -from src.services.courses.courses import Course, create_course, get_course, get_course_meta, get_courses, get_courses_orgslug, update_course, delete_course, update_course_thumbnail +from src.services.courses.courses import Course, create_course, get_course, get_course_meta, get_courses_orgslug, update_course, delete_course, update_course_thumbnail from src.services.users.users import PublicUser @@ -41,21 +41,12 @@ async def api_get_course_meta(request: Request, course_id: str, current_user: P """ return await get_course_meta(request, course_id, current_user=current_user) - -@router.get("/org_id/{org_id}/page/{page}/limit/{limit}") -async def api_get_course_by(request: Request, page: int, limit: int, org_id: str): - """ - Get houses by page and limit - """ - return await get_courses(request, page, limit, org_id) - - @router.get("/org_slug/{org_slug}/page/{page}/limit/{limit}") -async def api_get_course_by_orgslug(request: Request, page: int, limit: int, org_slug: str): +async def api_get_course_by_orgslug(request: Request, page: int, limit: int, org_slug: str, current_user: PublicUser = Depends(get_current_user)): """ Get houses by page and limit """ - return await get_courses_orgslug(request, page, limit, org_slug) + return await get_courses_orgslug(request, current_user, page, limit, org_slug) @router.put("/{course_id}") diff --git a/src/routers/dev.py b/src/routers/dev.py new file mode 100644 index 00000000..318154e8 --- /dev/null +++ b/src/routers/dev.py @@ -0,0 +1,18 @@ +from fastapi import APIRouter, Request +from config.config import get_learnhouse_config +from src.services.dev.mocks.initial import create_initial_data + + +router = APIRouter() + + +@router.get("/config") +async def config(): + config = get_learnhouse_config() + return config.dict() + + +@router.get("/mock/initial") +async def initial_data(request: Request): + await create_initial_data(request) + return {"Message": "Initial data created 🤖"} diff --git a/src/security/auth.py b/src/security/auth.py index a24ee2c8..32309774 100644 --- a/src/security/auth.py +++ b/src/security/auth.py @@ -4,6 +4,7 @@ from fastapi import Depends, HTTPException, Request, status from fastapi.security import OAuth2PasswordBearer from jose import JWTError, jwt from datetime import datetime, timedelta +from src.services.dev.dev import isDevModeEnabled from src.services.users.schemas.users import AnonymousUser, PublicUser from src.services.users.users import security_get_user, security_verify_password from src.security.security import ALGORITHM, SECRET_KEY @@ -14,10 +15,10 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login") #### JWT Auth #################################################### class Settings(BaseModel): - authjwt_secret_key: str = "secret" + authjwt_secret_key: str = "secret" if isDevModeEnabled() else SECRET_KEY authjwt_token_location = {"cookies", "headers"} authjwt_cookie_csrf_protect = False - authjwt_access_token_expires = False # (pre-alpha only) # TODO: set to 1 hour + authjwt_access_token_expires = False if isDevModeEnabled() else 3600 authjwt_cookie_samesite = "lax" authjwt_cookie_secure = True authjwt_cookie_domain = get_learnhouse_config().hosting_config.cookie_config.domain diff --git a/src/security/security.py b/src/security/security.py index f6384b09..b5918389 100644 --- a/src/security/security.py +++ b/src/security/security.py @@ -1,6 +1,7 @@ from fastapi import HTTPException, status, Request from passlib.context import CryptContext from passlib.hash import pbkdf2_sha256 +from config.config import get_learnhouse_config from src.services.roles.schemas.roles import RoleInDB from src.services.users.schemas.users import UserInDB, UserRolesInOrganization @@ -10,7 +11,7 @@ from src.services.users.schemas.users import UserInDB, UserRolesInOrganization pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") ACCESS_TOKEN_EXPIRE_MINUTES = 30 -SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" +SECRET_KEY = get_learnhouse_config().security_config.auth_jwt_secret_key ALGORITHM = "HS256" ### 🔒 JWT ############################################################## @@ -18,6 +19,7 @@ ALGORITHM = "HS256" ### 🔒 Passwords Hashing ############################################################## + async def security_hash_password(password: str): return pbkdf2_sha256.hash(password) @@ -25,63 +27,64 @@ async def security_hash_password(password: str): async def security_verify_password(plain_password: str, hashed_password: str): return pbkdf2_sha256.verify(plain_password, hashed_password) + ### 🔒 Passwords Hashing ############################################################## ### 🔒 Roles checking ############################################################## -async def verify_user_rights_with_roles(request: Request, action: str, user_id: str, element_id: str, element_org_id: str): +async def verify_user_rights_with_roles( + request: Request, action: str, user_id: str, element_id: str, element_org_id: str +): """ Check if the user has the right to perform the action on the element """ - roles = request.app.db["roles"] + request.app.db["roles"] users = request.app.db["users"] user = await users.find_one({"user_id": user_id}) - # Check if user is available + ######### + # Users existence verification + ######### + if not user and user_id != "anonymous": raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, detail="User not found") - + status_code=status.HTTP_404_NOT_FOUND, detail="User rights : User not found" + ) + # Check if user is anonymous if user_id == "anonymous": return False - # Check if the user is an admin + # Get User user: UserInDB = UserInDB(**await users.find_one({"user_id": user_id})) - # Organization roles verification + ######### + # Organization Roles verification + ######### + for org in user.orgs: - # TODO: Check if the org_id (user) is the same as the org_id (element) - if org.org_id == element_org_id: - return True + # Check if user is owner or reader of the organization + if org.org_role == ("owner" or "editor"): + return True - # Check if user is owner or reader of the organization - if org.org_role == ("owner" or "editor"): - return True - - # If the user is not an owner or a editor, check if he has a role - # Get user roles + ######### + # Roles verification + ######### user_roles = user.roles - # TODO: Check if the org_id of the role is the same as the org_id of the element using find - if action != "create": - await check_user_role_org_with_element_org(request, element_id, user_roles) - - # Check if user has the right role - - element_type = await check_element_type(element_id) - for role_id in user_roles: - role = RoleInDB(**await roles.find_one({"role_id": role_id})) - if role.elements[element_type][f"action_{action}"]: - return True + return await check_user_role_org_with_element_org( + request, element_id, user_roles, action + ) # If no role is found, raise an error raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, detail="You don't have the right to perform this action") + status_code=status.HTTP_403_FORBIDDEN, + detail="User rights : You don't have the right to perform this action", + ) async def check_element_type(element_id): @@ -104,11 +107,17 @@ async def check_element_type(element_id): return "activities" else: raise HTTPException( - status_code=status.HTTP_409_CONFLICT, detail="Issue verifying element nature") + status_code=status.HTTP_409_CONFLICT, + detail="User rights : Issue verifying element nature", + ) -async def check_user_role_org_with_element_org(request: Request, element_id: str, roles_list: list[UserRolesInOrganization]): - +async def check_user_role_org_with_element_org( + request: Request, + element_id: str, + roles_list: list[UserRolesInOrganization], + action: str, +): element_type = await check_element_type(element_id) element = request.app.db[element_type] roles = request.app.db["roles"] @@ -117,19 +126,26 @@ async def check_user_role_org_with_element_org(request: Request, element_id: str singular_form_element = element_type[:-1] element_type_id = singular_form_element + "_id" - + element_org = await element.find_one({element_type_id: element_id}) - - for role_id in roles_list: - role = RoleInDB(**await roles.find_one({"role_id": role_id})) - if role.org_id == element_org["org_id"]: - return True - if role.org_id == "*": - return True + for role in roles_list: + # Check if The role belongs to the same organization as the element + role_db = await roles.find_one({"role_id": role.role_id}) + role = RoleInDB(**role_db) + if role.org_id == element_org["org_id"] or role.org_id == "*": + # Check if user has the right role + for role in roles_list: + role_db = await roles.find_one({"role_id": role.role_id}) + role = RoleInDB(**role_db) + if role.elements[element_type][f"action_{action}"]: + return True else: raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, detail="You don't have the right to perform this action") + status_code=status.HTTP_403_FORBIDDEN, + detail="User rights (roles) : You don't have the right to perform this action", + ) + ### 🔒 Roles checking ############################################################## diff --git a/src/services/courses/activities/activities.py b/src/services/courses/activities/activities.py index 8816461a..e04bf2f9 100644 --- a/src/services/courses/activities/activities.py +++ b/src/services/courses/activities/activities.py @@ -84,6 +84,11 @@ async def get_activity(request: Request, activity_id: str, current_user: PublicU course = await courses.find_one({"chapters": coursechapter_id}) isCoursePublic = course["public"] + isAuthor = current_user.user_id in course["authors"] + + if isAuthor: + activity = ActivityInDB(**activity) + return activity # verify course rights hasRoleRights = await verify_user_rights_with_roles( diff --git a/src/services/courses/courses.py b/src/services/courses/courses.py index bd6f2786..aa79f77d 100644 --- a/src/services/courses/courses.py +++ b/src/services/courses/courses.py @@ -313,31 +313,15 @@ async def delete_course(request: Request, course_id: str, current_user: PublicUs #################################################### -async def get_courses( - request: Request, page: int = 1, limit: int = 10, org_id: str | None = None -): - courses = request.app.db["courses"] - # TODO : Get only courses that user is admin/has roles of - # get all courses from database - all_courses = ( - courses.find({"org_id": org_id}) - .sort("name", 1) - .skip(10 * (page - 1)) - .limit(limit) - ) - - return [ - json.loads(json.dumps(course, default=str)) - for course in await all_courses.to_list(length=100) - ] - - async def get_courses_orgslug( - request: Request, page: int = 1, limit: int = 10, org_slug: str | None = None + request: Request, + current_user: PublicUser, + page: int = 1, + limit: int = 10, + org_slug: str | None = None, ): courses = request.app.db["courses"] orgs = request.app.db["organizations"] - # TODO : Get only courses that user is admin/has roles of # get org_id from slug org = await orgs.find_one({"slug": org_slug}) @@ -347,13 +331,21 @@ async def get_courses_orgslug( status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist" ) - # get all courses from database - all_courses = ( - courses.find({"org_id": org["org_id"]}) - .sort("name", 1) - .skip(10 * (page - 1)) - .limit(limit) - ) + # show only public courses if user is not logged in + if current_user.user_id == "anonymous": + all_courses = ( + courses.find({"org_id": org["org_id"], "public": True}) + .sort("name", 1) + .skip(10 * (page - 1)) + .limit(limit) + ) + else: + all_courses = ( + courses.find({"org_id": org["org_id"]}) + .sort("name", 1) + .skip(10 * (page - 1)) + .limit(limit) + ) return [ json.loads(json.dumps(course, default=str)) @@ -374,6 +366,11 @@ async def verify_rights( course = await courses.find_one({"course_id": course_id}) + isAuthor = current_user.user_id in course["authors"] + + if isAuthor: + return True + if ( current_user.user_id == "anonymous" and course["public"] is True @@ -390,7 +387,6 @@ async def verify_rights( hasRoleRights = await verify_user_rights_with_roles( request, action, current_user.user_id, course_id, course["org_id"] ) - isAuthor = current_user.user_id in course["authors"] if not hasRoleRights and not isAuthor: raise HTTPException( diff --git a/src/services/dev/__init__.py b/src/services/dev/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/services/dev/dev.py b/src/services/dev/dev.py new file mode 100644 index 00000000..6c1e1879 --- /dev/null +++ b/src/services/dev/dev.py @@ -0,0 +1,14 @@ +from fastapi import HTTPException +from config.config import get_learnhouse_config + + +def isDevModeEnabled(): + config = get_learnhouse_config() + if config.general_config.development_mode: + return True + else: + raise HTTPException( + status_code=403, + detail="Development mode is not enabled", + ) + diff --git a/src/services/dev/mocks/__init__.py b/src/services/dev/mocks/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/services/mocks/initial.py b/src/services/dev/mocks/initial.py similarity index 99% rename from src/services/mocks/initial.py rename to src/services/dev/mocks/initial.py index 70bdaa7c..6c2da907 100644 --- a/src/services/mocks/initial.py +++ b/src/services/dev/mocks/initial.py @@ -124,7 +124,7 @@ async def create_initial_data(request: Request): await database_orgs.delete_many({}) organizations = [] - for i in range(0, 5): + for i in range(0, 2): company = fake.company() # remove whitespace and special characters and make lowercase slug = ''.join(e for e in company if e.isalnum()).lower()