Skip to content

Latest commit

 

History

History
563 lines (404 loc) · 18 KB

File metadata and controls

563 lines (404 loc) · 18 KB

Contributing to stellar_Earn

Thank you for contributing to stellar_Earn! This guide covers the standards every contributor must follow to keep the codebase consistent, secure, and maintainable.


Table of Contents

  1. Getting Started
  2. Branch & Commit Conventions
  3. Pull Request Requirements
  4. NestJS Best Practices
  5. Testing Standards
  6. Swagger / API Documentation
  7. Smart Contract (Rust / Soroban)
  8. Reviewer Checklist
  9. Labels
  10. Script Inventory

1. Getting Started

Prerequisites

Tool Min Version Required? Notes
Node.js ≥ 22 ✅ Required Use nvm or fnm
npm ≥ 10 ✅ Required Bundled with Node 22+
Bun ≥ 1 ⚠️ Recommended Used for migration commands (bun run migration:*)
TypeScript (tsc) ≥ 5 ✅ Required Installed via npm ci as devDependency
Git ≥ 2 ✅ Required
Rust / Cargo ≥ 1.80 (stable) ✅ Required Install via rustup
PostgreSQL ≥ 14 ✅ Required Local dev; Docker image works fine
Redis ≥ 7 ✅ Required Local dev; Docker image works fine
Docker ≥ 24 ⚠️ Recommended Easiest way to run Postgres + Redis locally
Stellar CLI (stellar) ≥ 22 ⚠️ Recommended Required to build/deploy Soroban contracts

Automated check: Run the onboarding preflight script to validate all CLI tool versions in one go:

cd BackEnd && npm install && npm run check:toolchain

Required tools failing → exit code 1. Recommended tools failing → warning only.

Local Setup

# Backend
cd BackEnd
cp .env.example .env          # Fill in your local values
npm install
npm run check:toolchain       # Verify your local toolchain first
npm run typeorm:run-migrations
npm run start:dev

# Frontend
cd FrontEnd/my-app
npm install
npm run dev

# Contracts
cd Contract
cargo build --workspace

Lockfile Policy

This project uses npm as the package manager for the frontend (FrontEnd/my-app). The package-lock.json file must be committed to the repository and kept up to date.

  • Always run npm install (never yarn, pnpm, or bun) inside FrontEnd/my-app.
  • Commit package-lock.json whenever you add, remove, or update dependencies. CI runs npm ci, which requires a valid lockfile.
  • Do not delete or add package-lock.json to .gitignore.
  • If you see a merge conflict in package-lock.json, resolve it by running npm install after resolving package.json, then commit the regenerated lockfile.

2. Branch & Commit Conventions

Branch Naming

<type>/<short-description>
Type When to use
feat/ New feature
fix/ Bug fix
chore/ Tooling, CI, dependencies
docs/ Documentation only
refactor/ Code restructuring without behavior change
test/ Tests only
security/ Security-related fix

Example: feat/quest-reward-distribution

Commit Messages

Follow Conventional Commits:

<type>(scope): <short description>

[optional body]

[optional footer: Closes #<issue>]

Example:

feat(quests): add reward distribution via Soroban contract

Implements payout logic triggered on quest completion.
Uses BullMQ job queue to handle async Stellar transactions.

Closes #42

3. Pull Request Requirements

All PRs must use the PR template. PRs that skip required sections or are not linked to an issue will be blocked from merging.

Mandatory Before Opening a PR

  • Branch is up to date with main
  • Linked to an open GitHub issue
  • PR template fully filled out (no placeholder text left)
  • All CI checks pass (lint, format, build, tests)
  • Self-reviewed every line of the diff

PR Size Guidelines

Keep PRs focused. If a PR touches more than ~400 lines (excluding migrations and generated files), consider splitting it. Large PRs take much longer to review and are more likely to introduce bugs.

Documentation Placement

Backend documentation belongs under BackEnd/docs/. Do not commit ad-hoc status/report markdown files to the root of BackEnd/ or the repository root — instead consolidate new content into an existing doc, create a topic-appropriate doc under BackEnd/docs/, or record the change in the relevant module CHANGELOG.md. One-off status reports that no longer reflect current state should not be committed at all.

10. Script Inventory

Repository-maintained scripts are tracked in docs/script-inventory.md. When you add or change a script, update that inventory with:

  • its purpose
  • the owning team or function
  • its lifecycle state
  • any notable maintenance or verification notes

4. NestJS Best Practices

4.1 DTO Validation

Every public endpoint must validate its input through a DTO. Raw, unvalidated request data must never reach the service layer.

Rules

  • Create a dedicated DTO file: src/modules/<module>/dto/<action>-<resource>.dto.ts
  • Use class-validator decorators for all fields
  • Use class-transformer decorators for type coercion and sanitization
  • Mark optional fields with @IsOptional() before all other decorators
  • Use @ApiProperty / @ApiPropertyOptional on every field for Swagger

Example

import { IsString, IsNotEmpty, IsOptional, IsUUID, MaxLength } from 'class-validator';
import { Transform } from 'class-transformer';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';

export class CreateQuestDto {
  @ApiProperty({ description: 'Quest title', maxLength: 120 })
  @IsString()
  @IsNotEmpty()
  @MaxLength(120)
  @Transform(({ value }) => value?.trim())
  title: string;

  @ApiPropertyOptional({ description: 'Optional description' })
  @IsOptional()
  @IsString()
  @MaxLength(1000)
  description?: string;

  @ApiProperty({ description: 'Reward in stroops' })
  @IsUUID()
  rewardAssetId: string;
}

Do NOT

// Never access req.body directly in a service or controller without a DTO
@Post()
create(@Body() body: any) { ... } // untyped body

4.2 Error Handling & Exceptions

Use the appropriate NestJS HTTP exception class. Never throw a generic Error from a controller or service.

Exception Reference

Situation Exception Class
Resource not found NotFoundException
Malformed / invalid input BadRequestException
Unauthenticated request UnauthorizedException
Authenticated but not allowed ForbiddenException
Duplicate / conflict state ConflictException
Unprocessable business logic UnprocessableEntityException
Upstream / third-party failure ServiceUnavailableException

Example

import { NotFoundException, ForbiddenException } from '@nestjs/common';

async findQuest(id: string, userId: string): Promise<Quest> {
  const quest = await this.questsRepository.findOne({ where: { id } });

  if (!quest) {
    throw new NotFoundException(`Quest ${id} not found`);
  }

  if (quest.ownerId !== userId) {
    throw new ForbiddenException('You do not have access to this quest');
  }

  return quest;
}

Stellar / Soroban Errors

Wrap all contract and Horizon calls in try/catch and translate failures into NestJS exceptions:

try {
  await this.sorobanService.invokeContract(payload);
} catch (err) {
  this.logger.error('Soroban contract invocation failed', err.stack);
  throw new ServiceUnavailableException('Blockchain transaction failed. Please retry.');
}

Do NOT

throw new Error('Quest not found');              // generic Error
throw new HttpException('Forbidden', 403);       // magic status code
if (!quest) return null;                         // silent failure

4.3 Logging

The project uses Winston via nest-winston. Inject LoggerService from @nestjs/common — do not use console.log anywhere in production code.

Log Levels

Level When to use
error Exceptions, failed external calls, unexpected states
warn Recoverable issues, deprecated usage, rate limit hits
log (info) Significant lifecycle events (service started, job completed)
debug Detailed flow tracing — only in development (NODE_ENV=development)
verbose High-frequency events — disabled in production

Example

import { Injectable, Logger } from '@nestjs/common';

@Injectable()
export class QuestsService {
  private readonly logger = new Logger(QuestsService.name);

  async completeQuest(id: string): Promise<void> {
    this.logger.log(`Completing quest ${id}`);

    try {
      await this.processReward(id);
      this.logger.log(`Quest ${id} completed and reward dispatched`);
    } catch (err) {
      this.logger.error(`Failed to complete quest ${id}`, err.stack);
      throw err;
    }
  }
}

Never Log

  • Passwords, JWT tokens, or refresh tokens
  • Stellar secret keys (S...) or contract IDs that are sensitive
  • Full request/response bodies containing user PII
  • Raw database query results with credentials

The global LoggerMiddleware already handles request/response logging — do not duplicate it.


4.4 Guards & Authorization

Authentication

All endpoints requiring a signed-in user must be protected:

import { UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from 'src/common/guards/jwt-auth.guard';

@UseGuards(JwtAuthGuard)
@Get('profile')
getProfile(@CurrentUser() user: User) { ... }

Public Endpoints

Use the @Public() decorator to explicitly opt out of authentication — do not remove or disable guards:

import { Public } from 'src/common/decorators/public.decorator';

@Public()
@Get('health')
healthCheck() { ... }

Admin Endpoints

Admin-restricted routes must use the admin guard in addition to JwtAuthGuard:

@UseGuards(JwtAuthGuard, AdminGuard)
@Delete('quests/:id')
adminDeleteQuest(@Param('id') id: string) { ... }

Throttler

The global AppThrottlerGuard applies to all routes. If a specific endpoint needs a custom limit use @Throttle():

import { Throttle } from '@nestjs/throttler';

@Throttle({ default: { limit: 5, ttl: 60000 } })
@Post('auth/login')
login(@Body() dto: LoginDto) { ... }

Do NOT

// Never bypass guards with role checks inside the handler
@Get('admin/users')
getUsers(@CurrentUser() user: User) {
  if (user.role !== 'admin') throw new ForbiddenException(); // use a guard instead
}

5. Testing Standards

Unit Tests

  • Co-locate spec files with the source: <name>.service.spec.ts
  • Mock all external dependencies (repositories, external services, config)
  • Aim for ≥ 80% branch coverage on all new services
  • Run before pushing: npm run test:cov

E2E Tests

  • Located in BackEnd/test/
  • Use supertest against a test application instance
  • Each test must clean up its own database state

Test Naming Convention

describe('QuestsService', () => {
  describe('completeQuest', () => {
    it('should dispatch a reward when quest exists and is pending', async () => { ... });
    it('should throw NotFoundException when quest does not exist', async () => { ... });
    it('should throw ForbiddenException when user does not own the quest', async () => { ... });
  });
});

6. Swagger / API Documentation

  • Run the app and verify changes at http://localhost:3000/api/docs
  • Every controller method must have @ApiOperation({ summary: '...' })
  • Every possible response status must have an @ApiResponse decorator
  • DTOs must have @ApiProperty on every field — use example: to show realistic values
  • Protected endpoints must have @ApiBearerAuth()

7. Smart Contract (Rust / Soroban)

  • All contract changes must pass cargo test --workspace and cargo clippy -- -D warnings

  • Snapshot tests in contracts/earn-quest/test_snapshots/ must be updated when behavior changes

  • Never expose raw contract errors to API consumers — translate them in the NestJS service layer

  • Soroban RPC calls must be async and non-blocking to the main request thread (use BullMQ jobs)

  • Changes to contracts/earn-quest/src/** must update contracts/earn-quest/CHANGELOG.md

  • Contract-breaking changes must use Conventional Commit breaking metadata (type(scope)!:) and a BREAKING CHANGE: explanation

  • Contract-breaking changelog entries must live under ## [Unreleased] -> ### Breaking Changes and include impact, affected files, and migration steps

  • See contracts/earn-quest/docs/CHANGELOG_DISCIPLINE.md for the full policy and CI-enforced expectations


8. Reviewer Checklist

Use this checklist when reviewing any PR targeting the NestJS backend:

Architecture & Structure

  • Module boundaries respected — no cross-module direct repository access
  • Business logic lives in the service layer, not controllers or guards
  • New entities registered in AppModule TypeORM configuration
  • New modules imported in AppModule (or the appropriate parent module)

DTO Validation

  • Every endpoint body/query has a typed DTO (no any or plain objects)
  • class-validator decorators are complete and sensible
  • Optional fields use @IsOptional() before other decorators
  • @ApiProperty present on all DTO fields

Error Handling

  • Correct NestJS HTTP exception class used for every failure case
  • No silent failures (returning null/undefined without throwing)
  • Stellar/Soroban failures are caught and translated to NestJS exceptions
  • Global exception filter not bypassed

Logging

  • Logger from @nestjs/common used — no console.log
  • Errors logged with err.stack
  • No sensitive data in log messages
  • Log level is appropriate (not everything logged at error)

Guards & Authorization

  • All authenticated routes use JwtAuthGuard (or equivalent)
  • Admin routes have an additional admin guard
  • Public routes explicitly marked with @Public()
  • Throttler not disabled without justification

Testing

  • Unit tests cover the happy path and all edge cases
  • Tests use mocks — no real DB or network calls in unit tests
  • E2E tests added for new endpoints
  • All tests pass in CI

Security

  • No secrets or keys hardcoded in source
  • .env.example updated for new env vars
  • Input sanitized via class-transformer @Transform where needed
  • No new direct SQL queries bypassing TypeORM

Database

  • Migration created for schema changes
  • Migration is reversible
  • No synchronize: true in non-development environments

9. Labels

Label Description
backend Changes to the NestJS backend
frontend Changes to the Next.js frontend
contract Changes to Rust/Soroban smart contracts
collaboration Process, templates, or contribution workflow
developer-experience Tooling, CI/CD, or dev environment improvements
priority-high Blocking issue or critical bug
priority-medium Important but not blocking
priority-low Nice to have
security Security-related change — requires security review
breaking-change Introduces a breaking API or contract change
needs-review Ready for reviewer attention
work-in-progress Not ready for review yet

For questions, open a GitHub Discussion or ping a maintainer in the PR comments.


10. Frontend Changelog Policy (FE-068)

The frontend (FrontEnd/my-app/) maintains a Keep-a-Changelog-style CHANGELOG.md so that every breaking TypeScript type, interface, enum, Zod schema, or API model change is announced and migratable.

Read the full policy: FrontEnd/my-app/docs/TYPE_CHANGES_POLICY.md

In short:

  1. If your PR modifies any file under FrontEnd/my-app/lib/types/**, lib/api/**, lib/schemas/**, lib/validation/**, or context/walletTypes.ts, you must either update CHANGELOG.md or add a file in FrontEnd/my-app/.changeset/.

  2. For breaking changes the entry must include a before/after Migration: code block.

  3. CI runs npm run changelog:check via the frontend-changelog workflow.

  4. To bypass for a verified non-breaking change, add the changelog-skip label or [changelog-skip] token to the PR title.

    Backend Development: Test/Lint/Typecheck Order

Required Order for Running Checks

When developing backend features, always run these commands in this exact order:

1. Generate GraphQL Types (FIRST - REQUIRED)

npm run gql:build


## 9. Backend Development: Test & Code Quality Workflow

### 9.1 Available Commands

### 9.1 Available Commands

| Command | Purpose |
|---------|---------|
| `npm run test` | Run all tests (located in `tests/run-tests.js`) |
| `npm run lint-staged` | Run Prettier formatter on staged files |
| `npm run prepare` | Install git hooks (runs automatically after `npm install`) |
| `npm run typecheck` (if available) | Run TypeScript type checking (`tsc --noEmit`) |

**Note:** This project uses TypeScript (`.ts` files). If a `typecheck` script is not yet configured, run type checking manually with:
```bash
npx tsc --noEmit