# upcloud

A powerful and user friendly npm package for smooth file management and uploads with Cloudinary that integrates Multer to process incoming file requests.

## Badges

![TypeScript](https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white)
![JavaScript](https://img.shields.io/badge/JavaScript-F7DF1E?style=for-the-badge&logo=javascript&logoColor=black)
![npm](https://img.shields.io/badge/npm-CB3837?style=for-the-badge&logo=npm&logoColor=white)
![Cloudinary](https://img.shields.io/badge/Cloudinary-3448C5?style=for-the-badge&logo=cloudinary&logoColor=white)
![Multer](https://img.shields.io/badge/Multer-000000?style=for-the-badge&logo=npm&logoColor=white)
![Files](https://img.shields.io/badge/Files-4285F4?style=for-the-badge&logo=google-drive&logoColor=white)
![upcloud](https://img.shields.io/badge/upcloud-007ACC?style=for-the-badge&logo=npm&logoColor=white)

## Key Features

- **Effortless Cloudinary Integration**: Easily upload and manage files on Cloudinary
- **Multer Middleware**: Seamlessly integrate with Express.js for handling `multipart/form-data` primarily for file uploads
- **Flexible Upload Options**: Supports single and multiple file uploads with options for specifying folders and resource types
- **File Deletion**: Conveniently delete files from Cloudinary using their public IDs
- **Comprehensive Error Handling**: Provides detailed error responses for robust application development
- **Multiple Upload Methods**: Supports uploading from local paths, buffers, and streams, including a dedicated function for large files
- **Dual Module Support**: Works seamlessly with both CommonJS (`require`) and ES Modules (`import`)

## Installation

You can install `upcloud` using npm or yarn:

```bash
npm install upcloud
# or
yarn add upcloud
```

Additionally, for Express.js integration, you'll need `express` and `dotenv` (for environment variables):

```bash
npm install express dotenv
```

## Usage

### 1. Configure Cloudinary

Before using any upload functions, you need to configure your Cloudinary credentials. It's recommended to use environment variables.

**Create a `.env` file in your project root:**

```dotenv
CLOUDINARY_CLOUD_NAME=your_cloud_name
CLOUDINARY_API_KEY=your_api_key
CLOUDINARY_API_SECRET=your_api_secret
```

**CommonJS (`.js` file):**

```javascript
const { configureCloudinary } = require("upcloud");
const dotenv = require("dotenv");

dotenv.config();

configureCloudinary({
  cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
  api_key: process.env.CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET,
});
```

**ES Modules (`.mjs` file):**

```javascript
import { configureCloudinary } from "upcloud";
import dotenv from "dotenv";

dotenv.config();

configureCloudinary({
  cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
  api_key: process.env.CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET,
});
```

### 2. Express.js Integration with Multer

This demonstrates setting up an Express server to handle file uploads via HTTP POST requests.

**CommonJS (`server.js`):**

```javascript
const express = require("express");
const dotenv = require("dotenv");
const fs = require("fs/promises");
const {
  configureCloudinary,
  createMulterDiskMiddleware,
  uploadFile,
  uploadMultipleFiles,
} = require("upcloud");

dotenv.config();
configureCloudinary({
  cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
  api_key: process.env.CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET,
});

const app = express();
const port = 3000;
const upload = createMulterDiskMiddleware({ dest: "./uploads" });

fs.mkdir("./uploads", { recursive: true }).catch(console.error);

// single file upload
app.post("/upload-single", upload.single("image"), async (req, res) => {
  if (!req.file) return res.status(400).json({ message: "No file uploaded" });
  try {
    const result = await uploadFile(req.file.path, {
      folder: "express-uploads",
    });
    await fs.unlink(req.file.path);
    res.status(result.statusCode).json(result);
  } catch (error) {
    console.error("Upload error:", error);
    res
      .status(500)
      .json({ message: "File upload failed", error: error.message });
  }
});

// multiple files upload
app.post("/upload-multiple", upload.array("images", 5), async (req, res) => {
  if (!req.files || req.files.length === 0)
    return res.status(400).json({ message: "No files uploaded" });
  const filePaths = req.files.map((file) => file.path);
  try {
    const results = await uploadMultipleFiles(filePaths, {
      folder: "express-multiple-uploads",
    });
    await Promise.all(filePaths.map((p) => fs.unlink(p)));
    res.status(200).json({
      status: true,
      statusCode: 200,
      message: "Files uploaded successfully",
      data: results,
    });
  } catch (error) {
    console.error("Multiple upload error:", error);
    res
      .status(500)
      .json({ message: "Multiple file upload failed", error: error.message });
  }
});

app.listen(port, () =>
  console.log(`Server running on http://localhost:${port}`)
);
```

**ES Modules (`server.mjs`):**

```javascript
import express from "express";
import dotenv from "dotenv";
import fs from "fs/promises";
import { fileURLToPath } from "url";
import path from "path";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

import {
  configureCloudinary,
  createMulterDiskMiddleware,
  uploadFile,
  uploadMultipleFiles,
} from "upcloud";

dotenv.config();
configureCloudinary({
  cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
  api_key: process.env.CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET,
});

const app = express();
const port = 3000;
const upload = createMulterDiskMiddleware({ dest: "./uploads" });

fs.mkdir("./uploads", { recursive: true }).catch(console.error);

app.post("/upload-single", upload.single("image"), async (req, res) => {
  if (!req.file) return res.status(400).json({ message: "No file uploaded" });
  try {
    const result = await uploadFile(req.file.path, {
      folder: "express-uploads-esm",
    });
    await fs.unlink(req.file.path);
    res.status(result.statusCode).json(result);
  } catch (error) {
    console.error("Upload error:", error);
    res
      .status(500)
      .json({ message: "File upload failed", error: error.message });
  }
});

app.post("/upload-multiple", upload.array("images", 5), async (req, res) => {
  if (!req.files || req.files.length === 0)
    return res.status(400).json({ message: "No files uploaded" });
  const filePaths = req.files.map((file) => file.path);
  try {
    const results = await uploadMultipleFiles(filePaths, {
      folder: "express-multiple-uploads-esm",
    });
    await Promise.all(filePaths.map((p) => fs.unlink(p)));
    res.status(200).json({
      status: true,
      statusCode: 200,
      message: "Files uploaded successfully",
      data: results,
    });
  } catch (error) {
    console.error("Multiple upload error:", error);
    res
      .status(500)
      .json({ message: "Multiple file upload failed", error: error.message });
  }
});

app.listen(port, () =>
  console.log(`Server running on http://localhost:${port}`)
);
```

**To test the Express routes:**

1.  Start the server: `node server.js` (for CommonJS) or `node server.mjs` (for ES Modules)
2.  Use `curl` or a tool like Postman/Insomnia to send `multipart/form-data` requests
    - **Single file:** `curl -X POST -F 'image=@./path/to/your/image.jpg' http://localhost:3000/upload-single`
    - **Multiple files:** `curl -X POST -F 'images=@./path/to/doc1.pdf' -F 'images=@./path/to/doc2.pdf' http://localhost:3000/upload-multiple`

### 3. Direct File Operations

These examples show how to use the `upcloud` functions directly without an Express server.

**CommonJS (`app.js`):**

```javascript
const {
  configureCloudinary,
  uploadFile,
  uploadMultipleFiles,
  deleteFile,
} = require("upcloud");
const {
  uploadFromPath,
  uploadBuffer,
  uploadStream,
  uploadLarge,
  cloudinary,
} = require("upcloud/cloudinaryClient");
const dotenv = require("dotenv");
const path = require("path");
const fs = require("fs");

dotenv.config();
configureCloudinary({
  cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
  api_key: process.env.CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET,
});

async function runDirectExamples() {
  const imagePath = path.join(__dirname, "test-image.jpg");
  const docPaths = [
    path.join(__dirname, "test-doc1.pdf"),
    path.join(__dirname, "test-doc2.pdf"),
  ];
  const videoPath = path.join(__dirname, "test-video.mp4");

  let uploadedPublicId = null;

  // Example: Upload Single File
  try {
    const response = await uploadFile(imagePath, { folder: "direct-commonjs" });
    console.log("Upload successful:", response.data[0].secure_url);
    uploadedPublicId = response.data[0].public_id;
  } catch (error) {
    console.error("Upload failed:", error.message);
  }

  // Example: Upload Multiple Files
  try {
    const results = await uploadMultipleFiles(docPaths, {
      folder: "direct-commonjs-docs",
    });
    results.forEach((res) => console.log(res.secure_url));
  } catch (error) {
    console.error("Multiple uploads failed:", error.message);
  }

  // Example: Upload from Buffer
  try {
    const buffer = Buffer.from("This is a test text file from a buffer.");
    const result = await uploadBuffer(buffer, {
      resource_type: "raw",
      public_id: "buffer-test-commonjs",
    });
    console.log("Buffer upload successful:", result.secure_url);
  } catch (error) {
    console.error("Buffer upload failed:", error.message);
  }

  // Example: Upload from Stream
  try {
    const readableStream = fs.createReadStream(videoPath);
    const result = await uploadStream(readableStream, {
      resource_type: "video",
      folder: "stream-commonjs",
    });
    console.log("Stream upload successful:", result.secure_url);
  } catch (error) {
    console.error("Stream upload failed:", error.message);
  }

  // Example: Delete File
  if (uploadedPublicId) {
    try {
      const result = await deleteFile(uploadedPublicId);
      console.log("Deletion successful:", result);
    } catch (error) {
      console.error("Deletion failed:", error.message);
    }
  } else {
  }

  // Example: Using raw cloudinary instance
  try {
    const resources = await cloudinary.api.resources({
      type: "upload",
      max_results: 3,
    });
    console.log(
      "First 3 uploaded resources:",
      resources.resources.map((r) => r.public_id)
    );
  } catch (error) {
    console.error("Failed to list resources:", error.message);
  }
}

runDirectExamples();
```

**ES Modules (`app.mjs`):**

```javascript
import {
  configureCloudinary,
  uploadFile,
  uploadMultipleFiles,
  deleteFile,
} from "upcloud";
import {
  uploadFromPath,
  uploadBuffer,
  uploadStream,
  uploadLarge,
  cloudinary,
} from "upcloud/cloudinaryClient";
import dotenv from "dotenv";
import path from "path";
import fs from "fs";
import { fileURLToPath } from "url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

dotenv.config();
configureCloudinary({
  cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
  api_key: process.env.CLOUDINARY_API_KEY,
  api_secret: process.env.CLOUDINARY_API_SECRET,
});

async function runDirectExamples() {
  const imagePath = path.join(__dirname, "test-image.jpg");
  const docPaths = [
    path.join(__dirname, "test-doc1.pdf"),
    path.join(__dirname, "test-doc2.pdf"),
  ];
  const videoPath = path.join(__dirname, "test-video.mp4");

  let uploadedPublicId = null;

  try {
    const response = await uploadFile(imagePath, { folder: "direct-esm" });
    console.log("Upload successful:", response.data[0].secure_url);
    uploadedPublicId = response.data[0].public_id;
  } catch (error) {
    console.error("Upload failed:", error.message);
  }

  try {
    const results = await uploadMultipleFiles(docPaths, {
      folder: "direct-esm-docs",
    });
    results.forEach((res) => console.log(res.secure_url));
  } catch (error) {
    console.error("Multiple uploads failed:", error.message);
  }

  try {
    const buffer = Buffer.from("This is a test text file from a buffer (ESM).");
    const result = await uploadBuffer(buffer, {
      resource_type: "raw",
      public_id: "buffer-test-esm",
    });
    console.log("Buffer upload successful:", result.secure_url);
  } catch (error) {
    console.error("Buffer upload failed:", error.message);
  }

  try {
    const readableStream = fs.createReadStream(videoPath);
    const result = await uploadStream(readableStream, {
      resource_type: "video",
      folder: "stream-esm",
    });
    console.log("Stream upload successful:", result.secure_url);
  } catch (error) {
    console.error("Stream upload failed:", error.message);
  }

  // Example: Delete File
  if (uploadedPublicId) {
    try {
      const result = await deleteFile(uploadedPublicId);
      console.log("Deletion successful:", result);
    } catch (error) {
      console.error("Deletion failed:", error.message);
    }
  } else {
  }

  // Example: Using raw cloudinary instance
  try {
    const resources = await cloudinary.api.resources({
      type: "upload",
      max_results: 3,
    });
    console.log(
      "First 3 uploaded resources:",
      resources.resources.map((r) => r.public_id)
    );
  } catch (error) {
    console.error("Failed to list resources:", error.message);
  }
}

runDirectExamples();
```

**To run these direct usage examples:**

- For CommonJS: `node app.js`
- For ES Modules: `node app.mjs`

## Available Functions and Options

| Function / Interface         | Description                                                                                       | Parameters                                                | Returns                                                                  |
| :--------------------------- | :------------------------------------------------------------------------------------------------ | :-------------------------------------------------------- | :----------------------------------------------------------------------- |
| `configureCloudinary`        | Configures the Cloudinary SDK with your credentials.                                              | `config: CloudinaryConfig`                                | `void`                                                                   |
| `createMulterDiskMiddleware` | Creates a Multer middleware instance configured for disk storage.                                 | `options?: multer.Options` (Multer options)               | `multer.Multer` instance                                                 |
| `uploadFile`                 | Uploads a single file from a local path to Cloudinary.                                            | `filePath: string`, `options?: UploadApiOptions`          | `{ status: boolean; statusCode: number; message: string; data: any[]; }` |
| `uploadMultipleFiles`        | Uploads an array of files from local paths to Cloudinary.                                         | `filePaths: string[]`, `options?: UploadApiOptions`       | `Promise<UploadApiResponse[]>`                                           |
| `deleteFile`                 | Deletes a file from Cloudinary using its public ID.                                               | `publicId: string`, `options?: UploadApiOptions`          | `Promise<UploadApiResponse>`                                             |
| `uploadFromPath`             | (Advanced) Uploads a file from a local path. Returns raw Cloudinary response.                     | `localPath: string`, `opts?: Record<string, any>`         | `Promise<UploadResult>`                                                  |
| `uploadBuffer`               | (Advanced) Uploads a file from a Buffer. Returns raw Cloudinary response.                         | `buffer: Buffer`, `opts?: Record<string, any>`            | `Promise<UploadResult>`                                                  |
| `uploadStream`               | (Advanced) Uploads a file from a Readable Stream. Returns raw Cloudinary response.                | `readable: stream.Readable`, `opts?: Record<string, any>` | `Promise<UploadResult>`                                                  |
| `uploadLarge`                | (Advanced) Uploads a very large file (>100MB) from a local path. Returns raw Cloudinary response. | `localPath: string`, `opts?: Record<string, any>`         | `Promise<UploadResult>`                                                  |
| `cloudinary`                 | (Advanced) The raw configured Cloudinary SDK instance for direct API calls.                       | N/A                                                       | `cloudinary.v2` instance                                                 |

---

#### `CloudinaryConfig` Interface

| Property     | Type     | Description                 | Required |
| :----------- | :------- | :-------------------------- | :------- |
| `cloud_name` | `string` | Your Cloudinary cloud name. | Yes      |
| `api_key`    | `string` | Your Cloudinary API key.    | Yes      |
| `api_secret` | `string` | Your Cloudinary API secret. | Yes      |

#### `UploadApiOptions` Interface

| Property        | Type                                                     | Description                                                                                       | Required |
| :-------------- | :------------------------------------------------------- | :------------------------------------------------------------------------------------------------ | :------- |
| `folder`        | `string`                                                 | The name of the folder in your Cloudinary account to upload the file to.                          | No       |
| `resource_type` | `ResourceType` (`"image" \| "video" \| "raw" \| "auto"`) | The type of resource being uploaded. Defaults to `"image"`. Use `"auto"` for automatic detection. | No       |

#### `UploadApiResponse` Interface

| Property      | Type     | Description                                  |
| :------------ | :------- | :------------------------------------------- |
| `public_id`   | `string` | The public ID of the uploaded asset.         |
| `secure_url`  | `string` | The secure URL of the uploaded asset.        |
| `[k: string]` | `any`    | Other properties returned by Cloudinary API. |

## Contribution

Contributions are welcome. If you have suggestions for improvements, bug fixes or new features please open an issue or submit a pull request.

1.  Fork the repository
2.  Create a new branch (`git checkout -b feature/your-feature`)
3.  Make your changes
4.  Commit your changes (`git commit -m 'Add new feature'`)
5.  Push to the branch (`git push origin feature/your-feature`)
6.  Open a pull request

## License

This project is licensed under the MIT License - see the [LICENSE](https://mit-license.org) file for details.
