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
114 changes: 29 additions & 85 deletions apps/docs/src/pages/en/self-hosting/self-host.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,100 +84,44 @@ docker compose up

If you want to upload media (images, videos etc.) to your school, you need to configure [MediaLit](https://hub.docker.com/r/codelit/medialit). MediaLit powers CourseLit's media management and optimisation. MediaLit offers a Docker image which you can self host.

To self host, paste the following code in your `docker-compose.yml` file, under the existing content.
To self host, follow the following steps.

```
medialit:
image: codelit/medialit
environment:
- DB_CONNECTION_STRING=${DB_CONNECTION_STRING_MEDIALIT}
- CLOUD_ENDPOINT=${CLOUD_ENDPOINT}
- CLOUD_REGION=${CLOUD_REGION}
- CLOUD_KEY=${CLOUD_KEY}
- CLOUD_SECRET=${CLOUD_SECRET}
- CLOUD_BUCKET_NAME=${CLOUD_BUCKET_NAME}
- CDN_ENDPOINT=${CDN_ENDPOINT}
- TEMP_FILE_DIR_FOR_UPLOADS=${TEMP_FILE_DIR_FOR_UPLOADS}
- PORT=8000
- EMAIL_HOST=${EMAIL_HOST}
- EMAIL_USER=${EMAIL_USER}
- EMAIL_PASS=${EMAIL_PASS}
- EMAIL_FROM=${EMAIL_FROM}
- ENABLE_TRUST_PROXY=${ENABLE_TRUST_PROXY}
- CLOUD_PREFIX=${CLOUD_PREFIX}
ports:
- "8000:8000"
container_name: medialit
restart: on-failure
```

In your `.env` file, paste the following code (under the existing content) and change the values as per your environment.

```
# Medialit Server
DB_CONNECTION_STRING_MEDIALIT=mongodb_connection_string
CLOUD_ENDPOINT=aws_s3_endpoint
CLOUD_REGION=aws_s3_region
CLOUD_KEY=aws_s3_key
CLOUD_SECRET=aws_s3_secret
CLOUD_BUCKET_NAME=aws_s3_bucket_name
CDN_ENDPOINT=aws_s3_cdn_endpoint
TEMP_FILE_DIR_FOR_UPLOADS=path_to_directory
PORT=8000
CLOUD_PREFIX=medialit
```

Restart the services by running the following commands.

```
docker compose stop
docker compose up
```

> **NOTE**: The MediaLit installation is done but is not yet integrated with CourseLit! There are a few more steps. Keep reading.

#### Obtain the API key from MediaLit

First you need to obtain the container id of your MediaLit instance. To do this, run:

```
docker ps
```

Once you have the ID of the `MediaLit` container, run the following to generate an API key

```
docker exec <container_id | container_name> node /app/apps/api/dist/src/scripts/create-local-user.js <email>
```

Keep the generated API key safe. We will use it in the following step.

> For the most up-to-date instructions, refer to the official [Readme](https://github.com/codelitdev/medialit?tab=readme-ov-file#creating-a-local-user) of MediaLit.
1. Uncomment the block under the `app` service in `docker-compose.yml` which says the following.

#### Using Self-hosted MediaLit With CourseLit
```
# - MEDIALIT_APIKEY=${MEDIALIT_APIKEY}
# - MEDIALIT_SERVER=http://host.docker.internal:8000
```

Open the `.env` file and add the following lines.
2. Uncomment the block titled `MediaLit` in `docker-compose.yml`.

```
MEDIALIT_SERVER=http://localhost:8000
MEDIALIT_APIKEY=key_from_above_step
```
3. In your `.env` file, paste the following code (under the existing content) and change the values as per your environment.

Now, in the `docker-compose.yml` file, add the following two lines under the `environment` block of the `app` service.
```
# Medialit Server
CLOUD_ENDPOINT=aws_s3_endpoint
CLOUD_REGION=aws_s3_region
CLOUD_KEY=aws_s3_key
CLOUD_SECRET=aws_s3_secret
CLOUD_BUCKET_NAME=aws_s3_bucket_name
S3_ENDPOINT=aws_s3_cdn_endpoint
CLOUD_PREFIX=medialit
MEDIALIT_APIKEY=key_to_be_obtained_docker_compose_logs
```

```
- MEDIALIT_APIKEY=${MEDIALIT_APIKEY}
- MEDIALIT_SERVER=${MEDIALIT_SERVER}
```
4. Restart the services once to generate a user and an API key in MediaLit database. The API key
will be printed to the docker compose logs. The relevant logs will look something like the following.
`sh
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ API key: testcktI8Sa71QUgYtest @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
`

Restart the server by running the following commands.
Copy the API key.

```
docker compose stop
docker compose up
```
5. Update the `MEDIALIT_APIKEY` value in `.env` file and restart the service once again.

That's it! You now have a fully functioning LMS powered by CourseLit.
6. That's it! You now have a fully functioning LMS powered by CourseLit and MediaLit.

## Hosted version

Expand Down
159 changes: 80 additions & 79 deletions apps/web/components/community/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
CommunityMedia,
CommunityPost,
Constants,
Media,
} from "@courselit/common-models";
import LoadingSkeleton from "./loading-skeleton";
import { formattedLocaleDate, hasCommunityPermission } from "@ui-lib/utils";
Expand Down Expand Up @@ -458,94 +459,93 @@ export function CommunityForum({
return response.url;
};

const removeFile = async (mediaId: string) => {
try {
const fetch = new FetchBuilder()
.setUrl(`${address.backend}/api/media/${mediaId}`)
.setHttpMethod("DELETE")
.setIsGraphQLEndpoint(false)
.build();
const response = await fetch.exec();
if (response.message !== "success") {
throw new Error(response.message);
}
} catch (err: any) {
console.error("Error in removing file", err.message);
}
};
// const removeFile = async (mediaId: string) => {
// try {
// const fetch = new FetchBuilder()
// .setUrl(`${address.backend}/api/media/${mediaId}`)
// .setHttpMethod("DELETE")
// .setIsGraphQLEndpoint(false)
// .build();
// const response = await fetch.exec();
// if (response.message !== "success") {
// throw new Error(response.message);
// }
// } catch (err: any) {
// console.error("Error in removing file", err.message);
// }
// };

const createPost = async (
newPost: Pick<CommunityPost, "title" | "content" | "category"> & {
media: MediaItem[];
},
) => {
if (newPost.media.length > 0) {
newPost.media = await uploadAttachments(newPost.media);
}
const mutation = `
mutation ($id: String!, $title: String!, $content: String!, $category: String!, $media: [CommunityPostInputMedia]) {
post: createCommunityPost(
id: $id,
title: $title,
content: $content,
category: $category,
media: $media
) {
communityId
postId
title
content
category
media {
type
try {
if (newPost.media.length > 0) {
newPost.media = await uploadAttachments(newPost.media);
}
const mutation = `
mutation ($id: String!, $title: String!, $content: String!, $category: String!, $media: [CommunityPostInputMedia]) {
post: createCommunityPost(
id: $id,
title: $title,
content: $content,
category: $category,
media: $media
) {
communityId
postId
title
url
content
category
media {
mediaId
file
thumbnail
originalFileName
type
title
url
media {
mediaId
file
thumbnail
originalFileName
}
}
}
likesCount
commentsCount
updatedAt
hasLiked
user {
userId
name
avatar {
mediaId
file
thumbnail
likesCount
commentsCount
updatedAt
hasLiked
user {
userId
name
avatar {
mediaId
file
thumbnail
}
}
pinned
}
pinned
}
}
`;

const fetch = new FetchBuilder()
.setUrl(`${address.backend}/api/graph`)
.setPayload({
query: mutation,
variables: {
id: community?.communityId,
content: newPost.content,
category: newPost.category,
title: newPost.title,
media: newPost.media.map((m) => ({
type: m.type,
title: m.title,
url: m.url,
media: m.media,
})),
},
})
.setIsGraphQLEndpoint(true)
.build();
`;

try {
const fetch = new FetchBuilder()
.setUrl(`${address.backend}/api/graph`)
.setPayload({
query: mutation,
variables: {
id: community?.communityId,
content: newPost.content,
category: newPost.category,
title: newPost.title,
media: newPost.media.map((m) => ({
type: m.type,
title: m.title,
url: m.url,
media: m.media,
})),
},
})
.setIsGraphQLEndpoint(true)
.build();
const response = await fetch.exec();
if (response.post) {
setPosts((prevPosts) => [response.post, ...prevPosts]);
Expand All @@ -557,8 +557,9 @@ export function CommunityForum({
}
} catch (err: any) {
toast({
title: "Error",
title: TOAST_TITLE_ERROR,
description: err.message,
variant: "destructive",
});
}
};
Expand All @@ -580,8 +581,8 @@ export function CommunityForum({
const presignedUrl = await getPresignedUrl();
const media = await uploadToServer(presignedUrl, file);
return media;
} catch (err: any) {
console.error(err.message);
} catch (err) {
throw new Error(`Media upload: ${err.message}`);
}
};

Expand Down Expand Up @@ -905,7 +906,7 @@ export function CommunityForum({
setReportReason("");
} catch (err: any) {
toast({
title: "Error",
title: TOAST_TITLE_ERROR,
description: err.message,
variant: "destructive",
});
Expand Down
2 changes: 1 addition & 1 deletion apps/web/pages/api/media/presigned.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default async function handler(
);
return res.status(200).json({ url: response });
} catch (err: any) {
error(err.mssage, {
error(err.message, {
stack: err.stack,
});
return res.status(500).json({ error: err.message });
Expand Down
Loading
Loading