From 09441c495b073e9bf27b2d4a5b247b03d93e8a58 Mon Sep 17 00:00:00 2001 From: "Tom (Whity)" <129990841+deployor@users.noreply.github.com> Date: Sat, 18 Jan 2025 03:49:27 +0100 Subject: [PATCH 01/11] =?UTF-8?q?Codebase=20Rewrite=20=E2=80=93=20Slack=20?= =?UTF-8?q?Bot,=20Backblaze=20B2=20Migration,=20API=20v3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This update is a full rewrite of the codebase with major improvements 💪 : - Slack Bot Integration – Added now built-in Slack bot! - Backblaze B2 Migration – Switched from Vercel to B2, cutting storage / egress costs by around 90%. - API v3 – New version includes file hashes, sizes, and additional metadata. - API Token Requirement – ⚠️ All older API versions (v1, v2) now require authentication tokens. ⚠️ -- Deployor 💜 --- .gitignore | 8 +- README.md | 285 ++++++++++++++++++++++++++++++++++++------- api/v1/new.ts | 128 ------------------- api/v1/newSingle.ts | 64 ---------- api/v2/deploy.ts | 40 ------ api/v2/new.ts | 37 ------ api/v2/upload.ts | 53 -------- api/v2/utils.ts | 57 --------- index.js | 88 +++++++++++++ logger.js | 9 ++ package.json | 22 ++++ src/api.js | 17 +++ src/api/deploy.js | 26 ++++ src/api/index.js | 85 +++++++++++++ src/api/upload.js | 56 +++++++++ src/api/utils.js | 45 +++++++ src/backblaze.js | 32 +++++ src/config/logger.js | 23 ++++ src/fileUpload.js | 230 ++++++++++++++++++++++++++++++++++ src/upload.js | 32 +++++ src/utils.js | 8 ++ vercel.json | 11 -- 22 files changed, 919 insertions(+), 437 deletions(-) delete mode 100644 api/v1/new.ts delete mode 100644 api/v1/newSingle.ts delete mode 100644 api/v2/deploy.ts delete mode 100644 api/v2/new.ts delete mode 100644 api/v2/upload.ts delete mode 100644 api/v2/utils.ts create mode 100644 index.js create mode 100644 logger.js create mode 100644 package.json create mode 100644 src/api.js create mode 100644 src/api/deploy.js create mode 100644 src/api/index.js create mode 100644 src/api/upload.js create mode 100644 src/api/utils.js create mode 100644 src/backblaze.js create mode 100644 src/config/logger.js create mode 100644 src/fileUpload.js create mode 100644 src/upload.js create mode 100644 src/utils.js delete mode 100644 vercel.json diff --git a/.gitignore b/.gitignore index edaaef5..01d6024 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ -.env -.vercel -.vscode +/node_modules/ +/splitfornpm/ +/.idea/ +/.env +/package-lock.json diff --git a/README.md b/README.md index f01d49a..3a883d1 100644 --- a/README.md +++ b/README.md @@ -1,44 +1,241 @@ -
Deep under the waves and storms there lies a vault...
-
Illustration above by @maxwofford.
- ---- - -CDN powers the [#cdn](https://app.slack.com/client/T0266FRGM/C016DEDUL87) channel in the [Hack Club Slack](https://hackclub.com/slack). - -## Version 2
-
-Post this JSON...
-```js
-[
- "website.com/somefile.png",
- "website.com/somefile.gif",
-]
-```
-
-And it'll return the following:
-```js
-{
- "0somefile.png": "cdnlink.vercel.app/0somefile.png",
- "1somefile.gif": "cdnlink.vercel.app/1somefile.gif"
-}
-```
-
-## Version 1
-
-Post this JSON...
-```js
-[
- "website.com/somefile.png",
- "website.com/somefile.gif",
-]
-```
-
-And it'll return the following:
-```js
-[
- "cdnlink.vercel.app/0somefile.png",
- "cdnlink.vercel.app/1somefile.gif"
-]
-```
+A CDN solution for Hack Club!
+Deep under the waves and storms there lies a vault...
+ + + +## 🚀 Features + +- **Multi-version API Support** (v1, v2, v3) +- **Slack Bot Integration** + - Upload up to 10 files per message + - Automatic file sanitization + - file organization +- **Secure API Endpoints** +- **Cost-Effective Storage** (87-98% cost reduction vs. Vercel CDN) +- **Prevent File Deduplication** +- **Organized Storage Structure** + +## 🔧 Setup + +### 1. Slack App Configuration + +1. Create a new Slack App at [api.slack.com](https://api.slack.com/apps) +2. Enable Socket Mode in the app settings +3. Add the following Bot Token Scopes: + - `channels:history` + - `channels:read` + - `chat:write` + - `files:read` + - `files:write` + - `groups:history` + - `reactions:write` +4. Enable Event Subscriptions and subscribe to `file_shared` event +5. Install the app to your workspace + +### 2. CDN Configuration (Cloudflare + Backblaze) + +1. Create a Backblaze B2 bucket +2. Set up Cloudflare DNS: + - Add a CNAME record pointing to your B2 bucket (e.g., `f003.backblazeb2.com`) you can upload a file and check in info! + - Enable Cloudflare proxy +3. Configure SSL/TLS: + - Set SSL mode to "Full (strict)" + - ⚠️ **WARNING**: This setting may break other configurations on your domain! You could use another domain! +4. Create a Transform Rule: + - Filter: `hostname equals "your-cdn.example.com"` + - Rewrite to: `concat("/file/(bucket name)", http.request.uri.path)` (make sure u get the bucket name) + - Preserve query string + +### 3. Environment Setup + +Create a `.env` file with: +```env +# Slack +SLACK_BOT_TOKEN=xoxb- # From OAuth & Permissions +SLACK_SIGNING_SECRET= # From Basic Information +SLACK_APP_TOKEN=xapp- # From Basic Information (for Socket Mode) +SLACK_CHANNEL_ID=channel-id # Channel where bot operates + +# Backblaze (Public Bucket) +B2_APP_KEY_ID=key-id # From B2 Application Keys +B2_APP_KEY=app-key # From B2 Application Keys +B2_BUCKET_ID=bucket-id # From B2 Bucket Settings +B2_CDN_URL=https://cdn.example.com + +# API +API_TOKEN=beans # Set a secure random string +PORT=3000 +``` + +### 4. Installation & Running + +```bash +npm install +node index.js +``` +Feel free to use pm2! + +## 📡 API Usage + +⚠️ **IMPORTANT SECURITY NOTE**: +- All API endpoints require authentication via `Authorization: Bearer api-token` header +- This includes all versions (v1, v2, v3) - no exceptions! +- Use the API_TOKEN from your environment configuration +- Failure to include a valid token will result in 401 Unauthorized responses + +### V3 API (Latest) +
+
+**Endpoint:** `POST https://e2.deployor.hackclub.app/api/v3/new`
+
+**Headers:**
+```
+Authorization: Bearer api-token
+Content-Type: application/json
+```
+
+**Request Example:**
+```bash
+curl --location 'https://e2.deployor.hackclub.app/api/v3/new' \
+--header 'Authorization: Bearer beans' \
+--header 'Content-Type: application/json' \
+--data '[
+ "https://assets.hackclub.com/flag-standalone.svg",
+ "https://assets.hackclub.com/flag-orpheus-left.png",
+ "https://assets.hackclub.com/icon-progress-marker.svg"
+]'
+```
+
+**Response:**
+```json
+{
+ "files": [
+ {
+ "deployedUrl": "https://cdn.deployor.dev/s/v3/3e48b91a4599a3841c028e9a683ef5ce58cea372_flag-standalone.svg",
+ "file": "0_16361167e11b0d172a47e726b40d70e9873c792b_upload_1736985095691",
+ "sha": "16361167e11b0d172a47e726b40d70e9873c792b",
+ "size": 90173
+ }
+ // Other files
+ ],
+ "cdnBase": "https://cdn.deployor.dev"
+}
+```
+
+
+
+**Endpoint:** `POST https://e2.deployor.hackclub.app/api/v2/new`
+
+**Headers:**
+```
+Authorization: Bearer api-token
+Content-Type: application/json
+```
+
+**Request Example:**
+```json
+[
+ "https://assets.hackclub.com/flag-standalone.svg",
+ "https://assets.hackclub.com/flag-orpheus-left.png",
+ "https://assets.hackclub.com/icon-progress-marker.svg"
+]
+```
+
+**Response:**
+```json
+{
+ "flag-standalone.svg": "https://cdn.deployor.dev/s/v2/flag-standalone.svg",
+ "flag-orpheus-left.png": "https://cdn.deployor.dev/s/v2/flag-orpheus-left.png",
+ "icon-progress-marker.svg": "https://cdn.deployor.dev/s/v2/icon-progress-marker.svg"
+}
+```
+
+
+**Endpoint:** `POST https://e2.deployor.hackclub.app/api/v1/new`
+
+**Headers:**
+```
+Authorization: Bearer api-token
+Content-Type: application/json
+```
+
+**Request Example:**
+```json
+[
+ "https://assets.hackclub.com/flag-standalone.svg",
+ "https://assets.hackclub.com/flag-orpheus-left.png",
+ "https://assets.hackclub.com/icon-progress-marker.svg"
+]
+```
+
+**Response:**
+```json
+[
+ "https://cdn.deployor.dev/s/v1/0_flag-standalone.svg",
+ "https://cdn.deployor.dev/s/v1/1_flag-orpheus-left.png",
+ "https://cdn.deployor.dev/s/v1/2_icon-progress-marker.svg"
+]
+```
+A CDN solution for Hack Club!
Deep under the waves and storms there lies a vault...
+
Banner illustration by @maxwofford.
@@ -79,13 +79,45 @@ API_TOKEN=beans # Set a secure random string PORT=3000 ``` -### 4. Installation & Running +Here's an improved version of your README section with better clarity and formatting: + +--- + +### **4. Installation & Running** + +#### **Install Dependencies** +Make sure you have [Bun](https://bun.sh/) installed, then run: ```bash -npm install -node index.js +bun install +``` + +#### **Run the Application** +You can start the application using any of the following methods: + +```bash +# Using Node.js +node index.js + +# Using Bun +bun index.js + +# Using Bun with script +bun run start +``` + +#### **Using PM2 (Optional)** +For auto-starting the application, you can use PM2: + +```bash +pm2 start bun --name "HC-CDN1" -- run start + +# Optionally, save the process list +pm2 save + +# Optionally, generate startup script +pm2 startup ``` -Feel free to use pm2! ## 📡 API Usage @@ -121,16 +153,33 @@ curl --location 'https://e2.deployor.hackclub.app/api/v3/new' \ **Response:** ```json { - "files": [ - { - "deployedUrl": "https://cdn.deployor.dev/s/v3/3e48b91a4599a3841c028e9a683ef5ce58cea372_flag-standalone.svg", - "file": "0_16361167e11b0d172a47e726b40d70e9873c792b_upload_1736985095691", - "sha": "16361167e11b0d172a47e726b40d70e9873c792b", - "size": 90173 - } - // Other files - ], - "cdnBase": "https://cdn.deployor.dev" + "files": [ + { + "deployedUrl": "https://cdn.deployor.dev/s/v3/3e48b91a4599a3841c028e9a683ef5ce58cea372_flag-standalone.svg", + "file": "0_16361167e11b0d172a47e726b40d70e9873c792b_upload_1736985095691", + "sha": "16361167e11b0d172a47e726b40d70e9873c792b", + "size": 90173 + }, + { + "deployedUrl": "https://cdn.deployor.dev/s/v3/4e48b91a4599a3841c028e9a683ef5ce58cea372_flag-orpheus-left.png", + "file": "1_16361167e11b0d172a47e726b40d70e9873c792b_upload_1736985095692", + "sha": "16361167e11b0d172a47e726b40d70e9873c792b", + "size": 80234 + }, + { + "deployedUrl": "https://cdn.deployor.dev/s/v3/5e48b91a4599a3841c028e9a683ef5ce58cea372_icon-progress-marker.svg", + "file": "2_16361167e11b0d172a47e726b40d70e9873c792b_upload_1736985095693", + "sha": "16361167e11b0d172a47e726b40d70e9873c792b", + "size": 70345 + }, + { + "deployedUrl": "https://cdn.deployor.dev/s/v3/6e48b91a4599a3841c028e9a683ef5ce58cea372_flag-orpheus-right.png", + "file": "3_16361167e11b0d172a47e726b40d70e9873c792b_upload_1736985095694", + "sha": "16361167e11b0d172a47e726b40d70e9873c792b", + "size": 60456 + } + ], + "cdnBase": "https://cdn.deployor.dev" } ``` From 495d1410a93e55c233efb06537e721b5fcb28d6c Mon Sep 17 00:00:00 2001 From: "Tom (Whity)" <129990841+deployor@users.noreply.github.com> Date: Thu, 23 Jan 2025 15:18:53 +0100 Subject: [PATCH 07/11] Migrated fully to S3 API --- src/backblaze.js | 32 -------------------------------- 1 file changed, 32 deletions(-) delete mode 100644 src/backblaze.js diff --git a/src/backblaze.js b/src/backblaze.js deleted file mode 100644 index 15147e0..0000000 --- a/src/backblaze.js +++ /dev/null @@ -1,32 +0,0 @@ -const B2 = require('backblaze-b2'); -const logger = require('./config/logger'); - -const b2 = new B2({ - applicationKeyId: process.env.B2_APP_KEY_ID, - applicationKey: process.env.B2_APP_KEY -}); - -async function uploadToBackblaze(userDir, uniqueFileName, buffer) { - try { - await b2.authorize(); - const {data} = await b2.getUploadUrl({ - bucketId: process.env.B2_BUCKET_ID - }); - - await b2.uploadFile({ - uploadUrl: data.uploadUrl, - uploadAuthToken: data.authorizationToken, - fileName: `${userDir}/${uniqueFileName}`, - data: buffer - }); - - return true; - } catch (error) { - logger.error('B2 upload failed:', error.message); - return false; - } -} - -module.exports = {uploadToBackblaze}; - -// So easy i love it! \ No newline at end of file From 16c690583df9db964564f3081a41310d3068c273 Mon Sep 17 00:00:00 2001 From: "Tom (Whity)" <129990841+deployor@users.noreply.github.com> Date: Thu, 23 Jan 2025 15:19:24 +0100 Subject: [PATCH 08/11] Migrated Fully to S3 Api 2 --- README.md | 94 +++++++------ index.js | 42 +++--- package.json | 4 +- src/api/deploy.js | 3 +- src/api/index.js | 4 +- src/api/upload.js | 63 +++++++-- src/api/utils.js | 2 +- src/config/logger.js | 30 ++--- src/fileUpload.js | 130 +++++++++--------- src/storage.js | 310 +++++++++++++++++++++++++++++++++++++++++++ src/upload.js | 11 +- src/utils.js | 2 +- 12 files changed, 526 insertions(+), 169 deletions(-) create mode 100644 src/storage.js diff --git a/README.md b/README.md index 93fcf98..1a6e014 100644 --- a/README.md +++ b/README.md @@ -44,19 +44,36 @@ 4. Enable Event Subscriptions and subscribe to `file_shared` event 5. Install the app to your workspace -### 2. CDN Configuration (Cloudflare + Backblaze) +### 2. Storage Configuration -1. Create a Backblaze B2 bucket -2. Set up Cloudflare DNS: - - Add a CNAME record pointing to your B2 bucket (e.g., `f003.backblazeb2.com`) you can upload a file and check in info! - - Enable Cloudflare proxy -3. Configure SSL/TLS: - - Set SSL mode to "Full (strict)" - - ⚠️ **WARNING**: This setting may break other configurations on your domain! You could use another domain! -4. Create a Transform Rule: - - Filter: `hostname equals "your-cdn.example.com"` - - Rewrite to: `concat("/file/(bucket name)", http.request.uri.path)` (make sure u get the bucket name) - - Preserve query string +This CDN supports any S3-compatible storage service. Here's how to set it up using Cloudflare R2 as an example: + +#### Setting up Cloudflare R2 (Example) + +1. **Create R2 Bucket** + - Go to Cloudflare Dashboard > R2 + - Click "Create Bucket" + - Name your bucket + - Enable public access + +2. **Generate API Credentials** + - Go to R2 + - Click "Manage API tokens" in API + - Click "Create API Token" + - Permissions: "Object Read & Write" + - Save both Access Key ID and Secret Access Key (S3) + +3. **Get Your URL** + - Go to R2 + - Click "Use R2 with APIs" in API + - Select S3 Compatible API + - The URL is your Endpoint + + +4. **Configure Custom Domain (Optional)** + - Go to R2 > Bucket Settings > Custom Domains + - Add your domain (e.g., cdn.beans.com) + - Follow DNS configuration steps ### 3. Environment Setup @@ -68,21 +85,19 @@ SLACK_SIGNING_SECRET= # From Basic Information SLACK_APP_TOKEN=xapp- # From Basic Information (for Socket Mode) SLACK_CHANNEL_ID=channel-id # Channel where bot operates -# Backblaze (Public Bucket) -B2_APP_KEY_ID=key-id # From B2 Application Keys -B2_APP_KEY=app-key # From B2 Application Keys -B2_BUCKET_ID=bucket-id # From B2 Bucket Settings -B2_CDN_URL=https://cdn.example.com +# S3 Config CF in this example +AWS_ACCESS_KEY_ID=1234567890abcdef +AWS_SECRET_ACCESS_KEY=abcdef1234567890 +AWS_BUCKET_NAME=my-cdn-bucket +AWS_REGION=auto +AWS_ENDPOINT=https://
-**Endpoint:** `POST https://e2.deployor.hackclub.app/api/v3/new`
+**Endpoint:** `POST https://e2.example.hackclub.app/api/v3/new`
**Headers:**
```
@@ -140,7 +155,7 @@ Content-Type: application/json
**Request Example:**
```bash
-curl --location 'https://e2.deployor.hackclub.app/api/v3/new' \
+curl --location 'https://e2.example.hackclub.app/api/v3/new' \
--header 'Authorization: Bearer beans' \
--header 'Content-Type: application/json' \
--data '[
@@ -155,31 +170,31 @@ curl --location 'https://e2.deployor.hackclub.app/api/v3/new' \
{
"files": [
{
- "deployedUrl": "https://cdn.deployor.dev/s/v3/3e48b91a4599a3841c028e9a683ef5ce58cea372_flag-standalone.svg",
+ "deployedUrl": "https://cdn.example.dev/s/v3/3e48b91a4599a3841c028e9a683ef5ce58cea372_flag-standalone.svg",
"file": "0_16361167e11b0d172a47e726b40d70e9873c792b_upload_1736985095691",
"sha": "16361167e11b0d172a47e726b40d70e9873c792b",
"size": 90173
},
{
- "deployedUrl": "https://cdn.deployor.dev/s/v3/4e48b91a4599a3841c028e9a683ef5ce58cea372_flag-orpheus-left.png",
+ "deployedUrl": "https://cdn.example.dev/s/v3/4e48b91a4599a3841c028e9a683ef5ce58cea372_flag-orpheus-left.png",
"file": "1_16361167e11b0d172a47e726b40d70e9873c792b_upload_1736985095692",
"sha": "16361167e11b0d172a47e726b40d70e9873c792b",
"size": 80234
},
{
- "deployedUrl": "https://cdn.deployor.dev/s/v3/5e48b91a4599a3841c028e9a683ef5ce58cea372_icon-progress-marker.svg",
+ "deployedUrl": "https://cdn.example.dev/s/v3/5e48b91a4599a3841c028e9a683ef5ce58cea372_icon-progress-marker.svg",
"file": "2_16361167e11b0d172a47e726b40d70e9873c792b_upload_1736985095693",
"sha": "16361167e11b0d172a47e726b40d70e9873c792b",
"size": 70345
},
{
- "deployedUrl": "https://cdn.deployor.dev/s/v3/6e48b91a4599a3841c028e9a683ef5ce58cea372_flag-orpheus-right.png",
+ "deployedUrl": "https://cdn.example.dev/s/v3/6e48b91a4599a3841c028e9a683ef5ce58cea372_flag-orpheus-right.png",
"file": "3_16361167e11b0d172a47e726b40d70e9873c792b_upload_1736985095694",
"sha": "16361167e11b0d172a47e726b40d70e9873c792b",
"size": 60456
}
],
- "cdnBase": "https://cdn.deployor.dev"
+ "cdnBase": "https://cdn.example.dev"
}
```
@@ -188,7 +203,7 @@ curl --location 'https://e2.deployor.hackclub.app/api/v3/new' \
-**Endpoint:** `POST https://e2.deployor.hackclub.app/api/v2/new`
+**Endpoint:** `POST https://e2.example.hackclub.app/api/v2/new`
**Headers:**
```
@@ -208,9 +223,9 @@ Content-Type: application/json
**Response:**
```json
{
- "flag-standalone.svg": "https://cdn.deployor.dev/s/v2/flag-standalone.svg",
- "flag-orpheus-left.png": "https://cdn.deployor.dev/s/v2/flag-orpheus-left.png",
- "icon-progress-marker.svg": "https://cdn.deployor.dev/s/v2/icon-progress-marker.svg"
+ "flag-standalone.svg": "https://cdn.example.dev/s/v2/flag-standalone.svg",
+ "flag-orpheus-left.png": "https://cdn.example.dev/s/v2/flag-orpheus-left.png",
+ "icon-progress-marker.svg": "https://cdn.example.dev/s/v2/icon-progress-marker.svg"
}
```
@@ -220,7 +235,7 @@ Content-Type: application/json
-**Endpoint:** `POST https://e2.deployor.hackclub.app/api/v1/new`
+**Endpoint:** `POST https://e2.example.hackclub.app/api/v1/new`
**Headers:**
```
@@ -240,9 +255,9 @@ Content-Type: application/json
**Response:**
```json
[
- "https://cdn.deployor.dev/s/v1/0_flag-standalone.svg",
- "https://cdn.deployor.dev/s/v1/1_flag-orpheus-left.png",
- "https://cdn.deployor.dev/s/v1/2_icon-progress-marker.svg"
+ "https://cdn.example.dev/s/v1/0_flag-standalone.svg",
+ "https://cdn.example.dev/s/v1/1_flag-orpheus-left.png",
+ "https://cdn.example.dev/s/v1/2_icon-progress-marker.svg"
]
```
@@ -264,7 +279,7 @@ Content-Type: application/json
- **Storage Structure:** `/s/v3/{HASH}_{filename}`
- **File Naming:** `/s/{slackUserId}/{unix}_{sanitizedFilename}`
-- **Cost Efficiency:** Uses B2 storage for significant cost savings
+- **Cost Efficiency:** Uses object storage for significant cost savings
- **Security:** Token-based authentication for API access
## 💻 Slack Bot Behavior
@@ -279,8 +294,7 @@ Content-Type: application/json
## 💰 Cost Optimization
-- Uses Cloudflare CDN with Backblaze B2 storage
-- Free egress thanks to Cloudflare-Backblaze Alliance
+- Uses Object storage
- 87-98% cost reduction compared to Vercel CDN
-**Endpoint:** `POST https://e2.example.hackclub.app/api/v3/new`
+**Endpoint:** `POST https://cdn.hackclub.com/api/v3/new`
**Headers:**
```
@@ -155,7 +136,7 @@ Content-Type: application/json
**Request Example:**
```bash
-curl --location 'https://e2.example.hackclub.app/api/v3/new' \
+curl --location 'https://cdn.hackclub.com/api/v3/new' \
--header 'Authorization: Bearer beans' \
--header 'Content-Type: application/json' \
--data '[
@@ -203,7 +184,7 @@ curl --location 'https://e2.example.hackclub.app/api/v3/new' \
-**Endpoint:** `POST https://e2.example.hackclub.app/api/v2/new`
+**Endpoint:** `POST https://cdn.hackclub.com/api/v2/new`
**Headers:**
```
@@ -235,7 +216,7 @@ Content-Type: application/json
-**Endpoint:** `POST https://e2.example.hackclub.app/api/v1/new`
+**Endpoint:** `POST https://cdn.hackclub.com/api/v1/new`
**Headers:**
```