Initial commit: nick docs

This commit is contained in:
moojttaba
2026-05-23 20:35:34 +03:30
commit 0da235ae27
90 changed files with 18268 additions and 0 deletions

View File

@@ -0,0 +1,380 @@
---
title: Coding Standards
tags: [development]
---
# Coding Standards
This page is the authoritative source for code style, file layout, and review rules across both repos. The UI section is a condensation of `backend/.cursor/rules/ui-development-standards.mdc`**that file is binding for any UI work**.
---
## 1. TypeScript
### Backend (`tsconfig.json`)
- `strict: true` — no implicit any, strict null checks, all the trimmings.
- `target: ES2020`, `module: commonjs` (Node 22 ESM is not used yet).
- Path aliases (use them, do not write deep relative imports):
```ts
import { config } from "@shared/config"; // src/shared/config
import { paymentSvc } from "@services/payment"; // src/services/payment
import { redis } from "@infrastructure/redis"; // src/infrastructure/redis
```
- `declaration: true` + `sourceMap: true` — keep this on. Source maps are required by Sentry stack traces.
### Frontend
- `strict: true`, `jsx: preserve` for Next.
- All component props **must** be typed and exported (`export type ComponentNameProps = …`).
- Prefer `type` over `interface` except when declaring something that must be extendable from a consumer module.
- Re-export from a barrel `index.ts` per folder — never deep-import (`import x from 'src/components/foo/internal/x'`).
---
## 2. ESLint & Prettier
### Backend ESLint (`backend/eslint.config.js`)
Flat config, TypeScript-only:
- `@typescript-eslint/no-unused-vars: warn`
- `@typescript-eslint/no-explicit-any: warn``any` is allowed when you justify it, but failing this rule will be flagged in code review.
- `no-console: off` — backend uses `src/utils/logger.ts` (a thin `console.log` wrapper) so console statements are fine in scripts. Inside services, prefer `log(...)` from the logger so you can later swap to structured logging.
Commands:
```bash
npm run lint # check
npm run lint:fix # auto-fix
npm run format # prettier --write src/**/*.ts
npm run typecheck # tsc --noEmit
```
### Frontend ESLint (`frontend/eslint.config.mjs`)
A heavier config combining `typescript-eslint`, `eslint-plugin-react`, `eslint-plugin-react-hooks`, `eslint-plugin-import`, `eslint-plugin-perfectionist` (for sorting), and `eslint-plugin-unused-imports`.
The most-cited rule in PR review: **import sorting**. The `perfectionist` plugin enforces this order — `eslint --fix` will reorder automatically:
```ts
// 1. Style imports
import './styles.css';
// 2. Side effects
import 'react-hot-toast';
// 3. Type imports (always isolated)
import type { ComponentProps } from '@mui/material';
// 4. External libraries
import { useState } from 'react';
import { motion } from 'framer-motion';
// 5. MUI components
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
// 6. Internal (routes → hooks → utils → components → sections → auth → types)
import { paths } from 'src/routes/paths';
import { useAuthContext } from 'src/auth/hooks';
import { formatNumber } from 'src/utils/format-number';
import { Iconify } from 'src/components/iconify';
import { HomeHero } from 'src/sections/home';
import type { User } from 'src/types/user';
```
Run before pushing:
```bash
yarn lint
yarn lint:fix
```
### Prettier
Both repos use Prettier defaults from the local config:
- 2-space indent
- single quotes
- trailing commas (`es5` on frontend, `all` on backend)
- semicolons on
`yarn lint:fix` / `npm run format` both run Prettier.
---
## 3. Naming conventions
| Kind | Convention | Example |
|------|------------|---------|
| TS files (general) | kebab-case | `format-number.ts` |
| React component file | kebab-case folder, `component.tsx` inside | `request-card/component.tsx` |
| Class | PascalCase | `class PaymentService` |
| Function | camelCase | `createPayInIntent()` |
| React component | PascalCase | `RequestCard` |
| Hook | camelCase starting with `use` | `useSocket`, `useAuthContext` |
| Constant | SCREAMING_SNAKE | `MAX_FILE_SIZE` |
| Mongoose model | PascalCase singular | `User`, `PurchaseRequest` |
| Mongo collection | lowercase plural (auto) | `users`, `purchaserequests` |
| Route handler | `<verb><Noun>` | `getRequestById`, `createOffer` |
| Express route file | `<domain>Routes.ts` | `paymentRoutes.ts` |
---
## 4. Backend — service file layout
A typical service folder under `src/services/`:
```
src/services/marketplace/
├── index.ts # Barrel — only public exports
├── marketplaceRoutes.ts # Router (express.Router) — auth middleware, validation, controller calls
├── marketplaceController.ts # HTTP layer — parses req, calls service, formats response envelope
├── marketplaceService.ts # Business logic — talks to models, throws domain errors
└── marketplaceRepository.ts # Optional Mongoose query helpers (when service grows)
```
### Response envelope
Every JSON response (success or error) uses the same envelope so the frontend can rely on a single response shape:
```ts
// success
res.status(200).json({
success: true,
data: <payload>,
message?: 'Optional human message',
});
// error (always via next(err) → errorHandler middleware)
res.status(err.status || 500).json({
success: false,
error: err.code || 'INTERNAL_ERROR',
message: err.message,
details?: err.details,
});
```
### Error handler pattern
Throw typed errors and let `src/shared/middleware/errorHandler.ts` catch them:
```ts
// Controllers / services
class HttpError extends Error {
constructor(public status: number, public code: string, message: string, public details?: unknown) {
super(message);
}
}
throw new HttpError(404, 'REQUEST_NOT_FOUND', 'Purchase request not found');
```
Wrap async route handlers so rejected promises reach `next()`:
```ts
const asyncHandler = (fn) => (req, res, next) =>
Promise.resolve(fn(req, res, next)).catch(next);
router.get('/:id', asyncHandler(controller.getById));
```
### Logging
Use `src/utils/logger.ts`:
```ts
import { log, logError } from "src/utils/logger";
log(`✅ Payment ${id} confirmed`);
logError("SHKeeper webhook verification failed", err);
```
Never use raw `console.error` in service code — it bypasses Sentry breadcrumbs.
---
## 5. Frontend — UI standards
**Authoritative source: `backend/.cursor/rules/ui-development-standards.mdc`.** Summary below.
### Component file layout
For non-trivial components, use a folder:
```
src/components/request-card/
├── index.ts # Barrel: export * from './component'
├── component.tsx # The React component
├── classes.ts # Styled classes or sx fragments
└── types.ts # Props type definitions
```
For one-file atoms, a single `name.tsx` is fine.
### MUI sx-prop — array syntax
The codebase uses the **array form** of the `sx` prop everywhere, because it composes cleanly with the spread-from-parent pattern:
```tsx
<Box
sx={[
{ p: 3, borderRadius: 2, bgcolor: 'background.paper' },
(theme) => ({ [theme.breakpoints.up('md')]: { flexDirection: 'row' } }),
isActive && { backgroundColor: 'primary.main' },
...(Array.isArray(sx) ? sx : [sx]),
]}
{...other}
/>
```
Reasons:
1. Conditional styles compose naturally (`condition && {...}` is ignored when false).
2. Theme callbacks are first-class items in the array.
3. The trailing spread allows parent overrides without prop drilling.
### No inline colors — use the theme
```tsx
// ❌ Wrong
sx={{ color: '#00A76F', bgcolor: '#FFF' }}
// ✅ Right
sx={(theme) => ({
color: theme.vars.palette.primary.main,
bgcolor: 'background.paper',
})}
```
Use `theme.vars.palette` (CSS variables) — automatically dark/light aware.
### Forms — React Hook Form + Zod
All forms use `react-hook-form` with `zodResolver` and the `RHF*` components from `src/components/hook-form`:
```tsx
const schema = z.object({
email: z.string().email('Invalid email'),
amount: z.number().min(1),
});
const methods = useForm({
resolver: zodResolver(schema),
defaultValues: { email: '', amount: 0 },
});
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)}>
<RHFTextField name="email" label="Email" />
<RHFTextField name="amount" type="number" label="Amount" />
</form>
</FormProvider>
```
### Icons — Iconify only
There is exactly one icon component:
```tsx
import { Iconify } from 'src/components/iconify';
<Iconify icon="eva:home-fill" />
<Iconify icon="solar:user-outline" width={24} height={24} />
```
Do **not** introduce another icon library (no `lucide-react`, no `@mui/icons-material`). Iconify covers everything via its registered icon sets.
### Hooks
- File: `use-<kebab>.ts`. Component name: `useCamelCase`.
- One hook per file. If a hook needs sub-helpers, colocate them in the same file.
- Custom hooks live in `src/hooks/` if generic, otherwise next to the feature.
### Accessibility & responsiveness
Mandatory checks:
- Touch targets ≥ 44px.
- Keyboard focus visible.
- Color contrast meets WCAG AA (theme already conforms).
- Use `Container` for page-level padding and the breakpoint system (`theme.breakpoints.up('md')`) for layouts.
---
## 6. Commit conventions
Authoritative file: `frontend/.commitlintrc.json` (extends `@commitlint/config-conventional`). Backend follows the same convention informally — the AI versioning script (`scripts/ai-enhanced.sh`) reads commit messages to decide on a major/minor/patch bump.
### Allowed types
`feat`, `fix`, `docs`, `style`, `refactor`, `test`, `chore`, `perf`, `ci`, `build`, `revert`
### Rules enforced
- Type must be present, lower-case.
- Subject must be present, lower-case.
- Header (`type(scope): subject`) max 100 chars.
### Examples
```
feat: add seller payout history table
fix(payment): handle missing transaction hash from shkeeper
docs: clarify env vars table
refactor(auth): extract jwt refresh into helper
chore: bump dependencies
feat!: breaking change to api response shape
```
### Breaking changes
Append `!` after the type or include `BREAKING CHANGE:` in the body. This triggers a major-version bump in the auto-version script. See [[Git Workflow#versioning]].
### Skip a version bump
Include `[skip-version]` anywhere in the message — the AI script will recognise it and skip the bump.
---
## 7. Testing standards
- All new code should be reachable by at least one test. See [[Testing]].
- Test file naming: `*.test.ts(x)` for unit/integration, `*.spec.ts` for Playwright.
- Place new backend tests in `__tests__/` — Jest discovers them via `**/__tests__/**/*.test.ts`.
- Place new frontend tests in `__tests__/<domain>-test/` or colocate `Component.test.tsx` next to the component.
---
## 8. PR review checklist
Before requesting review:
- [ ] Lint passes (`yarn lint` / `npm run lint`)
- [ ] Typecheck passes (`npm run typecheck`)
- [ ] Relevant tests added / updated
- [ ] No `console.log` in shipped code (frontend uses `src/utils/logger.ts`)
- [ ] No new icon library introduced (Iconify only)
- [ ] No inline hex colors (theme only)
- [ ] Import order obeys the linter
- [ ] Commit messages follow the convention
- [ ] If touching env vars, [[Environment Variables]] is updated
- [ ] If adding scripts, [[Scripts]] is updated
---
## 9. Banned patterns
| Don't | Do |
|-------|-----|
| `any` in new code | derive a precise type, fall back to `unknown` + narrowing |
| `console.log` outside scripts | `log(...)` from `utils/logger` |
| Deep relative imports (`../../../foo`) | path aliases (`@shared/foo` backend, `src/...` frontend) |
| Inline `style={{ color: '#fff' }}` | `sx` prop with theme tokens |
| `@mui/icons-material`, `react-icons`, `lucide-react` | `Iconify` |
| `useState` for global state that 3+ components need | a context in `src/contexts/` or a custom hook |
| Direct `axios.create` calls in components | use `src/lib/axios.ts` or an action in `src/actions/` |
| Hard-coded URLs | constants in `src/routes/paths.ts` (frontend) or env vars (backend) |
| Schema changes without a migration | add a migration script in `src/scripts/` and document it |

View File

@@ -0,0 +1,276 @@
---
title: Environment Variables
tags: [development]
---
# Environment Variables
Every environment variable read by either repo. Use this as the canonical reference when filling in a new `.env`, debugging missing-config errors, or reviewing a PR that touches config.
Sources scanned:
- Backend: `src/shared/config/index.ts`, `src/app.ts`, every `process.env.*` reference in `src/`, and `.env.sentry.example`.
- Frontend: `.env.development`, `.env.local`, `.env.production`, `.env.sentry.example`, `next.config.ts`, all `process.env.*` references in `src/`.
> [!warning] Many secrets in the checked-in `.env.*` files of the frontend are publicly visible (Alchemy key, WalletConnect ID, Google OAuth client ID, Sentry DSN). Rotate these immediately if the repo leaks. Anything not marked `NEXT_PUBLIC_` is **not** exposed to the browser.
---
## How env is loaded
### Backend
`src/shared/config/index.ts` calls `dotenv.config({ path: '.env.development' })` and then `dotenv.config()` (default `.env`). In Docker dev, `docker-compose.dev.yml` injects `env_file: .env.local` and in production `env_file: .env`. There is **no fallback**: if a required var is missing, the typed access (`process.env.JWT_SECRET!`) yields `undefined` and the service crashes on first use.
### Frontend
Next.js auto-loads `.env`, `.env.local`, `.env.development`, `.env.production` in the standard precedence order. Only variables prefixed `NEXT_PUBLIC_` are exposed to the browser bundle. The production Dockerfile **hard-codes** several `NEXT_PUBLIC_*` values via `ENV` directives at build time so they are baked into the static bundle (see [[Docker Setup#frontend-dockerfile]]).
---
## Database
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `MONGODB_URI` | backend | ✅ | — | `mongodb://mongodb:27017` | Mongo connection string (no auth in dev) |
| `DB_NAME` | backend | ✅ | — | `marketplace` | Database name appended to the URI |
In `docker-compose.production.yml` the Mongo service is `mongodb` and is reachable as `mongodb://mongodb:27017` from the backend container.
---
## Cache / Redis
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `REDIS_URI` | backend | ✅ | — | `redis://redis:6379` | Connection string used by `services/redis` |
| `REDIS_PASSWORD` | backend | prod only | — | `super-secret` | Substituted into the prod Redis command line (`--requirepass`) |
In dev, Redis runs without a password. In production the compose entrypoint is `redis-server --requirepass "$REDIS_PASSWORD"` so include `:password@` in `REDIS_URI` accordingly.
---
## Auth / JWT
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `JWT_SECRET` | backend | ✅ | — | 64 hex chars | HMAC key for access tokens |
| `JWT_EXPIRES_IN` | backend | ✅ | — | `1h` | Access token lifetime |
| `REFRESH_TOKEN_EXPIRES_IN` | backend | ✅ | — | `30d` | Refresh token lifetime |
| `ADMIN_EMAIL` | backend | optional | `admin@marketplace.com` | — | Email of the initial admin created by `init-admin` |
| `ADMIN_PASSWORD` | backend | optional | `Moji6364` | — | Password for the initial admin |
| `ADMIN_FIRST_NAME` | backend | optional | `Admin` | — | First name for the seeded admin |
| `ADMIN_LAST_NAME` | backend | optional | `User` | — | Last name for the seeded admin |
| `GOOGLE_CLIENT_ID` | backend | optional | — | `...apps.googleusercontent.com` | Verifies Google ID tokens server-side |
> [!warning] Rotate `JWT_SECRET` only during a maintenance window — every active session is invalidated.
---
## Email / SMTP
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `SMTP_HOST` | backend | ✅ | — | `smtp.zoho.com` | Outbound mail host |
| `SMTP_PORT` | backend | ✅ | — | `465` | TCP port (numeric) |
| `SMTP_SECURE` | backend | ✅ | — | `true` | `true` for TLS, `false` for STARTTLS |
| `SMTP_USER` | backend | ✅ | — | `no-reply@amn.gg` | SMTP username |
| `SMTP_PASS` | backend | ✅ | — | — | SMTP password (or app password) |
| `SMTP_FROM` | backend | ✅ | — | `"AMN" <no-reply@amn.gg>` | Default `From` header |
---
## Payments — SHKeeper
SHKeeper is the crypto payment gateway. See [[Payment Flow]] and [[SHKeeper Integration]] in the architecture section.
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `SHKEEPER_BASE_URL` | backend | ✅ | — | `https://shkeeper.example.com` | Base API URL |
| `SHKEEPER_API_URL` | backend | ✅ | — | `https://shkeeper.example.com/api/v1` | Versioned API URL |
| `SHKEEPER_API_KEY` | backend | ✅ | — | — | `X-Shkeeper-Api-Key` header |
| `SHKEEPER_WEBHOOK_SECRET` | backend | ✅ | — | — | HMAC secret for inbound webhook signatures |
| `SHKEEPER_CALLBACK_SECRET` | backend | ✅ | — | — | Older alias for webhook secret; some payloads still use it |
| `SHKEEPER_ALLOWED_TOKENS` | backend | optional | `USDT,USDC` | `USDT,USDC,BTC` | Comma-separated list of accepted tokens |
| `SHKEEPER_NETWORKS` | backend | optional | `bsc,polygon` | `bsc,polygon,eth` | Networks enabled in checkout |
| `SHKEEPER_ENVIRONMENT` | backend | optional | `production` | `sandbox` | Switches SHKeeper sandbox vs prod behaviour |
| `SHKEEPER_FORCE_PAYOUT_DEMO` | backend | optional | `false` | `true` | Skips real-chain payout; demo-confirms after 5s |
| `SHKEEPER_FORCE_REAL` | backend | optional | `false` | `true` | Forces real-chain even in dev/sandbox |
| `ADMIN_PAYOUT_WALLET_ADDRESS` | backend | ✅ for payouts | — | `0xAc23…` | Wallet that receives platform fees / payouts |
| `ESCROW_WALLET_ADDRESS` | backend | ✅ | — | `0xa304…` | Master escrow address used by payments service |
| `RECEIVER_WALLET_ADDRESS` | backend | optional | — | `0x…` | Used by alternative payout flows |
---
## Payments — DePay / Web3 (frontend)
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `NEXT_PUBLIC_DEPAY_INTEGRATION_ID` | frontend | ✅ for DePay | — | `1330e2d3-…` | DePay widget integration ID |
| `NEXT_PUBLIC_ESCROW_WALLET_ADDRESS` | frontend | ✅ | — | `0xa304…` | Escrow address shown to buyers in the wallet flow |
| `NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID` | frontend | ✅ | — | `283b54dd…` | WalletConnect v2 project ID |
| `NEXT_PUBLIC_ALCHEMY_API_KEY_MAINNET` | frontend | ✅ | — | — | Alchemy RPC for mainnet |
| `NEXT_PUBLIC_ALCHEMY_API_KEY_POLYGON` | frontend | ✅ | — | — | Alchemy RPC for Polygon |
| `NEXT_PUBLIC_ALCHEMY_API_KEY_SEPOLIA` | frontend | optional | — | — | Alchemy RPC for Sepolia (testing) |
---
## OAuth
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `GOOGLE_CLIENT_ID` | backend | optional | — | `…apps.googleusercontent.com` | Verifies Google ID tokens server-side |
| `NEXT_PUBLIC_GOOGLE_CLIENT_ID` | frontend | ✅ for Google login | — | `…apps.googleusercontent.com` | Client ID used by Google Identity Services in the browser |
---
## OpenAI
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `OPENAI_API_KEY` | backend | ✅ for AI features | — | `sk-…` | Used by `services/ai` |
| `OPENAI_DEFAULT_MODEL` | backend | ✅ for AI | `gpt-4o-mini` | `gpt-4o` | Default chat model |
| `OPENAI_MODEL` | backend | optional | falls back to default | `gpt-4o` | Per-call override read in legacy paths |
| `OPENAI_MAX_TOKENS` | backend | ✅ for AI | `1024` | `4096` | Hard cap per request |
| `OPENAI_TEMPERATURE` | backend | ✅ for AI | `0.7` | `0.2` | Decoded as float |
---
## App URLs
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `FRONTEND_URL` | backend | ✅ | — | `http://localhost:8083` | Used by CORS, Socket.IO `origin`, password-reset email links |
| `BACKEND_URL` | backend | optional | derived from `PORT` | `http://localhost:5001` | Used in webhook callbacks & emails |
| `API_URL` | backend | optional | `${BACKEND_URL}/api` | `http://localhost:5001/api` | Self-reference for outbound webhooks |
| `PORT` | backend | ✅ | — | `5001` | HTTP listen port |
| `TRUST_PROXY` | backend | optional | auto-on in prod | `true` | Enables `app.set('trust proxy', 1)` for Nginx |
| `NEXT_PUBLIC_APP_URL` | frontend | ✅ | — | `http://localhost:8083` | Self-URL used in metadata + OG tags |
| `NEXT_PUBLIC_APP_NAME` | frontend | optional | `AMN` | `ایسکرو آنلاین` | Display name in nav / titles |
| `NEXT_PUBLIC_APP_VERSION` | frontend | optional | `package.json` | `1.0.2` | Shown in the version logger |
| `NEXT_PUBLIC_API_URL` | frontend | ✅ | — | `http://localhost:5001/api` | Axios base URL |
| `NEXT_PUBLIC_API_BASE_URL` | frontend | optional | derived | `http://localhost:5001` | Used by a few legacy callers |
| `NEXT_PUBLIC_BACKEND_URL` | frontend | ✅ | — | `http://localhost:5001` | Used by file URL builders |
| `NEXT_PUBLIC_SERVER_URL` | frontend | optional | mirror of backend URL | `http://localhost:5001` | Server-side rendering fallback |
| `NEXT_PUBLIC_SOCKET_URL` | frontend | ✅ | — | `http://localhost:5001` | Socket.IO endpoint |
| `NEXT_PUBLIC_ASSETS_DIR` | frontend | optional | `""` | `/assets` | Prefix for static asset URLs |
| `NEXT_PUBLIC_MAPBOX_API_KEY` | frontend | optional | — | `pk.…` | Mapbox token for delivery map |
---
## Rate limiting / files
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `RATE_LIMIT_WINDOW_MS` | backend | ✅ | — | `900000` | Express-rate-limit window |
| `RATE_LIMIT_MAX_REQUESTS` | backend | ✅ | — | `100` | Requests allowed per window per IP |
| `MAX_FILE_SIZE` | backend | ✅ | — | `10485760` (10 MB) | Multer upload cap (bytes) |
| `UPLOAD_PATH` | backend | optional | `/app/uploads` | `/var/uploads` | Disk path for uploads (mounted volume) |
---
## Feature flags / seeding
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `AUTO_SEED_ON_START` | backend | optional | `false` | `true` | Re-seeds users/addresses/templates on boot if `users` is empty |
| `SEED_USERS` | backend | optional | `false` | `true` | One-shot user seeding flag honoured by `seedUsers.ts` |
| `FORCE_SEED_TEMPLATES` | backend | optional | `false` | `true` | Re-creates request templates even if some exist |
| `BUILD_STATIC_EXPORT` | frontend | optional | `false` | `true` | Static export build (currently unused) |
| `NEXT_PUBLIC_IS_DEVELOPMENT` | frontend | optional | `false` | `true` | Shows the dev-only banner & debug helpers |
| `NEXT_PUBLIC_ENABLE_DEBUG` | frontend | optional | `false` | `true` | Verbose console logging in the browser |
| `NEXT_TELEMETRY_DISABLED` | frontend | optional | `0` | `1` | Disables Next.js telemetry |
---
## Passkey / WebAuthn (frontend)
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `NEXT_PUBLIC_PASSKEY_RP_NAME` | frontend | optional | `Amn` | `Amn` | Relying-party display name |
| `NEXT_PUBLIC_PASSKEY_RP_ID` | frontend | optional | `localhost` | `amn.gg` | Relying-party origin host |
| `NEXT_PUBLIC_PASSKEY_ORIGIN` | frontend | optional | derived | `https://amn.gg` | Allowed origin for the WebAuthn challenge |
---
## Sentry
| Name | Repo | Required | Default | Example | Purpose |
|------|------|----------|---------|---------|---------|
| `NEXT_PUBLIC_SENTRY_DSN` | frontend | optional | — | `https://…ingest.sentry.io/…` | Browser & server Sentry DSN |
| `SENTRY_ORG` | frontend | build-time | — | `manawenuz` | Used by `@sentry/nextjs` source-map upload |
| `SENTRY_PROJECT` | frontend | build-time | — | `escrow-frontend` | Sentry project slug |
| `SENTRY_AUTH_TOKEN` | frontend | build-time | — | — | Auth token for source-map upload (CI secret) |
| `SENTRY_SUPPRESS_INSTRUMENTATION_FILE_WARNING` | frontend | optional | — | `1` | Silences known dev warning |
| `SENTRY_SUPPRESS_GLOBAL_ERROR_HANDLER_FILE_WARNING` | frontend | optional | — | `1` | Silences known dev warning |
| `SENTRY_DSN` | backend | optional | — | `https://…ingest.sentry.io/…` | Backend Sentry DSN (set in `src/config/sentry.ts`) |
The backend Sentry init runs **before** any other import (`src/app.ts` line 1) so DSN must be present in the env at process start.
---
## Quick `.env.local` template (backend, dev)
```bash
NODE_ENV=development
PORT=5001
TRUST_PROXY=false
# Database
MONGODB_URI=mongodb://mongodb:27017
DB_NAME=marketplace
# Cache
REDIS_URI=redis://redis:6379
# Auth
JWT_SECRET=<openssl rand -hex 32>
JWT_EXPIRES_IN=1h
REFRESH_TOKEN_EXPIRES_IN=30d
# URLs
FRONTEND_URL=http://localhost:8083
BACKEND_URL=http://localhost:5001
# Rate limit
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX_REQUESTS=100
# Files
MAX_FILE_SIZE=10485760
UPLOAD_PATH=/app/uploads
# SMTP
SMTP_HOST=smtp.example.com
SMTP_PORT=465
SMTP_SECURE=true
SMTP_USER=
SMTP_PASS=
SMTP_FROM="AMN <no-reply@amn.gg>"
# SHKeeper (set when ready)
SHKEEPER_BASE_URL=
SHKEEPER_API_URL=
SHKEEPER_API_KEY=
SHKEEPER_WEBHOOK_SECRET=
SHKEEPER_CALLBACK_SECRET=
SHKEEPER_FORCE_PAYOUT_DEMO=true
# OpenAI (optional)
OPENAI_API_KEY=
OPENAI_DEFAULT_MODEL=gpt-4o-mini
OPENAI_MAX_TOKENS=1024
OPENAI_TEMPERATURE=0.7
# Seeding
AUTO_SEED_ON_START=true
# Wallets
ESCROW_WALLET_ADDRESS=0xa3049825c0785095EEd5E7976E0E539466c84044
ADMIN_PAYOUT_WALLET_ADDRESS=
# OAuth
GOOGLE_CLIENT_ID=
```
> [!tip] Generate `JWT_SECRET` deterministically per environment so you don't accidentally invalidate sessions when restarting. Store it in your team's secret manager.

View File

@@ -0,0 +1,219 @@
---
title: Git Workflow
tags: [development]
---
# Git Workflow
How code moves from a developer's laptop to production.
---
## 1. Repositories & branches
Both repos are hosted on the self-hosted Gitea instance at **`git.manko.yoga`** (SSH on **port 222**, HTTPS on 443).
| Repo | Path | Branches |
|------|------|----------|
| Backend | `ssh://git@git.manko.yoga:222/nick/backend.git` | `main`, `development`, `feature/*`, `fix/*` |
| Frontend | `ssh://git@git.manko.yoga:222/nick/frontend.git` | `main`, `development`, `feature/*`, `fix/*` |
### Branch roles
| Branch | Role | Auto-deploy |
|--------|------|-------------|
| `main` (or `master`) | **Production.** Always deployable. | Yes — Gitea Actions builds + pushes `:latest`, Watchtower then pulls it. See [[CI-CD Pipeline]] and [[Deployment]]. |
| `development` | **Active dev.** Integration branch where features land before promotion. | Yes — builds + pushes `:dev` tag (not consumed by prod Watchtower). |
| `feature/<short-slug>` | One change in flight. Branched from `development`. | No |
| `fix/<short-slug>` | A bug fix. Branched from `development` (or `main` for a hotfix). | No |
| `hotfix/<slug>` | Urgent production fix branched from `main`. | No until merged |
```
main ─●────────●───────────────●───────► (prod, tagged v2.6.x)
▲ release merge
development ─●────●────●───●───●────●────●────●──► │
▲ ▲ ▲ ▲ ▲ │
│ │ │ │ │ │
feature/* branches off development │
hotfix/* ─────────────────────────────●──────────────┘
```
---
## 2. Day-to-day developer flow
```bash
# 1. Sync development
git checkout development
git pull --rebase
# 2. Create a branch
git checkout -b feature/add-seller-payouts
# 3. Hack
# ...
# 4. Verify locally
npm run lint && npm run typecheck && npm run test
# or for frontend
yarn lint && yarn test
# 5. Commit (Conventional Commits — see Coding Standards)
git commit -m "feat(payment): add seller payout history view"
# 6. Push & open PR
git push -u origin feature/add-seller-payouts
```
Open the PR in Gitea against `development`. CI will run on the PR if you have workflows enabled.
> [!tip] Use the `.gitmessage` template (`git config commit.template .gitmessage`) — it pre-fills the conventional-commit format with hints.
---
## 3. Commit message convention
Enforced on the frontend by `frontend/.commitlintrc.json` (and followed by convention on backend). See [[Coding Standards#commit-conventions]] for the full rules and examples.
Quick reference:
```
type(scope): subject
feat: add user authentication
fix(payment): handle missing tx hash
docs: clarify env vars table
chore: bump dependencies
feat!: redesign API endpoints # breaking change
```
Append `[skip-version]` to skip the auto-version bump when releasing.
---
## 4. PR review process
1. Open the PR with a description that covers **what**, **why**, and **how to test**.
2. Link to the related ticket / issue.
3. CI must be green (build, lint, tests where wired up).
4. **At least one reviewer** approves.
5. Squash-merge into `development` with a clean conventional-commit message.
6. Delete the source branch.
Reviewers check against [[Coding Standards#pr-review-checklist]].
> [!warning] Do **not** force-push to a PR branch after review starts — it invalidates review history. Use additional commits and let the squash-merge tidy up.
---
## 5. Releasing to production
The release path is `development → main → registry → production`.
```bash
# On a clean development that's ready to ship:
git checkout development
git pull --rebase
# Run AI-assisted version bump (chooses major/minor/patch from commit messages)
npm run smart-release
# This: bumps package.json, commits "chore: bump version to vX.Y.Z",
# creates a `vX.Y.Z` tag, and pushes commits + tags.
# Promote to main
git checkout main
git pull --rebase
git merge --ff-only development # fast-forward only — no merge commit
git push
```
What happens next:
- Pushing to `main` triggers the `docker-build-no-cache.yml` workflow on the backend (and `deploy.yml` on the frontend) — see [[CI-CD Pipeline]].
- That workflow builds the image and pushes both `:<version>` and `:latest` to `git.manko.yoga/manawenuz/escrow-backend`.
- **Watchtower** on the production host (see [[Deployment]]) polls the registry every interval, detects the new `:latest`, and rolls the production container.
### Manual builds
If you want to push a specific version without going through main, use:
```bash
# Backend
npm run release:patch # commits + tags + pushes; CI handles the rest
./scripts/build-and-push.sh # build locally and push dev tag
# Frontend
./scripts/deploy.sh # local build + push :latest
```
See [[Scripts]] for details.
---
## 6. Versioning
Semantic versioning, automated where possible.
- `package.json` `version` is the source of truth.
- The AI version scripts (`backend/scripts/ai-enhanced.sh`, `frontend/scripts/ai-enhanced.sh`) classify the last commit:
- `feat:` → minor
- `fix:` → patch
- `feat!:` or `BREAKING CHANGE:` → major
- `docs:`/`chore:`/`[skip-version]` → skip
- `auto-version.sh` applies the bump, commits, tags `v<version>`, and (with the npm wrappers) pushes.
Confidence levels (`high`/`medium`/`low`/`very-low`) gate the action — low confidence asks for a manual decision rather than auto-applying.
The CI workflows tag built images with both the `package.json` version and the moving tag (`:latest` for main, `:dev` for development).
---
## 7. Hotfix flow
```bash
# Branch from main
git checkout main && git pull --rebase
git checkout -b hotfix/critical-payment-bug
# Fix, test, commit
git commit -m "fix(payment): correct rounding on payout amount"
# Push + PR into main
git push -u origin hotfix/critical-payment-bug
# (open PR targeted at main; merge after review)
# After merge, port the fix back to development:
git checkout development && git pull --rebase
git merge --no-ff main
git push
```
Bump the patch version on `main` before merging so the new image gets a real tag, not just `:latest`.
---
## 8. SSH access on port 222
Gitea SSH listens on **222**, not 22, so the URL is always `ssh://git@git.manko.yoga:222/...`. Add this once to `~/.ssh/config`:
```ssh
Host git.manko.yoga
HostName git.manko.yoga
Port 222
User git
IdentityFile ~/.ssh/id_ed25519
```
Then `git clone git@git.manko.yoga:nick/backend.git` "just works" without the explicit `:222`.
Container registry uses standard ports — `git.manko.yoga/manawenuz/escrow-backend:latest` over HTTPS — authenticated by a personal access token (`GITEATOKEN` secret in CI).
---
## 9. Rules of thumb
> [!tip] Keep PRs small. Anything > 500 LOC of net change is a candidate for splitting.
> [!warning] Never commit `.env*` files containing real secrets. The repos do already have committed `.env.development` / `.env.production` with **public** values — if you add a real secret, treat it as leaked and rotate.
> [!warning] Never force-push `main` or `development`. If you absolutely must rewrite history, coordinate with the team and re-tag the affected versions.

View File

@@ -0,0 +1,273 @@
---
title: Local Setup
tags: [development]
---
# Local Setup
This guide walks you through running both repositories of the marketplace stack on your workstation. The platform is split into two services:
- **Backend** — Node.js 22+ / Express 5 / MongoDB 8 / Redis 8 / Socket.IO, served on port `5001`.
- **Frontend** — Next.js 16 / React 19 / MUI v7, served on port `8083` (or `3000` in Docker dev).
By the end of this page you will have the API running locally with MongoDB + Redis containers, a seeded set of test accounts, and the Next.js dashboard talking to it through your browser. For ongoing reference see [[Environment Variables]], [[Project Structure]], and [[Scripts]].
---
## 1. Prerequisites
Install the following before you start:
| Tool | Version | Why |
|------|---------|-----|
| Node.js | `>= 22` (backend), `>= 20` (frontend) | Runtime |
| Yarn | `1.22.22` (Classic) | Pinned via `packageManager` field |
| Docker Desktop | latest | Runs MongoDB + Redis + (optionally) backend/frontend |
| Git | `>= 2.40` | SSH-based clone from Gitea |
| OpenSSL | system default | For generating local secrets |
| `ngrok` (optional) | latest | For webhook testing — see [[Scripts#start-ngrok-sh]] |
> [!tip] Use a single Node version manager (`nvm`, `fnm`, or `volta`) and pin to `22`. Yarn Classic is required — do **not** upgrade to Berry, the lockfiles are incompatible.
You also need an SSH key registered with Gitea. The Git server runs on a non-standard port (`222`), so add an entry to `~/.ssh/config`:
```ssh
Host git.manko.yoga
HostName git.manko.yoga
Port 222
User git
IdentityFile ~/.ssh/id_ed25519
```
Verify connectivity:
```bash
ssh -T git@git.manko.yoga -p 222
```
You should see a Gitea welcome line. If you see "Permission denied (publickey)", upload your public key to your Gitea profile first.
---
## 2. Clone the repos
The two repos are siblings — keep them next to each other (the production compose file references `../frontend` from the backend folder):
```bash
mkdir -p ~/code && cd ~/code
git clone ssh://git@git.manko.yoga:222/nick/backend.git
git clone ssh://git@git.manko.yoga:222/nick/frontend.git
```
Switch each repo to the `development` branch:
```bash
cd ~/code/backend && git checkout development
cd ~/code/frontend && git checkout development
```
> [!warning] `main`/`master` is the production branch and is consumed by the Watchtower auto-update flow. Never push WIP commits there. See [[Git Workflow]].
---
## 3. Install dependencies
Backend uses **npm** for scripts but `yarn install` for lockfile parity with Docker, while the frontend is pure Yarn:
```bash
# Backend
cd ~/code/backend
yarn install --frozen-lockfile
# Frontend
cd ~/code/frontend
yarn install --frozen-lockfile
```
Both installs take 25 minutes on a cold cache. If `node-gyp`/`sharp` fail on macOS, install Xcode CLT (`xcode-select --install`).
---
## 4. Configure `.env` files
Each repo ships example files. Copy them and fill in secrets — full reference is in [[Environment Variables]].
### Backend (`/Users/mojtabaheidari/code/backend/.env.local`)
`docker-compose.dev.yml` reads `.env.local`. The container expects at minimum:
```bash
NODE_ENV=development
PORT=5001
MONGODB_URI=mongodb://mongodb:27017
DB_NAME=marketplace
REDIS_URI=redis://redis:6379
JWT_SECRET=$(openssl rand -hex 32)
JWT_EXPIRES_IN=1h
REFRESH_TOKEN_EXPIRES_IN=30d
FRONTEND_URL=http://localhost:8083
MAX_FILE_SIZE=10485760
UPLOAD_PATH=/app/uploads
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX_REQUESTS=100
```
For payments, OpenAI, SMTP, etc., refer to [[Environment Variables]].
### Frontend
Three env files already exist; pick the one that matches your scenario:
| File | When to use |
|------|-------------|
| `.env.local` | Local Next dev (`yarn dev`) — points at `http://localhost:5001` |
| `.env.development` | Docker compose dev — points at `dev.amn.gg` via Nginx |
| `.env.production` | Production build — points at `amn.gg` |
Next.js automatically picks `.env.local` for `next dev`. Do **not** check this file in if you change secrets.
---
## 5. Start the backend
You have two equivalent paths.
### Option A — All-in-Docker (recommended)
Builds the backend image, brings up MongoDB + Redis + backend on `nickapp-network`, and mounts `./src` for hot reload:
```bash
cd ~/code/backend
npm run docker:dev
```
Follow logs:
```bash
npm run docker:dev:logs
```
Stop:
```bash
npm run docker:dev:down
```
### Option B — Local Node + Docker datastores
Run only the datastores in Docker and the API on the host:
```bash
cd ~/code/backend
docker compose -f docker-compose.dev.yml up -d mongodb redis
npm run dev # ts-node + nodemon on port 5001
```
Override `MONGODB_URI=mongodb://localhost:27017` in `.env` if you take this route, since `mongodb` only resolves inside the compose network.
> [!tip] If port `5001` is already in use, set `PORT=5002` in `.env.local` and update `NEXT_PUBLIC_API_URL` in the frontend env to match.
---
## 6. Seed test data
Once MongoDB is healthy, populate it with default users, categories, addresses, and templates:
```bash
cd ~/code/backend
npm run seed:all # users + addresses (clears existing)
npm run seed:categories # marketplace taxonomy
```
`seed:all` creates the canonical test accounts (password `Moji6364` for all):
| Role | Email |
|------|-------|
| Admin | `admin@marketplace.com` |
| Buyer | `buyer@marketplace.com` |
| Seller | `seller@marketplace.com` |
| Seller (alt) | `seller2@marketplace.com` |
You can also enable auto-seeding on container start by adding `AUTO_SEED_ON_START=true` to `.env.local`. Auto-seed runs only when the `users` collection has no non-admin entries — safe to leave on.
See [[Scripts#seed-scripts]] for the full list (`seed:users`, `seed:addresses`, `seed:categories`, `seed:all`, plus `createSupportUser.ts`, `createTestRequest.ts`, etc.).
---
## 7. Start the frontend
```bash
cd ~/code/frontend
yarn dev
```
Next.js starts on **port 8083** (`next dev -p 8083 --turbopack`). The dashboard is at:
- http://localhost:8083 — landing
- http://localhost:8083/auth/jwt/sign-in — login
- http://localhost:8083/dashboard — authenticated area
If you used the Docker compose dev workflow with the production-mode container, port `3000` may also be exposed; check the compose output.
---
## 8. Verify
Run these smoke checks before you start coding:
```bash
# Backend health (should return JSON with success: true)
curl -s http://localhost:5001/health | jq .
# API root
curl -s http://localhost:5001/ | jq .
# Login (returns JWT)
curl -s -X POST http://localhost:5001/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@marketplace.com","password":"Moji6364"}' | jq .
```
In the browser, open http://localhost:8083, log in with `admin@marketplace.com / Moji6364`, and confirm the dashboard loads. If chat or notification badges show up, sockets connected too.
> [!tip] Tail backend logs in a separate terminal: `npm run docker:dev:logs`. Look for `✅ Connected to MongoDB`, `🔌 User connected`, and `🚀 Server running on port 5001`.
---
## 9. Common issues
| Symptom | Fix |
|---------|-----|
| `EADDRINUSE :::5001` | Another process owns the port — `lsof -i :5001` then `kill`, or change `PORT`. |
| `MongoServerError: Authentication failed` | The compose file does **not** set Mongo auth in dev; remove any `user:pass@` prefix from `MONGODB_URI`. |
| `ECONNREFUSED 127.0.0.1:6379` | Redis container is down — `docker compose -f docker-compose.dev.yml ps` to check. |
| CORS errors in the browser | `FRONTEND_URL` in backend `.env.local` must exactly match the origin you open in the browser (scheme + host + port). |
| `yarn install` hangs on `sharp` | Run `yarn config set network-timeout 600000` and retry. |
| `next dev` fails with module-not-found after a `git pull` | Run `yarn install` again — Next 16 is sensitive to drift in `react`/`react-dom`. |
| Sockets do not connect | Confirm `NEXT_PUBLIC_SOCKET_URL` matches the backend origin and that no browser extension blocks WebSockets. |
---
## 10. Quick reset
If your local state gets weird, the backend ships a one-shot reset script:
```bash
cd ~/code/backend
./scripts/reset-server.sh
```
This stops the dev compose stack, restarts it, runs health checks against MongoDB / Redis / `/health`, and probes the login endpoint with the seeded admin user. Output is colourised and ends with the canonical test credentials. See [[Scripts#reset-server-sh]] for details.
> [!warning] `reset-server.sh` does **not** drop volumes by default. To wipe the database, uncomment the `down -v` line in the script or run `docker compose -f docker-compose.dev.yml down -v` first.
---
## Next steps
- Walk the codebase via [[Project Structure]].
- Read [[Coding Standards]] before opening your first PR.
- Run the test suites — [[Testing]].
- Inspect what runs in each container — [[Docker Setup]] (Operations).
- For shipping changes through CI, see [[CI-CD Pipeline]] (Operations).

View File

@@ -0,0 +1,200 @@
---
title: Project Structure
tags: [development]
---
# Project Structure
A bird's-eye view of both repos. For deep dives, follow the cross-links to [[Backend Architecture]] and [[Frontend Architecture]].
---
## Backend — `/Users/mojtabaheidari/code/backend`
A service-oriented Express 5 app. Each business domain owns a folder under `src/services/` containing its routes, controllers, services, and (sometimes) its own models. Cross-cutting concerns live in `src/shared/` and `src/infrastructure/`.
```
backend/
├── src/
│ ├── app.ts # Express bootstrap: middleware, routes, Socket.IO, startup
│ ├── config/ # Sentry init (loaded before anything else)
│ ├── controllers/ # Thin HTTP controllers for orphan endpoints (disputes, points)
│ ├── routes/ # Router exports for orphan controllers above
│ ├── models/ # Mongoose schemas (single source of truth for data)
│ ├── infrastructure/
│ │ ├── database/ # Mongo connection + admin bootstrap
│ │ └── socket/ # Socket.IO server adapter & emitter helpers
│ ├── services/ # Domain services — see breakdown below
│ ├── shared/
│ │ ├── config/ # Typed `config` object (env loader)
│ │ ├── middleware/ # auth, errorHandler, rate limit, request logger
│ │ ├── types/ # Cross-domain type aliases
│ │ └── utils/ # Helpers reused across services
│ ├── utils/ # logger, currencyUtils, videoHelpers
│ ├── seeds/ # Idempotent data seeders
│ └── scripts/ # One-off operational scripts (TS + sh)
├── __tests__/ # Jest suites (see Testing)
├── scripts/ # Shell scripts (build/push, version, ngrok, reset)
├── nginx/ # Nginx conf (production compose)
├── mongo-init/ # Mongo initdb.d JS (one-time bootstrap)
├── uploads/ # User uploads — mounted as volume
├── Dockerfile.dev # Hot-reload image (ts-node + nodemon)
├── Dockerfile.prod # Multi-stage build image (compiled JS, non-root user)
├── docker-compose.dev.yml # Local stack: backend + mongo + redis
├── docker-compose.production.yml # Prod stack: nginx + backend + frontend + mongo + redis
├── .gitea/workflows/ # Gitea Actions CI
├── healthcheck.js # Container HEALTHCHECK probe
├── eslint.config.js # Flat ESLint config (TS strict)
├── jest.config.js # ts-jest preset
└── package.json
```
### `src/services/` folders
Each service folder follows the same shape: `<service>Routes.ts`, `<service>Controller.ts`, `<service>Service.ts`, sometimes a `<service>Repository.ts` and an `index.ts` barrel.
| Folder | Purpose |
|--------|---------|
| `address/` | CRUD for buyer/seller addresses (max 3 per user) |
| `admin/` | Admin-only data-cleanup + diagnostic endpoints |
| `ai/` | OpenAI-backed assistants (offer suggestions, content moderation) |
| `auth/` | Login, signup, refresh, Google OAuth, passkey/WebAuthn, temp verification |
| `blockchain/` | Generic wallet/chain helpers shared by payment providers |
| `blog/` | Public blog posts, comments, video helpers |
| `chat/` | Realtime messaging between buyers & sellers |
| `delivery/` | Delivery tracking + shipping events |
| `dispute/` | Dispute opening, evidence upload, arbitration |
| `email/` | Nodemailer service + transactional templates |
| `file/` | Multer uploads + Sharp image processing |
| `marketplace/` | Purchase requests, seller offers, accept/reject, categories |
| `notification/` | In-app notifications + delivery (socket + email) |
| `payment/` | Payment orchestration; sub-folders `shkeeper/`, `depay/`, `web3/` |
| `points/` | Reputation points + level config |
| `redis/` | Redis client wrapper (caching, rate counters) |
| `user/` | Profile, settings, role management |
### `src/models/`
Each `.ts` file is a Mongoose model — see [[Data Models]] for full schema docs. Highlights:
- `User`, `Address`, `Category` — identity & taxonomy
- `PurchaseRequest`, `SellerOffer`, `RequestTemplate` — marketplace core
- `Payment`, `PointTransaction`, `LevelConfig` — money + reputation
- `Chat`, `Notification`, `Dispute`, `Review`, `BlogPost`, `ShopSettings`, `TempVerification` — supporting domains
### `src/seeds/`
Idempotent, runnable via `npm run seed:*`. See [[Scripts#seed-scripts]].
---
## Frontend — `/Users/mojtabaheidari/code/frontend`
Next.js 16 App Router with the `src/` layout. The structure follows the Minimal v7 template: pages in `app/`, page-level UI in `sections/`, reusable atoms in `components/`, and a strong split between server data fetchers (`actions/`) and client UI.
```
frontend/
├── src/
│ ├── app/ # Next.js App Router — route groups: (auth) (dashboard) post shop
│ │ ├── layout.tsx # Root layout (providers, font, metadata)
│ │ ├── page.tsx # Landing page
│ │ ├── auth/ # /auth/* routes
│ │ ├── dashboard/ # /dashboard/* routes (authenticated)
│ │ ├── post/ # Public blog posts
│ │ ├── shop/ # Public shop pages
│ │ ├── error/ # Custom error route
│ │ ├── loading.tsx # Global loading skeleton
│ │ └── not-found.tsx # 404
│ ├── sections/ # Page-level UI grouped by domain
│ │ ├── account/ address/ blog/ chat/ dispute/ error/
│ │ ├── overview/ payment/ points/
│ │ ├── request/ request-template/ shop-settings/ user/
│ ├── components/ # Reusable UI atoms & molecules (MUI-based)
│ │ ├── hook-form/ # RHF-wrapped MUI inputs (RHFTextField, RHFSelect, …)
│ │ ├── iconify/ # The single icon component (use only this)
│ │ ├── animate/ carousel/ chart/ custom-* / nav-section/
│ │ ├── upload/ file/ image/ markdown/ editor/ map/ video-player/
│ │ └── … # see full list in the codebase
│ ├── contexts/ # Top-level React contexts (socket-context.tsx)
│ ├── hooks/ # Generic hooks: useBoolean, useSetState, useSnackbar, useSocket
│ ├── lib/ # axios.ts — singleton API client with interceptors
│ ├── locales/ # i18n: langs/, i18n-provider, server.ts, use-locales
│ ├── layouts/ # AppShell variants: auth-centered, auth-split, dashboard, main, simple
│ ├── theme/ # MUI theme (core + with-settings overrides)
│ ├── settings/ # Theme-switcher drawer + persisted user settings
│ ├── actions/ # Server-side / shared async API calls (axios)
│ ├── auth/ # JWT / OAuth / passkey context, guards, hooks, services
│ ├── socket/ # Socket.IO client, hooks, components, contexts
│ ├── web3/ # WalletConnect + Alchemy + DePay glue
│ ├── routes/ # Static path constants (paths object)
│ ├── utils/ # logger, format-number, format-time, localStorage, …
│ ├── types/ # Shared TS types (mirrors backend models where useful)
│ ├── assets/ # SVGs, illustrations
│ ├── _mock/ # Mock data for storybooks / tests
│ ├── global-config.ts # Read-only app config (name, version, paths)
│ └── global.css # Tailwind-less global styles
├── public/ # Static assets served at /
├── e2e/ # Playwright specs
├── __tests__/ # Jest + RTL specs (organised by domain)
├── scripts/ # Shell scripts (deploy, debug, version, console-log migration)
├── docs/ # In-repo design notes (not the Obsidian vault)
├── doc/ # Legacy docs folder
├── Dockerfile # Production multi-stage Next.js build
├── Dockerfile.dev # Dev image (yarn dev on port 3000)
├── next.config.ts # Standalone output, image domains, Sentry integration
├── playwright.config.ts # E2E test config
├── jest.config.js # Jest + jsdom + RTL
├── eslint.config.mjs # Flat ESLint with perfectionist sorting
├── prettier.config.mjs # Prettier config
├── .commitlintrc.json # Conventional commits enforcement
├── sentry.client.config.ts # Browser Sentry init
├── sentry.server.config.ts # Server runtime Sentry init
└── sentry.edge.config.ts # Edge runtime Sentry init
```
### `src/sections/` vs `src/components/`
A common confusion when first navigating the codebase:
- **components/** — generic, reused everywhere, no business logic. (e.g. `Iconify`, `RHFTextField`, `EmptyContent`.)
- **sections/** — page-level UI bound to a domain. (e.g. `sections/payment/CheckoutSummary.tsx`.) Sections may import from `components/` but not the other way around.
### `src/auth/` vs `src/socket/` vs `src/web3/`
These are feature-bundles that ship their own context, hooks, services, and components together. Treat each like an internal package — import only from its `index.ts` barrel.
### Routes & API
- `routes/paths.ts` — every URL the app navigates to, expressed as a typed object.
- `lib/axios.ts` — the single axios instance with auth interceptor and base URL from `NEXT_PUBLIC_API_URL`.
- `actions/*.ts` — async functions wrapping axios calls; consumed by both server components and client components.
---
## Repository layout (top-level)
```
~/code/
├── backend/ # this repo
├── frontend/ # this repo
└── docs/ # this Obsidian vault
```
The production `docker-compose.yml` lives in `backend/` but references `../frontend` for the frontend build context — keep both folders as siblings.
---
## Where to add new things
| You want to add… | Put it under… |
|---|---|
| A new public API route | `backend/src/services/<domain>/<domain>Routes.ts` (or a new domain folder) |
| A new Mongo schema | `backend/src/models/<Name>.ts` + export from `models/index.ts` |
| A reusable UI component | `frontend/src/components/<kebab-name>/` with `index.ts` + `component.tsx` + `types.ts` |
| A page-specific block | `frontend/src/sections/<domain>/` |
| A new dashboard page | `frontend/src/app/dashboard/<route>/page.tsx` |
| A shared hook | `frontend/src/hooks/use-<name>.ts` |
| A one-shot ops script | `backend/scripts/<name>.sh` (operational) or `backend/src/scripts/<name>.ts` (touches DB) |
| A seed script | `backend/src/seeds/<name>.ts` and add an `npm run seed:<name>` entry |
For the architectural rationale behind these splits see [[Backend Architecture]] and [[Frontend Architecture]].

365
07 - Development/Scripts.md Normal file
View File

@@ -0,0 +1,365 @@
---
title: Scripts
tags: [development]
---
# Scripts
A catalogue of every operational script across both repos. For each: **purpose**, **when to run**, and an **example invocation**. Scripts that touch the database have warnings.
> [!warning] Many of these scripts mutate live data (drop collections, push Docker tags, force version bumps). Read the script before running it on anything that matters.
---
## Backend — `backend/scripts/` (shell)
These shell scripts orchestrate the dev/build/release pipeline. They live next to the repo root and assume the repo is the current working directory.
### `auto-version.sh`
**Purpose.** Bump the `version` in `package.json` and create a matching git tag. Has an `auto` mode that asks `ai-enhanced.sh` to suggest the bump from the last commit message.
**When to run.** Before pushing a release. The `release:*` npm scripts call this for you.
**Example.**
```bash
./scripts/auto-version.sh patch # 2.6.3 → 2.6.4
./scripts/auto-version.sh minor # 2.6.3 → 2.7.0
./scripts/auto-version.sh major # 2.6.3 → 3.0.0
./scripts/auto-version.sh auto # let the AI decide
./scripts/auto-version.sh help
# or via npm wrappers:
npm run version:patch
npm run release:minor
npm run smart-release # auto + push + push --tags
```
The script creates a commit `chore: bump version to <v>` and tag `v<version>`.
> [!warning] Make sure your working tree is clean and on the branch you want tagged. The script will not stash changes.
---
### `ai-enhanced.sh`
**Purpose.** Analyse a commit message (last commit by default) against rules in `ai-rules.conf` and recommend `major`/`minor`/`patch`/`skip` plus a confidence level. Used by `auto-version.sh auto`.
**When to run.** Standalone diagnostic before a release; otherwise invoked indirectly.
**Example.**
```bash
./scripts/ai-enhanced.sh # analyse last commit
./scripts/ai-enhanced.sh "feat: add seller payouts" # analyse a string
npm run ai:detect
```
Exports `AI_VERSION_TYPE` and `AI_CONFIDENCE` for downstream scripts.
---
### `build-and-push.sh`
**Purpose.** Build the production Docker image (`Dockerfile.prod`) tagged with `dev-<package-version>` plus `latest`, then push to the Gitea registry.
**When to run.** For a manual dev release outside of CI. CI does the same thing automatically — see [[CI-CD Pipeline]].
**Example.**
```bash
./scripts/build-and-push.sh
```
You must `docker login git.manko.yoga -u manawenuz` first. Pushes both tags and asks whether to clean up the local images afterwards.
---
### `build-and-push-dev.sh`
**Purpose.** Variant of `build-and-push.sh` that tags only with the `dev-*` tag (no `latest`). Used when you want Watchtower to leave production untouched.
**When to run.** Manual dev-only push.
**Example.**
```bash
./scripts/build-and-push-dev.sh
```
---
### `build-image.sh`
**Purpose.** Local-only build (no push). Tags `<registry>/<image>:<version>` and `:latest`. Useful for testing the production Dockerfile locally without sending it anywhere.
**When to run.** Verifying a `Dockerfile.prod` change before pushing.
**Example.**
```bash
./scripts/build-image.sh
```
---
### `reset-server.sh`
**Purpose.** Stop the dev compose stack, restart it fresh, wait for services, then probe MongoDB, Redis, `/health`, and the login endpoint. Prints the seeded test credentials at the end.
**When to run.** When your local containers are in a weird state, or after pulling new images.
**Example.**
```bash
./scripts/reset-server.sh
```
> [!warning] By default this preserves volumes. To wipe the database, edit the script to uncomment `docker-compose ... down -v` or run that command manually first.
---
### `start-ngrok.sh`
**Purpose.** Start `ngrok http` against a local port (default `8083`) and print the public URL by polling the inspector at `127.0.0.1:4040`. Lets you receive SHKeeper webhooks on your laptop.
**When to run.** Local SHKeeper webhook development.
**Example.**
```bash
./scripts/start-ngrok.sh 5001 # tunnel to backend
./scripts/start-ngrok.sh # tunnel to default 8083
REGION=eu ./scripts/start-ngrok.sh
```
Logs go to `~/.ngrok-<port>.log` and the pid to `~/.ngrok-<port>.pid`. Requires `brew install ngrok` and `ngrok config add-authtoken <token>`.
---
### `fix-dispute-sellers.js`
**Purpose.** One-off Node script that walks the `disputes` collection and back-fills missing `sellerId` fields by joining through `purchaseRequests` + `sellerOffers`. Born from a real-world data-quality bug.
**When to run.** Only if disputes appear in admin UI without a seller. Otherwise: never.
**Example.**
```bash
MONGODB_URI=mongodb://localhost:27017/marketplace node ./scripts/fix-dispute-sellers.js
```
> [!warning] Mutates dispute documents. Take a backup first ([[Backup & Recovery]]).
---
## Backend — `backend/src/scripts/` (TypeScript + helpers)
These are runnable with `ts-node`. The `npm run seed:*` entries wrap the most common ones.
### Seed scripts
| Script | npm wrapper | Purpose |
|--------|-------------|---------|
| `seedUsers.ts` | `npm run seed:users` | Create the four canonical test users (admin, buyer, seller, seller2). Idempotent: skips users that already exist. |
| `seedAddresses.ts` | `npm run seed:addresses` | Wipe and re-create sample addresses for the test users (max 3 per user, primary flag set). **Requires users to exist.** |
| `seedUsersAndAddresses.ts` | `npm run seed:all` | Combined: drops and recreates users + addresses in the right order. The canonical "fresh database" command. |
| `seedCategories.ts` | `npm run seed:categories` | Insert the marketplace category tree. Idempotent. |
| `seeds/seedRequestTemplates.ts` | manual | Seed example request templates buyers can use as starting points. |
| `seeds/seedBlogPosts.ts` | manual | Seed sample blog content. |
| `seeds/seedLevels.ts` | manual | Insert level config for the points/reputation system. |
| `seeds/migrateUserPoints.ts` | manual | One-shot migration of legacy point balances. |
**Example.**
```bash
npm run seed:all
npm run seed:categories
ts-node src/seeds/seedRequestTemplates.ts
```
> [!warning] `seed:addresses` and `seed:all` drop existing data. Don't run against production unless that is what you want.
### Other TS helpers
| Script | Purpose |
|--------|---------|
| `clearCategories.ts` | Wipe the `categories` collection (paired with `seedCategories.ts`). |
| `clearChats.ts` | Wipe the `chats` collection. Useful in QA. |
| `createSupportUser.ts` | Create the marketplace support user (used in chat). |
| `createTestMessage.ts` | Insert a single test chat message between buyer and seller. |
| `createTestRequest.ts` | Create a sample purchase request from the seeded buyer. |
| `updateCategories.ts` | Apply schema updates to existing categories without re-seeding. |
| `updateRequestStatus.ts` | Force-set the `status` field of a purchase request — useful when QA-ing a flow. |
| `makeUserAdmin.ts` | Promote a user to admin. Pass email as an arg. |
| `createDemoShops.ts` | Generate demo seller shops for screenshots / staging. |
**Example.**
```bash
ts-node src/scripts/makeUserAdmin.ts user@example.com
ts-node src/scripts/createTestRequest.ts
ts-node src/scripts/updateRequestStatus.ts <requestId> completed
```
### Bash helpers under `src/scripts/`
These call the API with `curl` to drive larger demo-data flows:
- `createDemoTemplates.sh`, `createDemoWithPicsum.sh`, `createFullDemo.sh`, `createProductionTemplates.sh` — populate templates with placeholder data
- `deleteSellerTemplates.sh` — wipe templates owned by a given seller
- `updateShopImages.sh` — bulk-update shop images
Each script takes a base URL + admin token. Inspect them before running.
---
## Backend — root-level helpers
### `manual-test.ts`
**Purpose.** Local sanity check for the SHKeeper service: calls `createPayInIntent` with mock data and verifies a webhook signature in dev mode.
**When to run.** Smoke-test after changing SHKeeper code without running the full suite.
**Example.**
```bash
ts-node manual-test.ts
```
### `manual-payout-test.ts`
**Purpose.** POST `/api/payments/payout` to the local backend with hard-coded payout data, then poll status after 5 s.
**When to run.** End-to-end payout flow check against a running local server.
**Example.**
```bash
# Backend must be running first:
npm run dev &
ts-node manual-payout-test.ts
```
> [!warning] Will create a real payout record in the DB. With `SHKEEPER_FORCE_PAYOUT_DEMO=true` no on-chain transaction is sent; without that flag a real on-chain transfer can occur.
### `fix-transaction-hashes.js`
**Purpose.** One-off backfill — walks completed Payments missing `transactionHash`, queries SHKeeper for the original invoice, extracts the confirmed transaction hash, and updates the payment document.
**When to run.** Only if you see payments displayed as "completed" with a missing tx hash. Rate-limits itself with a 1s delay per record.
**Example.**
```bash
MONGODB_URI=mongodb://localhost:27017/marketplace \
SHKEEPER_BASE_URL=https://shkeeper.example.com \
SHKEEPER_API_KEY=... \
node fix-transaction-hashes.js
```
> [!warning] Hits the live SHKeeper API and writes to MongoDB. Take a backup ([[Backup & Recovery]]).
### `check-templates.js`, `get-admin-token.js`
Diagnostic helpers. `check-templates.js` lists templates in the DB; `get-admin-token.js` logs in as the seeded admin and prints the JWT.
---
## Frontend — `frontend/scripts/`
### Deployment
| Script | Purpose | When |
|--------|---------|------|
| `deploy.sh` | Build production image, tag with package version + `latest`, push to Gitea registry. Triggered by Gitea Actions (`deploy.yml`) on push to `main`. | Production release. |
| `deployDev.sh` | Same as `deploy.sh` but pushes only the `dev` tag. Triggered by `devDeploy.yml` on push to `development`. | Dev release. |
### Versioning
| Script | Purpose |
|--------|---------|
| `auto-version.sh` | Mirror of backend `auto-version.sh` for the frontend. |
| `ai-enhanced.sh` | Mirror of backend AI version detector. Reads `ai-rules.conf`. |
| `ai-version-detect.sh` | Legacy / simpler version detector kept for fallback. |
| `setup-versioning.sh` | One-shot setup: installs a `post-commit` git hook that prints the recommended version bump. |
| `post-commit-hook.sh` | The hook installed by `setup-versioning.sh`. |
### Debugging & maintenance
| Script | Purpose |
|--------|---------|
| `debug-env.sh` | Print every `NEXT_PUBLIC_*` env var currently exported. |
| `debug-ui-api.sh` | Curl the backend health, auth, and a handful of marketplace endpoints; reports each. |
| `cleanup-docker-networks.sh` | Prune dangling Docker networks left behind by failed compose runs. |
| `show-credentials.sh` | Print the seeded test credentials (admin/buyer/seller). |
| `find-console-logs.sh` | List every `console.log/info/warn/error` left in `src/`. |
| `clean-console-logs.sh` | Auto-remove or comment out console logs. |
| `migrate-all-console-logs.sh` | Convert `console.*` calls to `logger.*`. Read `migrate-to-logger.md` before running. |
### Testing helpers
| Script | Purpose |
|--------|---------|
| `quick-test-suite.sh` | Run a curated subset of Jest + Playwright tests for a fast PR check. |
| `integration-test.sh` | Heavier scripted integration run against a live backend. |
| `test-marketplace-workflow.js` | Node script that drives a buyer → seller → checkout → payout flow via the API. |
| `test-offer-rejection.js` | Reproduce the offer-rejection bug pattern; useful for regression checks. |
**Examples.**
```bash
yarn ./scripts/debug-env.sh
./scripts/debug-ui-api.sh http://localhost:5001
./scripts/quick-test-suite.sh
node ./scripts/test-marketplace-workflow.js
```
---
## Frontend — root-level helpers
### `cleanup-localstorage.js`
**Purpose.** Browser console snippet — paste into DevTools to clear stale `manual-payment-*`, `buyer-step-*`, `LAST_PAYMENT_*`, `LAST_TEMPLATE_*`, and `LAST_SOCKET_*` keys from `localStorage`.
**When to run.** When QA reports state-related glitches that disappear in incognito.
**Example.** Paste the entire file body into the browser console and press Enter.
---
## Summary cheat-sheet
```bash
# === Backend ===
npm run dev # local node, no docker
npm run docker:dev # full stack in docker
npm run docker:dev:down # tear it down
npm run docker:dev:logs # follow logs
npm run seed:all # reset users + addresses (DESTRUCTIVE)
npm run seed:categories # idempotent
npm run test # jest
npm run test:coverage # + coverage
./scripts/reset-server.sh # nuke and rebuild dev compose
npm run release:patch # bump + commit + tag + push
npm run smart-release # AI-decided bump + push
./scripts/build-and-push.sh # manual docker push (dev tag)
# === Frontend ===
yarn dev # next dev on 8083
yarn build && yarn start # production-mode locally
yarn test # jest
yarn test:e2e # playwright
./scripts/deploy.sh # manual prod deploy push
./scripts/debug-ui-api.sh # curl smoke tests
```

262
07 - Development/Testing.md Normal file
View File

@@ -0,0 +1,262 @@
---
title: Testing
tags: [development]
---
# Testing
Both repos use **Jest** as the unit/integration runner. The frontend additionally uses **React Testing Library** for component tests and **Playwright** for end-to-end browser tests. This page covers what exists today, how to run it, and how to add new tests.
---
## Backend testing
### Stack
- **Jest 29** + **ts-jest 29** — TypeScript transpilation on the fly
- **supertest 7** — HTTP assertions against the Express app
- **mongodb-memory-server 10** — in-memory MongoDB per test run (no Docker needed)
### Jest configuration
`backend/jest.config.js`:
- Preset: `ts-jest`
- Environment: `node`
- Test glob: `**/__tests__/**/*.test.ts` and `**/?(*.)+(spec|test).ts`
- `setupFilesAfterEach`: `__tests__/setup.ts` — boots `mongodb-memory-server`, connects mongoose, and cleans collections between tests
- `maxWorkers: 1` — tests run serially (DB-state-sensitive)
- `testTimeout: 30000`
### Test suites
`backend/__tests__/` contains:
| File | What it covers |
|------|----------------|
| `basic.test.ts` | Smoke test of the bootstrap |
| `file-service.test.ts` | Upload + Sharp image-processing pipeline |
| `payment-integration.test.ts` | End-to-end pay-in / pay-out across providers |
| `payment-system.test.ts` | Payment service unit tests |
| `shkeeper-webhook.test.ts` | Signature verification + status transition |
| `simple-marketplace.test.ts` | Purchase-request + offer flow |
| `simple-payment.test.ts` | Single-provider payment fast-path |
| `simple-user.test.ts` | Auth + signup + JWT issuance |
| `setup.ts` | Shared Jest setup (DB, env vars, helpers) |
There are also four large aggregate suites referenced in `package.json` (some may live in branches or be reintroduced as the codebase evolves):
- `models.test.ts` — every Mongoose schema, validation, indexes, relationships
- `payment-services.test.ts` — DePay, SHKeeper, Web3, admin operations
- `complete-backend.test.ts` — Auth, marketplace, chat, notification, address, user, file, email, AI
- `shkeeper-backend.test.ts` — Service layer + API endpoints for SHKeeper
### Commands
```bash
cd ~/code/backend
npm run test # run all *.test.ts files once (forceExit on)
npm run test:watch # interactive watch mode
npm run test:coverage # also emit coverage report to ./coverage/
npm run test:all # explicit __tests__/ folder
# Focused suites (each maps to a single file):
npm run test:models # jest __tests__/models.test.ts
npm run test:payment # jest __tests__/payment-services.test.ts
npm run test:complete # jest __tests__/complete-backend.test.ts
npm run test:shkeeper # jest __tests__/shkeeper-backend.test.ts
```
Pass extra Jest flags after `--`:
```bash
npm run test -- --testPathPattern=payment --verbose
```
### Coverage targets
- Statements & lines: **≥ 80 %** on changed files
- Branches: **≥ 70 %** on changed files
- Critical paths (auth, payment, escrow release) — aim for **≥ 90 %**
Coverage is collected from `src/**/*.ts` excluding `.d.ts` and `__tests__/`. View the HTML report at `coverage/lcov-report/index.html` after running `npm run test:coverage`.
### Adding a new backend test
1. Place file under `__tests__/` (or colocated `*.test.ts` next to the source).
2. Import the app and use `supertest`:
```ts
import request from 'supertest';
import { app } from '../src/app';
describe('GET /api/health', () => {
it('returns 200', async () => {
const res = await request(app).get('/health');
expect(res.status).toBe(200);
expect(res.body.success).toBe(true);
});
});
```
3. Use the in-memory DB — connections are wired in `setup.ts`. Each test starts with a clean collection.
4. Mock outbound HTTP (SHKeeper, OpenAI) with `jest.spyOn(axios, 'post')`. Never hit a real provider from tests.
> [!warning] `maxWorkers: 1` makes tests serial. Don't introduce timing-sensitive parallelism — instead, keep individual tests small and deterministic.
---
## Frontend testing
### Stack
- **Jest 29** + **ts-jest 29** + **jsdom 29** — component & util tests
- **@testing-library/react 16** + **@testing-library/jest-dom 6** + **@testing-library/user-event 14**
- **Playwright 1.56** — browser-driven E2E
- **identity-obj-proxy** — stubs CSS module imports
### Jest configuration
`frontend/jest.config.js`:
- Environment: `jsdom`
- Roots: `<rootDir>/src` and `<rootDir>/__tests__`
- Test globs: `**/__tests__/**/*.test.(ts|tsx|js)`, `**/__tests__/**/*.spec.(ts|tsx|js)`, `**/*.(test|spec).(ts|tsx|js)`
- Asset mapping: images & fonts → `__tests__/mocks/fileMock.js`, CSS → `identity-obj-proxy`
- Module aliases: `src/*``<rootDir>/src/*`
- `setupFilesAfterEach`: `jest.setup.js` (sets up RTL matchers, axios mocks, env)
### Test layout — `__tests__/`
Tests are grouped by domain:
```
__tests__/
├── account-test/
├── address-test/
├── auth-test/
├── chat-test/
├── components-test/
├── file-test/
├── integration-test/
├── marketplace-test/
├── notification-test/
├── payment-test/
├── user-test/
├── utils-test/
└── mocks/
```
Each folder contains one or more `*.test.tsx` files. See `__tests__/README.md`, `PROJECT_TEST_CHECKLIST.md`, and `TEST_ORGANIZATION_SUMMARY.md` in the repo for current status.
### Commands
```bash
cd ~/code/frontend
yarn test # full Jest suite
yarn test -- --watch # watch mode
yarn test -- --coverage # coverage report
yarn test -- payment # name-pattern filter
```
### Playwright E2E
`frontend/e2e/` contains four headless-Chromium suites:
| File | Coverage |
|------|----------|
| `auth.spec.ts` | Sign up, login, logout, password reset |
| `marketplace.spec.ts` | Browse, create request, accept offer |
| `shop.spec.ts` | Public shop pages |
| `performance.spec.ts` | Performance budgets (LCP, INP) |
```bash
yarn test:e2e # headless run
yarn test:e2e:ui # open Playwright Inspector
yarn test:e2e:headed # show the browser
yarn test:e2e:debug # step through with devtools
yarn test:perf # only e2e/performance.spec.ts
yarn playwright:install # one-time browser download
```
Playwright assumes the backend + frontend are reachable at the URLs in `playwright.config.ts` (defaults: `http://localhost:8083` and `http://localhost:5001`). Start both stacks first — see [[Local Setup]].
### Coverage targets (frontend)
- Components: **≥ 70 %** statement coverage
- Hooks / utilities: **≥ 90 %**
- Critical flows (login, checkout): covered by both unit and Playwright suites
### Adding a new component test
1. Colocate `MyComponent.test.tsx` next to `MyComponent.tsx`, or place in `__tests__/<domain>-test/`.
2. Render with RTL and assert via accessible queries:
```tsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ThemeProvider } from 'src/theme/theme-provider';
import { MyComponent } from './MyComponent';
const renderWithProviders = (ui: React.ReactElement) =>
render(<ThemeProvider>{ui}</ThemeProvider>);
it('submits the form', async () => {
renderWithProviders(<MyComponent />);
await userEvent.type(screen.getByLabelText(/email/i), 'a@b.com');
await userEvent.click(screen.getByRole('button', { name: /submit/i }));
expect(screen.getByText(/thanks/i)).toBeInTheDocument();
});
```
3. Mock `lib/axios` if the component makes API calls:
```ts
import api from 'src/lib/axios';
jest.mock('src/lib/axios');
const mockedApi = api as jest.Mocked<typeof api>;
mockedApi.post.mockResolvedValueOnce({ data: { success: true } });
```
### Adding a Playwright test
1. Add `e2e/<feature>.spec.ts`:
```ts
import { test, expect } from '@playwright/test';
test('user can log in', async ({ page }) => {
await page.goto('/auth/jwt/sign-in');
await page.getByLabel('Email').fill('admin@marketplace.com');
await page.getByLabel('Password').fill('Moji6364');
await page.getByRole('button', { name: 'Login' }).click();
await expect(page).toHaveURL(/\/dashboard/);
});
```
2. Run `yarn test:e2e:headed` to debug.
3. Keep specs idempotent — clean up any test data the spec creates (or rely on the seeded test users).
---
## CI integration
The Gitea Actions workflows (see [[CI-CD Pipeline]]) currently build and push Docker images but do not yet run Jest. If you add test gating, add a `Run tests` step before the `Build` step:
```yaml
- name: Install
run: yarn install --frozen-lockfile
- name: Test
run: yarn test --ci --runInBand
```
---
## Debugging tips
- **Backend test hangs** — add `--detectOpenHandles --forceExit`. Almost always a forgotten `mongoose.disconnect()` or open Redis client. The shared `setup.ts` handles this, but custom suites might not.
- **Frontend test fails on `window.matchMedia`** — `jest.setup.js` polyfills it; if you add a new test runner config, copy that polyfill.
- **Playwright flaky** — use `await expect(...).toBeVisible()` rather than `waitForSelector` and increase per-test timeout in `playwright.config.ts` for slow flows.
- **Coverage low but the test exists** — make sure the file is in `collectCoverageFrom` and not excluded by an `index.ts` filter.