offline-cloudinary 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +90 -0
- package/README.md +135 -16
- package/index.js +21 -0
- package/package.json +25 -7
- package/src/controllers/fileControllers.js +10 -0
- package/src/routes/fileRoutes.js +8 -0
- package/src/{index.js → utils/offline-cloudinary.js} +30 -16
- package/src/utils/uploads.json +1 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [2.0.0] - 2025-12-15
|
|
9
|
+
|
|
10
|
+
### Breaking Changes
|
|
11
|
+
|
|
12
|
+
- **Main export location changed**: Moved from `src/index.js` to root `index.js`
|
|
13
|
+
- **Port configuration**: Now requires `CLOUDINARY_OFFLINE_PORT` environment variable
|
|
14
|
+
- **Public ID format**: `public_id` is now a UUID instead of a file path
|
|
15
|
+
- **Upload response URL**: The `url` field now returns an HTTP endpoint (`http://localhost:PORT/file/{id}`) instead of a file path
|
|
16
|
+
- **Secure URL**: The `secure_url` field also now returns an HTTP endpoint (`http://localhost:PORT/file/{id}`) instead of a file path
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **HTTP Server Emulator**: New `startEmulator()` function to run a local Express server that serves uploaded files
|
|
21
|
+
- **File viewing endpoint**: GET `/file/:id` endpoint to view/download uploaded files via HTTP
|
|
22
|
+
- **Upload tracking**: Internal `uploads.json` file maps UUIDs to file paths
|
|
23
|
+
- **New environment variable**: `CLOUDINARY_OFFLINE_PORT` for configuring the server port
|
|
24
|
+
- **Express integration**: Built-in CORS and Express routing support
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- File tracking now uses a JSON mapping file (`uploads.json`) instead of direct file paths
|
|
29
|
+
- Upload method now generates UUID-based public IDs for better privacy and consistency
|
|
30
|
+
- Files can now be accessed via HTTP URLs when the emulator is running
|
|
31
|
+
|
|
32
|
+
### Migration Guide from v1.x to v2.x
|
|
33
|
+
|
|
34
|
+
#### 1. Update your `.env` file
|
|
35
|
+
|
|
36
|
+
Add the new port configuration:
|
|
37
|
+
|
|
38
|
+
```env
|
|
39
|
+
CLOUDINARY_OFFLINE_PATH=./offline_uploads
|
|
40
|
+
CLOUDINARY_OFFLINE_PORT=3000
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
#### 2. Start the emulator
|
|
44
|
+
|
|
45
|
+
```js
|
|
46
|
+
import { startEmulator, offlineCloudinary } from "offline-cloudinary";
|
|
47
|
+
|
|
48
|
+
// Start the HTTP server
|
|
49
|
+
startEmulator();
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
#### 3. Update public_id handling
|
|
53
|
+
|
|
54
|
+
**Before (v1):**
|
|
55
|
+
```js
|
|
56
|
+
const result = await offlineCloudinary.upload("./photo.jpg");
|
|
57
|
+
// result.public_id was a file path like "./offline_uploads/photo.jpg"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**After (v2):**
|
|
61
|
+
```js
|
|
62
|
+
const result = await offlineCloudinary.upload("./photo.jpg");
|
|
63
|
+
// result.public_id is now a UUID like "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
|
64
|
+
// result.url is "http://localhost:3000/file/a1b2c3d4-e5f6-7890-abcd-ef1234567890"
|
|
65
|
+
// result.secure_url is the actual file path
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
#### 4. Update destroy calls
|
|
69
|
+
|
|
70
|
+
Use the UUID from upload response:
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
const uploadResult = await offlineCloudinary.upload("./photo.jpg");
|
|
74
|
+
await offlineCloudinary.destroy(uploadResult.public_id); // Pass the UUID
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## [1.0.0] - Initial Release
|
|
80
|
+
|
|
81
|
+
### Added
|
|
82
|
+
|
|
83
|
+
- Initial release with basic upload, delete, and clearStorage functionality
|
|
84
|
+
- Environment-based path configuration via `CLOUDINARY_OFFLINE_PATH`
|
|
85
|
+
- Cloudinary-like response format
|
|
86
|
+
- Support for nested folder structures
|
|
87
|
+
- Custom filename option
|
|
88
|
+
|
|
89
|
+
[2.0.0]: https://github.com/MaxEssien/offline-cloudinary/compare/v1.0.0...v2.0.0
|
|
90
|
+
[1.0.0]: https://github.com/MaxEssien/offline-cloudinary/releases/tag/v1.0.0
|
package/README.md
CHANGED
|
@@ -6,9 +6,10 @@ An **offline Cloudinary-like file manager** for Node.js — designed for develop
|
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- Local file uploads and deletions
|
|
9
|
+
- Local file uploads and deletions with HTTP server emulator
|
|
10
10
|
- Cloudinary-style API responses
|
|
11
11
|
- Simple environment-based configuration
|
|
12
|
+
- Built-in Express server to serve uploaded files via HTTP
|
|
12
13
|
- Clear all stored uploads with one command
|
|
13
14
|
- Perfect for testing, prototyping, or offline development
|
|
14
15
|
|
|
@@ -30,20 +31,25 @@ yarn add offline-cloudinary
|
|
|
30
31
|
|
|
31
32
|
## Setup
|
|
32
33
|
|
|
33
|
-
In your project
|
|
34
|
+
In your project's `.env` file, define the configuration for offline uploads:
|
|
34
35
|
|
|
35
|
-
```
|
|
36
|
+
```env
|
|
36
37
|
CLOUDINARY_OFFLINE_PATH=./offline_uploads
|
|
38
|
+
CLOUDINARY_OFFLINE_PORT=3000
|
|
37
39
|
```
|
|
38
40
|
|
|
39
|
-
|
|
41
|
+
- **`CLOUDINARY_OFFLINE_PATH`**: Base directory where uploaded files will be stored
|
|
42
|
+
- **`CLOUDINARY_OFFLINE_PORT`**: Port number for the HTTP server emulator
|
|
40
43
|
|
|
41
44
|
---
|
|
42
45
|
|
|
43
|
-
##
|
|
46
|
+
## Quick Start
|
|
44
47
|
|
|
45
48
|
```js
|
|
46
|
-
import offlineCloudinary from "offline-cloudinary";
|
|
49
|
+
import { startEmulator, offlineCloudinary } from "offline-cloudinary";
|
|
50
|
+
|
|
51
|
+
// Start the HTTP server to serve uploaded files
|
|
52
|
+
startEmulator();
|
|
47
53
|
|
|
48
54
|
(async () => {
|
|
49
55
|
try {
|
|
@@ -54,6 +60,11 @@ import offlineCloudinary from "offline-cloudinary";
|
|
|
54
60
|
});
|
|
55
61
|
|
|
56
62
|
console.log("Upload result:", uploadResult);
|
|
63
|
+
// uploadResult.url: "http://localhost:3000/file/{uuid}"
|
|
64
|
+
// uploadResult.public_id: UUID identifier
|
|
65
|
+
|
|
66
|
+
// Access the file via HTTP
|
|
67
|
+
console.log(`View file at: ${uploadResult.url}`);
|
|
57
68
|
|
|
58
69
|
// Delete the uploaded file
|
|
59
70
|
const deleteResult = await offlineCloudinary.destroy(uploadResult.public_id);
|
|
@@ -72,16 +83,39 @@ import offlineCloudinary from "offline-cloudinary";
|
|
|
72
83
|
|
|
73
84
|
## API Reference
|
|
74
85
|
|
|
75
|
-
### **`
|
|
76
|
-
|
|
86
|
+
### **`startEmulator()`**
|
|
87
|
+
|
|
88
|
+
Starts the Express HTTP server to serve uploaded files.
|
|
89
|
+
|
|
90
|
+
**Important**: Must be called to enable HTTP access to uploaded files via the `url` field.
|
|
91
|
+
|
|
92
|
+
**Environment Variables Required:**
|
|
93
|
+
- `CLOUDINARY_OFFLINE_PORT` - Port number for the server
|
|
94
|
+
|
|
95
|
+
**Example:**
|
|
96
|
+
```js
|
|
97
|
+
import { startEmulator } from "offline-cloudinary";
|
|
98
|
+
|
|
99
|
+
startEmulator();
|
|
100
|
+
// Server running on port specified in CLOUDINARY_OFFLINE_PORT
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
### **`offlineCloudinary`**
|
|
106
|
+
|
|
107
|
+
The main instance for file operations. Automatically instantiated when imported.
|
|
77
108
|
Throws an error if `CLOUDINARY_OFFLINE_PATH` is not set.
|
|
78
109
|
|
|
79
110
|
---
|
|
80
111
|
|
|
81
|
-
### **`upload(tempFilePath, options)`**
|
|
112
|
+
### **`offlineCloudinary.upload(tempFilePath, options)`**
|
|
82
113
|
|
|
83
114
|
Uploads a file from a temporary path to your offline storage.
|
|
84
115
|
|
|
116
|
+
**Environment Variables Required:**
|
|
117
|
+
- `CLOUDINARY_OFFLINE_PORT` - Used to generate the HTTP URL
|
|
118
|
+
|
|
85
119
|
**Parameters**
|
|
86
120
|
| Name | Type | Description |
|
|
87
121
|
|------|------|--------------|
|
|
@@ -90,33 +124,67 @@ Uploads a file from a temporary path to your offline storage.
|
|
|
90
124
|
| `options.fileName` | `string` | Optional custom file name (without extension) |
|
|
91
125
|
|
|
92
126
|
**Returns**
|
|
127
|
+
|
|
93
128
|
A Cloudinary-like object containing:
|
|
94
129
|
```js
|
|
95
130
|
{
|
|
96
|
-
asset_id
|
|
97
|
-
|
|
131
|
+
asset_id: "uuid-v4",
|
|
132
|
+
public_id: "uuid-v4", // UUID identifier for this file
|
|
133
|
+
version: 1702654321000, // Timestamp
|
|
134
|
+
version_id: "uuid-v4",
|
|
135
|
+
signature: "hex-string",
|
|
136
|
+
width: null,
|
|
137
|
+
height: null,
|
|
138
|
+
format: "jpg", // File extension
|
|
139
|
+
resource_type: "image",
|
|
140
|
+
created_at: "2025-12-15T10:30:00.000Z",
|
|
141
|
+
tags: [],
|
|
142
|
+
pages: 1,
|
|
143
|
+
bytes: 102400, // File size in bytes
|
|
144
|
+
type: "upload",
|
|
145
|
+
etag: "hex-string",
|
|
146
|
+
placeholder: false,
|
|
147
|
+
url: "http://localhost:3000/file/{uuid}", // HTTP URL to access file
|
|
148
|
+
secure_url: "/path/to/actual/file.jpg" // Actual file system path
|
|
98
149
|
}
|
|
99
150
|
```
|
|
100
151
|
|
|
152
|
+
**Example:**
|
|
153
|
+
```js
|
|
154
|
+
const result = await offlineCloudinary.upload("./photo.jpg", {
|
|
155
|
+
folder: "users/avatars",
|
|
156
|
+
fileName: "profile-pic"
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
console.log(result.url); // "http://localhost:3000/file/abc123..."
|
|
160
|
+
console.log(result.public_id); // "abc123-def456-789..."
|
|
161
|
+
```
|
|
162
|
+
|
|
101
163
|
---
|
|
102
164
|
|
|
103
|
-
### **`destroy(public_id)`**
|
|
165
|
+
### **`offlineCloudinary.destroy(public_id)`**
|
|
104
166
|
|
|
105
|
-
Deletes a file from your offline storage.
|
|
167
|
+
Deletes a file from your offline storage using its UUID.
|
|
106
168
|
|
|
107
169
|
**Parameters**
|
|
108
170
|
| Name | Type | Description |
|
|
109
171
|
|------|------|--------------|
|
|
110
|
-
| `public_id` | `string` |
|
|
172
|
+
| `public_id` | `string` | UUID returned by the upload method |
|
|
111
173
|
|
|
112
174
|
**Returns**
|
|
113
175
|
```js
|
|
114
176
|
{ result: "ok" }
|
|
115
177
|
```
|
|
116
178
|
|
|
179
|
+
**Example:**
|
|
180
|
+
```js
|
|
181
|
+
const uploadResult = await offlineCloudinary.upload("./photo.jpg");
|
|
182
|
+
await offlineCloudinary.destroy(uploadResult.public_id);
|
|
183
|
+
```
|
|
184
|
+
|
|
117
185
|
---
|
|
118
186
|
|
|
119
|
-
### **`clearStorage()`**
|
|
187
|
+
### **`offlineCloudinary.clearStorage()`**
|
|
120
188
|
|
|
121
189
|
Deletes all files and folders in your offline Cloudinary storage.
|
|
122
190
|
|
|
@@ -125,12 +193,19 @@ Deletes all files and folders in your offline Cloudinary storage.
|
|
|
125
193
|
{ result: "ok" }
|
|
126
194
|
```
|
|
127
195
|
|
|
196
|
+
**Example:**
|
|
197
|
+
```js
|
|
198
|
+
await offlineCloudinary.clearStorage();
|
|
199
|
+
console.log("All files deleted");
|
|
200
|
+
```
|
|
201
|
+
|
|
128
202
|
---
|
|
129
203
|
|
|
130
204
|
## Example .env
|
|
131
205
|
|
|
132
|
-
```
|
|
206
|
+
```env
|
|
133
207
|
CLOUDINARY_OFFLINE_PATH=./uploads
|
|
208
|
+
CLOUDINARY_OFFLINE_PORT=3000
|
|
134
209
|
```
|
|
135
210
|
|
|
136
211
|
---
|
|
@@ -140,6 +215,7 @@ CLOUDINARY_OFFLINE_PATH=./uploads
|
|
|
140
215
|
```
|
|
141
216
|
project/
|
|
142
217
|
├── .env
|
|
218
|
+
├── uploads.json # Auto-generated UUID mapping
|
|
143
219
|
├── uploads/
|
|
144
220
|
│ └── users/
|
|
145
221
|
│ └── avatars/
|
|
@@ -149,6 +225,42 @@ project/
|
|
|
149
225
|
|
|
150
226
|
---
|
|
151
227
|
|
|
228
|
+
## Migration from v1.x to v2.x
|
|
229
|
+
|
|
230
|
+
### Breaking Changes
|
|
231
|
+
|
|
232
|
+
1. **New environment variable required**: `CLOUDINARY_OFFLINE_PORT`
|
|
233
|
+
2. **Import changed**: Now exports `{ startEmulator, offlineCloudinary }` instead of default export
|
|
234
|
+
3. **public_id format**: Now returns UUID instead of file path
|
|
235
|
+
4. **URL field**: Now returns HTTP endpoint instead of file path
|
|
236
|
+
5. **Must call startEmulator()** to serve files via HTTP
|
|
237
|
+
|
|
238
|
+
### Migration Steps
|
|
239
|
+
|
|
240
|
+
**Before (v1.x):**
|
|
241
|
+
```js
|
|
242
|
+
import offlineCloudinary from "offline-cloudinary";
|
|
243
|
+
|
|
244
|
+
const result = await offlineCloudinary.upload("./photo.jpg");
|
|
245
|
+
// result.public_id was a file path
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**After (v2.x):**
|
|
249
|
+
```js
|
|
250
|
+
import { startEmulator, offlineCloudinary } from "offline-cloudinary";
|
|
251
|
+
|
|
252
|
+
// Add CLOUDINARY_OFFLINE_PORT=3000 to your .env
|
|
253
|
+
|
|
254
|
+
startEmulator(); // Start the HTTP server
|
|
255
|
+
|
|
256
|
+
const result = await offlineCloudinary.upload("./photo.jpg");
|
|
257
|
+
// result.public_id is now a UUID
|
|
258
|
+
// result.url is "http://localhost:3000/file/{uuid}"
|
|
259
|
+
// result.secure_url is "http://localhost:3000/file/{uuid}"
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
152
264
|
## Author
|
|
153
265
|
|
|
154
266
|
**Max Essien**
|
|
@@ -162,5 +274,12 @@ project/
|
|
|
162
274
|
This project is licensed under the **MIT License** — free for personal and commercial use.
|
|
163
275
|
|
|
164
276
|
---
|
|
277
|
+
|
|
278
|
+
## Changelog
|
|
279
|
+
|
|
280
|
+
See [CHANGELOG.md](./CHANGELOG.md) for version history and migration guides.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
165
284
|
**Offline Cloudinary** — your Cloudinary, anywhere, even offline.
|
|
166
285
|
|
package/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import fileRoutes from "./src/routes/fileRoutes.js";
|
|
3
|
+
import offlineCloudinary from "./src/utils/offline-cloudinary";
|
|
4
|
+
import cors from "cors";
|
|
5
|
+
|
|
6
|
+
const app = express();
|
|
7
|
+
|
|
8
|
+
app.use(cors());
|
|
9
|
+
|
|
10
|
+
app.use("/file", fileRoutes);
|
|
11
|
+
|
|
12
|
+
const startEmulator = () => {
|
|
13
|
+
const portNumber = process.env.CLOUDINARY_OFFLINE_PORT;
|
|
14
|
+
if (!portNumber)
|
|
15
|
+
throw new Error("Please set CLOUDINARY_OFFLINE_PORT in your .env file");
|
|
16
|
+
app.listen(portNumber, () =>
|
|
17
|
+
console.log("Offline Cloudinary running on port", portNumber)
|
|
18
|
+
);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export { startEmulator, offlineCloudinary };
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "offline-cloudinary",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "An offline Cloudinary-like file manager for local uploads and
|
|
5
|
-
"main": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "An offline Cloudinary-like file manager with HTTP server emulator for local uploads, deletions, and testing without internet access.",
|
|
5
|
+
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"cloudinary",
|
|
@@ -12,7 +12,12 @@
|
|
|
12
12
|
"mock",
|
|
13
13
|
"testing",
|
|
14
14
|
"file-system",
|
|
15
|
-
"nodejs"
|
|
15
|
+
"nodejs",
|
|
16
|
+
"express",
|
|
17
|
+
"server",
|
|
18
|
+
"emulator",
|
|
19
|
+
"http",
|
|
20
|
+
"file-upload"
|
|
16
21
|
],
|
|
17
22
|
"author": "Max Essien",
|
|
18
23
|
"license": "MIT",
|
|
@@ -20,12 +25,25 @@
|
|
|
20
25
|
"type": "git",
|
|
21
26
|
"url": "https://github.com/MaxEssien/offline-cloudinary"
|
|
22
27
|
},
|
|
23
|
-
"files": [
|
|
28
|
+
"files": [
|
|
29
|
+
"src",
|
|
30
|
+
"index.js",
|
|
31
|
+
"README.md",
|
|
32
|
+
"CHANGELOG.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
24
35
|
"scripts": {
|
|
25
|
-
"test": "
|
|
36
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
37
|
+
},
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18.0.0"
|
|
26
40
|
},
|
|
27
41
|
"bugs": {
|
|
28
42
|
"url": "https://github.com/MaxEssien/offline-cloudinary/issues"
|
|
29
43
|
},
|
|
30
|
-
"homepage": "https://github.com/MaxEssien/offline-cloudinary#readme"
|
|
44
|
+
"homepage": "https://github.com/MaxEssien/offline-cloudinary#readme",
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"cors": "^2.8.5",
|
|
47
|
+
"express": "^5.2.1"
|
|
48
|
+
}
|
|
31
49
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import offlineCloudinary from '../utils/offline-cloudinary.js';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
|
|
4
|
+
export const viewImage = async(req, res)=>{
|
|
5
|
+
const uploadId = req.params.id
|
|
6
|
+
const data = await fs.readFile("uploads.json", "utf-8")
|
|
7
|
+
const mappings = JSON.parse(data)
|
|
8
|
+
res.setHeader("Content-Disposition", "inline")
|
|
9
|
+
return res.sendFile(`${offlineCloudinary.rootPath}/${mappings[uploadId]}`)
|
|
10
|
+
}
|
|
@@ -5,9 +5,7 @@ import crypto from "crypto";
|
|
|
5
5
|
class OfflineCloudinary {
|
|
6
6
|
constructor() {
|
|
7
7
|
if (!process.env.CLOUDINARY_OFFLINE_PATH) {
|
|
8
|
-
throw new Error(
|
|
9
|
-
"Please set CLOUDINARY_OFFLINE_PATH in your .env file"
|
|
10
|
-
);
|
|
8
|
+
throw new Error("Please set CLOUDINARY_OFFLINE_PATH in your .env file");
|
|
11
9
|
}
|
|
12
10
|
this.rootPath = process.env.CLOUDINARY_OFFLINE_PATH;
|
|
13
11
|
}
|
|
@@ -19,7 +17,12 @@ class OfflineCloudinary {
|
|
|
19
17
|
* @returns Cloudinary-like response
|
|
20
18
|
*/
|
|
21
19
|
async upload(tempFilePath, options = {}) {
|
|
22
|
-
|
|
20
|
+
const portNumber = process.env.CLOUDINARY_OFFLINE_PORT;
|
|
21
|
+
if (!portNumber)
|
|
22
|
+
throw new Error("Please set CLOUDINARY_OFFLINE_PORT in your .env file");
|
|
23
|
+
await fs.access(tempFilePath).catch(() => {
|
|
24
|
+
throw new Error(`File not found: ${tempFilePath}`);
|
|
25
|
+
});
|
|
23
26
|
const folder = options.folder || "";
|
|
24
27
|
const name = options?.fileName || crypto.randomUUID();
|
|
25
28
|
const fullFolderPath = path.join(this.rootPath, folder);
|
|
@@ -29,7 +32,7 @@ class OfflineCloudinary {
|
|
|
29
32
|
|
|
30
33
|
// Generate unique filename
|
|
31
34
|
const ext = path.extname(tempFilePath);
|
|
32
|
-
if (!ext?.trim()) throw new Error("Unsupported file type")
|
|
35
|
+
if (!ext?.trim()) throw new Error("Unsupported file type");
|
|
33
36
|
const fileName = name + ext;
|
|
34
37
|
|
|
35
38
|
const finalPath = path.join(fullFolderPath, fileName);
|
|
@@ -42,10 +45,19 @@ class OfflineCloudinary {
|
|
|
42
45
|
|
|
43
46
|
const now = new Date().toISOString();
|
|
44
47
|
|
|
48
|
+
const uploadId = crypto.randomUUID();
|
|
49
|
+
|
|
50
|
+
const data = await fs.readFile("uploads.json", "utf-8");
|
|
51
|
+
|
|
52
|
+
const mappings = JSON.parse(data);
|
|
53
|
+
mappings[uploadId] = finalPath;
|
|
54
|
+
|
|
55
|
+
await fs.writeFile("uploads.json", JSON.stringify(mappings));
|
|
56
|
+
|
|
45
57
|
// Return Cloudinary-like response
|
|
46
58
|
return {
|
|
47
59
|
asset_id: crypto.randomUUID(),
|
|
48
|
-
public_id:
|
|
60
|
+
public_id: uploadId,
|
|
49
61
|
version: Date.now(),
|
|
50
62
|
version_id: crypto.randomUUID(),
|
|
51
63
|
signature: crypto.randomBytes(16).toString("hex"),
|
|
@@ -60,8 +72,8 @@ class OfflineCloudinary {
|
|
|
60
72
|
type: "upload",
|
|
61
73
|
etag: crypto.randomBytes(8).toString("hex"),
|
|
62
74
|
placeholder: false,
|
|
63
|
-
url:
|
|
64
|
-
secure_url:
|
|
75
|
+
url: `http://localhost:${portNumber}/file/${uploadId}`,
|
|
76
|
+
secure_url: `http://localhost:${portNumber}/file/${uploadId}`,
|
|
65
77
|
};
|
|
66
78
|
}
|
|
67
79
|
|
|
@@ -71,8 +83,10 @@ class OfflineCloudinary {
|
|
|
71
83
|
* @returns {object} { result: "ok" } if deleted or { result: "not found" }
|
|
72
84
|
*/
|
|
73
85
|
async destroy(public_id) {
|
|
74
|
-
const
|
|
75
|
-
await fs.
|
|
86
|
+
const uploadId = public_id;
|
|
87
|
+
const data = await fs.readFile("uploads.json", "utf-8");
|
|
88
|
+
const mappings = JSON.parse(data);
|
|
89
|
+
await fs.unlink(mappings[uploadId]);
|
|
76
90
|
return { result: "ok" };
|
|
77
91
|
}
|
|
78
92
|
|
|
@@ -80,13 +94,13 @@ class OfflineCloudinary {
|
|
|
80
94
|
* Destroy every files and folder in the local offline cloudinary storage
|
|
81
95
|
* @returns {object} {result: ok} if successful
|
|
82
96
|
*/
|
|
83
|
-
async clearStorage(){
|
|
84
|
-
await fs.rm(this.rootPath, {recursive: true, force: true})
|
|
85
|
-
await fs.mkdir(this.rootPath)
|
|
86
|
-
return {result: "ok"}
|
|
97
|
+
async clearStorage() {
|
|
98
|
+
await fs.rm(this.rootPath, { recursive: true, force: true });
|
|
99
|
+
await fs.mkdir(this.rootPath);
|
|
100
|
+
return { result: "ok" };
|
|
87
101
|
}
|
|
88
102
|
}
|
|
89
103
|
|
|
90
|
-
const offlineCloudinary = new OfflineCloudinary()
|
|
104
|
+
const offlineCloudinary = new OfflineCloudinary();
|
|
91
105
|
|
|
92
|
-
export default offlineCloudinary;
|
|
106
|
+
export default offlineCloudinary;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|