mbkbucket 1.0.2 → 1.1.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 CHANGED
@@ -1,3 +1,10 @@
1
1
  mbkbucketVar={"APP_NAME":"mbkbucket","loginRedirectURL":"/dashboard","bucket":""}
2
2
  mbkauthShared={"Main_SECRET_TOKEN": 123,"DOMAIN":"mbktech.org","IS_DEPLOYED":"false","EncPass":"false","DEVICE_TRUST_DURATION_DAYS":7,"SESSION_SECRET_KEY":"123","LOGIN_DB":"postgres://","MBKAUTH_TWO_FA_ENABLE":"true","COOKIE_EXPIRE_TIME":2,"GITHUB_LOGIN_ENABLED":"f","GITHUB_CLIENT_ID":"123","GITHUB_CLIENT_SECRET":"123","GOOGLE_LOGIN_ENABLED":"f","GOOGLE_CLIENT_ID":"123","GOOGLE_CLIENT_SECRET":"123"}
3
- # See @node_modules/mbkauthe/docs/env.md for more details
3
+ # See @node_modules/mbkauthe/docs/env.md for more details
4
+ NODE_ENV=development
5
+
6
+ R2_Bucket={"BUCKET_NAME":"","ACCESS_KEY_ID":"","SECRET_ACCESS_KEY":"","ENDPOINT":""}
7
+ E2_Bucket={"BUCKET_NAME":"","ACCESS_KEY_ID":"","SECRET_ACCESS_KEY":"","ENDPOINT":"","Region":"ap-southeast-1"}
8
+ # Region is optional
9
+
10
+ PORT=3005
package/README.md CHANGED
@@ -3,4 +3,10 @@
3
3
  [![Version](https://img.shields.io/npm/v/mbkbucket.svg)](https://www.npmjs.com/package/mbkbucket)
4
4
  [![License](https://img.shields.io/badge/License-GPL--2.0-blue.svg)](LICENSE)
5
5
  [![Node.js](https://img.shields.io/badge/node-%3E%3D14.0.0-brightgreen.svg)](https://nodejs.org/)
6
- [![Publish](https://github.com/MIbnEKhalid/mbkbucket/actions/workflows/publish.yml/badge.svg?branch=main)](https://github.com/MIbnEKhalid/mbkbucket/actions/workflows/publish.yml)
6
+ [![Publish](https://github.com/MIbnEKhalid/mbkbucket/actions/workflows/publish.yml/badge.svg?branch=main)](https://github.com/MIbnEKhalid/mbkbucket/actions/workflows/publish.yml)
7
+
8
+ ## Contact
9
+
10
+ For questions or contributions, please contact Muhammad Bin Khalid at [mbktechstudio.com/Support](https://mbktechstudio.com/Support/?Project=MIbnEKhalidWeb), [support@mbktechstudio.com](mailto:support@mbktechstudio.com) or [chmuhammadbinkhalid28@gmail.com](mailto:chmuhammadbinkhalid28@gmail.com).
11
+
12
+ Developed by [Muhammad Bin Khalid](https://github.com/MIbnEKhalid) at [MBK Tech Studio](https://mbktech.org/).
package/index.d.ts CHANGED
@@ -1,6 +1,237 @@
1
- // Type definitions for mbkbucket
1
+ // Type definitions for mbkbucket 1.1.0
2
2
  // Project: https://github.com/MIbnEKhalid/mbkbucket
3
- // Definitions by: Muhammad Bin Khalid <https://github.com/MIbnEKhalid>
3
+ // Definitions by: Muhammad Bin Khalid <https://github.com/MIbnEKhalid">
4
4
 
5
- import { Request, Response, NextFunction, Router } from 'express';
6
- import { Pool } from 'pg';
5
+ import { Readable } from "stream";
6
+ import { Router, Application } from "express";
7
+
8
+ /**
9
+ * Shared types used across the package
10
+ */
11
+ export interface BucketConfig {
12
+ BUCKET_NAME: string;
13
+ ENDPOINT?: string;
14
+ ACCESS_KEY_ID?: string;
15
+ SECRET_ACCESS_KEY?: string;
16
+ region?: string;
17
+ [key: string]: any;
18
+ }
19
+
20
+ export interface S3ObjectResult {
21
+ Body: Readable;
22
+ ContentLength?: number;
23
+ ContentType?: string;
24
+ LastModified?: Date;
25
+ Key?: string;
26
+ [key: string]: any;
27
+ }
28
+
29
+ /**
30
+ * S3-related functions (available from `mbkbucket/lib/s3` and re-exported at package root)
31
+ */
32
+ export function getBucketConfig(bucketName?: string): BucketConfig;
33
+ export const bucketClient: any;
34
+ export function getAppName(): string;
35
+ export function ensureKeyHasAppPrefix(key?: string): string;
36
+ export function ensurePrefix(prefix?: string): string;
37
+ export function getBucketClient(bucketName?: string): any;
38
+ export function checkR2Health(): Promise<{ status: string; responseTime?: number; bucket?: string; error?: string; checkedAt: string; region?: string }>;
39
+
40
+ export function uploadFile(
41
+ key: string,
42
+ fileBuffer: Buffer | Uint8Array,
43
+ contentType: string,
44
+ options?: {
45
+ metadata?: Record<string, string>;
46
+ cacheControl?: string;
47
+ storageClass?: string;
48
+ serverSideEncryption?: string;
49
+ bucketName?: string;
50
+ [key: string]: any;
51
+ }
52
+ ): Promise<{ fileSize?: number; key: string; contentType?: string; uploadedAt?: string } & Record<string, any>>;
53
+
54
+ export function downloadFile(
55
+ key: string,
56
+ options?: {
57
+ range?: string | null;
58
+ ifNoneMatch?: string | null;
59
+ ifModifiedSince?: Date | string | null;
60
+ responseCacheControl?: string | null;
61
+ responseContentType?: string | null;
62
+ bucketName?: string;
63
+ [key: string]: any;
64
+ }
65
+ ): Promise<S3ObjectResult>;
66
+
67
+ export function deleteFile(key: string, bucketName?: string): Promise<{ key: string; deletedAt?: string } & Record<string, any>>;
68
+
69
+ export function deleteFiles(keys: string[], bucketName?: string): Promise<{
70
+ results: any[];
71
+ deletedCount: number;
72
+ errors: any[];
73
+ deletedAt: string;
74
+ }>;
75
+
76
+ export function listfiles(
77
+ prefix?: string,
78
+ options?: {
79
+ maxKeys?: number;
80
+ continuationToken?: string | null;
81
+ delimiter?: string | null;
82
+ fetchOwner?: boolean;
83
+ startAfter?: string | null;
84
+ bucketName?: string;
85
+ [key: string]: any;
86
+ }
87
+ ): Promise<Record<string, any>>;
88
+
89
+ export function getFileMetadata(key: string, bucketName?: string): Promise<{
90
+ key: string;
91
+ exists: boolean;
92
+ queriedAt: string;
93
+ ContentLength?: number;
94
+ ContentType?: string;
95
+ LastModified?: Date;
96
+ [key: string]: any;
97
+ }>;
98
+
99
+ export function fileExists(key: string, bucketName?: string): Promise<boolean>;
100
+ export function getFileSize(key: string, bucketName?: string): Promise<number | null>;
101
+
102
+ export function generateSignedUrl(
103
+ key: string,
104
+ operation?: 'getObject' | 'putObject' | string,
105
+ expiresIn?: number
106
+ ): Promise<{
107
+ url: string;
108
+ key: string;
109
+ operation: string;
110
+ expiresIn: number;
111
+ expiresAt: string;
112
+ generatedAt: string;
113
+ }>;
114
+
115
+ /**
116
+ * Exported router from `lib/bucket` (re-exported at package root as `bucket`)
117
+ */
118
+ export const bucket: Router;
119
+
120
+ /**
121
+ * Root default export: the configured Express application
122
+ */
123
+ declare const server: Application;
124
+ export default server;
125
+
126
+ /**
127
+ * Module declarations for subpath imports so TypeScript consumers can import:
128
+ * import { uploadFile } from 'mbkbucket/lib/s3';
129
+ * import { packageJson } from 'mbkbucket/lib/config/index';
130
+ * import bucketRouter from 'mbkbucket/lib/bucket';
131
+ */
132
+ declare module "mbkbucket/lib/s3" {
133
+ import { Readable } from "stream";
134
+ export interface BucketConfig {
135
+ BUCKET_NAME: string;
136
+ ENDPOINT?: string;
137
+ ACCESS_KEY_ID?: string;
138
+ SECRET_ACCESS_KEY?: string;
139
+ region?: string;
140
+ [key: string]: any;
141
+ }
142
+ export interface S3ObjectResult {
143
+ Body: Readable;
144
+ ContentLength?: number;
145
+ ContentType?: string;
146
+ LastModified?: Date;
147
+ Key?: string;
148
+ [key: string]: any;
149
+ }
150
+ export function getBucketConfig(bucketName?: string): BucketConfig;
151
+ export const bucketClient: any;
152
+ export function getAppName(): string;
153
+ export function ensureKeyHasAppPrefix(key?: string): string;
154
+ export function ensurePrefix(prefix?: string): string;
155
+ export function getBucketClient(bucketName?: string): any;
156
+ export function checkR2Health(): Promise<{ status: string; responseTime?: number; bucket?: string; error?: string; checkedAt: string; region?: string }>;
157
+ export function uploadFile(
158
+ key: string,
159
+ fileBuffer: Buffer | Uint8Array,
160
+ contentType: string,
161
+ options?: {
162
+ metadata?: Record<string, string>;
163
+ cacheControl?: string;
164
+ storageClass?: string;
165
+ serverSideEncryption?: string;
166
+ bucketName?: string;
167
+ [key: string]: any;
168
+ }
169
+ ): Promise<{ fileSize?: number; key: string; contentType?: string; uploadedAt?: string } & Record<string, any>>;
170
+ export function downloadFile(
171
+ key: string,
172
+ options?: {
173
+ range?: string | null;
174
+ ifNoneMatch?: string | null;
175
+ ifModifiedSince?: Date | string | null;
176
+ responseCacheControl?: string | null;
177
+ responseContentType?: string | null;
178
+ bucketName?: string;
179
+ [key: string]: any;
180
+ }
181
+ ): Promise<S3ObjectResult>;
182
+ export function deleteFile(key: string, bucketName?: string): Promise<{ key: string; deletedAt?: string } & Record<string, any>>;
183
+ export function deleteFiles(keys: string[], bucketName?: string): Promise<{
184
+ results: any[];
185
+ deletedCount: number;
186
+ errors: any[];
187
+ deletedAt: string;
188
+ }>;
189
+ export function listfiles(
190
+ prefix?: string,
191
+ options?: {
192
+ maxKeys?: number;
193
+ continuationToken?: string | null;
194
+ delimiter?: string | null;
195
+ fetchOwner?: boolean;
196
+ startAfter?: string | null;
197
+ bucketName?: string;
198
+ [key: string]: any;
199
+ }
200
+ ): Promise<Record<string, any>>;
201
+ export function getFileMetadata(key: string, bucketName?: string): Promise<{
202
+ key: string;
203
+ exists: boolean;
204
+ queriedAt: string;
205
+ ContentLength?: number;
206
+ ContentType?: string;
207
+ LastModified?: Date;
208
+ [key: string]: any;
209
+ }>;
210
+ export function fileExists(key: string, bucketName?: string): Promise<boolean>;
211
+ export function getFileSize(key: string, bucketName?: string): Promise<number | null>;
212
+ export function generateSignedUrl(
213
+ key: string,
214
+ operation?: 'getObject' | 'putObject' | string,
215
+ expiresIn?: number
216
+ ): Promise<{
217
+ url: string;
218
+ key: string;
219
+ operation: string;
220
+ expiresIn: number;
221
+ expiresAt: string;
222
+ generatedAt: string;
223
+ }>;
224
+ }
225
+
226
+ declare module "mbkbucket/lib/config/index" {
227
+ export const packageJson: Record<string, any>;
228
+ export const appVersion: string;
229
+ export function getLatestVersion(): Promise<string | null>;
230
+ export function checkVersion(): Promise<void>;
231
+ }
232
+
233
+ declare module "mbkbucket/lib/bucket" {
234
+ import { Router } from "express";
235
+ const router: Router;
236
+ export default router;
237
+ }
package/index.js CHANGED
@@ -1,9 +1,198 @@
1
+ import express from "express";
2
+ import path from "path";
3
+ import dotenv from "dotenv";
4
+ import { fileURLToPath } from "url";
5
+ import mbkauthe from "mbkauthe";
6
+ import { engine } from "express-handlebars";
7
+ import compression from "compression";
8
+ import rateLimit from 'express-rate-limit';
9
+ import bucket from "./lib/bucket.js";
1
10
  import { checkVersion } from "./lib/config/index.js";
2
11
 
12
+ dotenv.config();
13
+
14
+ const __filename = fileURLToPath(import.meta.url);
15
+ const __dirname = path.dirname(__filename);
16
+
17
+ const server = express();
18
+ server.set('trust proxy', 1);
19
+
20
+ server.use(compression());
21
+
22
+ // Rate limiting: general limiter for typical browsing/API usage and a stricter
23
+ // limiter for dashboard (admin) routes.
24
+ const generalLimiter = rateLimit({
25
+ windowMs: 2 * 60 * 1000, // 2 minutes
26
+ max: 150, // limit each IP to 150 requests per windowMs
27
+ standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
28
+ legacyHeaders: false, // Disable the `X-RateLimit-*` headers
29
+ handler: (req, res) => {
30
+ res.status(429).render('error.handlebars', { message: 'Too many requests from your IP. Try again later.', code: 429 });
31
+ }
32
+ });
33
+
34
+ server.use(express.json());
35
+ server.use(express.urlencoded({ extended: true }));
36
+
37
+ // Configure Handlebars (single setup)
38
+ server.engine("handlebars", engine({
39
+ extname: ".handlebars",
40
+ defaultLayout: "main",
41
+ partialsDir: [
42
+ path.join(__dirname, "views/templates"),
43
+ path.join(__dirname, "views/templates/notice"),
44
+ path.join(__dirname, "views"),
45
+ path.join(__dirname, "views/partial"),
46
+ path.join(__dirname, "node_modules/mbkauthe/views"),
47
+ ],
48
+ cache: process.env.NODE_ENV === "production",
49
+ helpers: {
50
+ formatDate: (date) => {
51
+ return new Date(date).toLocaleString('en-US', {
52
+ year: 'numeric',
53
+ month: 'long',
54
+ day: 'numeric',
55
+ hour: '2-digit',
56
+ minute: '2-digit',
57
+ second: '2-digit',
58
+ hour12: true
59
+ });
60
+ },
61
+ in: function (value, list) {
62
+ if (!list || !Array.isArray(list)) return false;
63
+ return list.includes(parseInt(value) || value);
64
+ },
65
+ trim: function (str) {
66
+ return str ? str.trim() : '';
67
+ },
68
+ eq: function (a, b) {
69
+ return a === b;
70
+ },
71
+ encodeURIComponent: function (str) {
72
+ return encodeURIComponent(str);
73
+ },
74
+ formatTimestamp: function (timestamp) {
75
+ return new Date(timestamp).toLocaleString();
76
+ },
77
+ jsonStringify: function (context) {
78
+ return JSON.stringify(context);
79
+ },
80
+ truncate: (str, len) => {
81
+ if (!str) return '';
82
+ if (str.length > len) {
83
+ return str.substring(0, len) + '...';
84
+ }
85
+ return str;
86
+ },
87
+ section: function (name, options) {
88
+ if (!this._sections) this._sections = {};
89
+ this._sections[name] = options.fn(this);
90
+ return null;
91
+ },
92
+ getCanonicalUrl: function (req, path) {
93
+ const protocol = req.protocol || 'https';
94
+ const host = req.get('host') || 'blog.mbktechstudio.com';
95
+ return `${protocol}://${host}${path}`;
96
+ },
97
+ index: function (array, idx) {
98
+ return array ? array[idx] : null;
99
+ },
100
+ add: function (a, b) {
101
+ return Number(a) + Number(b);
102
+ },
103
+ subtract: function (a, b) {
104
+ return Number(a) - Number(b);
105
+ },
106
+ gt: function (a, b) {
107
+ return Number(a) > Number(b);
108
+ },
109
+ gte: function (a, b) {
110
+ return Number(a) >= Number(b);
111
+ },
112
+ lte: function (a, b) {
113
+ return Number(a) <= Number(b);
114
+ },
115
+ and: function () {
116
+ return Array.prototype.slice.call(arguments, 0, -1).every(Boolean);
117
+ },
118
+ or: function () {
119
+ return Array.prototype.slice.call(arguments, 0, -1).some(Boolean);
120
+ },
121
+ range: function (start, end) {
122
+ const result = [];
123
+ for (let i = start; i < end; i++) {
124
+ result.push(i);
125
+ }
126
+ return result;
127
+ }
128
+ }
129
+ }));
130
+
131
+ server.set("view engine", "handlebars");
132
+ server.set("views", [
133
+ path.join(__dirname, "views"),
134
+ path.join(__dirname, "node_modules/mbkauthe/views"),
135
+ ]);
136
+
137
+ server.use(mbkauthe);
138
+ server.use(generalLimiter);
139
+ server.use(bucket);
140
+
3
141
  if (process.env.test === "dev") {
4
- console.log("[mbkbucket] Dev mode is enabled. Starting server in dev mode.");
142
+
143
+ console.log("[mbkbucket] Dev mode is enabled. Starting server in dev mode.");
144
+
145
+
146
+ // Request timing middleware: logs method, url, status and elapsed ms
147
+ server.use((req, res, next) => {
148
+ const start = process.hrtime();
149
+ res.on('finish', () => {
150
+ const diff = process.hrtime(start);
151
+ const ms = diff[0] * 1000 + diff[1] / 1e6;
152
+ console.log(`[mbkbucket] [${new Date().toISOString()}] ${req.method} ${req.originalUrl} ${res.statusCode} - ${ms.toFixed(3)} ms`);
153
+ });
154
+ next();
155
+ });
156
+
157
+ server.get("/", async (req, res) => {
158
+ res.redirect("/mbkbucket");
159
+ });
160
+
161
+ // 404 handler
162
+ server.use((req, res) => {
163
+ console.log(`[mbkbucket] Path not found: ${req.method} ${req.url}`);
164
+ return res.status(404).render("Error/dError.handlebars", {
165
+ layout: false,
166
+ code: 404,
167
+ error: "Not Found",
168
+ message: "The requested page was not found.",
169
+ pagename: "Home",
170
+ page: "/",
171
+ });
172
+ });
173
+
174
+ // Error handler
175
+ server.use((err, req, res, next) => {
176
+ console.error(`[mbkbucket] ${err.stack}`);
177
+ return res.status(500).render("Error/dError.handlebars", {
178
+ layout: false,
179
+ code: 500,
180
+ error: "Internal app Error",
181
+ message: "An unexpected error occurred on the app.",
182
+ details: err.message,
183
+ pagename: "Home",
184
+ page: "/",
185
+ });
186
+ });
187
+
188
+ const port = process.env.PORT || 3004;
189
+ server.listen(port, async () => {
190
+ console.log(`[mbkbucket] Server running on http://localhost:${port}`);
191
+ });
192
+
5
193
  }
6
194
 
7
- if (process.env.test !== "dev") {
8
- await checkVersion();
9
- }
195
+ await checkVersion();
196
+
197
+
198
+ export default server;