This commit is contained in:
senke 2026-04-05 18:54:15 +02:00
parent 66471934af
commit ef71d70b44
30 changed files with 8309 additions and 684 deletions

View file

@ -0,0 +1,500 @@
# Conventions OpenAPI — Veza
> Comment écrire, organiser et publier les spécifications OpenAPI des APIs Veza.
> Standard : **OpenAPI 3.1**
> Dernière mise à jour : avril 2026.
---
## 1. Pourquoi OpenAPI
- **Source de vérité** : un contrat API sans ambiguïté entre backend/frontend/mobile
- **Génération client** : SDKs TypeScript/Go/Rust auto-générés
- **Documentation vivante** : Swagger UI / Redoc auto-déployé
- **Tests contractuels** : vérification que l'implémentation respecte le contrat
- **Pas de dérive** : impossible de merger un code qui ne respecte pas le spec
---
## 2. Organisation des fichiers
### 2.1 Structure
```
API_Specs/
├── CONVENTIONS_OPENAPI.md # ce document
├── openapi.yaml # spec root (références les domaines)
├── domains/
│ ├── auth.yaml # endpoints /auth/*
│ ├── users.yaml # endpoints /users/*
│ ├── tracks.yaml # endpoints /tracks/*
│ ├── samples.yaml # endpoints /samples/*
│ ├── trades.yaml # endpoints /trades/*
│ ├── learn.yaml # endpoints /learn/*
│ ├── shop.yaml # endpoints /shop/*
│ ├── payments.yaml # endpoints /payments/*
│ ├── cloud.yaml # endpoints /cloud/*
│ ├── community.yaml # endpoints /community/*
│ └── stream.yaml # endpoints Rust stream server
├── components/
│ ├── schemas.yaml # modèles partagés (User, Track, Error, ...)
│ ├── responses.yaml # réponses communes (401, 403, 429, 500)
│ ├── parameters.yaml # paramètres communs (pagination, filters)
│ └── security.yaml # schémas d'auth
└── examples/
├── auth_register.json
├── sample_pack_create.json
└── ...
```
### 2.2 Fichier root `openapi.yaml`
```yaml
openapi: 3.1.0
info:
title: Veza API
version: 1.0.0
description: |
API REST de la plateforme Veza.
Base URL : https://api.veza.talas.co/v1
contact:
email: dev@talas.co
license:
name: Proprietary
servers:
- url: https://api.veza.talas.co/v1
description: Production
- url: http://localhost:18080/api/v1
description: Dev local
tags:
- name: Auth
description: Authentification et sessions
- name: Users
description: Gestion profils
- name: Tracks
description: Morceaux audio
# ... (un tag par domaine)
paths:
$ref: './domains/index.yaml' # aggregation
components:
schemas:
$ref: './components/schemas.yaml'
responses:
$ref: './components/responses.yaml'
parameters:
$ref: './components/parameters.yaml'
securitySchemes:
$ref: './components/security.yaml'
security:
- BearerAuth: []
- CookieAuth: []
```
---
## 3. Conventions de nommage
### 3.1 Paths
- **kebab-case** dans les URLs : `/user-profiles`, `/sample-packs`
- **Pluriel** pour les collections : `/tracks`, pas `/track`
- **Singulier** pour les singletons : `/me`, `/auth`
- **Verbes standards** : GET, POST, PUT, PATCH, DELETE
- **Pas de verbes dans les paths** sauf actions métier (`POST /orders/:id/cancel`)
### 3.2 Query parameters
- **snake_case** : `?user_id=...&created_after=...`
- **Arrays** : `?tags=rock,jazz` (comma-separated) ou `?tags[]=rock&tags[]=jazz`
### 3.3 Bodies JSON
- **snake_case** partout : `{ "user_id": "...", "created_at": "..." }`
- **Pas de camelCase** dans le JSON (cohérent avec PostgreSQL)
### 3.4 Opération IDs (pour codegen)
Convention : `verbResource` ou `verbResourceBy...`
```yaml
paths:
/tracks:
get:
operationId: listTracks
post:
operationId: createTrack
/tracks/{id}:
get:
operationId: getTrack
put:
operationId: updateTrack
delete:
operationId: deleteTrack
```
---
## 4. Pagination
### 4.1 Convention standard (page+limit)
Query params :
- `page` (default: 1, min: 1)
- `limit` (default: 20, max: 100)
Response :
```json
{
"data": [ ... ],
"pagination": {
"page": 1,
"limit": 20,
"total_items": 342,
"total_pages": 18
}
}
```
### 4.2 Cursor-based (gros volumes)
Pour les endpoints avec >10k résultats :
```
GET /tracks?cursor=eyJpZCI6...&limit=20
Response:
{
"data": [ ... ],
"next_cursor": "eyJpZCI6..."
}
```
---
## 5. Format d'erreur
### 5.1 Structure standard
```json
{
"error": {
"code": "validation_failed",
"message": "Un champ est invalide",
"details": [
{
"field": "email",
"rule": "required",
"message": "L'email est requis"
}
]
},
"request_id": "req_abc123"
}
```
### 5.2 Codes d'erreur métier (snake_case)
| Code | HTTP | Description |
|------|------|-------------|
| `validation_failed` | 400 | Payload invalide |
| `authentication_required` | 401 | Pas ou token invalide |
| `forbidden` | 403 | Permissions insuffisantes |
| `not_found` | 404 | Ressource absente |
| `conflict` | 409 | Conflit de version / duplicate |
| `rate_limited` | 429 | Trop de requêtes |
| `internal_error` | 500 | Erreur serveur |
### 5.3 Erreurs spécifiques domaine
Chaque domaine a ses codes (préfixés) :
- `auth_wrong_credentials` (401)
- `payment_declined` (402)
- `cart_item_out_of_stock` (409)
- `upload_file_too_large` (413)
- `upload_invalid_format` (415)
---
## 6. Authentification
### 6.1 Schémas sécurité
```yaml
components:
securitySchemes:
BearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
CookieAuth:
type: apiKey
in: cookie
name: access_token
CsrfToken:
type: apiKey
in: header
name: X-CSRF-Token
```
### 6.2 Application par endpoint
```yaml
paths:
/auth/login:
post:
security: [] # pas d'auth requise
/users/me:
get:
security:
- CookieAuth: []
# ou:
- BearerAuth: []
```
---
## 7. Versioning
### 7.1 Stratégie
- **Version dans URL** : `/api/v1/...`, `/api/v2/...`
- **Majeure uniquement** : pas de minor/patch dans URL
- **Cohabitation** : v1 et v2 actives pendant 6 mois min lors d'upgrade
### 7.2 Déprécation
- Header `Deprecation: true` + `Sunset: Wed, 31 Dec 2026 23:59:59 GMT`
- Champs JSON dépréciés marqués `deprecated: true` dans le spec
- Annonce 3 mois avant sunset
---
## 8. Exemples de spec
### 8.1 Endpoint simple (GET)
```yaml
/tracks/{id}:
get:
operationId: getTrack
tags: [Tracks]
summary: Récupère un track par ID
parameters:
- name: id
in: path
required: true
schema:
type: string
format: uuid
responses:
'200':
description: Track trouvé
content:
application/json:
schema:
$ref: '#/components/schemas/Track'
'404':
$ref: '#/components/responses/NotFound'
```
### 8.2 Endpoint avec body (POST)
```yaml
/tracks:
post:
operationId: createTrack
tags: [Tracks]
summary: Crée un nouveau track
security:
- CookieAuth: []
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateTrackInput'
examples:
basic:
$ref: '#/components/examples/track_create_basic'
responses:
'201':
description: Track créé
content:
application/json:
schema:
$ref: '#/components/schemas/Track'
'400':
$ref: '#/components/responses/ValidationError'
'401':
$ref: '#/components/responses/Unauthorized'
```
### 8.3 Schema avec validation
```yaml
CreateTrackInput:
type: object
required: [title, duration_sec]
properties:
title:
type: string
minLength: 1
maxLength: 200
example: "Morning Coffee"
duration_sec:
type: number
minimum: 1
maximum: 7200
tags:
type: array
items:
type: string
minLength: 2
maxLength: 40
maxItems: 10
is_public:
type: boolean
default: true
```
---
## 9. Outils
### 9.1 Édition
- **Swagger Editor** (self-hosted) : `http://editor.veza.internal`
- **VS Code extension** : OpenAPI (Swagger) Editor by 42Crunch
### 9.2 Documentation publique
**Redoc** (self-hosted) : `https://api.veza.talas.co/docs`
- Build statique à partir de `openapi.yaml`
- Regeneration sur chaque merge main
### 9.3 Génération de clients
| Langage | Outil | Output |
|---------|-------|--------|
| TypeScript | `openapi-typescript` | types seulement |
| TypeScript client | `openapi-fetch` | types + fetch wrapper |
| Go | `oapi-codegen` | types + server/client |
| Rust | `openapi-generator-cli` | types + reqwest client |
### 9.4 Tests contractuels
- **Dredd** (CI) : vérifie que le backend respecte le spec
- **Schemathesis** : property-based testing basé sur le spec
---
## 10. Workflow
### 10.1 Ajout d'un endpoint
1. **Design first** : écrire le spec OpenAPI d'abord
2. **Review** : validation en PR (au moins 1 reviewer)
3. **Generate** : regénérer types TypeScript et clients Go
4. **Implement** : backend + frontend utilisent les types générés
5. **Test** : Dredd en CI vérifie la conformité
6. **Deploy** : Redoc redéployé automatiquement
### 10.2 Modification breaking change
- **Ne pas modifier** un endpoint existant sauf bugfix
- **Ajouter v2** pour changement incompatible
- **Annoncer sunset** v1 avec délai
### 10.3 Validation CI
```yaml
# .github/workflows/openapi.yaml
- name: Validate OpenAPI
run: npx @stoplight/spectral-cli lint openapi.yaml
- name: Check contract
run: dredd openapi.yaml http://localhost:18080
```
---
## 11. Conventions spécifiques Veza
### 11.1 Timestamps
Toujours **ISO 8601 UTC** : `2026-04-05T14:30:00Z`
```yaml
created_at:
type: string
format: date-time
```
### 11.2 IDs
Toujours **UUID v4** :
```yaml
id:
type: string
format: uuid
example: "550e8400-e29b-41d4-a716-446655440000"
```
### 11.3 Montants
Toujours en **centimes (int)**, jamais en float :
```yaml
amount_cents:
type: integer
minimum: 0
example: 9900 # = 99.00 €
currency:
type: string
pattern: "^[A-Z]{3}$"
example: "EUR"
```
### 11.4 Enums
Enum as `snake_case` strings :
```yaml
status:
type: string
enum: [draft, published, archived]
```
---
## 12. Roadmap API_Specs
### V1.0 (Q2 2026)
- [ ] Créer structure de dossiers
- [ ] Documenter auth, users, tracks (ceux qui existent déjà)
- [ ] Setup Redoc auto-deploy
- [ ] Setup spectral linting CI
### V1.5 (Q3 2026)
- [ ] Domaines samples, trades, events
- [ ] Domaines learn, cloud
- [ ] Génération clients TS + Go en CI
- [ ] Dredd contract tests
### V2.0 (Q4 2026)
- [ ] Domaines shop, payments
- [ ] Async API (WebSocket) documenté avec AsyncAPI
- [ ] Webhooks documentés (spec Mollie webhooks)
---
## Voir aussi
- [[ROUTES_API]] — liste exhaustive endpoints actuels (source de conversion vers OpenAPI)
- [[ARCHITECTURE_VEZA]] — architecture globale
- [[Rust_Engines/OVERVIEW_RUST_MODULES]] — APIs spécifiques Rust

View file

@ -0,0 +1,386 @@
# Overview — Modules Rust Veza
> Vue d'ensemble des moteurs Rust de Veza, patterns communs et roadmap.
> Détail du stream-server existant : voir [[SERVEUR_STREAMING_RUST]]
> Dernière mise à jour : avril 2026.
---
## 1. Pourquoi Rust chez Veza
Veza utilise Rust **uniquement** pour les composants où ça fait la différence :
- **Performance CPU** : transcoding audio, DSP, traitement temps réel
- **Concurrence massive** : WebSocket streaming à N clients simultanés
- **Sûreté mémoire** : code proche du hardware sans GC pauses
- **Stabilité long-terme** : services qui tournent 24/7 sans leaks
Le reste (CRUD, business logic, auth) reste en **Go** (plus rapide à itérer, équipe plus large).
---
## 2. Modules existants
### 2.1 `veza-stream-server` (actif)
**Source** : `/home/senke/git/talas/veza/veza-stream-server/`
Responsabilités :
- Streaming HLS adaptatif multi-bitrate
- WebSocket temps réel avec sync lecture
- Transcoding audio via FFmpeg
- Métriques Prometheus
Stack : Axum, Tokio, FFmpeg bindings.
Voir [[SERVEUR_STREAMING_RUST]] pour détails.
---
## 3. Modules futurs (roadmap)
### 3.1 `veza-audio-analyzer` (V1.5, Q3 2026)
**Mission** : analyse audio asynchrone pour uploads.
**Tâches** :
- Détection tempo (BPM)
- Détection tonalité (key signature)
- Analyse spectrale (RMS, LUFS, crête)
- Détection fingerprint (anti-doublon)
- Génération waveform peaks (pour visualisation)
**Stack envisagée** :
- `aubio-rs` ou binding `essentia`
- `symphonia` (décodage audio pure-Rust)
- Workers RabbitMQ
**Intégration** :
```
[Backend Go] upload track
→ publish RabbitMQ "track.analyze"
→ [audio-analyzer Rust] consomme
→ analyse
→ écrit résultats en DB
→ publish "track.analyzed"
→ [Backend Go] met à jour metadata
```
### 3.2 `veza-dsp-engine` (V2.0, 2027)
**Mission** : moteur DSP pour effets audio temps réel.
**Use cases** :
- Preview de samples avec effets (EQ, reverb, compression) sans DAW
- Mastering automatique léger (LUFS target)
- Génération de previews "stylisées" (cassette, radio, vinyl)
**Stack envisagée** :
- `fundsp` ou `cpal` + `rubato`
- WASM export pour preview client-side (V2.5)
### 3.3 `veza-encoder-farm` (V2.0, 2027)
**Mission** : cluster de transcoding distribué.
**Problème actuel** : le stream-server fait le transcoding inline, ne scale pas au-delà de ~50 uploads/h.
**Solution** :
- N workers Rust sur le cluster
- Queue RabbitMQ pour distribution
- Output sur MinIO
- Métriques par worker
### 3.4 `veza-search-ranker` (V2+, optionnel)
**Mission** : ré-ranking des résultats Elasticsearch avec signaux Veza (popularité, affinité).
**Pertinence** : seulement si ES brut ne suffit pas.
---
## 4. Patterns communs
### 4.1 Structure projet Rust
```
veza-<module>/
├── Cargo.toml
├── src/
│ ├── main.rs # entry point (si service)
│ ├── lib.rs # si library
│ ├── config/ # config depuis env
│ ├── handlers/ # HTTP/RMQ handlers
│ ├── services/ # business logic
│ ├── models/ # types métier
│ ├── errors/ # AppError enum
│ └── telemetry/ # metrics, logs, tracing
├── tests/ # tests d'intégration
└── benches/ # benchmarks (criterion)
```
### 4.2 Gestion d'erreurs
**Pattern** : custom `AppError` enum + `thiserror`
```rust
use thiserror::Error;
#[derive(Error, Debug)]
pub enum AppError {
#[error("Database error: {0}")]
Database(#[from] sqlx::Error),
#[error("Upstream service error: {0}")]
Upstream(String),
#[error("Validation: {0}")]
Validation(String),
#[error("Not found: {0}")]
NotFound(String),
}
pub type AppResult<T> = Result<T, AppError>;
```
Conversion en HTTP :
```rust
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, code) = match &self {
AppError::Database(_) => (500, "internal_error"),
AppError::Upstream(_) => (502, "upstream_error"),
AppError::Validation(_) => (400, "validation_failed"),
AppError::NotFound(_) => (404, "not_found"),
};
// ... construit JSON error standard
}
}
```
### 4.3 Runtime & async
- **Tokio** multi-thread runtime (par défaut)
- **Pas de `block_on`** dans les handlers
- **`tokio::spawn`** pour les tâches détachées
### 4.4 Configuration
Pattern : struct `Config` hydratée depuis env via `serde` + `envy` ou `config-rs`.
```rust
#[derive(Deserialize)]
pub struct Config {
pub database_url: String,
pub redis_url: String,
pub rabbitmq_url: String,
pub port: u16,
pub log_level: String,
}
impl Config {
pub fn from_env() -> Result<Self, envy::Error> {
envy::prefixed("VEZA_").from_env()
}
}
```
### 4.5 Logging
Stack : **`tracing` + `tracing-subscriber`**
```rust
use tracing::{info, warn, error, instrument};
#[instrument(skip(db))]
pub async fn process_track(db: &Pool, track_id: Uuid) -> AppResult<()> {
info!(%track_id, "processing track");
// ...
}
```
Format : **JSON structuré en prod**, texte coloré en dev.
### 4.6 Metrics
Stack : **`prometheus` + `axum-prometheus`**
Chaque service expose `/metrics`. Dashboard Grafana centralisé.
### 4.7 Health checks
- `/healthz` → liveness (service alive, réponse 200)
- `/readyz` → readiness (dépendances OK : DB, RMQ, S3)
---
## 5. Interfaces entre services
### 5.1 Service-to-service
| Méthode | Usage |
|---------|-------|
| **REST interne** | Callbacks synchrones (Rust ↔ Go) |
| **RabbitMQ** | Events asynchrones (Rust consume, Go produce) |
| **PostgreSQL** | État partagé (Rust en lecture directe parfois) |
| **MinIO S3** | Blob storage commun |
### 5.2 Service → client
- **HTTP REST** : endpoints exposés via Axum
- **WebSocket** : streaming audio temps réel
- **Server-Sent Events** : si push unidirectionnel suffit
### 5.3 Pas de gRPC en V1
**Décision** : pas de gRPC tant que REST + RMQ suffisent. Complexité opérationnelle non justifiée.
---
## 6. Dépendances communes (Cargo.toml)
```toml
[dependencies]
# Runtime
tokio = { version = "1.42", features = ["full"] }
# HTTP
axum = "0.7"
tower = "0.5"
tower-http = { version = "0.6", features = ["cors", "trace"] }
# Serialization
serde = { version = "1", features = ["derive"] }
serde_json = "1"
# Errors
thiserror = "2"
anyhow = "1"
# Database
sqlx = { version = "0.8", features = ["postgres", "uuid", "time", "runtime-tokio"] }
# Storage S3
aws-sdk-s3 = "1"
# Messaging
lapin = "2" # RabbitMQ
# Observability
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] }
prometheus = "0.13"
# Config
envy = "0.4"
# UUID / time
uuid = { version = "1", features = ["v4", "serde"] }
time = { version = "0.3", features = ["serde"] }
[dev-dependencies]
criterion = "0.5" # benchmarks
mockall = "0.13" # mocking
```
---
## 7. Tests
### 7.1 Niveaux
| Niveau | Outil | Cible |
|--------|-------|-------|
| Unit | `cargo test` | Fonctions isolées |
| Integration | `cargo test --test ...` | Handlers avec DB test |
| Benchmarks | `criterion` | Hot paths (DSP, transcoding) |
| Load | `k6` ou `vegeta` | Endpoints HTTP/WS |
### 7.2 Conventions
- Tests unitaires dans le même fichier que le code (`#[cfg(test)] mod tests`)
- Tests d'intégration dans `tests/` avec base de données test (Docker)
- Benchmarks dans `benches/`
---
## 8. Build & deploy
### 8.1 Build
```bash
# Dev (rapide)
cargo run
# Release optimisé
cargo build --release
# Docker multi-stage
docker build -t veza-stream-server .
```
### 8.2 Image Docker type
```dockerfile
FROM rust:1.83 AS builder
WORKDIR /app
COPY . .
RUN cargo build --release
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ffmpeg ca-certificates
COPY --from=builder /app/target/release/veza-stream-server /usr/local/bin/
CMD ["veza-stream-server"]
```
### 8.3 Déploiement
Ansible role : `04_INFRA_DEPLOIEMENT/Ansible/roles/veza-rust-service/`
---
## 9. Benchmarks & objectifs
### 9.1 Stream server
| Métrique | Objectif | Mesure actuelle |
|----------|----------|-----------------|
| Connexions WS simultanées | 1000+ | À mesurer |
| Latence chunks audio | < 100ms p99 | À mesurer |
| Mémoire / 1000 WS | < 2 GB | À mesurer |
### 9.2 Audio analyzer (futur)
| Métrique | Objectif |
|----------|----------|
| Durée analyse / min audio | < 10 sec |
| Throughput workers | 100+ tracks/h / worker |
---
## 10. Roadmap
### V1.0 (actuel)
- [x] `veza-stream-server` production
### V1.5 (Q3 2026)
- [ ] `veza-audio-analyzer` (BPM, key, waveform)
- [ ] Benchmarks automatisés en CI
### V2.0 (2027)
- [ ] `veza-dsp-engine` (effets temps réel)
- [ ] `veza-encoder-farm` (scaling transcoding)
### V2.5+
- [ ] Export WASM pour DSP côté client
- [ ] Compatibilité WebCodecs (browser-native transcoding)
---
## Voir aussi
- [[SERVEUR_STREAMING_RUST]] — détail stream-server existant
- [[ARCHITECTURE_VEZA]] — architecture globale
- [[API_Specs/CONVENTIONS_OPENAPI]] — conventions API
- [[04_INFRA_DEPLOIEMENT]] — déploiement des services Rust

View file

@ -0,0 +1,464 @@
# Spec technique — Authentification & Core System
> Architecture auth de Veza : décisions, modèle de sécurité, RBAC, conformité.
> Ce document complète [[ROUTES_API]] qui liste les endpoints — ici on documente le **pourquoi** et le **comment**.
> Stack : Go (Gin + GORM) · PostgreSQL 16 · Redis · JWT RS256
> Dernière mise à jour : avril 2026.
---
## 1. Vue d'ensemble
Veza implémente une auth **multi-facteurs, multi-device, avec rotation de clés**. Défense en profondeur : chaque couche de sécurité est indépendante (JWT, CSRF, rate limit, verrouillage compte, audit).
### Principes fondateurs
1. **Auth centralisée** — un seul module pour toutes les apps (web, mobile, desktop)
2. **Tokens courts** — JWT access 15 min, refresh 30 jours
3. **Cookies HTTP-only** (prod) + header Authorization (mobile/API)
4. **Rotation de clés** RS256 automatique
5. **Audit complet** — toute action sensible est loggée
---
## 2. Modèle de sécurité (couches)
```
┌─────────────────────────────────────────────────────┐
│ 1. Rate limiting (global, par IP, par endpoint) │
├─────────────────────────────────────────────────────┤
│ 2. CSRF token (Redis-backed, obligatoire POST/PUT) │
├─────────────────────────────────────────────────────┤
│ 3. JWT validation (signature RS256 + expiration) │
├─────────────────────────────────────────────────────┤
│ 4. Permissions RBAC (role + resource check) │
├─────────────────────────────────────────────────────┤
│ 5. Audit logging (écriture DB async via worker) │
└─────────────────────────────────────────────────────┘
```
Chaque couche peut refuser la requête **indépendamment**.
---
## 3. Authentification
### 3.1 JWT — choix de design
| Décision | Valeur | Justification |
|----------|--------|---------------|
| Algorithme | **RS256** | Asymétrique, permet de distribuer la clé publique au stream-server sans partager le secret |
| Access token TTL | **15 min** | Compromis sécurité/UX |
| Refresh token TTL | **30 jours** | Évite re-login trop fréquent |
| Rotation des clés | **Automatique, toutes les 90 jours** | Via cron, ancienne clé conservée 15 jours pour validation |
| Stockage | **Cookie HTTP-only + Secure + SameSite=Lax** (web) / Header Authorization (API clients) | Protection XSS |
### 3.2 Cycle de vie des tokens
```
[Login]
POST /auth/login (email, password)
→ Backend vérifie password (bcrypt)
→ Génère access_token (15min) + refresh_token (30d)
→ refresh_token stocké en DB (hashé) dans table refresh_tokens
→ Cookies HTTP-only retournés
[Appel authentifié]
GET /auth/me
Cookie: access_token=...
→ Middleware valide JWT (signature + expiration)
→ Injecte user_id dans contexte Gin
[Refresh]
POST /auth/refresh
Cookie: refresh_token=...
→ Backend vérifie hash en DB
→ Génère nouveau access_token
→ Tourne le refresh_token (nouveau généré, ancien révoqué)
→ Response: nouveaux cookies
[Logout]
POST /auth/logout
→ Révoque le refresh_token en DB
→ Clear cookies
```
### 3.3 Table `refresh_tokens`
```sql
CREATE TABLE refresh_tokens (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
token_hash VARCHAR(64) NOT NULL, -- SHA-256 du token
device_id VARCHAR(100), -- fingerprint léger
user_agent VARCHAR(500),
ip_address INET,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ NOT NULL,
revoked_at TIMESTAMPTZ,
last_used_at TIMESTAMPTZ
);
CREATE INDEX idx_refresh_tokens_user ON refresh_tokens(user_id) WHERE revoked_at IS NULL;
CREATE INDEX idx_refresh_tokens_hash ON refresh_tokens(token_hash) WHERE revoked_at IS NULL;
```
### 3.4 Multi-device
Un user peut être connecté sur N devices simultanément. Chaque device = un refresh_token distinct.
- **Lister mes devices** : `GET /auth/sessions`
- **Déconnecter un device** : `DELETE /auth/sessions/:id`
- **Déconnecter tous les devices** : `DELETE /auth/sessions/all`
---
## 4. Password policy
### 4.1 Règles
| Règle | Valeur |
|-------|--------|
| Longueur min | 12 caractères |
| Longueur max | 128 caractères |
| Complexité | 3 des 4 : minuscule, majuscule, chiffre, symbole |
| Hash | bcrypt cost 12 |
| Vérification pwned | Hash SHA-1 partiel check via **HaveIBeenPwned API** (self-hosted mirror si possible) |
| Expiration | **Pas de rotation forcée** (NIST 800-63B) |
### 4.2 Verrouillage de compte
- 5 tentatives échouées → verrouillage 15 minutes
- 10 tentatives échouées (tous IPs) → verrouillage 24h + email admin
- Reset : via email de déverrouillage
### 4.3 Reset password
```
POST /auth/password/reset-request { email }
→ Génère token UUID (expire 1h)
→ Stocke hash en DB
→ Envoie email avec lien
GET /reset-password?token=xxx
→ Frontend vérifie via POST /auth/password/reset/validate
→ Affiche formulaire
POST /auth/password/reset { token, new_password }
→ Vérifie token (hash) + expiration
→ Change password
→ Révoque TOUS les refresh_tokens du user (force logout)
→ Invalide token
```
---
## 5. 2FA / MFA
### 5.1 Méthodes supportées
| Méthode | Priorité | Use case |
|---------|----------|----------|
| **TOTP** (Google Authenticator, Aegis, 2FAS) | Primaire | Standard ouvert |
| **WebAuthn / Passkeys** | Primaire | Meilleure UX, biométrie, hardware keys |
| **Backup codes** | Secondaire | 10 codes à usage unique |
| **SMS** | **NON** | Vulnérable SIM swap, pas GDPR friendly |
### 5.2 Flow TOTP
```
[Setup]
POST /auth/2fa/setup
→ Génère secret TOTP (32 chars base32)
→ Stocke temporairement en Redis (5 min)
→ Response: { qr_code_url, secret }
POST /auth/2fa/verify { code }
→ Vérifie le code TOTP
→ Persiste secret en DB (chiffré)
→ Génère 10 backup codes
→ Response: { backup_codes: [...] }
[Login avec 2FA]
POST /auth/login { email, password }
→ Si user.mfa_enabled : response 200 { requires_2fa: true, temp_token: ... }
POST /auth/login/2fa { temp_token, code }
→ Vérifie TOTP ou backup code
→ Génère access + refresh tokens
```
### 5.3 Flow WebAuthn (Passkeys)
Voir [[ROUTES_API]] §1 pour les endpoints. Utilise **bibliothèque go-webauthn** côté backend.
### 5.4 Chiffrement des secrets 2FA en DB
- Secrets TOTP chiffrés en AES-256-GCM
- Clé de chiffrement : **KMS léger** via variable d'env `TOTP_ENCRYPTION_KEY`
- Rotation de clé supportée (re-chiffrement batch)
---
## 6. OAuth 2.0 (providers externes)
### 6.1 Providers supportés
| Provider | Usage | Priorité V1 |
|----------|-------|-------------|
| **Discord** | Communautés musicales | Haute |
| **GitHub** | Dev-friendly | Moyenne |
| **Google** | Vaste adoption | Basse (GAFAM, contraire aux valeurs) |
| **Apple** | iOS users | Basse |
| **Mastodon** | Fediverse-friendly | Moyenne |
**Décision V1** : Discord + GitHub. Pas de Google/Apple en V1 (incohérent avec valeurs self-hosted).
### 6.2 Flow OAuth
Standard OAuth 2.0 Authorization Code + PKCE :
```
[Initiation]
GET /auth/oauth/discord
→ Backend génère state + code_verifier
→ Stocke en Redis (TTL 10min, keyed par session)
→ Redirect vers Discord avec code_challenge
[Callback]
GET /auth/oauth/discord/callback?code=xxx&state=yyy
→ Vérifie state contre Redis
→ POST Discord /token avec code_verifier
→ Récupère access_token Discord
→ GET Discord /users/@me
→ SI user Veza existe avec cet oauth_id : login
SINON : création user (email vérifié auto via Discord)
→ Génère JWT Veza
```
### 6.3 Chiffrement des tokens OAuth
Les refresh_tokens OAuth sont stockés **chiffrés** en DB (AES-256-GCM) pour pouvoir rafraîchir l'access token du provider si besoin.
### 6.4 Unlink
Un user peut dissocier un provider OAuth :
- Si c'est le SEUL moyen de login → exige de définir un password d'abord
- Sinon : suppression simple du lien
---
## 7. RBAC — Roles & Permissions
### 7.1 Rôles V1
| Role | Permissions |
|------|-------------|
| `user` | Standard : lecture publique, écriture sur ses contenus |
| `content_creator` | + publier courses, être featured |
| `moderator` | + modérer comments, flagger contenus |
| `admin` | + gestion users, publication courses, modification catalogue shop |
| `superadmin` | + gestion infra, rotation clés, accès audit logs |
### 7.2 Table `user_roles`
```sql
CREATE TABLE user_roles (
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
role VARCHAR(30) NOT NULL,
granted_by UUID REFERENCES users(id),
granted_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ,
PRIMARY KEY (user_id, role)
);
CREATE INDEX idx_user_roles_role ON user_roles(role);
```
### 7.3 Permissions granulaires (V2+)
En V1 : check par role (`user.HasRole("admin")`).
En V2 : table `permissions` et matrice `role_permissions` pour fine-grained control.
### 7.4 Middleware Gin
```go
router.POST("/admin/users/:id/ban",
middleware.RequireAuth(),
middleware.RequireRole("moderator", "admin"),
handlers.BanUser,
)
```
---
## 8. CSRF Protection
### 8.1 Stratégie
**Double-submit cookie + Redis backing** :
1. GET `/csrf-token` → génère UUID, stocke en Redis (TTL 24h), retourne dans cookie `_csrf` ET header `X-CSRF-Token`
2. POST/PUT/DELETE : frontend envoie header `X-CSRF-Token` (depuis cookie accessible JS)
3. Backend vérifie : cookie == header == Redis value
### 8.2 Exclusions
- Endpoints d'auth (login, register, refresh) → exemptés (pas de session encore)
- API clients externes (mobile) → utilisent Bearer token, CSRF non-applicable
### 8.3 Expiration
Token renouvelé à chaque login. TTL 24h actif.
---
## 9. Rate limiting
### 9.1 Niveaux
| Niveau | Limite | Backing |
|--------|--------|---------|
| Global | 1000 req/s | Redis |
| Par IP | 100 req/s | Redis + token bucket |
| Par endpoint sensible | Variable | Redis |
### 9.2 Endpoints spécifiquement limités
| Endpoint | Limite |
|----------|--------|
| `POST /auth/login` | 5 tentatives / 15 min / IP |
| `POST /auth/register` | 3 / heure / IP |
| `POST /auth/password/reset-request` | 3 / heure / email |
| `POST /auth/verify-email` | 10 / heure / user |
| Upload multipart | 5 / jour / user |
### 9.3 Réponse 429
```json
{
"error": "rate_limited",
"retry_after_seconds": 120,
"message": "Trop de tentatives. Réessaye dans 2 minutes."
}
```
Header `Retry-After: 120` systématique.
---
## 10. Audit logging
### 10.1 Actions loggées
- Login (succès/échec)
- Logout
- Changement password
- Activation/désactivation 2FA
- Création/révocation session
- Changement de role
- Actions admin (ban, suppression)
### 10.2 Table `audit_logs`
```sql
CREATE TABLE audit_logs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
actor_id UUID REFERENCES users(id),
action VARCHAR(50) NOT NULL,
target_type VARCHAR(50),
target_id UUID,
ip_address INET,
user_agent VARCHAR(500),
metadata JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_audit_logs_actor ON audit_logs(actor_id, created_at DESC);
CREATE INDEX idx_audit_logs_action ON audit_logs(action, created_at DESC);
CREATE INDEX idx_audit_logs_target ON audit_logs(target_type, target_id);
```
### 10.3 Écriture asynchrone
Via RabbitMQ : l'app push un événement, un worker l'écrit en DB. Évite de bloquer les requêtes API sur l'écriture d'audit.
---
## 11. Conformité RGPD
### 11.1 Droits implémentés
| Droit | Endpoint | Délai |
|-------|----------|-------|
| Accès | `GET /auth/export-data` | Génération async, email avec lien (24h) |
| Rectification | `PUT /users/me` | Instant |
| Effacement | `DELETE /auth/account` | Soft delete immédiat, purge après 30 jours |
| Portabilité | `GET /auth/export-data` format JSON | 24h |
| Opposition | Opt-out analytics dans settings | Instant |
### 11.2 Pseudonymisation
Après suppression de compte :
- Email → `deleted_<uuid>@deleted.veza`
- Username → `deleted_<uuid>`
- Contenus : soft delete (conservables pour droits des tiers si morceau remixé etc.)
### 11.3 Cookies
| Cookie | Finalité | Durée | Opt-in ? |
|--------|----------|-------|----------|
| `access_token` | Auth | 15 min | Obligatoire (fonctionnel) |
| `refresh_token` | Auth | 30 jours | Obligatoire (fonctionnel) |
| `_csrf` | Sécurité | 24h | Obligatoire (fonctionnel) |
| `session_analytics` | Stats internes | 30j | **Opt-in** |
---
## 12. Considérations spéciales
### 12.1 Partage de clé publique JWT
Le stream-server Rust valide les JWT. Il récupère la clé publique RS256 via :
- Endpoint interne `GET /internal/jwks` (liste clés publiques valides)
- Cache local (TTL 1h)
- Refresh automatique si clé inconnue rencontrée
### 12.2 Propagation user sur services internes
Le user_id est propagé via header custom `X-User-ID` entre services internes, signé par une clé partagée (HMAC) pour éviter spoofing.
---
## 13. Roadmap
### V1.0 (actuel, avril 2026)
- [x] JWT RS256 + refresh
- [x] Password bcrypt
- [x] 2FA TOTP
- [x] OAuth Discord + GitHub
- [x] RBAC 5 rôles
- [x] CSRF Redis
- [x] Rate limiting multi-niveaux
- [x] Audit logging
### V1.5 (été 2026)
- [ ] WebAuthn/Passkeys
- [ ] Vérification password pwned (HIBP)
- [ ] Export données RGPD complet
- [ ] Rotation clés RS256 automatique
### V2.0 (fin 2026)
- [ ] Permissions fine-grained (au-delà des rôles)
- [ ] SSO SAML pour entreprises (si demandé)
- [ ] Audit dashboard admin
---
## Voir aussi
- [[ROUTES_API]] — liste complète des endpoints `/auth/*`
- [[ARCHITECTURE_VEZA]] — architecture globale
- [[SCHEMA_BASE_DE_DONNEES]] — tables users, sessions
- [[08_CONFORMITE_JURIDIQUE/RGPD]] — politique RGPD
- [[04_INFRA_DEPLOIEMENT/Sécurité]] — sécurité infra

View file

@ -0,0 +1,469 @@
# Spec technique — Module Formation
> Implémentation backend des parcours de formation Veza : tutoriels, masterclasses, bootcamps.
> Stack : Go (Gin + GORM) · PostgreSQL 16 · MinIO S3 · Rust stream-server · Redis
> Dernière mise à jour : avril 2026.
Voir aussi le curriculum côté contenu : [[06_COMMUNAUTE_ECOSYSTEME/Formation_Creators/CURRICULUM_FORMATION_CREATORS]]
---
## 1. Vue d'ensemble
Le module **Formation** héberge des **parcours d'apprentissage** composés de leçons (vidéo + texte + exercices). Positionnement : un Coursera artisanal, sans gamification agressive, sans certification bidon.
```
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Frontend │─────▶│ Backend API │─────▶│ PostgreSQL │
│ React │ │ Go / Gin │ │ (content + │
│ + Player │ └──────┬───────┘ │ progress) │
└──────┬───────┘ │ └──────────────┘
│ │
│ HLS stream ├────────────▶ MinIO S3
│ │ (vidéos, PDFs)
▼ │
┌──────────────┐ └────────────▶ Stream Server
│ Stream Server│◀─────────────────────────── (Rust, HLS)
│ Rust/Axum │
└──────────────┘
```
---
## 2. Modèle de données
### 2.1 Table `courses` (parcours complet)
```sql
CREATE TABLE courses (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
slug VARCHAR(100) NOT NULL UNIQUE,
title VARCHAR(150) NOT NULL,
subtitle VARCHAR(300),
description TEXT NOT NULL,
author_id UUID NOT NULL REFERENCES users(id),
cover_image_s3_key VARCHAR(500),
difficulty VARCHAR(20) NOT NULL CHECK (difficulty IN ('debutant','intermediaire','avance')),
language VARCHAR(5) NOT NULL DEFAULT 'fr',
estimated_hours NUMERIC(4,1),
category VARCHAR(50) NOT NULL,
-- 'production', 'mixing', 'mastering', 'sound_design',
-- 'recording', 'business', 'hardware', 'theory'
tags VARCHAR(40)[] DEFAULT '{}',
status VARCHAR(20) NOT NULL DEFAULT 'draft',
-- 'draft', 'published', 'archived'
is_free BOOLEAN NOT NULL DEFAULT true,
lessons_count INT NOT NULL DEFAULT 0,
enrolled_count INT NOT NULL DEFAULT 0,
completed_count INT NOT NULL DEFAULT 0,
published_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_courses_published ON courses(published_at DESC) WHERE status = 'published' AND deleted_at IS NULL;
CREATE INDEX idx_courses_category ON courses(category) WHERE status = 'published' AND deleted_at IS NULL;
CREATE INDEX idx_courses_author ON courses(author_id);
```
### 2.2 Table `course_modules` (chapitres)
```sql
CREATE TABLE course_modules (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
course_id UUID NOT NULL REFERENCES courses(id) ON DELETE CASCADE,
order_index INT NOT NULL,
title VARCHAR(150) NOT NULL,
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (course_id, order_index)
);
CREATE INDEX idx_course_modules_course ON course_modules(course_id, order_index);
```
### 2.3 Table `course_lessons` (leçons individuelles)
```sql
CREATE TABLE course_lessons (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
module_id UUID NOT NULL REFERENCES course_modules(id) ON DELETE CASCADE,
course_id UUID NOT NULL REFERENCES courses(id) ON DELETE CASCADE,
order_index INT NOT NULL,
title VARCHAR(150) NOT NULL,
content_markdown TEXT, -- contenu textuel de la leçon
lesson_type VARCHAR(20) NOT NULL,
-- 'video', 'article', 'exercise', 'quiz', 'download'
-- si video
video_s3_key VARCHAR(500),
video_duration_sec NUMERIC(6,1),
hls_manifest_url VARCHAR(500), -- URL vers le manifest HLS généré
-- si download (fichiers attachés)
attachments_s3_keys VARCHAR(500)[] DEFAULT '{}',
-- si exercise / quiz
exercise_config JSONB, -- structure flexible selon type
is_free_preview BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (module_id, order_index)
);
CREATE INDEX idx_course_lessons_course ON course_lessons(course_id, order_index);
CREATE INDEX idx_course_lessons_module ON course_lessons(module_id, order_index);
```
### 2.4 Table `course_enrollments`
```sql
CREATE TABLE course_enrollments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
course_id UUID NOT NULL REFERENCES courses(id) ON DELETE CASCADE,
enrolled_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
last_accessed_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
progress_percent NUMERIC(5,2) NOT NULL DEFAULT 0,
UNIQUE (user_id, course_id)
);
CREATE INDEX idx_course_enrollments_user ON course_enrollments(user_id, last_accessed_at DESC);
CREATE INDEX idx_course_enrollments_course ON course_enrollments(course_id);
```
### 2.5 Table `lesson_progress`
```sql
CREATE TABLE lesson_progress (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
lesson_id UUID NOT NULL REFERENCES course_lessons(id) ON DELETE CASCADE,
course_id UUID NOT NULL REFERENCES courses(id) ON DELETE CASCADE,
status VARCHAR(20) NOT NULL DEFAULT 'not_started',
-- 'not_started', 'in_progress', 'completed'
-- si vidéo : timestamp de reprise
video_position_sec NUMERIC(8,1) DEFAULT 0,
-- si exercise/quiz : résultat
exercise_result JSONB,
started_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (user_id, lesson_id)
);
CREATE INDEX idx_lesson_progress_user_course ON lesson_progress(user_id, course_id);
```
### 2.6 Table `course_comments` (discussions par leçon)
```sql
CREATE TABLE course_comments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
lesson_id UUID NOT NULL REFERENCES course_lessons(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
parent_id UUID REFERENCES course_comments(id) ON DELETE CASCADE,
body TEXT NOT NULL,
video_timestamp_sec NUMERIC(8,1), -- si commentaire ancré à un moment de la vidéo
edited BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_course_comments_lesson ON course_comments(lesson_id, created_at) WHERE deleted_at IS NULL;
```
### 2.7 Table `course_reviews` (évaluations)
```sql
CREATE TABLE course_reviews (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id),
course_id UUID NOT NULL REFERENCES courses(id) ON DELETE CASCADE,
rating INT NOT NULL CHECK (rating BETWEEN 1 AND 5),
body TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (user_id, course_id)
);
CREATE INDEX idx_course_reviews_course ON course_reviews(course_id);
```
---
## 3. API Endpoints
Base : `/api/v1/learn`
### 3.1 Parcours — consultation (public)
| Méthode | Route | Auth | Description |
|---------|-------|------|-------------|
| GET | `/courses` | 🔓 | Liste courses publiés (filtres) |
| GET | `/courses/:slug` | 🔓 | Détail d'un course (syllabus) |
| GET | `/courses/:slug/modules` | 🔓 | Arborescence modules + leçons |
| GET | `/lessons/:id/preview` | 🔓 | Preview (si `is_free_preview=true`) |
### 3.2 Inscription et progression
| Méthode | Route | Auth | Description |
|---------|-------|------|-------------|
| POST | `/courses/:id/enroll` | 🔒 | S'inscrire à un course |
| DELETE | `/courses/:id/enroll` | 🔒 | Se désinscrire |
| GET | `/me/courses` | 🔒 | Mes inscriptions |
| GET | `/me/courses/:course_id/progress` | 🔒 | Progression détaillée |
| GET | `/lessons/:id` | 🔒 | Contenu leçon (inscrit au course) |
| POST | `/lessons/:id/start` | 🔒 | Marquer commencée |
| POST | `/lessons/:id/complete` | 🔒 | Marquer terminée |
| PUT | `/lessons/:id/progress` | 🔒 | Sauvegarder position (video_position_sec) |
| POST | `/lessons/:id/exercise` | 🔒 | Soumettre résultat d'exercice |
### 3.3 Interactions
| Méthode | Route | Auth | Description |
|---------|-------|------|-------------|
| GET | `/lessons/:id/comments` | 🔓 | Liste commentaires |
| POST | `/lessons/:id/comments` | 🔒 | Poster commentaire |
| PUT | `/comments/:id` | 🔒 | Modifier (own only) |
| DELETE | `/comments/:id` | 🔒 | Soft delete (own only) |
| GET | `/courses/:id/reviews` | 🔓 | Reviews |
| POST | `/courses/:id/review` | 🔒 | Poster review (une seule par user) |
### 3.4 Author / Admin
| Méthode | Route | Auth | Description |
|---------|-------|------|-------------|
| POST | `/courses` | 🔒🎨 | Créer course (status=draft) |
| PUT | `/courses/:id` | 🔒🎨 | Éditer course (own only) |
| POST | `/courses/:id/modules` | 🔒🎨 | Ajouter module |
| POST | `/modules/:id/lessons` | 🔒🎨 | Ajouter leçon |
| POST | `/lessons/:id/video-upload` | 🔒🎨 | Upload vidéo (génère URL signée) |
| POST | `/courses/:id/publish` | 🔒🎨 | Publier course |
---
## 4. Flux techniques
### 4.1 Création d'un course (auteur)
```
[1] POST /courses
Body: { title, subtitle, description, difficulty, language, category, tags }
→ Backend: INSERT courses (status='draft', author=current_user)
→ response: { course }
[2] POST /courses/:id/modules (plusieurs fois)
Body: { title, description, order_index }
→ Backend: INSERT course_modules
[3] POST /modules/:id/lessons (plusieurs fois)
Body: { title, lesson_type, content_markdown, order_index, is_free_preview }
→ Backend: INSERT course_lessons
→ response: { lesson, upload_url } (si video)
[4] PUT <upload_url> (binary vidéo)
→ Upload MinIO direct
[5] Worker "process_video" (RabbitMQ):
- Transcode FFmpeg multi-bitrate
- Génère manifest HLS (via stream-server Rust)
- UPDATE course_lessons SET video_duration_sec, hls_manifest_url
[6] POST /courses/:id/publish
→ Backend:
- Validation (min 1 module, min 1 lesson, cover image, description>100car)
- UPDATE courses SET status='published', published_at=NOW()
- Notif aux followers de l'auteur
```
### 4.2 Progression (vue étudiant)
```
[1] POST /courses/:id/enroll
→ INSERT course_enrollments
→ UPDATE courses SET enrolled_count += 1
[2] GET /lessons/:id
→ Check enrollment existe
→ Retourne contenu + HLS URL signé (expire 2h)
[3] Pendant la lecture vidéo (toutes les 10 sec):
PUT /lessons/:id/progress
Body: { video_position_sec }
→ UPSERT lesson_progress
→ Update course_enrollments.last_accessed_at
[4] À la fin de la vidéo (ou clic "Terminé"):
POST /lessons/:id/complete
→ UPDATE lesson_progress SET status='completed', completed_at=NOW()
→ Recalcul course_enrollments.progress_percent
→ Si 100% : SET completed_at, UPDATE courses.completed_count += 1
```
### 4.3 Streaming vidéo
- Les vidéos sont servies en **HLS adaptatif** via le stream-server Rust (réutilise l'infra existante pour l'audio)
- URL signée générée au moment de `GET /lessons/:id` (expire 2h)
- Protection : URL liée à l'user_id (JWT check au niveau stream-server)
---
## 5. Exercices et quiz
### 5.1 Structure `exercise_config` (JSONB)
Selon le `lesson_type`, le champ `exercise_config` varie :
**Type `quiz`** :
```json
{
"questions": [
{
"id": "q1",
"text": "Quelle est la fréquence de Nyquist pour un sample rate de 44.1kHz ?",
"type": "single_choice",
"options": ["22.05 kHz", "44.1 kHz", "88.2 kHz"],
"correct": 0,
"explanation": "La fréquence de Nyquist est la moitié..."
}
]
}
```
**Type `exercise`** (exercice pratique à soumettre) :
```json
{
"instructions": "Crée un beat de 16 mesures avec uniquement kick + snare + hi-hat. Upload le WAV final.",
"submission_type": "audio_upload",
"criteria": ["Structure 16 mesures", "3 instruments seulement", "Export WAV 44.1kHz/16bit"],
"auto_grade": false
}
```
### 5.2 Résultats dans `lesson_progress.exercise_result` (JSONB)
```json
{
"score": 80,
"max_score": 100,
"answers": { "q1": 0, "q2": 2 },
"submitted_at": "2026-06-01T10:30:00Z",
"submission_s3_key": "...", // si upload audio
"reviewer_feedback": null // si corrigé manuellement plus tard
}
```
---
## 6. Roles et permissions
| Action | Role requis |
|--------|------------|
| Voir courses publiés | 🔓 Public |
| S'inscrire | 🔒 User connecté |
| Créer/éditer course | 🔒🎨 content_creator |
| Modérer commentaires | 🔒 moderator |
| Publier course (validation) | 🔒 content_creator (+ approbation admin en V1) |
**V1** : tout course créé passe par une **validation manuelle** avant publication (qualité, légalité, alignement Talas).
**V2+** : content_creators vérifiés peuvent publier sans validation.
---
## 7. Règles de qualité minimum (pour publication)
Checks automatiques avant `POST /courses/:id/publish` :
- [ ] Cover image uploadée
- [ ] Description ≥ 200 caractères
- [ ] Au moins 1 module
- [ ] Au moins 3 leçons
- [ ] Au moins 1 leçon en `is_free_preview=true`
- [ ] Durée estimée renseignée
- [ ] Toutes les leçons vidéo ont un `hls_manifest_url`
---
## 8. Cache Redis
| Clé | TTL | Contenu |
|-----|-----|---------|
| `learn:courses:published:p<N>` | 5min | Feed courses |
| `learn:course:<slug>` | 10min | Détail course |
| `learn:course:<id>:modules` | 10min | Arborescence |
| `learn:user:<uid>:enrollments` | 1min | Mes inscriptions |
---
## 9. Roadmap implémentation
### V0.1 (MVP, juin 2026)
- [ ] Tables DB + migrations
- [ ] CRUD courses/modules/lessons (auteur)
- [ ] Upload vidéo + HLS (via stream-server)
- [ ] Inscription + progression (étudiant)
- [ ] Feed public
### V0.5 (beta, juillet 2026)
- [ ] Commentaires par leçon
- [ ] Reviews
- [ ] Quiz (auto-graded)
- [ ] Tracking position vidéo
### V1.0 (publique, sept 2026)
- [ ] Exercices (upload audio + review manuelle)
- [ ] Validation admin avant publication
- [ ] Notifications (nouvelle leçon, feedback exercise)
- [ ] Statistiques auteur
### V1.5+
- [ ] Certificats (PDF téléchargeable après 100%)
- [ ] Parcours multi-courses (tracks)
- [ ] Transcriptions / sous-titres auto
- [ ] Export SCORM pour intégrations externes
- [ ] Monétisation optionnelle (V2 ou jamais)
---
## 10. Tests
### 10.1 Tests unitaires
- Validation schémas course/module/lesson
- Calcul progress_percent
- Validation publication
### 10.2 Tests d'intégration
- Flow auteur : création → upload vidéo → publication
- Flow étudiant : enroll → progress → complete
- Permissions (content_creator vs user normal)
### 10.3 Tests de charge
- 50 étudiants en stream HLS simultané
- 500 lectures `GET /courses`
---
## Voir aussi
- [[06_COMMUNAUTE_ECOSYSTEME/Formation_Creators/CURRICULUM_FORMATION_CREATORS]] — curriculum produit
- [[03_APPS_&_SERVICES/ARCHITECTURE_VEZA]] — stream-server Rust réutilisé pour HLS
- [[03_APPS_&_SERVICES/Community/Partage/SPEC_TECHNIQUE_PARTAGE]] — upload pattern similaire
- [[REGLES_MODERATION]] — règles applicables aux commentaires

View file

@ -0,0 +1,447 @@
# Spec technique — Module Partage (Samples & Presets)
> Implémentation backend du dépôt de samples/presets/loops Veza.
> Stack : Go (Gin + GORM) · PostgreSQL 16 · MinIO S3 · Redis · RabbitMQ
> Dernière mise à jour : avril 2026.
Voir la spec produit : [[06_COMMUNAUTE_ECOSYSTEME/Partage_Samples_Presets/SPEC_DEPOT_SAMPLES]]
---
## 1. Vue d'ensemble
Le module **Partage** gère l'upload, le stockage, la curation et la découverte de packs de samples/presets sur Veza.
```
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Frontend │─────▶│ Backend API │─────▶│ PostgreSQL │
│ React │ │ Go / Gin │ │ (metadata) │
└──────────────┘ └──────┬───────┘ └──────────────┘
├──────────────▶ MinIO S3
│ (blobs)
├──────────────▶ RabbitMQ
│ (jobs post-upload)
└──────────────▶ Redis
(cache + rate-limit)
```
---
## 2. Modèle de données
### 2.1 Table `sample_packs`
```sql
CREATE TABLE sample_packs (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
creator_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
title VARCHAR(80) NOT NULL,
description TEXT NOT NULL CHECK (char_length(description) <= 1000),
pack_type VARCHAR(30) NOT NULL, -- 'sample_pack', 'one_shot', 'loop', 'preset_synth', etc.
license VARCHAR(30) NOT NULL DEFAULT 'CC-BY-SA-4.0', -- enum licences
bpm NUMERIC(6,2), -- optionnel
key_signature VARCHAR(10), -- ex: 'C#m', 'F'
genre VARCHAR(50),
tools_used TEXT,
remix_allowed BOOLEAN NOT NULL DEFAULT true,
parent_pack_id UUID REFERENCES sample_packs(id), -- si c'est un remix
-- storage
s3_bucket VARCHAR(63) NOT NULL,
s3_object_key VARCHAR(500) NOT NULL, -- path du zip dans MinIO
file_size_bytes BIGINT NOT NULL,
file_count INT NOT NULL,
checksum_sha256 VARCHAR(64) NOT NULL,
-- preview
preview_s3_key VARCHAR(500), -- preview MP3 généré
preview_duration_sec NUMERIC(4,2),
-- modération
status VARCHAR(20) NOT NULL DEFAULT 'published',
-- 'draft', 'published', 'flagged', 'removed'
flagged_reason TEXT,
-- metrics (dénormalisées pour perf)
likes_count INT NOT NULL DEFAULT 0,
downloads_count INT NOT NULL DEFAULT 0,
remix_count INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ -- soft delete
);
CREATE INDEX idx_sample_packs_creator ON sample_packs(creator_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_sample_packs_status ON sample_packs(status) WHERE deleted_at IS NULL;
CREATE INDEX idx_sample_packs_created ON sample_packs(created_at DESC) WHERE deleted_at IS NULL AND status = 'published';
CREATE INDEX idx_sample_packs_downloads ON sample_packs(downloads_count DESC) WHERE deleted_at IS NULL AND status = 'published';
CREATE INDEX idx_sample_packs_parent ON sample_packs(parent_pack_id) WHERE parent_pack_id IS NOT NULL;
CREATE INDEX idx_sample_packs_genre ON sample_packs(genre) WHERE deleted_at IS NULL AND status = 'published';
```
### 2.2 Table `sample_pack_tags` (N-N)
```sql
CREATE TABLE sample_pack_tags (
pack_id UUID REFERENCES sample_packs(id) ON DELETE CASCADE,
tag VARCHAR(40) NOT NULL,
PRIMARY KEY (pack_id, tag)
);
CREATE INDEX idx_sample_pack_tags_tag ON sample_pack_tags(tag);
```
### 2.3 Table `sample_pack_files` (listing interne d'un pack zip)
```sql
CREATE TABLE sample_pack_files (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
pack_id UUID NOT NULL REFERENCES sample_packs(id) ON DELETE CASCADE,
filename VARCHAR(300) NOT NULL,
relative_path VARCHAR(500) NOT NULL,
file_size_bytes BIGINT NOT NULL,
duration_sec NUMERIC(8,3), -- pour les audios
mime_type VARCHAR(50)
);
CREATE INDEX idx_sample_pack_files_pack ON sample_pack_files(pack_id);
```
### 2.4 Table `sample_pack_likes`
```sql
CREATE TABLE sample_pack_likes (
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
pack_id UUID REFERENCES sample_packs(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (user_id, pack_id)
);
```
### 2.5 Table `sample_pack_downloads` (log + analytics)
```sql
CREATE TABLE sample_pack_downloads (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
pack_id UUID NOT NULL REFERENCES sample_packs(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id) ON DELETE SET NULL,
ip_hash VARCHAR(64), -- SHA-256 de l'IP (GDPR friendly)
user_agent VARCHAR(500),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_sample_pack_downloads_pack ON sample_pack_downloads(pack_id, created_at);
CREATE INDEX idx_sample_pack_downloads_user ON sample_pack_downloads(user_id, created_at);
```
### 2.6 Table `sample_pack_flags` (signalements)
```sql
CREATE TABLE sample_pack_flags (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
pack_id UUID NOT NULL REFERENCES sample_packs(id) ON DELETE CASCADE,
reporter_id UUID NOT NULL REFERENCES users(id),
reason VARCHAR(50) NOT NULL,
-- 'stolen', 'copyright', 'inappropriate', 'low_quality', 'wrong_license'
details TEXT,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
-- 'pending', 'resolved', 'rejected'
resolved_by UUID REFERENCES users(id),
resolved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```
---
## 3. API Endpoints
Base : `/api/v1/samples`
### 3.1 Packs — CRUD
| Méthode | Route | Auth | Description |
|---------|-------|------|-------------|
| GET | `/packs` | 🔓 | Liste paginée (filtres, tri) |
| GET | `/packs/:id` | 🔓 | Détail d'un pack |
| GET | `/packs/:id/files` | 🔓 | Liste des fichiers dans le pack |
| GET | `/packs/:id/remixes` | 🔓 | Arbre des remix (enfants) |
| POST | `/packs` | 🔒 | Créer un pack (metadata, status=draft) |
| POST | `/packs/:id/upload` | 🔒 | Upload binaire (multipart, max 500MB) |
| POST | `/packs/:id/publish` | 🔒 | Publier (status=draft → published) |
| PUT | `/packs/:id` | 🔒 | Modifier metadata (own only) |
| DELETE | `/packs/:id` | 🔒 | Soft delete (own only) |
### 3.2 Interactions
| Méthode | Route | Auth | Description |
|---------|-------|------|-------------|
| POST | `/packs/:id/like` | 🔒 | Like un pack |
| DELETE | `/packs/:id/like` | 🔒 | Retirer like |
| GET | `/packs/:id/download` | 🔒 | Télécharger (URL signée MinIO, expire 5 min) |
| POST | `/packs/:id/flag` | 🔒 | Signaler |
| POST | `/packs/:id/remix` | 🔒 | Créer un pack enfant (remix) |
### 3.3 Découverte
| Méthode | Route | Auth | Description |
|---------|-------|------|-------------|
| GET | `/feed` | 🔓 | Feed principal (récents + populaires) |
| GET | `/feed/featured` | 🔓 | Pack de la semaine + curation |
| GET | `/tags/popular` | 🔓 | Top 50 tags |
| GET | `/search` | 🔓 | Recherche full-text (title + tags + desc) |
### 3.4 Modération
| Méthode | Route | Auth | Description |
|---------|-------|------|-------------|
| GET | `/flags` | 🔒👑 | Liste des signalements pending |
| POST | `/flags/:id/resolve` | 🔒👑 | Résoudre signalement |
| POST | `/packs/:id/feature` | 🔒👑 | Mettre en "pack de la semaine" |
### 3.5 Filtres de `/packs`
```
?type=sample_pack,one_shot,preset_synth # multi-valeurs
?license=CC-BY-SA-4.0
?genre=hip-hop,techno
?bpm_min=80&bpm_max=100
?key=Cm,C
?tags=lofi,organic # AND
?creator=<uuid>
?remix_allowed=true
?sort=recent|popular|downloads|likes
?page=1&limit=20 # max 50
```
---
## 4. Flux techniques
### 4.1 Upload d'un pack (du draft à la publication)
```
[1] POST /packs (metadata)
← Backend crée sample_packs{status:draft, s3_key:<pregenerated>}
→ response: {id, upload_url} (URL présignée MinIO)
[2] PUT <upload_url> (binary)
→ Frontend upload direct vers MinIO
[3] POST /packs/:id/publish
→ Backend:
- vérifie checksum
- scan antivirus (ClamAV)
- publie (status=published)
- enqueue job RabbitMQ "post_upload"
→ response: {pack}
[4] Worker "post_upload" (async):
- unzip temporaire en mémoire stream
- extraction metadata audio (FFprobe)
- insert sample_pack_files
- génération preview MP3 (60 sec max, FFmpeg)
- upload preview vers MinIO
- UPDATE sample_packs SET preview_s3_key, preview_duration_sec
```
**Limites** :
- Taille max : 500 MB
- Formats autorisés : .zip uniquement (on n'accepte pas .rar, .7z en V1)
- Timeout upload : 30 minutes
- Rate limit : 5 packs / jour / user
### 4.2 Téléchargement
```
GET /packs/:id/download
→ Backend:
- check status == 'published' AND deleted_at IS NULL
- generate presigned URL (MinIO, expire 5 min)
- INSERT sample_pack_downloads (log)
- UPDATE sample_packs SET downloads_count = downloads_count + 1
→ response: {download_url}
```
**Note** : on n'incrémente PAS via trigger PG pour éviter les contentions. On bump la colonne dénormalisée depuis l'app.
### 4.3 Remix
```
POST /packs/:id/remix (body: {title, description, ...})
→ Backend:
- check remix_allowed sur parent
- check licence parent compatible
- INSERT sample_packs{parent_pack_id: :id, status:draft}
- UPDATE parent SET remix_count = remix_count + 1
→ response: {new_pack_id, upload_url}
```
---
## 5. Storage MinIO
### 5.1 Buckets
| Bucket | Contenu | Visibilité |
|--------|---------|-----------|
| `veza-samples-packs` | ZIP des packs | Privé (accès via URL signée) |
| `veza-samples-previews` | MP3 previews | Public (CDN possible) |
### 5.2 Naming convention
```
veza-samples-packs/
├── <user_uuid>/
│ └── <pack_uuid>/
│ └── original.zip
veza-samples-previews/
├── <pack_uuid>/
│ └── preview.mp3
```
### 5.3 Lifecycle
- Packs soft-deleted : objets MinIO supprimés après 30 jours (cron)
- Previews : jamais supprimées (tant que le pack existe)
---
## 6. Sécurité et validation
### 6.1 Validation metadata (backend)
Validations Zod-like côté backend (Go struct tags + validator) :
```go
type CreatePackRequest struct {
Title string `json:"title" validate:"required,min=3,max=80"`
Description string `json:"description" validate:"required,min=10,max=1000"`
PackType string `json:"pack_type" validate:"required,oneof=sample_pack one_shot loop preset_synth preset_fx preset_daw ir field_recording drum_kit sound_bank"`
License string `json:"license" validate:"required,oneof=CC0 CC-BY-4.0 CC-BY-SA-4.0 veza_only"`
BPM *float64 `json:"bpm" validate:"omitempty,min=20,max=300"`
KeySignature string `json:"key_signature" validate:"omitempty,max=10"`
Genre string `json:"genre" validate:"omitempty,max=50"`
Tags []string `json:"tags" validate:"required,min=3,max=10,dive,min=2,max=40"`
RemixAllowed bool `json:"remix_allowed"`
ParentPackID *string `json:"parent_pack_id" validate:"omitempty,uuid"`
}
```
### 6.2 Antivirus (ClamAV)
Tout zip uploadé passe par **ClamAV** avant publication. Si détection → status=`removed` + notif admin.
### 6.3 Anti-duplication
Checksum SHA-256 du zip comparé avant insertion. Si checksum existe déjà → refus avec message "un pack identique existe".
### 6.4 Rate limiting
- 5 uploads / jour / user
- 50 téléchargements / heure / user (anti-scraping)
- 20 likes / minute / user
### 6.5 RGPD
- Logs downloads anonymisés (IP → hash)
- Right to deletion : soft delete cascade les fichiers MinIO après délai
---
## 7. Cache Redis
| Clé | TTL | Contenu |
|-----|-----|---------|
| `samples:feed:recent:p<N>` | 60s | Page N du feed récent |
| `samples:feed:featured` | 5min | Pack de la semaine |
| `samples:pack:<id>` | 5min | Détail pack |
| `samples:tags:popular` | 1h | Top 50 tags |
| `samples:user:<uid>:likes` | 1h | Set des packs likés par user |
Invalidation : sur write (publish/delete/like) on invalide les clés concernées.
---
## 8. Considérations de performance
### 8.1 Feed
- Pas de `ORDER BY random()` — utiliser un cron qui pré-calcule "featured"
- Pagination par curseur (`created_at + id`) plutôt que offset pour les gros jeux
### 8.2 Comptages dénormalisés
- `likes_count`, `downloads_count`, `remix_count` mis à jour depuis l'app (pas trigger PG)
- Job nocturne de reconciliation (anti-drift) : recompte les 3 colonnes
### 8.3 Recherche full-text
- Phase 1 : PostgreSQL full-text (`tsvector` sur title + description)
- Phase 2 (quand > 1000 packs) : Elasticsearch déjà dans l'infra
---
## 9. Roadmap implémentation
### V0.1 (MVP interne, avril 2026)
- [ ] Tables DB + migrations
- [ ] CRUD packs (endpoints 3.1)
- [ ] Upload vers MinIO avec URL signée
- [ ] ClamAV scan
- [ ] Feed simple (récents)
### V0.5 (beta fondateurs, juin 2026)
- [ ] Preview MP3 auto (FFmpeg)
- [ ] Likes, downloads log
- [ ] Search PG full-text
- [ ] Filtres feed
- [ ] Modération (flags + résolution)
### V1.0 (ouverture publique, juillet 2026)
- [ ] Remix (parent_pack_id)
- [ ] Curation "pack de la semaine"
- [ ] Tags populaires
- [ ] Analytics basiques pour créateurs
### V1.5 (post-lancement)
- [ ] Elasticsearch pour search
- [ ] Extraction BPM/key automatique (aubio / essentia)
- [ ] Recommandations "packs similaires" (tags match)
---
## 10. Tests
### 10.1 Tests unitaires (Go)
- Validation metadata (validator tags)
- Calcul checksum
- Signature URL MinIO
### 10.2 Tests d'intégration
- Flow complet upload → publish → download
- Rate limiting
- Anti-duplication checksum
- RBAC (seul le créateur modifie son pack)
### 10.3 Tests de charge
- 100 uploads concurrents
- 1000 téléchargements / seconde
- Feed avec 10 000 packs
---
## Voir aussi
- [[06_COMMUNAUTE_ECOSYSTEME/Partage_Samples_Presets/SPEC_DEPOT_SAMPLES]] — spec produit
- [[03_APPS_&_SERVICES/ARCHITECTURE_VEZA]] — architecture globale
- [[03_APPS_&_SERVICES/APIs_&_Rust_Modules/ROUTES_API]] — toutes les routes Veza
- [[04_INFRA_DEPLOIEMENT]] — infra MinIO, Redis, RabbitMQ

View file

@ -0,0 +1,427 @@
# Spec technique — Module Troc
> Implémentation backend du troc communautaire Veza.
> Stack : Go (Gin + GORM) · PostgreSQL 16 · Redis · WebSocket
> Dernière mise à jour : avril 2026.
Voir la spec produit : [[06_COMMUNAUTE_ECOSYSTEME/Plateforme_Echange/SPEC_TROC_COMMUNAUTAIRE]]
---
## 1. Vue d'ensemble
Le module **Troc** est un tableau d'annonces avec messagerie intégrée. Pas de marketplace, pas de transactions financières — juste un feed d'annonces et des DMs entre artistes.
```
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Frontend │─────▶│ Backend API │─────▶│ PostgreSQL │
└──────────────┘ │ Go / Gin │ │ │
└──────┬───────┘ └──────────────┘
├──────────────▶ Chat existant (DMs)
│ (module Groupes_Chat)
└──────────────▶ Redis
(feed cache, rate-limit)
```
---
## 2. Modèle de données
### 2.1 Table `trade_listings`
```sql
CREATE TABLE trade_listings (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
creator_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
title VARCHAR(80) NOT NULL,
offering TEXT NOT NULL CHECK (char_length(offering) <= 500),
seeking TEXT NOT NULL CHECK (char_length(seeking) <= 500),
category_offer VARCHAR(30) NOT NULL,
category_seek VARCHAR(30) NOT NULL,
-- 'audio_service', 'visual_creation', 'music_production',
-- 'training_coaching', 'hardware', 'samples_presets', 'other'
delivery_days INT NOT NULL CHECK (delivery_days BETWEEN 1 AND 30),
constraints TEXT, -- optionnel
location VARCHAR(100), -- optionnel (ville)
status VARCHAR(20) NOT NULL DEFAULT 'open',
-- 'open', 'in_discussion', 'concluded', 'cancelled', 'removed'
-- si conclu, info sur le closing
concluded_with_id UUID REFERENCES users(id),
concluded_at TIMESTAMPTZ,
-- metrics
views_count INT NOT NULL DEFAULT 0,
interested_count INT NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ, -- auto-archive après 60 jours
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_trade_listings_creator ON trade_listings(creator_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_trade_listings_status ON trade_listings(status) WHERE deleted_at IS NULL;
CREATE INDEX idx_trade_listings_category ON trade_listings(category_offer, category_seek) WHERE deleted_at IS NULL;
CREATE INDEX idx_trade_listings_open ON trade_listings(created_at DESC) WHERE status = 'open' AND deleted_at IS NULL;
```
### 2.2 Table `trade_listing_examples` (liens vers morceaux Veza)
```sql
CREATE TABLE trade_listing_examples (
listing_id UUID REFERENCES trade_listings(id) ON DELETE CASCADE,
track_id UUID REFERENCES tracks(id) ON DELETE CASCADE,
PRIMARY KEY (listing_id, track_id)
);
```
### 2.3 Table `trade_interests` (qui s'intéresse à quoi)
```sql
CREATE TABLE trade_interests (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
listing_id UUID NOT NULL REFERENCES trade_listings(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
conversation_id UUID, -- référence vers DM créée
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (listing_id, user_id)
);
CREATE INDEX idx_trade_interests_listing ON trade_interests(listing_id);
CREATE INDEX idx_trade_interests_user ON trade_interests(user_id);
```
### 2.4 Table `trade_recognitions` (badges post-closing)
```sql
CREATE TABLE trade_recognitions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
listing_id UUID NOT NULL REFERENCES trade_listings(id) ON DELETE CASCADE,
from_user_id UUID NOT NULL REFERENCES users(id),
to_user_id UUID NOT NULL REFERENCES users(id),
badges VARCHAR(20)[] NOT NULL,
-- array : ['reliable', 'punctual', 'inventive', 'patient', 'precise', 'warm']
note TEXT, -- optionnel, privé
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (listing_id, from_user_id, to_user_id)
);
CREATE INDEX idx_trade_recognitions_to_user ON trade_recognitions(to_user_id);
```
### 2.5 Vue matérialisée `user_trade_reputation`
```sql
CREATE MATERIALIZED VIEW user_trade_reputation AS
SELECT
u.id AS user_id,
COUNT(DISTINCT tl.id) FILTER (WHERE tl.status = 'concluded') AS concluded_count,
COUNT(DISTINCT tl.id) FILTER (WHERE tl.status = 'cancelled') AS cancelled_count,
COUNT(*) FILTER (WHERE 'reliable' = ANY(tr.badges)) AS badge_reliable,
COUNT(*) FILTER (WHERE 'punctual' = ANY(tr.badges)) AS badge_punctual,
COUNT(*) FILTER (WHERE 'inventive' = ANY(tr.badges)) AS badge_inventive,
COUNT(*) FILTER (WHERE 'patient' = ANY(tr.badges)) AS badge_patient,
COUNT(*) FILTER (WHERE 'precise' = ANY(tr.badges)) AS badge_precise,
COUNT(*) FILTER (WHERE 'warm' = ANY(tr.badges)) AS badge_warm
FROM users u
LEFT JOIN trade_listings tl ON tl.creator_id = u.id OR tl.concluded_with_id = u.id
LEFT JOIN trade_recognitions tr ON tr.to_user_id = u.id
GROUP BY u.id;
CREATE UNIQUE INDEX idx_user_trade_reputation ON user_trade_reputation(user_id);
-- Refresh nocturne via cron
```
### 2.6 Table `trade_flags` (signalements)
```sql
CREATE TABLE trade_flags (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
listing_id UUID NOT NULL REFERENCES trade_listings(id),
reporter_id UUID NOT NULL REFERENCES users(id),
reason VARCHAR(50) NOT NULL,
-- 'spam', 'inappropriate', 'money_involved', 'stolen_content', 'multi_account'
details TEXT,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
resolved_by UUID REFERENCES users(id),
resolved_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```
---
## 3. API Endpoints
Base : `/api/v1/trades`
### 3.1 Annonces — CRUD
| Méthode | Route | Auth | Description |
|---------|-------|------|-------------|
| GET | `/listings` | 🔓 | Liste paginée (filtres) |
| GET | `/listings/:id` | 🔓 | Détail d'une annonce |
| POST | `/listings` | 🔒 | Créer annonce |
| PUT | `/listings/:id` | 🔒 | Modifier (own only, tant que status='open') |
| POST | `/listings/:id/cancel` | 🔒 | Annuler (own only) |
| POST | `/listings/:id/conclude` | 🔒 | Clôturer avec un user |
| DELETE | `/listings/:id` | 🔒 | Soft delete (own only) |
### 3.2 Interactions
| Méthode | Route | Auth | Description |
|---------|-------|------|-------------|
| POST | `/listings/:id/interest` | 🔒 | "Je suis intéressé" → crée DM |
| POST | `/listings/:id/flag` | 🔒 | Signaler annonce |
| POST | `/listings/:id/recognition` | 🔒 | Donner reconnaissances après closing |
### 3.3 Découverte
| Méthode | Route | Auth | Description |
|---------|-------|------|-------------|
| GET | `/feed` | 🔓 | Feed général (filtres, tri) |
| GET | `/categories` | 🔓 | Liste des catégories |
| GET | `/users/:id/reputation` | 🔓 | Réputation troc d'un user |
| GET | `/users/:id/listings` | 🔓 | Annonces d'un user (actives + historique) |
| GET | `/me/listings` | 🔒 | Mes annonces |
| GET | `/me/interests` | 🔒 | Annonces auxquelles je suis intéressé |
### 3.4 Modération
| Méthode | Route | Auth | Description |
|---------|-------|------|-------------|
| GET | `/flags` | 🔒👑 | Liste des signalements |
| POST | `/flags/:id/resolve` | 🔒👑 | Résoudre signalement |
| POST | `/listings/:id/remove` | 🔒👑 | Retirer annonce (admin) |
### 3.5 Filtres de `/feed`
```
?category_offer=audio_service,music_production
?category_seek=music_production
?status=open # default
?delivery_days_max=7
?creator=<uuid>
?location=Paris
?sort=recent|popular # popular = interested_count DESC
?page=1&limit=20
```
---
## 4. Flux techniques
### 4.1 Créer une annonce
```
POST /listings
Body: { title, offering, seeking, category_offer, category_seek,
delivery_days, constraints?, location?, example_track_ids? }
Backend:
- Validation metadata
- Check rate limit (max 5 annonces actives simultanées par user)
- INSERT trade_listings (status='open', expires_at=NOW()+60days)
- INSERT trade_listing_examples si track_ids fournis
- Notification broadcast : "nouvelle annonce dans catégorie X"
Response: { listing }
```
### 4.2 Marquer son intérêt (le pivot central)
```
POST /listings/:id/interest
Backend:
- Check listing.status == 'open'
- Check creator_id != current_user_id (pas d'auto-intérêt)
- Check unique (listing_id, user_id)
- INSERT trade_interests
- UPDATE trade_listings SET interested_count += 1
- Créer conversation DM via module Chat :
POST internal /chat/conversations {
participants: [listing.creator_id, current_user_id],
context: { type: 'trade', listing_id: :id }
}
- Premier message auto-injecté :
"🤝 [Username] s'intéresse à ton annonce : '<title>'
Il/elle offre : <offering>
Il/elle cherche : <seeking>"
- Notification au créateur de l'annonce
Response: { conversation_id }
```
### 4.3 Clôturer un troc
```
POST /listings/:id/conclude
Body: { concluded_with_user_id }
Backend:
- Check current_user == listing.creator_id
- Check concluded_with_user_id existe dans trade_interests(listing_id)
- UPDATE trade_listings SET status='concluded', concluded_with_id=..., concluded_at=NOW()
- Notification aux deux : "Le troc est clôturé. Tu peux laisser des reconnaissances."
- Créer 2 tasks backend (lien d'action dans notif) pour demander les reconnaissances
Response: { listing }
```
### 4.4 Donner des reconnaissances
```
POST /listings/:id/recognition
Body: { to_user_id, badges: ['reliable', 'warm'], note? }
Backend:
- Check listing.status == 'concluded'
- Check current_user IN (listing.creator_id, listing.concluded_with_id)
- Check to_user_id is the other party
- Check unique (listing_id, from_user_id, to_user_id)
- INSERT trade_recognitions
- Incremental update user_trade_reputation (ou attendre refresh nocturne)
Response: { recognition }
```
### 4.5 Auto-archivage
**Cron nocturne** : listings avec `expires_at < NOW()` et `status = 'open'` → status `cancelled` (auto).
---
## 5. Intégration avec module Chat existant
Le troc **réutilise** le système de DMs du module `Groupes_Chat` (pas de chat dédié). Intégration :
- Chaque "intérêt" crée une conversation DM
- Les conversations issues de troc ont un **context** `{type: 'trade', listing_id: ...}`
- Affichage spécial dans le chat : bandeau "Discussion autour de l'annonce : <title>"
- Bouton rapide dans le chat : "Clôturer ce troc" (visible au créateur de l'annonce uniquement)
---
## 6. Règles de validation
### 6.1 Validation metadata
```go
type CreateListingRequest struct {
Title string `validate:"required,min=5,max=80"`
Offering string `validate:"required,min=20,max=500"`
Seeking string `validate:"required,min=20,max=500"`
CategoryOffer string `validate:"required,oneof=audio_service visual_creation music_production training_coaching hardware samples_presets other"`
CategorySeek string `validate:"required,oneof=audio_service visual_creation music_production training_coaching hardware samples_presets other"`
DeliveryDays int `validate:"required,min=1,max=30"`
Constraints string `validate:"omitempty,max=300"`
Location string `validate:"omitempty,max=100"`
ExampleTracks []string `validate:"omitempty,max=5,dive,uuid"`
}
```
### 6.2 Détection de mots interdits
Check automatique dans `offering` et `seeking` :
- Si mention de montants en euros/dollars/etc. → **refus** avec message "Le troc n'implique pas d'argent"
- Mots : `€`, `$`, `EUR`, `USD`, `euros`, `dollars`, `payer`, `vendre`, `acheter`
- Liste maintenue dans config, facile à étendre
### 6.3 Rate limiting
- Max 5 annonces actives simultanées par user
- Max 2 nouvelles annonces / jour
- Max 10 "intérêts" / jour (anti-spam DMs)
- 1 seul intérêt par listing (unique constraint)
---
## 7. Cache Redis
| Clé | TTL | Contenu |
|-----|-----|---------|
| `trades:feed:open:p<N>:<filter_hash>` | 30s | Page feed filtrée |
| `trades:listing:<id>` | 5min | Détail listing |
| `trades:user:<uid>:reputation` | 1h | Réputation user (vue matérialisée) |
Invalidation : sur write (create/update/cancel/conclude), invalider `trades:feed:open:*` + la clé listing concernée.
---
## 8. Sécurité
### 8.1 Abus anti-argent
- Détection regex sur montants (§6.2)
- Signalement user si mention argent confirmée
- Escalade : 2 signalements → review manuelle
### 8.2 Multi-compte
- Fingerprint léger : IP + user-agent + creation date
- Vérification manuelle si suspicion (pas auto-action)
### 8.3 Harcèlement via DMs
- Bloquer un user ferme la conversation troc
- Un user bloqué ne peut plus "s'intéresser" aux annonces du bloqueur
---
## 9. Roadmap implémentation
### V0.1 (MVP interne, mai 2026)
- [ ] Tables DB + migrations
- [ ] CRUD annonces (endpoints 3.1)
- [ ] Création DM au "intérêt"
- [ ] Feed simple (récent, filtres basiques)
### V0.5 (beta fondateurs, juin 2026)
- [ ] Filtres avancés
- [ ] Reconnaissances post-closing
- [ ] Vue matérialisée user_trade_reputation
- [ ] Modération (flags)
- [ ] Détection anti-argent
### V1.0 (ouverture publique, juillet 2026)
- [ ] Notifications (email + push in-app)
- [ ] Profil public "réputation troc"
- [ ] Export historique de ses trocs
- [ ] Feed personnalisé (catégories préférées)
### V1.5+
- [ ] Suggestions automatiques d'annonces complémentaires
- [ ] Statistiques publiques (nb trocs Veza, top catégories)
- [ ] Calendrier des trocs (pour les services à date)
---
## 10. Tests
### 10.1 Tests unitaires
- Validation metadata (validator tags + regex anti-argent)
- Règles métier (own listing, status transitions)
### 10.2 Tests d'intégration
- Flow complet : création → intérêt → closing → reconnaissance
- Cascade deletes
- Cron auto-archive
### 10.3 Tests de charge
- 100 créations d'annonces / minute
- Feed avec 5 000 annonces actives
---
## Voir aussi
- [[06_COMMUNAUTE_ECOSYSTEME/Plateforme_Echange/SPEC_TROC_COMMUNAUTAIRE]] — spec produit
- [[03_APPS_&_SERVICES/Community/Groupes_Chat/README]] — module Chat (DMs réutilisés)
- [[03_APPS_&_SERVICES/ARCHITECTURE_VEZA]] — architecture globale
- [[REGLES_MODERATION]] — règles communauté

View file

@ -0,0 +1,437 @@
# Spec technique — Cloud Perso Veza
> Espace de stockage personnel pour chaque artiste Veza : fichiers audio, projets DAW, presets, snapshots.
> Stack : Go (Gin + GORM) · PostgreSQL 16 · MinIO S3 · Redis
> Dernière mise à jour : avril 2026.
---
## 1. Vue d'ensemble
### 1.1 Positionnement
**Cloud Perso ≠ Partage Samples (public)**
| | Cloud Perso | Partage Samples |
|---|-------------|-----------------|
| Audience | Privé (le user + ses invités) | Public (tout Veza) |
| Finalité | Travail en cours, backup, projets | Partager avec la communauté |
| Structure | Arborescence fichiers/dossiers | Packs catalogués |
| Quota | Limité par plan | Quota public séparé |
### 1.2 Concurrents de référence
- Dropbox, Google Drive : UX simple, mais GAFAM
- Nextcloud, Seafile : self-hosted sérieux mais UX lourde
- Splice Studio : audio-spécifique, SaaS
**Positionnement Veza** : un espace perso **audio-centré**, intégré à la plateforme, simple.
### 1.3 Cas d'usage principaux
1. Sauvegarder ses projets DAW en cours
2. Transférer des fichiers entre son PC studio et son laptop de route
3. Partager un WIP avec un collaborateur via lien privé temporaire
4. Garder un backup des morceaux finalisés
5. Stocker ses presets perso organisés
---
## 2. Architecture
```
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Frontend Web │───────▶│ Backend API │───────▶│ PostgreSQL │
│ (file browser│ │ Go / Gin │ │ (metadata) │
└──────────────┘ └──────┬───────┘ └──────────────┘
┌──────────────┐ │
│ Desktop Sync │ └───────────────▶ MinIO S3
│ (V2, future) │ (blobs)
└──────────────┘ │
│ lifecycle
Cold storage (30j+)
```
---
## 3. Modèle de données
### 3.1 Table `user_cloud_quotas`
```sql
CREATE TABLE user_cloud_quotas (
user_id UUID PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
plan VARCHAR(30) NOT NULL DEFAULT 'free',
-- 'free', 'plus', 'pro', 'custom'
quota_bytes BIGINT NOT NULL,
used_bytes BIGINT NOT NULL DEFAULT 0,
file_count INT NOT NULL DEFAULT 0,
max_file_size_bytes BIGINT NOT NULL DEFAULT 2147483648, -- 2 GB
max_files INT NOT NULL DEFAULT 10000,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```
### 3.2 Table `cloud_folders`
```sql
CREATE TABLE cloud_folders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
parent_id UUID REFERENCES cloud_folders(id) ON DELETE CASCADE,
name VARCHAR(200) NOT NULL,
path VARCHAR(2000) NOT NULL, -- path complet denormalise
is_trash BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ,
UNIQUE (user_id, parent_id, name) DEFERRABLE INITIALLY DEFERRED
);
CREATE INDEX idx_cloud_folders_user_parent ON cloud_folders(user_id, parent_id) WHERE deleted_at IS NULL;
CREATE INDEX idx_cloud_folders_path ON cloud_folders(user_id, path) WHERE deleted_at IS NULL;
```
### 3.3 Table `cloud_files`
```sql
CREATE TABLE cloud_files (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
folder_id UUID REFERENCES cloud_folders(id) ON DELETE CASCADE,
name VARCHAR(300) NOT NULL,
extension VARCHAR(20),
mime_type VARCHAR(100),
file_size_bytes BIGINT NOT NULL,
checksum_sha256 VARCHAR(64) NOT NULL,
-- storage
s3_bucket VARCHAR(63) NOT NULL,
s3_object_key VARCHAR(500) NOT NULL,
-- metadata audio (si applicable, extraite à l'upload)
audio_metadata JSONB,
-- ex: { duration_sec, sample_rate, bit_depth, channels, format }
-- versioning (pointeur vers version précédente)
previous_version_id UUID REFERENCES cloud_files(id),
version_number INT NOT NULL DEFAULT 1,
-- trash
is_trash BOOLEAN NOT NULL DEFAULT false,
trashed_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_cloud_files_user_folder ON cloud_files(user_id, folder_id) WHERE deleted_at IS NULL AND is_trash = false;
CREATE INDEX idx_cloud_files_trash ON cloud_files(user_id, trashed_at) WHERE is_trash = true;
CREATE INDEX idx_cloud_files_checksum ON cloud_files(user_id, checksum_sha256);
```
### 3.4 Table `cloud_share_links`
```sql
CREATE TABLE cloud_share_links (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
file_id UUID REFERENCES cloud_files(id) ON DELETE CASCADE,
folder_id UUID REFERENCES cloud_folders(id) ON DELETE CASCADE,
token VARCHAR(40) NOT NULL UNIQUE, -- random URL-safe
password_hash VARCHAR(100), -- optionnel
max_downloads INT, -- null = illimité
downloads_count INT NOT NULL DEFAULT 0,
expires_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
revoked_at TIMESTAMPTZ,
CHECK ((file_id IS NOT NULL) OR (folder_id IS NOT NULL))
);
CREATE INDEX idx_cloud_share_links_token ON cloud_share_links(token) WHERE revoked_at IS NULL;
```
---
## 4. Plans et quotas
### 4.1 V1 — un seul plan gratuit
| Plan | Quota | Max file | Max files |
|------|-------|----------|-----------|
| `free` | 10 GB | 2 GB | 10 000 |
### 4.2 V2 — plans additionnels (si demande)
| Plan | Quota | Max file | Max files | Prix (HT) |
|------|-------|----------|-----------|-----------|
| `free` | 10 GB | 2 GB | 10 000 | 0 € |
| `plus` | 100 GB | 5 GB | 50 000 | 4 €/mois |
| `pro` | 500 GB | 10 GB | 200 000 | 12 €/mois |
| `custom` | Sur-mesure | - | - | Devis |
**Note** : les plans payants n'arriveront que si la demande émerge. Philosophie : gratuit solide par défaut.
---
## 5. API Endpoints
Base : `/api/v1/cloud`
### 5.1 Navigation
| Méthode | Route | Auth | Description |
|---------|-------|------|-------------|
| GET | `/folders/:id` | 🔒 | Contenu d'un dossier (sous-dossiers + fichiers) |
| GET | `/folders/root` | 🔒 | Dossier racine |
| GET | `/files/:id` | 🔒 | Metadata fichier |
| GET | `/search?q=...` | 🔒 | Recherche dans tout le cloud |
| GET | `/quota` | 🔒 | Mon quota |
### 5.2 CRUD fichiers
| Méthode | Route | Auth | Description |
|---------|-------|------|-------------|
| POST | `/files` | 🔒 | Créer file entry (metadata) + génère upload URL |
| POST | `/files/:id/complete-upload` | 🔒 | Finaliser upload (vérif checksum) |
| PUT | `/files/:id` | 🔒 | Renommer / déplacer |
| DELETE | `/files/:id` | 🔒 | Soft delete → trash |
| POST | `/files/:id/restore` | 🔒 | Restaurer depuis trash |
| GET | `/files/:id/download` | 🔒 | URL signée téléchargement |
### 5.3 CRUD dossiers
| Méthode | Route | Auth | Description |
|---------|-------|------|-------------|
| POST | `/folders` | 🔒 | Créer dossier |
| PUT | `/folders/:id` | 🔒 | Renommer / déplacer |
| DELETE | `/folders/:id` | 🔒 | Soft delete → trash |
### 5.4 Partage
| Méthode | Route | Auth | Description |
|---------|-------|------|-------------|
| POST | `/share` | 🔒 | Créer lien partage (file ou folder) |
| GET | `/share/:token` | 🔓 | Accéder à un partage (password si défini) |
| POST | `/share/:token/download` | 🔓 | Télécharger |
| DELETE | `/share/:id` | 🔒 | Révoquer lien |
| GET | `/me/shares` | 🔒 | Mes partages actifs |
### 5.5 Trash
| Méthode | Route | Auth | Description |
|---------|-------|------|-------------|
| GET | `/trash` | 🔒 | Contenu de la corbeille |
| POST | `/trash/empty` | 🔒 | Vider corbeille |
| POST | `/trash/purge-item/:id` | 🔒 | Supprimer définitivement un item |
---
## 6. Flux techniques
### 6.1 Upload fichier
```
[1] POST /cloud/files
Body: { name, size_bytes, mime_type, folder_id, checksum_sha256 }
→ Backend:
- Check quota disponible
- Check nom unique dans folder
- INSERT cloud_files (s3_object_key pre-généré)
- Génère upload URL MinIO (PUT, expire 1h)
→ response: { file_id, upload_url }
[2] PUT <upload_url> (binary)
→ Upload direct MinIO
[3] POST /cloud/files/:id/complete-upload
→ Backend:
- GET object MinIO, vérifie checksum
- Extraction metadata audio (si audio, via FFprobe async)
- UPDATE user_cloud_quotas (used_bytes += size)
- UPDATE cloud_files (mettre audio_metadata)
→ response: { file }
```
### 6.2 Download fichier
```
GET /cloud/files/:id/download
→ Backend:
- Check ownership
- Generate presigned URL (expire 5 min)
→ response: { download_url }
```
### 6.3 Partage via lien
```
POST /cloud/share
Body: { file_id, password?, max_downloads?, expires_in_hours? }
→ Backend:
- Génère token (40 chars random URL-safe)
- Hash password si fourni (bcrypt)
- INSERT cloud_share_links
→ response: { share_url: "/s/<token>" }
GET /cloud/share/<token> (public)
→ Backend:
- Load link (check not revoked, not expired)
- Si password requis : demande password
→ response: { file_info, needs_password }
POST /cloud/share/<token>/download (public)
Body: { password? }
→ Backend:
- Check password si défini
- Check max_downloads non atteint
- UPDATE downloads_count
- Generate presigned URL MinIO
→ response: { download_url }
```
---
## 7. Versioning
### 7.1 Règle
**V1** : pas de versioning automatique. Upload d'un fichier de même nom au même endroit **remplace** l'ancien.
**V2+** : versioning automatique, max 10 versions retenues, historique visible dans UI.
### 7.2 Comment ça marchera (V2)
- Chaque "remplacement" crée un nouveau `cloud_files` row
- Le précédent garde `previous_version_id`
- L'UI expose un bouton "Historique" par fichier
- Rotation : au-delà de 10, les plus anciennes sont purgées
---
## 8. Storage MinIO
### 8.1 Bucket
`veza-cloud-perso` — privé, accès via URLs signées uniquement.
### 8.2 Naming convention
```
veza-cloud-perso/
├── <user_uuid>/
│ └── <file_uuid>/
│ └── blob
```
**Note** : on utilise l'UUID et non le nom pour éviter les problèmes de charset / renommage.
### 8.3 Lifecycle
- **Fichiers trash** : purge automatique après 30 jours
- **Fichiers supprimés définitivement** : purge immédiate
- **Comptes supprimés (user)** : purge après 30 jours (cascade soft delete → purge)
---
## 9. Sécurité
### 9.1 Contrôle d'accès
- Chaque endpoint vérifie `user_id == current_user.id`
- Pas d'ACL partagée entre users (V1). Sharing = lien public.
### 9.2 Antivirus
**V1** : pas de scan antivirus (les fichiers ne sont jamais téléchargés par d'autres users sauf via liens de partage).
**V2+** : scan ClamAV sur les fichiers partagés publiquement.
### 9.3 Rate limiting
- Upload : 50 fichiers / heure / user
- Download : 200 / heure / user
- Partage : 20 liens actifs simultanés / user
### 9.4 Chiffrement
- **At rest** : chiffrement MinIO côté serveur (AES-256-SSE)
- **In transit** : HTTPS obligatoire, URLs signées avec signature SHA-256
---
## 10. Desktop sync (V2, futur)
### 10.1 Vision
Un client desktop (Electron ou natif) qui synchronise un dossier local avec le cloud perso.
### 10.2 Architecture envisagée
- Protocol : basé sur WebSocket pour notifications + REST pour upload
- Algorithm : delta sync (checksum des blocks)
- Conflict resolution : "latest wins" avec backup du précédent
### 10.3 Plateforms V2
- Linux (AppImage)
- macOS (.dmg)
- Windows (.exe MSI)
---
## 11. Monitoring
| Métrique | Cible |
|----------|-------|
| Upload réussi ratio | > 98% |
| Temps upload 100MB | < 30s |
| Temps génération URL signée | < 50ms |
| Quota usage moyen user | Monitorer |
| Sharing link usage | Monitorer |
---
## 12. Roadmap
### V1.0 (Q3 2026, post-lancement public)
- [ ] Tables DB + migrations
- [ ] CRUD fichiers/dossiers
- [ ] Upload/download via URL signée
- [ ] Quotas
- [ ] Partage via lien (password + expiration)
- [ ] Trash + purge auto 30j
- [ ] File browser web
### V1.5 (Q4 2026)
- [ ] Recherche full-text (noms)
- [ ] Extraction métadonnées audio auto
- [ ] Prévisualisation audio (player intégré)
- [ ] Drag & drop multi-fichiers
- [ ] Upload en arrière-plan
### V2.0 (2027)
- [ ] Versioning
- [ ] Desktop sync client
- [ ] Plans payants (si demande)
- [ ] Scan ClamAV sur fichiers partagés
- [ ] Compression à la volée (option)
---
## Voir aussi
- [[Personal/README]] — vue d'ensemble module Personal
- [[Personal/AudioGridder_Client/SPEC_AUDIOGRIDDER]] — autre feature Personal
- [[03_APPS_&_SERVICES/Community/Partage/SPEC_TECHNIQUE_PARTAGE]] — stockage public (vs perso)
- [[04_INFRA_DEPLOIEMENT]] — MinIO, storage

View file

@ -0,0 +1,442 @@
# Spec technique — Backend Shop
> Backend boutique Veza : catalogue produits, stock, commandes, expédition.
> Stack : Go (Gin + GORM) · PostgreSQL 16 · Redis · RabbitMQ
> Dernière mise à jour : avril 2026.
---
## 1. Vue d'ensemble
Le backend Shop gère le cycle de vie complet d'une commande : du catalogue produits à la livraison, en passant par le panier, le checkout et l'expédition.
```
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Frontend │─────▶│ Backend API │─────▶│ PostgreSQL │
│ Shop │ │ Go / Gin │ │ │
└──────────────┘ └──────┬───────┘ └──────────────┘
├──▶ Paiement (Mollie)
├──▶ RabbitMQ
│ (order.paid, shipment.ready)
└──▶ Email (listmonk self-hosted)
```
---
## 2. Catalogue produits
### 2.1 Structure — micro modulaire
Un micro Talas = **produit configurable**. Le client choisit :
- Le modèle (Talas Lite electret OU Talas One condenser)
- La finition (brute, noire, anodisée couleur)
- Les options (pochette tissu, suspension, bonnette)
### 2.2 Table `products`
```sql
CREATE TABLE products (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
slug VARCHAR(100) NOT NULL UNIQUE,
title VARCHAR(150) NOT NULL,
subtitle VARCHAR(200),
description_md TEXT NOT NULL,
product_type VARCHAR(30) NOT NULL,
-- 'microphone', 'accessory', 'bundle', 'spare_part'
base_price_cents INT NOT NULL, -- prix de référence HT
cover_image_s3_key VARCHAR(500),
gallery_s3_keys VARCHAR(500)[] DEFAULT '{}',
weight_grams INT, -- pour calcul shipping
dimensions_mm JSONB, -- { l, w, h }
is_active BOOLEAN NOT NULL DEFAULT false,
is_preorder BOOLEAN NOT NULL DEFAULT false,
available_from TIMESTAMPTZ, -- pour précommandes
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
deleted_at TIMESTAMPTZ
);
CREATE INDEX idx_products_active ON products(is_active) WHERE deleted_at IS NULL;
CREATE INDEX idx_products_type ON products(product_type) WHERE is_active = true;
```
### 2.3 Table `product_variants`
```sql
CREATE TABLE product_variants (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
product_id UUID NOT NULL REFERENCES products(id) ON DELETE CASCADE,
sku VARCHAR(50) NOT NULL UNIQUE,
variant_name VARCHAR(100) NOT NULL, -- ex: "Finition noire"
attributes JSONB NOT NULL, -- { finition: "noir", couleur: null }
price_modifier_cents INT NOT NULL DEFAULT 0, -- différence vs base
stock_quantity INT NOT NULL DEFAULT 0,
reserved_quantity INT NOT NULL DEFAULT 0, -- stock en cours d'achat
low_stock_threshold INT NOT NULL DEFAULT 3,
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_product_variants_product ON product_variants(product_id) WHERE is_active = true;
CREATE INDEX idx_product_variants_sku ON product_variants(sku);
```
### 2.4 Table `product_options` (options ajoutables)
```sql
CREATE TABLE product_options (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
product_id UUID NOT NULL REFERENCES products(id) ON DELETE CASCADE,
option_name VARCHAR(100) NOT NULL, -- "Pochette tissu"
option_type VARCHAR(20) NOT NULL, -- 'addon', 'required_choice'
price_cents INT NOT NULL DEFAULT 0,
sort_order INT NOT NULL DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT true
);
```
---
## 3. Gestion du stock
### 3.1 Principe "réservation temporaire"
Quand un user ajoute un variant au panier → **pas de réservation**.
Quand il passe au checkout → **réservation 15 minutes**.
Si paiement OK → réservation devient consommation.
Si paiement échoue / timeout → réservation libérée.
### 3.2 Table `stock_reservations`
```sql
CREATE TABLE stock_reservations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
variant_id UUID NOT NULL REFERENCES product_variants(id),
order_id UUID NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
quantity INT NOT NULL,
reserved_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
expires_at TIMESTAMPTZ NOT NULL,
released_at TIMESTAMPTZ,
consumed_at TIMESTAMPTZ
);
CREATE INDEX idx_stock_reservations_variant ON stock_reservations(variant_id) WHERE released_at IS NULL AND consumed_at IS NULL;
CREATE INDEX idx_stock_reservations_expires ON stock_reservations(expires_at) WHERE released_at IS NULL AND consumed_at IS NULL;
```
### 3.3 Cron de libération
Cron toutes les 5 min : `UPDATE stock_reservations SET released_at = NOW() WHERE expires_at < NOW() AND released_at IS NULL AND consumed_at IS NULL`.
Puis `UPDATE product_variants SET reserved_quantity = recalcul`.
### 3.4 Alertes stock bas
Quand `stock_quantity - reserved_quantity <= low_stock_threshold` → email admin.
---
## 4. Panier (Cart)
### 4.1 Deux modes
**Anonyme** : panier stocké en cookie/localStorage frontend (TTL 7 jours).
**Connecté** : panier persisté en DB, sync entre devices.
### 4.2 Table `carts` (user connecté)
```sql
CREATE TABLE carts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (user_id)
);
CREATE TABLE cart_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
cart_id UUID NOT NULL REFERENCES carts(id) ON DELETE CASCADE,
variant_id UUID NOT NULL REFERENCES product_variants(id),
quantity INT NOT NULL DEFAULT 1 CHECK (quantity > 0),
selected_options UUID[] DEFAULT '{}', -- IDs product_options
added_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (cart_id, variant_id, selected_options)
);
```
### 4.3 Fusion panier anonyme → connecté
À la connexion : merge des items du localStorage dans le cart DB. Stratégie : si conflit sur variant, garder qté max.
---
## 5. Commandes (Orders)
### 5.1 Table `orders`
```sql
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
order_number VARCHAR(20) NOT NULL UNIQUE, -- 'TALAS-2026-00123'
user_id UUID NOT NULL REFERENCES users(id),
status VARCHAR(30) NOT NULL DEFAULT 'draft',
-- 'draft', 'awaiting_payment', 'paid', 'preparing',
-- 'shipped', 'delivered', 'cancelled', 'refunded'
-- montants en centimes
subtotal_cents INT NOT NULL,
shipping_cents INT NOT NULL,
discount_cents INT NOT NULL DEFAULT 0,
vat_rate NUMERIC(4,2) NOT NULL DEFAULT 0,
vat_cents INT NOT NULL DEFAULT 0,
total_cents INT NOT NULL,
currency VARCHAR(3) NOT NULL DEFAULT 'EUR',
-- contact
contact_email VARCHAR(200) NOT NULL,
contact_phone VARCHAR(30),
-- adresses (snapshot au moment de la commande)
shipping_address JSONB NOT NULL,
billing_address JSONB NOT NULL,
-- expédition
shipping_method VARCHAR(30),
tracking_number VARCHAR(100),
tracking_url VARCHAR(500),
shipped_at TIMESTAMPTZ,
delivered_at TIMESTAMPTZ,
-- notes
customer_note TEXT,
internal_note TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
cancelled_at TIMESTAMPTZ,
cancellation_reason VARCHAR(100)
);
CREATE INDEX idx_orders_user ON orders(user_id, created_at DESC);
CREATE INDEX idx_orders_status ON orders(status) WHERE status NOT IN ('cancelled', 'delivered');
CREATE INDEX idx_orders_number ON orders(order_number);
```
### 5.2 Table `order_items`
```sql
CREATE TABLE order_items (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
order_id UUID NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
variant_id UUID NOT NULL REFERENCES product_variants(id),
-- snapshot au moment de la commande
product_title VARCHAR(200) NOT NULL,
variant_name VARCHAR(100) NOT NULL,
sku VARCHAR(50) NOT NULL,
unit_price_cents INT NOT NULL,
quantity INT NOT NULL CHECK (quantity > 0),
options_description TEXT,
options_price_cents INT NOT NULL DEFAULT 0,
line_total_cents INT NOT NULL -- (unit+options)*qty
);
```
### 5.3 Numérotation commande
Format : `TALAS-YYYY-NNNNN` (séquence PG annuelle, rotation 1er janvier).
---
## 6. Lifecycle commande
```
[draft]
│ (user construit sa commande)
[awaiting_payment]
│ (checkout validé, paiement créé)
├── timeout 15min sans paiement → [cancelled]
▼ (webhook payment.paid)
[paid]
│ (stock consommé, email envoyé)
[preparing]
│ (manuel : Nikola prépare le colis)
[shipped]
│ (tracking_number renseigné, email envoyé)
[delivered]
│ (manuel ou auto via webhook transporteur)
[fin]
Transitions alternatives :
- [paid/preparing] → [cancelled] (annulation, trigger refund)
- [shipped] → [refunded] (retour après livraison)
```
---
## 7. Expédition
### 7.1 Transporteurs supportés V1
| Transporteur | Zone | API |
|-------------|------|-----|
| **La Poste Colissimo** | FR + intl | API Colissimo |
| **Mondial Relay** | FR + Benelux | API relais |
| **Chronopost** | FR + intl | API Chronopost |
| **SEUR** | ES + EU | API SEUR (phase 2) |
### 7.2 Calcul des frais de port
**V1** : grille tarifaire statique par zone + poids.
```sql
CREATE TABLE shipping_rates (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
carrier VARCHAR(30) NOT NULL,
method_name VARCHAR(100) NOT NULL,
zone_code VARCHAR(10) NOT NULL, -- 'FR', 'EU1', 'EU2', 'INTL'
weight_min_g INT NOT NULL,
weight_max_g INT NOT NULL,
price_cents INT NOT NULL,
estimated_days_min INT,
estimated_days_max INT,
is_active BOOLEAN NOT NULL DEFAULT true
);
```
Table `shipping_zones` définit les zones → pays.
**V2** : appels API live pour tarifs temps réel.
### 7.3 Étiquettes d'expédition
**V1 manuel** : Nikola génère l'étiquette sur le site transporteur, saisit le `tracking_number` dans l'admin Veza.
**V2 automatisé** : API d'étiquette, PDF généré auto, déposé dans MinIO.
---
## 8. API Endpoints
Base : `/api/v1/shop`
### 8.1 Catalogue (public)
| Méthode | Route | Auth | Description |
|---------|-------|------|-------------|
| GET | `/products` | 🔓 | Liste produits actifs |
| GET | `/products/:slug` | 🔓 | Détail produit + variants + options |
| GET | `/categories` | 🔓 | Types de produits |
### 8.2 Panier
| Méthode | Route | Auth | Description |
|---------|-------|------|-------------|
| GET | `/cart` | 🔒 | Mon panier |
| POST | `/cart/items` | 🔒 | Ajouter item |
| PUT | `/cart/items/:id` | 🔒 | Modifier qté |
| DELETE | `/cart/items/:id` | 🔒 | Retirer item |
| DELETE | `/cart` | 🔒 | Vider panier |
| POST | `/cart/merge` | 🔒 | Merge depuis localStorage |
### 8.3 Checkout & commande
| Méthode | Route | Auth | Description |
|---------|-------|------|-------------|
| POST | `/checkout/shipping-rates` | 🔒 | Calcul frais (poids, destination) |
| POST | `/checkout/validate` | 🔒 | Valider addresses + panier |
| POST | `/orders` | 🔒 | Créer order depuis cart |
| GET | `/orders` | 🔒 | Mes commandes |
| GET | `/orders/:id` | 🔒 | Détail commande |
| POST | `/orders/:id/cancel` | 🔒 | Annuler (si encore possible) |
### 8.4 Admin
| Méthode | Route | Auth | Description |
|---------|-------|------|-------------|
| GET | `/admin/orders` | 🔒👑 | Toutes commandes (filtres) |
| PUT | `/admin/orders/:id/status` | 🔒👑 | Changer status |
| POST | `/admin/orders/:id/ship` | 🔒👑 | Marquer expédié (tracking_number) |
| POST | `/admin/products` | 🔒👑 | Créer produit |
| PUT | `/admin/products/:id` | 🔒👑 | Modifier |
| POST | `/admin/variants/:id/stock` | 🔒👑 | Ajuster stock |
---
## 9. Events RabbitMQ
| Event | Publisher | Consumers |
|-------|-----------|-----------|
| `order.created` | Shop | Email, Analytics |
| `order.paid` | Payment webhook | Shop (update status), Email |
| `order.cancelled` | Shop | Payment (refund), Email, Stock |
| `order.shipped` | Admin action | Email (tracking), Analytics |
| `stock.low` | Shop cron | Email admin |
---
## 10. Sécurité
- **Verrouillage stock** : `SELECT FOR UPDATE` pendant checkout
- **Idempotence** création commande : header `Idempotency-Key` côté client
- **Rate limit** : 10 orders créées / jour / user
- **Validation adresse** : service externe (Addressify self-hosted ou regex FR/EU)
---
## 11. Roadmap
### V1.0 (lancement, août 2026)
- [ ] Catalogue produits + variants
- [ ] Panier (anon + connecté)
- [ ] Checkout + calcul shipping
- [ ] Lifecycle commande 8 statuts
- [ ] Réservation stock 15min
- [ ] Admin basique
### V1.5 (Q4 2026)
- [ ] Codes promo / remises
- [ ] Alertes stock bas
- [ ] Reviews produit
- [ ] Wishlist
### V2.0 (2027)
- [ ] API étiquettes expédition
- [ ] Multi-devise
- [ ] Précommandes / crowdfunding
- [ ] Bundles produits
---
## Voir aussi
- [[Shop/Paiement/SPEC_PAIEMENT]] — intégration Mollie, TVA, factures
- [[Shop/Frontend/SPEC_FRONTEND_SHOP]] — UX frontend
- [[02_PRODUITS_PHYSIQUES/Microphone/FICHE_PRODUIT]] — détails produit micro
- [[09_MODELE_ECONOMIQUE/Modèle_Marge]] — calcul des prix
- [[08_CONFORMITE_JURIDIQUE/Politique_Garantie_Retour]] — politique retours

View file

@ -0,0 +1,367 @@
# Spec technique — Frontend Shop
> Interface boutique Veza : parcours d'achat, panier, checkout, espace client.
> Stack : React 18 · TypeScript · Tailwind v4 · Zustand · TanStack Query · React Router v6
> Design : aligné SUMI V3 (lavis japonais, cyan accent)
> Dernière mise à jour : avril 2026.
---
## 1. Vue d'ensemble
Le frontend shop est une **section intégrée** de l'app Veza (`/shop/*`), pas une app séparée. Elle partage l'auth, le design system SUMI V3, et les composants de base.
### Principes UX
1. **Mobile-first** — 60% du trafic probable sur mobile
2. **Parcours court** — max 3 clics entre produit et paiement
3. **Pas de dark patterns** — pas d'urgence fake, pas de stock mensonger
4. **Transparence radicale** — prix HT/TTC, délais, réparabilité affichés
5. **Cohérence visuelle** — même "lavis japonais" que le reste de Veza
---
## 2. Architecture composants
```
apps/web/src/
├── pages/shop/
│ ├── ShopHome.tsx # /shop
│ ├── ProductListPage.tsx # /shop/products
│ ├── ProductDetailPage.tsx # /shop/products/:slug
│ ├── CartPage.tsx # /shop/cart
│ ├── CheckoutPage.tsx # /shop/checkout
│ ├── CheckoutSuccessPage.tsx # /shop/checkout/success
│ ├── OrderListPage.tsx # /shop/orders
│ └── OrderDetailPage.tsx # /shop/orders/:id
├── components/shop/
│ ├── ProductCard.tsx
│ ├── ProductGallery.tsx
│ ├── ProductConfigurator.tsx # variants + options
│ ├── PriceDisplay.tsx # HT/TTC/devise
│ ├── CartDrawer.tsx # panier en overlay
│ ├── CartItemRow.tsx
│ ├── CheckoutStepIndicator.tsx
│ ├── AddressForm.tsx
│ ├── ShippingMethodSelector.tsx
│ ├── PaymentMethodSelector.tsx
│ ├── OrderSummary.tsx
│ ├── OrderStatusBadge.tsx
│ └── OrderTimeline.tsx
├── stores/shop/
│ ├── cartStore.ts # Zustand + persist localStorage
│ └── checkoutStore.ts
├── services/api/shop.ts # TanStack Query hooks
└── hooks/shop/
├── useCart.ts
├── useProducts.ts
└── useOrders.ts
```
---
## 3. Parcours d'achat (5 écrans clés)
### 3.1 `/shop` — Accueil boutique
**Contenu** :
- Hero statement (1 phrase forte + photo atelier)
- Grid des produits actifs (max 6)
- Section "Pourquoi Talas" (3 arguments clés, pas 10)
- Section réassurance (garanties, open-hardware, réparabilité)
- CTA vers page produit
**Interactions** :
- Scroll fluide
- Hover sur ProductCard : preview autres vues
### 3.2 `/shop/products/:slug` — Fiche produit
**Zones principales** :
```
┌─────────────────────────────────────────────────────┐
│ [Galerie photos × 5-8] │ Titre + sous-titre │
│ │ Prix HT/TTC │
│ [Player audio demo] │ │
│ │ [Configurateur] │
│ │ • Variant radio │
│ │ • Options checkbox │
│ │ │
│ │ Prix final mis à jour │
│ │ [Ajouter au panier] │
│ │ │
├──────────────────────────┴──────────────────────────┤
│ Onglets : Description | Specs | Réparabilité | │
│ Dans la boîte | Garantie | FAQ │
├─────────────────────────────────────────────────────┤
│ Avis utilisateurs │
├─────────────────────────────────────────────────────┤
│ Produits associés │
└─────────────────────────────────────────────────────┘
```
**Configurateur** :
- Sélection variant = obligatoire
- Options = optionnelles, cochables
- Prix recalculé instantanément
- Stock affiché honnêtement ("En stock" / "Précommande" / "Plus que N")
### 3.3 `/shop/cart` — Panier
- Liste des items avec vignette, titre, variant, options, prix unitaire, quantité
- Calcul sous-total
- Champ code promo (V1.5)
- Boutons : modifier qté, supprimer, vider panier
- CTA "Passer au checkout"
- Liens retour boutique
**Drawer alternatif** : `CartDrawer` overlay latéral déclenché depuis header (ajout rapide sans quitter la page produit).
### 3.4 `/shop/checkout` — Tunnel de commande
**3 étapes visibles** (`CheckoutStepIndicator`) :
```
[1. Coordonnées] → [2. Livraison] → [3. Paiement]
```
**Étape 1 — Coordonnées**
- Email (pré-rempli si connecté)
- Adresse de livraison (form avec autocomplete OSM nominatim)
- Checkbox "Adresse de facturation identique"
- Adresse de facturation (si différente)
- Téléphone (optionnel)
**Étape 2 — Livraison**
- Méthodes disponibles avec prix + délai estimé
- Radio sélection
- Note : "Colis expédié en matériaux recyclés"
**Étape 3 — Paiement**
- Récap commande (items + totaux + adresse)
- Sélection méthode : CB (Mollie), iDEAL, SEPA, virement direct
- Checkbox CGV obligatoire
- CTA "Payer XX,XX €"
- Redirection vers Mollie
### 3.5 `/shop/checkout/success` — Confirmation
- ✓ Message succès
- Numéro commande
- Résumé commande
- Délai estimé de préparation
- Lien "Suivre ma commande"
- Lien télécharger facture (quand disponible)
---
## 4. State management
### 4.1 `cartStore.ts` (Zustand + persist)
```typescript
interface CartState {
items: CartItem[]
addItem: (variant: Variant, options: string[], qty: number) => void
removeItem: (itemId: string) => void
updateQuantity: (itemId: string, qty: number) => void
clear: () => void
subtotal: () => number // computed
itemCount: () => number // computed
}
```
Persisté en `localStorage` → panier survit à la fermeture onglet.
Sur login, fusion avec panier serveur via `POST /api/v1/shop/cart/merge`.
### 4.2 `checkoutStore.ts`
Stocke les données du tunnel (adresses, shipping method, payment method) pour ne pas perdre si back/forward.
Nettoyé après commande validée ou abandon > 1h.
---
## 5. Data fetching (TanStack Query)
### 5.1 Hooks principaux
| Hook | Query key | Endpoint |
|------|-----------|----------|
| `useProducts(filters)` | `['shop', 'products', filters]` | `GET /shop/products` |
| `useProduct(slug)` | `['shop', 'product', slug]` | `GET /shop/products/:slug` |
| `useCart()` | `['shop', 'cart']` | `GET /shop/cart` |
| `useShippingRates(address, weight)` | `['shop', 'shipping', address]` | `POST /shop/checkout/shipping-rates` |
| `useOrders()` | `['shop', 'orders']` | `GET /shop/orders` |
| `useOrder(id)` | `['shop', 'order', id]` | `GET /shop/orders/:id` |
### 5.2 Stratégies de cache
- **Products** : staleTime 5min, refetch on focus
- **Cart** : staleTime 0 (toujours frais)
- **Orders** : staleTime 30s, refetch on mount
---
## 6. Design system — cohérence SUMI V3
### 6.1 Composants qui héritent
- `Button`, `Input`, `Select`, `Checkbox`, `Radio` → réutilisés de SUMI V3
- `Card`, `Drawer`, `Modal`, `Toast` → idem
### 6.2 Spécificités shop
- `PriceDisplay` : typographie tabulaire, cyan pour prix principal
- `ProductCard` : hover = léger scale + ombre encre
- `OrderTimeline` : trait d'encre qui se rempliuit selon statuts
### 6.3 Couleurs de statut
| Statut commande | Couleur badge |
|-----------------|---------------|
| Draft | gris #9A958D |
| Awaiting payment | ambre dilué |
| Paid | cyan #0098B5 |
| Preparing | cyan muted |
| Shipped | cyan + trait plein |
| Delivered | encre noire |
| Cancelled | gris barré |
| Refunded | ambre barré |
---
## 7. Espace client
### 7.1 `/shop/orders` — Mes commandes
- Liste chronologique
- Filtres : statut, période
- Chaque ligne : numéro, date, total, statut (badge), lien détail
### 7.2 `/shop/orders/:id` — Détail commande
**Contenu** :
- Numéro commande + statut (badge grand format)
- `OrderTimeline` (visual status)
- Items commandés (vignettes + descriptions)
- Adresses (livraison + facturation)
- Totaux détaillés
- Si expédiée : tracking_number + lien transporteur
- Si facture disponible : bouton télécharger PDF
- Bouton "Annuler" si statut le permet
- Bouton "Contacter le support"
---
## 8. Accessibilité
### 8.1 Minimum WCAG AA
- Contraste texte ≥ 4.5:1 (vérifié avec palette SUMI)
- Focus visible partout (outline cyan 2px)
- Navigation clavier complète (Tab, Enter, Escape)
- ARIA labels sur tous boutons icon-only
- Skip to content
- Formulaires avec `<label for>` et messages d'erreur explicites
### 8.2 Lecteurs d'écran
- `role="status"` sur notifications
- `aria-live="polite"` sur cart count, total updates
- Alt texts descriptifs sur produits
---
## 9. Performance
### 9.1 Objectifs
| Métrique | Cible |
|----------|-------|
| LCP (Largest Contentful Paint) | < 2.5s |
| CLS (Cumulative Layout Shift) | < 0.1 |
| TTI (Time to Interactive) | < 3.5s |
| Bundle shop (gzipped) | < 80 KB |
### 9.2 Stratégies
- **Lazy loading** des images produits (`loading="lazy"`)
- **Route splitting** : pages shop chargées à la demande
- **Image optim** : WebP/AVIF, srcset responsive
- **Prefetch** au hover sur ProductCard
---
## 10. Internationalisation (i18n)
- Utilise i18next (existant dans Veza)
- Fichiers : `locales/fr/shop.json`, `locales/en/shop.json`, `locales/es/shop.json`
- Devise affichée selon locale (€, $, £)
- Formats date/heure locaux
V1 : FR complet, EN partiel.
V1.5 : EN complet, ES basique.
---
## 11. Tests
### 11.1 Unit (Vitest)
- Reducers cartStore
- Calculs totaux, TVA
- Composants isolés (PriceDisplay, ProductCard)
### 11.2 Integration (Vitest + Testing Library)
- Flow complet : ajout panier → checkout → validation
- Gestion erreurs API (rupture stock, paiement refusé)
### 11.3 E2E (Playwright)
- Parcours d'achat complet
- Connexion → commande → espace client
- Annulation commande
### 11.4 Visual regression (Storybook + Chromatic-like)
- Snapshots des composants shop dans le design system
---
## 12. Roadmap
### V1.0 (lancement, août 2026)
- [ ] Pages shop home, produit, panier, checkout
- [ ] Cart store + sync serveur
- [ ] Checkout 3 étapes
- [ ] Redirection Mollie
- [ ] Espace client (commandes + facture)
### V1.5 (Q4 2026)
- [ ] CartDrawer latéral
- [ ] Codes promo
- [ ] Reviews produit (avec photos)
- [ ] Wishlist
- [ ] Améliorations a11y
### V2.0 (2027)
- [ ] Multi-devise
- [ ] Checkout guest (sans compte)
- [ ] Paiement en plusieurs fois
- [ ] Récap PDF de commande
---
## Voir aussi
- [[Shop/Backend/SPEC_BACKEND_SHOP]] — API consommée
- [[Shop/Paiement/SPEC_PAIEMENT]] — flow paiement Mollie
- [[05_EXPERIENCE_UTILISATEUR/SUMI_V3_SPECIFICATION]] — design system
- [[05_EXPERIENCE_UTILISATEUR/DIRECTION_ARTISTIQUE_TALAS]] — direction visuelle
- [[FRONTEND_REACT]] — architecture frontend globale

View file

@ -0,0 +1,404 @@
# Spec technique — Module Paiement
> Stratégie de paiement Veza Shop pour micro-entreprise française, self-hosted first.
> Contrainte : pas de dépendance à un gros SaaS US, privilège aux acteurs européens.
> Stack : Go backend · webhooks HTTPS · PDF generation · PostgreSQL
> Dernière mise à jour : avril 2026.
---
## 1. Contraintes
### 1.1 Contraintes projet
- **Structure juridique** : micro-entreprise (Nikola, solo)
- **Philosophie** : self-hosted first, pas de cloud SaaS si évitable
- **Localisation** : siège France, vente EU + international
- **Valeurs** : éthique, éviter GAFAM-adjacent
### 1.2 Contraintes fiscales (micro-entreprise FR)
- **Franchise en base de TVA** : < 37 500 HT / an (plafond 2026, vérifier)
- **Si dépassé** : bascule TVA classique
- **Conservation factures** : 10 ans (DGFiP)
- **Numérotation obligatoire** : séquentielle, continue, sans trou
- **Mentions légales obligatoires** sur facture
---
## 2. Choix du processor
### 2.1 Options étudiées
| Processor | Origine | Pour | Contre |
|-----------|---------|------|--------|
| **Mollie** | 🇳🇱 NL | API clean, fees raisonnables (1.8%+0.25€ CB EU), european | Dépendance |
| **SumUp** | 🇩🇪 DE/FR | Présence FR forte, fees 2.5% CB, pas mal de méthodes | API moins fluide que Mollie |
| **Lemonway** | 🇫🇷 FR | Agréé ACPR, escrow | Complexe, cher |
| **PayPlug** | 🇫🇷 FR | Filiale Natixis, FR | Moins d'intégrations |
| **HelloAsso** | 🇫🇷 FR | Associatif, sans frais | Pas adapté à e-commerce commercial |
| Stripe | 🇺🇸 US | Meilleur API, ultra répandu | US SaaS, contre les valeurs |
| PayPal | 🇺🇸 US | Universel | GAFAM-adjacent, fees élevés |
### 2.2 Décision V1 : Mollie + SEPA direct
**Primary** : **Mollie**
- API moderne, docs bien faites
- Fees compétitifs en Europe
- Couvre CB, Bancontact, iDEAL, SEPA, Apple/Google Pay
- Webhooks fiables
- Société européenne
**Secondary** : **SEPA virement bancaire direct**
- Pour commandes > 300 €
- Gratuit, mais délai 1-3 jours
- IBAN Talas communiqué au client, vérification manuelle
**Explicitement exclus en V1** : Stripe, PayPal (valeurs).
### 2.3 Pourquoi pas 100% SEPA direct
- UX catastrophique pour commandes < 200
- Délai de vérification tue la conversion
- Risque d'erreurs (montant, référence)
→ Compromis : Mollie pour fluidité, SEPA pour gros montants / utilisateurs exigeants.
---
## 3. Architecture
```
┌──────────────────┐ ┌──────────────────┐
│ Frontend Shop │──────▶│ Backend API │
│ (checkout page) │ │ (Go / Gin) │
└──────────────────┘ └────────┬─────────┘
├──▶ Mollie API
│ (creation paiement)
├──▶ PostgreSQL
│ (payments, invoices)
└──▶ PDF generator
(go-pdf ou gotenberg)
┌──────────────────┐
│ Mollie │
│ webhook POST │───▶ /webhooks/mollie
└──────────────────┘ │
│ signature verify
│ update payment status
│ send email client
│ trigger shipment prep
```
---
## 4. Modèle de données
### 4.1 Table `payments`
```sql
CREATE TABLE payments (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
order_id UUID NOT NULL REFERENCES orders(id),
user_id UUID NOT NULL REFERENCES users(id),
amount_cents INT NOT NULL, -- en centimes
currency VARCHAR(3) NOT NULL DEFAULT 'EUR',
processor VARCHAR(20) NOT NULL, -- 'mollie', 'sepa_direct'
processor_payment_id VARCHAR(100), -- ID chez le processor
method VARCHAR(30), -- 'ideal', 'card', 'sepa', ...
status VARCHAR(20) NOT NULL DEFAULT 'pending',
-- 'pending', 'authorized', 'paid', 'failed',
-- 'expired', 'refunded', 'partially_refunded'
paid_at TIMESTAMPTZ,
failed_at TIMESTAMPTZ,
failure_reason VARCHAR(200),
refunded_amount_cents INT NOT NULL DEFAULT 0,
webhook_payload JSONB, -- dernier payload reçu
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX idx_payments_order ON payments(order_id);
CREATE INDEX idx_payments_user ON payments(user_id, created_at DESC);
CREATE INDEX idx_payments_status ON payments(status) WHERE status IN ('pending', 'authorized');
CREATE INDEX idx_payments_processor ON payments(processor, processor_payment_id);
```
### 4.2 Table `invoices`
```sql
CREATE TABLE invoices (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
order_id UUID NOT NULL REFERENCES orders(id),
user_id UUID NOT NULL REFERENCES users(id),
payment_id UUID REFERENCES payments(id),
invoice_number VARCHAR(30) NOT NULL UNIQUE, -- ex: 'TALAS-2026-00042'
invoice_type VARCHAR(20) NOT NULL, -- 'invoice', 'credit_note'
billing_name VARCHAR(150) NOT NULL,
billing_address JSONB NOT NULL, -- address complète
billing_country VARCHAR(2) NOT NULL,
subtotal_cents INT NOT NULL,
vat_rate NUMERIC(4,2) NOT NULL, -- 0.00 ou 20.00
vat_cents INT NOT NULL,
total_cents INT NOT NULL,
pdf_s3_key VARCHAR(500), -- chemin MinIO
issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
sent_at TIMESTAMPTZ
);
CREATE UNIQUE INDEX idx_invoices_number ON invoices(invoice_number);
CREATE INDEX idx_invoices_user ON invoices(user_id, issued_at DESC);
```
### 4.3 Numérotation des factures
Format : `TALAS-YYYY-NNNNN` (ex: `TALAS-2026-00042`)
- Séquence PostgreSQL par année : `CREATE SEQUENCE invoice_seq_2026;`
- Génération atomique en transaction
- Rotation au 1er janvier
---
## 5. Flux paiement
### 5.1 Paiement Mollie (standard)
```
[1] Frontend POST /api/v1/payments/create
Body: { order_id, method: 'ideal'|'card'|'sepa' }
[2] Backend:
- Lock order (SELECT FOR UPDATE)
- Check order.status == 'awaiting_payment'
- Calcul montant TTC
- INSERT payments (status='pending')
- Appel Mollie API POST /payments
Body: { amount, description, redirectUrl, webhookUrl, metadata }
- Stocke processor_payment_id
- Response: { checkout_url }
[3] Frontend redirige vers checkout_url Mollie
User paie sur UI Mollie
[4] Mollie → webhook POST /webhooks/mollie
Body: { id: 'tr_...' }
[5] Backend webhook handler:
- Vérifie signature
- GET Mollie /payments/:id
- Match avec payments.processor_payment_id
- UPDATE payments SET status=... paid_at=...
- Si 'paid' : UPDATE orders SET status='paid'
- Émet événement order.paid sur RabbitMQ
- Génère facture (async via worker)
- Envoie email de confirmation
[6] Frontend sur redirectUrl Mollie
GET /shop/checkout/success?id=...
- Backend vérifie status payment
- Affiche succès / message d'attente
```
### 5.2 Paiement SEPA direct (manuel)
```
[1] User choisit "SEPA virement direct"
[2] Backend:
- INSERT payments (status='pending', method='sepa_direct')
- Génère référence unique: 'TALAS-<order_id_short>'
- Envoie email avec IBAN + référence + montant
[3] User fait le virement
[4] Talas vérifie manuellement (interface admin)
- Matche virement entrant avec payment
- POST /admin/payments/:id/confirm-sepa
- UPDATE payments SET status='paid', paid_at=NOW()
- Reste du flow identique (facture, email, commande)
```
### 5.3 Remboursement
```
POST /admin/payments/:id/refund { amount_cents, reason }
Backend:
- Vérifie status == 'paid'
- Appel Mollie POST /payments/:id/refunds (si Mollie)
- OU marque à rembourser manuellement (si SEPA)
- INSERT refund_request (statut pending)
- Sur webhook Mollie refund.paid :
- UPDATE payments SET refunded_amount_cents += amount
- Si total refund : status='refunded'
- Si partiel : status='partially_refunded'
- Génère facture d'avoir (credit_note)
- Email client
```
---
## 6. TVA
### 6.1 Calcul selon statut
**Phase 1 : franchise en base (V1, par défaut)**
- `vat_rate = 0.00`
- Mention obligatoire sur facture : _"TVA non applicable, art. 293 B du CGI"_
**Phase 2 : TVA assujettie (si > 37 500 € CA)**
- France / UE B2C : 20% (TVA FR)
- UE B2B (avec n° TVA valide) : 0% (autoliquidation, reverse charge)
- Hors UE : 0% (export)
### 6.2 Vérification n° TVA intracommunautaire (B2B UE)
Service VIES de la Commission européenne : endpoint SOAP public, self-hostable aussi.
### 6.3 Règles OSS (One-Stop Shop)
Si > 10 000 € de ventes B2C intra-UE hors France :
- Inscription OSS
- TVA au taux du pays destinataire
- Déclaration trimestrielle unique
Implémentation V2 : table `vat_rates_eu` mise à jour manuellement.
---
## 7. Génération de factures PDF
### 7.1 Outil
**Décision** : **gotenberg** (self-hosted, open source, HTML→PDF via Chromium)
- Conteneur Docker sur l'infra R720
- Template HTML stylé Tailwind
- Génération async via RabbitMQ
Alternative : **go-pdf** direct (plus simple, moins joli)
### 7.2 Template facture
Champs obligatoires (DGFiP) :
- Numéro unique + date d'émission
- Identité vendeur (Talas, SIREN, adresse)
- Identité client (nom, adresse)
- Désignation produits/services (qté, PU HT, total HT)
- Taux TVA appliqué + mention légale
- Total HT, TVA, TTC
- Date échéance / conditions de règlement
- Mention pénalités de retard
- Mention escompte
### 7.3 Stockage
- PDF généré → uploadé dans MinIO bucket `veza-invoices`
- Accès : URL signée (expire 24h), lien envoyé par email + disponible dans espace client
- Conservation : **10 ans minimum** (backup offsite)
---
## 8. Sécurité
### 8.1 PCI-DSS
**Scope réduit** : Veza ne stocke **AUCUNE** donnée carte.
- Tokenization par Mollie
- Frontend ne transite jamais par Veza pour les données carte (iframe Mollie ou redirect)
- Scope SAQ-A (le plus bas)
### 8.2 Webhooks — sécurité
- **Vérification signature** : Mollie n'envoie pas de signature, on vérifie l'authenticité via re-call API (GET /payments/:id)
- **Idempotence** : webhook peut arriver plusieurs fois → check status avant update
- **IP allowlist** : optionnel, Mollie liste ses IPs
### 8.3 Anti-fraude basique V1
- Rate limit : 10 tentatives de paiement / heure / user
- Check IP vs billing country (warning si mismatch)
- Blocage pays haut-risque (configurable)
- Verrouillage user après 3 paiements échoués / jour
---
## 9. API Endpoints
Base : `/api/v1/payments`
| Méthode | Route | Auth | Description |
|---------|-------|------|-------------|
| POST | `/create` | 🔒 | Créer paiement pour un order |
| GET | `/:id` | 🔒 | Status paiement |
| GET | `/me` | 🔒 | Mes paiements |
| POST | `/:id/retry` | 🔒 | Relancer paiement échoué |
| GET | `/invoices/:id/download` | 🔒 | Télécharger PDF facture |
| POST | `/webhooks/mollie` | 🔓 | Webhook Mollie (public) |
| POST | `/admin/:id/confirm-sepa` | 🔒👑 | Confirmer SEPA manuel |
| POST | `/admin/:id/refund` | 🔒👑 | Rembourser |
---
## 10. Monitoring & alertes
| Alerte | Seuil | Action |
|--------|-------|--------|
| Webhook Mollie échec signature | > 3/h | Alerte Sentry |
| Taux échec paiement | > 10% | Check Mollie status |
| Paiements pending > 24h | > 5 | Review manuelle |
| Latence Mollie API | > 3s | Alerte monitoring |
| Facture non générée | > 1h après paid | Retry worker |
---
## 11. Roadmap
### V1.0 (lancement shop, août 2026)
- [ ] Intégration Mollie (CB + iDEAL + SEPA)
- [ ] SEPA direct manuel
- [ ] Génération facture PDF via gotenberg
- [ ] Remboursement complet
- [ ] Franchise en base TVA
### V1.5 (Q4 2026)
- [ ] Remboursement partiel
- [ ] Email transactionnels avancés
- [ ] Retry automatique paiements échoués
- [ ] Dashboard admin payments
### V2.0 (si CA > 37 500 €)
- [ ] TVA assujettie
- [ ] OSS multi-pays EU
- [ ] Vérification VIES
- [ ] Export comptable (CSV, FEC)
### V2.5+
- [ ] Crypto (BTC Lightning) si demandé
- [ ] Paiement en plusieurs fois (partnership)
- [ ] Abonnements (si produits récurrents)
---
## Voir aussi
- [[Shop/Backend/SPEC_BACKEND_SHOP]] — lifecycle commandes, triggers paiement
- [[Shop/Frontend/SPEC_FRONTEND_SHOP]] — UX checkout
- [[08_CONFORMITE_JURIDIQUE/CGU_CGV]] — conditions de vente
- [[09_MODELE_ECONOMIQUE/OPTIMISATION_FISCALE_ETHIQUE]] — stratégie fiscale
- [[01_PILOTAGE/CHECKLIST_IMMATRICULATION]] — micro-entreprise setup

View file

@ -0,0 +1,109 @@
# Workbook d'identité visuelle — Talas / Veza
> **À imprimer, à dessiner, à remplir à la main.**
> Ce workbook n'est pas un document numérique à compléter au clavier.
> Il est conçu pour être **sorti sur papier**, annoté au crayon, au feutre, avec de la gouache, des collages, du scotch et du café renversé.
---
## À qui s'adresse ce workbook
À toi, Nikola, seul face à une table à dessin.
Tu as déjà validé une direction artistique (**lavis japonais**) mais tu n'as pas encore :
- dessiné le **symbole** du logo
- testé physiquement les **nuances** de cyan
- dessiné les **icônes** en style geste
- défini l'**identité sonore**
- synthétisé la **charte** sur une seule feuille de référence
Ce workbook te guide sur chacun de ces points, exercice par exercice.
---
## Comment l'utiliser
### Préparation
1. **Imprime les fiches** en A4, une par une ou d'un coup
2. Prépare un **classeur** ou une **pochette cartonnée** pour les regrouper
3. Matériel minimum :
- crayon HB + gomme
- feutre noir fin (0.3) + feutre noir large (1.0)
- un pinceau + encre de chine (ou feutre brush-pen)
- crayons de couleur ou feutres (bleu cyan, noirs, gris)
- papier calque (facultatif mais utile)
- ciseaux + colle pour les moodboards
### Ordre recommandé
Les fiches sont numérotées dans un ordre logique (fondations → exploration → synthèse) mais tu peux les faire dans le désordre si une te parle plus qu'une autre.
**Parcours "minimum viable" (4 fiches, ~3h)** :
1. Fiche 01 — Manifeste et personnalité
2. Fiche 06 — Croquis du symbole
3. Fiche 08 — Exploration palette
4. Fiche 14 — Synthèse finale
**Parcours "complet" (15 fiches, ~2 weekends)** :
Dans l'ordre de 00 à 14.
### Règles du jeu
- **Pas de clavier.** Si tu tapes une phrase au lieu de l'écrire, relis cette ligne.
- **Pas de pression.** Une fiche ratée = tu reprends sur une nouvelle feuille.
- **Pas de "c'est laid".** Le but est d'explorer, pas de produire du fini.
- **Dater** chaque fiche en haut à droite pour suivre ton évolution.
---
## Liste complète des fiches
### Fondations (01 — 02)
- [ ] **01** — Manifeste et personnalité
- [ ] **02** — Territoire de marque (ce qu'on est / ce qu'on n'est pas)
### Moodboards (03 — 04)
- [ ] **03** — Moodboard ambiance générale
- [ ] **04** — Moodboard matières & geste
### Logo (05 — 07)
- [ ] **05** — Brainstorm logo (mots, formes, associations)
- [ ] **06** — Croquis du symbole (grille de 48 tentatives)
- [ ] **07** — Synthèse et déclinaisons logo
### Couleur (08 — 09)
- [ ] **08** — Exploration palette (nuanciers vides à remplir)
- [ ] **09** — Combinaisons et mises en situation
### Typographie & icônes (10 — 11)
- [ ] **10** — Tests typographiques (hiérarchie, espacements)
- [ ] **11** — Croquis d'icônes (grille de 24)
### Imagerie & son (12 — 13)
- [ ] **12** — Direction photographique
- [ ] **13** — Identité sonore
### Synthèse (14)
- [ ] **14** — Synthèse finale sur une page
---
## Après le workbook
Une fois les fiches remplies :
1. **Photographie** ou scanne tes meilleures pages
2. Classe-les dans `Identite_Visuelle/logo/`, `palette/`, `icones/`
3. **Numérise** le symbole final dans Inkscape
4. Mets à jour `CHARTE_GRAPHIQUE_TALAS.md` avec les décisions prises
5. Le workbook papier reste ton archive de travail — ne le jette pas
---
## Voir aussi
- [[DIRECTION_ARTISTIQUE_TALAS]] — le "pourquoi" de la direction
- [[CHARTE_GRAPHIQUE_TALAS]] — les règles actuelles
- [[GUIDE_CREATION_LOGO]] — pistes pour le symbole
- [[SUMI_V3_SPECIFICATION]] — implémentation technique web

View file

@ -0,0 +1,127 @@
# Fiche 01 — Manifeste & personnalité
> **Temps estimé** : 45 min
> **Matériel** : stylo noir, crayon
> **Objectif** : écrire en 15 phrases qui est Talas, avec tes propres mots.
Date : ____ / ____ / ________
---
## Exercice 1 — Les trois phrases fondatrices
Écris à la main, sans te corriger, trois phrases qui terminent ces débuts.
Ne cherche pas l'éloquence. Cherche la vérité.
**Talas existe parce que...**
___________________________________________________________________
___________________________________________________________________
___________________________________________________________________
**Ce que je refuse de faire dans ce projet, c'est...**
___________________________________________________________________
___________________________________________________________________
___________________________________________________________________
**Si Talas disparaissait demain, ce qui manquerait c'est...**
___________________________________________________________________
___________________________________________________________________
___________________________________________________________________
---
## Exercice 2 — Cinq adjectifs
Écris 5 adjectifs qui décrivent Talas **en tant que personne**.
Puis entoure les 3 que tu garderais même si on te forçait à en enlever 2.
1. _______________________
2. _______________________
3. _______________________
4. _______________________
5. _______________________
---
## Exercice 3 — L'archétype
Coche **un seul** archétype dominant (celui qui te gêne le moins) :
- [ ] **L'Artisan** — précision, patience, savoir-faire, outils
- [ ] **Le Créateur** — innovation, imagination, expression, liberté
- [ ] **Le Sage** — transmission, vérité, compréhension, pédagogie
- [ ] **L'Explorateur** — découverte, indépendance, liberté, authenticité
- [ ] **Le Rebelle** — rupture, contre-culture, refus du système
- [ ] **Le Magicien** — transformation, vision, rêve réalisé
Archétype secondaire (s'il t'en manque) : _________________________
**Pourquoi ce choix ?** (3 lignes max)
___________________________________________________________________
___________________________________________________________________
___________________________________________________________________
---
## Exercice 4 — La personne incarnée
**Si Talas était une personne que tu croisais dans un café, décris-la** :
Âge approximatif : ______________________
Ce qu'elle porte : ______________________________________________
Ce qu'elle boit : _______________________________________________
Ce qu'elle lit : ________________________________________________
Sa voix : ______________________________________________________
Son défaut : ___________________________________________________
Ce qu'elle répète souvent : ____________________________________
---
## Synthèse — à remplir en dernier
**Les 3 mots du projet, classés** :
1. _______________________
2. _______________________
3. _______________________
**La phrase qui résume tout** (une seule phrase, courte) :
___________________________________________________________________
___________________________________________________________________
**Prochaine fiche** : → `02_TERRITOIRE_MARQUE.md`

View file

@ -0,0 +1,124 @@
# Fiche 02 — Territoire de marque
> **Temps estimé** : 30 min
> **Matériel** : stylo noir, stylo rouge
> **Objectif** : cartographier ce que Talas EST et ce qu'il N'EST PAS.
Date : ____ / ____ / ________
---
## Exercice 1 — Le plan croisé
Place chaque concurrent/référence sur l'axe. Mets aussi **Talas** quelque part.
```
PREMIUM / LUXE
CORPORATE ───────────────┼─────────────── ARTISANAL
ACCESSIBLE / DIY
```
Positionne (écris les noms sur le plan) :
- Rode, Neumann, Shure, Audio-Technica
- Shelldock, BeyerDynamic, Earthworks
- Un fabricant que tu respectes
- Ton positionnement idéal = **TALAS** (entoure-le)
---
## Exercice 2 — Liste noire
**Talas ne doit JAMAIS ressembler à** :
☒ _______________________________________________________________
☒ _______________________________________________________________
☒ _______________________________________________________________
☒ _______________________________________________________________
☒ _______________________________________________________________
**Pourquoi ça me hérisse ?** (écris en rouge)
___________________________________________________________________
___________________________________________________________________
___________________________________________________________________
---
## Exercice 3 — Références admirées (hors audio)
Liste **5 marques/projets/personnes** que tu admires visuellement, **qui ne sont pas du tout du domaine audio**.
| # | Référence | Ce que j'aime chez eux |
|---|-----------|------------------------|
| 1 | | |
| 2 | | |
| 3 | | |
| 4 | | |
| 5 | | |
**Ce que ça dit de Talas** :
___________________________________________________________________
___________________________________________________________________
---
## Exercice 4 — Le test des contraires
Coche le mot de chaque paire qui colle à Talas :
- [ ] Chaud ↔ [ ] Froid
- [ ] Lisse ↔ [ ] Texturé
- [ ] Silencieux ↔ [ ] Expressif
- [ ] Strict ↔ [ ] Libre
- [ ] Précis ↔ [ ] Imprécis volontairement
- [ ] Nouveau ↔ [ ] Ancien
- [ ] Dense ↔ [ ] Aéré
- [ ] Rapide ↔ [ ] Lent
- [ ] Brillant ↔ [ ] Mat
- [ ] Solide ↔ [ ] Fragile
- [ ] Individuel ↔ [ ] Collectif
- [ ] Sobre ↔ [ ] Riche
- [ ] Sérieux ↔ [ ] Joueur
- [ ] Méthodique ↔ [ ] Intuitif
- [ ] Bavard ↔ [ ] Laconique
**Le mot surprenant** (celui que tu n'attendais pas) :
___________________________________________________________________
---
## Synthèse — le territoire en une phrase
**"Talas se situe à l'intersection entre _______________________ et _______________________, pour des gens qui _______________________."**
___________________________________________________________________
___________________________________________________________________
___________________________________________________________________
**Prochaine fiche** : → `03_MOODBOARD_AMBIANCE.md`

View file

@ -0,0 +1,94 @@
# Fiche 03 — Moodboard ambiance générale
> **Temps estimé** : 1h30
> **Matériel** : cette feuille (A4), ciseaux, colle ou scotch, magazines / imprimés, imprimante (optionnel)
> **Objectif** : assembler 12 images qui, ensemble, donnent l'ambiance de Talas.
Date : ____ / ____ / ________
---
## Consigne
Colle ou dessine **12 images** dans la grille ci-dessous.
Pas de logos, pas de textes. Des **ambiances, des matières, des lumières, des gestes**.
**Tu peux sourcer** : magazines, livres d'art, photos de ton atelier, captures d'écran imprimées, dessins à main levée, morceaux de tissu, écorces, photos de famille.
**Tu ne dois PAS** : chercher "moodboard minimaliste japonais" sur Pinterest. Trop facile, trop générique.
---
## Grille 4×3 (A4 paysage conseillé)
```
┌──────────────┬──────────────┬──────────────┬──────────────┐
│ │ │ │ │
│ 1 │ 2 │ 3 │ 4 │
│ │ │ │ │
│ │ │ │ │
├──────────────┼──────────────┼──────────────┼──────────────┤
│ │ │ │ │
│ 5 │ 6 │ 7 │ 8 │
│ │ │ │ │
│ │ │ │ │
├──────────────┼──────────────┼──────────────┼──────────────┤
│ │ │ │ │
│ 9 │ 10 │ 11 │ 12 │
│ │ │ │ │
│ │ │ │ │
└──────────────┴──────────────┴──────────────┴──────────────┘
```
*(Si la grille est trop petite imprimée, prends une feuille A3 et redessine-la.)*
---
## Après collage — annotation
Pour chaque image, écris **un seul mot** qui la résume :
| # | Mot-clé | # | Mot-clé |
|---|---------|---|---------|
| 1 | ______________ | 7 | ______________ |
| 2 | ______________ | 8 | ______________ |
| 3 | ______________ | 9 | ______________ |
| 4 | ______________ | 10 | ______________ |
| 5 | ______________ | 11 | ______________ |
| 6 | ______________ | 12 | ______________ |
---
## Le test à distance
Accroche la feuille au mur. Recule de 3 mètres. Regarde.
**Ce que je ressens en regardant ça de loin** :
___________________________________________________________________
___________________________________________________________________
**L'image qui détonne** (celle qui ne colle pas) : #______
**Si je devais retirer 3 images** : #____, #____, #____
**Si je devais garder que 3 images** : #____, #____, #____
---
## Synthèse
**L'ambiance générale en 3 mots** :
1. _______________________
2. _______________________
3. _______________________
**Ce qui revient le plus souvent dans mon moodboard** (matière, couleur, sujet) :
___________________________________________________________________
**Prochaine fiche** : → `04_MOODBOARD_MATIERES_GESTE.md`

View file

@ -0,0 +1,133 @@
# Fiche 04 — Moodboard matières & geste
> **Temps estimé** : 1h
> **Matériel** : papier, colle, pinceau, encre de chine (ou feutre brush-pen), vrais échantillons de matière
> **Objectif** : sentir physiquement les matières et les gestes de la marque.
Date : ____ / ____ / ________
---
## Pourquoi cette fiche
Talas est une marque **hybride** : numérique (Veza) et physique (micros).
Pour que l'identité tienne, elle doit être **tactile** avant d'être visuelle.
Cette fiche t'oblige à penser avec les doigts, pas juste les yeux.
---
## Partie 1 — Échantillonneur de matières
Colle un **vrai morceau** de chaque matière dans la case prévue.
Ou trace une **empreinte à la mine de crayon** (frottis) si tu ne peux pas coller.
### Matières "papier"
```
┌─────────────────────┬─────────────────────┐
│ │ │
│ Papier kraft │ Papier washi │
│ (non blanchi) │ (ou calque) │
│ │ │
│ │ │
└─────────────────────┴─────────────────────┘
┌─────────────────────┬─────────────────────┐
│ │ │
│ Papier recyclé │ Carton brut │
│ (bureau) │ (colis) │
│ │ │
│ │ │
└─────────────────────┴─────────────────────┘
```
### Matières "produit" (métal, tissu, bois)
```
┌─────────────────────┬─────────────────────┐
│ │ │
│ Métal brossé │ Métal mat │
│ (frottis mine) │ (frottis mine) │
│ │ │
│ │ │
└─────────────────────┴─────────────────────┘
┌─────────────────────┬─────────────────────┐
│ │ │
│ Tissu noir │ Mousse/grille │
│ (coton/lin) │ (bonnette) │
│ │ │
│ │ │
└─────────────────────┴─────────────────────┘
```
---
## Partie 2 — Geste d'encre
**Trace 8 gestes différents** au pinceau (ou brush-pen) dans cette grille.
Ne cherche pas à faire joli. Cherche à **varier** : rapide/lent, plein/vide, courbe/droit.
```
┌───────────┬───────────┬───────────┬───────────┐
│ │ │ │ │
│ 1 │ 2 │ 3 │ 4 │
│ │ │ │ │
│ │ │ │ │
├───────────┼───────────┼───────────┼───────────┤
│ │ │ │ │
│ 5 │ 6 │ 7 │ 8 │
│ │ │ │ │
│ │ │ │ │
└───────────┴───────────┴───────────┴───────────┘
```
**Sous chaque geste, écris sa durée** (ex : "2 sec", "1 min"), la **pression** (légère/forte), et **un verbe** qui le décrit (glisser, frapper, effleurer, écraser...).
| # | Durée | Pression | Verbe |
|---|-------|----------|-------|
| 1 | | | |
| 2 | | | |
| 3 | | | |
| 4 | | | |
| 5 | | | |
| 6 | | | |
| 7 | | | |
| 8 | | | |
---
## Partie 3 — Le geste qui est Talas
Parmi tes 8 gestes, **entoure celui** qui ressemble le plus à l'identité de Talas.
**Pourquoi celui-là ?**
___________________________________________________________________
___________________________________________________________________
___________________________________________________________________
---
## Synthèse
**Les matières retenues pour Talas** (coche max 3) :
- [ ] Kraft non blanchi
- [ ] Washi / calque
- [ ] Papier recyclé
- [ ] Métal brossé
- [ ] Métal mat anodisé
- [ ] Tissu noir
- [ ] Mousse grille
- [ ] Autre : _______________
**Le geste signature** (1 phrase qui le décrit) :
___________________________________________________________________
___________________________________________________________________
**Prochaine fiche** : → `05_LOGO_BRAINSTORM.md`

View file

@ -0,0 +1,137 @@
# Fiche 05 — Brainstorm logo
> **Temps estimé** : 45 min
> **Matériel** : stylo, crayon, timer
> **Objectif** : vider ta tête sur le symbole avant de dessiner.
Date : ____ / ____ / ________
---
## Règle du jeu
**Ne dessine pas encore.** Cette fiche sert à **trier les idées**.
Les croquis viennent en fiche 06.
---
## Exercice 1 — Les mots de "Talas"
Le nom Talas évoque une **vague** (en bulgare, en russe). Écris tout ce qui te passe par la tête, sans filtrer, pendant **5 minutes** (timer obligatoire).
**Ce que "Talas" évoque visuellement** :
___________________________________________________________________
___________________________________________________________________
___________________________________________________________________
___________________________________________________________________
___________________________________________________________________
___________________________________________________________________
___________________________________________________________________
___________________________________________________________________
___________________________________________________________________
___________________________________________________________________
---
## Exercice 2 — Les formes possibles
Écris dans chaque case **une forme géométrique** qui pourrait représenter Talas.
Puis entoure les 3 qui t'attirent le plus.
```
┌────────────────┬────────────────┬────────────────┐
│ │ │ │
│ │ │ │
│ │ │ │
├────────────────┼────────────────┼────────────────┤
│ │ │ │
│ │ │ │
│ │ │ │
├────────────────┼────────────────┼────────────────┤
│ │ │ │
│ │ │ │
│ │ │ │
└────────────────┴────────────────┴────────────────┘
```
*(Exemples pour t'aider : onde, cercle, trait, éclair, montagne, sceau, point, croissant, spirale... écris, ne dessine pas encore.)*
---
## Exercice 3 — Ce que le logo doit dire
Le symbole Talas doit évoquer **en 0.5 seconde** :
_____________________________________________________ (obligatoire)
_____________________________________________________ (obligatoire)
_____________________________________________________ (si possible)
_____________________________________________________ (si possible)
_____________________________________________________ (à éviter)
_____________________________________________________ (à éviter)
---
## Exercice 4 — Les logos que j'admire
Liste **5 logos de marques** (de n'importe quel secteur) qui te touchent visuellement.
| # | Marque | Ce qui fonctionne | En 1 mot |
|---|--------|-------------------|----------|
| 1 | | | |
| 2 | | | |
| 3 | | | |
| 4 | | | |
| 5 | | | |
**Le dénominateur commun entre ces 5 logos** :
___________________________________________________________________
___________________________________________________________________
---
## Exercice 5 — Le pire logo possible pour Talas
**Décris en 3 phrases le logo que Talas ne doit JAMAIS devenir** :
___________________________________________________________________
___________________________________________________________________
___________________________________________________________________
---
## Synthèse
**Les 3 mots-clés visuels du symbole** :
1. _______________________
2. _______________________
3. _______________________
**Le symbole fonctionnera si** (1 critère que tu te donnes) :
___________________________________________________________________
**Prochaine fiche** : → `06_LOGO_CROQUIS_SYMBOLE.md`

View file

@ -0,0 +1,187 @@
# Fiche 06 — Croquis du symbole (48 tentatives)
> **Temps estimé** : 2h (en 2-3 sessions)
> **Matériel** : crayon HB, feutre noir fin, feutre noir large, pinceau + encre
> **Objectif** : 48 tentatives de symbole. Oui, 48. On ne valide rien en-dessous.
Date début : ____ / ____ / ________
---
## Les règles
1. **Chaque case = 1 tentative** (pas plus, pas moins)
2. **Tu n'as pas le droit de gommer** — si une case est ratée, passe à la suivante
3. **Pas deux fois la même idée** — varie épaisseur, geste, complexité
4. **Alterne les outils** : 12 au crayon, 12 au feutre fin, 12 au feutre large, 12 au pinceau
5. **Respire entre les sessions** — fais-en 16 par jour sur 3 jours
---
## Grille A : Crayon HB (12 cases)
```
┌────────┬────────┬────────┬────────┐
│ │ │ │ │
│ 1 │ 2 │ 3 │ 4 │
│ │ │ │ │
│ │ │ │ │
├────────┼────────┼────────┼────────┤
│ │ │ │ │
│ 5 │ 6 │ 7 │ 8 │
│ │ │ │ │
│ │ │ │ │
├────────┼────────┼────────┼────────┤
│ │ │ │ │
│ 9 │ 10 │ 11 │ 12 │
│ │ │ │ │
│ │ │ │ │
└────────┴────────┴────────┴────────┘
```
---
## Grille B : Feutre noir fin (12 cases)
```
┌────────┬────────┬────────┬────────┐
│ │ │ │ │
│ 13 │ 14 │ 15 │ 16 │
│ │ │ │ │
│ │ │ │ │
├────────┼────────┼────────┼────────┤
│ │ │ │ │
│ 17 │ 18 │ 19 │ 20 │
│ │ │ │ │
│ │ │ │ │
├────────┼────────┼────────┼────────┤
│ │ │ │ │
│ 21 │ 22 │ 23 │ 24 │
│ │ │ │ │
│ │ │ │ │
└────────┴────────┴────────┴────────┘
```
---
## Grille C : Feutre noir large (12 cases)
```
┌────────┬────────┬────────┬────────┐
│ │ │ │ │
│ 25 │ 26 │ 27 │ 28 │
│ │ │ │ │
│ │ │ │ │
├────────┼────────┼────────┼────────┤
│ │ │ │ │
│ 29 │ 30 │ 31 │ 32 │
│ │ │ │ │
│ │ │ │ │
├────────┼────────┼────────┼────────┤
│ │ │ │ │
│ 33 │ 34 │ 35 │ 36 │
│ │ │ │ │
│ │ │ │ │
└────────┴────────┴────────┴────────┘
```
---
## Grille D : Pinceau + encre (12 cases)
```
┌────────┬────────┬────────┬────────┐
│ │ │ │ │
│ 37 │ 38 │ 39 │ 40 │
│ │ │ │ │
│ │ │ │ │
├────────┼────────┼────────┼────────┤
│ │ │ │ │
│ 41 │ 42 │ 43 │ 44 │
│ │ │ │ │
│ │ │ │ │
├────────┼────────┼────────┼────────┤
│ │ │ │ │
│ 45 │ 46 │ 47 │ 48 │
│ │ │ │ │
│ │ │ │ │
└────────┴────────┴────────┴────────┘
```
---
## Tri
**Session 1** — entoure tes **8 préférés** (tous outils confondus).
Numéros : ____ , ____ , ____ , ____ , ____ , ____ , ____ , ____
**Session 2 (le lendemain)** — entoure tes **3 préférés** parmi les 8.
Numéros : ____ , ____ , ____
**Session 3** — choisis **UN** symbole. Celui-là.
Numéro : ____
---
## Test de réduction
Recopie ton symbole final **à 3 tailles** :
```
┌──────────────────────┐
│ │ 64mm (taille normale)
│ │
│ │
│ │
│ │
└──────────────────────┘
┌──────────┐
│ │ 24mm (moyen)
│ │
└──────────┘
┌──┐
│ │ 8mm (favicon)
└──┘
```
**Est-ce qu'il tient à 8mm ?** Oui / Non
**Si non** — qu'est-ce qu'il faut simplifier ?
___________________________________________________________________
---
## Test de contraste
Recopie-le **en blanc sur fond noir** (ou sur papier noir) :
```
████████████████████████████████
█ █
█ █
█ █
█ █
█ █
█ █
█ █
█ █
████████████████████████████████
```
**Fonctionne-t-il en inverse ?** Oui / Non
---
## Synthèse
**Mon symbole** = tentative numéro ____ / 48
**Prochaine fiche** : → `07_LOGO_SYNTHESE_DECLINAISONS.md`

View file

@ -0,0 +1,209 @@
# Fiche 07 — Synthèse & déclinaisons logo
> **Temps estimé** : 1h
> **Matériel** : crayon, feutre noir, règle, calque
> **Objectif** : verrouiller le logo final et ses 8 déclinaisons nécessaires.
Date : ____ / ____ / ________
---
## Partie 1 — Le logo définitif
### Symbole seul
Dessine ton symbole **propre** (la version définitive issue de la fiche 06) :
```
┌────────────────────────────────────────────┐
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
└────────────────────────────────────────────┘
```
### Logotype seul
Dessine "TALAS" en MAJUSCULES, Space Grotesk Bold, letter-spacing large.
Trace d'abord des repères au crayon, puis repasse au feutre.
```
┌────────────────────────────────────────────┐
│ │
│ │
│ │
│ │
│ │
└────────────────────────────────────────────┘
```
---
## Partie 2 — Les 8 déclinaisons
### Version 1 — Horizontal (symbole + logotype côte à côte)
```
┌────────────────────────────────────────────┐
│ │
│ │
│ │
│ │
└────────────────────────────────────────────┘
```
### Version 2 — Vertical (symbole au-dessus, logotype dessous)
```
┌──────────────────────┐
│ │
│ │
│ │
│ │
│ │
│ │
└──────────────────────┘
```
### Version 3 — Symbole seul (favicon) — 32×32 et 16×16
```
┌────────┐ ┌──┐
│ │ │ │
│ │ └──┘
│ │
└────────┘
```
### Version 4 — Monochrome noir sur blanc
```
┌────────────────────────────┐
│ │
│ │
│ │
└────────────────────────────┘
```
### Version 5 — Monochrome blanc sur noir
```
████████████████████████████████
█ █
█ █
█ █
████████████████████████████████
```
### Version 6 — Gravure laser (contours uniquement, pas de remplissage)
```
┌────────────────────────────┐
│ │
│ │
│ │
└────────────────────────────┘
```
### Version 7 — Broderie sur tissu (simplifié, traits épais)
```
┌────────────────────────────┐
│ │
│ │
│ │
└────────────────────────────┘
```
### Version 8 — Sceau (hanko) — symbole dans un carré
```
┌────────────────────────────┐
│ ╔════════════════════════╗ │
│ ║ ║ │
│ ║ ║ │
│ ║ ║ │
│ ║ ║ │
│ ╚════════════════════════╝ │
└────────────────────────────┘
```
---
## Partie 3 — Zone de protection
Dessine le symbole entouré de sa zone de protection (espace vide minimal autour).
**Règle** : la zone de protection = hauteur du symbole / X. X = ____
```
┌──────────────────────────────────────────────┐
│ ← ─── ─── ─── ─── ─── ─── ─── ─── ─── ───→ │
│ │ │ │
│ │ │ │
│ │ │ │
│ │ [ SYMBOLE ] │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ ← ─── ─── ─── ─── ─── ─── ─── ─── ─── ───→ │
└──────────────────────────────────────────────┘
```
---
## Partie 4 — Test d'application
Dessine rapidement comment ton logo apparaît sur :
### Carte de visite (85×55mm)
```
┌──────────────────────┐
│ │
│ │
│ │
│ │
└──────────────────────┘
```
### Bouton web
```
┌──────────────────────┐
│ │
│ │
└──────────────────────┘
```
### Sticker rond 50mm
```
╭────────╮
│ │
│ │
╰────────╯
```
---
## Checklist finale
- [ ] Le symbole fonctionne en 8mm
- [ ] Le logo tient sur fond noir ET blanc
- [ ] Le symbole est reconnaissable **sans** le logotype
- [ ] Il n'y a pas de petits détails qui disparaissent en gravure laser
- [ ] Il évoque bien les mots-clés de la fiche 05
- [ ] Je suis fier de le montrer
- [ ] Il ne ressemble à **aucun autre logo** que je connais
**Prochaine fiche** : → `08_PALETTE_EXPLORATION.md`

View file

@ -0,0 +1,131 @@
# Fiche 08 — Exploration palette
> **Temps estimé** : 1h30
> **Matériel** : gouache / aquarelle / crayons de couleur / feutres, nuancier Pantone (facultatif), papier washi ou kraft
> **Objectif** : peindre physiquement les couleurs pour les confronter au vrai monde.
Date : ____ / ____ / ________
---
## Rappel de la direction
La palette Talas est **monochrome + cyan**. Objectif de cette fiche : trouver **le bon cyan** et **la bonne nuance de papier**.
---
## Partie 1 — Les neutres (papiers & encres)
Peins / colorie chaque case **pleine**. Puis note ton impression à côté.
### Papiers (fond)
| Échantillon | Nom | Code HEX | Impression (1 mot) |
|:---:|:---|:---|:---|
| ▓▓▓▓▓▓▓▓▓▓ | Blanc pur | #FFFFFF | ______________ |
| ▓▓▓▓▓▓▓▓▓▓ | Blanc crème | #F7F3EC | ______________ |
| ▓▓▓▓▓▓▓▓▓▓ | Washi doux | #F2EDE6 | ______________ |
| ▓▓▓▓▓▓▓▓▓▓ | Kraft clair | #E5DCC8 | ______________ |
| ▓▓▓▓▓▓▓▓▓▓ | Ivoire vieilli | #EFE6D2 | ______________ |
**Papier retenu** : ______________
### Encres (texte/élément)
| Échantillon | Nom | Code HEX | Impression |
|:---:|:---|:---|:---|
| ▓▓▓▓▓▓▓▓▓▓ | Noir pur | #000000 | ______________ |
| ▓▓▓▓▓▓▓▓▓▓ | Noir sumi | #1A1A1E | ______________ |
| ▓▓▓▓▓▓▓▓▓▓ | Noir chaud | #201C17 | ______________ |
| ▓▓▓▓▓▓▓▓▓▓ | Noir bleuté | #151820 | ______________ |
**Encre retenue** : ______________
### Gris (encre diluée)
| Échantillon | Nom | Code HEX | Impression |
|:---:|:---|:---|:---|
| ▓▓▓▓▓▓▓▓▓▓ | Gris chaud | #9A958D | ______________ |
| ▓▓▓▓▓▓▓▓▓▓ | Gris froid | #8D9298 | ______________ |
| ▓▓▓▓▓▓▓▓▓▓ | Gris moyen | #7D7A74 | ______________ |
| ▓▓▓▓▓▓▓▓▓▓ | Gris clair | #BFB9AF | ______________ |
**Gris retenu** : ______________
---
## Partie 2 — Le cyan (la seule couleur)
**C'est LA décision la plus importante de cette fiche.**
Peins ces 8 cyans différents. Regarde-les **sur washi** ET **sur noir**.
### Les 8 candidats
| # | Échantillon | Nom | Code HEX | Sur papier | Sur noir |
|:---:|:---:|:---|:---|:---|:---|
| 1 | ▓▓▓▓▓▓ | Cyan classique | #00B4D8 | ______ | ______ |
| 2 | ▓▓▓▓▓▓ | Cyan profond | #0098B5 | ______ | ______ |
| 3 | ▓▓▓▓▓▓ | Teal sombre | #006B7F | ______ | ______ |
| 4 | ▓▓▓▓▓▓ | Cyan électrique | #00D4FF | ______ | ______ |
| 5 | ▓▓▓▓▓▓ | Cyan doux | #5BB6C7 | ______ | ______ |
| 6 | ▓▓▓▓▓▓ | Cyan pétrole | #1A6E82 | ______ | ______ |
| 7 | ▓▓▓▓▓▓ | Cyan poudré | #8EC5D0 | ______ | ______ |
| 8 | ▓▓▓▓▓▓ | Cyan Klein | #002FA7 | ______ | ______ |
### Le test du pigment rare
**Règle** : le cyan doit ressembler à **un pigment rare** posé sur un lavis monochrome. Pas à un bouton web Bootstrap.
**Mon classement top 3** :
1. #____ — ____________________________________________________
2. #____ — ____________________________________________________
3. #____ — ____________________________________________________
**Mon cyan définitif** : #____ Code HEX : ________________
---
## Partie 3 — Test sur le produit physique
Si tu as un micro en main (même un proto), pose un petit collage/post-it de ton cyan **contre le métal**.
**Est-ce que le cyan "tient" contre le métal ?** Oui / Non
**Est-ce qu'il est trop fort ? trop faible ?** ____________________
**Note pour la broderie de la pochette** :
___________________________________________________________________
---
## Partie 4 — Couleurs fonctionnelles (erreurs, succès, warnings)
Règle : **jamais en aplat**, **jamais plus fort que le cyan**.
| Rôle | Couleur proposée | Code | Trouvé OK ? |
|------|-----------------|------|-------------|
| Succès | Vert sauge dilué | #5A8C64 40% | _______ |
| Erreur | Rouge brique dilué | #B45046 40% | _______ |
| Warning | Ambre dilué | #BE963C 40% | _______ |
| Info | = Cyan principal | (voir ci-dessus) | _______ |
---
## Synthèse — la palette Talas
```
┌────────────────────────────────────────────────┐
│ │
│ PAPIER ENCRE GRIS CYAN │
│ │
│ ▓▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓▓ │
│ │
#______ #______ #______ #______
│ │
└────────────────────────────────────────────────┘
```
**Prochaine fiche** : → `09_PALETTE_COMBINAISONS.md`

View file

@ -0,0 +1,153 @@
# Fiche 09 — Combinaisons & mises en situation
> **Temps estimé** : 1h
> **Matériel** : les couleurs validées de la fiche 08, règle, crayon
> **Objectif** : tester les combinaisons dans des contextes réels avant de les verrouiller.
Date : ____ / ____ / ________
---
## Partie 1 — Hiérarchie visuelle
**Question** : dans une page web, dans quel ordre dois-je voir les éléments ?
Classe les couleurs de **1 (vu en premier)** à **4 (vu en dernier)** :
- [ ] _____ Cyan (accent)
- [ ] _____ Encre / noir sumi (textes)
- [ ] _____ Gris chaud (textes secondaires)
- [ ] _____ Papier washi (fond)
**Pourquoi cet ordre ?**
___________________________________________________________________
---
## Partie 2 — Combinaisons à tester
Colorie chaque "mini-composition". Observe ce qui fonctionne.
### Combinaison A — Site web (fond clair)
```
┌─────────────────────────────────┐
│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │ fond = papier
│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
│ ░░ ████ ░░░░░░░░░░░░░░ ▓▓▓▓ ░░░ │ titre=encre CTA=cyan
│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
│ ░░ ──── ────── ── ────── ─── ░░ │ corps=gris chaud
│ ░░ ──── ────── ── ────── ─── ░░ │
│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
└─────────────────────────────────┘
```
**Impression** : ____________________________________________________
### Combinaison B — Site web (fond sombre)
```
┌─────────────────────────────────┐
│ ███████████████████████████████ │ fond = noir encre
│ ███████████████████████████████ │
│ ██ ░░░░ ░░░░░░░░░░░░░░ ▓▓▓▓ ██ │ titre=blanc CTA=cyan
│ ███████████████████████████████ │
│ ██ ──── ────── ── ────── ─── ██ │ corps=gris
│ ██ ──── ────── ── ────── ─── ██ │
│ ███████████████████████████████ │
└─────────────────────────────────┘
```
**Impression** : ____________________________________________________
### Combinaison C — Packaging produit
```
┌─────────────────────────────────┐
│ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ │ fond = kraft
│ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ │
│ ▒▒▒▒▒▒▒▒▒▒▒ ████ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ │ logo = encre noire
│ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ │
│ ▒▒▒▒▒▒▒▒▒▒▒ TALAS ▒▒▒▒▒▒▒▒▒▒▒▒▒ │
│ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ │
└─────────────────────────────────┘
```
**Impression** : ____________________________________________________
### Combinaison D — Pochette micro
```
┌─────────────────────────────────┐
│ █████████████████████████████ │ tissu = noir
│ █████████████████████████████ │
│ ████████ ▓▓▓▓ █████████████ │ broderie = cyan
│ █████████████████████████████ │
│ █████████████████████████████ │
└─────────────────────────────────┘
```
**Impression** : ____________________________________________________
---
## Partie 3 — Proportion des couleurs
Dans une page complète, quelle proportion pour chaque couleur ?
**Règle du "60-30-5-5"** (à valider) :
- _____ % papier (fond)
- _____ % encre (textes)
- _____ % gris (textes secondaires)
- _____ % cyan (accent — JAMAIS plus de 5%)
Total = 100 ✓
**Dessine un rectangle de 100 cases et colorie selon tes proportions** :
```
██████████████████████████████████████████████████
██████████████████████████████████████████████████
██████████████████████████████████████████████████
██████████████████████████████████████████████████
██████████████████████████████████████████████████
██████████████████████████████████████████████████
██████████████████████████████████████████████████
██████████████████████████████████████████████████
██████████████████████████████████████████████████
██████████████████████████████████████████████████
```
---
## Partie 4 — Interdits
Interdiction formelle (coche pour confirmer) :
- [ ] Jamais deux couleurs d'accent côte à côte
- [ ] Jamais de rouge sur cyan (ou cyan sur rouge)
- [ ] Jamais de gradient cyan → violet / cyan → vert
- [ ] Jamais d'aplat cyan qui couvre + 20% d'une surface
- [ ] Jamais de texte cyan sur fond clair (contraste insuffisant)
- [ ] Jamais de bouton cyan + bordure cyan + fond cyan (overdose)
Ajoute tes propres interdits :
☒ _______________________________________________________________
☒ _______________________________________________________________
---
## Synthèse
**La règle d'or de la palette Talas** (écris-la comme un serment) :
___________________________________________________________________
___________________________________________________________________
___________________________________________________________________
**Prochaine fiche** : → `10_TYPOGRAPHIE_TEST.md`

View file

@ -0,0 +1,168 @@
# Fiche 10 — Tests typographiques
> **Temps estimé** : 45 min
> **Matériel** : imprimante + spécimens des 3 polices (Space Grotesk, Inter, JetBrains Mono), règle, crayon
> **Objectif** : tester la hiérarchie typographique sur papier avant de la coder.
Date : ____ / ____ / ________
---
## Préparation
Imprime des spécimens des 3 polices dans différentes tailles et poids.
Tu peux utiliser [fonts.google.com](https://fonts.google.com) et imprimer une capture.
**Sans internet** : recopie à la main les alphabets.
---
## Partie 1 — Le logotype (Space Grotesk Bold)
Écris **TALAS** dans 5 tailles, en variant l'espacement :
```
TALAS (letter-spacing 0em)
T A L A S (letter-spacing 0.05em)
T A L A S (letter-spacing 0.10em)
T A L A S (letter-spacing 0.15em)
T A L A S (letter-spacing 0.20em)
```
**L'espacement idéal** (entoure) : 0 / 0.05 / 0.10 / 0.15 / 0.20
**Pourquoi celui-là ?**
___________________________________________________________________
---
## Partie 2 — Hiérarchie de titres
Recopie cette hiérarchie avec les bonnes tailles. Ajuste jusqu'à ce que ça respire.
```
┌───────────────────────────────────────────────────┐
│ │
│ GRAND TITRE PAGE (H1) │ 48-64pt
│ │
│ SOUS-TITRE DE SECTION (H2) │ 32-40pt
│ │
│ Titre de bloc (H3) │ 24-28pt
│ │
│ Sous-titre (H4) │ 18-20pt
│ │
│ Corps de texte normal, lisible à l'aise sur │ 14-16pt
│ plusieurs lignes sans effort. Le texte doit │
│ flotter dans le papier avec un interligne de │
│ 1.6 à 1.8. │
│ │
│ Note secondaire, légende, petite info. │ 11-12pt
│ │
└───────────────────────────────────────────────────┘
```
**Ce qui marche** : ______________________________________________
**Ce qui coince** : _______________________________________________
---
## Partie 3 — Comparaison de polices
Écris le même texte dans les 3 polices. **Observe lequel te parle le plus.**
Texte : _"Talas fabrique des micros qu'on répare soi-même."_
### En Space Grotesk (Regular) :
___________________________________________________________________
___________________________________________________________________
### En Inter (Regular) :
___________________________________________________________________
___________________________________________________________________
### En JetBrains Mono (Regular) :
___________________________________________________________________
___________________________________________________________________
**La police qui donne le ton Talas** : _____________________________
---
## Partie 4 — Le "flottement"
Règle Talas : **le texte flotte dans le papier, il n'y est pas collé**.
Teste l'interligne (leading) :
```
Version serrée (1.2) — étouffée :
Lorem ipsum dolor sit amet, consectetur adipiscing.
Lorem ipsum dolor sit amet, consectetur adipiscing.
Lorem ipsum dolor sit amet, consectetur adipiscing.
Version moyenne (1.5) — classique :
Lorem ipsum dolor sit amet, consectetur adipiscing.
Lorem ipsum dolor sit amet, consectetur adipiscing.
Version aérée (1.8) — Talas :
Lorem ipsum dolor sit amet, consectetur adipiscing.
Lorem ipsum dolor sit amet, consectetur adipiscing.
```
**Mon interligne préféré** : 1.2 / 1.5 / **1.8** / autre : ______
---
## Partie 5 — Le texte dans un bloc
Dessine un bloc de texte avec **bordures** visibles et **marge intérieure** (padding) :
```
┌─────────────────────────────────────────────┐
│ │ ← marge haut = ____
│ │
│ Texte texte texte texte texte texte │ ← marge gauche = ____
│ texte texte texte texte texte texte │
│ texte texte texte texte texte texte │
│ texte texte texte texte. │
│ │
│ │
└─────────────────────────────────────────────┘
```
**Règle retenue** : marge = ____ × hauteur d'une ligne de texte
---
## Synthèse
**Mes règles typographiques Talas** :
1. Titre = **Space Grotesk** Bold, letter-spacing _____, MAJUSCULES ✓
2. Corps = **Inter** Regular, taille _____, interligne _____
3. Monospace (code/specs) = **JetBrains Mono**
4. Marge intérieure minimum = _____
5. Texte secondaire = gris #9A958D (voir fiche 08)
**Prochaine fiche** : → `11_ICONES_CROQUIS.md`

View file

@ -0,0 +1,149 @@
# Fiche 11 — Croquis d'icônes (grille de 24)
> **Temps estimé** : 2h (en 2 sessions)
> **Matériel** : feutre noir fin (0.3 et 0.5), crayon, règle
> **Objectif** : dessiner 24 icônes dans le style "geste de pinceau" cohérent avec le symbole.
Date : ____ / ____ / ________
---
## Règles du style Talas
1. **Trait unique** si possible — pas de remplissage
2. **Irrégularités volontaires** — pas lisse
3. **Épaisseur constante** par icône (varie entre icônes)
4. **Taille cible** : 24×24 px (dessine en 40×40mm sur papier)
5. **Grille invisible** : tout tient dans un carré
---
## Liste des 24 icônes nécessaires
### Navigation / UI (8)
1. Menu (hamburger)
2. Recherche (loupe)
3. Compte (avatar/personne)
4. Notifications (cloche)
5. Paramètres (engrenage ou curseurs)
6. Flèche retour
7. Flèche suivant
8. Fermer (croix)
### Contenu / Action (8)
9. Partager
10. Aimer / cœur
11. Commenter (bulle)
12. Télécharger
13. Uploader
14. Jouer (play)
15. Pause
16. Couper / muter
### Audio / Talas spécifique (8)
17. Micro
18. Onde sonore
19. Volume
20. Pattern polaire (cardio/omni)
21. Sample / fichier audio
22. Bibliothèque
23. Artiste / créateur
24. Communauté / groupe
---
## Grille de croquis (24 cases)
### Première passe — crayon
```
┌──────┬──────┬──────┬──────┐
│ │ │ │ │
│ 1 │ 2 │ 3 │ 4 │
│ │ │ │ │
├──────┼──────┼──────┼──────┤
│ │ │ │ │
│ 5 │ 6 │ 7 │ 8 │
│ │ │ │ │
├──────┼──────┼──────┼──────┤
│ │ │ │ │
│ 9 │ 10 │ 11 │ 12 │
│ │ │ │ │
├──────┼──────┼──────┼──────┤
│ │ │ │ │
│ 13 │ 14 │ 15 │ 16 │
│ │ │ │ │
├──────┼──────┼──────┼──────┤
│ │ │ │ │
│ 17 │ 18 │ 19 │ 20 │
│ │ │ │ │
├──────┼──────┼──────┼──────┤
│ │ │ │ │
│ 21 │ 22 │ 23 │ 24 │
│ │ │ │ │
└──────┴──────┴──────┴──────┘
```
### Deuxième passe — feutre noir
*(Recopie les icônes qui marchent sur une deuxième grille, en feutre, sans crayon.)*
```
┌──────┬──────┬──────┬──────┐
│ │ │ │ │
│ │ │ │ │
│ │ │ │ │
├──────┼──────┼──────┼──────┤
│ │ │ │ │
│ │ │ │ │
│ │ │ │ │
├──────┼──────┼──────┼──────┤
│ │ │ │ │
│ │ │ │ │
│ │ │ │ │
├──────┼──────┼──────┼──────┤
│ │ │ │ │
│ │ │ │ │
│ │ │ │ │
├──────┼──────┼──────┼──────┤
│ │ │ │ │
│ │ │ │ │
│ │ │ │ │
├──────┼──────┼──────┼──────┤
│ │ │ │ │
│ │ │ │ │
│ │ │ │ │
└──────┴──────┴──────┴──────┘
```
---
## Test de cohérence
**Pose la grille à 2 mètres. Regarde.**
Les 24 icônes forment-elles **une famille** ? Oui / Non
**Les 3 icônes qui détonnent** (à refaire) : ____, ____, ____
**L'icône la plus réussie** : #____
**Pourquoi celle-là marche** :
___________________________________________________________________
___________________________________________________________________
---
## Checklist finale
- [ ] Les 24 icônes ont la même épaisseur de trait
- [ ] Les 24 tiennent dans le même carré
- [ ] Elles ont toutes une "respiration" similaire (densité de trait)
- [ ] Elles évoquent le geste / pinceau, pas le vecteur rigide
- [ ] Elles sont toutes reconnaissables en 1 seconde
**Prochaine fiche** : → `12_DIRECTION_PHOTO.md`

View file

@ -0,0 +1,190 @@
# Fiche 12 — Direction photographique
> **Temps estimé** : 1h
> **Matériel** : smartphone ou appareil photo, cette feuille, stylo
> **Objectif** : définir comment on photographie les micros et l'atelier.
Date : ____ / ____ / ________
---
## Partie 1 — Les 6 types de photos dont Talas a besoin
Pour chaque type, colle/dessine une référence visuelle qui te plaît.
### Type 1 — Photo produit "fiche technique"
*Fond neutre, lumière égale, le micro en entier, précis comme un catalogue.*
```
┌────────────────────────────┐
│ │
│ │
│ RÉFÉRENCE │
│ │
│ │
└────────────────────────────┘
```
Référence : ______________________________________________________
### Type 2 — Photo produit "portrait"
*Lumière dramatique, détails, le micro comme un objet précieux.*
```
┌────────────────────────────┐
│ │
│ │
│ RÉFÉRENCE │
│ │
│ │
└────────────────────────────┘
```
Référence : ______________________________________________________
### Type 3 — Photo d'atelier
*Le désordre organisé, les outils, les mains, les étapes de fabrication.*
```
┌────────────────────────────┐
│ │
│ │
│ RÉFÉRENCE │
│ │
│ │
└────────────────────────────┘
```
Référence : ______________________________________________________
### Type 4 — Photo "usage"
*Quelqu'un utilise le micro — studio home, enregistrement, scène.*
```
┌────────────────────────────┐
│ │
│ │
│ RÉFÉRENCE │
│ │
│ │
└────────────────────────────┘
```
Référence : ______________________________________________________
### Type 5 — Photo "détail/macro"
*Très près. Une soudure, une grille, un filetage, une signature gravée.*
```
┌────────────────────────────┐
│ │
│ │
│ RÉFÉRENCE │
│ │
│ │
└────────────────────────────┘
```
Référence : ______________________________________________________
### Type 6 — Photo "humaine"
*Portrait d'un artiste/musicien. Visage, regard, mains.*
```
┌────────────────────────────┐
│ │
│ │
│ RÉFÉRENCE │
│ │
│ │
└────────────────────────────┘
```
Référence : ______________________________________________________
---
## Partie 2 — Règles photo Talas
Coche ou écris :
**Lumière** :
- [ ] Naturelle (fenêtre)
- [ ] Artificielle douce (diffuseur)
- [ ] Dure (ombres marquées)
- [ ] Mixte
**Traitement** :
- [ ] Noir & blanc (cohérent lavis)
- [ ] Couleur désaturée
- [ ] Couleur normale
- [ ] Noir & blanc + cyan isolé (accent couleur)
**Grain/texture** :
- [ ] Lisse numérique
- [ ] Grain argentique léger
- [ ] Grain argentique marqué
- [ ] Flou de mouvement occasionnel
**Cadrage** :
- [ ] Centré
- [ ] Règle des tiers
- [ ] Décentré radical
- [ ] Minimaliste (beaucoup de vide)
**Fond** :
- [ ] Toujours neutre (papier, béton, bois)
- [ ] Contextuel (atelier, studio)
- [ ] Noir profond
- [ ] Washi/texturé
---
## Partie 3 — Les anti-photos
**On ne photographie JAMAIS Talas comme** :
☒ _______________________________________________________________
☒ _______________________________________________________________
☒ _______________________________________________________________
☒ _______________________________________________________________
*(Exemples : photo studio pack blanc, flou lens-ball, drone aérien, couleurs saturées Instagram, personnes souriant caméra...)*
---
## Partie 4 — Photos à prendre cette semaine
Liste **3 photos** que tu peux aller prendre maintenant dans ton atelier :
1. ________________________________________________________________
2. ________________________________________________________________
3. ________________________________________________________________
---
## Synthèse — la règle photo Talas en 1 phrase
**"Les photos Talas sont _______________________, avec _______________________, et jamais _______________________."**
___________________________________________________________________
___________________________________________________________________
___________________________________________________________________
**Prochaine fiche** : → `13_IDENTITE_SONORE.md`

View file

@ -0,0 +1,165 @@
# Fiche 13 — Identité sonore
> **Temps estimé** : 1h30
> **Matériel** : stylo, casque, ton micro, DAW ou Audacity, papier à musique (facultatif)
> **Objectif** : définir le son de la marque. Talas fabrique du son — sa marque doit sonner.
Date : ____ / ____ / ________
---
## Pourquoi cette fiche existe
Talas est une marque **audio**. Elle doit avoir une signature sonore cohérente avec le lavis visuel : brute, artisanale, unique.
Cette fiche n'est pas optionnelle. **Si tu la saute, ta marque sera muette.**
---
## Partie 1 — Les mots qui décrivent le son Talas
Écris 5 mots qui décrivent **comment sonne** Talas (pas comment sonnent les micros — comment sonne la **marque**) :
1. _______________________
2. _______________________
3. _______________________
4. _______________________
5. _______________________
---
## Partie 2 — Analogies sonores
Coche ou complète :
**Le son Talas, c'est comme entendre** :
- [ ] Une porte en bois qui se ferme
- [ ] Un verre d'eau posé sur une table en bois
- [ ] Une page tournée
- [ ] Un pinceau qui frotte sur du papier
- [ ] Un coup de marteau sur du métal
- [ ] Un souffle (inspiration/expiration)
- [ ] Une allumette qui craque
- [ ] Autre : _______________________
**Le son Talas N'est PAS** :
- [ ] Un synthétiseur neon
- [ ] Un jingle de pub
- [ ] Un "ding" corporate Apple
- [ ] Un violon triste
- [ ] Un gong de yoga
- [ ] Autre à éviter : _______________________
---
## Partie 3 — Les 4 sons à créer
Talas a besoin de 4 sons signatures.
### Son 1 — Le logo sonore (3 secondes max)
*À jouer en intro de vidéos YouTube, podcasts, pitchs.*
**Description textuelle** :
___________________________________________________________________
___________________________________________________________________
**Instrumentation** :
___________________________________________________________________
**Brouillon en notation libre** (dessine la forme d'onde ou note les éléments) :
```
┌──────────────────────────────────────────────┐
│ │
│ │
│ │
│ │
└──────────────────────────────────────────────┘
t=0 t=1s t=2s t=3s
```
### Son 2 — Le son de notification (Veza)
*À jouer quand l'utilisateur reçoit un sample/message.*
**Description** :
___________________________________________________________________
**Durée** : _____ ms **Caractère** : discret / marqué / chaleureux / sec
### Son 3 — Le son de succès (action réussie)
*Upload réussi, achat finalisé, etc.*
**Description** :
___________________________________________________________________
**Durée** : _____ ms
### Son 4 — Le son d'erreur
*Discret, pas aggressif. Pas un buzzer.*
**Description** :
___________________________________________________________________
**Durée** : _____ ms
---
## Partie 4 — Les vraies sources sonores
Liste les **vrais objets** que tu peux enregistrer avec ton NT1-A ou ton prototype Talas pour construire ces sons :
### Objets disponibles à l'atelier
1. ________________________________________________________________
2. ________________________________________________________________
3. ________________________________________________________________
4. ________________________________________________________________
5. ________________________________________________________________
6. ________________________________________________________________
**Principe Talas** : on n'utilise **pas de samples génériques**. Tous les sons sont enregistrés à l'atelier avec un micro Talas.
---
## Partie 5 — Style musical d'accompagnement
Pour les vidéos / réseaux sociaux, l'ambiance musicale est :
- [ ] Aucune (silence + voix)
- [ ] Drone / ambient très minimal
- [ ] Piano seul dépouillé
- [ ] Field recording (sons de l'atelier)
- [ ] Musique traditionnelle (préciser : _____________________)
- [ ] Autre : _______________________
**3 artistes/albums de référence** :
1. _______________________________________________________________
2. _______________________________________________________________
3. _______________________________________________________________
---
## Synthèse — la règle sonore Talas
**"Le son Talas vient toujours du _______________________, jamais du _______________________."**
**Mon prochain pas concret** :
___________________________________________________________________
___________________________________________________________________
**Prochaine fiche** : → `14_SYNTHESE_FINALE.md`

View file

@ -0,0 +1,164 @@
# Fiche 14 — Synthèse finale (fiche de référence)
> **Temps estimé** : 1h
> **Matériel** : les 13 fiches précédentes remplies, un stylo noir, du temps
> **Objectif** : condenser tout le workbook en UNE fiche à afficher au-dessus de la table à dessin.
Date de synthèse : ____ / ____ / ________
---
## Identité en 1 phrase
*(Reprend la synthèse de la fiche 01)*
___________________________________________________________________
___________________________________________________________________
___________________________________________________________________
---
## Les 3 mots
1. **_______________________**
2. **_______________________**
3. **_______________________**
---
## Logo
**Symbole retenu** : version #____ (fiche 06)
```
┌──────────────────────┐
│ │
│ │
│ [SYMBOLE] │
│ │
│ │
└──────────────────────┘
```
**Logotype** : TALAS en Space Grotesk Bold, letter-spacing ____
---
## Palette définitive
```
┌────────┬────────┬────────┬────────┐
│ │ │ │ │
│ PAPIER │ ENCRE │ GRIS │ CYAN │
│ │ │ │ │
│ │ │ │ │
├────────┼────────┼────────┼────────┤
#____#____#____#____
└────────┴────────┴────────┴────────┘
```
**Proportion 60-30-5-5** : ____ / ____ / ____ / ____
---
## Typographie
| Usage | Police | Taille |
|-------|--------|--------|
| Titres | Space Grotesk Bold, MAJUSCULES, ls ____ | ____ |
| Corps | Inter Regular, interligne ____ | ____ |
| Code | JetBrains Mono | ____ |
---
## Style photo
En 1 phrase : _____________________________________________________
___________________________________________________________________
---
## Style sonore
En 1 phrase : _____________________________________________________
___________________________________________________________________
---
## Les 3 interdits absolus
☒ _______________________________________________________________
☒ _______________________________________________________________
☒ _______________________________________________________________
---
## Le serment
**Signature** (écris à la main que tu t'engages à respecter cette identité) :
___________________________________________________________________
___________________________________________________________________
Signé le ____ / ____ / ________ à _______________________
Signature : _______________________________________________________
---
## Affichage
**Photocopie cette fiche. Colle-la au-dessus de ton poste de travail.**
Tu la regardes chaque fois que tu hésites sur un choix visuel.
---
## Et maintenant — la suite
Une fois le workbook rempli :
1. [ ] **Photographier** les meilleures fiches et les classer dans :
- `05_EXPERIENCE_UTILISATEUR/Identite_Visuelle/logo/croquis/`
- `05_EXPERIENCE_UTILISATEUR/Identite_Visuelle/palette/echantillons/`
- `05_EXPERIENCE_UTILISATEUR/Identite_Visuelle/icones/croquis/`
2. [ ] **Numériser le symbole** définitif dans Inkscape (SVG + PNG 64/256/1024px)
3. [ ] **Mettre à jour** `CHARTE_GRAPHIQUE_TALAS.md` avec les codes HEX finaux
4. [ ] **Mettre à jour** `SUMI_V3_SPECIFICATION.md` si les couleurs web changent
5. [ ] **Enregistrer** les 4 sons de la fiche 13 à l'atelier
6. [ ] **Créer** un kit de presse visuel (logo, palette, règles) en PDF
7. [ ] **Afficher** cette fiche 14 au-dessus de la table à dessin
---
## Le workbook est terminé
Tu as maintenant une identité visuelle qui n'est pas **une idée** — c'est **un objet physique** fait avec tes mains, sur papier, à l'encre.
Cette identité est **la tienne**. Personne d'autre au monde n'a ce cahier.
Maintenant tu peux construire Veza et Talas dessus.
---
## Voir aussi
- [[IDENTITE_VISUELLE]] — le brief initial
- [[DIRECTION_ARTISTIQUE_TALAS]] — le "pourquoi"
- [[CHARTE_GRAPHIQUE_TALAS]] — les règles à mettre à jour après workbook
- [[GUIDE_CREATION_LOGO]] — pistes complémentaires pour le symbole
- [[SUMI_V3_SPECIFICATION]] — implémentation web SUMI v3

View file

@ -0,0 +1,307 @@
# Spec produit — Dépôt samples & presets Veza
> Spécification fonctionnelle du dépôt collaboratif de samples, presets, loops et one-shots.
> Générateur d'activité à faible friction pour la beta fondateurs.
> Dernière mise à jour : avril 2026.
---
## 1. Pourquoi ce dépôt
Les créateurs ont **déjà** ces fichiers sur leur disque dur. Uploader un pack = 5 minutes. Télécharger un pack = valeur immédiate.
C'est le générateur d'activité le plus simple à amorcer sur Veza. Il alimente aussi concrètement l'écosystème : les samples d'un artiste deviennent la matière première d'un autre, créant un circuit court **entre les membres de la communauté**.
Et contrairement à Splice ou Loopcloud, c'est **gratuit, attribué, et fait main**.
---
## 2. Principes fondateurs
### 2.1 Attribution, pas propriété
Le partage n'est **pas un don anonyme**. Chaque pack porte le nom de son créateur. L'utilisateur qui télécharge et utilise doit (moralement) créditer.
### 2.2 Qualité > quantité
Mieux vaut 10 packs travaillés que 200 dumps de samples douteux. La curation éditoriale est **au centre** du dépôt.
### 2.3 Fait main
Le dépôt valorise les packs créés **par la personne qui les poste**. Pas de re-partage de packs commerciaux (interdit). Pas de re-upload de Splice (interdit).
### 2.4 Pas de monétisation en V1
Tout est gratuit. Pas de packs payants. Pas de tips. Pas de premium. Voir §11 pour l'évolution V2.
---
## 3. Types de contenu
| Type | Format | Taille typique |
|------|--------|---------------|
| **Sample pack** | .zip contenant des WAV/FLAC | 10-500 MB |
| **One-shot** | Fichier WAV/FLAC seul | < 5 MB |
| **Loop** | WAV/FLAC avec tempo marqué | < 10 MB |
| **Preset synth** | Fichiers .fxp, .vital, .h2p, .nmsv, etc. | < 1 MB |
| **Preset effets** | .aupreset, .vstpreset, .fxp | < 1 MB |
| **Preset DAW** | Templates Ableton/Reaper/FL/Studio One | < 50 MB |
| **IR (Impulse Response)** | WAV | < 10 MB |
| **Field recording** | WAV long format | 10-500 MB |
| **Drum kit** | .zip structuré | 10-200 MB |
| **Sound bank** | Structure custom | Variable |
---
## 4. Anatomie d'un pack
### 4.1 Métadonnées obligatoires
| Champ | Description |
|-------|-------------|
| **Titre** | Nom du pack (80 car. max) |
| **Description** | Ce qu'il contient, comment il a été fait (1000 car.) |
| **Créateur** | Pseudo Veza (auto) |
| **Type** | Une des 10 catégories ci-dessus |
| **Licence** | CC-BY, CC-BY-SA, CC0, ou "usage non-commercial Veza uniquement" |
| **Tags** | 3 à 10 tags (genre, BPM, instrument, tonalité, ambiance) |
| **Preview audio** | MP3 court (< 30s) généré auto ou fourni par l'artiste |
### 4.2 Métadonnées optionnelles
- **BPM** / **Tonalité** (si applicable)
- **Genre** (hip-hop, techno, ambient, cinematic, etc.)
- **Outils utilisés** (Serum, Vital, Pigments, micro maison, etc.)
- **Inspiration / contexte** (texte libre — "enregistré dans ma cuisine un dimanche")
- **Liens vers des morceaux de démo** utilisant le pack
- **Remix permis ?** (oui/non)
### 4.3 Exemple de fiche pack
```
┌────────────────────────────────────────────────────────────┐
│ MORNING KITCHEN DRUMS │
│ par @senke
│ │
│ Type : Sample pack (drum kit) │
│ Licence : CC-BY-SA 4.0 │
│ Taille : 48 MB · 60 samples │
│ Tags : lofi, home-made, one-shot, drums, organic │
│ BPM : multi · Tonalité : - │
│ │
│ Description : │
│ Enregistré dimanche matin avec deux cuillères, un bol en │
│ inox, la machine à café, et le grille-pain. Traité au │
│ minimum : un peu de comp et de saturation. Toutes les │
│ sources sont étiquetées dans les filenames. │
│ │
│ ▶ Preview (28 sec) │
│ │
│ [ Télécharger ] [ Aimer ] [ Remixer ] [ Signaler ] │
│ │
│ 42 likes · 178 téléchargements · 3 remix │
└────────────────────────────────────────────────────────────┘
```
---
## 5. Licences
### 5.1 Les 4 licences proposées (V1)
| Licence | Droits donnés | Contraintes |
|---------|--------------|-------------|
| **CC0** | Usage libre total, même commercial | Aucune (attribution facultative) |
| **CC-BY 4.0** | Usage libre, même commercial | Doit créditer l'auteur |
| **CC-BY-SA 4.0** | Usage libre, même commercial | Doit créditer + toute œuvre dérivée doit être partagée sous la même licence |
| **Usage Veza uniquement** | Libre pour les œuvres partagées sur Veza | Pas d'usage commercial hors Veza |
**Licence par défaut** : CC-BY-SA 4.0 (celle qui favorise le plus la réciprocité).
### 5.2 Pas de licence propriétaire "payante"
En V1, pas de licence de type "100€ pour usage commercial". Si un artiste veut monétiser ses samples, il doit le faire **hors Veza** (Splice, Gumroad, Beatstars). Veza reste un espace de partage gratuit.
### 5.3 Attribution — comment créditer
Un pack sous CC-BY demande ce format d'attribution dans les crédits du morceau ou les métadonnées :
```
Samples: "Morning Kitchen Drums" by @senke
via Veza (veza.talas.co), CC-BY-SA 4.0
```
---
## 6. Flux utilisateur
### 6.1 Uploader un pack (le créateur)
1. Clic sur "Partager un pack" depuis son profil ou le menu
2. Upload fichier (drag&drop, jusqu'à 500 MB)
3. Choix du type
4. Remplissage des métadonnées (titre, description, tags, licence)
5. Génération auto de la preview audio (ou upload manuel)
6. Preview de la fiche pack
7. Publication
**Durée attendue** : 5-10 minutes pour un pack standard.
### 6.2 Parcourir le dépôt (le consommateur)
1. Accès via l'onglet "Samples" dans le menu principal
2. Feed par défaut : **récents** + **recommandés**
3. Filtres : type, licence, genre, BPM, tonalité, créateur
4. Tri : récent, populaire, plus téléchargé, nouveauté de la semaine
5. Écoute des previews sans télécharger
6. Téléchargement (zip direct ou fichier individuel)
### 6.3 Remixer un pack
Un bouton "Remixer" permet de créer un **pack dérivé** qui :
- Cite l'original dans sa fiche (backlink auto)
- Apparaît dans l'arbre "remix" de l'original
- Est soumis aux contraintes de licence de l'original
Cette fonctionnalité crée **l'arbre généalogique** d'un son — une des identités fortes de Veza.
---
## 7. Curation et qualité
### 7.1 Curation éditoriale (humaine)
Toi (fondateur) ou des modérateurs désignés font une **sélection hebdomadaire** :
- **"Pack de la semaine"** (1 pack mis en avant, home + newsletter)
- **"Découvertes"** (3-5 packs sélectionnés, moins vus mais qualitatifs)
- **"Best of genre"** (par mois, top 3 par genre)
### 7.2 Signalements
Un bouton "Signaler" permet de remonter :
- Contenu volé (re-upload non-attribué)
- Violation de licence
- Contenu inapproprié
- Qualité décevante (mal étiqueté, pas ce qu'annonce la description)
### 7.3 Pas d'algo de recommandation IA en V1
La recommandation est **humaine** (curation) + **mécanique simple** (récents, populaires, tags similaires). Pas de moteur ML.
---
## 8. Règles de la maison
### 8.1 Interdit
1. **Samples volés** — pas de re-upload de Splice, Loopcloud, Spitfire, KSHMR, etc.
2. **Samples sous droits** — pas de découpe d'un morceau commercial ("sample de drake")
3. **Dumps sans curation** — pas de "500 samples random" sans description
4. **Multi-compte** pour booster les likes
5. **Packs trompeurs** — titre et contenu doivent correspondre
### 8.2 Toléré mais encadré
- **Field recordings de lieux publics** : OK sauf conversations privées identifiables
- **Samples de synthés hardware** : OK, mentionner le synthé
- **Packs inspirés d'un artiste** : OK tant que ce n'est pas un re-sampling direct
- **Re-upload de ses propres vieux packs** d'autres plateformes : OK
### 8.3 Sanctions
| Infraction | Sanction V1 |
|-----------|-------------|
| Pack clairement volé | Suppression immédiate + avertissement |
| Récidive | Suppression des 3 derniers packs + restriction upload 30 jours |
| 3e récidive | Exclusion |
| Multi-compte | Fusion des comptes ou exclusion |
---
## 9. Interface utilisateur — écrans
### 9.1 Écran "Dépôt samples" (flux général)
- Feed vertical type Instagram
- Chaque carte : titre, créateur, type, tags, preview audio inline, likes, téléchargements
- Filtres en sidebar (desktop) ou modale (mobile)
- Bouton flottant "Partager un pack"
### 9.2 Écran "Fiche pack"
- Metadata complet
- Player audio intégré (preview)
- Liste des fichiers (nom + durée + format)
- Bouton téléchargement
- Arbre des remix (si applicable)
- Section commentaires
### 9.3 Écran "Mes packs"
- Mes uploads (éditables, analytiques : vues, téléchargements, likes)
- Mes téléchargements (historique)
- Mes likes
### 9.4 Section "Packs" sur un profil artiste
- Liste chronologique des uploads de l'artiste
- Statistiques cumulées (nb packs, téléchargements totaux, likes totaux)
- Filtre par type
---
## 10. Métriques de succès
| Métrique | Objectif phase beta (juin) | Objectif fin 2026 |
|----------|---------------------------|-------------------|
| Packs publiés | 30+ | 300+ |
| Packs téléchargés (cumul) | 100+ | 3 000+ |
| Ratio upload / membre | 60% des fondateurs | 30% des membres |
| Téléchargements / pack médian | 5+ | 10+ |
| Packs "remixés" | 3+ | 50+ |
| Packs signalés | 0-2 | < 1% des packs |
---
## 11. Évolutions possibles (V2+)
### Ce qu'on peut ajouter si ça marche
- **Tips libres** : donner 1-5 EUR à un créateur (Liberapay ou crypto)
- **Packs "éphémères"** disponibles 7 jours uniquement
- **Collaborations de packs** (3 artistes font un pack ensemble)
- **Metadata enrichies** : tempo detection auto, key detection auto, analyse spectrale
- **Preview spectrogramme** visuel
- **Compatibilité DAW** (import direct dans Ableton/FL via plugin Veza)
- **"Challenges pack"** : tout le monde fait un pack sur le même thème
### Ce qu'on ne fera pas (même en V2)
- Packs premium payants
- Abonnement type Splice
- Royalties algorithmiques
- Fingerprinting anti-piratage
- Embed commercial (Veza reste non-commercial pour ce module)
---
## 12. Interactions avec les autres modules
| Module | Interaction |
|--------|-------------|
| **Troc** | Un artiste peut proposer "un pack custom" contre un service |
| **Challenges hebdo** | Les résultats de certains challenges peuvent devenir des packs |
| **Profil artiste** | Packs visibles dans l'onglet "Samples" du profil |
| **Morceaux** | Un morceau peut créditer les packs Veza utilisés |
| **Chat/Groupes** | Un pack peut être partagé dans un groupe |
---
## Voir aussi
- [[STRATEGIE_LANCEMENT_COMMUNAUTAIRE]] — les Talas Starter Packs à préparer
- [[CHALLENGES_HEBDO_FONDATEURS]] — Challenge #1 génère du sample matière
- [[03_APPS_&_SERVICES/Community/Partage/SPEC_TECHNIQUE_PARTAGE]] — implémentation backend
- [[REGLES_MODERATION]] — règles générales Veza
- [[Plateforme_Echange/SPEC_TROC_COMMUNAUTAIRE]] — autre module communautaire

View file

@ -0,0 +1,276 @@
# Spec produit — Troc communautaire Veza
> Spécification fonctionnelle du système de troc entre artistes sur Veza.
> Un différenciant unique de la plateforme : échanger des services et du matériel sans argent.
> Dernière mise à jour : avril 2026.
---
## 1. Pourquoi le troc
Sur les autres plateformes (SoundCloud, Discord, Reddit), les artistes s'écoutent mais n'échangent rien. Ils sont en position de **consommation mutuelle**, pas de **collaboration**.
Le troc change ça : chaque artiste a quelque chose à offrir (un skill, un sample pack, une heure de mix, un beat) et quelque chose à demander. Le troc force l'interaction humaine, crée des relations durables et donne une **raison concrète** d'être sur Veza.
C'est aussi un pied de nez à la logique marchande : sur Veza, on peut avancer sans argent.
---
## 2. Principe général
### 2.1 Un tableau d'annonces, pas un marketplace
Veza n'est pas Vinted. Le troc n'est pas un e-commerce déguisé. C'est un **tableau d'annonces** où chaque artiste publie :
- ce qu'il **offre** (son skill, son temps, un objet)
- ce qu'il **cherche** en échange
Les deux parties discutent, tombent d'accord, et se débrouillent. Veza **n'arbitre pas l'échange**. Veza fournit :
- la visibilité de l'annonce
- un système de messagerie privée
- un système de réputation léger
### 2.2 Pas de crédits (décision actuelle)
**Décision** : pas de système de crédits communautaires dans la V1.
Pourquoi : un système de crédits introduit de la complexité (comptabilité interne, émission, inflation, fraude). Il peut attendre la V2 si la communauté le demande.
La V1 = troc direct 1-pour-1 ou N-pour-1 (un artiste peut avoir plusieurs trocs en parallèle).
### 2.3 Catégories d'annonces
| Catégorie | Exemples d'offres | Exemples de demandes |
|-----------|-------------------|----------------------|
| **Services audio** | Mix, mastering, enregistrement, prise de son | Idem |
| **Création visuelle** | Cover art, logo, photo, vidéo | Idem |
| **Production musicale** | Beat, topline, arrangement, collab | Idem |
| **Formation / coaching** | 1h de coaching DAW, review de mix | Idem |
| **Matériel** | Micro inutilisé, carte son, câbles | Idem |
| **Samples / presets** | Pack custom fait main | Idem (mais → plutôt dans le dépôt samples) |
| **Autre** | Tout ce qui ne rentre pas | |
---
## 3. Anatomie d'une annonce
### 3.1 Champs obligatoires
| Champ | Description | Contrainte |
|-------|-------------|-----------|
| **Titre** | Résume l'échange en une phrase | 80 caractères max |
| **J'offre** | Ce que propose l'artiste | 500 caractères |
| **Je cherche** | Ce qu'il veut en échange | 500 caractères |
| **Catégorie** | Une des 7 catégories ci-dessus | Liste fermée |
| **Délai de réalisation** | En combien de temps l'artiste peut livrer | 1 jour à 1 mois |
| **Statut** | `ouvert`, `en discussion`, `conclu`, `annulé` | |
### 3.2 Champs optionnels
- **Exemples de son travail** (liens vers ses uploads Veza, ou fichiers joints)
- **Contraintes** ("pas de death metal", "uniquement en FR", etc.)
- **Localisation** (si besoin de rencontre IRL)
### 3.3 Exemple concret
```
┌─────────────────────────────────────────────────────────────┐
│ Titre : Beat hip-hop contre mix d'un EP 4 titres │
│ Catégorie : Production musicale ↔ Services audio │
│ │
│ J'offre : Un beat original (90-100 BPM, sombre, mélodique), │
│ exclusif, avec stems. Je produis depuis 3 ans, quelques │
│ exemples sur mon profil Veza. │
│ │
│ Je cherche : Le mix complet d'un EP de 4 titres rap (pas │
│ mastering). Je fournis les stems propres. Révisions │
│ incluses raisonnables. │
│ │
│ Délai de ma part : 1 semaine │
│ Statut : ouvert · 3 personnes intéressées │
└─────────────────────────────────────────────────────────────┘
```
---
## 4. Flux de troc — du post au closing
### 4.1 Étape 1 : publication de l'annonce
1. L'artiste clique sur "Nouvelle annonce" dans la section Troc
2. Remplit le formulaire (obligatoires + optionnels)
3. Preview avant publication
4. L'annonce apparaît dans le flux général ET dans le groupe correspondant à sa catégorie
### 4.2 Étape 2 : intérêt d'un autre artiste
1. Un autre artiste voit l'annonce
2. Il clique sur "Je suis intéressé" → un DM s'ouvre avec l'annonceur
3. Le message inclut automatiquement un rappel du contexte de l'annonce
4. Les deux discutent en privé
### 4.3 Étape 3 : discussion et accord
- Les deux se mettent d'accord via DM
- Ils définissent leurs attentes précises
- Ils se donnent une deadline (souple)
- L'annonceur passe l'annonce en statut `en discussion`
### 4.4 Étape 4 : réalisation
- Chacun fait son travail de son côté
- Échanges de fichiers via DM Veza (attachments)
- Révisions si besoin
### 4.5 Étape 5 : closing
- Une fois les deux parties satisfaites, l'annonceur passe le statut en `conclu`
- **Système de reconnaissance** : chaque partie peut laisser une réaction simple à l'autre (voir §5 Réputation)
- L'annonce reste visible en archive avec badge "conclu"
### 4.6 Étape d'annulation (n'importe quand)
- L'une des deux peut annuler sans justification
- L'annonce repasse à `ouvert` ou à `annulé`
- Pas de pénalité automatique (la répétition éveille la modération — voir §5)
---
## 5. Système de réputation
### 5.1 Philosophie
**Pas de notation 5 étoiles.** On n'est pas Uber.
On utilise un système de **reconnaissance mutuelle simple** : après un troc conclu, chaque partie choisit une ou plusieurs qualités à attribuer à l'autre :
### 5.2 Les 6 reconnaissances
| Badge | Signification |
|-------|---------------|
| 🤝 **Fiable** | A livré ce qui était promis |
| ⏱️ **Ponctuel** | A respecté les délais |
| 💡 **Inventif** | A apporté plus que prévu |
| 🧘 **Patient** | Bonne communication, gestion des révisions |
| 🎯 **Précis** | Travail technique de qualité |
| 🤗 **Chaleureux** | Échange humain agréable |
Chaque artiste accumule ces reconnaissances sur son profil (visible publiquement).
### 5.3 Pas de notes négatives publiques
Il n'y a pas de "mauvaise note" publique. À la place :
- Après un échange raté, chacun peut signaler **en privé à la modération** (toi)
- La modération observe les patterns (répétition = problème)
- En cas de problème répété avéré, l'artiste est contacté puis éventuellement exclu
### 5.4 Premier troc d'un nouveau membre
Les nouveaux arrivants ont un **badge "nouveau"** (visible 30 jours) pour que les autres soient un peu plus indulgents/curieux. Pas un badge "débutant" — c'est un **badge d'accueil**.
---
## 6. Règles de la maison (modération)
### 6.1 Interdit
1. **Argent** — Aucun troc n'implique d'argent. Pas de "je fais ton mix contre 100€". Pour ça, il y a une freelance.
2. **Contenu sous copyright** — Pas de samples volés, pas de beats volés, pas de plug-ins crackés.
3. **Services inappropriés** — Pas de troc à caractère sexuel, raciste, violent.
4. **Multi-compte** — Un seul compte par personne. Pas de faux comptes pour boost ses reconnaissances.
5. **Démarchage massif** — Si tu postes 10 annonces identiques, elles sont toutes supprimées.
### 6.2 Toléré mais modéré
- **Annonces "je cherche un feedback"** — OK, mais incite plutôt à utiliser les groupes thématiques
- **Annonces IRL** (rencontre physique) — OK, à la charge des deux parties d'évaluer le risque
- **Trocs mixtes** (part service + part matériel) — OK tant que c'est clair
### 6.3 Sanctions
| Infraction | Sanction V1 |
|-----------|-------------|
| Spam d'annonces | Suppression + avertissement privé |
| Contenu volé | Suppression + avertissement |
| Répétition (2e fois) | Restriction de poster des annonces 30 jours |
| Répétition (3e fois) | Exclusion de la plateforme |
| Comportement toxique en DM | Exclusion immédiate après vérification |
---
## 7. Interface utilisateur — écrans
### 7.1 Écran "Troc" (flux général)
- Feed des annonces ouvertes
- Filtres : catégorie, délai, statut
- Tri : récent / populaire (nb de "Je suis intéressé")
- Bouton flottant "Nouvelle annonce"
### 7.2 Écran "Nouvelle annonce"
- Formulaire guidé (étape par étape pour aider les nouveaux)
- Preview avant publication
- Astuces contextuelles ("bon titre = verbe d'action")
### 7.3 Écran "Mes annonces"
- Mes annonces actives (éditables)
- Mes annonces conclues (archive, avec reconnaissances reçues)
- Mes annonces annulées
### 7.4 Onglet "Troc" sur un profil artiste
- Badge "a fait X trocs" (nombre total conclus)
- Badges de reconnaissance accumulés
- Annonces actives
- Historique (public, simple)
---
## 8. Métriques de succès
| Métrique | Objectif fin 2026 | Seuil d'alerte |
|----------|-------------------|----------------|
| Annonces publiées (cumul) | 200+ | < 50 |
| Annonces conclues (ratio) | > 40% des annonces | < 20% |
| Temps moyen de closing | < 3 semaines | > 6 semaines |
| Taux de réponse aux DMs | > 60% | < 30% |
| Signalements modération / mois | < 3 | > 10 |
| Trocs mentionnés spontanément sur RS | Quelques / mois | 0 |
---
## 9. Ce qu'on ne fera PAS en V1
- Système de crédits / monnaie interne
- Escrow ou séquestre de fichiers
- Arbitrage automatique par la plateforme
- Notation 1-5 étoiles
- Algo de matching automatique (mise en relation IA)
- Annonces sponsorisées / mises en avant payantes
- Marketplace de vraies ventes (argent réel)
Ces features sont des pièges qui transformeraient le troc en mini-Vinted. On reste simple.
---
## 10. Évolutions possibles (V2+)
- **Crédits communautaires** : si la demande émerge naturellement
- **Trocs de groupe** (3+ personnes en cercle)
- **Trocs "ouverts"** (je propose X, qui fait quoi en échange ?)
- **Templates d'annonce** pour les échanges récurrents
- **Histoire visible** d'un troc (timeline des étapes)
- **Intégration calendrier** pour les trocs ponctuels (sessions de coaching)
---
## Voir aussi
- [[STRATEGIE_LANCEMENT_COMMUNAUTAIRE]] — contexte stratégique, phases de lancement
- [[REGLES_MODERATION]] — règles générales de la maison Veza
- [[03_APPS_&_SERVICES/Community/Troc/SPEC_TECHNIQUE_TROC]] — implémentation backend
- [[05_EXPERIENCE_UTILISATEUR/CONCEPTS_INNOVANTS_VEZA]] — autres features uniques de Veza
- [[06_COMMUNAUTE_ECOSYSTEME/GUIDE_ONBOARDING_FONDATEUR]] — comment présenter le troc aux fondateurs

View file

@ -0,0 +1,304 @@
# Spec produit — Événements participatifs Veza
> Spécification des formats événementiels créés par Talas et/ou portés par la communauté.
> Les événements sont le rythme cardiaque de la communauté.
> Dernière mise à jour : avril 2026.
---
## 1. Pourquoi des événements
Une plateforme sans rythme est un musée. Les événements créent :
- **Un rythme** — les membres reviennent parce qu'il se passe quelque chose
- **Des pics d'activité** — le contenu se concentre, la visibilité est max
- **Des moments mémorables** — un event raté ou réussi devient une histoire qu'on raconte
- **Un ancrage temporel** — "j'étais là au premier TalasLive"
Sans événements, Veza serait un feed infini comme les autres. Avec, c'est un lieu qui **vit par saisons**.
---
## 2. Typologie des événements
### 2.1 Les 5 formats
| Format | Fréquence | Durée | Qui organise |
|--------|-----------|-------|--------------|
| **Challenge hebdo** | 1×/semaine | 7 jours | Talas (équipe fondatrice) |
| **TalasLive** | 1×/mois | 1-2h | Talas, en direct |
| **Jam en ligne** | 1×/2 mois | 2-4h | Talas ou membre volontaire |
| **Concours thématique** | 2-3×/an | 2-4 semaines | Talas, parfois avec partenaire |
| **IRL local** | Ponctuel | Variable | Membre (Talas soutient) |
---
## 3. Challenge hebdomadaire
*(Voir [[CHALLENGES_HEBDO_FONDATEURS]] pour le détail des 6 premiers challenges.)*
### 3.1 Format standard
- **Annoncé** : lundi matin dans le groupe #partage
- **Deadline** : dimanche soir
- **Règles** : contrainte créative claire (outils, durée, thème)
- **Soumissions** : upload dans #partage avec tag du challenge
- **Récompense** : zéro matérielle, partage RS du meilleur
### 3.2 Rythme et variété
Les challenges alternent les formats pour ne pas lasser :
| Type de challenge | Exemples |
|-------------------|----------|
| **Contrainte d'outils** | "Fais un beat sans VST" · "Utilise uniquement des samples de chez toi" |
| **Contrainte de temps** | "30 min chrono" · "Un beat par jour pendant 7 jours" |
| **Contrainte de matière** | "Utilise le Starter Pack #3" · "Remix du morceau de X" |
| **Contrainte de format** | "10 secondes max" · "Boucle infinie" · "Field recording uniquement" |
| **Contrainte collaborative** | "Continue le morceau de quelqu'un" · "Duo imposé" |
### 3.3 Sélection du meilleur
**Règle** : pas de vote communautaire public. C'est subjectif, ça crée des déceptions.
À la place : l'équipe Talas **choisit 3-5 soumissions marquantes** à mettre en avant sur les RS, sans classement. Message type :
> "Voici les 5 soumissions qui m'ont marqué cette semaine — dans le désordre."
---
## 4. TalasLive (mensuel)
### 4.1 Concept
Un **live streaming mensuel** où Talas (toi) :
- Montre l'avancée du projet (hardware + logiciel)
- Répond aux questions de la communauté en direct
- Invite parfois un artiste fondateur à présenter son travail
- Partage une astuce technique ou une anecdote atelier
### 4.2 Format type
- **Durée** : 60 à 90 minutes
- **Plateforme** : streaming Veza direct (WebRTC) OU YouTube en fallback
- **Interaction** : chat en temps réel via Veza
- **Annonce** : 1 semaine avant dans #fondateurs et sur les RS
### 4.3 Structure d'un live (exemple)
```
00:00 — Intro, news du mois
10:00 — Ce qui a été fait sur le micro (photos, schémas)
25:00 — Ce qui a été fait sur Veza (screenshots, démo live)
40:00 — Invité surprise (artiste fondateur présente son setup)
55:00 — Questions du chat
85:00 — Prochaines étapes, teasing du mois suivant
90:00 — Fin
```
### 4.4 Archive
Chaque live est **enregistré** et disponible :
- Sur Veza dans une section "Archives TalasLive"
- Sur le YouTube Talas (avec chapitrage)
---
## 5. Jam en ligne
### 5.1 Concept
Un **créneau synchrone** où les membres travaillent ensemble sur un même thème, en partageant leur progression en temps réel.
### 5.2 Format type
- **Durée** : 2 à 4 heures
- **Thème** : annoncé 48h avant
- **Participation** : inscription préalable (limite 30 participants en V1)
- **Plateforme** : chat Veza + salon vocal (intégré ou Jitsi/Mumble self-hosted)
- **Livrable** : chaque participant upload son résultat dans un thread dédié
### 5.3 Rythme
```
00:00 — Intro, présentation du thème, règles
00:15 — Début de la jam, silence ou bruit ambiant optionnel
01:00 — Premier point check-in (chacun partage son progrès en 30 sec)
02:00 — Deuxième point check-in
02:30 — Deadline soft, finalisation
02:45 — Partage des résultats dans le thread
03:00 — Écoute collective commentée
```
### 5.4 Thèmes possibles
- "Fais une boucle de 8 mesures avec 3 pistes max"
- "Transforme ce field recording en beat"
- "Compose une intro de podcast en 20 sec"
- "Sound design : le son d'un robot qui pleure"
---
## 6. Concours thématique
### 6.1 Concept
Événement plus long et plus ambitieux qu'un challenge hebdo. **2-3 fois par an max** pour préserver la valeur.
### 6.2 Format type
- **Durée** : 2 à 4 semaines
- **Annonce** : 2 semaines avant
- **Thème** : large mais précis ("Le son de ta ville", "Hommage à une chanson oubliée")
- **Format attendu** : morceau audio (1-5 min)
- **Jury** : 3 à 5 personnes (Talas + 2-4 artistes invités)
- **Récompense** : visibilité (feature sur RS, passage dans le TalasLive suivant) + **bon matos** à gagner (1 micro prototype Talas en dotation, ou équivalent)
### 6.3 Critères de jugement (transparents)
| Critère | Poids |
|---------|-------|
| Originalité du parti pris | 30% |
| Qualité de réalisation | 25% |
| Cohérence avec le thème | 20% |
| Émotion / impact | 25% |
### 6.4 Règles anti-compétition toxique
- Pas de classement 1-2-3, juste un "top 3 à ex-æquo"
- Mention encourageante pour 5-10 soumissions au-delà du top 3
- Feedback public constructif sur toutes les soumissions (ou au minimum sur 50%)
- **Aucun perdant** — tout le monde a eu le courage de soumettre
---
## 7. IRL local (Talas Live physique)
### 7.1 Concept
Un **membre de la communauté** organise un événement physique dans sa ville :
- Écoute de morceaux communautaires
- Mini-jam improvisée
- Démo d'un setup ou d'une technique
- Rencontre entre créateurs
**Talas ne les organise pas** — Talas les soutient.
### 7.2 Kit organisateur (fourni par Talas)
- Un **brief d'organisation** (how-to, checklist)
- Des **visuels** à imprimer (affiche, stickers, badges)
- Une **annonce relai** sur les canaux Veza et RS Talas
- Éventuellement un **micro prototype prêté** pour la session
### 7.3 Conditions pour soutenir un IRL
- Gratuit ou participation modique (max 5 EUR)
- Pas de sponsors commerciaux (sauf validés par Talas)
- Respect de la charte Talas (inclusivité, bienveillance)
- Retour de l'organisateur post-event (photos, feedback)
---
## 8. Calendrier-type d'une année
```
JAN │ Challenge hebdo ×4 │ TalasLive │ Jam en ligne (début d'année)
FEV │ Challenge hebdo ×4 │ TalasLive
MAR │ Challenge hebdo ×4 │ TalasLive │ Concours thématique (semaine 2-4)
AVR │ Challenge hebdo ×4 │ TalasLive │ Jam en ligne
MAI │ Challenge hebdo ×4 │ TalasLive
JUN │ Challenge hebdo ×4 │ TalasLive │ Concours "solstice d'été"
JUL │ Challenge hebdo ×4 │ TalasLive │ Jam en ligne
AOU │ Challenge hebdo ×2 │ (pause été partielle)
SEP │ Challenge hebdo ×4 │ TalasLive │ Jam en ligne
OCT │ Challenge hebdo ×4 │ TalasLive │ Concours "Halloween / sound design"
NOV │ Challenge hebdo ×4 │ TalasLive │ Jam en ligne
DEC │ Challenge hebdo ×2 │ TalasLive │ "Best of de l'année" (non-compétitif)
```
**Total année** : ~46 challenges hebdo, 11 TalasLive, 6 jams, 3 concours, IRL variable.
---
## 9. Documentation des événements
### 9.1 Chaque événement a sa page Veza
- Description + règles
- Liste des participants
- Soumissions (pour challenges/concours)
- Archive (replays, photos, résultats)
- Page indexée dans la section "Événements"
### 9.2 Archives permanentes
Les événements passés restent **accessibles pour toujours**. C'est le patrimoine de la communauté.
### 9.3 Bilan après chaque événement
Court message de bilan dans #fondateurs :
- Nombre de participants / soumissions
- Ce qui a bien marché
- Ce qui a moins bien marché
- Une anecdote marquante
---
## 10. Interface utilisateur — écrans
### 10.1 Écran "Événements"
- **À venir** : prochains événements (dates, inscriptions)
- **En cours** : événements actifs avec CTA (participer, soumettre)
- **Archive** : événements passés avec résumé + replays
### 10.2 Écran "Fiche événement"
- Titre, type, dates, règles
- Liste/grille des soumissions (pour challenges/concours)
- Bouton "Participer" / "Soumettre"
- Fil de discussion autour de l'événement
### 10.3 Widget "Événement du moment" sur la home
Sur la page d'accueil Veza, un widget met en avant :
- Le challenge de la semaine en cours
- Le prochain TalasLive (si < 7 jours)
- Un concours si actif
---
## 11. Métriques de succès
| Métrique | Objectif phase beta | Objectif fin 2026 |
|----------|---------------------|-------------------|
| Taux de participation challenge hebdo | 20% des actifs | 15% des actifs |
| Inscrits TalasLive live | 20+ / session | 50+ / session |
| Replays TalasLive vus | 50+ / mois | 200+ / mois |
| Jam : participants | 10-20 | 25-40 |
| Concours : soumissions | 15+ | 50+ |
| IRL organisés par membres | 0 | 3-5 dans l'année |
---
## 12. Risques et garde-fous
| Risque | Garde-fou |
|--------|-----------|
| Participation décroissante aux challenges | Varier les formats (§3.2) |
| Jams peu peuplées | Limiter à 1×/2 mois, bien préparer |
| Concours crée des jalousies | Pas de classement serré (§6.4) |
| TalasLive usant pour le fondateur | Raccourcir si besoin, pré-enregistrer certaines parties |
| IRL : risques juridiques/sécurité | Charte claire, pas de sponsoring Talas en V1 |
---
## Voir aussi
- [[CHALLENGES_HEBDO_FONDATEURS]] — les 6 premiers challenges détaillés
- [[STRATEGIE_LANCEMENT_COMMUNAUTAIRE]] — rôle des événements dans le lancement
- [[07_CONTENUS_MARKETING/STRATEGIE_CONTENU]] — comment relayer les événements sur RS
- [[REGLES_MODERATION]] — règles comportementales pendant les événements
- [[Artistes_&_Ambassadeurs/README]] — rôle des ambassadeurs dans les événements

File diff suppressed because it is too large Load diff

View file

@ -5,8 +5,12 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{template "title" .}}</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&family=Space+Grotesk:wght@700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/static/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/nord.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/github.min.css" media="(prefers-color-scheme: light), (prefers-color-scheme: no-preference)">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/github-dark.min.css" media="(prefers-color-scheme: dark)">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.css">
<link rel="alternate" type="application/rss+xml" title="Talas Wiki RSS" href="/rss">
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml">
@ -68,7 +72,19 @@
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"></script>
<script>hljs.highlightAll();</script>
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
<script>mermaid.initialize({theme:'dark',themeVariables:{primaryColor:'#a3be8c',primaryTextColor:'#eceff4',lineColor:'#88c0d0',secondaryColor:'#2e3440',tertiaryColor:'#121212'}});</script>
<script>
const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
mermaid.initialize({
theme: 'base',
themeVariables: {
primaryColor: isDark ? '#161618' : '#EAE6DF',
primaryTextColor: isDark ? '#E8E3DB' : '#1A1A1E',
lineColor: isDark ? '#0098B5' : '#006B7F',
secondaryColor: isDark ? '#3A352D' : '#D1CFC9',
tertiaryColor: isDark ? '#0D0D0F' : '#F2EDE6'
}
});
</script>
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/contrib/auto-render.min.js"></script>
<script>document.addEventListener('DOMContentLoaded',function(){renderMathInElement(document.body,{delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false}]})});</script>