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
- Initial Setup
- Project Structure
- Development Workflow
- Code Conventions
- Repository Pattern
- Testing Guidelines
- Pull Request Process
- Database Migrations
- 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
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:
- 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 featuresfix/description- Bug fixesrefactor/description- Code refactoringdocs/description- Documentation updatestest/description- Test additions/updateschore/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 featurefix: Bug fixdocs: Documentation changesstyle: Code style changes (formatting, semicolons, etc.)refactor: Code refactoringtest: Adding or updating testschore: 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
- Use TypeScript strict mode - Already enabled in
tsconfig.json - Prefer interfaces over types for object shapes
- Use explicit return types for all functions
- Avoid
anytype - Useunknownif type is truly unknown - 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
Iprefix -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 operationsIReadOnlyRepository<T>- Read-only operationsIWriteOnlyRepository<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
-
Update your branch with the latest
main:git checkout main git pull origin main git checkout your-branch git rebase main -
Run all checks:
npm run lint npm run typecheck npm test npm run build -
Update documentation if needed
-
Write/update tests for new features
PR Guidelines
-
Title: Use conventional commit format
- Example:
feat(projects): add project template system
- Example:
-
Description: Include:
- What changed and why
- Related issue numbers (if applicable)
- Screenshots (for UI changes)
- Migration instructions (if applicable)
-
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
- Always test migrations on a copy of production data
- Make migrations reversible - Implement
down()method - Include data migrations if schema changes affect existing data
- Use transactions for data integrity
- 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
buglabel - Feature Requests: Create an issue with the
enhancementlabel - Questions: Create an issue with the
questionlabel
Thank you for contributing to Platform Marketing Content!