platform-marketing-content/CONTRIBUTING.md

703 lines
17 KiB
Markdown

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