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
281 changes: 281 additions & 0 deletions apps/api/openapi.json

Large diffs are not rendered by default.

34 changes: 31 additions & 3 deletions apps/api/src/openapi.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, expect, it } from "bun:test";
import { contract } from "@courselit/api-contract";
import { createOpenApiDocument } from "./openapi.js";

describe("CourseLit OpenAPI tags", () => {
Expand Down Expand Up @@ -27,9 +28,9 @@ describe("CourseLit OpenAPI tags", () => {
expect(document.paths["/v1/learner/auth/email-code/verify"]?.post?.tags).toEqual([
"Learners",
]);
expect(document.paths["/v1/public/{schoolId}/checkout-sessions"]?.post?.tags).toEqual([
"Storefront & Payments",
]);
expect(
document.paths["/v1/public/{schoolId}/checkout-sessions"]?.post?.tags,
).toEqual(["Storefront & Payments"]);
expect(document.paths["/v1/public/{schoolId}/site/pages"]?.get?.tags).toEqual([
"Public Website",
]);
Expand Down Expand Up @@ -102,3 +103,30 @@ describe("CourseLit OpenAPI tags", () => {
}
});
});

describe("CourseLit OpenAPI operation ids", () => {
const document = createOpenApiDocument("https://api.example.test");
const operations = Object.values(document.paths).flatMap((item) =>
["get", "post", "put", "patch", "delete"].flatMap((method) => {
const operation = (item as Record<string, { operationId?: string } | undefined>)[
method
];
return operation ? [operation] : [];
}),
);

it("names every operation after its contract key, once", () => {
const ids = operations.map((operation) => operation.operationId);
expect(ids.every((id) => typeof id === "string" && id.length > 0)).toBe(true);
expect(new Set(ids).size).toBe(ids.length);
expect(new Set(ids)).toEqual(new Set(Object.keys(contract)));
});

it("uses the contract key for representative routes", () => {
expect(document.paths["/v1/products"]?.get?.operationId).toBe(
Object.entries(contract).find(
([, route]) => route.method === "GET" && route.path === "/v1/products",
)?.[0],
);
});
});
5 changes: 5 additions & 0 deletions apps/api/src/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,13 @@ export function createOpenApiDocument(publicApiUrl: string) {
{
operationMapper: (operation, route) => {
const security = securityFor(route);
const operationId = operationIdByRoute.get(route);
if (!operationId) throw new Error("OpenAPI route is missing from the contract");
return {
...operation,
// The contract key, so tools such as the docs API reference get
// stable, readable operation URLs (and MCP tool names match).
operationId,
tags: [tagForPath((route as { path?: string }).path)],
...(security ? { security } : {}),
};
Expand Down
5 changes: 5 additions & 0 deletions frontlit/docs/_sections.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
"name": "Getting started",
"order": 10
},
{
"folder": "migrate",
"name": "Migrate to CourseLit",
"order": 15
},
{
"folder": "products",
"name": "Courses and downloads",
Expand Down
2 changes: 1 addition & 1 deletion frontlit/docs/developers/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ CourseLit has two programmatic interfaces to a school: a **REST API** for your o

## What you can build

Use the API to create and publish courses and downloads, manage sections, lessons and payment plans, grant learners access, read learner progress and sales figures, manage contacts and segments, work with the media library, and edit the school website. The API reference lists every operation with its parameters and example responses.
Use the API to create and publish courses and downloads, manage sections, lessons and payment plans, grant learners access, read learner progress and sales figures, manage contacts and segments, work with the media library, and edit the school website. The [API reference](/docs/api) lists every operation with its parameters and example responses.

## Base URL and versioning

Expand Down
45 changes: 45 additions & 0 deletions frontlit/docs/getting-started/cloud-vs-self-hosted.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
title: Cloud or self-hosted
description: Compare CourseLit's hosted cloud with running CourseLit on your own servers, and pick the one that suits your school.
nav_order: 15
---

You can use CourseLit in two ways. **CourseLit Cloud** at courselit.app is run for you. **Self-hosted CourseLit** is the same open-source software, running on servers you manage. Both have the same features and the same admin dashboard, and your learners can't tell them apart.

## Side by side

| | CourseLit Cloud | Self-hosted |
| --- | --- | --- |
| Price | Pro $29 a month or Business $79 a month, after a 14-day free trial | Free. You pay for your own servers and services |
| Features | Pro covers most schools. SCORM lessons, learner single sign-on and the audit log need Business | Every feature, with no licence key |
| Setup | Sign up and start building | Deploy the API, worker, admin and storefront yourself |
| Hosting and updates | Done for you | Done by you |
| Storage and email | Included, within your plan's limits | Connect your own media storage and email services |
| Fee on your sales | None | None |
| Support | Email on Pro, priority on Business | Community support on Discord |

On both, learners pay through your own Stripe, Lemon Squeezy or Razorpay account, and CourseLit takes no cut of your sales.

## Choose CourseLit Cloud if

- You want to teach and sell, not run servers.
- You want updates, backups and email delivery handled for you.
- You'd like to start today: create a school in a few minutes, with no card needed for the trial.

See [Plans and limits](/docs/getting-started/plans-and-limits) for what each plan includes, then [create your school](/docs/getting-started/create-a-school).

## Choose self-hosting if

- You need your data on your own infrastructure.
- You are comfortable running containers, PostgreSQL and a reverse proxy.
- You want every feature without a subscription.

<Callout type="warning" title="Self-hosting is for experienced operators today">

CourseLit 1.0 is in pre-release. There is no one-command installer, and details can change between pre-releases. Read [Self-hosting](/docs/self-hosting) before you start.

</Callout>

## Switching later

Your school's content is reachable through the [REST API](/docs/developers) on both, so you are not locked in to either choice. There is no one-click move between the cloud and a self-hosted deployment yet.
12 changes: 12 additions & 0 deletions frontlit/docs/getting-started/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@ Publish a free course and give a learner access in a few minutes.

</Card>

<Card title="Cloud or self-hosted" href="/docs/getting-started/cloud-vs-self-hosted">

Compare CourseLit Cloud with running CourseLit on your own servers.

</Card>

<Card title="Plans and limits" href="/docs/getting-started/plans-and-limits">

What the free trial, Pro and Business include.

</Card>

<Card title="Finish setting up" href="/docs/getting-started/setup-checklist">

Use the Onboarding checklist to get ready to sell and bring people in.
Expand Down
53 changes: 53 additions & 0 deletions frontlit/docs/getting-started/plans-and-limits.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
title: Plans and limits
description: What the CourseLit Cloud free trial, Pro and Business plans include, their email and storage limits, and what happens when a trial or plan ends.
nav_order: 16
---

CourseLit Cloud has two paid plans, **Pro** and **Business**, and every new school starts with a free trial. Self-hosted CourseLit has every feature and no limits. See [Cloud or self-hosted](/docs/getting-started/cloud-vs-self-hosted).

None of the plans charge a fee on your sales. Learners pay through your own payment provider account, and only that provider's fees apply. See [Set up payments](/docs/settings/payments).

## Plans

| | Pro | Business |
| --- | --- | --- |
| Price | $29 a month or $290 a year | $79 a month or $790 a year |
| Courses, downloads and learners | Unlimited | Unlimited |
| Team members | Unlimited | Unlimited |
| Community, website, blog and custom domain | Included | Included |
| Email marketing (broadcasts, sequences, drip emails) | 10,000 emails a month | 50,000 emails a month |
| Storage | 20 GB | 200 GB |
| Remove CourseLit branding | Yes | Yes |
| Code injection | Yes | Yes |
| REST API, API keys and MCP server | Yes | Yes |
| SCORM lessons | No | Yes |
| Learner single sign-on (SAML) | No | Yes |
| Audit log | No | Yes |
| Support | Email | Priority |

Paying yearly costs the same as ten months.

The email limit counts marketing email only. Sign-in codes, receipts, access emails and other messages your learners need always go out and never count towards it.

## Free trial

Every account gets one 14-day free trial, with no card needed.

- The trial includes every Business feature, except removing CourseLit branding.
- Code you add with code injection runs only for you, the school owner, not for visitors.
- Marketing emails don't send during the trial. Learners still get sign-in codes, receipts and access emails.
- You have 500 MB of storage.
- A custom domain works, but emails CourseLit sends to learners link to your school's courselit.app address until you choose a plan.

To keep your school, choose a plan from [Billing and plan](/docs/settings/billing-and-plan) before the trial ends.

## When a trial or plan ends

If a trial ends without a plan, or a subscription ends, your school becomes read-only for 15 days. You can still sign in and choose a plan in that time. After 15 days, the school and all its data are deleted, including its files and contacts.

<Callout type="warning" title="Deletion can't be undone">

Choose a plan within the 15 days if you want to keep your school.

</Callout>
18 changes: 18 additions & 0 deletions frontlit/docs/migrate/from-courselit-v0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
title: Move from CourseLit v0
description: CourseLit 1.0 moves from MongoDB to PostgreSQL. A guide to moving your school from CourseLit v0 is on the way.
nav_title: From CourseLit v0
nav_order: 10
---

CourseLit 1.0 is a rewrite. Among other changes, it stores your school in **PostgreSQL** instead of **MongoDB**, so a school can't be upgraded in place from CourseLit v0 (0.x).

We are working on a guide to moving your courses, downloads, community, learners and contacts from v0 to 1.0. This page will hold it.

<Callout type="info" title="Until the guide is ready">

- Keep running your school on CourseLit v0. The [v0 docs](/docs/legacy/introduction) still apply to it.
- If you self-host v0, don't point a 1.0 deployment at your existing MongoDB database. 1.0 needs its own PostgreSQL database.
- Questions? Ask in the [CourseLit community on Discord](https://discord.com/invite/GR4bQsN).

</Callout>
Loading