feat: sqlite instead memory cache

This commit is contained in:
xHyroM 2022-07-13 17:49:09 +02:00
parent 73ad04a894
commit 6511816229
5 changed files with 113 additions and 75 deletions

BIN
files/database.sqlite Normal file

Binary file not shown.

View file

@ -23,8 +23,8 @@ new Command({
description: 'Issue numer/name, PR number/name or direct link to Github Issue or PR', description: 'Issue numer/name, PR number/name or direct link to Github Issue or PR',
type: ApplicationCommandOptionType.String, type: ApplicationCommandOptionType.String,
required: true, required: true,
run: (ctx) => { run: async(ctx) => {
return ctx.respond(search(ctx.value, ctx?.options?.[1]?.value as string || 'oven-sh/bun')); 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 repositoryOwner = repositorySplit[0];
const repositoryName = repositorySplit[1]; const repositoryName = repositorySplit[1];
let issueOrPR = getIssueOrPR(parseInt(query), repository); let issueOrPR = await getIssueOrPR(parseInt(query), repository);
if (!issueOrPR) { if (!issueOrPR) {
const res = await fetch(`https://api.github.com/search/issues?q=${encodeURIComponent(query)}${encodeURIComponent(' repo:oven-sh/bun')}`); const res = await fetch(`https://api.github.com/search/issues?q=${encodeURIComponent(query)}${encodeURIComponent(' repo:oven-sh/bun')}`);

View file

@ -12,7 +12,7 @@ import { Commands } from './managers/CommandManager';
import registerCommands from './utils/registerCommands'; import registerCommands from './utils/registerCommands';
import { Option, OptionOptions } from './structures/Option'; import { Option, OptionOptions } from './structures/Option';
import { AutocompleteContext } from './structures/contexts/AutocompleteContext'; 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'; import createHmac from 'create-hmac';
await fetchIssues(); await fetchIssues();
@ -95,15 +95,14 @@ app.post('/github_webhook', bodyParse(), (c) => {
const issueOrPr = c.req.parsedBody; const issueOrPr = c.req.parsedBody;
if (issueOrPr.action !== 'deleted') { if (issueOrPr.action !== 'deleted') {
if (issueOrPr.issue) { if (issueOrPr.issue) {
console.log('issue');
setIssue({ setIssue({
id: issueOrPr.issue.number, id: issueOrPr.issue.number,
repository: issueOrPr.issue.repository_url.replace('https://api.github.com/repos/', ''), repository: issueOrPr.issue.repository_url.replace('https://api.github.com/repos/', ''),
title: issueOrPr.issue.title, title: issueOrPr.issue.title,
number: issueOrPr.issue.number, number: issueOrPr.issue.number,
state: issueOrPr.issue.state, state: issueOrPr.issue.state,
created_at: new Date(issueOrPr.issue.created_at), created_at: issueOrPr.issue.created_at,
closed_at: new Date(issueOrPr.issue.closed_at), closed_at: issueOrPr.issue.closed_at,
html_url: issueOrPr.issue.html_url, html_url: issueOrPr.issue.html_url,
user_login: issueOrPr.issue.user.login, user_login: issueOrPr.issue.user.login,
user_html_url: issueOrPr.issue.user.html_url, user_html_url: issueOrPr.issue.user.html_url,
@ -117,9 +116,9 @@ app.post('/github_webhook', bodyParse(), (c) => {
title: issueOrPr.pull_request.title, title: issueOrPr.pull_request.title,
number: issueOrPr.pull_request.number, number: issueOrPr.pull_request.number,
state: issueOrPr.pull_request.state, state: issueOrPr.pull_request.state,
created_at: new Date(issueOrPr.pull_request.created_at), created_at: issueOrPr.pull_request.created_at,
closed_at: new Date(issueOrPr.pull_request.closed_at), closed_at: issueOrPr.pull_request.closed_at,
merged_at: new Date(issueOrPr.pull_request.merged_at), merged_at: issueOrPr.pull_request.merged_at,
html_url: issueOrPr.pull_request.html_url, html_url: issueOrPr.pull_request.html_url,
user_login: issueOrPr.pull_request.user.login, user_login: issueOrPr.pull_request.user.login,
user_html_url: issueOrPr.pull_request.user.html_url, user_html_url: issueOrPr.pull_request.user.html_url,
@ -127,10 +126,10 @@ app.post('/github_webhook', bodyParse(), (c) => {
}) })
} }
} else { } else {
if (issueOrPr.issue) deleteIssue( if (issueOrPr.issue) deleteIssueOrPR(
issueOrPr.issue.number, issueOrPr.issue.repository_url.replace('https://api.github.com/repos/', '') issueOrPr.issue.number, issueOrPr.issue.repository_url.replace('https://api.github.com/repos/', '')
); );
else deletePullRequest( else deleteIssueOrPR(
issueOrPr.pull_request.number, issueOrPr.pull_request.number,
issueOrPr.pull_request.html_url issueOrPr.pull_request.html_url
.replace('https://github.com/', '') .replace('https://github.com/', '')

View file

@ -23,7 +23,7 @@ export interface OptionOptions {
maxValue?: number; maxValue?: number;
minLength?: number; minLength?: number;
maxLength?: number; maxLength?: number;
run?: (ctx: AutocompleteContext) => Response; run?: (ctx: AutocompleteContext) => Response | Promise<Response>;
} }
export class Option { export class Option {
@ -40,7 +40,7 @@ export class Option {
public maxValue?: number; public maxValue?: number;
public minLength?: number; public minLength?: number;
public maxLength?: number; public maxLength?: number;
public run?: (ctx: AutocompleteContext) => Response; public run?: (ctx: AutocompleteContext) => Response | Promise<Response>;
public constructor(options: OptionOptions) { public constructor(options: OptionOptions) {
this.name = options.name; this.name = options.name;

View file

@ -5,6 +5,7 @@ import utilities from '../../files/utilities.toml';
import MiniSearch from 'minisearch'; import MiniSearch from 'minisearch';
import { Logger } from './Logger'; import { Logger } from './Logger';
import { APIApplicationCommandOptionChoice } from 'discord-api-types/v10'; import { APIApplicationCommandOptionChoice } from 'discord-api-types/v10';
import { Database } from 'bun:sqlite';
interface Issue { interface Issue {
id: number; id: number;
@ -12,8 +13,8 @@ interface Issue {
title: string; title: string;
number: number; number: number;
state: 'open' | 'closed', state: 'open' | 'closed',
created_at: Date; created_at: string;
closed_at: Date | null; closed_at: string | null;
html_url: string; html_url: string;
user_login: string; user_login: string;
user_html_url: string; user_html_url: string;
@ -21,11 +22,19 @@ interface Issue {
} }
interface PullRequest extends Issue { interface PullRequest extends Issue {
merged_at: Date | null; merged_at: string | null;
} }
export let issues: Issue[] = []; export const db = new Database('./files/database.sqlite');
export let pulls: PullRequest[] = []; 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() => { export const fetchIssues = async() => {
for await (const repository of utilities.github.repositories) { for await (const repository of utilities.github.repositories) {
@ -43,22 +52,24 @@ export const fetchIssues = async() => {
for (const issue of res) { for (const issue of res) {
if ('pull_request' in issue) continue; if ('pull_request' in issue) continue;
issues.push({ // @ts-expect-error it works
id: issue.number, addToDb.run([
repository: issue.repository_url.replace('https://api.github.com/repos/', ''), issue.repository_url.replace('https://api.github.com/repos/', ''),
title: issue.title, issue.title,
number: issue.number, issue.number,
state: issue.state, issue.state,
created_at: new Date(issue.created_at), issue.created_at,
closed_at: new Date(issue.closed_at), issue.closed_at,
html_url: issue.html_url, null,
user_login: issue.user.login, issue.html_url,
user_html_url: issue.user.html_url, issue.user.login,
type: '(ISSUE)' issue.user.html_url,
}) '(ISSUE)'
]);
issues++;
} }
Logger.debug(`Fetching issues for ${repository} - ${issues.length} * ${page}`); Logger.debug(`Fetching issues for ${repository} - ${issues} * ${page}`);
page++; page++;
if (res.length === 0) { 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; })).json() as any;
for (const pull of res) { for (const pull of res) {
pulls.push({ // @ts-expect-error it works
id: pull.number, addToDb.run([
repository: pull.html_url.replace('https://github.com/', '').replace(`/pull/${pull.number}`, ''), pull.html_url.replace('https://github.com/', '').replace(`/pull/${pull.number}`, ''),
title: pull.title, pull.title,
number: pull.number, pull.number,
state: pull.state, pull.state,
created_at: new Date(pull.created_at), pull.created_at,
closed_at: new Date(pull.closed_at), pull.closed_at,
merged_at: new Date(pull.merged_at), pull.merged_at,
html_url: pull.html_url, pull.html_url,
user_login: pull.user.login, pull.user.login,
user_html_url: pull.user.html_url, pull.user.html_url,
type: '(PR)', '(PR)'
}) ]);
pulls++;
} }
Logger.debug(`Fetching pull requests for ${repository} - ${pulls.length} * ${page}`); Logger.debug(`Fetching pull requests for ${repository} - ${pulls} * ${page}`);
page++; page++;
if (res.length === 0) { 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) => { export const setIssue = async(issue: Issue) => {
const exists = issues.findIndex(i => i.number === issue.number && i.repository === issue.repository); const exists = await db.prepare(`SELECT * FROM issuesandprs WHERE number = ${issue.number} AND repository = '${issue.repository}'`).get();
if (exists >= 0) issues[exists] = issue; if (typeof exists == 'object') {
else issues.push(issue); 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) => { export const setPullRequest = async(pull: PullRequest) => {
const exists = pulls.findIndex(i => i.number === pull.number); const exists = await db.prepare(`SELECT * FROM issuesandprs WHERE number = ${pull.number} AND repository = '${pull.repository}'`).get();
if (exists >= 0) pulls[exists] = pull; if (typeof exists == 'object') {
else pulls.push(pull); 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) => { export const deleteIssueOrPR = (number: number, repository: string) => {
issues = issues.filter(i => i.number === number && i.repository === repository); db.exec(`DELETE FROM issuesandprs WHERE repository = '${repository}' AND number = ${number}`);
} }
export const deletePullRequest = (number: number, repository: string) => { export const search = async(query: string, repository: string): Promise<APIApplicationCommandOptionChoice[]> => {
pulls = pulls.filter(p => p.number === number && p.repository === repository);
}
export const search = (query: string, repository: string): APIApplicationCommandOptionChoice[] => {
try { try {
const pullsFiltered = pulls.filter(pull => pull.repository === repository); const arrayFiltered = await db.prepare(`SELECT * FROM issuesandprs WHERE repository = '${repository}'`).all();
const issuesFiltered = issues.filter(issue => issue.repository === repository);
if (!query) { 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({ 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() value: issueOrPr.number.toString()
})) as APIApplicationCommandOptionChoice[] })) as APIApplicationCommandOptionChoice[]
} }
const array = [].concat(pullsFiltered, issuesFiltered);
const searcher = new MiniSearch({ const searcher = new MiniSearch({
fields: ['title', 'number', 'type'], fields: ['title', 'number', 'type'],
storeFields: ['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); 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 => { export const getIssueOrPR = async(number: number, repository: string): Promise<Issue | PullRequest> => {
return issues.find(issue => issue.number === number && issue.repository === repository) || const issueOrPR = await db.prepare(`SELECT * FROM issuesandprs WHERE repository = '${repository}' AND number = ${number}`).get();
pulls.find(pull => pull.number === number && pull.repository === repository); return issueOrPR;
} }