mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: use subdomains for organizations
This commit is contained in:
parent
600bb96603
commit
fac6b57ab3
16 changed files with 57 additions and 60 deletions
3
app.py
3
app.py
|
|
@ -1,5 +1,6 @@
|
|||
import logging
|
||||
from fastapi import FastAPI, Request
|
||||
import re
|
||||
from src.core.config.config import Settings, get_settings
|
||||
from src.core.events.events import shutdown_app, startup_app
|
||||
from src.main import global_router
|
||||
|
|
@ -23,9 +24,11 @@ app = FastAPI(
|
|||
root_path="/"
|
||||
)
|
||||
|
||||
origin_regex = re.compile(r"^http://[\w.-]+\.localhost:3000$")
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origin_regex=str(origin_regex.pattern),
|
||||
allow_origins=["http://localhost:3000", "http://localhost:3001"],
|
||||
allow_methods=["*"],
|
||||
allow_credentials=True,
|
||||
|
|
|
|||
|
|
@ -6,12 +6,6 @@ services:
|
|||
- "1338:80"
|
||||
volumes:
|
||||
- .:/usr/learnhouse
|
||||
frontend:
|
||||
build: ./front
|
||||
ports:
|
||||
- "3001:3000"
|
||||
volumes:
|
||||
- ./front:/usr/learnhouse/front
|
||||
mongo:
|
||||
image: mongo:5.0
|
||||
restart: always
|
||||
|
|
|
|||
|
|
@ -4,9 +4,9 @@ import { default as React, useEffect, useRef } from "react";
|
|||
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { getLecture } from "../../../../../../../../services/courses/lectures";
|
||||
import AuthProvider from "../../../../../../../../components/Security/AuthProvider";
|
||||
import EditorWrapper from "../../../../../../../../components/Editor/EditorWrapper";
|
||||
import { getLecture } from "@services/courses/lectures";
|
||||
import AuthProvider from "@components/Security/AuthProvider";
|
||||
import EditorWrapper from "@components/Editor/EditorWrapper";
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { getAPIUrl } from "@services/config";
|
||||
import { swrFetcher } from "@services/utils/requests";
|
||||
1
front/app/_editor/main.ts
Normal file
1
front/app/_editor/main.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
export const EDITOR = "main";
|
||||
|
|
@ -4,7 +4,7 @@ import Link from "next/link";
|
|||
import React, { useMemo } from "react";
|
||||
import Layout from "@components/UI/Layout";
|
||||
import { getLecture } from "@services/courses/lectures";
|
||||
import { getAPIUrl, getBackendUrl } from "@services/config";
|
||||
import { getAPIUrl, getBackendUrl, getUriWithOrg } from "@services/config";
|
||||
import Canva from "@components/LectureViews/DynamicCanva/DynamicCanva";
|
||||
import styled from "styled-components";
|
||||
import { getCourse } from "@services/courses/courses";
|
||||
|
|
@ -39,7 +39,7 @@ function LecturePage(params: any) {
|
|||
<LectureLayout>
|
||||
<LectureTopWrapper>
|
||||
<LectureThumbnail>
|
||||
<Link href={`/org/${orgslug}/course/${courseid}`}>
|
||||
<Link href={getUriWithOrg(orgslug,"") +`/course/${courseid}`}>
|
||||
<img src={`${getBackendUrl()}content/uploads/img/${course.course.thumbnail}`} alt="" />
|
||||
</Link>
|
||||
</LectureThumbnail>
|
||||
|
|
@ -56,7 +56,7 @@ function LecturePage(params: any) {
|
|||
{chapter.lectures.map((lecture: any) => {
|
||||
return (
|
||||
<>
|
||||
<Link href={`/org/${orgslug}/course/${courseid}/lecture/${lecture.id.replace("lecture_", "")}`}>
|
||||
<Link href={getUriWithOrg(orgslug,"") +`/course/${courseid}/lecture/${lecture.id.replace("lecture_", "")}`}>
|
||||
<ChapterIndicator key={lecture.id} />
|
||||
</Link>{" "}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { closeActivity, createActivity } from "@services/courses/activity";
|
|||
import Link from "next/link";
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { getAPIUrl, getBackendUrl } from "@services/config";
|
||||
import { getAPIUrl, getBackendUrl, getUriWithOrg } from "@services/config";
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { swrFetcher } from "@services/utils/requests";
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ const CourseIdPage = (params: any) => {
|
|||
<p>Course</p>
|
||||
<h1>
|
||||
{course.course.name}{" "}
|
||||
<Link href={`/org/${orgslug}/course/${courseid}/edit`} rel="noopener noreferrer">
|
||||
<Link href={getUriWithOrg(orgslug,"") +`/course/${courseid}/edit`} rel="noopener noreferrer">
|
||||
<Pencil2Icon />
|
||||
</Link>{" "}
|
||||
</h1>
|
||||
|
|
@ -56,7 +56,7 @@ const CourseIdPage = (params: any) => {
|
|||
{chapter.lectures.map((lecture: any) => {
|
||||
return (
|
||||
<>
|
||||
<Link href={`/org/${orgslug}/course/${courseid}/lecture/${lecture.id.replace("lecture_", "")}`}>
|
||||
<Link href={getUriWithOrg(orgslug,"") +`/course/${courseid}/lecture/${lecture.id.replace("lecture_", "")}`}>
|
||||
<ChapterIndicator />
|
||||
</Link>{" "}
|
||||
</>
|
||||
|
|
@ -97,7 +97,7 @@ const CourseIdPage = (params: any) => {
|
|||
<>
|
||||
<p>
|
||||
Lecture {lecture.name}
|
||||
<Link href={`/org/${orgslug}/course/${courseid}/lecture/${lecture.id.replace("lecture_", "")}`} rel="noopener noreferrer">
|
||||
<Link href={getUriWithOrg(orgslug,"") +`/course/${courseid}/lecture/${lecture.id.replace("lecture_", "")}`} rel="noopener noreferrer">
|
||||
<EyeOpenIcon />
|
||||
</Link>{" "}
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ const CoursesIndexPage = (params: any) => {
|
|||
<button style={{ backgroundColor: "red", border: "none" }} onClick={() => deleteCourses(course.course_id)}>
|
||||
Delete <Trash size={10}></Trash>
|
||||
</button>
|
||||
<Link href={"/org/" + orgslug + "/course/" + removeCoursePrefix(course.course_id)}>
|
||||
<Link href={"/org/" + orgslug + "/course/" + removeCoursePrefix(course.course_id) + "/edit"}>
|
||||
<Link href={getUriWithOrg(orgslug,"") + "/course/" + removeCoursePrefix(course.course_id)}>
|
||||
<Link href={getUriWithOrg(orgslug,"") + "/course/" + removeCoursePrefix(course.course_id) + "/edit"}>
|
||||
<button>
|
||||
Edit <Edit2 size={10}></Edit2>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ import "@styles/globals.css";
|
|||
import { Menu } from "@components/UI/Elements/Menu";
|
||||
import AuthProvider from "@components/Security/AuthProvider";
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
export default function RootLayout({ children, params }: { children: React.ReactNode , params:any}) {
|
||||
|
||||
return (
|
||||
<>
|
||||
<AuthProvider>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"use client";
|
||||
import { Title } from "@components/UI/Elements/Styles/Title";
|
||||
import { getUriWithOrg } from "@services/config";
|
||||
import Link from "next/link";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
|
|
@ -10,7 +11,7 @@ const OrgHomePage = (params: any) => {
|
|||
return (
|
||||
<div>
|
||||
<Title>Welcome {orgslug} 👋🏻</Title>
|
||||
<Link href={pathname + "/courses"}>
|
||||
<Link href={getUriWithOrg(orgslug,"/courses")}>
|
||||
<button>See Courses </button>
|
||||
</Link>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ import { Title } from "../../components/UI/Elements/Styles/Title";
|
|||
import { loginAndGetToken } from "../../services/auth/auth";
|
||||
|
||||
const Login = () => {
|
||||
const [email, setEmail] = React.useState("");
|
||||
const [password, setPassword] = React.useState("");
|
||||
const [email, setEmail] = React.useState("admin@admin.admin");
|
||||
const [password, setPassword] = React.useState("admin");
|
||||
const router = useRouter();
|
||||
|
||||
const handleSubmit = (e: any) => {
|
||||
|
|
@ -39,7 +39,7 @@ const Login = () => {
|
|||
<Title>Login</Title>
|
||||
|
||||
<form>
|
||||
<input onChange={handleEmailChange} type="text" placeholder="email" />
|
||||
<input onChange={handleEmailChange} type="text" placeholder="email" />
|
||||
<input onChange={handlePasswordChange} type="password" placeholder="password" />
|
||||
<button onClick={handleSubmit} type="submit">
|
||||
Login
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { Title } from "../../components/UI/Elements/Styles/Title";
|
|||
import { deleteOrganizationFromBackend } from "@services/orgs";
|
||||
import useSWR, { mutate } from "swr";
|
||||
import { swrFetcher } from "@services/utils/requests";
|
||||
import { getAPIUrl } from "@services/config";
|
||||
import { getAPIUrl, getUriWithOrg } from "@services/config";
|
||||
|
||||
const Organizations = () => {
|
||||
const { data : organizations , error } = useSWR(`${getAPIUrl()}orgs/user/page/1/limit/10`, swrFetcher)
|
||||
|
|
@ -32,7 +32,7 @@ const Organizations = () => {
|
|||
<div>
|
||||
{organizations.map((org: any) => (
|
||||
<div key={org.org_id}>
|
||||
<Link href={`/org/${org.slug}`}>
|
||||
<Link href={getUriWithOrg(org.slug,"/")}>
|
||||
<h3>{org.name}</h3>
|
||||
</Link>
|
||||
<button onClick={() => deleteOrganization(org.org_id)}>Delete</button>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import React from "react";
|
|||
import { Draggable } from "react-beautiful-dnd";
|
||||
import { EyeOpenIcon, Pencil2Icon } from '@radix-ui/react-icons'
|
||||
import styled from "styled-components";
|
||||
import { getUriWithOrg } from "@services/config";
|
||||
|
||||
function Lecture(props: any) {
|
||||
|
||||
|
|
@ -12,13 +13,13 @@ function Lecture(props: any) {
|
|||
<LectureWrapper key={props.lecture.id} {...provided.draggableProps} {...provided.dragHandleProps} ref={provided.innerRef}>
|
||||
<p>{props.lecture.name} </p>
|
||||
<Link
|
||||
href={`/org/${props.orgslug}/course/${props.courseid}/lecture/${props.lecture.id.replace("lecture_", "")}`}
|
||||
href={getUriWithOrg(props.orgslug,"")+`/course/${props.courseid}/lecture/${props.lecture.id.replace("lecture_", "")}`}
|
||||
|
||||
rel="noopener noreferrer">
|
||||
<EyeOpenIcon/>
|
||||
</Link>
|
||||
<Link
|
||||
href={`/org/${props.orgslug}/course/${props.courseid}/lecture/${props.lecture.id.replace("lecture_", "")}/edit`}
|
||||
href={getUriWithOrg(props.orgslug,"") +`/course/${props.courseid}/lecture/${props.lecture.id.replace("lecture_", "")}/edit`}
|
||||
rel="noopener noreferrer">
|
||||
<Pencil2Icon/>
|
||||
</Link>
|
||||
|
|
|
|||
|
|
@ -20,11 +20,6 @@ export default function middleware(req: NextRequest) {
|
|||
// Get hostname of request (e.g. demo.vercel.pub, demo.localhost:3000)
|
||||
const hostname = req.headers.get("host") || "learnhouse.app";
|
||||
|
||||
// Only for demo purposes - remove this if you want to use your root domain as the landing page
|
||||
if (hostname === "vercel.pub" || hostname === "platforms.vercel.app") {
|
||||
return NextResponse.redirect("https://demo.vercel.pub");
|
||||
}
|
||||
|
||||
/* You have to replace ".vercel.pub" with your own domain if you deploy this example under your domain.
|
||||
You can also use wildcard subdomains on .vercel.app links that are associated with your Vercel team slug
|
||||
in this case, our team slug is "platformize", thus *.platformize.vercel.app works. Do note that you'll
|
||||
|
|
@ -33,31 +28,29 @@ export default function middleware(req: NextRequest) {
|
|||
process.env.NODE_ENV === "production" && process.env.VERCEL === "1"
|
||||
? hostname.replace(`.vercel.pub`, "").replace(`.platformize.vercel.app`, "")
|
||||
: hostname.replace(`.localhost:3000`, "");
|
||||
|
||||
// if url starts with "/organizations" rewrite to path
|
||||
|
||||
/* Editor route */
|
||||
if (url.pathname.match(/^\/course\/[^/]+\/lecture\/[^/]+\/edit$/)) {
|
||||
url.pathname = `/_editor${url.pathname}`;
|
||||
console.log("editor route", url.pathname);
|
||||
|
||||
return NextResponse.rewrite(url, { headers: { orgslug: currentHost } });
|
||||
}
|
||||
|
||||
/* Organizations route */
|
||||
if (url.pathname.startsWith("/organizations")) {
|
||||
url.pathname = url.pathname.replace("/organizations", `/organizations${currentHost}`);
|
||||
// remove localhost:3000 from url
|
||||
url.pathname = url.pathname.replace(`localhost:3000`, "");
|
||||
console.log(url);
|
||||
|
||||
|
||||
return NextResponse.rewrite(url);
|
||||
}
|
||||
|
||||
else if (url.pathname.startsWith("/org")) {
|
||||
url.pathname = url.pathname.replace("/organizations", `/_orgs/${currentHost}`);
|
||||
// remove localhost:3000 from url
|
||||
|
||||
url.pathname = `/_orgs/${currentHost}${url.pathname}`;
|
||||
url.pathname = url.pathname.replace(`localhost:3000/org/`, "");
|
||||
console.log(url);
|
||||
|
||||
return NextResponse.rewrite(url);
|
||||
}
|
||||
console.log("currentHost", url);
|
||||
|
||||
// rewrite everything else to `/_sites/[site] dynamic route
|
||||
url.pathname = `/_orgs/${currentHost}${url.pathname}`;
|
||||
console.log(url);
|
||||
|
||||
return NextResponse.rewrite(url, { headers: { "olgslug": currentHost } });
|
||||
|
||||
return NextResponse.rewrite(url, { headers: { olgslug: currentHost } });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,10 @@ interface LoginAndGetTokenResponse {
|
|||
|
||||
export async function loginAndGetToken(username: string, password: string): Promise<LoginAndGetTokenResponse> {
|
||||
// Request Config
|
||||
const HeadersConfig = new Headers({ "Content-Type": "application/x-www-form-urlencoded" , Origin: "http://localhost:3000" });
|
||||
|
||||
// get origin
|
||||
const origin = window.location.origin;
|
||||
const HeadersConfig = new Headers({ "Content-Type": "application/x-www-form-urlencoded" , Origin: origin });
|
||||
const urlencoded = new URLSearchParams({ username: username, password: password });
|
||||
|
||||
const requestOptions: any = {
|
||||
|
|
@ -28,7 +31,8 @@ export async function loginAndGetToken(username: string, password: string): Prom
|
|||
}
|
||||
|
||||
export async function getUserInfo(token: string): Promise<any> {
|
||||
const HeadersConfig = new Headers({ Authorization: `Bearer ${token}`, Origin: "http://localhost:3000" });
|
||||
const origin = window.location.origin;
|
||||
const HeadersConfig = new Headers({ Authorization: `Bearer ${token}`, Origin:origin });
|
||||
|
||||
const requestOptions: any = {
|
||||
method: "GET",
|
||||
|
|
|
|||
|
|
@ -6,16 +6,13 @@ export const getAPIUrl = () => LEARNHOUSE_API_URL;
|
|||
export const getBackendUrl = () => LEARNHOUSE_BACKEND_URL;
|
||||
|
||||
export const getUriWithOrg = (orgslug: string, path: string) => {
|
||||
return `http://localhost:3000/org/${orgslug}${path}`;
|
||||
return `http://${orgslug}.localhost:3000${path}`;
|
||||
};
|
||||
|
||||
export const getOrgFromUri = (uri: any) => {
|
||||
// if url contains /org
|
||||
if (uri.includes("/org/")) {
|
||||
let org = uri.match(/\/org\/([\w]+)/)[1];
|
||||
return org;
|
||||
}
|
||||
else {
|
||||
return "";
|
||||
}
|
||||
export const getOrgFromUri = () => {
|
||||
const hostname = window.location.hostname;
|
||||
// get the orgslug from the hostname
|
||||
const orgslug = hostname.split(".")[0];
|
||||
return orgslug;
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ class Settings(BaseModel):
|
|||
authjwt_token_location = {"cookies"}
|
||||
authjwt_cookie_csrf_protect = False
|
||||
authjwt_access_token_expires = False # (pre-alpha only) # TODO: set to 1 hour
|
||||
authjwt_cookie_samesite = "none"
|
||||
authjwt_cookie_secure = True
|
||||
|
||||
|
||||
@AuthJWT.load_config # type: ignore
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue