From 5b40d14f245b9f14785d7ed529e260ead254e6b6 Mon Sep 17 00:00:00 2001 From: Alberto Quiles Date: Mon, 5 Oct 2026 00:31:15 +0200 Subject: [PATCH] feat: add mailpit - fix #13 --- README.md | 1 + compose.yml | 18 ++++++++++++++++++ configs/auth.yml | 6 ++++++ deno.json | 2 +- seed.ts | 40 +++++++++++++++++++++++++++++++++++++++- 5 files changed, 65 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ffc503e..3e49f1e 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Run all OpenHotel stack locally with Docker. | asset-editor | `openhotel/asset-editor` | http://localhost:2030 | | static | `openhotel/static` | http://localhost:1995 | | s3 | SeaweedFS | http://localhost:9000 | +| mail | Mailpit | http://localhost:8025 | ## Requirements diff --git a/compose.yml b/compose.yml index 3923625..e5fb314 100644 --- a/compose.yml +++ b/compose.yml @@ -20,6 +20,7 @@ services: - "2030:2030" # asset-editor - "1995:1995" # static - "9000:9000" # s3 + - "8025:8025" # mail s3: image: chrislusf/seaweedfs:latest @@ -32,9 +33,26 @@ services: - ./s3/config.json:/etc/seaweedfs/s3.json:ro - s3-data:/data + mail: + image: axllent/mailpit:latest + container_name: mail + network_mode: service:net + depends_on: [net] + restart: unless-stopped + command: >- + --smtp-tls-cert sans:localhost + --smtp-tls-key sans:localhost + --smtp-require-tls + --smtp-auth-accept-any + auth: <<: *dev container_name: auth + depends_on: + net: + condition: service_started + mail: + condition: service_healthy build: context: . args: diff --git a/configs/auth.yml b/configs/auth.yml index cc1b9c3..011b09f 100644 --- a/configs/auth.yml +++ b/configs/auth.yml @@ -1 +1,7 @@ version: development +email: + enabled: true + hostname: localhost + port: 1025 + username: no-reply@openhotel.local + password: devkit diff --git a/deno.json b/deno.json index ad5a25e..7c12693 100644 --- a/deno.json +++ b/deno.json @@ -1,7 +1,7 @@ { "tasks": { "setup": "deno run --allow-read --allow-write --allow-run=git,docker setup.ts", - "seed": "deno run --allow-read --allow-write --allow-net=localhost:2024 seed.ts", + "seed": "deno run --allow-read --allow-write --allow-net=localhost:2024,localhost:8025 seed.ts", "reset": "deno run --allow-read --allow-write --allow-run=docker reset.ts", "up": "docker compose up -d --build", "logs": "docker compose logs -f --tail=100", diff --git a/seed.ts b/seed.ts index 5959d88..2b9f989 100644 --- a/seed.ts +++ b/seed.ts @@ -22,6 +22,7 @@ type Integration = { }; const AUTH_API = "http://localhost:2024/api/v3"; +const MAIL_API = "http://localhost:8025/api/v1"; const FINGERPRINT = "devkit-seed"; const INTEGRATIONS: Record = { client: { @@ -77,12 +78,49 @@ const register = async ({ username, email, password }: User) => { else fail(`${username}: register failed (${status} ${message ?? ""})`); }; -const login = async ({ username, email, password }: User): Promise => { +const verifyEmail = async ({ username, email }: User) => { + for (let attempt = 0; attempt < 10; attempt++) { + const { messages } = await fetch( + `${MAIL_API}/search?query=${encodeURIComponent(`to:"${email}"`)}`, + ).then((response) => response.json()); + + if (messages.length) { + const { HTML } = await fetch( + `${MAIL_API}/message/${messages[0].ID}`, + ).then((response) => response.json()); + + const [, id, token] = + /verify\?id=([^&"'\s<]+)&(?:amp;)?token=([^&"'\s<]+)/.exec(HTML) ?? []; + if (!id || !token) break; + + const { status } = await request( + "GET", + `/account/verify?id=${id}&token=${token}`, + ); + if (status !== 200) break; + + ok(`${username}: email verified`); + return; + } + + await new Promise((resolve) => setTimeout(resolve, 500)); + } + + fail(`${username}: email could not be verified`); +}; + +const login = async (user: User, retry = true): Promise => { + const { username, email, password } = user; const { status, data, message } = await request<{ accountId: string; token: string; }>("POST", "/account/login", { body: { email, password } }); + if (retry && message === "Your email is not verified!") { + await verifyEmail(user); + return login(user, false); + } + if (status !== 200 || !data) { return fail(`${username}: login failed (${status} ${message ?? ""})`); }