> ## Documentation Index
> Fetch the complete documentation index at: https://docs.insforge.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# 儲存空間 SDK 參考

> 使用 InsForge Kotlin SDK 進行檔案上傳、下載和管理

## 安裝

1. Add InsForge dependencies to your project

<Tabs>
  <Tab title="Maven Central">
    build.gradle.kts:

    ```kotlin theme={null}
    repositories {
        mavenLocal() // For local development
        mavenCentral()
    }

    dependencies {
        implementation("dev.insforge:insforge-kotlin:0.1.6")
    }
    ```
  </Tab>

  <Tab title="GitHub Packages">
    First, create a GitHub Personal Access Token:

    * Go to GitHub → Settings → Developer settings → Personal access tokens → Tokens (classic)
    * Select permission: `read:packages`

    Then configure your project using one of the following methods:

    <AccordionGroup>
      <Accordion title="Option A: Environment Variables">
        settings.gradle.kts (or build.gradle.kts):

        ```kotlin theme={null}
        repositories {
            mavenCentral()
            maven {
                url = uri("https://maven.pkg.github.com/InsForge/insforge-kotlin")
                credentials {
                    username = System.getenv("GITHUB_USER") ?: ""
                    password = System.getenv("GITHUB_TOKEN") ?: ""
                }
            }
        }
        ```

        build.gradle.kts:

        ```kotlin theme={null}
        dependencies {
            implementation("dev.insforge:insforge-kotlin:0.1.6")
        }
        ```

        Set environment variables before building:

        ```bash theme={null}
        export GITHUB_USER="your-github-username"
        export GITHUB_TOKEN="your-personal-access-token"
        ```
      </Accordion>

      <Accordion title="Option B: gradle.properties (Local Development)">
        Add credentials to your global Gradle properties file:

        \~/.gradle/gradle.properties:

        ```properties theme={null}
        gpr.user=your-github-username
        gpr.token=ghp_xxxxxxxxxxxx
        ```

        settings.gradle.kts:

        ```kotlin theme={null}
        repositories {
            mavenCentral()
            maven {
                url = uri("https://maven.pkg.github.com/InsForge/insforge-kotlin")
                credentials {
                    username = providers.gradleProperty("gpr.user").orNull ?: ""
                    password = providers.gradleProperty("gpr.token").orNull ?: ""
                }
            }
        }
        ```

        build.gradle.kts:

        ```kotlin theme={null}
        dependencies {
            implementation("dev.insforge:insforge-kotlin:0.1.6")
        }
        ```

        <Note>
          The `~/.gradle/gradle.properties` file is stored outside your project, so credentials won't be accidentally committed to version control.
        </Note>
      </Accordion>
    </AccordionGroup>
  </Tab>
</Tabs>

2. Initialize InsForge SDK

```kotlin theme={null}
import dev.insforge.createInsforgeClient
import dev.insforge.auth.Auth
import dev.insforge.database.Database
import dev.insforge.storage.Storage
import dev.insforge.functions.Functions
import dev.insforge.realtime.Realtime
import dev.insforge.ai.AI

val client = createInsforgeClient(
    baseUrl = "https://your-app.insforge.app",
    anonKey = "your-api-key"
) {
    install(Auth)
    install(Database)
    install(Storage)
    install(Functions)
    install(Realtime) {
        autoReconnect = true
        reconnectDelay = 5000
    }
    install(AI)
}
```

3. Enable Logging (Optional)

For debugging, you can configure the SDK log level:

```kotlin theme={null}
import dev.insforge.InsforgeLogLevel

val client = createInsforgeClient(
    baseUrl = "https://your-app.insforge.app",
    anonKey = "your-api-key"
) {
    // DEBUG: logs request method/URL and response status
    // VERBOSE: logs full headers and request/response bodies
    logLevel = InsforgeLogLevel.DEBUG

    install(Auth)
    install(Database)
    // ... other modules
}
```

| Log Level | Description                                       |
| --------- | ------------------------------------------------- |
| `NONE`    | No logging (default, recommended for production)  |
| `ERROR`   | Only errors                                       |
| `WARN`    | Warnings and errors                               |
| `INFO`    | Informational messages                            |
| `DEBUG`   | Debug info (request method, URL, response status) |
| `VERBOSE` | Full details (headers, request/response bodies)   |

<Note>
  Use `NONE` or `ERROR` in production to avoid exposing sensitive data in logs.
</Note>

### Android Initialization

1. Add Chrome Custom Tabs dependency to your `build.gradle.kts`:

```kotlin theme={null}
dependencies {
    implementation("androidx.browser:browser:1.9.0")
}
```

2. Initialize InsForge SDK (With Chrome Custom Tabs and Session Storage)

```kotlin theme={null}
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.browser.customtabs.CustomTabsIntent
import dev.insforge.createInsforgeClient
import dev.insforge.ai.AI
import dev.insforge.auth.Auth
import dev.insforge.auth.BrowserLauncher
import dev.insforge.auth.ClientType
import dev.insforge.auth.SessionStorage
import dev.insforge.database.Database
import dev.insforge.functions.Functions
import dev.insforge.realtime.Realtime
import dev.insforge.storage.Storage

class InsforgeManager(private val context: Context) {

    val client = createInsforgeClient(
        baseUrl = "https://your-app.insforge.app",
        anonKey = "your-anon-key"
    ) {
        install(Auth) {
            // 1. Chrome Custom Tabs - opens in-app, similar to iOS ASWebAuthenticationSession
            browserLauncher = BrowserLauncher { url ->
                val customTabsIntent = CustomTabsIntent.Builder()
                    .setShowTitle(true)
                    .build()
                // Handle non-Activity context safely
                if (context is Activity) {
                    customTabsIntent.launchUrl(context, Uri.parse(url))
                } else {
                    customTabsIntent.intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                    customTabsIntent.launchUrl(context, Uri.parse(url))
                }
            }

            // 2. enable session persistence
            persistSession = true

            // 3. config SessionStorage (use SharedPreferences)
            sessionStorage = object : SessionStorage {
                private val prefs = context.getSharedPreferences(
                    "insforge_auth",
                    Context.MODE_PRIVATE
                )

                override suspend fun save(key: String, value: String) {
                    prefs.edit().putString(key, value).apply()
                }

                override suspend fun get(key: String): String? {
                    return prefs.getString(key, null)
                }

                override suspend fun remove(key: String) {
                    prefs.edit().remove(key).apply()
                }
            }

            // 4. set client type for mobile
            clientType = ClientType.MOBILE
        }
        // Install Database module
        install(Database)

        // Install Realtime module for real-time subscriptions
        install(Realtime) {
            debug = true
        }
        // Install other modules
        install(Storage)
        install(Functions)
        install(AI)
    }
}
```

3. Use Jetpack DataStore for Session Storage (Optional)

```kotlin theme={null}
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map

val Context.authDataStore: DataStore<Preferences> by preferencesDataStore(name = "insforge_auth")

class DataStoreSessionStorage(private val context: Context) : SessionStorage {
    
    override suspend fun save(key: String, value: String) {
        context.authDataStore.edit { prefs ->
            prefs[stringPreferencesKey(key)] = value
        }
    }
    
    override suspend fun get(key: String): String? {
        return context.authDataStore.data.map { prefs ->
            prefs[stringPreferencesKey(key)]
        }.first()
    }
    
    override suspend fun remove(key: String) {
        context.authDataStore.edit { prefs ->
            prefs.remove(stringPreferencesKey(key))
        }
    }
}

// Then use it in your InsForge client
install(Auth) {
    browserLauncher = ...
    persistSession = true
    sessionStorage = DataStoreSessionStorage(context)
}
```

## from()

取得用於檔案作業的貯體執行個體。

### 範例

```kotlin theme={null}
val bucket = insforge.storage.from("images")
```

***

## upload()

上傳具有特定路徑/金鑰的檔案。

### 參數

* `path` (String) - 貯體中的檔案路徑/金鑰
* `data` (ByteArray) - 檔案內容（以位元組形式）
* `options` (DSL block, optional) - 上傳選項

### UploadOptions

| 選項            | 類型                       | 說明                       |
| ------------- | ------------------------ | ------------------------ |
| `contentType` | String?                  | MIME 類型（例如 "image/jpeg"） |
| `upsert`      | Boolean                  | 如果檔案存在，覆寫（預設：false）      |
| `metadata`    | `Map\<String, String\>?` | 自訂中繼資料                   |

### 範例

```kotlin theme={null}
// Basic upload
val imageData = bitmap.toByteArray()
val result = insforge.storage
    .from("images")
    .upload("posts/post-123/cover.jpg", imageData)

Log.d("Storage", "Uploaded: ${result.url}")

// Upload with options
val result = insforge.storage
    .from("images")
    .upload("posts/post-123/cover.jpg", imageData) {
        contentType = "image/jpeg"
        upsert = true  // Overwrite if exists
        metadata = mapOf("userId" to "123")
    }

// Using bracket syntax
val result = insforge.storage["images"]
    .upload("avatars/user-123.jpg", imageData) {
        contentType = "image/jpeg"
    }
```

### 從 Android Uri 上傳

開發者可以使用下列函式從 Android Uri 上傳檔案：

```kotlin theme={null}
// Android Uri Upload sample:
suspend fun uploadFromUri(
    bucket: BucketApi,
    uri: Uri,
    context: Context,
    path: String
): FileUploadResponse {
    val data = context.contentResolver.openInputStream(uri)?.use {
        it.readBytes()
    } ?: throw IllegalArgumentException("Cannot read Uri")

    val contentType = context.contentResolver.getType(uri)

    return bucket.upload(path, data) {
        this.contentType = contentType
    }
}
```

***

## uploadWithAutoKey()

上傳具有自動產生的唯一金鑰的檔案。

### 參數

* `filename` (String) - 原始檔案名稱（用於副檔名偵測）
* `data` (ByteArray) - 檔案內容（以位元組形式）
* `options` (DSL block, optional) - 上傳選項

### 範例

```kotlin theme={null}
val imageData = bitmap.toByteArray()
val result = insforge.storage
    .from("uploads")
    .uploadWithAutoKey("photo.jpg", imageData) {
        contentType = "image/jpeg"
    }

Log.d("Storage", "Auto-generated key: ${result.key}")
Log.d("Storage", "URL: ${result.url}")

// Save to database
insforge.database
    .from("posts")
    .insertTyped(listOf(
        Post(
            imageUrl = result.url,
            imageKey = result.key,
            userId = userId
        )
    ))
    .returning()
    .execute<Post>()
```

***

## download()

將檔案下載為 ByteArray。

### 範例

```kotlin theme={null}
// Download file
val data = insforge.storage
    .from("images")
    .download(path = "posts/post-123/cover.jpg")

// Convert to Bitmap
val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size)
imageView.setImageBitmap(bitmap)
```

***

## delete()

從儲存空間中刪除檔案。

### 範例

```kotlin theme={null}
// Get the file key from database
val posts = insforge.database
    .from("posts")
    .select("image_key")
    .eq("id", "post-123")
    .execute<Post>()  // Returns List<Post>

val post = posts.firstOrNull() ?: return

// Delete from storage
insforge.storage
    .from("images")
    .delete(post.imageKey)

// Clear database reference
insforge.database
    .from("posts")
    .update(buildJsonObject {
        put("image_url", JsonNull)
        put("image_key", JsonNull)
    })
    .eq("id", "post-123")
    .execute<Post>()
```

***

## createSignedUrl()

為檔案暫時存取建立簽署的 URL。

### 參數

* `path` (String) - 貯體中的檔案路徑
* `expiresIn` (Int) - 過期時間（以秒為單位）

### 範例

```kotlin theme={null}
// Create a signed URL (expires in 1 hour)
val url = insforge.storage
    .from("images")
    .createSignedUrl("posts/post-123/cover.jpg", expiresIn = 3600)

Log.d("Storage", "Signed URL: $url")
```

***

## getDownloadUrl()

取得檔案的下載 URL。

### 範例

```kotlin theme={null}
val strategy = insforge.storage
    .from("images")
    .getDownloadUrl("posts/post-123/cover.jpg")

Log.d("Storage", "Download URL: ${strategy.url}")
```

***

## list()

列出貯體中具有選擇性篩選的檔案。

### BucketListFilter 選項

| 選項          | 類型        | 說明                 |
| ----------- | --------- | ------------------ |
| `prefix`    | String?   | 按路徑首碼篩選            |
| `limit`     | Int       | 最大結果數（預設：100）      |
| `offset`    | Int       | 分頁位移（預設：0）         |
| `sortBy`    | String?   | 排序欄位               |
| `sortOrder` | SortOrder | ASC 或 DESC（預設：ASC） |

### 範例

```kotlin theme={null}
// List all files in bucket
val files = insforge.storage
    .from("images")
    .list()

files.forEach { file ->
    Log.d("Storage", "File: ${file.name}, Size: ${file.metadata?.size}")
}

// List with filters
val files = insforge.storage
    .from("images")
    .list {
        prefix = "posts/"
        limit = 50
        offset = 0
        sortBy = "created_at"
        sortOrder = SortOrder.DESC
    }
```

***

## 貯體管理

### listBuckets()

列出所有儲存空間貯體。

```kotlin theme={null}
val buckets = insforge.storage.listBuckets()

buckets.forEach { bucket ->
    Log.d("Storage", "Bucket: ${bucket.id}, Public: ${bucket.isPublic}")
}
```

### createBucket()

建立新儲存空間貯體。

```kotlin theme={null}
val bucket = insforge.storage.createBucket("my-bucket") {
    isPublic = true
}

Log.d("Storage", "Created bucket: ${bucket.id}")
```

### updateBucket()

更新貯體設定。

```kotlin theme={null}
insforge.storage.updateBucket("my-bucket") {
    isPublic = false
}
```

### deleteBucket()

刪除儲存空間貯體。

```kotlin theme={null}
insforge.storage.deleteBucket("my-bucket")
```

***

## 進階上傳

### getUploadStrategy()

取得用於大型檔案或可繼續上傳的上傳策略。

```kotlin theme={null}
val strategy = insforge.storage
    .from("videos")
    .getUploadStrategy("videos/large-file.mp4", fileSize = 100_000_000)

// Use strategy.url for upload
Log.d("Storage", "Upload URL: ${strategy.url}")
```

### confirmUpload()

確認已完成的上傳。

```kotlin theme={null}
insforge.storage
    .from("videos")
    .confirmUpload("videos/large-file.mp4")
```
