Skip to content

Commit 00aa5d3

Browse files
author
Rajat Saxena
committed
Large file uploads
Fixes #657
1 parent 57a9da5 commit 00aa5d3

13 files changed

Lines changed: 715 additions & 86 deletions

File tree

apps/docs/public/assets/schools/self-host.svg

Lines changed: 3 additions & 0 deletions
Loading

apps/docs/src/pages/en/self-hosting/introduction.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ You should self host CourseLit, if you:
1515
- want complete control of your data
1616
- want to host it behind a firewall for internal use
1717

18+
## How to self host?
19+
20+
![Self hosting options](/assets/schools/self-host.svg)
21+
1822
### Self host CourseLit
1923

2024
See [the self hosting guide](/en/self-hosting/self-host).

apps/web/app/api/media/presigned/route.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import { NextRequest } from "next/server";
22
import { responses } from "@/config/strings";
3-
import * as medialitService from "@/services/medialit";
43
import { UIConstants as constants } from "@courselit/common-models";
54
import { checkPermission } from "@courselit/utils";
65
import User from "@models/User";
76
import DomainModel, { Domain } from "@models/Domain";
87
import { auth } from "@/auth";
98
import { error } from "@/services/logger";
9+
import { MediaLit } from "medialit";
10+
11+
const medialit = new MediaLit({
12+
apiKey: process.env.MEDIALIT_APIKEY,
13+
endpoint: process.env.MEDIALIT_SERVER
14+
})
1015

1116
export async function POST(req: NextRequest) {
1217
const domain = await DomainModel.findOne<Domain>({
@@ -41,10 +46,13 @@ export async function POST(req: NextRequest) {
4146
}
4247

4348
try {
44-
let response = await medialitService.getPresignedUrlForUpload(
45-
domain.name,
46-
);
47-
return Response.json({ url: response });
49+
let signature = await medialit.getSignature({
50+
group: domain.name
51+
})
52+
return Response.json({
53+
signature,
54+
endpoint: medialit.endpoint
55+
});
4856
} catch (err: any) {
4957
error(err.message, {
5058
stack: err.stack,

apps/web/next-env.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/// <reference types="next" />
22
/// <reference types="next/image-types/global" />
3+
/// <reference types="next/navigation-types/compat/navigation" />
34
/// <reference path="./.next/types/routes.d.ts" />
45

56
// NOTE: This file should not be edited

apps/web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"jsdom": "^26.1.0",
5858
"lodash.debounce": "^4.0.8",
5959
"lucide-react": "^0.544.0",
60+
"medialit": "^0.1.0",
6061
"mongodb": "^6.15.0",
6162
"mongoose": "^8.13.1",
6263
"next": "^15.5.4",

packages/components-library/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,17 +61,19 @@
6161
"@dnd-kit/sortable": "^8.0.0",
6262
"@dnd-kit/utilities": "^3.2.2",
6363
"@radix-ui/react-accordion": "^1.1.2",
64+
"@radix-ui/react-alert-dialog": "^1.1.11",
6465
"@radix-ui/react-avatar": "^1.0.4",
6566
"@radix-ui/react-checkbox": "^1.0.4",
6667
"@radix-ui/react-dialog": "^1.1.14",
6768
"@radix-ui/react-dropdown-menu": "^2.0.6",
6869
"@radix-ui/react-form": "^0.0.3",
6970
"@radix-ui/react-label": "^2.1.2",
7071
"@radix-ui/react-popover": "^1.0.7",
72+
"@radix-ui/react-progress": "^1.1.7",
7173
"@radix-ui/react-scroll-area": "^1.0.5",
7274
"@radix-ui/react-select": "^2.1.6",
7375
"@radix-ui/react-slider": "^1.1.2",
74-
"@radix-ui/react-slot": "^1.1.2",
76+
"@radix-ui/react-slot": "^1.2.3",
7577
"@radix-ui/react-switch": "^1.1.3",
7678
"@radix-ui/react-tabs": "^1.1.3",
7779
"@radix-ui/react-toast": "^1.2.2",
@@ -83,6 +85,7 @@
8385
"lucide-react": "^0.309.0",
8486
"react-dom": "^18.2.0",
8587
"tailwind-merge": "^2.2.0",
86-
"tailwindcss-animate": "^1.0.7"
88+
"tailwindcss-animate": "^1.0.7",
89+
"tus-js-client": "^4.3.1"
8790
}
8891
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
"use client";
2+
3+
import * as React from "react";
4+
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog";
5+
6+
import { cn } from "@/lib/utils";
7+
import { buttonVariants } from "@/components/ui/button";
8+
9+
const AlertDialog = AlertDialogPrimitive.Root;
10+
11+
const AlertDialogTrigger = AlertDialogPrimitive.Trigger;
12+
13+
const AlertDialogPortal = AlertDialogPrimitive.Portal;
14+
15+
const AlertDialogOverlay = React.forwardRef<
16+
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
17+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
18+
>(({ className, ...props }, ref) => (
19+
<AlertDialogPrimitive.Overlay
20+
className={cn(
21+
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
22+
className,
23+
)}
24+
{...props}
25+
ref={ref}
26+
/>
27+
));
28+
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;
29+
30+
const AlertDialogContent = React.forwardRef<
31+
React.ElementRef<typeof AlertDialogPrimitive.Content>,
32+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
33+
>(({ className, ...props }, ref) => (
34+
<AlertDialogPortal>
35+
<AlertDialogOverlay />
36+
<AlertDialogPrimitive.Content
37+
ref={ref}
38+
className={cn(
39+
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
40+
className,
41+
)}
42+
{...props}
43+
/>
44+
</AlertDialogPortal>
45+
));
46+
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;
47+
48+
const AlertDialogHeader = ({
49+
className,
50+
...props
51+
}: React.HTMLAttributes<HTMLDivElement>) => (
52+
<div
53+
className={cn(
54+
"flex flex-col space-y-2 text-center sm:text-left",
55+
className,
56+
)}
57+
{...props}
58+
/>
59+
);
60+
AlertDialogHeader.displayName = "AlertDialogHeader";
61+
62+
const AlertDialogFooter = ({
63+
className,
64+
...props
65+
}: React.HTMLAttributes<HTMLDivElement>) => (
66+
<div
67+
className={cn(
68+
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
69+
className,
70+
)}
71+
{...props}
72+
/>
73+
);
74+
AlertDialogFooter.displayName = "AlertDialogFooter";
75+
76+
const AlertDialogTitle = React.forwardRef<
77+
React.ElementRef<typeof AlertDialogPrimitive.Title>,
78+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
79+
>(({ className, ...props }, ref) => (
80+
<AlertDialogPrimitive.Title
81+
ref={ref}
82+
className={cn("text-lg font-semibold", className)}
83+
{...props}
84+
/>
85+
));
86+
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;
87+
88+
const AlertDialogDescription = React.forwardRef<
89+
React.ElementRef<typeof AlertDialogPrimitive.Description>,
90+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
91+
>(({ className, ...props }, ref) => (
92+
<AlertDialogPrimitive.Description
93+
ref={ref}
94+
className={cn("text-sm text-muted-foreground", className)}
95+
{...props}
96+
/>
97+
));
98+
AlertDialogDescription.displayName =
99+
AlertDialogPrimitive.Description.displayName;
100+
101+
const AlertDialogAction = React.forwardRef<
102+
React.ElementRef<typeof AlertDialogPrimitive.Action>,
103+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
104+
>(({ className, ...props }, ref) => (
105+
<AlertDialogPrimitive.Action
106+
ref={ref}
107+
className={cn(buttonVariants(), className)}
108+
{...props}
109+
/>
110+
));
111+
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;
112+
113+
const AlertDialogCancel = React.forwardRef<
114+
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
115+
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
116+
>(({ className, ...props }, ref) => (
117+
<AlertDialogPrimitive.Cancel
118+
ref={ref}
119+
className={cn(
120+
buttonVariants({ variant: "outline" }),
121+
"mt-2 sm:mt-0",
122+
className,
123+
)}
124+
{...props}
125+
/>
126+
));
127+
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
128+
129+
export {
130+
AlertDialog,
131+
AlertDialogPortal,
132+
AlertDialogOverlay,
133+
AlertDialogTrigger,
134+
AlertDialogContent,
135+
AlertDialogHeader,
136+
AlertDialogFooter,
137+
AlertDialogTitle,
138+
AlertDialogDescription,
139+
AlertDialogAction,
140+
AlertDialogCancel,
141+
};

packages/components-library/src/components/ui/input.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,13 @@ import * as React from "react";
22

33
import { cn } from "@/lib/utils";
44

5-
export interface InputProps
6-
extends React.InputHTMLAttributes<HTMLInputElement> {}
7-
8-
const Input = React.forwardRef<HTMLInputElement, InputProps>(
5+
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
96
({ className, type, ...props }, ref) => {
107
return (
118
<input
129
type={type}
1310
className={cn(
14-
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
11+
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
1512
className,
1613
)}
1714
ref={ref}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import * as React from "react";
2+
import * as ProgressPrimitive from "@radix-ui/react-progress";
3+
4+
import { cn } from "@/lib/utils";
5+
6+
const Progress = React.forwardRef<
7+
React.ElementRef<typeof ProgressPrimitive.Root>,
8+
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
9+
>(({ className, value, ...props }, ref) => (
10+
<ProgressPrimitive.Root
11+
ref={ref}
12+
className={cn(
13+
"relative h-4 w-full overflow-hidden rounded-full bg-secondary",
14+
className,
15+
)}
16+
{...props}
17+
>
18+
<ProgressPrimitive.Indicator
19+
className="h-full w-full flex-1 bg-primary transition-all"
20+
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
21+
/>
22+
</ProgressPrimitive.Root>
23+
));
24+
Progress.displayName = ProgressPrimitive.Root.displayName;
25+
26+
export { Progress };

0 commit comments

Comments
 (0)