1 2const CommentDB = { 3 dbName: 'CommentDB', 4 dbVersion: 1, 5 storeNames: { 6 reactions: 'reactions', 7 timestamps: 'timestamps', 8 }, 9 db: null, 10 11 async open() { 12 return new Promise((resolve, reject) => { 13 const request = indexedDB.open(this.dbName, this.dbVersion); 14 15 request.onupgradeneeded = (event) => { 16 const db = event.target.result; 17 18 if (!db.objectStoreNames.contains(this.storeNames.reactions)) { 19 const reactionsStore = db.createObjectStore(this.storeNames.reactions, { keyPath: 'comment_id' }); 20 // Create index on user_id for faster queries 21 reactionsStore.createIndex('reactor_id', 'reactor_id', { unique: false }); 22 } 23 24 if (!db.objectStoreNames.contains('timestamps')) { 25 db.createObjectStore('timestamps'); 26 } 27 }; 28 29 request.onsuccess = (event) => { 30 this.db = event.target.result; 31 resolve(this.db); 32 }; 33 34 request.onerror = (event) => reject(event.target.error); 35 }); 36 }, 37 38 async setTimestamp(key) { 39 await this.open(); 40 const tx = this.db.transaction(this.storeNames.timestamps, 'readwrite'); 41 const store = tx.objectStore(this.storeNames.timestamps); 42 store.put(Date.now(), key); 43 }, 44 45 async getTimestamp(key) { 46 await this.open(); 47 const tx = this.db.transaction(this.storeNames.timestamps, 'readonly'); 48 const store = tx.objectStore(this.storeNames.timestamps); 49 return new Promise((resolve) => { 50 const req = store.get(key); 51 req.onsuccess = () => resolve(req.result || 0); 52 req.onerror = () => resolve(0); 53 }); 54 }, 55 56 async saveToStore(storeName, items) { 57 await this.open(); 58 const tx = this.db.transaction(storeName, 'readwrite'); 59 const store = tx.objectStore(storeName); 60 items.forEach(item => store.put(item)); 61 }, 62 63 async getFromStore(storeName) { 64 await this.open(); 65 return new Promise((resolve, reject) => { 66 const tx = this.db.transaction(storeName, 'readonly'); 67 const store = tx.objectStore(storeName); 68 const req = store.getAll(); 69 70 req.onsuccess = () => resolve(req.result); 71 req.onerror = (e) => reject(e.target.error); 72 }); 73 }, 74 75 async getReactionsByUserId(userId) { 76 await this.open(); 77 78 return new Promise((resolve, reject) => { 79 const tx = this.db.transaction(this.storeNames.reactions, 'readonly'); 80 const store = tx.objectStore(this.storeNames.reactions); 81 const index = store.index('reactor_id'); 82 const req = index.getAll(userId.toString()); 83 84 req.onsuccess = () => resolve(req.result); 85 req.onerror = (e) => reject(e.target.error); 86 }); 87 }, 88 89 async getReactionByCommentAndUser(commentId, userId) { 90 await this.open(); 91 92 return new Promise((resolve, reject) => { 93 const tx = this.db.transaction(this.storeNames.reactions, 'readonly'); 94 const store = tx.objectStore(this.storeNames.reactions); 95 const req = store.get(commentId); 96 97 req.onsuccess = () => { 98 const reaction = req.result; 99 if (reaction && reaction.reactor_id === userId) { 100 resolve(reaction); 101 } else { 102 resolve(null); 103 } 104 }; 105 106 req.onerror = (e) => reject(e.target.error); 107 }); 108 }, 109 110 async fetchAndCacheMyReactions(userId, apiUrl = '/api/comments/my-reactions') { 111 try { 112 const timeoutMs = 1000 * 60 * 60; 113 const lastFetched = await this.getTimestamp('lastFetchReactions'); 114 const now = Date.now(); 115 116 if (now - lastFetched < timeoutMs) { 117 return await this.getFromStore(this.storeNames.reactions); 118 } 119 120 const response = await fetch(apiUrl); 121 const { data } = await response.json(); 122 123 const reactions = data.reactions || []; 124 125 await this.clearUserReactions(userId); 126 127 await this.saveToStore(this.storeNames.reactions, reactions); 128 129 await this.setTimestamp('lastFetchReactions'); 130 131 return reactions; 132 } catch (err) { 133 console.error('Error fetching lastFetchReactions:', err); 134 return []; 135 } 136 }, 137 138 async clearUserReactions(userId) { 139 await this.open(); 140 141 return new Promise((resolve, reject) => { 142 const tx = this.db.transaction(this.storeNames.reactions, 'readwrite'); 143 const store = tx.objectStore(this.storeNames.reactions); 144 const index = store.index('reactor_id'); 145 const req = index.getAllKeys(userId); 146 147 req.onsuccess = () => { 148 const commentIds = req.result; 149 150 // Delete all reactions for this user 151 commentIds.forEach(commentId => { 152 store.delete(commentId); 153 }); 154 155 resolve(); 156 }; 157 158 req.onerror = (e) => reject(e.target.error); 159 }); 160 }, 161 162 async getReactions(timeoutMs = 1000 * 60 * 60) { 163 const lastFetched = await this.getTimestamp('lastFetchReactions'); 164 const now = Date.now(); 165 166 if (now - lastFetched < timeoutMs) { 167 return await this.getFromStore(this.storeNames.reactions); 168 } 169 170 return await this.fetchAndCacheMyReactions(); 171 }, 172 173 async saveReaction(reactionData) { 174 await this.open(); 175 const tx = this.db.transaction(this.storeNames.reactions, 'readwrite'); 176 const store = tx.objectStore(this.storeNames.reactions); 177 store.put(reactionData); // This handles both create and update 178 }, 179 180 async deleteReaction(commentId) { 181 await this.open(); 182 const tx = this.db.transaction(this.storeNames.reactions, 'readwrite'); 183 const store = tx.objectStore(this.storeNames.reactions); 184 store.delete(commentId); 185 }, 186}; 187 188window.CommentDB = CommentDB;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.