# Contributing to Platform Marketing Content (PMC) Thank you for your interest in contributing to Platform Marketing Content! This document provides guidelines and standards for contributing to this project. ## Table of Contents - [Prerequisites](#prerequisites) - [Initial Setup](#initial-setup) - [Project Structure](#project-structure) - [Development Workflow](#development-workflow) - [Code Conventions](#code-conventions) - [Repository Pattern](#repository-pattern) - [Testing Guidelines](#testing-guidelines) - [Pull Request Process](#pull-request-process) - [Database Migrations](#database-migrations) - [API Documentation](#api-documentation) --- ## Prerequisites Before you begin, ensure you have the following installed: - **Node.js**: Version 20+ (LTS recommended) - **npm**: Version 9+ (comes with Node.js) - **PostgreSQL**: Version 16 - **Redis**: Version 7 - **Git**: Latest version - **MinIO** (Optional): For local object storage testing - **ComfyUI** (Optional): For AI image generation features ### Recommended Tools - **Visual Studio Code** with extensions: - ESLint - Prettier - TypeScript and JavaScript Language Features - REST Client --- ## Initial Setup ### 1. Clone the Repository ```bash git clone cd platform_marketing_content ``` ### 2. Install Dependencies ```bash # Backend cd apps/backend npm install # Frontend cd ../frontend npm install ``` ### 3. Environment Configuration #### Backend Setup ```bash cd apps/backend cp .env.example .env ``` Edit `.env` with your local configuration: ```env # Database DB_HOST=localhost DB_PORT=5432 DB_NAME=pmc_dev DB_USER=pmc_user DB_PASSWORD=pmc_secret_2024 # Redis REDIS_HOST=localhost REDIS_PORT=6379 # JWT JWT_SECRET=your-secret-key-here JWT_EXPIRES_IN=1d # Server PORT=3111 NODE_ENV=development ``` #### Frontend Setup ```bash cd apps/frontend cp .env.example .env ``` Edit `.env`: ```env VITE_API_URL=http://localhost:3111 VITE_WS_URL=ws://localhost:3111 ``` ### 4. Database Setup Create the database: ```bash # Connect to PostgreSQL psql -U postgres # Create user and database CREATE USER pmc_user WITH PASSWORD 'pmc_secret_2024'; CREATE DATABASE pmc_dev OWNER pmc_user; GRANT ALL PRIVILEGES ON DATABASE pmc_dev TO pmc_user; \q ``` Run migrations: ```bash cd apps/backend npm run typeorm migration:run ``` ### 5. Start Development Servers ```bash # Backend (Terminal 1) cd apps/backend npm run start:dev # Frontend (Terminal 2) cd apps/frontend npm run dev ``` Access the application: - Frontend: http://localhost:3110 - Backend API: http://localhost:3111 - API Documentation: http://localhost:3111/api --- ## Project Structure ``` platform_marketing_content/ ├── apps/ │ ├── backend/ # NestJS Backend API │ │ ├── src/ │ │ │ ├── common/ # Shared utilities, decorators, filters │ │ │ ├── config/ # Configuration modules │ │ │ ├── modules/ # Feature modules │ │ │ │ ├── auth/ # Authentication & authorization │ │ │ │ ├── tenants/ # Multi-tenancy │ │ │ │ ├── projects/ # Content projects │ │ │ │ ├── assets/ # Media asset management │ │ │ │ └── crm/ # Clients, brands, products │ │ │ ├── shared/ # Shared entities, services, repositories │ │ │ │ ├── constants/ # Global constants │ │ │ │ ├── dto/ # Data Transfer Objects │ │ │ │ ├── entities/ # Shared TypeORM entities │ │ │ │ ├── repositories/# Repository interfaces & factory │ │ │ │ └── services/ # Shared services │ │ │ ├── app.module.ts │ │ │ └── main.ts │ │ ├── test/ # E2E tests │ │ └── package.json │ │ │ └── frontend/ # React Frontend │ ├── src/ │ │ ├── components/ # Reusable UI components │ │ ├── pages/ # Page components │ │ ├── hooks/ # Custom React hooks │ │ ├── services/ # API service layer │ │ └── utils/ # Utility functions │ └── package.json │ ├── database/ # Database schemas and migrations ├── docs/ # Documentation ├── docker/ # Docker configurations ├── nginx/ # Nginx configurations ├── orchestration/ # Deployment guides ├── scripts/ # Utility scripts ├── .env.ports # Port configuration registry └── README.md ``` --- ## Development Workflow ### Branch Naming Follow these conventions: - `feature/description` - New features - `fix/description` - Bug fixes - `refactor/description` - Code refactoring - `docs/description` - Documentation updates - `test/description` - Test additions/updates - `chore/description` - Maintenance tasks Example: `feature/add-project-templates` ### Commit Messages Follow the [Conventional Commits](https://www.conventionalcommits.org/) specification: ``` (): [optional body] [optional footer] ``` **Types:** - `feat`: New feature - `fix`: Bug fix - `docs`: Documentation changes - `style`: Code style changes (formatting, semicolons, etc.) - `refactor`: Code refactoring - `test`: Adding or updating tests - `chore`: Maintenance tasks **Examples:** ```bash feat(projects): add project template system fix(auth): resolve JWT token expiration issue docs(api): update Swagger documentation for content endpoints refactor(repositories): implement repository pattern for all services test(crm): add unit tests for client service ``` --- ## Code Conventions ### TypeScript Style Guide #### General Rules 1. **Use TypeScript strict mode** - Already enabled in `tsconfig.json` 2. **Prefer interfaces over types** for object shapes 3. **Use explicit return types** for all functions 4. **Avoid `any` type** - Use `unknown` if type is truly unknown 5. **Use meaningful variable names** - Avoid abbreviations except for common ones (e.g., `id`, `dto`) #### Formatting The project uses **Prettier** and **ESLint** for code formatting: ```bash # Format code npm run format # Lint code npm run lint # Type check npm run typecheck ``` #### Naming Conventions - **Classes**: PascalCase - `ProjectService`, `ClientController` - **Interfaces**: PascalCase with `I` prefix - `IBaseRepository`, `IProjectService` - **Types**: PascalCase - `ServiceContext`, `PaginationOptions` - **Functions/Methods**: camelCase - `findById`, `createProject` - **Variables**: camelCase - `projectData`, `userId` - **Constants**: UPPER_SNAKE_CASE - `DEFAULT_PAGE_SIZE`, `MAX_FILE_SIZE` - **Enums**: PascalCase (name) and UPPER_SNAKE_CASE (values) ```typescript enum UserRole { SUPER_ADMIN = 'SUPER_ADMIN', TENANT_ADMIN = 'TENANT_ADMIN', USER = 'USER', } ``` ### NestJS Conventions #### Module Structure Each feature module should follow this structure: ``` module-name/ ├── controllers/ │ └── module-name.controller.ts ├── services/ │ └── module-name.service.ts ├── repositories/ │ └── module-name.repository.ts ├── dto/ │ ├── create-module-name.dto.ts │ └── update-module-name.dto.ts ├── entities/ │ └── module-name.entity.ts └── module-name.module.ts ``` #### Dependency Injection Use constructor injection for all dependencies: ```typescript @Injectable() export class ProjectService { constructor( private readonly projectRepository: ProjectRepository, private readonly logger: Logger, ) {} } ``` --- ## Repository Pattern ### Overview This project implements the **Repository Pattern** to abstract data access logic and decouple business logic from database operations. ### Repository Interfaces Located in `/apps/backend/src/shared/repositories/`: - `IBaseRepository` - Full CRUD operations - `IReadOnlyRepository` - Read-only operations - `IWriteOnlyRepository` - Write-only operations ### Using the Repository Pattern #### 1. Create a Repository Interface (Optional) For domain-specific operations, extend the base interface: ```typescript // apps/backend/src/modules/projects/repositories/project.repository.interface.ts import { IBaseRepository, ServiceContext } from '@shared/repositories'; import { Project } from '../entities/project.entity'; export interface IProjectRepository extends IBaseRepository { findByClientId(ctx: ServiceContext, clientId: string): Promise; findActiveProjects(ctx: ServiceContext): Promise; } ``` #### 2. Implement the Repository ```typescript // apps/backend/src/modules/projects/repositories/project.repository.ts import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { IProjectRepository } from './project.repository.interface'; import { Project } from '../entities/project.entity'; import { ServiceContext } from '@shared/repositories'; @Injectable() export class ProjectRepository implements IProjectRepository { constructor( @InjectRepository(Project) private readonly repository: Repository, ) {} async findById(ctx: ServiceContext, id: string): Promise { return this.repository.findOne({ where: { id, tenantId: ctx.tenantId }, }); } async findByClientId(ctx: ServiceContext, clientId: string): Promise { return this.repository.find({ where: { clientId, tenantId: ctx.tenantId }, }); } async findActiveProjects(ctx: ServiceContext): Promise { return this.repository.find({ where: { tenantId: ctx.tenantId, isActive: true, }, }); } // Implement other IBaseRepository methods... } ``` #### 3. Register in Factory (Optional) For advanced dependency injection scenarios: ```typescript // apps/backend/src/app.module.ts import { RepositoryFactory } from '@shared/repositories'; @Module({ // ... }) export class AppModule implements OnModuleInit { constructor(private readonly projectRepository: ProjectRepository) {} onModuleInit() { const factory = RepositoryFactory.getInstance(); factory.register('ProjectRepository', this.projectRepository); } } ``` #### 4. Use in Services ```typescript // apps/backend/src/modules/projects/services/project.service.ts import { Injectable } from '@nestjs/common'; import { ProjectRepository } from '../repositories/project.repository'; import { ServiceContext } from '@shared/repositories'; @Injectable() export class ProjectService { constructor(private readonly projectRepository: ProjectRepository) {} async getProjectById(ctx: ServiceContext, id: string) { return this.projectRepository.findById(ctx, id); } async getClientProjects(ctx: ServiceContext, clientId: string) { return this.projectRepository.findByClientId(ctx, clientId); } } ``` ### ServiceContext All repository methods require a `ServiceContext` for multi-tenancy: ```typescript export interface ServiceContext { tenantId: string; userId: string; } ``` Extract from request in controllers: ```typescript @Get(':id') async getProject( @Param('id') id: string, @Req() req: Request, ) { const ctx: ServiceContext = { tenantId: req.user.tenantId, userId: req.user.id, }; return this.projectService.getProjectById(ctx, id); } ``` --- ## Testing Guidelines ### Unit Tests Location: `*.spec.ts` files next to source files ```typescript // project.service.spec.ts describe('ProjectService', () => { let service: ProjectService; let repository: MockType; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ ProjectService, { provide: ProjectRepository, useFactory: mockRepository, }, ], }).compile(); service = module.get(ProjectService); repository = module.get(ProjectRepository); }); it('should find project by id', async () => { const mockProject = { id: '1', name: 'Test Project' }; repository.findById.mockResolvedValue(mockProject); const ctx: ServiceContext = { tenantId: 't1', userId: 'u1' }; const result = await service.getProjectById(ctx, '1'); expect(result).toEqual(mockProject); expect(repository.findById).toHaveBeenCalledWith(ctx, '1'); }); }); ``` ### E2E Tests Location: `apps/backend/test/` ```bash npm run test:e2e ``` ### Running Tests ```bash # Run all tests npm test # Run tests in watch mode npm run test:watch # Run tests with coverage npm run test:cov # Run specific test file npm test -- project.service.spec.ts ``` ### Test Coverage Requirements - **Minimum coverage**: 80% for services and repositories - **Controllers**: Test happy paths and error cases - **Services**: Test all business logic - **Repositories**: Test database queries --- ## Pull Request Process ### Before Submitting 1. **Update your branch** with the latest `main`: ```bash git checkout main git pull origin main git checkout your-branch git rebase main ``` 2. **Run all checks**: ```bash npm run lint npm run typecheck npm test npm run build ``` 3. **Update documentation** if needed 4. **Write/update tests** for new features ### PR Guidelines 1. **Title**: Use conventional commit format - Example: `feat(projects): add project template system` 2. **Description**: Include: - What changed and why - Related issue numbers (if applicable) - Screenshots (for UI changes) - Migration instructions (if applicable) 3. **Checklist**: - [ ] Code follows style guidelines - [ ] Tests added/updated and passing - [ ] Documentation updated - [ ] No console.log or debugging code - [ ] TypeScript types are properly defined - [ ] Database migrations created (if needed) ### Review Process - **At least one approval** required - **All CI checks** must pass - **Resolve all comments** before merging - Use **Squash and Merge** for feature branches --- ## Database Migrations ### Creating a Migration ```bash cd apps/backend # Generate migration based on entity changes npm run typeorm migration:generate -- -n MigrationName # Create empty migration npm run typeorm migration:create -- -n MigrationName ``` ### Running Migrations ```bash # Run pending migrations npm run typeorm migration:run # Revert last migration npm run typeorm migration:revert # Show migration status npm run typeorm migration:show ``` ### Migration Best Practices 1. **Always test migrations** on a copy of production data 2. **Make migrations reversible** - Implement `down()` method 3. **Include data migrations** if schema changes affect existing data 4. **Use transactions** for data integrity 5. **Document breaking changes** in PR description --- ## API Documentation ### Swagger/OpenAPI The API is documented using Swagger. Access at: `http://localhost:3111/api` ### Documenting Endpoints Use NestJS decorators: ```typescript @ApiTags('projects') @ApiBearerAuth() @Controller('projects') export class ProjectController { @Post() @ApiOperation({ summary: 'Create a new project' }) @ApiResponse({ status: 201, description: 'Project created successfully', type: Project, }) @ApiResponse({ status: 400, description: 'Invalid input data', }) async create(@Body() dto: CreateProjectDto) { // ... } } ``` ### DTOs Document all DTOs: ```typescript export class CreateProjectDto { @ApiProperty({ description: 'Project name', example: 'Summer Campaign 2024', }) @IsString() @MinLength(3) name: string; @ApiProperty({ description: 'Project description', example: 'Marketing campaign for summer collection', required: false, }) @IsOptional() @IsString() description?: string; } ``` --- ## Questions or Issues? - **Bug Reports**: Create an issue with the `bug` label - **Feature Requests**: Create an issue with the `enhancement` label - **Questions**: Create an issue with the `question` label Thank you for contributing to Platform Marketing Content!