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"