Browse Source

storage-node-v2: Refactor web API

- move out state endpoints to the separate `stateApi.ts` file
Shamil Gadelshin 3 years ago
parent
commit
8a20d9eb39

+ 5 - 5
storage-node-v2/src/api-spec/openapi.yaml

@@ -183,7 +183,7 @@ paths:
 
   /state/data-objects:
     get:
-      operationId: publicApi.getAllLocalDataObjects
+      operationId: stateApi.getAllLocalDataObjects
       description: Returns all local data objects.
       tags:
         - state
@@ -195,9 +195,9 @@ paths:
               schema:
                 $ref: '#/components/schemas/DataObjectResponse'
 
-  /state/data-objects/{bagId}:
+  /state/bags/{bagId}/data-objects:
     get:
-      operationId: publicApi.getLocalDataObjectsByBagId
+      operationId: stateApi.getLocalDataObjectsByBagId
       description: Returns local data objects for the bag.
       tags:
         - state
@@ -211,7 +211,7 @@ paths:
 
   /version:
     get:
-      operationId: publicApi.getVersion
+      operationId: stateApi.getVersion
       description: Returns server version.
       tags:
         - state
@@ -277,7 +277,7 @@ components:
         version:
           type: string
         userAgent:
-          type: string    
+          type: string
     DataObjectResponse:
       type: array
       items:

+ 1 - 2
storage-node-v2/src/commands/server.ts

@@ -74,7 +74,6 @@ export default class Server extends ApiCommandBase {
     const queryNodeUrl = `http://${flags.queryNodeHost}/graphql`
     logger.info(`Query node endpoint set: ${queryNodeUrl}`)
 
-
     if (flags.dev) {
       await this.ensureDevelopmentChain()
     }
@@ -108,7 +107,7 @@ export default class Server extends ApiCommandBase {
 		maxFileSize,
         this.config,
         queryNodeUrl,
-        elasticUrl,
+        elasticUrl
       )
       logger.info(`Listening on http://localhost:${port}`)
       app.listen(port)

+ 4 - 1
storage-node-v2/src/services/sync/synchronizer.ts

@@ -1,4 +1,7 @@
-import { getStorageObligationsFromRuntime, DataObligations } from './storageObligations'
+import {
+  getStorageObligationsFromRuntime,
+  DataObligations,
+} from './storageObligations'
 import logger from '../../services/logger'
 import {
   SyncTask,

+ 31 - 0
storage-node-v2/src/services/webApi/controllers/common.ts

@@ -0,0 +1,31 @@
+import * as express from 'express'
+
+/**
+ * Returns a directory for file uploading from the response.
+ *
+ * @remarks
+ * This is a helper function. It parses the response object for a variable and
+ * throws an error on failure.
+ */
+export function getUploadsDir(res: express.Response): string {
+  if (res.locals.uploadsDir) {
+    return res.locals.uploadsDir
+  }
+
+  throw new Error('No upload directory path loaded.')
+}
+
+/**
+ * Returns worker ID from the response.
+ *
+ * @remarks
+ * This is a helper function. It parses the response object for a variable and
+ * throws an error on failure.
+ */
+export function getWorkerId(res: express.Response): number {
+  if (res.locals.workerId || res.locals.workerId === 0) {
+    return res.locals.workerId
+  }
+
+  throw new Error('No Joystream worker ID loaded.')
+}

+ 2 - 6
storage-node-v2/src/services/webApi/controllers/publicApi.ts

@@ -12,7 +12,6 @@ import {
   createNonce,
   getTokenExpirationTime,
 } from '../../../services/helpers/tokenNonceKeeper'
-import { getLocalDataObjects } from '../../../services/sync/synchronizer'
 import { getFileInfo } from '../../../services/helpers/fileInfo'
 import { parseBagId } from '../../helpers/bagTypes'
 import { BagId } from '@joystream/types/storage'
@@ -26,8 +25,7 @@ import send from 'send'
 import { CLIError } from '@oclif/errors'
 import { hexToString } from '@polkadot/util'
 import { timeout } from 'promise-timeout'
-import _ from 'lodash'
-import { getStorageObligationsFromRuntime } from '../../sync/storageObligations'
+import { getUploadsDir, getWorkerId } from './common'
 const fsPromises = fs.promises
 
 /**
@@ -397,8 +395,6 @@ function sendResponseWithError(res: express.Response, err: Error, errorType: str
 function isNofileError(err: Error): boolean {
   return err.toString().includes('ENOENT')
 }
-
-/**
  * Get the status code by error.
  *
  * @param err - error
@@ -546,4 +542,4 @@ function getCommandConfig(res: express.Response): {
   }
 
   throw new Error('No Query Node URL loaded.')
-}
+}

+ 136 - 0
storage-node-v2/src/services/webApi/controllers/stateApi.ts

@@ -0,0 +1,136 @@
+import { getLocalDataObjects } from '../../../services/sync/synchronizer'
+import * as express from 'express'
+import _ from 'lodash'
+import { getStorageObligationsFromRuntime } from '../../sync/storageObligations'
+import { getUploadsDir, getWorkerId } from './common'
+
+/**
+ * A public endpoint: serves files by CID.
+
+/**
+ * A public endpoint: return all local data objects.
+ */
+export async function getAllLocalDataObjects(
+  req: express.Request,
+  res: express.Response
+): Promise<void> {
+  try {
+    const uploadsDir = getUploadsDir(res)
+
+    const cids = await getLocalDataObjects(uploadsDir)
+
+    res.status(200).json(cids)
+  } catch (err) {
+    res.status(500).json({
+      type: 'all_data_objects',
+      message: err.toString(),
+    })
+  }
+}
+
+/**
+ * A public endpoint: return local data objects for the bag.
+ */
+export async function getLocalDataObjectsByBagId(
+  req: express.Request,
+  res: express.Response
+): Promise<void> {
+  try {
+    const uploadsDir = getUploadsDir(res)
+
+    const workerId = getWorkerId(res)
+    const queryNodeUrl = getQueryNodeUrl(res)
+    const bagId = getBagId(req)
+
+    // TODO: Introduce dedicated QueryNode method.
+    const [cids, obligations] = await Promise.all([
+      getLocalDataObjects(uploadsDir),
+      getStorageObligationsFromRuntime(queryNodeUrl, workerId),
+    ])
+
+    const requiredCids = obligations.dataObjects
+      .filter((obj) => obj.bagId === bagId)
+      .map((obj) => obj.cid)
+
+    const localDataForBag = _.intersection(cids, requiredCids)
+
+    res.status(200).json(localDataForBag)
+  } catch (err) {
+    res.status(500).json({
+      type: 'data_objects_by_bag',
+      message: err.toString(),
+    })
+  }
+}
+
+/**
+ * A public endpoint: return the server version.
+ */
+export async function getVersion(
+  req: express.Request,
+  res: express.Response
+): Promise<void> {
+  try {
+    const config = getCommandConfig(res)
+
+    // Copy from an object, because the actual object could contain more data.
+    res.status(200).json({
+      version: config.version,
+      userAgent: config.userAgent,
+    })
+  } catch (err) {
+    res.status(500).json({
+      type: 'version',
+      message: err.toString(),
+    })
+  }
+}
+
+/**
+ * Returns a command config.
+ *
+ * @remarks
+ * This is a helper function. It parses the response object for a variable and
+ * throws an error on failure.
+ */
+function getCommandConfig(res: express.Response): {
+  version: string
+  userAgent: string
+} {
+  if (res.locals.config) {
+    return res.locals.config
+  }
+
+  throw new Error('No upload directory path loaded.')
+}
+
+/**
+ * Returns Bag ID from the request.
+ *
+ * @remarks
+ * This is a helper function. It parses the request object for a variable and
+ * throws an error on failure.
+ */
+function getBagId(req: express.Request): string {
+  const bagId = req.params.bagId || ''
+  if (bagId.length > 0) {
+    return bagId
+  }
+
+  throw new Error('No bagId provided.')
+}
+
+/**
+ * Returns the QueryNode URL from the starting parameters.
+ *
+ * @remarks
+ * This is a helper function. It parses the response object for a variable and
+ * throws an error on failure.
+ */
+function getQueryNodeUrl(res: express.Response): string {
+  if (res.locals.queryNodeUrl) {
+    return res.locals.queryNodeUrl
+  }
+
+  throw new Error('No Query Node URL loaded.')
+}