add bin/ and store secret in path

master
Yorick van Pelt 2019-05-18 19:59:44 +02:00
parent 4d5c11ce24
commit 67bdb9f84c
Signed by: yorick
GPG Key ID: A36E70F9DC014A15
4 changed files with 66 additions and 35 deletions

27
bin/skl-auto-payslip.js Executable file
View File

@ -0,0 +1,27 @@
#!/usr/bin/env node
const App = require('../index.js')
const sig = fn =>
fn.toString().split('\n')[0].replace(/^async /, '').replace(/ \{/, '')
const methods = App.prototype
// poor man's CLI
const [,, method, ...args] = process.argv
if (method in methods) {
if (args.length < methods[method].length) {
console.error("not enough arguments for method", sig(methods[method]))
} else {
App.authAll().then((auth) => {
const app = new App(auth)
app[method](...args)
})
}
} else {
if (method && method != 'help') console.log("unknown subcall", method)
console.log("valid methods:")
for(const realMethod of Object.getOwnPropertyNames(methods)) {
if (realMethod == 'constructor') continue
if (methods[realMethod])
console.log(" ", sig(methods[realMethod]))
}
process.exit(1)
}

3
default.nix Normal file
View File

@ -0,0 +1,3 @@
with import <nixpkgs> {};
with callPackage (fetchTarball https://github.com/serokell/nix-npm-buildpackage/archive/master.tar.gz) {};
buildNpmPackage { src = ./.; }

View File

@ -8,19 +8,22 @@ const {promisify} = require('util')
const readFile = promisify(fs.readFile) const readFile = promisify(fs.readFile)
const base64url = require('base64url') const base64url = require('base64url')
const process = require('process') const process = require('process')
const path = require('path')
const exec = promisify(require('child_process').exec)
const {spawn} = require('child_process')
// If modifying these scopes, delete token.json. // If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/drive','https://www.googleapis.com/auth/gmail.compose']; const SCOPES = ['https://www.googleapis.com/auth/drive','https://www.googleapis.com/auth/gmail.compose'];
const USER = process.env['USER'] const USER = process.env['USER']
const HOME = process.env['HOME']
const UserCapitalized = USER.charAt(0).toUpperCase() + USER.slice(1) const UserCapitalized = USER.charAt(0).toUpperCase() + USER.slice(1)
const config = { const config = {
tokenPath: 'token.json',
fileId: "1CqUglyFNoEWL0lx-e7arqUGOOZEYW__JjcwBmNaXE6E", fileId: "1CqUglyFNoEWL0lx-e7arqUGOOZEYW__JjcwBmNaXE6E",
template: `${UserCapitalized} ${(new Date()).getFullYear()} Template`, template: `${UserCapitalized} ${(new Date()).getFullYear()} Template`,
to: "billing@serokell.io", to: "billing@serokell.io",
destdir: "payslips" destdir: `${HOME}/serokell/invoices/payslips`
} }
async function authAll() { async function authAll() {
@ -36,7 +39,7 @@ async function authorize(credentials) {
// Check if we have previously stored a token. // Check if we have previously stored a token.
let token let token
try { try {
token = await readFile(config.tokenPath) token = await pass_get("skl-auto-payslip")
} catch(e) { } catch(e) {
return getAccessToken(oAuth2Client) return getAccessToken(oAuth2Client)
} }
@ -53,6 +56,21 @@ async function ask(question) {
rl.close() rl.close()
return answer return answer
} }
function pass_insert(name, val) {
return new Promise((resolve, reject) => {
const pass = spawn("pass", ['insert', 'skl-auto-payslip', '-m'], {stdio: ['pipe', 'inherit', 'inherit']})
pass.stdin.write(val)
pass.stdin.end()
pass.on('close', (code) => {
if (code != 0) reject(`pass exited with code ${code}`)
else { resolve() }
})
})
}
async function pass_get(name) {
const {stdout, stderr} = await exec("pass skl-auto-payslip")
return stdout
}
async function getAccessToken(oAuth2Client) { async function getAccessToken(oAuth2Client) {
const authUrl = oAuth2Client.generateAuthUrl({ const authUrl = oAuth2Client.generateAuthUrl({
@ -63,10 +81,10 @@ async function getAccessToken(oAuth2Client) {
const code = await ask('Enter the code from that page here: ') const code = await ask('Enter the code from that page here: ')
const token = (await oAuth2Client.getToken(code)).tokens const token = (await oAuth2Client.getToken(code)).tokens
oAuth2Client.setCredentials(token); oAuth2Client.setCredentials(token);
await promisify(fs.writeFile)(config.tokenPath, JSON.stringify(token)) await pass_insert('skl-auto-payslip', JSON.stringify(token))
console.log('Token stored to', config.tokenPath);
return oAuth2Client return oAuth2Client
} }
class InvoiceDocument { class InvoiceDocument {
constructor(auth, fileId) { constructor(auth, fileId) {
Object.assign(this, { Object.assign(this, {
@ -171,16 +189,20 @@ The total is ${total} to ${iban} (same as always). Thanks!`
} }
const datefmt = date => const datefmt = date =>
`${date.getFullYear()}-${(""+(date.getMonth()+1)).padStart(2, '0')}-${(""+date.getDate()).padStart(2, '0')}` `${date.getFullYear()}-${(""+(date.getMonth()+1)).padStart(2, '0')}-${(""+date.getDate()).padStart(2, '0')}`
const methods = { class App {
constructor(auth) {
this.auth = auth
this.doc = new InvoiceDocument(auth, config.fileId)
}
async export(sheetname, pdfOut) { // save sheet as pdf async export(sheetname, pdfOut) { // save sheet as pdf
console.log("Exporting", sheetname, "to", pdfOut) console.log("Exporting", sheetname, "to", pdfOut)
const sheetId = await this.doc.sheetByName(sheetname) const sheetId = await this.doc.sheetByName(sheetname)
return this.doc.sheetAsPdf(pdfOut, sheetId) return this.doc.sheetAsPdf(pdfOut, sheetId)
}, }
async create(sheetname, template=config.template) { // copy sheet from template async create(sheetname, template=config.template) { // copy sheet from template
console.log("Creating", sheetname, "from", template) console.log("Creating", sheetname, "from", template)
return this.doc.copyTo(template, sheetname) return this.doc.copyTo(template, sheetname)
}, }
async update(sheetname, startDate, endDate, hours) { // set values in sheet async update(sheetname, startDate, endDate, hours) { // set values in sheet
console.log("Setting data in", sheetname) console.log("Setting data in", sheetname)
hours = +hours hours = +hours
@ -194,7 +216,7 @@ const methods = {
D16: values.regular, D17: values.overwork, D16: values.regular, D17: values.overwork,
B9: values.startDate, D9: values.endDate B9: values.startDate, D9: values.endDate
}) })
}, }
async draft(sheetname) { // export pdf+create gmail draft async draft(sheetname) { // export pdf+create gmail draft
const pdfname = `${config.destdir}/${sheetname}.pdf` const pdfname = `${config.destdir}/${sheetname}.pdf`
try { try {
@ -207,7 +229,7 @@ const methods = {
const iban = paid.replace(/^Paid to /, '') const iban = paid.replace(/^Paid to /, '')
console.log(paid, total) console.log(paid, total)
composeDraft(sheetname, iban, total, pdfname, this.auth) composeDraft(sheetname, iban, total, pdfname, this.auth)
}, }
async auto(hours) { // just do everything async auto(hours) { // just do everything
const date = new Date() const date = new Date()
const day = date.getDate() const day = date.getDate()
@ -244,31 +266,9 @@ const methods = {
} }
await this.update(sheetname, startDate, endDate, hours) await this.update(sheetname, startDate, endDate, hours)
await this.draft(sheetname) await this.draft(sheetname)
}, }
async authenticate() { // just auth async authenticate() { // just auth
} }
} }
module.exports = App
const sig = fn => module.exports.authAll = authAll
fn.toString().split('\n')[0].replace(/^async /, '').replace(/ \{/, '')
// poor man's CLI
const [,, method, ...args] = process.argv
if (method in methods) {
if (args.length < methods[method].length) {
console.error("not enough arguments for method", sig(methods[method]))
} else {
authAll().then((auth) => {
methods.auth = auth
methods.doc = new InvoiceDocument(auth, config.fileId)
methods[method](...args)
})
}
} else {
if (method && method != 'help') console.log("unknown subcall", method)
console.log("valid methods:")
for(const realMethod in methods) {
console.log(" ", sig(methods[realMethod]))
}
process.exit(1)
}

View File

@ -6,6 +6,7 @@
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"bin": "bin/skl-auto-payslip.js",
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {