openapi: 3.1.0
info:
  title: Ulti Suite API
  version: 1.0.0
  description: |
    API REST Ultimail, UltiDrive et Contacts exposée par **ultid** sous `/api/v1`.

    ## Authentification

    Deux modes :

    | Mode | Header | Usage |
    |------|--------|-------|
    | **Session utilisateur** | `Authorization: Bearer <jwt_oidc>` | Interface web, apps avec login OIDC (Authentik) |
    | **Token API** | `Authorization: Bearer ulti_<secret>` | Agents IA, scripts, intégrations programmatiques |

    Les tokens API portent des permissions **fine-grained** (lecture/écriture par ressource) et des **scopes** optionnels (comptes mail, dossiers Drive).

    ## Permissions tokens API

    Ressources principales :

    - **Mail** : `mail.mailboxes`, `mail.labels`, `mail.messages`, `mail.search`, `mail.send`, `mail.attachments`, `mail.settings`, `mail.identities`, `mail.automation`
    - **Drive** : `drive.folders`, `drive.files`, `drive.thumbnails`, `drive.download`, `drive.share`, `drive.upload`, `drive.rename`, `drive.move`, `drive.copy`
    - **Contacts** : `contacts.read`, `contacts.search`, `contacts.write`, `contacts.delete`, `contacts.labels`
    - **Automatisations** : `automation.rules`, `automation.webhooks`, `automation.llm`, `automation.search`, `automation.api_tokens` (super admin)

    Chaque ressource accepte `read` et/ou `write` selon le cas.

    ## Scopes

    - **mail_scope** : `{ "all_accounts": true }` ou `{ "all_accounts": false, "account_ids": ["uuid", ...] }`
    - **drive_scope** : `{ "all_folders": true }` ou `{ "all_folders": false, "folder_paths": ["/Projects", ...] }`

servers:
  - url: /api/v1
    description: API ultid (proxifiée par nginx)

tags:
  - name: Tokens API
    description: Gestion des jetons programmatiques
  - name: Mail
    description: Messages, boîtes, envoi
  - name: Drive
    description: Fichiers et dossiers Nextcloud
  - name: Contacts
    description: Carnet d'adresses
  - name: Automatisations
    description: Règles, webhooks, fournisseurs

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT or ulti_token
      description: JWT OIDC (session) ou token API `ulti_…`
  schemas:
    ApiTokenPermissionGrant:
      type: object
      required: [resource, read, write]
      properties:
        resource:
          type: string
          example: mail.messages
        read:
          type: boolean
        write:
          type: boolean
    ApiTokenMailScope:
      type: object
      properties:
        all_accounts:
          type: boolean
        account_ids:
          type: array
          items:
            type: string
            format: uuid
    ApiTokenDriveScope:
      type: object
      properties:
        all_folders:
          type: boolean
        folder_paths:
          type: array
          items:
            type: string
            example: /Projects
    ApiToken:
      type: object
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
        token_prefix:
          type: string
          example: ulti_a3f9b2c1
        permissions:
          type: array
          items:
            $ref: '#/components/schemas/ApiTokenPermissionGrant'
        mail_scope:
          $ref: '#/components/schemas/ApiTokenMailScope'
        drive_scope:
          $ref: '#/components/schemas/ApiTokenDriveScope'
        created_at:
          type: string
          format: date-time
        last_used_at:
          type: string
          format: date-time
        expires_at:
          type: string
          format: date-time
    ApiTokenCreated:
      allOf:
        - $ref: '#/components/schemas/ApiToken'
        - type: object
          required: [token]
          properties:
            token:
              type: string
              description: Secret complet — affiché une seule fois à la création
    CreateApiTokenRequest:
      type: object
      required: [name, permissions, mail_scope, drive_scope]
      properties:
        name:
          type: string
        permissions:
          type: array
          items:
            $ref: '#/components/schemas/ApiTokenPermissionGrant'
        mail_scope:
          $ref: '#/components/schemas/ApiTokenMailScope'
        drive_scope:
          $ref: '#/components/schemas/ApiTokenDriveScope'
        expires_at:
          type: string
          format: date-time
    MessageSummary:
      type: object
      properties:
        id:
          type: string
          format: uuid
        account_id:
          type: string
          format: uuid
        subject:
          type: string
        snippet:
          type: string
        date:
          type: string
          format: date-time
    DriveFile:
      type: object
      properties:
        path:
          type: string
        name:
          type: string
        type:
          type: string
          enum: [file, directory]
        size:
          type: integer
        mime_type:
          type: string
        last_modified:
          type: string
          format: date-time
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
            message:
              type: string

security:
  - bearerAuth: []

paths:
  /mail/api-tokens:
    get:
      tags: [Tokens API]
      summary: Lister les tokens API
      description: Nécessite une session OIDC ou un token avec `automation.api_tokens` (écriture).
      responses:
        '200':
          description: Liste des tokens actifs (sans secret)
          content:
            application/json:
              schema:
                type: object
                properties:
                  tokens:
                    type: array
                    items:
                      $ref: '#/components/schemas/ApiToken'
    post:
      tags: [Tokens API]
      summary: Créer un token API
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateApiTokenRequest'
            example:
              name: Agent tri support
              permissions:
                - resource: mail.messages
                  read: true
                  write: false
                - resource: mail.labels
                  read: true
                  write: true
              mail_scope:
                all_accounts: false
                account_ids: ["550e8400-e29b-41d4-a716-446655440000"]
              drive_scope:
                all_folders: true
                folder_paths: []
      responses:
        '201':
          description: Token créé — copier le champ `token` immédiatement
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiTokenCreated'

  /mail/api-tokens/{tokenID}:
    delete:
      tags: [Tokens API]
      summary: Révoquer un token API
      parameters:
        - name: tokenID
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '204':
          description: Token révoqué

  /mail/messages:
    get:
      tags: [Mail]
      summary: Lister les messages
      description: |
        Permission requise : `mail.messages` (lecture).
        Scope mail appliqué automatiquement si le token est restreint à certains comptes.
      parameters:
        - name: account_id
          in: query
          schema:
            type: string
            format: uuid
        - name: folder
          in: query
          schema:
            type: string
          example: inbox
        - name: page
          in: query
          schema:
            type: integer
        - name: page_size
          in: query
          schema:
            type: integer
      responses:
        '200':
          description: Page de messages
          content:
            application/json:
              schema:
                type: object
                properties:
                  messages:
                    type: array
                    items:
                      $ref: '#/components/schemas/MessageSummary'

  /mail/messages/{messageID}:
    get:
      tags: [Mail]
      summary: Lire un message
      description: Vérifie que le message appartient à un compte autorisé par le token.
      parameters:
        - name: messageID
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Message complet
        '403':
          description: Compte hors scope du token

  /mail/search:
    get:
      tags: [Mail]
      summary: Rechercher des messages
      description: Permission `mail.search` (lecture).
      parameters:
        - name: q
          in: query
          schema:
            type: string
        - name: account_id
          in: query
          schema:
            type: string
            format: uuid
        - name: from
          in: query
          schema:
            type: string
      responses:
        '200':
          description: Résultats de recherche

  /mail/send:
    post:
      tags: [Mail]
      summary: Envoyer un message
      description: Permission `mail.send` (écriture). `account_id` doit être dans le scope.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [account_id, to, subject]
              properties:
                account_id:
                  type: string
                  format: uuid
                to:
                  type: array
                  items:
                    type: string
                subject:
                  type: string
                body_html:
                  type: string
      responses:
        '200':
          description: Message envoyé ou mis en file

  /mail/rules:
    get:
      tags: [Automatisations]
      summary: Lister les règles de tri
      description: Permission `automation.rules` (lecture).
      responses:
        '200':
          description: Règles
    post:
      tags: [Automatisations]
      summary: Créer une règle
      description: Permission `automation.rules` (écriture).
      responses:
        '201':
          description: Règle créée

  /mail/webhooks:
    get:
      tags: [Automatisations]
      summary: Lister les webhooks
      description: Permission `automation.webhooks` (lecture).
      responses:
        '200':
          description: Webhooks
    post:
      tags: [Automatisations]
      summary: Créer un webhook
      responses:
        '201':
          description: Webhook créé

  /drive/files/{path}:
    get:
      tags: [Drive]
      summary: Lister un dossier
      description: Permission `drive.folders` ou `drive.files` (lecture). Path relatif au Drive.
      parameters:
        - name: path
          in: path
          required: true
          schema:
            type: string
          example: Projects/docs
      responses:
        '200':
          description: Contenu du dossier
          content:
            application/json:
              schema:
                type: object
                properties:
                  files:
                    type: array
                    items:
                      $ref: '#/components/schemas/DriveFile'
    post:
      tags: [Drive]
      summary: Uploader un fichier
      description: Permission `drive.upload` (écriture). Path = dossier cible.
      parameters:
        - name: path
          in: path
          required: true
          schema:
            type: string
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
      responses:
        '201':
          description: Fichier uploadé

  /drive/download/{path}:
    get:
      tags: [Drive]
      summary: Télécharger un fichier
      description: Permission `drive.download` (lecture).
      parameters:
        - name: path
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Contenu binaire

  /drive/preview/{path}:
    get:
      tags: [Drive]
      summary: Miniature / aperçu
      description: Permission `drive.thumbnails` (lecture).
      parameters:
        - name: path
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Image ou flux de prévisualisation

  /contacts/search:
    get:
      tags: [Contacts]
      summary: Rechercher des contacts
      description: Permission `contacts.search` (lecture).
      parameters:
        - name: q
          in: query
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Contacts correspondants

  /contacts/books/{bookID}:
    get:
      tags: [Contacts]
      summary: Lister les contacts d'un carnet
      description: Permission `contacts.read` (lecture).
      parameters:
        - name: bookID
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: Contacts du carnet
    post:
      tags: [Contacts]
      summary: Créer un contact
      description: Permission `contacts.write` (écriture).
      responses:
        '201':
          description: Contact créé

  /contacts/discovery/llm-settings:
    get:
      tags: [Automatisations]
      summary: Lire les fournisseurs LLM
      description: Permission `automation.llm` (lecture).
      responses:
        '200':
          description: Configuration LLM
    put:
      tags: [Automatisations]
      summary: Mettre à jour les fournisseurs LLM
      description: Permission `automation.llm` (écriture).
      responses:
        '200':
          description: Configuration mise à jour

  /search:
    get:
      tags: [Mail, Drive, Contacts]
      summary: Recherche cross-suite
      description: |
        Vérifie les permissions selon `types` :
        - `mail` → `mail.search`
        - `drive` → `drive.files`
        - `contacts` → `contacts.search`
      parameters:
        - name: q
          in: query
          required: true
          schema:
            type: string
        - name: types
          in: query
          schema:
            type: string
          example: mail,contacts,drive
        - name: account_id
          in: query
          schema:
            type: string
            format: uuid
      responses:
        '200':
          description: Résultats agrégés
