Backend Go: - Remplacement complet des anciennes migrations par la base V1 alignée sur ORIGIN. - Durcissement global du parsing JSON (BindAndValidateJSON + RespondWithAppError). - Sécurisation de config.go, CORS, statuts de santé et monitoring. - Implémentation des transactions P0 (RBAC, duplication de playlists, social toggles). - Ajout d’un job worker structuré (emails, analytics, thumbnails) + tests associés. - Nouvelle doc backend : AUDIT_CONFIG, BACKEND_CONFIG, AUTH_PASSWORD_RESET, JOB_WORKER_*. Chat server (Rust): - Refonte du pipeline JWT + sécurité, audit et rate limiting avancé. - Implémentation complète du cycle de message (read receipts, delivered, edit/delete, typing). - Nettoyage des panics, gestion d’erreurs robuste, logs structurés. - Migrations chat alignées sur le schéma UUID et nouvelles features. Stream server (Rust): - Refonte du moteur de streaming (encoding pipeline + HLS) et des modules core. - Transactions P0 pour les jobs et segments, garanties d’atomicité. - Documentation détaillée de la pipeline (AUDIT_STREAM_*, DESIGN_STREAM_PIPELINE, TRANSACTIONS_P0_IMPLEMENTATION). Documentation & audits: - TRIAGE.md et AUDIT_STABILITY.md à jour avec l’état réel des 3 services. - Cartographie complète des migrations et des transactions (DB_MIGRATIONS_*, DB_TRANSACTION_PLAN, AUDIT_DB_TRANSACTIONS, TRANSACTION_TESTS_PHASE3). - Scripts de reset et de cleanup pour la lab DB et la V1. Ce commit fige l’ensemble du travail de stabilisation P0 (UUID, backend, chat et stream) avant les phases suivantes (Coherence Guardian, WS hardening, etc.).
43 KiB
ORIGIN_CODE_STANDARDS.md
📋 RÉSUMÉ EXÉCUTIF
Ce document définit les standards de code complets et définitifs pour la plateforme Veza. Il couvre les conventions de code, architectures, patterns, et anti-patterns pour Go (backend), Rust (services temps réel), TypeScript/React (frontend), CSS/Tailwind, Git, et documentation. Ces standards garantissent la maintenabilité, lisibilité, performance, et cohérence du code pendant 24 mois.
🎯 OBJECTIFS
Objectif Principal
Établir des standards de code stricts et immuables qui garantissent la qualité, maintenabilité, et cohérence du codebase pendant 24 mois avec une équipe de 10+ développeurs.
Objectifs Secondaires
- Réduire la dette technique (< 5% du temps de développement)
- Faciliter l'onboarding (< 1 semaine pour nouveaux développeurs)
- Garantir la lisibilité (code self-documenting)
- Optimiser les performances (hot paths identifiés)
- Standardiser le style (linters, formatters automatiques)
📖 TABLE DES MATIÈRES
- General Principles
- Go Standards (Backend)
- Rust Standards (Services)
- TypeScript Standards (Frontend)
- React Standards
- CSS/Tailwind Standards
- Git Standards
- Documentation Standards
- Code Review Process
- Refactoring Guidelines
- Anti-Patterns Library
🔒 RÈGLES IMMUABLES
- Formatters OBLIGATOIRES: gofmt (Go), rustfmt (Rust), Prettier (TS/React)
- Linters OBLIGATOIRES: golangci-lint (Go), clippy (Rust), ESLint (TS)
- Tests OBLIGATOIRES pour toute nouvelle feature (coverage ≥ 80%)
- Code review OBLIGATOIRE (2 approbations minimum)
- Naming conventions STRICTES (camelCase/PascalCase/snake_case selon langage)
- Documentation OBLIGATOIRE pour fonctions publiques
- Error handling COMPLET (pas de panic/unwrap en production)
- Magic numbers INTERDITS (utiliser constantes nommées)
- Code mort INTERDIT (suppression immédiate)
- Complexity limit: Fonctions max 50 lignes, cyclomatic complexity < 10
1. GENERAL PRINCIPLES
1.1 SOLID Principles
Single Responsibility Principle (SRP):
// ❌ Bad: UserService does too much
type UserService struct{}
func (s *UserService) CreateUser() {}
func (s *UserService) SendEmail() {}
func (s *UserService) ProcessPayment() {}
// ✅ Good: Separate services
type UserService struct{}
func (s *UserService) CreateUser() {}
type EmailService struct{}
func (s *EmailService) SendEmail() {}
type PaymentService struct{}
func (s *PaymentService) ProcessPayment() {}
Open/Closed Principle (OCP):
// ✅ Good: Open for extension, closed for modification
type NotificationSender interface {
Send(message string) error
}
type EmailNotification struct{}
func (e *EmailNotification) Send(message string) error { /* ... */ }
type SMSNotification struct{}
func (s *SMSNotification) Send(message string) error { /* ... */ }
// Add new notification types without modifying existing code
type PushNotification struct{}
func (p *PushNotification) Send(message string) error { /* ... */ }
Liskov Substitution Principle (LSP):
// ✅ Good: Subtypes can replace base types
type Storage interface {
Save(data []byte) error
Load() ([]byte, error)
}
type S3Storage struct{}
func (s *S3Storage) Save(data []byte) error { /* ... */ }
func (s *S3Storage) Load() ([]byte, error) { /* ... */ }
type LocalStorage struct{}
func (l *LocalStorage) Save(data []byte) error { /* ... */ }
func (l *LocalStorage) Load() ([]byte, error) { /* ... */ }
// Can swap implementations
var storage Storage = &S3Storage{} // or &LocalStorage{}
Interface Segregation Principle (ISP):
// ❌ Bad: Fat interface
type Worker interface {
Work()
Eat()
Sleep()
Code()
}
// ✅ Good: Segregated interfaces
type Workable interface {
Work()
}
type Eatable interface {
Eat()
}
type Sleepable interface {
Sleep()
}
type Codeable interface {
Code()
}
Dependency Inversion Principle (DIP):
// ❌ Bad: High-level module depends on low-level module
type UserService struct {
repo *PostgresUserRepository // Concrete dependency
}
// ✅ Good: Both depend on abstraction
type UserRepository interface {
Create(user *User) error
FindByID(id uuid.UUID) (*User, error)
}
type UserService struct {
repo UserRepository // Abstract dependency
}
// Implementations
type PostgresUserRepository struct{}
type MongoUserRepository struct{}
1.2 DRY (Don't Repeat Yourself)
Extract Common Logic:
// ❌ Bad: Duplication
func CreateUser(req CreateUserRequest) error {
if req.Email == "" {
return errors.New("email required")
}
if !isValidEmail(req.Email) {
return errors.New("invalid email")
}
// ... create user
}
func UpdateUser(req UpdateUserRequest) error {
if req.Email == "" {
return errors.New("email required")
}
if !isValidEmail(req.Email) {
return errors.New("invalid email")
}
// ... update user
}
// ✅ Good: Extract validation
func validateEmail(email string) error {
if email == "" {
return errors.New("email required")
}
if !isValidEmail(email) {
return errors.New("invalid email")
}
return nil
}
func CreateUser(req CreateUserRequest) error {
if err := validateEmail(req.Email); err != nil {
return err
}
// ... create user
}
1.3 KISS (Keep It Simple, Stupid)
Favor Simplicity:
// ❌ Bad: Overengineered
func IsEven(n int) bool {
return n & 1 == 0
}
// ✅ Good: Simple and readable
func IsEven(n int) bool {
return n % 2 == 0
}
1.4 YAGNI (You Aren't Gonna Need It)
Don't Add Features Until Needed:
// ❌ Bad: Premature generalization
type Cache interface {
Get(key string) (interface{}, error)
Set(key string, value interface{}) error
Delete(key string) error
GetMulti(keys []string) ([]interface{}, error) // Not needed yet
SetMulti(map[string]interface{}) error // Not needed yet
Flush() error // Not needed yet
GetStats() CacheStats // Not needed yet
}
// ✅ Good: Start simple, add when needed
type Cache interface {
Get(key string) (interface{}, error)
Set(key string, value interface{}) error
Delete(key string) error
}
2. GO STANDARDS (BACKEND)
2.1 Project Structure (Clean Architecture)
veza-backend-api/
├── cmd/
│ └── api/
│ └── main.go # Entry point
├── internal/
│ ├── api/ # HTTP handlers (Delivery layer)
│ │ └── handlers/
│ │ ├── user_handlers.go
│ │ ├── track_handlers.go
│ │ └── auth_handlers.go
│ ├── core/ # Business logic (Use Cases)
│ │ ├── services/
│ │ │ ├── user_service.go
│ │ │ └── track_service.go
│ │ └── domain/ # Entities
│ │ ├── user.go
│ │ └── track.go
│ ├── repository/ # Data access (Repository pattern)
│ │ ├── interfaces.go
│ │ ├── user_repository.go
│ │ └── track_repository.go
│ ├── infrastructure/ # External dependencies
│ │ ├── database/
│ │ │ └── postgres.go
│ │ ├── cache/
│ │ │ └── redis.go
│ │ └── storage/
│ │ └── s3.go
│ ├── middleware/ # HTTP middleware
│ │ ├── auth.go
│ │ ├── cors.go
│ │ └── logging.go
│ └── config/ # Configuration
│ └── config.go
├── pkg/ # Public packages (reusable)
│ ├── logger/
│ └── validator/
├── migrations/ # Database migrations
├── tests/
│ ├── unit/
│ └── integration/
├── go.mod
└── go.sum
2.2 Naming Conventions
Variables: camelCase
var userName string
var userCount int
Constants: PascalCase or SCREAMING_SNAKE_CASE (for exported)
const MaxRetries = 3
const DEFAULT_TIMEOUT = 30 * time.Second
Functions: PascalCase (exported), camelCase (unexported)
// Exported
func CreateUser(req *CreateUserRequest) (*User, error) {}
// Unexported
func validateEmail(email string) error {}
Types: PascalCase
type UserService struct {}
type CreateUserRequest struct {}
Interfaces: -er suffix (if single method)
type Reader interface {
Read(p []byte) (n int, err error)
}
type UserRepository interface { // Multi-method, no -er suffix
Create(user *User) error
FindByID(id uuid.UUID) (*User, error)
}
2.3 Error Handling
Always Check Errors:
// ❌ Bad: Ignoring error
user, _ := repo.FindByID(id)
// ✅ Good: Check and handle
user, err := repo.FindByID(id)
if err != nil {
return nil, fmt.Errorf("failed to find user: %w", err)
}
Wrap Errors with Context:
import "fmt"
func CreateUser(req *CreateUserRequest) error {
if err := validateEmail(req.Email); err != nil {
return fmt.Errorf("validation failed: %w", err)
}
if err := repo.Create(user); err != nil {
return fmt.Errorf("failed to create user in database: %w", err)
}
return nil
}
Custom Error Types (when needed):
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation error: %s - %s", e.Field, e.Message)
}
// Usage
if req.Email == "" {
return &ValidationError{Field: "email", Message: "required"}
}
Don't Panic in Production:
// ❌ Bad: Panic for recoverable errors
if err != nil {
panic(err)
}
// ✅ Good: Return error
if err != nil {
return fmt.Errorf("operation failed: %w", err)
}
// ⚠️ OK: Panic only for programmer errors (init, config)
func init() {
if os.Getenv("DATABASE_URL") == "" {
panic("DATABASE_URL environment variable not set")
}
}
2.4 Function Design
Keep Functions Small (< 50 lines):
// ❌ Bad: Too long
func CreateUser(c *gin.Context) {
var req CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": "invalid request"})
return
}
if req.Email == "" {
c.JSON(400, gin.H{"error": "email required"})
return
}
if !isValidEmail(req.Email) {
c.JSON(400, gin.H{"error": "invalid email"})
return
}
existingUser, _ := repo.FindByEmail(req.Email)
if existingUser != nil {
c.JSON(409, gin.H{"error": "email already exists"})
return
}
hash, err := bcrypt.GenerateFromPassword([]byte(req.Password), 12)
if err != nil {
c.JSON(500, gin.H{"error": "internal error"})
return
}
user := &User{
ID: uuid.New(),
Email: req.Email,
PasswordHash: string(hash),
CreatedAt: time.Now(),
}
if err := repo.Create(user); err != nil {
c.JSON(500, gin.H{"error": "failed to create user"})
return
}
c.JSON(201, user)
}
// ✅ Good: Extract to service layer
func CreateUser(c *gin.Context) {
var req CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, ErrorResponse{Code: 2000, Message: "Invalid request"})
return
}
user, err := userService.CreateUser(c.Request.Context(), &req)
if err != nil {
handleError(c, err)
return
}
c.JSON(201, user)
}
// Business logic in service
func (s *UserService) CreateUser(ctx context.Context, req *CreateUserRequest) (*User, error) {
if err := s.validateCreateRequest(req); err != nil {
return nil, err
}
if err := s.checkEmailExists(req.Email); err != nil {
return nil, err
}
user := s.buildUser(req)
if err := s.repo.Create(ctx, user); err != nil {
return nil, fmt.Errorf("failed to create user: %w", err)
}
return user, nil
}
Single Return Type (prefer):
// ❌ Bad: Multiple return patterns
func FindUser(id uuid.UUID) (*User, error) {
user, err := repo.FindByID(id)
if err == sql.ErrNoRows {
return nil, nil // nil user, nil error
}
if err != nil {
return nil, err // nil user, error
}
return user, nil // user, nil error
}
// ✅ Good: Consistent return pattern
func FindUser(id uuid.UUID) (*User, error) {
user, err := repo.FindByID(id)
if err == sql.ErrNoRows {
return nil, ErrUserNotFound
}
if err != nil {
return nil, fmt.Errorf("failed to find user: %w", err)
}
return user, nil
}
2.5 Concurrency
Use Context for Cancellation:
func ProcessTask(ctx context.Context, taskID uuid.UUID) error {
select {
case <-ctx.Done():
return ctx.Err() // Cancelled or timed out
case <-time.After(1 * time.Second):
// Continue processing
}
// ... do work
return nil
}
// Usage with timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := ProcessTask(ctx, taskID); err != nil {
log.Error("Task failed", zap.Error(err))
}
Goroutine Patterns:
// ✅ Good: Worker pool
func ProcessTracks(tracks []*Track) error {
const numWorkers = 10
jobs := make(chan *Track, len(tracks))
results := make(chan error, len(tracks))
// Start workers
for w := 0; w < numWorkers; w++ {
go func() {
for track := range jobs {
results <- processTrack(track)
}
}()
}
// Send jobs
for _, track := range tracks {
jobs <- track
}
close(jobs)
// Collect results
for i := 0; i < len(tracks); i++ {
if err := <-results; err != nil {
return err
}
}
return nil
}
Avoid Goroutine Leaks:
// ❌ Bad: Goroutine leak (no way to stop)
func StartWorker() {
go func() {
for {
doWork()
time.Sleep(1 * time.Second)
}
}()
}
// ✅ Good: Cancellable goroutine
func StartWorker(ctx context.Context) {
go func() {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return // Exit goroutine
case <-ticker.C:
doWork()
}
}
}()
}
2.6 Testing
Table-Driven Tests:
func TestIsValidEmail(t *testing.T) {
tests := []struct {
name string
email string
want bool
}{
{"valid email", "user@example.com", true},
{"missing @", "userexample.com", false},
{"missing domain", "user@", false},
{"empty", "", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := IsValidEmail(tt.email)
if got != tt.want {
t.Errorf("IsValidEmail(%q) = %v, want %v", tt.email, got, tt.want)
}
})
}
}
Use testify/assert:
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestCreateUser(t *testing.T) {
user, err := CreateUser(&CreateUserRequest{
Email: "test@example.com",
})
assert.NoError(t, err)
assert.NotNil(t, user)
assert.Equal(t, "test@example.com", user.Email)
}
Mock Dependencies:
// Interface
type UserRepository interface {
Create(user *User) error
}
// Mock implementation (using testify/mock)
type MockUserRepository struct {
mock.Mock
}
func (m *MockUserRepository) Create(user *User) error {
args := m.Called(user)
return args.Error(0)
}
// Test with mock
func TestUserService_CreateUser(t *testing.T) {
mockRepo := new(MockUserRepository)
mockRepo.On("Create", mock.Anything).Return(nil)
service := NewUserService(mockRepo)
user, err := service.CreateUser(&CreateUserRequest{
Email: "test@example.com",
})
assert.NoError(t, err)
assert.NotNil(t, user)
mockRepo.AssertExpectations(t)
}
3. RUST STANDARDS (SERVICES)
3.1 Project Structure
veza-chat-server/
├── src/
│ ├── main.rs # Entry point
│ ├── lib.rs # Library root
│ ├── handlers/ # WebSocket handlers
│ │ ├── mod.rs
│ │ ├── message.rs
│ │ └── presence.rs
│ ├── services/ # Business logic
│ │ ├── mod.rs
│ │ ├── message_service.rs
│ │ └── auth_service.rs
│ ├── repository/ # Data access
│ │ ├── mod.rs
│ │ └── message_repository.rs
│ ├── models/ # Data models
│ │ ├── mod.rs
│ │ ├── message.rs
│ │ └── room.rs
│ ├── utils/ # Utilities
│ │ ├── mod.rs
│ │ └── jwt.rs
│ └── config.rs # Configuration
├── migrations/ # SQLx migrations
├── tests/
│ ├── integration/
│ └── unit/
├── Cargo.toml
└── Cargo.lock
3.2 Naming Conventions
Variables/Functions: snake_case
let user_name = "John";
fn create_user() {}
Types/Traits: PascalCase
struct UserService;
trait Repository {}
Constants: SCREAMING_SNAKE_CASE
const MAX_CONNECTIONS: usize = 10000;
Lifetimes: short, lowercase
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {}
3.3 Error Handling
Use Result Type:
use anyhow::{Result, Context};
fn create_user(email: &str) -> Result<User> {
if email.is_empty() {
return Err(anyhow!("Email is required"));
}
let user = User::new(email)
.context("Failed to create user")?;
Ok(user)
}
Custom Error Types (with thiserror):
use thiserror::Error;
#[derive(Error, Debug)]
pub enum UserError {
#[error("User not found: {0}")]
NotFound(Uuid),
#[error("Email already exists: {0}")]
EmailExists(String),
#[error("Validation failed: {0}")]
Validation(String),
#[error(transparent)]
Database(#[from] sqlx::Error),
}
// Usage
fn find_user(id: Uuid) -> Result<User, UserError> {
let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id)
.fetch_one(&pool)
.await
.map_err(|_| UserError::NotFound(id))?;
Ok(user)
}
Avoid unwrap/expect in Production:
// ❌ Bad: unwrap can panic
let user = find_user(id).unwrap();
// ✅ Good: Handle error
let user = find_user(id)
.context("Failed to find user")?;
// ⚠️ OK: expect for programmer errors (init)
let config = Config::from_env()
.expect("CONFIG environment variables missing");
3.4 Ownership & Borrowing
Prefer Borrowing:
// ❌ Bad: Takes ownership (can't use afterwards)
fn process_user(user: User) {
println!("{}", user.name);
}
let user = User::new("John");
process_user(user);
// user is moved, can't use here
// ✅ Good: Borrows (can still use)
fn process_user(user: &User) {
println!("{}", user.name);
}
let user = User::new("John");
process_user(&user);
// user still usable here
Clone When Necessary:
// ✅ Clone for thread safety
let user = user.clone();
tokio::spawn(async move {
process_user(user).await;
});
3.5 Async/Await
Use async/await for IO:
use tokio;
async fn fetch_user(id: Uuid) -> Result<User> {
let user = sqlx::query_as!(
User,
"SELECT * FROM users WHERE id = $1",
id
)
.fetch_one(&pool)
.await?;
Ok(user)
}
#[tokio::main]
async fn main() -> Result<()> {
let user = fetch_user(uuid).await?;
println!("{:?}", user);
Ok(())
}
Concurrent Operations:
use tokio::try_join;
async fn fetch_user_data(user_id: Uuid) -> Result<(User, Vec<Track>)> {
let user_future = fetch_user(user_id);
let tracks_future = fetch_user_tracks(user_id);
// Run concurrently
let (user, tracks) = try_join!(user_future, tracks_future)?;
Ok((user, tracks))
}
3.6 Testing
Unit Tests:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_user_creation() {
let user = User::new("test@example.com");
assert_eq!(user.email, "test@example.com");
}
#[tokio::test]
async fn test_fetch_user() {
let pool = setup_test_db().await;
let user = fetch_user(test_uuid, &pool).await.unwrap();
assert_eq!(user.email, "test@example.com");
}
}
Property-Based Testing (proptest):
use proptest::prelude::*;
proptest! {
#[test]
fn test_email_validation(email in "[a-z]+@[a-z]+\\.[a-z]+") {
assert!(is_valid_email(&email));
}
}
4. TYPESCRIPT STANDARDS (FRONTEND)
4.1 TypeScript Configuration
tsconfig.json (strict mode):
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true,
"skipLibCheck": true,
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
4.2 Type Definitions
Prefer Interfaces over Types (for objects):
// ✅ Good: Interface (can be extended)
interface User {
id: string;
email: string;
name: string;
}
interface AdminUser extends User {
permissions: string[];
}
// ⚠️ Type (use for unions, intersections)
type UserRole = 'user' | 'creator' | 'admin';
type Optional<T> = T | null | undefined;
Avoid any:
// ❌ Bad: any (loses type safety)
function processData(data: any) {
return data.value;
}
// ✅ Good: Generic
function processData<T>(data: T): T {
return data;
}
// ✅ Good: unknown (safe any)
function processJSON(json: string): unknown {
return JSON.parse(json);
}
const data = processJSON('{"value": 42}');
// Must type-check before using
if (typeof data === 'object' && data !== null && 'value' in data) {
console.log(data.value);
}
Discriminated Unions:
type ApiResponse<T> =
| { status: 'success'; data: T }
| { status: 'error'; error: string };
function handleResponse<T>(response: ApiResponse<T>) {
if (response.status === 'success') {
// TypeScript knows response.data exists
console.log(response.data);
} else {
// TypeScript knows response.error exists
console.error(response.error);
}
}
4.3 Function Types
Type Function Parameters and Returns:
// ✅ Good: Typed
function calculateTotal(
price: number,
quantity: number,
discount?: number
): number {
const subtotal = price * quantity;
return discount ? subtotal * (1 - discount) : subtotal;
}
// ✅ Good: Arrow function
const calculateTotal = (
price: number,
quantity: number,
discount = 0
): number => {
const subtotal = price * quantity;
return subtotal * (1 - discount);
};
Async Functions:
async function fetchUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error('Failed to fetch user');
}
return response.json();
}
4.4 Null Safety
Use Optional Chaining:
// ❌ Bad: Verbose null checks
const city = user && user.profile && user.profile.location && user.profile.location.city;
// ✅ Good: Optional chaining
const city = user?.profile?.location?.city;
Use Nullish Coalescing:
// ❌ Bad: || can give unexpected results (0, '', false treated as falsy)
const port = process.env.PORT || 3000; // Problem if PORT=0
// ✅ Good: ?? only for null/undefined
const port = process.env.PORT ?? 3000;
4.5 Enums vs Union Types
Prefer String Unions (more type-safe):
// ✅ Good: String union (can't assign invalid values)
type UserRole = 'user' | 'creator' | 'premium' | 'moderator' | 'admin';
const role: UserRole = 'user'; // ✅
const invalidRole: UserRole = 'guest'; // ❌ Compile error
// ⚠️ Enum (runtime overhead, can assign numbers)
enum UserRole {
User = 'user',
Creator = 'creator',
Admin = 'admin',
}
5. REACT STANDARDS
5.1 Component Structure
Functional Components (prefer over class):
// ✅ Good: Functional component with TypeScript
interface TrackCardProps {
track: Track;
onPlay: (trackId: string) => void;
className?: string;
}
export const TrackCard: React.FC<TrackCardProps> = ({
track,
onPlay,
className
}) => {
const [isLiked, setIsLiked] = useState(false);
const handleLike = useCallback(() => {
setIsLiked(!isLiked);
// API call...
}, [isLiked]);
return (
<div className={cn('track-card', className)}>
<h3>{track.title}</h3>
<button onClick={() => onPlay(track.id)}>Play</button>
<button onClick={handleLike}>
{isLiked ? '❤️' : '🤍'}
</button>
</div>
);
};
5.2 Hooks
Use Hooks Correctly:
// ✅ Good: Hooks at top level
function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUser(userId).then(setUser).finally(() => setLoading(false));
}, [userId]);
if (loading) return <Spinner />;
if (!user) return <div>User not found</div>;
return <div>{user.name}</div>;
}
// ❌ Bad: Conditional hooks
function UserProfile({ userId }: { userId: string }) {
if (!userId) return null;
const [user, setUser] = useState<User | null>(null); // ❌ Conditional hook
// ...
}
Custom Hooks:
// ✅ Good: Extract reusable logic
function useUser(userId: string) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
setLoading(true);
fetchUser(userId)
.then(setUser)
.catch(setError)
.finally(() => setLoading(false));
}, [userId]);
return { user, loading, error };
}
// Usage
function UserProfile({ userId }: { userId: string }) {
const { user, loading, error } = useUser(userId);
if (loading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
if (!user) return <div>User not found</div>;
return <div>{user.name}</div>;
}
useMemo and useCallback:
// ✅ Good: Memoize expensive computations
function TrackList({ tracks }: { tracks: Track[] }) {
const sortedTracks = useMemo(() => {
return [...tracks].sort((a, b) => b.playCount - a.playCount);
}, [tracks]);
const handlePlay = useCallback((trackId: string) => {
playTrack(trackId);
}, []);
return (
<div>
{sortedTracks.map(track => (
<TrackCard key={track.id} track={track} onPlay={handlePlay} />
))}
</div>
);
}
5.3 State Management
Local State (useState):
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
Global State (Zustand):
import { create } from 'zustand';
interface UserStore {
user: User | null;
setUser: (user: User) => void;
logout: () => void;
}
export const useUserStore = create<UserStore>((set) => ({
user: null,
setUser: (user) => set({ user }),
logout: () => set({ user: null }),
}));
// Usage
function Header() {
const user = useUserStore((state) => state.user);
const logout = useUserStore((state) => state.logout);
return (
<header>
{user ? (
<>
<span>{user.name}</span>
<button onClick={logout}>Logout</button>
</>
) : (
<a href="/login">Login</a>
)}
</header>
);
}
Server State (React Query):
import { useQuery, useMutation } from '@tanstack/react-query';
function useTrack(trackId: string) {
return useQuery({
queryKey: ['track', trackId],
queryFn: () => fetchTrack(trackId),
});
}
function useLikeTrack() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: (trackId: string) => likeTrack(trackId),
onSuccess: (data, trackId) => {
// Invalidate cache
queryClient.invalidateQueries({ queryKey: ['track', trackId] });
},
});
}
// Usage
function TrackPage({ trackId }: { trackId: string }) {
const { data: track, isLoading, error } = useTrack(trackId);
const likeMutation = useLikeTrack();
if (isLoading) return <Spinner />;
if (error) return <ErrorMessage error={error} />;
if (!track) return <div>Track not found</div>;
return (
<div>
<h1>{track.title}</h1>
<button
onClick={() => likeMutation.mutate(track.id)}
disabled={likeMutation.isPending}
>
Like
</button>
</div>
);
}
5.4 Component Composition
Avoid Prop Drilling:
// ❌ Bad: Prop drilling
function App() {
const [user, setUser] = useState<User | null>(null);
return <Layout user={user} setUser={setUser} />;
}
function Layout({ user, setUser }: { user: User | null; setUser: (user: User) => void }) {
return <Header user={user} setUser={setUser} />;
}
function Header({ user, setUser }: { user: User | null; setUser: (user: User) => void }) {
return <UserMenu user={user} setUser={setUser} />;
}
// ✅ Good: Context or global state
function App() {
return <Layout />;
}
function Header() {
const user = useUserStore((state) => state.user);
return <UserMenu />;
}
Compound Components:
// ✅ Good: Compound pattern
interface CardProps {
children: React.ReactNode;
}
export const Card = ({ children }: CardProps) => {
return <div className="card">{children}</div>;
};
Card.Header = ({ children }: CardProps) => {
return <div className="card-header">{children}</div>;
};
Card.Body = ({ children }: CardProps) => {
return <div className="card-body">{children}</div>;
};
Card.Footer = ({ children }: CardProps) => {
return <div className="card-footer">{children}</div>;
};
// Usage
<Card>
<Card.Header>Title</Card.Header>
<Card.Body>Content</Card.Body>
<Card.Footer>Actions</Card.Footer>
</Card>
6. CSS/TAILWIND STANDARDS
6.1 Tailwind Utilities
Prefer Tailwind Utilities:
// ✅ Good: Tailwind utilities
<div className="flex items-center justify-between p-4 bg-white rounded-lg shadow-md">
<h2 className="text-xl font-bold text-gray-900">Title</h2>
<button className="px-4 py-2 text-white bg-blue-600 rounded hover:bg-blue-700">
Action
</button>
</div>
// ❌ Bad: Inline styles
<div style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '16px',
backgroundColor: 'white',
borderRadius: '8px',
boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
}}>
{/* ... */}
</div>
Use cn() Helper (for conditional classes):
import { cn } from '@/lib/utils';
interface ButtonProps {
variant?: 'primary' | 'secondary';
size?: 'sm' | 'md' | 'lg';
disabled?: boolean;
children: React.ReactNode;
}
export const Button = ({
variant = 'primary',
size = 'md',
disabled = false,
children
}: ButtonProps) => {
return (
<button
className={cn(
'rounded font-semibold transition-colors',
{
'bg-blue-600 text-white hover:bg-blue-700': variant === 'primary',
'bg-gray-200 text-gray-900 hover:bg-gray-300': variant === 'secondary',
},
{
'px-2 py-1 text-sm': size === 'sm',
'px-4 py-2 text-base': size === 'md',
'px-6 py-3 text-lg': size === 'lg',
},
{
'opacity-50 cursor-not-allowed': disabled,
}
)}
disabled={disabled}
>
{children}
</button>
);
};
6.2 Custom Components (Extract Reusable)
@apply for Component Classes:
/* components.css */
@layer components {
.btn {
@apply px-4 py-2 rounded font-semibold transition-colors;
}
.btn-primary {
@apply bg-blue-600 text-white hover:bg-blue-700;
}
.btn-secondary {
@apply bg-gray-200 text-gray-900 hover:bg-gray-300;
}
.card {
@apply p-4 bg-white rounded-lg shadow-md;
}
}
7. GIT STANDARDS
7.1 Commit Messages
Format: Conventional Commits
<type>(<scope>): <subject>
<body>
<footer>
Types:
feat: New feature
fix: Bug fix
docs: Documentation changes
style: Code style (formatting, no logic change)
refactor: Code refactoring (no feature/bug change)
perf: Performance improvements
test: Add/update tests
chore: Build process, dependencies, tooling
Examples:
# Good commit messages
git commit -m "feat(auth): add two-factor authentication"
git commit -m "fix(api): resolve rate limiting bug on login endpoint"
git commit -m "refactor(user): extract validation logic to separate service"
git commit -m "docs(readme): update installation instructions"
# Bad commit messages
git commit -m "fix bug"
git commit -m "WIP"
git commit -m "update"
7.2 Branch Naming
Format: <type>/<ticket>-<description>
feature/VEZ-123-user-authentication
bugfix/VEZ-456-fix-login-error
hotfix/VEZ-789-critical-security-patch
refactor/VEZ-012-extract-user-service
docs/VEZ-345-api-documentation
7.3 Pull Request Template
## Description
Brief description of changes
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
## Testing
- [ ] Unit tests pass
- [ ] Integration tests pass
- [ ] Manual testing completed
## Checklist
- [ ] Code follows style guidelines
- [ ] Self-review completed
- [ ] Documentation updated
- [ ] No new warnings
8. DOCUMENTATION STANDARDS
8.1 Code Comments
Go (godoc):
// CreateUser creates a new user in the system.
// It validates the request, checks for duplicate emails,
// hashes the password, and stores the user in the database.
//
// Returns the created user and nil error on success.
// Returns nil and error if validation fails or database error occurs.
func CreateUser(req *CreateUserRequest) (*User, error) {
// Validation
if err := validateCreateRequest(req); err != nil {
return nil, fmt.Errorf("validation failed: %w", err)
}
// ... implementation
}
Rust (rustdoc):
/// Creates a new user in the system.
///
/// # Arguments
///
/// * `email` - The user's email address
/// * `password` - The user's plain-text password (will be hashed)
///
/// # Returns
///
/// Returns `Ok(User)` on success, or `Err(UserError)` if:
/// - Email is invalid
/// - Email already exists
/// - Database error occurs
///
/// # Examples
///
/// ```
/// let user = create_user("test@example.com", "SecurePass123").await?;
/// assert_eq!(user.email, "test@example.com");
/// ```
pub async fn create_user(email: &str, password: &str) -> Result<User, UserError> {
// Implementation...
}
TypeScript (JSDoc):
/**
* Fetches a user by ID from the API.
*
* @param userId - The UUID of the user to fetch
* @returns A Promise that resolves to the User object
* @throws {Error} If the user is not found or API request fails
*
* @example
* ```typescript
* const user = await fetchUser('550e8400-e29b-41d4-a716-446655440000');
* console.log(user.name);
* ```
*/
export async function fetchUser(userId: string): Promise<User> {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) {
throw new Error('Failed to fetch user');
}
return response.json();
}
8.2 README Files
Every Module/Package Needs README.md:
# User Service
User management service for Veza platform.
## Features
- User registration with email verification
- Password authentication with bcrypt
- JWT token generation
- Two-factor authentication (TOTP)
## Usage
```go
import "veza/internal/services"
userService := services.NewUserService(db, redis)
user, err := userService.CreateUser(&CreateUserRequest{
Email: "test@example.com",
Password: "SecurePass123",
})
Testing
go test ./... -v
Dependencies
- GORM (database ORM)
- bcrypt (password hashing)
- jwt-go (JWT tokens)
## 9. CODE REVIEW PROCESS
### 9.1 Review Checklist
**Reviewer Must Check**:
- [ ] Code follows style guidelines (linters pass)
- [ ] Tests are included and pass
- [ ] Documentation is updated
- [ ] No security vulnerabilities (SQL injection, XSS, etc.)
- [ ] Error handling is complete
- [ ] Performance is acceptable (no N+1 queries, etc.)
- [ ] Breaking changes are documented
### 9.2 Review Comments
**Constructive Feedback**:
✅ Good: "Consider extracting this logic into a separate function for better testability: func validateEmail(email string) error { ... }"
❌ Bad: "This code is terrible."
**Approval Process**:
- 2 approvals required (1 from senior engineer)
- All comments resolved
- CI/CD passes
## 10. REFACTORING GUIDELINES
### 10.1 When to Refactor
**Triggers**:
- Duplicated code (DRY violation)
- Functions > 50 lines
- Cyclomatic complexity > 10
- Test coverage < 80%
- Code smells (magic numbers, deep nesting)
**Boy Scout Rule**: Leave code cleaner than you found it
### 10.2 Refactoring Techniques
**Extract Method**:
```go
// Before
func ProcessOrder(order *Order) error {
// Validate order (20 lines)
// ...
// Calculate total (15 lines)
// ...
// Process payment (25 lines)
// ...
}
// After
func ProcessOrder(order *Order) error {
if err := validateOrder(order); err != nil {
return err
}
total := calculateTotal(order)
if err := processPayment(order, total); err != nil {
return err
}
return nil
}
11. ANTI-PATTERNS LIBRARY
11.1 God Object
Problem: One class/module does everything
Solution: Split into smaller, focused modules (SRP)
11.2 Spaghetti Code
Problem: Tangled, unstructured code
Solution: Use clear architecture (Clean Architecture, layered)
11.3 Magic Numbers
Problem: Unexplained literal values
// ❌ Bad
if user.Age > 18 && user.Age < 65 {
// ...
}
// ✅ Good
const (
MinimumAdultAge = 18
RetirementAge = 65
)
if user.Age >= MinimumAdultAge && user.Age < RetirementAge {
// ...
}
11.4 Premature Optimization
Problem: Optimizing before identifying bottlenecks
Solution: Profile first, then optimize
12. ERROR PREVENTION
12.1 Pre-Flight Checks (OBLIGATOIRE)
Avant de commencer TOUTE nouvelle tâche :
- Exécuter
./scripts/pre-flight-check.sh - Vérifier qu'aucune erreur P0/P1 existe
- Tests existants passent
- Linter clean
- Code à jour avec
main
Référence complète : docs/ORIGIN/ORIGIN_ERROR_PREVENTION_GUIDE.md
12.2 Templates de Code (OBLIGATOIRE)
Utiliser les templates validés pour créer de nouveaux fichiers :
- Backend Go :
dev-environment/templates/backend-*.template.go - Frontend React :
dev-environment/templates/frontend-*.template.tsx - Rust :
dev-environment/templates/rust-*.template.rs
NE JAMAIS créer un fichier sans utiliser un template (sauf exception approuvée).
12.3 Patterns Sûrs
Backend Go :
- ✅ Interfaces dans
internal/types/ouinternal/interfaces/ - ✅ Services dépendent uniquement d'interfaces
- ✅ Handlers dépendent uniquement d'interfaces
- ✅ Types cohérents (toujours
stringOU toujours*string)
Frontend TypeScript/React :
- ✅ Types explicites pour toutes les fonctions
- ✅ Self-closing tags JSX (
<Component />) - ✅ Mocks configurés pour tous les tests
- ✅ Logger au lieu de
console.log
Référence : docs/ORIGIN/ORIGIN_ERROR_PATTERNS.md pour tous les patterns.
12.4 Quality Gates
Pre-Commit (Husky) :
- Formatage automatique
- Linter (zero errors)
- Tests unitaires rapides
- Type checking
Pre-Merge (GitHub Actions) :
- Architecture validation (import cycles)
- Type safety
- Test coverage ≥ 80%
- Linter validation
- Build validation
Si un gate échoue : Corriger l'erreur, NE PAS contourner.
12.5 Documentation des Erreurs
Si une nouvelle erreur est découverte :
- Documenter dans
docs/ORIGIN/ORIGIN_ERROR_PATTERNS.md - Ajouter la solution standard
- Mettre à jour les checklists de prévention
- Communiquer à l'équipe
✅ CHECKLIST DE VALIDATION
Code Quality
- Formatters run (gofmt, rustfmt, Prettier)
- Linters pass (golangci-lint, clippy, ESLint)
- Tests written (coverage ≥ 80%)
- Documentation updated (README, comments)
Error Prevention
- Pre-flight check exécuté et passé
- Template utilisé pour nouveau fichier
- Patterns sûrs suivis
- Aucun anti-pattern introduit
Architecture
- Clean Architecture followed
- SOLID principles respected
- Dependencies injected
- Separation of concerns
- No import cycles
Performance
- No N+1 queries
- Expensive operations optimized
- Caching considered
- Bundle size acceptable (frontend)
Security
- Input validated
- Errors handled
- Secrets not committed
- SQL injection prevented
📊 MÉTRIQUES DE SUCCÈS
Code Quality Metrics
- Cyclomatic Complexity: < 10 per function
- Function Length: < 50 lines
- Test Coverage: ≥ 80%
- Code Duplication: < 3%
- Linter Warnings: 0
🔄 HISTORIQUE DES VERSIONS
| Version | Date | Changements |
|---|---|---|
| 1.0.0 | 2025-11-02 | Version initiale - Standards complets |
⚠️ AVERTISSEMENT
CES STANDARDS SONT IMMUABLES
Les standards de code définis ici sont OBLIGATOIRES. Toute dérogation nécessite:
- RFC Code Standards Exception avec justification
- Approbation Lead Engineer
- Documentation de l'exception
Le non-respect des standards bloque la merge de la PR.
Document créé par: Engineering Team
Date de création: 2025-11-02
Prochaine révision: Annuelle
Propriétaire: Lead Engineers (Go, Rust, Frontend)
Statut: ✅ APPROUVÉ ET VERROUILLÉ