chore: initial project structure and root configuration

Adds root-level config files: solution (.slnx), NuGet, global.json,
Docker Compose files for all services (API, dashboard, website, finder,
admin), environment example, and developer documentation.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-05-27 21:33:10 +03:30
commit 45cd028d1c
15 changed files with 1672 additions and 0 deletions
+52
View File
@@ -0,0 +1,52 @@
# Copy to .env and adjust if ports conflict on your machine:
# copy .env.example .env
# Host ports (what you open in the browser)
WEB_PORT=3101 # Dashboard http://localhost:3101/fa/login
WEBSITE_PORT=3010 # Website http://localhost:3010/fa
ADMIN_WEB_PORT=3102 # Admin panel http://localhost:3102/fa/admin/login
API_PORT=5080 # Main API http://localhost:5080/swagger
ADMIN_API_PORT=5081 # Admin API http://localhost:5081/swagger
# Optional: expose DB/Redis on host (for local tools). Change if already in use.
POSTGRES_PORT=5434
REDIS_PORT=6381
# Browser must reach the API on the host (not Docker service names)
NEXT_PUBLIC_API_URL=http://localhost:5080
NEXT_PUBLIC_ADMIN_API_URL=http://localhost:5081
# Marketing website — public URL (used for sitemap, JSON-LD, canonical)
NEXT_PUBLIC_SITE_URL=http://localhost:3010
# API Docker base images (if build fails — see docs/DOCKER.md)
# DOTNET_SDK_IMAGE=mcr.microsoft.com/dotnet/sdk:10.0
# DOTNET_ASPNET_IMAGE=mcr.microsoft.com/dotnet/aspnet:10.0
# --- API (docker-compose / Arvan) ---
# ConnectionStrings__DefaultConnection=Host=postgres;Port=5432;Database=meezi;Username=meezi;Password=...
# ConnectionStrings__Redis=redis:6379
# Jwt__Key=<32+ char secret>
# App__PublicBaseUrl=http://localhost:5080
# App__QrPublicBaseUrl=http://localhost:3101
# Billing__DashboardBaseUrl=http://localhost:3101
# RUN_MIGRATIONS=true
# ZarinPal (empty = mock payment in dev)
# Get your merchant ID from: https://panel.zarinpal.com → API → MerchantID
ZARINPAL_MERCHANT_ID=
ZARINPAL_SANDBOX=true
# Snappfood webhook HMAC secret (dev default in appsettings)
# Snappfood__WebhookSecret=meezi-dev-snappfood-secret
# Taraz / سامانه مودیان (optional; stub without cert)
# Taraz__Username=
# Taraz__Password=
# Taraz__CertificatePath=
# Kavenegar SMS (empty = OTP logged to API console in dev)
# Kavenegar__ApiKey=
# CORS (comma-separated origins for production)
# Cors__Origins__0=https://app.meezi.ir
+40
View File
@@ -0,0 +1,40 @@
# Dependencies
node_modules/
web/dashboard/node_modules/
web/dashboard/.next/
.pnpm-store/
# Build outputs
.next/
out/
build/
dist/
bin/
obj/
# IDE
.vs/
*.user
*.suo
.idea/
.cursor/
# Claude Code (local settings and skill data — not project code)
.claude/settings.local.json
.claude/skills/
.config/
# Environment
.env*
!.env.example
# Flutter
mobile/meezi_app/.dart_tool/
mobile/meezi_app/build/
# OS
.DS_Store
Thumbs.db
# Logs
*.log
+83
View File
@@ -0,0 +1,83 @@
# Meezi — Production deployment (Arvan Cloud)
## Prerequisites
- Arvan Cloud account (Iran region)
- Domain `meezi.ir` DNS pointed to Arvan load balancer
- ZarinPal merchant ID (production, sandbox off)
- Kavenegar API key
- PostgreSQL 16 + Redis managed or VMs
## Services
| Service | Suggested host |
|---------|----------------|
| API | `api.meezi.ir` → ASP.NET container port 8080 |
| Dashboard | `app.meezi.ir` or `meezi.ir` → Next.js standalone |
| QR landing | `meezi.ir/q/*` → same Next.js app (`/q/[code]` route) |
| Postgres | Private network only |
| Redis | Private network only |
## Environment variables (API)
```
ConnectionStrings__DefaultConnection=Host=...;Database=meezi;...
ConnectionStrings__Redis=...
Jwt__Key=<32+ char secret>
App__PublicBaseUrl=https://api.meezi.ir
App__QrPublicBaseUrl=https://meezi.ir
Billing__DashboardBaseUrl=https://app.meezi.ir
ZarinPal__MerchantId=<merchant>
ZarinPal__Sandbox=false
Kavenegar__ApiKey=<key>
Snappfood__WebhookSecret=<secret>
Taraz__Username=<moadian-user>
Taraz__Password=<moadian-pass>
Taraz__CertificatePath=/secrets/taraz.pfx
RUN_MIGRATIONS=true
Cors__Origins__0=https://app.meezi.ir
Cors__Origins__1=https://meezi.ir
```
### ZarinPal / Taraz validation
1. **Sandbox first:** set `ZarinPal__Sandbox=true` and a sandbox merchant; complete subscribe flow; confirm redirect `?billing=success` and JWT refresh on settings.
2. **Production:** set `ZarinPal__Sandbox=false` and production `ZarinPal__MerchantId`; verify callback URL is reachable from ZarinPal.
3. **Taraz:** with real credentials, submit from Settings → تاراز; confirm tracking in API logs (stub logs until full SDK wired).
## Environment variables (Web)
```
NEXT_PUBLIC_API_URL=https://api.meezi.ir
```
## Routing
- Customer QR codes encode `https://meezi.ir/q/{qrCode}` (see `App:QrPublicBaseUrl`).
- Next.js route [`web/dashboard/src/app/q/[code]/page.tsx`](web/dashboard/src/app/q/[code]/page.tsx) resolves via public `GET /api/q/{code}`.
- Flutter app parses scanned URL and calls the same API.
## Arvan checklist
- [ ] Postgres + Redis on private network (no public ports)
- [ ] `api_uploads` persistent volume mounted at `/app/uploads`
- [ ] `RUN_MIGRATIONS=true` on API deploy only
- [ ] Hangfire `/hangfire` behind VPN or basic auth
- [ ] CORS origins: dashboard + marketing domain
- [ ] `App__QrPublicBaseUrl=https://meezi.ir`
- [ ] `Billing__DashboardBaseUrl=https://app.meezi.ir` (locale path added by API)
- [ ] TLS on load balancer for `api.*` and `app.*`
- [ ] Kavenegar + ZarinPal production keys in Arvan secrets (not in git)
## Deploy steps
1. Build and push Docker images (`docker compose` Dockerfiles in `docker/`).
2. Run EF migrations on API startup (`RUN_MIGRATIONS=true`) once per release.
3. Configure Hangfire dashboard behind auth in production.
4. Smoke test: OTP login, POS terminal register, create order, menu images visible, ZarinPal subscribe (sandbox first).
## CI suggestion
- `dotnet build` + `dotnet test` on PR
- `npm run build` in `web/dashboard`
- Deploy on tag to Arvan registry
+182
View File
@@ -0,0 +1,182 @@
# Meezi — Docker (full platform)
Run all services with one command.
## Services
| Service | Description | Default Port |
|---------|-------------|-------------|
| **postgres** | PostgreSQL 16 database | 5434 (host) |
| **redis** | Redis 7 (session, rate-limit, refresh tokens) | 6381 (host) |
| **api** | Main ASP.NET Core 10 API | 5080 |
| **admin-api** | Admin ASP.NET Core 10 API | 5081 |
| **web** | Customer-facing Next.js dashboard | 3101 |
| **website** | Marketing / landing website | 3010 |
| **admin-web** | Admin panel Next.js | 3102 |
---
## Quick start — full stack (all 7 services)
```powershell
cd F:\Projects\Meezi
copy .env.example .env
docker compose -f docker-compose.full.yml up -d --build
```
## Quick start — core stack only (dashboard + API)
```powershell
docker compose up -d --build
```
## Quick start — with admin panel
```powershell
docker compose -f docker-compose.yml -f docker-compose.admin.yml up -d --build
```
---
## Default URLs
| Service | URL |
|---------|-----|
| Dashboard (customer) | http://localhost:**3101**/fa/login |
| Marketing website | http://localhost:**3010**/fa |
| Admin panel | http://localhost:**3102**/fa/admin/login |
| Main API Swagger | http://localhost:**5080**/swagger |
| Admin API Swagger | http://localhost:**5081**/swagger |
| Health (main API) | http://localhost:**5080**/health |
| Health (admin API) | http://localhost:**5081**/health |
| Hangfire (main API) | http://localhost:**5080**/hangfire |
Demo login: `09121234567` — OTP appears in API logs (`docker compose logs -f api`).
---
## Ports (change in `.env`)
| Variable | Default | Purpose |
|----------|---------|---------|
| `WEB_PORT` | 3101 | Next.js dashboard |
| `WEBSITE_PORT` | 3010 | Marketing website |
| `ADMIN_WEB_PORT` | 3102 | Admin panel |
| `API_PORT` | 5080 | Main ASP.NET API |
| `ADMIN_API_PORT` | 5081 | Admin ASP.NET API |
| `POSTGRES_PORT` | 5434 | Postgres on host |
| `REDIS_PORT` | 6381 | Redis on host |
If a port is taken, edit `.env`:
```powershell
copy .env.example .env
# Edit .env — change WEB_PORT, WEBSITE_PORT, etc.
docker compose -f docker-compose.full.yml up -d --build
```
---
## Build args and API URLs
`NEXT_PUBLIC_API_URL` — what the **browser** uses to call the Main API. Must be a host URL (not `api:8080`).
`MEEZI_API_URL` — what the **website server** uses for internal SSR calls. Uses the Docker service name `http://api:8080`.
```powershell
# Rebuild only the website after changing NEXT_PUBLIC_SITE_URL:
docker compose -f docker-compose.full.yml up -d --build website
```
---
## Useful commands
```powershell
# Status
docker compose -f docker-compose.full.yml ps
# Logs
docker compose -f docker-compose.full.yml logs -f api
docker compose -f docker-compose.full.yml logs -f website
docker compose -f docker-compose.full.yml logs -f web
# Stop everything
docker compose -f docker-compose.full.yml down
# Stop and remove volumes (wipes DB!)
docker compose -f docker-compose.full.yml down -v
# Rebuild a single service
docker compose -f docker-compose.full.yml up -d --build website
docker compose -f docker-compose.full.yml up -d --build api
```
---
## Printer setup (inside Docker)
The Meezi API sends print jobs directly from the **browser dashboard** — the API itself does not talk to printers. This means:
- Your thermal printer only needs to be on the **same WiFi/LAN** as the browser running the dashboard
- **No Docker-specific configuration needed** for printers
- See the full guide at: http://localhost:3010/fa/printer-guide
---
## Dev without Docker
Still works:
```powershell
docker compose up -d postgres redis
# then in separate terminals:
dotnet run --project src/Meezi.API
dotnet run --project src/Meezi.Admin.API
cd web/website && npm run dev # port 3010
cd web/dashboard && npm run dev # port 3000 → maps to 3101
cd web/admin && npm run dev # port 3102
```
---
## Sprint 10 — billing & integrations (dev)
**ZarinPal (mock):** leave `ZarinPal:MerchantId` empty. In dashboard **تنظیمات** → upgrade Pro/Business → redirected through mock pay URL back to `/fa/settings?billing=success`.
**Snappfood webhook** (demo café vendor `demo_vendor`):
```powershell
$body = '{"orderId":"sf-001","vendorId":"demo_vendor","customerName":"Test","customerPhone":"09121111111","total":150000,"items":[{"name":"لاته","quantity":1,"unitPrice":150000}]}'
$hmac = [System.BitConverter]::ToString((New-Object System.Security.Cryptography.HMACSHA256([Text.Encoding]::UTF8.GetBytes("meezi-dev-snappfood-secret"))).ComputeHash([Text.Encoding]::UTF8.GetBytes($body))).Replace("-","").ToLower()
Invoke-RestMethod -Method Post -Uri "http://localhost:5080/api/webhooks/snappfood" -Body $body -ContentType "application/json" -Headers @{ "X-Snappfood-Signature" = $hmac }
```
**Taraz:** Settings → «ارسال به تاراز» (logs only until `Taraz:Username` is set).
**Hangfire:** renewal reminder job runs daily — dashboard at `http://localhost:5080/hangfire` (dev).
---
## Tables & QR
- Dashboard: `/fa/tables` — floor plan, add table, print QR (PNG)
- Dev QR URL in codes: `http://localhost:3101/q/{qrCode}` (see `App__QrPublicBaseUrl`)
- Scan `demo_table_01` in Flutter or open manual entry on QR screen
---
## Production env (Arvan)
See [DEPLOY.md](DEPLOY.md). Key additional website vars:
```env
NEXT_PUBLIC_SITE_URL=https://meezi.ir
MEEZI_API_URL=http://api:8080
```
---
## Menu images (Food-101)
- Manifest: `data/menu-image-manifest.json`
- API upserts images on dev seed via `EnsureMenuImagesAsync`
- Optional import: `dotnet run --project tools/MenuImageImporter -- --food101 <path-to-food-101/images>`
+10
View File
@@ -0,0 +1,10 @@
<Project>
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
</Project>
+33
View File
@@ -0,0 +1,33 @@
<Project>
<PropertyGroup>
<MicrosoftPackageVersion>10.0.0</MicrosoftPackageVersion>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="CsvHelper" Version="33.0.1" />
<PackageVersion Include="EPPlus" Version="7.5.2" />
<PackageVersion Include="FluentValidation" Version="11.11.0" />
<PackageVersion Include="FluentValidation.DependencyInjectionExtensions" Version="11.11.0" />
<PackageVersion Include="Hangfire.AspNetCore" Version="1.8.17" />
<PackageVersion Include="Hangfire.MemoryStorage" Version="1.8.1.1" />
<PackageVersion Include="Hangfire.PostgreSql" Version="1.20.10" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="$(MicrosoftPackageVersion)" />
<PackageVersion Include="Microsoft.AspNetCore.Mvc.Testing" Version="$(MicrosoftPackageVersion)" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="$(MicrosoftPackageVersion)" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.InMemory" Version="$(MicrosoftPackageVersion)" />
<PackageVersion Include="Microsoft.EntityFrameworkCore.Tools" Version="$(MicrosoftPackageVersion)" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="$(MicrosoftPackageVersion)" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="$(MicrosoftPackageVersion)" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="$(MicrosoftPackageVersion)" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0" />
<PackageVersion Include="QRCoder" Version="1.6.0" />
<PackageVersion Include="QuestPDF" Version="2024.12.3" />
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageVersion Include="StackExchange.Redis" Version="2.8.16" />
<PackageVersion Include="System.Security.Cryptography.Xml" Version="10.0.6" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="7.2.0" />
<PackageVersion Include="xunit" Version="2.9.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
</ItemGroup>
</Project>
+762
View File
@@ -0,0 +1,762 @@
# راهنمای کامل توسعه Meezi با Cursor
> از صفر تا اولین سفارش واقعی — گام به گام
---
## پیش‌نیازها — قبل از هر چیز نصب کن
```bash
# 1. Node.js 20+
node --version # باید v20 یا بالاتر باشد
# دانلود: https://nodejs.org
# 2. .NET 10 SDK
dotnet --version # باید 8.x باشد
# دانلود: https://dotnet.microsoft.com/download/dotnet/8.0
# 3. Flutter 3.x
flutter --version
# دانلود: https://docs.flutter.dev/get-started/install
# 4. Docker Desktop (برای PostgreSQL و Redis محلی)
docker --version
# دانلود: https://www.docker.com/products/docker-desktop
# 5. pnpm
npm install -g pnpm
# 6. Cursor
# دانلود: https://cursor.com
```
---
## مرحله ۱ — ساختار پروژه را بساز
```bash
# یک پوشه اصلی بساز
mkdir meezi && cd meezi
# Git راه‌اندازی کن
git init
echo "node_modules/\n.next/\nbuild/\n*.user\n.env*\n!.env.example" > .gitignore
```
---
## مرحله ۲ — فایل‌های کلیدی را بریز
### `.cursorrules` — در ریشه پروژه
```
You are building Meezi (میزی) — a Persian-first SaaS POS and community
platform for Iranian cafés in Tehran and Karaj.
CONTEXT: Read MEEZI_PRD.md for full product details before any task.
STACK:
- Backend: ASP.NET Core 10 (C#) in src/Meezi.API
- Web: Next.js 14 TypeScript in web/dashboard
- Mobile: Flutter 3 Dart in mobile/meezi_app
- DB: PostgreSQL + Redis
- ORM: Entity Framework Core 10
PRODUCT:
- Brand: Meezi (میزی) | میزت منتظرته
- Markets: Tehran + Karaj (V1)
- Languages: Farsi (default) + Arabic + English
- Competitor: Sepidz (legacy license, no SaaS)
- Pricing: Free / Pro 1.49M / Business 3.49M / Enterprise
C# RULES:
- Async/await everywhere — never .Result or .Wait()
- EVERY EF query filters by CafeId (multi-tenant)
- Return ApiResponse<T> always: { bool Success, T? Data, ApiError? Error }
- Use record types for DTOs
- FluentValidation for all inputs
- Hangfire for background jobs
- SignalR for real-time KDS
- Never Console.WriteLine — use ILogger<T>
NEXT.JS RULES:
- next-intl for i18n — ALL strings in messages/fa.json, ar.json, en.json
- RTL for fa/ar — LTR for en
- ms-* me-* ps-* pe-* for spacing (never ml mr pl pr)
- TanStack Query v5 for server state
- Zustand for cart and UI state
- date-fns-jalali for ALL dates — never show Gregorian
- Numbers: .toLocaleString('fa-IR')
- Currency: n.toLocaleString('fa-IR') + ' ت'
- shadcn/ui components always
FLUTTER RULES:
- Riverpod 2 for all state
- GoRouter for navigation
- Drift SQLite for offline
- shamsi_date for all dates
- 3 locales: fa (RTL), ar (RTL), en (LTR)
- Feature-first: lib/features/{feature}/
SECURITY:
- Validate CafeId ownership on every protected endpoint
- Rate limit OTP: 5/hour per phone via Redis
- Never log phone, national ID, or payment tokens
- Soft delete only — never hard delete
API FORMAT:
Success: { "success": true, "data": {...} }
Error: { "success": false, "error": { "code": "...", "message": "..." } }
List: { "success": true, "data": [...], "meta": { "total": 0, "page": 1 } }
```
### `docker-compose.yml` — در ریشه پروژه
```yaml
version: '3.8'
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: meezi
POSTGRES_USER: meezi
POSTGRES_PASSWORD: meezi_local_pass
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
postgres_data:
```
---
## مرحله ۳ — Backend راه‌اندازی کن
```bash
# پوشه backend
mkdir src && cd src
# پروژه‌های .NET بساز
dotnet new webapi -n Meezi.API --use-controllers
dotnet new classlib -n Meezi.Core
dotnet new classlib -n Meezi.Infrastructure
dotnet new classlib -n Meezi.Shared
# Solution بساز
cd ..
dotnet new sln -n Meezi
dotnet sln add src/Meezi.API
dotnet sln add src/Meezi.Core
dotnet sln add src/Meezi.Infrastructure
dotnet sln add src/Meezi.Shared
# Reference ها
cd src/Meezi.API
dotnet add reference ../Meezi.Core
dotnet add reference ../Meezi.Infrastructure
dotnet add reference ../Meezi.Shared
cd ../Meezi.Infrastructure
dotnet add reference ../Meezi.Core
dotnet add reference ../Meezi.Shared
```
### حالا Cursor را باز کن و این prompt را بده:
```
I'm building Meezi.API — ASP.NET Core 10 Web API for a SaaS POS system.
Read .cursorrules for full context.
Add these NuGet packages to Meezi.API:
- Npgsql.EntityFrameworkCore.PostgreSQL
- Microsoft.EntityFrameworkCore.Design
- FluentValidation + FluentValidation.DependencyInjectionExtensions
- AutoMapper.Extensions.Microsoft.DependencyInjection
- Hangfire.AspNetCore + Hangfire.PostgreSql
- Serilog.AspNetCore + Serilog.Sinks.Console
- StackExchange.Redis
- Microsoft.AspNetCore.Authentication.JwtBearer
- Microsoft.AspNetCore.SignalR
- QuestPDF (for PDF generation)
- EPPlus (for Excel export)
- CsvHelper
Add these to Meezi.Infrastructure:
- Npgsql.EntityFrameworkCore.PostgreSQL
- Microsoft.EntityFrameworkCore.Tools
Then create this folder structure inside Meezi.API:
Controllers/ Services/ Jobs/ Middleware/ Hubs/ Extensions/
And this in Meezi.Core:
Entities/ Enums/ Interfaces/ Constants/
And this in Meezi.Infrastructure:
Data/ Data/Migrations/ Repositories/ ExternalServices/
Create the base ApiResponse<T> record in Meezi.Shared/ApiResponse.cs
Create Program.cs with full middleware pipeline setup.
```
---
## مرحله ۴ — Database Schema
در Cursor بده:
```
Create all EF Core entity classes in Meezi.Core/Entities/ based on this schema:
Cafe: Id(string/cuid), Name, NameAr, NameEn, Slug(unique), Phone,
Address, City, LogoUrl, PlanTier(enum), PlanExpiresAt, IsVerified,
PreferredLanguage, CreatedAt
Branch: Id, CafeId(FK), Name, Address, City
Table: Id, CafeId, BranchId(nullable), Number, Capacity(default 4),
Floor, QrCode(unique), IsActive
Employee: Id, CafeId, Name, Phone, NationalId, Role(enum EmployeeRole),
BaseSalary, PinCode, CreatedAt
MenuCategory: Id, CafeId, Name, NameAr, NameEn, SortOrder, TaxId,
DiscountPercent, IsActive
MenuItem: Id, CafeId, CategoryId(FK), Name, NameAr, NameEn,
Description, Price, ImageUrl, IsAvailable
Order: Id, CafeId, BranchId, TableId, CustomerId, EmployeeId,
OrderType(enum), Status(enum), CouponId, DiscountAmount,
Subtotal, TaxTotal, Total, SnappfoodOrderId, CreatedAt
OrderItem: Id, OrderId(FK), MenuItemId(FK), Quantity, UnitPrice, Notes
Payment: Id, OrderId(FK), Method(enum PaymentMethod), Amount,
Status(enum), Reference
Customer: Id, CafeId, Name, Phone, NationalId, BirthDateJalali,
Group(enum CustomerGroup), LoyaltyPoints, ReferredBy, CreatedAt
Coupon: Id, CafeId, Code, Type(enum CouponType), Value, MinOrderAmount,
MaxDiscount, UsageLimit, UsedCount, TargetGroup,
StartsAt, ExpiresAt, IsActive
Tax: Id, CafeId, Name, Rate, IsDefault, IsRequired, IsCompound
EmployeeSalary: Id, EmployeeId(FK), MonthYear(string YYYY-MM),
BaseSalary, OvertimePay, Deductions, NetSalary, IsPaid
Attendance: Id, EmployeeId(FK), Date, ClockIn, ClockOut, Notes
Shift: Id, EmployeeId(FK), DayOfWeek(int), ShiftType(enum: Morning/Evening/Off)
LeaveRequest: Id, EmployeeId(FK), StartDate, EndDate, Reason,
Status(enum: Pending/Approved/Rejected), ReviewedBy
Enums to create in Meezi.Core/Enums/:
PlanTier: Free, Pro, Business, Enterprise
OrderType: DineIn, Takeaway, Delivery
OrderStatus: Pending, Confirmed, Preparing, Ready, Delivered, Cancelled
PaymentMethod: Cash, Card, Credit
EmployeeRole: Owner, Manager, Cashier, Waiter, Chef, Delivery
CustomerGroup: Regular, VIP, New, Employee
CouponType: Percentage, FixedAmount, FreeItem
ShiftType: Morning, Evening, DayOff
Then create AppDbContext in Meezi.Infrastructure/Data/AppDbContext.cs
with all DbSets and proper relationships.
Create the initial EF migration called "InitialCreate".
```
---
## مرحله ۵ — Auth System
```
Build the complete auth system for Meezi:
1. In Meezi.Infrastructure/ExternalServices/KavenegarSmsService.cs:
- HTTP client calling Kavenegar REST API
- Method: SendOtpAsync(string phone, string otp)
- Read API key from IConfiguration
2. In Meezi.API/Services/AuthService.cs:
- SendOtpAsync(string phone):
* Generate 6-digit OTP
* Store in Redis with key "otp:{phone}" TTL 5 minutes
* Store attempt counter "otp:attempts:{phone}" TTL 1 hour
* Block if attempts > 5 (return error RATE_LIMITED)
* Call KavenegarSmsService
- VerifyOtpAsync(string phone, string code, string cafeId):
* Check Redis for code
* If valid: generate JWT with claims { userId, cafeId, role, planTier, lang }
* Delete OTP from Redis
* Return token + refresh token
3. In Meezi.API/Controllers/AuthController.cs:
POST /api/auth/send-otp → body: { phone }
POST /api/auth/verify-otp → body: { phone, code }
POST /api/auth/refresh → body: { refreshToken }
4. In Meezi.API/Middleware/TenantMiddleware.cs:
- Extract cafeId from JWT claims
- Inject ITenantContext into request scope
- Return 401 if cafeId missing on protected routes
5. In Meezi.API/Middleware/PlanLimitMiddleware.cs:
- Read planTier from JWT
- Check against PlanLimits constants
- Return PLAN_LIMIT_REACHED error if exceeded
JWT claims: { sub: userId, cafeId, role, planTier, lang, iat, exp }
Token expiry: 7 days access, 30 days refresh
```
---
## مرحله ۶ — Core POS APIs
```
Build these API endpoints in Meezi.API/Controllers/:
MenuController.cs:
GET /api/cafes/{cafeId}/menu/categories
POST /api/cafes/{cafeId}/menu/categories
PATCH /api/cafes/{cafeId}/menu/categories/{id}
DELETE /api/cafes/{cafeId}/menu/categories/{id}
GET /api/cafes/{cafeId}/menu/items
POST /api/cafes/{cafeId}/menu/items
PATCH /api/cafes/{cafeId}/menu/items/{id}
PATCH /api/cafes/{cafeId}/menu/items/{id}/availability
TablesController.cs:
GET /api/cafes/{cafeId}/tables
POST /api/cafes/{cafeId}/tables → auto-generate QR code URL
GET /api/q/{qrCode} → public, returns cafeSlug + tableId
OrdersController.cs:
GET /api/cafes/{cafeId}/orders → with status filter
POST /api/cafes/{cafeId}/orders → create + check plan limits
PATCH /api/cafes/{cafeId}/orders/{id}/status
POST /api/cafes/{cafeId}/orders/{id}/payments → record payment(s)
GET /api/cafes/{cafeId}/orders/live → SSE or SignalR for KDS
Every controller must:
- Use [Authorize] attribute
- Validate CafeId matches JWT claim
- Use FluentValidation for request body
- Return ApiResponse<T> format
- Use async/await throughout
```
---
## مرحله ۷ — Next.js Dashboard
```bash
# از ریشه پروژه
mkdir web && cd web
npx create-next-app@latest dashboard --typescript --tailwind --app --src-dir
cd dashboard
# پکیج‌های اضافی
pnpm add @tanstack/react-query zustand next-intl date-fns-jalali
pnpm add @hookform/resolvers react-hook-form zod
pnpm add recharts lucide-react
pnpm add -D @types/node
```
### در Cursor:
```
Set up the Next.js 14 dashboard for Meezi with these steps:
1. Configure next-intl for 3 locales: fa (default, RTL), ar (RTL), en (LTR)
- Create messages/fa.json, messages/ar.json, messages/en.json
- Wrap app in [locale] dynamic route
- Set dir="rtl" for fa/ar, dir="ltr" for en
- Load Vazirmatn font for fa/ar, Inter for en
2. Install and configure shadcn/ui:
npx shadcn-ui@latest init
Add components: button, input, card, dialog, table, select,
badge, skeleton, toast, sheet, dropdown-menu
3. Create the app layout at app/[locale]/(dashboard)/layout.tsx:
- Sidebar with icons for: POS, CRM, Coupons, Inventory, HR,
Reports, Reservations, SMS, Taxes, Settings
- Top bar with: cafe name, plan badge, language switcher, user menu
- RTL-aware: sidebar on RIGHT for fa/ar, LEFT for en
4. Create Zustand store at lib/stores/cart.store.ts:
State: { items: CartItem[], couponCode, appliedCoupon, tableId }
Actions: addItem, removeItem, updateQty, applyCoupon, clearCart
5. Create API client at lib/api/client.ts:
- Axios instance with baseURL from env
- JWT interceptor (attach token from localStorage)
- 401 interceptor (redirect to login)
- Response unwrapper (extract .data from ApiResponse<T>)
6. Create the main POS page at app/[locale]/(dashboard)/pos/page.tsx:
- Left panel (60%): category tabs + item grid (3 cols)
- Right panel (40%): order items + coupon + tax + split payment
- All text from fa.json translation keys
- Farsi numbers everywhere
- Toman currency format
```
---
## مرحله ۸ — Flutter App
```bash
# از ریشه پروژه
mkdir mobile && cd mobile
flutter create meezi_app --org ir.meezi --platforms android,ios,windows
cd meezi_app
```
### `pubspec.yaml` — جایگزین کن:
```yaml
name: meezi_app
description: Meezi - میزی - سیستم کافه و رستوران
environment:
sdk: '>=3.3.0 <4.0.0'
flutter: ">=3.19.0"
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
# State
flutter_riverpod: ^2.5.1
riverpod_annotation: ^2.3.5
# Navigation
go_router: ^14.2.0
# HTTP
dio: ^5.4.3
retrofit: ^4.3.0
# Local DB (offline)
drift: ^2.18.0
sqlite3_flutter_libs: ^0.5.24
path_provider: ^2.1.3
path: ^1.9.0
# i18n
intl: ^0.19.0
# Shamsi date
shamsi_date: ^1.1.1
# QR
qr_flutter: ^4.1.0
mobile_scanner: ^5.2.3
# Printer
esc_pos_utils_plus: ^2.0.1
bluetooth_print: ^4.4.0
# Notifications
firebase_messaging: ^15.1.0
flutter_local_notifications: ^17.2.3
# Storage
flutter_secure_storage: ^9.2.2
shared_preferences: ^2.3.2
# UI
cached_network_image: ^3.3.1
image_picker: ^1.1.2
shimmer: ^3.0.0
flutter_svg: ^2.0.10+1
# Utils
freezed_annotation: ^2.4.1
json_annotation: ^4.9.0
equatable: ^2.0.5
uuid: ^4.4.2
dev_dependencies:
flutter_test:
sdk: flutter
build_runner: ^2.4.11
freezed: ^2.5.2
json_serializable: ^6.8.0
retrofit_generator: ^8.1.0
riverpod_generator: ^2.4.0
drift_dev: ^2.18.0
```
### در Cursor:
```
Set up the Flutter app structure for Meezi following feature-first architecture.
Create this folder structure in lib/:
app/
router.dart → GoRouter with all routes
providers.dart → Riverpod ProviderScope setup
features/
auth/ → OTP login screen
pos/ → Owner POS tablet view
menu/ → Customer menu browse
cart/ → Cart + checkout
track/ → Order tracking
reserve/ → Table reservation
discover/ → کافه‌یاب discovery
hr/ → Employee clock-in + leave request
profile/ → Customer profile + history
core/
api/
api_client.dart → Dio + Retrofit setup
interceptors.dart → JWT + error interceptors
db/
app_database.dart → Drift database
daos/ → Data access objects
sync/
sync_engine.dart → Offline queue + sync logic
i18n/
app_localizations.dart
utils/
jalali_utils.dart → shamsi_date helpers
currency_utils.dart → Toman formatting
shared/
widgets/
theme/
app_theme.dart → RTL-aware theme
Routes to create in router.dart:
/ → redirect to /discover or /pos based on user role
/login → OTP auth screen
/discover → Café discovery (public)
/cafe/:slug → Café public profile
/cafe/:slug/menu → Menu (reads tableId from QR)
/cafe/:slug/cart → Checkout
/order/:id/track → Delivery tracking
/reserve/:slug → Table reservation
/pos → Owner POS (requires OWNER/MANAGER/CASHIER role)
/profile → Customer profile
Configure localization for fa (RTL), ar (RTL), en (LTR).
Create MaterialApp.router with locale switching support.
```
---
## مرحله ۹ — Docker و اجرای محلی
```bash
# از ریشه پروژه
docker-compose up -d
# PostgreSQL روی port 5432 بالا می‌آید
# Redis روی port 6379
# Migration اجرا کن
cd src/Meezi.API
dotnet ef database update
# Backend اجرا کن
dotnet run
# روی https://localhost:7001 بالا می‌آید
# Dashboard اجرا کن (terminal جدید)
cd web/dashboard
pnpm dev
# روی http://localhost:3000 بالا می‌آید
# Flutter اجرا کن (terminal جدید)
cd mobile/meezi_app
flutter run -d chrome # برای تست وب
flutter run # برای اندروید/شبیه‌ساز
```
---
## مرحله ۱۰ — Prompt های آماده برای هر ماژول
### CRM + جستجو با کد ملی:
```
Build the CRM module:
- GET /api/cafes/{cafeId}/customers?q={search}
Search by name, phone, or nationalId (10-digit)
- POST /api/cafes/{cafeId}/customers
Fields: name, phone, nationalId, birthDateJalali (YYYY/MM/DD), group
- Check FREE plan limit: max 50 customers → return PLAN_LIMIT_REACHED
- Next.js page: /crm with search input, customer cards, add modal
- All in Farsi with RTL layout
```
### HR + حضور و غیاب Flutter:
```
Build the HR attendance feature in Flutter:
- Screen: features/hr/attendance_screen.dart
- Show employee's today shift (Morning 8-16 / Evening 16-00 / DayOff)
- Clock In button: POST /api/employees/{id}/attendance/clock-in
- Clock Out button: POST /api/employees/{id}/attendance/clock-out
- Leave request form: reason + date range in Shamsi calendar
- Works OFFLINE: queue attendance locally, sync when online
- Shamsi date display throughout
- Farsi UI with RTL layout
```
### KDS آشپزخانه realtime:
```
Build the Kitchen Display System:
- SignalR Hub in Meezi.API/Hubs/KdsHub.cs
- Group orders by cafeId
- Broadcast new orders to kitchen screen
- Broadcast status changes
- Next.js page: /kds
- Connect to SignalR hub
- Show live order cards (Pending → Preparing → Ready)
- Color coded: yellow=Pending, blue=Preparing, green=Ready
- Each card: table number, items list, time elapsed
- Click to advance status
- Auto-refresh every 30s as fallback
- All Farsi, RTL layout
```
### گزارش مالی + Excel خروجی:
```
Build the reports API:
GET /api/cafes/{cafeId}/reports/daily?date=1403-10-16
Returns: totalOrders, newCustomers, returningCustomers,
revenue, taxTotal, discountTotal, topItems(5)
GET /api/cafes/{cafeId}/reports/monthly?month=1403-10
Returns: daily breakdown, totalRevenue, totalCosts breakdown
(salary from EmployeeSalary, plus manual cost entries)
netProfit = revenue - costs
GET /api/cafes/{cafeId}/reports/export?month=1403-10&format=excel
Returns: Excel file using EPPlus
Sheets: Sales, Items, Customers, Employees
Next.js page /reports:
- Stats cards: today customers (new vs returning)
- Bar chart (Recharts): 7-day revenue vs cost
- Top items list
- Monthly P&L table
- Export button
All Farsi, RTL, Toman currency, Shamsi dates
```
### پرداخت ZarinPal:
```
Build ZarinPal subscription payment in C#:
1. PaymentService.cs:
- InitiateSubscriptionAsync(cafeId, planTier, months):
* Call ZarinPal /pg/v4/payment/request.json
* Store pending payment record
* Return payment URL
- VerifyPaymentAsync(authority, status):
* Call ZarinPal /pg/v4/payment/verify.json
* On success: update cafe PlanTier + PlanExpiresAt
* Send confirmation SMS via Kavenegar
2. BillingController.cs:
POST /api/billing/subscribe → body: { planTier, months }
→ returns { paymentUrl }
GET /api/billing/verify?Authority=...&Status=OK
→ ZarinPal callback, redirect to dashboard
GET /api/billing/status
→ current plan, expiry, usage stats
3. Hangfire job: SubscriptionRenewalReminderJob
- Run daily
- Find cafes expiring in 3 days
- Send SMS reminder via Kavenegar
```
### Snappfood Integration:
```
Build Snappfood webhook receiver:
POST /api/webhooks/snappfood (no auth, verify with HMAC secret)
- Parse Snappfood order payload
- Map to Meezi Order:
* OrderType = Delivery
* Status = Confirmed (already paid via Snappfood)
* SnappfoodOrderId = external ID
* Items mapped from Snappfood items to MenuItems by name match
- Save order to DB
- Broadcast to KDS via SignalR
- Return 200 OK within 5 seconds
Also:
PATCH /api/cafes/{cafeId}/orders/{id}/status
- When status → Delivered: notify Snappfood API if SnappfoodOrderId exists
```
---
## مرحله ۱۱ — اشتباهات رایج که باید از آن‌ها بپرهیزی
```
❌ نکن:
- Console.WriteLine در C# → از ILogger<T> استفاده کن
- ml-4 یا mr-4 در Next.js → از ms-4 و me-4 استفاده کن
- new Date() برای نمایش تاریخ → از date-fns-jalali استفاده کن
- متن فارسی hardcode در کامپوننت → همه در fa.json باشد
- query بدون .Where(x => x.CafeId == cafeId) → داده tenant دیگر برمی‌گرداند
- setState در Flutter برای business logic → از Riverpod استفاده کن
- .Result یا .Wait() در C# → deadlock می‌دهد
✅ بکن:
- هر mutation در Flutter اول به Drift ذخیره کن، بعد sync کن
- هر endpoint با [Authorize] + validate CafeId شروع کن
- هر string قابل نمایش را در fa.json/ar.json/en.json بریز
- تمام اعداد نمایشی را با .toLocaleString('fa-IR') فرمت کن
- هر job background را در Hangfire بریز، نه await inline
```
---
## مرحله ۱۲ — ترتیب توسعه پیشنهادی (Solo با Cursor)
```
هفته ۱-۲: Backend foundation (auth + schema + tenant middleware)
هفته ۳-۴: POS core (menu + orders + tables + QR + payments)
هفته ۵-۶: Dashboard Next.js (POS screen + KDS + basic reports)
هفته ۷-۸: CRM + Coupons + SMS marketing (Kavenegar)
هفته ۹-۱۰: HR module (shifts + attendance + payroll)
هفته ۱۱-۱۲: Flutter customer app (QR + menu + cart + reservation)
هفته ۱۳-۱۴: Discovery platform (کافه‌یاب) + Reviews
هفته ۱۵-۱۶: ZarinPal billing + Taraz + Snappfood + production deploy
اولین beta: 5 کافه در شمال تهران — هفته ۸
اولین پول: هفته ۱۶
```
---
## چک‌لیست شروع روز اول
```
[ ] Node 20+ نصب است
[ ] .NET 10 SDK نصب است
[ ] Flutter 3.x نصب است
[ ] Docker Desktop در حال اجرا است
[ ] Cursor نصب است و باز است
[ ] پوشه meezi/ ساخته شده
[ ] .cursorrules در ریشه قرار دارد
[ ] MEEZI_PRD.md در ریشه قرار دارد
[ ] docker-compose up -d اجرا شده
[ ] در Cursor: Cmd+L → "Read .cursorrules and MEEZI_PRD.md,
then start Sprint 1: create the .NET solution structure"
```
+8
View File
@@ -0,0 +1,8 @@
<Solution>
<Folder Name="/src/">
<Project Path="src/Meezi.API/Meezi.API.csproj" />
<Project Path="src/Meezi.Core/Meezi.Core.csproj" />
<Project Path="src/Meezi.Infrastructure/Meezi.Infrastructure.csproj" />
<Project Path="src/Meezi.Shared/Meezi.Shared.csproj" />
</Folder>
</Solution>
+77
View File
@@ -0,0 +1,77 @@
# Meezi — Day 1 Quick Start
## Step 1 — Copy these 4 files to your project root
.cursorrules ← AI rules for Cursor
MEEZI_CURSOR_GUIDE.md ← Full development guide
docker-compose.yml ← Local DB + Redis
README.md ← This file
## Step 2 — Start local services
docker-compose up -d
## Step 3 — Create project structure
mkdir meezi && cd meezi
git init
mkdir src web mobile
cd src
dotnet new sln -n Meezi
dotnet new webapi -n Meezi.API --use-controllers
dotnet new classlib -n Meezi.Core
dotnet new classlib -n Meezi.Infrastructure
dotnet new classlib -n Meezi.Shared
dotnet sln add Meezi.API Meezi.Core Meezi.Infrastructure Meezi.Shared
cd ../web
npx create-next-app@latest dashboard --typescript --tailwind --app
cd ../mobile
flutter create meezi_app --org ir.meezi
## Step 4 — Open in Cursor
cursor . (from the meezi/ root folder)
## Step 5 — Paste this FIRST prompt in Cursor chat (Cmd+L)
Read .cursorrules and MEEZI_CURSOR_GUIDE.md completely.
Then do Sprint 1 Week 1:
1. Set up the .NET solution with proper references between projects
2. Add all NuGet packages listed in the guide to each project
3. Create the complete EF Core entity schema from the guide
4. Set up AppDbContext with all DbSets
5. Create TenantMiddleware and ITenantContext
6. Set up Program.cs with full middleware pipeline
---
## Cursor Chat Tips
### Ask one sprint at a time
"Do Sprint 2: build the POS order APIs (menu, tables, orders)"
### Reference specific sections
"Build the CRM endpoint from Step 10 of MEEZI_CURSOR_GUIDE.md"
### Debug with context
"The order API returns 403. Check if TenantMiddleware is injecting CafeId correctly"
### Generate Flutter screens
"Build the Flutter POS screen from Sprint 2 in the guide. RTL, Farsi, Riverpod state"
---
## Useful Cursor Keyboard Shortcuts
Cmd+L → Open AI chat
Cmd+K → Inline AI edit (select code first)
Cmd+I → Composer (multi-file edits)
Cmd+. → Quick fix / suggestion
Tab → Accept autocomplete
---
## Local URLs when running
Backend API: https://localhost:7001
API Swagger: https://localhost:7001/swagger
Dashboard: http://localhost:3000
Hangfire UI: https://localhost:7001/hangfire
Flutter web: http://localhost:8080
+59
View File
@@ -0,0 +1,59 @@
# Meezi platform admin — use WITH main stack (shared Postgres + Redis)
#
# docker compose up -d postgres redis
# docker compose -f docker-compose.yml -f docker-compose.admin.yml up -d --build
#
# URLs:
# Admin web http://localhost:3102/fa/admin/login
# Admin API http://localhost:5081/swagger
# Health http://localhost:5081/health
services:
admin-api:
build:
context: .
dockerfile: docker/admin-api/Dockerfile
args:
DOTNET_SDK_IMAGE: ${DOTNET_SDK_IMAGE:-mcr.microsoft.com/dotnet/sdk:10.0}
DOTNET_ASPNET_IMAGE: ${DOTNET_ASPNET_IMAGE:-mcr.microsoft.com/dotnet/aspnet:10.0}
container_name: meezi-admin-api
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment:
ASPNETCORE_ENVIRONMENT: Development
ASPNETCORE_URLS: http://+:8080
RUN_MIGRATIONS: "false"
ConnectionStrings__DefaultConnection: Host=postgres;Port=5432;Database=meezi;Username=meezi;Password=meezi_local_pass
ConnectionStrings__Redis: redis:6379
Cors__Origins__0: http://localhost:${ADMIN_WEB_PORT:-3102}
Cors__Origins__1: http://localhost:3101
Kavenegar__ApiKey: ""
ports:
- "${ADMIN_API_PORT:-5081}:8080"
healthcheck:
test: ["CMD-SHELL", "bash -c 'cat </dev/null >/dev/tcp/127.0.0.1/8080' || exit 1"]
interval: 10s
timeout: 5s
retries: 12
start_period: 40s
admin-web:
build:
context: .
dockerfile: docker/admin-web/Dockerfile
args:
NEXT_PUBLIC_ADMIN_API_URL: ${NEXT_PUBLIC_ADMIN_API_URL:-http://localhost:5081}
container_name: meezi-admin-web
restart: unless-stopped
depends_on:
admin-api:
condition: service_healthy
environment:
PORT: "3000"
HOSTNAME: 0.0.0.0
ports:
- "${ADMIN_WEB_PORT:-3102}:3000"
+188
View File
@@ -0,0 +1,188 @@
# Meezi — FULL platform (all 7 services in one file)
#
# Includes: Postgres, Redis, Main API, Admin API,
# Dashboard (web), Admin Panel (admin-web), Marketing Website
#
# Setup:
# copy .env.example .env
# docker compose -f docker-compose.full.yml up -d --build
#
# URLs (defaults):
# Dashboard http://localhost:3101/fa/login
# Website http://localhost:3010/fa
# Admin panel http://localhost:3102/fa/admin/login
# Main API http://localhost:5080/swagger
# Admin API http://localhost:5081/swagger
services:
# ── Infrastructure ──────────────────────────────────────────────────────────
postgres:
image: postgres:16-alpine
container_name: meezi-db
restart: unless-stopped
environment:
POSTGRES_DB: meezi
POSTGRES_USER: meezi
POSTGRES_PASSWORD: meezi_local_pass
ports:
- "${POSTGRES_PORT:-5434}:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U meezi -d meezi"]
interval: 5s
timeout: 5s
retries: 10
redis:
image: redis:7-alpine
container_name: meezi-redis
restart: unless-stopped
ports:
- "${REDIS_PORT:-6381}:6379"
command: redis-server --appendonly yes
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 10
# ── Backend APIs ────────────────────────────────────────────────────────────
api:
build:
context: .
dockerfile: docker/api/Dockerfile
args:
DOTNET_SDK_IMAGE: ${DOTNET_SDK_IMAGE:-mcr.microsoft.com/dotnet/sdk:10.0}
DOTNET_ASPNET_IMAGE: ${DOTNET_ASPNET_IMAGE:-mcr.microsoft.com/dotnet/aspnet:10.0}
container_name: meezi-api
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment:
ASPNETCORE_ENVIRONMENT: Development
ASPNETCORE_URLS: http://+:8080
RUN_MIGRATIONS: "true"
ConnectionStrings__DefaultConnection: Host=postgres;Port=5432;Database=meezi;Username=meezi;Password=meezi_local_pass
ConnectionStrings__Redis: redis:6379
App__PublicBaseUrl: ${NEXT_PUBLIC_API_URL:-http://localhost:5080}
App__QrPublicBaseUrl: http://localhost:${WEB_PORT:-3101}
Cors__Origins__0: http://localhost:${WEB_PORT:-3101}
Cors__Origins__1: http://localhost:${WEBSITE_PORT:-3010}
Cors__Origins__2: http://localhost:${ADMIN_WEB_PORT:-3102}
Auth__MaxOtpAttemptsPerHour: "100"
Kavenegar__ApiKey: ""
Billing__DashboardBaseUrl: http://localhost:${WEB_PORT:-3101}
Snappfood__WebhookSecret: meezi-dev-snappfood-secret
ZarinPal__MerchantId: "104c093d-2f5b-470d-978b-e4edefbf6cc8"
ZarinPal__Sandbox: "true"
ports:
- "${API_PORT:-5080}:8080"
volumes:
- api_uploads:/app/uploads
healthcheck:
test: ["CMD-SHELL", "bash -c 'cat </dev/null >/dev/tcp/127.0.0.1/8080' || exit 1"]
interval: 10s
timeout: 5s
retries: 12
start_period: 40s
admin-api:
build:
context: .
dockerfile: docker/admin-api/Dockerfile
args:
DOTNET_SDK_IMAGE: ${DOTNET_SDK_IMAGE:-mcr.microsoft.com/dotnet/sdk:10.0}
DOTNET_ASPNET_IMAGE: ${DOTNET_ASPNET_IMAGE:-mcr.microsoft.com/dotnet/aspnet:10.0}
container_name: meezi-admin-api
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment:
ASPNETCORE_ENVIRONMENT: Development
ASPNETCORE_URLS: http://+:8080
RUN_MIGRATIONS: "false"
ConnectionStrings__DefaultConnection: Host=postgres;Port=5432;Database=meezi;Username=meezi;Password=meezi_local_pass
ConnectionStrings__Redis: redis:6379
Cors__Origins__0: http://localhost:${ADMIN_WEB_PORT:-3102}
Cors__Origins__1: http://localhost:${WEB_PORT:-3101}
Kavenegar__ApiKey: ""
ports:
- "${ADMIN_API_PORT:-5081}:8080"
healthcheck:
test: ["CMD-SHELL", "bash -c 'cat </dev/null >/dev/tcp/127.0.0.1/8080' || exit 1"]
interval: 10s
timeout: 5s
retries: 12
start_period: 40s
# ── Frontend Services ───────────────────────────────────────────────────────
web:
build:
context: .
dockerfile: docker/web/Dockerfile
args:
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:5080}
container_name: meezi-web
restart: unless-stopped
depends_on:
api:
condition: service_healthy
environment:
PORT: "3000"
HOSTNAME: 0.0.0.0
ports:
- "${WEB_PORT:-3101}:3000"
admin-web:
build:
context: .
dockerfile: docker/admin-web/Dockerfile
args:
NEXT_PUBLIC_ADMIN_API_URL: ${NEXT_PUBLIC_ADMIN_API_URL:-http://localhost:5081}
container_name: meezi-admin-web
restart: unless-stopped
depends_on:
admin-api:
condition: service_healthy
environment:
PORT: "3000"
HOSTNAME: 0.0.0.0
ports:
- "${ADMIN_WEB_PORT:-3102}:3000"
website:
build:
context: .
dockerfile: docker/website/Dockerfile
args:
MEEZI_API_URL: http://api:8080
NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_SITE_URL:-http://localhost:3010}
container_name: meezi-website
restart: unless-stopped
depends_on:
api:
condition: service_healthy
environment:
PORT: "3000"
HOSTNAME: 0.0.0.0
MEEZI_API_URL: http://api:8080
NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_SITE_URL:-http://localhost:3010}
ports:
- "${WEBSITE_PORT:-3010}:3000"
volumes:
postgres_data:
redis_data:
api_uploads:
+154
View File
@@ -0,0 +1,154 @@
# Meezi — full stack (Postgres, Redis, API, Dashboard, Marketing Website)
#
# Setup:
# copy .env.example .env
# powershell -File scripts/docker-up-full.ps1
# — or — docker compose up -d --build
#
# If image pulls fail (Iran / MCR timeout): VPN on, or see docs/DOCKER.md
#
# URLs (defaults):
# Dashboard http://localhost:3101/fa/login
# Website http://localhost:3010/fa
# Finder http://localhost:3103/fa
# API http://localhost:5080/swagger
# Health http://localhost:5080/health
services:
postgres:
image: postgres:16-alpine
container_name: meezi-db
restart: unless-stopped
environment:
POSTGRES_DB: meezi
POSTGRES_USER: meezi
POSTGRES_PASSWORD: meezi_local_pass
ports:
- "${POSTGRES_PORT:-5434}:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U meezi -d meezi"]
interval: 5s
timeout: 5s
retries: 10
redis:
image: redis:7-alpine
container_name: meezi-redis
restart: unless-stopped
ports:
- "${REDIS_PORT:-6381}:6379"
command: redis-server --appendonly yes
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 10
api:
build:
context: .
dockerfile: docker/api/Dockerfile
args:
DOTNET_SDK_IMAGE: ${DOTNET_SDK_IMAGE:-mcr.microsoft.com/dotnet/sdk:10.0}
DOTNET_ASPNET_IMAGE: ${DOTNET_ASPNET_IMAGE:-mcr.microsoft.com/dotnet/aspnet:10.0}
container_name: meezi-api
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
environment:
ASPNETCORE_ENVIRONMENT: Development
ASPNETCORE_URLS: http://+:8080
RUN_MIGRATIONS: "true"
ConnectionStrings__DefaultConnection: Host=postgres;Port=5432;Database=meezi;Username=meezi;Password=meezi_local_pass
ConnectionStrings__Redis: redis:6379
App__PublicBaseUrl: ${NEXT_PUBLIC_API_URL:-http://localhost:5080}
App__QrPublicBaseUrl: http://localhost:${WEB_PORT:-3101}
Cors__Origins__0: http://localhost:${WEB_PORT:-3101}
Cors__Origins__1: http://localhost:${WEBSITE_PORT:-3010}
Cors__Origins__2: http://localhost:${FINDER_PORT:-3103}
Auth__MaxOtpAttemptsPerHour: "100"
Kavenegar__ApiKey: ""
Billing__DashboardBaseUrl: http://localhost:${WEB_PORT:-3101}
Snappfood__WebhookSecret: meezi-dev-snappfood-secret
ZarinPal__MerchantId: "${ZARINPAL_MERCHANT_ID:-}"
ZarinPal__Sandbox: "${ZARINPAL_SANDBOX:-true}"
ports:
- "${API_PORT:-5080}:8080"
volumes:
- api_uploads:/app/uploads
healthcheck:
# TCP probe only — no apt-get/curl in image (build works offline / without Ubuntu mirrors)
test: ["CMD-SHELL", "bash -c 'cat </dev/null >/dev/tcp/127.0.0.1/8080' || exit 1"]
interval: 10s
timeout: 5s
retries: 12
start_period: 40s
web:
build:
context: .
dockerfile: docker/web/Dockerfile
args:
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:5080}
container_name: meezi-web
restart: unless-stopped
depends_on:
api:
condition: service_healthy
environment:
PORT: "3000"
HOSTNAME: 0.0.0.0
ports:
- "${WEB_PORT:-3101}:3000"
website:
build:
context: .
dockerfile: docker/website/Dockerfile
args:
MEEZI_API_URL: http://api:8080
NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_SITE_URL:-http://localhost:3010}
container_name: meezi-website
restart: unless-stopped
depends_on:
api:
condition: service_healthy
environment:
PORT: "3000"
HOSTNAME: 0.0.0.0
MEEZI_API_URL: http://api:8080
NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_SITE_URL:-http://localhost:3010}
ports:
- "${WEBSITE_PORT:-3010}:3000"
finder:
build:
context: .
dockerfile: docker/finder/Dockerfile
args:
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:5080}
NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_FINDER_URL:-http://localhost:3103}
container_name: meezi-finder
restart: unless-stopped
depends_on:
api:
condition: service_healthy
environment:
PORT: "3000"
HOSTNAME: 0.0.0.0
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-http://localhost:5080}
NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_FINDER_URL:-http://localhost:3103}
ports:
- "${FINDER_PORT:-3103}:3000"
volumes:
postgres_data:
redis_data:
api_uploads:
+7
View File
@@ -0,0 +1,7 @@
{
"sdk": {
"version": "10.0.100",
"rollForward": "latestFeature",
"allowPrerelease": false
}
}
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
<config>
<!-- Helps flaky TLS / filtered networks during docker build restore -->
<add key="http_retry_count" value="8" />
</config>
</configuration>
+6
View File
@@ -0,0 +1,6 @@
{
"name": "Meezi",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}