Initial commit: nick docs
This commit is contained in:
380
07 - Development/Coding Standards.md
Normal file
380
07 - Development/Coding Standards.md
Normal 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 |
|
||||
276
07 - Development/Environment Variables.md
Normal file
276
07 - Development/Environment Variables.md
Normal 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.
|
||||
219
07 - Development/Git Workflow.md
Normal file
219
07 - Development/Git Workflow.md
Normal 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.
|
||||
273
07 - Development/Local Setup.md
Normal file
273
07 - Development/Local Setup.md
Normal 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 2–5 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).
|
||||
200
07 - Development/Project Structure.md
Normal file
200
07 - Development/Project Structure.md
Normal 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
365
07 - Development/Scripts.md
Normal 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
262
07 - Development/Testing.md
Normal 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.
|
||||
Reference in New Issue
Block a user