crypsol_storage 0.1.0

AWS S3 storage library for Rust services with image processing, validation, and thumbnail generation.
Documentation
# Crypsol Storage

A production-ready AWS S3 storage library for Rust services with image processing, validation, and thumbnail generation.

[![Crates.io](https://img.shields.io/crates/v/crypsol_storage.svg)](https://crates.io/crates/crypsol_storage)
[![Documentation](https://docs.rs/crypsol_storage/badge.svg)](https://docs.rs/crypsol_storage)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

## Features

- **Image Upload with Processing**: Automatic resizing and thumbnail generation
- **Multiple Format Support**: JPEG, PNG, GIF, WebP
- **High-Quality Resizing**: Uses Lanczos3 filter for optimal quality
- **Configurable Dimensions**: Customize image and thumbnail sizes
- **File Validation**: Content-type and file size validation
- **Presigned URLs**: Generate temporary access URLs for private objects
- **Async/Await**: Built on Tokio for high-performance async operations
- **Serde Support**: Optional serialization support for API responses

## Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
crypsol_storage = "0.1"
```

With Serde support:

```toml
[dependencies]
crypsol_storage = { version = "0.1", features = ["serde"] }
```

## Environment Variables

| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `S3_AWS_ACCESS_KEY` | Yes | - | AWS Access Key ID |
| `S3_AWS_SECRET_KEY` | Yes | - | AWS Secret Access Key |
| `S3_AWS_REGION` | No | `us-east-1` | AWS Region |
| `S3_BUCKET_NAME` | No | `crypsol-storage` | S3 Bucket Name |
| `S3_PUBLIC_BASE_URL` | No | Auto-generated | CDN or custom domain URL |
| `S3_MAX_FILE_SIZE_MB` | No | `5` | Maximum file size in MB |

## Quick Start

### Upload Image with Thumbnail

```rust
use crypsol_storage::{upload_image_with_config, ImageUploadConfig, Error};

#[tokio::main]
async fn main() -> Result<(), Error> {
    // Read image bytes
    let image_data = std::fs::read("photo.jpg")?;
    
    // Upload with default config (200x200 main, 50x50 thumbnail)
    let config = ImageUploadConfig::default();
    let result = upload_image_with_config(&image_data, "image/jpeg", &config).await?;
    
    println!("Image URL: {}", result.url);
    println!("Thumbnail URL: {}", result.thumbnail_url);
    
    Ok(())
}
```

### Upload with Custom Dimensions

```rust
use crypsol_storage::{upload_image_with_config, ImageUploadConfig};

let config = ImageUploadConfig {
    width: 800,
    height: 600,
    thumbnail_width: 150,
    thumbnail_height: 150,
    folder: "products".to_string(),
    maintain_aspect_ratio: true,
};

let result = upload_image_with_config(&image_data, "image/png", &config).await?;
```

### Upload with Custom S3 Key

```rust
use crypsol_storage::upload_image_with_key;

// Full control over the S3 path
let result = upload_image_with_key(
    &image_data,
    "image/jpeg",
    "users/123/profile.jpg",  // Custom key
    200,   // width
    200,   // height
    50,    // thumbnail_width
    50,    // thumbnail_height
).await?;
```

### Upload Raw File (No Processing)

```rust
use crypsol_storage::upload_file;

let pdf_data = std::fs::read("document.pdf")?;
let result = upload_file(&pdf_data, "application/pdf", "documents", "pdf").await?;

println!("File URL: {}", result.url);
```

### Delete Image with Thumbnail

```rust
use crypsol_storage::delete_image_with_thumbnail;

// Deletes both main image and thumbnail
delete_image_with_thumbnail("profiles/2025/01/21/abc123.jpg").await?;
```

### Check if File Exists

```rust
use crypsol_storage::file_exists;

if file_exists("profiles/2025/01/21/abc123.jpg").await? {
    println!("File exists!");
}
```

### Generate Presigned URL

```rust
use crypsol_storage::generate_presigned_url;

// Generate URL valid for 1 hour (3600 seconds)
let url = generate_presigned_url("private/document.pdf", 3600).await?;
println!("Temporary URL: {}", url);
```

### Extract Key from URL

```rust
use crypsol_storage::extract_key_from_url;

let url = "https://bucket.s3.us-east-1.amazonaws.com/profiles/image.jpg";
if let Some(key) = extract_key_from_url(url) {
    println!("Key: {}", key);  // "profiles/image.jpg"
}
```

## Validation

### Content Type Validation

```rust
use crypsol_storage::{validate_content_type, ALLOWED_IMAGE_TYPES};

// Check if content type is allowed
validate_content_type("image/jpeg")?;  // Ok
validate_content_type("application/pdf")?;  // Error

// Allowed types: image/jpeg, image/png, image/gif, image/webp
println!("Allowed types: {:?}", ALLOWED_IMAGE_TYPES);
```

### File Size Validation

```rust
use crypsol_storage::{validate_file_size, get_max_file_size};

let file_size = 2 * 1024 * 1024;  // 2MB
validate_file_size(file_size)?;  // Ok if under limit

println!("Max size: {} bytes", get_max_file_size());
```

## Error Handling

```rust
use crypsol_storage::Error;

match upload_image_with_config(&data, content_type, &config).await {
    Ok(result) => println!("Uploaded: {}", result.url),
    Err(Error::InvalidFileType(got, allowed)) => {
        println!("Invalid type: {}. Allowed: {}", got, allowed);
    }
    Err(Error::FileTooLarge(size, max)) => {
        println!("File too large: {} bytes (max: {})", size, max);
    }
    Err(Error::ImageProcessing(msg)) => {
        println!("Image processing failed: {}", msg);
    }
    Err(Error::AwsS3(msg)) => {
        println!("S3 error: {}", msg);
    }
    Err(e) => println!("Error: {}", e),
}
```

## S3 Bucket Configuration

### Required IAM Permissions

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:DeleteObject",
                "s3:HeadObject"
            ],
            "Resource": "arn:aws:s3:::your-bucket-name/*"
        }
    ]
}
```

### CORS Configuration (for browser uploads)

```json
[
    {
        "AllowedHeaders": ["*"],
        "AllowedMethods": ["GET", "PUT", "POST", "DELETE", "HEAD"],
        "AllowedOrigins": ["https://your-domain.com"],
        "ExposeHeaders": ["ETag"],
        "MaxAgeSeconds": 3600
    }
]
```

## License

MIT License - see [LICENSE](LICENSE) for details.

## Author

Zuhair Thabit - [admin@crypsol.tech](mailto:admin@crypsol.tech)