v0.3.5: restore full push notification system (14 channels)

Restored from v0.2.4:
- notifiers/: 14 push channels (bark/serverchan/telegram/lark/webhook/wechat/discord/smtp/...)
- push-user.service.ts: multi-user push config linked to cloud_configs.promotion_account
- notification.service.ts: full dispatcher with per-config + global fallback

New integrations:
- cloud.service.ts: notifyConfigEvent on save_success/cookie_expire/save_fail
- admin.routes.ts: 7 new API endpoints for push users, notify providers, channel test
- database.ts: migration for cloud_configs.notify_config column

How it works:
- Configure push channels in /admin/system-configs (global_notify_config JSON)
- Or per-cloud: link push_users.account = cloud_configs.promotion_account
- Notify events: save_success (green), cookie_expire (red), save_fail >=3 consecutive (yellow)
This commit is contained in:
2026-05-17 05:15:26 +08:00
parent 29e0fcbd43
commit 64b00661a2
22 changed files with 985 additions and 68 deletions

View File

@@ -8,6 +8,8 @@ import { getCloudConfigs, getCloudConfigById, saveCloudConfig, deleteCloudConfig
import { dailyCheckIn, skipCheckin, getCheckinSummary, getDrivesForCheckin } from '../cloud/checkin.service';
import { getAllCloudTypes } from '../cloud/cloud-types.service';
import { login, authMiddleware, verifyToken, changePassword } from '../admin/auth.service';
import { getAllPushUsers, upsertPushUser, updatePushUser, deletePushUser } from '../cloud/push-user.service';
import { getAllNotifierParams, testChannel, saveConfigNotifySettings, getConfigNotifySettingsJSON } from '../cloud/notification.service';
import { getStats } from '../admin/stats.service';
import { getAllSystemConfigs, updateSystemConfig, updateSystemConfigs, getSystemConfig } from '../admin/system-config.service';
import { getDb } from '../database/database';
@@ -672,4 +674,106 @@ router.post('/admin/update-pansou', async (_req: Request, res: Response) => {
}
});
// ======================== Notification / Push Users ========================
/** GET /api/admin/cloud-configs/:id/notify */
router.get('/admin/cloud-configs/:id/notify', (req: Request, res: Response) => {
try {
const id = parseInt(req.params.id as string);
const settings = getConfigNotifySettingsJSON(id);
res.json(settings);
} catch (err: any) {
res.status(400).json({ error: err.message || 'Failed to get notification settings' });
}
});
/** PUT /api/admin/cloud-configs/:id/notify */
router.put('/admin/cloud-configs/:id/notify', (req: Request, res: Response) => {
try {
const id = parseInt(req.params.id as string);
const settings = req.body;
saveConfigNotifySettings(id, settings);
res.json({ success: true, message: 'Push config saved' });
} catch (err: any) {
res.status(400).json({ error: err.message || 'Failed to save notification settings' });
}
});
/** POST /api/admin/notify/test */
router.post('/admin/notify/test', async (req: Request, res: Response) => {
try {
const { channelType, account, configId, params } = req.body;
const ctx = account || (configId ? String(configId) : undefined);
const result = await testChannel(channelType, ctx, params);
res.json(result);
} catch (err: any) {
res.json({ success: false, message: err.message || 'Test send failed' });
}
});
/** GET /api/admin/notify/providers */
router.get('/admin/notify/providers', (_req: Request, res: Response) => {
try {
const providers = getAllNotifierParams();
res.json(providers);
} catch (err: any) {
res.status(500).json({ error: err.message || 'Failed to get providers' });
}
});
/** GET /api/admin/push-users */
router.get('/admin/push-users', (_req: Request, res: Response) => {
try {
const users = getAllPushUsers();
const parsed = users.map(u => ({
...u,
notify_config: (() => { try { return JSON.parse(u.notify_config); } catch { return {}; } })(),
}));
res.json(parsed);
} catch (err: any) {
res.status(500).json({ error: err.message || 'Failed to list push users' });
}
});
/** POST /api/admin/push-users */
router.post('/admin/push-users', (req: Request, res: Response) => {
try {
const { account, notify_config } = req.body;
if (!account) return res.status(400).json({ error: 'account is required' });
const configStr = typeof notify_config === 'string' ? notify_config : JSON.stringify(notify_config || {});
const user = upsertPushUser(account, configStr);
res.json({ ...user, notify_config: JSON.parse(user!.notify_config) });
} catch (err: any) {
res.status(400).json({ error: err.message || 'Failed to save push user' });
}
});
/** PUT /api/admin/push-users/:id */
router.put('/admin/push-users/:id', (req: Request, res: Response) => {
try {
const id = parseInt(req.params.id as string);
const { account, notify_config } = req.body;
if (!account) return res.status(400).json({ error: 'account is required' });
const configStr = typeof notify_config === 'string' ? notify_config : JSON.stringify(notify_config || {});
const user = updatePushUser(id, account, configStr);
res.json({ ...user, notify_config: JSON.parse(user!.notify_config) });
} catch (err: any) {
res.status(400).json({ error: err.message || 'Failed to update push user' });
}
});
/** DELETE /api/admin/push-users/:id */
router.delete('/admin/push-users/:id', (req: Request, res: Response) => {
try {
const id = parseInt(req.params.id as string);
const ok = deletePushUser(id);
if (ok) res.json({ success: true });
else res.status(404).json({ error: 'Push user not found' });
} catch (err: any) {
res.status(400).json({ error: err.message || 'Failed to delete push user' });
}
});
export default router;