Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/code-quality.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: '18.x'
node-version: '24.x'
- uses: pnpm/action-setup@v2
with:
version: 8
Expand Down
10 changes: 5 additions & 5 deletions apps/docs/src/pages/en/website/blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ You will also see the newly added link on the header itself.
3. Click on the pencil icon against the newly added link to edit it as shown above.
4. Change the label (displayed as text on the header block) and the URL (where the user should be taken upon clicking the label on the header) and click `Done` to save.
![Header edit link](/assets/pages/header-edit-link.png)
</details>
</details>

### [Rich Text](#rich-text)

Expand Down Expand Up @@ -69,7 +69,7 @@ The rich text block uses the same text editor available elsewhere on the platfor
2. Click on the floating `link` icon to reveal a text input.
3. In the popup text input, enter the URL as shown below and press <kbd>Enter</kbd>.
![Create a hyperlink in rich text block](/assets/pages/courselit-text-editor-create-links.gif)
</details>
</details>

### [Hero](#hero)

Expand All @@ -95,7 +95,7 @@ Following is how it looks on a page.
4. In the button action, enter the URL the user should be taken to upon clicking.
a. If the URL is from your own school, use its relative form, i.e., `/courses`.
b. If the URL is from some external website, use the absolute (complete) URL, i.e., `https://website.com/courses`.
</details>
</details>

### [Grid](#grid)

Expand Down Expand Up @@ -140,7 +140,7 @@ A grid block comes in handy when you want to show some sort of list, for example
4. In the button action, enter the URL the user should be taken to upon clicking.
a. If the URL is from your own school, use its relative form, i.e., `/courses`.
b. If the URL is from some external website, use the absolute (complete) URL, i.e., `https://website.com/courses`.
</details>
</details>

### [Featured](#featured)

Expand Down Expand Up @@ -322,7 +322,7 @@ In the `Design` panel, you can customize:
- Maximum width
- Vertical padding
- Social media links (Facebook, Twitter, Instagram, LinkedIn, YouTube, Discord, GitHub)
</details>
</details>

## [Shared blocks](#shared-blocks)

Expand Down
36 changes: 18 additions & 18 deletions apps/docs/src/pages/en/website/themes.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,30 +192,30 @@ The typography editor lets you customize text styles across your website. These
- Header 3: Smaller titles for subsections
- Header 4: Small titles for minor sections
- Preheader: Introductory text that appears above headers
</details>
</details>

<details>
<summary>Subheaders</summary>

- Subheader 1: Primary subheaders for section introductions
- Subheader 2: Secondary subheaders for supporting text
</details>
</details>

<details>
<summary>Body Text</summary>

- Text 1: Main body text for content
- Text 2: Secondary body text for supporting content
- Caption: Small text for image captions and footnotes
</details>
</details>

<details>
<summary>Interactive Elements</summary>

- Link: Text for clickable links
- Button: Text for buttons and calls-to-action
- Input: Text for form fields and search boxes
</details>
</details>

For each text style, you can customize:

Expand Down Expand Up @@ -243,7 +243,7 @@ CourseLit provides a carefully curated selection of professional fonts, organize
- **Mulish**: A geometric sans-serif with a modern feel
- **Nunito**: A well-balanced font with rounded terminals
- **Work Sans**: A clean, modern font with a geometric feel
</details>
</details>

<details>
<summary>Serif Fonts</summary>
Expand All @@ -253,7 +253,7 @@ CourseLit provides a carefully curated selection of professional fonts, organize
- **Playfair Display**: An elegant serif font for headings
- **Roboto Slab**: A serif variant of Roboto
- **Source Serif 4**: A serif font designed for digital reading
</details>
</details>

<details>
<summary>Display Fonts</summary>
Expand All @@ -264,15 +264,15 @@ CourseLit provides a carefully curated selection of professional fonts, organize
- **Rubik**: A sans-serif with a geometric feel
- **Oswald**: A reworking of the classic style
- **Bebas Neue**: A display font with a strong personality
</details>
</details>

<details>
<summary>Modern Fonts</summary>

- **Lato**: A sans-serif font with a warm feel
- **PT Sans**: A font designed for public use
- **Quicksand**: A display sans-serif with rounded terminals
</details>
</details>

Each font is optimized for web use and includes multiple weights for flexibility in design. All fonts support Latin characters and are carefully selected for their readability and professional appearance.

Expand All @@ -290,7 +290,7 @@ The interactives editor allows you to customize the appearance of interactive el
- Shadow effects: From None to 2X Large
- Custom styles: Add your own custom styles using [supported Tailwind classes](#supported-tailwind-classes)
- Disabled state: How the button looks when it can't be clicked
</details>
</details>

<details>
<summary>Link</summary>
Expand All @@ -300,7 +300,7 @@ The interactives editor allows you to customize the appearance of interactive el
- Text shadow: Add depth to your links
- Custom styles: Add your own custom styles using [supported Tailwind classes](#supported-tailwind-classes)
- Disabled state: How the link looks when it can't be clicked
</details>
</details>

<details>
<summary>Card</summary>
Expand All @@ -309,7 +309,7 @@ The interactives editor allows you to customize the appearance of interactive el
- Border style: Choose from various border styles
- Shadow effects: Add depth to your cards
- Custom styles: Add your own custom styles using [supported Tailwind classes](#supported-tailwind-classes)
</details>
</details>

<details>
<summary>Input</summary>
Expand All @@ -320,7 +320,7 @@ The interactives editor allows you to customize the appearance of interactive el
- Shadow effects: Add depth to your input fields
- Custom styles: Add your own custom styles using [supported Tailwind classes](#supported-tailwind-classes)
- Disabled state: How the input looks when it can't be used
</details>
</details>

### 4. Structure

Expand All @@ -332,14 +332,14 @@ The structure editor lets you customize the layout of your pages, like section p
<summary>Page</summary>

- Maximum width options: - 2XL (42rem): Compact layout - 3XL (48rem): Standard layout - 4XL (56rem): Wide layout - 5XL (64rem): Extra wide layout - 6XL (72rem): Full width layout
</details>
</details>

<details>
<summary>Section</summary>

- Horizontal padding: Space on the left and right sides (None to 9X Large)
- Vertical padding: Space on the top and bottom (None to 9X Large)
</details>
</details>

## Publishing Changes

Expand Down Expand Up @@ -387,7 +387,7 @@ When adding custom styles to interactive elements, you can use the following Tai
- `text-6xl`: 6X large text
- `text-7xl`: 7X large text
- `text-8xl`: 8X large text
</details>
</details>

<details>
<summary>Padding</summary>
Expand All @@ -399,7 +399,7 @@ When adding custom styles to interactive elements, you can use the following Tai
#### Horizontal Padding

- `px-4` to `px-20`: Horizontal padding from 1rem to 5rem
</details>
</details>

<details>
<summary>Colors</summary>
Expand Down Expand Up @@ -454,7 +454,7 @@ Variants available: `hover`, `disabled`, `dark`
- `ease-out`: Ease out
- `ease-in-out`: Ease in and out
- `ease-linear`: Linear
</details>
</details>

<details>
<summary>Transforms</summary>
Expand All @@ -481,7 +481,7 @@ Variants available: `hover`, `disabled`, `dark`
- `scale-110`: 110% scale
- `scale-125`: 125% scale
- `scale-150`: 150% scale
</details>
</details>

<details>
<summary>Shadows</summary>
Expand Down
7 changes: 6 additions & 1 deletion apps/web/__mocks__/slugify.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
const slugify = jest.fn();
const slugify = jest.fn((value: string) =>
value
.toLowerCase()
.replace(/[^a-z0-9]+/gi, "-")
.replace(/^-+|-+$/g, ""),
);

export default slugify;
43 changes: 24 additions & 19 deletions apps/web/app/api/graph/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import schema from "@/graphql";
import { graphql } from "graphql";
import { getAddress } from "@/lib/utils";
import User from "@models/User";
import DomainModel, { Domain } from "@models/Domain";
import { auth } from "@/auth";
import { als } from "@/async-local-storage";
import { getCachedDomain } from "@/lib/domain-cache";

async function updateLastActive(user: any) {
const dateNow = new Date();
Expand All @@ -20,26 +20,36 @@ async function updateLastActive(user: any) {
}

export async function POST(req: NextRequest) {
const domain = await DomainModel.findOne<Domain>({
name: req.headers.get("domain"),
});
if (!domain) {
return Response.json({ message: "Domain not found" }, { status: 404 });
const domainName = req.headers.get("domain");
if (!domainName) {
return Response.json(
{ errors: [{ message: "Domain header is missing" }] },
{ status: 400 },
);
}

const map = new Map();
map.set("domain", req.headers.get("domain"));
map.set("domainId", req.headers.get("domainId"));
als.enterWith(map);
const [domain, session, body] = await Promise.all([
getCachedDomain(domainName),
auth.api.getSession({ headers: req.headers }),
req.json(),
]);

if (!domain) {
return Response.json(
{ errors: [{ message: "Domain not found" }] },
{ status: 404 },
);
}

const session = await auth.api.getSession({
headers: req.headers,
});
const body = await req.json();
if (!body.hasOwnProperty("query")) {
return Response.json({ error: "Query is missing" }, { status: 400 });
}

const map = new Map();
map.set("domain", domainName);
map.set("domainId", req.headers.get("domainId") || domain._id.toString());
als.enterWith(map);

let user;
if (session) {
user = await User.findOne({
Expand All @@ -53,11 +63,6 @@ export async function POST(req: NextRequest) {
}
}

// const body = await req.json();
// if (!body.hasOwnProperty("query")) {
// return Response.json({ error: "Query is missing" }, { status: 400 });
// }

let query, variables;
if (typeof body.query === "string") {
query = body.query;
Expand Down
92 changes: 92 additions & 0 deletions apps/web/app/verify-domain/__tests__/resolve-domain.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* @jest-environment node
*/

import { resolveDomainFromHost } from "@/app/verify-domain/resolve-domain";
import { getCachedDomain, getDomainFromHost } from "@/lib/domain-cache";

jest.mock("@/lib/domain-cache", () => ({
getCachedDomain: jest.fn(),
getDomainFromHost: jest.fn(),
}));

describe("verify-domain resolution", () => {
const mockGetCachedDomain = getCachedDomain as jest.Mock;
const mockGetDomainFromHost = getDomainFromHost as jest.Mock;

beforeEach(() => {
jest.clearAllMocks();
});

it("MULTITENANT=true resolves school by subdomain host", async () => {
mockGetDomainFromHost.mockResolvedValue({ name: "domain1" });

const domain = await resolveDomainFromHost({
multitenant: true,
host: "domain1.clqa.site",
domainNameForSingleTenancy: "main",
});

expect(mockGetDomainFromHost).toHaveBeenCalledWith("domain1.clqa.site");
expect(mockGetCachedDomain).not.toHaveBeenCalled();
expect(domain?.name).toBe("domain1");
});

it("MULTITENANT=true resolves school by custom domain host", async () => {
mockGetDomainFromHost.mockResolvedValue({ name: "domain1" });

const domain = await resolveDomainFromHost({
multitenant: true,
host: "school.example.com",
domainNameForSingleTenancy: "main",
});

expect(mockGetDomainFromHost).toHaveBeenCalledWith(
"school.example.com",
);
expect(mockGetCachedDomain).not.toHaveBeenCalled();
expect(domain?.name).toBe("domain1");
});

it("MULTITENANT=false resolves school by DOMAIN_NAME_FOR_SINGLE_TENANCY for custom domain access", async () => {
mockGetCachedDomain.mockResolvedValue({ name: "main" });

const domain = await resolveDomainFromHost({
multitenant: false,
host: "school.example.com",
domainNameForSingleTenancy: "main",
});

expect(mockGetCachedDomain).toHaveBeenCalledWith("main");
expect(mockGetDomainFromHost).not.toHaveBeenCalled();
expect(domain?.name).toBe("main");
});

it("MULTITENANT=false resolves school by DOMAIN_NAME_FOR_SINGLE_TENANCY for IP access", async () => {
mockGetCachedDomain.mockResolvedValue({ name: "main" });

const domain = await resolveDomainFromHost({
multitenant: false,
host: "10.0.0.1",
domainNameForSingleTenancy: "main",
});

expect(mockGetCachedDomain).toHaveBeenCalledWith("main");
expect(mockGetDomainFromHost).not.toHaveBeenCalled();
expect(domain?.name).toBe("main");
});

it("MULTITENANT=false resolves school by DOMAIN_NAME_FOR_SINGLE_TENANCY for IP:PORT access", async () => {
mockGetCachedDomain.mockResolvedValue({ name: "main" });

const domain = await resolveDomainFromHost({
multitenant: false,
host: "192.168.0.1:3000",
domainNameForSingleTenancy: "main",
});

expect(mockGetCachedDomain).toHaveBeenCalledWith("main");
expect(mockGetDomainFromHost).not.toHaveBeenCalled();
expect(domain?.name).toBe("main");
});
});
Loading
Loading