703 lines
17 KiB
Markdown
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!
|