index.js (8383B)
1 import * as Sentry from '@sentry/node'; 2 import fs from 'fs'; 3 import path from 'path'; 4 import dotenv from 'dotenv'; 5 import middie from '@fastify/middie'; 6 import Fastify from 'fastify'; 7 import Database from 'better-sqlite3'; 8 9 import { App } from 'octokit'; 10 import { createNodeMiddleware } from '@octokit/webhooks'; 11 import { CronJob } from 'cron'; 12 13 import { getNumberOfDays } from './tools/numberofdays.js'; 14 15 import { unlabeled } from './webhooks/unlabeled.js'; 16 import { opened } from './webhooks/opened.js'; 17 import { closed } from './webhooks/closed.js'; 18 import { labeled } from './webhooks/labeled.js'; 19 20 // Load environment variables from .env file 21 dotenv.config(); 22 23 // Set configured values 24 const sentryDsn = process.env.SENTRY; 25 const appId = process.env.APP_ID; 26 const installationId = process.env.INSTALLATION_ID; 27 const privateKey = fs.readFileSync( 28 path.resolve(process.env.PRIVATE_KEY_PATH), 29 'utf8' 30 ); 31 const secret = process.env.WEBHOOK_SECRET; 32 export const numberOfDays = 3; 33 34 Sentry.init({ 35 dsn: sentryDsn, 36 // Tracing 37 tracesSampleRate: 1.0, // Capture 100% of the transactions 38 // Enable logs to be sent to Sentry 39 enableLogs: true, 40 }); 41 42 // Database 43 44 export const db = new Database(process.env.DB_PATH); 45 46 /*db.exec(` 47 CREATE TABLE LIST ( 48 username TEXT NOT NULL, 49 prnumber TEXT NOT NULL, 50 time TEXT NOT NULL, 51 repoowner TEXT NOT NULL, 52 repo TEXT NOT NULL 53 ); 54 `);*/ 55 56 // Create an authenticated Octokit client authenticated as a GitHub App 57 const app = new App({ 58 appId, 59 privateKey, 60 webhooks: { 61 secret, 62 }, 63 }); 64 65 const appOctokit = await app.getInstallationOctokit(installationId); 66 67 /* 68 saving this for incase 69 70 const installationOctokit = new Octokit({ 71 authStrategy: createAppAuth, 72 auth: { 73 appId, 74 privateKey, 75 installationId, 76 }, 77 }); 78 */ 79 80 // Get & log the authenticated app's name 81 const { data } = await app.octokit.request('/app'); 82 83 // https://github.com/octokit/core.js#logging 84 app.octokit.log.debug(`Authenticated as '${data.name}'`); 85 86 // Check if a low priority pull request has been in the database for over 3 days 87 let job = new CronJob( 88 '0 * * * *', // cronTime 0 * * * * 89 async function () { 90 let date = new Date(); 91 try { 92 let res = await db.prepare(`SELECT * FROM LIST`).all(); 93 let resJson = JSON.stringify(res); 94 if (resJson !== '[]') { 95 let parsed = JSON.parse(resJson); 96 for (let i in parsed) { 97 if (getNumberOfDays(parsed[i].time, date) >= numberOfDays) { 98 await db 99 .prepare(`DELETE FROM LIST WHERE time = ?`) 100 .run(parsed[i].time); 101 await appOctokit.request( 102 'DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{name}', 103 { 104 owner: parsed[i].repoowner, 105 repo: parsed[i].repo, 106 issue_number: parsed[i].prnumber, 107 name: 'status: low priority', 108 } 109 ); 110 console.log( 111 `Removed #${parsed[i].prnumber} from the database since it has existed for 3 or more days.` 112 ); 113 } 114 } 115 } else if (resJson === '[]') { 116 return; 117 } 118 } catch (error) { 119 Sentry.captureException(error); 120 fastify.log.error(error); 121 if (error.response) { 122 console.error( 123 `Error! Status: ${error.response.status}. Message: ${error.response.data.message}` 124 ); 125 } else { 126 console.error(error); 127 } 128 } 129 }, // onTick 130 null, // onComplete 131 true, // start 132 'Europe/Bucharest' // timeZone 133 ); 134 135 app.webhooks.on('pull_request.opened', async ({ payload }) => { 136 try { 137 await opened( 138 appOctokit, 139 payload.repository.owner.login, 140 payload.repository.name, 141 payload.repository.full_name, 142 payload.pull_request.number, 143 payload.pull_request.updated_at, 144 payload.pull_request.user.login, 145 payload.pull_request.draft 146 ); 147 } catch (error) { 148 Sentry.captureException(error); 149 fastify.log.error(error); 150 if (error.response) { 151 console.error( 152 `Error! Status: ${error.response.status}. Message: ${error.response.data.message}` 153 ); 154 } else { 155 console.error(error); 156 } 157 } 158 }); 159 160 // https://github.com/octokit/webhooks.js/?tab=readme-ov-file#webhook-events 161 app.webhooks.on('pull_request.closed', async ({ payload }) => { 162 console.log( 163 `Received a closed pull request event for #${payload.pull_request.number} on https://github.com/${payload.repository.full_name}` 164 ); 165 try { 166 await closed( 167 appOctokit, 168 payload.pull_request.merged, 169 payload.repository.owner.login, 170 payload.repository.name, 171 payload.repository.full_name, 172 payload.pull_request.number, 173 payload.pull_request.user.login, 174 payload.sender.login 175 ); 176 } catch (error) { 177 Sentry.captureException(error); 178 fastify.log.error(error); 179 if (error.response) { 180 console.error( 181 `Error! Status: ${error.response.status}. Message: ${error.response.data.message}` 182 ); 183 } else { 184 console.error(error); 185 } 186 } 187 }); 188 189 // Label system 190 app.webhooks.on('pull_request.labeled', async ({ payload }) => { 191 try { 192 await labeled( 193 appOctokit, 194 payload.label.name, 195 payload.repository.owner.login, 196 payload.repository.name, 197 payload.repository.full_name, 198 payload.pull_request.number, 199 payload.pull_request.updated_at, 200 payload.pull_request.user.login 201 ); 202 } catch (error) { 203 Sentry.captureException(error); 204 fastify.log.error(error); 205 if (error.response) { 206 console.error( 207 `Error! Status: ${error.response.status}. Message: ${error.response.data.message}` 208 ); 209 } else { 210 console.error(error); 211 } 212 } 213 }); 214 215 app.webhooks.on('pull_request.unlabeled', async ({ payload }) => { 216 try { 217 await unlabeled( 218 payload.label.name, 219 payload.pull_request.user.login, 220 payload.pull_request.number, 221 payload.repository.full_name 222 ); 223 } catch (error) { 224 Sentry.captureException(error); 225 fastify.log.error(error); 226 if (error.response) { 227 console.error( 228 `Error! Status: ${error.response.status}. Message: ${error.response.data.message}` 229 ); 230 } else { 231 console.error(error); 232 } 233 } 234 }); 235 236 // Handle errors 237 app.webhooks.onError((error) => { 238 Sentry.captureException(error); 239 fastify.log.error(error); 240 if (error.name === 'AggregateError') { 241 // Log Secret verification errors 242 console.log(`Error processing request: ${error.event}`); 243 } else { 244 console.log(error); 245 } 246 }); 247 248 // Launch a web server to listen for GitHub webhooks 249 const port = process.env.PORT || 3000; 250 const host = process.env.NODE_ENV === 'production' ? '0.0.0.0' : 'localhost'; 251 const webhookPath = '/api/webhook'; 252 const localWebhookUrl = `http://${host}:${port}${webhookPath}`; 253 254 // https://github.com/octokit/webhooks.js/#createnodemiddleware 255 const middleware = createNodeMiddleware(app.webhooks, { path: webhookPath }); 256 257 const fastify = Fastify({ 258 logger: false, 259 }); 260 await fastify.register(middie); 261 fastify.use(middleware); 262 263 await fastify.listen({ port, host }); 264 265 console.log(`Server is listening for events at: ${localWebhookUrl}`); 266 console.log('Press Ctrl + C to quit.'); 267 268 /* 269 Standard node http server (use "import http from 'http' if you want to use this instead of Fastify") 270 271 http.createServer(middleware).listen(port, () => { 272 console.log(`Server is listening for events at: ${localWebhookUrl}`) 273 console.log('Press Ctrl + C to quit.') 274 }) 275 */