Compare commits
8 Commits
58f5af27db
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
e5af5cb2a3
|
|||
|
a1cecd6de4
|
|||
|
053aa97983
|
|||
|
7d86903565
|
|||
|
32b3102070
|
|||
|
1dc6e8b4f9
|
|||
|
1299fc4dd3
|
|||
|
309b0c618f
|
@@ -22,6 +22,24 @@ steps:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- Install
|
- Install
|
||||||
|
|
||||||
|
- name: Send Prettier Status Notification (failure)
|
||||||
|
image: curlimages/curl
|
||||||
|
environment:
|
||||||
|
MATTERMOST_BOT_ACCESS_TOKEN:
|
||||||
|
from_secret: mattermost_bot_access_token
|
||||||
|
MATTERMOST_CHANNEL_ID:
|
||||||
|
from_secret: mattermost_tests_channel_id
|
||||||
|
MATTERMOST_POST_API_URL:
|
||||||
|
from_secret: mattermost_post_api_url
|
||||||
|
commands:
|
||||||
|
- |
|
||||||
|
BODY=$(printf '{"channel_id":"%s","message":"[%s - Build #%s] Prettier failure 💩"}' "$MATTERMOST_CHANNEL_ID" "$CI_REPO" "$CI_PIPELINE_NUMBER")
|
||||||
|
curl -sS -X POST -H "Content-Type: application/json" -d "$BODY" -H "Authorization: Bearer $MATTERMOST_BOT_ACCESS_TOKEN" $MATTERMOST_POST_API_URL
|
||||||
|
depends_on:
|
||||||
|
- Prettier
|
||||||
|
when:
|
||||||
|
- status: [failure]
|
||||||
|
|
||||||
- name: Lint
|
- name: Lint
|
||||||
image: node:22-bookworm-slim
|
image: node:22-bookworm-slim
|
||||||
commands:
|
commands:
|
||||||
@@ -38,6 +56,24 @@ steps:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- Lint
|
- Lint
|
||||||
|
|
||||||
|
- name: Send Lint Status Notification (failure)
|
||||||
|
image: curlimages/curl
|
||||||
|
environment:
|
||||||
|
MATTERMOST_BOT_ACCESS_TOKEN:
|
||||||
|
from_secret: mattermost_bot_access_token
|
||||||
|
MATTERMOST_CHANNEL_ID:
|
||||||
|
from_secret: mattermost_tests_channel_id
|
||||||
|
MATTERMOST_POST_API_URL:
|
||||||
|
from_secret: mattermost_post_api_url
|
||||||
|
commands:
|
||||||
|
- |
|
||||||
|
BODY=$(printf '{"channel_id":"%s","message":"[%s - Build #%s] Lint failure 💩"}' "$MATTERMOST_CHANNEL_ID" "$CI_REPO" "$CI_PIPELINE_NUMBER")
|
||||||
|
curl -sS -X POST -H "Content-Type: application/json" -d "$BODY" -H "Authorization: Bearer $MATTERMOST_BOT_ACCESS_TOKEN" $MATTERMOST_POST_API_URL
|
||||||
|
depends_on:
|
||||||
|
- Lint
|
||||||
|
when:
|
||||||
|
- status: [failure]
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
image: node:22-bookworm-slim
|
image: node:22-bookworm-slim
|
||||||
commands:
|
commands:
|
||||||
@@ -46,23 +82,41 @@ steps:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- Tests & Coverage
|
- Tests & Coverage
|
||||||
|
|
||||||
# build-full:
|
- name: Send Build Status Notification (failure)
|
||||||
# image: node:22-bookworm-slim
|
image: curlimages/curl
|
||||||
# commands:
|
environment:
|
||||||
# - apt-get update
|
MATTERMOST_BOT_ACCESS_TOKEN:
|
||||||
# - apt-get install -y --no-install-recommends ca-certificates libasound2 libatk-bridge2.0-0 libatk1.0-0 libcups2 libdrm2 libgbm1 libgtk-3-0 libnss3 libxcomposite1 libxdamage1 libxfixes3 libxkbcommon0 libxrandr2
|
from_secret: mattermost_bot_access_token
|
||||||
# - rm -rf /var/lib/apt/lists/*
|
MATTERMOST_CHANNEL_ID:
|
||||||
# - corepack enable && corepack prepare pnpm@latest --activate
|
from_secret: mattermost_tests_channel_id
|
||||||
# - pnpm run critical-css:install
|
MATTERMOST_POST_API_URL:
|
||||||
# - pnpm run build:full
|
from_secret: mattermost_post_api_url
|
||||||
# depends_on:
|
commands:
|
||||||
# - build
|
- |
|
||||||
|
BODY=$(printf '{"channel_id":"%s","message":"[%s - Build #%s] Build failure 💩"}' "$MATTERMOST_CHANNEL_ID" "$CI_REPO" "$CI_PIPELINE_NUMBER")
|
||||||
|
curl -sS -X POST -H "Content-Type: application/json" -d "$BODY" -H "Authorization: Bearer $MATTERMOST_BOT_ACCESS_TOKEN" $MATTERMOST_POST_API_URL
|
||||||
|
depends_on:
|
||||||
|
- Build
|
||||||
|
when:
|
||||||
|
- status: [failure]
|
||||||
|
|
||||||
# e2e:
|
- name: Send CI Pipeline Status Notification (success)
|
||||||
# image: node:22-bookworm-slim
|
image: curlimages/curl
|
||||||
# commands:
|
environment:
|
||||||
# - corepack enable && corepack prepare pnpm@latest --activate
|
MATTERMOST_BOT_ACCESS_TOKEN:
|
||||||
# - pnpm exec playwright install chromium --with-deps
|
from_secret: mattermost_bot_access_token
|
||||||
# - pnpm run test:e2e
|
MATTERMOST_CHANNEL_ID:
|
||||||
# depends_on:
|
from_secret: mattermost_tests_channel_id
|
||||||
# - build
|
MATTERMOST_POST_API_URL:
|
||||||
|
from_secret: mattermost_post_api_url
|
||||||
|
commands:
|
||||||
|
- |
|
||||||
|
BODY=$(printf '{"channel_id":"%s","message":"[%s - Build #%s] CI pipeline success 🎉"}' "$MATTERMOST_CHANNEL_ID" "$CI_REPO" "$CI_PIPELINE_NUMBER")
|
||||||
|
curl -sS -X POST -H "Content-Type: application/json" -d "$BODY" -H "Authorization: Bearer $MATTERMOST_BOT_ACCESS_TOKEN" $MATTERMOST_POST_API_URL
|
||||||
|
depends_on:
|
||||||
|
- Install
|
||||||
|
- Lint
|
||||||
|
- Prettier
|
||||||
|
- Build
|
||||||
|
when:
|
||||||
|
- status: [success]
|
||||||
|
|||||||
@@ -55,12 +55,16 @@ steps:
|
|||||||
- name: Send Build Status Notification (success)
|
- name: Send Build Status Notification (success)
|
||||||
image: curlimages/curl
|
image: curlimages/curl
|
||||||
environment:
|
environment:
|
||||||
DISCORD_WEBHOOK_URL:
|
MATTERMOST_BOT_ACCESS_TOKEN:
|
||||||
from_secret: discord_webhook_url
|
from_secret: mattermost_bot_access_token
|
||||||
|
MATTERMOST_CHANNEL_ID:
|
||||||
|
from_secret: mattermost_pushes_channel_id
|
||||||
|
MATTERMOST_POST_API_URL:
|
||||||
|
from_secret: mattermost_post_api_url
|
||||||
commands:
|
commands:
|
||||||
- |
|
- |
|
||||||
BODY=$(printf '{"username":"WoodpeckerBot","content":"[%s - Build #%s] Docker images build success 🎉"}' "$CI_REPO" "$CI_PIPELINE_NUMBER")
|
BODY=$(printf '{"channel_id":"%s","message":"[%s - Build #%s] Docker images build success 🎉"}' "$MATTERMOST_CHANNEL_ID" "$CI_REPO" "$CI_PIPELINE_NUMBER")
|
||||||
curl -sS -X POST -H "Content-Type: application/json" -d "$BODY" "$DISCORD_WEBHOOK_URL"
|
curl -sS -X POST -H "Content-Type: application/json" -d "$BODY" -H "Authorization: Bearer $MATTERMOST_BOT_ACCESS_TOKEN" $MATTERMOST_POST_API_URL
|
||||||
depends_on:
|
depends_on:
|
||||||
- Docker image build (qr-api + qr-web)
|
- Docker image build (qr-api + qr-web)
|
||||||
when:
|
when:
|
||||||
@@ -69,12 +73,16 @@ steps:
|
|||||||
- name: Send Build Status Notification (failure)
|
- name: Send Build Status Notification (failure)
|
||||||
image: curlimages/curl
|
image: curlimages/curl
|
||||||
environment:
|
environment:
|
||||||
DISCORD_WEBHOOK_URL:
|
MATTERMOST_BOT_ACCESS_TOKEN:
|
||||||
from_secret: discord_webhook_url
|
from_secret: mattermost_bot_access_token
|
||||||
|
MATTERMOST_CHANNEL_ID:
|
||||||
|
from_secret: mattermost_pushes_channel_id
|
||||||
|
MATTERMOST_POST_API_URL:
|
||||||
|
from_secret: mattermost_post_api_url
|
||||||
commands:
|
commands:
|
||||||
- |
|
- |
|
||||||
BODY=$(printf '{"username":"WoodpeckerBot","content":"[%s - Build #%s] Docker images build failure 💩"}' "$CI_REPO" "$CI_PIPELINE_NUMBER")
|
BODY=$(printf '{"channel_id":"%s","message":"[%s - Build #%s] Docker images build failure 💩"}' "$MATTERMOST_CHANNEL_ID" "$CI_REPO" "$CI_PIPELINE_NUMBER")
|
||||||
curl -sS -X POST -H "Content-Type: application/json" -d "$BODY" "$DISCORD_WEBHOOK_URL"
|
curl -sS -X POST -H "Content-Type: application/json" -d "$BODY" -H "Authorization: Bearer $MATTERMOST_BOT_ACCESS_TOKEN" $MATTERMOST_POST_API_URL
|
||||||
depends_on:
|
depends_on:
|
||||||
- Docker image build (qr-api + qr-web)
|
- Docker image build (qr-api + qr-web)
|
||||||
when:
|
when:
|
||||||
@@ -103,12 +111,16 @@ steps:
|
|||||||
- name: Send Deploy Status Notification (success)
|
- name: Send Deploy Status Notification (success)
|
||||||
image: curlimages/curl
|
image: curlimages/curl
|
||||||
environment:
|
environment:
|
||||||
DISCORD_WEBHOOK_URL:
|
MATTERMOST_BOT_ACCESS_TOKEN:
|
||||||
from_secret: discord_webhook_url
|
from_secret: mattermost_bot_access_token
|
||||||
|
MATTERMOST_CHANNEL_ID:
|
||||||
|
from_secret: mattermost_pushes_channel_id
|
||||||
|
MATTERMOST_POST_API_URL:
|
||||||
|
from_secret: mattermost_post_api_url
|
||||||
commands:
|
commands:
|
||||||
- |
|
- |
|
||||||
BODY=$(printf '{"username":"WoodpeckerBot","content":"[%s - Build #%s] Production Deploy success 🎉"}' "$CI_REPO" "$CI_PIPELINE_NUMBER")
|
BODY=$(printf '{"channel_id":"%s","message":"[%s - Build #%s] Production Deploy success 🎉"}' "$MATTERMOST_CHANNEL_ID" "$CI_REPO" "$CI_PIPELINE_NUMBER")
|
||||||
curl -sS -X POST -H "Content-Type: application/json" -d "$BODY" "$DISCORD_WEBHOOK_URL"
|
curl -sS -X POST -H "Content-Type: application/json" -d "$BODY" -H "Authorization: Bearer $MATTERMOST_BOT_ACCESS_TOKEN" $MATTERMOST_POST_API_URL
|
||||||
depends_on:
|
depends_on:
|
||||||
- Trigger Portainer stack redeploy
|
- Trigger Portainer stack redeploy
|
||||||
when:
|
when:
|
||||||
@@ -117,12 +129,16 @@ steps:
|
|||||||
- name: Send Deploy Status Notification (failure)
|
- name: Send Deploy Status Notification (failure)
|
||||||
image: curlimages/curl
|
image: curlimages/curl
|
||||||
environment:
|
environment:
|
||||||
DISCORD_WEBHOOK_URL:
|
MATTERMOST_BOT_ACCESS_TOKEN:
|
||||||
from_secret: discord_webhook_url
|
from_secret: mattermost_bot_access_token
|
||||||
|
MATTERMOST_CHANNEL_ID:
|
||||||
|
from_secret: mattermost_pushes_channel_id
|
||||||
|
MATTERMOST_POST_API_URL:
|
||||||
|
from_secret: mattermost_post_api_url
|
||||||
commands:
|
commands:
|
||||||
- |
|
- |
|
||||||
BODY=$(printf '{"username":"WoodpeckerBot","content":"[%s - Build #%s] Production Deploy failure 💩"}' "$CI_REPO" "$CI_PIPELINE_NUMBER")
|
BODY=$(printf '{"channel_id":"%s","message":"[%s - Build #%s] Production Deploy failure 💩"}' "$MATTERMOST_CHANNEL_ID" "$CI_REPO" "$CI_PIPELINE_NUMBER")
|
||||||
curl -sS -X POST -H "Content-Type: application/json" -d "$BODY" "$DISCORD_WEBHOOK_URL"
|
curl -sS -X POST -H "Content-Type: application/json" -d "$BODY" -H "Authorization: Bearer $MATTERMOST_BOT_ACCESS_TOKEN" $MATTERMOST_POST_API_URL
|
||||||
depends_on:
|
depends_on:
|
||||||
- Trigger Portainer stack redeploy
|
- Trigger Portainer stack redeploy
|
||||||
when:
|
when:
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
# Portainer stack: registry-based images (no build). Use with CI/CD webhook redeploy.
|
# Portainer stack: registry-based images (no build). Use with CI/CD webhook redeploy.
|
||||||
# Set in Portainer stack env (or .env): REGISTRY, IMAGE_TAG (defaults below).
|
# Set in Portainer stack env (or .env): REGISTRY, IMAGE_TAG (defaults below).
|
||||||
# Images: ${REGISTRY}/mifi-holdings/shorty-qr-api:${IMAGE_TAG}, shorty-qr-web:${IMAGE_TAG}
|
# Images: ${REGISTRY}/mifi-holdings/shorty-qr-api:${IMAGE_TAG}, shorty-qr-web:${IMAGE_TAG}
|
||||||
|
|
||||||
services:
|
services:
|
||||||
kutt_db:
|
kutt_db:
|
||||||
image: postgres:16-alpine
|
image: postgres:16-alpine
|
||||||
@@ -64,24 +63,27 @@ services:
|
|||||||
start_period: 40s
|
start_period: 40s
|
||||||
environment:
|
environment:
|
||||||
ADMIN_EMAILS: ${ADMIN_EMAILS:?Set ADMIN_EMAILS}
|
ADMIN_EMAILS: ${ADMIN_EMAILS:?Set ADMIN_EMAILS}
|
||||||
DEFAULT_DOMAIN: mifi.me
|
CUSTOM_DOMAIN_USE_HTTPS: 'true'
|
||||||
|
DEFAULT_DOMAIN: ${DEFAULT_DOMAIN:?Set DEFAULT_DOMAIN}
|
||||||
DB_CLIENT: pg
|
DB_CLIENT: pg
|
||||||
DB_HOST: kutt_db
|
DB_HOST: kutt_db
|
||||||
DB_PORT: '5432'
|
DB_PORT: '5432'
|
||||||
DB_USER: ${DB_USER:-kutt}
|
DB_USER: ${DB_USER:-kutt}
|
||||||
DB_PASSWORD: ${DB_PASSWORD:?Set DB_PASSWORD}
|
DB_PASSWORD: ${DB_PASSWORD:?Set DB_PASSWORD}
|
||||||
|
DB_POOL_MIN: '2'
|
||||||
|
DB_POOL_MAX: '10'
|
||||||
DB_NAME: ${DB_NAME:-kutt}
|
DB_NAME: ${DB_NAME:-kutt}
|
||||||
DISALLOW_ANONYMOUS_LINKS: 'true'
|
DISALLOW_ANONYMOUS_LINKS: 'true'
|
||||||
DISALLOW_REGISTRATION: 'true'
|
DISALLOW_REGISTRATION: 'true'
|
||||||
LINK_LENGTH: 6
|
LINK_LENGTH: 6
|
||||||
JWT_SECRET: ${JWT_SECRET:?Set JWT_SECRET}
|
JWT_SECRET: ${JWT_SECRET:?Set JWT_SECRET}
|
||||||
MAIL_ENABLED: 'true'
|
MAIL_ENABLED: 'true'
|
||||||
MAIL_HOST: 'mail.mifi.holdings'
|
MAIL_HOST: ${MAIL_HOST:?Set MAIL_HOST}
|
||||||
MAIL_PORT: '465'
|
MAIL_PORT: ${MAIL_PORT:?Set MAIL_PORT}
|
||||||
MAIL_SECURE: 'true'
|
MAIL_SECURE: 'true'
|
||||||
MAIL_USER: 'mailbot@mifi.ventures'
|
MAIL_USER: ${MAIL_USER:?Set MAIL_USER}
|
||||||
MAIL_FROM: 'mifi Holdings Shorty <noreply@mifi.holdings>'
|
MAIL_FROM: ${MAIL_FROM:?Set MAIL_FROM}
|
||||||
MAIL_PASSWORD: '${SMTP_PASSWORD:?Set SMTP_PASSWORD}'
|
MAIL_PASSWORD: '${MAIL_PASSWORD:?Set MAIL_PASSWORD}'
|
||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
# OIDC_ENABLED: 'true'
|
# OIDC_ENABLED: 'true'
|
||||||
# OIDC_ISSUER: 'https://git.mifi.dev'
|
# OIDC_ISSUER: 'https://git.mifi.dev'
|
||||||
@@ -90,18 +92,24 @@ services:
|
|||||||
REDIS_ENABLED: 'true'
|
REDIS_ENABLED: 'true'
|
||||||
REDIS_HOST: kutt_redis
|
REDIS_HOST: kutt_redis
|
||||||
REDIS_PORT: '6379'
|
REDIS_PORT: '6379'
|
||||||
SITE_DOMAIN: link.mifi.me
|
SERVER_CNAME_ADDRESS: ${SERVER_CNAME_ADDRESS:-${DEFAULT_DOMAIN}}
|
||||||
SITE_NAME: 'mifi Shorty'
|
SITE_NAME: ${SITE_NAME:-Kutt}
|
||||||
labels:
|
labels:
|
||||||
- 'traefik.enable=true'
|
- 'traefik.enable=true'
|
||||||
- 'docker.network=marina-net'
|
- 'docker.network=marina-net'
|
||||||
- 'traefik.http.routers.kutt-mifi.rule=Host(`mifi.me`)'
|
- 'traefik.http.routers.kutt-mifi.rule=Host(`${DEFAULT_DOMAIN}`)'
|
||||||
- 'traefik.http.routers.kutt-mifi.entrypoints=websecure'
|
- 'traefik.http.routers.kutt-mifi.entrypoints=websecure'
|
||||||
- 'traefik.http.routers.kutt-mifi.tls.certresolver=letsencrypt'
|
- 'traefik.http.routers.kutt-mifi.tls.certresolver=letsencrypt'
|
||||||
- 'traefik.http.routers.kutt-mifi.service=kutt-short'
|
- 'traefik.http.routers.kutt-mifi.service=kutt-short'
|
||||||
- 'traefik.http.services.kutt-short.loadbalancer.server.port=3000'
|
- 'traefik.http.services.kutt-short.loadbalancer.server.port=3000'
|
||||||
# Backend timeout: use transport from file provider (see traefik-kutt-timeout.example.yml).
|
|
||||||
- 'traefik.http.services.kutt-short.loadbalancer.serversTransport=kutt-long-timeout@file'
|
- 'traefik.http.services.kutt-short.loadbalancer.serversTransport=kutt-long-timeout@file'
|
||||||
|
# UI
|
||||||
|
- 'traefik.http.routers.kutt-mifi-ui.rule=Host(`${SERVER_CNAME_ADDRESS}`)'
|
||||||
|
- 'traefik.http.routers.kutt-mifi-ui.entrypoints=websecure'
|
||||||
|
- 'traefik.http.routers.kutt-mifi-ui.tls.certresolver=letsencrypt'
|
||||||
|
- 'traefik.http.routers.kutt-mifi-ui.service=kutt-short-ui'
|
||||||
|
- 'traefik.http.services.kutt-short-ui.loadbalancer.server.port=3000'
|
||||||
|
- 'traefik.http.services.kutt-short-ui.loadbalancer.serversTransport=kutt-long-timeout@file'
|
||||||
|
|
||||||
qr_api:
|
qr_api:
|
||||||
image: ${REGISTRY:-git.mifi.dev}/mifi-holdings/shorty-qr-api:${IMAGE_TAG:-latest}
|
image: ${REGISTRY:-git.mifi.dev}/mifi-holdings/shorty-qr-api:${IMAGE_TAG:-latest}
|
||||||
@@ -140,7 +148,7 @@ services:
|
|||||||
# Use service_started so the stack can deploy even if qr_api is still broken; switch to service_healthy once qr_api image is fixed.
|
# Use service_started so the stack can deploy even if qr_api is still broken; switch to service_healthy once qr_api image is fixed.
|
||||||
depends_on:
|
depends_on:
|
||||||
qr_api:
|
qr_api:
|
||||||
condition: service_started
|
condition: service_healthy
|
||||||
environment:
|
environment:
|
||||||
QR_API_URL: http://qr_api:8080
|
QR_API_URL: http://qr_api:8080
|
||||||
healthcheck:
|
healthcheck:
|
||||||
@@ -148,7 +156,7 @@ services:
|
|||||||
- CMD
|
- CMD
|
||||||
- node
|
- node
|
||||||
- -e
|
- -e
|
||||||
- 'require("http").get("http://0.0.0.0:3000/", {timeout: 5000}, (r) => { r.resume(); process.exit(r.statusCode >= 200 && r.statusCode < 500 ? 0 : 1); }).on("error", () => process.exit(1))'
|
- 'require("http").get("http://localhost:3000/", {timeout: 5000}, (r) => { r.resume(); process.exit(r.statusCode >= 200 && r.statusCode < 500 ? 0 : 1); }).on("error", () => process.exit(1))'
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|||||||
@@ -76,11 +76,7 @@ services:
|
|||||||
- 'traefik.http.routers.kutt-mifi.tls.certresolver=letsencrypt'
|
- 'traefik.http.routers.kutt-mifi.tls.certresolver=letsencrypt'
|
||||||
- 'traefik.http.routers.kutt-mifi.service=kutt-short'
|
- 'traefik.http.routers.kutt-mifi.service=kutt-short'
|
||||||
- 'traefik.http.services.kutt-short.loadbalancer.server.port=3000'
|
- 'traefik.http.services.kutt-short.loadbalancer.server.port=3000'
|
||||||
- 'traefik.http.routers.kutt-link.rule=Host(`link.mifi.me`)'
|
- 'traefik.http.services.kutt-short.loadbalancer.serversTransport=kutt-long-timeout@file'
|
||||||
- 'traefik.http.routers.kutt-link.entrypoints=websecure'
|
|
||||||
- 'traefik.http.routers.kutt-link.tls.certresolver=letsencrypt'
|
|
||||||
- 'traefik.http.routers.kutt-link.service=kutt'
|
|
||||||
- 'traefik.http.services.kutt.loadbalancer.server.port=3000'
|
|
||||||
|
|
||||||
qr_api:
|
qr_api:
|
||||||
build:
|
build:
|
||||||
@@ -131,7 +127,7 @@ services:
|
|||||||
- CMD
|
- CMD
|
||||||
- node
|
- node
|
||||||
- -e
|
- -e
|
||||||
- 'require("http").get("http://0.0.0.0:3000/", {timeout: 5000}, (r) => { r.resume(); process.exit(r.statusCode >= 200 && r.statusCode < 500 ? 0 : 1); }).on("error", () => process.exit(1))'
|
- 'require("http").get("http://localhost:3000/", {timeout: 5000}, (r) => { r.resume(); process.exit(r.statusCode >= 200 && r.statusCode < 500 ? 0 : 1); }).on("error", () => process.exit(1))'
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|||||||
@@ -35,7 +35,10 @@ describe('shortenUrl', () => {
|
|||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-API-Key': 'test-key',
|
'X-API-Key': 'test-key',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ target: 'https://example.com' }),
|
body: JSON.stringify({
|
||||||
|
target: 'https://example.com',
|
||||||
|
domain: 'mifi.me',
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -54,6 +57,7 @@ describe('shortenUrl', () => {
|
|||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
target: 'https://example.com',
|
target: 'https://example.com',
|
||||||
|
domain: 'mifi.me',
|
||||||
customurl: 'myslug',
|
customurl: 'myslug',
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -18,14 +18,23 @@ export async function shortenUrl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const base = env.KUTT_BASE_URL.replace(/\/$/, '');
|
const base = env.KUTT_BASE_URL.replace(/\/$/, '');
|
||||||
|
const apiKey = env.KUTT_API_KEY.trim();
|
||||||
|
|
||||||
|
// Extract domain from SHORT_DOMAIN (e.g., "https://mifi.me" -> "mifi.me")
|
||||||
|
const domain = env.SHORT_DOMAIN.replace(/^https?:\/\//, '').replace(
|
||||||
|
/\/$/,
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
|
||||||
const res = await fetch(`${base}/api/v2/links`, {
|
const res = await fetch(`${base}/api/v2/links`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-API-Key': env.KUTT_API_KEY,
|
'X-API-Key': apiKey,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
target: body.targetUrl,
|
target: body.targetUrl,
|
||||||
|
domain: domain,
|
||||||
...(body.customSlug && { customurl: body.customSlug }),
|
...(body.customSlug && { customurl: body.customSlug }),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ RUN corepack enable && corepack prepare pnpm@latest --activate
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
|
ENV HOSTNAME=0.0.0.0
|
||||||
COPY --from=builder /app/package.json ./
|
COPY --from=builder /app/package.json ./
|
||||||
COPY --from=builder /app/pnpm-lock.yaml* ./
|
COPY --from=builder /app/pnpm-lock.yaml* ./
|
||||||
RUN pnpm install --prod
|
RUN pnpm install --prod
|
||||||
|
|||||||
@@ -1020,19 +1020,7 @@ export function Editor({ id }: EditorProps) {
|
|||||||
updateProject({ recipeJson: JSON.stringify(r) });
|
updateProject({ recipeJson: JSON.stringify(r) });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Select
|
{/* Shape select removed - circle shape has rendering issues in qr-code-styling library */}
|
||||||
label="Shape"
|
|
||||||
data={[
|
|
||||||
{ value: 'square', label: 'Square' },
|
|
||||||
{ value: 'circle', label: 'Circle' },
|
|
||||||
]}
|
|
||||||
value={recipe.shape ?? 'square'}
|
|
||||||
onChange={(v) => {
|
|
||||||
const r = { ...recipe };
|
|
||||||
r.shape = (v as 'square' | 'circle') ?? 'square';
|
|
||||||
updateProject({ recipeJson: JSON.stringify(r) });
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Group grow>
|
<Group grow>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
label="Margin"
|
label="Margin"
|
||||||
|
|||||||
@@ -127,14 +127,13 @@ describe('buildQrStylingOptions', () => {
|
|||||||
).toEqual(g);
|
).toEqual(g);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('uses imageOptions and shape from recipe', () => {
|
it('uses imageOptions from recipe (shape always square)', () => {
|
||||||
const opts = buildQrStylingOptions({
|
const opts = buildQrStylingOptions({
|
||||||
imageOptions: {
|
imageOptions: {
|
||||||
hideBackgroundDots: false,
|
hideBackgroundDots: false,
|
||||||
imageSize: 0.5,
|
imageSize: 0.5,
|
||||||
margin: 5,
|
margin: 5,
|
||||||
},
|
},
|
||||||
shape: 'circle',
|
|
||||||
});
|
});
|
||||||
expect(
|
expect(
|
||||||
(opts.imageOptions as { hideBackgroundDots: boolean })
|
(opts.imageOptions as { hideBackgroundDots: boolean })
|
||||||
@@ -144,7 +143,8 @@ describe('buildQrStylingOptions', () => {
|
|||||||
0.5,
|
0.5,
|
||||||
);
|
);
|
||||||
expect((opts.imageOptions as { margin: number }).margin).toBe(5);
|
expect((opts.imageOptions as { margin: number }).margin).toBe(5);
|
||||||
expect(opts.shape).toBe('circle');
|
// shape is always 'square' (circle has rendering issues in qr-code-styling)
|
||||||
|
expect(opts.shape).toBe('square');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export function buildQrStylingOptions(
|
|||||||
data: overrides.data ?? recipe.data ?? ' ',
|
data: overrides.data ?? recipe.data ?? ' ',
|
||||||
image: overrides.image,
|
image: overrides.image,
|
||||||
type: 'canvas',
|
type: 'canvas',
|
||||||
shape: recipe.shape ?? 'square',
|
shape: 'square', // circle shape has rendering issues in qr-code-styling library
|
||||||
margin: recipe.margin ?? 0,
|
margin: recipe.margin ?? 0,
|
||||||
qrOptions: {
|
qrOptions: {
|
||||||
type: 'canvas',
|
type: 'canvas',
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export interface RecipeOptions {
|
|||||||
type?: string;
|
type?: string;
|
||||||
gradient?: QrGradient;
|
gradient?: QrGradient;
|
||||||
};
|
};
|
||||||
shape?: 'square' | 'circle';
|
// shape removed - circle shape has rendering issues in qr-code-styling library
|
||||||
margin?: number;
|
margin?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user