# 08 - Development Guidelines ## 8.1 Project Structure ``` openwa/ ├── src/ │ ├── main.ts # Application entry │ ├── app.module.ts # Root module │ ├── common/ # Shared cache, security, storage, errors, utils │ ├── config/ # Runtime config, env validation, bootstrap security, Swagger │ ├── core/ # Hook and plugin framework │ ├── database/ # TypeORM data sources and migrations │ ├── engine/ # WhatsApp engine abstraction, adapters, identity mapping │ ├── modules/ # API feature modules │ └── plugins/ # Built-in engine and extension plugins ├── test/ # E2E smoke tests and mocks ├── dashboard/ # React/Vite dashboard ├── sdk/ # JavaScript and Python SDK scaffolds ├── docs/ # Documentation ├── scripts/ # Utility scripts ├── .github/workflows/ # CI and release workflows ├── package.json ├── tsconfig.json ├── nest-cli.json ├── eslint.config.mjs ├── docker-compose.yml ├── docker-compose.dev.yml ├── Dockerfile └── README.md ``` ## 8.2 Coding Standards ### TypeScript Configuration ```json // tsconfig.json { "compilerOptions": { "module": "commonjs", "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, "target": "ES2022", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, "strictNullChecks": true, "noImplicitAny": true, "strictBindCallApply": true, "forceConsistentCasingInFileNames": true, "noFallthroughCasesInSwitch": true, "paths": { "@/*": ["src/*"], "@common/*": ["src/common/*"], "@modules/*": ["src/modules/*"], "@config/*": ["src/config/*"] } } } ``` ### ESLint Configuration The backend uses ESLint flat config in `eslint.config.mjs` with type-aware TypeScript rules, Prettier integration, and an architecture guard for controllers. HTTP controllers must call capability services; they must not import `IWhatsAppEngine` or call `getEngine()` directly. ```bash npm run lint npm run lint:fix ``` The dashboard has its own package scripts: ```bash cd dashboard npm run lint ``` ### Naming Conventions ```typescript // Files: kebab-case session.controller.ts send-message.dto.ts api-key.guard.ts // Classes: PascalCase class SessionController {} class SendMessageDto {} class ApiKeyGuard {} // Interfaces: PascalCase with 'I' prefix (optional) interface ISessionConfig {} interface SessionConfig {} // Also acceptable // Functions/Methods: camelCase function createSession() {} async sendMessage() {} // Variables: camelCase const sessionId = 'abc'; let messageCount = 0; // Constants: UPPER_SNAKE_CASE const MAX_RETRY_COUNT = 3; const DEFAULT_TIMEOUT = 30000; // Enums: PascalCase with PascalCase values enum SessionStatus { Created = 'created', Ready = 'ready', Disconnected = 'disconnected', } ``` ## 8.3 Module Structure ### Standard Module Template ```typescript // modules/example/example.module.ts import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { ExampleController } from './example.controller'; import { ExampleService } from './example.service'; import { ExampleRepository } from './example.repository'; import { Example } from './entities/example.entity'; @Module({ imports: [TypeOrmModule.forFeature([Example], 'data')], controllers: [ExampleController], providers: [ExampleService, ExampleRepository], exports: [ExampleService], }) export class ExampleModule {} ``` ### Controller Template ```typescript // modules/example/example.controller.ts import { Controller, Get, Post, Body, Headers, Param, Delete, HttpCode, HttpStatus, } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; import { ExampleService } from './example.service'; import { CreateExampleDto } from './dto/create-example.dto'; import { ExampleResponseDto } from './dto/example-response.dto'; @ApiTags('examples') @Controller('examples') export class ExampleController { constructor(private readonly exampleService: ExampleService) {} @Post() @HttpCode(HttpStatus.CREATED) @ApiOperation({ summary: 'Create example' }) @ApiResponse({ status: 201, type: ExampleResponseDto }) async create( @Body() dto: CreateExampleDto, @Headers('x-request-id') requestId?: string ): Promise { return this.exampleService.create(dto, { requestId }); } @Get(':id') @ApiOperation({ summary: 'Get example by ID' }) @ApiResponse({ status: 200, type: ExampleResponseDto }) async findOne(@Param('id') id: string): Promise { return this.exampleService.findOne(id); } @Delete(':id') @HttpCode(HttpStatus.NO_CONTENT) @ApiOperation({ summary: 'Delete example' }) async remove(@Param('id') id: string): Promise { return this.exampleService.remove(id); } } ``` Controllers are protected by the global API key guard unless marked with `@Public()`. Keep controllers thin: validate transport input through DTOs, delegate behavior to services, and never call `SessionService.getEngine()` directly from a controller. Engine-specific details belong behind capability services and engine adapters. ### Service Template ```typescript // modules/example/example.service.ts import { Injectable, NotFoundException, Logger } from '@nestjs/common'; import { ExampleRepository } from './example.repository'; import { CreateExampleDto } from './dto/create-example.dto'; import { Example } from './entities/example.entity'; @Injectable() export class ExampleService { private readonly logger = new Logger(ExampleService.name); constructor(private readonly repository: ExampleRepository) {} async create( dto: CreateExampleDto, context?: { requestId?: string } ): Promise { this.logger.log(`Creating example: ${dto.name}`, context); const example = this.repository.create(dto); return this.repository.save(example); } async findOne(id: string): Promise { const example = await this.repository.findOne({ where: { id } }); if (!example) { throw new NotFoundException(`Example with ID ${id} not found`); } return example; } async remove(id: string): Promise { const example = await this.findOne(id); await this.repository.remove(example); this.logger.log(`Deleted example: ${id}`); } } ``` ### DTO Template ```typescript // modules/example/dto/create-example.dto.ts import { IsString, IsOptional, MaxLength, IsUrl } from 'class-validator'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; export class CreateExampleDto { @ApiProperty({ description: 'Example name', example: 'My Example' }) @IsString() @MaxLength(100) name: string; @ApiPropertyOptional({ description: 'Optional description' }) @IsOptional() @IsString() @MaxLength(500) description?: string; @ApiPropertyOptional({ description: 'Callback URL' }) @IsOptional() @IsUrl({ protocols: ['https'] }) callbackUrl?: string; } ``` ## 8.4 Git Workflow ### Branch Strategy ```mermaid gitGraph commit id: "initial" branch develop commit id: "setup" branch feature/session-api commit id: "session controller" commit id: "session service" checkout develop merge feature/session-api branch feature/webhook commit id: "webhook impl" checkout develop merge feature/webhook checkout main merge develop tag: "v1.0.0" checkout develop branch hotfix/bug-fix commit id: "fix bug" checkout main merge hotfix/bug-fix tag: "v1.0.1" checkout develop merge hotfix/bug-fix ``` ### Branch Naming ``` main # Production-ready code develop # Integration branch feature/* # New features bugfix/* # Bug fixes hotfix/* # Production hotfixes release/* # Release preparation Examples: feature/session-management feature/webhook-retry bugfix/qr-code-timeout hotfix/security-patch release/1.0.0 ``` ### Commit Message Convention ``` ():