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
|
||||
logs/
|
||||
.codegraph/
|
||||
# moon
|
||||
.moon/cache
|
||||
.moon/docker
|
||||
worktrees/
|
||||
.worktrees/
|
||||
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",
|
||||
"services/**/*.ts",
|
||||
"services/**/*.tsx",
|
||||
"packages/**/*.ts",
|
||||
"*.json",
|
||||
"*.ts",
|
||||
"!vendor",
|
||||
"!.claude",
|
||||
"!services/**/dist",
|
||||
"!packages/**/dist",
|
||||
"!services/**/.next",
|
||||
"!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
|
||||
|
||||
# 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
|
||||
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
|
||||
|
||||
# Copy dependency definition files first for better caching
|
||||
COPY pnpm-workspace.yaml .
|
||||
COPY pnpm-lock.yaml .
|
||||
COPY package.json .
|
||||
COPY services/backend/package.json services/backend/pnpm-lock.yaml* ./services/backend/
|
||||
|
||||
# Copy patches (pnpm patchedDependencies)
|
||||
COPY patches ./patches
|
||||
|
||||
# Copy packages (workspace dependencies)
|
||||
COPY packages/shared ./packages/shared
|
||||
# Copy service source (shared code is embedded in src/shared/)
|
||||
COPY services/backend/ ./services/backend/
|
||||
|
||||
# Copy service
|
||||
COPY services/backend ./services/backend
|
||||
|
||||
# Install dependencies with build cache
|
||||
# Install dependencies
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
|
||||
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
|
||||
cd services/backend && pnpm install --frozen-lockfile
|
||||
|
||||
# Build backend
|
||||
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
|
||||
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}"
|
||||
|
||||
# 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 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)
|
||||
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
|
||||
COPY packages/shared ./packages/shared
|
||||
# Source code (shared code is embedded in src/shared/)
|
||||
COPY services/discord-gateway ./services/discord-gateway
|
||||
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 \
|
||||
pnpm --filter './packages/shared' run build \
|
||||
&& pnpm --filter './services/discord-gateway' run build \
|
||||
cd services/discord-gateway && pnpm run build \
|
||||
&& apt-get clean \
|
||||
&& 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) ----
|
||||
FROM node:22-slim
|
||||
|
||||
@@ -57,11 +40,10 @@ RUN apt-get update -qq && apt-get install -y -qq --no-install-recommends \
|
||||
ffmpeg ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Copy production-only artifacts from deploy
|
||||
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 built artifacts
|
||||
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
|
||||
|
||||
# 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/*
|
||||
|
||||
# Copy dependency manifests first for layer caching
|
||||
COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./
|
||||
COPY packages/shared/package.json ./packages/shared/package.json
|
||||
COPY services/frontend/package.json ./services/frontend/package.json
|
||||
COPY services/frontend/package.json services/frontend/pnpm-lock.yaml* ./services/frontend/
|
||||
COPY services/frontend/tsconfig.json ./services/frontend/tsconfig.json
|
||||
|
||||
# Copy patches (pnpm patchedDependencies)
|
||||
COPY patches/ ./patches/
|
||||
|
||||
# Install dependencies (frontend + shared)
|
||||
# Install frontend dependencies (standalone, no shared workspace dep)
|
||||
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 packages/shared/ ./packages/shared/
|
||||
COPY services/frontend/ ./services/frontend/
|
||||
|
||||
# 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_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 \
|
||||
pnpm --filter './packages/shared' run build
|
||||
RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \
|
||||
pnpm --filter frontend run build
|
||||
cd services/frontend && pnpm run build
|
||||
|
||||
# ---- Stage 2: Nginx ----
|
||||
FROM nginx:alpine
|
||||
|
||||
+11
-18
@@ -1,31 +1,24 @@
|
||||
{
|
||||
"name": "discord-voice-recorder",
|
||||
"name": "bete-monorepo",
|
||||
"version": "1.0.0",
|
||||
"description": "Discord bot that joins a voice channel and records audio",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@11.1.3",
|
||||
"private": true,
|
||||
"description": "Bete Discord Moderation Services — standalone services in one repo",
|
||||
"scripts": {
|
||||
"dev:backend": "pnpm --filter './services/backend' run dev",
|
||||
"dev:discord-gateway": "pnpm --filter './services/discord-gateway' run dev",
|
||||
"dev:web": "pnpm --filter './services/frontend' run dev",
|
||||
"build:web": "pnpm --filter './services/frontend' run build",
|
||||
"typecheck:web": "pnpm --filter './services/frontend' run build",
|
||||
"build:backend": "pnpm --filter './services/backend' run build",
|
||||
"build:discord-gateway": "pnpm --filter './services/discord-gateway' run build",
|
||||
"typecheck": "pnpm -r run typecheck",
|
||||
"dev:backend": "pnpm --prefix services/backend dev",
|
||||
"dev:discord-gateway": "pnpm --prefix services/discord-gateway dev",
|
||||
"dev:web": "pnpm --prefix services/frontend dev",
|
||||
"build:web": "pnpm --prefix services/frontend build",
|
||||
"build:backend": "pnpm --prefix services/backend build",
|
||||
"build:discord-gateway": "pnpm --prefix services/discord-gateway build",
|
||||
"typecheck": "pnpm --prefix services/backend typecheck && pnpm --prefix services/discord-gateway typecheck",
|
||||
"lint": "biome check --diagnostic-level=error .",
|
||||
"format": "biome format --write .",
|
||||
"test": "pnpm -r run test",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "drizzle-kit migrate",
|
||||
"db:studio": "drizzle-kit studio",
|
||||
"test": "pnpm --prefix services/backend test && pnpm --prefix services/discord-gateway test",
|
||||
"db:truncate": "psql \"$DATABASE_URL\" -f scripts/truncate-all.sql",
|
||||
"install:yt-dlp": "sh scripts/install-yt-dlp.sh"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.5.5",
|
||||
"drizzle-kit": "^0.31.10",
|
||||
"tsx": "^4.22.2",
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bete/shared": "workspace:*",
|
||||
"@discordjs/voice": "^0.19.2",
|
||||
"axios": "^1.16.1",
|
||||
"dotenv": "^17.4.2",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import { createChildLogger } from "@/shared/logger/index";
|
||||
import express, {
|
||||
type Express,
|
||||
type NextFunction,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 { initializeDatabase } from "../shared/database/index.js";
|
||||
import { startRedisBridge } from "../ws/redis-bridge.js";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 { closeDatabase } from "./shared/database/index.js";
|
||||
import { stopCommandBridge } from "./shared/redis/index.js";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { pgMessagesTable } from "@bete/shared";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import { pgMessagesTable } from "../../shared/index.js";
|
||||
import { createChildLogger } from "../../shared/logger/index.js";
|
||||
import { and, desc, eq, ilike, type SQL } from "drizzle-orm";
|
||||
import { getDatabase } from "../../shared/database/index.js";
|
||||
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 express from "express";
|
||||
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 type { AnalysisSearchQuery } 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 { asyncHandler } from "../../shared/middlewares/index.js";
|
||||
import { chatbotService } from "./chatbot.service.js";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { pgChatbotMessagesTable, pgMessagesTable } from "@bete/shared";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import { pgChatbotMessagesTable, pgMessagesTable } from "../../shared/index.js";
|
||||
import { createChildLogger } from "../../shared/logger/index.js";
|
||||
import { and, desc, eq, type SQL, sql } from "drizzle-orm";
|
||||
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 type {
|
||||
ChatbotContext,
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
pgUserProfilesTable,
|
||||
pgUserReputationsTable,
|
||||
pgVoiceRecordingsTable,
|
||||
} from "@bete/shared";
|
||||
} from "../../shared/index.js";
|
||||
import type { SQL } from "drizzle-orm";
|
||||
import { sql } from "drizzle-orm";
|
||||
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 express from "express";
|
||||
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";
|
||||
|
||||
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 { 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 express from "express";
|
||||
import { asyncHandler, validateBody } from "../../shared/middlewares/index.js";
|
||||
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
COMMAND_MEDIA_STOP,
|
||||
COMMAND_MEDIA_VOLUME,
|
||||
MEDIA_STATUS_KEY,
|
||||
} from "@bete/shared";
|
||||
} from "../../shared/index.js";
|
||||
import {
|
||||
createChildLogger,
|
||||
tryCommandThenFallback,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import { createChildLogger } from "@/shared/logger/index";
|
||||
import type { Request, Response } from "express";
|
||||
import { asyncHandler } from "../../shared/middlewares/index.js";
|
||||
import { messageQuerySchema } from "./messages.schema.js";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { PageResult } from "@bete/shared";
|
||||
import { pgAttachmentsTable, pgMessagesTable } from "@bete/shared";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import type { PageResult } from "../../shared/index.js";
|
||||
import { pgAttachmentsTable, pgMessagesTable } from "../../shared/index.js";
|
||||
import { createChildLogger } from "../../shared/logger/index.js";
|
||||
import {
|
||||
and,
|
||||
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 express from "express";
|
||||
import { asyncHandler, validateBody } from "../../shared/middlewares/index.js";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NotFoundError, ValidationError } from "@bete/shared/errors";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import { NotFoundError, ValidationError } from "@/shared/errors/index";
|
||||
import { createChildLogger } from "@/shared/logger/index";
|
||||
import { messagesRepository } from "./messages.repository.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 express from "express";
|
||||
import { asyncHandler } from "../../shared/middlewares/index.js";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { pgVoiceRecordingsTable } from "@bete/shared";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import { pgVoiceRecordingsTable } from "../../shared/index.js";
|
||||
import { createChildLogger } from "../../shared/logger/index.js";
|
||||
import { and, desc, eq, lt, type SQL } from "drizzle-orm";
|
||||
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 express from "express";
|
||||
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 { 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 { asyncHandler } from "../../shared/middlewares/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 express from "express";
|
||||
import { asyncHandler, validateBody } from "../../shared/middlewares/index.js";
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
type CommandReply,
|
||||
pgMessagesTable,
|
||||
VOICE_STATUS_KEY,
|
||||
} from "@bete/shared";
|
||||
} from "../../shared/index.js";
|
||||
import { eq } from "drizzle-orm";
|
||||
import {
|
||||
createChildLogger,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { CommandReply } from "@bete/shared";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
import type { CommandReply } from "./index.js";
|
||||
import { createChildLogger } from "./logger/index.js";
|
||||
|
||||
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,
|
||||
getPool as sharedGetPool,
|
||||
initializeDatabase as sharedInit,
|
||||
} from "@bete/shared/database/init";
|
||||
import { createChildLogger } from "@bete/shared/logger";
|
||||
} from "@/shared/database/init";
|
||||
import { createChildLogger } from "@/shared/logger/index";
|
||||
import { config } from "../config/index.js";
|
||||
|
||||
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 type { Pool, PoolClient } from "pg";
|
||||
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