Basic app
This commit is contained in:
commit
fc5913316e
|
@ -0,0 +1,2 @@
|
|||
MASTODON_HOST=https://kosmos.social
|
||||
MASTODON_AUTH_TOKEN=123456abcdefg
|
|
@ -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
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
|
@ -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}`)
|
||||
})
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
File diff suppressed because it is too large
Load Diff
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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 })
|
||||
})
|
||||
})
|
||||
|
||||
})
|
|
@ -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!'
|
||||
})
|
||||
)
|
||||
})
|
|
@ -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 }
|
|
@ -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 }
|
|
@ -0,0 +1,4 @@
|
|||
import factory from './factories'
|
||||
import fixtures from './fixtures'
|
||||
|
||||
export { factory, fixtures }
|
|
@ -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"
|
||||
]
|
||||
}
|
Reference in New Issue