📝 Updated docs with warning
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
# ClassQuiz
|
# ClassQuiz
|
||||||
|
|
||||||
ClassQuiz is a quiz-application like KAHOOT!, but open-source which is very important if it is a product for educational
|
ClassQuiz is a quiz-application like KAHOOT!, but open-source which is very important if it is a product for educational
|
||||||
purposes. You can create quizzes and play them remotely with other people. It is mainly made for teachers, who create a
|
purposes.
|
||||||
|
You can create quizzes and play them remotely with other people.
|
||||||
|
It is mainly made for teachers, who create a
|
||||||
quiz, so students can compete with their knowledge against each other.
|
quiz, so students can compete with their knowledge against each other.
|
||||||
|
|
||||||
## Try it
|
## Try it
|
||||||
@@ -20,7 +22,7 @@ See https://classquiz.mawoka.eu/docs/develop
|
|||||||
|
|
||||||
## Docs
|
## Docs
|
||||||
|
|
||||||
The docs are https://classquiz.mawoka.eu/docs on the website
|
The docs are online at https://classquiz.mawoka.eu/docs
|
||||||
|
|
||||||
### Things to know about the structure
|
### Things to know about the structure
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<script lang='ts'>
|
<script lang="ts">
|
||||||
import { getLocalization } from '$lib/i18n';
|
import { getLocalization } from '$lib/i18n';
|
||||||
|
|
||||||
const { t } = getLocalization();
|
const { t } = getLocalization();
|
||||||
@@ -62,82 +62,82 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<label class='pl-2 w-2/6 flex gap-2 flex-row'>
|
<label class="pl-2 w-2/6 flex gap-2 flex-row">
|
||||||
<input type='checkbox' class='w-fit' bind:checked={data.public} />
|
<input type="checkbox" class="w-fit" bind:checked={data.public} />
|
||||||
<span class='w-fit'>{$t('words.public')}?</span>
|
<span class="w-fit">{$t('words.public')}?</span>
|
||||||
</label>
|
</label>
|
||||||
<label class='pl-2 flex flex-row gap-2 w-3/5'>
|
<label class="pl-2 flex flex-row gap-2 w-3/5">
|
||||||
<span class='w-fit'>{$t('words.title')}:</span>
|
<span class="w-fit">{$t('words.title')}:</span>
|
||||||
<input
|
<input
|
||||||
type='text'
|
type="text"
|
||||||
placeholder={$t('words.title')}
|
placeholder={$t('words.title')}
|
||||||
bind:value={data.title}
|
bind:value={data.title}
|
||||||
class='text-black bg-inherit border-dotted border-b-2 border-black w-full'
|
class="text-black bg-inherit border-dotted border-b-2 border-black w-full"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label class='pl-2 flex flex-row gap-2 w-3/5'>
|
<label class="pl-2 flex flex-row gap-2 w-3/5">
|
||||||
{$t('words.description')}:
|
{$t('words.description')}:
|
||||||
<textarea
|
<textarea
|
||||||
placeholder={$t('words.description')}
|
placeholder={$t('words.description')}
|
||||||
bind:value={data.description}
|
bind:value={data.description}
|
||||||
class='text-black w-full'
|
class="text-black w-full"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
{#each data.questions as question, index_question}
|
{#each data.questions as question, index_question}
|
||||||
<div class='ml-8 grid grid-cols-1 gap-2 m-2 border border-black border-2'>
|
<div class="ml-8 grid grid-cols-1 gap-2 m-2 border border-black border-2">
|
||||||
<h1 class='text-3xl m-1'>{$t('words.question')} {index_question + 1}</h1>
|
<h1 class="text-3xl m-1">{$t('words.question')} {index_question + 1}</h1>
|
||||||
|
|
||||||
<label class='m-1 flex flex-row gap-2 w-3/5'>
|
<label class="m-1 flex flex-row gap-2 w-3/5">
|
||||||
{$t('words.question')}:
|
{$t('words.question')}:
|
||||||
<input
|
<input
|
||||||
type='text'
|
type="text"
|
||||||
placeholder={$t('words.question')}
|
placeholder={$t('words.question')}
|
||||||
bind:value={question.question}
|
bind:value={question.question}
|
||||||
class='text-black w-full bg-inherit border-dotted border-b-2 border-black'
|
class="text-black w-full bg-inherit border-dotted border-b-2 border-black"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label class='m-1 flex flex-row gap-2 w-3/5'>
|
<label class="m-1 flex flex-row gap-2 w-3/5">
|
||||||
{$t('words.image')}:
|
{$t('words.image')}:
|
||||||
<input
|
<input
|
||||||
type='text'
|
type="text"
|
||||||
placeholder='https://i.imgur.com/kSPCidY.png'
|
placeholder="https://i.imgur.com/kSPCidY.png"
|
||||||
bind:value={question.image}
|
bind:value={question.image}
|
||||||
class='text-black w-full bg-inherit border-dotted border-b-2 border-black'
|
class="text-black w-full bg-inherit border-dotted border-b-2 border-black"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label class='m-1 flex flex-row gap-2 w-3/5 flex-nowrap whitespace-nowrap'>
|
<label class="m-1 flex flex-row gap-2 w-3/5 flex-nowrap whitespace-nowrap">
|
||||||
{$t('editor.time_in_seconds')}:
|
{$t('editor.time_in_seconds')}:
|
||||||
<input
|
<input
|
||||||
type='text'
|
type="text"
|
||||||
placeholder='20'
|
placeholder="20"
|
||||||
bind:value={question.time}
|
bind:value={question.time}
|
||||||
class='text-black w-full bg-inherit border-dotted border-b-2 border-black'
|
class="text-black w-full bg-inherit border-dotted border-b-2 border-black"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
{#each question.answers as answer, index_answer}
|
{#each question.answers as answer, index_answer}
|
||||||
<div class='ml-8 grid grid-cols-1 gap-2 m-2 border border-black border-2 m-1'>
|
<div class="ml-8 grid grid-cols-1 gap-2 m-2 border border-black border-2 m-1">
|
||||||
<h1 class='text-3xl m-1'>{$t('words.answer')} {index_answer + 1}</h1>
|
<h1 class="text-3xl m-1">{$t('words.answer')} {index_answer + 1}</h1>
|
||||||
<p class='m-1'>
|
<p class="m-1">
|
||||||
{$t('words.answer')}: {index_answer + 1}
|
{$t('words.answer')}: {index_answer + 1}
|
||||||
{$t('words.question')}: {index_question + 1}
|
{$t('words.question')}: {index_question + 1}
|
||||||
</p>
|
</p>
|
||||||
<label class='m-1 flex flex-row gap-2 w-3/5 flex-nowrap whitespace-nowrap'>
|
<label class="m-1 flex flex-row gap-2 w-3/5 flex-nowrap whitespace-nowrap">
|
||||||
{$t('words.answer')}:
|
{$t('words.answer')}:
|
||||||
<input
|
<input
|
||||||
type='text'
|
type="text"
|
||||||
placeholder={$t('words.answer')}
|
placeholder={$t('words.answer')}
|
||||||
bind:value={data.questions[index_question].answers[index_answer].answer}
|
bind:value={data.questions[index_question].answers[index_answer].answer}
|
||||||
class='text-black w-full bg-inherit border-dotted border-b-2 border-black'
|
class="text-black w-full bg-inherit border-dotted border-b-2 border-black"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
<label class='m-1 flex flex-row gap-2 w-2/6 flex-nowrap whitespace-nowrap'>
|
<label class="m-1 flex flex-row gap-2 w-2/6 flex-nowrap whitespace-nowrap">
|
||||||
<input type='checkbox' bind:checked={answer.right} class='text-black w-fit' />
|
<input type="checkbox" bind:checked={answer.right} class="text-black w-fit" />
|
||||||
<span class='w-fit'>{$t('editor.right_or_true?')}</span>
|
<span class="w-fit">{$t('editor.right_or_true?')}</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class='text-left border-yellow-500 border-2 w-fit m-1'
|
class="text-left border-yellow-500 border-2 w-fit m-1"
|
||||||
type='button'
|
type="button"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
data.questions[index_question].answers.splice(index_answer, 1);
|
data.questions[index_question].answers.splice(index_answer, 1);
|
||||||
data.questions[index_question].answers =
|
data.questions[index_question].answers =
|
||||||
@@ -149,8 +149,8 @@
|
|||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
<button
|
<button
|
||||||
class='text-left border-green-500 border-2 w-fit m-1'
|
class="text-left border-green-500 border-2 w-fit m-1"
|
||||||
type='button'
|
type="button"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
data.questions[index_question].answers = [
|
data.questions[index_question].answers = [
|
||||||
...data.questions[index_question].answers,
|
...data.questions[index_question].answers,
|
||||||
@@ -161,8 +161,8 @@
|
|||||||
{$t('editor.add_new_answer')}
|
{$t('editor.add_new_answer')}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class='text-left border-red-500 border-2 w-fit m-1'
|
class="text-left border-red-500 border-2 w-fit m-1"
|
||||||
type='button'
|
type="button"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
data.questions.splice(index_question, 1);
|
data.questions.splice(index_question, 1);
|
||||||
data.questions = data.questions;
|
data.questions = data.questions;
|
||||||
@@ -173,8 +173,8 @@
|
|||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
<button
|
<button
|
||||||
class='text-left'
|
class="text-left"
|
||||||
type='button'
|
type="button"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
data.questions = [...data.questions, { ...empty_question }];
|
data.questions = [...data.questions, { ...empty_question }];
|
||||||
}}
|
}}
|
||||||
@@ -182,12 +182,12 @@
|
|||||||
{$t('editor.add_new_question')}
|
{$t('editor.add_new_question')}
|
||||||
</button>
|
</button>
|
||||||
{#if imgur_links_valid}
|
{#if imgur_links_valid}
|
||||||
<p class='text-blue-600 text-xl text-center w-fit mx-auto'>
|
<p class="text-blue-600 text-xl text-center w-fit mx-auto">
|
||||||
Not all links are imgur-links! <!-- TODO: Add translation -->
|
Not all links are imgur-links! <!-- TODO: Add translation -->
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
<button
|
<button
|
||||||
type='submit'
|
type="submit"
|
||||||
class='text-xl disabled:cursor-not-allowed disabled:border disabled:border-red-500 w-fit mx-auto'
|
class="text-xl disabled:cursor-not-allowed disabled:border disabled:border-red-500 w-fit mx-auto"
|
||||||
disabled={imgur_links_valid}>{submit_button_text}</button
|
disabled={imgur_links_valid}>{submit_button_text}</button
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
<script lang='ts'>
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import '$lib/hljs.css';
|
import '$lib/hljs.css';
|
||||||
|
|
||||||
@@ -11,33 +11,42 @@
|
|||||||
<svelte:head>
|
<svelte:head>
|
||||||
<title>ClassQuiz/docs - Self-Host</title>
|
<title>ClassQuiz/docs - Self-Host</title>
|
||||||
<meta
|
<meta
|
||||||
name='description'
|
name="description"
|
||||||
content='How to self-host ClassQuiz, the open-source quiz-application'
|
content="How to self-host ClassQuiz, the open-source quiz-application"
|
||||||
/>
|
/>
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
<article
|
<article
|
||||||
class='prose prose-sm sm:prose lg:prose-lg xl:prose-xl mx-auto mt-10 prose-pink text-yellow-50 px-4'
|
class="prose prose-sm sm:prose lg:prose-lg xl:prose-xl mx-auto mt-10 prose-pink text-yellow-50 px-4"
|
||||||
>
|
>
|
||||||
<h1>Self-Hosting</h1>
|
<h1>Self-Hosting</h1>
|
||||||
<p>Since ClassQuiz is open-source, it can also be self-hosted.</p>
|
<p>Since ClassQuiz is open-source, it can also be self-hosted.</p>
|
||||||
|
|
||||||
|
<h2>Warning</h2>
|
||||||
|
<p>
|
||||||
|
Since ClassQuiz is in pretty early development, breaking changes come and go! Please check
|
||||||
|
these docs again before updating your instance. I may also add more dependencies like <a
|
||||||
|
href="https://typesense.org/">Typesense</a
|
||||||
|
>
|
||||||
|
ore something similar in the future, but I'll always provide a
|
||||||
|
<code>docker-compose.yml</code> file, so the self-hosting process is still easy.
|
||||||
|
</p>
|
||||||
|
|
||||||
<h2>Requirements</h2>
|
<h2>Requirements</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href='https://docker.com'>Docker</a></li>
|
<li><a href="https://docker.com">Docker</a></li>
|
||||||
<li><a href='https://git-scm.com/'>Git</a></li>
|
<li><a href="https://git-scm.com/">Git</a></li>
|
||||||
<li>
|
<li>
|
||||||
A <a href='https://redis.com'>Redis</a>-Server (I recommend
|
A <a href="https://redis.com">Redis</a>-Server (I recommend
|
||||||
<a href='https://upstash.com'>Upstash</a>)
|
<a href="https://upstash.com">Upstash</a>)
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2>Installation</h2>
|
<h2>Installation</h2>
|
||||||
<p>At first, clone the repo:</p>
|
<p>At first, clone the repo:</p>
|
||||||
|
|
||||||
<pre><code class='language-bash'
|
<pre><code class="language-bash"
|
||||||
>git clone https://github.com/mawoka-myblock/classquiz && cd ClassQuiz</code
|
>git clone https://github.com/mawoka-myblock/classquiz && cd ClassQuiz</code
|
||||||
></pre>
|
></pre>
|
||||||
<p>
|
<p>
|
||||||
Now, set a <b>VALID</b> Redis-URI in <code>frontend/Dockerfile</code> and, if you want Sentry,
|
Now, set a <b>VALID</b> Redis-URI in <code>frontend/Dockerfile</code> and, if you want Sentry,
|
||||||
set a valid Sentry-DSN.
|
set a valid Sentry-DSN.
|
||||||
@@ -49,25 +58,29 @@
|
|||||||
<h2>Configuration</h2>
|
<h2>Configuration</h2>
|
||||||
<h3>Storage Provider</h3>
|
<h3>Storage Provider</h3>
|
||||||
<p>
|
<p>
|
||||||
You'll have to set up a storage provider for some pictures (these getting imported from KAHOOT!). For now, you
|
You'll have to set up a storage provider for some pictures (these getting imported from
|
||||||
can use <a href='https://deta.sh'>Deta</a> or the local filesystem. Please note that I would <b>NOT</b> use it
|
KAHOOT!). For now, you can use <a href="https://deta.sh">Deta</a> or the local filesystem.
|
||||||
because of these funny path-things. I tried to prevent these attacks, but i really wouldn't trust it.
|
Please note that I would <b>NOT</b> use it because of these funny path-things. I tried to
|
||||||
You'll have to set the <code>STORAGE_BACKEND</code>-environment-variable to either <code>deta</code> or <code>local</code>.
|
prevent these attacks, but i really wouldn't trust it. You'll have to set the
|
||||||
|
<code>STORAGE_BACKEND</code>-environment-variable to either <code>deta</code> or
|
||||||
|
<code>local</code>.
|
||||||
</p>
|
</p>
|
||||||
<h4>If you chose Deta...</h4>
|
<h4>If you chose Deta...</h4>
|
||||||
<p>
|
<p>
|
||||||
...you'll also have to set the <code>DETA_PROJECT_KEY</code> and the <code>DETA_PROJECT_ID</code>.
|
...you'll also have to set the <code>DETA_PROJECT_KEY</code> and the
|
||||||
|
<code>DETA_PROJECT_ID</code>.
|
||||||
</p>
|
</p>
|
||||||
<h4>If you chose the local filesystem...</h4>
|
<h4>If you chose the local filesystem...</h4>
|
||||||
<p>
|
<p>
|
||||||
...you'll have to set the <code>STORAGE_PATH</code> enviromnent variable. The path must be absolute (so start with a <code>/</code>).
|
...you'll have to set the <code>STORAGE_PATH</code> enviromnent variable. The path must be
|
||||||
|
absolute (so start with a <code>/</code>).
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Before you can start your stack, you have to set some environment-variables in your
|
Before you can start your stack, you have to set some environment-variables in your
|
||||||
<code>docker-compose.yml</code>.
|
<code>docker-compose.yml</code>.
|
||||||
</p>
|
</p>
|
||||||
<pre><code class='language-yaml'
|
<pre><code class="language-yaml"
|
||||||
>version: "3"
|
>version: "3"
|
||||||
|
|
||||||
services:
|
services:
|
||||||
frontend:
|
frontend:
|
||||||
@@ -143,5 +156,5 @@ volumes:
|
|||||||
data:
|
data:
|
||||||
</code></pre>
|
</code></pre>
|
||||||
<p>Now build and deploy:</p>
|
<p>Now build and deploy:</p>
|
||||||
<pre><code class='language-bash'>docker compose build && docker compose up -d</code></pre>
|
<pre><code class="language-bash">docker compose build && docker compose up -d</code></pre>
|
||||||
</article>
|
</article>
|
||||||
|
|||||||
Reference in New Issue
Block a user