openapi: 3.1.0
info:
  title: REST API Manajemen Tugas
  summary: API JSON untuk mengelola daftar tugas.
  description: |
    API manajemen tugas yang dibangun dengan PHP murni tanpa framework.
    Seluruh response dan request body berbentuk JSON. Endpoint /api/tugas
    terlindungi bearer token; minta token lewat POST /api/login, lalu
    kirim sebagai header "Authorization: Bearer <token>".
  version: 1.0.0
servers:
  - url: http://127.0.0.1:8097
    description: Server pengembangan lokal (php -S)
  - url: http://localhost:8080
    description: Container Docker
tags:
  - name: kesehatan
    description: Pemeriksaan status layanan.
  - name: auth
    description: Login dan logout bearer token.
  - name: tugas
    description: CRUD daftar tugas (membutuhkan token).
paths:
  /api/kesehatan:
    get:
      tags: [kesehatan]
      summary: Periksa status layanan
      responses:
        '200':
          description: Layanan hidup.
          content:
            application/json:
              schema:
                type: object
                required: [status]
                properties:
                  status:
                    type: string
                    const: ok
              example:
                status: ok
        '405':
          $ref: '#/components/responses/MethodNotAllowed'
  /api/login:
    post:
      tags: [auth]
      summary: Tukar email dan sandi dengan bearer token
      description: |
        Satu-satunya cara memperoleh token. Sandi diverifikasi terhadap
        hash bcrypt; kegagalan apa pun (email tak dikenal atau sandi salah)
        dijawab pesan yang sama.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/LoginInput'
            example:
              email: budi@example.com
              sandi: sandi-budi-2026
      responses:
        '200':
          description: Login berhasil; token berlaku 1 jam.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Token'
              example:
                token: 79a2e78d8883fe1c4626cb26074d8c6ec1f2c3671cd7b8b322f1368d16594396
        '401':
          description: Email atau sandi salah.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: UNAUTHORIZED
                  message: Email atau sandi salah.
  /api/logout:
    post:
      tags: [auth]
      summary: Cabut token yang dipakai request ini
      security:
        - bearerAuth: []
      responses:
        '204':
          description: Token dicabut; request berikutnya dengan token yang sama ditolak 401.
        '401':
          $ref: '#/components/responses/TidakSah'
  /api/tugas:
    get:
      tags: [tugas]
      summary: Daftar seluruh tugas
      security:
        - bearerAuth: []
      responses:
        '200':
          description: Daftar tugas terurut menaik berdasarkan id.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Tugas'
              example:
                - id: 1
                  judul: Belajar PHP dasar
                  status: selesai
                  dibuat_pada: '2026-01-05 08:00:00'
                - id: 2
                  judul: Merancang REST API
                  status: proses
                  dibuat_pada: '2026-01-10 09:30:00'
                - id: 3
                  judul: Menulis dokumentasi API
                  status: belum
                  dibuat_pada: '2026-01-12 14:15:00'
        '401':
          $ref: '#/components/responses/TidakSah'
    post:
      tags: [tugas]
      summary: Buat tugas baru
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TugasInput'
            example:
              judul: Belajar authentication
              status: belum
      responses:
        '201':
          description: Tugas tersimpan.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Tugas'
              example:
                id: 4
                judul: Belajar authentication
                status: belum
                dibuat_pada: '2026-10-13 22:20:56'
        '401':
          $ref: '#/components/responses/TidakSah'
        '422':
          $ref: '#/components/responses/ValidasiGagal'
  /api/tugas/{id}:
    get:
      tags: [tugas]
      summary: Baca satu tugas
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/IdTugas'
      responses:
        '200':
          description: Detail tugas.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Tugas'
              example:
                id: 1
                judul: Belajar PHP dasar
                status: selesai
                dibuat_pada: '2026-01-05 08:00:00'
        '401':
          $ref: '#/components/responses/TidakSah'
        '404':
          $ref: '#/components/responses/TidakDitemukan'
    put:
      tags: [tugas]
      summary: Ubah tugas sebagian atau seluruhnya
      description: |
        Kolom yang tidak dikirim membiarkan nilai lama tetap; kolom yang
        dikirim divalidasi dengan aturan yang sama seperti pembuatan.
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/IdTugas'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TugasPerubahan'
            example:
              status: selesai
      responses:
        '200':
          description: Tugas terbarui; seluruh kolom dikirim balik.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Tugas'
              example:
                id: 4
                judul: Belajar authentication
                status: selesai
                dibuat_pada: '2026-10-13 22:20:56'
        '401':
          $ref: '#/components/responses/TidakSah'
        '404':
          $ref: '#/components/responses/TidakDitemukan'
        '422':
          $ref: '#/components/responses/ValidasiGagal'
    delete:
      tags: [tugas]
      summary: Hapus satu tugas
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/IdTugas'
      responses:
        '204':
          description: Tugas terhapus; tidak ada body.
        '401':
          $ref: '#/components/responses/TidakSah'
        '404':
          $ref: '#/components/responses/TidakDitemukan'
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: |
        Token 64 karakter heksadesimal dari POST /api/login. Kirim sebagai
        header "Authorization: Bearer <token>". Berlaku 1 jam dan bisa
        dicabut lewat POST /api/logout.
  parameters:
    IdTugas:
      name: id
      in: path
      required: true
      description: id tugas.
      schema:
        type: integer
        minimum: 1
  responses:
    TidakSah:
      description: Token tidak dikirim, tidak dikenal, atau kedaluwarsa.
      headers:
        WWW-Authenticate:
          description: Selalu berisi nilai Bearer.
          schema:
            type: string
          example: Bearer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: UNAUTHORIZED
              message: "Kirim token lewat header Authorization: Bearer <token>."
    TidakDitemukan:
      description: Tugas dengan id tersebut tidak ada.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: NOT_FOUND
              message: Tugas dengan id 99 tidak ditemukan.
    ValidasiGagal:
      description: Body tidak lolos validasi; details berisi daftar pesan per kolom.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: VALIDATION_ERROR
              message: Data yang dikirim tidak valid.
              details:
                judul:
                  - "Kolom 'judul' minimal 3 karakter."
                status:
                  - "Kolom 'status' harus salah satu dari: belum, proses, selesai."
    MethodNotAllowed:
      description: Method tidak diizinkan untuk endpoint ini.
      headers:
        Allow:
          description: Daftar method yang diizinkan, dipisah koma.
          schema:
            type: string
          example: GET
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: METHOD_NOT_ALLOWED
              message: Method PUT tidak diizinkan untuk endpoint ini.
  schemas:
    Tugas:
      type: object
      required: [id, judul, status, dibuat_pada]
      properties:
        id:
          type: integer
          example: 4
        judul:
          type: string
          example: Belajar SQLite
        status:
          type: string
          enum: [belum, proses, selesai]
          example: belum
        dibuat_pada:
          type: string
          description: Waktu pembuatan format YYYY-MM-DD HH:MM:SS (waktu server).
          example: '2026-01-15 08:00:00'
    TugasInput:
      type: object
      required: [judul]
      properties:
        judul:
          type: string
          minLength: 3
          maxLength: 100
          example: Belajar authentication
        status:
          type: string
          enum: [belum, proses, selesai]
          default: belum
    TugasPerubahan:
      type: object
      description: Kolom yang dikirim divalidasi; kolom yang tidak dikirim membiarkan nilai lama.
      properties:
        judul:
          type: string
          minLength: 3
          maxLength: 100
        status:
          type: string
          enum: [belum, proses, selesai]
    LoginInput:
      type: object
      required: [email, sandi]
      properties:
        email:
          type: string
          format: email
          example: budi@example.com
        sandi:
          type: string
          example: sandi-budi-2026
    Token:
      type: object
      required: [token]
      properties:
        token:
          type: string
          description: Token acak 64 karakter heksadesimal; nilai berbeda pada setiap login.
          example: 79a2e78d8883fe1c4626cb26074d8c6ec1f2c3671cd7b8b322f1368d16594396
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [code, message]
          properties:
            code:
              type: string
              description: Kode error yang stabil untuk diprogram klien.
              example: NOT_FOUND
            message:
              type: string
              description: Pesan yang ditujukan untuk manusia.
              example: Tugas dengan id 99 tidak ditemukan.
            details:
              type: object
              description: Informasi tambahan; hanya muncul bila relevan.
