Commit Graph

163 Commits

Author SHA1 Message Date
soroush.asadi 99aa6b7048 Centralize OTP dev logging in Kavenegar SMS service
Move the dev-mode OTP logging into KavenegarSmsService so consumer and
admin auth flows no longer duplicate the fallback log.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 17:15:11 +03:30
soroush.asadi c68cca4f17 Add OTP login flow and multi-cafe role switching
Introduce an OTP input box on login/register, surface user roles and a
cafe chooser, add a dashboard switch button in the POS screen, and
register OTP validators explicitly to survive Docker layer caching.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 17:14:46 +03:30
soroush.asadi 923a00b113 Redesign thermal receipts with Vazirmatn font and cafe branding
Embed Vazirmatn web font in printed bills, add branded header with logo
and tagline, and wait for fonts to load before printing for clean output.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 17:14:32 +03:30
soroush.asadi 963d02a265 Add push notifications (Pushe) + Capacitor shell for Koja
Iran-safe push for the Koja Android app (Cafe Bazaar / Myket / direct APK):

Backend
- PushDevice entity + EF migration; idempotent device register/unregister.
- IPushSender / PusheNotificationSender (Pushe REST) — SendToTopic for
  marketing (city-{slug}) and saved-café (cafe-{slug}) pushes, SendToTokens
  for targeted order/reservation updates.
- Public register/unregister endpoints + authorized topic broadcast.

App
- capacitor.config.ts (native WebView loads the live PWA via server.url).
- push.ts Pushe glue: topic helpers + backend device registration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 17:06:42 +03:30
soroush.asadi 289c808257 Rename public discovery app from "finder" to "koja"
Rebrand the public café-discovery app: directories web/finder→web/koja and
docker/finder→docker/koja, plus all service wiring (docker-compose, Caddy
subdomain koja.meezi.ir, env vars KOJA_PORT / NEXT_PUBLIC_KOJA_URL, CI
workflows) and the app's display name (Koja / کجا).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 17:02:22 +03:30
soroush.asadi 16cff8730b feat : kavenegar otp added 2026-05-29 10:18:47 +03:30
soroush.asadi 27e61d257e Set Kavenegar OTP template to meeziotp
Template content: کد ورود : %token / میزی

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 02:55:02 +03:30
soroush.asadi fc21471131 Native thermal printing via iframe — 80mm, RTL, no blank tail
Problem: window.print() on the main page used A4 height (blank paper
after receipt), no RTL direction, and Tailwind styles leaked into print.

Solution — iframe isolation:
- lib/thermal-print.ts: builds a self-contained HTML document
  (@page { size: 80mm auto; margin: 0 }, html { direction: rtl })
  and fires it through a hidden off-screen <iframe>. The iframe
  document contains only the receipt so height == content height.
- pos-slip-modal.tsx: Print button calls printThermal(buildThermalDocument())
  instead of window.print(). Preview panel is unchanged (screen only).
- pos-receipt-print.css: updated @page + direction as fallback for any
  remaining window.print() callers.

Works with USB driver (Atom A300) as default printer — OS print spooler
receives the job exactly as if it were any other document.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 02:52:46 +03:30
soroush.asadi 42d7667735 Fully implement Kavenegar SMS support
Core changes:
- ISmsService: add SendBulkAsync (batches of 200) + GetAccountInfoAsync
- KavenegarSmsService: POST requests, sender number config, bulk send
  via comma-separated receptors, account balance, full error code mapping
  (HTTP 400-432), enabled-flag check before any send
- SmsMarketingService: replaced per-recipient loop with SendBulkAsync
- SmsController: new GET /sms/balance endpoint returns Kavenegar credit
- SmsDtos: SmsBalanceDto
- IntegrationDtos + PlatformIntegrationService: SenderNumber field
- appsettings.json + docker-compose: Kavenegar__SenderNumber = 90005671

Dashboard:
- sms-screen: char counter, SMS parts indicator (Persian 70/67 chars,
  Latin 160/153), account balance card, sender line display, result banner

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 02:38:06 +03:30
soroush.asadi b78f2affb6 Move POS page to fullscreen layout for full viewport width
POS terminal needs the entire screen — the dashboard navigation
sidebar (224px) was eating into the cashier's working space.
Moving /pos from (dashboard) to (fullscreen) gives the POS the
full viewport with no chrome. Auth redirect and CafeThemeProvider
are applied directly in the new page.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 02:20:43 +03:30
soroush.asadi a21cb7dd8e fix(dashboard): fallback for crypto.randomUUID on HTTP (non-secure context)
crypto.randomUUID() is only available over HTTPS. Add a timestamp+random
fallback so the dashboard works on plain HTTP during development/IP access.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 02:01:21 +03:30
soroush.asadi 83758fe68a fix(admin-web): add .gitkeep to public/ so Docker COPY finds /app/public
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 01:46:56 +03:30
soroush.asadi c87c40ffe6 fix(website): add .gitkeep to public/fonts so Docker COPY finds /app/public
Empty directories are not tracked by git — without this the runner stage
COPY --from=builder /app/public ./public fails with "not found".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 01:31:56 +03:30
Soroush.Asadi b8e56b8f83 ci: retrigger build with fixed Dockerfiles 4 2026-05-28 23:13:23 +03:30
Soroush.Asadi 112113dbfc ci: retrigger build with fixed Dockerfiles 3 2026-05-28 23:07:03 +03:30
soroush.asadi 060f724f7c fix(website): revert generateStaticParams params to non-Promise
generateStaticParams receives plain params (not Promise).
Only page/layout default exports get Promise params in Next.js 15+.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 22:56:50 +03:30
soroush.asadi 98a7efc719 fix(website): update all route/layout params to Promise for Next.js 16
Next.js 15+ requires params to be typed as Promise<{...}> and awaited.
Fixed 17 files: all [locale] pages, layouts, and blog [slug] page.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 22:46:09 +03:30
Soroush.Asadi 5f4ec511cb ci: retrigger build with fixed Dockerfiles 2 2026-05-28 22:29:34 +03:30
Soroush.Asadi 3b234df4f9 ci: retrigger build with fixed Dockerfiles 2026-05-28 22:29:02 +03:30
soroush.asadi 1559ee95c7 fix(website): update route params type for Next.js 16
Next.js 15+ changed dynamic route params to Promise<...>.
Update GET and POST type signatures in blog comments route.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 22:20:14 +03:30
soroush.asadi aa4612e06b fix: use Liara mirrors directly for images with Nexus OCI proxy issues
- aspnet:10.0, postgres:16-alpine, redis:7-alpine all fail on first
  fetch through Nexus proxy (OCI manifest format bug in Nexus)
- Change DOTNET_ASPNET_IMAGE default to mcr-mirror.liara.ir directly
- Change postgres/redis service images to docker-mirror.liara.ir
- CI service containers (api-build job) also use Liara directly
- All images parameterized so ENV_FILE can override for any registry

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 22:08:07 +03:30
Soroush.Asadi ce41b2306d ci: retrigger build with fixed Dockerfiles 2026-05-28 21:36:57 +03:30
Soroush.Asadi aaf61e0b98 Merge branch 'main' of https://github.com/codesoroush/Meezi 2026-05-28 21:03:02 +03:30
soroush.asadi 979dcaa949 fix: route all Docker builds through local Nexus mirrors
- All Node Dockerfiles rewritten with NODE_IMAGE + NPM_REGISTRY build args
  defaulting to local Nexus proxies (171.22.25.73:5000/library/node:20-alpine
  and http://mirror:8081/repository/npm-group/)
- Add extra_hosts: mirror:host-gateway to every build section so the
  mirror hostname resolves during docker build
- Replace nuget.org with nuget.docker.config (Nexus mirror) in api/admin-api
  Dockerfiles to fix NuGet restore in Iranian network
- Rewrite admin-web and website Dockerfiles (were referencing non-existent
  meezi-node:20-alpine base image with no npm install step)
- Update dotnet image defaults to 171.22.25.73:5002 MCR proxy in admin-api
  and docker-compose.admin.yml

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 21:01:43 +03:30
Soroush.Asadi b1365c501e CI CD 2 2026-05-28 20:43:35 +03:30
Soroush.Asadi 96dad89016 CI CD 2026-05-28 19:50:23 +03:30
Soroush.Asadi d5a3056bfb Merge branch 'main' of https://github.com/codesoroush/Meezi 2026-05-28 19:28:56 +03:30
soroush.asadi 25154f9dd9 fix(ci): set PATH in deploy job so docker binary is found
act runner (host mode) inherits a minimal PATH from the process
environment — docker is not found even though it is installed.
Explicitly include all standard locations plus /snap/bin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 19:28:15 +03:30
Soroush.Asadi 665de97c49 ci : workflow updated 15 2026-05-28 19:18:24 +03:30
soroush.asadi 34040503cf docs: rewrite DEPLOY.md with self-hosted setup guide
Replaces outdated Arvan Cloud instructions with the current
self-hosted stack: Gitea CI, Nexus mirror, Docker Compose,
IP-based access today and Caddy+domain tomorrow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 19:06:28 +03:30
soroush.asadi cb80afaf42 fix(ci): replace actions/checkout@v4 in deploy job with shell git
Node.js is not in PATH on the self-hosted:host runner, so JS actions
(actions/checkout@v4) fail with "cannot find node". Use the same shell
git init/fetch/checkout pattern used in all other jobs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 18:57:58 +03:30
soroush.asadi 88a9f96108 feat(infra): parameterize secrets, add Caddy reverse proxy for domain
All hardcoded passwords/keys replaced with env vars so .env controls
everything in both dev and production:
  - DB_PASSWORD, DB_CONNECTION_STRING, JWT_KEY
  - CORS_ORIGIN_*, ASPNETCORE_ENVIRONMENT
  - All ZarinPal/Kavenegar/Snappfood secrets

New files for tomorrow's domain setup:
  - Caddyfile        → routes all subdomains with auto TLS
  - docker-compose.caddy.yml → adds Caddy service to the stack

.env.example now has clear TODAY (IP) vs TOMORROW (domain) sections.
Fixed hardcoded ZarinPal MerchantId in docker-compose.full.yml.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 18:54:55 +03:30
soroush.asadi 0a33497d40 feat(admin-web): add web/admin to repo
Initial commit of the Super-Admin web panel (Next.js + TypeScript).
CI admin-web-check job was failing because the directory was never
tracked in git.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 18:46:25 +03:30
Soroush.Asadi f717c02467 Merge branch 'main' of https://github.com/codesoroush/Meezi 2026-05-28 18:34:56 +03:30
soroush.asadi 539165b6bb fix(ci): replace python3 with cat heredoc; sync local registry IPs
python3 is not in PATH inside dotnet/sdk:10.0 container — replace the
"Write NuGet config" step with a cat heredoc which works in any container.

Also syncs GitHub with the Gitea-side changes:
  - All images pulled from local Nexus mirrors (no internet round-trip)
      171.22.25.73:5000 → docker-hub-proxy (node, postgres, redis)
      171.22.25.73:5002 → mcr-proxy        (dotnet/sdk)
  - npm steps already on npm-group (Liara + Runflare fallback)
  - docker-compose.mirror.yml: expose port 5002 for mcr-proxy

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 18:34:06 +03:30
Soroush.Asadi da81be926a ci : workflow updated 13 2026-05-28 18:24:29 +03:30
Soroush.Asadi 37afa965e7 ci : workflow updated 12 2026-05-28 18:08:49 +03:30
soroush.asadi 720bab457e feat(mirror): add Liara npm/PyPI/Ubuntu APT mirrors to Nexus
Adds mirrors/nexus/add-liara-mirrors.sh that provisions:
  - npm-liara-proxy  → https://package-mirror.liara.ir/repository/npm/
  - npm-group        → npm-liara-proxy + npm-proxy (Liara first, Runflare fallback)
  - pypi-liara-proxy → https://package-mirror.liara.ir/repository/pypi/
  - pypi-group       → pypi-liara-proxy + pypi-proxy
  - ubuntu-proxy     → http://linux-mirror.liara.ir/repository/ubuntu/
  - ubuntu-security-proxy → http://linux-mirror.liara.ir/repository/ubuntu-security/

Also updates CI npm install steps to use npm-group instead of npm-proxy
so all four Node.js jobs benefit from the Liara-first, Runflare-fallback
group from day one.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 17:35:01 +03:30
soroush.asadi f825c72ca2 perf(ci): replace apk add git with Gitea archive API for Node.js jobs
apk add git downloads from dl-cdn.alpinelinux.org (Fastly CDN) which is
slow/blocked in Iran — caused 6m+ checkout times.

New approach: wget the repo tarball from Gitea's archive API endpoint.
wget + tar (busybox) are already in node:20-alpine — no package install.
Gitea is on the same machine as the runner = download is instant.

GET /api/v1/repos/{owner}/{repo}/archive/{sha}.tar.gz
Authorization: Bearer {token}

dotnet/sdk jobs unchanged — Debian base has git pre-installed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 17:12:49 +03:30
soroush.asadi ca17cffee6 fix(ci): write NuGet config inline to allow HTTP source (NU1302)
NuGet 10 blocks HTTP sources by default. allowInsecureConnections=true
must be set in a config file — the --source CLI flag doesn't support it.

Write the config to /tmp/nuget.ci.config inline in the step so there is
no dependency on any file existing in the workspace.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 16:55:19 +03:30
soroush.asadi 32d9900e07 fix(ci): use --source flag instead of --configfile for NuGet restore
--configfile nuget.mirror.config fails when the file isn't present in
the workspace (e.g. when Gitea is behind GitHub on commits).

--source inline URL is simpler, self-contained, and replaces all
configured sources — no extra file dependency in CI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 16:39:36 +03:30
soroush.asadi 3cb39c9b81 fix(mirror): Docker proxy port is 5000 not 8083 (matches Nexus httpPort config)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 16:33:04 +03:30
soroush.asadi d8e26c6fad fix(mirror): point NuGet source to nuget-group (was nuget-proxy which doesn't exist)
nuget-group aggregates nuget.org-proxy (Liara) and nuget-runflare-proxy
(Runflare) — automatic fallback if one upstream is down.

npm-proxy and docker-hub-proxy names match exactly, no changes needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 16:28:18 +03:30
soroush.asadi 7321c59e43 fix(mirror): Nexus Docker proxy with optional Liara upstream + credential support
provision.sh: Docker proxy defaults to registry-1.docker.io (works directly
  from VPS). Set DOCKER_MIRROR_URL/USER/PASS env vars to route through
  docker-mirror.liara.ir once Liara credentials are obtained.

update-docker-upstream.sh: swap Docker proxy upstream at any time without
  re-running the full provision (useful after getting Liara credentials).

indexType auto-selects: HUB for docker.io direct, REGISTRY for Liara/Harbor.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 15:56:22 +03:30
soroush.asadi 61e44c63ab refactor(mirror): replace 3 services with single Nexus Repository Manager
Consolidates BaGet + Verdaccio + registry:2 into one Sonatype Nexus OSS
instance with a REST API provisioning script.

docker-compose.mirror.yml: single nexus service, ports 8081 (UI/NuGet/npm)
  and 8083 (Docker Hub pull-through proxy)
mirrors/nexus/provision.sh: idempotent setup — changes admin password,
  enables anonymous access, creates nuget-proxy / npm-proxy / docker-hub-proxy
nuget.mirror.config: updated source URL to Nexus NuGet proxy endpoint
ci-cd.yml: updated npm --registry to Nexus npm proxy endpoint

Run once on server: docker compose -f docker-compose.mirror.yml up -d
  then: ./mirrors/nexus/provision.sh

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 14:35:55 +03:30
soroush.asadi 6f85cfe4d3 feat(infra): add local pull-through mirrors for NuGet, npm, Docker Hub
docker-compose.mirror.yml:
  - BaGet  (port 5101) → proxies nuget.org
  - Verdaccio (port 4873) → proxies npmjs.com
  - registry:2 (port 5100) → proxies Docker Hub

nuget.mirror.config: points dotnet restore at http://mirror:5101
mirrors/verdaccio/config.yaml: open reads, upstream npmjs fallback

CI workflow:
  - All container jobs: --add-host=mirror:host-gateway
  - dotnet restore --configfile nuget.mirror.config
  - npm install --registry http://mirror:4873

First run: packages fetched from upstream through the VPS.
All subsequent runs: served from local disk, no CDN needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 14:31:12 +03:30
soroush.asadi c452df8988 fix(ci): add --add-host=gitea:host-gateway to all container jobs
github.server_url returns 'http://gitea:3000' (Gitea ROOT_URL using Docker
service name). CI job containers run on an isolated network and can't resolve
the 'gitea' hostname.

host-gateway maps to the Docker bridge IP (172.17.0.1). Gitea publishes
port 3000 on all interfaces, so http://gitea:3000 becomes reachable inside
every job container via the bridge.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 11:59:45 +03:30
soroush.asadi 8ddb427edd fix(ci): replace actions/checkout with shell git for container jobs
actions/checkout@v4 is a JS action executed inside the job container:
- dotnet/sdk:10.0 has no Node.js  → exit 127
- node:20-alpine  has no git      → checkout fails

Fix: manual git clone via shell using http.extraheader for token auth.
Token never appears in process list or git log. deploy job (self-hosted:host)
keeps actions/checkout — the act_runner image has both node and git.

Also removes defaults.run.working-directory from Node.js jobs (the checkout
step must start in workspace root, not web/<app>).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 11:46:34 +03:30
soroush.asadi 6c868f5f30 fix(ci): use docker:// runner mode with pre-built SDK images
Switch CI jobs to container: image: overrides so jobs run inside official
SDK containers (dotnet/sdk:10.0, node:20-alpine) instead of the bare
runner container. This bypasses blocked CDN downloads for dotnet/node.

Deploy job stays on self-hosted:host where Docker CLI is available.
Update workflow comments to explain the required runner label config:
  ubuntu-latest:docker://node:20-alpine (CI jobs)
  self-hosted:host (deploy)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 11:27:49 +03:30
soroush.asadi dcddcf77d6 Fix Gitea CI stuck at setup-dotnet/setup-node due to blocked CDNs
Root cause: actions/setup-dotnet@v4 downloads .NET from
download.visualstudio.microsoft.com and actions/setup-node@v4 downloads
Node from nodejs.org — both CDNs are blocked from Iran so jobs hang at 0s.

Fix:
- All .NET jobs: add container: mcr.microsoft.com/dotnet/sdk:10.0
  so .NET is already inside the image — no download needed.
  Remove actions/setup-dotnet step entirely.
- All Node.js jobs: add container: node:20-alpine
  so Node/npm are already inside the image — no download needed.
  Remove actions/setup-node step entirely.
- api-build: add postgres + redis service containers + env vars so
  dotnet test can actually connect to a database (was silently failing).
- deploy job: change back to runs-on: self-hosted
  ubuntu-latest containers don't have Docker CLI — docker compose
  commands would fail immediately. Deploy MUST run on the server.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 11:08:42 +03:30