chore: sync port references and docs to 4000s infra
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
# Telegram Bot Uploader Backend Implementation Plan
|
||||
|
||||
> ⚠️ **LEGACY** — Dokumen historis (2026-05-17). Port & infrastruktur sudah berubah:
|
||||
> produksi kini berjalan di port `4000` (Nix + systemd + Caddy, domain `upload.asepharyana.my.id`)
|
||||
> dan database via PgBouncer pool `100.121.180.82:6432` (bukan port 5432, bukan localhost).
|
||||
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Production-ready backend for Telegram file uploader with dual upload methods (bot + HTTP API), PostgreSQL storage, and redirect-based downloads.
|
||||
@@ -75,9 +80,9 @@ schema.sql
|
||||
```bash
|
||||
BOT_TOKEN=isi_token_bot_telegram
|
||||
STORAGE_CHANNEL_ID=-1001234567890
|
||||
BASE_URL=https://tele.asepharyana.my.id
|
||||
DATABASE_URL=postgresql://user:password@localhost:5432/telegram_uploader
|
||||
PORT=3000
|
||||
BASE_URL=https://upload.asepharyana.my.id
|
||||
DATABASE_URL=postgresql://asephs:***@100.121.180.82:6432/uploader
|
||||
PORT=4000
|
||||
NODE_ENV=production
|
||||
LOG_LEVEL=info
|
||||
RATE_LIMIT_WINDOW_MS=60000
|
||||
@@ -148,7 +153,7 @@ bun run start # Production mode
|
||||
## FAQ
|
||||
|
||||
**URL permanen maksudnya apa?**
|
||||
URL backend tetap permanen: `https://tele.asepharyana.my.id/f/{public_id}`
|
||||
URL backend tetap permanen: `https://upload.asepharyana.my.id/f/{public_id}`
|
||||
Ini berarti URL service Anda fix, bukan jaminan file Telegram abadi.
|
||||
|
||||
## Testing
|
||||
@@ -250,7 +255,7 @@ export const config = {
|
||||
storageChatId: parseInt(process.env.STORAGE_CHANNEL_ID, 10),
|
||||
baseUrl: process.env.BASE_URL,
|
||||
databaseUrl: process.env.DATABASE_URL,
|
||||
port: parseInt(process.env.PORT, 10) || 3000,
|
||||
port: parseInt(process.env.PORT, 10) || 4000,
|
||||
nodeEnv: process.env.NODE_ENV || 'development',
|
||||
logLevel: process.env.LOG_LEVEL || 'info',
|
||||
rateLimitWindowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS, 10) || 60000,
|
||||
@@ -1009,14 +1014,14 @@ bun run dev
|
||||
- [ ] **Step 2: Test health endpoint**
|
||||
|
||||
```bash
|
||||
curl http://localhost:3000/health
|
||||
curl http://localhost:4000/health
|
||||
# Expected: {"status":"ok"}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Upload test file via HTTP API (multipart)**
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/upload \
|
||||
curl -X POST http://localhost:4000/api/upload \
|
||||
-F "file=@/path/to/testfile.txt" \
|
||||
-F "fileName=test.txt"
|
||||
```
|
||||
@@ -1024,14 +1029,14 @@ curl -X POST http://localhost:3000/api/upload \
|
||||
- [ ] **Step 4: Check file info endpoint**
|
||||
|
||||
```bash
|
||||
curl http://localhost:3000/file/{public_id}/info
|
||||
curl http://localhost:4000/file/{public_id}/info
|
||||
# Expected: JSON with file metadata
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Download redirect**
|
||||
|
||||
```bash
|
||||
curl -I http://localhost:3000/f/{public_id}
|
||||
curl -I http://localhost:4000/f/{public_id}
|
||||
# Expected: HTTP 302 with Location header to Telegram CDN
|
||||
```
|
||||
|
||||
@@ -1047,7 +1052,7 @@ curl -I http://localhost:3000/f/{public_id}
|
||||
- [ ] **Step 7: Test error handling (file too large)**
|
||||
|
||||
```bash
|
||||
curl -X POST http://localhost:3000/api/upload \
|
||||
curl -X POST http://localhost:4000/api/upload \
|
||||
-F "file=@/dev/null" \
|
||||
-H "Content-Length: 10000000000"
|
||||
# Expected: HTTP 400 with error message
|
||||
@@ -1057,7 +1062,7 @@ curl -X POST http://localhost:3000/api/upload \
|
||||
|
||||
```bash
|
||||
# Send 31 requests within 1 minute
|
||||
for i in {1..31}; do curl http://localhost:3000/f/{public_id} & done
|
||||
for i in {1..31}; do curl http://localhost:4000/f/{public_id} & done
|
||||
wait
|
||||
# Expected: First 30 succeed, last one returns 429
|
||||
```
|
||||
@@ -1066,7 +1071,7 @@ wait
|
||||
|
||||
```bash
|
||||
# In terminal 1: bun run dev
|
||||
# In terminal 2: curl http://localhost:3000/health && sleep 0.1 && curl http://localhost:3000/health
|
||||
# In terminal 2: curl http://localhost:4000/health && sleep 0.1 && curl http://localhost:4000/health
|
||||
# Send SIGINT to server (Ctrl+C in terminal 1)
|
||||
# Check if server stops cleanly, logs show shutdown sequence
|
||||
```
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Docker, Traefik, and GitHub Actions Deployment Plan
|
||||
|
||||
> ⚠️ **LEGACY** — Dokumen historis (2026-05-18). Arsitektur Docker + Traefik +
|
||||
> GitHub Actions sudah digantikan (2026-08-02) oleh Nix + systemd + Caddy di
|
||||
> orangevps: port `4000`, domain `upload.asepharyana.my.id`, database via
|
||||
> PgBouncer pool `100.121.180.82:6432` (bukan 5432/localhost).
|
||||
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Containerize TeleUploader using Bun, configure Traefik labels for routing `upload.asepharyana.my.id`, and set up full GitHub Actions CI/CD to VPS `45.127.35.244`.
|
||||
@@ -57,7 +63,7 @@ WORKDIR /usr/src/app
|
||||
|
||||
# Set production environment variables
|
||||
ENV NODE_ENV=production
|
||||
ENV PORT=3000
|
||||
ENV PORT=4000
|
||||
|
||||
# Copy necessary files from builder and repo
|
||||
COPY --from=builder /usr/src/app/dist/index.js ./dist/index.js
|
||||
@@ -65,7 +71,7 @@ COPY --from=builder /usr/src/app/package.json ./package.json
|
||||
COPY schema.sql ./schema.sql
|
||||
|
||||
# Expose server port
|
||||
EXPOSE 3000
|
||||
EXPOSE 4000
|
||||
|
||||
# Start server
|
||||
CMD ["bun", "dist/index.js"]
|
||||
@@ -104,7 +110,7 @@ services:
|
||||
- STORAGE_CHANNEL_ID=${STORAGE_CHANNEL_ID}
|
||||
- BASE_URL=${BASE_URL}
|
||||
- DATABASE_URL=${DATABASE_URL}
|
||||
- PORT=3000
|
||||
- PORT=4000
|
||||
- NODE_ENV=production
|
||||
- LOG_LEVEL=info
|
||||
networks:
|
||||
@@ -115,7 +121,7 @@ services:
|
||||
- "traefik.http.routers.teleuploader.entrypoints=websecure"
|
||||
- "traefik.http.routers.teleuploader.tls=true"
|
||||
- "traefik.http.routers.teleuploader.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.teleuploader.loadbalancer.server.port=3000"
|
||||
- "traefik.http.services.teleuploader.loadbalancer.server.port=4000"
|
||||
|
||||
networks:
|
||||
app-shared-net:
|
||||
@@ -166,9 +172,9 @@ jobs:
|
||||
env:
|
||||
BOT_TOKEN: "mock_token"
|
||||
STORAGE_CHANNEL_ID: "123456"
|
||||
BASE_URL: "http://localhost:3000"
|
||||
DATABASE_URL: "postgresql://postgres:postgres@localhost:5432/postgres"
|
||||
PORT: "3000"
|
||||
BASE_URL: "http://localhost:4000"
|
||||
DATABASE_URL: "postgresql://asephs:***@100.121.180.82:6432/postgres"
|
||||
PORT: "4000"
|
||||
run: bun run test
|
||||
|
||||
build-and-push:
|
||||
@@ -223,7 +229,7 @@ jobs:
|
||||
- STORAGE_CHANNEL_ID=${STORAGE_CHANNEL_ID}
|
||||
- BASE_URL=${BASE_URL}
|
||||
- DATABASE_URL=${DATABASE_URL}
|
||||
- PORT=3000
|
||||
- PORT=4000
|
||||
- NODE_ENV=production
|
||||
- LOG_LEVEL=info
|
||||
networks:
|
||||
@@ -234,7 +240,7 @@ jobs:
|
||||
- "traefik.http.routers.teleuploader.entrypoints=websecure"
|
||||
- "traefik.http.routers.teleuploader.tls=true"
|
||||
- "traefik.http.routers.teleuploader.tls.certresolver=letsencrypt"
|
||||
- "traefik.http.services.teleuploader.loadbalancer.server.port=3000"
|
||||
- "traefik.http.services.teleuploader.loadbalancer.server.port=4000"
|
||||
|
||||
networks:
|
||||
app-shared-net:
|
||||
@@ -248,7 +254,7 @@ jobs:
|
||||
STORAGE_CHANNEL_ID=${{ secrets.STORAGE_CHANNEL_ID }}
|
||||
BASE_URL=${{ secrets.BASE_URL }}
|
||||
DATABASE_URL=${{ secrets.DATABASE_URL }}
|
||||
PORT=3000
|
||||
PORT=4000
|
||||
EOF
|
||||
|
||||
# Pull latest docker image
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# S3-Compatible TeleUploader Implementation Plan
|
||||
|
||||
> ⚠️ **LEGACY** — Dokumen historis (2026-07-06). Port & infrastruktur sudah berubah:
|
||||
> produksi kini berjalan di port `4000` (Nix + systemd + Caddy, domain
|
||||
> `upload.asepharyana.my.id`), database via PgBouncer pool `100.121.180.82:6432`
|
||||
> (bukan 5432/localhost). Contoh kode di bawah memakai `localhost:4000` untuk dev.
|
||||
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Transform TeleUploader into an S3-compatible storage server (Telegram-backed) with a web file manager UI.
|
||||
@@ -2675,7 +2681,7 @@ describe('S3 Bucket Operations', () => {
|
||||
process.env.S3_DEFAULT_REGION = 'us-east-1';
|
||||
process.env.BOT_TOKEN = '123456:ABC-DEF';
|
||||
process.env.STORAGE_CHANNEL_ID = '-1001234567890';
|
||||
process.env.BASE_URL = 'http://localhost:3000';
|
||||
process.env.BASE_URL = 'http://localhost:4000';
|
||||
process.env.DATABASE_URL = 'postgresql://localhost/test';
|
||||
});
|
||||
|
||||
@@ -2684,7 +2690,7 @@ describe('S3 Bucket Operations', () => {
|
||||
});
|
||||
|
||||
it('should return 403 for unauthorized requests', async () => {
|
||||
const req = new Request('http://localhost:3000/', {
|
||||
const req = new Request('http://localhost:4000/', {
|
||||
method: 'GET',
|
||||
headers: { authorization: 'Invalid' },
|
||||
});
|
||||
@@ -2849,7 +2855,7 @@ describe('Web API v1', () => {
|
||||
mockDbExecute.mockClear();
|
||||
process.env.BOT_TOKEN = '123456:ABC-DEF';
|
||||
process.env.STORAGE_CHANNEL_ID = '-1001234567890';
|
||||
process.env.BASE_URL = 'http://localhost:3000';
|
||||
process.env.BASE_URL = 'http://localhost:4000';
|
||||
process.env.DATABASE_URL = 'postgresql://localhost/test';
|
||||
});
|
||||
|
||||
@@ -2858,7 +2864,7 @@ describe('Web API v1', () => {
|
||||
});
|
||||
|
||||
it('should list buckets via GET /api/v1/buckets', async () => {
|
||||
const req = new Request('http://localhost:3000/api/v1/buckets');
|
||||
const req = new Request('http://localhost:4000/api/v1/buckets');
|
||||
const res = await handleWebApiV1(req);
|
||||
expect(res.status).toBe(200);
|
||||
const data = await res.json();
|
||||
@@ -2867,7 +2873,7 @@ describe('Web API v1', () => {
|
||||
});
|
||||
|
||||
it('should return 404 for unknown API path', async () => {
|
||||
const req = new Request('http://localhost:3000/api/v1/unknown');
|
||||
const req = new Request('http://localhost:4000/api/v1/unknown');
|
||||
const res = await handleWebApiV1(req);
|
||||
expect(res.status).toBe(404);
|
||||
const data = await res.json();
|
||||
@@ -2875,7 +2881,7 @@ describe('Web API v1', () => {
|
||||
});
|
||||
|
||||
it('should return bucket object listing', async () => {
|
||||
const req = new Request('http://localhost:3000/api/v1/buckets/test-bucket/objects?prefix=');
|
||||
const req = new Request('http://localhost:4000/api/v1/buckets/test-bucket/objects?prefix=');
|
||||
const res = await handleWebApiV1(req);
|
||||
// Should return 200 even with empty results
|
||||
expect(res.status).toBe(200);
|
||||
@@ -2885,7 +2891,7 @@ describe('Web API v1', () => {
|
||||
});
|
||||
|
||||
it('should reject invalid bucket name on create', async () => {
|
||||
const req = new Request('http://localhost:3000/api/v1/buckets', {
|
||||
const req = new Request('http://localhost:4000/api/v1/buckets', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ name: 'INVALID_NAME!' }),
|
||||
|
||||
Reference in New Issue
Block a user