mirror of
https://git.meshkee.com/Meshkee/backend.git
synced 2026-08-11 22:30:59 +04:30
Add invoice template CRUD, key points/accounts, public GET endpoint, and migration 039 for account_holder_name. Co-authored-by: Cursor <cursoragent@cursor.com>
653 lines
24 KiB
Markdown
653 lines
24 KiB
Markdown
# Meshkee CMS API — Project Context
|
|
|
|
> Living reference for developers and AI assistants working on this codebase.
|
|
> Last updated: July 24, 2026
|
|
|
|
## What This Project Is
|
|
|
|
**Meshkee CMS API** (`meshkee-cms-api`) is a multi-tenant backend for Meshkee business websites. Each business gets its own domain, content (products, categories, media), team members, and customer registrations. Platform super admins manage businesses and domains; business owners and staff manage per-business content through a permission-based dashboard.
|
|
|
|
**API base URL:** `/api/v1`
|
|
|
|
---
|
|
|
|
## Tech Stack
|
|
|
|
| Layer | Technology |
|
|
|-------|------------|
|
|
| Runtime | Node.js, TypeScript (ES2021, strict) |
|
|
| Framework | NestJS 11 |
|
|
| ORM | Prisma 6 (`prisma db pull` — schema is introspected, not migrated via Prisma) |
|
|
| Database | PostgreSQL 16 |
|
|
| Cache | Redis 7 (OTP storage) |
|
|
| Auth | JWT (access + refresh), Passport, bcrypt |
|
|
| Validation | class-validator + class-transformer |
|
|
| File storage | S3-compatible (Parmin), Sharp for image processing |
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
### Multi-tenancy model
|
|
|
|
```
|
|
Platform (super admin)
|
|
└── Business (tenant root)
|
|
├── Domains (host → business resolution)
|
|
├── Team members (BusinessUser + Role)
|
|
├── Customers (BusinessCustomer)
|
|
├── Categories (per entityType: product | blog | portfolio)
|
|
├── Products
|
|
│ └── Store items (product variants — price & stock per variation combo)
|
|
├── Media library
|
|
└── Settings (JSON)
|
|
```
|
|
|
|
- **Tenant resolution:** `GET /tenants/:host` resolves a domain to a business (public, no auth).
|
|
- **Business-scoped APIs:** Most CMS routes use `businesses/:businessId/...` with JWT + business permission checks.
|
|
- **Platform APIs:** User/business/domain management requires `super_admin` (checked in services).
|
|
|
|
### Two "category" concepts
|
|
|
|
| Concept | Table | Purpose |
|
|
|---------|-------|---------|
|
|
| **Content Category** | `categories` | Per-business taxonomy for products, blogs, portfolios |
|
|
| **Business Category** | `business_categories` | Platform-wide taxonomy classifying businesses (Retail, Creative, etc.) |
|
|
|
|
Do not confuse them when reading or writing code.
|
|
|
|
---
|
|
|
|
## Directory Structure
|
|
|
|
```
|
|
src/
|
|
├── main.ts # Bootstrap, global prefix, pipes, interceptors
|
|
├── app.module.ts # Root module wiring
|
|
├── auth/ # JWT, OTP/SMS, permissions, guards, decorators
|
|
├── users/ # Super-admin user management
|
|
├── roles/ # Global + team role listing
|
|
├── business-admin/ # Super-admin business CRUD + domains
|
|
├── business-team/ # Per-business team invite/manage
|
|
├── tenant/ # Domain → business resolution (public)
|
|
├── domain-admin/ # Super-admin domain management
|
|
├── categories/ # Content categories, variations, technical forms
|
|
├── products/ # Products, variants, technical info
|
|
├── cart/ # Customer shopping cart
|
|
├── orders/ # Customer checkout + admin order management
|
|
├── media/ # Upload/list/update/delete media
|
|
├── storage/ # S3 driver abstraction
|
|
├── website-docs/ # Public website API docs pack
|
|
├── invoices/ # Platform invoices + item templates (super-admin; business-ready schema)
|
|
├── prisma/ # PrismaModule + PrismaService
|
|
├── redis/ # Redis client + OTP helpers
|
|
└── common/ # Shared interceptors (BigInt serialization)
|
|
|
|
prisma/schema.prisma # Introspected from PostgreSQL (source of truth after migrations)
|
|
database/
|
|
├── migrations/ # Raw SQL migrations (numbered)
|
|
├── seeds/ # Dev sample data + production super-admin
|
|
├── migrate.sh, seed.sh, setup.sh
|
|
postman/ # API collection for manual testing
|
|
```
|
|
|
|
### NestJS module pattern
|
|
|
|
Each feature follows: `*.module.ts` → `*.controller.ts` → `*.service.ts` → `dto/`
|
|
|
|
---
|
|
|
|
## Getting Started
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
docker compose up -d # Postgres (auto-runs migrations on first init) + Redis
|
|
./database/seed.sh # Sample dev data
|
|
npm install
|
|
npm run prisma:generate
|
|
npm run start:dev # http://localhost:3000/api/v1
|
|
```
|
|
|
|
**Dev credentials** (after `001_sample_data.sql` seed): any seeded user, password `password`.
|
|
|
|
Example super admin: `+989121111111` / `password`
|
|
|
|
---
|
|
|
|
## Database & Migrations
|
|
|
|
### Workflow
|
|
|
|
1. Write a new SQL file in `database/migrations/` (e.g. `012_feature.sql`).
|
|
2. Apply via `./database/migrate.sh` or `docker exec` into Postgres.
|
|
3. Run `npm run prisma:pull` to sync `prisma/schema.prisma`.
|
|
4. Run `npm run prisma:generate` to regenerate the client.
|
|
|
|
**Important:** This project does **not** use Prisma Migrate. SQL migrations are the authoritative schema history. Prisma schema is maintained separately via introspection.
|
|
|
|
### Migration files
|
|
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `001_initial_schema.sql` | Users, businesses, domains |
|
|
| `002_phone_permissions_content_media.sql` | RBAC, media, products, blogs, portfolios |
|
|
| `003_categories.sql` | Content categories + assignments |
|
|
| `004_user_types_and_business_members.sql` | Super admin, business owner, customer roles |
|
|
| `005_business_team_roles.sql` | Team roles (admin, editor, viewer) |
|
|
| `006_business_categories.sql` | Platform business category taxonomy |
|
|
| `006_user_profile.sql` | User profile JSON |
|
|
| `007_business_i18n_fields.sql` | Business Persian name |
|
|
| `007_domain_expiry_active.sql` | Domain expiry/active flags |
|
|
| `008_remove_name_en.sql` | Schema cleanup |
|
|
| `009_categories_name_fa.sql` | Category `name_fa` |
|
|
| `010_category_variations.sql` | Category variations, product variants (store items) |
|
|
| `011_category_technical_forms.sql` | Technical forms + product values |
|
|
| `012_comments.sql` | Product comments |
|
|
| `013_expert_reviews.sql` | Expert reviews |
|
|
| `014_addresses.sql` | User/business street addresses |
|
|
| `015_business_profile.sql` | Business profile fields |
|
|
| `015_cities.sql` | Location cities (country → province → city) |
|
|
| `016_product_variation_values.sql` | Product-level variation option selections (input for store items) |
|
|
| `017_product_variant_festival.sql` | Festival flag on store items |
|
|
| `018_product_variant_reward_points.sql` | Reward points on store items |
|
|
| `019_cart_and_orders.sql` | Shopping cart, orders, order items + order permissions |
|
|
| `020_store_items_and_variants.sql` | `store_items` + `store_item_variants` (replaces `product_variants`) |
|
|
| `036_invoices.sql` | Invoices, invoice items, invoice item templates + permissions |
|
|
| `037_invoice_name.sql` | Optional `invoices.name` |
|
|
| `038_invoice_templates.sql` | Full invoice templates + key points/accounts on invoices |
|
|
| `039_invoice_account_holder.sql` | `account_holder_name` on invoice / template accounts |
|
|
|
|
Docker mounts `./database/migrations` into Postgres init — migrations run automatically only on **first** volume creation. Use `migrate.sh` for subsequent migrations.
|
|
|
|
---
|
|
|
|
## Domain Model
|
|
|
|
### Enums
|
|
|
|
| Enum | Values |
|
|
|------|--------|
|
|
| `MediaEntityType` | `product`, `blog`, `portfolio` |
|
|
| `ContentStatus` | `draft`, `published`, `archived` |
|
|
| `VariationType` | `color`, `size`, `custom` |
|
|
| `OrderStatus` | `pending`, `confirmed`, `processing`, `shipped`, `delivered`, `cancelled` |
|
|
| `OrderSource` | `website`, `admin` |
|
|
| `InvoiceOwnerScope` | `platform`, `business` |
|
|
| `InvoiceStatus` | `draft`, `issued`, `paid`, `cancelled` |
|
|
| `TechnicalFieldType` | `text`, `textarea`, `select`, `multi_select` |
|
|
| `MediaType` | `image`, `video` |
|
|
|
|
### Core relationships
|
|
|
|
```
|
|
Business 1──* Domain
|
|
Business 1──* Category (entityType: product|blog|portfolio)
|
|
Business 1──* Product
|
|
Business 1──* Media
|
|
|
|
Category 1──* CategoryVariation 1──* CategoryVariationOption
|
|
Category 1──0..1 CategoryTechnicalForm 1──* CategoryTechnicalFormField
|
|
CategoryTechnicalFormField 1──* CategoryTechnicalFormFieldOption
|
|
|
|
Product *──0..1 Category (via CategoryAssignment)
|
|
Product 1──* ProductVariationValue → CategoryVariationOption (which options this product offers)
|
|
Product 1──* ProductVariationValue → CategoryVariationOption (which options this product offers)
|
|
Product 1──0..1 StoreItem (one shop listing per product)
|
|
StoreItem 1──* StoreItemVariant (purchasable SKUs: price, stock, variation combo)
|
|
StoreItemVariant 1──* StoreItemVariantSelection → CategoryVariationOption
|
|
Product 1──* ProductTechnicalFieldValue → CategoryTechnicalFormField
|
|
|
|
Business 1──* Cart (per customer) 1──* CartItem → ProductVariant
|
|
Business 1──* Order 1──* OrderItem → ProductVariant (snapshot on order)
|
|
User 1──* Cart, Order (as customer)
|
|
|
|
Business 1──* Invoice (billed party) 1──* InvoiceItem
|
|
InvoiceItemTemplate (platform or per-business predefined lines)
|
|
Invoice.issuedBy → User
|
|
|
|
Media 1──* MediaAttachment (polymorphic: entityType + entityId)
|
|
```
|
|
|
|
### Schema gap: blogs & portfolios
|
|
|
|
SQL migrations create `blogs` and `portfolios` tables and seed data populates them. Permissions exist (`blogs.*`, `portfolios.*`). However:
|
|
|
|
- No Prisma models for Blog/Portfolio
|
|
- No NestJS modules or API endpoints
|
|
|
|
Categories and media attachments already support `blog` and `portfolio` entity types — infrastructure is ready, API is not.
|
|
|
|
---
|
|
|
|
## API Reference
|
|
|
|
All routes are prefixed with `/api/v1`.
|
|
|
|
### Public
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| POST | `/auth/register` | Customer registration by domain |
|
|
| POST | `/auth/login` | Cell + password |
|
|
| POST | `/auth/refresh` | Refresh token |
|
|
| POST | `/auth/send-otp` | Send OTP (Redis-backed) |
|
|
| POST | `/auth/verify-otp` | Verify OTP |
|
|
| GET | `/tenants/:host` | Resolve business from domain |
|
|
| GET | `/tenants/:host/store-specials` | Active store specials |
|
|
| GET | `/tenants/:host/website/category-groups` | Homepage category rows |
|
|
| GET | `/tenants/:host/website/brand-groups` | Homepage brand rows |
|
|
| GET | `/tenants/:host/website/sliders` | Homepage sliders with slides |
|
|
|
|
### Authenticated (JWT)
|
|
|
|
| Method | Path | Access |
|
|
|--------|------|--------|
|
|
| GET | `/auth/me` | Any user |
|
|
| PATCH | `/auth/profile` | Any user |
|
|
| POST | `/auth/change-password` | Any user |
|
|
| GET | `/roles?scope=global\|team` | Super admin / team.read |
|
|
| GET | `/business-categories` | Super admin or `business_categories.read` |
|
|
|
|
### Super admin only (service-level check)
|
|
|
|
| Module | Base path |
|
|
|--------|-----------|
|
|
| Users | `/users` |
|
|
| Businesses | `/businesses` |
|
|
| Domains | `/domains` |
|
|
|
|
### Business-scoped (JWT + `BusinessPermissionGuard`)
|
|
|
|
Pattern: `/businesses/:businessId/<resource>`
|
|
|
|
| Module | Base path | Key permissions |
|
|
|--------|-----------|-----------------|
|
|
| Team | `/team` | `business.team.*` |
|
|
| Media | `/media` | `media.*` |
|
|
| Categories | `/categories` | `categories.*` |
|
|
| Products | `/products` | `products.*` |
|
|
| Website | `/website/category-groups`, `/website/brand-groups`, `/website/sliders` | `website.*` |
|
|
|
|
#### Categories — notable sub-routes
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| GET | `/categories/color-presets` | Predefined color palette |
|
|
| GET/PUT | `/categories/:id/variations` | Category variation options |
|
|
| GET/PUT | `/categories/:id/technical-form` | Technical form definition |
|
|
|
|
#### Products — notable sub-routes
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| GET/PUT | `/products/:id/variations` | Which category variation options apply to this product |
|
|
| GET/POST/PATCH/DELETE | `/products/:id/variants` | Removed — use `/store-items` |
|
|
| GET/PUT | `/products/:id/technical-info` | Product technical data |
|
|
|
|
#### Cart (customer — JWT, must be business customer)
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| GET | `/cart` | Get current user's cart |
|
|
| POST | `/cart/items` | Add store item variant to cart (`storeItemVariantId`, `quantity`) |
|
|
| PATCH | `/cart/items/:itemId` | Update cart item quantity |
|
|
| DELETE | `/cart/items/:itemId` | Remove cart item |
|
|
| DELETE | `/cart` | Clear cart |
|
|
| POST | `/cart/checkout` | Place order from cart (requires `addressId` or `shippingAddress`) |
|
|
|
|
#### Orders
|
|
|
|
| Method | Path | Access | Description |
|
|
|--------|------|--------|-------------|
|
|
| GET | `/orders` | Customer (own) or `orders.read` (all) | List orders |
|
|
| GET | `/orders/:orderId` | Customer (own) or `orders.read` | Order detail |
|
|
| POST | `/orders` | `orders.create` | Admin: create order for a customer |
|
|
| PATCH | `/orders/:orderId` | `orders.update` | Admin: update status / admin notes |
|
|
|
|
---
|
|
|
|
## Auth & RBAC
|
|
|
|
### Global user roles
|
|
|
|
| Role slug | Dashboard | Notes |
|
|
|-----------|-----------|-------|
|
|
| `super_admin` | `super_admin` | Full platform access |
|
|
| `business_owner` | `business` | Assigned to business owners |
|
|
| `business_staff` | `business` | Legacy; team uses per-business roles |
|
|
| `customer` | `customer` | Website registrants |
|
|
|
|
### Per-business team roles
|
|
|
|
Assigned via `business_users.role_id`:
|
|
|
|
| Role | Slug | Typical access |
|
|
|------|------|----------------|
|
|
| Owner | `isOwner=true` | All `business_owner` permissions |
|
|
| Admin | `admin` | Full content + team read |
|
|
| Editor | `editor` | Create/edit/publish content |
|
|
| Viewer | `viewer` | Read-only |
|
|
|
|
### Permission groups (seeded)
|
|
|
|
`business.*`, `domains.*`, `products.*`, `blogs.*`, `portfolios.*`, `media.*`, `categories.*`, `users.*`, `roles.manage`, `business.team.*`, `business_categories.*`
|
|
|
|
Each resource typically has: `read`, `create`, `update`, `delete` (+ `publish` for content).
|
|
|
|
### Guards & decorators
|
|
|
|
```typescript
|
|
@UseGuards(JwtAuthGuard, BusinessPermissionGuard)
|
|
@RequireBusinessPermission('products.read')
|
|
```
|
|
|
|
- `JwtAuthGuard` — validates Bearer JWT (`type: 'access'`)
|
|
- `BusinessPermissionGuard` — checks permission for `businessId` route param
|
|
- `PermissionsService` — `isSuperAdmin()`, `hasBusinessPermission()`
|
|
- Super admins bypass business permission checks
|
|
- `@CurrentUser()` injects `AuthUser` into handlers
|
|
|
|
### Auth flow notes
|
|
|
|
- Registration resolves tenant by `domain` → creates/links user → assigns `customer` role
|
|
- OTP stored in Redis (`otp:{cellNumber}`), 5-min TTL; disabled when `SMS_ENABLED=false`
|
|
- JWT payload: `sub`, `cellNumber`, `roles`, `dashboard`, `type`
|
|
|
|
---
|
|
|
|
## Key Features
|
|
|
|
### Products
|
|
|
|
- CRUD with slug, status, featured media, gallery attachments
|
|
- One category per product (via `CategoryAssignment`)
|
|
- Content JSON: `{ nameFa, html }`; metadata JSON: `{ tags }`
|
|
- i18n: Persian name in `content.nameFa`, summary in `description`
|
|
|
|
### Category variations (product categories)
|
|
|
|
- Types: `color` (preset palette), `size`, `custom`
|
|
- One color + one size per category (DB partial unique indexes)
|
|
- `PUT /categories/:id/variations` replaces all variations (delete-all + recreate)
|
|
- Color presets: `GET /categories/color-presets` (15 named colors with hex)
|
|
|
|
### Store (shop listings)
|
|
|
|
The **store** turns CMS products into purchasable items using a two-level model:
|
|
|
|
```
|
|
Product variations → which options this product offers (product_variation_values)
|
|
Store item → one shop listing per product (store_items)
|
|
Store item variants → purchasable SKUs with price/stock per combination (store_item_variants)
|
|
```
|
|
|
|
| Layer | Table | API | Purpose |
|
|
|-------|-------|-----|---------|
|
|
| Product variations | `product_variation_values` | `PUT /products/:id/variations` | Which category variation options apply to this product |
|
|
| Store item | `store_items` | `GET /store-items/by-product/:productId` | One listing per product in the shop |
|
|
| Store item variants | `store_item_variants` + `store_item_variant_selections` | `POST /store-items`, `PUT /store-items/sync` | Purchasable combinations with price & stock |
|
|
|
|
**Store item variant fields** (`store_item_variants`):
|
|
|
|
| Field | DB column | Notes |
|
|
|-------|-----------|-------|
|
|
| `sku` | `sku` | Optional merchant SKU |
|
|
| `price` | `price` | Selling price (`NUMERIC(12,2)`) |
|
|
| `compareAtPrice` | `compare_at_price` | Optional strike-through / was-price |
|
|
| `stockQuantity` | `stock_quantity` | Integer ≥ 0; `null` = untracked |
|
|
| `isActive` | `is_active` | Whether variant is available for purchase |
|
|
| `selections` | `store_item_variant_selections` | Exactly one `CategoryVariationOption` per category variation |
|
|
|
|
**Rules:**
|
|
|
|
- One `store_items` row per product per business.
|
|
- Create variants under the store item after setting product variation values.
|
|
- Each variant picks one option per variation; the combination must be unique per store item.
|
|
- Cart and orders reference `storeItemVariantId` (not the product directly).
|
|
|
|
**Example — batch create variants for a product:**
|
|
|
|
```json
|
|
POST /businesses/:businessId/store-items
|
|
{
|
|
"productId": "1",
|
|
"items": [
|
|
{
|
|
"selections": [
|
|
{ "variationId": "1", "optionId": "1" },
|
|
{ "variationId": "2", "optionId": "5" }
|
|
],
|
|
"price": 99.99,
|
|
"stockQuantity": 10
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Example — update a variant:**
|
|
|
|
```json
|
|
PATCH /businesses/:businessId/store-items/variants/:variantId
|
|
{
|
|
"price": 89.99,
|
|
"stockQuantity": 25
|
|
}
|
|
```
|
|
|
|
### Technical forms (product categories)
|
|
|
|
- One form per product category
|
|
- Field types: `text`, `textarea`, `select`, `multi_select`
|
|
- `PUT /categories/:id/technical-form` — define/replace form fields
|
|
- `PUT /products/:id/technical-info` — fill product values (validated against category form)
|
|
- Product must have a category assignment
|
|
|
|
**Example — define form:**
|
|
|
|
```json
|
|
PUT /businesses/:businessId/categories/:categoryId/technical-form
|
|
{
|
|
"fields": [
|
|
{ "label": "Weight", "type": "text", "isRequired": true },
|
|
{ "label": "Material", "type": "select", "options": ["Cotton", "Polyester"] },
|
|
{ "label": "Features", "type": "multi_select", "options": ["Waterproof", "Breathable"] }
|
|
]
|
|
}
|
|
```
|
|
|
|
**Example — fill product data:**
|
|
|
|
```json
|
|
PUT /businesses/:businessId/products/:productId/technical-info
|
|
{
|
|
"values": [
|
|
{ "fieldKey": "weight", "value": "500g" },
|
|
{ "fieldKey": "material", "value": "cotton" },
|
|
{ "fieldKey": "features", "value": ["waterproof", "breathable"] }
|
|
]
|
|
}
|
|
```
|
|
|
|
Field keys are auto-slugified from labels (e.g. `"Weight"` → `"weight"`).
|
|
|
|
### Media
|
|
|
|
- Multipart upload to S3 via Sharp processing
|
|
- Polymorphic attachments to products (and future blog/portfolio entities)
|
|
- Featured image on products via `featuredMediaId`
|
|
|
|
---
|
|
|
|
## Coding Conventions
|
|
|
|
### DTOs
|
|
|
|
- `class-validator` decorators on all request bodies and query params
|
|
- `@Type(() => Number)` for query param coercion
|
|
- Slug format: `^[a-z0-9]+(?:-[a-z0-9]+)*$`
|
|
- Cell numbers: E.164 `^\+[1-9]\d{6,14}$`
|
|
|
|
### Services
|
|
|
|
- Inject `PrismaService`, `PermissionsService`
|
|
- Convert route param IDs with `BigInt(idRaw)`
|
|
- Private `assertPermission()` / `assertSuperAdmin()` helpers
|
|
- Use `$transaction` for multi-step writes
|
|
- **Replace semantics** for nested resources (variations, technical forms) — delete-all then recreate
|
|
|
|
### Error handling
|
|
|
|
Use Nest exceptions: `NotFoundException`, `ForbiddenException`, `BadRequestException`, `ConflictException`, `UnauthorizedException`.
|
|
|
|
### Serialization
|
|
|
|
- Global `BigIntSerializerInterceptor` converts BigInt → Number in JSON responses
|
|
- Services use private `serialize()` methods for consistent output shapes
|
|
- IDs returned as strings in API responses
|
|
|
|
### Global validation pipe (`main.ts`)
|
|
|
|
```typescript
|
|
new ValidationPipe({
|
|
whitelist: true,
|
|
forbidNonWhitelisted: true,
|
|
transform: true,
|
|
})
|
|
```
|
|
|
|
---
|
|
|
|
## Environment Variables
|
|
|
|
See `.env.example` for the full list. Key groups:
|
|
|
|
| Group | Variables |
|
|
|-------|-----------|
|
|
| Database | `DATABASE_URL`, `POSTGRES_*` |
|
|
| Redis | `REDIS_URL`, `REDIS_HOST`, `REDIS_PORT` |
|
|
| API | `PORT` |
|
|
| JWT | `JWT_ACCESS_SECRET`, `JWT_REFRESH_SECRET`, `JWT_*_EXPIRES_IN` |
|
|
| SMS | `SMS_ENABLED` |
|
|
| S3 | `S3_ENDPOINT`, `S3_BUCKET`, `S3_PUBLIC_URL`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY` |
|
|
| Media | `MEDIA_MAX_FILE_SIZE_MB` |
|
|
|
|
---
|
|
|
|
## Implemented vs Planned
|
|
|
|
### Implemented
|
|
|
|
- Multi-tenant auth (register, login, OTP, profile)
|
|
- Super admin: users, businesses, domains, system business categories
|
|
- Business team management
|
|
- Media upload (S3 + Sharp)
|
|
- Categories (all entity types in DB; API supports `entityType` filter)
|
|
- Products CRUD
|
|
- Product variation values (which options a product offers)
|
|
- Store items / product variants (price, stock, SKU)
|
|
- Shopping cart + checkout + orders (customer + admin)
|
|
- Product technical info
|
|
- Category variations & technical forms
|
|
- Tenant resolution by domain
|
|
- RBAC with granular permissions
|
|
|
|
### Planned / partial
|
|
|
|
| Feature | DB | Permissions | API | Prisma |
|
|
|---------|----|-------------|-----|--------|
|
|
| Blogs | Yes | Yes | No | No model |
|
|
| Portfolios | Yes | Yes | No | No model |
|
|
| Customer dashboard | Partial | No | Register only | Yes |
|
|
| SMS provider | — | — | Stub | — |
|
|
| Store checkout (cart, orders) | Yes | Yes | Yes | Yes |
|
|
| Customer favorites | — | `favorites.*` seeded | No | No |
|
|
|
|
---
|
|
|
|
## Invoices (platform / super-admin)
|
|
|
|
Super admins issue invoices **to** a business. Schema is ready for future business-scoped issuing (`owner_scope=business`).
|
|
|
|
### Tables
|
|
|
|
| Table | Purpose |
|
|
|-------|---------|
|
|
| `invoice_item_templates` | Predefined line items (`owner_scope` platform \| business) |
|
|
| `invoice_templates` | Full blueprints: name, top_text |
|
|
| `invoice_template_items` / `_key_points` / `_accounts` | Nested template content |
|
|
| `invoices` | Invoice header (`business_id` = billed party, optional `name`, `top_text`, `notes`, `invoice_template_id`, `status`) |
|
|
| `invoice_items` / `invoice_key_points` / `invoice_accounts` | Issued invoice nested content |
|
|
|
|
### API (super_admin only today)
|
|
|
|
| Method | Path |
|
|
|--------|------|
|
|
| GET/POST | `/invoice-item-templates` |
|
|
| PATCH/DELETE | `/invoice-item-templates/:templateId` |
|
|
| GET/POST | `/invoice-templates` |
|
|
| GET/PATCH/DELETE | `/invoice-templates/:templateId` |
|
|
| GET/POST | `/businesses/:businessId/invoices` |
|
|
| GET/PATCH/DELETE | `/businesses/:businessId/invoices/:invoiceId` |
|
|
| GET | `/public/invoices/:invoiceId` (no auth; issued/paid only) |
|
|
|
|
Auth (admin routes): `JwtAuthGuard` + service `assertSuperAdmin`.
|
|
|
|
Serialized platform invoices include `publicUrl`: `https://{INVOICE_PUBLIC_DOMAIN}/invoices/{id}` (default `meshkee.com`), or `{INVOICE_PUBLIC_BASE_URL}/invoices/{id}` when set. Accounts include optional `accountHolderName`.
|
|
|
|
Public HTML viewer lives in the dashboards super-admin SPA (`/invoices/:id`); API serves JSON via `/public/invoices/:id`.
|
|
|
|
Permissions seeded for future business dashboard: `invoices.*`, `invoice_templates.*`.
|
|
|
|
Module: `src/invoices/` · Migrations: `036`, `037`, `038`, `039`
|
|
|
|
---
|
|
|
|
## Testing
|
|
|
|
**Postman collection:** `postman/Meshkee-CMS-Auth.postman_collection.json`
|
|
|
|
Variables: `baseUrl`, `accessToken`, `refreshToken`, `domain`, `businessId`, `categoryId`, `productId`, `variantId`, `variationId`, `optionId`, `cartItemId`, `orderId`
|
|
|
|
**Store workflow in Postman:** `Business Categories` → set variations → `Business Products` → create product → set product variations → create store item (variant) with price & stock → `Website - Cart` (login as customer) → add to cart → checkout → `Business Orders` (login as owner) to manage.
|
|
|
|
---
|
|
|
|
## Common Tasks for Contributors
|
|
|
|
### Add a new API feature
|
|
|
|
1. Write SQL migration in `database/migrations/`
|
|
2. Apply migration, then `npm run prisma:pull && npm run prisma:generate`
|
|
3. Create module: `src/<feature>/` with controller, service, DTOs
|
|
4. Register in `app.module.ts`
|
|
5. Add permissions to migration if business-scoped
|
|
6. Update this document
|
|
|
|
### Add a business-scoped endpoint
|
|
|
|
1. Controller: `@Controller('businesses/:businessId/...')`
|
|
2. Guards: `@UseGuards(JwtAuthGuard, BusinessPermissionGuard)`
|
|
3. Permission: `@RequireBusinessPermission('resource.action')`
|
|
4. Service: `assertPermission(businessId, actor.id, 'resource.action')` (defense in depth)
|
|
|
|
### Add a nested replace resource (like variations)
|
|
|
|
Follow the pattern in `CategoryVariationsService` / `CategoryTechnicalFormService`:
|
|
|
|
- `GET` returns current state
|
|
- `PUT` validates input, deletes all existing, recreates in a transaction
|
|
|
|
---
|
|
|
|
## Related Files
|
|
|
|
| Purpose | Path |
|
|
|---------|------|
|
|
| Prisma schema | `prisma/schema.prisma` |
|
|
| Env template | `.env.example` (`INVOICE_PUBLIC_DOMAIN` / optional `INVOICE_PUBLIC_BASE_URL`) |
|
|
| Invoices module | `src/invoices/` |
|
|
| Invoice migrations | `database/migrations/036_invoices.sql` … `039_invoice_account_holder.sql` |
|
|
| Docker services | `docker-compose.yml` |
|
|
| Dev seed data | `database/seeds/001_sample_data.sql` |
|
|
| Postman | `postman/Meshkee-CMS-Auth.postman_collection.json` |
|