├── jest.setup.js ├── .gitignore ├── jest.config.js ├── src ├── routes │ ├── v1 │ │ ├── info.js │ │ ├── live.js │ │ ├── events.js │ │ ├── raw.js │ │ ├── analysed.js │ │ ├── company.js │ │ └── launches.js │ ├── v2 │ │ ├── info.js │ │ ├── live.js │ │ ├── events.js │ │ ├── raw.js │ │ ├── analysed.js │ │ ├── company.js │ │ └── launches.js │ ├── info.js │ └── auth-routes.js ├── models │ ├── user-model.js │ ├── api.js │ ├── v1 │ │ ├── company.js │ │ └── launch.js │ └── v2 │ │ ├── company.js │ │ └── launch.js ├── auth │ ├── tokens.js │ ├── keys.js │ └── passport-setup.js ├── middleware │ ├── v1 │ │ ├── validator.js │ │ ├── launch_validator.js │ │ └── request_splitting.js │ ├── v2 │ │ ├── validator.js │ │ ├── launch_validator.js │ │ └── request_splitting.js │ ├── error_handler.js │ └── confirm_auth.js ├── controllers │ ├── v1 │ │ ├── info.js │ │ ├── events.js │ │ ├── live.js │ │ ├── raw.js │ │ ├── analysed.js │ │ ├── company.js │ │ └── launches.js │ ├── v2 │ │ ├── info.js │ │ ├── events.js │ │ ├── live.js │ │ ├── raw.js │ │ ├── analysed.js │ │ ├── company.js │ │ └── launches.js │ └── info.js ├── helpers │ ├── cache_helper.js │ ├── v1 │ │ ├── mongo_helper.js │ │ ├── s3_helper.js │ │ └── telemetry_helper.js │ └── v2 │ │ ├── mongo_helper.js │ │ ├── s3_helper.js │ │ └── telemetry_helper.js └── app.js ├── Dockerfile ├── .eslintrc.json ├── test ├── other │ ├── database │ │ ├── connection.js │ │ └── finding_test.js │ └── routes │ │ ├── launches_test.js │ │ ├── raw_test.js │ │ └── analysed_test.js ├── controllers │ └── v1 │ │ ├── events.test0.js │ │ └── launches.test0.js └── middleware │ └── middleware.test.js ├── LICENSE ├── CONTRIBUTING.md ├── package.json ├── .github └── workflows │ └── main.yml ├── CODE_OF_CONDUCT.md └── README.md /jest.setup.js: -------------------------------------------------------------------------------- 1 | jest.setTimeout(30000); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .vscode/\ 3 | test/other -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: "node" 3 | }; 4 | -------------------------------------------------------------------------------- /src/routes/v1/info.js: -------------------------------------------------------------------------------- 1 | // Imports 2 | const express = require("express"); 3 | const info = require("../../controllers/v1/info"); 4 | // Create an express router 5 | const router = express.Router(); 6 | 7 | router.get("/", info.versions); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /src/routes/v2/info.js: -------------------------------------------------------------------------------- 1 | // Imports 2 | const express = require("express"); 3 | const info = require("../../controllers/v2/info"); 4 | // Create an express router 5 | const router = express.Router(); 6 | 7 | router.get("/", info.versions); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /src/models/user-model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const Schema = mongoose.Schema; 3 | 4 | const userSchema = new Schema({ 5 | username: String, 6 | googleId: String 7 | }); 8 | 9 | const User = mongoose.model("user", userSchema); 10 | 11 | module.exports = User; 12 | -------------------------------------------------------------------------------- /src/auth/tokens.js: -------------------------------------------------------------------------------- 1 | const s3 = require("../helpers/v1/s3_helper"); 2 | const keys = require("./keys"); 3 | 4 | module.exports = { 5 | setKeys: async () => { 6 | module.exports.pubKey = await s3.readFile(keys.jwt.pubKey); 7 | module.exports.priKey = await s3.readFile(keys.jwt.priKey); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/middleware/v1/validator.js: -------------------------------------------------------------------------------- 1 | const _ = require("lodash"); 2 | 3 | module.exports = { 4 | 5 | checkIdentifiers: function(req, res, next){ 6 | if (_.isEmpty(req.identifiers)){ 7 | throw new Error("Missing \"flight_number\", \"mission_id\" or \"launch_library_id\""); 8 | } 9 | next(); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /src/middleware/v2/validator.js: -------------------------------------------------------------------------------- 1 | const _ = require("lodash"); 2 | 3 | module.exports = { 4 | 5 | checkIdentifiers: function(req, res, next){ 6 | if (_.isEmpty(req.identifiers)){ 7 | throw new Error("Missing \"flight_number\", \"mission_id\" or \"launch_library_2_id\""); 8 | } 9 | next(); 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /src/routes/info.js: -------------------------------------------------------------------------------- 1 | // Imports 2 | const express = require("express"); 3 | const info = require("../controllers/info"); 4 | // Create an express router 5 | const router = express.Router(); 6 | 7 | router.get("/", info.api); 8 | router.post("/", info.addApiInfo); 9 | router.put("/", info.updateApiInfo); 10 | 11 | 12 | module.exports = router; 13 | -------------------------------------------------------------------------------- /src/middleware/v1/launch_validator.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | checkLaunch: function(req, res, next){ 4 | if(!req.body){ 5 | throw new Error("Request body is missing"); 6 | } 7 | 8 | if (!req.body.company_id){ 9 | throw new Error("\"company_id\" is missing"); 10 | } 11 | next(); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /src/middleware/v2/launch_validator.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | checkLaunch: function(req, res, next){ 4 | if(!req.body){ 5 | throw new Error("Request body is missing"); 6 | } 7 | 8 | if (!req.body.company_id){ 9 | throw new Error("\"company_id\" is missing"); 10 | } 11 | next(); 12 | } 13 | }; 14 | -------------------------------------------------------------------------------- /src/routes/v1/live.js: -------------------------------------------------------------------------------- 1 | // Imports 2 | const express = require("express"); 3 | const live = require("../../controllers/v2/live"); 4 | // Create an express router 5 | const router = express.Router(); 6 | 7 | 8 | router.get("/info", live.info); 9 | router.get("/telemetry", live.telemetry); 10 | router.post("/reset", live.resetLiveEvent); 11 | 12 | 13 | module.exports = router; 14 | -------------------------------------------------------------------------------- /src/routes/v2/live.js: -------------------------------------------------------------------------------- 1 | // Imports 2 | const express = require("express"); 3 | const live = require("../../controllers/v2/live"); 4 | // Create an express router 5 | const router = express.Router(); 6 | 7 | 8 | router.get("/info", live.info); 9 | router.get("/telemetry", live.telemetry); 10 | router.post("/reset", live.resetLiveEvent); 11 | 12 | 13 | module.exports = router; 14 | -------------------------------------------------------------------------------- /src/middleware/error_handler.js: -------------------------------------------------------------------------------- 1 | function handleError(err, req, res, next){ 2 | 3 | if (err.status === undefined){ 4 | res. 5 | status(400). 6 | send({error: err.message}); 7 | }else{ 8 | res. 9 | status(err.status). 10 | send({error: err.message}); 11 | } 12 | 13 | next(); 14 | } 15 | 16 | module.exports = handleError; 17 | -------------------------------------------------------------------------------- /src/routes/v1/events.js: -------------------------------------------------------------------------------- 1 | // Import 2 | const express = require("express"); 3 | const events = require("../../controllers/v1/events"); 4 | const { checkIdentifiers } = require("../../middleware/v1/validator"); 5 | // Create an express router 6 | const router = express.Router(); 7 | 8 | 9 | // Get the events from a launch 10 | router.get("/:company", checkIdentifiers, events.getOne); 11 | 12 | 13 | module.exports = router; 14 | -------------------------------------------------------------------------------- /src/routes/v2/events.js: -------------------------------------------------------------------------------- 1 | // Import 2 | const express = require("express"); 3 | const events = require("../../controllers/v2/events"); 4 | const { checkIdentifiers } = require("../../middleware/v2/validator"); 5 | // Create an express router 6 | const router = express.Router(); 7 | 8 | 9 | // Get the events from a launch 10 | router.get("/:company", checkIdentifiers, events.getOne); 11 | 12 | 13 | module.exports = router; 14 | -------------------------------------------------------------------------------- /src/routes/v1/raw.js: -------------------------------------------------------------------------------- 1 | // Import 2 | const express = require("express"); 3 | const raw = require("../../controllers/v1/raw"); 4 | // middleware 5 | const { checkIdentifiers } = require("../../middleware/v1/validator"); 6 | // Create an express router 7 | const router = express.Router(); 8 | 9 | // Get the raw data from a launch 10 | router.get("/:company", checkIdentifiers, raw.getOne); 11 | 12 | 13 | module.exports = router; 14 | -------------------------------------------------------------------------------- /src/routes/v2/raw.js: -------------------------------------------------------------------------------- 1 | // Import 2 | const express = require("express"); 3 | const raw = require("../../controllers/v2/raw"); 4 | // middleware 5 | const { checkIdentifiers } = require("../../middleware/v2/validator"); 6 | // Create an express router 7 | const router = express.Router(); 8 | 9 | // Get the raw data from a launch 10 | router.get("/:company", checkIdentifiers, raw.getOne); 11 | 12 | 13 | module.exports = router; 14 | -------------------------------------------------------------------------------- /src/routes/v1/analysed.js: -------------------------------------------------------------------------------- 1 | // Import 2 | const express = require("express"); 3 | const analysed = require("../../controllers/v1/analysed"); 4 | const { checkIdentifiers } = require("../../middleware/v1/validator"); 5 | // Create an express router 6 | const router = express.Router(); 7 | 8 | 9 | // Get the analysed data from a launch 10 | router.get("/:company", checkIdentifiers, analysed.getOne); 11 | 12 | 13 | module.exports = router; 14 | -------------------------------------------------------------------------------- /src/routes/v2/analysed.js: -------------------------------------------------------------------------------- 1 | // Import 2 | const express = require("express"); 3 | const analysed = require("../../controllers/v2/analysed"); 4 | const { checkIdentifiers } = require("../../middleware/v2/validator"); 5 | // Create an express router 6 | const router = express.Router(); 7 | 8 | 9 | // Get the analysed data from a launch 10 | router.get("/:company", checkIdentifiers, analysed.getOne); 11 | 12 | 13 | module.exports = router; 14 | -------------------------------------------------------------------------------- /src/controllers/v1/info.js: -------------------------------------------------------------------------------- 1 | const Api = require("../../models/api"); 2 | 3 | 4 | 5 | module.exports = { 6 | versions: function(req, res, next){ 7 | Api.findOne({ }, "versions"). 8 | then(function(result){ 9 | if (!result) 10 | throw {status: 404, message: "Not Found"}; 11 | 12 | res.send(result.versions[0]); 13 | }).catch(next); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/controllers/v2/info.js: -------------------------------------------------------------------------------- 1 | const Api = require("../../models/api"); 2 | 3 | 4 | 5 | module.exports = { 6 | versions: function(req, res, next){ 7 | Api.findOne({}, "versions"). 8 | then(function(result){ 9 | if (!result) 10 | throw {status: 404, message: "Not Found"}; 11 | 12 | res.send(result.versions[1]); 13 | }).catch(next); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12 2 | 3 | # Create app directory 4 | WORKDIR /usr/src/app 5 | 6 | # Install app dependencies 7 | # A wildcard is used to ensure both package.json AND package-lock.json are copied 8 | # where available (npm@5+) 9 | COPY package*.json ./ 10 | 11 | RUN npm install 12 | # If you are building your code for production 13 | # RUN npm ci --only=production 14 | 15 | # Bundle app source 16 | COPY . . 17 | 18 | EXPOSE 3000 19 | CMD [ "node", "src/app.js" ] -------------------------------------------------------------------------------- /src/models/api.js: -------------------------------------------------------------------------------- 1 | const mongoose = require("mongoose"); 2 | const Schema = mongoose.Schema; 3 | 4 | 5 | const versionSchema = new Schema({ 6 | project_name: String, 7 | version: String, 8 | project_link: String, 9 | docs: String, 10 | organization: String, 11 | organization_link: String, 12 | description: String 13 | }, { _id : false }); 14 | 15 | 16 | const apiSchema = new Schema({ 17 | api_info: String, 18 | versions: [versionSchema] 19 | }); 20 | 21 | const Api = mongoose.model("api", apiSchema); 22 | 23 | module.exports = Api; 24 | -------------------------------------------------------------------------------- /src/helpers/cache_helper.js: -------------------------------------------------------------------------------- 1 | function doCache() { 2 | return process.env.caching != undefined; 3 | } 4 | 5 | module.exports = { 6 | doCache: doCache, 7 | 8 | add: (key, value, expire) => { 9 | if (doCache() && key && value){ 10 | global.REDIS_CLIENT.set(key, value); 11 | 12 | if (expire) 13 | global.REDIS_CLIENT.expire(key, expire); 14 | } 15 | }, 16 | 17 | get: async (key) => { 18 | if (key != undefined && doCache()) 19 | return await global.REDIS_CLIENT.get(key); 20 | 21 | return null; 22 | } 23 | }; -------------------------------------------------------------------------------- /src/routes/v1/company.js: -------------------------------------------------------------------------------- 1 | // Imports 2 | const express = require("express"); 3 | const company = require("../../controllers/v1/company"); 4 | // Create an express router 5 | const router = express.Router(); 6 | 7 | 8 | // Get all available data about every company in the database 9 | router.get("/", company.getAll); 10 | 11 | // Get all the available data about a company 12 | router.get("/:company", company.getOne); 13 | 14 | // Add a company to the database 15 | router.post("/", company.addOne); 16 | 17 | // Update company data 18 | router.put("/", company.updateOne); 19 | 20 | // Delete a company from the database 21 | router.delete("/:company", company.deleteOne); 22 | 23 | 24 | module.exports = router; 25 | -------------------------------------------------------------------------------- /src/routes/v2/company.js: -------------------------------------------------------------------------------- 1 | // Imports 2 | const express = require("express"); 3 | const company = require("../../controllers/v2/company"); 4 | // Create an express router 5 | const router = express.Router(); 6 | 7 | 8 | // Get all available data about every company in the database 9 | router.get("/", company.getAll); 10 | 11 | // Get all the available data about a company 12 | router.get("/:company", company.getOne); 13 | 14 | // Add a company to the database 15 | router.post("/", company.addOne); 16 | 17 | // Update company data 18 | router.put("/", company.updateOne); 19 | 20 | // Delete a company from the database 21 | router.delete("/:company", company.deleteOne); 22 | 23 | 24 | module.exports = router; 25 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "es6": true, 4 | "node": true, 5 | "jest": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "parserOptions": { 9 | "ecmaVersion": 2018, 10 | "sourceType": "module" 11 | }, 12 | "rules": { 13 | "linebreak-style": [ 14 | "error", 15 | "windows" 16 | ], 17 | "quotes": [ 18 | "error", 19 | "double" 20 | ], 21 | "semi": [ 22 | "error", 23 | "always" 24 | ], 25 | "no-unused-vars": [ 26 | "warn" 27 | ], 28 | "no-console": [ 29 | "off" 30 | ] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/auth/keys.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | google: { 3 | clientID: process.env.GOOGLE_CLIENT_ID, 4 | clientSecret: process.env.GOOGLE_CLIENT_SECRET 5 | }, 6 | 7 | mongodb: { 8 | connectionString: process.env.CONNECTION_STRING, 9 | }, 10 | 11 | s3: { 12 | region: process.env.AWS_S3_REGION, 13 | keyID: process.env.AWS_S3_KEY_ID, 14 | secretKey: process.env.AWS_S3_SECRET_KEY, 15 | bucketName: process.env.AWS_S3_BUCKET_NAME 16 | }, 17 | 18 | redis: { 19 | redisConnectionString: process.env.REDIS_CONNECTION_STRING 20 | }, 21 | 22 | jwt: { 23 | pubKey: process.env.JWT_PUBLIC_KEY, 24 | priKey: process.env.JWT_PRIVATE_KEY, 25 | passphrase: process.env.JWT_PASSPHRASE 26 | } 27 | 28 | }; 29 | 30 | -------------------------------------------------------------------------------- /src/controllers/v1/events.js: -------------------------------------------------------------------------------- 1 | const _ = require("lodash"); 2 | const mongoHelper = require("../../helpers/v1/mongo_helper"); 3 | const s3Helper = require("../../helpers/v1/s3_helper"); 4 | 5 | 6 | 7 | module.exports = { 8 | // Get the events of a specific launch 9 | getOne: async function(req, res, next){ 10 | try{ 11 | // Get the launch from the database 12 | let launchMetadata = await mongoHelper.findLaunchMetadata(req.params.company, req.identifiers); 13 | 14 | if (!launchMetadata){ 15 | throw {status: 404, message: "Not Found"}; 16 | } 17 | 18 | res.send(await s3Helper.getFile(launchMetadata.events_path)); 19 | }catch(err){ 20 | next(err, req, res, next); 21 | } 22 | } 23 | 24 | 25 | }; 26 | -------------------------------------------------------------------------------- /src/controllers/v2/events.js: -------------------------------------------------------------------------------- 1 | const _ = require("lodash"); 2 | const mongoHelper = require("../../helpers/v2/mongo_helper"); 3 | const s3Helper = require("../../helpers/v2/s3_helper"); 4 | 5 | 6 | 7 | module.exports = { 8 | // Get the events of a specific launch 9 | getOne: async function(req, res, next){ 10 | try{ 11 | // Get the launch from the database 12 | let launchMetadata = await mongoHelper.findLaunchMetadata(req.params.company, req.identifiers); 13 | 14 | if (!launchMetadata){ 15 | throw {status: 404, message: "Not Found"}; 16 | } 17 | 18 | res.send(await s3Helper.getFile(launchMetadata.events_path)); 19 | }catch(err){ 20 | next(err, req, res, next); 21 | } 22 | } 23 | 24 | 25 | }; 26 | -------------------------------------------------------------------------------- /src/middleware/confirm_auth.js: -------------------------------------------------------------------------------- 1 | const router = require("express").Router(); 2 | const jwt = require("jsonwebtoken"); 3 | const tokens = require("../auth/tokens"); 4 | 5 | function checkAuth(req, res, next){ 6 | let authorization = req.get("Authorization"); 7 | 8 | if (!authorization || authorization.split(" ").length < 2){ 9 | res. 10 | status(401). 11 | send({ error: "Unauthorized" }); 12 | return; 13 | } 14 | 15 | const token = authorization.split(" ")[1]; 16 | 17 | jwt.verify(token, tokens.pubKey, { algorithm: "RS256"}, (err, decoded) => { 18 | if (err){ 19 | res. 20 | status(401). 21 | send({ error: "Unauthorized" }); 22 | } 23 | else{ 24 | next(); 25 | } 26 | }); 27 | } 28 | 29 | router.post("*", checkAuth); 30 | router.put("*", checkAuth); 31 | router.delete("*", checkAuth); 32 | 33 | module.exports = router; 34 | -------------------------------------------------------------------------------- /src/controllers/info.js: -------------------------------------------------------------------------------- 1 | const Api = require("../models/api"); 2 | 3 | module.exports = { 4 | api: function(req, res, next){ 5 | Api.findOne({}, "api_info").then( 6 | function(result){ 7 | if (!result) 8 | throw {status: 404, message: "Not Found"}; 9 | 10 | res.send(result.api_info); 11 | }).catch(next); 12 | }, 13 | 14 | // Add API info to the database 15 | addApiInfo: function(req, res, next){ 16 | Api.create(req.body). 17 | then(function(result){ 18 | res.send(result); 19 | }). 20 | catch(next); 21 | }, 22 | 23 | updateApiInfo: function(req, res, next){ 24 | Api.findOneAndUpdate({}, req.body). 25 | then(function(result){ 26 | res.send(result); 27 | }). 28 | catch(next); 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /src/routes/auth-routes.js: -------------------------------------------------------------------------------- 1 | const router = require("express").Router(); 2 | const passport = require("passport"); 3 | const jwt = require("jsonwebtoken"); 4 | const tokens = require("../auth/tokens"); 5 | const keys = require("../auth/keys"); 6 | const moment = require("moment"); 7 | 8 | 9 | // Logging out 10 | router.get("/logout", (req, res, next) => { 11 | // Handle with passport.js 12 | req.logout(); 13 | res.redirect("/"); 14 | }); 15 | 16 | 17 | // Authentiacation with Google 18 | router.get("/google", passport.authenticate("google", { 19 | scope: ["profile"] 20 | })); 21 | 22 | 23 | // Callback uri 24 | router.get("/google/redirect", passport.authenticate("google", { session: false }), (req, res) => { 25 | const token = jwt.sign({ user: { id: req.user._id, username: req.user.username, date: moment().unix() } }, {key: tokens.priKey, passphrase: keys.jwt.passphrase}, { algorithm: "RS256"}); 26 | res.status(200).json({token}); 27 | }); 28 | 29 | 30 | module.exports = router; 31 | -------------------------------------------------------------------------------- /src/auth/passport-setup.js: -------------------------------------------------------------------------------- 1 | const passport = require("passport"); 2 | const GoogleStrategy = require("passport-google-oauth20"); 3 | const keys = require("./keys"); 4 | const User = require("../models/user-model"); 5 | const crypto = require("crypto"); 6 | 7 | function getId(id){ 8 | return crypto.createHash("sha256").update(id).digest("hex"); 9 | } 10 | 11 | passport.use( 12 | new GoogleStrategy({ 13 | callbackURL: "/auth/google/redirect", 14 | clientID: keys.google.clientID, 15 | clientSecret: keys.google.clientSecret 16 | }, (accessToken, refreshToken, profile, done) => { 17 | const userId = getId(profile.id); 18 | 19 | // Check if user already exists in the database 20 | User.findOne({googleId: userId}). 21 | then((result) => { 22 | if (result){ 23 | done(null, result, null); 24 | }else{ 25 | done(null, null, null); 26 | } 27 | }); 28 | 29 | }) 30 | ); 31 | -------------------------------------------------------------------------------- /test/other/database/connection.js: -------------------------------------------------------------------------------- 1 | // Import mongodb 2 | const mongoose = require("mongoose"); 3 | const keys = require("../../src/auth/keys"); 4 | 5 | 6 | 7 | 8 | // Connect to the database before any test run 9 | beforeAll(function(done){ 10 | global.CONNECTION_STRING = `mongodb+srv://${keys.mongodb.userID}:${keys.mongodb.userKey}@cluster0-q6hdl.mongodb.net/test?retryWrites=true`; 11 | // Connect to monsgoose and create/connect to the db 12 | 13 | mongoose.connect(global.CONNECTION_STRING, {useNewUrlParser: true}); 14 | 15 | mongoose.connection.once("open", function(){ 16 | console.log("Successfuly connected to the database!"); 17 | done(); 18 | }).on("error", function(err){ 19 | console.log(`Connection Error: ${err}`); 20 | }); 21 | }); 22 | 23 | 24 | // Close the connection to the database when the tests are done 25 | afterAll(async function(done){ 26 | console.log("Closed the connection to the database"); 27 | await mongoose.connection.close(); 28 | done(); 29 | }); 30 | -------------------------------------------------------------------------------- /test/controllers/v1/events.test0.js: -------------------------------------------------------------------------------- 1 | const request = require("supertest"); 2 | const app = require("../../../src/app"); 3 | 4 | 5 | test("Get the events of JASON-3", async () => { 6 | const response = await request(app).get("/v1/events/spacex?flight_number=26"); 7 | const telemetry = JSON.parse(response.text); 8 | 9 | expect(response.statusCode).toBe(200); 10 | 11 | expect(telemetry).toEqual([ 12 | { key: "maxq", time: 79 }, 13 | { key: "throttle_down_start", time: null }, 14 | { key: "throttle_down_end", time: null }, 15 | { key: "meco", time: 155 }, 16 | { key: "boostback_start", time: null }, 17 | { key: "boostback_end", time: null }, 18 | { key: "apogee", time: null }, 19 | { key: "entry_start", time: null }, 20 | { key: "entry_end", time: null }, 21 | { key: "landing_start", time: null }, 22 | { key: "landing_end", time: null }, 23 | { key: "ses1", time: 166 }, 24 | { key: "seco1", time: 545 }, 25 | { key: "ses2", time: null }, 26 | { key: "seco2", time: null } 27 | ]); 28 | }); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/controllers/v1/live.js: -------------------------------------------------------------------------------- 1 | const {cropTelemetry} = require("../../helpers/v1/telemetry_helper"); 2 | 3 | 4 | global.LIVE_TELEMETRY = { 5 | "raw": [], 6 | "analysed": [] 7 | }; 8 | 9 | 10 | const allowedEvents = ["raw", "analysed"]; 11 | 12 | 13 | module.exports = { 14 | info: function(req, res, next){ 15 | res.send({ 16 | telemetryType: allowedEvents 17 | }); 18 | }, 19 | 20 | 21 | telemetry: function(req, res, next){ 22 | if(!req.modifiers.type || !allowedEvents.includes(req.modifiers.type)){ 23 | throw {status: 404, message: "\"type\" not Found"}; 24 | } 25 | 26 | res.send(cropTelemetry( 27 | global.LIVE_TELEMETRY[req.modifiers.type], 28 | req.modifiers.start, 29 | req.modifiers.end, 30 | undefined, 31 | undefined 32 | )); 33 | }, 34 | 35 | 36 | resetLiveEvent: function(req, res, next){ 37 | Object.keys(global.LIVE_TELEMETRY).forEach(function(key){ 38 | global.LIVE_TELEMETRY[key] = []; 39 | }); 40 | res.send("reset live event"); 41 | } 42 | 43 | }; 44 | -------------------------------------------------------------------------------- /src/controllers/v2/live.js: -------------------------------------------------------------------------------- 1 | const {cropTelemetry} = require("../../helpers/v2/telemetry_helper"); 2 | 3 | 4 | global.LIVE_TELEMETRY = { 5 | "raw": [], 6 | "analysed": [] 7 | }; 8 | 9 | 10 | const allowedEvents = ["raw", "analysed"]; 11 | 12 | 13 | module.exports = { 14 | info: function(req, res, next){ 15 | res.send({ 16 | telemetryType: allowedEvents 17 | }); 18 | }, 19 | 20 | 21 | telemetry: function(req, res, next){ 22 | if(!req.modifiers.type || !allowedEvents.includes(req.modifiers.type)){ 23 | throw {status: 404, message: "\"type\" not Found"}; 24 | } 25 | 26 | res.send(cropTelemetry( 27 | global.LIVE_TELEMETRY[req.modifiers.type], 28 | req.modifiers.start, 29 | req.modifiers.end, 30 | undefined, 31 | undefined 32 | )); 33 | }, 34 | 35 | 36 | resetLiveEvent: function(req, res, next){ 37 | Object.keys(global.LIVE_TELEMETRY).forEach(function(key){ 38 | global.LIVE_TELEMETRY[key] = []; 39 | }); 40 | res.send("reset live event"); 41 | } 42 | 43 | }; 44 | -------------------------------------------------------------------------------- /src/routes/v1/launches.js: -------------------------------------------------------------------------------- 1 | // Import 2 | const express = require("express"); 3 | const launches = require("../../controllers/v1/launches"); 4 | // Create an express router 5 | const router = express.Router(); 6 | // middleware 7 | const { checkIdentifiers } = require("../../middleware/v1/validator"); 8 | const { checkLaunch } = require("../../middleware/v1/launch_validator"); 9 | 10 | 11 | // Get launch using launch_library_id 12 | router.get("/", launches.getLaunchFromLaunchLibraryProvider); 13 | 14 | // Get all the available data about a launch 15 | router.get("/:company", launches.getLaunches); 16 | 17 | // Get the latest launch of a launch provider 18 | router.get("/latest/:company", launches.getLatestLaunch); 19 | 20 | // Add a launch to the database 21 | router.post("/", checkLaunch, launches.addOne); 22 | 23 | // Update launch data 24 | router.put("/", checkLaunch, launches.updateOne); 25 | 26 | // Delete a launch from the database 27 | router.delete("/:company", checkIdentifiers, launches.deleteOne); 28 | 29 | // Get information about launches (all/single) 30 | router.get("/info/:company", launches.info); 31 | 32 | 33 | 34 | 35 | module.exports = router; 36 | -------------------------------------------------------------------------------- /src/routes/v2/launches.js: -------------------------------------------------------------------------------- 1 | // Import 2 | const express = require("express"); 3 | const launches = require("../../controllers/v2/launches"); 4 | // Create an express router 5 | const router = express.Router(); 6 | // middleware 7 | const { checkIdentifiers } = require("../../middleware/v2/validator"); 8 | const { checkLaunch } = require("../../middleware/v2/launch_validator"); 9 | 10 | 11 | // Get launch using launch_library_id 12 | router.get("/", launches.getLaunchFromLaunchLibraryProvider); 13 | 14 | // Get all the available data about a launch 15 | router.get("/:company", launches.getLaunches); 16 | 17 | // Get the latest launch of a launch provider 18 | router.get("/latest/:company", launches.getLatestLaunch); 19 | 20 | // Add a launch to the database 21 | router.post("/", checkLaunch, launches.addOne); 22 | 23 | // Update launch data 24 | router.put("/", checkLaunch, launches.updateOne); 25 | 26 | // Delete a launch from the database 27 | router.delete("/:company", checkIdentifiers, launches.deleteOne); 28 | 29 | // Get information about launches (all/single) 30 | router.get("/info/:company", launches.info); 31 | 32 | 33 | 34 | 35 | module.exports = router; 36 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Pull Request Steps 2 | 3 | **NOTE**: Make sure every change is covered by a unit test. 4 | 5 | 1. Fork the repo, create a new branch from `develop`, make changes, add tests, commit, and submit a Pull Request (to `develop`) 6 | 7 | 2. New Pull Requests will automatically trigger a Travis CI Build 8 | 9 | 3. If the build fails, look at the [Build Logs](https://travis-ci.org/shahar603/Launch-Dashboard-API/). 10 | Changes will not be merged unless the build passes 11 | 12 | 4. If the build succeeds, the pull request will be reviewed, and when merged will be automatically 13 | pushed to the production server at [api.launchdashboard.space](https://api.launchdashboard.space) 14 | 15 | ## Suggestions for new features, data or telemetry 16 | 17 | Add a comment with your addition to on the [Features, Data and Telemetry Suggestions](https://github.com/shahar603/Launch-Dashboard-API/issues/17) issue 18 | 19 | ## Corrections 20 | 21 | Open a new issue on [Launch-Dashboard-API/issues](https://github.com/shahar603/Launch-Dashboard-API/issues) 22 | 23 | ## Have any questions? 24 | 25 | Open a new issue [here](https://github.com/shahar603/Launch-Dashboard-API/issues/). Please mark it with a 'Question' tag. 26 | -------------------------------------------------------------------------------- /test/middleware/middleware.test.js: -------------------------------------------------------------------------------- 1 | // Import mocha and assert to unit test the database 2 | const assert = require("assert"); 3 | const requestSplitter = require("../../src/middleware/v1/request_splitting"); 4 | // Equality function 5 | const _ = require("lodash"); 6 | 7 | describe("Testing the middleware", function(){ 8 | 9 | it("Basic object splitting", function(done){ 10 | let req = {query: {mission_id: "crs-12", start: 1234}}; 11 | let res = {}; 12 | let next = function(){ 13 | assert(_.isEqual(req.identifiers, {mission_id: "crs-12"}) && 14 | _.isEqual(req.modifyers, {start: 1234})); 15 | }; 16 | 17 | requestSplitter(req, res, next); 18 | done(); 19 | }); 20 | 21 | 22 | it("More complex object splitting", function(done){ 23 | let req = {query: {mission_id: "crs-13", flight_number: 1337, finish: 1235, start: 1234}}; 24 | let res = {}; 25 | let next = function(){ 26 | assert(_.isEqual(req.identifiers, {mission_id: "crs-13", flight_number: 1337}) && 27 | _.isEqual(req.modifyers, { finish: 1235, start: 1234})); 28 | }; 29 | 30 | requestSplitter(req, res, next); 31 | done(); 32 | }); 33 | 34 | 35 | }); 36 | -------------------------------------------------------------------------------- /src/controllers/v1/raw.js: -------------------------------------------------------------------------------- 1 | const {getTelemetry} = require("../../helpers/v1/telemetry_helper"); 2 | const cacheHelper = require("../../helpers/cache_helper"); 3 | 4 | 5 | 6 | 7 | module.exports = { 8 | // Get the raw telemetry from a specific launch 9 | getOne: async (req, res, next) => { 10 | const cacheKey = `raw:${JSON.stringify(req.identifiers)}`; 11 | 12 | try{ 13 | if (!req.modifiers) { 14 | let result = await cacheHelper.get(cacheKey); 15 | 16 | if (result){ 17 | res.type("json").send(result); 18 | return; 19 | } 20 | } 21 | 22 | let out = await getTelemetry("raw", req.params.company, req.identifiers, req.modifiers); 23 | 24 | if (!out) 25 | throw new Error("Telemetry data is unavailable (even thought it should be)\nPlease report this issue on the Launch Dashboard API GitHub Repository"); 26 | 27 | if (req.modifiers === {}){ 28 | cacheHelper.add(cacheKey, JSON.stringify(out), 60); 29 | } 30 | 31 | res.send(out); 32 | }catch(err){ 33 | next(err, req, res, next); 34 | } 35 | 36 | } 37 | 38 | 39 | }; 40 | -------------------------------------------------------------------------------- /src/controllers/v2/raw.js: -------------------------------------------------------------------------------- 1 | const {getTelemetry} = require("../../helpers/v2/telemetry_helper"); 2 | const cacheHelper = require("../../helpers/cache_helper"); 3 | 4 | 5 | 6 | 7 | module.exports = { 8 | // Get the raw telemetry from a specific launch 9 | getOne: async (req, res, next) => { 10 | const cacheKey = `raw:${JSON.stringify(req.identifiers)}`; 11 | 12 | try{ 13 | if (!req.modifiers) { 14 | let result = await cacheHelper.get(cacheKey); 15 | 16 | if (result){ 17 | res.type("json").send(result); 18 | return; 19 | } 20 | } 21 | 22 | let out = await getTelemetry("raw", req.params.company, req.identifiers, req.modifiers); 23 | 24 | if (!out) 25 | throw new Error("Telemetry data is unavailable (even thought it should be)\nPlease report this issue on the Launch Dashboard API GitHub Repository"); 26 | 27 | if (req.modifiers === {}){ 28 | cacheHelper.add(cacheKey, JSON.stringify(out), 60); 29 | } 30 | 31 | res.send(out); 32 | }catch(err){ 33 | next(err, req, res, next); 34 | } 35 | 36 | } 37 | 38 | 39 | }; 40 | -------------------------------------------------------------------------------- /src/controllers/v1/analysed.js: -------------------------------------------------------------------------------- 1 | const {getTelemetry} = require("../../helpers/v1/telemetry_helper"); 2 | const cacheHelper = require("../../helpers/cache_helper"); 3 | 4 | module.exports = { 5 | // Get the analysed telemetry from a specific launch 6 | getOne: async function(req, res, next){ 7 | const cacheKey = `analysed:${JSON.stringify(req.identifiers)}`; 8 | 9 | try{ 10 | if (!req.modifiers) { 11 | let result = await cacheHelper.get(cacheKey); 12 | 13 | if (result){ 14 | res.type("json").send(result); 15 | return; 16 | } 17 | } 18 | 19 | let out = await getTelemetry("analysed", req.params.company, req.identifiers, req.modifiers); 20 | 21 | if (!out) 22 | throw new Error("Telemetry data is unavailable (even thought it should be)\nPlease report this issue on the Launch Dashboard API GitHub Repository"); 23 | 24 | if (req.modifiers === {}){ 25 | cacheHelper.add(cacheKey, JSON.stringify(out), 60); 26 | } 27 | 28 | res.send(out); 29 | }catch(err){ 30 | next(err, req, res, next); 31 | } 32 | } 33 | 34 | 35 | }; 36 | -------------------------------------------------------------------------------- /src/controllers/v2/analysed.js: -------------------------------------------------------------------------------- 1 | const {getTelemetry} = require("../../helpers/v2/telemetry_helper"); 2 | const cacheHelper = require("../../helpers/cache_helper"); 3 | 4 | 5 | module.exports = { 6 | // Get the analysed telemetry from a specific launch 7 | getOne: async function(req, res, next){ 8 | const cacheKey = `analysed:${JSON.stringify(req.identifiers)}`; 9 | 10 | try{ 11 | if (!req.modifiers) { 12 | let result = await cacheHelper.get(cacheKey); 13 | 14 | if (result){ 15 | res.type("json").send(result); 16 | return; 17 | } 18 | } 19 | 20 | let out = await getTelemetry("analysed", req.params.company, req.identifiers, req.modifiers); 21 | 22 | if (!out) 23 | throw new Error("Telemetry data is unavailable (even thought it should be)\nPlease report this issue on the Launch Dashboard API GitHub Repository"); 24 | 25 | if (req.modifiers === {}){ 26 | cacheHelper.add(cacheKey, JSON.stringify(out), 60); 27 | } 28 | 29 | res.send(out); 30 | }catch(err){ 31 | next(err, req, res, next); 32 | } 33 | } 34 | 35 | 36 | }; 37 | -------------------------------------------------------------------------------- /src/models/v1/company.js: -------------------------------------------------------------------------------- 1 | // Import mongoose and create a Schema 2 | const mongoose = require("mongoose"); 3 | // Get the Schema object from mongoose 4 | const Schema = mongoose.Schema; 5 | 6 | 7 | // Schema of Launch document 8 | const LaunchFileSchema = new Schema({ 9 | mission_id: { 10 | type: String, 11 | required: true 12 | }, 13 | name: { 14 | type: String, 15 | required: true 16 | }, 17 | flight_number: { 18 | type: Number, 19 | required: true, 20 | min: 0 21 | }, 22 | launch_library_id: { 23 | type: Number, 24 | required: true, 25 | min: 0 26 | }, 27 | remark: String, 28 | raw_path: String, 29 | analysed_path: String, 30 | events_path: String 31 | }); 32 | 33 | 34 | // Schema of Company document 35 | const CompanySchema = new Schema({ 36 | company_id: { 37 | type: String, 38 | required: true 39 | }, 40 | name: { 41 | type: String, 42 | required: true 43 | }, 44 | lsp: { 45 | type: Number, 46 | required: false 47 | }, 48 | launches: [LaunchFileSchema] 49 | }, { collection: "company"}); 50 | 51 | const Company = mongoose.model("company", CompanySchema); 52 | module.exports = Company; 53 | -------------------------------------------------------------------------------- /src/models/v2/company.js: -------------------------------------------------------------------------------- 1 | // Import mongoose and create a Schema 2 | const mongoose = require("mongoose"); 3 | // Get the Schema object from mongoose 4 | const Schema = mongoose.Schema; 5 | 6 | 7 | // Schema of Launch document 8 | const LaunchFileSchema = new Schema({ 9 | mission_id: { 10 | type: String, 11 | required: true 12 | }, 13 | name: { 14 | type: String, 15 | required: true 16 | }, 17 | flight_number: { 18 | type: Number, 19 | required: true, 20 | min: 0 21 | }, 22 | launch_library_2_id: { 23 | type: String, 24 | required: true, 25 | min: 0 26 | }, 27 | remark: String, 28 | raw_path: String, 29 | analysed_path: String, 30 | events_path: String 31 | }); 32 | 33 | 34 | // Schema of Company document 35 | const CompanySchema = new Schema({ 36 | company_id: { 37 | type: String, 38 | required: true 39 | }, 40 | name: { 41 | type: String, 42 | required: true 43 | }, 44 | lsp: { 45 | type: Number, 46 | required: false 47 | }, 48 | launches: [LaunchFileSchema] 49 | }, { collection: "companyV2"}); 50 | 51 | 52 | const Company = mongoose.model("companyV2", CompanySchema); 53 | module.exports = Company; 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "launch-dashboard-api", 3 | "version": "1.0.0", 4 | "description": "A REST API for rocket Telemetry", 5 | "main": "src/app.js", 6 | "scripts": { 7 | "test": "jest", 8 | "start": "node src/app.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/shahar603/Launch-Dashboard-API" 13 | }, 14 | "author": "Shahar603", 15 | "license": "ISC", 16 | "dependencies": { 17 | "aws-sdk": "^2.1130.0", 18 | "bluebird": "^3.7.2", 19 | "config": "^3.3.7", 20 | "content-filter": "^1.1.2", 21 | "express": "^4.18.1", 22 | "ioredis": "^4.28.5", 23 | "jest": "^26.6.3", 24 | "joi": "^14.3.1", 25 | "joigoose": "^7.1.2", 26 | "jsonwebtoken": "^8.5.1", 27 | "lodash": "^4.17.21", 28 | "moment": "^2.29.3", 29 | "mongoose": "^6.0.7", 30 | "morgan": "^1.10.0", 31 | "passport": "^0.4.1", 32 | "passport-google-oauth20": "^2.0.0", 33 | "request": "^2.88.2", 34 | "request-promise": "^4.2.6", 35 | "socket.io": "^4.5.0", 36 | "supertest": "^4.0.2" 37 | }, 38 | "devDependencies": { 39 | "acorn": "^7.4.1", 40 | "babel-eslint": "^10.1.0", 41 | "eslint": "^6.8.0", 42 | "eslint-config-google": "^0.14.0", 43 | "eslint-plugin-import": "^2.26.0", 44 | "eslint-plugin-mocha": "^6.3.0", 45 | "eslint-plugin-react": "^7.29.4" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/models/v1/launch.js: -------------------------------------------------------------------------------- 1 | const Joi = require("joi"); 2 | 3 | const rawTelemetry = Joi.object().keys({ 4 | time: Joi.number().required(), 5 | velocity: Joi.number().required(), 6 | altitude: Joi.number().required() 7 | }); 8 | 9 | const analysedTelemetry = Joi.object().keys({ 10 | time: Joi.number(), 11 | velocity: Joi.number(), 12 | altitude: Joi.number(), 13 | velocity_y: Joi.number(), 14 | velocity_x: Joi.number(), 15 | acceleration: Joi.number(), 16 | downrange_distance: Joi.number(), 17 | angle: Joi.number(), 18 | q: Joi.number() 19 | }); 20 | 21 | const eventData = Joi.object().keys({ 22 | key: Joi.string().required(), 23 | time: Joi.number().required().allow(null) 24 | }); 25 | 26 | const rawData = Joi.object().keys({ 27 | stage: Joi.number().integer().min(0), 28 | telemetry: Joi.array().items(rawTelemetry) 29 | }); 30 | 31 | const analysedData = Joi.object().keys({ 32 | stage: Joi.number().integer().min(0), 33 | telemetry: Joi.array().items(analysedTelemetry) 34 | }); 35 | 36 | const SpaceXLaunchFile = Joi.object({ 37 | company_id: Joi.string().required(), 38 | mission_id: Joi.string().required(), 39 | flight_number: Joi.number().integer().positive().required(), 40 | launch_library_id: Joi.number().integer().required(), 41 | name: Joi.string().required(), 42 | raw: Joi.array().items(rawData), 43 | analysed: Joi.array().items(analysedData), 44 | events: Joi.array().items(eventData) 45 | }); 46 | 47 | module.exports = SpaceXLaunchFile; 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/models/v2/launch.js: -------------------------------------------------------------------------------- 1 | const Joi = require("joi"); 2 | 3 | 4 | 5 | const rawTelemetry = Joi.object().keys({ 6 | time: Joi.number().required(), 7 | velocity: Joi.number().required(), 8 | altitude: Joi.number().required() 9 | }); 10 | 11 | 12 | const analysedTelemetry = Joi.object().keys({ 13 | time: Joi.number(), 14 | velocity: Joi.number(), 15 | altitude: Joi.number(), 16 | velocity_y: Joi.number(), 17 | velocity_x: Joi.number(), 18 | acceleration: Joi.number(), 19 | downrange_distance: Joi.number(), 20 | angle: Joi.number(), 21 | q: Joi.number() 22 | }); 23 | 24 | 25 | const eventData = Joi.object().keys({ 26 | key: Joi.string().required(), 27 | time: Joi.number().required().allow(null) 28 | }); 29 | 30 | 31 | const rawData = Joi.object().keys({ 32 | stage: Joi.number().integer().min(0), 33 | telemetry: Joi.array().items(rawTelemetry) 34 | }); 35 | 36 | 37 | const analysedData = Joi.object().keys({ 38 | stage: Joi.number().integer().min(0), 39 | telemetry: Joi.array().items(analysedTelemetry) 40 | }); 41 | 42 | 43 | const SpaceXLaunchFile = Joi.object({ 44 | company_id: Joi.string().required(), 45 | mission_id: Joi.string().required(), 46 | flight_number: Joi.number().integer().positive().required(), 47 | launch_library_id: Joi.number().integer().required(), 48 | name: Joi.string().required(), 49 | raw: Joi.array().items(rawData), 50 | analysed: Joi.array().items(analysedData), 51 | events: Joi.array().items(eventData) 52 | }); 53 | 54 | module.exports = SpaceXLaunchFile; 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/helpers/v1/mongo_helper.js: -------------------------------------------------------------------------------- 1 | const Company = require("../../models/v1/company"); 2 | 3 | module.exports = { 4 | findLaunchMetadata: async function(company_id, identifiers){ 5 | 6 | function launchExists(identifiers, launch){ 7 | let match = true; 8 | 9 | for(let key in identifiers){ 10 | match = match && (identifiers[key] === launch[key]); 11 | } 12 | 13 | return match; 14 | } 15 | 16 | 17 | let launches = await Company.findOne({company_id: company_id}, "launches"); 18 | 19 | for(let i = 0; i < launches.launches.length; i++){ 20 | if (launchExists(identifiers, launches.launches[i])){ 21 | return launches.launches[i]; 22 | } 23 | } 24 | 25 | return undefined; 26 | }, 27 | 28 | 29 | addLaunchMetadata: async function(company_id, launch){ 30 | return await Company.findOneAndUpdate({company_id: company_id}, {$push: {launches: launch}}); 31 | }, 32 | 33 | 34 | updateLaunchMetadata: async function(company_id, identifiers, launch){ 35 | return await Company.findOneAndUpdate({company_id: company_id, launches: identifiers}, {$set: {launches: launch}}); 36 | }, 37 | 38 | 39 | deleteLaunchMetadata: async function(company_id, identifiers){ 40 | return await Company.findOneAndUpdate({company_id: company_id}, {$pull: {launches: identifiers}}); 41 | }, 42 | 43 | findLaunch: function findLaunch(identifiers, launch){ 44 | let match = true; 45 | 46 | Object.keys(identifiers).forEach(function(key) { 47 | if (identifiers[key] != launch[key]) 48 | match = false; 49 | }); 50 | 51 | return match; 52 | } 53 | 54 | }; 55 | -------------------------------------------------------------------------------- /src/helpers/v2/mongo_helper.js: -------------------------------------------------------------------------------- 1 | const Company = require("../../models/v2/company"); 2 | 3 | module.exports = { 4 | findLaunchMetadata: async function(company_id, identifiers){ 5 | 6 | function launchExists(identifiers, launch){ 7 | let match = true; 8 | 9 | for(let key in identifiers){ 10 | match = match && (identifiers[key] === launch[key]); 11 | } 12 | 13 | return match; 14 | } 15 | 16 | 17 | let launches = await Company.findOne({company_id: company_id}, "launches"); 18 | 19 | for(let i = 0; i < launches.launches.length; i++){ 20 | if (launchExists(identifiers, launches.launches[i])){ 21 | return launches.launches[i]; 22 | } 23 | } 24 | 25 | return undefined; 26 | }, 27 | 28 | 29 | addLaunchMetadata: async function(company_id, launch){ 30 | return await Company.findOneAndUpdate({company_id: company_id}, {$push: {launches: launch}}); 31 | }, 32 | 33 | 34 | updateLaunchMetadata: async function(company_id, identifiers, launch){ 35 | return await Company.findOneAndUpdate({company_id: company_id, launches: identifiers}, {$set: {launches: launch}}); 36 | }, 37 | 38 | 39 | deleteLaunchMetadata: async function(company_id, identifiers){ 40 | return await Company.findOneAndUpdate({company_id: company_id}, {$pull: {launches: identifiers}}); 41 | }, 42 | 43 | findLaunch: function findLaunch(identifiers, launch){ 44 | let match = true; 45 | 46 | Object.keys(identifiers).forEach(function(key) { 47 | if (identifiers[key] != launch[key]) 48 | match = false; 49 | }); 50 | 51 | return match; 52 | } 53 | 54 | }; 55 | -------------------------------------------------------------------------------- /src/middleware/v2/request_splitting.js: -------------------------------------------------------------------------------- 1 | // Require the utilities function 2 | const Joi = require("joi"); 3 | 4 | // name of url paramters used to identify keys from the database 5 | let identifierKeys = Joi.object({ 6 | "mission_id": Joi.string(), 7 | "flight_number": Joi.number().integer().positive(), 8 | "launch_library_2_id": Joi.string() 9 | }).options({ stripUnknown: true }); 10 | 11 | 12 | // name of url paramters used to modify lookup 13 | let modifierKeys = Joi.object({ 14 | "type": Joi.string(), 15 | "event": Joi.string(), 16 | "start": Joi.alternatives().try(Joi.string(), Joi.number()), 17 | "end": Joi.alternatives().try(Joi.string(), Joi.number()), 18 | "stage": Joi.number().integer().positive(), 19 | "start_offset": Joi.number(), 20 | "end_offset": Joi.number(), 21 | "event_offset": Joi.number(), 22 | "event_window": Joi.number().positive(), 23 | "frame_rate": Joi.number().positive(), 24 | "interval": Joi.number().positive() 25 | }).options({ stripUnknown: true }); 26 | 27 | async function validate(req){ 28 | req.identifiers = await Joi.validate(req.query, identifierKeys); 29 | req.modifiers = await Joi.validate(req.query, modifierKeys); 30 | return req; 31 | } 32 | 33 | // Split the query parameters to two objects 34 | // identifiers: keys used to find data in the database 35 | // modifiers: keys used to filter the data 36 | function requestSplitter(req, res, next){ 37 | validate(req).then(() => { 38 | next(); 39 | }).catch((err) => { 40 | if (res !== undefined && 41 | res !== null && 42 | res.status !== undefined && 43 | res.send !== undefined){ 44 | res. 45 | status(400). 46 | send({ error: err.details[0].message }); 47 | } 48 | }); 49 | } 50 | 51 | // Export the function 52 | module.exports = requestSplitter; 53 | -------------------------------------------------------------------------------- /src/middleware/v1/request_splitting.js: -------------------------------------------------------------------------------- 1 | // Require the utilities function 2 | const Joi = require("joi"); 3 | 4 | // name of url paramters used to identify keys from the database 5 | let identifierKeys = Joi.object({ 6 | "mission_id": Joi.string(), 7 | "flight_number": Joi.number().integer().positive(), 8 | "launch_library_id": Joi.number().integer().positive() 9 | }).options({ stripUnknown: true }); 10 | 11 | // name of url paramters used to modify lookup 12 | let modifierKeys = Joi.object({ 13 | "type": Joi.string(), 14 | "event": Joi.string(), 15 | "start": Joi.alternatives().try(Joi.string(), Joi.number()), 16 | "end": Joi.alternatives().try(Joi.string(), Joi.number()), 17 | "stage": Joi.number().integer().positive(), 18 | "start_offset": Joi.number(), 19 | "end_offset": Joi.number(), 20 | "event_offset": Joi.number(), 21 | "event_window": Joi.number().positive(), 22 | "frame_rate": Joi.number().positive(), 23 | "interval": Joi.number().positive() 24 | }).options({ stripUnknown: true }); 25 | 26 | async function validate(req){ 27 | req.identifiers = await Joi.validate(req.query, identifierKeys); 28 | req.modifiers = await Joi.validate(req.query, modifierKeys); 29 | return req; 30 | } 31 | 32 | // Split the query parameters to two objects 33 | // identifiers: keys used to find data in the database 34 | // modifiers: keys used to filter the data 35 | function requestSplitter(req, res, next){ 36 | validate(req).then(() => { 37 | next(); 38 | }).catch((err) => { 39 | if (res !== undefined && 40 | res !== null && 41 | res.status !== undefined && 42 | res.send !== undefined){ 43 | res. 44 | status(400). 45 | send({ error: err.details[0].message }); 46 | } 47 | }); 48 | } 49 | 50 | // Export the function 51 | module.exports = requestSplitter; 52 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. Triggers the workflow on push or pull request 6 | # events but only for the main branch 7 | on: 8 | push: 9 | branches: [ master ] 10 | pull_request: 11 | branches: [ master ] 12 | 13 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 14 | jobs: 15 | deploy-to-digital-ocean-droplet: 16 | runs-on: ubuntu-latest 17 | name: Deploy App 18 | steps: 19 | - name: Checkout main 20 | uses: actions/checkout@v2 21 | - name: Publish to Github Packages Registry 22 | uses: elgohr/Publish-Docker-Github-Action@master 23 | with: 24 | name: shahar603/launch-dashboard-api/launch_dashboard_api 25 | registry: docker.pkg.github.com 26 | username: ${{ secrets.USERNAME }} 27 | password: ${{ secrets.GITHUB_TOKEN }} 28 | dockerfile: Dockerfile 29 | tags: latest 30 | 31 | - name: Deploy package to digitalocean 32 | uses: appleboy/ssh-action@master 33 | env: 34 | GITHUB_USERNAME: ${{ secrets.USERNAME }} 35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 36 | with: 37 | host: ${{ secrets.HOST_IP }} 38 | username: ${{ secrets.HOST_USERNAME }} 39 | password: ${{ secrets.HOST_PASSWORD }} 40 | port: ${{ secrets.HOST_PORT }} 41 | envs: GITHUB_USERNAME, GITHUB_TOKEN 42 | script: | 43 | /ci/clean_docker.sh 44 | docker login docker.pkg.github.com -u $GITHUB_USERNAME -p $GITHUB_TOKEN 45 | docker pull docker.pkg.github.com/shahar603/launch-dashboard-api/launch_dashboard_api:latest 46 | docker run --env-file ~/env/env.list -dit -p 3000:3000 --restart on-failure docker.pkg.github.com/shahar603/launch-dashboard-api/launch_dashboard_api:latest 47 | -------------------------------------------------------------------------------- /test/controllers/v1/launches.test0.js: -------------------------------------------------------------------------------- 1 | const request = require("supertest"); 2 | const app = require("../../../src/app"); 3 | 4 | beforeAll((done) => { 5 | app.on("ready", () => { 6 | done(); 7 | }); 8 | }); 9 | 10 | 11 | test("Get info for all launches", async (done) => { 12 | const response = await request(app).get("/v1/launches"); 13 | expect(response.statusCode).toBe(422); 14 | done(); 15 | }); 16 | 17 | 18 | test("Get all telemetry of JASON-3", async () => { 19 | const response = await request(app).get("/v1/launches/spacex?flight_number=26"); 20 | const telemetry = JSON.parse(response.text); 21 | 22 | expect(response.statusCode).toBe(200); 23 | expect(new Set(Object.keys(telemetry))).toEqual(new Set(["mission_id", "name", "flight_number", "raw", "analysed", "events"])); 24 | 25 | expect(telemetry.raw.length).toBe(1); 26 | expect(telemetry.raw[0].stage).toBe(2); 27 | expect(telemetry.raw[0].telemetry.length).toBe(34795); 28 | 29 | expect(telemetry.analysed.length).toBe(1); 30 | expect(telemetry.analysed[0].stage).toBe(2); 31 | expect(telemetry.analysed[0].telemetry.length).toBe(557); 32 | 33 | 34 | expect(new Set(Object.keys(telemetry.raw[0].telemetry[0]))).toEqual(new Set(["time", "velocity", "altitude"])); 35 | expect(new Set(Object.keys(telemetry.analysed[0].telemetry[0]))).toEqual(new Set(["time", "velocity", "altitude", 36 | "velocity_x", "velocity_y", "acceleration", "downrange_distance", "angle", "q"])); 37 | 38 | expect(telemetry.events).toEqual([ 39 | { key: "maxq", time: 79 }, 40 | { key: "throttle_down_start", time: null }, 41 | { key: "throttle_down_end", time: null }, 42 | { key: "meco", time: 155 }, 43 | { key: "boostback_start", time: null }, 44 | { key: "boostback_end", time: null }, 45 | { key: "apogee", time: null }, 46 | { key: "entry_start", time: null }, 47 | { key: "entry_end", time: null }, 48 | { key: "landing_start", time: null }, 49 | { key: "landing_end", time: null }, 50 | { key: "ses1", time: 166 }, 51 | { key: "seco1", time: 545 }, 52 | { key: "ses2", time: null }, 53 | { key: "seco2", time: null } 54 | ]); 55 | }); 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/controllers/v1/company.js: -------------------------------------------------------------------------------- 1 | const Company = require("../../models/v1/company"); 2 | const Joi = require("joi"); 3 | 4 | async function getCompany(company_id){ 5 | return await Company.findOne({company_id: company_id}, "-_id company_id name lsp"); 6 | } 7 | 8 | module.exports = { 9 | exists: async function(company_id){ 10 | return await getCompany(company_id) != null; 11 | }, 12 | 13 | 14 | getAll: async function(req, res, next){ 15 | 16 | if(req.query.lsp){ 17 | try{ 18 | // Returned the parsed lsp value. Throws a validation exception if given lsp value isn't a positive integer 19 | const lsp = await Joi.validate(req.query.lsp, Joi.number().positive().integer()); 20 | 21 | const result = await Company.findOne({lsp: lsp}, "-_id company_id name lsp"); 22 | 23 | if (result) 24 | res.send(result); 25 | else 26 | throw {status: 404, message: `Company with lsp="${lsp}" does not exist`}; 27 | }catch(ex){ 28 | next(ex); 29 | } 30 | } 31 | else{ 32 | res.send(await Company.find({}, "-_id company_id name lsp")); 33 | } 34 | }, 35 | 36 | getOne: async function(req, res, next){ 37 | try{ 38 | const result = getCompany(req.params.company); 39 | 40 | if (!result) 41 | throw {status: 404, message: `Company "${req.params.company}" does not exist`}; 42 | 43 | res.send(result); 44 | }catch(ex){ 45 | next(ex); 46 | } 47 | }, 48 | 49 | 50 | addOne: async function(req, res, next){ 51 | try{ 52 | let result = await Company.create(req.body); 53 | 54 | if (!result){ 55 | throw {status: 500, message: "Failed to create company"}; 56 | } 57 | 58 | res.send(result); 59 | }catch(ex){ 60 | next(ex); 61 | } 62 | }, 63 | 64 | 65 | updateOne: async function(req, res, next){ 66 | try{ 67 | let result = await Company.findOneAndUpdate(req.body); 68 | 69 | if (!result){ 70 | throw {status: 500, message: "Failed to update company"}; 71 | } 72 | 73 | res.send(result); 74 | }catch(ex){ 75 | next(ex); 76 | } }, 77 | 78 | deleteOne: async function(req, res, next){ 79 | res.send(await Company.findOneAndDelete({company_id: req.params.company})); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/controllers/v2/company.js: -------------------------------------------------------------------------------- 1 | const Company = require("../../models/v2/company"); 2 | const Joi = require("joi"); 3 | 4 | async function getCompany(company_id){ 5 | return await Company.findOne({company_id: company_id}, "-_id company_id name lsp"); 6 | } 7 | 8 | module.exports = { 9 | exists: async function(company_id){ 10 | return await getCompany(company_id) != null; 11 | }, 12 | 13 | 14 | getAll: async function(req, res, next){ 15 | 16 | if(req.query.lsp){ 17 | try{ 18 | // Returned the parsed lsp value. Throws a validation exception if given lsp value isn't a positive integer 19 | const lsp = await Joi.validate(req.query.lsp, Joi.number().positive().integer()); 20 | 21 | const result = await Company.findOne({lsp: lsp}, "-_id company_id name lsp"); 22 | 23 | if (result) 24 | res.send(result); 25 | else 26 | throw {status: 404, message: `Company with lsp="${lsp}" does not exist`}; 27 | }catch(ex){ 28 | next(ex); 29 | } 30 | } 31 | else{ 32 | res.send(await Company.find({}, "-_id company_id name lsp")); 33 | } 34 | }, 35 | 36 | getOne: async function(req, res, next){ 37 | try{ 38 | const result = getCompany(req.params.company); 39 | 40 | if (!result) 41 | throw {status: 404, message: `Company "${req.params.company}" does not exist`}; 42 | 43 | res.send(result); 44 | }catch(ex){ 45 | next(ex); 46 | } 47 | }, 48 | 49 | 50 | addOne: async function(req, res, next){ 51 | try{ 52 | let result = await Company.create(req.body); 53 | 54 | if (!result){ 55 | throw {status: 500, message: "Failed to create company"}; 56 | } 57 | 58 | res.send(result); 59 | }catch(ex){ 60 | next(ex); 61 | } 62 | }, 63 | 64 | 65 | updateOne: async function(req, res, next){ 66 | try{ 67 | let result = await Company.findOneAndUpdate(req.body); 68 | 69 | if (!result){ 70 | throw {status: 500, message: "Failed to update company"}; 71 | } 72 | 73 | res.send(result); 74 | }catch(ex){ 75 | next(ex); 76 | } }, 77 | 78 | deleteOne: async function(req, res, next){ 79 | res.send(await Company.findOneAndDelete({company_id: req.params.company})); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at help@launchdashboard.space. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /src/helpers/v1/s3_helper.js: -------------------------------------------------------------------------------- 1 | // This file contains functions to put, get, update and delete data from the AWS S3 bucket 2 | const AWS = require("aws-sdk"); 3 | const Keys = require("../../auth/keys"); 4 | 5 | // configuring the AWS environment 6 | AWS.config.update({ 7 | region: Keys.s3.region, 8 | accessKeyId: Keys.s3.keyID, 9 | secretAccessKey: Keys.s3.secretKey 10 | }); 11 | 12 | // create an S3 instance 13 | let s3 = new AWS.S3(); 14 | 15 | async function readFile(key){ 16 | let params = { 17 | Bucket: Keys.s3.bucketName, 18 | Key: key, 19 | }; 20 | 21 | try{ 22 | let obj = await s3.getObject(params).promise(); 23 | return obj.Body.toString("utf-8"); 24 | } catch(e){ 25 | console.log(e); 26 | return undefined; 27 | } 28 | } 29 | 30 | // This function gets the content of the file with name 'key' from the S3 bucket 31 | async function getFile(key){ 32 | let params = { 33 | Bucket: Keys.s3.bucketName, 34 | Key: key, 35 | }; 36 | 37 | try{ 38 | let obj = await s3.getObject(params).promise(); 39 | return JSON.parse(obj.Body.toString("utf-8")); 40 | } catch(e){ 41 | console.log(e); 42 | return undefined; 43 | } 44 | } 45 | 46 | // This function writes content as a file to the S3 bucket 47 | async function addFile(content, targetPath){ 48 | let params = { 49 | Bucket: Keys.s3.bucketName, 50 | Body : JSON.stringify(content), 51 | Key : targetPath, 52 | ContentType: "application/json" 53 | }; 54 | 55 | return await s3.upload(params).promise(); 56 | } 57 | 58 | // This function deletes a file named 'key' from the S3 bucket 59 | async function deleteFile(key){ 60 | let params = { 61 | Bucket: Keys.s3.bucketName, 62 | Key: key 63 | }; 64 | 65 | return await s3.deleteObject(params).promise(); 66 | } 67 | 68 | async function getOneLaunch(launch){ 69 | let rawData, analysedData, eventData; 70 | 71 | rawData = await getFile(launch.raw_path); 72 | analysedData = await getFile(launch.analysed_path); 73 | eventData = await getFile(launch.events_path); 74 | 75 | return {rawData: rawData, 76 | analysedData: analysedData, 77 | eventData: eventData}; 78 | } 79 | 80 | async function addOneLaunch(launch){ 81 | let rawPath, analysedPath, eventsPath; 82 | 83 | let launchMetadata = { 84 | mission_id: launch.mission_id, 85 | name: launch.name, 86 | flight_number: launch.flight_number, 87 | launch_library_id: launch.launch_library_id 88 | }; 89 | 90 | 91 | rawPath = await addFile(launch.raw, `Telemetry/raw-${launch.company_id}-${launchMetadata.flight_number}.json`); 92 | analysedPath = await addFile(launch.analysed, `Telemetry/analysed-${launch.company_id}-${launchMetadata.flight_number}.json`); 93 | eventsPath = await addFile(launch.events, `Telemetry/events-${launch.company_id}-${launchMetadata.flight_number}.json`); 94 | 95 | if (rawPath) 96 | launchMetadata.raw_path = rawPath.key; 97 | if (analysedPath) 98 | launchMetadata.analysed_path = analysedPath.key; 99 | if (eventsPath) 100 | launchMetadata.events_path = eventsPath.key; 101 | 102 | return launchMetadata; 103 | } 104 | 105 | async function deleteOneLaunch(launch){ 106 | await deleteFile(launch.raw_path); 107 | await deleteFile(launch.analysed_path); 108 | await deleteFile(launch.events_path); 109 | } 110 | 111 | async function updateOneLaunch(launch){ 112 | if (deleteOneLaunch(launch)) 113 | return addOneLaunch(launch); 114 | 115 | return undefined; 116 | } 117 | 118 | module.exports = { 119 | readFile: readFile, 120 | getFile: getFile, 121 | getOneLaunch: getOneLaunch, 122 | addOneLaunch: addOneLaunch, 123 | updateOneLaunch: updateOneLaunch, 124 | deleteOneLaunch: deleteOneLaunch 125 | }; 126 | -------------------------------------------------------------------------------- /src/helpers/v2/s3_helper.js: -------------------------------------------------------------------------------- 1 | // This file contains functions to put, get, update and delete data from the AWS S3 bucket 2 | const AWS = require("aws-sdk"); 3 | const Keys = require("../../auth/keys"); 4 | 5 | // configuring the AWS environment 6 | AWS.config.update({ 7 | region: Keys.s3.region, 8 | accessKeyId: Keys.s3.keyID, 9 | secretAccessKey: Keys.s3.secretKey 10 | }); 11 | 12 | // create an S3 instance 13 | let s3 = new AWS.S3(); 14 | 15 | async function readFile(key){ 16 | let params = { 17 | Bucket: Keys.s3.bucketName, 18 | Key: key, 19 | }; 20 | 21 | try{ 22 | let obj = await s3.getObject(params).promise(); 23 | return obj.Body.toString("utf-8"); 24 | } catch(e){ 25 | console.log(e); 26 | return undefined; 27 | } 28 | } 29 | 30 | // This function gets the content of the file with name 'key' from the S3 bucket 31 | async function getFile(key){ 32 | let params = { 33 | Bucket: Keys.s3.bucketName, 34 | Key: key, 35 | }; 36 | 37 | try{ 38 | let obj = await s3.getObject(params).promise(); 39 | return JSON.parse(obj.Body.toString("utf-8")); 40 | } catch(e){ 41 | console.log(e); 42 | return undefined; 43 | } 44 | } 45 | 46 | // This function writes content as a file to the S3 bucket 47 | async function addFile(content, targetPath){ 48 | let params = { 49 | Bucket: Keys.s3.bucketName, 50 | Body : JSON.stringify(content), 51 | Key : targetPath, 52 | ContentType: "application/json" 53 | }; 54 | 55 | return await s3.upload(params).promise(); 56 | } 57 | 58 | // This function deletes a file named 'key' from the S3 bucket 59 | async function deleteFile(key){ 60 | let params = { 61 | Bucket: Keys.s3.bucketName, 62 | Key: key 63 | }; 64 | 65 | return await s3.deleteObject(params).promise(); 66 | } 67 | 68 | async function getOneLaunch(launch){ 69 | let rawData, analysedData, eventData; 70 | 71 | rawData = await getFile(launch.raw_path); 72 | analysedData = await getFile(launch.analysed_path); 73 | eventData = await getFile(launch.events_path); 74 | 75 | return {rawData: rawData, 76 | analysedData: analysedData, 77 | eventData: eventData}; 78 | } 79 | 80 | async function addOneLaunch(launch){ 81 | let rawPath, analysedPath, eventsPath; 82 | 83 | let launchMetadata = { 84 | mission_id: launch.mission_id, 85 | name: launch.name, 86 | flight_number: launch.flight_number, 87 | launch_library_2_id: launch.launch_library_2_id 88 | }; 89 | 90 | 91 | rawPath = await addFile(launch.raw, `Telemetry/raw-${launch.company_id}-${launchMetadata.flight_number}.json`); 92 | analysedPath = await addFile(launch.analysed, `Telemetry/analysed-${launch.company_id}-${launchMetadata.flight_number}.json`); 93 | eventsPath = await addFile(launch.events, `Telemetry/events-${launch.company_id}-${launchMetadata.flight_number}.json`); 94 | 95 | if (rawPath) 96 | launchMetadata.raw_path = rawPath.key; 97 | if (analysedPath) 98 | launchMetadata.analysed_path = analysedPath.key; 99 | if (eventsPath) 100 | launchMetadata.events_path = eventsPath.key; 101 | 102 | return launchMetadata; 103 | } 104 | 105 | async function deleteOneLaunch(launch){ 106 | await deleteFile(launch.raw_path); 107 | await deleteFile(launch.analysed_path); 108 | await deleteFile(launch.events_path); 109 | } 110 | 111 | async function updateOneLaunch(launch){ 112 | if (deleteOneLaunch(launch)) 113 | return addOneLaunch(launch); 114 | 115 | return undefined; 116 | } 117 | 118 | module.exports = { 119 | readFile: readFile, 120 | getFile: getFile, 121 | getOneLaunch: getOneLaunch, 122 | addOneLaunch: addOneLaunch, 123 | updateOneLaunch: updateOneLaunch, 124 | deleteOneLaunch: deleteOneLaunch 125 | }; 126 | -------------------------------------------------------------------------------- /test/other/database/finding_test.js: -------------------------------------------------------------------------------- 1 | // Import bluebird for promises 2 | const Promise = require("bluebird"); 3 | // Import the Launch model 4 | const Launch = Promise.promisifyAll(require("../../src/models/launch")); 5 | // Import lodash for utility functions 6 | const _ = require("lodash"); 7 | 8 | 9 | 10 | // Create a series of tests 11 | describe("Finding record", function(){ 12 | 13 | // Find one record from the database using the mission_id 14 | test("Finds one record from the database by mission_id", function(done){ 15 | Launch.findOne({ mission_id: "orbcomm-og2-m2" }).then(function(result){ 16 | expect(result.name).toBe("Orbcomm OG2"); 17 | done(); 18 | }); 19 | }); 20 | 21 | 22 | 23 | 24 | // Test the flight number property by searching a record using it 25 | it("Finds one record from the database by flight number", function(done){ 26 | Launch.findOne({ flight_number: 45 }).then(function(result){ 27 | expect(result.name).toBe("SpaceX CRS-12"); 28 | done(); 29 | }); 30 | }); 31 | 32 | 33 | 34 | 35 | // Finds one record from the database by flight number and mission_id 36 | it("Finds one record from the database by flight number and mission_id", function(done){ 37 | Launch.findOne({ mission_id: "crs-12", flight_number: 45 }).then(function(result){ 38 | expect(result.name).toBe("SpaceX CRS-12"); 39 | done(); 40 | }); 41 | }); 42 | 43 | 44 | 45 | // Give incorrect mission_id and correct flight number 46 | it("Find nothing due to incorrect mission_id and correct flight number", function(done){ 47 | Launch.findOne({ mission_id: "Hello", flight_number: 45 }).then(function(result){ 48 | expect(result).toBeNull(); 49 | done(); 50 | }); 51 | }); 52 | 53 | 54 | 55 | // Give correct mission_id and incorrect flight number 56 | it("Find nothing due to correct mission_id and incorrect flight number", function(done){ 57 | Launch.findOne({ mission_id: "crs-12", flight_number: 11111 }).then(function(result){ 58 | expect(result).toBeNull(); 59 | done(); 60 | }); 61 | }); 62 | 63 | 64 | 65 | // Mongo check if flight_number is undefined 66 | it("Find nothing due to correct mission_id but undefined flight_number", function(done){ 67 | Launch.findOne({ mission_id: "crs-12", flight_number: undefined }).then(function(result){ 68 | expect(result).toBeNull(); 69 | done(); 70 | }); 71 | }); 72 | 73 | 74 | 75 | // Add additional keys that are not in the database and find nothing 76 | it("Add additional keys that are not in the database and find nothing", function(done){ 77 | Launch.findOne({ mission_id: "crs-12", start: 1234 }).then(function(result){ 78 | expect(result).toBeNull(); 79 | done(); 80 | }); 81 | }); 82 | 83 | 84 | // Remove additional keys and find an element 85 | it("Remove additional keys and find an element", function(done){ 86 | Launch.findOne(_.pick({ mission_id: "crs-12", start: 1234 }, ["mission_id"])).then(function(result){ 87 | expect(result.name).toBe("SpaceX CRS-12"); 88 | done(); 89 | }); 90 | }); 91 | 92 | 93 | 94 | // Test case sensitivity of strings in mongo 95 | it("Is mongo case sensetive?", function(done){ 96 | Launch.findOne({ mission_id: "CRS-12", flight_number: 11111 }).then(function(result){ 97 | expect(result).toBeNull(); 98 | done(); 99 | }); 100 | }); 101 | 102 | 103 | 104 | 105 | 106 | // Does find returns an array? 107 | it("Perform find that returns one element and check if it is an array", function(done){ 108 | Launch.find({ mission_id: "crs-12" }).then(function(result){ 109 | expect(Array.isArray(result)).toBe(true); 110 | done(); 111 | }); 112 | }); 113 | 114 | 115 | 116 | // Does find returns an array? 117 | it("Check whether select perform selection of the elements in the array", function(done){ 118 | Launch.find({}, "mission_id name flight_number").then(function(result){ 119 | let firstResultKeys = Object.keys(result[0].toObject()); 120 | 121 | ["_id", "mission_id", "name", "flight_number"].forEach(elm => expect(firstResultKeys.includes(elm)).toBe(true)); 122 | done(); 123 | }); 124 | }); 125 | 126 | 127 | 128 | }); 129 | -------------------------------------------------------------------------------- /test/other/routes/launches_test.js: -------------------------------------------------------------------------------- 1 | // Import assert to unit test the database 2 | const assert = require("assert"); 3 | const controller = require("../../src/controllers/launches"); 4 | 5 | 6 | /* 7 | 8 | // Create a series of tests 9 | describe("Testing the /launches endpoint is returning the correct data", function(){ 10 | 11 | 12 | // Make sure the info returns only metadata fields 13 | it("launches/info with empty res returns all the launches without the telemetry", function(done){ 14 | let req = {}; 15 | 16 | let res = { 17 | send: function(launches) { 18 | for(let i = 0; i < launches.length; i++){ 19 | Object.keys(launches[i].toJSON()).forEach((val) => assert(["_id", "name", "mission_id", "flight_number"].includes(val))); 20 | } 21 | 22 | for(let i = 0; i < launches.length - 1; i++){ 23 | assert(launches[i].toJSON().flight_number < launches[i+1].toJSON().flight_number); 24 | } 25 | 26 | assert(launches[0].mission_id === "orbcomm-og2-m2"); 27 | assert(launches[0].name === "Orbcomm OG2"); 28 | assert(launches[0].flight_number === 25); 29 | done(); 30 | }, 31 | next: function(err){ 32 | assert(false); 33 | done(); 34 | } 35 | }; 36 | 37 | controller.info(req, res, null); 38 | }); 39 | 40 | 41 | 42 | it("launches/info with a valid flight_number returns a single launch with telemetry", function(done){ 43 | let req = { 44 | identifiers: { 45 | flight_number: 25 46 | } 47 | }; 48 | 49 | let res = { 50 | send: function(res) { 51 | let launch = res.toJSON(); 52 | let objKeys = ["_id", "name", "mission_id", "flight_number"]; 53 | Object.keys(launch).forEach((val) => assert(objKeys.includes(val))); 54 | assert(objKeys.length === Object.keys(launch).length); 55 | assert(launch.mission_id === "orbcomm-og2-m2"); 56 | assert(launch.name === "Orbcomm OG2"); 57 | assert(launch.flight_number === 25); 58 | done(); 59 | }, 60 | next: function(err){ 61 | assert(false); 62 | done(); 63 | } 64 | }; 65 | 66 | controller.info(req, res, null); 67 | }); 68 | 69 | 70 | 71 | 72 | it("launches/info with an invalid flight_number shoud returns a 404 not found error", function(done){ 73 | let req = { 74 | identifiers: { 75 | flight_number: 123456 76 | } 77 | }; 78 | 79 | let res = { 80 | send: function(res) { 81 | assert(false); 82 | done(); 83 | } 84 | }; 85 | 86 | 87 | controller.info(req, res, function(err){ 88 | assert(err.status === 404); 89 | done(); 90 | }); 91 | }); 92 | 93 | 94 | 95 | it("launches/info with a valid mission_id returns a single launch with telemetry", function(done){ 96 | let req = { 97 | identifiers: { 98 | mission_id: "orbcomm-og2-m2" 99 | } 100 | }; 101 | 102 | let res = { 103 | send: function(res) { 104 | let launch = res.toJSON(); 105 | let objKeys = ["_id", "name", "mission_id", "flight_number"]; 106 | Object.keys(launch).forEach((val) => assert(objKeys.includes(val))); 107 | assert(objKeys.length === Object.keys(launch).length); 108 | assert(launch.mission_id === "orbcomm-og2-m2"); 109 | assert(launch.name === "Orbcomm OG2"); 110 | assert(launch.flight_number === 25); 111 | done(); 112 | }, 113 | next: function(err){ 114 | assert(false); 115 | done(); 116 | } 117 | }; 118 | 119 | controller.info(req, res, null); 120 | }); 121 | 122 | 123 | 124 | 125 | 126 | 127 | it("launches/info with contradicting mission_id and flight_number returns a 404 error", function(done){ 128 | let req = { 129 | identifiers: { 130 | mission_id: "orbcomm-og2-m2", 131 | flight_number: 123 132 | } 133 | }; 134 | 135 | let res = { 136 | send: function(res) { 137 | assert(false); 138 | done(); 139 | }, 140 | next: function(err){ 141 | assert(false); 142 | done(); 143 | } 144 | }; 145 | 146 | controller.info(req, res, function(err){ 147 | assert(err.status === 404); 148 | done(); 149 | }); 150 | }); 151 | }); 152 | 153 | */ 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Launch Dashboard API

2 |

An Open Source REST API of Rocket Launch telemetry

3 | 4 |

5 | 6 | 7 | 8 | 9 | 10 | 11 |

12 | 13 |

14 | 15 |

16 | 17 |

Architecture

18 | 19 |

20 | 21 |

22 | 23 | 24 | 25 | 26 |

27 | Documentation - 28 | Clients - 29 | Apps - 30 | Data Sources 31 |

32 | 33 | 34 | ## Usage 35 | 36 | ```javascript 37 | curl -s http://api.launchdashboard.space/v2/launches/latest/spacex | jq 38 | ``` 39 | 40 | ```javascript 41 | { 42 | "mission_id": "amos-17", 43 | "name": "Amos-17", 44 | "flight_number": 84, 45 | "launch_library_2_id": "68ecf878-2491-4209-95fe-54f74fb163aa", 46 | "raw": [ 47 | { 48 | "stage": 2, 49 | "telemetry": [ 50 | { "time": 0, "velocity": 0.277, "altitude": 0 }, 51 | { "time": 0.033, "velocity": 0.555, "altitude": 0 }, 52 | { "time": 0.067, "velocity": 0.555, "altitude": 0}, 53 | { "time": 0.1, "velocity": 0.555, "altitude": 0 }, 54 | { "time": 0.133, "velocity": 0.555, "altitude": 0 }, 55 | { "time": 0.166, "velocity": 0.833, "altitude": 0 }, 56 | { "time": 0.2, "velocity": 0.833, "altitude": 0 }, 57 | ... 58 | { "time": 1918.333, "velocity": 9518.333, "altitude": 525}, 59 | { "time": 1918.366, "velocity": 9518.333, "altitude": 525} 60 | ] 61 | } 62 | ], 63 | "analysed": [ 64 | { 65 | "stage": 2, 66 | "telemetry": [ 67 | { "time": 0, "velocity": 0.277, "altitude": 0, "velocity_y": -0.219, "velocity_x": 0.168, "acceleration": 10.698 "downrange_distance": 0, "angle": 90, "q": 0.046996480116054146 }, 68 | { "time": 1, "velocity": 1.97, "altitude": 0.001, "velocity_y": 2.039, "velocity_x": -0.038, "acceleration": 11.285, "downrange_distance": 0, "angle": 90, "q": 2.3762015600538513 }, 69 | ... 70 | { "time": 506, "velocity": 7424.372, "altitude": 163.85, "velocity_y": -32.707, "velocity_x": 7424.301, "acceleration": -0.016, "downrange_distance": 1585.22, "angle": -0.252, "q": 0 } 71 | ] 72 | } 73 | ], 74 | "events": [ 75 | { "key": "maxq", "time": 66 }, 76 | { "key": "throttle_down_start", "time": 45 }, 77 | { "key": "throttle_down_end", "time": 91 }, 78 | { "key": "meco", "time": 169 }, 79 | ... 80 | ] 81 | } 82 | ``` 83 | 84 | ## Contributions 85 | 86 | See [Contributing.md](https://github.com/shahar603/Launch-Dashboard-API/blob/master/CONTRIBUTING.md) for contribution instructions. 87 | 88 | 89 | ## Derivative Content and Features 90 | Content and features made using Launch Dashboard API 91 | 92 | |Content|Description|Creator|Preview| 93 | |-----|-----|----|---| 94 | |[Comparison between Falcon 9 Block 4 and Block 5 ASDS landings](https://www.reddit.com/r/spacex/comments/af7bco/iridium_8_telemetry_comparison_between_block_4/) | A post I've written comparing two versions (blocks) of the Falcon 9 rocket and how the latest upgrades have allowed SpaceX to reuse block 5 much faster than block 4 by reducing the stress from launch to landing | [Shahar603](https://github.com/shahar603) | | 95 | |[Starlink-18 First Stage Telemetry and Analysis](https://www.reddit.com/r/spacex/comments/ldkz7c/starlink18_first_stage_telemetry_and_analysis/)| My analysis of first stage telemetry of a SpaceX Starlink (SpaceX's internet constellation) launch|[Shahar603](https://github.com/shahar603)|| 96 | |[Unplanned engine shutdown of Falcon 9 rocket](https://www.reddit.com/r/SpaceXLounge/comments/fkpb37/starlink5_telemetry_confirming_the_early_engine/)|Acceleration graph from LD API affirmed the space community suspicion of an unplanned engine shutdown during Falcon 9 ascent. Later confirmed by [Elon Musk on twitter](https://twitter.com/elonmusk/status/1240262636547100672)|[Shahar603](https://github.com/shahar603)|| 97 | |[Webcast vs Simulation graph](https://twitter.com/flightclubio/status/1131794017606340608)|Used by trajectory creators on FlightClub.io to compare their simulations to the real telemetry|[u/TheVehicleDestroyer](https://www.reddit.com/user/thevehicledestroyer)|| 98 | |[Aerodynamic Pressure animation](https://twitter.com/JcAsHcan/status/1188125678396493825)|An animation of the aerodynamic pressure(Q) during ascent on the AMOS-17 mission|[Jake Hannan](https://twitter.com/JcAsHcan)|| 99 | |[Mission Creation](https://twitter.com/flightclubio/status/1344060720892166146)|A tools that builds a flight plan that matches the webcast telemetry in the API |[u/TheVehicleDestroyer](https://www.reddit.com/user/thevehicledestroyer)|| 100 | |[Falcon Boosters' Entry Energy Comparison](https://www.reddit.com/r/spacex/comments/elzp52/falcon_boosters_entry_energy_comparison/)|A diagram that compares total energy of the Falcon boosters' reentry energy|[Shahar603](https://github.com/shahar603)|| 101 | 102 | 103 | 104 | ## Apps and Clients 105 | Clients and Apps using Launch Dashboard API 106 | 107 | |Client|Description|Creator| 108 | |-----|-----|----| 109 | |[FlightClub.io](https://www2.flightclub.io/)|Rocket Launch Simulation and Visualization|[u/TheVehicleDestroyer](https://www.reddit.com/user/thevehicledestroyer)| 110 | |[.NET Launch Dashboard](https://github.com/Tearth/.NET-Launch-Dashboard)| .NET Wrapper for Launch Dashboard API|[Tearth](https://github.com/Tearth) 111 | 112 | ## Data Sources 113 | The Source of the data in the API 114 | 115 | |Launch Provider|Tool|Creator| 116 | |----|-----|-----| 117 | |SpaceX|[SpaceXtract](https://github.com/shahar603/SpaceXtract)|[Shahar603](https://github.com/shahar603)| 118 | |RocketLab|[SpaceXtract](https://github.com/shahar603/SpaceXtract)|[Shahar603](https://github.com/shahar603)| 119 | |Arianespace|[VideoTelemetryParser](https://github.com/Togusa09/VideoTelemetryParser)|[Hitura-Nobad](https://www.reddit.com/user/hitura-nobad/)| 120 | 121 | 122 | -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | // Import packages 2 | const express = require("express"); 3 | const mongoose = require("mongoose"); 4 | const socketio = require("socket.io"); 5 | const Redis = require("ioredis"); 6 | // Import middleware 7 | const bodyParser = require("body-parser"); 8 | const requestSplitterV1 = require("./middleware/v1/request_splitting"); 9 | const requestSplitterV2 = require("./middleware/v2/request_splitting"); 10 | const errorHandler = require("./middleware/error_handler"); 11 | const filter = require("content-filter"); 12 | // Live 13 | const live = require("./routes/v1/live"); 14 | const info = require("./routes/info"); 15 | // Import routes V1 16 | const launchesV1 = require("./routes/v1/launches"); 17 | const rawV1 = require("./routes/v1/raw"); 18 | const analysedV1 = require("./routes/v1/analysed"); 19 | const eventsV1 = require("./routes/v1/events"); 20 | const infoV1 = require("./routes/v1/info"); 21 | const companyV1 = require("./routes/v1/company"); 22 | // Import routes V2 23 | const launchesV2 = require("./routes/v2/launches"); 24 | const rawV2 = require("./routes/v2/raw"); 25 | const analysedV2 = require("./routes/v2/analysed"); 26 | const eventsV2 = require("./routes/v2/events"); 27 | const infoV2 = require("./routes/v2/info"); 28 | const companyV2 = require("./routes/v2/company"); 29 | // Authentication imports 30 | const confirmAuth = require("./middleware/confirm_auth"); 31 | const authRoutes = require("./routes/auth-routes"); 32 | const keys = require("./auth/keys"); 33 | const passport = require("passport"); 34 | const passportSetup = require("./auth/passport-setup"); 35 | const cacheHelper = require("./helpers/cache_helper"); 36 | const tokens = require("./auth/tokens"); 37 | const jwt = require("jsonwebtoken"); 38 | var morgan = require("morgan"); 39 | process.setMaxListeners(0); 40 | 41 | global.REDIS_CONNECTION_STRING = keys.redis.redisConnectionString; 42 | global.CONNECTION_STRING = keys.mongodb.connectionString; 43 | 44 | // Create an express app 45 | const app = express(); 46 | 47 | 48 | 49 | // create and connect redis client to Elasticache instance. 50 | if (cacheHelper.doCache()){ 51 | global.REDIS_CLIENT = new Redis({ 52 | port: 6379, 53 | host: global.REDIS_CONNECTION_STRING, 54 | reconnectOnError: function (err) { 55 | const targetError = "READONLY"; 56 | if (err.message.slice(0, targetError.length) === targetError) { 57 | // Only reconnect when the error starts with "READONLY" 58 | return true; // or `return 1;` 59 | } 60 | } 61 | }); 62 | 63 | 64 | // Print redis errors to the console 65 | global.REDIS_CLIENT.on("error", (err) => { 66 | console.log("Error " + err); 67 | }); 68 | 69 | // Print redis connection to the console 70 | global.REDIS_CLIENT.on("connect", (err) => { 71 | console.log("Connected to Redis"); 72 | }); 73 | } 74 | 75 | 76 | 77 | 78 | // ######################### AUTHENTICATION TOKEN ################### 79 | 80 | // Initialize passport 81 | app.use(passport.initialize()); 82 | 83 | 84 | // ##################### MIDDLEWARE ##################### 85 | 86 | // Alllow post requests with a lot of telemetry 87 | app.use(bodyParser.json({limit: "10mb"})); 88 | // support parsing of application/x-www-form-urlencoded post data 89 | app.use(bodyParser.urlencoded({limit: "10mb", extended: true})); 90 | // Validate the input to prevent NoSQL injection 91 | app.use(filter()); 92 | // If the user tries to modify the database, make sure he/she is authenticated 93 | app.use("*", confirmAuth); 94 | // Allow Cross Origin Requests 95 | app.use(function(req, res, next) { 96 | res.header("Access-Control-Allow-Origin", "*"); 97 | res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); 98 | next(); 99 | }); 100 | 101 | 102 | 103 | 104 | // Logging the request (without identifing details); 105 | app.use(morgan(function (tokens, req, res) { 106 | return [ 107 | "[", tokens.date(req, res), "]", 108 | tokens.method(req, res), 109 | tokens.url(req, res), 110 | tokens.status(req, res), 111 | tokens.res(req, res, "content-length"), "-", 112 | tokens["response-time"](req, res), "ms" 113 | ].join(" "); 114 | })); 115 | 116 | // ##################### ROUTES ##################### 117 | 118 | app.use("/", info); 119 | 120 | app.all("/v1/*", requestSplitterV1); 121 | // Use the routes we set up on routes/api.js 122 | app.use("/v1/company", companyV1); 123 | app.use("/v1/launches", launchesV1); 124 | app.use("/v1/raw", rawV1); 125 | app.use("/v1/analysed", analysedV1); 126 | app.use("/v1/events", eventsV1); 127 | app.use("/v1/live", live); 128 | app.use("/v1", infoV1); 129 | 130 | app.all("/v2/*", requestSplitterV2); 131 | // Use the routes we set up on routes/api.js 132 | app.use("/v2/company", companyV2); 133 | app.use("/v2/launches", launchesV2); 134 | app.use("/v2/raw", rawV2); 135 | app.use("/v2/analysed", analysedV2); 136 | app.use("/v2/events", eventsV2); 137 | app.use("/v2/live", live); 138 | app.use("/v2", infoV2); 139 | 140 | // set up authentiacation routes 141 | app.use("/auth", authRoutes); 142 | 143 | 144 | // ##################### ERROR HANDLING ##################### 145 | 146 | 147 | // this is default in case of unmatched routes 148 | app.use(function(req, res) { 149 | throw {status: 404, message: `path "${req.path}" does not exist`}; 150 | }); 151 | 152 | // Promise rejection handling 153 | app.use(errorHandler); 154 | 155 | 156 | module.exports = app; 157 | 158 | 159 | (async function(){ 160 | mongoose.connect(global.CONNECTION_STRING, {useNewUrlParser: true}); 161 | tokens.setKeys(); 162 | 163 | function setupLive(){ 164 | // Start the server on port 3000 165 | const server = app.listen(process.env.PORT || 3000, () => { 166 | app.emit("ready"); 167 | console.log("Running on port 3000"); 168 | }); 169 | 170 | 171 | const allowedEvents = ["raw", "analysed"]; 172 | const io = socketio(server); 173 | 174 | io.on("connect", async function(socket){ 175 | console.log("Made a connection", socket.id); 176 | 177 | try{ 178 | const user = await jwt.verify(socket.handshake.query.access_token, tokens.pubKey, { algorithm: "RS256"}); 179 | socket.user = user; 180 | }catch(ex){ 181 | console.log(ex); 182 | } 183 | 184 | 185 | 186 | function registerEvent(event){ 187 | socket.on(event, function(data) { 188 | if (Object.keys(global.LIVE_TELEMETRY).includes(event)){ 189 | global.LIVE_TELEMETRY[event].push(data); 190 | socket.broadcast.to(event).emit(event, data); 191 | } 192 | }); 193 | } 194 | 195 | socket.on("register", function(events) { 196 | if (!socket.user) return; 197 | 198 | events.forEach((event) => { 199 | if (allowedEvents.includes(event)) 200 | registerEvent(event); 201 | }); 202 | }); 203 | 204 | 205 | socket.on("join", function(rooms) { 206 | rooms.forEach((room) => { 207 | if (allowedEvents.includes(room)) 208 | socket.join(room); 209 | }); 210 | }); 211 | 212 | }); 213 | 214 | } 215 | setupLive(); 216 | })(); 217 | -------------------------------------------------------------------------------- /src/helpers/v1/telemetry_helper.js: -------------------------------------------------------------------------------- 1 | const _ = require("lodash"); 2 | const s3Helper = require("./s3_helper"); 3 | const mongoHelper = require("./mongo_helper"); 4 | 5 | function eventsToStartEnd(events, modifiers){ 6 | let start, end, eventTime; 7 | 8 | if (!events || !modifiers) 9 | return {}; 10 | 11 | function eventToTime(eventsArray, event){ 12 | // If event is not defined then return 'undefined' 13 | if(!event) 14 | return undefined; 15 | 16 | // If the 'event' is not a number then try to find the event 17 | if (_.isNaN(Number(event))){ 18 | let res = _.find(eventsArray, {key: event}); 19 | 20 | if (!res) 21 | return undefined; 22 | 23 | return res.time; 24 | } 25 | 26 | // 'event' is a number 27 | return Number(event); 28 | } 29 | 30 | 31 | // Get time values for "start", "end" and "eventTime" 32 | start = eventToTime(events, modifiers.start); 33 | end = eventToTime(events, modifiers.end); 34 | eventTime = eventToTime(events, modifiers.event); 35 | 36 | // Check the validity of "start", "end" and "eventTime" 37 | if (start === undefined && modifiers.start !== undefined) 38 | throw new Error("\"start\" has to a number or an event"); 39 | else if (start === null && modifiers.start !== undefined) 40 | throw new Error(`event ${modifiers.start} of "start" is not set in the data`); 41 | 42 | if (end === undefined && modifiers.end !== undefined) 43 | throw new Error("\"end\" has to a number or an event"); 44 | else if (end === null && modifiers.end !== undefined) 45 | throw new Error(`event ${modifiers.end} of "end" is not set in the data`); 46 | 47 | if (eventTime === undefined && modifiers.event !== undefined) 48 | throw new Error("\"event\" has to a number or an event"); 49 | else if (eventTime === null && modifiers.event !== undefined) 50 | throw new Error(`event ${modifiers.event} of "event" is not set in the data`); 51 | 52 | if (start && end && start > end) 53 | throw new Error("\"end\" cannot be smaller than \"start\""); 54 | 55 | 56 | // Apply offset modifiers 57 | if (start !== undefined && modifiers.start_offset != undefined){ 58 | start += modifiers.start_offset; 59 | } 60 | if (end !== undefined && modifiers.end_offset != undefined){ 61 | end += modifiers.end_offset; 62 | } 63 | if (eventTime !== undefined && modifiers.event_offset != undefined){ 64 | eventTime += modifiers.event_offset; 65 | } 66 | 67 | return {start: start, end: end, event: eventTime}; 68 | } 69 | 70 | function cropTelemetry(telemetry, start, end ,event, eventWindow){ 71 | eventWindow = eventWindow || 1; 72 | 73 | if (!telemetry || telemetry.length === 0) 74 | return []; 75 | if (_.isNil(start)) 76 | start = telemetry[0].time; 77 | if (_.isNil(end)) 78 | end = telemetry[telemetry.length - 1].time; 79 | if (!_.isNil(event)) 80 | start = end = event; 81 | 82 | 83 | let startIndex = telemetry.findIndex(function(element) { 84 | return element.time >= start; 85 | }); 86 | 87 | let endIndex = telemetry.findIndex(function(element) { 88 | return element.time > end; 89 | }); 90 | 91 | // If start is after all the telemetry or 92 | // end is before all the telemetry 93 | if (startIndex === -1 || (start != end && telemetry[0].time > end)) 94 | return []; 95 | 96 | if (endIndex === -1) 97 | endIndex = telemetry.length; 98 | 99 | if (startIndex < endIndex){ 100 | // Interval between start and end 101 | return telemetry.slice(Math.max(0, startIndex), endIndex); 102 | }else{ 103 | // Event 104 | let eventTelemetry = telemetry[startIndex]; 105 | 106 | // Check the telemetry is close enough to the requested time (in the window) 107 | if (Math.abs(eventTelemetry.time - start) > eventWindow){ 108 | // Telemetry is out of the window 109 | return []; 110 | }else{ 111 | // Telemetry is in the window 112 | return [eventTelemetry]; 113 | } 114 | } 115 | } 116 | 117 | function intervalTelemetry(telemetry, interval){ 118 | if (_.isNil(telemetry) || telemetry.length === 0) 119 | return []; 120 | 121 | let filteredTelemetry = [telemetry[0]]; 122 | let prevTime = telemetry[0].time; 123 | 124 | for(let i = 1; i < telemetry.length; i++){ 125 | if (telemetry[i].time - prevTime >= interval){ 126 | filteredTelemetry.push(telemetry[i]); 127 | prevTime = telemetry[i].time; 128 | } 129 | } 130 | 131 | return filteredTelemetry; 132 | } 133 | 134 | function getInterval(modifiers){ 135 | let interval = 0; 136 | 137 | if (!_.isNil(modifiers.frame_rate) && modifiers.frame_rate != 0) 138 | interval = 1/modifiers.frame_rate; 139 | 140 | if (!_.isNil(modifiers.interval)) 141 | interval = modifiers.interval; 142 | 143 | return interval; 144 | } 145 | 146 | function chooseStagesAndTelemetryRange(data, stage, start, end, event, eventWindow, interval){ 147 | let out = []; 148 | let tmpTelemetry; 149 | 150 | for (let i = 0; i < data.length; i++){ 151 | if (stage === undefined || stage === data[i].stage){ 152 | tmpTelemetry = cropTelemetry(data[i].telemetry, start, end, event, eventWindow); 153 | tmpTelemetry = intervalTelemetry(tmpTelemetry, interval); 154 | 155 | out.push({ 156 | stage: data[i].stage, 157 | telemetry: tmpTelemetry 158 | }); 159 | } 160 | } 161 | 162 | return out; 163 | } 164 | 165 | // Get 'key' telemetry from the 'identifiers' and modified using 'modifiers' 166 | async function getTelemetry(key, company, identifiers, modifiers){ 167 | if (!key || _.isEmpty(identifiers) || _.isNil(modifiers)) 168 | throw {status: 404, message: "Not Found"}; 169 | 170 | // Get the launch from the database 171 | let launchMetadata = await mongoHelper.findLaunchMetadata(company, identifiers); 172 | 173 | if (!launchMetadata){ 174 | throw {status: 404, message: "Not Found"}; 175 | } 176 | 177 | let events = await s3Helper.getFile(launchMetadata.events_path); 178 | let data; 179 | 180 | if (key === "raw") 181 | data = await s3Helper.getFile(launchMetadata.raw_path); 182 | else if (key === "analysed") 183 | data = await s3Helper.getFile(launchMetadata.analysed_path); 184 | 185 | if (!data) 186 | return undefined; 187 | 188 | let {start, end, event} = eventsToStartEnd(events, modifiers); 189 | let interval = getInterval(modifiers); 190 | return chooseStagesAndTelemetryRange(data, 191 | modifiers.stage, 192 | start, 193 | end, 194 | event, 195 | modifiers.event_window, 196 | interval); 197 | } 198 | 199 | function modifyData(rawData, analysedData, eventData, modifiers){ 200 | let {start, end, event} = eventsToStartEnd(eventData, modifiers); 201 | let interval = getInterval(modifiers); 202 | rawData = chooseStagesAndTelemetryRange(rawData, 203 | modifiers.stage, 204 | start, 205 | end, 206 | event, 207 | modifiers.event_window, 208 | interval); 209 | 210 | analysedData = chooseStagesAndTelemetryRange(analysedData, 211 | modifiers.stage, 212 | start, 213 | end, 214 | event, 215 | modifiers.event_window, 216 | interval); 217 | 218 | return {raw: rawData, analysed: analysedData}; 219 | } 220 | 221 | module.exports = { 222 | modifyData: modifyData, 223 | cropTelemetry: cropTelemetry, 224 | getTelemetry: getTelemetry 225 | }; 226 | -------------------------------------------------------------------------------- /src/helpers/v2/telemetry_helper.js: -------------------------------------------------------------------------------- 1 | const _ = require("lodash"); 2 | const s3Helper = require("./s3_helper"); 3 | const mongoHelper = require("./mongo_helper"); 4 | 5 | function eventsToStartEnd(events, modifiers){ 6 | let start, end, eventTime; 7 | 8 | if (!events || !modifiers) 9 | return {}; 10 | 11 | function eventToTime(eventsArray, event){ 12 | // If event is not defined then return 'undefined' 13 | if(!event) 14 | return undefined; 15 | 16 | // If the 'event' is not a number then try to find the event 17 | if (_.isNaN(Number(event))){ 18 | let res = _.find(eventsArray, {key: event}); 19 | 20 | if (!res) 21 | return undefined; 22 | 23 | return res.time; 24 | } 25 | 26 | // 'event' is a number 27 | return Number(event); 28 | } 29 | 30 | 31 | // Get time values for "start", "end" and "eventTime" 32 | start = eventToTime(events, modifiers.start); 33 | end = eventToTime(events, modifiers.end); 34 | eventTime = eventToTime(events, modifiers.event); 35 | 36 | // Check the validity of "start", "end" and "eventTime" 37 | if (start === undefined && modifiers.start !== undefined) 38 | throw new Error("\"start\" has to a number or an event"); 39 | else if (start === null && modifiers.start !== undefined) 40 | throw new Error(`event ${modifiers.start} of "start" is not set in the data`); 41 | 42 | if (end === undefined && modifiers.end !== undefined) 43 | throw new Error("\"end\" has to a number or an event"); 44 | else if (end === null && modifiers.end !== undefined) 45 | throw new Error(`event ${modifiers.end} of "end" is not set in the data`); 46 | 47 | if (eventTime === undefined && modifiers.event !== undefined) 48 | throw new Error("\"event\" has to a number or an event"); 49 | else if (eventTime === null && modifiers.event !== undefined) 50 | throw new Error(`event ${modifiers.event} of "event" is not set in the data`); 51 | 52 | if (start && end && start > end) 53 | throw new Error("\"end\" cannot be smaller than \"start\""); 54 | 55 | 56 | // Apply offset modifiers 57 | if (start !== undefined && modifiers.start_offset != undefined){ 58 | start += modifiers.start_offset; 59 | } 60 | if (end !== undefined && modifiers.end_offset != undefined){ 61 | end += modifiers.end_offset; 62 | } 63 | if (eventTime !== undefined && modifiers.event_offset != undefined){ 64 | eventTime += modifiers.event_offset; 65 | } 66 | 67 | return {start: start, end: end, event: eventTime}; 68 | } 69 | 70 | function cropTelemetry(telemetry, start, end ,event, eventWindow){ 71 | eventWindow = eventWindow || 1; 72 | 73 | if (!telemetry || telemetry.length === 0) 74 | return []; 75 | if (_.isNil(start)) 76 | start = telemetry[0].time; 77 | if (_.isNil(end)) 78 | end = telemetry[telemetry.length - 1].time; 79 | if (!_.isNil(event)) 80 | start = end = event; 81 | 82 | 83 | let startIndex = telemetry.findIndex(function(element) { 84 | return element.time >= start; 85 | }); 86 | 87 | let endIndex = telemetry.findIndex(function(element) { 88 | return element.time > end; 89 | }); 90 | 91 | // If start is after all the telemetry or 92 | // end is before all the telemetry 93 | if (startIndex === -1 || (start != end && telemetry[0].time > end)) 94 | return []; 95 | 96 | if (endIndex === -1) 97 | endIndex = telemetry.length; 98 | 99 | if (startIndex < endIndex){ 100 | // Interval between start and end 101 | return telemetry.slice(Math.max(0, startIndex), endIndex); 102 | }else{ 103 | // Event 104 | let eventTelemetry = telemetry[startIndex]; 105 | 106 | // Check the telemetry is close enough to the requested time (in the window) 107 | if (Math.abs(eventTelemetry.time - start) > eventWindow){ 108 | // Telemetry is out of the window 109 | return []; 110 | }else{ 111 | // Telemetry is in the window 112 | return [eventTelemetry]; 113 | } 114 | } 115 | } 116 | 117 | function intervalTelemetry(telemetry, interval){ 118 | if (_.isNil(telemetry) || telemetry.length === 0) 119 | return []; 120 | 121 | let filteredTelemetry = [telemetry[0]]; 122 | let prevTime = telemetry[0].time; 123 | 124 | for(let i = 1; i < telemetry.length; i++){ 125 | if (telemetry[i].time - prevTime >= interval){ 126 | filteredTelemetry.push(telemetry[i]); 127 | prevTime = telemetry[i].time; 128 | } 129 | } 130 | 131 | return filteredTelemetry; 132 | } 133 | 134 | function getInterval(modifiers){ 135 | let interval = 0; 136 | 137 | if (!_.isNil(modifiers.frame_rate) && modifiers.frame_rate != 0) 138 | interval = 1/modifiers.frame_rate; 139 | 140 | if (!_.isNil(modifiers.interval)) 141 | interval = modifiers.interval; 142 | 143 | return interval; 144 | } 145 | 146 | function chooseStagesAndTelemetryRange(data, stage, start, end, event, eventWindow, interval){ 147 | let out = []; 148 | let tmpTelemetry; 149 | 150 | for (let i = 0; i < data.length; i++){ 151 | if (stage === undefined || stage === data[i].stage){ 152 | tmpTelemetry = cropTelemetry(data[i].telemetry, start, end, event, eventWindow); 153 | tmpTelemetry = intervalTelemetry(tmpTelemetry, interval); 154 | 155 | out.push({ 156 | stage: data[i].stage, 157 | telemetry: tmpTelemetry 158 | }); 159 | } 160 | } 161 | 162 | return out; 163 | } 164 | 165 | // Get 'key' telemetry from the 'identifiers' and modified using 'modifiers' 166 | async function getTelemetry(key, company, identifiers, modifiers){ 167 | if (!key || _.isEmpty(identifiers) || _.isNil(modifiers)) 168 | throw {status: 404, message: "Not Found"}; 169 | 170 | // Get the launch from the database 171 | let launchMetadata = await mongoHelper.findLaunchMetadata(company, identifiers); 172 | 173 | if (!launchMetadata){ 174 | throw {status: 404, message: "Not Found"}; 175 | } 176 | 177 | let events = await s3Helper.getFile(launchMetadata.events_path); 178 | let data; 179 | 180 | if (key === "raw") 181 | data = await s3Helper.getFile(launchMetadata.raw_path); 182 | else if (key === "analysed") 183 | data = await s3Helper.getFile(launchMetadata.analysed_path); 184 | 185 | if (!data) 186 | return undefined; 187 | 188 | let {start, end, event} = eventsToStartEnd(events, modifiers); 189 | let interval = getInterval(modifiers); 190 | return chooseStagesAndTelemetryRange(data, 191 | modifiers.stage, 192 | start, 193 | end, 194 | event, 195 | modifiers.event_window, 196 | interval); 197 | } 198 | 199 | function modifyData(rawData, analysedData, eventData, modifiers){ 200 | let {start, end, event} = eventsToStartEnd(eventData, modifiers); 201 | let interval = getInterval(modifiers); 202 | rawData = chooseStagesAndTelemetryRange(rawData, 203 | modifiers.stage, 204 | start, 205 | end, 206 | event, 207 | modifiers.event_window, 208 | interval); 209 | 210 | analysedData = chooseStagesAndTelemetryRange(analysedData, 211 | modifiers.stage, 212 | start, 213 | end, 214 | event, 215 | modifiers.event_window, 216 | interval); 217 | 218 | return {raw: rawData, analysed: analysedData}; 219 | } 220 | 221 | module.exports = { 222 | modifyData: modifyData, 223 | cropTelemetry: cropTelemetry, 224 | getTelemetry: getTelemetry 225 | }; 226 | -------------------------------------------------------------------------------- /test/other/routes/raw_test.js: -------------------------------------------------------------------------------- 1 | // Import assert to unit test the database 2 | const assert = require("assert"); 3 | const controller = require("../../src/controllers/raw"); 4 | const _ = require("lodash"); 5 | 6 | /* 7 | 8 | 9 | // Create a series of tests 10 | describe("Testing the /raw endpoint is returning the correct data", function(){ 11 | 12 | it("raw/ with a valid flight_number", function(done){ 13 | let req = { 14 | identifiers: { 15 | flight_number: 25 16 | }, 17 | modifiers: {} 18 | }; 19 | 20 | let res = { 21 | send: function(raw) { 22 | assert(raw.length === 1); 23 | assert(Object.keys(raw[0]).length === 2); 24 | assert(raw[0].stage === 2); 25 | assert(raw[0].telemetry !== undefined); 26 | done(); 27 | }, 28 | next: function(err){ 29 | assert(false); 30 | done(); 31 | } 32 | }; 33 | 34 | controller.getOne(req, res, function(err){ 35 | assert(false); 36 | done(); 37 | }); 38 | }); 39 | 40 | 41 | 42 | 43 | it("raw/ with an empty identifiers object throws a 404 error", function(done){ 44 | let req = { 45 | identifiers: {}, 46 | modifiers: {} 47 | }; 48 | 49 | let res = { 50 | send: function(raw) { 51 | assert(false); 52 | done(); 53 | }, 54 | next: function(err){ 55 | assert(false); 56 | done(); 57 | } 58 | }; 59 | 60 | controller.getOne(req, res, function(err){ 61 | assert(err !== undefined); 62 | assert(err.status === 404); 63 | done(); 64 | }); 65 | }); 66 | 67 | 68 | 69 | 70 | it("raw/ with conflicting flight_number and mission_id throws a 404 error", function(done){ 71 | let req = { 72 | identifiers: { 73 | flight_number: 45, 74 | mission_id: "not crs-12" 75 | }, 76 | modifiers: {} 77 | }; 78 | 79 | let res = { 80 | send: function(raw) { 81 | assert(false); 82 | done(); 83 | }, 84 | next: function(err){ 85 | assert(false); 86 | done(); 87 | } 88 | }; 89 | 90 | controller.getOne(req, res, function(err){ 91 | assert(err !== undefined); 92 | assert(err.status === 404); 93 | done(); 94 | }); 95 | }); 96 | 97 | 98 | 99 | 100 | it("raw/ with a valid flight_number, start and end numeric values", function(done){ 101 | let req = { 102 | identifiers: { 103 | flight_number: 45 104 | }, 105 | modifiers: { 106 | start: 60, 107 | end: 70, 108 | } 109 | }; 110 | 111 | let res = { 112 | send: function(raw) { 113 | assert(raw.length === 2); 114 | assert(Object.keys(raw[0]).length === 2); 115 | assert(raw[0].stage === 1); 116 | assert(raw[0].telemetry !== undefined); 117 | raw[0].telemetry.forEach((val) => { 118 | assert(val.time >= 60); 119 | assert(val.time <= 70); 120 | }); 121 | done(); 122 | }, 123 | next: function(err){ 124 | assert(false); 125 | done(); 126 | } 127 | }; 128 | 129 | controller.getOne(req, res, function(err){ 130 | assert(false); 131 | done(); 132 | }); 133 | }); 134 | 135 | 136 | 137 | 138 | it("raw/ with a valid flight_number, start and end numeric values but start is negative", function(done){ 139 | let req = { 140 | identifiers: { 141 | flight_number: 45 142 | }, 143 | modifiers: { 144 | start: -5, 145 | end: 70, 146 | } 147 | }; 148 | 149 | let res = { 150 | send: function(raw) { 151 | assert(raw.length === 2); 152 | assert(Object.keys(raw[0]).length === 2); 153 | assert(raw[0].stage === 1); 154 | assert(raw[0].telemetry !== undefined && raw[0].telemetry.length > 0); 155 | assert(raw[1].telemetry !== undefined && raw[1].telemetry.length === 0); 156 | raw[0].telemetry.forEach((val) => { 157 | assert(val.time >= -5); 158 | assert(val.time <= 70); 159 | }); 160 | done(); 161 | }, 162 | next: function(err){ 163 | assert(false); 164 | done(); 165 | } 166 | }; 167 | 168 | controller.getOne(req, res, function(err){ 169 | assert(false); 170 | done(); 171 | }); 172 | }); 173 | 174 | 175 | 176 | 177 | it("raw/ with a valid flight_number but start and end are bigger than the end of the telemetry", function(done){ 178 | let req = { 179 | identifiers: { 180 | flight_number: 45 181 | }, 182 | modifiers: { 183 | start: 100000, 184 | end: 1000000, 185 | } 186 | }; 187 | 188 | let res = { 189 | send: function(raw) { 190 | assert(raw.length === 2); 191 | assert(Object.keys(raw[0]).length === 2); 192 | assert(raw[0].stage === 1); 193 | assert(raw[0].telemetry !== undefined); 194 | raw.forEach((val) => assert(val.telemetry.length === 0)); 195 | done(); 196 | }, 197 | next: function(err){ 198 | assert(false); 199 | done(); 200 | } 201 | }; 202 | 203 | controller.getOne(req, res, function(err){ 204 | assert(false); 205 | done(); 206 | }); 207 | }); 208 | 209 | 210 | 211 | 212 | it("raw/ with a valid flight_number, start and end are events", function(done){ 213 | let req = { 214 | identifiers: { 215 | flight_number: 45 216 | }, 217 | modifiers: { 218 | start: "boostback_start", 219 | end: "boostback_end", 220 | } 221 | }; 222 | 223 | let res = { 224 | send: function(raw) { 225 | assert(raw.length === 2); 226 | assert(Object.keys(raw[0]).length === 2); 227 | assert(raw[0].stage === 1); 228 | assert(raw[0].telemetry !== undefined); 229 | raw[0].telemetry.forEach((val) => { 230 | assert(val.time >= 166.032); 231 | assert(val.time <= 211.978); 232 | }); 233 | done(); 234 | }, 235 | next: function(err){ 236 | assert(false); 237 | done(); 238 | } 239 | }; 240 | 241 | controller.getOne(req, res, function(err){ 242 | assert(false); 243 | done(); 244 | }); 245 | }); 246 | 247 | 248 | 249 | 250 | it("raw/ with valid flight_number but with an invalid event in start", function(done){ 251 | let req = { 252 | identifiers: { 253 | flight_number: 45, 254 | }, 255 | modifiers: { 256 | start: "abc" 257 | } 258 | }; 259 | 260 | let res = { 261 | send: function(raw) { 262 | assert(false); 263 | done(); 264 | }, 265 | next: function(err){ 266 | assert(false); 267 | done(); 268 | } 269 | }; 270 | 271 | controller.getOne(req, res, function(err){ 272 | assert(err !== undefined); 273 | done(); 274 | }); 275 | }); 276 | 277 | 278 | 279 | it("raw/ with a valid flight_number and event", function(done){ 280 | let req = { 281 | identifiers: { 282 | flight_number: 45 283 | }, 284 | modifiers: { 285 | event: "boostback_start", 286 | } 287 | }; 288 | 289 | let res = { 290 | send: function(raw) { 291 | assert(raw.length === 2); 292 | assert(Object.keys(raw[0]).length === 2); 293 | assert(raw[0].stage === 1); 294 | assert(raw[0].telemetry !== undefined); 295 | assert(raw[0].telemetry.length === 1); 296 | assert(_.isEqual(raw[0].telemetry[0].toJSON(), { time: 166.032, velocity: 1524.444, altitude: 80.9 })); 297 | done(); 298 | }, 299 | next: function(err){ 300 | 301 | assert(false); 302 | done(); 303 | } 304 | }; 305 | 306 | controller.getOne(req, res, function(err){ 307 | 308 | assert(false); 309 | done(); 310 | }); 311 | }); 312 | 313 | 314 | 315 | 316 | 317 | it("raw/ with a valid flight_number and an invalid event should return an error", function(done){ 318 | let req = { 319 | identifiers: { 320 | flight_number: 45 321 | }, 322 | modifiers: { 323 | event: "not a real event", 324 | } 325 | }; 326 | 327 | let res = { 328 | send: function(raw) { 329 | assert(false); 330 | done(); 331 | }, 332 | next: function(err){ 333 | 334 | assert(false); 335 | done(); 336 | } 337 | }; 338 | 339 | controller.getOne(req, res, function(err){ 340 | 341 | assert(err !== undefined); 342 | done(); 343 | }); 344 | }); 345 | 346 | 347 | 348 | 349 | it("raw/ with a valid flight_number and event = null should return an error", function(done){ 350 | let req = { 351 | identifiers: { 352 | flight_number: 45 353 | }, 354 | modifiers: { 355 | event: null, 356 | } 357 | }; 358 | 359 | let res = { 360 | send: function(raw) { 361 | assert(false); 362 | done(); 363 | }, 364 | next: function(err){ 365 | assert(false); 366 | done(); 367 | } 368 | }; 369 | 370 | controller.getOne(req, res, function(err){ 371 | assert(true); 372 | done(); 373 | }); 374 | }); 375 | 376 | }); 377 | 378 | 379 | */ 380 | -------------------------------------------------------------------------------- /src/controllers/v1/launches.js: -------------------------------------------------------------------------------- 1 | const Company = require("../../models/v1/company"); 2 | const s3Helper = require("../../helpers/v1/s3_helper"); 3 | const mongoHelper = require("../../helpers/v1/mongo_helper"); 4 | const _ = require("lodash"); 5 | const { checkIdentifiers } = require("../../middleware/v1/validator"); 6 | const company = require("./company"); 7 | const {modifyData} = require("../../helpers/v1/telemetry_helper"); 8 | 9 | 10 | async function exists(company_id, identifiers){ 11 | return await mongoHelper.findLaunchMetadata(company_id, identifiers) != null; 12 | } 13 | 14 | 15 | 16 | 17 | function findLaunch(companies, launch_library_id){ 18 | for(let company of companies){ 19 | for(let launch of company.launches) { 20 | if(launch_library_id === launch.launch_library_id){ 21 | return {company: company, launch: launch}; 22 | } 23 | } 24 | } 25 | 26 | return {company: null, launch: null}; 27 | } 28 | 29 | 30 | module.exports = { 31 | // Get information about launches from the database 32 | info: async function getInfo(req, res, next){ 33 | try{ 34 | // Get all launches 35 | if (_.isEmpty(req.identifiers)){ 36 | let result = await Company.findOne({company_id: req.params.company}, "launches.mission_id launches.name launches.flight_number launches.launch_library_id"); 37 | 38 | if (!result) 39 | throw {status: 404, message: "Not Found"}; 40 | 41 | res.send(result.launches.sort((elm1, elm2) => elm1.flight_number - elm2.flight_number)); 42 | } 43 | // Get a specific launch 44 | else{ 45 | let result = await mongoHelper.findLaunchMetadata(req.params.company, req.identifiers); 46 | 47 | if (!result) 48 | throw {status: 404, message: "Not Found"}; 49 | 50 | res.send({ 51 | mission_id: result.mission_id, 52 | name: result.name, 53 | flight_number: result.flight_number, 54 | launch_library_id: result.launch_library_id 55 | }); 56 | } 57 | }catch(ex){ 58 | next(ex); 59 | } 60 | }, 61 | 62 | 63 | 64 | 65 | getLaunchFromLaunchLibraryProvider: async function(req, res, next){ 66 | try{ 67 | if(!req.identifiers.launch_library_id){ 68 | throw new Error("Missing \"launch_library_id\" or \"ccompany\" parameters"); 69 | } 70 | 71 | const companies = await Company.find({}); 72 | const {company, launch} = findLaunch(companies, req.identifiers.launch_library_id); 73 | 74 | if (!launch){ 75 | next({status: 404, message: `Launch with launch_library_id: ${req.identifiers.launch_library_id} doesn't exists`}); 76 | return; 77 | } 78 | 79 | let result = await mongoHelper.findLaunchMetadata(company.company_id, {launch_library_id: launch.launch_library_id}); 80 | 81 | // If no launch was found return a "Not Found" error 82 | if (!result){ 83 | next({status: 404, message: "Not Found"}); 84 | return; 85 | } 86 | 87 | // Get the telemetry 88 | let { rawData, analysedData, eventData } = await s3Helper.getOneLaunch(result); 89 | 90 | if (!rawData || !analysedData || !eventData) 91 | throw new Error("Telemetry data is unavailable (even thought it should be)\nPlease report this issue on the Launch Dashboard API GitHub Repository"); 92 | 93 | const {raw, analysed} = modifyData(rawData, analysedData, eventData, req.modifiers); 94 | 95 | // box the metadata and telemetry and send it 96 | res.send( 97 | { 98 | mission_id: result.mission_id, 99 | name: result.name, 100 | flight_number: result.flight_number, 101 | launch_library_id: result.launch_library_id, 102 | raw: raw, 103 | analysed: analysed, 104 | events: eventData 105 | } 106 | ); 107 | }catch(ex){ 108 | next(ex); 109 | } 110 | }, 111 | 112 | 113 | // Get all the available data about a specific launch 114 | getLaunches: async function(req, res, next){ 115 | if(!await company.exists(req.params.company)){ 116 | next({status: 404, message: `Company "${req.params.company}" is not found`}); 117 | return; 118 | } 119 | 120 | // Get all launches 121 | if (_.isEmpty(req.identifiers)){ 122 | try{ 123 | let result = await Company.findOne({company_id: req.params.company}, "launches.mission_id launches.name launches.flight_number launches.launch_library_id"); 124 | 125 | if (!result) 126 | throw {status: 404, message: "Not Found`"}; 127 | 128 | res.send(result.launches.sort((elm1, elm2) => elm1.flight_number - elm2.flight_number)); 129 | }catch(ex){ 130 | next(ex); 131 | } 132 | 133 | }else if (checkIdentifiers(req, res, async () => { 134 | try{ 135 | let result = await mongoHelper.findLaunchMetadata(req.params.company, req.identifiers); 136 | 137 | // If no launch was found return a "Not Found" error 138 | if (!result){ 139 | next({status: 404, message: "Not Found"}); 140 | return; 141 | } 142 | 143 | // Get the telemetry 144 | let { rawData, analysedData, eventData } = await s3Helper.getOneLaunch(result); 145 | 146 | if (!rawData || !analysedData || !eventData) 147 | throw new Error("Telemetry data is unavailable (even thought it should be)\nPlease report this issue on the Launch Dashboard API GitHub Repository"); 148 | 149 | const {raw, analysed} = modifyData(rawData, analysedData, eventData, req.modifiers); 150 | 151 | // box the metadata and telemetry and send it 152 | res.send( 153 | { 154 | mission_id: result.mission_id, 155 | name: result.name, 156 | flight_number: result.flight_number, 157 | launch_library_id: result.launch_library_id, 158 | raw: raw, 159 | analysed: analysed, 160 | events: eventData 161 | } 162 | ); 163 | }catch(ex){ 164 | next(ex); 165 | } 166 | })); 167 | }, 168 | 169 | 170 | getLatestLaunch: async function(req, res, next){ 171 | if(!await company.exists(req.params.company)){ 172 | next({status: 404, message: `Company "${req.params.company}" is not found`}); 173 | return; 174 | } 175 | 176 | 177 | try{ 178 | const flight_numbers = await Company.findOne({company_id: req.params.company}, "launches.flight_number"); 179 | 180 | if (flight_numbers.length === 0) 181 | throw new Error(`No launches found for ${req.params.company}`); 182 | 183 | 184 | // Inefficent but easy in JS 185 | const launches = flight_numbers.launches.sort((elm1, elm2) => elm1.flight_number - elm2.flight_number); 186 | const result = await mongoHelper.findLaunchMetadata(req.params.company, {flight_number: launches[launches.length-1].flight_number}); 187 | 188 | // If no launch was found return a "Not Found" error 189 | if (!result){ 190 | next({status: 404, message: "Not Found"}); 191 | return; 192 | } 193 | 194 | // Get the telemetry 195 | let { rawData, analysedData, eventData } = await s3Helper.getOneLaunch(result); 196 | 197 | if (!rawData || !analysedData || !eventData) 198 | throw new Error("Telemetry data is unavailable (even thought it should be)\nPlease report this issue on the Launch Dashboard API GitHub Repository"); 199 | 200 | const {raw, analysed} = modifyData(rawData, analysedData, eventData, req.modifiers); 201 | 202 | // box the metadata and telemetry and send it 203 | res.send( 204 | { 205 | mission_id: result.mission_id, 206 | name: result.name, 207 | flight_number: result.flight_number, 208 | launch_library_id: result.launch_library_id, 209 | raw: raw, 210 | analysed: analysed, 211 | events: eventData 212 | } 213 | ); 214 | }catch(ex){ 215 | next(ex); 216 | } 217 | }, 218 | 219 | 220 | // Add a launch to the database 221 | addOne: async function(req, res, next){ 222 | try{ 223 | // If the company doesn't exist or if the launch already exists throw an error 224 | if(!await company.exists(req.body.company_id)) 225 | throw new Error(`Company "${req.body.company_id}" doesn't exists`); 226 | 227 | if (await exists(req.body.company_id, {flight_number: req.body.flight_number})) 228 | throw new Error(`Launch "${req.body.mission_id}" already exists`); 229 | 230 | 231 | // Put the telemetry in storage 232 | let launchMetadata = await s3Helper.addOneLaunch(req.body); 233 | 234 | // Put the metadata in the database 235 | res.send( 236 | mongoHelper.addLaunchMetadata(req.body.company_id, launchMetadata) 237 | ); 238 | }catch(e){ 239 | next(e); 240 | } 241 | }, 242 | 243 | 244 | 245 | updateOne: async function(req, res, next){ 246 | // Update the telemetry in storage 247 | let launchMetadata = await s3Helper.updateOneLaunch(req.body); 248 | 249 | if (launchMetadata) 250 | throw {status: 500, message: "Failed to update launch"}; 251 | 252 | // Update the metadata in the database 253 | res.send( 254 | await mongoHelper.updateOneLaunch(req.identifiers, launchMetadata) 255 | ); 256 | }, 257 | 258 | 259 | deleteOne: async function(req, res, next){ 260 | if (!req.params.company || _.isEmpty(req.identifiers)){ 261 | throw new Error("Missing \"flight_number\", \"mission_id\" or \"launch_library_id\""); 262 | } 263 | // Get launch file name (key) from db 264 | let launch = await mongoHelper.findLaunchMetadata(req.params.company, req.identifiers); 265 | 266 | if (!launch){ 267 | next({status: 404, message: "Not Found"}); 268 | return; 269 | } 270 | 271 | if (!await mongoHelper.deleteLaunchMetadata(req.params.company, req.identifiers)){ 272 | next({status: 500, message: "Failed to delete launch"}); 273 | return; 274 | } 275 | 276 | await s3Helper.deleteOneLaunch(launch); 277 | 278 | res.send(launch); 279 | }, 280 | 281 | 282 | 283 | }; 284 | -------------------------------------------------------------------------------- /src/controllers/v2/launches.js: -------------------------------------------------------------------------------- 1 | const Company = require("../../models/v2/company"); 2 | const s3Helper = require("../../helpers/v2/s3_helper"); 3 | const mongoHelper = require("../../helpers/v2/mongo_helper"); 4 | const _ = require("lodash"); 5 | const { checkIdentifiers } = require("../../middleware/v2/validator"); 6 | const company = require("./company"); 7 | const {modifyData} = require("../../helpers/v2/telemetry_helper"); 8 | 9 | async function exists(company_id, identifiers){ 10 | return await mongoHelper.findLaunchMetadata(company_id, identifiers) != null; 11 | } 12 | 13 | function findLaunch(companies, launch_library_2_id){ 14 | for(let company of companies){ 15 | for(let launch of company.launches) { 16 | if(launch_library_2_id === launch.launch_library_2_id){ 17 | return {company: company, launch: launch}; 18 | } 19 | } 20 | } 21 | 22 | return {company: null, launch: null}; 23 | } 24 | 25 | module.exports = { 26 | // Get information about launches from the database 27 | info: async function getInfo(req, res, next){ 28 | try{ 29 | // Get all launches 30 | if (_.isEmpty(req.identifiers)){ 31 | let result = await Company.findOne({company_id: req.params.company}, "launches.mission_id launches.name launches.flight_number launches.launch_library_2_id"); 32 | 33 | if (!result) 34 | throw {status: 404, message: "Not Found"}; 35 | 36 | res.send(result.launches.sort((elm1, elm2) => elm1.flight_number - elm2.flight_number)); 37 | } 38 | // Get a specific launch 39 | else{ 40 | let result = await mongoHelper.findLaunchMetadata(req.params.company, req.identifiers); 41 | 42 | if (!result) 43 | throw {status: 404, message: "Not Found"}; 44 | 45 | res.send({ 46 | mission_id: result.mission_id, 47 | name: result.name, 48 | flight_number: result.flight_number, 49 | launch_library_2_id: result.launch_library_2_id 50 | }); 51 | } 52 | }catch(ex){ 53 | next(ex); 54 | } 55 | }, 56 | 57 | 58 | 59 | 60 | getLaunchFromLaunchLibraryProvider: async function(req, res, next){ 61 | try{ 62 | if(!req.identifiers.launch_library_2_id){ 63 | throw new Error("Missing \"launch_library_2_id\" or \"ccompany\" parameters"); 64 | } 65 | 66 | const companies = await Company.find({}); 67 | const {company, launch} = findLaunch(companies, req.identifiers.launch_library_2_id); 68 | 69 | if (!launch){ 70 | next({status: 404, message: `Launch with launch_library_2_id: ${req.identifiers.launch_library_2_id} doesn't exists`}); 71 | return; 72 | } 73 | 74 | let result = await mongoHelper.findLaunchMetadata(company.company_id, {launch_library_2_id: launch.launch_library_2_id}); 75 | 76 | // If no launch was found return a "Not Found" error 77 | if (!result){ 78 | next({status: 404, message: "Not Found"}); 79 | return; 80 | } 81 | 82 | // Get the telemetry 83 | let { rawData, analysedData, eventData } = await s3Helper.getOneLaunch(result); 84 | 85 | if (!rawData || !analysedData || !eventData) 86 | throw new Error("Telemetry data is unavailable (even thought it should be)\nPlease report this issue on the Launch Dashboard API GitHub Repository"); 87 | 88 | const {raw, analysed} = modifyData(rawData, analysedData, eventData, req.modifiers); 89 | 90 | // box the metadata and telemetry and send it 91 | res.send( 92 | { 93 | mission_id: result.mission_id, 94 | name: result.name, 95 | flight_number: result.flight_number, 96 | launch_library_2_id: result.launch_library_2_id, 97 | raw: raw, 98 | analysed: analysed, 99 | events: eventData 100 | } 101 | ); 102 | }catch(ex){ 103 | next(ex); 104 | } 105 | }, 106 | 107 | 108 | // Get all the available data about a specific launch 109 | getLaunches: async function(req, res, next){ 110 | if(!await company.exists(req.params.company)){ 111 | next({status: 404, message: `Company "${req.params.company}" is not found`}); 112 | return; 113 | } 114 | 115 | // Get all launches 116 | if (_.isEmpty(req.identifiers)){ 117 | try{ 118 | let result = await Company.findOne({company_id: req.params.company}, "launches.mission_id launches.name launches.flight_number launches.launch_library_2_id"); 119 | 120 | if (!result) 121 | throw {status: 404, message: "Not Found`"}; 122 | 123 | res.send(result.launches.sort((elm1, elm2) => elm1.flight_number - elm2.flight_number)); 124 | }catch(ex){ 125 | next(ex); 126 | } 127 | 128 | }else if (checkIdentifiers(req, res, async () => { 129 | try{ 130 | let result = await mongoHelper.findLaunchMetadata(req.params.company, req.identifiers); 131 | 132 | // If no launch was found return a "Not Found" error 133 | if (!result){ 134 | next({status: 404, message: "Not Found"}); 135 | return; 136 | } 137 | 138 | // Get the telemetry 139 | let { rawData, analysedData, eventData } = await s3Helper.getOneLaunch(result); 140 | 141 | if (!rawData || !analysedData || !eventData) 142 | throw new Error("Telemetry data is unavailable (even thought it should be)\nPlease report this issue on the Launch Dashboard API GitHub Repository"); 143 | 144 | const {raw, analysed} = modifyData(rawData, analysedData, eventData, req.modifiers); 145 | 146 | // box the metadata and telemetry and send it 147 | res.send( 148 | { 149 | mission_id: result.mission_id, 150 | name: result.name, 151 | flight_number: result.flight_number, 152 | launch_library_2_id: result.launch_library_2_id, 153 | raw: raw, 154 | analysed: analysed, 155 | events: eventData 156 | } 157 | ); 158 | }catch(ex){ 159 | next(ex); 160 | } 161 | })); 162 | }, 163 | 164 | 165 | getLatestLaunch: async function(req, res, next){ 166 | if(!await company.exists(req.params.company)){ 167 | next({status: 404, message: `Company "${req.params.company}" is not found`}); 168 | return; 169 | } 170 | 171 | 172 | try{ 173 | const flight_numbers = await Company.findOne({company_id: req.params.company}, "launches.flight_number"); 174 | 175 | if (flight_numbers.length === 0) 176 | throw new Error(`No launches found for ${req.params.company}`); 177 | 178 | 179 | // Inefficent but easy in JS 180 | const launches = flight_numbers.launches.sort((elm1, elm2) => elm1.flight_number - elm2.flight_number); 181 | const result = await mongoHelper.findLaunchMetadata(req.params.company, {flight_number: launches[launches.length-1].flight_number}); 182 | 183 | // If no launch was found return a "Not Found" error 184 | if (!result){ 185 | next({status: 404, message: "Not Found"}); 186 | return; 187 | } 188 | 189 | // Get the telemetry 190 | let { rawData, analysedData, eventData } = await s3Helper.getOneLaunch(result); 191 | 192 | if (!rawData || !analysedData || !eventData) 193 | throw new Error("Telemetry data is unavailable (even thought it should be)\nPlease report this issue on the Launch Dashboard API GitHub Repository"); 194 | 195 | const {raw, analysed} = modifyData(rawData, analysedData, eventData, req.modifiers); 196 | 197 | // box the metadata and telemetry and send it 198 | res.send( 199 | { 200 | mission_id: result.mission_id, 201 | name: result.name, 202 | flight_number: result.flight_number, 203 | launch_library_2_id: result.launch_library_2_id, 204 | raw: raw, 205 | analysed: analysed, 206 | events: eventData 207 | } 208 | ); 209 | }catch(ex){ 210 | next(ex); 211 | } 212 | }, 213 | 214 | 215 | // Add a launch to the database 216 | addOne: async function(req, res, next){ 217 | try{ 218 | // If the company doesn't exist or if the launch already exists throw an error 219 | if(!await company.exists(req.body.company_id)) 220 | throw new Error(`Company "${req.body.company_id}" doesn't exists`); 221 | 222 | if (await exists(req.body.company_id, {flight_number: req.body.flight_number})) 223 | throw new Error(`Launch "${req.body.mission_id}" already exists`); 224 | 225 | 226 | // Put the telemetry in storage 227 | let launchMetadata = await s3Helper.addOneLaunch(req.body); 228 | 229 | // Put the metadata in the database 230 | res.send( 231 | mongoHelper.addLaunchMetadata(req.body.company_id, launchMetadata) 232 | ); 233 | }catch(e){ 234 | next(e); 235 | } 236 | }, 237 | 238 | 239 | 240 | updateOne: async function(req, res, next){ 241 | // Update the telemetry in storage 242 | let launchMetadata = await s3Helper.updateOneLaunch(req.body); 243 | 244 | if (launchMetadata) 245 | throw {status: 500, message: "Failed to update launch"}; 246 | 247 | // Update the metadata in the database 248 | res.send( 249 | await mongoHelper.updateOneLaunch(req.identifiers, launchMetadata) 250 | ); 251 | }, 252 | 253 | 254 | deleteOne: async function(req, res, next){ 255 | if (!req.params.company || _.isEmpty(req.identifiers)){ 256 | throw new Error("Missing \"flight_number\", \"mission_id\" or \"launch_library_2_id\""); 257 | } 258 | // Get launch file name (key) from db 259 | let launch = await mongoHelper.findLaunchMetadata(req.params.company, req.identifiers); 260 | 261 | if (!launch){ 262 | next({status: 404, message: "Not Found"}); 263 | return; 264 | } 265 | 266 | if (!await mongoHelper.deleteLaunchMetadata(req.params.company, req.identifiers)){ 267 | next({status: 500, message: "Failed to delete launch"}); 268 | return; 269 | } 270 | 271 | await s3Helper.deleteOneLaunch(launch); 272 | 273 | res.send(launch); 274 | }, 275 | 276 | 277 | 278 | }; 279 | -------------------------------------------------------------------------------- /test/other/routes/analysed_test.js: -------------------------------------------------------------------------------- 1 | // Import assert to unit test the database 2 | const assert = require("assert"); 3 | const controller = require("../../src/controllers/analysed"); 4 | const _ = require("lodash"); 5 | 6 | /* 7 | 8 | // Create a series of tests 9 | describe("Testing the /analysed endpoint is returning the correct data", function(){ 10 | 11 | it("analysed/ with a valid flight_number", function(done){ 12 | let req = { 13 | identifiers: { 14 | flight_number: 25 15 | }, 16 | modifiers: {} 17 | }; 18 | 19 | let res = { 20 | send: function(analysed) { 21 | assert(analysed.length === 1); 22 | assert(Object.keys(analysed[0]).length === 2); 23 | assert(analysed[0].stage === 2); 24 | assert(analysed[0].telemetry !== undefined); 25 | done(); 26 | }, 27 | next: function(err){ 28 | assert(false); 29 | done(); 30 | } 31 | }; 32 | 33 | controller.getOne(req, res, function(err){ 34 | assert(false); 35 | done(); 36 | }); 37 | }); 38 | 39 | 40 | 41 | 42 | it("analysed/ with an empty identifiers object throws a 404 error", function(done){ 43 | let req = { 44 | identifiers: {}, 45 | modifiers: {} 46 | }; 47 | 48 | let res = { 49 | send: function(analysed) { 50 | assert(false); 51 | done(); 52 | }, 53 | next: function(err){ 54 | assert(false); 55 | done(); 56 | } 57 | }; 58 | 59 | controller.getOne(req, res, function(err){ 60 | assert(err !== undefined); 61 | assert(err.status === 404); 62 | done(); 63 | }); 64 | }); 65 | 66 | 67 | 68 | 69 | it("analysed/ with conflicting flight_number and mission_id throws a 404 error", function(done){ 70 | let req = { 71 | identifiers: { 72 | flight_number: 45, 73 | mission_id: "not crs-12" 74 | }, 75 | modifiers: {} 76 | }; 77 | 78 | let res = { 79 | send: function(analysed) { 80 | assert(false); 81 | done(); 82 | }, 83 | next: function(err){ 84 | assert(false); 85 | done(); 86 | } 87 | }; 88 | 89 | controller.getOne(req, res, function(err){ 90 | assert(err !== undefined); 91 | assert(err.status === 404); 92 | done(); 93 | }); 94 | }); 95 | 96 | 97 | 98 | 99 | it("analysed/ with a valid flight_number, start and end numeric values", function(done){ 100 | let req = { 101 | identifiers: { 102 | flight_number: 45 103 | }, 104 | modifiers: { 105 | start: 60, 106 | end: 70, 107 | } 108 | }; 109 | 110 | let res = { 111 | send: function(analysed) { 112 | assert(analysed.length === 1); 113 | assert(Object.keys(analysed[0]).length === 2); 114 | assert(analysed[0].stage === 1); 115 | assert(analysed[0].telemetry !== undefined); 116 | analysed[0].telemetry.forEach((val) => { 117 | assert(val.time >= 60); 118 | assert(val.time <= 70); 119 | }); 120 | done(); 121 | }, 122 | next: function(err){ 123 | assert(false); 124 | done(); 125 | } 126 | }; 127 | 128 | controller.getOne(req, res, function(err){ 129 | assert(false); 130 | done(); 131 | }); 132 | }); 133 | 134 | 135 | 136 | 137 | it("analysed/ with a valid flight_number, start and end numeric values but start is negative", function(done){ 138 | let req = { 139 | identifiers: { 140 | flight_number: 45 141 | }, 142 | modifiers: { 143 | start: -5, 144 | end: 70, 145 | } 146 | }; 147 | 148 | let res = { 149 | send: function(analysed) { 150 | assert(analysed.length === 1); 151 | assert(Object.keys(analysed[0]).length === 2); 152 | assert(analysed[0].stage === 1); 153 | assert(analysed[0].telemetry !== undefined); 154 | analysed[0].telemetry.forEach((val) => { 155 | assert(val.time >= -5); 156 | assert(val.time <= 70); 157 | }); 158 | done(); 159 | }, 160 | next: function(err){ 161 | assert(false); 162 | done(); 163 | } 164 | }; 165 | 166 | controller.getOne(req, res, function(err){ 167 | assert(false); 168 | done(); 169 | }); 170 | }); 171 | 172 | 173 | 174 | 175 | it("analysed/ with a valid flight_number but start and end are bigger than the end of the telemetry", function(done){ 176 | let req = { 177 | identifiers: { 178 | flight_number: 45 179 | }, 180 | modifiers: { 181 | start: 100000, 182 | end: 1000000, 183 | } 184 | }; 185 | 186 | let res = { 187 | send: function(analysed) { 188 | assert(analysed.length === 1); 189 | assert(Object.keys(analysed[0]).length === 2); 190 | assert(analysed[0].stage === 1); 191 | assert(analysed[0].telemetry !== undefined); 192 | analysed.forEach((val) => assert(val.telemetry.length === 0)); 193 | done(); 194 | }, 195 | next: function(err){ 196 | assert(false); 197 | done(); 198 | } 199 | }; 200 | 201 | controller.getOne(req, res, function(err){ 202 | assert(false); 203 | done(); 204 | }); 205 | }); 206 | 207 | 208 | 209 | 210 | it("analysed/ with a valid flight_number, start and end are events", function(done){ 211 | let req = { 212 | identifiers: { 213 | flight_number: 45 214 | }, 215 | modifiers: { 216 | start: "boostback_start", 217 | end: "boostback_end", 218 | } 219 | }; 220 | 221 | let res = { 222 | send: function(analysed) { 223 | assert(analysed.length === 1); 224 | assert(Object.keys(analysed[0]).length === 2); 225 | assert(analysed[0].stage === 1); 226 | assert(analysed[0].telemetry !== undefined); 227 | analysed[0].telemetry.forEach((val) => { 228 | assert(val.time >= 166); 229 | assert(val.time <= 212); 230 | }); 231 | done(); 232 | }, 233 | next: function(err){ 234 | assert(false); 235 | done(); 236 | } 237 | }; 238 | 239 | controller.getOne(req, res, function(err){ 240 | assert(false); 241 | done(); 242 | }); 243 | }); 244 | 245 | 246 | 247 | 248 | it("analysed/ with valid flight_number but with an invalid event in start", function(done){ 249 | let req = { 250 | identifiers: { 251 | flight_number: 45, 252 | }, 253 | modifiers: { 254 | start: "abc" 255 | } 256 | }; 257 | 258 | let res = { 259 | send: function(analysed) { 260 | assert(false); 261 | done(); 262 | }, 263 | next: function(err){ 264 | assert(false); 265 | done(); 266 | } 267 | }; 268 | 269 | controller.getOne(req, res, function(err){ 270 | assert(err !== undefined); 271 | done(); 272 | }); 273 | }); 274 | 275 | 276 | 277 | it("analysed/ with a valid flight_number and event", function(done){ 278 | let req = { 279 | identifiers: { 280 | flight_number: 45 281 | }, 282 | modifiers: { 283 | event: "boostback_start", 284 | } 285 | }; 286 | 287 | let res = { 288 | send: function(analysed) { 289 | assert(analysed.length === 1); 290 | assert(Object.keys(analysed[0]).length === 2); 291 | assert(analysed[0].stage === 1); 292 | assert(analysed[0].telemetry !== undefined); 293 | assert(analysed[0].telemetry.length === 1); 294 | assert(_.isEqual(analysed[0].telemetry[0].toJSON(), { time: 166, 295 | velocity: 1524.714, 296 | altitude: 80.887, 297 | velocity_y: 971.201, 298 | velocity_x: 1174.682, 299 | acceleration: -3.256, 300 | downrange_distance: 64.931, 301 | angle: 39.16, 302 | q: 0 })); 303 | done(); 304 | }, 305 | next: function(err){ 306 | assert(false); 307 | done(); 308 | } 309 | }; 310 | 311 | controller.getOne(req, res, function(err){ 312 | assert(false); 313 | done(); 314 | }); 315 | }); 316 | 317 | 318 | 319 | 320 | 321 | it("analysed/ with a valid flight_number and an invalid event should return an error", function(done){ 322 | let req = { 323 | identifiers: { 324 | flight_number: 45 325 | }, 326 | modifiers: { 327 | event: "not a real event", 328 | } 329 | }; 330 | 331 | let res = { 332 | send: function(analysed) { 333 | assert(false); 334 | done(); 335 | }, 336 | next: function(err){ 337 | 338 | assert(false); 339 | done(); 340 | } 341 | }; 342 | 343 | controller.getOne(req, res, function(err){ 344 | 345 | assert(err !== undefined); 346 | done(); 347 | }); 348 | }); 349 | 350 | 351 | 352 | 353 | it("analysed/ with a valid flight_number and event = null should return an error", function(done){ 354 | let req = { 355 | identifiers: { 356 | flight_number: 45 357 | }, 358 | modifiers: { 359 | event: null, 360 | } 361 | }; 362 | 363 | let res = { 364 | send: function(analysed) { 365 | assert(false); 366 | done(); 367 | }, 368 | next: function(err){ 369 | assert(false); 370 | done(); 371 | } 372 | }; 373 | 374 | controller.getOne(req, res, function(err){ 375 | assert(true); 376 | done(); 377 | }); 378 | }); 379 | 380 | }); 381 | 382 | 383 | */ 384 | --------------------------------------------------------------------------------