kosmos/akkounts-api
kosmos
/
akkounts-api
Archived
8
1
Fork 0

Basic app

This commit is contained in:
Basti 2019-03-05 18:20:33 +07:00
commit fc5913316e
No known key found for this signature in database
GPG Key ID: BE4634D632D39B67
19 changed files with 4782 additions and 0 deletions

2
.env.example Normal file
View File

@ -0,0 +1,2 @@
MASTODON_HOST=https://kosmos.social
MASTODON_AUTH_TOKEN=123456abcdefg

49
.eslintrc.yaml Normal file
View File

@ -0,0 +1,49 @@
---
extends: airbnb-base
env:
node: true
mocha: true
es6: true
parser: typescript-eslint-parser
parserOptions:
sourceType: module
ecmaFeatures:
modules: true
rules:
generator-star-spacing:
- 2
- before: true
after: true
no-shadow: 0
import/no-unresolved: 0
import/extensions: 0
require-yield: 0
no-param-reassign: 0
comma-dangle: 0
no-underscore-dangle: 0
no-control-regex: 0
import/no-extraneous-dependencies:
- 2
- devDependencies: true
func-names: 0
no-unused-expressions: 0
prefer-arrow-callback: 1
no-use-before-define:
- 2
- functions: false
space-before-function-paren:
- 2
- always
max-len:
- 2
- 120
- 2
semi:
- 2
- never
strict:
- 2
- global
arrow-parens:
- 2
- always

13
.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
.env
node_modules
dist
npm-debug.log
pids
logs
chef/.vagrant
chef/bin/*
chef/.bundle/*
chef/vendor/
chef/.chef/encrypted_data_bag_secret
chef/nodes/vagrant-node.json

29
README.md Normal file
View File

@ -0,0 +1,29 @@
## Kosmos Accounts API
### Development
Compile app, run API server, watch code and reload automatically:
npm run dev
### Running tests
npm test
### Linting
npm run lint
### Resources
#### TypeScript, Express.js & Co.
* [TypeScript documentation](https://www.typescriptlang.org/docs/)
* [Express documentation](https://expressjs.com/)
#### Testing
* [Mocha test framework](https://mochajs.org/#getting-started)
* [Chai assertions (expect/should)](https://www.chaijs.com/api/bdd/)
* [Sinon JS mocks & stubs](https://sinonjs.org/releases/v7.1.1/)
* [Factory Girl factories](https://github.com/aexmachina/factory-girl#readme)

34
app/api.ts Normal file
View File

@ -0,0 +1,34 @@
import * as express from 'express'
import * as cors from 'cors'
import IndexRoute from "./routes/index";
import MastodonUsernameLookupRoute from "./routes/accounts/mastodon/username_lookup";
require('dotenv').config()
class API {
public express
constructor () {
this.express = express()
this.config()
this.routes()
}
public config() {
this.express.use(express.json())
this.express.use(cors())
this.express.set('etag', false)
}
private routes (): void {
const router = express.Router()
IndexRoute.create(router)
MastodonUsernameLookupRoute.create(router)
this.express.use('/', router)
}
}
export default new API().express

9
app/index.ts Normal file
View File

@ -0,0 +1,9 @@
import * as http from 'http'
import API from './api'
const port = process.env.PORT || 3000
API.listen(port, (err) => {
if (err) return console.log(err)
return console.log(`API server is listening on ${port}`)
})

View File

@ -0,0 +1,38 @@
class DerivationPath {
shortNotation: string
method: string
network: string
constructor(shortNotation: string, opts?: { method?: string, network?: string }) {
if (typeof opts == 'undefined') { opts = {} }
this.shortNotation = shortNotation
this.method = opts.method || 'BIP44'
this.network = opts.network || 'testnet'
}
toArray(): number[] {
let path
const derivation = this.shortNotation.split('/')
.slice(1)
.map(i => parseInt(i))
switch(this.network) {
case 'testnet':
path = [2147483692, 2147483649, 2147483648].concat(derivation)
break
default:
throw Error('not implemented')
}
return path
}
static toArray(shortNotation: string, opts?: object) {
const path = new DerivationPath(shortNotation, opts)
return path.toArray()
}
}
export default DerivationPath

View File

@ -0,0 +1,38 @@
import { Request, Response, Router } from 'express'
import { BaseRoute } from '../../base'
import { inspect } from 'util'
class MastodonUsernameLookupRoute extends BaseRoute {
constructor() { super() }
public static create(router: Router) {
router.get('/accounts/mastodon/username_lookup', (req, res) => {
new MastodonUsernameLookupRoute().lookup(req, res)
})
}
public async lookup(req: Request, res: Response) {
const username = req.query.q
if (typeof username !== 'string') {
return res.status(422).json({
error: { message: 'Query param "q" must be a string' }
})
}
const axios = this.createMastodonClient()
axios.get(`/accounts/search?q=${username}`)
.then(result => {
const accounts = result.data
if (accounts.find(a => a.username.toLowerCase() === username.toLowerCase())) {
res.status(200).json({ available: false })
} else {
res.status(200).json({ available: true })
}
})
.catch(err => {
return this.handleError(res, err)
})
}
}
export default MastodonUsernameLookupRoute

26
app/routes/base.ts Normal file
View File

@ -0,0 +1,26 @@
import { Request, Response } from 'express';
import * as colors from 'colors/safe'
import axios from 'axios'
import { inspect } from 'util'
export class BaseRoute {
constructor() {
}
public handleError(res: Response, err: { message: string, stack: any }) {
console.log(colors.red("Encountered an error, aborting request (500):"))
console.log(colors.gray(err.stack))
res.sendStatus(500)
}
public createMastodonClient () {
const host = process.env.MASTODON_HOST
const authToken = process.env.MASTODON_AUTH_TOKEN
const client = axios.create()
axios.defaults.baseURL = `${host}/api/v1`
axios.defaults.headers.common['Authorization'] = `Bearer ${authToken}`
return client
}
}

20
app/routes/index.ts Normal file
View File

@ -0,0 +1,20 @@
import { Request, Response, Router } from "express";
import { BaseRoute } from "./base";
class IndexRoute extends BaseRoute {
constructor() { super() }
public static create(router: Router) {
router.get("/", (req: Request, res: Response) => {
new IndexRoute().index(req, res)
})
}
public index(req: Request, res: Response) {
res.json({ message: 'Hello blockchain!' })
}
}
export default IndexRoute

4318
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

44
package.json Normal file
View File

@ -0,0 +1,44 @@
{
"name": "akkounts-api",
"version": "1.0.0",
"description": "",
"main": "dist/app/index.js",
"scripts": {
"dev": "tsc --watch & nodemon dist/app",
"test": "rm -rf dist/spec/ && tsc && MONGO_DATABASE=custody_test BWS_DATABASE=bws_test mocha -s 300 --recursive dist/spec/",
"lint": "eslint src --ext ts",
"start": "tsc && ./scripts/start.sh",
"stop": "./scripts/stop.sh",
"deploy:5apps": "git subtree push --prefix public/ 5apps master",
"webhooks:server": "node scripts/webhook-demo.js",
"webhooks:ngrok": "ngrok http -subdomain=webhook-test -region=eu 8081"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^0.18.0",
"cors": "^2.8.5",
"dotenv": "^6.2.0",
"express": "4.16.3",
"nodemon": "^1.18.7"
},
"devDependencies": {
"@types/chai": "^4.1.5",
"@types/mocha": "2.2.41",
"@types/node": "10.12.10",
"chai": "^4.2.0",
"colors": "^1.3.2",
"eslint": "4.0.0",
"eslint-config-airbnb-base": "11.2.0",
"eslint-plugin-import": "2.3.0",
"eslint-plugin-promise": "3.5.0",
"factory-girl": "^5.0.3",
"mocha": "3.4.2",
"sinon": "^7.1.1",
"supertest": "github:5apps/supertest#add-agent-default-support",
"tsc": "1.20150623.0",
"typescript": "2.3.3",
"typescript-eslint-parser": "3.0.0"
}
}

20
spec/factories.ts Normal file
View File

@ -0,0 +1,20 @@
import * as FactoryGirl from 'factory-girl'
import * as fixtures from './support/fixtures'
const factory = FactoryGirl.factory
// factory.define('customer', Customer, {
// companyName: 'Scrooge McDuck'
// })
//
// factory.define('account', Account, {
// owner: factory.assoc('customer', '_id'),
// walletId: factory.assoc('wallet', 'id'),
// asset: 'BTC'
// })
//
// factory.define('apiKey', APIKey, {
// customer: factory.assoc('customer', '_id')
// })
export default factory

View File

@ -0,0 +1,70 @@
import { expect } from 'chai'
import { supertest, sandbox, factory, setup } from '../../../spec_helper'
import { BaseRoute } from '../../../../app/routes/base'
setup()
describe('GET /accounts/mastodon/username_lookup', () => {
describe('query param missing', () => {
it('returns an error', async () => {
await supertest
.get(`/accounts/mastodon/username_lookup`)
.set('Accept', 'application/json')
.expect(422)
})
})
context('username does not exist', () => {
before(() => {
sandbox.stub(BaseRoute.prototype, 'createMastodonClient').returns({
get: url => { return Promise.resolve({ data: [] }) }
})
})
it('returns the correct response', async () => {
const username = 'thegrinch'
await supertest
.get(`/accounts/mastodon/username_lookup?q=${username}`)
.set('Accept', 'application/json')
.expect(200, { available: true })
})
})
context('username exists', () => {
before(() => {
sandbox.stub(BaseRoute.prototype, 'createMastodonClient').returns({
get: url => { return Promise.resolve({ data: [
{ username: 'satoshi', display_name: 'Satoshi Nakamoto' }
] }) }
})
})
it('returns the correct response', async () => {
const username = 'satoshi'
await supertest
.get(`/accounts/mastodon/username_lookup?q=${username}`)
.set('Accept', 'application/json')
.expect(200, { available: false })
})
})
context('username exists with different capitalization', () => {
before(() => {
sandbox.stub(BaseRoute.prototype, 'createMastodonClient').returns({
get: url => { return Promise.resolve({ data: [
{ username: 'Satoshi', display_name: 'Satoshi Nakamoto' }
] }) }
})
})
it('returns the correct response', async () => {
const username = 'satoshi'
await supertest
.get(`/accounts/mastodon/username_lookup?q=${username}`)
.set('Accept', 'application/json')
.expect(200, { available: false })
})
})
})

15
spec/routes/index.spec.ts Normal file
View File

@ -0,0 +1,15 @@
import { expect } from 'chai'
import { setup, supertest } from '../spec_helper'
setup()
describe('GET /', () => {
it('works', async () =>
await supertest
.get('/')
.expect('Content-Type', /json/)
.expect(200, {
message: 'Hello blockchain!'
})
)
})

25
spec/spec_helper.ts Normal file
View File

@ -0,0 +1,25 @@
import * as _supertest from 'supertest'
import { createSandbox, spy, stub } from 'sinon'
import factory from './factories'
import API from '../app/api'
const supertest = _supertest(API)
const sandbox = createSandbox()
let setup = () => {
// before("Clear databases", async () => {
// await dropTestDatabases()
// })
// afterEach("Clear databases", async () => {
// await dropTestDatabases()
// })
afterEach(() => sandbox.restore())
}
// let dropTestDatabases = async () => {
// await db.dropDatabase()
// await db_bws.dropDatabase()
// }
export { supertest, sandbox, spy, stub, factory, setup }

9
spec/support/fixtures.ts Normal file
View File

@ -0,0 +1,9 @@
import * as fs from 'fs'
import * as path from 'path'
let loadJSON = (name) => {
let data = fs.readFileSync(path.join('spec/fixtures', name), 'utf8')
return JSON.parse(data)
}
export { loadJSON }

4
spec/support/index.js Normal file
View File

@ -0,0 +1,4 @@
import factory from './factories'
import fixtures from './fixtures'
export { factory, fixtures }

19
tsconfig.json Normal file
View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "dist",
"sourceMap": true
},
"files": [
"./node_modules/@types/mocha/index.d.ts",
"./node_modules/@types/node/index.d.ts"
],
"include": [
"app/**/*.ts",
"spec/**/*.ts"
],
"exclude": [
"node_modules"
]
}