Aethel Bot OSS repository! aethel.xyz
bot fun ai discord discord-bot aethel
at dev 5.6 kB view raw
1import pool from './pgClient'; 2import logger from './logger'; 3 4interface ReminderData { 5 reminder_id: string; 6 user_id: string; 7 user_tag: string; 8 channel_id: string; 9 guild_id: string | null; 10 message: string; 11 expires_at: Date; 12 created_at?: Date; 13 locale: string; 14 metadata?: { 15 source: string; 16 command_id?: string; 17 original_message_id?: string; 18 original_channel_id?: string; 19 message_url?: string; 20 }; 21} 22 23class DatabaseError extends Error { 24 public readonly isDatabaseError = true; 25 public readonly isUserFriendly: boolean; 26 public readonly userMessage: string; 27 28 constructor(message: string, userMessage?: string, isUserFriendly = false) { 29 super(message); 30 this.name = 'DatabaseError'; 31 this.userMessage = userMessage || 'A database error occurred. Please try again later.'; 32 this.isUserFriendly = isUserFriendly; 33 } 34} 35 36function createDatabaseError(error: unknown, operation: string): DatabaseError { 37 const errorCode = (error as { code?: string }).code; 38 const errorMessage = (error as { message?: string }).message || 'Unknown database error'; 39 40 logger.error(`Database error during ${operation}:`, error); 41 42 switch (errorCode) { 43 case 'ECONNREFUSED': 44 case 'ENOTFOUND': 45 return new DatabaseError( 46 errorMessage, 47 'Unable to connect to the database. Please try again later.', 48 true, 49 ); 50 case '42703': 51 return new DatabaseError( 52 errorMessage, 53 'Database schema error. Please contact support.', 54 true, 55 ); 56 case '23505': 57 return new DatabaseError(errorMessage, 'This reminder already exists.', true); 58 case '23502': 59 return new DatabaseError( 60 errorMessage, 61 'Missing required information. Please try again.', 62 true, 63 ); 64 case '23503': 65 return new DatabaseError(errorMessage, 'Invalid reference. Please try again.', true); 66 default: 67 return new DatabaseError( 68 errorMessage, 69 'A database error occurred. Please try again later.', 70 false, 71 ); 72 } 73} 74 75async function ensureUserRegistered( 76 userId: string, 77 userTag: string, 78 language = 'en', 79): Promise<void> { 80 const query = ` 81 SELECT ensure_user_registered($1, $2, $3) 82 `; 83 84 try { 85 await pool.query(query, [userId, userTag, language]); 86 } catch (error) { 87 throw createDatabaseError(error, 'user registration'); 88 } 89} 90 91async function saveReminder(reminderData: ReminderData): Promise<ReminderData> { 92 await ensureUserRegistered( 93 reminderData.user_id, 94 reminderData.user_tag, 95 reminderData.locale || 'en', 96 ); 97 98 const query = ` 99 INSERT INTO reminders ( 100 reminder_id, user_id, user_tag, channel_id, guild_id, 101 message, expires_at, metadata 102 ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) 103 RETURNING * 104 `; 105 106 const values = [ 107 reminderData.reminder_id, 108 reminderData.user_id, 109 reminderData.user_tag, 110 reminderData.channel_id, 111 reminderData.guild_id, 112 reminderData.message, 113 reminderData.expires_at, 114 reminderData.metadata || {}, 115 ]; 116 117 try { 118 const result = await pool.query<ReminderData>(query, values); 119 return result.rows[0]; 120 } catch (error) { 121 throw createDatabaseError(error, 'saving reminder'); 122 } 123} 124 125async function completeReminder(reminderId: string) { 126 const query = ` 127 UPDATE reminders 128 SET is_completed = TRUE, completed_at = CURRENT_TIMESTAMP 129 WHERE reminder_id = $1 130 RETURNING * 131 `; 132 133 try { 134 const result = await pool.query(query, [reminderId]); 135 return result.rows[0]; 136 } catch (error) { 137 throw createDatabaseError(error, 'completing reminder'); 138 } 139} 140 141async function getActiveReminders() { 142 const query = ` 143 SELECT * FROM reminders 144 WHERE is_completed = FALSE 145 AND expires_at > CURRENT_TIMESTAMP 146 ORDER BY expires_at ASC 147 `; 148 149 try { 150 const result = await pool.query(query); 151 return result.rows; 152 } catch (error) { 153 throw createDatabaseError(error, 'fetching active reminders'); 154 } 155} 156 157async function getReminder(reminderId: string) { 158 const query = 'SELECT * FROM reminders WHERE reminder_id = $1'; 159 160 try { 161 const result = await pool.query(query, [reminderId]); 162 return result.rows[0] || null; 163 } catch (error) { 164 throw createDatabaseError(error, 'fetching reminder'); 165 } 166} 167 168async function getUserReminders(userId: string) { 169 const query = ` 170 SELECT * FROM reminders 171 WHERE user_id = $1 172 ORDER BY created_at DESC 173 LIMIT 50 174 `; 175 176 try { 177 const result = await pool.query(query, [userId]); 178 return result.rows; 179 } catch (error) { 180 throw createDatabaseError(error, 'fetching user reminders'); 181 } 182} 183 184async function cleanupReminders(days = 30) { 185 const query = ` 186 DELETE FROM reminders 187 WHERE is_completed = TRUE 188 AND completed_at < CURRENT_TIMESTAMP - ($1 * INTERVAL '1 day') 189 RETURNING * 190 `; 191 192 try { 193 const result = await pool.query(query, [days]); 194 return result.rowCount; 195 } catch (error) { 196 throw createDatabaseError(error, 'cleaning up reminders'); 197 } 198} 199 200async function clearCompletedReminders(userId: string) { 201 const query = ` 202 DELETE FROM reminders 203 WHERE user_id = $1 204 AND is_completed = TRUE 205 RETURNING * 206 `; 207 208 try { 209 const result = await pool.query(query, [userId]); 210 return result.rowCount; 211 } catch (error) { 212 throw createDatabaseError(error, 'clearing completed reminders'); 213 } 214} 215 216export { 217 saveReminder, 218 completeReminder, 219 getActiveReminders, 220 getReminder, 221 getUserReminders, 222 cleanupReminders, 223 clearCompletedReminders, 224 ensureUserRegistered, 225 DatabaseError, 226};