platform-marketing-content/CONTRIBUTING.md

17 KiB

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

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
  • Visual Studio Code with extensions:
    • ESLint
    • Prettier
    • TypeScript and JavaScript Language Features
    • REST Client

Initial Setup

1. Clone the Repository

git clone <repository-url>
cd platform_marketing_content

2. Install Dependencies

# Backend
cd apps/backend
npm install

# Frontend
cd ../frontend
npm install

3. Environment Configuration

Backend Setup

cd apps/backend
cp .env.example .env

Edit .env with your local configuration:

# 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

cd apps/frontend
cp .env.example .env

Edit .env:

VITE_API_URL=http://localhost:3111
VITE_WS_URL=ws://localhost:3111

4. Database Setup

Create the database:

# 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:

cd apps/backend
npm run typeorm migration:run

5. Start Development Servers

# Backend (Terminal 1)
cd apps/backend
npm run start:dev

# Frontend (Terminal 2)
cd apps/frontend
npm run dev

Access the application:


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 specification:

<type>(<scope>): <subject>

[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:

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:

# 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)
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:

@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<T> - Full CRUD operations
  • IReadOnlyRepository<T> - Read-only operations
  • IWriteOnlyRepository<T> - Write-only operations

Using the Repository Pattern

1. Create a Repository Interface (Optional)

For domain-specific operations, extend the base interface:

// 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<Project> {
  findByClientId(ctx: ServiceContext, clientId: string): Promise<Project[]>;
  findActiveProjects(ctx: ServiceContext): Promise<Project[]>;
}

2. Implement the Repository

// 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<Project>,
  ) {}

  async findById(ctx: ServiceContext, id: string): Promise<Project | null> {
    return this.repository.findOne({
      where: { id, tenantId: ctx.tenantId },
    });
  }

  async findByClientId(ctx: ServiceContext, clientId: string): Promise<Project[]> {
    return this.repository.find({
      where: { clientId, tenantId: ctx.tenantId },
    });
  }

  async findActiveProjects(ctx: ServiceContext): Promise<Project[]> {
    return this.repository.find({
      where: {
        tenantId: ctx.tenantId,
        isActive: true,
      },
    });
  }

  // Implement other IBaseRepository methods...
}

3. Register in Factory (Optional)

For advanced dependency injection scenarios:

// 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

// 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:

export interface ServiceContext {
  tenantId: string;
  userId: string;
}

Extract from request in controllers:

@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

// project.service.spec.ts
describe('ProjectService', () => {
  let service: ProjectService;
  let repository: MockType<ProjectRepository>;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        ProjectService,
        {
          provide: ProjectRepository,
          useFactory: mockRepository,
        },
      ],
    }).compile();

    service = module.get<ProjectService>(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/

npm run test:e2e

Running Tests

# 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:

    git checkout main
    git pull origin main
    git checkout your-branch
    git rebase main
    
  2. Run all checks:

    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

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

# 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:

@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:

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!