|
1 | 1 | // import bodyParser from 'body-parser'
|
2 |
| -import { asArray, asObject, asOptional, asString } from 'cleaners' |
3 | 2 | import cors from 'cors'
|
4 | 3 | import express from 'express'
|
5 | 4 | import nano from 'nano'
|
6 | 5 |
|
7 | 6 | import { config } from './config'
|
8 |
| -import { cacheAnalytic } from './dbutils' |
9 |
| -import { asApps, asDbTx } from './types' |
| 7 | +import { analyticsRouter } from './routes/v1/analytics' |
| 8 | +import { checkTxsRouter } from './routes/v1/checkTxs' |
| 9 | +import { getAppIdRouter } from './routes/v1/getAppId' |
| 10 | +import { getPluginIdsRouter } from './routes/v1/getPluginIds' |
| 11 | +import { getTxInfoRouter } from './routes/v1/getTxInfo' |
| 12 | +import { HttpError } from './util/httpErrors' |
10 | 13 |
|
11 |
| -const asAnalyticsReq = asObject({ |
12 |
| - start: asString, |
13 |
| - end: asString, |
14 |
| - appId: asString, |
15 |
| - pluginIds: asArray(asString), |
16 |
| - timePeriod: asString |
17 |
| -}) |
18 |
| - |
19 |
| -const asCheckTxsReq = asObject({ |
20 |
| - apiKey: asString, |
21 |
| - data: asArray( |
22 |
| - asObject({ |
23 |
| - pluginId: asString, |
24 |
| - orderId: asString |
25 |
| - }) |
26 |
| - ) |
27 |
| -}) |
28 |
| - |
29 |
| -const asCheckTxsFetch = asArray( |
30 |
| - asObject({ |
31 |
| - key: asString, |
32 |
| - doc: asOptional(asDbTx), |
33 |
| - error: asOptional(asString) |
34 |
| - }) |
35 |
| -) |
36 |
| - |
37 |
| -const asPluginIdsReq = asObject({ |
38 |
| - appId: asString |
39 |
| -}) |
40 |
| -const asPartnerIdsDbReq = asObject({ |
41 |
| - partnerIds: asObject( |
42 |
| - asObject({ |
43 |
| - pluginId: asOptional(asString), |
44 |
| - apiKeys: asObject(asString) |
45 |
| - }) |
46 |
| - ) |
47 |
| -}) |
48 |
| - |
49 |
| -const asAppIdReq = asObject({ |
50 |
| - apiKey: asString |
51 |
| -}) |
52 |
| -const asAppIdDbReq = asObject({ |
53 |
| - appId: asString |
54 |
| -}) |
55 |
| - |
56 |
| -interface CheckTxsResponse { |
57 |
| - pluginId: string |
58 |
| - orderId: string |
59 |
| - error?: string |
60 |
| - usdValue?: number |
61 |
| -} |
62 |
| - |
63 |
| -const CHECKTXS_BATCH_LIMIT = 100 |
64 |
| - |
65 |
| -const nanoDb = nano(config.couchDbFullpath) |
| 14 | +export const nanoDb = nano(config.couchDbFullpath) |
| 15 | +export const reportsTransactions = nanoDb.use('reports_transactions') |
| 16 | +export const reportsApps = nanoDb.use('reports_apps') |
66 | 17 |
|
67 | 18 | async function main(): Promise<void> {
|
68 | 19 | // start express and couch db server
|
69 | 20 | const app = express()
|
70 |
| - const reportsTransactions = nanoDb.use('reports_transactions') |
71 |
| - const reportsApps = nanoDb.use('reports_apps') |
72 | 21 |
|
73 | 22 | app.use(express.json())
|
74 | 23 | // app.use(bodyParser.json({ type: 'application/json' }))
|
75 | 24 | app.use(cors())
|
76 | 25 | app.use('/', express.static('dist'))
|
77 | 26 |
|
78 |
| - const query = { |
79 |
| - selector: { |
80 |
| - appId: { $exists: true } |
81 |
| - }, |
82 |
| - limit: 1000000 |
83 |
| - } |
84 |
| - |
85 |
| - const rawApps = await reportsApps.find(query) |
86 |
| - const apps = asApps(rawApps.docs) |
87 |
| - |
88 |
| - app.post(`/v1/analytics/`, async function(req, res) { |
89 |
| - let analyticsQuery: ReturnType<typeof asAnalyticsReq> |
90 |
| - try { |
91 |
| - analyticsQuery = asAnalyticsReq(req.body) |
92 |
| - } catch { |
93 |
| - res.status(400).send(`Missing Request Fields`) |
94 |
| - return |
95 |
| - } |
96 |
| - const { start, end, appId, pluginIds } = analyticsQuery |
97 |
| - const timePeriod = analyticsQuery.timePeriod.toLowerCase() |
98 |
| - const queryStart = new Date(start).getTime() / 1000 |
99 |
| - const queryEnd = new Date(end).getTime() / 1000 |
100 |
| - if ( |
101 |
| - !(queryStart > 0) || |
102 |
| - !(queryEnd > 0) || |
103 |
| - (!timePeriod.includes('month') && |
104 |
| - !timePeriod.includes('day') && |
105 |
| - !timePeriod.includes('hour')) |
106 |
| - ) { |
107 |
| - res.status(400).send(`Bad Request Fields`) |
108 |
| - return |
109 |
| - } |
110 |
| - if (queryStart > queryEnd) { |
111 |
| - res.status(400).send(`Start must be less than End`) |
112 |
| - return |
113 |
| - } |
114 |
| - |
115 |
| - const result = await cacheAnalytic( |
116 |
| - queryStart, |
117 |
| - queryEnd, |
118 |
| - appId, |
119 |
| - pluginIds, |
120 |
| - timePeriod |
121 |
| - ) |
122 |
| - |
123 |
| - res.json(result) |
124 |
| - }) |
125 |
| - |
126 |
| - app.post('/v1/checkTxs/', async function(req, res) { |
127 |
| - let queryResult |
128 |
| - try { |
129 |
| - queryResult = asCheckTxsReq(req.body) |
130 |
| - } catch (e) { |
131 |
| - return res.status(400).send(`Missing Request fields.`) |
132 |
| - } |
133 |
| - if (queryResult.data.length > CHECKTXS_BATCH_LIMIT) { |
134 |
| - return res.status(400).send(`Exceeded Limit of ${CHECKTXS_BATCH_LIMIT}`) |
135 |
| - } |
136 |
| - const searchedAppId = apps.find(app => app._id === queryResult.apiKey) |
137 |
| - if (typeof searchedAppId === 'undefined') { |
138 |
| - return res.status(400).send(`API Key has no match.`) |
139 |
| - } |
140 |
| - const { appId } = searchedAppId |
141 |
| - const keys = queryResult.data.map(tx => { |
142 |
| - return `${appId}_${tx.pluginId}:${tx.orderId}`.toLowerCase() |
143 |
| - }) |
144 |
| - try { |
145 |
| - const dbResult = await reportsTransactions.fetch({ keys }) |
146 |
| - const cleanedResult = asCheckTxsFetch(dbResult.rows) |
147 |
| - const data: CheckTxsResponse[] = cleanedResult.map((result, index) => { |
148 |
| - const tx: CheckTxsResponse = { |
149 |
| - pluginId: queryResult.data[index].pluginId, |
150 |
| - orderId: queryResult.data[index].orderId, |
151 |
| - usdValue: undefined |
152 |
| - } |
153 |
| - if (result.error != null) { |
154 |
| - return { |
155 |
| - ...tx, |
156 |
| - error: `Could not find transaction: ${result.key}` |
157 |
| - } |
| 27 | + app.use('/v1/analytics/', analyticsRouter) |
| 28 | + app.use('/v1/checkTxs/', checkTxsRouter) |
| 29 | + app.use('/v1/getAppId/', getAppIdRouter) |
| 30 | + app.use('/v1/getPluginIds/', getPluginIdsRouter) |
| 31 | + app.use('/v1/getTxInfo/', getTxInfoRouter) |
| 32 | + |
| 33 | + // Error router |
| 34 | + app.use(function(err, _req, res, _next) { |
| 35 | + console.error(err.stack) |
| 36 | + if (err instanceof HttpError) { |
| 37 | + console.error(`HTTP status ${err.status}:`, err.error) |
| 38 | + res.status(err.status).send({ |
| 39 | + error: { |
| 40 | + message: err.error.message |
158 | 41 | }
|
159 |
| - if (result.doc != null) tx.usdValue = result.doc.usdValue |
160 |
| - return tx |
161 | 42 | })
|
162 |
| - res.json({ appId, data }) |
163 |
| - } catch (e) { |
164 |
| - console.log(e) |
165 |
| - return res.status(500).send(`Internal Server Error.`) |
166 |
| - } |
167 |
| - }) |
168 |
| - |
169 |
| - app.get('/v1/getPluginIds/', async function(req, res) { |
170 |
| - let queryResult |
171 |
| - try { |
172 |
| - queryResult = asPluginIdsReq(req.query) |
173 |
| - } catch (e) { |
174 |
| - res.status(400).send(`Missing Request fields.`) |
175 |
| - return |
176 |
| - } |
177 |
| - const query = { |
178 |
| - selector: { |
179 |
| - appId: { $eq: queryResult.appId.toLowerCase() } |
180 |
| - }, |
181 |
| - fields: ['partnerIds'], |
182 |
| - limit: 1 |
183 |
| - } |
184 |
| - let partnerIds |
185 |
| - try { |
186 |
| - const rawApp = await reportsApps.find(query) |
187 |
| - const app = asPartnerIdsDbReq(rawApp.docs[0]) |
188 |
| - partnerIds = Object.keys(app.partnerIds) |
189 |
| - } catch (e) { |
190 |
| - res.status(404).send(`App ID not found.`) |
191 |
| - return |
192 |
| - } |
193 |
| - res.json(partnerIds) |
194 |
| - }) |
195 |
| - |
196 |
| - app.get('/v1/getAppId/', async function(req, res) { |
197 |
| - let queryResult |
198 |
| - try { |
199 |
| - queryResult = asAppIdReq(req.query) |
200 |
| - } catch (e) { |
201 |
| - res.status(400).send(`Missing Request fields.`) |
202 |
| - return |
203 |
| - } |
204 |
| - let appId |
205 |
| - try { |
206 |
| - const dbResult = await reportsApps.get(queryResult.apiKey) |
207 |
| - appId = asAppIdDbReq(dbResult).appId |
208 |
| - } catch (e) { |
209 |
| - res.status(400).send(`API KEY UNRECOGNIZED.`) |
210 |
| - return |
| 43 | + } else { |
| 44 | + res.status(500).send({ |
| 45 | + error: 'Internal Server Error' |
| 46 | + }) |
211 | 47 | }
|
212 |
| - res.json(appId) |
213 | 48 | })
|
214 | 49 |
|
215 | 50 | app.listen(config.httpPort, function() {
|
|
0 commit comments