Add Soroush CI/CD (Gitea + Nexus) + self-host fonts for offline build
CI/CD / CI - API (dotnet build + engine sim) (push) Failing after 1m40s
CI/CD / CI - Web (tsc + next build) (push) Failing after 1m20s
CI/CD / Deploy - local stack (db + server + web) (push) Has been skipped

Pipeline (.gitea/workflows/ci-cd.yml), all images/packages via Nexus mirror:
- CI api-build: dotnet restore/build server/Hokm.slnx + run Hokm.Sim (rules).
- CI web-check: npm install + tsc --noEmit + next build (static export).
- deploy (self-hosted): pre-deploy pg_dump backup, rollback image tag, build,
  bring up db -> server -> web with stop+rm+up --no-deps (no force-recreate,
  no bare compose down), health-wait each, prune.

Local stack (docker-compose.yml), ports in 1500-1600 so it coexists with manual
dev on 3000/5005:  web :1500 (nginx static) -> server :1505 (.NET) -> db :1510
(postgres, named volume + backups). Dockerfiles: server (.NET, NuGet via
nuget.docker.config, binds 0.0.0.0, busybox wget healthcheck) + web (Next static
export -> nginx, NEXT_PUBLIC_* baked as build args). nginx.conf SPA fallback.

Config: server CORS is now config-driven (Cors__Origins) so the deployed web
origin is allowed without code edits. deploy/ENV_FILE.example documents the
Gitea ENV_FILE secret; DEPLOY.md covers setup/run/LAN-IP/rollback/migrations.

Fonts: switch Vazirmatn + Plus Jakarta Sans from next/font/google (build-time
Google fetch -> fails on the Iran CI runner) to self-hosted @fontsource-variable
packages. Build is offline and ~3x faster; 7 woff2 emitted into out/.

Verified locally: dotnet build slnx + Hokm.Sim (300 matches, exit 0); tsc clean;
next build clean with self-hosted fonts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-04 19:09:31 +03:30
parent e778e8b5bd
commit 89d42184a1
15 changed files with 534 additions and 21 deletions
+15
View File
@@ -0,0 +1,15 @@
node_modules
.next
out
android
server
.git
.gitea
*.md
.env*
!.env.example
Dockerfile
.dockerignore
docker-compose*.yml
npm-debug.log*
tsconfig.tsbuildinfo
+185
View File
@@ -0,0 +1,185 @@
name: CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: hokm-cicd-${{ github.ref }}
cancel-in-progress: true
jobs:
# ---------------------------------------------------------------- API (.NET)
api-build:
name: "CI - API (dotnet build + engine sim)"
runs-on: ubuntu-latest
container:
image: mirror.soroushasadi.com/dotnet/sdk:10.0
options: --add-host=gitea:host-gateway
steps:
- name: Checkout
env:
TOKEN: ${{ github.token }}
REF: ${{ github.ref }}
run: |
git init -q
git remote add origin "${{ github.server_url }}/${{ github.repository }}.git"
git config http.extraheader "Authorization: Bearer ${TOKEN}"
git fetch --depth=1 origin "${REF}"
git checkout -q FETCH_HEAD
- name: Write NuGet config
run: |
cat > /tmp/nuget.ci.config << 'EOF'
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nexus" value="https://mirror.soroushasadi.com/repository/nuget-group/index.json" protocolVersion="3" />
</packageSources>
<config>
<add key="http_retry_count" value="8" />
<add key="http_retry_delay_milliseconds" value="1000" />
</config>
</configuration>
EOF
- name: Restore
run: dotnet restore server/Hokm.slnx --configfile /tmp/nuget.ci.config
env:
DOTNET_CLI_TELEMETRY_OPTOUT: 1
DOTNET_NOLOGO: 1
- name: Build
run: dotnet build server/Hokm.slnx --no-restore -c Release
- name: Engine simulation (rules validation)
run: dotnet run --project server/tools/Hokm.Sim/Hokm.Sim.csproj -c Release --no-build
# ----------------------------------------------------------- Web (Next.js)
web-check:
name: "CI - Web (tsc + next build)"
runs-on: ubuntu-latest
container:
image: mirror.soroushasadi.com/node:20-alpine
options: --add-host=gitea:host-gateway
steps:
- name: Checkout (tarball)
env:
TOKEN: ${{ github.token }}
SHA: ${{ github.sha }}
run: |
wget -q --header "Authorization: Bearer ${TOKEN}" \
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/archive/${SHA}.tar.gz" \
-O /tmp/repo.tar.gz
tar -xzf /tmp/repo.tar.gz --strip-components=1
- name: Install
run: npm install --legacy-peer-deps --ignore-scripts --registry https://mirror.soroushasadi.com/repository/npm-group/
- name: TypeScript check
run: npx tsc --noEmit
- name: Build (static export)
run: npm run build
# -------------------------------------------------------------- Deploy
deploy:
name: "Deploy - local stack (db + server + web)"
runs-on: self-hosted
needs: [api-build, web-check]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
timeout-minutes: 40
env:
# act host runner starts with a minimal PATH — extend so docker is found.
PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
steps:
- name: Checkout
env:
TOKEN: ${{ github.token }}
REF: ${{ github.ref }}
run: |
git init -q
git remote add origin "${{ github.server_url }}/${{ github.repository }}.git"
git config http.extraheader "Authorization: Bearer ${TOKEN}"
git fetch --depth=1 origin "${REF}"
git checkout -q FETCH_HEAD
- name: Write .env
run: printf '%s' "$ENV_FILE" > .env
env:
ENV_FILE: ${{ secrets.ENV_FILE }}
- name: Backup database (if running)
run: |
mkdir -p /opt/hokm-backups
if docker ps --format '{{.Names}}' | grep -q '^hokm-db$'; then
TS=$(date +%Y%m%d-%H%M%S)
docker exec hokm-db pg_dump -U hokm hokm > "/opt/hokm-backups/hokm-${TS}.sql" \
&& echo "backed up to /opt/hokm-backups/hokm-${TS}.sql" \
|| echo "WARN: pg_dump failed (continuing)"
else
echo "no hokm-db container yet — first deploy, nothing to back up"
fi
- name: Tag rollback image
run: |
CURRENT=$(docker inspect hokm-server --format='{{.Config.Image}}' 2>/dev/null || echo "")
if [ -n "$CURRENT" ]; then docker tag "$CURRENT" hokm-server:rollback && echo "rollback tag = $CURRENT"; fi
- name: Build images
run: docker compose build --parallel server web
env:
DOCKER_BUILDKIT: 1
COMPOSE_DOCKER_CLI_BUILD: 1
- name: Start database
run: docker compose up -d --no-deps db
- name: Wait for database healthy
run: |
for i in $(seq 1 20); do
S=$(docker inspect --format='{{.State.Health.Status}}' hokm-db 2>/dev/null || echo missing)
echo " [$i/20] db: $S"
[ "$S" = "healthy" ] && break
[ "$i" = "20" ] && { echo "TIMEOUT db"; docker logs --tail=40 hokm-db; exit 1; }
sleep 3
done
- name: Deploy server (stop + rm + up, no force-recreate)
run: |
docker stop hokm-server 2>/dev/null || true
docker rm hokm-server 2>/dev/null || true
docker compose up -d --no-deps server
- name: Wait for server healthy
run: |
for i in $(seq 1 24); do
S=$(docker inspect --format='{{.State.Health.Status}}' hokm-server 2>/dev/null || echo missing)
echo " [$i/24] server: $S"
[ "$S" = "healthy" ] && { echo "OK hokm-server healthy"; break; }
[ "$i" = "24" ] && { echo "TIMEOUT hokm-server"; docker compose logs --tail=60 server; exit 1; }
sleep 5
done
- name: Deploy web (stop + rm + up, no force-recreate)
run: |
docker stop hokm-web 2>/dev/null || true
docker rm hokm-web 2>/dev/null || true
docker compose up -d --no-deps web
- name: Wait for web healthy
run: |
for i in $(seq 1 18); do
S=$(docker inspect --format='{{.State.Health.Status}}' hokm-web 2>/dev/null || echo missing)
echo " [$i/18] web: $S"
[ "$S" = "healthy" ] && { echo "OK hokm-web healthy"; break; }
[ "$i" = "18" ] && { echo "TIMEOUT hokm-web"; docker compose logs --tail=40 web; exit 1; }
sleep 5
done
- name: Prune dangling images
if: success()
run: docker image prune -f
+85
View File
@@ -0,0 +1,85 @@
# Deploy — Barg-e Vasat (Soroush CI/CD)
CI/CD runs on **Gitea Actions** (`git.soroushasadi.com`) with all packages and
base images pulled through the **Nexus mirror** (`mirror.soroushasadi.com`).
Pushing to `main` triggers build → deploy.
## Topology
| Service | Image | Container | Host port | Notes |
|---|---|---|---|---|
| Postgres | `postgres:16-alpine` | `hokm-db` | `1510` | named volume `hokm_db_data` |
| API (.NET SignalR) | `hokm-server:latest` | `hokm-server` | `1505` → 5005 | EF Core → Postgres |
| Web (static Next → nginx) | `hokm-web:latest` | `hokm-web` | `1500` → 80 | `NEXT_PUBLIC_*` baked at build |
Ports are in **15001600** on purpose, so the deployed stack runs alongside a
manual `npm run dev` (:3000) and `dotnet run` (:5005) without colliding.
## Pipeline (`.gitea/workflows/ci-cd.yml`)
1. **CI API**: restore (Nexus NuGet) → `dotnet build server/Hokm.slnx` → run `Hokm.Sim` (engine rules validation).
2. **CI Web**: `npm install` (Nexus npm) → `tsc --noEmit``next build` (static export).
3. **Deploy** (`self-hosted`, push to `main` only): backup DB → tag rollback image → build images → bring up `db` (wait healthy) → `server` (stop+rm+up, wait healthy) → `web` (stop+rm+up, wait healthy) → prune.
Deploy follows the safety rules: pre-deploy `pg_dump` backup to `/opt/hokm-backups`,
rollback tag before replace, explicit `stop + rm + up --no-deps` (no
`--force-recreate`, no bare `docker compose down`).
## One-time setup
1. **Secret**: fill `deploy/ENV_FILE.example` and paste into the Gitea repo secret
`ENV_FILE` at `.../HokmPlay/settings/secrets`. At minimum set `JWT_KEY`
(`openssl rand -hex 32`) and `POSTGRES_PASSWORD`.
2. **Runner**: an `act_runner` registered with both labels
(`ubuntu-latest:docker://...` for CI, `self-hosted:host` for deploy) — reused
from existing Soroush projects.
3. **Push**: `git push origin main` → watch `.../HokmPlay/actions`.
## Reaching the stack
- Same machine as the deploy host: open `http://localhost:1500`.
- Different machine (browser elsewhere): set `NEXT_PUBLIC_SERVER_URL` and
`CORS_ORIGINS` in `ENV_FILE` to the host **LAN IP** (e.g.
`http://172.28.144.1:1505` / `http://172.28.144.1:1500`) and push again —
the API URL is baked into the web bundle at build time. (localhost can be
hijacked by the VPN; prefer the LAN IP.)
## Local test (no Gitea, on your machine)
```bash
cd D:\Projects\hokm
copy deploy\ENV_FILE.example .env # then edit JWT_KEY / POSTGRES_PASSWORD
docker compose build server web
docker compose up -d
# web → http://localhost:1500 api → http://localhost:1505/
docker compose logs -f server
```
Tear down (keeps the DB volume):
```bash
docker compose stop
```
## Migrations
The server auto-applies EF migrations when any exist, else `EnsureCreated()`
(current state — no migration classes yet, so the Postgres schema is created on
first boot). When you generate them:
```bash
cd server/src/Hokm.Server
$env:HOKM_DESIGN_CONN="Host=localhost;Port=1510;Database=hokm;Username=hokm;Password=<pw>"
dotnet ef migrations add Init
```
Then the next deploy runs `Database.Migrate()` automatically.
## Rollback
```bash
docker stop hokm-server && docker rm hokm-server
docker run -d --name hokm-server --network hokm_default -p 1505:5005 \
--env-file <(grep -E '^(JWT_|Database__|ConnectionStrings__|Cors__|Zarinpal__)' .env) \
hokm-server:rollback
```
(or just revert the commit and push — CI redeploys the previous code.)
+22
View File
@@ -0,0 +1,22 @@
# Barg-e Vasat web (Next.js 16 static export → nginx)
# The app is output:"export" (fully client-side), so we build the static `out/`
# and serve it with nginx. NEXT_PUBLIC_* are baked at build time.
FROM mirror.soroushasadi.com/node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm install --legacy-peer-deps --ignore-scripts \
--registry https://mirror.soroushasadi.com/repository/npm-group/
COPY . .
# Live mode + the API origin the BROWSER will use (host-mapped port / LAN IP).
ARG NEXT_PUBLIC_USE_SERVER=1
ARG NEXT_PUBLIC_SERVER_URL=http://localhost:1505
ENV NEXT_PUBLIC_USE_SERVER=$NEXT_PUBLIC_USE_SERVER
ENV NEXT_PUBLIC_SERVER_URL=$NEXT_PUBLIC_SERVER_URL
RUN npm run build
FROM mirror.soroushasadi.com/nginx:alpine
COPY --from=build /app/out /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
HEALTHCHECK --interval=10s --timeout=5s --retries=6 --start-period=10s \
CMD wget -q -O- http://127.0.0.1/ || exit 1
+36
View File
@@ -0,0 +1,36 @@
# ──────────────────────────────────────────────────────────────────────────
# Barg-e Vasat — ENV_FILE
# Paste the contents of this file (filled in) into the Gitea repo secret:
# https://git.soroushasadi.com/soroushdes/HokmPlay/settings/secrets → ENV_FILE
# The deploy job writes it verbatim to `.env`, which docker compose reads.
#
# NOTE: NEXT_PUBLIC_SERVER_URL is baked into the web bundle at BUILD time —
# changing it requires a new CI run (push a commit) to take effect.
# ──────────────────────────────────────────────────────────────────────────
# Host ports (15001600 range so the stack coexists with manual dev on 3000/5005)
WEB_PORT=1500
API_PORT=1505
DB_PORT=1510
# Database (postgres container)
POSTGRES_PASSWORD=change-me-strong-password
# JWT — generate with: openssl rand -hex 32
JWT_KEY=CHANGE-ME-to-a-32+char-random-secret
JWT_ISSUER=hokm
JWT_AUDIENCE=hokm-clients
# Browser-facing API origin (host-mapped api port).
# If the browser is NOT on the deploy host, use the host LAN IP instead of
# localhost, e.g. http://172.28.144.1:1505 (localhost can be VPN-hijacked).
NEXT_PUBLIC_SERVER_URL=http://localhost:1505
# Origins allowed by the API's CORS (comma-separated). Must include the web URL.
CORS_ORIGINS=http://localhost:1500
# ZarinPal (sandbox for now — switch in admin/panel later)
ZARINPAL_MERCHANT_ID=299685fb-cadf-4dfc-98e2-d4af5d81528d
ZARINPAL_SANDBOX=true
ZARINPAL_CALLBACK_URL=http://localhost:1505/api/coins/pay/callback
ZARINPAL_CLIENT_RETURN_URL=http://localhost:1500
+86
View File
@@ -0,0 +1,86 @@
# Barg-e Vasat — local/self-hosted stack.
# Ports live in the 15001600 range so this stack can run alongside a manual
# `npm run dev` (:3000) and `dotnet run` (:5005) without colliding.
# web → http://localhost:1500
# api → http://localhost:1505
# db → localhost:1510 (postgres)
# All values come from .env (the deploy job writes it from the ENV_FILE secret).
services:
db:
image: mirror.soroushasadi.com/postgres:16-alpine
container_name: hokm-db
restart: unless-stopped
environment:
POSTGRES_DB: hokm
POSTGRES_USER: hokm
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-hokm_dev_pass}
volumes:
- hokm_db_data:/var/lib/postgresql/data
ports:
- "${DB_PORT:-1510}:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U hokm -d hokm"]
interval: 5s
timeout: 5s
retries: 10
server:
build:
context: ./server
dockerfile: Dockerfile
image: hokm-server:latest
container_name: hokm-server
restart: unless-stopped
depends_on:
db:
condition: service_healthy
environment:
ASPNETCORE_ENVIRONMENT: Production
ASPNETCORE_URLS: http://0.0.0.0:5005
Database__Provider: postgres
ConnectionStrings__Default: "Host=db;Port=5432;Database=hokm;Username=hokm;Password=${POSTGRES_PASSWORD:-hokm_dev_pass}"
Jwt__Key: ${JWT_KEY:?set JWT_KEY in .env}
Jwt__Issuer: ${JWT_ISSUER:-hokm}
Jwt__Audience: ${JWT_AUDIENCE:-hokm-clients}
# Comma-separated origins the browser uses to reach the web app.
Cors__Origins: ${CORS_ORIGINS:-http://localhost:1500}
Zarinpal__MerchantId: ${ZARINPAL_MERCHANT_ID:-299685fb-cadf-4dfc-98e2-d4af5d81528d}
Zarinpal__Sandbox: ${ZARINPAL_SANDBOX:-true}
Zarinpal__CallbackUrl: ${ZARINPAL_CALLBACK_URL:-http://localhost:1505/api/coins/pay/callback}
Zarinpal__ClientReturnUrl: ${ZARINPAL_CLIENT_RETURN_URL:-http://localhost:1500}
ports:
- "${API_PORT:-1505}:5005"
healthcheck:
test: ["CMD", "wget", "-q", "-O-", "http://127.0.0.1:5005/"]
interval: 10s
timeout: 5s
retries: 12
start_period: 20s
web:
build:
context: .
dockerfile: Dockerfile
args:
# Baked into the static bundle at build time. Must be the address the
# BROWSER uses to reach the API (host-mapped api port, or LAN IP).
NEXT_PUBLIC_USE_SERVER: "1"
NEXT_PUBLIC_SERVER_URL: ${NEXT_PUBLIC_SERVER_URL:-http://localhost:1505}
image: hokm-web:latest
container_name: hokm-web
restart: unless-stopped
depends_on:
server:
condition: service_healthy
ports:
- "${WEB_PORT:-1500}:80"
healthcheck:
test: ["CMD", "wget", "-q", "-O-", "http://127.0.0.1/"]
interval: 10s
timeout: 5s
retries: 6
start_period: 10s
volumes:
hokm_db_data:
+18
View File
@@ -0,0 +1,18 @@
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
# Static Next.js export: serve the file, its .html twin, or fall back to the
# SPA shell (the app uses client-side hash routing).
location / {
try_files $uri $uri.html $uri/ /index.html;
}
# Long-cache immutable build assets.
location /_next/static/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
+20
View File
@@ -10,6 +10,8 @@
"dependencies": {
"@capacitor/app": "^8.1.0",
"@capacitor/core": "^8.4.0",
"@fontsource-variable/plus-jakarta-sans": "^5.2.8",
"@fontsource-variable/vazirmatn": "^5.2.8",
"@microsoft/signalr": "^10.0.0",
"clsx": "^2.1.1",
"framer-motion": "^12.40.0",
@@ -537,6 +539,24 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@fontsource-variable/plus-jakarta-sans": {
"version": "5.2.8",
"resolved": "https://registry.npmjs.org/@fontsource-variable/plus-jakarta-sans/-/plus-jakarta-sans-5.2.8.tgz",
"integrity": "sha512-iQecBizIdZxezODNHzOn4SvvRMrZL/S8k4MEXGDynCmUrImVW0VmX+tIAMqnADwH4haXlHSXqMgU6+kcfBQJdw==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@fontsource-variable/vazirmatn": {
"version": "5.2.8",
"resolved": "https://registry.npmjs.org/@fontsource-variable/vazirmatn/-/vazirmatn-5.2.8.tgz",
"integrity": "sha512-2YzXfH4PNOeoZsBsgJgjbnm+IC2nonGyMLX3gXLm8FQrP+wpi1uGBIWYWlEzJ0lmntgdyAc3lRViHHfZWKvbog==",
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@humanfs/core": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz",
+2
View File
@@ -14,6 +14,8 @@
"dependencies": {
"@capacitor/app": "^8.1.0",
"@capacitor/core": "^8.4.0",
"@fontsource-variable/plus-jakarta-sans": "^5.2.8",
"@fontsource-variable/vazirmatn": "^5.2.8",
"@microsoft/signalr": "^10.0.0",
"clsx": "^2.1.1",
"framer-motion": "^12.40.0",
+7
View File
@@ -0,0 +1,7 @@
**/bin
**/obj
*.db
*.db-shm
*.db-wal
.git
*.user
+21
View File
@@ -0,0 +1,21 @@
# Hokm.Server (.NET 10 ASP.NET Core + SignalR)
# Build context = ./server (so Hokm.Engine + Hokm.Server are both in scope)
FROM mirror.soroushasadi.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY nuget.docker.config /tmp/nuget.config
COPY Directory.Build.props ./
COPY src/ ./src/
RUN dotnet restore src/Hokm.Server/Hokm.Server.csproj --configfile /tmp/nuget.config
RUN dotnet publish src/Hokm.Server/Hokm.Server.csproj -c Release -o /out --no-restore
FROM mirror.soroushasadi.com/dotnet/aspnet:10.0
WORKDIR /app
# aspnet image ships no wget/curl — borrow busybox so the healthcheck has wget.
COPY --from=mirror.soroushasadi.com/busybox:1.36 /bin/busybox /usr/bin/wget
COPY --from=build /out ./
# Bind all interfaces (appsettings binds localhost only, unreachable across the port map).
ENV ASPNETCORE_URLS=http://0.0.0.0:5005
EXPOSE 5005
HEALTHCHECK --interval=10s --timeout=5s --retries=12 --start-period=20s \
CMD wget -q -O- http://127.0.0.1:5005/ || exit 1
ENTRYPOINT ["dotnet", "Hokm.Server.dll"]
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- NuGet source for Docker image builds: Soroush Nexus group only, with retries
(the proxy can be slow on a cold cache). Used by server/Dockerfile. -->
<configuration>
<packageSources>
<clear />
<add key="nexus"
value="https://mirror.soroushasadi.com/repository/nuget-group/index.json"
protocolVersion="3" />
</packageSources>
<config>
<add key="http_retry_count" value="8" />
<add key="http_retry_delay_milliseconds" value="1000" />
</config>
</configuration>
+12 -3
View File
@@ -81,10 +81,19 @@ builder.Services
builder.Services.AddAuthorization();
// --- CORS for the Next.js client ---
// Origins are config-driven (Cors:Origins, comma/semicolon/space separated) so a
// deployed web origin can be allowed via env (Cors__Origins) without a code change.
// Falls back to the local dev origins when unset.
var corsRaw = builder.Configuration["Cors:Origins"];
var corsOrigins = string.IsNullOrWhiteSpace(corsRaw)
? new[]
{
"http://localhost:3000", "http://localhost:3002", "http://localhost:3020",
"http://127.0.0.1:3000", "http://127.0.0.1:3002", "http://127.0.0.1:3020",
}
: corsRaw.Split(new[] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
builder.Services.AddCors(o => o.AddDefaultPolicy(p => p
.WithOrigins(
"http://localhost:3000", "http://localhost:3002", "http://localhost:3020",
"http://127.0.0.1:3000", "http://127.0.0.1:3002", "http://127.0.0.1:3020")
.WithOrigins(corsOrigins)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()));
+6
View File
@@ -39,6 +39,12 @@
--font-sans: var(--font-vazir), var(--font-jakarta), system-ui, sans-serif;
}
:root {
/* Self-hosted @fontsource families (see app/layout.tsx imports). */
--font-vazir: "Vazirmatn Variable", system-ui, sans-serif;
--font-jakarta: "Plus Jakarta Sans Variable", system-ui, sans-serif;
}
html,
body {
height: 100%;
+4 -18
View File
@@ -1,20 +1,10 @@
import type { Metadata, Viewport } from "next";
import { Vazirmatn, Plus_Jakarta_Sans } from "next/font/google";
// Self-hosted fonts (no Google fetch at build time → CI builds work offline / in Iran).
import "@fontsource-variable/vazirmatn";
import "@fontsource-variable/plus-jakarta-sans";
import "./globals.css";
import { I18nProvider } from "@/lib/i18n";
const vazir = Vazirmatn({
variable: "--font-vazir",
subsets: ["arabic", "latin"],
display: "swap",
});
const jakarta = Plus_Jakarta_Sans({
variable: "--font-jakarta",
subsets: ["latin"],
display: "swap",
});
export const metadata: Metadata = {
title: "برگ وسط | Barg-e Vasat — بازی حکم آنلاین",
description: "برگ وسط — بازی حکم آنلاین ایرانی با حریف‌های واقعی و هوشمند (Barg-e Vasat — online Persian Hokm)",
@@ -34,11 +24,7 @@ export default function RootLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
return (
<html
lang="fa"
dir="rtl"
className={`${vazir.variable} ${jakarta.variable} h-full antialiased`}
>
<html lang="fa" dir="rtl" className="h-full antialiased">
<body className="min-h-full">
<I18nProvider>{children}</I18nProvider>
</body>