# Ikat Storage SDK for Next.js

A modern, TypeScript-first SDK for Ikat file storage API with automatic image optimization.

## Features

- ✅ **Automatic Image Optimization** - WebP conversion in 3 sizes (large, small, thumb)
- ✅ **File Protection** - Public or protected file access control
- ✅ **Type-Safe** - Full TypeScript support with detailed types
- ✅ **Bucket Management** - Organize files in buckets
- ✅ **Batch Operations** - Upload/delete multiple files
- ✅ **Quota Tracking** - Monitor storage usage
- ✅ **Next.js Optimized** - Works seamlessly with Next.js API routes

## Installation

```bash
npm install axios form-data
# or
yarn add axios form-data
```

Copy `ikat-storage.ts` to your `lib` folder.

## Environment Variables

Create a `.env.local` file:

```env
STORAGE_BASE_URL=https://your-ikat-instance.com
STORAGE_SECRET_KEY=your_api_key_here
STORAGE_BUCKET_NAME=default-bucket
STORAGE_ORIGIN=https://yourapp.com  # Optional: for CORS restriction
```

## Quick Start

### Basic Usage

```typescript
import { uploadFile, listFiles, removeFile } from '@/lib/ikat-storage';

// Upload a file (public by default)
const result = await uploadFile(file);
console.log(result.urls.original); // Original file
console.log(result.urls.thumb);    // Thumbnail (300px)
console.log(result.urls.small);    // Small (800px)
console.log(result.urls.large);    // Large (1920px)

// List all files
const files = await listFiles();
console.log(files.files);

// Delete a file
await removeFile('photo.jpg');
```

### Protected Files

```typescript
// Upload a protected file (requires authentication)
const result = await uploadFile(file, {
  allowPublicAccess: false
});

// To access protected files, include Authorization header:
// fetch(url, { headers: { 'Authorization': 'Bearer YOUR_API_KEY' } })
```

### Using the Class Instance

```typescript
import storage from '@/lib/ikat-storage';

// Upload with custom options
const result = await storage.upload(file, {
  allowPublicAccess: false,
  bucket: 'custom-bucket'
});

// Get file metadata
const fileInfo = await storage.get('photo.jpg');
console.log(fileInfo.allowPublicAccess); // true or false
console.log(fileInfo.size); // File size in bytes

// Check storage usage
const usage = await storage.getUsage();
console.log(`Using ${usage.data.percentUsed}% of quota`);
```

## API Reference

### `uploadFile(file, options?)`

Upload a file with automatic image optimization.

**Parameters:**
- `file: File` - The file to upload
- `options?: UploadOptions`
  - `allowPublicAccess?: boolean` - Default: `true`. Set to `false` for protected files
  - `bucket?: string` - Custom bucket name (overrides default)

**Returns:** `Promise<ImageUploadResponse | FileUploadResponse>`

**Example:**
```typescript
// Public image upload
const result = await uploadFile(imageFile);
console.log(result.urls);
// {
//   original: "https://ikat.id/userId/bucket/uuid.jpg",
//   large: "https://ikat.id/userId/bucket/uuid-large.webp",
//   small: "https://ikat.id/userId/bucket/uuid-small.webp",
//   thumb: "https://ikat.id/userId/bucket/uuid-thumb.webp"
// }

// Protected PDF upload
const result = await uploadFile(pdfFile, { allowPublicAccess: false });
console.log(result.urls);
// {
//   original: "https://ikat.id/userId/bucket/uuid.pdf"
// }
```

---

### `listFiles(bucketName?)`

List all files in a bucket.

**Parameters:**
- `bucketName?: string` - Optional bucket name (defaults to configured bucket)

**Returns:** `Promise<ListFilesResponse>`

**Example:**
```typescript
const response = await listFiles();
console.log(response.files);
// [
//   {
//     id: "uuid",
//     original: "photo.jpg",
//     key: "uuid.jpg",
//     mimetype: "image/jpeg",
//     size: 1234567,
//     bucket: "images",
//     allowPublicAccess: true,
//     createdAt: "2025-11-11T12:00:00.000Z",
//     url: "https://ikat.id/userId/images/uuid.jpg"
//   }
// ]
```

---

### `getFile(key, bucketName?)`

Get specific file metadata.

**Parameters:**
- `key: string` - File key or full URL
- `bucketName?: string` - Optional bucket name

**Returns:** `Promise<FileMetadata>`

**Example:**
```typescript
const file = await getFile('photo.jpg');
console.log(file.allowPublicAccess); // true or false
console.log(file.mimetype); // "image/jpeg"
```

---

### `removeFile(key, bucketName?)`

Delete a file and all its versions (WebP).

**Parameters:**
- `key: string` - File key or full URL
- `bucketName?: string` - Optional bucket name

**Returns:** `Promise<DeleteResponse>`

**Example:**
```typescript
await removeFile('photo.jpg');
// or
await removeFile('https://ikat.id/userId/bucket/photo.jpg');
```

---

### `removeBucket(bucketName?)`

Delete an entire bucket with all files.

**Parameters:**
- `bucketName?: string` - Optional bucket name

**Returns:** `Promise<DeleteBucketResponse>`

**Example:**
```typescript
const result = await removeBucket('old-bucket');
console.log(`Deleted ${result.filesDeleted} files`);
```

---

### `replaceFile(file, oldUrl?, options?)`

Replace an old file with a new one. Continues uploading even if deletion fails.

**Parameters:**
- `file: File` - New file to upload
- `oldUrl?: string | null` - URL of old file to delete
- `options?: UploadOptions` - Upload options for new file

**Returns:** `Promise<ImageUploadResponse | FileUploadResponse>`

**Example:**
```typescript
await replaceFile(
  newFile,
  'https://ikat.id/userId/bucket/old-photo.jpg',
  { allowPublicAccess: true }
);
```

---

### `uploadMultipleFiles(files, options?)`

Upload multiple files at once.

**Parameters:**
- `files: File[]` - Array of files
- `options?: UploadOptions` - Options applied to all files

**Returns:** `Promise<Array<{ file: string; success: boolean; data?: any; error?: string }>>`

**Example:**
```typescript
const results = await uploadMultipleFiles([file1, file2, file3]);
const successful = results.filter(r => r.success);
const failed = results.filter(r => !r.success);
console.log(`Uploaded ${successful.length}, Failed ${failed.length}`);
```

---

### `deleteMultipleFiles(keys, bucketName?)`

Delete multiple files at once.

**Parameters:**
- `keys: string[]` - Array of file keys or URLs
- `bucketName?: string` - Optional bucket name

**Returns:** `Promise<Array<{ key: string; success: boolean; error?: string }>>`

**Example:**
```typescript
const results = await deleteMultipleFiles([
  'file1.jpg',
  'https://ikat.id/userId/bucket/file2.png'
]);
```

---

### `getUsage()`

Get storage usage and quota information.

**Returns:** `Promise<UsageResponse>`

**Example:**
```typescript
const usage = await getUsage();
console.log(`Used: ${usage.data.usedQuota} bytes`);
console.log(`Total: ${usage.data.totalQuota} bytes`);
console.log(`Remaining: ${usage.data.remainingQuota} bytes`);
console.log(`Percent: ${usage.data.percentUsed}%`);
```

---

## Next.js API Route Example

```typescript
// app/api/upload/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { uploadFile } from '@/lib/ikat-storage';

export async function POST(request: NextRequest) {
  try {
    const formData = await request.formData();
    const file = formData.get('file') as File;
    const isPublic = formData.get('public') === 'true';

    if (!file) {
      return NextResponse.json(
        { error: 'No file provided' },
        { status: 400 }
      );
    }

    const result = await uploadFile(file, {
      allowPublicAccess: isPublic
    });

    return NextResponse.json(result);
  } catch (error) {
    console.error('Upload error:', error);
    return NextResponse.json(
      { error: 'Upload failed' },
      { status: 500 }
    );
  }
}
```

## Client Component Example

```typescript
'use client';

import { useState } from 'react';

export default function UploadForm() {
  const [file, setFile] = useState<File | null>(null);
  const [uploading, setUploading] = useState(false);
  const [result, setResult] = useState<any>(null);

  const handleUpload = async () => {
    if (!file) return;

    setUploading(true);
    const formData = new FormData();
    formData.append('file', file);
    formData.append('public', 'true');

    try {
      const res = await fetch('/api/upload', {
        method: 'POST',
        body: formData,
      });

      const data = await res.json();
      setResult(data);
    } catch (error) {
      console.error('Upload error:', error);
    } finally {
      setUploading(false);
    }
  };

  return (
    <div>
      <input
        type="file"
        onChange={(e) => setFile(e.target.files?.[0] || null)}
      />
      <button onClick={handleUpload} disabled={!file || uploading}>
        {uploading ? 'Uploading...' : 'Upload'}
      </button>

      {result && (
        <div>
          <h3>Upload Successful!</h3>
          {result.urls.thumb && (
            <div>
              <img src={result.urls.thumb} alt="Thumbnail" />
              <p>Thumbnail: {result.urls.thumb}</p>
              <p>Small: {result.urls.small}</p>
              <p>Large: {result.urls.large}</p>
              <p>Original: {result.urls.original}</p>
            </div>
          )}
        </div>
      )}
    </div>
  );
}
```

## Image Optimization

When you upload an image, Ikat automatically generates 4 versions:

| Version | Max Width | Format | Use Case |
|---------|-----------|--------|----------|
| `original` | Original size | Original format | Downloads, archives |
| `large` | 1920px | WebP 80% | Desktop displays |
| `small` | 800px | WebP 80% | Mobile, tablets |
| `thumb` | 300px | WebP 80% | Thumbnails, previews |

**Best Practices:**
- Use `thumb` for grid listings (~5-10KB)
- Use `small` for mobile content (~20-50KB)
- Use `large` for desktop hero images (~100-200KB)
- Use `original` for downloads and print

## File Protection

### Public Files (`allowPublicAccess: true`)
- Accessible by anyone with the URL
- No authentication required
- Ideal for: public images, downloads, CDN assets

### Protected Files (`allowPublicAccess: false`)
- Requires authentication (API key or session)
- Returns 404 for unauthorized access (hides file existence)
- Ideal for: private documents, user-specific content

**Accessing Protected Files:**
```typescript
// Server-side with API key
const response = await fetch(protectedFileUrl, {
  headers: {
    'Authorization': `Bearer ${process.env.STORAGE_SECRET_KEY}`
  }
});

// Client-side (requires user to be logged in with session)
const response = await fetch(protectedFileUrl);
```

## Error Handling

```typescript
import { uploadFile } from '@/lib/ikat-storage';

try {
  const result = await uploadFile(file);
  console.log('Success:', result);
} catch (error) {
  if (error instanceof Error) {
    if (error.message.includes('QUOTA_EXCEEDED')) {
      console.error('Storage quota exceeded');
    } else if (error.message.includes('UNAUTHORIZED')) {
      console.error('Invalid API key');
    } else {
      console.error('Upload failed:', error.message);
    }
  }
}
```

## TypeScript Types

All types are exported from the SDK:

```typescript
import {
  IkatStorage,
  UploadOptions,
  ImageUploadResponse,
  FileUploadResponse,
  FileMetadata,
  ListFilesResponse,
  DeleteResponse,
  DeleteBucketResponse,
  UsageResponse
} from '@/lib/ikat-storage';
```

## Rate Limits

Be aware of API rate limits:
- Upload: 120 requests/minute
- List: 120 requests/minute
- Delete: 50 requests/minute
- Usage: 100 requests/minute

Implement exponential backoff for 429 errors.

## Support

For issues or questions:
- Email: hi@liupurnomo.com
- Documentation: https://your-ikat-instance.com/docs

## License

MIT
