refactor: break monorepo into 3 standalone services (gateway, backend, frontend)
Build & Deploy / build-and-push (backend) (push) Failing after 35s
Build & Deploy / build-and-push (discord-gateway) (push) Failing after 25s
Build & Deploy / build-and-push (proxy) (push) Failing after 25s
Build & Deploy / build-and-push (backend) (push) Failing after 35s
Build & Deploy / build-and-push (discord-gateway) (push) Failing after 25s
Build & Deploy / build-and-push (proxy) (push) Failing after 25s
- Remove pnpm workspace, moon repo, and all monorepo tooling - Delete packages/shared/, embed shared code directly into each service - Copy packages/shared/src/* -> services/backend/src/shared/ and services/discord-gateway/src/shared/ - Replace all @bete/shared imports with @/shared/ path alias - Remove @bete/shared workspace dependency from both services - Update root package.json scripts from --filter to --prefix - Rewrite Dockerfiles to build each service standalone - Clean up biome.json, .gitignore, remove root drizzle.config.ts
This commit is contained in:
1 parent
8eb7fa49e4
commit
dcd13482c2
167 files changed
+1886
-533
No files matched your search
@@ -8,9 +8,6 @@ public/app/
|
|||||||
.env.test
|
.env.test
|
||||||
logs/
|
logs/
|
||||||
.codegraph/
|
.codegraph/
|
||||||
# moon
|
|
||||||
.moon/cache
|
|
||||||
.moon/docker
|
|
||||||
worktrees/
|
worktrees/
|
||||||
.worktrees/
|
.worktrees/
|
||||||
services/frontend/frontend/dist/
|
services/frontend/frontend/dist/
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
$schema: 'https://moonrepo.dev/schema/config/v2.json'
|
|
||||||
|
|
||||||
# Global task templates — each project must explicitly inherit via workspace.inheritedTasks
|
|
||||||
# In moon v2, these define reusable task templates
|
|
||||||
tasks:
|
|
||||||
lint:
|
|
||||||
command: biome
|
|
||||||
args:
|
|
||||||
- check
|
|
||||||
- '--diagnostic-level=error'
|
|
||||||
- .
|
|
||||||
options:
|
|
||||||
runFromWorkspaceRoot: false
|
|
||||||
inputs:
|
|
||||||
- 'src/**/*'
|
|
||||||
- '*.js'
|
|
||||||
- '*.mjs'
|
|
||||||
- '*.ts'
|
|
||||||
- '*.mts'
|
|
||||||
|
|
||||||
format:
|
|
||||||
command: biome
|
|
||||||
args:
|
|
||||||
- format
|
|
||||||
- '--write'
|
|
||||||
- .
|
|
||||||
options:
|
|
||||||
runFromWorkspaceRoot: false
|
|
||||||
inputs:
|
|
||||||
- 'src/**/*'
|
|
||||||
- '*.js'
|
|
||||||
- '*.mjs'
|
|
||||||
- '*.ts'
|
|
||||||
- '*.mts'
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
$schema: 'https://moonrepo.dev/schema/config/v2.json'
|
|
||||||
|
|
||||||
# Moon Workspace Configuration
|
|
||||||
projects:
|
|
||||||
- 'services/*'
|
|
||||||
- 'packages/*'
|
|
||||||
|
|
||||||
vcs:
|
|
||||||
defaultBranch: main
|
|
||||||
provider: github
|
|
||||||
@@ -11,13 +11,11 @@
|
|||||||
"tests/**/*.ts",
|
"tests/**/*.ts",
|
||||||
"services/**/*.ts",
|
"services/**/*.ts",
|
||||||
"services/**/*.tsx",
|
"services/**/*.tsx",
|
||||||
"packages/**/*.ts",
|
|
||||||
"*.json",
|
"*.json",
|
||||||
"*.ts",
|
"*.ts",
|
||||||
"!vendor",
|
"!vendor",
|
||||||
"!.claude",
|
"!.claude",
|
||||||
"!services/**/dist",
|
"!services/**/dist",
|
||||||
"!packages/**/dist",
|
|
||||||
"!services/**/.next",
|
"!services/**/.next",
|
||||||
"!services/**/out"
|
"!services/**/out"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import { defineConfig } from "drizzle-kit";
|
|
||||||
|
|
||||||
const databaseUrl = process.env.DATABASE_URL;
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
schema: "./src/database/schema.ts",
|
|
||||||
out: "./drizzle/migrations",
|
|
||||||
dialect: "postgresql",
|
|
||||||
dbCredentials: databaseUrl
|
|
||||||
? { url: databaseUrl }
|
|
||||||
: {
|
|
||||||
host: process.env.POSTGRES_HOST || "localhost",
|
|
||||||
port: parseInt(process.env.POSTGRES_PORT || "5432", 10),
|
|
||||||
user: process.env.POSTGRES_USER || "postgres",
|
|
||||||
password: process.env.POSTGRES_PASSWORD || "",
|
|
||||||
database: process.env.POSTGRES_DB || "moderation_bot",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
@@ -2,12 +2,6 @@ FROM node:22-slim
|
|||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Build args for frontend API URLs
|
|
||||||
ARG VITE_BE_API_URL
|
|
||||||
ARG VITE_BE_WS_URL
|
|
||||||
ENV VITE_BE_API_URL=${VITE_BE_API_URL}
|
|
||||||
ENV VITE_BE_WS_URL=${VITE_BE_WS_URL}
|
|
||||||
|
|
||||||
# Install pnpm
|
# Install pnpm
|
||||||
RUN npm install -g pnpm
|
RUN npm install -g pnpm
|
||||||
|
|
||||||
@@ -18,30 +12,21 @@ RUN apt-get update -qq && apt-get install -y -qq --no-install-recommends python3
|
|||||||
RUN groupadd --system app && useradd --system -g app app
|
RUN groupadd --system app && useradd --system -g app app
|
||||||
|
|
||||||
# Copy dependency definition files first for better caching
|
# Copy dependency definition files first for better caching
|
||||||
COPY pnpm-workspace.yaml .
|
COPY services/backend/package.json services/backend/pnpm-lock.yaml* ./services/backend/
|
||||||
COPY pnpm-lock.yaml .
|
|
||||||
COPY package.json .
|
|
||||||
|
|
||||||
# Copy patches (pnpm patchedDependencies)
|
# Copy patches (pnpm patchedDependencies)
|
||||||
COPY patches ./patches
|
COPY patches ./patches
|
||||||
|
|
||||||
# Copy packages (workspace dependencies)
|
# Copy service source (shared code is embedded in src/shared/)
|
||||||
COPY packages/shared ./packages/shared
|
COPY services/backend/ ./services/backend/
|
||||||
|
|
||||||
# Copy service
|
# Install dependencies
|
||||||
COPY services/backend ./services/backend
|
|
||||||
|
|
||||||
# Install dependencies with build cache
|
|
||||||
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
|
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
|
||||||
pnpm install --frozen-lockfile
|
cd services/backend && pnpm install --frozen-lockfile
|
||||||
|
|
||||||
# Build shared workspace dependency first
|
|
||||||
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
|
|
||||||
pnpm --filter './packages/shared' run build
|
|
||||||
|
|
||||||
# Build backend
|
# Build backend
|
||||||
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
|
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
|
||||||
pnpm --filter './services/backend' run build
|
cd services/backend && pnpm run build
|
||||||
|
|
||||||
# Own dist + node_modules by non-root user
|
# Own dist + node_modules by non-root user
|
||||||
RUN chown -R app:app /app
|
RUN chown -R app:app /app
|
||||||
|
|||||||
@@ -13,40 +13,23 @@ RUN apt-get update -qq && apt-get install -y -qq --no-install-recommends \
|
|||||||
ENV PATH="/root/.cargo/bin:${PATH}"
|
ENV PATH="/root/.cargo/bin:${PATH}"
|
||||||
|
|
||||||
# Dependency manifests
|
# Dependency manifests
|
||||||
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
|
COPY services/discord-gateway/package.json services/discord-gateway/pnpm-lock.yaml* ./services/discord-gateway/
|
||||||
COPY patches ./patches
|
COPY patches ./patches
|
||||||
COPY packages/shared/package.json ./packages/shared/package.json
|
|
||||||
COPY services/discord-gateway/package.json ./services/discord-gateway/package.json
|
|
||||||
|
|
||||||
# Install ALL deps (including devDeps needed for build)
|
# Install ALL deps (including devDeps needed for build)
|
||||||
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
|
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
|
||||||
pnpm install --frozen-lockfile
|
cd services/discord-gateway && pnpm install --frozen-lockfile
|
||||||
|
|
||||||
# Source code
|
# Source code (shared code is embedded in src/shared/)
|
||||||
COPY packages/shared ./packages/shared
|
|
||||||
COPY services/discord-gateway ./services/discord-gateway
|
COPY services/discord-gateway ./services/discord-gateway
|
||||||
COPY services/discord-gateway/drizzle ./drizzle
|
COPY services/discord-gateway/drizzle ./drizzle
|
||||||
|
|
||||||
# Build (combined to reduce layers)
|
# Build
|
||||||
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
|
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
|
||||||
pnpm --filter './packages/shared' run build \
|
cd services/discord-gateway && pnpm run build \
|
||||||
&& pnpm --filter './services/discord-gateway' run build \
|
|
||||||
&& apt-get clean \
|
&& apt-get clean \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Create production-only deployment (no devDeps)
|
|
||||||
RUN pnpm deploy --legacy --filter '@bete/discord-gateway' /tmp/deploy \
|
|
||||||
&& rm -rf /tmp/deploy/node_modules/.pnpm/@biomejs+biome@* \
|
|
||||||
/tmp/deploy/node_modules/.pnpm/@biomejs+cli-linux-x64@* \
|
|
||||||
/tmp/deploy/node_modules/.pnpm/typescript@* \
|
|
||||||
/tmp/deploy/node_modules/.pnpm/drizzle-kit@* \
|
|
||||||
/tmp/deploy/node_modules/.pnpm/vitest@* \
|
|
||||||
/tmp/deploy/node_modules/.pnpm/esbuild@* \
|
|
||||||
/tmp/deploy/node_modules/.pnpm/tsx@* \
|
|
||||||
/tmp/deploy/node_modules/.pnpm/@types* \
|
|
||||||
/tmp/deploy/node_modules/.pnpm/@rolldown* \
|
|
||||||
/tmp/deploy/node_modules/.pnpm/@vitest*
|
|
||||||
|
|
||||||
# ---- Stage 2: Runtime (minimal) ----
|
# ---- Stage 2: Runtime (minimal) ----
|
||||||
FROM node:22-slim
|
FROM node:22-slim
|
||||||
|
|
||||||
@@ -57,11 +40,10 @@ RUN apt-get update -qq && apt-get install -y -qq --no-install-recommends \
|
|||||||
ffmpeg ca-certificates \
|
ffmpeg ca-certificates \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Copy production-only artifacts from deploy
|
# Copy built artifacts
|
||||||
COPY --from=builder /tmp/deploy/node_modules ./node_modules
|
|
||||||
COPY --from=builder /tmp/deploy/package.json ./package.json
|
|
||||||
COPY --from=builder /app/packages/shared/dist ./packages/shared/dist
|
|
||||||
COPY --from=builder /app/services/discord-gateway/dist ./services/discord-gateway/dist
|
COPY --from=builder /app/services/discord-gateway/dist ./services/discord-gateway/dist
|
||||||
|
COPY --from=builder /app/services/discord-gateway/node_modules ./services/discord-gateway/node_modules
|
||||||
|
COPY --from=builder /app/services/discord-gateway/package.json ./services/discord-gateway/package.json
|
||||||
COPY --from=builder /app/drizzle ./drizzle
|
COPY --from=builder /app/drizzle ./drizzle
|
||||||
|
|
||||||
# Create recordings dir and set ownership
|
# Create recordings dir and set ownership
|
||||||
|
|||||||
@@ -11,20 +11,14 @@ RUN apt-get update -qq && apt-get install -y -qq --no-install-recommends \
|
|||||||
python3 make g++ && rm -rf /var/lib/apt/lists/*
|
python3 make g++ && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Copy dependency manifests first for layer caching
|
# Copy dependency manifests first for layer caching
|
||||||
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
|
COPY services/frontend/package.json services/frontend/pnpm-lock.yaml* ./services/frontend/
|
||||||
COPY packages/shared/package.json ./packages/shared/package.json
|
|
||||||
COPY services/frontend/package.json ./services/frontend/package.json
|
|
||||||
COPY services/frontend/tsconfig.json ./services/frontend/tsconfig.json
|
COPY services/frontend/tsconfig.json ./services/frontend/tsconfig.json
|
||||||
|
|
||||||
# Copy patches (pnpm patchedDependencies)
|
# Install frontend dependencies (standalone, no shared workspace dep)
|
||||||
COPY patches/ ./patches/
|
|
||||||
|
|
||||||
# Install dependencies (frontend + shared)
|
|
||||||
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
|
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
|
||||||
pnpm install --no-frozen-lockfile --filter './packages/shared' --filter './services/frontend'
|
cd services/frontend && pnpm install --no-frozen-lockfile
|
||||||
|
|
||||||
# Copy source code
|
# Copy source code
|
||||||
COPY packages/shared/ ./packages/shared/
|
|
||||||
COPY services/frontend/ ./services/frontend/
|
COPY services/frontend/ ./services/frontend/
|
||||||
|
|
||||||
# Pass API/WS URLs as build args
|
# Pass API/WS URLs as build args
|
||||||
@@ -33,11 +27,9 @@ ARG VITE_BE_WS_URL
|
|||||||
ENV VITE_BE_API_URL=${VITE_BE_API_URL}
|
ENV VITE_BE_API_URL=${VITE_BE_API_URL}
|
||||||
ENV VITE_BE_WS_URL=${VITE_BE_WS_URL}
|
ENV VITE_BE_WS_URL=${VITE_BE_WS_URL}
|
||||||
|
|
||||||
# Build shared lib first, then frontend static export
|
# Build frontend static export
|
||||||
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
|
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
|
||||||
pnpm --filter './packages/shared' run build
|
cd services/frontend && pnpm run build
|
||||||
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
|
|
||||||
pnpm --filter frontend run build
|
|
||||||
|
|
||||||
# ---- Stage 2: Nginx ----
|
# ---- Stage 2: Nginx ----
|
||||||
FROM nginx:alpine
|
FROM nginx:alpine
|
||||||
|
|||||||
+11
-18
@@ -1,31 +1,24 @@
|
|||||||
{
|
{
|
||||||
"name": "discord-voice-recorder",
|
"name": "bete-monorepo",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Discord bot that joins a voice channel and records audio",
|
"private": true,
|
||||||
"type": "module",
|
"description": "Bete Discord Moderation Services — standalone services in one repo",
|
||||||
"packageManager": "pnpm@11.1.3",
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev:backend": "pnpm --filter './services/backend' run dev",
|
"dev:backend": "pnpm --prefix services/backend dev",
|
||||||
"dev:discord-gateway": "pnpm --filter './services/discord-gateway' run dev",
|
"dev:discord-gateway": "pnpm --prefix services/discord-gateway dev",
|
||||||
"dev:web": "pnpm --filter './services/frontend' run dev",
|
"dev:web": "pnpm --prefix services/frontend dev",
|
||||||
"build:web": "pnpm --filter './services/frontend' run build",
|
"build:web": "pnpm --prefix services/frontend build",
|
||||||
"typecheck:web": "pnpm --filter './services/frontend' run build",
|
"build:backend": "pnpm --prefix services/backend build",
|
||||||
"build:backend": "pnpm --filter './services/backend' run build",
|
"build:discord-gateway": "pnpm --prefix services/discord-gateway build",
|
||||||
"build:discord-gateway": "pnpm --filter './services/discord-gateway' run build",
|
"typecheck": "pnpm --prefix services/backend typecheck && pnpm --prefix services/discord-gateway typecheck",
|
||||||
"typecheck": "pnpm -r run typecheck",
|
|
||||||
"lint": "biome check --diagnostic-level=error .",
|
"lint": "biome check --diagnostic-level=error .",
|
||||||
"format": "biome format --write .",
|
"format": "biome format --write .",
|
||||||
"test": "pnpm -r run test",
|
"test": "pnpm --prefix services/backend test && pnpm --prefix services/discord-gateway test",
|
||||||
"db:generate": "drizzle-kit generate",
|
|
||||||
"db:migrate": "drizzle-kit migrate",
|
|
||||||
"db:studio": "drizzle-kit studio",
|
|
||||||
"db:truncate": "psql \"$DATABASE_URL\" -f scripts/truncate-all.sql",
|
"db:truncate": "psql \"$DATABASE_URL\" -f scripts/truncate-all.sql",
|
||||||
"install:yt-dlp": "sh scripts/install-yt-dlp.sh"
|
"install:yt-dlp": "sh scripts/install-yt-dlp.sh"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^2.5.5",
|
"@biomejs/biome": "^2.5.5",
|
||||||
"drizzle-kit": "^0.31.10",
|
|
||||||
"tsx": "^4.22.2",
|
|
||||||
"typescript": "^5.9.3"
|
"typescript": "^5.9.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
$schema: 'https://moonrepo.dev/schema/config/v2.json'
|
|
||||||
|
|
||||||
language: typescript
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
typecheck:
|
|
||||||
command: tsc --noEmit
|
|
||||||
options:
|
|
||||||
runFromWorkspaceRoot: false
|
|
||||||
inputs:
|
|
||||||
- src
|
|
||||||
- tsconfig.json
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@bete/shared",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "Shared utilities, types, and errors for Bete microservices",
|
|
||||||
"type": "module",
|
|
||||||
"main": "dist/index.js",
|
|
||||||
"types": "dist/index.d.ts",
|
|
||||||
"exports": {
|
|
||||||
".": "./dist/index.js",
|
|
||||||
"./config": "./dist/config/index.js",
|
|
||||||
"./database/pool": "./dist/database/pool.js",
|
|
||||||
"./database/schema": "./dist/database/schema.js",
|
|
||||||
"./database/init": "./dist/database/init.js",
|
|
||||||
"./errors": "./dist/errors/index.js",
|
|
||||||
"./logger": "./dist/logger/index.js",
|
|
||||||
"./moderation-types": "./dist/moderation-types.js",
|
|
||||||
"./redis-channels": "./dist/redis-channels.js",
|
|
||||||
"./utils": "./dist/utils/index.js"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build": "tsc",
|
|
||||||
"typecheck": "tsc --noEmit"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"drizzle-orm": "^0.45.2",
|
|
||||||
"pg": "^8.13.0",
|
|
||||||
"pino": "^9.0.0",
|
|
||||||
"zod": "^4.4.3"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/node": "^25.9.0",
|
|
||||||
"@types/pg": "^8.11.0",
|
|
||||||
"pino-pretty": "^13.1.3",
|
|
||||||
"typescript": "^5.9.3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ES2020",
|
|
||||||
"module": "ESNext",
|
|
||||||
"lib": ["ES2020"],
|
|
||||||
"declaration": true,
|
|
||||||
"declarationMap": true,
|
|
||||||
"outDir": "./dist",
|
|
||||||
"rootDir": "./src",
|
|
||||||
"strict": true,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"moduleResolution": "bundler"
|
|
||||||
},
|
|
||||||
"include": ["src"],
|
|
||||||
"exclude": ["node_modules", "dist"]
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
packages:
|
|
||||||
- services/*
|
|
||||||
- packages/*
|
|
||||||
|
|
||||||
allowBuilds:
|
|
||||||
'@discordjs/opus': true
|
|
||||||
'@lng2004/node-datachannel': true
|
|
||||||
esbuild: true
|
|
||||||
node-av: true
|
|
||||||
node-crc: true
|
|
||||||
sharp: true
|
|
||||||
zeromq: true
|
|
||||||
|
|
||||||
minimumReleaseAgeExclude:
|
|
||||||
- '@types/node@25.9.0'
|
|
||||||
- pg-cloudflare@1.4.0
|
|
||||||
- pg-connection-string@2.13.0
|
|
||||||
- pg-pool@3.14.0
|
|
||||||
- pg-protocol@1.14.0
|
|
||||||
- pg@8.21.0
|
|
||||||
- tsx@4.22.2
|
|
||||||
- '@next/env@16.2.12'
|
|
||||||
- '@next/swc-darwin-arm64@16.2.12'
|
|
||||||
- '@next/swc-darwin-x64@16.2.12'
|
|
||||||
- '@next/swc-linux-arm64-gnu@16.2.12'
|
|
||||||
- '@next/swc-linux-arm64-musl@16.2.12'
|
|
||||||
- '@next/swc-linux-x64-gnu@16.2.12'
|
|
||||||
- '@next/swc-linux-x64-musl@16.2.12'
|
|
||||||
- '@next/swc-win32-arm64-msvc@16.2.12'
|
|
||||||
- '@next/swc-win32-x64-msvc@16.2.12'
|
|
||||||
- lucide-react@1.27.0
|
|
||||||
- next@16.2.12
|
|
||||||
- shadcn@4.15.0
|
|
||||||
|
|
||||||
onlyBuiltDependencies:
|
|
||||||
- '@discordjs/opus'
|
|
||||||
- '@lng2004/node-datachannel'
|
|
||||||
- esbuild
|
|
||||||
- node-av
|
|
||||||
- sharp
|
|
||||||
- zeromq
|
|
||||||
|
|
||||||
patchedDependencies:
|
|
||||||
node-crc@4.0.0: patches/node-crc@4.0.0.patch
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
# Backend service - Discord moderation monitoring
|
|
||||||
language: typescript
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
dev:
|
|
||||||
command: tsx watch src/index.ts
|
|
||||||
preset: server
|
|
||||||
|
|
||||||
build:
|
|
||||||
command: tsc
|
|
||||||
options:
|
|
||||||
runFromWorkspaceRoot: false
|
|
||||||
inputs:
|
|
||||||
- src
|
|
||||||
- tsconfig.json
|
|
||||||
outputs:
|
|
||||||
- dist
|
|
||||||
|
|
||||||
typecheck:
|
|
||||||
command: tsc --noEmit
|
|
||||||
options:
|
|
||||||
runFromWorkspaceRoot: false
|
|
||||||
inputs:
|
|
||||||
- src
|
|
||||||
- tsconfig.json
|
|
||||||
|
|
||||||
lint:
|
|
||||||
command: biome check --diagnostic-level=error .
|
|
||||||
options:
|
|
||||||
runFromWorkspaceRoot: false
|
|
||||||
inputs:
|
|
||||||
- src
|
|
||||||
|
|
||||||
format:
|
|
||||||
command: biome format --write .
|
|
||||||
options:
|
|
||||||
runFromWorkspaceRoot: false
|
|
||||||
inputs:
|
|
||||||
- src
|
|
||||||
|
|
||||||
test:
|
|
||||||
command: vitest run
|
|
||||||
options:
|
|
||||||
runFromWorkspaceRoot: false
|
|
||||||
inputs:
|
|
||||||
- src
|
|
||||||
- tests
|
|
||||||
- tsconfig.json
|
|
||||||
@@ -14,7 +14,6 @@
|
|||||||
"test": "vitest run"
|
"test": "vitest run"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bete/shared": "workspace:*",
|
|
||||||
"@discordjs/voice": "^0.19.2",
|
"@discordjs/voice": "^0.19.2",
|
||||||
"axios": "^1.16.1",
|
"axios": "^1.16.1",
|
||||||
"dotenv": "^17.4.2",
|
"dotenv": "^17.4.2",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createChildLogger } from "@bete/shared/logger";
|
import { createChildLogger } from "@/shared/logger/index";
|
||||||
import express, {
|
import express, {
|
||||||
type Express,
|
type Express,
|
||||||
type NextFunction,
|
type NextFunction,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createServer, type Server } from "node:http";
|
import { createServer, type Server } from "node:http";
|
||||||
import { createChildLogger } from "@bete/shared/logger";
|
import { createChildLogger } from "@/shared/logger/index";
|
||||||
import { config } from "../shared/config/index.js";
|
import { config } from "../shared/config/index.js";
|
||||||
import { initializeDatabase } from "../shared/database/index.js";
|
import { initializeDatabase } from "../shared/database/index.js";
|
||||||
import { startRedisBridge } from "../ws/redis-bridge.js";
|
import { startRedisBridge } from "../ws/redis-bridge.js";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Server } from "node:http";
|
import type { Server } from "node:http";
|
||||||
import { createChildLogger } from "@bete/shared/logger";
|
import { createChildLogger } from "@/shared/logger/index";
|
||||||
import { startHttpServer } from "./http/server.js";
|
import { startHttpServer } from "./http/server.js";
|
||||||
import { closeDatabase } from "./shared/database/index.js";
|
import { closeDatabase } from "./shared/database/index.js";
|
||||||
import { stopCommandBridge } from "./shared/redis/index.js";
|
import { stopCommandBridge } from "./shared/redis/index.js";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { pgMessagesTable } from "@bete/shared";
|
import { pgMessagesTable } from "../../shared/index.js";
|
||||||
import { createChildLogger } from "@bete/shared/logger";
|
import { createChildLogger } from "../../shared/logger/index.js";
|
||||||
import { and, desc, eq, ilike, type SQL } from "drizzle-orm";
|
import { and, desc, eq, ilike, type SQL } from "drizzle-orm";
|
||||||
import { getDatabase } from "../../shared/database/index.js";
|
import { getDatabase } from "../../shared/database/index.js";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createChildLogger } from "@bete/shared/logger";
|
import { createChildLogger } from "@/shared/logger/index";
|
||||||
import type { Request, Response, Router } from "express";
|
import type { Request, Response, Router } from "express";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import { asyncHandler } from "../../shared/middlewares/index.js";
|
import { asyncHandler } from "../../shared/middlewares/index.js";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createChildLogger } from "@bete/shared/logger";
|
import { createChildLogger } from "@/shared/logger/index";
|
||||||
import { config } from "../../shared/config/index.js";
|
import { config } from "../../shared/config/index.js";
|
||||||
import type { AnalysisSearchQuery } from "./analysis.repository.js";
|
import type { AnalysisSearchQuery } from "./analysis.repository.js";
|
||||||
import { analysisRepository } from "./analysis.repository.js";
|
import { analysisRepository } from "./analysis.repository.js";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createChildLogger } from "@bete/shared/logger";
|
import { createChildLogger } from "@/shared/logger/index";
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from "express";
|
||||||
import { asyncHandler } from "../../shared/middlewares/index.js";
|
import { asyncHandler } from "../../shared/middlewares/index.js";
|
||||||
import { chatbotService } from "./chatbot.service.js";
|
import { chatbotService } from "./chatbot.service.js";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { pgChatbotMessagesTable, pgMessagesTable } from "@bete/shared";
|
import { pgChatbotMessagesTable, pgMessagesTable } from "../../shared/index.js";
|
||||||
import { createChildLogger } from "@bete/shared/logger";
|
import { createChildLogger } from "../../shared/logger/index.js";
|
||||||
import { and, desc, eq, type SQL, sql } from "drizzle-orm";
|
import { and, desc, eq, type SQL, sql } from "drizzle-orm";
|
||||||
import { getDatabase } from "../../shared/database/index.js";
|
import { getDatabase } from "../../shared/database/index.js";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createChildLogger } from "@bete/shared/logger";
|
import { createChildLogger } from "@/shared/logger/index";
|
||||||
import { config } from "../../shared/config/index.js";
|
import { config } from "../../shared/config/index.js";
|
||||||
import type {
|
import type {
|
||||||
ChatbotContext,
|
ChatbotContext,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
pgUserProfilesTable,
|
pgUserProfilesTable,
|
||||||
pgUserReputationsTable,
|
pgUserReputationsTable,
|
||||||
pgVoiceRecordingsTable,
|
pgVoiceRecordingsTable,
|
||||||
} from "@bete/shared";
|
} from "../../shared/index.js";
|
||||||
import type { SQL } from "drizzle-orm";
|
import type { SQL } from "drizzle-orm";
|
||||||
import { sql } from "drizzle-orm";
|
import { sql } from "drizzle-orm";
|
||||||
import { getDatabase } from "../../shared/database/index.js";
|
import { getDatabase } from "../../shared/database/index.js";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createChildLogger } from "@bete/shared/logger";
|
import { createChildLogger } from "@/shared/logger/index";
|
||||||
import type { Request, Response, Router } from "express";
|
import type { Request, Response, Router } from "express";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import { asyncHandler } from "../../shared/middlewares/index.js";
|
import { asyncHandler } from "../../shared/middlewares/index.js";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createChildLogger } from "@bete/shared/logger";
|
import { createChildLogger } from "@/shared/logger/index";
|
||||||
import { dashboardRepository } from "./dashboard.repository.js";
|
import { dashboardRepository } from "./dashboard.repository.js";
|
||||||
|
|
||||||
const logger = createChildLogger("dashboard.service");
|
const logger = createChildLogger("dashboard.service");
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createChildLogger } from "@bete/shared/logger";
|
import { createChildLogger } from "@/shared/logger/index";
|
||||||
import { sql } from "drizzle-orm";
|
import { sql } from "drizzle-orm";
|
||||||
import { getDatabase } from "../../shared/database/index.js";
|
import { getDatabase } from "../../shared/database/index.js";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createChildLogger } from "@bete/shared/logger";
|
import { createChildLogger } from "@/shared/logger/index";
|
||||||
import type { Request, Response, Router } from "express";
|
import type { Request, Response, Router } from "express";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import { asyncHandler, validateBody } from "../../shared/middlewares/index.js";
|
import { asyncHandler, validateBody } from "../../shared/middlewares/index.js";
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
COMMAND_MEDIA_STOP,
|
COMMAND_MEDIA_STOP,
|
||||||
COMMAND_MEDIA_VOLUME,
|
COMMAND_MEDIA_VOLUME,
|
||||||
MEDIA_STATUS_KEY,
|
MEDIA_STATUS_KEY,
|
||||||
} from "@bete/shared";
|
} from "../../shared/index.js";
|
||||||
import {
|
import {
|
||||||
createChildLogger,
|
createChildLogger,
|
||||||
tryCommandThenFallback,
|
tryCommandThenFallback,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createChildLogger } from "@bete/shared/logger";
|
import { createChildLogger } from "@/shared/logger/index";
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from "express";
|
||||||
import { asyncHandler } from "../../shared/middlewares/index.js";
|
import { asyncHandler } from "../../shared/middlewares/index.js";
|
||||||
import { messageQuerySchema } from "./messages.schema.js";
|
import { messageQuerySchema } from "./messages.schema.js";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { PageResult } from "@bete/shared";
|
import type { PageResult } from "../../shared/index.js";
|
||||||
import { pgAttachmentsTable, pgMessagesTable } from "@bete/shared";
|
import { pgAttachmentsTable, pgMessagesTable } from "../../shared/index.js";
|
||||||
import { createChildLogger } from "@bete/shared/logger";
|
import { createChildLogger } from "../../shared/logger/index.js";
|
||||||
import {
|
import {
|
||||||
and,
|
and,
|
||||||
desc,
|
desc,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createChildLogger } from "@bete/shared/logger";
|
import { createChildLogger } from "@/shared/logger/index";
|
||||||
import type { Request, Response, Router } from "express";
|
import type { Request, Response, Router } from "express";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import { asyncHandler, validateBody } from "../../shared/middlewares/index.js";
|
import { asyncHandler, validateBody } from "../../shared/middlewares/index.js";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { NotFoundError, ValidationError } from "@bete/shared/errors";
|
import { NotFoundError, ValidationError } from "@/shared/errors/index";
|
||||||
import { createChildLogger } from "@bete/shared/logger";
|
import { createChildLogger } from "@/shared/logger/index";
|
||||||
import { messagesRepository } from "./messages.repository.js";
|
import { messagesRepository } from "./messages.repository.js";
|
||||||
import type { MessageQuery } from "./messages.schema.js";
|
import type { MessageQuery } from "./messages.schema.js";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createChildLogger } from "@bete/shared/logger";
|
import { createChildLogger } from "@/shared/logger/index";
|
||||||
import type { Request, Response, Router } from "express";
|
import type { Request, Response, Router } from "express";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import { asyncHandler } from "../../shared/middlewares/index.js";
|
import { asyncHandler } from "../../shared/middlewares/index.js";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { pgVoiceRecordingsTable } from "@bete/shared";
|
import { pgVoiceRecordingsTable } from "../../shared/index.js";
|
||||||
import { createChildLogger } from "@bete/shared/logger";
|
import { createChildLogger } from "../../shared/logger/index.js";
|
||||||
import { and, desc, eq, lt, type SQL } from "drizzle-orm";
|
import { and, desc, eq, lt, type SQL } from "drizzle-orm";
|
||||||
import { getDatabase } from "../../shared/database/index.js";
|
import { getDatabase } from "../../shared/database/index.js";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createChildLogger } from "@bete/shared/logger";
|
import { createChildLogger } from "@/shared/logger/index";
|
||||||
import type { Request, Response, Router } from "express";
|
import type { Request, Response, Router } from "express";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import { asyncHandler } from "../../shared/middlewares/index.js";
|
import { asyncHandler } from "../../shared/middlewares/index.js";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createChildLogger } from "@bete/shared/logger";
|
import { createChildLogger } from "@/shared/logger/index";
|
||||||
import { sql } from "drizzle-orm";
|
import { sql } from "drizzle-orm";
|
||||||
import { getDatabase } from "../../shared/database/index.js";
|
import { getDatabase } from "../../shared/database/index.js";
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createChildLogger } from "@bete/shared/logger";
|
import { createChildLogger } from "@/shared/logger/index";
|
||||||
import type { Request, Response } from "express";
|
import type { Request, Response } from "express";
|
||||||
import { asyncHandler } from "../../shared/middlewares/index.js";
|
import { asyncHandler } from "../../shared/middlewares/index.js";
|
||||||
import { publishCommandNoReply } from "../../shared/redis/index.js";
|
import { publishCommandNoReply } from "../../shared/redis/index.js";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createChildLogger } from "@bete/shared/logger";
|
import { createChildLogger } from "@/shared/logger/index";
|
||||||
import type { Request, Response, Router } from "express";
|
import type { Request, Response, Router } from "express";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import { asyncHandler, validateBody } from "../../shared/middlewares/index.js";
|
import { asyncHandler, validateBody } from "../../shared/middlewares/index.js";
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
type CommandReply,
|
type CommandReply,
|
||||||
pgMessagesTable,
|
pgMessagesTable,
|
||||||
VOICE_STATUS_KEY,
|
VOICE_STATUS_KEY,
|
||||||
} from "@bete/shared";
|
} from "../../shared/index.js";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import {
|
import {
|
||||||
createChildLogger,
|
createChildLogger,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { CommandReply } from "@bete/shared";
|
import type { CommandReply } from "./index.js";
|
||||||
import { createChildLogger } from "@bete/shared/logger";
|
import { createChildLogger } from "./logger/index.js";
|
||||||
|
|
||||||
export { createChildLogger };
|
export { createChildLogger };
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,311 @@
|
|||||||
import "dotenv/config";
|
/**
|
||||||
import { config as sharedConfig } from "@bete/shared/config";
|
* Unified configuration schema shared by all services.
|
||||||
|
*
|
||||||
|
* This is the single source of truth for all environment variables.
|
||||||
|
* Individual services re-export from here; they do NOT define their own schemas.
|
||||||
|
*/
|
||||||
|
|
||||||
export const config = sharedConfig;
|
import { z } from "zod";
|
||||||
|
import { ConfigError } from "../errors/index.js";
|
||||||
|
|
||||||
|
export const configSchema = z
|
||||||
|
.object({
|
||||||
|
// ── Discord ──────────────────────────────────────────────────────────
|
||||||
|
DISCORD_TOKEN: z
|
||||||
|
.string()
|
||||||
|
.min(1, "DISCORD_TOKEN is required")
|
||||||
|
.transform((value) => value.replace(/^("|')|(?:("|'))$/g, "")),
|
||||||
|
MONITOR_GUILD_IDS: z
|
||||||
|
.string()
|
||||||
|
.default("")
|
||||||
|
.transform((v) => v.split(",").filter(Boolean)),
|
||||||
|
MONITOR_GUILD_ID: z.string().min(1).optional(),
|
||||||
|
TEXT_GUILD_ID: z.string().min(1).optional(),
|
||||||
|
TEXT_CHANNEL_ID: z.string().min(1).optional(),
|
||||||
|
EXCLUDED_CHANNEL_IDS: z
|
||||||
|
.string()
|
||||||
|
.default("")
|
||||||
|
.transform((v) => v.split(",").filter(Boolean))
|
||||||
|
.describe("Channel IDs to exclude from capture"),
|
||||||
|
EXCLUDED_THREAD_IDS: z
|
||||||
|
.string()
|
||||||
|
.default("")
|
||||||
|
.transform((v) => v.split(",").filter(Boolean))
|
||||||
|
.describe("Thread IDs to exclude from capture"),
|
||||||
|
|
||||||
|
// ── Legacy voice ─────────────────────────────────────────────────────
|
||||||
|
VOICE_GUILD_ID: z.string().min(1).optional(),
|
||||||
|
VOICE_CHANNEL_ID: z.string().min(1).optional(),
|
||||||
|
|
||||||
|
// ── Recording ────────────────────────────────────────────────────────
|
||||||
|
RECORDINGS_DIR: z.string().default("./recordings"),
|
||||||
|
RECORDING_SEGMENT_MS: z.coerce.number().positive().default(5000),
|
||||||
|
|
||||||
|
// ── Decoder ──────────────────────────────────────────────────────────
|
||||||
|
DECODER_ROTATE_MS: z.coerce.number().positive().default(5000),
|
||||||
|
DECODER_COOLDOWN_MS: z.coerce.number().positive().default(30000),
|
||||||
|
|
||||||
|
// ── Audio ────────────────────────────────────────────────────────────
|
||||||
|
AUDIO_STREAM_SILENCE_DURATION_MS: z.coerce
|
||||||
|
.number()
|
||||||
|
.positive()
|
||||||
|
.default(3000),
|
||||||
|
PACKET_FILTER_MIN_SIZE: z.coerce.number().positive().default(8),
|
||||||
|
OPUS_FRAME_SIZE: z.coerce.number().positive().default(960),
|
||||||
|
AUDIO_SAMPLE_RATE: z.coerce.number().positive().default(48000),
|
||||||
|
AUDIO_CHANNELS: z.coerce.number().positive().default(2),
|
||||||
|
AVATAR_SIZE: z.coerce.number().positive().default(64),
|
||||||
|
|
||||||
|
// ── Server ───────────────────────────────────────────────────────────
|
||||||
|
WEBSERVER_PORT: z.coerce.number().positive().default(3001),
|
||||||
|
NODE_ENV: z
|
||||||
|
.enum(["development", "production", "test"])
|
||||||
|
.default("development"),
|
||||||
|
LOG_LEVEL: z
|
||||||
|
.enum(["error", "warn", "info", "http", "verbose", "debug", "silly"])
|
||||||
|
.default("info"),
|
||||||
|
VERBOSE: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((v) => v === "true")
|
||||||
|
.default(false),
|
||||||
|
ADMIN_PASSWORD: z.string().default("admin123"),
|
||||||
|
WEBHOOK_URLS: z
|
||||||
|
.string()
|
||||||
|
.default("")
|
||||||
|
.transform((v) => v.split(",").filter(Boolean)),
|
||||||
|
WEBHOOK_EVENTS: z
|
||||||
|
.string()
|
||||||
|
.default("message_flagged,auto_deleted,high_severity")
|
||||||
|
.transform((v) => v.split(",").filter(Boolean)),
|
||||||
|
METRICS_PORT: z.coerce.number().positive().default(9090),
|
||||||
|
|
||||||
|
// ── Database (PostgreSQL) ────────────────────────────────────────────
|
||||||
|
DATABASE_URL: z.string().optional(),
|
||||||
|
POSTGRES_HOST: z.string().default("localhost"),
|
||||||
|
POSTGRES_PORT: z.coerce.number().int().positive().default(5432),
|
||||||
|
POSTGRES_USER: z.string().optional(),
|
||||||
|
POSTGRES_PASSWORD: z.string().optional(),
|
||||||
|
POSTGRES_DB: z.string().optional(),
|
||||||
|
POSTGRES_POOL_MIN: z.coerce.number().int().positive().default(2),
|
||||||
|
POSTGRES_POOL_MAX: z.coerce.number().int().positive().default(10),
|
||||||
|
|
||||||
|
// ── Redis ────────────────────────────────────────────────────────────
|
||||||
|
REDIS_URL: z.string().default("redis://localhost:6379"),
|
||||||
|
// ── Voice PCM WebSocket (direct gateway→backend, bypasses Redis) ────
|
||||||
|
VOICE_PCM_WS_ENABLED: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((v) => v === "true")
|
||||||
|
.default(true),
|
||||||
|
BACKEND_WS_URL: z.string().default("ws://backend:3000/ws"),
|
||||||
|
BACKEND_WS_TOKEN: z.string().optional().default(""),
|
||||||
|
|
||||||
|
// ── Connection ───────────────────────────────────────────────────────
|
||||||
|
VOICE_CONNECTION_TIMEOUT_MS: z.coerce.number().positive().default(15000),
|
||||||
|
RECONNECT_TIMEOUT_MS: z.coerce.number().positive().default(5000),
|
||||||
|
|
||||||
|
// ── Attachments ─────────────────────────────────────────────────────
|
||||||
|
TELE_UPLOAD_URL: z
|
||||||
|
.string()
|
||||||
|
.url()
|
||||||
|
.default("https://upload.asepharyana.my.id/api/upload"),
|
||||||
|
ATTACHMENT_UPLOAD_TIMEOUT_MS: z.coerce.number().positive().default(30000),
|
||||||
|
ATTACHMENT_MAX_SIZE_MB: z.coerce.number().positive().default(100),
|
||||||
|
ATTACHMENT_RETRY_ATTEMPTS: z.coerce.number().positive().default(3),
|
||||||
|
BACKLOG_SYNC_HOURS: z.coerce.number().positive().default(24),
|
||||||
|
BACKLOG_SYNC_BATCH_SIZE: z.coerce
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.positive()
|
||||||
|
.max(100)
|
||||||
|
.default(100),
|
||||||
|
|
||||||
|
// ── AI Analysis ─────────────────────────────────────────────────────
|
||||||
|
AI_ANALYSIS_ENABLED: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((v) => v === "true")
|
||||||
|
.default(false),
|
||||||
|
AI_LLM_API_KEY: z.string().optional(),
|
||||||
|
AI_LLM_BASE_URL: z
|
||||||
|
.string()
|
||||||
|
.url()
|
||||||
|
.default("https://9router.asepharyana.my.id/v1"),
|
||||||
|
AI_LLM_MODEL: z.string().default("text"),
|
||||||
|
AI_LLM_VISION_MODEL: z.string().optional(),
|
||||||
|
AI_LLM_MAX_CONCURRENT: z.coerce.number().int().positive().default(5),
|
||||||
|
AI_LLM_IMAGE_MAX_DIMENSION: z.coerce
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.positive()
|
||||||
|
.default(1024),
|
||||||
|
AI_LLM_TEXT_BATCH_SIZE: z.coerce.number().int().positive().default(20),
|
||||||
|
AI_LLM_MEDIA_ANALYSIS_TIMEOUT_MS: z.coerce
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.positive()
|
||||||
|
.default(60000),
|
||||||
|
// ── AI Model (new unified keys) ───────────────────────────────────
|
||||||
|
AI_MODEL_FAST_CLASSIFIER_ENABLED: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((v) => v === "true")
|
||||||
|
.default(true)
|
||||||
|
.describe("Enable Layer 1 fast heuristic classifier"),
|
||||||
|
AI_MODEL_LLM_TIMEOUT_MS: z.coerce
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.positive()
|
||||||
|
.default(30000)
|
||||||
|
.describe("Timeout for individual LLM moderation calls"),
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ── AI Analysis Timing ──────────────────────────────────────────────
|
||||||
|
AI_ANALYSIS_DEBOUNCE_MS: z.coerce.number().positive().default(500),
|
||||||
|
AI_ANALYSIS_RECOVERY_INTERVAL_MS: z.coerce
|
||||||
|
.number()
|
||||||
|
.positive()
|
||||||
|
.default(15000),
|
||||||
|
AI_ANALYSIS_ERROR_COOLDOWN_MS: z.coerce.number().positive().default(30000),
|
||||||
|
|
||||||
|
// ── AI Analysis Batch ───────────────────────────────────────────────
|
||||||
|
AI_ANALYSIS_MAX_BATCH_SIZE: z.coerce.number().int().positive().default(200),
|
||||||
|
AI_ANALYSIS_MAX_CONTEXT_TOKENS: z.coerce.number().positive().default(8000),
|
||||||
|
AI_ANALYSIS_MAX_TARGET_TOKENS: z.coerce.number().positive().default(4000),
|
||||||
|
AI_ANALYSIS_CONTEXT_MESSAGE_LIMIT: z.coerce
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.positive()
|
||||||
|
.default(20),
|
||||||
|
AI_ANALYSIS_PROCESSING_TIMEOUT_MS: z.coerce
|
||||||
|
.number()
|
||||||
|
.positive()
|
||||||
|
.default(120000),
|
||||||
|
AI_ANALYSIS_INDIVIDUAL_MAX_CONCURRENT: z.coerce
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.positive()
|
||||||
|
.default(50),
|
||||||
|
AI_ANALYSIS_INDIVIDUAL_CB_THRESHOLD: z.coerce
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.positive()
|
||||||
|
.default(50),
|
||||||
|
PISCINA_MAX_THREADS: z.coerce.number().int().positive().optional(),
|
||||||
|
|
||||||
|
// ── Voice Transcription ────────────────────────────────────────────────
|
||||||
|
AI_VOICE_TRANSCRIPTION_ENABLED: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((v) => v === "true")
|
||||||
|
.default(false),
|
||||||
|
|
||||||
|
// ── OpenAI Moderation ───────────────────────────────────────────────
|
||||||
|
OPENAI_MODERATION_API_KEY: z.string().optional(),
|
||||||
|
OPENAI_MODERATION_BASE_URL: z
|
||||||
|
.string()
|
||||||
|
.url()
|
||||||
|
.default("https://api.openai.com/v1"),
|
||||||
|
OPENAI_MODERATION_MODEL: z.string().default("omni-moderation-latest"),
|
||||||
|
|
||||||
|
// ── Auto Delete ─────────────────────────────────────────────────────
|
||||||
|
AUTO_DELETE_FLAGGED_ENABLED: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((v) => v === "true")
|
||||||
|
.default(true),
|
||||||
|
AUTO_DELETE_FLAGGED_DRY_RUN: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((v) => v === "true")
|
||||||
|
.default(false),
|
||||||
|
AUTO_DELETE_FLAGGED_DELAY_MS: z.coerce.number().min(0).default(0),
|
||||||
|
AUTO_DELETE_MIN_CONFIDENCE: z.coerce.number().min(0).max(1).default(0.5),
|
||||||
|
AUTO_DELETE_ALLOWED_SEVERITIES: z
|
||||||
|
.string()
|
||||||
|
.default("critical,high,medium,low"),
|
||||||
|
AUTO_DELETE_ALLOWED_CATEGORIES: z.string().default(""),
|
||||||
|
AUTO_DELETE_EXCLUDED_CHANNEL_IDS: z.string().default(""),
|
||||||
|
AUTO_DELETE_EXCLUDED_USER_IDS: z.string().default(""),
|
||||||
|
AUTO_DELETE_NOTIFY_USER: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((v) => v === "true")
|
||||||
|
.default(false),
|
||||||
|
AUTO_DELETE_LOG_CHANNEL_ID: z.string().default(""),
|
||||||
|
|
||||||
|
// ── Retention ───────────────────────────────────────────────────────
|
||||||
|
RETENTION_MESSAGES_DAYS: z.coerce.number().int().min(0).default(0),
|
||||||
|
RETENTION_ATTACHMENTS_DAYS: z.coerce.number().int().min(0).default(0),
|
||||||
|
RETENTION_VOICE_DAYS: z.coerce.number().int().min(0).default(0),
|
||||||
|
RETENTION_CLEANUP_INTERVAL_MS: z.coerce
|
||||||
|
.number()
|
||||||
|
.positive()
|
||||||
|
.default(24 * 60 * 60 * 1000),
|
||||||
|
RETENTION_DRY_RUN: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((v) => v === "true")
|
||||||
|
.default(true),
|
||||||
|
AUTO_MIGRATE_ON_STARTUP: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((v) => v === "true")
|
||||||
|
.default(true),
|
||||||
|
})
|
||||||
|
.superRefine((value, ctx) => {
|
||||||
|
if (!value.AI_ANALYSIS_ENABLED) {
|
||||||
|
// skip: AI analysis not enabled
|
||||||
|
} else if (!value.AI_LLM_API_KEY) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ["AI_LLM_API_KEY"],
|
||||||
|
message: "AI_LLM_API_KEY is required when AI_ANALYSIS_ENABLED=true",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate database configuration
|
||||||
|
if (!value.DATABASE_URL && !value.POSTGRES_HOST) {
|
||||||
|
ctx.addIssue({
|
||||||
|
code: z.ZodIssueCode.custom,
|
||||||
|
path: ["DATABASE_URL"],
|
||||||
|
message: "Either DATABASE_URL or POSTGRES_HOST must be provided",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export type AppConfig = z.infer<typeof configSchema> & {
|
||||||
|
EFFECTIVE_TEXT_GUILD_ID?: string;
|
||||||
|
EFFECTIVE_VOICE_GUILD_ID?: string;
|
||||||
|
EFFECTIVE_MONITOR_GUILD_IDS: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function loadConfig(env: NodeJS.ProcessEnv = process.env): AppConfig {
|
||||||
|
try {
|
||||||
|
const parsed = configSchema.parse(env);
|
||||||
|
return {
|
||||||
|
...parsed,
|
||||||
|
EFFECTIVE_TEXT_GUILD_ID: parsed.TEXT_GUILD_ID ?? parsed.MONITOR_GUILD_ID,
|
||||||
|
EFFECTIVE_VOICE_GUILD_ID: parsed.VOICE_GUILD_ID,
|
||||||
|
EFFECTIVE_MONITOR_GUILD_IDS:
|
||||||
|
parsed.MONITOR_GUILD_IDS.length > 0
|
||||||
|
? parsed.MONITOR_GUILD_IDS
|
||||||
|
: parsed.MONITOR_GUILD_ID
|
||||||
|
? [parsed.MONITOR_GUILD_ID]
|
||||||
|
: [],
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof z.ZodError) {
|
||||||
|
const messages = error.issues
|
||||||
|
.map((e) => `${e.path.join(".")}: ${e.message}`)
|
||||||
|
.join("\n");
|
||||||
|
throw new ConfigError(`Configuration validation failed:\n${messages}`);
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Singleton config loaded from process.env at import time. */
|
||||||
|
export const config = loadConfig();
|
||||||
@@ -3,8 +3,8 @@ import {
|
|||||||
getDatabase as sharedGetDb,
|
getDatabase as sharedGetDb,
|
||||||
getPool as sharedGetPool,
|
getPool as sharedGetPool,
|
||||||
initializeDatabase as sharedInit,
|
initializeDatabase as sharedInit,
|
||||||
} from "@bete/shared/database/init";
|
} from "@/shared/database/init";
|
||||||
import { createChildLogger } from "@bete/shared/logger";
|
import { createChildLogger } from "@/shared/logger/index";
|
||||||
import { config } from "../config/index.js";
|
import { config } from "../config/index.js";
|
||||||
|
|
||||||
const logger = createChildLogger("database");
|
const logger = createChildLogger("database");
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { createChildLogger } from "@bete/shared/logger";
|
import { createChildLogger } from "../logger/index.js";
|
||||||
import { drizzle } from "drizzle-orm/node-postgres";
|
import { drizzle } from "drizzle-orm/node-postgres";
|
||||||
import type { Pool, PoolClient } from "pg";
|
import type { Pool, PoolClient } from "pg";
|
||||||
import { closePool, createPoolFromConfig } from "./pool.js";
|
import { closePool, createPoolFromConfig } from "./pool.js";
|
||||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Loaded 50 of 167 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user