mbkbucket 2.1.0 → 3.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/.env.example +4 -2
- package/.github/PACKAGE.md +6 -0
- package/.mbkbucket.config.example.json +14 -0
- package/LICENSE +161 -335
- package/README.md +262 -81
- package/cli.js +977 -0
- package/index.js +18 -140
- package/lib/index.js +7 -7
- package/package.json +18 -6
- package/public/bucketadmin.js +41 -6
- package/src/app.js +110 -0
- package/src/cli/auth-helper.js +223 -0
- package/{lib → src}/config/index.js +26 -19
- package/{lib/routes/api.js → src/controllers/api.controller.js} +125 -221
- package/{lib/routes/index.js → src/controllers/dashboard.controller.js} +8 -36
- package/src/controllers/info.controller.js +37 -0
- package/src/controllers/view.controller.js +276 -0
- package/src/middleware/async-handler.js +38 -0
- package/src/middleware/bucket-resolver.js +43 -0
- package/src/middleware/error-handler.js +34 -0
- package/src/middleware/rate-limiter.js +30 -0
- package/src/middleware/security.js +43 -0
- package/src/routes/api.routes.js +66 -0
- package/src/routes/index.js +22 -0
- package/src/routes/info.routes.js +10 -0
- package/src/routes/view.routes.js +25 -0
- package/{lib/s3.js → src/services/s3.service.js} +116 -190
- package/src/utils/errors.js +76 -0
- package/src/utils/helpers.js +173 -0
- package/{lib/debug.js → src/utils/logger.js} +5 -0
- package/test/config-validation.test.js +1 -1
- package/views/bucket.handlebars +0 -1
- package/lib/routes/info.js +0 -28
- package/lib/routes/view.js +0 -531
- /package/{lib → src}/config/validation.js +0 -0
package/README.md
CHANGED
|
@@ -1,169 +1,350 @@
|
|
|
1
1
|
# mbkbucket
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Flexible S3/R2 bucket management — library, Express router, and CLI for mbktech.org applications.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/mbkbucket)
|
|
6
6
|
[](https://www.npmjs.com/package/mbkbucket)
|
|
7
|
-
[](LICENSE)
|
|
8
8
|
[](https://nodejs.org/)
|
|
9
9
|
[](https://github.com/MIbnEKhalid/mbkbucket/actions/workflows/publish.yml)
|
|
10
10
|
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Table of Contents
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
- [Installation](#installation-)
|
|
16
|
+
- [Quick Start](#quick-start-)
|
|
17
|
+
- [CLI Usage](#cli-usage-)
|
|
18
|
+
- [Library API](#library-api-)
|
|
19
|
+
- [Express Router](#express-router-)
|
|
20
|
+
- [Environment Configuration](#environment-configuration-)
|
|
21
|
+
- [Supported Storage Providers](#supported-storage-providers-)
|
|
22
|
+
- [Tests](#automated-tests-)
|
|
23
|
+
- [Support](#contact--support)
|
|
13
24
|
|
|
14
|
-
|
|
25
|
+
---
|
|
15
26
|
|
|
16
|
-
|
|
27
|
+
## Installation 📦
|
|
17
28
|
|
|
18
29
|
```bash
|
|
19
30
|
npm install mbkbucket
|
|
20
31
|
```
|
|
21
32
|
|
|
22
|
-
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Quick Start 🚀
|
|
23
36
|
|
|
24
|
-
|
|
37
|
+
### As a Library
|
|
25
38
|
|
|
26
39
|
```js
|
|
27
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
uploadFile,
|
|
42
|
+
downloadFile,
|
|
43
|
+
listfiles,
|
|
44
|
+
deleteFile,
|
|
45
|
+
deleteFolder,
|
|
46
|
+
getFileMetadata,
|
|
47
|
+
generateSignedUrl,
|
|
48
|
+
bucket, // Express router
|
|
49
|
+
packageJson // Package metadata
|
|
50
|
+
} from 'mbkbucket';
|
|
51
|
+
|
|
52
|
+
// Upload a file
|
|
53
|
+
const result = await uploadFile(
|
|
54
|
+
'photos/sunset.jpg',
|
|
55
|
+
fileBuffer,
|
|
56
|
+
'image/jpeg',
|
|
57
|
+
{ bucketName: 'R2_Bucket' }
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// List files
|
|
61
|
+
const { Contents, hasMore } = await listfiles('photos/', {
|
|
62
|
+
delimiter: '/',
|
|
63
|
+
maxKeys: 100
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Download a file
|
|
67
|
+
const { Body, ContentType } = await downloadFile('photos/sunset.jpg');
|
|
68
|
+
|
|
69
|
+
// Generate a pre-signed URL (valid for 1 hour)
|
|
70
|
+
const { url } = await generateSignedUrl('photos/sunset.jpg', 'getObject', 3600);
|
|
28
71
|
```
|
|
29
72
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
This imports the configured Express server instance:
|
|
73
|
+
### As Express Middleware
|
|
33
74
|
|
|
34
75
|
```js
|
|
35
76
|
import mbkbucket from 'mbkbucket';
|
|
36
|
-
|
|
77
|
+
|
|
78
|
+
// mbkbucket is a fully configured Express app with auth, sessions, and bucket routes
|
|
79
|
+
mbkbucket.listen(3004, () => {
|
|
80
|
+
console.log('mbkbucket server running on http://localhost:3004');
|
|
81
|
+
});
|
|
37
82
|
```
|
|
38
83
|
|
|
39
|
-
|
|
84
|
+
The Express app includes:
|
|
85
|
+
- **`/mbkbucket`** — Admin dashboard (SuperAdmin only)
|
|
86
|
+
- **`/mbkbucket/api/*`** — REST API for file operations
|
|
87
|
+
- **`/mbkbucket/p_view/:key(*)`** — Public file view
|
|
88
|
+
- **`/mbkbucket/info`** — Health & config info
|
|
40
89
|
|
|
41
|
-
|
|
90
|
+
---
|
|
42
91
|
|
|
43
|
-
|
|
44
|
-
import { uploadFile } from 'mbkbucket/lib/s3';
|
|
45
|
-
import { packageJson } from 'mbkbucket/lib/config/index';
|
|
46
|
-
import bucketRouter from 'mbkbucket/lib/bucket';
|
|
47
|
-
```
|
|
92
|
+
## CLI Usage 💻
|
|
48
93
|
|
|
49
|
-
|
|
94
|
+
> **⚠️ Under active development.** The CLI is functional but some features may change.
|
|
50
95
|
|
|
51
|
-
|
|
52
|
-
- TypeScript support with full type declarations (`index.d.ts`) for IDE intellisense and type safety.
|
|
53
|
-
- The default export is the configured Express `server` instance; access the `bucket` router as a named export.
|
|
54
|
-
- Supports Node.js 14.0.0 and higher with ES Modules.
|
|
96
|
+
mbkbucket ships with a command-line interface for managing buckets directly from the terminal.
|
|
55
97
|
|
|
56
|
-
|
|
98
|
+
### Setup
|
|
57
99
|
|
|
58
|
-
|
|
100
|
+
```bash
|
|
101
|
+
# Set your mbkauthe server URL (one-time)
|
|
102
|
+
mbkbucket config set serverUrl https://your-server.example.com
|
|
59
103
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
- **listfiles()** - List contents of S3 bucket
|
|
63
|
-
- **getBucketConfig()** - Get current bucket configuration
|
|
64
|
-
- **bucket** - Express router with bucket management endpoints
|
|
104
|
+
# Set your API token profile key (one-time, optional)
|
|
105
|
+
mbkbucket config set profileKey your-profile-key
|
|
65
106
|
|
|
66
|
-
|
|
107
|
+
# Authenticate via device flow
|
|
108
|
+
mbkbucket login
|
|
109
|
+
```
|
|
67
110
|
|
|
68
|
-
|
|
111
|
+
### Commands
|
|
112
|
+
|
|
113
|
+
| Command | Alias | Description |
|
|
114
|
+
|---|---|---|
|
|
115
|
+
| `mbkbucket login` | — | Authenticate via mbkauthe device flow |
|
|
116
|
+
| `mbkbucket logout` | — | Clear stored credentials |
|
|
117
|
+
| `mbkbucket whoami` | — | Show login status |
|
|
118
|
+
| | | |
|
|
119
|
+
| `mbkbucket list [prefix]` | `ls` | List files and folders |
|
|
120
|
+
| `mbkbucket upload <file> [key]` | `up` | Upload a file or folder |
|
|
121
|
+
| `mbkbucket download <key> [dest]` | `dl` | Download a file or folder |
|
|
122
|
+
| `mbkbucket delete <key>` | `rm` | Delete a file |
|
|
123
|
+
| `mbkbucket delete-folder <prefix>` | `rmdir` | Recursively delete a folder |
|
|
124
|
+
| `mbkbucket info <key>` | `stat` | Show file metadata (size, type, etc.) |
|
|
125
|
+
| `mbkbucket signed-url <key>` | `sign` | Generate a pre-signed download URL |
|
|
126
|
+
| | | |
|
|
127
|
+
| `mbkbucket config` | `cfg` | Show all config values |
|
|
128
|
+
| `mbkbucket config get <key>` | — | Get a specific config value |
|
|
129
|
+
| `mbkbucket config set <k> <v>` | — | Set a config value |
|
|
130
|
+
| `mbkbucket config unset <key>` | — | Remove a config value |
|
|
131
|
+
| `mbkbucket config path` | — | Show config file location |
|
|
132
|
+
| `mbkbucket config reset` | — | Reset to demo values |
|
|
133
|
+
| `mbkbucket config edit` | — | Open config file in editor |
|
|
134
|
+
|
|
135
|
+
### Global Options
|
|
136
|
+
|
|
137
|
+
| Flag | Description |
|
|
138
|
+
|---|---|
|
|
139
|
+
| `--app`, `-a <name>` | Override `APP_NAME` for key prefix isolation |
|
|
140
|
+
| `--bucket`, `-b <name>` | Override default bucket name |
|
|
141
|
+
| `--help`, `-h` | Show help |
|
|
142
|
+
|
|
143
|
+
### Login Options
|
|
144
|
+
|
|
145
|
+
| Flag | Description |
|
|
146
|
+
|---|---|
|
|
147
|
+
| `--server <url>` | mbkauthe server URL |
|
|
148
|
+
| `--profile-key <k>` | API token profile key |
|
|
149
|
+
|
|
150
|
+
### Config File
|
|
151
|
+
|
|
152
|
+
CLI configuration is stored at `~/.mbkbucket/config.json`. A demo config is created automatically on first use.
|
|
69
153
|
|
|
70
|
-
|
|
154
|
+
---
|
|
71
155
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
156
|
+
## Library API 📚
|
|
157
|
+
|
|
158
|
+
All functions are exported from the package root. Full TypeScript declarations in [index.d.ts](index.d.ts).
|
|
159
|
+
|
|
160
|
+
### File Operations
|
|
161
|
+
|
|
162
|
+
| Function | Description |
|
|
163
|
+
|---|---|
|
|
164
|
+
| `uploadFile(key, buffer, contentType, options?)` | Upload a file |
|
|
165
|
+
| `downloadFile(key, options?)` | Download a file |
|
|
166
|
+
| `deleteFile(key, bucketName?)` | Delete a single file |
|
|
167
|
+
| `deleteFiles(keys, bucketName?)` | Batch delete up to 1000 files |
|
|
168
|
+
| `deleteFolder(prefix, bucketName?)` | Recursively delete all files under a prefix |
|
|
169
|
+
| `listfiles(prefix?, options?)` | List files with pagination and filtering |
|
|
170
|
+
| `getFileMetadata(key, bucketName?)` | Get file metadata without downloading |
|
|
171
|
+
| `fileExists(key, bucketName?)` | Check if a file exists |
|
|
172
|
+
| `getFileSize(key, bucketName?)` | Get file size in bytes |
|
|
173
|
+
| `generateSignedUrl(key, operation?, expiresIn?, bucketName?)` | Generate a pre-signed URL |
|
|
174
|
+
|
|
175
|
+
### Multipart Upload
|
|
176
|
+
|
|
177
|
+
| Function | Description |
|
|
178
|
+
|---|---|
|
|
179
|
+
| `createMultipartUpload(key, contentType?, metadata?, bucketName?)` | Start a multipart upload |
|
|
180
|
+
| `uploadPart(key, uploadId, partNumber, buffer, bucketName?)` | Upload a single part |
|
|
181
|
+
| `completeMultipartUpload(key, uploadId, parts, bucketName?)` | Complete a multipart upload |
|
|
182
|
+
| `abortMultipartUpload(key, uploadId, bucketName?)` | Abort a multipart upload |
|
|
183
|
+
| `listIncompleteMultipartUploads(prefix?, bucketName?)` | List incomplete uploads |
|
|
184
|
+
| `cleanupIncompleteMultipartUploads(olderThanDays?, prefix?, bucketName?)` | Clean up stale uploads |
|
|
185
|
+
|
|
186
|
+
### Bucket Management
|
|
187
|
+
|
|
188
|
+
| Function | Description |
|
|
189
|
+
|---|---|
|
|
190
|
+
| `getAvailableBucketNames()` | List configured bucket names |
|
|
191
|
+
| `resolveBucketName(bucketName?)` | Resolve and validate a bucket name |
|
|
192
|
+
| `getBucketConfig(bucketName?)` | Get bucket credentials/config |
|
|
193
|
+
| `getBucketClient(bucketName?)` | Get an S3 client instance |
|
|
194
|
+
| `checkHealth()` | Check S3 connectivity |
|
|
195
|
+
|
|
196
|
+
### Key Prefixing
|
|
197
|
+
|
|
198
|
+
| Function | Description |
|
|
199
|
+
|---|---|
|
|
200
|
+
| `ensureKeyHasAppPrefix(key?)` | Prefix a key with the app name |
|
|
201
|
+
| `ensurePrefix(prefix?)` | Prefix a folder path with the app name |
|
|
202
|
+
| `getAppName()` | Get the current app name |
|
|
203
|
+
|
|
204
|
+
### Config
|
|
205
|
+
|
|
206
|
+
| Export | Description |
|
|
207
|
+
|---|---|
|
|
208
|
+
| `packageJson` | Package metadata (version, etc.) |
|
|
209
|
+
| `appVersion` | Parent project version |
|
|
210
|
+
| `mbkbucketVar` | Parsed mbkbucketVar config |
|
|
211
|
+
| `checkVersion()` | Check for newer package versions |
|
|
212
|
+
|
|
213
|
+
### Express
|
|
214
|
+
|
|
215
|
+
| Export | Description |
|
|
216
|
+
|---|---|
|
|
217
|
+
| `bucket` (named) | Express Router with all bucket routes |
|
|
218
|
+
| `default` | Fully configured Express application |
|
|
75
219
|
|
|
76
220
|
---
|
|
77
221
|
|
|
78
|
-
##
|
|
222
|
+
## Express Router 🖥️
|
|
223
|
+
|
|
224
|
+
### Routes
|
|
225
|
+
|
|
226
|
+
| Route | Auth | Description |
|
|
227
|
+
|---|---|---|
|
|
228
|
+
| `GET /mbkbucket` | SuperAdmin | Admin dashboard |
|
|
229
|
+
| `GET /mbkbucket/info` | Any | Health check & config info |
|
|
230
|
+
| `GET /mbkbucket/p_view/:key(*)` | Public | Public file viewer |
|
|
231
|
+
| `POST /mbkbucket/api/list` | SuperAdmin | List files (JSON) |
|
|
232
|
+
| `POST /mbkbucket/api/upload` | SuperAdmin | Upload file(s) |
|
|
233
|
+
| `POST /mbkbucket/api/delete` | SuperAdmin | Delete file(s) |
|
|
234
|
+
| `POST /mbkbucket/api/delete-folder` | SuperAdmin | Delete folder |
|
|
235
|
+
| `POST /mbkbucket/api/create-folder` | SuperAdmin | Create folder marker |
|
|
236
|
+
| `POST /mbkbucket/api/rename` | SuperAdmin | Rename/move a file |
|
|
237
|
+
| `GET /mbkbucket/api/download/:key(*)` | SuperAdmin | Download a file |
|
|
238
|
+
| `GET /mbkbucket/api/metadata/:key(*)` | SuperAdmin | Get file metadata |
|
|
239
|
+
| `POST /mbkbucket/api/multipart/init` | SuperAdmin | Init multipart upload |
|
|
240
|
+
| `POST /mbkbucket/api/multipart/part` | SuperAdmin | Upload multipart chunk |
|
|
241
|
+
| `POST /mbkbucket/api/multipart/complete` | SuperAdmin | Complete multipart |
|
|
242
|
+
| `POST /mbkbucket/api/multipart/abort` | SuperAdmin | Abort multipart |
|
|
243
|
+
| `GET /mbkbucket/api/multipart/list` | SuperAdmin | List incomplete uploads |
|
|
244
|
+
| `POST /mbkbucket/api/multipart/cleanup` | SuperAdmin | Clean up stale uploads |
|
|
245
|
+
|
|
246
|
+
### Views
|
|
247
|
+
|
|
248
|
+
| Template | Description |
|
|
249
|
+
|---|---|
|
|
250
|
+
| `bucketportal.handlebars` | Admin dashboard shell |
|
|
251
|
+
| `bucketadmincontent.handlebars` | File listing & management UI |
|
|
252
|
+
| `bucketadmin_alerts.handlebars` | Alert/notification partial |
|
|
253
|
+
| `bucketadmin_delete_modal.handlebars` | Delete confirmation modal |
|
|
254
|
+
| `bucketadmin_preview_modal.handlebars` | File preview modal |
|
|
255
|
+
| `bucketadmin_filelist_skeleton.handlebars` | Loading skeleton |
|
|
256
|
+
| `bucket.handlebars` | Public file viewer |
|
|
257
|
+
| `mbkbucket_info.handlebars` | Info/health page |
|
|
79
258
|
|
|
80
|
-
|
|
259
|
+
---
|
|
81
260
|
|
|
82
|
-
|
|
261
|
+
## Environment Configuration ⚙️
|
|
83
262
|
|
|
84
|
-
`
|
|
263
|
+
### `BucketConnection` (required)
|
|
85
264
|
|
|
86
|
-
|
|
265
|
+
JSON mapping of bucket names to S3-compatible credentials:
|
|
87
266
|
|
|
88
267
|
```env
|
|
89
|
-
|
|
268
|
+
BucketConnection={"R2_Bucket":{"BUCKET_NAME":"my-bucket","ACCESS_KEY_ID":"...","SECRET_ACCESS_KEY":"...","ENDPOINT":"https://<id>.r2.cloudflarestorage.com"}}
|
|
90
269
|
```
|
|
91
270
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
Use a JSON object where each key is a selectable connection name.
|
|
271
|
+
| Field | Required | Description |
|
|
272
|
+
|---|---|---|
|
|
273
|
+
| `BUCKET_NAME` | Yes | Target bucket name |
|
|
274
|
+
| `ACCESS_KEY_ID` | Yes | Access key |
|
|
275
|
+
| `SECRET_ACCESS_KEY` | Yes | Secret key |
|
|
276
|
+
| `ENDPOINT` | Yes | S3-compatible endpoint URL |
|
|
277
|
+
| `region` | No | AWS region (default: `auto`) |
|
|
100
278
|
|
|
101
|
-
|
|
279
|
+
### `mbkautheVar` (required)
|
|
102
280
|
|
|
103
|
-
|
|
104
|
-
BucketConnection={"R2_Bucket":{"BUCKET_NAME":"my-r2-bucket","ACCESS_KEY_ID":"...","SECRET_ACCESS_KEY":"...","ENDPOINT":"https://<account-id>.r2.cloudflarestorage.com"},"S3_Bucket":{"BUCKET_NAME":"my-s3-bucket","ACCESS_KEY_ID":"...","SECRET_ACCESS_KEY":"...","ENDPOINT":"https://s3.ap-southeast-1.amazonaws.com"}}
|
|
105
|
-
```
|
|
281
|
+
Standard mbkauthe configuration. Key fields for mbkbucket:
|
|
106
282
|
|
|
107
|
-
|
|
283
|
+
| Field | Description |
|
|
284
|
+
|---|---|
|
|
285
|
+
| `APP_NAME` | App name used for key prefix isolation |
|
|
286
|
+
| `bucket` | Default bucket name (falls back to first in `BucketConnection`) |
|
|
287
|
+
| `CLI_AUTH_ENABLED` | Enable CLI device-flow login (`"true"` / `"false"`) |
|
|
108
288
|
|
|
109
|
-
|
|
110
|
-
- `ACCESS_KEY_ID`
|
|
111
|
-
- `SECRET_ACCESS_KEY`
|
|
112
|
-
- `ENDPOINT`
|
|
289
|
+
### `mbkbucketVar` (optional)
|
|
113
290
|
|
|
114
|
-
|
|
291
|
+
| Field | Default | Description |
|
|
292
|
+
|---|---|---|
|
|
293
|
+
| `publiView_enabled` | `"false"` | Enable public file viewer |
|
|
294
|
+
| `p_view_inline` | `"false"` | Show files inline vs download |
|
|
115
295
|
|
|
116
|
-
|
|
296
|
+
### Common Mistakes
|
|
117
297
|
|
|
118
298
|
```env
|
|
299
|
+
# ❌ Wrong — inner object quoted as string
|
|
119
300
|
BucketConnection={"R2_Bucket":"{\"BUCKET_NAME\":\"...\"}"}
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
2. Correct inner object (valid)
|
|
123
301
|
|
|
124
|
-
|
|
302
|
+
# ✅ Correct — proper JSON nesting
|
|
125
303
|
BucketConnection={"R2_Bucket":{"BUCKET_NAME":"...","ACCESS_KEY_ID":"...","SECRET_ACCESS_KEY":"...","ENDPOINT":"https://..."}}
|
|
126
304
|
```
|
|
127
305
|
|
|
128
|
-
|
|
306
|
+
---
|
|
129
307
|
|
|
130
|
-
|
|
131
|
-
- Public view route `/mbkbucket/p_view/:key(*)` always uses the default bucket from `mbkautheVar.bucket`.
|
|
308
|
+
## Supported Storage Providers 🗄️
|
|
132
309
|
|
|
133
|
-
|
|
310
|
+
Any S3-compatible object storage:
|
|
134
311
|
|
|
135
|
-
|
|
312
|
+
- **AWS S3**
|
|
313
|
+
- **Cloudflare R2**
|
|
314
|
+
- **IDrive e2**
|
|
315
|
+
- **MinIO**
|
|
316
|
+
- **Backblaze B2** (via S3-compatible API)
|
|
317
|
+
- **DigitalOcean Spaces**
|
|
136
318
|
|
|
137
|
-
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## Automated Tests 🧪
|
|
138
322
|
|
|
139
323
|
```bash
|
|
140
324
|
npm test
|
|
141
325
|
```
|
|
142
326
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
- `mbkbucketVar` parsing/defaults/boolean normalization
|
|
327
|
+
Covers:
|
|
328
|
+
- `mbkbucketVar` parsing, defaults, and boolean normalization
|
|
146
329
|
- `BucketConnection` shape validation and required fields
|
|
147
330
|
|
|
148
331
|
---
|
|
149
332
|
|
|
150
333
|
## Contact & Support
|
|
151
334
|
|
|
152
|
-
For questions, issues, or contributions, please reach out:
|
|
153
|
-
|
|
154
335
|
- **Website**: [mbktech.org/Support](https://mbktech.org/Support/?Project=MIbnEKhalidWeb)
|
|
155
|
-
- **Email**: [support@mbktech.org](mailto:support@mbktech.org)
|
|
156
|
-
- **GitHub**: [MIbnEKhalid](https://github.com/MIbnEKhalid)
|
|
336
|
+
- **Email**: [support@mbktech.org](mailto:support@mbktech.org)
|
|
337
|
+
- **GitHub**: [MIbnEKhalid/mbkbucket](https://github.com/MIbnEKhalid/mbkbucket)
|
|
157
338
|
|
|
158
339
|
---
|
|
159
340
|
|
|
160
341
|
## About
|
|
161
342
|
|
|
162
343
|
Developed by [Muhammad Bin Khalid](https://github.com/MIbnEKhalid)
|
|
163
|
-
Part of [MBK Tech
|
|
344
|
+
Part of [MBK Tech](https://mbktech.org/)
|
|
164
345
|
|
|
165
346
|
---
|
|
166
347
|
|
|
167
348
|
## License
|
|
168
349
|
|
|
169
|
-
Licensed under the
|
|
350
|
+
Licensed under the LGPL-3.0-only License. See [LICENSE](LICENSE) for details.
|