()
| 203 | const newRules: { [key: string]: ShiftRule } = {}; |
| 204 | for (const rule of rules) { |
| 205 | newRules[String(rule.source_id)] = { |
| 206 | target_id: rule.target_id, |
| 207 | options: JSON.parse(rule.options || "[]"), |
| 208 | target_type: rule.target_type, |
| 209 | paused: rule.paused === 1, |
| 210 | created_at: rule.created_at, |
| 211 | filters: JSON.parse(rule.filters || "[]"), |
| 212 | }; |
| 213 | } |
| 214 | |
| 215 | // 转换统计 |
| 216 | const newStats: { [date: string]: { [sourceId: string]: any } } = {}; |
| 217 | for (const stat of stats) { |
| 218 | const parts = stat.stats_key.split("."); |
| 219 | if (parts.length >= 4) { |
| 220 | const date = parts[3]; |
| 221 | const sourceId = parts[2]; |
| 222 | if (!newStats[date]) newStats[date] = {}; |
| 223 | newStats[date][sourceId] = JSON.parse(stat.stats_data); |
| 224 | } |
| 225 | } |
| 226 | |
| 227 | // 创建 lowdb 数据库 |
| 228 | const migratedData: ShiftDatabaseV2 = { |
| 229 | version: "2.0.0", |
| 230 | rules: newRules, |
| 231 | stats: newStats, |
| 232 | backups: {}, |
| 233 | config: { |
| 234 | autoCleanOldStats: true, |
| 235 | statsRetentionDays: 30, |
| 236 | enableChainForwarding: true, |
| 237 | useLowdb: true, |
| 238 | }, |
| 239 | }; |
| 240 | |
| 241 | lowdb = await JSONFilePreset<ShiftDatabaseV2>(lowdbPath, migratedData); |
| 242 | await lowdb.write(); |
| 243 | |
| 244 | // 备份旧数据库 |
| 245 | const backupPath = sqlitePath + `.backup.${Date.now()}`; |
| 246 | fs.renameSync(sqlitePath, backupPath); |
| 247 | |
| 248 | sqliteDb.close(); |
| 249 | sqliteDb = null; |
| 250 | useLowdb = true; |
| 251 | |
| 252 | console.log( |
| 253 | `[SHIFT] 成功迁移 ${rules.length} 条规则,备份至: ${backupPath}` |
| 254 | ); |
| 255 | return true; |
| 256 | } catch (error) { |
| 257 | console.error("[SHIFT] 迁移失败:", error); |
| 258 | return false; |
| 259 | } |
| 260 | } |
| 261 | |
| 262 | // Rule interface |
no test coverage detected