Merge pull request #98 from learnhouse/swve/eng-76-fix-various-bugs-leftovers-cleanup-code

Fix various bugs & issues
This commit is contained in:
Badr B 2023-06-28 17:53:06 +02:00 committed by GitHub
commit aea9da388f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 503 additions and 387 deletions

20
app.py
View file

@ -1,15 +1,13 @@
import logging
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from config.config import LearnHouseConfig, get_learnhouse_config from config.config import LearnHouseConfig, get_learnhouse_config
from src.core.events.events import shutdown_app, startup_app 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.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi_jwt_auth.exceptions import AuthJWTException from fastapi_jwt_auth.exceptions import AuthJWTException
from fastapi.middleware.gzip import GZipMiddleware from fastapi.middleware.gzip import GZipMiddleware
from src.services.mocks.initial import create_initial_data
# 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 # Global Routes
app.include_router(global_router) app.include_router(v1_router)
# General Routes # General Routes
@ -71,18 +69,4 @@ app.include_router(global_router)
async def root(): async def root():
return {"Message": "Welcome to LearnHouse ✨"} 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 🤖"}

View file

@ -14,6 +14,14 @@ class CookieConfig(BaseModel):
domain: str domain: str
class GeneralConfig(BaseModel):
development_mode: bool
class SecurityConfig(BaseModel):
auth_jwt_secret_key: str
class HostingConfig(BaseModel): class HostingConfig(BaseModel):
domain: str domain: str
ssl: bool ssl: bool
@ -33,8 +41,10 @@ class LearnHouseConfig(BaseModel):
site_name: str site_name: str
site_description: str site_description: str
contact_email: str contact_email: str
general_config: GeneralConfig
hosting_config: HostingConfig hosting_config: HostingConfig
database_config: DatabaseConfig database_config: DatabaseConfig
security_config: SecurityConfig
def get_learnhouse_config() -> LearnHouseConfig: def get_learnhouse_config() -> LearnHouseConfig:
@ -44,7 +54,19 @@ def get_learnhouse_config() -> LearnHouseConfig:
# Load the YAML file # Load the YAML file
with open(yaml_path, "r") as f: with open(yaml_path, "r") as f:
yaml_config = yaml.safe_load(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 # Check if environment variables are defined
env_site_name = os.environ.get("LEARNHOUSE_SITE_NAME") env_site_name = os.environ.get("LEARNHOUSE_SITE_NAME")
env_site_description = os.environ.get("LEARNHOUSE_SITE_DESCRIPTION") env_site_description = os.environ.get("LEARNHOUSE_SITE_DESCRIPTION")
@ -89,10 +111,10 @@ def get_learnhouse_config() -> LearnHouseConfig:
"self_hosted" "self_hosted"
) )
cookies_domain = env_cookie_domain or yaml_config.get("hosting_config", {}).get("cookies_config", {}).get("domain") cookies_domain = env_cookie_domain or yaml_config.get("hosting_config", {}).get(
cookie_config = CookieConfig( "cookies_config", {}
domain=cookies_domain ).get("domain")
) cookie_config = CookieConfig(domain=cookies_domain)
# Database config # Database config
mongodb_connection_string = env_mongodb_connection_string or yaml_config.get( 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_name=site_name,
site_description=site_description, site_description=site_description,
contact_email=contact_email, contact_email=contact_email,
general_config=GeneralConfig(development_mode=bool(development_mode)),
hosting_config=hosting_config, hosting_config=hosting_config,
database_config=database_config, database_config=database_config,
security_config=SecurityConfig(auth_jwt_secret_key=auth_jwt_secret_key),
) )
return config return config

View file

@ -2,6 +2,12 @@ site_name: LearnHouse
site_description: LearnHouse is an open-source platform tailored for learning experiences. site_description: LearnHouse is an open-source platform tailored for learning experiences.
contact_email: hi@learnhouse.app contact_email: hi@learnhouse.app
general:
development_mode: true
security:
auth_jwt_secret_key: secret
hosting_config: hosting_config:
domain: learnhouse.app domain: learnhouse.app
ssl: true ssl: true

8
front/.eslintrc Normal file
View file

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

View file

@ -1,3 +0,0 @@
{
"extends": "next/core-web-vitals"
}

View file

@ -14,13 +14,13 @@ const CollectionAdminEditsArea = (props: any) => {
const deleteCollectionUI = async (collectionId: number) => { const deleteCollectionUI = async (collectionId: number) => {
await deleteCollection(collectionId); await deleteCollection(collectionId);
revalidateTags(["collections"]); revalidateTags(["collections"], props.orgslug);
// reload the page // reload the page
router.refresh(); router.refresh();
router.push(getUriWithOrg(props.orgslug, "/collections")); router.push(getUriWithOrg(props.orgslug, "/collections"));
// refresh page (FIX for Next.js BUG) // refresh page (FIX for Next.js BUG)
window.location.reload(); //window.location.reload();
} }
return ( return (

View file

@ -43,13 +43,11 @@ function NewCollection(params: any) {
org_id: org.org_id, org_id: org.org_id,
}; };
await createCollection(collection); await createCollection(collection);
revalidateTags(["collections"]); revalidateTags(["collections"], orgslug);
router.prefetch(getUriWithOrg(orgslug, "/collections")); router.prefetch(getUriWithOrg(orgslug, "/collections"));
router.push(getUriWithOrg(orgslug, "/collections")); router.push(getUriWithOrg(orgslug, "/collections"));
router.refresh(); router.refresh();
// refresh page (FIX for Next.js BUG)
window.location.reload();
}; };

View file

@ -10,6 +10,7 @@ import DocumentPdfActivity from "@components/Pages/Activities/DocumentPdf/Docume
import ActivityIndicators from "@components/Pages/Courses/ActivityIndicators"; import ActivityIndicators from "@components/Pages/Courses/ActivityIndicators";
import GeneralWrapperStyled from "@components/StyledElements/Wrappers/GeneralWrapper"; import GeneralWrapperStyled from "@components/StyledElements/Wrappers/GeneralWrapper";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import AuthenticatedClientElement from "@components/Security/AuthenticatedClientElement";
interface ActivityClientProps { interface ActivityClientProps {
activityid: string; activityid: string;
@ -64,8 +65,10 @@ function ActivityClient(props: ActivityClientProps) {
<h1 className="font-bold text-gray-950 text-2xl first-letter:uppercase" >{activity.name}</h1> <h1 className="font-bold text-gray-950 text-2xl first-letter:uppercase" >{activity.name}</h1>
</div> </div>
<div className="flex space-x-2"> <div className="flex space-x-2">
<MarkStatus activityid={activityid} course={course} orgslug={orgslug} courseid={courseid} /> <AuthenticatedClientElement checkMethod="authentication">
<MarkStatus activityid={activityid} course={course} orgslug={orgslug} courseid={courseid} />
</AuthenticatedClientElement>
</div> </div>
</div> </div>

View file

@ -22,7 +22,7 @@ const CourseClient = (props: any) => {
async function startCourseUI() { async function startCourseUI() {
// Create activity // Create activity
await startCourse("course_" + courseid, orgslug); await startCourse("course_" + courseid, orgslug);
revalidateTags(['courses']); revalidateTags(['courses'], orgslug);
router.refresh(); router.refresh();
// refresh page (FIX for Next.js BUG) // refresh page (FIX for Next.js BUG)
@ -33,7 +33,7 @@ const CourseClient = (props: any) => {
// Close activity // Close activity
let activity = await removeCourse("course_" + courseid, orgslug); let activity = await removeCourse("course_" + courseid, orgslug);
// Mutate course // Mutate course
revalidateTags(['courses']); revalidateTags(['courses'], orgslug);
router.refresh(); router.refresh();
// refresh page (FIX for Next.js BUG) // refresh page (FIX for Next.js BUG)

View file

@ -34,7 +34,7 @@ function Courses(props: CourseProps) {
async function deleteCourses(course_id: any) { async function deleteCourses(course_id: any) {
await deleteCourseFromBackend(course_id); await deleteCourseFromBackend(course_id);
revalidateTags(['courses']); revalidateTags(['courses'], orgslug);
router.refresh(); router.refresh();
} }

View file

@ -1,9 +1,10 @@
import React from "react"; import React from "react";
import Courses from "./courses"; import Courses from "./courses";
import { getOrgCourses } from "@services/courses/courses"; import { getOrgCoursesWithAuthHeader } from "@services/courses/courses";
import { Metadata } from "next"; import { Metadata } from "next";
import { getOrganizationContextInfo } from "@services/organizations/orgs"; import { getOrganizationContextInfo } from "@services/organizations/orgs";
import { cookies } from "next/headers";
type MetadataProps = { type MetadataProps = {
params: { orgslug: string }; params: { orgslug: string };
@ -24,7 +25,9 @@ export async function generateMetadata(
const CoursesPage = async (params: any) => { const CoursesPage = async (params: any) => {
const orgslug = params.params.orgslug; 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 ( return (
<div> <div>

View file

@ -10,8 +10,8 @@ import Toast from '@components/StyledElements/Toast/Toast';
import { toast } from 'react-hot-toast'; import { toast } from 'react-hot-toast';
const Login = () => { const Login = () => {
const [email, setEmail] = React.useState("admin@admin.admin"); const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState("admin"); const [password, setPassword] = React.useState("");
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const router = useRouter(); const router = useRouter();
@ -44,13 +44,13 @@ const Login = () => {
return ( return (
<div> <div>
<LoginPage> <LoginPage>
<Toast></Toast> <Toast></Toast>
<LoginBox> <LoginBox>
<FormLayout onSubmit={handleSubmit}> <FormLayout onSubmit={handleSubmit}>
<FormField name="login-email"> <FormField name="login-email">
<Flex css={{ alignItems: 'baseline', justifyContent: 'space-between' }}> <Flex css={{ alignItems: 'baseline', justifyContent: 'space-between' }}>
<FormLabel>Email</FormLabel> <FormLabel>Email</FormLabel>
<FormMessage style={{color:"black"}} match="valueMissing">Please provide an email</FormMessage> <FormMessage style={{ color: "black" }} match="valueMissing">Please provide an email</FormMessage>
</Flex> </Flex>
<Form.Control asChild> <Form.Control asChild>
<Input onChange={handleEmailChange} type="text" /> <Input onChange={handleEmailChange} type="text" />
@ -59,7 +59,7 @@ const Login = () => {
<FormField name="login-password"> <FormField name="login-password">
<Flex css={{ alignItems: 'baseline', justifyContent: 'space-between' }}> <Flex css={{ alignItems: 'baseline', justifyContent: 'space-between' }}>
<FormLabel>Password</FormLabel> <FormLabel>Password</FormLabel>
<FormMessage style={{color:"black"}} match="valueMissing">Please provide a password</FormMessage> <FormMessage style={{ color: "black" }} match="valueMissing">Please provide a password</FormMessage>
</Flex> </Flex>
<Form.Control asChild> <Form.Control asChild>
<Input type="password" onChange={handlePasswordChange} /> <Input type="password" onChange={handlePasswordChange} />

View file

@ -6,38 +6,45 @@ import LearnHouseWhiteLogo from '@public/learnhouse_text_white.png';
import AuthProvider, { AuthContext } from '@components/Security/AuthProvider'; import AuthProvider, { AuthContext } from '@components/Security/AuthProvider';
import Avvvatars from 'avvvatars-react'; import Avvvatars from 'avvvatars-react';
import Image from 'next/image'; 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 auth: any = React.useContext(AuthContext);
const orgslug = params.orgslug;
let org = await getOrganizationContextInfo(orgslug, {});
return ( return (
<> <>
<AuthProvider> <AuthProvider>
<Main> <Main>
<LeftWrapper> <LeftWrapper>
<LeftTopArea> <LeftTopArea>
<Link href={"/"}><Image alt="Learnhouse logo" width={128} src={LearnHouseWhiteLogo}/></Link> <Link href={"/"}><Image alt="Learnhouse logo" width={128} src={LearnHouseWhiteLogo} /></Link>
{auth.isAuthenticated && ( {auth.isAuthenticated && (
<Avvvatars value={auth.userInfo.user_object.user_id} style="shape" /> <Avvvatars value={auth.userInfo.user_object.user_id} style="shape" />
)} )}
</LeftTopArea> </LeftTopArea>
<LeftMenuWrapper> <LeftMenuWrapper>
<MenuTitle>Account</MenuTitle> <MenuTitle>Account</MenuTitle>
<ul> <ul>
<li><Link href="/settings/account/profile">Profile</Link></li> <li><Link href="/settings/account/profile">Profile</Link></li>
<li><Link href="/settings/account/passwords">Passwords</Link></li> <li><Link href="/settings/account/passwords">Passwords</Link></li>
</ul> </ul>
<MenuTitle>Organization</MenuTitle> <AuthenticatedClientElement checkMethod='roles' orgId={org.org_id} >
<ul> <MenuTitle>Organization</MenuTitle>
<li><Link href="/settings/organization/general">General</Link></li> <ul>
</ul> <li><Link href="/settings/organization/general">General</Link></li>
</LeftMenuWrapper> </ul>
</LeftWrapper> </AuthenticatedClientElement>
<RightWrapper> </LeftMenuWrapper>
{children} </LeftWrapper>
</RightWrapper> <RightWrapper>
</Main></AuthProvider> {children}
</RightWrapper>
</Main></AuthProvider>
</> </>
) )
} }
@ -59,7 +66,7 @@ const LeftWrapper = styled('div', {
const LeftTopArea = styled('div', { const LeftTopArea = styled('div', {
display: 'flex', display: 'flex',
marginLeft: '20px', marginLeft: '20px',
alignItems: 'center', alignItems: 'center',
img: { img: {
@ -72,7 +79,7 @@ const LeftTopArea = styled('div', {
placeContent: 'center', placeContent: 'center',
} }
}) })
const LeftMenuWrapper = styled('div', { const LeftMenuWrapper = styled('div', {

View file

@ -33,12 +33,9 @@ function OrganizationClient(props: any) {
let org_id = org.org_id; let org_id = org.org_id;
await uploadOrganizationLogo(org_id, selectedFile); await uploadOrganizationLogo(org_id, selectedFile);
setSelectedFile(null); // Reset the selected file setSelectedFile(null); // Reset the selected file
revalidateTags(['organizations']); revalidateTags(['organizations'], org.slug);
router.refresh(); 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) => { const updateOrg = async (values: OrganizationValues) => {
let org_id = org.org_id; let org_id = org.org_id;
await updateOrganization(org_id, values); await updateOrganization(org_id, values);
// Mutate the org
revalidateTags(['organizations'], org.slug);
router.refresh();
} }

View file

@ -348,6 +348,42 @@ export const EditorContentWrapper = styled.div`
// disable chrome outline // disable chrome outline
.ProseMirror { .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-left: 20px;
padding-right: 20px; padding-right: 20px;
padding-bottom: 20px; padding-bottom: 20px;

View file

@ -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() { function PageLoading() {
return ( return (
<div className="max-w-7xl mx-auto px-4 py-20"> <motion.main
variants={variants} // Pass the variant object into Framer Motion
initial="hidden" // Set the initial state to variants.hidden
animate="enter" // Animated state to variants.enter
exit="exit" // Exit state (used later) to variants.exit
transition={{ type: "linear" }} // Set the transition to linear
className=""
>
<div className="max-w-7xl mx-auto px-4 py-20 transition-all">
<div className="animate-pulse mx-auto flex space-x-4"> <div className="animate-pulse mx-auto flex space-x-4">
<svg className="mx-auto" width="295" height="295" viewBox="0 0 295 295" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg className="mx-auto" width="295" height="295" viewBox="0 0 295 295" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect opacity="0.51" x="6.5" y="6.5" width="282" height="282" rx="78.5" stroke="#454545" strokeOpacity="0.46" stroke-width="13" stroke-dasharray="11 11" /> <rect opacity="0.51" x="6.5" y="6.5" width="282" height="282" rx="78.5" stroke="#454545" strokeOpacity="0.46" stroke-width="13" stroke-dasharray="11 11" />
@ -10,6 +26,7 @@ function PageLoading() {
</svg> </svg>
</div> </div>
</div> </div>
</motion.main>
) )
} }

View file

@ -43,7 +43,7 @@ function CreateCourseModal({ closeModal, orgslug }: any) {
e.preventDefault(); e.preventDefault();
setIsSubmitting(true); setIsSubmitting(true);
let status = await createNewCourse(orgId, { name, description }, thumbnail); let status = await createNewCourse(orgId, { name, description }, thumbnail);
revalidateTags(['courses']); revalidateTags(['courses'], orgslug);
setIsSubmitting(false); setIsSubmitting(false);
if (status.org_id == orgId) { if (status.org_id == orgId) {

View file

@ -18,7 +18,7 @@ function TrailCourseElement(props: TrailCourseElementProps) {
// Close activity // Close activity
let activity = await removeCourse(course_id, props.orgslug); let activity = await removeCourse(course_id, props.orgslug);
// Mutate course // Mutate course
revalidateTags(['courses']); revalidateTags(['courses'], props.orgslug);
// Mutate // Mutate
mutate(`${getAPIUrl()}trail/org_slug/${props.orgslug}/trail`); mutate(`${getAPIUrl()}trail/org_slug/${props.orgslug}/trail`);

View file

@ -9,7 +9,7 @@ interface AuthenticatedClientElementProps {
} }
function AuthenticatedClientElement(props: AuthenticatedClientElementProps) { export const AuthenticatedClientElement = (props: AuthenticatedClientElementProps) => {
const auth: any = React.useContext(AuthContext); const auth: any = React.useContext(AuthContext);
// Available roles // Available roles

View file

@ -3,6 +3,7 @@ function GeneralWrapperStyled({ children }: { children: React.ReactNode }) {
return ( return (
<div <div
className='max-w-screen-2xl mx-auto px-16 py-5 tracking-tight' className='max-w-screen-2xl mx-auto px-16 py-5 tracking-tight'
>{children}</div> >{children}</div>
) )
} }

371
front/package-lock.json generated
View file

@ -51,7 +51,7 @@
"@types/styled-components": "^5.1.26", "@types/styled-components": "^5.1.26",
"@types/uuid": "^9.0.0", "@types/uuid": "^9.0.0",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"eslint": "8.23.1", "eslint": "^8.43.0",
"eslint-config-next": "^13.0.6", "eslint-config-next": "^13.0.6",
"postcss": "^8.4.23", "postcss": "^8.4.23",
"tailwindcss": "^3.3.2", "tailwindcss": "^3.3.2",
@ -1930,16 +1930,40 @@
"resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz",
"integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" "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": { "node_modules/@eslint/eslintrc": {
"version": "1.3.2", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.2.tgz", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz",
"integrity": "sha512-AXYd23w1S/bv3fTs3Lz0vjiYemS08jWkI3hYyS9I1ry+0f+Yjs1wm+sU0BS8qDOPrBIkp4qHYC16I8uVtpLajQ==", "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"ajv": "^6.12.4", "ajv": "^6.12.4",
"debug": "^4.3.2", "debug": "^4.3.2",
"espree": "^9.4.0", "espree": "^9.5.2",
"globals": "^13.15.0", "globals": "^13.19.0",
"ignore": "^5.2.0", "ignore": "^5.2.0",
"import-fresh": "^3.2.1", "import-fresh": "^3.2.1",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
@ -1953,6 +1977,15 @@
"url": "https://opencollective.com/eslint" "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": { "node_modules/@floating-ui/core": {
"version": "0.7.3", "version": "0.7.3",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz",
@ -1980,29 +2013,19 @@
} }
}, },
"node_modules/@humanwhocodes/config-array": { "node_modules/@humanwhocodes/config-array": {
"version": "0.10.4", "version": "0.11.10",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
"integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==", "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@humanwhocodes/object-schema": "^1.2.1", "@humanwhocodes/object-schema": "^1.2.1",
"debug": "^4.1.1", "debug": "^4.1.1",
"minimatch": "^3.0.4" "minimatch": "^3.0.5"
}, },
"engines": { "engines": {
"node": ">=10.10.0" "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": { "node_modules/@humanwhocodes/module-importer": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
@ -3770,9 +3793,9 @@
} }
}, },
"node_modules/acorn": { "node_modules/acorn": {
"version": "8.8.0", "version": "8.9.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz",
"integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==",
"dev": true, "dev": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
@ -4663,39 +4686,40 @@
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
"version": "8.23.1", "version": "8.43.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.23.1.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.43.0.tgz",
"integrity": "sha512-w7C1IXCc6fNqjpuYd0yPlcTKKmHlHHktRkzmBPZ+7cvNBQuiNjx0xaMTjAJGCafJhQkrFJooREv0CtrVzmHwqg==", "integrity": "sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"@eslint/eslintrc": "^1.3.2", "@eslint-community/eslint-utils": "^4.2.0",
"@humanwhocodes/config-array": "^0.10.4", "@eslint-community/regexpp": "^4.4.0",
"@humanwhocodes/gitignore-to-minimatch": "^1.0.2", "@eslint/eslintrc": "^2.0.3",
"@eslint/js": "8.43.0",
"@humanwhocodes/config-array": "^0.11.10",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
"ajv": "^6.10.0", "ajv": "^6.10.0",
"chalk": "^4.0.0", "chalk": "^4.0.0",
"cross-spawn": "^7.0.2", "cross-spawn": "^7.0.2",
"debug": "^4.3.2", "debug": "^4.3.2",
"doctrine": "^3.0.0", "doctrine": "^3.0.0",
"escape-string-regexp": "^4.0.0", "escape-string-regexp": "^4.0.0",
"eslint-scope": "^7.1.1", "eslint-scope": "^7.2.0",
"eslint-utils": "^3.0.0", "eslint-visitor-keys": "^3.4.1",
"eslint-visitor-keys": "^3.3.0", "espree": "^9.5.2",
"espree": "^9.4.0", "esquery": "^1.4.2",
"esquery": "^1.4.0",
"esutils": "^2.0.2", "esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
"file-entry-cache": "^6.0.1", "file-entry-cache": "^6.0.1",
"find-up": "^5.0.0", "find-up": "^5.0.0",
"glob-parent": "^6.0.1", "glob-parent": "^6.0.2",
"globals": "^13.15.0", "globals": "^13.19.0",
"globby": "^11.1.0", "graphemer": "^1.4.0",
"grapheme-splitter": "^1.0.4",
"ignore": "^5.2.0", "ignore": "^5.2.0",
"import-fresh": "^3.0.0", "import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4", "imurmurhash": "^0.1.4",
"is-glob": "^4.0.0", "is-glob": "^4.0.0",
"js-sdsl": "^4.1.4", "is-path-inside": "^3.0.3",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"json-stable-stringify-without-jsonify": "^1.0.1", "json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1", "levn": "^0.4.1",
@ -4703,7 +4727,6 @@
"minimatch": "^3.1.2", "minimatch": "^3.1.2",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
"optionator": "^0.9.1", "optionator": "^0.9.1",
"regexpp": "^3.2.0",
"strip-ansi": "^6.0.1", "strip-ansi": "^6.0.1",
"strip-json-comments": "^3.1.0", "strip-json-comments": "^3.1.0",
"text-table": "^0.2.0" "text-table": "^0.2.0"
@ -5014,9 +5037,9 @@
} }
}, },
"node_modules/eslint-scope": { "node_modules/eslint-scope": {
"version": "7.1.1", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz",
"integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"esrecurse": "^4.3.0", "esrecurse": "^4.3.0",
@ -5024,53 +5047,32 @@
}, },
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "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": { "funding": {
"url": "https://github.com/sponsors/mysticatea" "url": "https://opencollective.com/eslint"
},
"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"
} }
}, },
"node_modules/eslint-visitor-keys": { "node_modules/eslint-visitor-keys": {
"version": "3.3.0", "version": "3.4.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz",
"integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/espree": { "node_modules/espree": {
"version": "9.4.0", "version": "9.5.2",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz",
"integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"acorn": "^8.8.0", "acorn": "^8.8.0",
"acorn-jsx": "^5.3.2", "acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^3.3.0" "eslint-visitor-keys": "^3.4.1"
}, },
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@ -5080,9 +5082,9 @@
} }
}, },
"node_modules/esquery": { "node_modules/esquery": {
"version": "1.4.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
"integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"estraverse": "^5.1.0" "estraverse": "^5.1.0"
@ -5475,9 +5477,9 @@
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="
}, },
"node_modules/globals": { "node_modules/globals": {
"version": "13.17.0", "version": "13.20.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
"integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"type-fest": "^0.20.2" "type-fest": "^0.20.2"
@ -5546,10 +5548,10 @@
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="
}, },
"node_modules/grapheme-splitter": { "node_modules/graphemer": {
"version": "1.0.4", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
"integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"dev": true "dev": true
}, },
"node_modules/has": { "node_modules/has": {
@ -5888,6 +5890,15 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/is-reference": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
@ -6001,12 +6012,6 @@
"jiti": "bin/jiti.js" "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": { "node_modules/js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -7005,9 +7010,9 @@
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
}, },
"node_modules/punycode": { "node_modules/punycode": {
"version": "2.1.1", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
"dev": true, "dev": true,
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@ -7350,18 +7355,6 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/regexpu-core": {
"version": "5.2.2", "version": "5.2.2",
"resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", "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", "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.0.tgz",
"integrity": "sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==" "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": { "@eslint/eslintrc": {
"version": "1.3.2", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.2.tgz", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz",
"integrity": "sha512-AXYd23w1S/bv3fTs3Lz0vjiYemS08jWkI3hYyS9I1ry+0f+Yjs1wm+sU0BS8qDOPrBIkp4qHYC16I8uVtpLajQ==", "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"ajv": "^6.12.4", "ajv": "^6.12.4",
"debug": "^4.3.2", "debug": "^4.3.2",
"espree": "^9.4.0", "espree": "^9.5.2",
"globals": "^13.15.0", "globals": "^13.19.0",
"ignore": "^5.2.0", "ignore": "^5.2.0",
"import-fresh": "^3.2.1", "import-fresh": "^3.2.1",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
@ -9912,6 +9920,12 @@
"strip-json-comments": "^3.1.1" "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": { "@floating-ui/core": {
"version": "0.7.3", "version": "0.7.3",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-0.7.3.tgz",
@ -9935,22 +9949,16 @@
} }
}, },
"@humanwhocodes/config-array": { "@humanwhocodes/config-array": {
"version": "0.10.4", "version": "0.11.10",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz",
"integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==", "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@humanwhocodes/object-schema": "^1.2.1", "@humanwhocodes/object-schema": "^1.2.1",
"debug": "^4.1.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": { "@humanwhocodes/module-importer": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
@ -11206,9 +11214,9 @@
} }
}, },
"acorn": { "acorn": {
"version": "8.8.0", "version": "8.9.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz",
"integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==",
"dev": true "dev": true
}, },
"acorn-jsx": { "acorn-jsx": {
@ -11820,39 +11828,40 @@
"dev": true "dev": true
}, },
"eslint": { "eslint": {
"version": "8.23.1", "version": "8.43.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.23.1.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.43.0.tgz",
"integrity": "sha512-w7C1IXCc6fNqjpuYd0yPlcTKKmHlHHktRkzmBPZ+7cvNBQuiNjx0xaMTjAJGCafJhQkrFJooREv0CtrVzmHwqg==", "integrity": "sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"@eslint/eslintrc": "^1.3.2", "@eslint-community/eslint-utils": "^4.2.0",
"@humanwhocodes/config-array": "^0.10.4", "@eslint-community/regexpp": "^4.4.0",
"@humanwhocodes/gitignore-to-minimatch": "^1.0.2", "@eslint/eslintrc": "^2.0.3",
"@eslint/js": "8.43.0",
"@humanwhocodes/config-array": "^0.11.10",
"@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
"ajv": "^6.10.0", "ajv": "^6.10.0",
"chalk": "^4.0.0", "chalk": "^4.0.0",
"cross-spawn": "^7.0.2", "cross-spawn": "^7.0.2",
"debug": "^4.3.2", "debug": "^4.3.2",
"doctrine": "^3.0.0", "doctrine": "^3.0.0",
"escape-string-regexp": "^4.0.0", "escape-string-regexp": "^4.0.0",
"eslint-scope": "^7.1.1", "eslint-scope": "^7.2.0",
"eslint-utils": "^3.0.0", "eslint-visitor-keys": "^3.4.1",
"eslint-visitor-keys": "^3.3.0", "espree": "^9.5.2",
"espree": "^9.4.0", "esquery": "^1.4.2",
"esquery": "^1.4.0",
"esutils": "^2.0.2", "esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
"file-entry-cache": "^6.0.1", "file-entry-cache": "^6.0.1",
"find-up": "^5.0.0", "find-up": "^5.0.0",
"glob-parent": "^6.0.1", "glob-parent": "^6.0.2",
"globals": "^13.15.0", "globals": "^13.19.0",
"globby": "^11.1.0", "graphemer": "^1.4.0",
"grapheme-splitter": "^1.0.4",
"ignore": "^5.2.0", "ignore": "^5.2.0",
"import-fresh": "^3.0.0", "import-fresh": "^3.0.0",
"imurmurhash": "^0.1.4", "imurmurhash": "^0.1.4",
"is-glob": "^4.0.0", "is-glob": "^4.0.0",
"js-sdsl": "^4.1.4", "is-path-inside": "^3.0.3",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"json-stable-stringify-without-jsonify": "^1.0.1", "json-stable-stringify-without-jsonify": "^1.0.1",
"levn": "^0.4.1", "levn": "^0.4.1",
@ -11860,7 +11869,6 @@
"minimatch": "^3.1.2", "minimatch": "^3.1.2",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
"optionator": "^0.9.1", "optionator": "^0.9.1",
"regexpp": "^3.2.0",
"strip-ansi": "^6.0.1", "strip-ansi": "^6.0.1",
"strip-json-comments": "^3.1.0", "strip-json-comments": "^3.1.0",
"text-table": "^0.2.0" "text-table": "^0.2.0"
@ -12094,53 +12102,36 @@
"requires": {} "requires": {}
}, },
"eslint-scope": { "eslint-scope": {
"version": "7.1.1", "version": "7.2.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz",
"integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==",
"dev": true, "dev": true,
"requires": { "requires": {
"esrecurse": "^4.3.0", "esrecurse": "^4.3.0",
"estraverse": "^5.2.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": { "eslint-visitor-keys": {
"version": "3.3.0", "version": "3.4.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz",
"integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==",
"dev": true "dev": true
}, },
"espree": { "espree": {
"version": "9.4.0", "version": "9.5.2",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz",
"integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==",
"dev": true, "dev": true,
"requires": { "requires": {
"acorn": "^8.8.0", "acorn": "^8.8.0",
"acorn-jsx": "^5.3.2", "acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^3.3.0" "eslint-visitor-keys": "^3.4.1"
} }
}, },
"esquery": { "esquery": {
"version": "1.4.0", "version": "1.5.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
"integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",
"dev": true, "dev": true,
"requires": { "requires": {
"estraverse": "^5.1.0" "estraverse": "^5.1.0"
@ -12441,9 +12432,9 @@
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="
}, },
"globals": { "globals": {
"version": "13.17.0", "version": "13.20.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
"integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"type-fest": "^0.20.2" "type-fest": "^0.20.2"
@ -12495,10 +12486,10 @@
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="
}, },
"grapheme-splitter": { "graphemer": {
"version": "1.0.4", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
"integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"dev": true "dev": true
}, },
"has": { "has": {
@ -12724,6 +12715,12 @@
"has-tostringtag": "^1.0.0" "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": { "is-reference": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
@ -12803,12 +12800,6 @@
"integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==", "integrity": "sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==",
"dev": true "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": { "js-tokens": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -13533,9 +13524,9 @@
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
}, },
"punycode": { "punycode": {
"version": "2.1.1", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
"dev": true "dev": true
}, },
"queue-microtask": { "queue-microtask": {
@ -13767,12 +13758,6 @@
"functions-have-names": "^1.2.2" "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": { "regexpu-core": {
"version": "5.2.2", "version": "5.2.2",
"resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.2.tgz",

View file

@ -52,7 +52,7 @@
"@types/styled-components": "^5.1.26", "@types/styled-components": "^5.1.26",
"@types/uuid": "^9.0.0", "@types/uuid": "^9.0.0",
"autoprefixer": "^10.4.14", "autoprefixer": "^10.4.14",
"eslint": "8.23.1", "eslint": "^8.43.0",
"eslint-config-next": "^13.0.6", "eslint-config-next": "^13.0.6",
"postcss": "^8.4.23", "postcss": "^8.4.23",
"tailwindcss": "^3.3.2", "tailwindcss": "^3.3.2",

View file

@ -9,7 +9,6 @@ import { RequestBody, RequestBodyForm, RequestBodyWithAuthHeader, errorHandling
export async function getOrgCourses(org_id: number, next: any) { 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 result: any = await fetch(`${getAPIUrl()}courses/org_slug/${org_id}/page/1/limit/10`, RequestBody("GET", null, next));
const res = await errorHandling(result); const res = await errorHandling(result);
return res; return res;
} }

View file

@ -1,6 +1,6 @@
import { AppRouterInstance } from "next/dist/shared/lib/app-router-context"; import { AppRouterInstance } from "next/dist/shared/lib/app-router-context";
import { denyAccessToUser } from "../react/middlewares/views"; 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) => { export const RequestBody = (method: string, data: any, next: any) => {
let HeadersConfig = new Headers({ "Content-Type": "application/json" }); 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) => { export const errorHandling = (res: any) => {
if (!res.ok) { if (!res.ok) {
const error: any = new Error(`${res.status}: ${res.statusText}`, {}); const error: any = new Error(`${res.statusText}`);
error.status = res.status; error.status = res.status;
throw error; throw error;
} }
return res.json(); return res.json();
}; };
export const revalidateTags = (tags: string[]) => { export const revalidateTags = (tags: string[], orgslug: string) => {
const url = getUriWithOrg(orgslug, "");
tags.forEach((tag) => { tags.forEach((tag) => {
fetch(`${LEARNHOUSE_HTTP_PROTOCOL}${LEARNHOUSE_DOMAIN}/api/revalidate?tag=${tag}`); fetch(`${url}/api/revalidate?tag=${tag}`);
}); });
}; };

View file

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

25
src/router.py Normal file
View file

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

View file

@ -1,5 +1,6 @@
from fastapi import Depends, APIRouter, HTTPException, Response, status, Request from fastapi import Depends, APIRouter, HTTPException, Response, status, Request
from fastapi.security import OAuth2PasswordRequestForm from fastapi.security import OAuth2PasswordRequestForm
from config.config import get_learnhouse_config
from src.security.auth import AuthJWT, authenticate_user from src.security.auth import AuthJWT, authenticate_user
from src.services.users.users import PublicUser from src.services.users.users import PublicUser
@ -41,7 +42,7 @@ async def login(
refresh_token = Authorize.create_refresh_token(subject=form_data.username) refresh_token = Authorize.create_refresh_token(subject=form_data.username)
Authorize.set_refresh_cookies(refresh_token) Authorize.set_refresh_cookies(refresh_token)
# set cookies using fastapi # 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()) user = PublicUser(**user.dict())
result = { result = {

View file

@ -1,7 +1,7 @@
from fastapi import APIRouter, Depends, UploadFile, Form, Request from fastapi import APIRouter, Depends, UploadFile, Form, Request
from src.security.auth import get_current_user 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 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) 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}") @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 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}") @router.put("/{course_id}")

18
src/routers/dev.py Normal file
View file

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

View file

@ -4,6 +4,7 @@ from fastapi import Depends, HTTPException, Request, status
from fastapi.security import OAuth2PasswordBearer from fastapi.security import OAuth2PasswordBearer
from jose import JWTError, jwt from jose import JWTError, jwt
from datetime import datetime, timedelta 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.schemas.users import AnonymousUser, PublicUser
from src.services.users.users import security_get_user, security_verify_password from src.services.users.users import security_get_user, security_verify_password
from src.security.security import ALGORITHM, SECRET_KEY from src.security.security import ALGORITHM, SECRET_KEY
@ -14,10 +15,10 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/auth/login")
#### JWT Auth #################################################### #### JWT Auth ####################################################
class Settings(BaseModel): class Settings(BaseModel):
authjwt_secret_key: str = "secret" authjwt_secret_key: str = "secret" if isDevModeEnabled() else SECRET_KEY
authjwt_token_location = {"cookies", "headers"} authjwt_token_location = {"cookies", "headers"}
authjwt_cookie_csrf_protect = False 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_samesite = "lax"
authjwt_cookie_secure = True authjwt_cookie_secure = True
authjwt_cookie_domain = get_learnhouse_config().hosting_config.cookie_config.domain authjwt_cookie_domain = get_learnhouse_config().hosting_config.cookie_config.domain

View file

@ -1,6 +1,7 @@
from fastapi import HTTPException, status, Request from fastapi import HTTPException, status, Request
from passlib.context import CryptContext from passlib.context import CryptContext
from passlib.hash import pbkdf2_sha256 from passlib.hash import pbkdf2_sha256
from config.config import get_learnhouse_config
from src.services.roles.schemas.roles import RoleInDB from src.services.roles.schemas.roles import RoleInDB
from src.services.users.schemas.users import UserInDB, UserRolesInOrganization 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") pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
ACCESS_TOKEN_EXPIRE_MINUTES = 30 ACCESS_TOKEN_EXPIRE_MINUTES = 30
SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" SECRET_KEY = get_learnhouse_config().security_config.auth_jwt_secret_key
ALGORITHM = "HS256" ALGORITHM = "HS256"
### 🔒 JWT ############################################################## ### 🔒 JWT ##############################################################
@ -18,6 +19,7 @@ ALGORITHM = "HS256"
### 🔒 Passwords Hashing ############################################################## ### 🔒 Passwords Hashing ##############################################################
async def security_hash_password(password: str): async def security_hash_password(password: str):
return pbkdf2_sha256.hash(password) 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): async def security_verify_password(plain_password: str, hashed_password: str):
return pbkdf2_sha256.verify(plain_password, hashed_password) return pbkdf2_sha256.verify(plain_password, hashed_password)
### 🔒 Passwords Hashing ############################################################## ### 🔒 Passwords Hashing ##############################################################
### 🔒 Roles checking ############################################################## ### 🔒 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 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"] users = request.app.db["users"]
user = await users.find_one({"user_id": user_id}) user = await users.find_one({"user_id": user_id})
# Check if user is available #########
# Users existence verification
#########
if not user and user_id != "anonymous": if not user and user_id != "anonymous":
raise HTTPException( 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 # Check if user is anonymous
if user_id == "anonymous": if user_id == "anonymous":
return False return False
# Check if the user is an admin # Get User
user: UserInDB = UserInDB(**await users.find_one({"user_id": user_id})) user: UserInDB = UserInDB(**await users.find_one({"user_id": user_id}))
# Organization roles verification #########
# Organization Roles verification
#########
for org in user.orgs: 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: 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"): # Roles verification
return True #########
# If the user is not an owner or a editor, check if he has a role
# Get user roles
user_roles = user.roles 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": if action != "create":
await check_user_role_org_with_element_org(request, element_id, user_roles) return await check_user_role_org_with_element_org(
request, element_id, user_roles, action
# 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
# If no role is found, raise an error # If no role is found, raise an error
raise HTTPException( 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): async def check_element_type(element_id):
@ -104,11 +107,17 @@ async def check_element_type(element_id):
return "activities" return "activities"
else: else:
raise HTTPException( 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_type = await check_element_type(element_id)
element = request.app.db[element_type] element = request.app.db[element_type]
roles = request.app.db["roles"] 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] singular_form_element = element_type[:-1]
element_type_id = singular_form_element + "_id" element_type_id = singular_form_element + "_id"
element_org = await element.find_one({element_type_id: element_id}) element_org = await element.find_one({element_type_id: element_id})
for role in roles_list:
for role_id in roles_list: # Check if The role belongs to the same organization as the element
role = RoleInDB(**await roles.find_one({"role_id": role_id})) role_db = await roles.find_one({"role_id": role.role_id})
if role.org_id == element_org["org_id"]: role = RoleInDB(**role_db)
return True if role.org_id == element_org["org_id"] or role.org_id == "*":
if role.org_id == "*": # Check if user has the right role
return True 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: else:
raise HTTPException( 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 ############################################################## ### 🔒 Roles checking ##############################################################

View file

@ -84,6 +84,11 @@ async def get_activity(request: Request, activity_id: str, current_user: PublicU
course = await courses.find_one({"chapters": coursechapter_id}) course = await courses.find_one({"chapters": coursechapter_id})
isCoursePublic = course["public"] isCoursePublic = course["public"]
isAuthor = current_user.user_id in course["authors"]
if isAuthor:
activity = ActivityInDB(**activity)
return activity
# verify course rights # verify course rights
hasRoleRights = await verify_user_rights_with_roles( hasRoleRights = await verify_user_rights_with_roles(

View file

@ -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( 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"] courses = request.app.db["courses"]
orgs = request.app.db["organizations"] orgs = request.app.db["organizations"]
# TODO : Get only courses that user is admin/has roles of
# get org_id from slug # get org_id from slug
org = await orgs.find_one({"slug": org_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" status_code=status.HTTP_409_CONFLICT, detail="Organization does not exist"
) )
# get all courses from database # show only public courses if user is not logged in
all_courses = ( if current_user.user_id == "anonymous":
courses.find({"org_id": org["org_id"]}) all_courses = (
.sort("name", 1) courses.find({"org_id": org["org_id"], "public": True})
.skip(10 * (page - 1)) .sort("name", 1)
.limit(limit) .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 [ return [
json.loads(json.dumps(course, default=str)) json.loads(json.dumps(course, default=str))
@ -374,6 +366,11 @@ async def verify_rights(
course = await courses.find_one({"course_id": course_id}) course = await courses.find_one({"course_id": course_id})
isAuthor = current_user.user_id in course["authors"]
if isAuthor:
return True
if ( if (
current_user.user_id == "anonymous" current_user.user_id == "anonymous"
and course["public"] is True and course["public"] is True
@ -390,7 +387,6 @@ async def verify_rights(
hasRoleRights = await verify_user_rights_with_roles( hasRoleRights = await verify_user_rights_with_roles(
request, action, current_user.user_id, course_id, course["org_id"] 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: if not hasRoleRights and not isAuthor:
raise HTTPException( raise HTTPException(

View file

14
src/services/dev/dev.py Normal file
View file

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

View file

View file

@ -124,7 +124,7 @@ async def create_initial_data(request: Request):
await database_orgs.delete_many({}) await database_orgs.delete_many({})
organizations = [] organizations = []
for i in range(0, 5): for i in range(0, 2):
company = fake.company() company = fake.company()
# remove whitespace and special characters and make lowercase # remove whitespace and special characters and make lowercase
slug = ''.join(e for e in company if e.isalnum()).lower() slug = ''.join(e for e in company if e.isalnum()).lower()