diff --git a/files/database.sqlite b/files/database.sqlite new file mode 100644 index 0000000..e22ac7d Binary files /dev/null and b/files/database.sqlite differ diff --git a/src/commands/github.ts b/src/commands/github.ts index d13a98b..cef951b 100644 --- a/src/commands/github.ts +++ b/src/commands/github.ts @@ -23,8 +23,8 @@ new Command({ description: 'Issue numer/name, PR number/name or direct link to Github Issue or PR', type: ApplicationCommandOptionType.String, required: true, - run: (ctx) => { - return ctx.respond(search(ctx.value, ctx?.options?.[1]?.value as string || 'oven-sh/bun')); + run: async(ctx) => { + return ctx.respond(await search(ctx.value, ctx?.options?.[1]?.value as string || 'oven-sh/bun')); } }, { @@ -64,7 +64,7 @@ new Command({ const repositoryOwner = repositorySplit[0]; const repositoryName = repositorySplit[1]; - let issueOrPR = getIssueOrPR(parseInt(query), repository); + let issueOrPR = await getIssueOrPR(parseInt(query), repository); if (!issueOrPR) { const res = await fetch(`https://api.github.com/search/issues?q=${encodeURIComponent(query)}${encodeURIComponent(' repo:oven-sh/bun')}`); diff --git a/src/index.ts b/src/index.ts index 35bd1e6..512fc08 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,7 +12,7 @@ import { Commands } from './managers/CommandManager'; import registerCommands from './utils/registerCommands'; import { Option, OptionOptions } from './structures/Option'; import { AutocompleteContext } from './structures/contexts/AutocompleteContext'; -import { deleteIssue, deletePullRequest, fetchIssues, fetchPullRequests, issues, setIssue, setPullRequest } from './utils/githubUtils'; +import { deleteIssueOrPR, fetchIssues, fetchPullRequests, setIssue, setPullRequest } from './utils/githubUtils'; import createHmac from 'create-hmac'; await fetchIssues(); @@ -95,15 +95,14 @@ app.post('/github_webhook', bodyParse(), (c) => { const issueOrPr = c.req.parsedBody; if (issueOrPr.action !== 'deleted') { if (issueOrPr.issue) { - console.log('issue'); setIssue({ id: issueOrPr.issue.number, repository: issueOrPr.issue.repository_url.replace('https://api.github.com/repos/', ''), title: issueOrPr.issue.title, number: issueOrPr.issue.number, state: issueOrPr.issue.state, - created_at: new Date(issueOrPr.issue.created_at), - closed_at: new Date(issueOrPr.issue.closed_at), + created_at: issueOrPr.issue.created_at, + closed_at: issueOrPr.issue.closed_at, html_url: issueOrPr.issue.html_url, user_login: issueOrPr.issue.user.login, user_html_url: issueOrPr.issue.user.html_url, @@ -117,9 +116,9 @@ app.post('/github_webhook', bodyParse(), (c) => { title: issueOrPr.pull_request.title, number: issueOrPr.pull_request.number, state: issueOrPr.pull_request.state, - created_at: new Date(issueOrPr.pull_request.created_at), - closed_at: new Date(issueOrPr.pull_request.closed_at), - merged_at: new Date(issueOrPr.pull_request.merged_at), + created_at: issueOrPr.pull_request.created_at, + closed_at: issueOrPr.pull_request.closed_at, + merged_at: issueOrPr.pull_request.merged_at, html_url: issueOrPr.pull_request.html_url, user_login: issueOrPr.pull_request.user.login, user_html_url: issueOrPr.pull_request.user.html_url, @@ -127,10 +126,10 @@ app.post('/github_webhook', bodyParse(), (c) => { }) } } else { - if (issueOrPr.issue) deleteIssue( + if (issueOrPr.issue) deleteIssueOrPR( issueOrPr.issue.number, issueOrPr.issue.repository_url.replace('https://api.github.com/repos/', '') ); - else deletePullRequest( + else deleteIssueOrPR( issueOrPr.pull_request.number, issueOrPr.pull_request.html_url .replace('https://github.com/', '') diff --git a/src/structures/Option.ts b/src/structures/Option.ts index 9ed1739..e4683a9 100644 --- a/src/structures/Option.ts +++ b/src/structures/Option.ts @@ -23,7 +23,7 @@ export interface OptionOptions { maxValue?: number; minLength?: number; maxLength?: number; - run?: (ctx: AutocompleteContext) => Response; + run?: (ctx: AutocompleteContext) => Response | Promise; } export class Option { @@ -40,7 +40,7 @@ export class Option { public maxValue?: number; public minLength?: number; public maxLength?: number; - public run?: (ctx: AutocompleteContext) => Response; + public run?: (ctx: AutocompleteContext) => Response | Promise; public constructor(options: OptionOptions) { this.name = options.name; diff --git a/src/utils/githubUtils.ts b/src/utils/githubUtils.ts index 5c289d9..7e65e2e 100644 --- a/src/utils/githubUtils.ts +++ b/src/utils/githubUtils.ts @@ -5,6 +5,7 @@ import utilities from '../../files/utilities.toml'; import MiniSearch from 'minisearch'; import { Logger } from './Logger'; import { APIApplicationCommandOptionChoice } from 'discord-api-types/v10'; +import { Database } from 'bun:sqlite'; interface Issue { id: number; @@ -12,8 +13,8 @@ interface Issue { title: string; number: number; state: 'open' | 'closed', - created_at: Date; - closed_at: Date | null; + created_at: string; + closed_at: string | null; html_url: string; user_login: string; user_html_url: string; @@ -21,11 +22,19 @@ interface Issue { } interface PullRequest extends Issue { - merged_at: Date | null; + merged_at: string | null; } -export let issues: Issue[] = []; -export let pulls: PullRequest[] = []; +export const db = new Database('./files/database.sqlite'); +await db.exec('DROP TABLE IF EXISTS issuesandprs'); +await db.exec('CREATE TABLE issuesandprs (id INTEGER PRIMARY KEY, repository TEXT, title TEXT, number INTEGER, state TEXT, created_at TEXT, closed_at TEXT, merged_at TEXT, html_url TEXT, user_login TEXT, user_html_url TEXT, type TEXT)'); + +const addToDb = db.prepare( + 'INSERT INTO issuesandprs (repository, title, number, state, created_at, closed_at, merged_at, html_url, user_login, user_html_url, type) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)' +); + +export let issues: number = 0; +export let pulls: number = 0; export const fetchIssues = async() => { for await (const repository of utilities.github.repositories) { @@ -43,22 +52,24 @@ export const fetchIssues = async() => { for (const issue of res) { if ('pull_request' in issue) continue; - issues.push({ - id: issue.number, - repository: issue.repository_url.replace('https://api.github.com/repos/', ''), - title: issue.title, - number: issue.number, - state: issue.state, - created_at: new Date(issue.created_at), - closed_at: new Date(issue.closed_at), - html_url: issue.html_url, - user_login: issue.user.login, - user_html_url: issue.user.html_url, - type: '(ISSUE)' - }) + // @ts-expect-error it works + addToDb.run([ + issue.repository_url.replace('https://api.github.com/repos/', ''), + issue.title, + issue.number, + issue.state, + issue.created_at, + issue.closed_at, + null, + issue.html_url, + issue.user.login, + issue.user.html_url, + '(ISSUE)' + ]); + issues++; } - Logger.debug(`Fetching issues for ${repository} - ${issues.length} * ${page}`); + Logger.debug(`Fetching issues for ${repository} - ${issues} * ${page}`); page++; if (res.length === 0) { @@ -66,7 +77,8 @@ export const fetchIssues = async() => { } } - Logger.success(`Issues have been fetched for ${repository} - ${issues.length}`); + Logger.success(`Issues have been fetched for ${repository} - ${issues}`); + issues = 0; } } @@ -84,23 +96,24 @@ export const fetchPullRequests = async() => { })).json() as any; for (const pull of res) { - pulls.push({ - id: pull.number, - repository: pull.html_url.replace('https://github.com/', '').replace(`/pull/${pull.number}`, ''), - title: pull.title, - number: pull.number, - state: pull.state, - created_at: new Date(pull.created_at), - closed_at: new Date(pull.closed_at), - merged_at: new Date(pull.merged_at), - html_url: pull.html_url, - user_login: pull.user.login, - user_html_url: pull.user.html_url, - type: '(PR)', - }) + // @ts-expect-error it works + addToDb.run([ + pull.html_url.replace('https://github.com/', '').replace(`/pull/${pull.number}`, ''), + pull.title, + pull.number, + pull.state, + pull.created_at, + pull.closed_at, + pull.merged_at, + pull.html_url, + pull.user.login, + pull.user.html_url, + '(PR)' + ]); + pulls++; } - Logger.debug(`Fetching pull requests for ${repository} - ${pulls.length} * ${page}`); + Logger.debug(`Fetching pull requests for ${repository} - ${pulls} * ${page}`); page++; if (res.length === 0) { @@ -108,45 +121,71 @@ export const fetchPullRequests = async() => { } } - Logger.success(`Pull requests have been fetched for ${repository} - ${pulls.length}`); + Logger.success(`Pull requests have been fetched for ${repository} - ${pulls}`); + pulls = 0; } } -export const setIssue = (issue: Issue) => { - const exists = issues.findIndex(i => i.number === issue.number && i.repository === issue.repository); - if (exists >= 0) issues[exists] = issue; - else issues.push(issue); +export const setIssue = async(issue: Issue) => { + const exists = await db.prepare(`SELECT * FROM issuesandprs WHERE number = ${issue.number} AND repository = '${issue.repository}'`).get(); + if (typeof exists == 'object') { + db.exec(`UPDATE issuesandprs SET state = '${issue.state}', closed_at = '${issue.closed_at}', title = '${issue.title}' WHERE number = ${issue.number} AND repository = '${issue.repository}'`); + } else { + // @ts-expect-error + addToDb.run([ + issue.repository.replace('https://api.github.com/repos/', ''), + issue.title, + issue.number, + issue.state, + issue.created_at, + issue.closed_at, + null, + issue.html_url, + issue.user_login, + issue.user_html_url, + '(ISSUE)' + ]); + } } -export const setPullRequest = (pull: PullRequest) => { - const exists = pulls.findIndex(i => i.number === pull.number); - if (exists >= 0) pulls[exists] = pull; - else pulls.push(pull); +export const setPullRequest = async(pull: PullRequest) => { + const exists = await db.prepare(`SELECT * FROM issuesandprs WHERE number = ${pull.number} AND repository = '${pull.repository}'`).get(); + if (typeof exists == 'object') { + db.exec(`UPDATE issuesandprs SET state = '${pull.state}', closed_at = '${pull.closed_at}', merged_at = '${pull.merged_at}', title = '${pull.title}' WHERE number = ${pull.number} AND repository = '${pull.repository}'`); + } else { + // @ts-expect-error + addToDb.run([ + pull.repository.replace('https://api.github.com/repos/', ''), + pull.title, + pull.number, + pull.state, + pull.created_at, + pull.closed_at, + pull.merged_at, + pull.html_url, + pull.user_login, + pull.user_html_url, + '(ISSUE)' + ]); + } } -export const deleteIssue = (number: number, repository: string) => { - issues = issues.filter(i => i.number === number && i.repository === repository); +export const deleteIssueOrPR = (number: number, repository: string) => { + db.exec(`DELETE FROM issuesandprs WHERE repository = '${repository}' AND number = ${number}`); } -export const deletePullRequest = (number: number, repository: string) => { - pulls = pulls.filter(p => p.number === number && p.repository === repository); -} - -export const search = (query: string, repository: string): APIApplicationCommandOptionChoice[] => { +export const search = async(query: string, repository: string): Promise => { try { - const pullsFiltered = pulls.filter(pull => pull.repository === repository); - const issuesFiltered = issues.filter(issue => issue.repository === repository); + const arrayFiltered = await db.prepare(`SELECT * FROM issuesandprs WHERE repository = '${repository}'`).all(); if (!query) { - const array = [].concat(pullsFiltered.slice(0, 13), issuesFiltered.slice(0, 12)); + const array = arrayFiltered.slice(0, 25); return array.map((issueOrPr: Issue | PullRequest) => new Object({ - name: `${issueOrPr.type} ${issueOrPr.title.slice(0, 93)}`, + name: `${issueOrPr.type} ${issueOrPr.title.slice(0, 93).replace(/[^a-z0-9 ]/gi, '')}`, value: issueOrPr.number.toString() })) as APIApplicationCommandOptionChoice[] } - const array = [].concat(pullsFiltered, issuesFiltered); - const searcher = new MiniSearch({ fields: ['title', 'number', 'type'], storeFields: ['title', 'number', 'type'], @@ -156,7 +195,7 @@ export const search = (query: string, repository: string): APIApplicationCommand }, }); - searcher.addAll(array); + searcher.addAll(arrayFiltered); const result = searcher.search(query); @@ -169,7 +208,7 @@ export const search = (query: string, repository: string): APIApplicationCommand } } -export const getIssueOrPR = (number: number, repository: string): Issue | PullRequest => { - return issues.find(issue => issue.number === number && issue.repository === repository) || - pulls.find(pull => pull.number === number && pull.repository === repository); +export const getIssueOrPR = async(number: number, repository: string): Promise => { + const issueOrPR = await db.prepare(`SELECT * FROM issuesandprs WHERE repository = '${repository}' AND number = ${number}`).get(); + return issueOrPR; } \ No newline at end of file