feat: add PostgreSQL config and dependencies

This commit is contained in:
MythEclipse
2026-05-14 14:44:01 +07:00
parent 0eee7b9390
commit 818a059121
5 changed files with 257 additions and 2 deletions
+19
View File
@@ -45,3 +45,22 @@ AI_LLM_BASE_URL=https://9router.asepharyana.tech/v1
AI_LLM_MODEL=free AI_LLM_MODEL=free
AI_ANALYSIS_TIMEOUT_MS=30000 AI_ANALYSIS_TIMEOUT_MS=30000
# Database Configuration
DATABASE_TYPE=sqlite
# DATABASE_TYPE=postgres
# PostgreSQL Configuration (used when DATABASE_TYPE=postgres)
# Option 1: Use DATABASE_URL for connection string
# DATABASE_URL=postgresql://user:password@localhost:5432/discord_bot
# Option 2: Use individual connection parameters
# POSTGRES_HOST=localhost
# POSTGRES_PORT=5432
# POSTGRES_USER=postgres
# POSTGRES_PASSWORD=your_password_here
# POSTGRES_DB=discord_bot
# PostgreSQL Connection Pool Configuration
# POSTGRES_POOL_MIN=2
# POSTGRES_POOL_MAX=10
+2
View File
@@ -17,6 +17,7 @@
"@discordjs/opus": "^0.10.0", "@discordjs/opus": "^0.10.0",
"@discordjs/voice": "^0.19.1", "@discordjs/voice": "^0.19.1",
"@snazzah/davey": "^0.1.10", "@snazzah/davey": "^0.1.10",
"@types/pg": "^8.20.0",
"better-sqlite3": "^12.10.0", "better-sqlite3": "^12.10.0",
"class-transformer": "^0.5.1", "class-transformer": "^0.5.1",
"class-validator": "^0.15.1", "class-validator": "^0.15.1",
@@ -27,6 +28,7 @@
"helmet": "^8.1.0", "helmet": "^8.1.0",
"libsodium-wrappers": "^0.8.2", "libsodium-wrappers": "^0.8.2",
"p-retry": "^6.2.0", "p-retry": "^6.2.0",
"pg": "^8.20.0",
"pino": "^9.4.0", "pino": "^9.4.0",
"pino-http": "^11.0.0", "pino-http": "^11.0.0",
"prism-media": "2.0.0-alpha.0", "prism-media": "2.0.0-alpha.0",
+116
View File
@@ -17,6 +17,9 @@ importers:
'@snazzah/davey': '@snazzah/davey':
specifier: ^0.1.10 specifier: ^0.1.10
version: 0.1.11(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) version: 0.1.11(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)
'@types/pg':
specifier: ^8.20.0
version: 8.20.0
better-sqlite3: better-sqlite3:
specifier: ^12.10.0 specifier: ^12.10.0
version: 12.10.0 version: 12.10.0
@@ -47,6 +50,9 @@ importers:
p-retry: p-retry:
specifier: ^6.2.0 specifier: ^6.2.0
version: 6.2.1 version: 6.2.1
pg:
specifier: ^8.20.0
version: 8.20.0
pino: pino:
specifier: ^9.4.0 specifier: ^9.4.0
version: 9.14.0 version: 9.14.0
@@ -614,6 +620,9 @@ packages:
'@types/node@24.12.4': '@types/node@24.12.4':
resolution: {integrity: sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==} resolution: {integrity: sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==}
'@types/pg@8.20.0':
resolution: {integrity: sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==}
'@types/qs@6.15.1': '@types/qs@6.15.1':
resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==} resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==}
@@ -1384,6 +1393,40 @@ packages:
pathe@2.0.3: pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
pg-cloudflare@1.3.0:
resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==}
pg-connection-string@2.12.0:
resolution: {integrity: sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==}
pg-int8@1.0.1:
resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
engines: {node: '>=4.0.0'}
pg-pool@3.13.0:
resolution: {integrity: sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==}
peerDependencies:
pg: '>=8.0'
pg-protocol@1.13.0:
resolution: {integrity: sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==}
pg-types@2.2.0:
resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
engines: {node: '>=4'}
pg@8.20.0:
resolution: {integrity: sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==}
engines: {node: '>= 16.0.0'}
peerDependencies:
pg-native: '>=3.0.1'
peerDependenciesMeta:
pg-native:
optional: true
pgpass@1.0.5:
resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==}
picocolors@1.1.1: picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -1426,6 +1469,22 @@ packages:
resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==}
engines: {node: ^10 || ^12 || >=14} engines: {node: ^10 || ^12 || >=14}
postgres-array@2.0.0:
resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
engines: {node: '>=4'}
postgres-bytea@1.0.1:
resolution: {integrity: sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==}
engines: {node: '>=0.10.0'}
postgres-date@1.0.7:
resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==}
engines: {node: '>=0.10.0'}
postgres-interval@1.2.0:
resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
engines: {node: '>=0.10.0'}
prebuild-install@7.1.3: prebuild-install@7.1.3:
resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -1900,6 +1959,10 @@ packages:
utf-8-validate: utf-8-validate:
optional: true optional: true
xtend@4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
y18n@4.0.3: y18n@4.0.3:
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
@@ -2328,6 +2391,12 @@ snapshots:
dependencies: dependencies:
undici-types: 7.16.0 undici-types: 7.16.0
'@types/pg@8.20.0':
dependencies:
'@types/node': 24.12.4
pg-protocol: 1.13.0
pg-types: 2.2.0
'@types/qs@6.15.1': {} '@types/qs@6.15.1': {}
'@types/range-parser@1.2.7': {} '@types/range-parser@1.2.7': {}
@@ -3066,6 +3135,41 @@ snapshots:
pathe@2.0.3: {} pathe@2.0.3: {}
pg-cloudflare@1.3.0:
optional: true
pg-connection-string@2.12.0: {}
pg-int8@1.0.1: {}
pg-pool@3.13.0(pg@8.20.0):
dependencies:
pg: 8.20.0
pg-protocol@1.13.0: {}
pg-types@2.2.0:
dependencies:
pg-int8: 1.0.1
postgres-array: 2.0.0
postgres-bytea: 1.0.1
postgres-date: 1.0.7
postgres-interval: 1.2.0
pg@8.20.0:
dependencies:
pg-connection-string: 2.12.0
pg-pool: 3.13.0(pg@8.20.0)
pg-protocol: 1.13.0
pg-types: 2.2.0
pgpass: 1.0.5
optionalDependencies:
pg-cloudflare: 1.3.0
pgpass@1.0.5:
dependencies:
split2: 4.2.0
picocolors@1.1.1: {} picocolors@1.1.1: {}
picomatch@4.0.4: {} picomatch@4.0.4: {}
@@ -3145,6 +3249,16 @@ snapshots:
picocolors: 1.1.1 picocolors: 1.1.1
source-map-js: 1.2.1 source-map-js: 1.2.1
postgres-array@2.0.0: {}
postgres-bytea@1.0.1: {}
postgres-date@1.0.7: {}
postgres-interval@1.2.0:
dependencies:
xtend: 4.0.2
prebuild-install@7.1.3: prebuild-install@7.1.3:
dependencies: dependencies:
detect-libc: 2.1.2 detect-libc: 2.1.2
@@ -3600,6 +3714,8 @@ snapshots:
ws@8.20.1: {} ws@8.20.1: {}
xtend@4.0.2: {}
y18n@4.0.3: {} y18n@4.0.3: {}
yallist@4.0.0: {} yallist@4.0.0: {}
+97
View File
@@ -0,0 +1,97 @@
#!/bin/bash
# PostgreSQL Migration Quick Start Script
# Run this to set up PostgreSQL migration
set -e
echo "🚀 Discord Bot - PostgreSQL Migration Setup"
echo "==========================================="
echo ""
# Check if PostgreSQL is installed
if ! command -v psql &> /dev/null; then
echo "❌ PostgreSQL is not installed. Please install PostgreSQL first."
echo " macOS: brew install postgresql"
echo " Ubuntu: sudo apt-get install postgresql postgresql-contrib"
echo " Windows: Download from https://www.postgresql.org/download/windows/"
exit 1
fi
echo "✅ PostgreSQL found"
echo ""
# Check if Node.js is installed
if ! command -v node &> /dev/null; then
echo "❌ Node.js is not installed."
exit 1
fi
echo "✅ Node.js found"
echo ""
# Create .env if it doesn't exist
if [ ! -f .env ]; then
echo "📝 Creating .env file..."
cp .env.example .env
echo "⚠️ Please update .env with your PostgreSQL connection string"
echo " DATABASE_URL=postgresql://user:password@localhost:5432/discord_bot"
echo ""
fi
# Install dependencies
echo "📦 Installing dependencies..."
pnpm add pg pg-pool node-pg-migrate
pnpm add -D @types/pg @types/node-pg-migrate
pnpm remove better-sqlite3 @types/better-sqlite3 2>/dev/null || true
echo "✅ Dependencies installed"
echo ""
# Create database
read -p "Enter PostgreSQL username (default: postgres): " PG_USER
PG_USER=${PG_USER:-postgres}
read -p "Enter PostgreSQL password: " -s PG_PASSWORD
echo ""
read -p "Enter database name (default: discord_bot): " DB_NAME
DB_NAME=${DB_NAME:-discord_bot}
read -p "Enter PostgreSQL host (default: localhost): " PG_HOST
PG_HOST=${PG_HOST:-localhost}
read -p "Enter PostgreSQL port (default: 5432): " PG_PORT
PG_PORT=${PG_PORT:-5432}
echo ""
echo "🗄️ Creating database..."
PGPASSWORD=$PG_PASSWORD psql -h $PG_HOST -U $PG_USER -p $PG_PORT -tc "SELECT 1 FROM pg_database WHERE datname = '$DB_NAME'" | grep -q 1 || \
PGPASSWORD=$PG_PASSWORD psql -h $PG_HOST -U $PG_USER -p $PG_PORT -c "CREATE DATABASE $DB_NAME"
echo "✅ Database created"
echo ""
# Update .env with connection string
DATABASE_URL="postgresql://$PG_USER:$PG_PASSWORD@$PG_HOST:$PG_PORT/$DB_NAME"
sed -i.bak "s|DATABASE_URL=.*|DATABASE_URL=$DATABASE_URL|" .env
rm -f .env.bak
echo "✅ .env updated with DATABASE_URL"
echo ""
# Run migrations
echo "🔄 Running migrations..."
npx node-pg-migrate up
echo "✅ Migrations completed"
echo ""
echo "🎉 PostgreSQL migration setup complete!"
echo ""
echo "Next steps:"
echo "1. Review the migration files in src/db/migrations/"
echo "2. Update imports in your code (see POSTGRES_IMPLEMENTATION.md)"
echo "3. Run: pnpm run typecheck"
echo "4. Run: pnpm run dev"
echo ""
+23 -2
View File
@@ -46,15 +46,36 @@ const configSchema = z.object({
AI_LLM_BASE_URL: z.string().url().default("https://9router.asepharyana.tech/v1"), AI_LLM_BASE_URL: z.string().url().default("https://9router.asepharyana.tech/v1"),
AI_LLM_MODEL: z.string().default("free"), AI_LLM_MODEL: z.string().default("free"),
AI_ANALYSIS_TIMEOUT_MS: z.coerce.number().positive().default(30000), AI_ANALYSIS_TIMEOUT_MS: z.coerce.number().positive().default(30000),
DATABASE_TYPE: z.enum(["sqlite", "postgres"]).default("sqlite"),
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),
}).superRefine((value, ctx) => { }).superRefine((value, ctx) => {
if (!value.AI_ANALYSIS_ENABLED) return; if (!value.AI_ANALYSIS_ENABLED) {
if (!value.AI_LLM_API_KEY) { // Continue to database validation
} else if (!value.AI_LLM_API_KEY) {
ctx.addIssue({ ctx.addIssue({
code: z.ZodIssueCode.custom, code: z.ZodIssueCode.custom,
path: ["AI_LLM_API_KEY"], path: ["AI_LLM_API_KEY"],
message: "AI_LLM_API_KEY is required when AI_ANALYSIS_ENABLED=true", message: "AI_LLM_API_KEY is required when AI_ANALYSIS_ENABLED=true",
}); });
} }
// Validate PostgreSQL configuration
if (value.DATABASE_TYPE === "postgres") {
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 when DATABASE_TYPE=postgres",
});
}
}
}); });
export type AppConfig = z.infer<typeof configSchema>; export type AppConfig = z.infer<typeof configSchema>;