openapi: 3.0.3
info:
  title: Teknik Danışman API
  description: |
    Multi-tenant IT varlık ve iş yönetim platformu için REST API v1.

    **Auth:** Bearer token. Etiketinizi `Authorization: Bearer tdk_...` header'ı ile gönderin.

    **Rate limit:** Dakika başına 100 istek per key (sliding window). Her cevapta:
      - `X-RateLimit-Limit: 100`
      - `X-RateLimit-Remaining: <kalan>`
      - `X-RateLimit-Reset: <unix epoch>` — limitin sıfırlanacağı zaman
    Aşıldığında 429 + `Retry-After: 60`.

    **Pagination:** Cursor-based; response'da `next_cursor` döner, sonraki sayfa için `?cursor=<id>` gönderin.

    **Idempotency:** POST/PUT/DELETE için `Idempotency-Key: <unique>` header'ı önerilir.
  version: "1.0.0"
  contact:
    name: Teknik Danışman
    url: https://teknikdanisman.net
servers:
  - url: https://teknikdanisman.net/api/v1
    description: Production
security:
  - BearerAuth: []
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: tdk_xxxxxxxx
  schemas:
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code:    { type: string, example: invalid_key }
            message: { type: string }
            status:  { type: integer, example: 401 }
    PaginatedList:
      type: object
      properties:
        data:        { type: array, items: { type: object } }
        has_more:    { type: boolean }
        next_cursor: { type: integer, nullable: true }
        limit:       { type: integer }
    Customer:
      type: object
      properties:
        id:           { type: integer }
        object:       { type: string, example: customer }
        company:      { type: string }
        contact_name: { type: string, nullable: true }
        email:        { type: string, nullable: true }
        phone:        { type: string, nullable: true }
        address:      { type: string, nullable: true }
        tax_office:   { type: string, nullable: true }
        tax_number:   { type: string, nullable: true }
        created_at:   { type: string, format: date-time }
    Employee:
      type: object
      properties:
        id:                 { type: integer }
        object:             { type: string, example: employee }
        customer_id:        { type: integer }
        name:               { type: string }
        role:               { type: string, nullable: true }
        email:              { type: string, nullable: true }
        phone:              { type: string, nullable: true }
        department:         { type: string, nullable: true }
        status:             { type: string, enum: [aktif, pasif, ayrildi] }
        mfa_enabled:        { type: boolean }
        hire_date:          { type: string, format: date, nullable: true }
        termination_date:   { type: string, format: date, nullable: true }
        termination_reason: { type: string, nullable: true }
        created_at:         { type: string, format: date-time }
    Hardware:
      type: object
      properties:
        id:                 { type: integer }
        object:             { type: string, example: hardware }
        customer_id:        { type: integer, nullable: true }
        employee_id:        { type: integer, nullable: true }
        employee_name:      { type: string, nullable: true }
        status:             { type: string, enum: [active, idle, scrap] }
        device_type:        { type: string }
        brand:              { type: string, nullable: true }
        model:              { type: string, nullable: true }
        serial:             { type: string, nullable: true }
        operating_system:   { type: string, nullable: true }
        cpu:                { type: string, nullable: true }
        ram:                { type: string, nullable: true }
        disk:               { type: string, nullable: true }
        purchase_date:      { type: string, format: date, nullable: true }
        warranty_end:       { type: string, format: date, nullable: true }
        warehouse_code:     { type: string, nullable: true }
        released_at:        { type: string, format: date-time, nullable: true }
        released_from_name: { type: string, nullable: true }
        notes:              { type: string, nullable: true }
        created_at:         { type: string, format: date-time }
paths:
  /ping.php:
    get:
      summary: Health check
      security: []
      responses:
        "200":
          description: API ayakta
          content:
            application/json:
              schema:
                type: object
                properties:
                  ok:          { type: boolean, example: true }
                  api_version: { type: string,  example: v1 }
                  service:     { type: string }
                  time:        { type: string, format: date-time }
  /customers.php:
    get:
      summary: Müşterileri listele veya tek müşteri çek
      parameters:
        - { name: id,     in: query, schema: { type: integer }, description: Tek bir müşteri }
        - { name: limit,  in: query, schema: { type: integer, default: 25, maximum: 100 } }
        - { name: cursor, in: query, schema: { type: integer }, description: Önceki yanıttaki next_cursor }
        - { name: q,      in: query, schema: { type: string  }, description: Şirket/yetkili/e-posta'da arama }
      responses:
        "200":
          description: Liste veya tek kayıt
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/PaginatedList'
                  - type: object
                    properties:
                      data: { $ref: '#/components/schemas/Customer' }
        "401": { description: Auth gerekli, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
        "404": { description: Bulunamadı, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
        "429": { description: Rate limited }
  /employees.php:
    get:
      summary: Personel listesi (stok placeholder hariç) veya tek personel
      parameters:
        - { name: id,          in: query, schema: { type: integer } }
        - { name: customer_id, in: query, schema: { type: integer } }
        - { name: status,      in: query, schema: { type: string, enum: [aktif, pasif, ayrildi] } }
        - { name: q,           in: query, schema: { type: string  } }
        - { name: limit,       in: query, schema: { type: integer, default: 25 } }
        - { name: cursor,      in: query, schema: { type: integer } }
      responses:
        "200":
          description: Liste veya tek kayıt
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/PaginatedList'
                  - type: object
                    properties:
                      data: { $ref: '#/components/schemas/Employee' }
  /hardware.php:
    get:
      summary: Donanım listesi veya tek donanım
      parameters:
        - { name: id,          in: query, schema: { type: integer } }
        - { name: customer_id, in: query, schema: { type: integer } }
        - { name: employee_id, in: query, schema: { type: integer } }
        - { name: status,      in: query, schema: { type: string, enum: [active, idle, scrap] } }
        - { name: device_type, in: query, schema: { type: string } }
        - { name: limit,       in: query, schema: { type: integer, default: 25 } }
        - { name: cursor,      in: query, schema: { type: integer } }
      responses:
        "200":
          description: Liste veya tek kayıt
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/PaginatedList'
                  - type: object
                    properties:
                      data: { $ref: '#/components/schemas/Hardware' }
  /renew.php:
    post:
      summary: Domain/hosting/SSL servis veya yazılım lisansını +1 yıl yenile
      description: |
        İlgili kaydın yenileme tarih kolonlarına 1 yıl ekler. Web panelindeki "Yenilendi"
        butonu ile aynı `RenewalService`'i kullanır (strangler-fig dogfood).

        Servis kaynağı için dolu olan `end_date`, `domain_expiry`, `hosting_expiry`,
        `expiry_date` kolonlarının her birine +1 yıl eklenir. Hiçbiri yoksa `end_date =
        bugün + 1 yıl` atanır (`fallback_used: true`).

        Lisans kaynağı için `renewal_date` +1 yıl. Boşsa bugün+1 yıl.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [resource, id]
              properties:
                resource: { type: string, enum: [service, license], example: service }
                id:       { type: integer, example: 42 }
      responses:
        "200":
          description: Yenileme başarılı
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      object:          { type: string, example: renewal }
                      resource:        { type: string, example: service }
                      id:              { type: integer, example: 42 }
                      updated_columns: { type: array, items: { type: string } }
                      fallback_used:   { type: boolean }
        "400": { description: invalid_request, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
        "401": { description: unauthorized, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
        "403": { description: forbidden — yazma yetkisi yok, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
        "404": { description: Kayıt bulunamadı, content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } } }
        "429": { description: Rate limited }
