openapi: 3.0.3
info:
  title: Insforge Storage API
  version: 2.0.0
  description: Bucket-based storage system similar to S3
paths:
  /api/storage/buckets:
    get:
      summary: List All Buckets
      tags:
        - Admin
      security:
        - apiKey: []
      responses:
        '200':
          description: List of bucket names
          content:
            application/json:
              schema:
                type: object
                properties:
                  buckets:
                    type: array
                    items:
                      type: string
                    example:
                      - avatars
                      - documents
                      - uploads
              example:
                buckets:
                  - avatars
                  - documents
                  - uploads
                  - public
                  - private
    post:
      summary: Create New Bucket
      tags:
        - Admin
      security:
        - apiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - bucketName
              properties:
                bucketName:
                  type: string
                  pattern: ^[a-zA-Z0-9_-]+$
                  description: Bucket name (alphanumeric, underscore, and hyphen only)
                  example: avatars
                isPublic:
                  type: boolean
                  description: Whether the bucket is publicly accessible
                  default: true
                  example: true
      responses:
        '201':
          description: Bucket created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: Bucket created successfully
                  bucketName:
                    type: string
                    example: avatars
              example:
                message: Bucket created successfully
                bucket: avatars
        '400':
          description: Invalid bucket name
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: INVALID_BUCKET_NAME
                message: >-
                  Bucket name must contain only alphanumeric characters,
                  underscores, and hyphens
                statusCode: 400
                nextActions: Use a valid bucket name format
        '409':
          description: Bucket already exists
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: BUCKET_EXISTS
                message: Bucket 'avatars' already exists
                statusCode: 409
                nextActions: Choose a different bucket name
  /api/storage/buckets/{bucketName}:
    patch:
      summary: Update Bucket
      tags:
        - Admin
      security:
        - apiKey: []
      parameters:
        - name: bucketName
          in: path
          required: true
          schema:
            type: string
            pattern: ^[a-zA-Z0-9_-]+$
          example: avatars
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - is_public
              properties:
                isPublic:
                  type: boolean
                  description: Whether the bucket should be publicly accessible
                  example: true
      responses:
        '200':
          description: Bucket visibility updated successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: Bucket visibility updated
                  bucket:
                    type: string
                    example: avatars
                  isPublic:
                    type: boolean
                    example: true
              example:
                message: Bucket visibility updated
                bucket: avatars
                isPublic: true
        '404':
          description: Bucket not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: BUCKET_NOT_FOUND
                message: Bucket 'nonexistent' does not exist
                statusCode: 404
                nextActions: Check bucket name and try again
  /api/storage/buckets/{bucketName}/objects:
    get:
      summary: List Objects in Bucket
      tags:
        - Admin
      security:
        - apiKey: []
      parameters:
        - name: bucketName
          in: path
          required: true
          schema:
            type: string
            pattern: ^[a-zA-Z0-9_-]+$
          example: avatars
        - name: prefix
          in: query
          required: false
          schema:
            type: string
          description: Filter objects by key prefix
          example: users/
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 1000
            default: 100
        - name: offset
          in: query
          required: false
          schema:
            type: integer
            minimum: 0
            default: 0
      responses:
        '200':
          description: List of objects in bucket
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/StoredFile'
                  pagination:
                    type: object
                    properties:
                      offset:
                        type: integer
                        example: 0
                      limit:
                        type: integer
                        example: 100
                      total:
                        type: integer
                        example: 2
                  nextActions:
                    type: string
                    example: >-
                      You can use PUT
                      /api/storage/buckets/:bucketName/objects/:objectKey to
                      upload with a specific key, or POST
                      /api/storage/buckets/:bucketName/objects to upload with
                      auto-generated key, and GET
                      /api/storage/buckets/:bucketName/objects/:objectKey to
                      download an object.
              example:
                data:
                  - bucket: avatars
                    key: users/user123.jpg
                    size: 102400
                    mimeType: image/jpeg
                    uploadedAt: '2024-01-15T10:30:00Z'
                    url: /api/storage/buckets/avatars/objects/users/user123.jpg
                  - bucket: avatars
                    key: users/user456.png
                    size: 204800
                    mimeType: image/png
                    uploadedAt: '2024-01-16T11:00:00Z'
                    url: /api/storage/buckets/avatars/objects/users/user456.png
                pagination:
                  offset: 0
                  limit: 100
                  total: 2
                nextActions: >-
                  You can use PUT
                  /api/storage/buckets/:bucketName/objects/:objectKey to upload
                  with a specific key, or POST
                  /api/storage/buckets/:bucketName/objects to upload with
                  auto-generated key, and GET
                  /api/storage/buckets/:bucketName/objects/:objectKey to
                  download an object.
    delete:
      summary: Delete Bucket
      tags:
        - Admin
      security:
        - apiKey: []
      parameters:
        - name: bucketName
          in: path
          required: true
          schema:
            type: string
            pattern: ^[a-zA-Z0-9_-]+$
          example: avatars
      responses:
        '200':
          description: Bucket deleted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
              example:
                message: Bucket deleted successfully
        '404':
          description: Bucket not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: BUCKET_NOT_FOUND
                message: Bucket 'nonexistent' does not exist
                statusCode: 404
                nextActions: Check bucket name and try again
    post:
      summary: Upload Object with Auto-Generated Key
      tags:
        - Client
      security:
        - apiKey: []
      parameters:
        - name: bucketName
          in: path
          required: true
          schema:
            type: string
            pattern: ^[a-zA-Z0-9_-]+$
          example: avatars
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                  description: File to upload
              required:
                - file
      responses:
        '201':
          description: Object uploaded successfully with auto-generated key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StoredFile'
              example:
                bucket: avatars
                key: image-1737546841234-a3f2b1.jpg
                size: 102400
                mimeType: image/jpeg
                uploadedAt: '2024-01-21T10:30:00Z'
                url: >-
                  /api/storage/buckets/avatars/objects/image-1737546841234-a3f2b1.jpg
        '400':
          description: Invalid bucket name or file
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: INVALID_FILE
                message: No file provided in the request
                statusCode: 400
                nextActions: Include a file in the multipart form data
        '404':
          description: Bucket not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: BUCKET_NOT_FOUND
                message: Bucket 'nonexistent' does not exist
                statusCode: 404
                nextActions: Create the bucket first
  /api/storage/buckets/{bucketName}/upload-strategy:
    post:
      summary: Get Upload Strategy (Direct or Presigned URL)
      description: >-
        Returns upload strategy based on storage backend (S3 returns presigned
        URLs, local returns direct upload endpoints)
      tags:
        - Client
      security:
        - apiKey: []
      parameters:
        - name: bucketName
          in: path
          required: true
          schema:
            type: string
            pattern: ^[a-zA-Z0-9_-]+$
          example: avatars
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - filename
              properties:
                filename:
                  type: string
                  description: Original filename for generating unique key
                  example: profile-photo.jpg
                contentType:
                  type: string
                  description: MIME type of the file
                  example: image/jpeg
                size:
                  type: integer
                  description: File size in bytes
                  example: 102400
      responses:
        '200':
          description: Upload strategy details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UploadStrategy'
              examples:
                s3:
                  summary: S3 Backend Response
                  value:
                    method: presigned
                    uploadUrl: https://s3-bucket.amazonaws.com/
                    fields:
                      bucket: my-s3-bucket
                      key: app-key/avatars/profile-photo-1234567890-abc123.jpg
                      X-Amz-Algorithm: AWS4-HMAC-SHA256
                      X-Amz-Credential: AKIA.../20250905/us-east-2/s3/aws4_request
                      X-Amz-Date: 20250905T000000Z
                      Policy: eyJ...
                      X-Amz-Signature: abc123...
                    key: profile-photo-1234567890-abc123.jpg
                    confirmRequired: true
                    confirmUrl: >-
                      /api/storage/buckets/avatars/objects/profile-photo-1234567890-abc123.jpg/confirm-upload
                    expiresAt: '2025-09-05T01:00:00Z'
                local:
                  summary: Local Storage Response
                  value:
                    method: direct
                    uploadUrl: >-
                      /api/storage/buckets/avatars/objects/profile-photo-1234567890-abc123.jpg
                    key: profile-photo-1234567890-abc123.jpg
                    confirmRequired: false
        '404':
          description: Bucket not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
  /api/storage/buckets/{bucketName}/objects/{objectKey}:
    put:
      summary: Upload Object
      tags:
        - Client
      security:
        - apiKey: []
      parameters:
        - name: bucketName
          in: path
          required: true
          schema:
            type: string
            pattern: ^[a-zA-Z0-9_-]+$
          example: avatars
        - name: objectKey
          in: path
          required: true
          schema:
            type: string
          example: user123.jpg
          description: Object key (can include forward slashes for pseudo-folders)
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file:
                  type: string
                  format: binary
                  description: File to upload
              required:
                - file
      responses:
        '201':
          description: Object uploaded successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StoredFile'
              example:
                bucket: avatars
                key: user123.jpg
                size: 102400
                mimeType: image/jpeg
                uploadedAt: '2024-01-21T10:30:00Z'
                url: /api/storage/buckets/avatars/objects/user123.jpg
        '400':
          description: Invalid bucket name, key, or file
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: INVALID_FILE
                message: No file provided in the request
                statusCode: 400
                nextActions: Include a file in the multipart form data
        '404':
          description: Bucket not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: BUCKET_NOT_FOUND
                message: Bucket 'nonexistent' does not exist
                statusCode: 404
                nextActions: Create the bucket first
    get:
      summary: Download Object
      tags:
        - Client
      security:
        - apiKey: []
      parameters:
        - name: bucketName
          in: path
          required: true
          schema:
            type: string
            pattern: ^[a-zA-Z0-9_-]+$
          example: avatars
        - name: key
          in: path
          required: true
          schema:
            type: string
          example: user123.jpg
      responses:
        '200':
          description: File content
          content:
            '*/*':
              schema:
                type: string
                format: binary
          headers:
            Content-Type:
              schema:
                type: string
              description: MIME type of the file
            Content-Length:
              schema:
                type: integer
              description: Size of the file in bytes
        '404':
          description: Object not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: OBJECT_NOT_FOUND
                message: Object 'user123.jpg' not found in bucket 'avatars'
                statusCode: 404
                nextActions: Check the bucket and key combination
    delete:
      summary: Delete Object
      tags:
        - Client
      security:
        - apiKey: []
      parameters:
        - name: bucketName
          in: path
          required: true
          schema:
            type: string
            pattern: ^[a-zA-Z0-9_-]+$
          example: avatars
        - name: key
          in: path
          required: true
          schema:
            type: string
          example: user123.jpg
      responses:
        '200':
          description: Object deleted successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
              example:
                message: Object deleted successfully
        '404':
          description: Object not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: OBJECT_NOT_FOUND
                message: Object 'user123.jpg' not found in bucket 'avatars'
                statusCode: 404
                nextActions: Check the bucket and key combination
  /api/storage/buckets/{bucketName}/objects/{objectKey}/confirm-upload:
    post:
      summary: Confirm Presigned Upload
      description: Confirms that a file was successfully uploaded to S3 using presigned URL
      tags:
        - Client
      security:
        - apiKey: []
      parameters:
        - name: bucketName
          in: path
          required: true
          schema:
            type: string
            pattern: ^[a-zA-Z0-9_-]+$
          example: avatars
        - name: objectKey
          in: path
          required: true
          schema:
            type: string
          example: profile-photo-1234567890-abc123.jpg
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - size
              properties:
                size:
                  type: integer
                  description: File size in bytes
                  example: 102400
                contentType:
                  type: string
                  description: MIME type of the file
                  example: image/jpeg
                etag:
                  type: string
                  description: S3 ETag of the uploaded object (optional)
                  example: 9bb58f26192e4ba00f01e2e7b136bbd8
      responses:
        '201':
          description: Upload confirmed successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StoredFile'
        '404':
          description: Upload not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: UPLOAD_NOT_FOUND
                message: >-
                  Upload not found for key 'profile-photo.jpg' in bucket
                  'avatars'
                statusCode: 404
        '409':
          description: Already confirmed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: ALREADY_CONFIRMED
                message: File 'profile-photo.jpg' already confirmed in bucket 'avatars'
                statusCode: 409
  /api/storage/buckets/{bucketName}/objects/{objectKey}/download-strategy:
    post:
      summary: Get Download Strategy (Direct or Presigned URL)
      description: >
        Returns download strategy based on storage backend and bucket
        visibility.

        - S3 with public bucket: Direct URLs (no presigning, better performance)

        - S3 with private bucket: Presigned URLs with expiration

        - Local storage: Always direct endpoints
      tags:
        - Client
      security:
        - apiKey: []
      parameters:
        - name: bucketName
          in: path
          required: true
          schema:
            type: string
            pattern: ^[a-zA-Z0-9_-]+$
          example: avatars
        - name: objectKey
          in: path
          required: true
          schema:
            type: string
          example: profile-photo.jpg
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                expiresIn:
                  type: integer
                  description: URL expiration time in seconds (default 3600)
                  example: 3600
                  default: 3600
      responses:
        '200':
          description: Download strategy details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DownloadStrategy'
              examples:
                s3-public:
                  summary: S3 Public Bucket Response
                  value:
                    method: direct
                    url: >-
                      https://s3-bucket.s3.us-east-2.amazonaws.com/app-key/public-assets/logo.png
                s3-private:
                  summary: S3 Private Bucket Response
                  value:
                    method: presigned
                    url: >-
                      https://s3-bucket.s3.us-east-2.amazonaws.com/app-key/avatars/profile.jpg?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=...
                    expiresAt: '2025-09-05T01:00:00Z'
                local:
                  summary: Local Storage Response
                  value:
                    method: direct
                    url: /api/storage/buckets/avatars/objects/profile-photo.jpg
        '404':
          description: Object not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: x-api-key
  schemas:
    StoredFile:
      type: object
      properties:
        bucket:
          type: string
          example: avatars
          description: Name of the bucket containing the object
        key:
          type: string
          example: user123.jpg
          description: Unique key identifying the object within the bucket
        size:
          type: integer
          example: 102400
          description: Size of the file in bytes
        mimeType:
          type: string
          example: image/jpeg
          description: MIME type of the file
        uploadedAt:
          type: string
          format: date-time
          example: '2024-01-15T10:30:00Z'
          description: ISO timestamp when the file was uploaded
        url:
          type: string
          example: /api/storage/buckets/avatars/objects/user123.jpg
          description: URL to download the file
      required:
        - bucket
        - key
        - size
        - uploadedAt
        - url
    Pagination:
      type: object
      properties:
        limit:
          type: integer
          example: 100
        offset:
          type: integer
          example: 0
        total:
          type: integer
          example: 1
      required:
        - limit
        - offset
        - total
    UploadStrategy:
      type: object
      required:
        - method
        - uploadUrl
        - key
        - confirmRequired
      properties:
        method:
          type: string
          enum:
            - presigned
            - direct
          description: Upload method - presigned for S3, direct for local storage
          example: presigned
        uploadUrl:
          type: string
          description: URL to upload the file to
          example: https://s3-bucket.amazonaws.com/
        fields:
          type: object
          description: Form fields for presigned POST (S3 only)
          additionalProperties: true
          example:
            bucket: my-s3-bucket
            key: app-key/avatars/profile.jpg
            X-Amz-Algorithm: AWS4-HMAC-SHA256
        key:
          type: string
          description: Generated unique key for the file
          example: profile-photo-1234567890-abc123.jpg
        confirmRequired:
          type: boolean
          description: Whether upload confirmation is required
          example: true
        confirmUrl:
          type: string
          description: URL to confirm the upload (if confirmRequired is true)
          example: /api/storage/buckets/avatars/objects/profile.jpg/confirm-upload
        expiresAt:
          type: string
          format: date-time
          description: Expiration time for presigned URL (S3 only)
          example: '2025-09-05T01:00:00Z'
    DownloadStrategy:
      type: object
      required:
        - method
        - url
      properties:
        method:
          type: string
          enum:
            - presigned
            - direct
          description: >
            Download method:

            - `direct`: Direct URL access (S3 public buckets or local storage)

            - `presigned`: Secure URL with signature and expiration (S3 private
            buckets)
          example: direct
        url:
          type: string
          description: URL to download the file from
          example: >-
            https://s3-bucket.s3.us-east-2.amazonaws.com/app-key/public-assets/logo.png
        expiresAt:
          type: string
          format: date-time
          description: >-
            Expiration time for presigned URLs (only present when method is
            'presigned')
          example: '2025-09-05T01:00:00Z'
        headers:
          type: object
          description: Optional headers to include in the download request
          additionalProperties: true
    ErrorResponse:
      type: object
      required:
        - error
        - message
        - statusCode
      properties:
        error:
          type: string
          description: Error code for programmatic handling
          example: VALIDATION_ERROR
        message:
          type: string
          description: Human-readable error message
          example: Invalid request
        statusCode:
          type: integer
          description: HTTP status code
          example: 400
        nextActions:
          type: string
          description: Suggested action to resolve the error
          example: Check your request parameters
