1
0
mirror of https://github.com/spacebarchat/spacebarchat.git synced 2024-11-22 10:22:32 +01:00
spacebarchat/scripts/notion/GithubNotionSync.js
2021-08-16 17:41:43 +02:00

237 lines
5.9 KiB
JavaScript

require("missing-native-js-functions");
const fetch = require("node-fetch");
const { Client } = require("@notionhq/client");
const bodyParser = require("body-parser");
const express = require("express");
const app = express();
const PRIORITIES = ["low priority", "medium priority", "high priority"];
class GithubNotionSync {
constructor({ github, notion }) {
this.githubAuth = github.token;
this.githubWebhookSecret = github.webhookSecret;
this.notionAuth = notion.token;
this.databaseID = notion.database_id;
this.org = github.organization;
this.notion = new Client({ auth: this.notionAuth });
this.urls = {
base: "https://api.github.com/",
};
}
async init() {
this.allNotionPages = await this.getAllNotionPages();
this.repos = await this.getAllIssueUrls();
app.use(bodyParser({}));
app.post("/github", this.handleWebhook.bind(this));
app.listen(3010, () => {
console.log("Github <-> Notion sync listening on :3010");
});
}
async handleWebhook(req, res) {
const { hook, issue } = req.body;
await this.addItemToDb(GithubNotionSync.convertIssue(issue));
res.sendStatus(200);
}
async execute() {
await this.init();
let issues = 0;
for (let repo of this.repos) {
for (let issue of await this.getAllIssuesPerRepo(repo)) {
this.addItemToDb(issue);
issues++;
}
}
return issues;
}
/**
* @returns array of urls in the following form:
* https://api.github.com/repos/${this.org}/${repo_name}/issues
*/
async getAllIssueUrls() {
let repos = await fetch(`${this.urls.base}orgs/${this.org}/repos`, {
headers: {
Authorization: `token ${this.githubAuth}`,
"User-Agent": this.org,
},
}).then((r) => r.json());
return repos.map((repo) => `${this.urls.base}repos/${this.org}/${repo.name}/issues`);
}
/**
* @param repoIssueUrl element of array returned by `getAllIssueUrls()`
* @returns array of issues for the given repo in the following json form:
* ```
* {
* url: 'https://api.github.com/repos/fosscord/fosscord-server/issues/78',
* title: '[Route] /guilds/:id/regions',
* body: '- [ ] regions',
* number: 78,
* state: 'open',
* label: 'Route',
* assignee: 'Stylix58'
* }
* ```
*/
async getAllIssuesPerRepo(repoIssueUrl) {
var allIssues = [];
var page = 1;
do {
var issues = await fetch(`${repoIssueUrl}?state=all&direction=asc&per_page=100&page=${page}`, {
headers: {
Authorization: `token ${this.githubAuth}`,
"User-Agent": this.org,
},
}).then((r) => r.json());
issues = issues.filter((x) => !x.pull_request).map(GithubNotionSync.convertIssue);
page++;
allIssues = allIssues.concat(issues);
} while (issues.length);
return allIssues;
}
static convertIssue(x) {
return {
url: x?.html_url,
title: x?.title,
body: x?.body,
number: x?.number,
state: x?.state,
labels: x?.labels,
assignees: x?.assignees,
};
}
/**
* @returns {Promise<import("@notionhq/client/build/src/api-types").Page[]>}
*/
async getAllNotionPages() {
var allPages = [];
var start_cursor;
do {
var pages = await this.notion.databases.query({
database_id: this.databaseID,
page_size: 100,
...(start_cursor && { start_cursor }),
});
start_cursor = pages.next_cursor;
allPages = allPages.concat(pages.results);
} while (pages.has_more);
return allPages;
}
async addItemToDb(issue) {
const priority = issue.labels.find((x) => PRIORITIES.includes(x.name.toLowerCase()))?.name;
const options = {
parent: { database_id: this.databaseID },
properties: {
Name: {
title: [
{
text: {
content: issue.title,
},
},
],
},
State: {
select: {
name: issue.state,
},
},
Repo: {
select: {
name: GithubNotionSync.getRepoNameFromUrl(issue.url),
},
},
Url: {
url: issue.url,
},
Number: { number: issue.number },
...(issue.assignees && {
Assignee: {
multi_select: issue.assignees.map((x) => ({ name: x.login })),
},
}),
...(issue.labels && {
Label: {
multi_select: issue.labels
.filter((x) => !PRIORITIES.includes(x.name.toLowerCase()))
.map((x) => ({ name: x.name })),
},
}),
...(priority && {
Priority: {
select: { name: priority },
},
}),
},
children: [
{
object: "block",
type: "paragraph",
paragraph: {
text: [
{
type: "text",
text: {
content: issue.body?.slice(0, 1990) || "",
},
},
],
},
},
],
};
const exists = this.allNotionPages.find(
(x) =>
x.properties.Number.number == issue.number &&
x.properties.Repo.select.name === GithubNotionSync.getRepoNameFromUrl(issue.url)
);
try {
if (exists) {
if (
exists.properties.Name?.title?.[0].plain_text !== issue.title ||
exists.properties.Priority?.select.name !== priority ||
exists.properties.State?.select.name !== issue.state ||
JSON.stringify(issue.labels.map((x) => x.name)) !==
JSON.stringify(exists.properties.Label?.multi_select.map((x) => x.name)) ||
JSON.stringify(issue.assignees.map((x) => x.login)) !==
JSON.stringify(exists.properties.Assignee?.multi_select.map((x) => x.name))
) {
console.log("update existing one");
await this.notion.pages.update({ page_id: exists.id, properties: options.properties });
exists.properties = options.properties;
}
} else {
console.log("adding new one");
const index = this.allNotionPages.push(options) - 1; // directly insert it as they might be inserted twice if the webhook is triggered in very short amount of time
this.allNotionPages[index] = await this.notion.pages.create(options);
}
} catch (error) {
console.log(issue);
console.error(error.body, error);
}
}
static getRepoNameFromUrl(url) {
return url?.match(/fosscord\/(fosscord-)?([\w.-]+)/)[2];
}
}
module.exports = { GithubNotionSync };