242 lines
8.0 KiB
TypeScript
242 lines
8.0 KiB
TypeScript
/**
|
|
* 数据库种子脚本
|
|
*
|
|
* 用法:
|
|
* npx ts-node scripts/seed.ts
|
|
*
|
|
* 或在 docker-compose 中启动时执行
|
|
*
|
|
* 环境变量:
|
|
* DATABASE_URL: 数据库连接字符串
|
|
* DB_HOST, DB_PORT, DB_USER, DB_PASSWORD, DB_NAME: 单独配置
|
|
*/
|
|
|
|
import { Client } from 'pg';
|
|
import * as bcrypt from 'bcrypt';
|
|
|
|
// 默认租户 ID
|
|
const DEFAULT_TENANT_ID = '00000000-0000-0000-0000-000000000001';
|
|
|
|
// 数据库连接配置
|
|
function getDbConfig() {
|
|
if (process.env.DATABASE_URL) {
|
|
return { connectionString: process.env.DATABASE_URL };
|
|
}
|
|
return {
|
|
host: process.env.DB_HOST || 'localhost',
|
|
port: parseInt(process.env.DB_PORT || '5432', 10),
|
|
user: process.env.DB_USER || 'postgres',
|
|
password: process.env.DB_PASSWORD || 'postgres',
|
|
database: process.env.DB_NAME || 'iconsulting',
|
|
};
|
|
}
|
|
|
|
async function seed() {
|
|
const client = new Client(getDbConfig());
|
|
|
|
try {
|
|
await client.connect();
|
|
console.log('Connected to database');
|
|
|
|
// 1. 检查并创建默认租户
|
|
await seedTenant(client);
|
|
|
|
// 2. 检查并创建默认管理员
|
|
await seedAdmin(client);
|
|
|
|
// 3. 检查并创建默认系统配置
|
|
await seedSystemConfigs(client);
|
|
|
|
// 4. 检查并创建默认服务定价
|
|
await seedServicePricing(client);
|
|
|
|
console.log('Seed completed successfully!');
|
|
} catch (error) {
|
|
console.error('Seed failed:', error);
|
|
process.exit(1);
|
|
} finally {
|
|
await client.end();
|
|
}
|
|
}
|
|
|
|
async function seedTenant(client: Client) {
|
|
console.log('Checking tenants table...');
|
|
|
|
// 检查 tenants 表是否存在
|
|
const tableExists = await client.query(`
|
|
SELECT EXISTS (
|
|
SELECT FROM information_schema.tables
|
|
WHERE table_schema = 'public'
|
|
AND table_name = 'tenants'
|
|
);
|
|
`);
|
|
|
|
if (!tableExists.rows[0].exists) {
|
|
console.log(' tenants table does not exist, skipping');
|
|
return;
|
|
}
|
|
|
|
// 检查默认租户是否存在
|
|
const tenantExists = await client.query(
|
|
'SELECT id FROM tenants WHERE id = $1',
|
|
[DEFAULT_TENANT_ID]
|
|
);
|
|
|
|
if (tenantExists.rows.length === 0) {
|
|
console.log(' Creating default tenant...');
|
|
await client.query(`
|
|
INSERT INTO tenants (id, name, slug, status, plan)
|
|
VALUES ($1, 'Default Tenant', 'default', 'ACTIVE', 'ENTERPRISE')
|
|
`, [DEFAULT_TENANT_ID]);
|
|
console.log(' Default tenant created');
|
|
} else {
|
|
console.log(' Default tenant already exists');
|
|
}
|
|
}
|
|
|
|
async function seedAdmin(client: Client) {
|
|
console.log('Checking admins table...');
|
|
|
|
// 检查 admins 表是否存在
|
|
const tableExists = await client.query(`
|
|
SELECT EXISTS (
|
|
SELECT FROM information_schema.tables
|
|
WHERE table_schema = 'public'
|
|
AND table_name = 'admins'
|
|
);
|
|
`);
|
|
|
|
if (!tableExists.rows[0].exists) {
|
|
console.log(' admins table does not exist, skipping');
|
|
return;
|
|
}
|
|
|
|
// 检查默认管理员是否存在
|
|
const adminExists = await client.query(
|
|
"SELECT id FROM admins WHERE username = 'admin'"
|
|
);
|
|
|
|
if (adminExists.rows.length === 0) {
|
|
console.log(' Creating default admin...');
|
|
// bcrypt hash for 'admin123' with cost 10
|
|
const passwordHash = await bcrypt.hash('admin123', 10);
|
|
|
|
// 检查表结构是否包含 tenant_id 和 is_super_admin
|
|
const hasNewColumns = await client.query(`
|
|
SELECT column_name FROM information_schema.columns
|
|
WHERE table_name = 'admins'
|
|
AND column_name IN ('tenant_id', 'is_super_admin')
|
|
`);
|
|
|
|
if (hasNewColumns.rows.length >= 2) {
|
|
// 新表结构
|
|
await client.query(`
|
|
INSERT INTO admins (tenant_id, is_super_admin, username, password_hash, name, role, permissions)
|
|
VALUES ($1, TRUE, 'admin', $2, '系统管理员', 'SUPER_ADMIN', '["*"]')
|
|
`, [DEFAULT_TENANT_ID, passwordHash]);
|
|
} else {
|
|
// 旧表结构
|
|
await client.query(`
|
|
INSERT INTO admins (username, password_hash, name, role, permissions)
|
|
VALUES ('admin', $1, '系统管理员', 'SUPER_ADMIN', '["*"]')
|
|
`, [passwordHash]);
|
|
}
|
|
console.log(' Default admin created (username: admin, password: admin123)');
|
|
} else {
|
|
console.log(' Default admin already exists');
|
|
}
|
|
}
|
|
|
|
async function seedSystemConfigs(client: Client) {
|
|
console.log('Checking system_configs table...');
|
|
|
|
// 检查 system_configs 表是否存在
|
|
const tableExists = await client.query(`
|
|
SELECT EXISTS (
|
|
SELECT FROM information_schema.tables
|
|
WHERE table_schema = 'public'
|
|
AND table_name = 'system_configs'
|
|
);
|
|
`);
|
|
|
|
if (!tableExists.rows[0].exists) {
|
|
console.log(' system_configs table does not exist, skipping');
|
|
return;
|
|
}
|
|
|
|
const configs = [
|
|
{ key: 'system_prompt_identity', value: '"专业、友善、耐心的香港移民顾问"', group: 'PROMPT', desc: '系统身份定位' },
|
|
{ key: 'system_prompt_style', value: '"专业但不生硬,用简洁明了的语言解答"', group: 'PROMPT', desc: '对话风格' },
|
|
{ key: 'system_prompt_rules', value: '["只回答移民相关问题", "复杂评估建议付费服务", "不做法律承诺"]', group: 'PROMPT', desc: '系统行为规则' },
|
|
{ key: 'max_free_messages_per_day', value: '50', group: 'SYSTEM', desc: '每日免费消息数限制' },
|
|
{ key: 'max_conversation_context_messages', value: '20', group: 'SYSTEM', desc: '对话上下文最大消息数' },
|
|
{ key: 'assessment_price_default', value: '99', group: 'PAYMENT', desc: '默认评估价格(元)' },
|
|
{ key: 'payment_timeout_minutes', value: '30', group: 'PAYMENT', desc: '支付超时时间(分钟)' },
|
|
{ key: 'sms_rate_limit_per_hour', value: '5', group: 'SYSTEM', desc: '每小时短信发送限制' },
|
|
{ key: 'enable_anonymous_chat', value: 'true', group: 'FEATURE', desc: '是否允许匿名用户聊天' },
|
|
{ key: 'require_phone_for_payment', value: 'true', group: 'FEATURE', desc: '支付时是否要求手机验证' },
|
|
];
|
|
|
|
for (const config of configs) {
|
|
const exists = await client.query(
|
|
'SELECT key FROM system_configs WHERE key = $1',
|
|
[config.key]
|
|
);
|
|
|
|
if (exists.rows.length === 0) {
|
|
await client.query(`
|
|
INSERT INTO system_configs (key, value, config_group, description)
|
|
VALUES ($1, $2, $3, $4)
|
|
`, [config.key, config.value, config.group, config.desc]);
|
|
console.log(` Created config: ${config.key}`);
|
|
}
|
|
}
|
|
console.log(' System configs seeded');
|
|
}
|
|
|
|
async function seedServicePricing(client: Client) {
|
|
console.log('Checking service_pricing table...');
|
|
|
|
// 检查 service_pricing 表是否存在
|
|
const tableExists = await client.query(`
|
|
SELECT EXISTS (
|
|
SELECT FROM information_schema.tables
|
|
WHERE table_schema = 'public'
|
|
AND table_name = 'service_pricing'
|
|
);
|
|
`);
|
|
|
|
if (!tableExists.rows[0].exists) {
|
|
console.log(' service_pricing table does not exist, skipping');
|
|
return;
|
|
}
|
|
|
|
const pricing = [
|
|
{ type: 'ASSESSMENT', category: 'QMAS', price: 99, originalPrice: 199, desc: '优才计划移民资格评估' },
|
|
{ type: 'ASSESSMENT', category: 'GEP', price: 99, originalPrice: 199, desc: '专才计划移民资格评估' },
|
|
{ type: 'ASSESSMENT', category: 'IANG', price: 79, originalPrice: 149, desc: '留学IANG移民资格评估' },
|
|
{ type: 'ASSESSMENT', category: 'TTPS', price: 99, originalPrice: 199, desc: '高才通移民资格评估' },
|
|
{ type: 'ASSESSMENT', category: 'CIES', price: 199, originalPrice: 399, desc: '投资移民资格评估' },
|
|
{ type: 'ASSESSMENT', category: 'TECHTAS', price: 99, originalPrice: 199, desc: '科技人才移民资格评估' },
|
|
];
|
|
|
|
for (const p of pricing) {
|
|
const exists = await client.query(
|
|
'SELECT id FROM service_pricing WHERE service_type = $1 AND category = $2',
|
|
[p.type, p.category]
|
|
);
|
|
|
|
if (exists.rows.length === 0) {
|
|
await client.query(`
|
|
INSERT INTO service_pricing (service_type, category, price, original_price, currency, description)
|
|
VALUES ($1, $2, $3, $4, 'CNY', $5)
|
|
`, [p.type, p.category, p.price, p.originalPrice, p.desc]);
|
|
console.log(` Created pricing: ${p.type} - ${p.category}`);
|
|
}
|
|
}
|
|
console.log(' Service pricing seeded');
|
|
}
|
|
|
|
// 运行种子脚本
|
|
seed();
|