# File Storage (R2)

Quickback provides built-in file storage using Cloudflare R2, with role-based access control and a dedicated files worker for serving.

## Architecture

```
┌─────────────────────────────────────────────────────────────────────┐
│                         Your Application                            │
│                                                                     │
│  Upload/Manage Files              Serve Files                       │
│  ─────────────────────           ──────────────                     │
│  api.yourdomain.com              files.yourdomain.com               │
│  /storage/v1/*                   /*                                 │
│                                                                     │
│  ┌─────────────────────┐         ┌─────────────────────┐           │
│  │ API Worker          │         │ Files Worker        │           │
│  │                     │         │                     │           │
│  │ POST   /bucket      │         │ GET /public/*       │           │
│  │ GET    /bucket      │         │   → No auth         │           │
│  │ POST   /object/*    │         │                     │           │
│  │ DELETE /object/*    │         │ GET /*              │           │
│  │                     │         │   → Session + RBAC  │           │
│  └──────────┬──────────┘         └──────────┬──────────┘           │
│             │                               │                       │
│             └───────────┬───────────────────┘                       │
│                         │                                           │
│                         ▼                                           │
│              ┌─────────────────────┐                                │
│              │   R2 Bucket         │                                │
│              │   quickback-files   │                                │
│              └─────────────────────┘                                │
└─────────────────────────────────────────────────────────────────────┘
```

## Enabling File Storage

Add the `fileStorage` provider to your `quickback.config.ts`:

```typescript
export default {
  name: 'my-app',
  providers: {
    runtime: { name: 'cloudflare' },
    database: { name: 'cloudflare-d1' },
    auth: { name: 'better-auth' },
    fileStorage: {
      name: 'cloudflare-r2',
      config: {
        binding: 'R2_BUCKET',
        bucketName: 'my-app-files',
        filesBinding: 'FILES_DB',
        maxFileSize: 10 * 1024 * 1024, // 10MB
        allowedTypes: ['image/jpeg', 'image/png', 'image/webp'],
      },
    },
  },
};
```

## API Endpoints

### Storage API (api.yourdomain.com/storage/v1)

| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/bucket` | Create a bucket |
| GET | `/bucket` | List buckets |
| GET | `/bucket/:name` | Get bucket info |
| DELETE | `/bucket/:name` | Delete bucket (must be empty) |
| POST | `/object/:bucket/*path` | Upload file |
| GET | `/object/:bucket/*path` | Download file |
| HEAD | `/object/:bucket/*path` | Get file metadata |
| DELETE | `/object/:bucket/*path` | Delete file (soft delete) |
| GET | `/object` | List objects |
| POST | `/url` | Get file URL for serving |

### Files Worker (files.yourdomain.com)

| Method | Path | Auth Required |
|--------|------|---------------|
| GET/HEAD | `/public/*` | No |
| GET/HEAD | `/*` | Yes (session + RBAC) |

## Buckets

Buckets organize files and define access control policies.

### Creating a Bucket

```bash
POST /storage/v1/bucket
{
  "name": "avatars",
  "readScope": "organization",
  "writeScope": "organization",
  "readRoles": ["admin", "member"],
  "writeRoles": ["admin"],
  "deleteRoles": ["admin"]
}
```

### Scope Options

| Scope | Read Behavior | Write Behavior |
|-------|---------------|----------------|
| `public` | Anyone can read | N/A |
| `organization` | Org members only | Org members only |
| `user` | Owner only | Owner only |

### Role-Based Access

You can restrict operations to specific roles:

```json
{
  "readRoles": ["admin", "member"],
  "writeRoles": ["admin", "editor"],
  "deleteRoles": ["admin"]
}
```

An empty array `[]` means no role restriction (all authenticated users).

## Uploading Files

```bash
POST /storage/v1/object/avatars/profile.jpg
Content-Type: image/jpeg
Content-Length: 12345

<binary data>
```

Response:
```json
{
  "id": "obj_123",
  "key": "org_abc/avatars/profile.jpg",
  "bucket": "avatars",
  "name": "profile.jpg",
  "size": 12345,
  "mimeType": "image/jpeg",
  "readScope": "organization"
}
```

## Serving Files

### Public Files

Files in buckets with `readScope: "public"` are stored with a `public/` prefix:

```
https://files.yourdomain.com/public/org_abc/avatars/logo.png
```

No authentication required.

### Private Files

Private files require a valid Better Auth session cookie:

```
https://files.yourdomain.com/org_abc/documents/report.pdf
```

The files worker:
1. Validates the session token
2. Checks the user's organization matches
3. Verifies role permissions (if `readRoles` configured)
4. Serves the file or returns 403

## Generated Files

When file storage is configured, the compiler generates:

| File | Purpose |
|------|---------|
| `src/routes/storage.ts` | Storage API routes |
| `src/files.ts` | Files database schema (buckets, objects) |
| `cloudflare-workers/files/index.ts` | Files worker for serving |
| `cloudflare-workers/files/wrangler.toml` | Files worker config |

## Deployment

After compiling:

1. **Create the R2 bucket:**
   ```bash
   wrangler r2 bucket create my-app-files
   ```

2. **Create the files database:**
   ```bash
   wrangler d1 create my-app-files
   ```

3. **Run migrations:**
   ```bash
   wrangler d1 migrations apply my-app-files --local
   wrangler d1 migrations apply my-app-files --remote
   ```

4. **Deploy the API:**
   ```bash
   wrangler deploy
   ```

5. **Deploy the files worker:**
   ```bash
   cd cloudflare-workers/files
   wrangler deploy
   ```

## Configuration Reference

| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `binding` | `string` | `R2_BUCKET` | R2 bucket binding name |
| `bucketName` | `string` | Required | R2 bucket name |
| `filesBinding` | `string` | `FILES_DB` | Files metadata D1 binding |
| `maxFileSize` | `number` | 10MB | Max upload size in bytes |
| `allowedTypes` | `string[]` | Images only | Allowed MIME types |
| `publicDomain` | `string` | - | Custom domain for files worker |

## Security Model

- **Upload security**: Enforced by API worker (auth middleware, org check, role check)
- **Serve security**: Enforced by files worker (session validation, metadata RBAC)
- **Soft deletes**: Files are marked deleted in metadata but retained in R2
- **Tenant isolation**: All files are prefixed with organization ID
