filling
This commit is contained in:
parent
66471934af
commit
ef71d70b44
30 changed files with 8309 additions and 684 deletions
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
464
03_APPS_&_SERVICES/Auth_&_Core/SPEC_AUTH_CORE.md
Normal file
464
03_APPS_&_SERVICES/Auth_&_Core/SPEC_AUTH_CORE.md
Normal 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
|
||||||
|
|
@ -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
|
||||||
447
03_APPS_&_SERVICES/Community/Partage/SPEC_TECHNIQUE_PARTAGE.md
Normal file
447
03_APPS_&_SERVICES/Community/Partage/SPEC_TECHNIQUE_PARTAGE.md
Normal 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
|
||||||
427
03_APPS_&_SERVICES/Community/Troc/SPEC_TECHNIQUE_TROC.md
Normal file
427
03_APPS_&_SERVICES/Community/Troc/SPEC_TECHNIQUE_TROC.md
Normal 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é
|
||||||
437
03_APPS_&_SERVICES/Personal/Cloud_Perso/SPEC_CLOUD_PERSO.md
Normal file
437
03_APPS_&_SERVICES/Personal/Cloud_Perso/SPEC_CLOUD_PERSO.md
Normal 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
|
||||||
442
03_APPS_&_SERVICES/Shop/Backend/SPEC_BACKEND_SHOP.md
Normal file
442
03_APPS_&_SERVICES/Shop/Backend/SPEC_BACKEND_SHOP.md
Normal 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
|
||||||
367
03_APPS_&_SERVICES/Shop/Frontend/SPEC_FRONTEND_SHOP.md
Normal file
367
03_APPS_&_SERVICES/Shop/Frontend/SPEC_FRONTEND_SHOP.md
Normal 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
|
||||||
404
03_APPS_&_SERVICES/Shop/Paiement/SPEC_PAIEMENT.md
Normal file
404
03_APPS_&_SERVICES/Shop/Paiement/SPEC_PAIEMENT.md
Normal 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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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`
|
||||||
|
|
@ -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`
|
||||||
|
|
@ -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`
|
||||||
|
|
@ -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`
|
||||||
|
|
@ -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`
|
||||||
|
|
@ -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`
|
||||||
|
|
@ -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`
|
||||||
|
|
@ -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`
|
||||||
|
|
@ -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`
|
||||||
|
|
@ -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`
|
||||||
|
|
@ -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`
|
||||||
|
|
@ -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`
|
||||||
|
|
@ -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`
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
|
@ -5,8 +5,12 @@
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{{template "title" .}}</title>
|
<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="/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="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="alternate" type="application/rss+xml" title="Talas Wiki RSS" href="/rss">
|
||||||
<link rel="icon" href="/static/favicon.svg" type="image/svg+xml">
|
<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 src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js"></script>
|
||||||
<script>hljs.highlightAll();</script>
|
<script>hljs.highlightAll();</script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></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/katex.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/contrib/auto-render.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>
|
<script>document.addEventListener('DOMContentLoaded',function(){renderMathInElement(document.body,{delimiters:[{left:'$$',right:'$$',display:true},{left:'$',right:'$',display:false}]})});</script>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue