Add Telegram notification integration for geofencing

Features:
- Multi-channel notifications (Email + Telegram)
- User-configurable notification settings per channel
- Telegram bot integration with rich messages, location pins, and inline buttons
- QR code generation for easy bot access (@myidbot support)
- Admin UI for notification settings management
- Test functionality for Telegram connection
- Comprehensive documentation

Implementation:
- lib/telegram-service.ts: Telegram API integration
- lib/notification-settings-db.ts: Database layer for user notification preferences
- lib/geofence-notifications.ts: Extended for parallel multi-channel delivery
- API routes for settings management and testing
- Admin UI with QR code display and step-by-step instructions
- Database table: UserNotificationSettings

Documentation:
- docs/telegram.md: Technical implementation guide
- docs/telegram-anleitung.md: User guide with @myidbot instructions
- docs/telegram-setup.md: Admin setup guide
- README.md: Updated NPM scripts section

Docker:
- Updated Dockerfile to copy public directory
- Added TELEGRAM_BOT_TOKEN environment variable
- Integrated notification settings initialization in db:init

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-04 14:54:19 +00:00
parent 17aaf130a8
commit 0d1dbeafda
18 changed files with 3200 additions and 21 deletions

View File

@@ -0,0 +1,157 @@
import Database from 'better-sqlite3';
import path from 'path';
const dbPath = path.join(process.cwd(), 'data', 'database.sqlite');
export interface UserNotificationSettings {
user_id: string;
email_enabled: boolean;
telegram_enabled: boolean;
telegram_chat_id: string | null;
created_at?: string;
updated_at?: string;
}
/**
* Get notification settings for a user
* Returns default settings if not found
*/
export function getUserNotificationSettings(
userId: string
): UserNotificationSettings {
const db = new Database(dbPath);
try {
const stmt = db.prepare(`
SELECT
user_id,
email_enabled,
telegram_enabled,
telegram_chat_id,
created_at,
updated_at
FROM UserNotificationSettings
WHERE user_id = ?
`);
const result = stmt.get(userId) as any;
// Return defaults if no settings found
if (!result) {
return {
user_id: userId,
email_enabled: true,
telegram_enabled: false,
telegram_chat_id: null,
};
}
// Convert SQLite integers to booleans
return {
user_id: result.user_id,
email_enabled: result.email_enabled === 1,
telegram_enabled: result.telegram_enabled === 1,
telegram_chat_id: result.telegram_chat_id,
created_at: result.created_at,
updated_at: result.updated_at,
};
} finally {
db.close();
}
}
/**
* Update notification settings for a user
*/
export function updateUserNotificationSettings(
userId: string,
settings: Partial<UserNotificationSettings>
): UserNotificationSettings {
const db = new Database(dbPath);
try {
// Check if settings exist
const existing = db
.prepare('SELECT user_id FROM UserNotificationSettings WHERE user_id = ?')
.get(userId);
if (existing) {
// Update existing
const updates: string[] = [];
const values: any[] = [];
if (settings.email_enabled !== undefined) {
updates.push('email_enabled = ?');
values.push(settings.email_enabled ? 1 : 0);
}
if (settings.telegram_enabled !== undefined) {
updates.push('telegram_enabled = ?');
values.push(settings.telegram_enabled ? 1 : 0);
}
if (settings.telegram_chat_id !== undefined) {
updates.push('telegram_chat_id = ?');
values.push(settings.telegram_chat_id);
}
updates.push('updated_at = datetime(\'now\')');
values.push(userId);
const stmt = db.prepare(`
UPDATE UserNotificationSettings
SET ${updates.join(', ')}
WHERE user_id = ?
`);
stmt.run(...values);
} else {
// Insert new
const stmt = db.prepare(`
INSERT INTO UserNotificationSettings (
user_id,
email_enabled,
telegram_enabled,
telegram_chat_id
) VALUES (?, ?, ?, ?)
`);
stmt.run(
userId,
settings.email_enabled !== undefined ? (settings.email_enabled ? 1 : 0) : 1,
settings.telegram_enabled !== undefined ? (settings.telegram_enabled ? 1 : 0) : 0,
settings.telegram_chat_id ?? null
);
}
return getUserNotificationSettings(userId);
} finally {
db.close();
}
}
/**
* Initialize notification settings table
*/
export function initNotificationSettingsTable(): void {
const db = new Database(dbPath);
try {
db.exec(`
CREATE TABLE IF NOT EXISTS UserNotificationSettings (
user_id TEXT PRIMARY KEY,
email_enabled INTEGER DEFAULT 1,
telegram_enabled INTEGER DEFAULT 0,
telegram_chat_id TEXT,
created_at TEXT DEFAULT (datetime('now')),
updated_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY (user_id) REFERENCES User(id) ON DELETE CASCADE,
CHECK (email_enabled IN (0, 1)),
CHECK (telegram_enabled IN (0, 1))
);
CREATE INDEX IF NOT EXISTS idx_user_notification_settings_user
ON UserNotificationSettings(user_id);
`);
console.log('✓ UserNotificationSettings table initialized');
} finally {
db.close();
}
}