# SOGOMS - Documentation Technique

**Service Oriented GO MicroServices** - Plateforme SaaS modulaire multi-tenant.

Version: 1.0.5
Date: 22 décembre 2025

---

## Table des matières

1. [Architecture](#architecture)
2. [Services](#services)
3. [Communication IPC](#communication-ipc)
4. [Configuration](#configuration)
5. [API REST](#api-rest)
6. [Système de Queries](#système-de-queries)
7. [Authentification](#authentification)
8. [Déploiement](#déploiement)
9. [Structure du projet](#structure-du-projet)

---

## Architecture

```
Client → Nginx(:443) → Sogoway(:8080) → Sogoms-* → MariaDB
                              ↓
                        Unix Sockets
                     /run/sogoms-*.sock
```

### Flux de données

1. **Client** envoie requête HTTPS vers Nginx
2. **Nginx** route `/api/*` vers sogoway:8080
3. **Sogoway** identifie l'app par hostname, valide JWT, route vers le bon service
4. **Sogoms-db** exécute les requêtes SQL
5. **Sogoms-logs** enregistre événements et erreurs

### Principes

- **Modularité** : 1 feature = 1 binaire Go
- **Configuration YAML** : requêtes SQL externalisées, pas de recompilation
- **Multi-tenant** : isolation par `user_id`, filtrage configurable par rôle
- **Supervision** : sogoctl gère le cycle de vie des services

---

## Services

| Binaire | Rôle | Port/Socket |
|---------|------|-------------|
| `sogoctl` | Superviseur PID 1, health checks, restart auto | - |
| `sogoway` | Gateway HTTP, auth JWT, routing CRUD | TCP :8080 |
| `sogoms-db` | Accès MariaDB, pool par application | `/run/sogoms-db.1.sock` |
| `sogoms-logs` | Logging centralisé, rotation auto | `/run/sogoms-logs.1.sock` |
| `sogoms-smtp` | Envoi d'emails, templates YAML | `/run/sogoms-smtp.1.sock` |
| `sogoms-cron` | Tâches planifiées, jobs périodiques | `/run/sogoms-cron.1.sock` |
| `sogoms-admin` | Interface web d'administration | TCP :9000 |

### sogoctl (Superviseur)

- Démarre les services dans l'ordre des dépendances
- Health checks périodiques (socket ou HTTP)
- Redémarrage automatique en cas de crash
- Arrêt gracieux sur SIGTERM/SIGINT
- **Socket de contrôle** `/run/sogoctl.sock` pour commandes runtime :
  - `reload <service>` : envoie SIGHUP au service (rechargement config)
  - `status` : affiche l'état des services

```yaml
# config/sogoctl.yaml
supervisor:
  health_interval: 10s
  restart_delay: 2s
  max_restarts: 5

services:
  sogoms-logs:
    binary: /opt/sogoms/bin/sogoms-logs
    args: ["-config", "/config", "-socket", "/run/sogoms-logs.1.sock"]
    health_socket: /run/sogoms-logs.1.sock

  sogoms-db:
    binary: /opt/sogoms/bin/sogoms-db
    args: ["-config", "/config", "-socket", "/run/sogoms-db.1.sock"]
    health_socket: /run/sogoms-db.1.sock
    depends_on: [sogoms-logs]

  sogoway:
    binary: /opt/sogoms/bin/sogoway
    args: ["-config", "/config", "-port", "8080"]
    health_url: http://localhost:8080/health
    depends_on: [sogoms-db, sogoms-logs]
```

### sogoway (Gateway HTTP)

- Routing par hostname → charge la config de l'app
- Authentification JWT (HS256)
- CRUD générique paramétré par YAML
- Logging des événements (login, register)
- **Rechargement à chaud** : SIGHUP recharge registry + JWT sans restart

### sogoms-db (Base de données)

Actions disponibles :
- `query` : SELECT multi-résultats
- `query_one` : SELECT un résultat
- `insert` : INSERT, retourne `insert_id`
- `update` : UPDATE, retourne `affected_rows`
- `delete` : DELETE, retourne `affected_rows`
- `health` : ping DB

### sogoms-logs (Logging)

Actions disponibles :
- `log_event` : événement applicatif (login, register, etc.)
- `log_error` : erreur avec niveau (error, warn, info)
- `health` : statut OK

Fichiers générés : `/var/log/sogoms/{app}-{YYYYMMDD}-{type}.log`

Format : JSON, une ligne par entrée

Rotation : suppression automatique des fichiers > 30 jours (configurable)

### sogoms-smtp (Emails)

Actions disponibles :
- `send` : envoi email simple (to, subject, body, body_html)
- `send_template` : envoi avec template YAML
- `send_bulk` : envoi en masse (tableau de destinataires)
- `health` : statut OK

Configuration SMTP dans `config/routes/{app}.yaml` :
```yaml
smtp:
  host: mail.example.com
  port: 587
  user: noreply@example.com
  password_file: /secrets/{app}_smtp_pass
  from: noreply@example.com
  from_name: Mon App
  tls: false  # false = STARTTLS (587), true = TLS (465)
```

Templates dans `config/emails/{app}/*.yaml` :
```yaml
subject: "Bienvenue {{.Name}} !"
body: |
  Bonjour {{.Name}},
  Bienvenue sur notre plateforme.
body_html: |
  <h1>Bienvenue {{.Name}} !</h1>
  <p>Bienvenue sur notre plateforme.</p>
```

### sogoms-cron (Tâches planifiées)

Exécute des jobs périodiques définis en YAML avec support cron standard.

Actions disponibles :
- `list` : liste les jobs configurés avec prochain run
- `trigger` : déclenche un job manuellement
- `status` : historique des dernières exécutions
- `health` : statut OK

Types de jobs :
- `query_email` : requête DB + envoi email groupé par utilisateur
- `http` : appel HTTP (GET/POST) vers endpoint interne ou externe
- `service` : appel service interne (sogoms-db, sogoms-smtp, etc.)

Configuration dans `config/apps/{app}/cron.yaml` :
```yaml
timezone: Europe/Paris
retry:
  max_attempts: 3
  delay: 5m
history_days: 7

jobs:
  tasks_today:
    schedule: "0 8 * * 1-5"    # 8h00 lun-ven
    type: query_email
    query: |
      SELECT u.id AS user_id, u.email, u.name AS user_name,
             t.title, p.name AS project_name, s.name AS status_name
      FROM users u
      INNER JOIN tasks t ON t.user_id = u.id
      LEFT JOIN projects p ON t.project_id = p.id
      LEFT JOIN statuses s ON t.status_id = s.id
      WHERE t.date_end <= CURDATE()
    group_by: user_id
    template: tasks_today
    enabled: true
```

Format cron : `minute heure jour mois jour_semaine`
- `0 8 * * 1-5` : 8h00 du lundi au vendredi
- `*/15 * * * *` : toutes les 15 minutes
- `0 9 1 * *` : 9h00 le premier de chaque mois

### sogoms-admin (Interface web)

Interface d'administration web pour gérer les applications SOGOMS.

**Rôles :**
- `super_admin` : accès global à toutes les apps et services
- `app_admin` : accès limité aux apps assignées avec permissions fines

**Stack :**
- Backend : Go net/http
- Frontend : Go templates + htmx + Pico.css (embarqués via go:embed)
- Auth : sessions cookies signées (HMAC-SHA256)

**Sécurité :**
- Passwords : bcrypt cost=12
- Sessions : Cookie HttpOnly + Secure + SameSite=Strict
- CSRF : Token par session
- Rate limiting : 5 tentatives/min par IP
- **2FA obligatoire** : TOTP (Google Authenticator, Authy) + codes de secours

**Authentification à deux facteurs (2FA) :**

Le 2FA est obligatoire pour les rôles configurés dans `required_roles`.

Flux de connexion avec 2FA :
```
Login (password) → Session pending → /admin/2fa/verify → Session complète → Dashboard
                                   ↓
                            Code invalide → Retry (rate limited)
```

Première connexion (2FA requis mais pas configuré) :
```
Login (password) → /admin/2fa/setup → Scanner QR + sauvegarder codes → Dashboard
```

**Routes :**
- `GET /admin/login` : page de connexion
- `POST /admin/login` : authentification
- `GET /admin/` : dashboard principal
- `POST /admin/logout` : déconnexion
- `GET /admin/apps/{app}` : détail application
- `POST /admin/apps/{app}/scan` : scan DB et génération schema
- `GET /admin/api/apps` : liste apps (htmx partial)
- `GET /admin/api/services/health` : statut services (htmx partial)
- `GET /admin/2fa/verify` : page saisie code TOTP
- `POST /admin/2fa/verify` : validation code TOTP ou backup
- `GET /admin/2fa/setup` : page activation 2FA (QR code)
- `POST /admin/2fa/setup` : confirmation activation 2FA
- `POST /admin/2fa/disable` : désactivation 2FA
- `GET /admin/security` : page paramètres sécurité

**Scan DB et génération automatique :**

Le bouton "Scanner la base" sur la page détail d'une app :
1. Introspection de la DB via `INFORMATION_SCHEMA`
2. Génération de `schema.yaml` (tables, colonnes, types, contraintes)
3. Génération automatique de `login_data` dans `queries/auth.yaml`
4. Rechargement du registry et de sogoway (SIGHUP)

Les tables avec colonne `user_id` reçoivent automatiquement :
- `filter: owner` pour filtrage par utilisateur
- CRUD activé (list, show, create, update, delete)
- Requête SELECT dans `login_data` pour le login enrichi

**Configuration :**
```yaml
# /secrets/admin_users.yaml
session:
  secret_file: /secrets/admin_session_secret
  max_age: 3600
  cookie_name: sogoms_admin_sid

rate_limit:
  login_max: 5
  login_window: 60

two_fa:
  enabled: true
  issuer_name: "SOGOMS Admin"
  required_roles:
    - super_admin    # 2FA obligatoire pour ce rôle

users:
  - username: pierre
    password_hash: "$2a$12$..."
    role: super_admin
    email: pierre@example.com
    two_fa_enabled: true
    two_fa_secret: "BASE32SECRET..."      # généré lors du setup
    backup_codes:                          # bcrypt hashed
      - "$2a$10$..."
      - "$2a$10$..."

  - username: client1
    password_hash: "$2a$12$..."
    role: app_admin
    apps: [prokov]
    permissions:
      - schema:read
      - queries:read
      - cron:read
      - logs:read
```

**Permissions disponibles :**
- `schema:read/write/upload` : gestion schema
- `queries:read/write` : requêtes SQL
- `emails:read/write` : templates email
- `cron:read/write/trigger` : jobs cron
- `logs:read` : consultation logs
- `db:introspect` : introspection DB
- `*` : toutes (super_admin)

**Accès externe :** `admin.sogoms.com` via Nginx → :9000

---

## Communication IPC

Protocole Unix socket avec messages JSON length-prefixed :

```
[4 bytes: longueur BigEndian] [payload JSON]
```

### Request

```json
{
  "id": "req_20251216123456.000000",
  "action": "query",
  "params": {
    "app_id": "prokov",
    "query": "SELECT * FROM users WHERE id = ?",
    "args": [1]
  }
}
```

### Response (succès)

```json
{
  "id": "req_...",
  "status": "success",
  "result": { ... }
}
```

### Response (erreur)

```json
{
  "id": "req_...",
  "status": "error",
  "error": {
    "code": "DB_ERROR",
    "message": "connection refused"
  }
}
```

---

## Configuration

### Structure

```
config/
├── sogoctl.yaml              # Superviseur
├── routes/
│   └── prokov.yaml           # Config app (DB, auth, routes)
└── queries/
    └── prokov/
        ├── auth.yaml         # Requêtes authentification
        ├── projects.yaml     # CRUD projects
        ├── tasks.yaml        # CRUD tasks
        ├── tags.yaml         # CRUD tags
        └── statuses.yaml     # CRUD statuses
```

### Config application (routes/prokov.yaml)

```yaml
app: prokov
version: "1.0"
base_path: /api

hosts:
  - prokov.unikoffice.com
  - prokov.sogoms.com

database:
  host: 13.23.33.4
  port: 3306
  user: prokov_user
  password_file: /secrets/prokov_db_pass
  name: prokov

auth:
  jwt_secret_file: /secrets/prokov_jwt_secret
  jwt_expiry: 24h

logs:
  retention_days: 30
```

---

## API REST

### Authentification

| Méthode | Endpoint | Auth | Description |
|---------|----------|------|-------------|
| POST | `/api/auth/register` | Non | Inscription |
| POST | `/api/auth/login` | Non | Connexion |
| POST | `/api/auth/logout` | Oui | Déconnexion |
| GET | `/api/auth/me` | Oui | User courant |

### Login - Réponse enrichie

```json
{
  "success": true,
  "message": "Connexion réussie",
  "data": {
    "token": "eyJ...",
    "user": { "id": 1, "email": "...", "name": "..." },
    "projects": [...],
    "tasks": [...],
    "tags": [...],
    "statuses": [...]
  }
}
```

### CRUD Générique

| Méthode | Endpoint | Action |
|---------|----------|--------|
| GET | `/api/{resource}` | Liste |
| GET | `/api/{resource}/{id}` | Détail |
| POST | `/api/{resource}` | Création |
| PUT | `/api/{resource}/{id}` | Modification |
| DELETE | `/api/{resource}/{id}` | Suppression |

Resources disponibles : `projects`, `tasks`, `tags`, `statuses`

### Réponses standardisées

**Succès :**
```json
{
  "success": true,
  "message": "Created",
  "data": { "insert_id": 123 }
}
```

**Erreur :**
```json
{
  "success": false,
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Token expired"
  }
}
```

---

## Système de Queries

Les requêtes SQL sont externalisées dans des fichiers YAML pour éviter la recompilation.

### Structure Read (list/show)

```yaml
list:
  query: >
    SELECT id, name, description FROM projects
  filters:
    default: "user_id = :user_id"
    admin: ""
  order: "position ASC"

show:
  query: >
    SELECT id, name, description FROM projects WHERE id = :id
  filters:
    default: "user_id = :user_id"
    admin: ""
```

### Structure CUD (create/update/delete)

```yaml
create:
  table: projects
  fields:
    - user_id
    - name
    - description
    - position

update:
  table: projects
  fields:
    - name
    - description
    - position
  filters:
    default: "user_id = :user_id"
    admin: ""

delete:
  table: projects
  filters:
    default: "user_id = :user_id"
    admin: ""
```

### Placeholders

- `:user_id` → ID utilisateur depuis JWT
- `:id` → ID ressource depuis URL
- `:project_id` → ID projet (pour filtres)

### Filtres par rôle

Le filtre appliqué dépend du rôle de l'utilisateur :
- `default` : utilisateur standard, filtré par user_id
- `admin` : pas de filtre, voit tout

---

## Authentification

### JWT (JSON Web Token)

- Algorithme : HS256
- Expiration : configurable (défaut 24h)
- Secret : stocké dans fichier (`/secrets/{app}_jwt_secret`)

### Payload JWT

```json
{
  "sub": 1,
  "email": "user@example.com",
  "name": "User Name",
  "app": "prokov",
  "exp": 1765959278,
  "iat": 1765872878
}
```

### Header HTTP

```
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```

### Sécurité

- Passwords hashés avec bcrypt
- JWT stateless (pas de stockage serveur)
- Secrets dans fichiers séparés (pas dans YAML)
- Filtrage user_id automatique sur toutes les requêtes CRUD

---

## Déploiement

### Script deploy.sh

```bash
./deploy.sh
```

Étapes :
1. Build des binaires (linux/amd64)
2. Création des archives tar.gz
3. Copie vers jump server (IN3)
4. Déploiement dans container Incus (gw3)
5. Backup local des archives
6. Redémarrage automatique de sogoctl

### Cible

- **Jump host** : IN3 (195.154.80.116)
- **Container** : gw3 (Alpine, 13.23.33.5)
- **Binaires** : `/opt/sogoms/bin/`
- **Config** : `/config/`
- **Secrets** : `/secrets/`
- **Logs** : `/var/log/sogoms/`

### Commandes manuelles sur gw3

```bash
# Voir les logs
tail -f /var/log/sogoms/sogoctl.log

# Lister les processus
pgrep -la sogo

# Redémarrer
pkill -9 sogo && /opt/sogoms/bin/sogoctl &
```

---

## Structure du projet

```
sogoms/
├── cmd/
│   ├── sogoctl/main.go           # Superviseur
│   ├── sogoway/main.go           # Gateway HTTP
│   └── sogoms/
│       ├── db/main.go            # Microservice DB
│       ├── logs/main.go          # Microservice Logs
│       ├── smtp/main.go          # Microservice SMTP
│       ├── cron/main.go          # Microservice Cron
│       └── admin/                # Interface web admin
│           ├── main.go
│           ├── handlers.go
│           ├── middleware.go
│           ├── session.go
│           ├── services.go
│           └── templates/
├── internal/
│   ├── protocol/
│   │   ├── message.go            # Structs Request/Response
│   │   ├── server.go             # Serveur Unix socket
│   │   └── client.go             # Client + Pool connexions
│   ├── config/
│   │   └── config.go             # Registry, Queries, CUD
│   ├── cron/
│   │   └── scheduler.go          # Parser cron, calcul next run
│   ├── auth/
│   │   ├── jwt.go                # Génération/validation JWT
│   │   └── password.go           # Hash bcrypt
│   ├── admin/
│   │   ├── config.go             # Chargement admin_users.yaml
│   │   ├── permissions.go        # Vérification droits
│   │   └── audit.go              # Logging actions
│   └── version/
│       └── version.go            # Version, BuildTime
├── config/
│   ├── sogoctl.yaml
│   └── apps/
│       └── prokov/
│           ├── app.yaml          # Config app (DB, auth, SMTP)
│           ├── schema.yaml       # Schema DB généré
│           ├── cron.yaml         # Jobs planifiés
│           ├── queries/          # Requêtes SQL
│           │   ├── auth.yaml
│           │   ├── projects.yaml
│           │   └── ...
│           └── emails/           # Templates email
│               ├── welcome.yaml
│               └── tasks_today.yaml
├── bin/                          # Binaires compilés
├── deploy.sh                     # Script déploiement
├── VERSION                       # Numéro de version
├── go.mod
├── go.sum
├── README.md
├── CLAUDE.md                     # Instructions Claude Code
├── DOCTECH.md                    # Documentation technique
└── TODO.md                       # Roadmap
```

---

## Dépendances Go

```
go 1.24.0

github.com/go-sql-driver/mysql v1.9.3
gopkg.in/yaml.v3 v3.0.1
golang.org/x/crypto v0.46.0
```

---

## Roadmap

### Services planifiés

| Phase | Service | Description |
|-------|---------|-------------|
| 11 | `sogoms-crypt` | Chiffrement/déchiffrement données sensibles (AES-256-GCM) |
| 12b | `sogoms-imap` | Lecture boîtes email (IMAP) |
| 12c | `sogoms-mailproc` | Traitement emails (règles, webhooks) |
| 13 | `sogoms-cron` | Tâches planifiées (format cron standard) |
| 14 | `sogoms-push` | Push temps réel via MQTT (Mosquitto) |
| 15 | `sogoms-schema` | Génération d'API depuis schéma YAML |

### Vision Phase 15 : Schema-Driven API

```yaml
# config/schema/monapp.yaml - 1 fichier = 1 API complète
tables:
  users:
    fields:
      id: { type: int, primary: true, auto: true }
      email: { type: string, unique: true, auth: login }
      password: { type: string, auth: password, hidden: true }

  projects:
    fields:
      id: { type: int, primary: true, auto: true }
      user_id: { type: int, foreign: users.id, filter: owner }
      name: { type: string, required: true }
    crud: [list, show, create, update, delete]
```

Génère automatiquement : routes, queries, validation, filtres user_id, auth.

### Hors scope V1

- sogorch (orchestrateur scénarios)
- sogoms-pdf, sogoms-storage
- Multi-tenant avancé (workspaces, partage)
- Rate limiting
- Rôles utilisateurs (admin, manager, user)
