- Track gitea labels - Add deck card and avoid duplication - Re-add deck card if removed (based on gitea label) - Remove deck card based on label - archive deck card when issue closed - Post actions as comment on issue for user convenience. - Auto create gitea and netcloud labels if not exist.master
parent
97d4d6a99d
commit
8638f8465c
@ -1,3 +1,34 @@
|
|||||||
# deck-gitea-integration
|
# deck-gitea-integration
|
||||||
|
This NodeJS app automatically creates nextcloud [Deck](https://apps.nextcloud.com/apps/deck) cards based on [gitea](https://gitea.io/en-us/) issues.
|
||||||
|
|
||||||
May one day be published as a nextcloud/gitea plugin. Right now it's just for internal use.
|
May one day be published as a nextcloud/gitea plugin. Right now it's just for internal use. It's stable in my environment, but claims no guarantees to yours.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
You'll need to set-up a gitea webhook for each repository you want to auto-add to deck. The webhook must point to the address where this node app is running.
|
||||||
|
|
||||||
|
The webhook requests that are now being sent to NodeJS will be parsed and published on Deck. In order to do so you'll need to provide
|
||||||
|
- A Nextcloud isntallation (URL, not the api)
|
||||||
|
- A Nextcloud username+password (or username+appPassword) that has access to your Deck board
|
||||||
|
- A Nextcloud Deck board (numeric ID)
|
||||||
|
- The deck stack(column) where you want your cards to appear when first created (numeric ID).
|
||||||
|
- Gitea API url
|
||||||
|
- Gitea API key, owned by a dedicated 'Deck' or 'Deckbot' user or something (Don't use your own username!). User must have colaboration access to the repo.
|
||||||
|
|
||||||
|
After that, a 'Deck' label will be added to your gitea repo, and a 'Gitea' label to your deck board.
|
||||||
|
|
||||||
|
When you add the 'Deck' label to an issue, the NodeJS app will automatically pick-up on it, and add the issue to deck. It will also post a notification in the issue that a Deck card has been created.
|
||||||
|
|
||||||
|
When the 'Deck' label is removed from an issue, the Deck card is destroyed.
|
||||||
|
When you close an issue, the Deck card is archived.
|
||||||
|
|
||||||
|
When you remove a Deck card that's being tracked, the card will be re-added as soon as a webhook has been fired (AKA an action like commenting)
|
||||||
|
## Under the hood
|
||||||
|
WIP
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Deploy your own
|
||||||
|
|
||||||
|
WIP
|
@ -1,33 +1,216 @@
|
|||||||
class Gitea {
|
const axios = require('axios')
|
||||||
|
class Deck {
|
||||||
constructor(url, user, pass) { // Nextcloud's base URL, not the api!
|
constructor(url, user, pass, board, defaultstack) { // Nextcloud's base URL, not the api!
|
||||||
this.api = `${url}/index.php/apps/deck/api/v1.0`
|
this.api = `${url}/index.php/apps/deck/api/v1.0/boards/${board}`
|
||||||
this.user = user
|
this.user = user
|
||||||
this.pass = pass
|
this.pass = pass
|
||||||
|
this.board = board
|
||||||
|
this.defaultstack = defaultstack
|
||||||
|
}
|
||||||
|
|
||||||
|
checkLabelExist(label) {
|
||||||
|
let tmpVal = false
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
axios.get(
|
||||||
|
this.api,
|
||||||
|
{auth: {username: this.user, password: this.pass}}
|
||||||
|
).then((res) => {
|
||||||
|
res.data.labels.forEach((el) => {
|
||||||
|
if (el.title == label) {
|
||||||
|
resolve(el)
|
||||||
|
tmpVal = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}).then(() => {
|
||||||
|
if(!tmpVal) resolve(false)
|
||||||
|
})
|
||||||
|
.catch(e => console.log(e))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
createLabel(label, color) {
|
||||||
|
this.checkLabelExist(label).then((exist) => {
|
||||||
|
if (!exist) {
|
||||||
|
axios.post(`${this.api}/labels`,
|
||||||
|
{
|
||||||
|
title: label,
|
||||||
|
color: color
|
||||||
|
},
|
||||||
|
{auth: {username: this.user, password: this.pass}}
|
||||||
|
)
|
||||||
|
.then(console.log(`[${this.api}] - CREATE LABEL: '${label}' added`))
|
||||||
|
.catch(e => console.log(e))
|
||||||
|
} else {
|
||||||
|
console.log(`[${this.api}] - CREATE LABEL: '${label}' already exists.`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
checkCardExist(tracking) {
|
||||||
|
let tmpVal = false
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
|
axios.get(
|
||||||
|
`${this.api}/stacks`,
|
||||||
|
{auth: {username: this.user, password: this.pass}}
|
||||||
|
).then((res) => {
|
||||||
|
res.data.forEach((el) => {
|
||||||
|
if (el.cards) el.cards.forEach((el) => {
|
||||||
|
|
||||||
|
if(el.description) console.log("match"+el.description.match(tracking))
|
||||||
|
if (el.description && el.description.match(tracking) != null) {
|
||||||
|
resolve(el)
|
||||||
|
tmpVal = true
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}).then(() => {
|
||||||
|
|
||||||
checkTagExist() {
|
|
||||||
|
|
||||||
|
//Check archived stack aswell... Wrapped inside .then because we only want resolve(false) ADTER we checked everywhere, not simoultaniously
|
||||||
|
axios.get(
|
||||||
|
`${this.api}/stacks/archived`,
|
||||||
|
{auth: {username: this.user, password: this.pass}}
|
||||||
|
).then((res) => {
|
||||||
|
res.data.forEach((el) => {
|
||||||
|
if (el.cards) el.cards.forEach((el) => {
|
||||||
|
if (el.description && el.description.match(tracking) != null) {
|
||||||
|
resolve(el)
|
||||||
|
tmpVal = true
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}).then(() => {
|
||||||
|
if(!tmpVal) resolve(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(e => console.log(e))
|
||||||
|
|
||||||
createTag() {
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
createCard(title,description, tracking, tags, stack) {
|
||||||
|
let tmpVal
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.checkCardExist(tracking).then((exist) => {
|
||||||
|
console.log("checkcardexist then:" + exist)
|
||||||
|
if (!exist) {
|
||||||
|
axios.post(
|
||||||
|
`${this.api}/stacks/${this.defaultstack}/cards`,
|
||||||
|
{title: 'Giteabot comming trough... please reload :-)'},
|
||||||
|
{auth: {username: this.user, password: this.pass}}
|
||||||
|
).then((res) => {
|
||||||
|
res.data.title = `🍵 ${title}`
|
||||||
|
res.data.description = `[tracking]:${tracking}:\n\n${description}\n`
|
||||||
|
axios.put(
|
||||||
|
`${this.api}/stacks/${this.defaultstack}/cards/${res.data.id}`,
|
||||||
|
res.data,
|
||||||
|
{auth: {username: this.user, password: this.pass}}
|
||||||
|
).then(() => {
|
||||||
|
console.log(`[${this.api}] - ADD CARD: '${tracking}' added to stack '${this.defaultstack}'`)
|
||||||
|
resolve(true)
|
||||||
|
tmpVal = true
|
||||||
|
})
|
||||||
|
.catch(e => console.log(e))
|
||||||
|
})
|
||||||
|
.catch(e => console.log(e))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if(!tmpVal){
|
||||||
|
resolve(false)
|
||||||
|
console.log(`[${this.api}] - ADD CARD: '${tracking}' already exists in board '${this.board}'`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
checkCardExist() {
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
archiveCard(tracking) {
|
||||||
|
this.checkCardExist(tracking).then((card) => {
|
||||||
|
if (card) {
|
||||||
|
card.archived = true
|
||||||
|
axios.put(
|
||||||
|
`${this.api}/stacks/${this.defaultstack}/cards/${card.id}`,
|
||||||
|
card,
|
||||||
|
{auth: {username: this.user, password: this.pass}}
|
||||||
|
).then(() => {
|
||||||
|
console.log(`[${this.api}] - ARCHIVE CARD: '${tracking}' archived`)
|
||||||
|
})
|
||||||
|
.catch(e => console.log(e))
|
||||||
|
} else {
|
||||||
|
console.log(`[${this.api}] - ARCHIVE CARD: '${tracking}' doesn't exist`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
unarchiveCard(tracking) {
|
||||||
|
this.checkCardExist(tracking).then((card) => {
|
||||||
|
if (card) {
|
||||||
|
card.archived = false
|
||||||
|
axios.put(
|
||||||
|
`${this.api}/stacks/${this.defaultstack}/cards/${card.id}`,
|
||||||
|
card,
|
||||||
|
{auth: {username: this.user, password: this.pass}}
|
||||||
|
).then(() => {
|
||||||
|
console.log(`[${this.api}] - UNARCHIVE CARD: '${tracking}' archived`)
|
||||||
|
})
|
||||||
|
.catch(e => console.log(e))
|
||||||
|
} else {
|
||||||
|
console.log(`[${this.api}] - UNARCHIVE CARD: '${tracking}' doesn't exist`)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
createCard(board, card, title, tags) {
|
|
||||||
|
|
||||||
|
cardSet(tracking, property, value) {
|
||||||
|
let tmpVal = false
|
||||||
|
return new Promise((resolve,reject) => {
|
||||||
|
this.checkCardExist(tracking).then((card) => {
|
||||||
|
if (card) {
|
||||||
|
card[property] = value
|
||||||
|
axios.put(
|
||||||
|
`${this.api}/stacks/${this.defaultstack}/cards/${card.id}`,
|
||||||
|
card,
|
||||||
|
{auth: {username: this.user, password: this.pass}}
|
||||||
|
).then(() => {
|
||||||
|
console.log(`[${this.api}] - CARD PROPERTY SET: '${property}' to '${value}' on '${tracking}'`)
|
||||||
|
resolve(true)
|
||||||
|
tmpVal = true
|
||||||
|
})
|
||||||
|
.catch(e => console.log(e))
|
||||||
|
} else {
|
||||||
|
if(!tmpVal)
|
||||||
|
console.log(`[${this.api}] - CARD PROPERTY SET: '${tracking}' doesn't exist`)
|
||||||
|
resolve(false)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
closeCard() {
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteCard() {
|
deleteCard(tracking) {
|
||||||
|
return new Promise((resolve,reject) => {
|
||||||
|
this.checkCardExist(tracking).then((card) => {
|
||||||
|
if(card) {
|
||||||
|
axios.delete(
|
||||||
|
`${this.api}/stacks/${this.defaultstack}/cards/${card.id}`,
|
||||||
|
{auth: {username: this.user, password: this.pass}}
|
||||||
|
).then(() => {
|
||||||
|
console.log(`[${this.api}] - DELETE CARD: '${tracking}' deleted`)
|
||||||
|
resolve(true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
console.log(`[${this.api}] - DELETE CARD: '${tracking}' doesn't exist`)
|
||||||
|
resolve(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports = Deck
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "deck-gitea-integration",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "Integrate gitea issues into Nextcloud Deck using webhooks.",
|
||||||
|
"main": "index.js",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^0.19.0",
|
||||||
|
"express": "^4.17.1",
|
||||||
|
"md5": "^2.2.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {},
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.thijsdevries.net/dodedodo/deck-gitea-integration.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"deck",
|
||||||
|
"gitea",
|
||||||
|
"integration",
|
||||||
|
"nextcloud"
|
||||||
|
],
|
||||||
|
"author": "Thijs de Vries",
|
||||||
|
"license": "GPL-3.0"
|
||||||
|
}
|
Loading…
Reference in new issue