diff --git a/content/en/docs/03/Caddyfile-docker b/content/en/docs/03/Caddyfile-docker new file mode 100644 index 0000000..d6a4ec1 --- /dev/null +++ b/content/en/docs/03/Caddyfile-docker @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) +# +# SPDX-License-Identifier: MPL-2.0 + +:8080 { + reverse_proxy * http://frontend:3000 + reverse_proxy /api/* http://api:8081 + reverse_proxy /openapi.json http://api:8081 # Only use if you need to serve the OpenAPI spec + reverse_proxy /socket.io/* http://api:8081 + +} diff --git a/content/en/docs/03/_index.md b/content/en/docs/03/_index.md index bc9a57c..9c4d313 100644 --- a/content/en/docs/03/_index.md +++ b/content/en/docs/03/_index.md @@ -10,13 +10,14 @@ sectionnumber: 3 ### The Challenge After we have learned the basic Dagger Functions, we want to apply our new knowledge to solve a real life problem: + We would like to conduct a survey regarding the popularity of the different Dagger SDKs! ### The Candidate -Fortunately, there is a free open-source quiz app called [ClassQuiz](https://classquiz.de/) -It allows the creation of shareable, fully customizable quizzes and surveys. +Fortunately, there is a free open-source quiz app called [ClassQuiz](https://classquiz.de/)\ +It allows the creation of shareable, fully customizable quizzes and surveys.\ The app is split in a frontend and an api part: * The frontend is written in type script and uses a redis memcache. @@ -47,14 +48,48 @@ Since the app has some hard-coded configurations that would interfere with our s {{< /details >}} +Create first the `config.patch` file in the root of your git folder. Then add the content of the above patch. + ```bash patch classquiz/config.py < config.patch ``` -The app also binds the privileged port `80`, which would be an obstacle as well. So let's replace all occurrences of `:80` in `Caddyfile-docker` with `:8081` or another port of your choice. +{{% alert title="Note" color="primary" %}} + +If patching does not work, overwrite the file `classquiz/config.py` with the content from the following `config.py` file. + +{{< details title="show final config.py file" >}} + +{{< readfile file="config.py" code="true" lang="Python" >}} + +{{< /details >}} + +{{% /alert %}} + +The app also binds the privileged port `80`, which would be an obstacle as well.\ +So let's replace all occurrences of `:80` in `Caddyfile-docker` with `:8081`.\ +Additionally the missing protocol has to be added to the last `reverse_proxy` line. Add `http://` in front of `api:80`. + +Do it by hand or use the following `sed` commands: + +```bash +sed -i 's# api:80# http://api:80#g' Caddyfile-docker +sed -i 's#api:80#api:8081#g' Caddyfile-docker +``` + +If patching does not work, overwrite the file `Caddyfile-docker` with the content from the following `Caddyfile-docker` file. + +{{< details title="show final Caddyfile-docker file" >}} + +{{< readfile file="Caddyfile-docker" code="true" >}} + +{{< /details >}} + +#### Start using Dagger +As we learnt in the first labs, Dagger functions are needed to encapsulate our pipeline functionality. -#### Initialize a Dagger Module +In Dagger, everything is a Module, therefore the first step is to initialize a Dagger Module. A new Dagger module in Go, Python or TypeScript can be initialized by running `dagger init` inside the app's root directory, using the `--source` flag to specify a directory for the module's source code. diff --git a/content/en/docs/03/config.patch b/content/en/docs/03/config.patch index 132ab26..e78dda9 100644 --- a/content/en/docs/03/config.patch +++ b/content/en/docs/03/config.patch @@ -1,12 +1,17 @@ ---- config.py 2024-08-27 09:12:30.871018892 +0200 -+++ config.py 2024-08-27 09:12:53.055075688 +0200 -@@ -30,16 +30,16 @@ +diff --git a/classquiz/config.py b/classquiz/config.py +index b6f7635..fbc66d5 100644 +--- a/classquiz/config.py ++++ b/classquiz/config.py +@@ -29,21 +29,21 @@ class Settings(BaseSettings): + """ root_address: str = "http://127.0.0.1:8000" - redis: RedisDsn = "redis://localhost:6379/0?decode_responses=True" +- redis: RedisDsn = "redis://localhost:6379/0?decode_responses=True" - skip_email_verification: bool = False +- db_url: str | PostgresDsn = "postgresql://postgres:mysecretpassword@localhost:5432/classquiz" ++ redis: RedisDsn = "redis://redisd:6379/0?decode_responses=True" + skip_email_verification: bool = True - db_url: str | PostgresDsn = "postgresql://postgres:mysecretpassword@localhost:5432/classquiz" ++ db_url: str | PostgresDsn = "postgresql://postgres:classquiz@postgresd:5432/classquiz" hcaptcha_key: str | None = None recaptcha_key: str | None = None - mail_address: str @@ -24,7 +29,12 @@ access_token_expire_minutes: int = 30 cache_expiry: int = 86400 sentry_dsn: str | None -@@ -60,7 +60,7 @@ +- meilisearch_url: str = "http://127.0.0.1:7700" ++ meilisearch_url: str = "http://meilisearchd:7700" + meilisearch_index: str = "classquiz" + google_client_id: Optional[str] + google_client_secret: Optional[str] +@@ -60,7 +60,7 @@ class Settings(BaseSettings): storage_backend: str | None = "local" # if storage_backend == "local": diff --git a/content/en/docs/03/config.py b/content/en/docs/03/config.py new file mode 100644 index 0000000..fbc66d5 --- /dev/null +++ b/content/en/docs/03/config.py @@ -0,0 +1,107 @@ +# SPDX-FileCopyrightText: 2023 Marlon W (Mawoka) +# +# SPDX-License-Identifier: MPL-2.0 + +import re +from functools import lru_cache + +from redis import asyncio as redis_lib +import redis as redis_base_lib +from pydantic import BaseSettings, RedisDsn, PostgresDsn, BaseModel +import meilisearch as MeiliSearch +from typing import Optional +from arq import create_pool +from arq.connections import RedisSettings, ArqRedis + +from classquiz.storage import Storage + + +class CustomOpenIDProvider(BaseModel): + scopes: str = "openid email profile" + server_metadata_url: str + client_id: str + client_secret: str + + +class Settings(BaseSettings): + """ + Settings class for the shop app. + """ + + root_address: str = "http://127.0.0.1:8000" + redis: RedisDsn = "redis://redisd:6379/0?decode_responses=True" + skip_email_verification: bool = True + db_url: str | PostgresDsn = "postgresql://postgres:classquiz@postgresd:5432/classquiz" + hcaptcha_key: str | None = None + recaptcha_key: str | None = None + mail_address: str = "some@example.org" + mail_password: str = "some@example.org" + mail_username: str = "some@example.org" + mail_server: str = "some.example.org" + mail_port: int = "525" + secret_key: str = "secret" + access_token_expire_minutes: int = 30 + cache_expiry: int = 86400 + sentry_dsn: str | None + meilisearch_url: str = "http://meilisearchd:7700" + meilisearch_index: str = "classquiz" + google_client_id: Optional[str] + google_client_secret: Optional[str] + github_client_id: Optional[str] + github_client_secret: Optional[str] + custom_openid_provider: CustomOpenIDProvider | None = None + telemetry_enabled: bool = True + free_storage_limit: int = 1074000000 + pixabay_api_key: str | None = None + mods: list[str] = [] + registration_disabled: bool = False + + # storage_backend + storage_backend: str | None = "local" + + # if storage_backend == "local": + storage_path: str | None = "/app/data" + + # if storage_backend == "s3": + s3_access_key: str | None + s3_secret_key: str | None + s3_bucket_name: str = "classquiz" + s3_base_url: str | None + + class Config: + env_file = ".env" + env_file_encoding = "utf-8" + env_nested_delimiter = "__" + + +async def initialize_arq(): + # skipcq: PYL-W0603 + global arq + arq = await create_pool(RedisSettings.from_dsn(settings.redis)) + + +@lru_cache() +def settings() -> Settings: + return Settings() + + +pool = redis_lib.ConnectionPool().from_url(settings().redis) + +redis: redis_base_lib.client.Redis = redis_lib.Redis(connection_pool=pool) +arq: ArqRedis = ArqRedis(pool_or_conn=pool) +storage: Storage = Storage( + backend=settings().storage_backend, + storage_path=settings().storage_path, + access_key=settings().s3_access_key, + secret_key=settings().s3_secret_key, + bucket_name=settings().s3_bucket_name, + base_url=settings().s3_base_url, +) + +meilisearch = MeiliSearch.Client(settings().meilisearch_url) + +ALLOWED_TAGS_FOR_QUIZ = ["b", "strong", "i", "em", "small", "mark", "del", "sub", "sup"] + +ALLOWED_MIME_TYPES = ["image/png", "video/mp4", "image/jpeg", "image/gif", "image/webp"] + +server_regex = rf"^{re.escape(settings().root_address)}/api/v1/storage/download/.{{36}}--.{{36}}$"