[
  {
    "path": ".dockerignore",
    "content": ".git\nnode_modules\nnpm-debug.log\n*.md\ntest\ndocs\nDockerfile\ncoverage\nLICENSE\n.eslintrc\n.gitignore\n.nvmrc\n.env"
  },
  {
    "path": ".eslintrc.json",
    "content": "{\n  \"env\": {\n    \"node\": true,\n    \"es2022\": true,\n    \"jest\": true\n  },\n  \"extends\": [\"airbnb-base\"],\n  \"parser\": \"@typescript-eslint/parser\",\n  \"parserOptions\": {\n    \"ecmaVersion\": \"latest\",\n    \"sourceType\": \"module\"\n  },\n  \"plugins\": [\"@typescript-eslint\"],\n  \"overrides\": [\n    {\n      \"files\": [\"tests/**\"],\n      \"plugins\": [\"jest\"],\n      \"extends\": [\"plugin:jest/recommended\"]\n    }\n  ],\n  \"rules\": {\n    \"no-console\": 0,\n    \"import/extensions\": [\"error\", \"always\"],\n    \"import/no-unresolved\": [\"error\", { \"ignore\": [\"^got$\"] }]\n  }\n}\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n- package-ecosystem: npm\n  directory: \"/\"\n  schedule:\n    interval: weekly\n    day: saturday\n  open-pull-requests-limit: 10\n  ignore:\n  - dependency-name: eslint\n    versions:\n    - 7.21.0\n    - 7.24.0\n  - dependency-name: eslint-plugin-no-secrets\n    versions:\n    - 0.8.9\n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "name: \"CodeQL\"\n\non:\n  push:\n    branches: [master, ]\n  pull_request:\n    # The branches below must be a subset of the branches above\n    branches: [master]\n  schedule:\n    - cron: '0 4 * * 2'\n\njobs:\n  analyse:\n    name: Analyse\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v2\n      with:\n        # We must fetch at least the immediate parents so that if this is\n        # a pull request then we can checkout the head.\n        fetch-depth: 2\n\n    # If this run was triggered by a pull request event, then checkout\n    # the head of the pull request instead of the merge commit.\n    - run: git checkout HEAD^2\n      if: ${{ github.event_name == 'pull_request' }}\n\n    # Initializes the CodeQL tools for scanning.\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@v1\n      # Override language selection by uncommenting this and choosing your languages\n      # with:\n      #   languages: go, javascript, csharp, python, cpp, java\n\n    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).\n    # If this step fails, then you should remove it and run the build manually (see below)\n    - name: Autobuild\n      uses: github/codeql-action/autobuild@v1\n\n    # ℹ️ Command-line programs to run using the OS shell.\n    # 📚 https://git.io/JvXDl\n\n    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines\n    #    and modify them (or add more) to build your code if your project\n    #    uses a compiled language\n\n    #- run: |\n    #   make bootstrap\n    #   make release\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@v1\n"
  },
  {
    "path": ".github/workflows/deploy.yml",
    "content": "name: Deploy\n\non:\n  push:\n    # Publish `master` as Docker `latest` image.\n    branches:\n      - master\n\n    # Publish `v1.2.3` tags as releases.\n    tags:\n      - v*\n  pull_request:\n\nenv:\n  IMAGE_NAME: spacex-api\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v2\n      - uses: actions/setup-node@v1\n        with:\n          node-version: \"18.x\"\n      - run: npm install\n      - run: npm run check-dependencies\n      - run: npm test\n\n  # Push image to GitHub Packages.\n  # See also https://docs.docker.com/docker-hub/builds/\n  push:\n    # Ensure test job passes before pushing image.\n    needs: test\n\n    runs-on: ubuntu-latest\n    if: github.event_name == 'push'\n\n    steps:\n      - uses: actions/checkout@v2\n\n      - name: Build image\n        run: docker build . --file Dockerfile --tag $IMAGE_NAME\n\n      - name: Log into registry\n        run: echo \"${{ secrets.GITLAB_PASSWORD }}\" | docker login registry.gitlab.com -u ${{ secrets.GITLAB_USERNAME }} --password-stdin\n\n      - name: Push image\n        run: |\n          IMAGE_ID=registry.gitlab.com/jakewmeyer/spacex-api\n\n          # Change all uppercase to lowercase\n          IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]')\n\n          # Strip git ref prefix from version\n          VERSION=$(echo \"${{ github.ref }}\" | sed -e 's,.*/\\(.*\\),\\1,')\n\n          # Strip \"v\" prefix from tag name\n          [[ \"${{ github.ref }}\" == \"refs/tags/\"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//')\n\n          # Use Docker `latest` tag convention\n          [ \"$VERSION\" == \"master\" ] && VERSION=latest\n\n          echo IMAGE_ID=$IMAGE_ID\n          echo VERSION=$VERSION\n\n          docker tag $IMAGE_NAME $IMAGE_ID:$VERSION\n          docker push $IMAGE_ID:$VERSION\n"
  },
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n*.pid.lock\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# nyc test coverage\n.nyc_output\n\n# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# Bower dependency directory (https://bower.io/)\nbower_components\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (http://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directories\nnode_modules/\njspm_packages/\n\n# Typescript v1 declaration files\ntypings/\n\n# Optional npm cache directory\n.npm\n\n# Optional eslint cache\n.eslintcache\n\n# Optional REPL history\n.node_repl_history\n\n# Output of 'npm pack'\n*.tgz\n\n# Yarn Integrity file\n.yarn-integrity\n\n# dotenv environment variables file\n.env\n\n# General\n*.DS_Store\n.AppleDouble\n.LSOverride\n\n# Icon must end with two \\r\nIcon\n\n\n# Thumbnails\n._*\n\n# Files that might appear in the root of a volume\n.DocumentRevisions-V100\n.fseventsd\n.Spotlight-V100\n.TemporaryItems\n.Trashes\n.VolumeIcon.icns\n.com.apple.timemachine.donotpresent\n\n# Directories potentially created on remote AFP share\n.AppleDB\n.AppleDesktop\nNetwork Trash Folder\nTemporary Items\n.apdisk\n\n# JetBrains IDE project files\n/.idea\n\nTODO.md\ntest.json\nspacex-api\n.envrc"
  },
  {
    "path": "Dockerfile",
    "content": "\nFROM node:18-alpine\n\nLABEL maintainer=\"jakewmeyer@gmail.com\"\nLABEL autoheal=\"true\"\n\nHEALTHCHECK --interval=10s --timeout=3s \\\n  CMD ./lib/utils/healthcheck.js\n\nRUN apk add --no-cache --upgrade bash\n\nENV NODE_ENV=production\nENV HEALTH_URL=http://localhost:6673/v4/admin/health\n\nEXPOSE 6673\n\n# Run as an unprivileged user.\nRUN addgroup -S spacex && adduser -S -G spacex spacex \nRUN mkdir /app && chown spacex /app\nUSER spacex\n\nWORKDIR /app\nENTRYPOINT [\"/app/start.sh\"]\n\nCOPY --chown=spacex:spacex package.json package-lock.json /app/\n\nRUN npm install --production\n\nCOPY --chown=spacex:spacex . .\n"
  },
  {
    "path": "LICENSE",
    "content": "                                Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright 2017-2022 Jake Meyer\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License."
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\"><img src=\"https://live.staticflickr.com/65535/49185149122_37f5c52e43_k.jpg\"></p>\n\n<h1 align=\"center\">SpaceX REST API</h1>\n\n<h3 align=\"center\">\nOpen Source REST API for launch, rocket, core, capsule, starlink, launchpad, and landing pad data.\n</h3>\n\n<p align=\"center\">\n<a href=\"https://github.com/r-spacex/SpaceX-API/actions?query=workflow%3ATest\"><img src=\"https://img.shields.io/github/workflow/status/r-spacex/SpaceX-API/Test?style=flat-square\"></a>\n<a href=\"https://hub.docker.com/r/jakewmeyer/spacex-api/\"><img src=\"https://img.shields.io/docker/pulls/jakewmeyer/spacex-api?style=flat-square\"></a>\n<a href=\"https://github.com/r-spacex/SpaceX-API/releases\"><img src=\"https://img.shields.io/github/release/r-spacex/SpaceX-API.svg?longCache=true&style=flat-square\"></a>\n<a href=\"https://en.wikipedia.org/wiki/Representational_state_transfer\"><img src=\"https://img.shields.io/badge/interface-REST-brightgreen.svg?longCache=true&style=flat-square\"></a>\n</p>\n\n<h4 align=\"center\">\n  <i>\n    We are not affiliated, associated, authorized, endorsed by, or in any way officially connected with Space Exploration Technologies Corp (SpaceX), or any of its subsidiaries or its affiliates. The names SpaceX as well as related names, marks, emblems and images are registered trademarks of their respective owners.\n  </i>\n</h4>\n\n<h3 align=\"center\">\n<a href=\"docs/README.md\">Docs</a> - <a href=\"docs/clients.md\">API Clients</a> - <a href=\"docs/apps.md\">Apps</a> - <a href=\"https://status.spacexdata.com\">Status</a> - <a href=\"https://backups.spacexdata.com\">Database Exports</a>\n<br/>\n</h3>\n\n## Usage\n\n```\nGET https://api.spacexdata.com/v5/launches/latest\n```\n\n```json\n{\n  \"fairings\": null,\n  \"links\": {\n    \"patch\": {\n      \"small\": \"https://images2.imgbox.com/eb/0f/Vev7xkUX_o.png\",\n      \"large\": \"https://images2.imgbox.com/ab/79/Wyc9K7fv_o.png\"\n    },\n    \"reddit\": {\n      \"campaign\": \"https://www.reddit.com/r/spacex/comments/fjf6rr/dm2_launch_campaign_thread/\",\n      \"launch\": \"https://www.reddit.com/r/spacex/comments/glwz6n/rspacex_cctcap_demonstration_mission_2_general\",\n      \"media\": \"https://www.reddit.com/r/spacex/comments/gp1gf5/rspacex_dm2_media_thread_photographer_contest/\",\n      \"recovery\": \"https://www.reddit.com/r/spacex/comments/gu5gkd/cctcap_demonstration_mission_2_stage_1_recovery/\"\n    },\n    \"flickr\": {\n      \"small\": [],\n      \"original\": [\n        \"https://live.staticflickr.com/65535/49927519643_b43c6d4c44_o.jpg\",\n        \"https://live.staticflickr.com/65535/49927519588_8a39a3994f_o.jpg\",\n        \"https://live.staticflickr.com/65535/49928343022_6fb33cbd9c_o.jpg\",\n        \"https://live.staticflickr.com/65535/49934168858_cacb00d790_o.jpg\",\n        \"https://live.staticflickr.com/65535/49934682271_fd6a31becc_o.jpg\",\n        \"https://live.staticflickr.com/65535/49956109906_f88d815772_o.jpg\",\n        \"https://live.staticflickr.com/65535/49956109706_cffa847208_o.jpg\",\n        \"https://live.staticflickr.com/65535/49956109671_859b323ede_o.jpg\",\n        \"https://live.staticflickr.com/65535/49955609618_4cca01d581_o.jpg\",\n        \"https://live.staticflickr.com/65535/49956396622_975c116b71_o.jpg\",\n        \"https://live.staticflickr.com/65535/49955609378_9b77e5c771_o.jpg\",\n        \"https://live.staticflickr.com/65535/49956396262_ef41c1d9b0_o.jpg\"\n      ]\n    },\n    \"presskit\": \"https://www.nasa.gov/sites/default/files/atoms/files/commercialcrew_press_kit.pdf\",\n    \"webcast\": \"https://youtu.be/xY96v0OIcK4\",\n    \"youtube_id\": \"xY96v0OIcK4\",\n    \"article\": \"https://spaceflightnow.com/2020/05/30/nasa-astronauts-launch-from-us-soil-for-first-time-in-nine-years/\",\n    \"wikipedia\": \"https://en.wikipedia.org/wiki/Crew_Dragon_Demo-2\"\n  },\n  \"static_fire_date_utc\": \"2020-05-22T17:39:00.000Z\",\n  \"static_fire_date_unix\": 1590169140,\n  \"tdb\": false,\n  \"net\": false,\n  \"window\": 0,\n  \"rocket\": \"5e9d0d95eda69973a809d1ec\",\n  \"success\": true,\n  \"failures\": [],\n  \"details\": \"SpaceX will launch the second demonstration mission of its Crew Dragon vehicle as part of NASA's Commercial Crew Transportation Capability Program (CCtCap), carrying two NASA astronauts to the International Space Station. Barring unexpected developments, this mission will be the first crewed flight to launch from the United States since the end of the Space Shuttle program in 2011. DM-2 demonstrates the Falcon 9 and Crew Dragon's ability to safely transport crew to the space station and back to Earth and it is the last major milestone for certification of Crew Dragon. Initially the mission duration was planned to be no longer than two weeks, however NASA has been considering an extension to as much as six weeks or three months. The astronauts have been undergoing additional training for the possible longer mission.\",\n  \"crew\": [\n    \"5ebf1b7323a9a60006e03a7b\",\n    \"5ebf1a6e23a9a60006e03a7a\"\n  ],\n  \"ships\": [\n    \"5ea6ed30080df4000697c913\",\n    \"5ea6ed2f080df4000697c90b\",\n    \"5ea6ed2f080df4000697c90c\",\n    \"5ea6ed2e080df4000697c909\",\n    \"5ea6ed2f080df4000697c90d\"\n  ],\n  \"capsules\": [\n    \"5e9e2c5df359188aba3b2676\"\n  ],\n  \"payloads\": [\n    \"5eb0e4d1b6c3bb0006eeb257\"\n  ],\n  \"launchpad\": \"5e9e4502f509094188566f88\",\n  \"auto_update\": true,\n  \"flight_number\": 94,\n  \"name\": \"CCtCap Demo Mission 2\",\n  \"date_utc\": \"2020-05-30T19:22:00.000Z\",\n  \"date_unix\": 1590866520,\n  \"date_local\": \"2020-05-30T15:22:00-04:00\",\n  \"date_precision\": \"hour\",\n  \"upcoming\": false,\n  \"cores\": [\n    {\n      \"core\": \"5e9e28a7f3591817f23b2663\",\n      \"flight\": 1,\n      \"gridfins\": true,\n      \"legs\": true,\n      \"reused\": false,\n      \"landing_attempt\": true,\n      \"landing_success\": true,\n      \"landing_type\": \"ASDS\",\n      \"landpad\": \"5e9e3032383ecb6bb234e7ca\"\n    }\n  ],\n  \"id\": \"5eb87d46ffd86e000604b388\"\n}\n```\n\n## Cron Job Status\n\n<p align=\"left\">\n<img src=\"https://healthchecks.io/badge/a99e6369-9795-417e-9a1c-31ea91/zDDqPvw1-2/Capsules.svg\">\n<br/>\n<img src=\"https://healthchecks.io/badge/a99e6369-9795-417e-9a1c-31ea91/iJIWcg9u-2/Cores.svg\">\n<br/>\n<img src=\"https://healthchecks.io/badge/a99e6369-9795-417e-9a1c-31ea91/soA1Z2t1-2/Landpads.svg\">\n<br/>\n<img src=\"https://healthchecks.io/badge/a99e6369-9795-417e-9a1c-31ea91/tc7aK4Iw-2/Launchpads.svg\">\n<br/>\n<img src=\"https://healthchecks.io/badge/a99e6369-9795-417e-9a1c-31ea91/uB7PIyUo-2/Past-Launches.svg\">\n<br/>\n<img src=\"https://healthchecks.io/badge/a99e6369-9795-417e-9a1c-31ea91/bQw1ZrmZ-2/Payloads.svg\">\n<br/>\n<img src=\"https://healthchecks.io/badge/a99e6369-9795-417e-9a1c-31ea91/HhIoHcF6-2/Roadster.svg\">\n<br/>\n<img src=\"https://healthchecks.io/badge/a99e6369-9795-417e-9a1c-31ea91/wPz7gFQJ-2/Starlink.svg\">\n<br/>\n<img src=\"https://healthchecks.io/badge/a99e6369-9795-417e-9a1c-31ea91/3L5HxZKX-2/Upcoming-Launches.svg\">\n<br/>\n<img src=\"https://healthchecks.io/badge/a99e6369-9795-417e-9a1c-31ea91/YvnZYkED-2/Webcast.svg\">\n<br/>\n<img src=\"https://healthchecks.io/badge/a99e6369-9795-417e-9a1c-31ea91/_Z-lNpev-2/launch-library-v2.svg\">\n</p>\n\n## Sponsors\n\n### [Studio 3T](https://studio3t.com/)\n\n[![Studio 3T](https://imgur.com/DbJSfAo.png)](https://studio3t.com/)\n\n## FAQ's\n\n* If you have any questions or corrections, please open an issue and we'll get it merged ASAP\n* For any other questions or concerns, just shoot me an email.\n"
  },
  {
    "path": "app.js",
    "content": "import conditional from 'koa-conditional-get';\nimport etag from 'koa-etag';\nimport cors from 'koa2-cors';\nimport dotenv from 'dotenv';\nimport helmet from 'koa-helmet';\nimport Koa from 'koa';\nimport bodyParser from 'koa-bodyparser';\nimport mongoose from 'mongoose';\nimport { responseTime, errors, logger } from './middleware/index.js';\nimport routes from './routes/index.js';\n\n// Env init\ndotenv.config();\n\nconst app = new Koa();\n\nmongoose.connect(process.env.SPACEX_MONGO, {\n  bufferCommands: false,\n  family: 4,\n});\n\nconst db = mongoose.connection;\n\ndb.on('error', (err) => {\n  logger.error(err);\n});\ndb.once('connected', () => {\n  logger.info('Mongo connected');\n  app.emit('ready');\n});\ndb.on('reconnected', () => {\n  logger.info('Mongo re-connected');\n});\ndb.on('disconnected', () => {\n  logger.info('Mongo disconnected');\n});\n\n// disable console.errors for pino\napp.silent = true;\n\n// Error handler\napp.use(errors);\n\napp.use(conditional());\n\napp.use(etag());\n\napp.use(bodyParser());\n\n// HTTP header security\napp.use(helmet());\n\n// Enable CORS for all routes\napp.use(cors({\n  origin: '*',\n  allowMethods: ['GET', 'POST', 'PATCH', 'DELETE'],\n  allowHeaders: ['Content-Type', 'Accept'],\n  exposeHeaders: ['spacex-api-cache', 'spacex-api-response-time'],\n}));\n\n// Set header with API response time\napp.use(responseTime);\n\n// Register routes\napp.use(await routes());\n\nexport default app;\n"
  },
  {
    "path": "docs/README.md",
    "content": "# r/SpaceX API Docs\n\n[![Run in Postman](https://run.pstmn.io/button.svg)](https://app.getpostman.com/run-collection/ed4ed700dcc55b2c1f1c)\n\n## Disclaimer\n\n*We are not affiliated, associated, authorized, endorsed by, or in any way officially connected with Space Exploration Technologies Corp (SpaceX), or any of its subsidiaries or its affiliates. The names SpaceX as well as related names, marks, emblems and images are registered trademarks of their respective owners.*\n\n## Base URL\n\n`https://api.spacexdata.com`\n\n## Versioning\n\nEach route is individually versioned, see route list below for all avaliable versions. The API can also be pinned to the lastest version with `https://api.spacexdata.com/latest`, but only do this if you're cool with potential breaking changes.\n\n## Authentication\n\nAuthentication via api key is required for all destructive routes. This includes all `create`, `update`, and `delete` routes.\n\nAuthenticate by passing the header `spacex-key` with your api key. Protected routes return `401` without a valid key.\n\n## Pagination + Custom Queries\n\nAll `/query` routes support pagination, custom queries, and other output controls.\n\nSee the [pagination + query](queries.md) guide for more details and examples.\n\n## Launch date FAQ's\n\n* **Why does the date appear wrong?** - This is usually due to the way we store and display partial dates in the api. For example, a launch scheduled for `2020 July` would be represented as `2020-07-01T00:00:00.000Z`. In this case, the field `date_precision` would be set as `month`, meaning the date is only valid to the `month` level, or `2020-07`\n\n## Launch date field explanations\n\n* `date_utc` -  UTC launch date/time in ISO 8601 format\n\n* `date_unix` - UTC launch date/time as a UNIX timestamp in seconds\n\n* `date_local` -  Local launch time with time zone offset in ISO 8601 format\n\n* `date_precision` - Gives the date precision for partial dates. Valid values are `quarter`, `half`, `year`, `month`, `day`, `hour`.\n\n* `tbd` - Set as true if date is `To be determined`\n\n* `net` - Set as true if the date is `No earlier than`\n\n## Caching\n\nThe api makes use of response caching via Redis for all `GET` requests, and `POST` requests on `/query` endpoints.\n\nStandard cache times are as follows:\n\n**launches** - 20 seconds\n\n**capsules**, **cores**, **launchpads**, **landpads**, **crew**, **ships**, **payloads** - 5 minutes\n\n**dragons**, **rockets** - 24 hours\n\nCache can be cleared with the following endpoint:\n\n* 🔒 [Clear cache](cache/clear.md) : `DELETE /admin/cache`\n\n## Routes\n\n### [Capsules](capsules) - Detailed info for serialized dragon capsules\n\n### [Company Info](company) - Detailed info about SpaceX as a company\n\n### [Cores](cores) - Detailed info for serialized first stage cores\n\n### [Crew](crew) - Detailed info on dragon crew members\n\n### [Dragons](dragons) - Detailed info about dragon capsule versions\n\n### [Landpads](landpads) - Detailed info about landing pads and ships\n\n### [Launches](launches) - Detailed info about launches\n\n### [Launchpads](launchpads) - Detailed info about launchpads\n\n### [Payloads](payloads) - Detailed info about launch payloads\n\n### [Roadster info](roadster) - Detailed info about Elon's Tesla roadster's current position\n\n### [Rockets](rockets) - Detailed info about rocket versions\n\n### [Ships](ships) - Detailed info about ships in the SpaceX fleet\n\n### [Starlink](starlink) - Detailed info about Starlink satellites and orbits\n\nIncludes raw orbit data from [Space Track](https://www.space-track.org/auth/login), updated hourly.\n\nSpace Track data adheres to the standard for [Orbit Data Messages](https://public.ccsds.org/Pubs/502x0b2c1e2.pdf)\n\n### [History](history) - Detailed info on SpaceX historical events\n"
  },
  {
    "path": "docs/apps.md",
    "content": "# List of known Apps / UI clients\n\n> _Do you, or do you know of some app, that makes use of this community maintained service? If so, please [create an issue](https://github.com/r-spacex/SpaceX-API/issues/new) or [submit a PR](https://github.com/r-spacex/SpaceX-API/pulls) with additions to this list. Thanks_\n\n|App|Type|Creator(s)|Repo|More|\n|:---|:---|:---|:---|:---|\n| [2Space](https://play.google.com/store/apps/details?id=abandonedstudio.app.tospace) | Android App | [Pawel Kremienowski](https://github.com/riddick-boss) | [GitHub](https://github.com/riddick-boss/2Space) | |\n| [SpaceX](http://spacex.elc0mpa.me/#/) | Website | [elC0mpa](https://github.com/elC0mpa) | [GitHub](https://github.com/elC0mpa/spacex) | built with Vue3 & Composition API |\n| [SpaceX History](https://spacex-history.netlify.app) | Website | [Bedirhan Karadoğan](https://github.com/bedirhankaradogan) | [GitHub](https://github.com/bedirhankaradogan/spacex-history) | built with React |\n| [🚀 SpaceX CLI](https://www.npmjs.com/package/@orhanemree/spacex-cli) | CLI Tool | [Orhan Emre Dikicigil](https://github.com/orhanemree) | [GitHub](https://github.com/orhanemree/spacex-cli) | built with Node |\n| [SpaceX Live Info 🔴️](https://spacex-live.netlify.app/) | Website | [Pawel Pisulski](https://github.com/pislagz) | [GitHub](https://github.com/pislagz/spacex-live) | built with React & Redux |\n| [SpaceXtale](https://tsensei.github.io/spaceXtale/) | Website | [tsensei](https://github.com/tsensei/) | [GitHub](https://github.com/tsensei/spaceXtale) | React app with spaceX-api |\n| [Rocket Downrange](https://rocketdownrange.com) | Website | [Cavan Lemasters](https://github.com/TheKicker) | [GitHub](https://github.com/TheKicker/rocket-downrange) | |\n| [SpaceX-Dashboard🚀](https://thespacexdashboard.netlify.app/) | Website | [Sanjay Rajesh](https://github.com/sanjayrjs16) | [GitHub](https://github.com/sanjayrjs16/spaceX-dashboard-react)||\n| [SpaceX-Launches](https://spacex.prutkowski.tech/) | Website | [Piotr Rutkowski](https://github.com/PiotrRut) | [GitHub](https://github.com/PiotrRut/SpaceX-Launches) | |\n| [SpaceX and Mars](https://www.spacexandmars.com/) | Website | [Jiacheng Zhang](https://github.com/jiachengzhang1) | [GitHub](https://github.com/jiachengzhang1/spacex-and-mars) | |\n| [When is the next SpaceX launch](https://whenisthenextspacexlaunch.com) | Website | [Warwick Ward](https://warwick.io) | [GitHub](https://github.com/warwickofthegh/whenisthenextspacexlaunch.com) | |\n| [SpaceX Mission Watch](https://spacexmissionwatch.com) | Website | Quent McCoy | | [QMDD](https://quentmccoy.com) |\n| [SpaceX Stats](http://spacexstats.xyz) | Website | Luke Davia | [GitHub](https://github.com/r-spacex/spacexstats-react) |\n| [SpaceX Data.Info](http://spacexdata.info) | Website | [Charles Omer](https://www.charlesomer.co.uk) | | [Zyndex](https://www.zyndex.co.uk) |\n| [SpaceX Wiki](https://www.spacexwiki.com/) | Website | [Chris Stielper](https://github.com/cstielper) | [GitHub](https://github.com/cstielper/react-spacex-wiki) |\n| [X-Watch](https://x-watch.xyz/) | Website | [Matt Mills](https://github.com/mattmillsxyz) | [GitHub](https://github.com/mattmillsxyz/x-watch) |\n| [SpaceXLaunches.com](https://spacexlaunches.com) | Website | [louisjc](https://github.com/louisjc/) | [GitHub](https://github.com/louisjc/spacexlaunches.com) |\n| [SpaceFax](https://spacefax1.web.app) | Website | [James Hendrie](https://github.com/jimmyboix) | [GitHub](https://github.com/jimmyboix/SpaceFax) | |\n| [Deploy to Space](https://spacex-fs.deployto.space/) | Website | [Yannick Durden](https://github.com/YannickDurden) | | |\n| [SpaceX Track](https://www.spacextrack.com/) | Website | [Joe Dineen](https://github.com/jdineen21) | [GitHub](https://github.com/jdineen21/space_django) | [Portfolio](https://www.jdineen.co.uk) |\n| [SpaceX Guide Site](https://spacex-guide.weebly.com) | Website | Jared-Base | | [Mission Control Page](https://spacex-guide.weebly.com/mission-control.html) |\n| [SpaceX info Site](https://infospacex.vercel.app/) | Website | [Tigran Mkrtchyan](https://github.com/mkrtchyan98) | [Github](https://github.com/mkrtchyan98/SpaceInfo) |\n| [spacexdash](https://spacexdash.github.io/x) | Website | [spacexdash](https://github.com/spacexdash) | [Github](https://github.com/spacexdash/x/tree/master) | Easy searching of SpaceX data |\n| [x-info](https://x-info.eu) | Website | [Patryk Wojcieszak](https://github.com/PatrykWojcieszak) | [GitHub](https://github.com/PatrykWojcieszak/X-Info) | |\n| [SpaceX Launch Tracker](https://www.spacexlaunchtracker.com/) | Website | [Emil Sadek](https://github.com/esadek) | [GitHub](https://github.com/esadek/spacex-launch-tracker) | |\n| [SpX](https://apps.apple.com/gb/app/spx/id1511355787) | iOS App | [Russell Warwick](https://github.com/waruss321) | |\n| [XLaunch](https://apps.apple.com/us/app/xlaunch/id1502939751) | iOS App | [Travis Stanifer](https://github.com/stanifert) |  |\n| [SpaceXPedia](https://itunes.apple.com/app/spacexpedia/id1434177600?mt=8) | iOS App | [Philip Engberg](https://github.com/philipengberg) | [GitHub](https://github.com/philipengberg/SpaceXPedia) |\n| [SpaceDash](https://apps.apple.com/in/app/space-dash/id1527766640) | iOS App | [Pushpinder Pal Singh](https://github.com/pushpinderpalsingh) | [GitHub](https://github.com/pushpinderpalsingh/SpaceDash) |\n| [Space Xploration](https://apps.apple.com/app/space-xploration/id1530580909) | iOS App | [Bartolome Estelrich](https://github.com/BEstelrich) | | |\n| [SpacExtra](https://apps.apple.com/ca/app/spacextra/id1559360281) | iOS App | [Charlie Kingsland](https://github.com/ChopsKingsland) | | [Website](https://spacextra.github.io) |\n| [Norminal](https://apps.apple.com/app/norminal/id1540171547) | iOS App | [Riccardo Persello](https://github.com/persello) | [GitHub](https://github.com/persello/norminal) | |\n| [SpaceX Tracker](https://play.google.com/store/apps/details?id=com.magnetar.spacexlauncher) | Android App | [Timo Nelen](https://github.com/TNelen) | [Github](https://github.com/TNelen/SpacexLaunchApp) |  |\n| [SpaceX Companion](https://play.google.com/store/apps/details?id=nl.studionoorderlicht.spacex) | Android App | [Jeroen Boumans](https://github.com/jeroenboumans) | Not open sourced yet | [Website](https://spacexcompanion.app)\n| [SpaceXLaunches](https://play.google.com/store/apps/details?id=com.danielscholte.spacexlaunches) | Android App | [Daniël Scholte](https://github.com/linuxfreak23) | Closed source |\n| [SpaceX GO!](https://play.google.com/store/apps/details?id=com.chechu.cherry) | Android App | [Chechu](https://github.com/jesusrp98) | [GitHub](https://github.com/jesusrp98/spacex-go) |\n| [SpaceX Universe](https://play.google.com/store/apps/details?id=com.aastudio.spacexuniverse) | Android App | [Oleksii Chernov](https://github.com/chert12) | [Github](https://github.com/chert12/SpaceX-Universe) | |\n| [SpaceXFollower](https://github.com/OMIsie11/SpaceXFollower) | Android App | [Oskar Misiewicz @OMIsie11](https://omisie11.github.io) | [Github](https://github.com/OMIsie11/SpaceXFollower) | |\n| [Spacexopedia](https://play.google.com/store/apps/details?id=com.thealphamerc.flutter_spacexopedia) | Android App | [Sonu Sharma](https://github.com/TheAlphamerc) | [GitHub](https://github.com/TheAlphamerc/flutter_spacexopedia) | |\n| [SpaceX Wiki](https://github.com/prasannajeet/SpaceX_Wiki_KMM_iOS_Android) | Android & iOS App | [Prasan @prasannajeet](https://github.com/prasannajeet) | [Github](https://github.com/prasannajeet/SpaceX_Wiki_KMM_iOS_Android) | Built with Kotlin Multiplatform Mobile |\n| [Launchpad](https://github.com/skyffx/Launchpad/releases) | Windows App | [Wojciech Piekielniak](https://github.com/skyffx/) | [Github](https://github.com/skyffx/Launchpad) |\n| [InElonWeTrust](https://github.com/Tearth/InElonWeTrust) | Discord bot | [Tearth](https://github.com/Tearth) | [GitHub](https://github.com/Tearth/InElonWeTrust) |\n| [SpaceX-Launch-Bot](https://github.com/SpaceXLaunchBot/SpaceXLaunchBot) | Discord bot | [Simon](https://github.com/psidex) | [Github](https://github.com/SpaceXLaunchBot/SpaceXLaunchBot) | |\n| [Space Launch Now](https://spacelaunchnow.me/) | API/Website/Apps | [Caleb Jones](https://github.com/ItsCalebJones) | [Github](https://github.com/ItsCalebJones/SpaceLaunchNow-Server) | |\n| [AV-SpaceX DB](https://av-spacex.surge.sh/) | API/Website | [Aswin Varghese](http://aswinvarghese.com) | [Github]() | |\n| SpaceX Info | Alexa Skill | Andrew Shapton | [Github](https://github.com/alshapton/Space-X-Info-Alexa.git) | Invoke with \"Alexa Open SpaceX Info\" |\n| [SpaceX - Launch Tracker](https://play.google.com/store/apps/details?id=uk.co.zac_h.spacex) | Android App | [Zac Hadjineophytou](https://github.com/zacdevil10) | [GitHub](https://github.com/zacdevil10/spacex-launch-tracker) |\n| [Space Bot](https://t.me/spacex_mezgoodle_bot) | Telegram Bot | [Maxim Zavalniuk](https://github.com/mezgoodle) | [GitHub](https://github.com/mezgoodle/space-bot) |\n| [SpaceX Launch Data](http://spacexlaunchdata.com) | Website | [Sam Girshovich](https://github.com/samg11) | [GitHub](https://github.com/samg11/SpaceX-Launch-Data) |\n| [SpaceX Times](https://play.google.com/store/apps/details?id=ru.alexmaryin.spacextimes_rx) | Android App | [Alex Maryin](https://github.com/alexmaryin) | [GitHub](https://github.com/alexmaryin/spacextimes) |\n| [SpaceX Missions](https://spacex-missions.netlify.app) | Website | [Bruno Porcel](https://github.com/bporcel) | [GitHub](https://github.com/bporcel/Space-X) |\n| [Launch Tracker - SpaceX](https://play.google.com/store/apps/details?id=com.bvdwalt.spacex_flights) | Android App | [Bennie van der Walt](https://github.com/bvdwalt) | [GitHub](https://github.com/bvdwalt/Launch-Tracker-SpaceX)\n| [SpaceX Dashboard](https://tdunn891.github.io/spacex-dashboard/) | Website | [Thomas Dunn](https://github.com/tdunn891) | [GitHub](https://github.com/tdunn891/spacex-dashboard) | |\n| [SpaceX Launches](https://spacexlaunches.github.io/) | Website | [l3ycle](https://github.com/l3ycle) | [GitHub](https://github.com/spacexlaunches/spacexlaunches.github.io/) |  |\n| [SpaceX-Launches](https://amazing-austin-1853eb.netlify.app) 🚀 | Website | [Atamyrat Abdyrahmanov](https://github.com/aaabdyrahmanov) | [GitHub](https://github.com/aaabdyrahmanov/SpaceX-Launches) |  |\n| [SpaceX history timeline](https://www.orbitaterrestre.com/la-storia-di-spaceX-in-timeline-component) | Website | [Vincenzo](https://github.com/vincenzomarcovecchio) | [GitHub](https://github.com/aaabdyrahmanov/SpaceX-Launches) |  |\n| [Elon Musk Tracker](https://play.google.com/store/apps/details?id=com.ingenuity.elonmusktracker) | Android/iOS App | [Aman Trifi](https://github.com/TrifiAmanallah) | Closed source |  |\n| [SpaceX Data](https://spacexdata.pages.dev/) | Website | [Chien Tran](https://github.com/chientrm) | Closed source | Built with SvelteKit, deployed with Cloudflare Pages |\n| [SpaceX Portal](https://spacex-portal.vercel.app) | Website | [Daniel Yasakov](https://github.com/ne-danik) | [GitHub](https://github.com/ne-danik/spacex-portal) | built with React |\n"
  },
  {
    "path": "docs/capsules/v4/all.md",
    "content": "# Get all capsules\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/capsules`\n\n**Auth required** : `False`\n\n## Success Responses\n\n**Code** : `200 OK`\n\n```json\n[\n    {\n        \"reuse_count\": 1,\n        \"water_landings\": 1,\n        \"land_landings\": 0,\n        \"last_update\": \"Reentered after three weeks in orbit\",\n        \"launches\": [\n            \"5eb87cdeffd86e000604b330\"\n        ],\n        \"serial\": \"C101\",\n        \"status\": \"retired\",\n        \"type\": \"Dragon 1.0\",\n        \"id\": \"5e9e2c5bf35918ed873b2664\"\n    },\n    ...\n]\n```\n"
  },
  {
    "path": "docs/capsules/v4/one.md",
    "content": "# Get one capsule\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/capsules/:id`\n\n**URL Parameters** : `id=[string]` where `id` is the ID of the capsule\n\n**Auth required** : `False`\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n    \"reuse_count\": 1,\n    \"water_landings\": 1,\n    \"land_landings\": 0,\n    \"last_update\": \"Reentered after three weeks in orbit\",\n    \"launches\": [\n        \"5eb87cdeffd86e000604b330\"\n    ],\n    \"serial\": \"C101\",\n    \"status\": \"retired\",\n    \"type\": \"Dragon 1.0\",\n    \"id\": \"5e9e2c5bf35918ed873b2664\"\n}\n```\n\n## Error Responses\n\n**Code** : `404 NOT FOUND`\n\n**Content** : `Not Found`\n"
  },
  {
    "path": "docs/capsules/v4/query.md",
    "content": "# Query capsules\n\n**Method** : `POST`\n\n**URL** : `https://api.spacexdata.com/v4/capsules/query`\n\n**Auth required** : `False`\n\n**Body** :\n\nSee [query](../../queries.md) guide for more details on building queries and paginating results.\n\n```json\n{\n  \"query\": {},\n  \"options\": {}\n}\n```\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n    \"docs\": [\n        {\n            \"reuse_count\": 1,\n            \"water_landings\": 1,\n            \"land_landings\": 0,\n            \"last_update\": \"Reentered after three weeks in orbit\",\n            \"launches\": [\n                \"5eb87cdeffd86e000604b330\"\n            ],\n            \"serial\": \"C101\",\n            \"status\": \"retired\",\n            \"type\": \"Dragon 1.0\",\n            \"id\": \"5e9e2c5bf35918ed873b2664\"\n        },\n        ...\n    ],\n    \"totalDocs\": 19,\n    \"offset\": 0,\n    \"limit\": 10,\n    \"totalPages\": 2,\n    \"page\": 1,\n    \"pagingCounter\": 1,\n    \"hasPrevPage\": false,\n    \"hasNextPage\": true,\n    \"prevPage\": null,\n    \"nextPage\": 2\n}\n```\n\n## Error Responses\n\n**Code** : `400 Bad Request`\n\n**Content** : Mongoose error is shown, with suggestions to fix the query.\n"
  },
  {
    "path": "docs/capsules/v4/schema.md",
    "content": "# Capsule Schema\n\n```json\n{\n  \"serial\": {\n    \"type\": \"String\",\n    \"required\": true,\n    \"unique\": true,\n  },\n  \"status\": {\n    \"type\": \"String\",\n    \"enum\": [\"unknown\", \"active\", \"retired\", \"destroyed\"],\n    \"required\": true,\n  },\n  \"type\": {\n    \"type\": \"String\",\n    \"enum\": [\"Dragon 1.0\", \"Dragon 1.1\", \"Dragon 2.0\"],\n    \"required\": true,\n  },\n  \"dragon\": {\n    \"type\": \"UUID\",\n  },\n  \"reuse_count\": {\n    \"type\": \"Number\",\n    \"default\": 0,\n  },\n  \"water_landings\": {\n    \"type\": \"Number\",\n    \"default\": 0,\n  },\n  \"land_landings\": {\n    \"type\": \"Number\",\n    \"default\": 0,\n  },\n  \"last_update\": {\n    \"type\": \"String\",\n    \"default\": null,\n  },\n  \"launches\": [{\n    \"type\": \"UUID\",\n  }],\n}\n```\n"
  },
  {
    "path": "docs/clients.md",
    "content": "# List of known API clients / wrappers\n\n> _Do you, or do you know of some client/wrapper, that makes use of this community maintained service? If so, please [create an issue](https://github.com/r-spacex/SpaceX-API/issues/new) or [submit a PR](https://github.com/r-spacex/SpaceX-API/pulls) with additions to this list. Thanks_\n\nNOTE: Clients are grouped by API version(s) supported\n\n### V4 (Latest)\n\n|Name|Lang|Creator(s)|Repo|\n|:---|:---|:---|:---|\n| Oddity | .NET  | Tearth | [GitHub](https://github.com/Tearth/Oddity) |\n| SpaceXPy | Python | Ryu JuHeon | [GitHub](https://github.com/SaidBySolo/SpaceXPy) |\n| KSBSpacexKit | Swift | SaiBalaji K| [GitHub](https://github.com/SaiBalaji22/KSBSpacexKit) |\n| Marsy | C++ | AzuxDario | [GitHub](https://github.com/AzuxDario/Marsy) |\n| Spacex-api.js | Node.js | AkiaCode | [Github](https://github.com/AkiaCode/spacex-api.js) |\n| spacex_api | Dart | Ahsanz024 | [Github](https://github.com/ahsanz024/spacex_api) |\n| spacex_api | Ruby | Victor Perez | [Github](https://github.com/victorperez/spacex-api-ruby) |\n| xploration-graphql | TypeScript | Kartik Kumar Gujarati | [Github](https://github.com/Kartikkumargujarati/xploration-graphql) |\n| spacex-graphql-gateway | TypeScript | Kevin Hermawan | [Github](https://github.com/kevinstd/spacex-graphql-gateway) |\n| r/SpaceX (Independent Publisher) | [Power Platform](https://docs.microsoft.com/en-us/connectors/rspacexip/) | Troy Taylor | [Github](https://github.com/troystaylor/PowerPlatformConnectors/tree/r/SpaceX/independent-publisher-connectors/rSpaceX) |\n| spacex-api | Java | Andrey Vasilyev | [Github](https://github.com/artfultom/spacex-api) |\n\n### V3, V2, V1 (Deprecated)\n\n|Name|Lang|Creator(s)|Repo|\n|:---|:---|:---|:---|\n| SpaceX-GraphQL | TypeScript | Jordan Owens | [GitHub](https://github.com/jor-dan/SpaceX-GraphQL) |\n| spacex-graphql-api | GraphQL | Emerson Laurentino | [GitHub](https://github.com/emersonlaurentino/spacex-qraphql-api) |\n| SpaceX-API-Wrapper | Node.js | Thomas Smyth | [GitHub](https://github.com/Thomas-Smyth/SpaceX-API-Wrapper) |\n| spacex-api | TypeScript / Node.js | Tomasz Borowski | [GitHub](https://github.com/tbprojects/spacex-api), [NPM](https://www.npmjs.com/package/spacex-api) |\n| SpaceX | PowerShell | François-Xavier Cat | [GitHub](https://github.com/lazywinadmin/SpaceX) |\n| SpacePY-X | Python | Andrew Shapton | [GitHub](https://github.com/alshapton/SpacePY-X) |\n| SpaceX-PY | Python | Kaylum Lally | [GitHub](https://github.com/HiKaylum/SpaceX-PY) |\n| SpaceNIM-X | Nim | Andrew Shapton | [GitHub](https://github.com/alshapton/SpaceNIM-X) |\n| SpaceCRYST-X | Crystal | Andrew Shapton | [GitHub](https://github.com/alshapton/SpaceCRYST-X) |\n| spacex | Go | Or Hiltch | [GitHub](https://github.com/orcaman/spacex) |\n| spacex-api-wrapper | Rust | Alex Gutan | [GitHub](https://github.com/AGutan/spacex-api-wrapper)|\n| spacex-graphql-rust | Rust | Aaron Feigenbaum | [GitHub](https://github.com/adace123/spacex-graphql-rust)|\n| space-rx | Rust | Tyler Wilcock | [GitHub](https://github.com/twilco/space-rx) |\n| spacex | Ruby | Rodolfo | [GitHub](https://github.com/rodolfobandeira/spacex) |\n| spacex | PHP | Aires Gonçalves | [GitHub](https://github.com/airesvsg/spacex) |\n| spacex_ex | Elixir | Chen Zhao | [GitHub](https://github.com/crunchysoul/spacex_ex) |\n| SpaceX | R | Johannes Friedrich | [GitHub](https://github.com/JohannesFriedrich/SpaceX) |\n| SpaceXAPI-Swift | Swift | Sami Sharafeddine | [GitHub](https://github.com/devsamsh/SpaceXAPI-Swift) |\n"
  },
  {
    "path": "docs/company/v4/all.md",
    "content": "# Get all company info\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/company`\n\n**Auth required** : `False`\n\n## Success Responses\n\n**Code** : `200 OK`\n\n```json\n{\n    \"headquarters\": {\n        \"address\": \"Rocket Road\",\n        \"city\": \"Hawthorne\",\n        \"state\": \"California\"\n    },\n    \"links\": {\n        \"website\": \"https://www.spacex.com/\",\n        \"flickr\": \"https://www.flickr.com/photos/spacex/\",\n        \"twitter\": \"https://twitter.com/SpaceX\",\n        \"elon_twitter\": \"https://twitter.com/elonmusk\"\n    },\n    \"name\": \"SpaceX\",\n    \"founder\": \"Elon Musk\",\n    \"founded\": 2002,\n    \"employees\": 8000,\n    \"vehicles\": 3,\n    \"launch_sites\": 3,\n    \"test_sites\": 1,\n    \"ceo\": \"Elon Musk\",\n    \"cto\": \"Elon Musk\",\n    \"coo\": \"Gwynne Shotwell\",\n    \"cto_propulsion\": \"Tom Mueller\",\n    \"valuation\": 52000000000,\n    \"summary\": \"SpaceX designs, manufactures and launches advanced rockets and spacecraft. The company was founded in 2002 to revolutionize space technology, with the ultimate goal of enabling people to live on other planets.\",\n    \"id\": \"5eb75edc42fea42237d7f3ed\"\n}\n```\n"
  },
  {
    "path": "docs/company/v4/schema.md",
    "content": "# Company Info Schema\n\n```json\n{\n  \"name\": {\n    \"type\": \"String\"\n  },\n  \"founder\": {\n    \"type\": \"String\"\n  },\n  \"founded\": {\n    \"type\": \"Number\"\n  },\n  \"employees\": {\n    \"type\": \"Number\"\n  },\n  \"vehicles\": {\n    \"type\": \"Number\"\n  },\n  \"launch_sites\": {\n    \"type\": \"Number\"\n  },\n  \"test_sites\": {\n    \"type\": \"Number\"\n  },\n  \"ceo\": {\n    \"type\": \"String\"\n  },\n  \"cto\": {\n    \"type\": \"String\"\n  },\n  \"coo\": {\n    \"type\": \"String\"\n  },\n  \"cto_propulsion\": {\n    \"type\": \"String\"\n  },\n  \"valuation\": {\n    \"type\": \"Number\"\n  },\n  \"headquarters\": {\n    \"address\": {\n      \"type\": \"String\"\n    },\n    \"city\": {\n      \"type\": \"String\"\n    },\n    \"state\": {\n      \"type\": \"String\"\n    }\n  },\n  \"links\": {\n    \"website\": {\n      \"type\": \"String\"\n    },\n    \"flickr\": {\n      \"type\": \"String\"\n    },\n    \"twitter\": {\n      \"type\": \"String\"\n    },\n    \"elon_twitter\": {\n      \"type\": \"String\"\n    }\n  },\n  \"summary\": {\n    \"type\": \"String\"\n  }\n}\n```\n"
  },
  {
    "path": "docs/cores/v4/all.md",
    "content": "# Get all cores\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/cores`\n\n**Auth required** : `False`\n\n## Success Responses\n\n**Code** : `200 OK`\n\n```json\n[\n    {\n        \"block\": 5,\n        \"reuse_count\": 3,\n        \"rtls_attempts\": 1,\n        \"rtls_landings\": 1,\n        \"asds_attempts\": 3,\n        \"asds_landings\": 3,\n        \"last_update\": \"Landed on OCISLY as of Jan 29, 2020. \",\n        \"launches\": [\n            \"5eb87d2bffd86e000604b375\",\n            \"5eb87d31ffd86e000604b379\",\n            \"5eb87d3fffd86e000604b382\",\n            \"5eb87d44ffd86e000604b386\"\n        ],\n        \"serial\": \"B1051\",\n        \"status\": \"active\",\n        \"id\": \"5e9e28a6f35918c0803b265c\"\n    },\n    ...\n]\n```\n"
  },
  {
    "path": "docs/cores/v4/one.md",
    "content": "# Get one core\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/cores/:id`\n\n**URL Parameters** : `id=[string]` where `id` is the ID of the core\n\n**Auth required** : `False`\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n    \"block\": 5,\n    \"reuse_count\": 3,\n    \"rtls_attempts\": 1,\n    \"rtls_landings\": 1,\n    \"asds_attempts\": 3,\n    \"asds_landings\": 3,\n    \"last_update\": \"Landed on OCISLY as of Jan 29, 2020. \",\n    \"launches\": [\n        \"5eb87d2bffd86e000604b375\",\n        \"5eb87d31ffd86e000604b379\",\n        \"5eb87d3fffd86e000604b382\",\n        \"5eb87d44ffd86e000604b386\"\n    ],\n    \"serial\": \"B1051\",\n    \"status\": \"active\",\n    \"id\": \"5e9e28a6f35918c0803b265c\"\n}\n```\n\n## Error Responses\n\n**Code** : `404 NOT FOUND`\n\n**Content** : `Not Found`\n"
  },
  {
    "path": "docs/cores/v4/query.md",
    "content": "# Query cores\n\n**Method** : `POST`\n\n**URL** : `https://api.spacexdata.com/v4/cores/query`\n\n**Auth required** : `False`\n\n**Body** :\n\nSee [query](../../queries.md) guide for more details on building queries and paginating results.\n\n```json\n{\n  \"query\": {},\n  \"options\": {}\n}\n```\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n    \"docs\": [\n      {\n        \"block\": 5,\n        \"reuse_count\": 3,\n        \"rtls_attempts\": 1,\n        \"rtls_landings\": 1,\n        \"asds_attempts\": 2,\n        \"asds_landings\": 2,\n        \"last_update\": \"Missed the droneship and made successful water landing; apparently scuttled at sea afterward. \",\n        \"launches\": [\n            \"5eb87d2effd86e000604b377\",\n            \"5eb87d36ffd86e000604b37b\",\n            \"5eb87d3bffd86e000604b37f\",\n            \"5eb87d41ffd86e000604b383\"\n        ],\n        \"serial\": \"B1056\",\n        \"status\": \"lost\",\n        \"id\": \"5e9e28a7f3591809313b2660\"\n      },\n      ...\n    ],\n    \"totalDocs\": 65,\n    \"offset\": 0,\n    \"limit\": 10,\n    \"totalPages\": 7,\n    \"page\": 1,\n    \"pagingCounter\": 1,\n    \"hasPrevPage\": false,\n    \"hasNextPage\": true,\n    \"prevPage\": null,\n    \"nextPage\": 2\n}\n```\n\n## Error Responses\n\n**Code** : `400 Bad Request`\n\n**Content** : Mongoose error is shown, with suggestions to fix the query.\n"
  },
  {
    "path": "docs/cores/v4/schema.md",
    "content": "# Core Schema\n\n```json\n{\n  \"serial\": {\n    \"type\": \"String\",\n    \"unique\": true,\n    \"required\": true,\n  },\n  \"block\": {\n    \"type\": \"Number\",\n    \"default\": null,\n  },\n  \"status\": {\n    \"type\": \"String\",\n    \"enum\": [\"active\", \"inactive\", \"unknown\", \"expended\", \"lost\", \"retired\"],\n    \"required\": true,\n  },\n  \"reuse_count\": {\n    \"type\": \"Number\",\n    \"default\": 0,\n  },\n  \"rtls_attempts\": {\n    \"type\": \"Number\",\n    \"default\": 0,\n  },\n  \"rtls_landings\": {\n    \"type\": \"Number\",\n    \"default\": 0,\n  },\n  \"asds_attempts\": {\n    \"type\": \"Number\",\n    \"default\": 0,\n  },\n  \"asds_landings\": {\n    \"type\": \"Number\",\n    \"default\": 0,\n  },\n  \"last_update\": {\n    \"type\": \"String\",\n    \"default\": null,\n  },\n  \"launches\": [{\n    \"type\": \"UUID\",\n  }],\n}\n```\n"
  },
  {
    "path": "docs/crew/v4/all.md",
    "content": "# Get all crew\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/crew`\n\n**Auth required** : `False`\n\n## Success Responses\n\n**Code** : `200 OK`\n\n```json\n[\n    {\n        \"name\": \"Robert Behnken\",\n        \"agency\": \"NASA\",\n        \"image\": \"https://imgur.com/0smMgMH.png\",\n        \"wikipedia\": \"https://en.wikipedia.org/wiki/Robert_L._Behnken\",\n        \"launches\": [\n            \"5eb87d46ffd86e000604b388\"\n        ],\n        \"status\": \"active\",\n        \"id\": \"5ebf1a6e23a9a60006e03a7a\"\n    },\n    ...\n]\n```\n"
  },
  {
    "path": "docs/crew/v4/one.md",
    "content": "# Get one crew member\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/crew/:id`\n\n**URL Parameters** : `id=[string]` where `id` is the ID of the crew member\n\n**Auth required** : `False`\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n    \"name\": \"Douglas Hurley\",\n    \"agency\": \"NASA\",\n    \"image\": \"https://i.imgur.com/ooaayWf.png\",\n    \"wikipedia\": \"https://en.wikipedia.org/wiki/Douglas_G._Hurley\",\n    \"launches\": [\n        \"5eb87d46ffd86e000604b388\"\n    ],\n    \"status\": \"active\",\n    \"id\": \"5ebf1b7323a9a60006e03a7b\"\n}\n```\n\n## Error Responses\n\n**Code** : `404 NOT FOUND`\n\n**Content** : `Not Found`\n"
  },
  {
    "path": "docs/crew/v4/query.md",
    "content": "# Query crew members\n\n**Method** : `POST`\n\n**URL** : `https://api.spacexdata.com/v4/crew/query`\n\n**Auth required** : `False`\n\n**Body** :\n\nSee [query](../../queries.md) guide for more details on building queries and paginating results.\n\n```json\n{\n  \"query\": {},\n  \"options\": {}\n}\n```\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n    \"docs\": [\n        {\n            \"name\": \"Robert Behnken\",\n            \"agency\": \"NASA\",\n            \"image\": \"https://imgur.com/0smMgMH.png\",\n            \"wikipedia\": \"https://en.wikipedia.org/wiki/Robert_L._Behnken\",\n            \"launches\": [\n                \"5eb87d46ffd86e000604b388\"\n            ],\n            \"status\": \"active\",\n            \"id\": \"5ebf1a6e23a9a60006e03a7a\"\n        },\n        ...\n    ],\n    \"totalDocs\": 2,\n    \"offset\": 0,\n    \"limit\": 10,\n    \"totalPages\": 1,\n    \"page\": 1,\n    \"pagingCounter\": 1,\n    \"hasPrevPage\": false,\n    \"hasNextPage\": false,\n    \"prevPage\": null,\n    \"nextPage\": null\n}\n```\n\n## Error Responses\n\n**Code** : `400 Bad Request`\n\n**Content** : Mongoose error is shown, with suggestions to fix the query.\n"
  },
  {
    "path": "docs/crew/v4/schema.md",
    "content": "# Crew Schema\n\n```json\n{\n  \"name\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"status\": {\n    \"type\": \"String\",\n    \"required\": true,\n    \"enum\": [\"active\", \"inactive\", \"retired\", \"unknown\"]\n  },\n  \"agency\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"image\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"wikipedia\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"launches\": [{\n      \"type\": \"UUID\"\n  }]\n}\n```\n"
  },
  {
    "path": "docs/dragons/v4/all.md",
    "content": "# Get all Dragons\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/dragons`\n\n**Auth required** : `False`\n\n## Success Responses\n\n**Code** : `200 OK`\n\n```json\n[\n  {\n      \"heat_shield\": {\n          \"material\": \"PICA-X\",\n          \"size_meters\": 3.6,\n          \"temp_degrees\": 3000,\n          \"dev_partner\": \"NASA\"\n      },\n      \"launch_payload_mass\": {\n          \"kg\": 6000,\n          \"lb\": 13228\n      },\n      \"launch_payload_vol\": {\n          \"cubic_meters\": 25,\n          \"cubic_feet\": 883\n      },\n      \"return_payload_mass\": {\n          \"kg\": 3000,\n          \"lb\": 6614\n      },\n      \"return_payload_vol\": {\n          \"cubic_meters\": 11,\n          \"cubic_feet\": 388\n      },\n      \"pressurized_capsule\": {\n          \"payload_volume\": {\n              \"cubic_meters\": 11,\n              \"cubic_feet\": 388\n          }\n      },\n      \"trunk\": {\n          \"trunk_volume\": {\n              \"cubic_meters\": 14,\n              \"cubic_feet\": 494\n          },\n          \"cargo\": {\n              \"solar_array\": 2,\n              \"unpressurized_cargo\": true\n          }\n      },\n      \"height_w_trunk\": {\n          \"meters\": 7.2,\n          \"feet\": 23.6\n      },\n      \"diameter\": {\n          \"meters\": 3.7,\n          \"feet\": 12\n      },\n      \"first_flight\": \"2010-12-08\",\n      \"flickr_images\": [\n          \"https://www.spacex.com/sites/spacex/files/styles/media_gallery_large/public/2015_-_04_crs5_dragon_orbit13.jpg?itok=9p8_l7UP\",\n          \"https://www.spacex.com/sites/spacex/files/styles/media_gallery_large/public/2012_-_4_dragon_grapple_cots2-1.jpg?itok=R2-SeuMX\",\n          \"https://farm3.staticflickr.com/2815/32761844973_4b55b27d3c_b.jpg\",\n          \"https://farm9.staticflickr.com/8618/16649075267_d18cbb4342_b.jpg\"\n      ],\n      \"name\": \"Dragon 1\",\n      \"type\": \"capsule\",\n      \"active\": true,\n      \"crew_capacity\": 0,\n      \"sidewall_angle_deg\": 15,\n      \"orbit_duration_yr\": 2,\n      \"dry_mass_kg\": 4200,\n      \"dry_mass_lb\": 9300,\n      \"thrusters\": [\n          {\n              \"type\": \"Draco\",\n              \"amount\": 18,\n              \"pods\": 4,\n              \"fuel_1\": \"nitrogen tetroxide\",\n              \"fuel_2\": \"monomethylhydrazine\",\n              \"isp\": 300,\n              \"thrust\": {\n                  \"kN\": 0.4,\n                  \"lbf\": 90\n              }\n          }\n      ],\n      \"wikipedia\": \"https://en.wikipedia.org/wiki/SpaceX_Dragon\",\n      \"description\": \"Dragon is a reusable spacecraft developed by SpaceX, an American private space transportation company based in Hawthorne, California. Dragon is launched into space by the SpaceX Falcon 9 two-stage-to-orbit launch vehicle. The Dragon spacecraft was originally designed for human travel, but so far has only been used to deliver cargo to the International Space Station (ISS).\",\n      \"id\": \"5e9d058759b1ff74a7ad5f8f\"\n  },\n  ...\n]\n```\n"
  },
  {
    "path": "docs/dragons/v4/one.md",
    "content": "# Get one Dragon\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/dragons/:id`\n\n**URL Parameters** : `id=[string]` where `id` is the ID of the dragon\n\n**Auth required** : `False`\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n    \"heat_shield\": {\n        \"material\": \"PICA-X\",\n        \"size_meters\": 3.6,\n        \"temp_degrees\": 3000,\n        \"dev_partner\": \"NASA\"\n    },\n    \"launch_payload_mass\": {\n        \"kg\": 6000,\n        \"lb\": 13228\n    },\n    \"launch_payload_vol\": {\n        \"cubic_meters\": 25,\n        \"cubic_feet\": 883\n    },\n    \"return_payload_mass\": {\n        \"kg\": 3000,\n        \"lb\": 6614\n    },\n    \"return_payload_vol\": {\n        \"cubic_meters\": 11,\n        \"cubic_feet\": 388\n    },\n    \"pressurized_capsule\": {\n        \"payload_volume\": {\n            \"cubic_meters\": 11,\n            \"cubic_feet\": 388\n        }\n    },\n    \"trunk\": {\n        \"trunk_volume\": {\n            \"cubic_meters\": 14,\n            \"cubic_feet\": 494\n        },\n        \"cargo\": {\n            \"solar_array\": 2,\n            \"unpressurized_cargo\": true\n        }\n    },\n    \"height_w_trunk\": {\n        \"meters\": 7.2,\n        \"feet\": 23.6\n    },\n    \"diameter\": {\n        \"meters\": 3.7,\n        \"feet\": 12\n    },\n    \"first_flight\": \"2010-12-08\",\n    \"flickr_images\": [\n        \"https://www.spacex.com/sites/spacex/files/styles/media_gallery_large/public/2015_-_04_crs5_dragon_orbit13.jpg?itok=9p8_l7UP\",\n        \"https://www.spacex.com/sites/spacex/files/styles/media_gallery_large/public/2012_-_4_dragon_grapple_cots2-1.jpg?itok=R2-SeuMX\",\n        \"https://farm3.staticflickr.com/2815/32761844973_4b55b27d3c_b.jpg\",\n        \"https://farm9.staticflickr.com/8618/16649075267_d18cbb4342_b.jpg\"\n    ],\n    \"name\": \"Dragon 1\",\n    \"type\": \"capsule\",\n    \"active\": true,\n    \"crew_capacity\": 0,\n    \"sidewall_angle_deg\": 15,\n    \"orbit_duration_yr\": 2,\n    \"dry_mass_kg\": 4200,\n    \"dry_mass_lb\": 9300,\n    \"thrusters\": [\n        {\n            \"type\": \"Draco\",\n            \"amount\": 18,\n            \"pods\": 4,\n            \"fuel_1\": \"nitrogen tetroxide\",\n            \"fuel_2\": \"monomethylhydrazine\",\n            \"isp\": 300,\n            \"thrust\": {\n                \"kN\": 0.4,\n                \"lbf\": 90\n            }\n        }\n    ],\n    \"wikipedia\": \"https://en.wikipedia.org/wiki/SpaceX_Dragon\",\n    \"description\": \"Dragon is a reusable spacecraft developed by SpaceX, an American private space transportation company based in Hawthorne, California. Dragon is launched into space by the SpaceX Falcon 9 two-stage-to-orbit launch vehicle. The Dragon spacecraft was originally designed for human travel, but so far has only been used to deliver cargo to the International Space Station (ISS).\",\n    \"id\": \"5e9d058759b1ff74a7ad5f8f\"\n}\n```\n\n## Error Responses\n\n**Code** : `404 NOT FOUND`\n\n**Content** : `Not Found`\n"
  },
  {
    "path": "docs/dragons/v4/query.md",
    "content": "# Query Dragons\n\n**Method** : `POST`\n\n**URL** : `https://api.spacexdata.com/v4/dragons/query`\n\n**Auth required** : `False`\n\n**Body** :\n\nSee [query](../../queries.md) guide for more details on building queries and paginating results.\n\n```json\n{\n  \"query\": {},\n  \"options\": {}\n}\n```\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n    \"docs\": [\n        {\n            \"heat_shield\": {\n                \"material\": \"PICA-X\",\n                \"size_meters\": 3.6,\n                \"temp_degrees\": 3000,\n                \"dev_partner\": \"NASA\"\n            },\n            \"launch_payload_mass\": {\n                \"kg\": 6000,\n                \"lb\": 13228\n            },\n            \"launch_payload_vol\": {\n                \"cubic_meters\": 25,\n                \"cubic_feet\": 883\n            },\n            \"return_payload_mass\": {\n                \"kg\": 3000,\n                \"lb\": 6614\n            },\n            \"return_payload_vol\": {\n                \"cubic_meters\": 11,\n                \"cubic_feet\": 388\n            },\n            \"pressurized_capsule\": {\n                \"payload_volume\": {\n                    \"cubic_meters\": 11,\n                    \"cubic_feet\": 388\n                }\n            },\n            \"trunk\": {\n                \"trunk_volume\": {\n                    \"cubic_meters\": 14,\n                    \"cubic_feet\": 494\n                },\n                \"cargo\": {\n                    \"solar_array\": 2,\n                    \"unpressurized_cargo\": true\n                }\n            },\n            \"height_w_trunk\": {\n                \"meters\": 7.2,\n                \"feet\": 23.6\n            },\n            \"diameter\": {\n                \"meters\": 3.7,\n                \"feet\": 12\n            },\n            \"first_flight\": \"2010-12-08\",\n            \"flickr_images\": [\n                \"https://www.spacex.com/sites/spacex/files/styles/media_gallery_large/public/2015_-_04_crs5_dragon_orbit13.jpg?itok=9p8_l7UP\",\n                \"https://www.spacex.com/sites/spacex/files/styles/media_gallery_large/public/2012_-_4_dragon_grapple_cots2-1.jpg?itok=R2-SeuMX\",\n                \"https://farm3.staticflickr.com/2815/32761844973_4b55b27d3c_b.jpg\",\n                \"https://farm9.staticflickr.com/8618/16649075267_d18cbb4342_b.jpg\"\n            ],\n            \"name\": \"Dragon 1\",\n            \"type\": \"capsule\",\n            \"active\": true,\n            \"crew_capacity\": 0,\n            \"sidewall_angle_deg\": 15,\n            \"orbit_duration_yr\": 2,\n            \"dry_mass_kg\": 4200,\n            \"dry_mass_lb\": 9300,\n            \"thrusters\": [\n                {\n                    \"type\": \"Draco\",\n                    \"amount\": 18,\n                    \"pods\": 4,\n                    \"fuel_1\": \"nitrogen tetroxide\",\n                    \"fuel_2\": \"monomethylhydrazine\",\n                    \"isp\": 300,\n                    \"thrust\": {\n                        \"kN\": 0.4,\n                        \"lbf\": 90\n                    }\n                }\n            ],\n            \"wikipedia\": \"https://en.wikipedia.org/wiki/SpaceX_Dragon\",\n            \"description\": \"Dragon is a reusable spacecraft developed by SpaceX, an American private space transportation company based in Hawthorne, California. Dragon is launched into space by the SpaceX Falcon 9 two-stage-to-orbit launch vehicle. The Dragon spacecraft was originally designed for human travel, but so far has only been used to deliver cargo to the International Space Station (ISS).\",\n            \"id\": \"5e9d058759b1ff74a7ad5f8f\"\n        },\n        ...\n    ],\n    \"totalDocs\": 2,\n    \"offset\": 0,\n    \"limit\": 10,\n    \"totalPages\": 1,\n    \"page\": 1,\n    \"pagingCounter\": 1,\n    \"hasPrevPage\": false,\n    \"hasNextPage\": false,\n    \"prevPage\": null,\n    \"nextPage\": null\n}\n```\n\n## Error Responses\n\n**Code** : `400 Bad Request`\n\n**Content** : Mongoose error is shown, with suggestions to fix the query.\n"
  },
  {
    "path": "docs/dragons/v4/schema.md",
    "content": "# Dragon Schema\n\n```json\n{\n  \"name\": {\n    \"type\": \"String\",\n    \"unique\": true,\n    \"required\": true\n  },\n  \"type\": {\n    \"type\": \"String\",\n    \"required\": true\n  },\n  \"active\": {\n    \"type\": \"Boolean\",\n    \"required\": true\n  },\n  \"crew_capacity\": {\n    \"type\": \"Number\",\n    \"required\": true\n  },\n  \"sidewall_angle_deg\": {\n    \"type\": \"Number\",\n    \"required\": true\n  },\n  \"orbit_duration_yr\": {\n    \"type\": \"Number\",\n    \"required\": true\n  },\n  \"dry_mass_kg\": {\n    \"type\": \"Number\",\n    \"required\": true\n  },\n  \"dry_mass_lb\": {\n    \"type\": \"Number\",\n    \"required\": true\n  },\n  \"first_flight\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"heat_shield\": {\n    \"material\": {\n      \"type\": \"String\",\n      \"required\": true\n    },\n    \"size_meters\": {\n      \"type\": \"Number\",\n      \"required\": true\n    },\n    \"temp_degrees\": {\n      \"type\": \"Number\"\n    },\n    \"dev_partner\": {\n      \"type\": \"String\"\n    }\n  },\n  \"thrusters\": {\n    \"type\": \"Object\"\n  },\n  \"launch_payload_mass\": {\n    \"kg\": {\n      \"type\": \"Number\"\n    },\n    \"lb\": {\n      \"type\": \"Number\"\n    }\n  },\n  \"launch_payload_vol\": {\n    \"cubic_meters\": {\n      \"type\": \"Number\"\n    },\n    \"cubic_feet\": {\n      \"type\": \"Number\"\n    }\n  },\n  \"return_payload_mass\": {\n    \"kg\": {\n      \"type\": \"Number\"\n    },\n    \"lb\": {\n      \"type\": \"Number\"\n    }\n  },\n  \"return_payload_vol\": {\n    \"cubic_meters\": {\n      \"type\": \"Number\"\n    },\n    \"cubic_feet\": {\n      \"type\": \"Number\"\n    }\n  },\n  \"pressurized_capsule\": {\n    \"payload_volume\": {\n      \"cubic_meters\": {\n        \"type\": \"Number\"\n      },\n      \"cubic_feet\": {\n        \"type\": \"Number\"\n      }\n    }\n  },\n  \"trunk\": {\n    \"trunk_volume\": {\n      \"cubic_meters\": {\n        \"type\": \"Number\"\n      },\n      \"cubic_feet\": {\n        \"type\": \"Number\"\n      }\n    },\n    \"cargo\": {\n      \"solar_array\": {\n        \"type\": \"Number\"\n      },\n      \"unpressurized_cargo\": {\n        \"type\": \"Boolean\"\n      }\n    }\n  },\n  \"height_w_trunk\": {\n    \"meters\": {\n      \"type\": \"Number\"\n    },\n    \"feet\": {\n      \"type\": \"Number\"\n    }\n  },\n  \"diameter\": {\n    \"meters\": {\n      \"type\": \"Number\"\n    },\n    \"feet\": {\n      \"type\": \"Number\"\n    }\n  },\n  \"flickr_images\": {\n    \"type\": [\n      \"String\"\n    ]\n  },\n  \"wikipedia\": {\n    \"type\": \"String\"\n  },\n  \"description\": {\n    \"type\": \"String\"\n  }\n}\n```\n"
  },
  {
    "path": "docs/history/v4/all.md",
    "content": "# Get all history events\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/history`\n\n**Auth required** : `False`\n\n## Success Responses\n\n**Code** : `200 OK`\n\n```json\n[\n  {\n    \"title\": \"SpaceX successfully launches humans to ISS\",\n    \"event_date_utc\": \"2020-05-30T19:22:00Z\",\n    \"event_date_unix\": 1590866520,\n    \"details\": \"This mission was the first crewed flight to launch from the United States since the end of the Space Shuttle program in 2011. It carried NASA astronauts Doug Hurley and Bob Behnken to the ISS.\",\n    \"links\": {\n      \"article\": \"https://spaceflightnow.com/2020/05/30/nasa-astronauts-launch-from-us-soil-for-first-time-in-nine-years/\"\n    }\n  }\n  ...\n]\n```\n"
  },
  {
    "path": "docs/history/v4/one.md",
    "content": "# Get one history event\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/history/:id`\n\n**URL Parameters** : `id=[string]` where `id` is the ID of the history event\n\n**Auth required** : `False`\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n    \"title\": \"SpaceX successfully launches humans to ISS\",\n    \"event_date_utc\": \"2020-05-30T19:22:00Z\",\n    \"event_date_unix\": 1590866520,\n    \"details\": \"This mission was the first crewed flight to launch from the United States since the end of the Space Shuttle program in 2011. It carried NASA astronauts Doug Hurley and Bob Behnken to the ISS.\",\n    \"links\": {\n        \"article\": \"https://spaceflightnow.com/2020/05/30/nasa-astronauts-launch-from-us-soil-for-first-time-in-nine-years/\"\n    }\n}\n```\n\n## Error Responses\n\n**Code** : `404 NOT FOUND`\n\n**Content** : `Not Found`\n"
  },
  {
    "path": "docs/history/v4/query.md",
    "content": "# Query history events\n\n**Method** : `POST`\n\n**URL** : `https://api.spacexdata.com/v4/history/query`\n\n**Auth required** : `False`\n\n**Body** :\n\nSee [query](../../queries.md) guide for more details on building queries and paginating results.\n\n```json\n{\n  \"query\": {},\n  \"options\": {}\n}\n```\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n    \"docs\": [\n        {\n            \"title\": \"SpaceX successfully launches humans to ISS\",\n            \"event_date_utc\": \"2020-05-30T19:22:00Z\",\n            \"event_date_unix\": 1590866520,\n            \"details\": \"This mission was the first crewed flight to launch from the United States since the end of the Space Shuttle program in 2011. It carried NASA astronauts Doug Hurley and Bob Behnken to the ISS.\",\n            \"links\": {\n                \"article\": \"https://spaceflightnow.com/2020/05/30/nasa-astronauts-launch-from-us-soil-for-first-time-in-nine-years/\"\n            }\n        }\n        ...\n    ],\n    \"totalDocs\": 7,\n    \"offset\": 0,\n    \"limit\": 10,\n    \"totalPages\": 1,\n    \"page\": 1,\n    \"pagingCounter\": 1,\n    \"hasPrevPage\": false,\n    \"hasNextPage\": false,\n    \"prevPage\": null,\n    \"nextPage\": null\n}\n```\n\n## Error Responses\n\n**Code** : `400 Bad Request`\n\n**Content** : Mongoose error is shown, with suggestions to fix the query.\n"
  },
  {
    "path": "docs/history/v4/schema.md",
    "content": "# History Event Schema\n\n```json\n{\n  \"title\": {\n    \"type\": \"String\",\n    \"default\": null,\n  },\n  \"event_date_utc\": {\n    \"type\": \"String\",\n    \"default\": null,\n  },\n  \"event_date_unix\": {\n    \"type\": \"Number\",\n    \"default\": null,\n  },\n  \"details\": {\n    \"type\": \"String\",\n    \"default\": null,\n  },\n  \"links\": {\n    \"article\": {\n      \"type\": \"String\",\n      \"default\": null,\n    },\n  },\n}\n```\n"
  },
  {
    "path": "docs/landpads/v4/all.md",
    "content": "# Get all landing pads\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/landpads`\n\n**Auth required** : `False`\n\n## Success Responses\n\n**Code** : `200 OK`\n\n```json\n[\n    {\n        \"name\": \"LZ-2\",\n        \"full_name\": \"Landing Zone 2\",\n        \"status\": \"active\",\n        \"type\": \"RTLS\",\n        \"locality\": \"Cape Canaveral\",\n        \"region\": \"Florida\",\n        \"latitude\": 28.485833,\n        \"longitude\": -80.544444,\n        \"landing_attempts\": 3,\n        \"landing_successes\": 3,\n        \"wikipedia\": \"https://en.wikipedia.org/wiki/Landing_Zones_1_and_2\",\n        \"details\": \"SpaceX's first east coast landing pad is Landing Zone 1, where the historic first Falcon 9 landing occurred in December 2015. LC-13 was originally used as a launch pad for early Atlas missiles and rockets from Lockheed Martin. LC-1 was later expanded to include Landing Zone 2 for side booster RTLS Falcon Heavy missions, and it was first used in February 2018 for that purpose.\",\n        \"launches\": [\n            \"5eb87d13ffd86e000604b360\",\n            \"5eb87d2dffd86e000604b376\",\n            \"5eb87d35ffd86e000604b37a\"\n        ],\n        \"id\": \"5e9e3032383ecb90a834e7c8\"\n    },\n    ...\n]\n```\n"
  },
  {
    "path": "docs/landpads/v4/one.md",
    "content": "# Get one landing pad\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/landpads/:id`\n\n**URL Parameters** : `id=[string]` where `id` is the ID of the landing pad\n\n**Auth required** : `False`\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n    \"name\": \"LZ-2\",\n    \"full_name\": \"Landing Zone 2\",\n    \"status\": \"active\",\n    \"type\": \"RTLS\",\n    \"locality\": \"Cape Canaveral\",\n    \"region\": \"Florida\",\n    \"latitude\": 28.485833,\n    \"longitude\": -80.544444,\n    \"landing_attempts\": 3,\n    \"landing_successes\": 3,\n    \"wikipedia\": \"https://en.wikipedia.org/wiki/Landing_Zones_1_and_2\",\n    \"details\": \"SpaceX's first east coast landing pad is Landing Zone 1, where the historic first Falcon 9 landing occurred in December 2015. LC-13 was originally used as a launch pad for early Atlas missiles and rockets from Lockheed Martin. LC-1 was later expanded to include Landing Zone 2 for side booster RTLS Falcon Heavy missions, and it was first used in February 2018 for that purpose.\",\n    \"launches\": [\n        \"5eb87d13ffd86e000604b360\",\n        \"5eb87d2dffd86e000604b376\",\n        \"5eb87d35ffd86e000604b37a\"\n    ],\n    \"id\": \"5e9e3032383ecb90a834e7c8\"\n}\n```\n\n## Error Responses\n\n**Code** : `404 NOT FOUND`\n\n**Content** : `Not Found`\n"
  },
  {
    "path": "docs/landpads/v4/query.md",
    "content": "# Query landing pads\n\n**Method** : `POST`\n\n**URL** : `https://api.spacexdata.com/v4/landpads/query`\n\n**Auth required** : `False`\n\n**Body** :\n\nSee [query](../../queries.md) guide for more details on building queries and paginating results.\n\n```json\n{\n  \"query\": {},\n  \"options\": {}\n}\n```\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n    \"docs\": [\n        {\n            \"name\": \"LZ-1\",\n            \"full_name\": \"Landing Zone 1\",\n            \"status\": \"active\",\n            \"type\": \"RTLS\",\n            \"locality\": \"Cape Canaveral\",\n            \"region\": \"Florida\",\n            \"latitude\": 28.485833,\n            \"longitude\": -80.544444,\n            \"landing_attempts\": 15,\n            \"landing_successes\": 14,\n            \"wikipedia\": \"https://en.wikipedia.org/wiki/Landing_Zones_1_and_2\",\n            \"details\": \"SpaceX's first east coast landing pad is Landing Zone 1, where the historic first Falcon 9 landing occurred in December 2015. LC-13 was originally used as a launch pad for early Atlas missiles and rockets from Lockheed Martin. LC-1 was later expanded to include Landing Zone 2 for side booster RTLS Falcon Heavy missions, and it was first used in February 2018 for that purpose.\",\n            \"launches\": [\n                \"5eb87cefffd86e000604b342\",\n                \"5eb87cf9ffd86e000604b349\",\n                \"5eb87cfeffd86e000604b34d\",\n                \"5eb87d01ffd86e000604b350\",\n                \"5eb87d03ffd86e000604b352\",\n                \"5eb87d07ffd86e000604b356\",\n                \"5eb87d09ffd86e000604b358\",\n                \"5eb87d0effd86e000604b35c\",\n                \"5eb87d10ffd86e000604b35e\",\n                \"5eb87d13ffd86e000604b360\",\n                \"5eb87d26ffd86e000604b371\",\n                \"5eb87d2dffd86e000604b376\",\n                \"5eb87d35ffd86e000604b37a\",\n                \"5eb87d36ffd86e000604b37b\",\n                \"5eb87d42ffd86e000604b384\"\n            ],\n            \"id\": \"5e9e3032383ecb267a34e7c7\"\n        },\n        ...\n    ],\n    \"totalDocs\": 7,\n    \"offset\": 0,\n    \"limit\": 10,\n    \"totalPages\": 1,\n    \"page\": 1,\n    \"pagingCounter\": 1,\n    \"hasPrevPage\": false,\n    \"hasNextPage\": false,\n    \"prevPage\": null,\n    \"nextPage\": null\n}\n```\n\n## Error Responses\n\n**Code** : `400 Bad Request`\n\n**Content** : Mongoose error is shown, with suggestions to fix the query.\n"
  },
  {
    "path": "docs/landpads/v4/schema.md",
    "content": "# Landing Pad Schema\n\n```json\n{\n  \"name\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"full_name\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"status\": {\n    \"type\": \"String\",\n    \"enum\": [\n      \"active\",\n      \"inactive\",\n      \"unknown\",\n      \"retired\",\n      \"lost\",\n      \"under construction\"\n    ],\n    \"required\": true\n  },\n  \"type\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"locality\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"region\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"latitude\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"longitude\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"landing_attempts\": {\n    \"type\": \"Number\",\n    \"default\": 0\n  },\n  \"landing_successes\": {\n    \"type\": \"Number\",\n    \"default\": 0\n  },\n  \"wikipedia\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"details\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"launches\": [\n    {\n      \"type\": \"UUID\"\n    }\n  ]\n}\n```\n"
  },
  {
    "path": "docs/launches/v4/all.md",
    "content": "# Get all launches\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/launches`\n\n**Auth required** : `False`\n\n## Success Responses\n\n**Code** : `200 OK`\n\n```json\n[\n    {\n        \"fairings\": null,\n        \"links\": {\n            \"patch\": {\n                \"small\": \"https://images2.imgbox.com/53/22/dh0XSLXO_o.png\",\n                \"large\": \"https://images2.imgbox.com/15/2b/NAcsTEB6_o.png\"\n            },\n            \"reddit\": {\n                \"campaign\": \"https://www.reddit.com/r/spacex/comments/ezn6n0/crs20_launch_campaign_thread\",\n                \"launch\": \"https://www.reddit.com/r/spacex/comments/fe8pcj/rspacex_crs20_official_launch_discussion_updates/\",\n                \"media\": \"https://www.reddit.com/r/spacex/comments/fes64p/rspacex_crs20_media_thread_videos_images_gifs/\",\n                \"recovery\": null\n            },\n            \"flickr\": {\n                \"small\": [],\n                \"original\": [\n                    \"https://live.staticflickr.com/65535/49635401403_96f9c322dc_o.jpg\",\n                    \"https://live.staticflickr.com/65535/49636202657_e81210a3ca_o.jpg\",\n                    \"https://live.staticflickr.com/65535/49636202572_8831c5a917_o.jpg\",\n                    \"https://live.staticflickr.com/65535/49635401423_e0bef3e82f_o.jpg\",\n                    \"https://live.staticflickr.com/65535/49635985086_660be7062f_o.jpg\"\n                ]\n            },\n            \"presskit\": \"https://www.spacex.com/sites/spacex/files/crs-20_mission_press_kit.pdf\",\n            \"webcast\": \"https://youtu.be/1MkcWK2PnsU\",\n            \"youtube_id\": \"1MkcWK2PnsU\",\n            \"article\": \"https://spaceflightnow.com/2020/03/07/late-night-launch-of-spacex-cargo-ship-marks-end-of-an-era/\",\n            \"wikipedia\": \"https://en.wikipedia.org/wiki/SpaceX_CRS-20\"\n        },\n        \"static_fire_date_utc\": \"2020-03-01T10:20:00.000Z\",\n        \"static_fire_date_unix\": 1583058000,\n        \"tdb\": false,\n        \"net\": false,\n        \"window\": 0,\n        \"rocket\": \"5e9d0d95eda69973a809d1ec\",\n        \"success\": true,\n        \"failures\": [],\n        \"details\": \"SpaceX's 20th and final Crew Resupply Mission under the original NASA CRS contract, this mission brings essential supplies to the International Space Station using SpaceX's reusable Dragon spacecraft. It is the last scheduled flight of a Dragon 1 capsule. (CRS-21 and up under the new Commercial Resupply Services 2 contract will use Dragon 2.) The external payload for this mission is the Bartolomeo ISS external payload hosting platform. Falcon 9 and Dragon will launch from SLC-40, Cape Canaveral Air Force Station and the booster will land at LZ-1. The mission will be complete with return and recovery of the Dragon capsule and down cargo.\",\n        \"crew\": [],\n        \"ships\": [],\n        \"capsules\": [\n            \"5e9e2c5cf359185d753b266f\"\n        ],\n        \"payloads\": [\n            \"5eb0e4d0b6c3bb0006eeb253\"\n        ],\n        \"launchpad\": \"5e9e4501f509094ba4566f84\",\n        \"auto_update\": true,\n        \"flight_number\": 91,\n        \"name\": \"CRS-20\",\n        \"date_utc\": \"2020-03-07T04:50:31.000Z\",\n        \"date_unix\": 1583556631,\n        \"date_local\": \"2020-03-06T23:50:31-05:00\",\n        \"date_precision\": \"hour\",\n        \"upcoming\": false,\n        \"cores\": [\n            {\n                \"core\": \"5e9e28a7f359187afd3b2662\",\n                \"flight\": 2,\n                \"gridfins\": true,\n                \"legs\": true,\n                \"reused\": true,\n                \"landing_attempt\": true,\n                \"landing_success\": true,\n                \"landing_type\": \"RTLS\",\n                \"landpad\": \"5e9e3032383ecb267a34e7c7\"\n            }\n        ],\n        \"id\": \"5eb87d42ffd86e000604b384\"\n    },\n    ...\n]\n```\n"
  },
  {
    "path": "docs/launches/v4/latest.md",
    "content": "# Get latest launch\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/launches/latest`\n\n**Auth required** : `False`\n\n## Success Responses\n\n**Code** : `200 OK`\n\n```json\n{\n    \"fairings\": null,\n    \"links\": {\n        \"patch\": {\n            \"small\": \"https://images2.imgbox.com/53/22/dh0XSLXO_o.png\",\n            \"large\": \"https://images2.imgbox.com/15/2b/NAcsTEB6_o.png\"\n        },\n        \"reddit\": {\n            \"campaign\": \"https://www.reddit.com/r/spacex/comments/ezn6n0/crs20_launch_campaign_thread\",\n            \"launch\": \"https://www.reddit.com/r/spacex/comments/fe8pcj/rspacex_crs20_official_launch_discussion_updates/\",\n            \"media\": \"https://www.reddit.com/r/spacex/comments/fes64p/rspacex_crs20_media_thread_videos_images_gifs/\",\n            \"recovery\": null\n        },\n        \"flickr\": {\n            \"small\": [],\n            \"original\": [\n                \"https://live.staticflickr.com/65535/49635401403_96f9c322dc_o.jpg\",\n                \"https://live.staticflickr.com/65535/49636202657_e81210a3ca_o.jpg\",\n                \"https://live.staticflickr.com/65535/49636202572_8831c5a917_o.jpg\",\n                \"https://live.staticflickr.com/65535/49635401423_e0bef3e82f_o.jpg\",\n                \"https://live.staticflickr.com/65535/49635985086_660be7062f_o.jpg\"\n            ]\n        },\n        \"presskit\": \"https://www.spacex.com/sites/spacex/files/crs-20_mission_press_kit.pdf\",\n        \"webcast\": \"https://youtu.be/1MkcWK2PnsU\",\n        \"youtube_id\": \"1MkcWK2PnsU\",\n        \"article\": \"https://spaceflightnow.com/2020/03/07/late-night-launch-of-spacex-cargo-ship-marks-end-of-an-era/\",\n        \"wikipedia\": \"https://en.wikipedia.org/wiki/SpaceX_CRS-20\"\n    },\n    \"static_fire_date_utc\": \"2020-03-01T10:20:00.000Z\",\n    \"static_fire_date_unix\": 1583058000,\n    \"tdb\": false,\n    \"net\": false,\n    \"window\": 0,\n    \"rocket\": \"5e9d0d95eda69973a809d1ec\",\n    \"success\": true,\n    \"failures\": [],\n    \"details\": \"SpaceX's 20th and final Crew Resupply Mission under the original NASA CRS contract, this mission brings essential supplies to the International Space Station using SpaceX's reusable Dragon spacecraft. It is the last scheduled flight of a Dragon 1 capsule. (CRS-21 and up under the new Commercial Resupply Services 2 contract will use Dragon 2.) The external payload for this mission is the Bartolomeo ISS external payload hosting platform. Falcon 9 and Dragon will launch from SLC-40, Cape Canaveral Air Force Station and the booster will land at LZ-1. The mission will be complete with return and recovery of the Dragon capsule and down cargo.\",\n    \"crew\": [],\n    \"ships\": [],\n    \"capsules\": [\n        \"5e9e2c5cf359185d753b266f\"\n    ],\n    \"payloads\": [\n        \"5eb0e4d0b6c3bb0006eeb253\"\n    ],\n    \"launchpad\": \"5e9e4501f509094ba4566f84\",\n    \"auto_update\": true,\n    \"flight_number\": 91,\n    \"name\": \"CRS-20\",\n    \"date_utc\": \"2020-03-07T04:50:31.000Z\",\n    \"date_unix\": 1583556631,\n    \"date_local\": \"2020-03-06T23:50:31-05:00\",\n    \"date_precision\": \"hour\",\n    \"upcoming\": false,\n    \"cores\": [\n        {\n            \"core\": \"5e9e28a7f359187afd3b2662\",\n            \"flight\": 2,\n            \"gridfins\": true,\n            \"legs\": true,\n            \"reused\": true,\n            \"landing_attempt\": true,\n            \"landing_success\": true,\n            \"landing_type\": \"RTLS\",\n            \"landpad\": \"5e9e3032383ecb267a34e7c7\"\n        }\n    ],\n    \"id\": \"5eb87d42ffd86e000604b384\"\n}\n```\n"
  },
  {
    "path": "docs/launches/v4/next.md",
    "content": "# Get next launch\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/launches/next`\n\n**Auth required** : `False`\n\n## Success Responses\n\n**Code** : `200 OK`\n\n```json\n{\n    \"fairings\": null,\n    \"links\": {\n        \"patch\": {\n            \"small\": \"https://images2.imgbox.com/53/22/dh0XSLXO_o.png\",\n            \"large\": \"https://images2.imgbox.com/15/2b/NAcsTEB6_o.png\"\n        },\n        \"reddit\": {\n            \"campaign\": \"https://www.reddit.com/r/spacex/comments/ezn6n0/crs20_launch_campaign_thread\",\n            \"launch\": \"https://www.reddit.com/r/spacex/comments/fe8pcj/rspacex_crs20_official_launch_discussion_updates/\",\n            \"media\": \"https://www.reddit.com/r/spacex/comments/fes64p/rspacex_crs20_media_thread_videos_images_gifs/\",\n            \"recovery\": null\n        },\n        \"flickr\": {\n            \"small\": [],\n            \"original\": [\n                \"https://live.staticflickr.com/65535/49635401403_96f9c322dc_o.jpg\",\n                \"https://live.staticflickr.com/65535/49636202657_e81210a3ca_o.jpg\",\n                \"https://live.staticflickr.com/65535/49636202572_8831c5a917_o.jpg\",\n                \"https://live.staticflickr.com/65535/49635401423_e0bef3e82f_o.jpg\",\n                \"https://live.staticflickr.com/65535/49635985086_660be7062f_o.jpg\"\n            ]\n        },\n        \"presskit\": \"https://www.spacex.com/sites/spacex/files/crs-20_mission_press_kit.pdf\",\n        \"webcast\": \"https://youtu.be/1MkcWK2PnsU\",\n        \"youtube_id\": \"1MkcWK2PnsU\",\n        \"article\": \"https://spaceflightnow.com/2020/03/07/late-night-launch-of-spacex-cargo-ship-marks-end-of-an-era/\",\n        \"wikipedia\": \"https://en.wikipedia.org/wiki/SpaceX_CRS-20\"\n    },\n    \"static_fire_date_utc\": \"2020-03-01T10:20:00.000Z\",\n    \"static_fire_date_unix\": 1583058000,\n    \"tdb\": false,\n    \"net\": false,\n    \"window\": 0,\n    \"rocket\": \"5e9d0d95eda69973a809d1ec\",\n    \"success\": true,\n    \"failures\": [],\n    \"details\": \"SpaceX's 20th and final Crew Resupply Mission under the original NASA CRS contract, this mission brings essential supplies to the International Space Station using SpaceX's reusable Dragon spacecraft. It is the last scheduled flight of a Dragon 1 capsule. (CRS-21 and up under the new Commercial Resupply Services 2 contract will use Dragon 2.) The external payload for this mission is the Bartolomeo ISS external payload hosting platform. Falcon 9 and Dragon will launch from SLC-40, Cape Canaveral Air Force Station and the booster will land at LZ-1. The mission will be complete with return and recovery of the Dragon capsule and down cargo.\",\n    \"crew\": [],\n    \"ships\": [],\n    \"capsules\": [\n        \"5e9e2c5cf359185d753b266f\"\n    ],\n    \"payloads\": [\n        \"5eb0e4d0b6c3bb0006eeb253\"\n    ],\n    \"launchpad\": \"5e9e4501f509094ba4566f84\",\n    \"auto_update\": true,\n    \"flight_number\": 91,\n    \"name\": \"CRS-20\",\n    \"date_utc\": \"2020-03-07T04:50:31.000Z\",\n    \"date_unix\": 1583556631,\n    \"date_local\": \"2020-03-06T23:50:31-05:00\",\n    \"date_precision\": \"hour\",\n    \"upcoming\": false,\n    \"cores\": [\n        {\n            \"core\": \"5e9e28a7f359187afd3b2662\",\n            \"flight\": 2,\n            \"gridfins\": true,\n            \"legs\": true,\n            \"reused\": true,\n            \"landing_attempt\": true,\n            \"landing_success\": true,\n            \"landing_type\": \"RTLS\",\n            \"landpad\": \"5e9e3032383ecb267a34e7c7\"\n        }\n    ],\n    \"id\": \"5eb87d42ffd86e000604b384\"\n}\n```\n"
  },
  {
    "path": "docs/launches/v4/one.md",
    "content": "# Get one launch\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/launches/:id`\n\n**URL Parameters** : `id=[string]` where `id` is the ID of the launch\n\n**Auth required** : `False`\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n    \"fairings\": null,\n    \"links\": {\n        \"patch\": {\n            \"small\": \"https://images2.imgbox.com/53/22/dh0XSLXO_o.png\",\n            \"large\": \"https://images2.imgbox.com/15/2b/NAcsTEB6_o.png\"\n        },\n        \"reddit\": {\n            \"campaign\": \"https://www.reddit.com/r/spacex/comments/ezn6n0/crs20_launch_campaign_thread\",\n            \"launch\": \"https://www.reddit.com/r/spacex/comments/fe8pcj/rspacex_crs20_official_launch_discussion_updates/\",\n            \"media\": \"https://www.reddit.com/r/spacex/comments/fes64p/rspacex_crs20_media_thread_videos_images_gifs/\",\n            \"recovery\": null\n        },\n        \"flickr\": {\n            \"small\": [],\n            \"original\": [\n                \"https://live.staticflickr.com/65535/49635401403_96f9c322dc_o.jpg\",\n                \"https://live.staticflickr.com/65535/49636202657_e81210a3ca_o.jpg\",\n                \"https://live.staticflickr.com/65535/49636202572_8831c5a917_o.jpg\",\n                \"https://live.staticflickr.com/65535/49635401423_e0bef3e82f_o.jpg\",\n                \"https://live.staticflickr.com/65535/49635985086_660be7062f_o.jpg\"\n            ]\n        },\n        \"presskit\": \"https://www.spacex.com/sites/spacex/files/crs-20_mission_press_kit.pdf\",\n        \"webcast\": \"https://youtu.be/1MkcWK2PnsU\",\n        \"youtube_id\": \"1MkcWK2PnsU\",\n        \"article\": \"https://spaceflightnow.com/2020/03/07/late-night-launch-of-spacex-cargo-ship-marks-end-of-an-era/\",\n        \"wikipedia\": \"https://en.wikipedia.org/wiki/SpaceX_CRS-20\"\n    },\n    \"static_fire_date_utc\": \"2020-03-01T10:20:00.000Z\",\n    \"static_fire_date_unix\": 1583058000,\n    \"tdb\": false,\n    \"net\": false,\n    \"window\": 0,\n    \"rocket\": \"5e9d0d95eda69973a809d1ec\",\n    \"success\": true,\n    \"failures\": [],\n    \"details\": \"SpaceX's 20th and final Crew Resupply Mission under the original NASA CRS contract, this mission brings essential supplies to the International Space Station using SpaceX's reusable Dragon spacecraft. It is the last scheduled flight of a Dragon 1 capsule. (CRS-21 and up under the new Commercial Resupply Services 2 contract will use Dragon 2.) The external payload for this mission is the Bartolomeo ISS external payload hosting platform. Falcon 9 and Dragon will launch from SLC-40, Cape Canaveral Air Force Station and the booster will land at LZ-1. The mission will be complete with return and recovery of the Dragon capsule and down cargo.\",\n    \"crew\": [],\n    \"ships\": [],\n    \"capsules\": [\n        \"5e9e2c5cf359185d753b266f\"\n    ],\n    \"payloads\": [\n        \"5eb0e4d0b6c3bb0006eeb253\"\n    ],\n    \"launchpad\": \"5e9e4501f509094ba4566f84\",\n    \"auto_update\": true,\n    \"flight_number\": 91,\n    \"name\": \"CRS-20\",\n    \"date_utc\": \"2020-03-07T04:50:31.000Z\",\n    \"date_unix\": 1583556631,\n    \"date_local\": \"2020-03-06T23:50:31-05:00\",\n    \"date_precision\": \"hour\",\n    \"upcoming\": false,\n    \"cores\": [\n        {\n            \"core\": \"5e9e28a7f359187afd3b2662\",\n            \"flight\": 2,\n            \"gridfins\": true,\n            \"legs\": true,\n            \"reused\": true,\n            \"landing_attempt\": true,\n            \"landing_success\": true,\n            \"landing_type\": \"RTLS\",\n            \"landpad\": \"5e9e3032383ecb267a34e7c7\"\n        }\n    ],\n    \"id\": \"5eb87d42ffd86e000604b384\"\n}\n```\n\n## Error Responses\n\n**Code** : `404 NOT FOUND`\n\n**Content** : `Not Found`\n"
  },
  {
    "path": "docs/launches/v4/past.md",
    "content": "# Get all past launches\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/launches/past`\n\n**Auth required** : `False`\n\n## Success Responses\n\n**Code** : `200 OK`\n\n```json\n[\n    {\n        \"fairings\": null,\n        \"links\": {\n            \"patch\": {\n                \"small\": \"https://images2.imgbox.com/53/22/dh0XSLXO_o.png\",\n                \"large\": \"https://images2.imgbox.com/15/2b/NAcsTEB6_o.png\"\n            },\n            \"reddit\": {\n                \"campaign\": \"https://www.reddit.com/r/spacex/comments/ezn6n0/crs20_launch_campaign_thread\",\n                \"launch\": \"https://www.reddit.com/r/spacex/comments/fe8pcj/rspacex_crs20_official_launch_discussion_updates/\",\n                \"media\": \"https://www.reddit.com/r/spacex/comments/fes64p/rspacex_crs20_media_thread_videos_images_gifs/\",\n                \"recovery\": null\n            },\n            \"flickr\": {\n                \"small\": [],\n                \"original\": [\n                    \"https://live.staticflickr.com/65535/49635401403_96f9c322dc_o.jpg\",\n                    \"https://live.staticflickr.com/65535/49636202657_e81210a3ca_o.jpg\",\n                    \"https://live.staticflickr.com/65535/49636202572_8831c5a917_o.jpg\",\n                    \"https://live.staticflickr.com/65535/49635401423_e0bef3e82f_o.jpg\",\n                    \"https://live.staticflickr.com/65535/49635985086_660be7062f_o.jpg\"\n                ]\n            },\n            \"presskit\": \"https://www.spacex.com/sites/spacex/files/crs-20_mission_press_kit.pdf\",\n            \"webcast\": \"https://youtu.be/1MkcWK2PnsU\",\n            \"youtube_id\": \"1MkcWK2PnsU\",\n            \"article\": \"https://spaceflightnow.com/2020/03/07/late-night-launch-of-spacex-cargo-ship-marks-end-of-an-era/\",\n            \"wikipedia\": \"https://en.wikipedia.org/wiki/SpaceX_CRS-20\"\n        },\n        \"static_fire_date_utc\": \"2020-03-01T10:20:00.000Z\",\n        \"static_fire_date_unix\": 1583058000,\n        \"tdb\": false,\n        \"net\": false,\n        \"window\": 0,\n        \"rocket\": \"5e9d0d95eda69973a809d1ec\",\n        \"success\": true,\n        \"failures\": [],\n        \"details\": \"SpaceX's 20th and final Crew Resupply Mission under the original NASA CRS contract, this mission brings essential supplies to the International Space Station using SpaceX's reusable Dragon spacecraft. It is the last scheduled flight of a Dragon 1 capsule. (CRS-21 and up under the new Commercial Resupply Services 2 contract will use Dragon 2.) The external payload for this mission is the Bartolomeo ISS external payload hosting platform. Falcon 9 and Dragon will launch from SLC-40, Cape Canaveral Air Force Station and the booster will land at LZ-1. The mission will be complete with return and recovery of the Dragon capsule and down cargo.\",\n        \"crew\": [],\n        \"ships\": [],\n        \"capsules\": [\n            \"5e9e2c5cf359185d753b266f\"\n        ],\n        \"payloads\": [\n            \"5eb0e4d0b6c3bb0006eeb253\"\n        ],\n        \"launchpad\": \"5e9e4501f509094ba4566f84\",\n        \"auto_update\": true,\n        \"flight_number\": 91,\n        \"name\": \"CRS-20\",\n        \"date_utc\": \"2020-03-07T04:50:31.000Z\",\n        \"date_unix\": 1583556631,\n        \"date_local\": \"2020-03-06T23:50:31-05:00\",\n        \"date_precision\": \"hour\",\n        \"upcoming\": false,\n        \"cores\": [\n            {\n                \"core\": \"5e9e28a7f359187afd3b2662\",\n                \"flight\": 2,\n                \"gridfins\": true,\n                \"legs\": true,\n                \"reused\": true,\n                \"landing_attempt\": true,\n                \"landing_success\": true,\n                \"landing_type\": \"RTLS\",\n                \"landpad\": \"5e9e3032383ecb267a34e7c7\"\n            }\n        ],\n        \"id\": \"5eb87d42ffd86e000604b384\"\n    }\n    ...\n]\n```\n"
  },
  {
    "path": "docs/launches/v4/query.md",
    "content": "# Query launches\n\n**Method** : `POST`\n\n**URL** : `https://api.spacexdata.com/v4/launches/query`\n\n**Auth required** : `False`\n\n**Body** :\n\nSee [query](../../queries.md) guide for more details on building queries and paginating results.\n\n```json\n{\n  \"query\": {},\n  \"options\": {}\n}\n```\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n  \"docs\": [\n    {\n      \"fairings\": {\n        \"reused\": false,\n        \"recovery_attempt\": true,\n        \"recovered\": false,\n        \"ships\": [\"5ea6ed2e080df4000697c908\"]\n      },\n      \"links\": {\n        \"patch\": {\n          \"small\": \"https://images2.imgbox.com/02/51/7NLaBm8c_o.png\",\n          \"large\": \"https://images2.imgbox.com/69/f5/04lBXd2F_o.png\"\n        },\n        \"reddit\": {\n          \"campaign\": \"https://www.reddit.com/r/spacex/comments/73ttkd/koreasat_5a_launch_campaign_thread/\",\n          \"launch\": \"https://www.reddit.com/r/spacex/comments/79iuvb/rspacex_koreasat_5a_official_launch_discussion/\",\n          \"media\": \"https://www.reddit.com/r/spacex/comments/79lmdu/rspacex_koreasat5a_media_thread_videos_images/\",\n          \"recovery\": null\n        },\n        \"flickr\": {\n          \"small\": [],\n          \"original\": [\n            \"https://farm5.staticflickr.com/4477/38056454431_a5f40f9fd7_o.jpg\",\n            \"https://farm5.staticflickr.com/4455/26280153979_b8016a829f_o.jpg\",\n            \"https://farm5.staticflickr.com/4459/38056455051_79ef2b949a_o.jpg\",\n            \"https://farm5.staticflickr.com/4466/26280153539_ecbc2b3fa9_o.jpg\",\n            \"https://farm5.staticflickr.com/4482/26280154209_bf08d76361_o.jpg\",\n            \"https://farm5.staticflickr.com/4493/38056455211_a4565a9cee_o.jpg\"\n          ]\n        },\n        \"presskit\": \"http://www.spacex.com/sites/spacex/files/koreasat5apresskit.pdf\",\n        \"webcast\": \"https://www.youtube.com/watch?v=RUjH14vhLxA\",\n        \"youtube_id\": \"RUjH14vhLxA\",\n        \"article\": \"https://spaceflightnow.com/2017/10/30/spacex-launches-and-lands-third-rocket-in-three-weeks/\",\n        \"wikipedia\": \"https://en.wikipedia.org/wiki/Koreasat_5A\"\n      },\n      \"static_fire_date_utc\": \"2017-10-26T16:00:00.000Z\",\n      \"static_fire_date_unix\": 1509033600,\n      \"tdb\": false,\n      \"net\": false,\n      \"window\": 8640,\n      \"rocket\": \"5e9d0d95eda69973a809d1ec\",\n      \"success\": true,\n      \"failures\": [],\n      \"details\": \"KoreaSat 5A is a Ku-band satellite capable of providing communication services from East Africa and Central Asia to southern India, Southeast Asia, the Philippines, Guam, Korea, and Japan. The satellite will be placed in GEO at 113Â° East Longitude, and will provide services ranging from broadband internet to broadcasting services and maritime communications.\",\n      \"crew\": [],\n      \"ships\": [\n        \"5ea6ed2f080df4000697c90d\",\n        \"5ea6ed2e080df4000697c908\",\n        \"5ea6ed30080df4000697c913\"\n      ],\n      \"capsules\": [],\n      \"payloads\": [\"5eb0e4c5b6c3bb0006eeb217\"],\n      \"launchpad\": \"5e9e4502f509094188566f88\",\n      \"auto_update\": true,\n      \"flight_number\": 50,\n      \"name\": \"KoreaSat 5A\",\n      \"date_utc\": \"2017-10-30T19:34:00.000Z\",\n      \"date_unix\": 1509392040,\n      \"date_local\": \"2017-10-30T15:34:00-04:00\",\n      \"date_precision\": \"hour\",\n      \"upcoming\": false,\n      \"cores\": [\n        {\n          \"core\": \"5e9e28a4f359185cc03b2651\",\n          \"flight\": 1,\n          \"gridfins\": true,\n          \"legs\": true,\n          \"reused\": false,\n          \"landing_attempt\": true,\n          \"landing_success\": true,\n          \"landing_type\": \"ASDS\",\n          \"landpad\": \"5e9e3032383ecb6bb234e7ca\"\n        }\n      ],\n      \"id\": \"5eb87d0dffd86e000604b35b\"\n    }\n  ],\n  \"totalDocs\": 109,\n  \"limit\": 10,\n  \"totalPages\": 11,\n  \"page\": 5,\n  \"pagingCounter\": 41,\n  \"hasPrevPage\": true,\n  \"hasNextPage\": true,\n  \"prevPage\": 4,\n  \"nextPage\": 6\n}\n```\n\n## Error Responses\n\n**Code** : `400 Bad Request`\n\n**Content** : Mongoose error is shown, with suggestions to fix the query.\n"
  },
  {
    "path": "docs/launches/v4/schema.md",
    "content": "# Launch Schema\n\n```json\n{\n  \"flight_number\": {\n    \"type\": \"Number\",\n    \"required\": true\n  },\n  \"name\": {\n    \"type\": \"String\",\n    \"unique\": true,\n    \"required\": true\n  },\n  \"date_utc\": {\n    \"type\": \"String\",\n    \"required\": true\n  },\n  \"date_unix\": {\n    \"type\": \"Number\",\n    \"required\": true\n  },\n  \"date_local\": {\n    \"type\": \"String\",\n    \"required\": true\n  },\n  \"date_precision\": {\n    \"type\": \"String\",\n    \"required\": true,\n    \"enum\": [\n      \"half\",\n      \"quarter\",\n      \"year\",\n      \"month\",\n      \"day\",\n      \"hour\"\n    ]\n  },\n  \"static_fire_date_utc\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"static_fire_date_unix\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"tdb\": {\n    \"type\": \"Boolean\",\n    \"default\": false\n  },\n  \"net\": {\n    \"type\": \"Boolean\",\n    \"default\": false\n  },\n  \"window\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"rocket\": {\n    \"type\": \"UUID\",\n    \"default\": null\n  },\n  \"success\": {\n    \"type\": \"Boolean\",\n    \"default\": null\n  },\n  \"failures\": [\n    {\n      \"time\": {\n        \"type\": \"Number\",\n      },\n      \"altitude\": {\n        \"type\": \"Number\",\n      },\n      \"reason\": {\n        \"type\": \"String\",\n      },\n    },\n  ],\n  \"upcoming\": {\n    \"type\": \"Boolean\",\n    \"required\": true\n  },\n  \"details\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"fairings\": {\n    \"reused\": {\n      \"type\": \"Boolean\",\n      \"default\": null\n    },\n    \"recovery_attempt\": {\n      \"type\": \"Boolean\",\n      \"default\": null\n    },\n    \"recovered\": {\n      \"type\": \"Boolean\",\n      \"default\": null\n    },\n    \"ships\": [\n      \"UUID\"\n    ]\n  },\n  \"crew\": [\n    \"UUID\"\n  ],\n  \"ships\": [\n    \"UUID\"\n  ],\n  \"capsules\": [\n    \"UUID\"\n  ],\n  \"payloads\": [\n    \"UUID\"\n  ],\n  \"launchpad\": {\n    \"type\": \"UUID\",\n    \"default\": null\n  },\n  \"cores\": [\n    {\n      \"core\": {\n        \"type\": \"UUID\",\n        \"default\": null\n      },\n      \"flight\": {\n        \"type\": \"Number\",\n        \"default\": null\n      },\n      \"gridfins\": {\n        \"type\": \"Boolean\",\n        \"default\": null\n      },\n      \"legs\": {\n        \"type\": \"Boolean\",\n        \"default\": null\n      },\n      \"reused\": {\n        \"type\": \"Boolean\",\n        \"default\": null\n      },\n      \"landing_attempt\": {\n        \"type\": \"Boolean\",\n        \"default\": null\n      },\n      \"landing_success\": {\n        \"type\": \"Boolean\",\n        \"default\": null\n      },\n      \"landing_type\": {\n        \"type\": \"String\",\n        \"default\": null\n      },\n      \"landpad\": {\n        \"type\": \"UUID\",\n        \"default\": null\n      }\n    }\n  ],\n  \"links\": {\n    \"patch\": {\n      \"small\": {\n        \"type\": \"String\",\n        \"default\": null\n      },\n      \"large\": {\n        \"type\": \"String\",\n        \"default\": null\n      }\n    },\n    \"reddit\": {\n      \"campaign\": {\n        \"type\": \"String\",\n        \"default\": null\n      },\n      \"launch\": {\n        \"type\": \"String\",\n        \"default\": null\n      },\n      \"media\": {\n        \"type\": \"String\",\n        \"default\": null\n      },\n      \"recovery\": {\n        \"type\": \"String\",\n        \"default\": null\n      }\n    },\n    \"flickr\": {\n      \"small\": [\n        \"String\"\n      ],\n      \"original\": [\n        \"String\"\n      ]\n    },\n    \"presskit\": {\n      \"type\": \"String\",\n      \"default\": null\n    },\n    \"webcast\": {\n      \"type\": \"String\",\n      \"default\": null\n    },\n    \"youtube_id\": {\n      \"type\": \"String\",\n      \"default\": null\n    },\n    \"article\": {\n      \"type\": \"String\",\n      \"default\": null\n    },\n    \"wikipedia\": {\n      \"type\": \"String\",\n      \"default\": null\n    }\n  },\n  \"auto_update\": {\n    \"type\": \"Boolean\",\n    \"default\": true\n  }\n}\n```\n"
  },
  {
    "path": "docs/launches/v4/upcoming.md",
    "content": "# Get all upcoming launches\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/launches/upcoming`\n\n**Auth required** : `False`\n\n## Success Responses\n\n**Code** : `200 OK`\n\n```json\n[\n    {\n        \"fairings\": null,\n        \"links\": {\n            \"patch\": {\n                \"small\": \"https://images2.imgbox.com/53/22/dh0XSLXO_o.png\",\n                \"large\": \"https://images2.imgbox.com/15/2b/NAcsTEB6_o.png\"\n            },\n            \"reddit\": {\n                \"campaign\": \"https://www.reddit.com/r/spacex/comments/ezn6n0/crs20_launch_campaign_thread\",\n                \"launch\": \"https://www.reddit.com/r/spacex/comments/fe8pcj/rspacex_crs20_official_launch_discussion_updates/\",\n                \"media\": \"https://www.reddit.com/r/spacex/comments/fes64p/rspacex_crs20_media_thread_videos_images_gifs/\",\n                \"recovery\": null\n            },\n            \"flickr\": {\n                \"small\": [],\n                \"original\": [\n                    \"https://live.staticflickr.com/65535/49635401403_96f9c322dc_o.jpg\",\n                    \"https://live.staticflickr.com/65535/49636202657_e81210a3ca_o.jpg\",\n                    \"https://live.staticflickr.com/65535/49636202572_8831c5a917_o.jpg\",\n                    \"https://live.staticflickr.com/65535/49635401423_e0bef3e82f_o.jpg\",\n                    \"https://live.staticflickr.com/65535/49635985086_660be7062f_o.jpg\"\n                ]\n            },\n            \"presskit\": \"https://www.spacex.com/sites/spacex/files/crs-20_mission_press_kit.pdf\",\n            \"webcast\": \"https://youtu.be/1MkcWK2PnsU\",\n            \"youtube_id\": \"1MkcWK2PnsU\",\n            \"article\": \"https://spaceflightnow.com/2020/03/07/late-night-launch-of-spacex-cargo-ship-marks-end-of-an-era/\",\n            \"wikipedia\": \"https://en.wikipedia.org/wiki/SpaceX_CRS-20\"\n        },\n        \"static_fire_date_utc\": \"2020-03-01T10:20:00.000Z\",\n        \"static_fire_date_unix\": 1583058000,\n        \"tdb\": false,\n        \"net\": false,\n        \"window\": 0,\n        \"rocket\": \"5e9d0d95eda69973a809d1ec\",\n        \"success\": true,\n        \"failures\": [],\n        \"details\": \"SpaceX's 20th and final Crew Resupply Mission under the original NASA CRS contract, this mission brings essential supplies to the International Space Station using SpaceX's reusable Dragon spacecraft. It is the last scheduled flight of a Dragon 1 capsule. (CRS-21 and up under the new Commercial Resupply Services 2 contract will use Dragon 2.) The external payload for this mission is the Bartolomeo ISS external payload hosting platform. Falcon 9 and Dragon will launch from SLC-40, Cape Canaveral Air Force Station and the booster will land at LZ-1. The mission will be complete with return and recovery of the Dragon capsule and down cargo.\",\n        \"crew\": [],\n        \"ships\": [],\n        \"capsules\": [\n            \"5e9e2c5cf359185d753b266f\"\n        ],\n        \"payloads\": [\n            \"5eb0e4d0b6c3bb0006eeb253\"\n        ],\n        \"launchpad\": \"5e9e4501f509094ba4566f84\",\n        \"auto_update\": true,\n        \"flight_number\": 91,\n        \"name\": \"CRS-20\",\n        \"date_utc\": \"2020-03-07T04:50:31.000Z\",\n        \"date_unix\": 1583556631,\n        \"date_local\": \"2020-03-06T23:50:31-05:00\",\n        \"date_precision\": \"hour\",\n        \"upcoming\": false,\n        \"cores\": [\n            {\n                \"core\": \"5e9e28a7f359187afd3b2662\",\n                \"flight\": 2,\n                \"gridfins\": true,\n                \"legs\": true,\n                \"reused\": true,\n                \"landing_attempt\": true,\n                \"landing_success\": true,\n                \"landing_type\": \"RTLS\",\n                \"landpad\": \"5e9e3032383ecb267a34e7c7\"\n            }\n        ],\n        \"id\": \"5eb87d42ffd86e000604b384\"\n    }\n    ...\n]\n```\n"
  },
  {
    "path": "docs/launches/v5/README.md",
    "content": "## Changes from v4 -> v5\n\n* Crew is now an array of objects, to allow for more data on an individual launch for a crew member\n\n### Old Format\n\n```json\n{\n  \"crew\": [\n    \"1234567890\",\n    \"123456789\",\n    \"123456789\",\n  ]\n}\n```\n\n### New Format\n\n```json\n{\n  \"crew\": [\n    \"1234567890\",\n    \"123456789\",\n    \"123456789\",\n  ]\n}\n```"
  },
  {
    "path": "docs/launches/v5/all.md",
    "content": "# Get all launches\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v5/launches`\n\n**Auth required** : `False`\n\n## Success Responses\n\n**Code** : `200 OK`\n\n```json\n[\n    {\n        \"fairings\": null,\n        \"links\": {\n            \"patch\": {\n                \"small\": \"https://images2.imgbox.com/53/22/dh0XSLXO_o.png\",\n                \"large\": \"https://images2.imgbox.com/15/2b/NAcsTEB6_o.png\"\n            },\n            \"reddit\": {\n                \"campaign\": \"https://www.reddit.com/r/spacex/comments/ezn6n0/crs20_launch_campaign_thread\",\n                \"launch\": \"https://www.reddit.com/r/spacex/comments/fe8pcj/rspacex_crs20_official_launch_discussion_updates/\",\n                \"media\": \"https://www.reddit.com/r/spacex/comments/fes64p/rspacex_crs20_media_thread_videos_images_gifs/\",\n                \"recovery\": null\n            },\n            \"flickr\": {\n                \"small\": [],\n                \"original\": [\n                    \"https://live.staticflickr.com/65535/49635401403_96f9c322dc_o.jpg\",\n                    \"https://live.staticflickr.com/65535/49636202657_e81210a3ca_o.jpg\",\n                    \"https://live.staticflickr.com/65535/49636202572_8831c5a917_o.jpg\",\n                    \"https://live.staticflickr.com/65535/49635401423_e0bef3e82f_o.jpg\",\n                    \"https://live.staticflickr.com/65535/49635985086_660be7062f_o.jpg\"\n                ]\n            },\n            \"presskit\": \"https://www.spacex.com/sites/spacex/files/crs-20_mission_press_kit.pdf\",\n            \"webcast\": \"https://youtu.be/1MkcWK2PnsU\",\n            \"youtube_id\": \"1MkcWK2PnsU\",\n            \"article\": \"https://spaceflightnow.com/2020/03/07/late-night-launch-of-spacex-cargo-ship-marks-end-of-an-era/\",\n            \"wikipedia\": \"https://en.wikipedia.org/wiki/SpaceX_CRS-20\"\n        },\n        \"static_fire_date_utc\": \"2020-03-01T10:20:00.000Z\",\n        \"static_fire_date_unix\": 1583058000,\n        \"tdb\": false,\n        \"net\": false,\n        \"window\": 0,\n        \"rocket\": \"5e9d0d95eda69973a809d1ec\",\n        \"success\": true,\n        \"failures\": [],\n        \"details\": \"SpaceX's 20th and final Crew Resupply Mission under the original NASA CRS contract, this mission brings essential supplies to the International Space Station using SpaceX's reusable Dragon spacecraft. It is the last scheduled flight of a Dragon 1 capsule. (CRS-21 and up under the new Commercial Resupply Services 2 contract will use Dragon 2.) The external payload for this mission is the Bartolomeo ISS external payload hosting platform. Falcon 9 and Dragon will launch from SLC-40, Cape Canaveral Air Force Station and the booster will land at LZ-1. The mission will be complete with return and recovery of the Dragon capsule and down cargo.\",\n        \"crew\": [],\n        \"ships\": [],\n        \"capsules\": [\n            \"5e9e2c5cf359185d753b266f\"\n        ],\n        \"payloads\": [\n            \"5eb0e4d0b6c3bb0006eeb253\"\n        ],\n        \"launchpad\": \"5e9e4501f509094ba4566f84\",\n        \"auto_update\": true,\n        \"flight_number\": 91,\n        \"name\": \"CRS-20\",\n        \"date_utc\": \"2020-03-07T04:50:31.000Z\",\n        \"date_unix\": 1583556631,\n        \"date_local\": \"2020-03-06T23:50:31-05:00\",\n        \"date_precision\": \"hour\",\n        \"upcoming\": false,\n        \"cores\": [\n            {\n                \"core\": \"5e9e28a7f359187afd3b2662\",\n                \"flight\": 2,\n                \"gridfins\": true,\n                \"legs\": true,\n                \"reused\": true,\n                \"landing_attempt\": true,\n                \"landing_success\": true,\n                \"landing_type\": \"RTLS\",\n                \"landpad\": \"5e9e3032383ecb267a34e7c7\"\n            }\n        ],\n        \"id\": \"5eb87d42ffd86e000604b384\"\n    },\n    ...\n]\n```\n"
  },
  {
    "path": "docs/launches/v5/latest.md",
    "content": "# Get latest launch\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v5/launches/latest`\n\n**Auth required** : `False`\n\n## Success Responses\n\n**Code** : `200 OK`\n\n```json\n{\n    \"fairings\": null,\n    \"links\": {\n        \"patch\": {\n            \"small\": \"https://images2.imgbox.com/53/22/dh0XSLXO_o.png\",\n            \"large\": \"https://images2.imgbox.com/15/2b/NAcsTEB6_o.png\"\n        },\n        \"reddit\": {\n            \"campaign\": \"https://www.reddit.com/r/spacex/comments/ezn6n0/crs20_launch_campaign_thread\",\n            \"launch\": \"https://www.reddit.com/r/spacex/comments/fe8pcj/rspacex_crs20_official_launch_discussion_updates/\",\n            \"media\": \"https://www.reddit.com/r/spacex/comments/fes64p/rspacex_crs20_media_thread_videos_images_gifs/\",\n            \"recovery\": null\n        },\n        \"flickr\": {\n            \"small\": [],\n            \"original\": [\n                \"https://live.staticflickr.com/65535/49635401403_96f9c322dc_o.jpg\",\n                \"https://live.staticflickr.com/65535/49636202657_e81210a3ca_o.jpg\",\n                \"https://live.staticflickr.com/65535/49636202572_8831c5a917_o.jpg\",\n                \"https://live.staticflickr.com/65535/49635401423_e0bef3e82f_o.jpg\",\n                \"https://live.staticflickr.com/65535/49635985086_660be7062f_o.jpg\"\n            ]\n        },\n        \"presskit\": \"https://www.spacex.com/sites/spacex/files/crs-20_mission_press_kit.pdf\",\n        \"webcast\": \"https://youtu.be/1MkcWK2PnsU\",\n        \"youtube_id\": \"1MkcWK2PnsU\",\n        \"article\": \"https://spaceflightnow.com/2020/03/07/late-night-launch-of-spacex-cargo-ship-marks-end-of-an-era/\",\n        \"wikipedia\": \"https://en.wikipedia.org/wiki/SpaceX_CRS-20\"\n    },\n    \"static_fire_date_utc\": \"2020-03-01T10:20:00.000Z\",\n    \"static_fire_date_unix\": 1583058000,\n    \"tdb\": false,\n    \"net\": false,\n    \"window\": 0,\n    \"rocket\": \"5e9d0d95eda69973a809d1ec\",\n    \"success\": true,\n    \"failures\": [],\n    \"details\": \"SpaceX's 20th and final Crew Resupply Mission under the original NASA CRS contract, this mission brings essential supplies to the International Space Station using SpaceX's reusable Dragon spacecraft. It is the last scheduled flight of a Dragon 1 capsule. (CRS-21 and up under the new Commercial Resupply Services 2 contract will use Dragon 2.) The external payload for this mission is the Bartolomeo ISS external payload hosting platform. Falcon 9 and Dragon will launch from SLC-40, Cape Canaveral Air Force Station and the booster will land at LZ-1. The mission will be complete with return and recovery of the Dragon capsule and down cargo.\",\n    \"crew\": [],\n    \"ships\": [],\n    \"capsules\": [\n        \"5e9e2c5cf359185d753b266f\"\n    ],\n    \"payloads\": [\n        \"5eb0e4d0b6c3bb0006eeb253\"\n    ],\n    \"launchpad\": \"5e9e4501f509094ba4566f84\",\n    \"auto_update\": true,\n    \"flight_number\": 91,\n    \"name\": \"CRS-20\",\n    \"date_utc\": \"2020-03-07T04:50:31.000Z\",\n    \"date_unix\": 1583556631,\n    \"date_local\": \"2020-03-06T23:50:31-05:00\",\n    \"date_precision\": \"hour\",\n    \"upcoming\": false,\n    \"cores\": [\n        {\n            \"core\": \"5e9e28a7f359187afd3b2662\",\n            \"flight\": 2,\n            \"gridfins\": true,\n            \"legs\": true,\n            \"reused\": true,\n            \"landing_attempt\": true,\n            \"landing_success\": true,\n            \"landing_type\": \"RTLS\",\n            \"landpad\": \"5e9e3032383ecb267a34e7c7\"\n        }\n    ],\n    \"id\": \"5eb87d42ffd86e000604b384\"\n}\n```\n"
  },
  {
    "path": "docs/launches/v5/next.md",
    "content": "# Get next launch\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v5/launches/next`\n\n**Auth required** : `False`\n\n## Success Responses\n\n**Code** : `200 OK`\n\n```json\n{\n    \"fairings\": null,\n    \"links\": {\n        \"patch\": {\n            \"small\": \"https://images2.imgbox.com/53/22/dh0XSLXO_o.png\",\n            \"large\": \"https://images2.imgbox.com/15/2b/NAcsTEB6_o.png\"\n        },\n        \"reddit\": {\n            \"campaign\": \"https://www.reddit.com/r/spacex/comments/ezn6n0/crs20_launch_campaign_thread\",\n            \"launch\": \"https://www.reddit.com/r/spacex/comments/fe8pcj/rspacex_crs20_official_launch_discussion_updates/\",\n            \"media\": \"https://www.reddit.com/r/spacex/comments/fes64p/rspacex_crs20_media_thread_videos_images_gifs/\",\n            \"recovery\": null\n        },\n        \"flickr\": {\n            \"small\": [],\n            \"original\": [\n                \"https://live.staticflickr.com/65535/49635401403_96f9c322dc_o.jpg\",\n                \"https://live.staticflickr.com/65535/49636202657_e81210a3ca_o.jpg\",\n                \"https://live.staticflickr.com/65535/49636202572_8831c5a917_o.jpg\",\n                \"https://live.staticflickr.com/65535/49635401423_e0bef3e82f_o.jpg\",\n                \"https://live.staticflickr.com/65535/49635985086_660be7062f_o.jpg\"\n            ]\n        },\n        \"presskit\": \"https://www.spacex.com/sites/spacex/files/crs-20_mission_press_kit.pdf\",\n        \"webcast\": \"https://youtu.be/1MkcWK2PnsU\",\n        \"youtube_id\": \"1MkcWK2PnsU\",\n        \"article\": \"https://spaceflightnow.com/2020/03/07/late-night-launch-of-spacex-cargo-ship-marks-end-of-an-era/\",\n        \"wikipedia\": \"https://en.wikipedia.org/wiki/SpaceX_CRS-20\"\n    },\n    \"static_fire_date_utc\": \"2020-03-01T10:20:00.000Z\",\n    \"static_fire_date_unix\": 1583058000,\n    \"tdb\": false,\n    \"net\": false,\n    \"window\": 0,\n    \"rocket\": \"5e9d0d95eda69973a809d1ec\",\n    \"success\": true,\n    \"failures\": [],\n    \"details\": \"SpaceX's 20th and final Crew Resupply Mission under the original NASA CRS contract, this mission brings essential supplies to the International Space Station using SpaceX's reusable Dragon spacecraft. It is the last scheduled flight of a Dragon 1 capsule. (CRS-21 and up under the new Commercial Resupply Services 2 contract will use Dragon 2.) The external payload for this mission is the Bartolomeo ISS external payload hosting platform. Falcon 9 and Dragon will launch from SLC-40, Cape Canaveral Air Force Station and the booster will land at LZ-1. The mission will be complete with return and recovery of the Dragon capsule and down cargo.\",\n    \"crew\": [],\n    \"ships\": [],\n    \"capsules\": [\n        \"5e9e2c5cf359185d753b266f\"\n    ],\n    \"payloads\": [\n        \"5eb0e4d0b6c3bb0006eeb253\"\n    ],\n    \"launchpad\": \"5e9e4501f509094ba4566f84\",\n    \"auto_update\": true,\n    \"flight_number\": 91,\n    \"name\": \"CRS-20\",\n    \"date_utc\": \"2020-03-07T04:50:31.000Z\",\n    \"date_unix\": 1583556631,\n    \"date_local\": \"2020-03-06T23:50:31-05:00\",\n    \"date_precision\": \"hour\",\n    \"upcoming\": false,\n    \"cores\": [\n        {\n            \"core\": \"5e9e28a7f359187afd3b2662\",\n            \"flight\": 2,\n            \"gridfins\": true,\n            \"legs\": true,\n            \"reused\": true,\n            \"landing_attempt\": true,\n            \"landing_success\": true,\n            \"landing_type\": \"RTLS\",\n            \"landpad\": \"5e9e3032383ecb267a34e7c7\"\n        }\n    ],\n    \"id\": \"5eb87d42ffd86e000604b384\"\n}\n```\n"
  },
  {
    "path": "docs/launches/v5/one.md",
    "content": "# Get one launch\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v5/launches/:id`\n\n**URL Parameters** : `id=[string]` where `id` is the ID of the launch\n\n**Auth required** : `False`\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n    \"fairings\": null,\n    \"links\": {\n        \"patch\": {\n            \"small\": \"https://images2.imgbox.com/53/22/dh0XSLXO_o.png\",\n            \"large\": \"https://images2.imgbox.com/15/2b/NAcsTEB6_o.png\"\n        },\n        \"reddit\": {\n            \"campaign\": \"https://www.reddit.com/r/spacex/comments/ezn6n0/crs20_launch_campaign_thread\",\n            \"launch\": \"https://www.reddit.com/r/spacex/comments/fe8pcj/rspacex_crs20_official_launch_discussion_updates/\",\n            \"media\": \"https://www.reddit.com/r/spacex/comments/fes64p/rspacex_crs20_media_thread_videos_images_gifs/\",\n            \"recovery\": null\n        },\n        \"flickr\": {\n            \"small\": [],\n            \"original\": [\n                \"https://live.staticflickr.com/65535/49635401403_96f9c322dc_o.jpg\",\n                \"https://live.staticflickr.com/65535/49636202657_e81210a3ca_o.jpg\",\n                \"https://live.staticflickr.com/65535/49636202572_8831c5a917_o.jpg\",\n                \"https://live.staticflickr.com/65535/49635401423_e0bef3e82f_o.jpg\",\n                \"https://live.staticflickr.com/65535/49635985086_660be7062f_o.jpg\"\n            ]\n        },\n        \"presskit\": \"https://www.spacex.com/sites/spacex/files/crs-20_mission_press_kit.pdf\",\n        \"webcast\": \"https://youtu.be/1MkcWK2PnsU\",\n        \"youtube_id\": \"1MkcWK2PnsU\",\n        \"article\": \"https://spaceflightnow.com/2020/03/07/late-night-launch-of-spacex-cargo-ship-marks-end-of-an-era/\",\n        \"wikipedia\": \"https://en.wikipedia.org/wiki/SpaceX_CRS-20\"\n    },\n    \"static_fire_date_utc\": \"2020-03-01T10:20:00.000Z\",\n    \"static_fire_date_unix\": 1583058000,\n    \"tdb\": false,\n    \"net\": false,\n    \"window\": 0,\n    \"rocket\": \"5e9d0d95eda69973a809d1ec\",\n    \"success\": true,\n    \"failures\": [],\n    \"details\": \"SpaceX's 20th and final Crew Resupply Mission under the original NASA CRS contract, this mission brings essential supplies to the International Space Station using SpaceX's reusable Dragon spacecraft. It is the last scheduled flight of a Dragon 1 capsule. (CRS-21 and up under the new Commercial Resupply Services 2 contract will use Dragon 2.) The external payload for this mission is the Bartolomeo ISS external payload hosting platform. Falcon 9 and Dragon will launch from SLC-40, Cape Canaveral Air Force Station and the booster will land at LZ-1. The mission will be complete with return and recovery of the Dragon capsule and down cargo.\",\n    \"crew\": [],\n    \"ships\": [],\n    \"capsules\": [\n        \"5e9e2c5cf359185d753b266f\"\n    ],\n    \"payloads\": [\n        \"5eb0e4d0b6c3bb0006eeb253\"\n    ],\n    \"launchpad\": \"5e9e4501f509094ba4566f84\",\n    \"auto_update\": true,\n    \"flight_number\": 91,\n    \"name\": \"CRS-20\",\n    \"date_utc\": \"2020-03-07T04:50:31.000Z\",\n    \"date_unix\": 1583556631,\n    \"date_local\": \"2020-03-06T23:50:31-05:00\",\n    \"date_precision\": \"hour\",\n    \"upcoming\": false,\n    \"cores\": [\n        {\n            \"core\": \"5e9e28a7f359187afd3b2662\",\n            \"flight\": 2,\n            \"gridfins\": true,\n            \"legs\": true,\n            \"reused\": true,\n            \"landing_attempt\": true,\n            \"landing_success\": true,\n            \"landing_type\": \"RTLS\",\n            \"landpad\": \"5e9e3032383ecb267a34e7c7\"\n        }\n    ],\n    \"id\": \"5eb87d42ffd86e000604b384\"\n}\n```\n\n## Error Responses\n\n**Code** : `404 NOT FOUND`\n\n**Content** : `Not Found`\n"
  },
  {
    "path": "docs/launches/v5/past.md",
    "content": "# Get all past launches\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v5/launches/past`\n\n**Auth required** : `False`\n\n## Success Responses\n\n**Code** : `200 OK`\n\n```json\n[\n    {\n        \"fairings\": null,\n        \"links\": {\n            \"patch\": {\n                \"small\": \"https://images2.imgbox.com/53/22/dh0XSLXO_o.png\",\n                \"large\": \"https://images2.imgbox.com/15/2b/NAcsTEB6_o.png\"\n            },\n            \"reddit\": {\n                \"campaign\": \"https://www.reddit.com/r/spacex/comments/ezn6n0/crs20_launch_campaign_thread\",\n                \"launch\": \"https://www.reddit.com/r/spacex/comments/fe8pcj/rspacex_crs20_official_launch_discussion_updates/\",\n                \"media\": \"https://www.reddit.com/r/spacex/comments/fes64p/rspacex_crs20_media_thread_videos_images_gifs/\",\n                \"recovery\": null\n            },\n            \"flickr\": {\n                \"small\": [],\n                \"original\": [\n                    \"https://live.staticflickr.com/65535/49635401403_96f9c322dc_o.jpg\",\n                    \"https://live.staticflickr.com/65535/49636202657_e81210a3ca_o.jpg\",\n                    \"https://live.staticflickr.com/65535/49636202572_8831c5a917_o.jpg\",\n                    \"https://live.staticflickr.com/65535/49635401423_e0bef3e82f_o.jpg\",\n                    \"https://live.staticflickr.com/65535/49635985086_660be7062f_o.jpg\"\n                ]\n            },\n            \"presskit\": \"https://www.spacex.com/sites/spacex/files/crs-20_mission_press_kit.pdf\",\n            \"webcast\": \"https://youtu.be/1MkcWK2PnsU\",\n            \"youtube_id\": \"1MkcWK2PnsU\",\n            \"article\": \"https://spaceflightnow.com/2020/03/07/late-night-launch-of-spacex-cargo-ship-marks-end-of-an-era/\",\n            \"wikipedia\": \"https://en.wikipedia.org/wiki/SpaceX_CRS-20\"\n        },\n        \"static_fire_date_utc\": \"2020-03-01T10:20:00.000Z\",\n        \"static_fire_date_unix\": 1583058000,\n        \"tdb\": false,\n        \"net\": false,\n        \"window\": 0,\n        \"rocket\": \"5e9d0d95eda69973a809d1ec\",\n        \"success\": true,\n        \"failures\": [],\n        \"details\": \"SpaceX's 20th and final Crew Resupply Mission under the original NASA CRS contract, this mission brings essential supplies to the International Space Station using SpaceX's reusable Dragon spacecraft. It is the last scheduled flight of a Dragon 1 capsule. (CRS-21 and up under the new Commercial Resupply Services 2 contract will use Dragon 2.) The external payload for this mission is the Bartolomeo ISS external payload hosting platform. Falcon 9 and Dragon will launch from SLC-40, Cape Canaveral Air Force Station and the booster will land at LZ-1. The mission will be complete with return and recovery of the Dragon capsule and down cargo.\",\n        \"crew\": [],\n        \"ships\": [],\n        \"capsules\": [\n            \"5e9e2c5cf359185d753b266f\"\n        ],\n        \"payloads\": [\n            \"5eb0e4d0b6c3bb0006eeb253\"\n        ],\n        \"launchpad\": \"5e9e4501f509094ba4566f84\",\n        \"auto_update\": true,\n        \"flight_number\": 91,\n        \"name\": \"CRS-20\",\n        \"date_utc\": \"2020-03-07T04:50:31.000Z\",\n        \"date_unix\": 1583556631,\n        \"date_local\": \"2020-03-06T23:50:31-05:00\",\n        \"date_precision\": \"hour\",\n        \"upcoming\": false,\n        \"cores\": [\n            {\n                \"core\": \"5e9e28a7f359187afd3b2662\",\n                \"flight\": 2,\n                \"gridfins\": true,\n                \"legs\": true,\n                \"reused\": true,\n                \"landing_attempt\": true,\n                \"landing_success\": true,\n                \"landing_type\": \"RTLS\",\n                \"landpad\": \"5e9e3032383ecb267a34e7c7\"\n            }\n        ],\n        \"id\": \"5eb87d42ffd86e000604b384\"\n    }\n    ...\n]\n```\n"
  },
  {
    "path": "docs/launches/v5/query.md",
    "content": "# Query launches\n\n**Method** : `POST`\n\n**URL** : `https://api.spacexdata.com/v5/launches/query`\n\n**Auth required** : `False`\n\n**Body** :\n\nSee [query](../../queries.md) guide for more details on building queries and paginating results.\n\n```json\n{\n  \"query\": {},\n  \"options\": {}\n}\n```\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n  \"docs\": [\n    {\n      \"fairings\": {\n        \"reused\": false,\n        \"recovery_attempt\": true,\n        \"recovered\": false,\n        \"ships\": [\"5ea6ed2e080df4000697c908\"]\n      },\n      \"links\": {\n        \"patch\": {\n          \"small\": \"https://images2.imgbox.com/02/51/7NLaBm8c_o.png\",\n          \"large\": \"https://images2.imgbox.com/69/f5/04lBXd2F_o.png\"\n        },\n        \"reddit\": {\n          \"campaign\": \"https://www.reddit.com/r/spacex/comments/73ttkd/koreasat_5a_launch_campaign_thread/\",\n          \"launch\": \"https://www.reddit.com/r/spacex/comments/79iuvb/rspacex_koreasat_5a_official_launch_discussion/\",\n          \"media\": \"https://www.reddit.com/r/spacex/comments/79lmdu/rspacex_koreasat5a_media_thread_videos_images/\",\n          \"recovery\": null\n        },\n        \"flickr\": {\n          \"small\": [],\n          \"original\": [\n            \"https://farm5.staticflickr.com/4477/38056454431_a5f40f9fd7_o.jpg\",\n            \"https://farm5.staticflickr.com/4455/26280153979_b8016a829f_o.jpg\",\n            \"https://farm5.staticflickr.com/4459/38056455051_79ef2b949a_o.jpg\",\n            \"https://farm5.staticflickr.com/4466/26280153539_ecbc2b3fa9_o.jpg\",\n            \"https://farm5.staticflickr.com/4482/26280154209_bf08d76361_o.jpg\",\n            \"https://farm5.staticflickr.com/4493/38056455211_a4565a9cee_o.jpg\"\n          ]\n        },\n        \"presskit\": \"http://www.spacex.com/sites/spacex/files/koreasat5apresskit.pdf\",\n        \"webcast\": \"https://www.youtube.com/watch?v=RUjH14vhLxA\",\n        \"youtube_id\": \"RUjH14vhLxA\",\n        \"article\": \"https://spaceflightnow.com/2017/10/30/spacex-launches-and-lands-third-rocket-in-three-weeks/\",\n        \"wikipedia\": \"https://en.wikipedia.org/wiki/Koreasat_5A\"\n      },\n      \"static_fire_date_utc\": \"2017-10-26T16:00:00.000Z\",\n      \"static_fire_date_unix\": 1509033600,\n      \"tdb\": false,\n      \"net\": false,\n      \"window\": 8640,\n      \"rocket\": \"5e9d0d95eda69973a809d1ec\",\n      \"success\": true,\n      \"failures\": [],\n      \"details\": \"KoreaSat 5A is a Ku-band satellite capable of providing communication services from East Africa and Central Asia to southern India, Southeast Asia, the Philippines, Guam, Korea, and Japan. The satellite will be placed in GEO at 113Â° East Longitude, and will provide services ranging from broadband internet to broadcasting services and maritime communications.\",\n      \"crew\": [],\n      \"ships\": [\n        \"5ea6ed2f080df4000697c90d\",\n        \"5ea6ed2e080df4000697c908\",\n        \"5ea6ed30080df4000697c913\"\n      ],\n      \"capsules\": [],\n      \"payloads\": [\"5eb0e4c5b6c3bb0006eeb217\"],\n      \"launchpad\": \"5e9e4502f509094188566f88\",\n      \"auto_update\": true,\n      \"flight_number\": 50,\n      \"name\": \"KoreaSat 5A\",\n      \"date_utc\": \"2017-10-30T19:34:00.000Z\",\n      \"date_unix\": 1509392040,\n      \"date_local\": \"2017-10-30T15:34:00-04:00\",\n      \"date_precision\": \"hour\",\n      \"upcoming\": false,\n      \"cores\": [\n        {\n          \"core\": \"5e9e28a4f359185cc03b2651\",\n          \"flight\": 1,\n          \"gridfins\": true,\n          \"legs\": true,\n          \"reused\": false,\n          \"landing_attempt\": true,\n          \"landing_success\": true,\n          \"landing_type\": \"ASDS\",\n          \"landpad\": \"5e9e3032383ecb6bb234e7ca\"\n        }\n      ],\n      \"id\": \"5eb87d0dffd86e000604b35b\"\n    }\n  ],\n  \"totalDocs\": 109,\n  \"limit\": 10,\n  \"totalPages\": 11,\n  \"page\": 5,\n  \"pagingCounter\": 41,\n  \"hasPrevPage\": true,\n  \"hasNextPage\": true,\n  \"prevPage\": 4,\n  \"nextPage\": 6\n}\n```\n\n## Error Responses\n\n**Code** : `400 Bad Request`\n\n**Content** : Mongoose error is shown, with suggestions to fix the query.\n"
  },
  {
    "path": "docs/launches/v5/schema.md",
    "content": "# Launch Schema\n\n```json\n{\n  \"flight_number\": {\n    \"type\": \"Number\",\n    \"required\": true\n  },\n  \"name\": {\n    \"type\": \"String\",\n    \"unique\": true,\n    \"required\": true\n  },\n  \"date_utc\": {\n    \"type\": \"String\",\n    \"required\": true\n  },\n  \"date_unix\": {\n    \"type\": \"Number\",\n    \"required\": true\n  },\n  \"date_local\": {\n    \"type\": \"String\",\n    \"required\": true\n  },\n  \"date_precision\": {\n    \"type\": \"String\",\n    \"required\": true,\n    \"enum\": [\n      \"half\",\n      \"quarter\",\n      \"year\",\n      \"month\",\n      \"day\",\n      \"hour\"\n    ]\n  },\n  \"static_fire_date_utc\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"static_fire_date_unix\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"tdb\": {\n    \"type\": \"Boolean\",\n    \"default\": false\n  },\n  \"net\": {\n    \"type\": \"Boolean\",\n    \"default\": false\n  },\n  \"window\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"rocket\": {\n    \"type\": \"UUID\",\n    \"default\": null\n  },\n  \"success\": {\n    \"type\": \"Boolean\",\n    \"default\": null\n  },\n  \"failures\": [\n    {\n      \"time\": {\n        \"type\": \"Number\",\n      },\n      \"altitude\": {\n        \"type\": \"Number\",\n      },\n      \"reason\": {\n        \"type\": \"String\",\n      },\n    },\n  ],\n  \"upcoming\": {\n    \"type\": \"Boolean\",\n    \"required\": true\n  },\n  \"details\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"fairings\": {\n    \"reused\": {\n      \"type\": \"Boolean\",\n      \"default\": null\n    },\n    \"recovery_attempt\": {\n      \"type\": \"Boolean\",\n      \"default\": null\n    },\n    \"recovered\": {\n      \"type\": \"Boolean\",\n      \"default\": null\n    },\n    \"ships\": [\n      \"UUID\"\n    ]\n  },\n  \"crew\": [\n    {\n      \"crew\": {\n        \"type\": \"UUID\",\n        \"default\": null\n      },\n      \"role\": {\n        \"type\": \"String\",\n        \"default\": null\n      },\n    }\n  ],\n  \"ships\": [\n    \"UUID\"\n  ],\n  \"capsules\": [\n    \"UUID\"\n  ],\n  \"payloads\": [\n    \"UUID\"\n  ],\n  \"launchpad\": {\n    \"type\": \"UUID\",\n    \"default\": null\n  },\n  \"cores\": [\n    {\n      \"core\": {\n        \"type\": \"UUID\",\n        \"default\": null\n      },\n      \"flight\": {\n        \"type\": \"Number\",\n        \"default\": null\n      },\n      \"gridfins\": {\n        \"type\": \"Boolean\",\n        \"default\": null\n      },\n      \"legs\": {\n        \"type\": \"Boolean\",\n        \"default\": null\n      },\n      \"reused\": {\n        \"type\": \"Boolean\",\n        \"default\": null\n      },\n      \"landing_attempt\": {\n        \"type\": \"Boolean\",\n        \"default\": null\n      },\n      \"landing_success\": {\n        \"type\": \"Boolean\",\n        \"default\": null\n      },\n      \"landing_type\": {\n        \"type\": \"String\",\n        \"default\": null\n      },\n      \"landpad\": {\n        \"type\": \"UUID\",\n        \"default\": null\n      }\n    }\n  ],\n  \"links\": {\n    \"patch\": {\n      \"small\": {\n        \"type\": \"String\",\n        \"default\": null\n      },\n      \"large\": {\n        \"type\": \"String\",\n        \"default\": null\n      }\n    },\n    \"reddit\": {\n      \"campaign\": {\n        \"type\": \"String\",\n        \"default\": null\n      },\n      \"launch\": {\n        \"type\": \"String\",\n        \"default\": null\n      },\n      \"media\": {\n        \"type\": \"String\",\n        \"default\": null\n      },\n      \"recovery\": {\n        \"type\": \"String\",\n        \"default\": null\n      }\n    },\n    \"flickr\": {\n      \"small\": [\n        \"String\"\n      ],\n      \"original\": [\n        \"String\"\n      ]\n    },\n    \"presskit\": {\n      \"type\": \"String\",\n      \"default\": null\n    },\n    \"webcast\": {\n      \"type\": \"String\",\n      \"default\": null\n    },\n    \"youtube_id\": {\n      \"type\": \"String\",\n      \"default\": null\n    },\n    \"article\": {\n      \"type\": \"String\",\n      \"default\": null\n    },\n    \"wikipedia\": {\n      \"type\": \"String\",\n      \"default\": null\n    }\n  },\n  \"auto_update\": {\n    \"type\": \"Boolean\",\n    \"default\": true\n  }\n}\n```\n"
  },
  {
    "path": "docs/launches/v5/upcoming.md",
    "content": "# Get all upcoming launches\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v5/launches/upcoming`\n\n**Auth required** : `False`\n\n## Success Responses\n\n**Code** : `200 OK`\n\n```json\n[\n    {\n        \"fairings\": null,\n        \"links\": {\n            \"patch\": {\n                \"small\": \"https://images2.imgbox.com/53/22/dh0XSLXO_o.png\",\n                \"large\": \"https://images2.imgbox.com/15/2b/NAcsTEB6_o.png\"\n            },\n            \"reddit\": {\n                \"campaign\": \"https://www.reddit.com/r/spacex/comments/ezn6n0/crs20_launch_campaign_thread\",\n                \"launch\": \"https://www.reddit.com/r/spacex/comments/fe8pcj/rspacex_crs20_official_launch_discussion_updates/\",\n                \"media\": \"https://www.reddit.com/r/spacex/comments/fes64p/rspacex_crs20_media_thread_videos_images_gifs/\",\n                \"recovery\": null\n            },\n            \"flickr\": {\n                \"small\": [],\n                \"original\": [\n                    \"https://live.staticflickr.com/65535/49635401403_96f9c322dc_o.jpg\",\n                    \"https://live.staticflickr.com/65535/49636202657_e81210a3ca_o.jpg\",\n                    \"https://live.staticflickr.com/65535/49636202572_8831c5a917_o.jpg\",\n                    \"https://live.staticflickr.com/65535/49635401423_e0bef3e82f_o.jpg\",\n                    \"https://live.staticflickr.com/65535/49635985086_660be7062f_o.jpg\"\n                ]\n            },\n            \"presskit\": \"https://www.spacex.com/sites/spacex/files/crs-20_mission_press_kit.pdf\",\n            \"webcast\": \"https://youtu.be/1MkcWK2PnsU\",\n            \"youtube_id\": \"1MkcWK2PnsU\",\n            \"article\": \"https://spaceflightnow.com/2020/03/07/late-night-launch-of-spacex-cargo-ship-marks-end-of-an-era/\",\n            \"wikipedia\": \"https://en.wikipedia.org/wiki/SpaceX_CRS-20\"\n        },\n        \"static_fire_date_utc\": \"2020-03-01T10:20:00.000Z\",\n        \"static_fire_date_unix\": 1583058000,\n        \"tdb\": false,\n        \"net\": false,\n        \"window\": 0,\n        \"rocket\": \"5e9d0d95eda69973a809d1ec\",\n        \"success\": true,\n        \"failures\": [],\n        \"details\": \"SpaceX's 20th and final Crew Resupply Mission under the original NASA CRS contract, this mission brings essential supplies to the International Space Station using SpaceX's reusable Dragon spacecraft. It is the last scheduled flight of a Dragon 1 capsule. (CRS-21 and up under the new Commercial Resupply Services 2 contract will use Dragon 2.) The external payload for this mission is the Bartolomeo ISS external payload hosting platform. Falcon 9 and Dragon will launch from SLC-40, Cape Canaveral Air Force Station and the booster will land at LZ-1. The mission will be complete with return and recovery of the Dragon capsule and down cargo.\",\n        \"crew\": [],\n        \"ships\": [],\n        \"capsules\": [\n            \"5e9e2c5cf359185d753b266f\"\n        ],\n        \"payloads\": [\n            \"5eb0e4d0b6c3bb0006eeb253\"\n        ],\n        \"launchpad\": \"5e9e4501f509094ba4566f84\",\n        \"auto_update\": true,\n        \"flight_number\": 91,\n        \"name\": \"CRS-20\",\n        \"date_utc\": \"2020-03-07T04:50:31.000Z\",\n        \"date_unix\": 1583556631,\n        \"date_local\": \"2020-03-06T23:50:31-05:00\",\n        \"date_precision\": \"hour\",\n        \"upcoming\": false,\n        \"cores\": [\n            {\n                \"core\": \"5e9e28a7f359187afd3b2662\",\n                \"flight\": 2,\n                \"gridfins\": true,\n                \"legs\": true,\n                \"reused\": true,\n                \"landing_attempt\": true,\n                \"landing_success\": true,\n                \"landing_type\": \"RTLS\",\n                \"landpad\": \"5e9e3032383ecb267a34e7c7\"\n            }\n        ],\n        \"id\": \"5eb87d42ffd86e000604b384\"\n    }\n    ...\n]\n```\n"
  },
  {
    "path": "docs/launchpads/v4/all.md",
    "content": "# Get all launchpads\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/launchpads`\n\n**Auth required** : `False`\n\n## Success Responses\n\n**Code** : `200 OK`\n\n```json\n[\n    {\n        \"name\": \"VAFB SLC 4E\",\n        \"full_name\": \"Vandenberg Air Force Base Space Launch Complex 4E\",\n        \"locality\": \"Vandenberg Air Force Base\",\n        \"region\": \"California\",\n        \"timezone\": \"America/Los_Angeles\",\n        \"latitude\": 34.632093,\n        \"longitude\": -120.610829,\n        \"launch_attempts\": 15,\n        \"launch_successes\": 15,\n        \"rockets\": [\n            \"5e9d0d95eda69973a809d1ec\"\n        ],\n        \"launches\": [\n            \"5eb87ce1ffd86e000604b334\",\n            \"5eb87cf0ffd86e000604b343\",\n            \"5eb87cfdffd86e000604b34c\",\n            \"5eb87d05ffd86e000604b354\",\n            \"5eb87d08ffd86e000604b357\",\n            \"5eb87d0affd86e000604b359\",\n            \"5eb87d0fffd86e000604b35d\",\n            \"5eb87d14ffd86e000604b361\",\n            \"5eb87d16ffd86e000604b363\",\n            \"5eb87d1affd86e000604b367\",\n            \"5eb87d1fffd86e000604b36b\",\n            \"5eb87d23ffd86e000604b36e\",\n            \"5eb87d25ffd86e000604b370\",\n            \"5eb87d28ffd86e000604b373\",\n            \"5eb87d31ffd86e000604b379\"\n        ],\n        \"status\": \"active\",\n        \"id\": \"5e9e4502f509092b78566f87\"\n    },\n    ...\n]\n```\n"
  },
  {
    "path": "docs/launchpads/v4/one.md",
    "content": "# Get one launchpad\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/launchpads/:id`\n\n**URL Parameters** : `id=[string]` where `id` is the ID of the launchpad\n\n**Auth required** : `False`\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n    \"name\": \"VAFB SLC 4E\",\n    \"full_name\": \"Vandenberg Air Force Base Space Launch Complex 4E\",\n    \"locality\": \"Vandenberg Air Force Base\",\n    \"region\": \"California\",\n    \"timezone\": \"America/Los_Angeles\",\n    \"latitude\": 34.632093,\n    \"longitude\": -120.610829,\n    \"launch_attempts\": 15,\n    \"launch_successes\": 15,\n    \"rockets\": [\n        \"5e9d0d95eda69973a809d1ec\"\n    ],\n    \"launches\": [\n        \"5eb87ce1ffd86e000604b334\",\n        \"5eb87cf0ffd86e000604b343\",\n        \"5eb87cfdffd86e000604b34c\",\n        \"5eb87d05ffd86e000604b354\",\n        \"5eb87d08ffd86e000604b357\",\n        \"5eb87d0affd86e000604b359\",\n        \"5eb87d0fffd86e000604b35d\",\n        \"5eb87d14ffd86e000604b361\",\n        \"5eb87d16ffd86e000604b363\",\n        \"5eb87d1affd86e000604b367\",\n        \"5eb87d1fffd86e000604b36b\",\n        \"5eb87d23ffd86e000604b36e\",\n        \"5eb87d25ffd86e000604b370\",\n        \"5eb87d28ffd86e000604b373\",\n        \"5eb87d31ffd86e000604b379\"\n    ],\n    \"status\": \"active\",\n    \"id\": \"5e9e4502f509092b78566f87\"\n}\n```\n\n## Error Responses\n\n**Code** : `404 NOT FOUND`\n\n**Content** : `Not Found`\n"
  },
  {
    "path": "docs/launchpads/v4/query.md",
    "content": "# Query launchpads\n\n**Method** : `POST`\n\n**URL** : `https://api.spacexdata.com/v4/launchpads/query`\n\n**Auth required** : `False`\n\n**Body** :\n\nSee [query](../../queries.md) guide for more details on building queries and paginating results.\n\n```json\n{\n  \"query\": {},\n  \"options\": {}\n}\n```\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n  \"docs\": [\n      {\n          \"name\": \"VAFB SLC 4E\",\n          \"full_name\": \"Vandenberg Air Force Base Space Launch Complex 4E\",\n          \"locality\": \"Vandenberg Air Force Base\",\n          \"region\": \"California\",\n          \"timezone\": \"America/Los_Angeles\",\n          \"latitude\": 34.632093,\n          \"longitude\": -120.610829,\n          \"launch_attempts\": 15,\n          \"launch_successes\": 15,\n          \"rockets\": [\n              \"5e9d0d95eda69973a809d1ec\"\n          ],\n          \"launches\": [\n              \"5eb87ce1ffd86e000604b334\",\n              \"5eb87cf0ffd86e000604b343\",\n              \"5eb87cfdffd86e000604b34c\",\n              \"5eb87d05ffd86e000604b354\",\n              \"5eb87d08ffd86e000604b357\",\n              \"5eb87d0affd86e000604b359\",\n              \"5eb87d0fffd86e000604b35d\",\n              \"5eb87d14ffd86e000604b361\",\n              \"5eb87d16ffd86e000604b363\",\n              \"5eb87d1affd86e000604b367\",\n              \"5eb87d1fffd86e000604b36b\",\n              \"5eb87d23ffd86e000604b36e\",\n              \"5eb87d25ffd86e000604b370\",\n              \"5eb87d28ffd86e000604b373\",\n              \"5eb87d31ffd86e000604b379\"\n          ],\n          \"status\": \"active\",\n          \"id\": \"5e9e4502f509092b78566f87\"\n      },\n      ...\n  ],\n  \"totalDocs\": 6,\n  \"offset\": 0,\n  \"limit\": 10,\n  \"totalPages\": 1,\n  \"page\": 1,\n  \"pagingCounter\": 1,\n  \"hasPrevPage\": false,\n  \"hasNextPage\": false,\n  \"prevPage\": null,\n  \"nextPage\": null\n}\n```\n\n## Error Responses\n\n**Code** : `400 Bad Request`\n\n**Content** : Mongoose error is shown, with suggestions to fix the query.\n"
  },
  {
    "path": "docs/launchpads/v4/schema.md",
    "content": "# Launchpad Schema\n\n```json\n{\n  \"name\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"full_name\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"status\": {\n    \"type\": \"String\",\n    \"enum\": [\n      \"active\",\n      \"inactive\",\n      \"unknown\",\n      \"retired\",\n      \"lost\",\n      \"under construction\"\n    ],\n    \"required\": true\n  },\n  \"locality\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"region\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"timezone\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"latitude\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"longitude\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"launch_attempts\": {\n    \"type\": \"Number\",\n    \"default\": 0\n  },\n  \"launch_successes\": {\n    \"type\": \"Number\",\n    \"default\": 0\n  },\n  \"rockets\": [\n    \"UUID\"\n  ],\n  \"launches\": [\n    \"UUID\"\n  ]\n}\n```\n"
  },
  {
    "path": "docs/payloads/v4/all.md",
    "content": "# Get all payloads\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/payloads`\n\n**Auth required** : `False`\n\n## Success Responses\n\n**Code** : `200 OK`\n\n```json\n[\n    {\n        \"dragon\": {\n            \"capsule\": null,\n            \"mass_returned_kg\": null,\n            \"mass_returned_lbs\": null,\n            \"flight_time_sec\": null,\n            \"manifest\": null,\n            \"water_landing\": null,\n            \"land_landing\": null\n        },\n        \"name\": \"Tintin A & B\",\n        \"type\": \"Satellite\",\n        \"reused\": false,\n        \"launch\": \"5eb87d14ffd86e000604b361\",\n        \"customers\": [\n            \"SpaceX\"\n        ],\n        \"norad_ids\": [\n            43216,\n            43217\n        ],\n        \"nationalities\": [\n            \"United States\"\n        ],\n        \"manufacturers\": [\n            \"SpaceX\"\n        ],\n        \"mass_kg\": 800,\n        \"mass_lbs\": 1763.7,\n        \"orbit\": \"SSO\",\n        \"reference_system\": \"geocentric\",\n        \"regime\": \"low-earth\",\n        \"longitude\": null,\n        \"semi_major_axis_km\": 6737.42,\n        \"eccentricity\": 0.0012995,\n        \"periapsis_km\": 350.53,\n        \"apoapsis_km\": 368.04,\n        \"inclination_deg\": 97.4444,\n        \"period_min\": 91.727,\n        \"lifespan_years\": 1,\n        \"epoch\": \"2020-06-13T13:46:31.000Z\",\n        \"mean_motion\": 15.69864906,\n        \"raan\": 176.6734,\n        \"arg_of_pericenter\": 174.2326,\n        \"mean_anomaly\": 185.9087,\n        \"id\": \"5eb0e4c6b6c3bb0006eeb21e\"\n    },\n    ...\n]\n```\n"
  },
  {
    "path": "docs/payloads/v4/one.md",
    "content": "# Get one payload\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/payloads/:id`\n\n**URL Parameters** : `id=[string]` where `id` is the ID of the payload\n\n**Auth required** : `False`\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n    \"dragon\": {\n        \"capsule\": null,\n        \"mass_returned_kg\": null,\n        \"mass_returned_lbs\": null,\n        \"flight_time_sec\": null,\n        \"manifest\": null,\n        \"water_landing\": null,\n        \"land_landing\": null\n    },\n    \"name\": \"Tintin A & B\",\n    \"type\": \"Satellite\",\n    \"reused\": false,\n    \"launch\": \"5eb87d14ffd86e000604b361\",\n    \"customers\": [\n        \"SpaceX\"\n    ],\n    \"norad_ids\": [\n        43216,\n        43217\n    ],\n    \"nationalities\": [\n        \"United States\"\n    ],\n    \"manufacturers\": [\n        \"SpaceX\"\n    ],\n    \"mass_kg\": 800,\n    \"mass_lbs\": 1763.7,\n    \"orbit\": \"SSO\",\n    \"reference_system\": \"geocentric\",\n    \"regime\": \"low-earth\",\n    \"longitude\": null,\n    \"semi_major_axis_km\": 6737.42,\n    \"eccentricity\": 0.0012995,\n    \"periapsis_km\": 350.53,\n    \"apoapsis_km\": 368.04,\n    \"inclination_deg\": 97.4444,\n    \"period_min\": 91.727,\n    \"lifespan_years\": 1,\n    \"epoch\": \"2020-06-13T13:46:31.000Z\",\n    \"mean_motion\": 15.69864906,\n    \"raan\": 176.6734,\n    \"arg_of_pericenter\": 174.2326,\n    \"mean_anomaly\": 185.9087,\n    \"id\": \"5eb0e4c6b6c3bb0006eeb21e\"\n}\n```\n\n## Error Responses\n\n**Code** : `404 NOT FOUND`\n\n**Content** : `Not Found`\n"
  },
  {
    "path": "docs/payloads/v4/query.md",
    "content": "# Query payloads\n\n**Method** : `POST`\n\n**URL** : `https://api.spacexdata.com/v4/payloads/query`\n\n**Auth required** : `False`\n\n**Body** :\n\nSee [query](../../queries.md) guide for more details on building queries and paginating results.\n\n```json\n{\n  \"query\": {},\n  \"options\": {}\n}\n```\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n  \"docs\": [\n    {\n      \"dragon\": {\n        \"capsule\": null,\n        \"mass_returned_kg\": null,\n        \"mass_returned_lbs\": null,\n        \"flight_time_sec\": null,\n        \"manifest\": null,\n        \"water_landing\": null,\n        \"land_landing\": null\n      },\n      \"name\": \"Tintin A & B\",\n      \"type\": \"Satellite\",\n      \"reused\": false,\n      \"launch\": \"5eb87d14ffd86e000604b361\",\n      \"customers\": [\n        \"SpaceX\"\n      ],\n      \"norad_ids\": [\n        43216,\n        43217\n      ],\n      \"nationalities\": [\n        \"United States\"\n      ],\n      \"manufacturers\": [\n        \"SpaceX\"\n      ],\n      \"mass_kg\": 800,\n      \"mass_lbs\": 1763.7,\n      \"orbit\": \"SSO\",\n      \"reference_system\": \"geocentric\",\n      \"regime\": \"low-earth\",\n      \"longitude\": null,\n      \"semi_major_axis_km\": 6737.42,\n      \"eccentricity\": 0.0012995,\n      \"periapsis_km\": 350.53,\n      \"apoapsis_km\": 368.04,\n      \"inclination_deg\": 97.4444,\n      \"period_min\": 91.727,\n      \"lifespan_years\": 1,\n      \"epoch\": \"2020-06-13T13:46:31.000Z\",\n      \"mean_motion\": 15.69864906,\n      \"raan\": 176.6734,\n      \"arg_of_pericenter\": 174.2326,\n      \"mean_anomaly\": 185.9087,\n      \"id\": \"5eb0e4c6b6c3bb0006eeb21e\"\n    }\n    ...\n  ],\n  \"totalDocs\": 136,\n  \"offset\": 0,\n  \"limit\": 10,\n  \"totalPages\": 14,\n  \"page\": 1,\n  \"pagingCounter\": 1,\n  \"hasPrevPage\": false,\n  \"hasNextPage\": true,\n  \"prevPage\": null,\n  \"nextPage\": 2\n}\n```\n\n## Error Responses\n\n**Code** : `400 Bad Request`\n\n**Content** : Mongoose error is shown, with suggestions to fix the query.\n"
  },
  {
    "path": "docs/payloads/v4/schema.md",
    "content": "# Payload Schema\n\n```json\n{\n  \"name\": {\n    \"type\": \"String\",\n    \"default\": null,\n    \"unique\": true\n  },\n  \"type\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"reused\": {\n    \"type\": \"Boolean\",\n    \"default\": false\n  },\n  \"launch\": {\n    \"type\": \"UUID\",\n    \"default\": null\n  },\n  \"customers\": [\n    \"String\"\n  ],\n  \"norad_ids\": [\n    \"Number\"\n  ],\n  \"nationalities\": [\n    \"String\"\n  ],\n  \"manufacturers\": [\n    \"String\"\n  ],\n  \"mass_kg\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"mass_lbs\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"orbit\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"reference_system\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"regime\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"longitude\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"semi_major_axis_km\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"eccentricity\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"periapsis_km\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"apoapsis_km\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"inclination_deg\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"period_min\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"lifespan_years\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"epoch\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"mean_motion\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"raan\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"arg_of_pericenter\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"mean_anomaly\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"dragon\": {\n    \"capsule\": {\n      \"type\": \"UUID\",\n      \"default\": null\n    },\n    \"mass_returned_kg\": {\n      \"type\": \"Number\",\n      \"default\": null\n    },\n    \"mass_returned_lbs\": {\n      \"type\": \"Number\",\n      \"default\": null\n    },\n    \"flight_time_sec\": {\n      \"type\": \"Number\",\n      \"default\": null\n    },\n    \"manifest\": {\n      \"type\": \"String\",\n      \"default\": null\n    },\n    \"water_landing\": {\n      \"type\": \"Boolean\",\n      \"default\": null\n    },\n    \"land_landing\": {\n      \"type\": \"Boolean\",\n      \"default\": null\n    }\n  }\n}\n```\n"
  },
  {
    "path": "docs/queries.md",
    "content": "# Query + Pagination Guide\n\nAll `/query` routes support pagination parameters via [mongoose-paginate](https://github.com/aravindnc/mongoose-paginate-v2).\n\nThe default body for `/query` routes is:\n\n```json\n{\n  \"query\": {},\n  \"options\": {},\n}\n```\n\n`query` accepts any valid MongoDB find() query, documented [here](https://docs.mongodb.com/manual/tutorial/query-documents/)\n\n`options` accepts any of the options documented [here](https://github.com/aravindnc/mongoose-paginate-v2#modelpaginatequery-options-callback), but here are some of the most common:\n\n- `select` { Object | String } - Fields to return (by default returns all fields). [Documentation](http://mongoosejs.com/docs/api.html#query_Query-select)\n- `sort` { Object | String } - Sort order. [Documentation](http://mongoosejs.com/docs/api.html#query_Query-sort)\n- `offset` { Number } - Use `offset` or `page` to set skip position\n- `page` { Number }\n- `limit` { Number }\n- `pagination` { Boolean } - If set to false, it will return all docs without adding limit condition. (Default: True)\n- `populate` {Array | Object | String} - Paths which should be populated with other documents. [Documentation](https://mongoosejs.com/docs/api.html#query_Query-populate)\n\nThis is the default return structure for paginated results:\n\n```json\n{\n    \"docs\": [],\n    \"totalDocs\": 0,\n    \"offset\": 0,\n    \"limit\": 10,\n    \"totalPages\": 1,\n    \"page\": 1,\n    \"pagingCounter\": 1,\n    \"hasPrevPage\": false,\n    \"hasNextPage\": false,\n    \"prevPage\": null,\n    \"nextPage\": null\n}\n```\n\nBy default, UUID's are used to reference documents in another collection. For example, the [launches](launches/query.md) endpoint has an array of UUID's named `payloads` that references a payload in the [payloads](payloads/query.md) endpoint.\n\n```json\n{\n  \"payloads\": [\n    \"5eb0e4c6b6c3bb0006eeb21e\"\n  ]\n}\n```\n\nThis allows us to populate or replace the UUID with the payload that it references. In this example, to populate `payloads` with the corresponding document, we would send a `POST` request to `https://api.spacexdata.com/v4/launches/query` with the following body:\n\n```json\n{\n  \"query\": {},\n  \"options\": {\n    \"populate\": [\n      \"payloads\"\n    ]\n  },\n}\n```\n\nWhich returns the linked payload object in place of the UUID:\n\n```json\n{\n  ...\n  \"payloads\": [\n    {\n      \"dragon\": {\n        \"capsule\": null,\n        \"mass_returned_kg\": null,\n        \"mass_returned_lbs\": null,\n        \"flight_time_sec\": null,\n        \"manifest\": null,\n        \"water_landing\": null,\n        \"land_landing\": null\n      },\n      \"name\": \"Tintin A & B\",\n      \"type\": \"Satellite\",\n      \"reused\": false,\n      \"launch\": \"5eb87d14ffd86e000604b361\",\n      \"customers\": [\n        \"SpaceX\"\n      ],\n      \"norad_ids\": [\n        43216,\n        43217\n      ],\n      \"nationalities\": [\n        \"United States\"\n      ],\n      \"manufacturers\": [\n        \"SpaceX\"\n      ],\n      \"mass_kg\": 800,\n      \"mass_lbs\": 1763.7,\n      \"orbit\": \"SSO\",\n      \"reference_system\": \"geocentric\",\n      \"regime\": \"low-earth\",\n      \"longitude\": null,\n      \"semi_major_axis_km\": 6737.42,\n      \"eccentricity\": 0.0012995,\n      \"periapsis_km\": 350.53,\n      \"apoapsis_km\": 368.04,\n      \"inclination_deg\": 97.4444,\n      \"period_min\": 91.727,\n      \"lifespan_years\": 1,\n      \"epoch\": \"2020-06-13T13:46:31.000Z\",\n      \"mean_motion\": 15.69864906,\n      \"raan\": 176.6734,\n      \"arg_of_pericenter\": 174.2326,\n      \"mean_anomaly\": 185.9087,\n      \"id\": \"5eb0e4c6b6c3bb0006eeb21e\"\n    }\n  ]\n  ...\n}\n```\n\nPopulate also allows you to select specific fields to return. For example, if you were only interested in the payload `name`, you could use the following:\n\n```json\n{\n  \"options\": {\n    \"populate\": [\n      {\n        \"path\": \"payloads\",\n        \"select\": {\n          \"name\": 1\n        }\n      }\n    ]\n  }\n}\n```\n\nWhich would return:\n\n```json\n{\n  \"payloads\": [\n    {\n      \"name\": \"Tintin A & B\",\n      \"id\": \"5eb0e4c6b6c3bb0006eeb21e\"\n    }\n  ]\n}\n```\n\nPopulate can also be nested inside another populate to recursively fill fields. For example, you could populate the `payloads` array, and also populate the `launch` property inside each payload:\n\n```json\n{\n  \"options\": {\n    \"populate\": [\n      {\n        \"path\":\"payloads\",\n        \"populate\": [\n          {\n            \"path\":\"launch\"\n          }\n        ]\n      }\n    ]\n  }\n}\n```\n\n## Examples\n\n### Query between 2 dates\n\nDates need to be ISO 8601 friendly for these operators to work properly\n\n```json\n{\n  \"query\": {\n    \"date_utc\": {\n      \"$gte\": \"2017-06-22T00:00:00.000Z\",\n      \"$lte\": \"2017-06-25T00:00:00.000Z\"\n    }\n }\n}\n```\n\n### Full text search\n\nThis will search all text indexes in a collection. All string fields get indexed\n\nSee the mongo [reference](https://docs.mongodb.com/manual/reference/operator/query/text/) for more details on additional operators.\n\n```json\n{\n \"query\": {\n    \"$text\": {\n      \"$search\": \"crs\"\n    }\n  }\n}\n```\n\n### Next Upcoming Launch\n\n```json\n{\n   \"query\":{\n      \"upcoming\":true\n   },\n   \"options\":{\n      \"limit\":1,\n      \"sort\":{\n         \"flight_number\":\"asc\"\n      }\n   }\n}\n```\n\n### Complex Query\n\n```json\n{\n  \"query\": {\n    \"date_utc\": {\n      \"$gte\": \"2017-06-22T00:00:00.000Z\",\n      \"$lte\": \"2017-06-25T00:00:00.000Z\"\n    },\n    \"$or\": [\n      {\n        \"flight_number\": {\n          \"$gt\": 30\n        }\n      },\n      {\n        \"tbd\": true\n      }\n    ],\n    \"date_precision\": {\n      \"$in\": [\n        \"month\",\n        \"day\"\n      ]\n    }\n  },\n  \"options\": {\n    \"sort\": {\n      \"flight_number\": \"asc\"\n    },\n    \"limit\": 50\n  }\n}\n```\n"
  },
  {
    "path": "docs/roadster/v4/get.md",
    "content": "# Get roadster info\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/roadster`\n\n**Auth required** : `False`\n\n## Success Responses\n\n**Code** : `200 OK`\n\n```json\n{\n    \"flickr_images\": [\n        \"https://farm5.staticflickr.com/4615/40143096241_11128929df_b.jpg\",\n        \"https://farm5.staticflickr.com/4702/40110298232_91b32d0cc0_b.jpg\",\n        \"https://farm5.staticflickr.com/4676/40110297852_5e794b3258_b.jpg\",\n        \"https://farm5.staticflickr.com/4745/40110304192_6e3e9a7a1b_b.jpg\"\n    ],\n    \"name\": \"Elon Musk's Tesla Roadster\",\n    \"launch_date_utc\": \"2018-02-06T20:45:00.000Z\",\n    \"launch_date_unix\": 1517949900,\n    \"launch_mass_kg\": 1350,\n    \"launch_mass_lbs\": 2976,\n    \"norad_id\": 43205,\n    \"epoch_jd\": 2459014.345891204,\n    \"orbit_type\": \"heliocentric\",\n    \"apoapsis_au\": 1.663950009802517,\n    \"periapsis_au\": 0.9859657216725529,\n    \"semi_major_axis_au\": 196.2991348009594,\n    \"eccentricity\": 0.2558512635239784,\n    \"inclination\": 1.077499248052439,\n    \"longitude\": 317.0839961949045,\n    \"periapsis_arg\": 177.5240278992875,\n    \"period_days\": 557.059427465354,\n    \"speed_kph\": 72209.97792,\n    \"speed_mph\": 44869.18619012833,\n    \"earth_distance_km\": 220606726.83228922,\n    \"earth_distance_mi\": 137078622.45850638,\n    \"mars_distance_km\": 89348334.47067611,\n    \"mars_distance_mi\": 55518463.93837848,\n    \"wikipedia\": \"https://en.wikipedia.org/wiki/Elon_Musk%27s_Tesla_Roadster\",\n    \"video\": \"https://youtu.be/wbSwFU6tY1c\",\n    \"details\": \"Elon Musk's Tesla Roadster is an electric sports car that served as the dummy payload for the February 2018 Falcon Heavy test flight and is now an artificial satellite of the Sun. Starman, a mannequin dressed in a spacesuit, occupies the driver's seat. The car and rocket are products of Tesla and SpaceX. This 2008-model Roadster was previously used by Musk for commuting, and is the only consumer car sent into space.\",\n    \"id\": \"5eb75f0842fea42237d7f3f4\"\n}\n```\n"
  },
  {
    "path": "docs/roadster/v4/query.md",
    "content": "# Query roadster\n\n**Method** : `POST`\n\n**URL** : `https://api.spacexdata.com/v4/roadster/query`\n\n**Auth required** : `False`\n\n**Body** :\n\n**NOTE:** Unlike other `/query` endpoints, this does not provide pagination, and only exposes the `select` ability in `options` to allow you to hide/show specific fields in the response. For more info on how select works, see the [mongoose](https://mongoosejs.com/docs/api.html#query_Query-select) docs.\n\n```json\n{\n  \"query\": {},\n  \"options\": {\n    \"select\":  {\n      \"norad_id\": 1,\n    }\n  }\n}\n```\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n    \"flickr_images\": [\n        \"https://farm5.staticflickr.com/4615/40143096241_11128929df_b.jpg\",\n        \"https://farm5.staticflickr.com/4702/40110298232_91b32d0cc0_b.jpg\",\n        \"https://farm5.staticflickr.com/4676/40110297852_5e794b3258_b.jpg\",\n        \"https://farm5.staticflickr.com/4745/40110304192_6e3e9a7a1b_b.jpg\"\n    ],\n    \"name\": \"Elon Musk's Tesla Roadster\",\n    \"launch_date_utc\": \"2018-02-06T20:45:00.000Z\",\n    \"launch_date_unix\": 1517949900,\n    \"launch_mass_kg\": 1350,\n    \"launch_mass_lbs\": 2976,\n    \"norad_id\": 43205,\n    \"epoch_jd\": 2459014.345891204,\n    \"orbit_type\": \"heliocentric\",\n    \"apoapsis_au\": 1.663950009802517,\n    \"periapsis_au\": 0.9859657216725529,\n    \"semi_major_axis_au\": 196.2991348009594,\n    \"eccentricity\": 0.2558512635239784,\n    \"inclination\": 1.077499248052439,\n    \"longitude\": 317.0839961949045,\n    \"periapsis_arg\": 177.5240278992875,\n    \"period_days\": 557.059427465354,\n    \"speed_kph\": 72209.97792,\n    \"speed_mph\": 44869.18619012833,\n    \"earth_distance_km\": 220606726.83228922,\n    \"earth_distance_mi\": 137078622.45850638,\n    \"mars_distance_km\": 89348334.47067611,\n    \"mars_distance_mi\": 55518463.93837848,\n    \"wikipedia\": \"https://en.wikipedia.org/wiki/Elon_Musk%27s_Tesla_Roadster\",\n    \"video\": \"https://youtu.be/wbSwFU6tY1c\",\n    \"details\": \"Elon Musk's Tesla Roadster is an electric sports car that served as the dummy payload for the February 2018 Falcon Heavy test flight and is now an artificial satellite of the Sun. Starman, a mannequin dressed in a spacesuit, occupies the driver's seat. The car and rocket are products of Tesla and SpaceX. This 2008-model Roadster was previously used by Musk for commuting, and is the only consumer car sent into space.\",\n    \"id\": \"5eb75f0842fea42237d7f3f4\"\n}\n```\n\n## Error Responses\n\n**Code** : `400 Bad Request`\n\n**Content** : Mongoose error is shown, with suggestions to fix the query.\n"
  },
  {
    "path": "docs/roadster/v4/schema.md",
    "content": "# Roadster Schema\n\n```json\n{\n  \"name\": {\n    \"type\": \"String\"\n  },\n  \"launch_date_utc\": {\n    \"type\": \"String\"\n  },\n  \"launch_date_unix\": {\n    \"type\": \"Number\"\n  },\n  \"launch_mass_kg\": {\n    \"type\": \"Number\"\n  },\n  \"launch_mass_lbs\": {\n    \"type\": \"Number\"\n  },\n  \"norad_id\": {\n    \"type\": \"Number\"\n  },\n  \"epoch_jd\": {\n    \"type\": \"Number\"\n  },\n  \"orbit_type\": {\n    \"type\": \"String\"\n  },\n  \"apoapsis_au\": {\n    \"type\": \"Number\"\n  },\n  \"periapsis_au\": {\n    \"type\": \"Number\"\n  },\n  \"semi_major_axis_au\": {\n    \"type\": \"Number\"\n  },\n  \"eccentricity\": {\n    \"type\": \"Number\"\n  },\n  \"inclination\": {\n    \"type\": \"Number\"\n  },\n  \"longitude\": {\n    \"type\": \"Number\"\n  },\n  \"periapsis_arg\": {\n    \"type\": \"Number\"\n  },\n  \"period_days\": {\n    \"type\": \"Number\"\n  },\n  \"speed_kph\": {\n    \"type\": \"Number\"\n  },\n  \"speed_mph\": {\n    \"type\": \"Number\"\n  },\n  \"earth_distance_km\": {\n    \"type\": \"Number\"\n  },\n  \"earth_distance_mi\": {\n    \"type\": \"Number\"\n  },\n  \"mars_distance_km\": {\n    \"type\": \"Number\"\n  },\n  \"mars_distance_mi\": {\n    \"type\": \"Number\"\n  },\n  \"flickr_images\": [\n    \"String\"\n  ],\n  \"wikipedia\": {\n    \"type\": \"String\"\n  },\n  \"video\": {\n    \"type\": \"String\"\n  },\n  \"details\": {\n    \"type\": \"String\"\n  }\n}\n```\n"
  },
  {
    "path": "docs/rockets/v4/all.md",
    "content": "# Get all rockets\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/rockets`\n\n**Auth required** : `False`\n\n## Success Responses\n\n**Code** : `200 OK`\n\n```json\n[\n  {\n    \"height\": {\n      \"meters\": 70,\n      \"feet\": 229.6\n    },\n    \"diameter\": {\n      \"meters\": 12.2,\n      \"feet\": 39.9\n    },\n    \"mass\": {\n      \"kg\": 1420788,\n      \"lb\": 3125735\n    },\n    \"first_stage\": {\n      \"thrust_sea_level\": {\n        \"kN\": 22819,\n        \"lbf\": 5130000\n      },\n      \"thrust_vacuum\": {\n        \"kN\": 24681,\n        \"lbf\": 5548500\n      },\n      \"reusable\": true,\n      \"engines\": 27,\n      \"fuel_amount_tons\": 1155,\n      \"burn_time_sec\": 162\n    },\n    \"second_stage\": {\n      \"thrust\": {\n        \"kN\": 934,\n        \"lbf\": 210000\n      },\n      \"payloads\": {\n        \"composite_fairing\": {\n          \"height\": {\n            \"meters\": 13.1,\n            \"feet\": 43\n          },\n          \"diameter\": {\n            \"meters\": 5.2,\n            \"feet\": 17.1\n          }\n        },\n        \"option_1\": \"dragon\"\n      },\n      \"reusable\": false,\n      \"engines\": 1,\n      \"fuel_amount_tons\": 90,\n      \"burn_time_sec\": 397\n    },\n    \"engines\": {\n      \"isp\": {\n        \"sea_level\": 288,\n        \"vacuum\": 312\n      },\n      \"thrust_sea_level\": {\n        \"kN\": 845,\n        \"lbf\": 190000\n      },\n      \"thrust_vacuum\": {\n        \"kN\": 914,\n        \"lbf\": 205500\n      },\n      \"number\": 27,\n      \"type\": \"merlin\",\n      \"version\": \"1D+\",\n      \"layout\": \"octaweb\",\n      \"engine_loss_max\": 6,\n      \"propellant_1\": \"liquid oxygen\",\n      \"propellant_2\": \"RP-1 kerosene\",\n      \"thrust_to_weight\": 180.1\n    },\n    \"landing_legs\": {\n      \"number\": 12,\n      \"material\": \"carbon fiber\"\n    },\n    \"payload_weights\": [\n      {\n        \"id\": \"leo\",\n        \"name\": \"Low Earth Orbit\",\n        \"kg\": 63800,\n        \"lb\": 140660\n      },\n      {\n        \"id\": \"gto\",\n        \"name\": \"Geosynchronous Transfer Orbit\",\n        \"kg\": 26700,\n        \"lb\": 58860\n      },\n      {\n        \"id\": \"mars\",\n        \"name\": \"Mars Orbit\",\n        \"kg\": 16800,\n        \"lb\": 37040\n      },\n      {\n        \"id\": \"pluto\",\n        \"name\": \"Pluto Orbit\",\n        \"kg\": 3500,\n        \"lb\": 7720\n      }\n    ],\n    \"flickr_images\": [\n      \"https://farm5.staticflickr.com/4599/38583829295_581f34dd84_b.jpg\",\n      \"https://farm5.staticflickr.com/4645/38583830575_3f0f7215e6_b.jpg\",\n      \"https://farm5.staticflickr.com/4696/40126460511_b15bf84c85_b.jpg\",\n      \"https://farm5.staticflickr.com/4711/40126461411_aabc643fd8_b.jpg\"\n    ],\n    \"name\": \"Falcon Heavy\",\n    \"type\": \"rocket\",\n    \"active\": true,\n    \"stages\": 2,\n    \"boosters\": 2,\n    \"cost_per_launch\": 90000000,\n    \"success_rate_pct\": 100,\n    \"first_flight\": \"2018-02-06\",\n    \"country\": \"United States\",\n    \"company\": \"SpaceX\",\n    \"wikipedia\": \"https://en.wikipedia.org/wiki/Falcon_Heavy\",\n    \"description\": \"With the ability to lift into orbit over 54 metric tons (119,000 lb)--a mass equivalent to a 737 jetliner loaded with passengers, crew, luggage and fuel--Falcon Heavy can lift more than twice the payload of the next closest operational vehicle, the Delta IV Heavy, at one-third the cost.\",\n    \"id\": \"5e9d0d95eda69974db09d1ed\"\n  },\n  ...\n]\n```\n"
  },
  {
    "path": "docs/rockets/v4/one.md",
    "content": "# Get one rocket\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/rockets/:id`\n\n**URL Parameters** : `id=[string]` where `id` is the ID of the rocket\n\n**Auth required** : `False`\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n    \"height\": {\n      \"meters\": 70,\n      \"feet\": 229.6\n    },\n    \"diameter\": {\n      \"meters\": 12.2,\n      \"feet\": 39.9\n    },\n    \"mass\": {\n      \"kg\": 1420788,\n      \"lb\": 3125735\n    },\n    \"first_stage\": {\n      \"thrust_sea_level\": {\n        \"kN\": 22819,\n        \"lbf\": 5130000\n      },\n      \"thrust_vacuum\": {\n        \"kN\": 24681,\n        \"lbf\": 5548500\n      },\n      \"reusable\": true,\n      \"engines\": 27,\n      \"fuel_amount_tons\": 1155,\n      \"burn_time_sec\": 162\n    },\n    \"second_stage\": {\n      \"thrust\": {\n        \"kN\": 934,\n        \"lbf\": 210000\n      },\n      \"payloads\": {\n        \"composite_fairing\": {\n          \"height\": {\n            \"meters\": 13.1,\n            \"feet\": 43\n          },\n          \"diameter\": {\n            \"meters\": 5.2,\n            \"feet\": 17.1\n          }\n        },\n        \"option_1\": \"dragon\"\n      },\n      \"reusable\": false,\n      \"engines\": 1,\n      \"fuel_amount_tons\": 90,\n      \"burn_time_sec\": 397\n    },\n    \"engines\": {\n      \"isp\": {\n        \"sea_level\": 288,\n        \"vacuum\": 312\n      },\n      \"thrust_sea_level\": {\n        \"kN\": 845,\n        \"lbf\": 190000\n      },\n      \"thrust_vacuum\": {\n        \"kN\": 914,\n        \"lbf\": 205500\n      },\n      \"number\": 27,\n      \"type\": \"merlin\",\n      \"version\": \"1D+\",\n      \"layout\": \"octaweb\",\n      \"engine_loss_max\": 6,\n      \"propellant_1\": \"liquid oxygen\",\n      \"propellant_2\": \"RP-1 kerosene\",\n      \"thrust_to_weight\": 180.1\n    },\n    \"landing_legs\": {\n      \"number\": 12,\n      \"material\": \"carbon fiber\"\n    },\n    \"payload_weights\": [\n      {\n        \"id\": \"leo\",\n        \"name\": \"Low Earth Orbit\",\n        \"kg\": 63800,\n        \"lb\": 140660\n      },\n      {\n        \"id\": \"gto\",\n        \"name\": \"Geosynchronous Transfer Orbit\",\n        \"kg\": 26700,\n        \"lb\": 58860\n      },\n      {\n        \"id\": \"mars\",\n        \"name\": \"Mars Orbit\",\n        \"kg\": 16800,\n        \"lb\": 37040\n      },\n      {\n        \"id\": \"pluto\",\n        \"name\": \"Pluto Orbit\",\n        \"kg\": 3500,\n        \"lb\": 7720\n      }\n    ],\n    \"flickr_images\": [\n      \"https://farm5.staticflickr.com/4599/38583829295_581f34dd84_b.jpg\",\n      \"https://farm5.staticflickr.com/4645/38583830575_3f0f7215e6_b.jpg\",\n      \"https://farm5.staticflickr.com/4696/40126460511_b15bf84c85_b.jpg\",\n      \"https://farm5.staticflickr.com/4711/40126461411_aabc643fd8_b.jpg\"\n    ],\n    \"name\": \"Falcon Heavy\",\n    \"type\": \"rocket\",\n    \"active\": true,\n    \"stages\": 2,\n    \"boosters\": 2,\n    \"cost_per_launch\": 90000000,\n    \"success_rate_pct\": 100,\n    \"first_flight\": \"2018-02-06\",\n    \"country\": \"United States\",\n    \"company\": \"SpaceX\",\n    \"wikipedia\": \"https://en.wikipedia.org/wiki/Falcon_Heavy\",\n    \"description\": \"With the ability to lift into orbit over 54 metric tons (119,000 lb)--a mass equivalent to a 737 jetliner loaded with passengers, crew, luggage and fuel--Falcon Heavy can lift more than twice the payload of the next closest operational vehicle, the Delta IV Heavy, at one-third the cost.\",\n    \"id\": \"5e9d0d95eda69974db09d1ed\"\n  }\n```\n\n## Error Responses\n\n**Code** : `404 NOT FOUND`\n\n**Content** : `Not Found`\n"
  },
  {
    "path": "docs/rockets/v4/query.md",
    "content": "# Query rockets\n\n**Method** : `POST`\n\n**URL** : `https://api.spacexdata.com/v4/rockets/query`\n\n**Auth required** : `False`\n\n**Body** :\n\nSee [query](../../queries.md) guide for more details on building queries and paginating results.\n\n```json\n{\n  \"query\": {},\n  \"options\": {}\n}\n```\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n  \"docs\": [\n    {\n      \"height\": {\n        \"meters\": 70,\n        \"feet\": 229.6\n      },\n      \"diameter\": {\n        \"meters\": 3.7,\n        \"feet\": 12\n      },\n      \"mass\": {\n        \"kg\": 549054,\n        \"lb\": 1207920\n      },\n      \"first_stage\": {\n        \"thrust_sea_level\": {\n          \"kN\": 7607,\n          \"lbf\": 1710000\n        },\n        \"thrust_vacuum\": {\n          \"kN\": 8227,\n          \"lbf\": 1849500\n        },\n        \"reusable\": true,\n        \"engines\": 9,\n        \"fuel_amount_tons\": 385,\n        \"burn_time_sec\": 162\n      },\n      \"second_stage\": {\n        \"thrust\": {\n          \"kN\": 934,\n          \"lbf\": 210000\n        },\n        \"payloads\": {\n          \"composite_fairing\": {\n            \"height\": {\n              \"meters\": 13.1,\n              \"feet\": 43\n            },\n            \"diameter\": {\n              \"meters\": 5.2,\n              \"feet\": 17.1\n            }\n          },\n          \"option_1\": \"dragon\"\n        },\n        \"reusable\": false,\n        \"engines\": 1,\n        \"fuel_amount_tons\": 90,\n        \"burn_time_sec\": 397\n      },\n      \"engines\": {\n        \"isp\": {\n          \"sea_level\": 288,\n          \"vacuum\": 312\n        },\n        \"thrust_sea_level\": {\n          \"kN\": 845,\n          \"lbf\": 190000\n        },\n        \"thrust_vacuum\": {\n          \"kN\": 914,\n          \"lbf\": 205500\n        },\n        \"number\": 9,\n        \"type\": \"merlin\",\n        \"version\": \"1D+\",\n        \"layout\": \"octaweb\",\n        \"engine_loss_max\": 2,\n        \"propellant_1\": \"liquid oxygen\",\n        \"propellant_2\": \"RP-1 kerosene\",\n        \"thrust_to_weight\": 180.1\n      },\n      \"landing_legs\": {\n        \"number\": 4,\n        \"material\": \"carbon fiber\"\n      },\n      \"payload_weights\": [\n        {\n          \"id\": \"leo\",\n          \"name\": \"Low Earth Orbit\",\n          \"kg\": 22800,\n          \"lb\": 50265\n        },\n        {\n          \"id\": \"gto\",\n          \"name\": \"Geosynchronous Transfer Orbit\",\n          \"kg\": 8300,\n          \"lb\": 18300\n        },\n        {\n          \"id\": \"mars\",\n          \"name\": \"Mars Orbit\",\n          \"kg\": 4020,\n          \"lb\": 8860\n        }\n      ],\n      \"flickr_images\": [\n        \"https://farm1.staticflickr.com/929/28787338307_3453a11a77_b.jpg\",\n        \"https://farm4.staticflickr.com/3955/32915197674_eee74d81bb_b.jpg\",\n        \"https://farm1.staticflickr.com/293/32312415025_6841e30bf1_b.jpg\",\n        \"https://farm1.staticflickr.com/623/23660653516_5b6cb301d1_b.jpg\",\n        \"https://farm6.staticflickr.com/5518/31579784413_d853331601_b.jpg\",\n        \"https://farm1.staticflickr.com/745/32394687645_a9c54a34ef_b.jpg\"\n      ],\n      \"name\": \"Falcon 9\",\n      \"type\": \"rocket\",\n      \"active\": true,\n      \"stages\": 2,\n      \"boosters\": 0,\n      \"cost_per_launch\": 50000000,\n      \"success_rate_pct\": 97,\n      \"first_flight\": \"2010-06-04\",\n      \"country\": \"United States\",\n      \"company\": \"SpaceX\",\n      \"wikipedia\": \"https://en.wikipedia.org/wiki/Falcon_9\",\n      \"description\": \"Falcon 9 is a two-stage rocket designed and manufactured by SpaceX for the reliable and safe transport of satellites and the Dragon spacecraft into orbit.\",\n      \"id\": \"5e9d0d95eda69973a809d1ec\"\n    },\n    ...\n  ],\n  \"totalDocs\": 4,\n  \"offset\": 0,\n  \"limit\": 10,\n  \"totalPages\": 1,\n  \"page\": 1,\n  \"pagingCounter\": 1,\n  \"hasPrevPage\": false,\n  \"hasNextPage\": false,\n  \"prevPage\": null,\n  \"nextPage\": null\n}\n```\n\n## Error Responses\n\n**Code** : `400 Bad Request`\n\n**Content** : Mongoose error is shown, with suggestions to fix the query.\n"
  },
  {
    "path": "docs/rockets/v4/schema.md",
    "content": "# Rocket Schema\n\n```json\n{\n  \"name\": {\n    \"type\": \"String\"\n  },\n  \"type\": {\n    \"type\": \"String\"\n  },\n  \"active\": {\n    \"type\": \"Boolean\"\n  },\n  \"stages\": {\n    \"type\": \"Number\"\n  },\n  \"boosters\": {\n    \"type\": \"Number\"\n  },\n  \"cost_per_launch\": {\n    \"type\": \"Number\"\n  },\n  \"success_rate_pct\": {\n    \"type\": \"Number\"\n  },\n  \"first_flight\": {\n    \"type\": \"String\"\n  },\n  \"country\": {\n    \"type\": \"String\"\n  },\n  \"company\": {\n    \"type\": \"String\"\n  },\n  \"height\": {\n    \"meters\": {\n      \"type\": \"Number\"\n    },\n    \"feet\": {\n      \"type\": \"Number\"\n    }\n  },\n  \"diameter\": {\n    \"meters\": {\n      \"type\": \"Number\"\n    },\n    \"feet\": {\n      \"type\": \"Number\"\n    }\n  },\n  \"mass\": {\n    \"kg\": {\n      \"type\": \"Number\"\n    },\n    \"lb\": {\n      \"type\": \"Number\"\n    }\n  },\n  \"payload_weights\": {\n    \"type\": [\n      \"Object\"\n    ]\n  },\n  \"first_stage\": {\n    \"reusable\": {\n      \"type\": \"Boolean\"\n    },\n    \"engines\": {\n      \"type\": \"Number\"\n    },\n    \"fuel_amount_tons\": {\n      \"type\": \"Number\"\n    },\n    \"burn_time_sec\": {\n      \"type\": \"Number\"\n    },\n    \"thrust_sea_level\": {\n      \"kN\": {\n        \"type\": \"Number\"\n      },\n      \"lbf\": {\n        \"type\": \"Number\"\n      }\n    },\n    \"thrust_vacuum\": {\n      \"kN\": {\n        \"type\": \"Number\"\n      },\n      \"lbf\": {\n        \"type\": \"Number\"\n      }\n    }\n  },\n  \"second_stage\": {\n    \"reusable\": {\n      \"type\": \"Boolean\"\n    },\n    \"engines\": {\n      \"type\": \"Number\"\n    },\n    \"fuel_amount_tons\": {\n      \"type\": \"Number\"\n    },\n    \"burn_time_sec\": {\n      \"type\": \"Number\"\n    },\n    \"thrust\": {\n      \"kN\": {\n        \"type\": \"Number\"\n      },\n      \"lbf\": {\n        \"type\": \"Number\"\n      }\n    },\n    \"payloads\": {\n      \"option_1\": {\n        \"type\": \"String\"\n      },\n      \"composite_fairing\": {\n        \"height\": {\n          \"meters\": {\n            \"type\": \"Number\"\n          },\n          \"feet\": {\n            \"type\": \"Number\"\n          }\n        },\n        \"diameter\": {\n          \"meters\": {\n            \"type\": \"Number\"\n          },\n          \"feet\": {\n            \"type\": \"Number\"\n          }\n        }\n      }\n    }\n  },\n  \"engines\": {\n    \"number\": {\n      \"type\": \"Number\"\n    },\n    \"type\": {\n      \"type\": \"String\"\n    },\n    \"version\": {\n      \"type\": \"String\"\n    },\n    \"layout\": {\n      \"type\": \"String\"\n    },\n    \"isp\": {\n      \"sea_level\": {\n        \"type\": \"Number\"\n      },\n      \"vacuum\": {\n        \"type\": \"Number\"\n      }\n    },\n    \"engine_loss_max\": {\n      \"type\": \"Number\"\n    },\n    \"propellant_1\": {\n      \"type\": \"String\"\n    },\n    \"propellant_2\": {\n      \"type\": \"String\"\n    },\n    \"thrust_sea_level\": {\n      \"kN\": {\n        \"type\": \"Number\"\n      },\n      \"lbf\": {\n        \"type\": \"Number\"\n      }\n    },\n    \"thrust_vacuum\": {\n      \"kN\": {\n        \"type\": \"Number\"\n      },\n      \"lbf\": {\n        \"type\": \"Number\"\n      }\n    },\n    \"thrust_to_weight\": {\n      \"type\": \"Number\"\n    }\n  },\n  \"landing_legs\": {\n    \"number\": {\n      \"type\": \"Number\"\n    },\n    \"material\": {\n      \"type\": \"Object\"\n    }\n  },\n  \"flickr_images\": {\n    \"type\": [\n      \"String\"\n    ]\n  },\n  \"wikipedia\": {\n    \"type\": \"String\"\n  },\n  \"description\": {\n    \"type\": \"String\"\n  }\n}\n```\n"
  },
  {
    "path": "docs/ships/v4/all.md",
    "content": "# Get all ships\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/ships`\n\n**Auth required** : `False`\n\n## Success Responses\n\n**Code** : `200 OK`\n\n```json\n[\n  {\n    \"legacy_id\": \"GOPURSUIT\",\n    \"model\": null,\n    \"type\": \"Cargo\",\n    \"roles\": [\n      \"Support Ship\",\n      \"Fairing Recovery\"\n    ],\n    \"imo\": 9458884,\n    \"mmsi\": 367191410,\n    \"abs\": 1201189,\n    \"class\": 7174230,\n    \"mass_kg\": 502999,\n    \"mass_lbs\": 1108925,\n    \"year_built\": 2007,\n    \"home_port\": \"Port Canaveral\",\n    \"status\": \"\",\n    \"speed_kn\": null,\n    \"course_deg\": null,\n    \"latitude\": null,\n    \"longitude\": null,\n    \"last_ais_update\": null,\n    \"link\": \"https://www.marinetraffic.com/en/ais/details/ships/shipid:439594/mmsi:367191410/imo:9458884/vessel:GO_PURSUIT\",\n    \"image\": \"https://i.imgur.com/5w1ZWre.jpg\",\n    \"launches\": [\n      \"5eb87d18ffd86e000604b365\",\n      \"5eb87d19ffd86e000604b366\",\n      \"5eb87d1bffd86e000604b368\",\n      \"5eb87d1effd86e000604b36a\"\n    ],\n    \"name\": \"GO Pursuit\",\n    \"active\": false,\n    \"id\": \"5ea6ed2e080df4000697c90a\"\n  },\n  ...\n]\n```\n"
  },
  {
    "path": "docs/ships/v4/one.md",
    "content": "# Get one ship\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/ships/:id`\n\n**URL Parameters** : `id=[string]` where `id` is the ID of the ship\n\n**Auth required** : `False`\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n    \"legacy_id\": \"GOPURSUIT\",\n    \"model\": null,\n    \"type\": \"Cargo\",\n    \"roles\": [\n        \"Support Ship\",\n        \"Fairing Recovery\"\n    ],\n    \"imo\": 9458884,\n    \"mmsi\": 367191410,\n    \"abs\": 1201189,\n    \"class\": 7174230,\n    \"mass_kg\": 502999,\n    \"mass_lbs\": 1108925,\n    \"year_built\": 2007,\n    \"home_port\": \"Port Canaveral\",\n    \"status\": \"\",\n    \"speed_kn\": null,\n    \"course_deg\": null,\n    \"latitude\": null,\n    \"longitude\": null,\n    \"last_ais_update\": null,\n    \"link\": \"https://www.marinetraffic.com/en/ais/details/ships/shipid:439594/mmsi:367191410/imo:9458884/vessel:GO_PURSUIT\",\n    \"image\": \"https://i.imgur.com/5w1ZWre.jpg\",\n    \"launches\": [\n        \"5eb87d18ffd86e000604b365\",\n        \"5eb87d19ffd86e000604b366\",\n        \"5eb87d1bffd86e000604b368\",\n        \"5eb87d1effd86e000604b36a\"\n    ],\n    \"name\": \"GO Pursuit\",\n    \"active\": false,\n    \"id\": \"5ea6ed2e080df4000697c90a\"\n}\n```\n\n## Error Responses\n\n**Code** : `404 NOT FOUND`\n\n**Content** : `Not Found`\n"
  },
  {
    "path": "docs/ships/v4/query.md",
    "content": "# Query ships\n\n**Method** : `POST`\n\n**URL** : `https://api.spacexdata.com/v4/ships/query`\n\n**Auth required** : `False`\n\n**Body** :\n\nSee [query](../../queries.md) guide for more details on building queries and paginating results.\n\n```json\n{\n  \"query\": {},\n  \"options\": {}\n}\n```\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n  \"docs\": [\n    {\n      \"legacy_id\": \"GOPURSUIT\",\n      \"model\": null,\n      \"type\": \"Cargo\",\n      \"roles\": [\n        \"Support Ship\",\n        \"Fairing Recovery\"\n      ],\n      \"imo\": 9458884,\n      \"mmsi\": 367191410,\n      \"abs\": 1201189,\n      \"class\": 7174230,\n      \"mass_kg\": 502999,\n      \"mass_lbs\": 1108925,\n      \"year_built\": 2007,\n      \"home_port\": \"Port Canaveral\",\n      \"status\": \"\",\n      \"speed_kn\": null,\n      \"course_deg\": null,\n      \"latitude\": null,\n      \"longitude\": null,\n      \"last_ais_update\": null,\n      \"link\": \"https://www.marinetraffic.com/en/ais/details/ships/shipid:439594/mmsi:367191410/imo:9458884/vessel:GO_PURSUIT\",\n      \"image\": \"https://i.imgur.com/5w1ZWre.jpg\",\n      \"launches\": [\n        \"5eb87d18ffd86e000604b365\",\n        \"5eb87d19ffd86e000604b366\",\n        \"5eb87d1bffd86e000604b368\",\n        \"5eb87d1effd86e000604b36a\"\n      ],\n      \"name\": \"GO Pursuit\",\n      \"active\": false,\n      \"id\": \"5ea6ed2e080df4000697c90a\"\n    },\n    ...\n  ],\n  \"totalDocs\": 22,\n  \"offset\": 0,\n  \"limit\": 10,\n  \"totalPages\": 3,\n  \"page\": 1,\n  \"pagingCounter\": 1,\n  \"hasPrevPage\": false,\n  \"hasNextPage\": true,\n  \"prevPage\": null,\n  \"nextPage\": 2\n}\n```\n\n## Error Responses\n\n**Code** : `400 Bad Request`\n\n**Content** : Mongoose error is shown, with suggestions to fix the query.\n"
  },
  {
    "path": "docs/ships/v4/schema.md",
    "content": "# Ship Schema\n\n```json\n{\n  \"name\": {\n    \"type\": \"String\",\n    \"unique\": true,\n    \"required\": true\n  },\n  \"legacy_id\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"model\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"type\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"roles\": [\n    \"String\"\n  ],\n  \"active\": {\n    \"type\": \"Boolean\",\n    \"required\": true\n  },\n  \"imo\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"mmsi\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"abs\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"class\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"mass_kg\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"mass_lbs\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"year_built\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"home_port\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"status\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"speed_kn\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"course_deg\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"latitude\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"longitude\": {\n    \"type\": \"Number\",\n    \"default\": null\n  },\n  \"last_ais_update\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"link\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"image\": {\n    \"type\": \"String\",\n    \"default\": null\n  },\n  \"launches\": [\n    {\n      \"type\": \"UUID\"\n    }\n  ]\n}\n```\n"
  },
  {
    "path": "docs/starlink/v4/all.md",
    "content": "# Get all Starlink satellites\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/starlink`\n\n**Auth required** : `False`\n\n## Success Responses\n\n**Code** : `200 OK`\n\n```json\n[\n  {\n    \"spaceTrack\": {\n        \"CCSDS_OMM_VERS\": \"2.0\",\n        \"COMMENT\": \"GENERATED VIA SPACE-TRACK.ORG API\",\n        \"CREATION_DATE\": \"2020-06-19 21:46:09\",\n        \"ORIGINATOR\": \"18 SPCS\",\n        \"OBJECT_NAME\": \"STARLINK-1506\",\n        \"OBJECT_ID\": \"2020-038T\",\n        \"CENTER_NAME\": \"EARTH\",\n        \"REF_FRAME\": \"TEME\",\n        \"TIME_SYSTEM\": \"UTC\",\n        \"MEAN_ELEMENT_THEORY\": \"SGP4\",\n        \"EPOCH\": \"2020-06-19 20:00:01.000224\",\n        \"MEAN_MOTION\": 15.88829743,\n        \"ECCENTRICITY\": 0.0087515,\n        \"INCLINATION\": 53.002,\n        \"RA_OF_ASC_NODE\": 266.3302,\n        \"ARG_OF_PERICENTER\": 69.9474,\n        \"MEAN_ANOMALY\": 221.4733,\n        \"EPHEMERIS_TYPE\": 0,\n        \"CLASSIFICATION_TYPE\": \"U\",\n        \"NORAD_CAT_ID\": 45747,\n        \"ELEMENT_SET_NO\": 999,\n        \"REV_AT_EPOCH\": 212,\n        \"BSTAR\": 0.01007,\n        \"MEAN_MOTION_DOT\": 0.03503094,\n        \"MEAN_MOTION_DDOT\": 0.01265,\n        \"SEMIMAJOR_AXIS\": 6683.699,\n        \"PERIOD\": 90.632,\n        \"APOAPSIS\": 364.057,\n        \"PERIAPSIS\": 247.072,\n        \"OBJECT_TYPE\": \"PAYLOAD\",\n        \"RCS_SIZE\": null,\n        \"COUNTRY_CODE\": \"US\",\n        \"LAUNCH_DATE\": \"2020-06-13\",\n        \"SITE\": \"AFETR\",\n        \"DECAY_DATE\": null,\n        \"DECAYED\": 0,\n        \"FILE\": 2768947,\n        \"GP_ID\": 155985688,\n        \"TLE_LINE0\": \"0 STARLINK-1506\",\n        \"TLE_LINE1\": \"1 45747U 20038T   20171.83334491  .03503094  12654-1  10068-1 0  9995\",\n        \"TLE_LINE2\": \"2 45747  53.0017 266.3302 0087515  69.9474 221.4733 15.88829743  2124\"\n      },\n      \"version\": \"v1.0\",\n      \"launch\": \"5eb87d46ffd86e000604b389\",\n      \"longitude\": 165.93047730624068,\n      \"latitude\": -52.91311434465077,\n      \"height_km\": 446.61936740361125,\n      \"velocity_kms\": 7.643507427834188,\n      \"id\": \"5eed7716096e590006985825\"\n  }\n  ...\n]\n```\n"
  },
  {
    "path": "docs/starlink/v4/one.md",
    "content": "# Get one Starlink satellite\n\n**Method** : `GET`\n\n**URL** : `https://api.spacexdata.com/v4/starlink/:id`\n\n**URL Parameters** : `id=[string]` where `id` is the ID of the Starlink sat\n\n**Auth required** : `False`\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n  \"spaceTrack\": {\n      \"CCSDS_OMM_VERS\": \"2.0\",\n      \"COMMENT\": \"GENERATED VIA SPACE-TRACK.ORG API\",\n      \"CREATION_DATE\": \"2020-06-19 21:46:09\",\n      \"ORIGINATOR\": \"18 SPCS\",\n      \"OBJECT_NAME\": \"STARLINK-1506\",\n      \"OBJECT_ID\": \"2020-038T\",\n      \"CENTER_NAME\": \"EARTH\",\n      \"REF_FRAME\": \"TEME\",\n      \"TIME_SYSTEM\": \"UTC\",\n      \"MEAN_ELEMENT_THEORY\": \"SGP4\",\n      \"EPOCH\": \"2020-06-19 20:00:01.000224\",\n      \"MEAN_MOTION\": 15.88829743,\n      \"ECCENTRICITY\": 0.0087515,\n      \"INCLINATION\": 53.002,\n      \"RA_OF_ASC_NODE\": 266.3302,\n      \"ARG_OF_PERICENTER\": 69.9474,\n      \"MEAN_ANOMALY\": 221.4733,\n      \"EPHEMERIS_TYPE\": 0,\n      \"CLASSIFICATION_TYPE\": \"U\",\n      \"NORAD_CAT_ID\": 45747,\n      \"ELEMENT_SET_NO\": 999,\n      \"REV_AT_EPOCH\": 212,\n      \"BSTAR\": 0.01007,\n      \"MEAN_MOTION_DOT\": 0.03503094,\n      \"MEAN_MOTION_DDOT\": 0.01265,\n      \"SEMIMAJOR_AXIS\": 6683.699,\n      \"PERIOD\": 90.632,\n      \"APOAPSIS\": 364.057,\n      \"PERIAPSIS\": 247.072,\n      \"OBJECT_TYPE\": \"PAYLOAD\",\n      \"RCS_SIZE\": null,\n      \"COUNTRY_CODE\": \"US\",\n      \"LAUNCH_DATE\": \"2020-06-13\",\n      \"SITE\": \"AFETR\",\n      \"DECAY_DATE\": null,\n      \"DECAYED\": 0,\n      \"FILE\": 2768947,\n      \"GP_ID\": 155985688,\n      \"TLE_LINE0\": \"0 STARLINK-1506\",\n      \"TLE_LINE1\": \"1 45747U 20038T   20171.83334491  .03503094  12654-1  10068-1 0  9995\",\n      \"TLE_LINE2\": \"2 45747  53.0017 266.3302 0087515  69.9474 221.4733 15.88829743  2124\"\n  },\n  \"version\": \"v1.0\",\n  \"launch\": \"5eb87d46ffd86e000604b389\",\n  \"longitude\": 165.93047730624068,\n  \"latitude\": -52.91311434465077,\n  \"height_km\": 446.61936740361125,\n  \"velocity_kms\": 7.643507427834188,\n  \"id\": \"5eed7716096e590006985825\"\n}\n```\n\n## Error Responses\n\n**Code** : `404 NOT FOUND`\n\n**Content** : `Not Found`\n"
  },
  {
    "path": "docs/starlink/v4/query.md",
    "content": "# Query Starlink satellites\n\n**Method** : `POST`\n\n**URL** : `https://api.spacexdata.com/v4/starlink/query`\n\n**Auth required** : `False`\n\n**Body** :\n\nSee [query](../../queries.md) guide for more details on building queries and paginating results.\n\n```json\n{\n  \"query\": {},\n  \"options\": {}\n}\n```\n\n## Success Response\n\n**Code** : `200 OK`\n\n**Content example** :\n\n```json\n{\n    \"docs\": [\n      {\n        \"spaceTrack\": {\n          \"CCSDS_OMM_VERS\": \"2.0\",\n          \"COMMENT\": \"GENERATED VIA SPACE-TRACK.ORG API\",\n          \"CREATION_DATE\": \"2020-06-19 21:36:08\",\n          \"ORIGINATOR\": \"18 SPCS\",\n          \"OBJECT_NAME\": \"STARLINK-30\",\n          \"OBJECT_ID\": \"2019-029K\",\n          \"CENTER_NAME\": \"EARTH\",\n          \"REF_FRAME\": \"TEME\",\n          \"TIME_SYSTEM\": \"UTC\",\n          \"MEAN_ELEMENT_THEORY\": \"SGP4\",\n          \"EPOCH\": \"2020-06-19 20:00:01.000224\",\n          \"MEAN_MOTION\": 15.43862877,\n          \"ECCENTRICITY\": 0.000125,\n          \"INCLINATION\": 52.996,\n          \"RA_OF_ASC_NODE\": 195.8544,\n          \"ARG_OF_PERICENTER\": 108.6906,\n          \"MEAN_ANOMALY\": 109.3199,\n          \"EPHEMERIS_TYPE\": 0,\n          \"CLASSIFICATION_TYPE\": \"U\",\n          \"NORAD_CAT_ID\": 44244,\n          \"ELEMENT_SET_NO\": 999,\n          \"REV_AT_EPOCH\": 5947,\n          \"BSTAR\": 0.00007,\n          \"MEAN_MOTION_DOT\": 0.00002829,\n          \"MEAN_MOTION_DDOT\": 0,\n          \"SEMIMAJOR_AXIS\": 6812.858,\n          \"PERIOD\": 93.272,\n          \"APOAPSIS\": 435.574,\n          \"PERIAPSIS\": 433.871,\n          \"OBJECT_TYPE\": \"PAYLOAD\",\n          \"RCS_SIZE\": \"LARGE\",\n          \"COUNTRY_CODE\": \"US\",\n          \"LAUNCH_DATE\": \"2019-05-24\",\n          \"SITE\": \"AFETR\",\n          \"DECAY_DATE\": null,\n          \"DECAYED\": 0,\n          \"FILE\": 2768931,\n          \"GP_ID\": 155985469,\n          \"TLE_LINE0\": \"0 STARLINK-30\",\n          \"TLE_LINE1\": \"1 44244U 19029K   20171.83334491  .00002829  00000-0  70479-4 0  9997\",\n          \"TLE_LINE2\": \"2 44244  52.9964 195.8544 0001250 108.6906 109.3199 15.43862877 59477\"\n        },\n        \"version\": \"v0.9\",\n        \"launch\": \"5eb87d30ffd86e000604b378\",\n        \"longitude\": 10.551678198548517,\n        \"latitude\": 8.26018124742001,\n        \"height_km\": 434.5577668080887,\n        \"velocity_kms\": 7.653046786650296,\n        \"id\": \"5eed770f096e59000698560d\"\n      },\n      ...\n    ],\n    \"totalDocs\": 537,\n    \"offset\": 0,\n    \"limit\": 10,\n    \"totalPages\": 54,\n    \"page\": 1,\n    \"pagingCounter\": 1,\n    \"hasPrevPage\": false,\n    \"hasNextPage\": true,\n    \"prevPage\": null,\n    \"nextPage\": 2\n}\n```\n\n## Error Responses\n\n**Code** : `400 Bad Request`\n\n**Content** : Mongoose error is shown, with suggestions to fix the query.\n"
  },
  {
    "path": "docs/starlink/v4/schema.md",
    "content": "# Starlink Schema\n\n```json\n{\n    \"version\": {\n        \"type\": \"String\",\n        \"default\": null\n    },\n    \"launch\": {\n        \"type\": \"UUID\",\n        \"ref\": \"Launch\",\n        \"default\": null\n    },\n    \"longitude\": {\n        \"type\": \"Number\",\n        \"default\": null,\n    },\n    \"latitude\": {\n        \"type\": \"Number\",\n        \"default\": null,\n    },\n    \"height_km\": {\n        \"type\": \"Number\",\n        \"default\": null,\n    },\n    \"velocity_kms\": {\n        \"type\": \"Number\",\n        \"default\": null,\n    },\n    \"spaceTrack\": {\n        \"CCSDS_OMM_VERS\": {\n            \"type\": \"String\",\n            \"default\": null\n        },\n        \"COMMENT\": {\n            \"type\": \"String\",\n            \"default\": null\n        },\n        \"CREATION_DATE\": {\n            \"type\": \"String\",\n            \"default\": null\n        },\n        \"ORIGINATOR\": {\n            \"type\": \"String\",\n            \"default\": null\n        },\n        \"OBJECT_NAME\": {\n            \"type\": \"String\",\n            \"default\": null\n        },\n        \"OBJECT_ID\": {\n            \"type\": \"String\",\n            \"default\": null\n        },\n        \"CENTER_NAME\": {\n            \"type\": \"String\",\n            \"default\": null\n        },\n        \"REF_FRAME\": {\n            \"type\": \"String\",\n            \"default\": null\n        },\n        \"TIME_SYSTEM\": {\n            \"type\": \"String\",\n            \"default\": null\n        },\n        \"MEAN_ELEMENT_THEORY\": {\n            \"type\": \"String\",\n            \"default\": null\n        },\n        \"EPOCH\": {\n            \"type\": \"String\",\n            \"default\": null\n        },\n        \"MEAN_MOTION\": {\n            \"type\": \"Number\",\n            \"default\": null\n        },\n        \"ECCENTRICITY\": {\n            \"type\": \"Number\",\n            \"default\": null\n        },\n        \"INCLINATION\": {\n            \"type\": \"Number\",\n            \"default\": null\n        },\n        \"RA_OF_ASC_NODE\": {\n            \"type\": \"Number\",\n            \"default\": null\n        },\n        \"ARG_OF_PERICENTER\": {\n            \"type\": \"Number\",\n            \"default\": null\n        },\n        \"MEAN_ANOMALY\": {\n            \"type\": \"Number\",\n            \"default\": null\n        },\n        \"EPHEMERIS_TYPE\": {\n            \"type\": \"Number\",\n            \"default\": null\n        },\n        \"CLASSIFICATION_TYPE\": {\n            \"type\": \"String\",\n            \"default\": null\n        },\n        \"NORAD_CAT_ID\": {\n            \"type\": \"Number\",\n            \"default\": null\n        },\n        \"ELEMENT_SET_NO\": {\n            \"type\": \"Number\",\n            \"default\": null\n        },\n        \"REV_AT_EPOCH\": {\n            \"type\": \"Number\",\n            \"default\": null\n        },\n        \"BSTAR\": {\n            \"type\": \"Number\",\n            \"default\": null\n        },\n        \"MEAN_MOTION_DOT\": {\n            \"type\": \"Number\",\n            \"default\": null\n        },\n        \"MEAN_MOTION_DDOT\": {\n            \"type\": \"Number\",\n            \"default\": null\n        },\n        \"SEMIMAJOR_AXIS\": {\n            \"type\": \"Number\",\n            \"default\": null\n        },\n        \"PERIOD\": {\n            \"type\": \"Number\",\n            \"default\": null\n        },\n        \"APOAPSIS\": {\n            \"type\": \"Number\",\n            \"default\": null\n        },\n        \"PERIAPSIS\": {\n            \"type\": \"Number\",\n            \"default\": null\n        },\n        \"OBJECT_TYPE\": {\n            \"type\": \"String\",\n            \"default\": null\n        },\n        \"RCS_SIZE\": {\n            \"type\": \"String\",\n            \"default\": null\n        },\n        \"COUNTRY_CODE\": {\n            \"type\": \"String\",\n            \"default\": null\n        },\n        \"LAUNCH_DATE\": {\n            \"type\": \"String\",\n            \"default\": null\n        },\n        \"SITE\": {\n            \"type\": \"String\",\n            \"default\": null\n        },\n        \"DECAY_DATE\": {\n            \"type\": \"String\",\n            \"default\": null\n        },\n        \"DECAYED\": {\n            \"type\": \"Number\",\n            \"default\": null\n        },\n        \"FILE\": {\n            \"type\": \"Number\",\n            \"default\": null\n        },\n        \"GP_ID\": {\n            \"type\": \"Number\",\n            \"default\": null\n        },\n        \"TLE_LINE0\": {\n            \"type\": \"String\",\n            \"default\": null\n        },\n        \"TLE_LINE1\": {\n            \"type\": \"String\",\n            \"default\": null\n        },\n        \"TLE_LINE2\": {\n            \"type\": \"String\",\n            \"default\": null\n        }\n    }\n}\n```\n"
  },
  {
    "path": "jobs/capsules.js",
    "content": "import got from 'got';\nimport { load } from 'cheerio';\nimport { logger } from '../middleware/index.js';\n\nconst API = process.env.SPACEX_API;\nconst KEY = process.env.SPACEX_KEY;\nconst HEALTHCHECK = process.env.CAPSULES_HEALTHCHECK;\nconst REDDIT_CAPSULES = 'https://old.reddit.com/r/spacex/wiki/capsules';\n\n/**\n * Update capsule landings/reuse count\n * @return {Promise<void>}\n */\nexport default async () => {\n  try {\n    const capsules = await got.post(`${API}/capsules/query`, {\n      json: {\n        options: {\n          pagination: false,\n        },\n      },\n      resolveBodyOnly: true,\n      responseType: 'json',\n    });\n\n    const result = await got(REDDIT_CAPSULES);\n    const $ = load(result.body);\n\n    const v1Capsules = $('div.md:nth-child(2) > table:nth-child(8) > tbody:nth-child(2)').text();\n    const v1CapsuleRow = v1Capsules.split('\\n').filter((v) => v !== '');\n    const v1CapsuleIds = v1CapsuleRow.filter((value, index) => index % 7 === 0);\n    if (!v1CapsuleIds.length) {\n      throw new Error('No v1 capsules found');\n    }\n    const v1CapsuleStatus = v1CapsuleRow.filter((value, index) => (index + 1) % 7 === 0).map((x) => x.replace(/\\[source\\]/gi, ''));\n\n    const v2Capsules = $('div.md:nth-child(2) > table:nth-child(10) > tbody:nth-child(2)').text();\n    const v2CapsuleRow = v2Capsules.split('\\n').filter((v) => v !== '');\n    const v2CapsuleIds = v2CapsuleRow.filter((value, index) => index % 8 === 0).map((x) => x.split(',')[0]);\n    if (!v2CapsuleIds.length) {\n      throw new Error('No v2 capsules found');\n    }\n    const v2CapsuleStatus = v2CapsuleRow.filter((value, index) => (index + 1) % 8 === 0).map((x) => x.replace(/\\[source\\]/gi, ''));\n\n    const capsuleIds = [...v1CapsuleIds, ...v2CapsuleIds];\n    const capsuleStatus = [...v1CapsuleStatus, ...v2CapsuleStatus];\n\n    const updates = capsules.docs.map(async (capsule) => {\n      const waterLandings = await got.post(`${API}/payloads/query`, {\n        json: {\n          query: {\n            'dragon.capsule': capsule.id,\n            'dragon.water_landing': true,\n          },\n          options: {\n            pagination: false,\n          },\n        },\n        resolveBodyOnly: true,\n        responseType: 'json',\n      });\n\n      const landLandings = await got.post(`${API}/payloads/query`, {\n        json: {\n          query: {\n            'dragon.capsule': capsule.id,\n            'dragon.land_landing': true,\n          },\n          options: {\n            pagination: false,\n          },\n        },\n        resolveBodyOnly: true,\n        responseType: 'json',\n      });\n\n      const index = capsuleIds.findIndex((id) => id === capsule.serial);\n      await got.patch(`${API}/capsules/${capsule.id}`, {\n        json: {\n          reuse_count: (capsule.launches.length > 0) ? capsule.launches.length - 1 : 0,\n          water_landings: waterLandings.totalDocs,\n          land_landings: landLandings.totalDocs,\n          last_update: capsuleStatus[parseInt(index, 10)],\n        },\n        headers: {\n          'spacex-key': KEY,\n        },\n      });\n    });\n\n    await Promise.all(updates);\n\n    logger.info('Capsules updated');\n\n    if (HEALTHCHECK) {\n      await got(HEALTHCHECK);\n    }\n  } catch (error) {\n    console.log(`Capsules Error: ${error.message}`);\n  }\n};\n"
  },
  {
    "path": "jobs/cores.js",
    "content": "import got from 'got';\nimport { load } from 'cheerio';\nimport { logger } from '../middleware/index.js';\n\nconst REDDIT_CORES = 'https://old.reddit.com/r/spacex/wiki/cores';\nconst API = process.env.SPACEX_API;\nconst KEY = process.env.SPACEX_KEY;\nconst HEALTHCHECK = process.env.CORES_HEALTHCHECK;\n\n/**\n * Update cores\n * @return {Promise<void>}\n */\nexport default async () => {\n  try {\n    const cores = await got.post(`${API}/cores/query`, {\n      json: {\n        options: {\n          pagination: false,\n        },\n      },\n      resolveBodyOnly: true,\n      responseType: 'json',\n    });\n\n    const result = await got(REDDIT_CORES);\n    const $ = load(result.body);\n\n    // Active Cores Table\n    const scrapedActive = [];\n    $('div.md:nth-child(2) > table:nth-child(13) > tbody:nth-child(2) > tr').each((index, element) => {\n      if (index === 0) return true;\n      const tds = $(element).find('td');\n      const coreSerial = $(tds[0]).text() || null;\n      const coreStatus = $(tds[5]).text().replace(/\\[source\\]/gi, '').trim() || null;\n      if (!coreSerial && !coreStatus) return true;\n      const tableRow = {\n        coreSerial,\n        coreStatus,\n      };\n      return scrapedActive.push(tableRow);\n    });\n    if (!scrapedActive.length) {\n      throw new Error('No active cores found');\n    }\n    const activeUpdates = scrapedActive.map(async (row) => {\n      const coreId = cores.docs.find((core) => core.serial === row.coreSerial);\n      if (coreId?.id) {\n        await got.patch(`${API}/cores/${coreId.id}`, {\n          json: {\n            last_update: row.coreStatus,\n            status: 'active',\n          },\n          headers: {\n            'spacex-key': KEY,\n          },\n        });\n      }\n    });\n    await Promise.all(activeUpdates);\n    logger.info('Active cores updated');\n    const inactive = $('div.md:nth-child(2) > table:nth-child(16) > tbody:nth-child(2)').text();\n    const inactiveRow = inactive.split('\\n').filter((v) => v !== '');\n    const inactiveCores = inactiveRow.filter((value, index) => index % 6 === 0);\n    if (!inactiveCores.length) {\n      throw new Error('No inactive cores found');\n    }\n    const inactiveStatus = inactiveRow.filter((value, index) => (index + 1) % 6 === 0).map((x) => x.replace(/\\[source\\]/gi, ''));\n    const inactiveUpdates = inactiveCores.map(async (coreSerial, index) => {\n      const coreId = cores.docs.find((core) => core.serial === coreSerial);\n      if (coreId?.id) {\n        await got.patch(`${API}/cores/${coreId.id}`, {\n          json: {\n            last_update: inactiveStatus[parseInt(index, 10)],\n            status: 'inactive',\n          },\n          headers: {\n            'spacex-key': KEY,\n          },\n        });\n      }\n    });\n    await Promise.all(inactiveUpdates);\n    logger.info('Inactive cores updated');\n\n    const lost = $('div.md:nth-child(2) > table:nth-child(20) > tbody:nth-child(2)').text();\n    const lostRow = lost.split('\\n').filter((v) => v !== '');\n    const lostCores = lostRow.filter((value, index) => index % 7 === 0);\n    if (!lostCores.length) {\n      throw new Error('No lost cores found');\n    }\n    const lostStatus = lostRow.filter((value, index) => (index + 1) % 7 === 0).map((x) => x.replace(/\\[source\\]/gi, ''));\n    const lostUpdates = lostCores.map(async (coreSerial, index) => {\n      const coreId = cores.docs.find((core) => core.serial === coreSerial);\n      if (coreId?.id) {\n        let status;\n        if (lostStatus[parseInt(index, 10)].match(/expended/i)) {\n          status = 'expended';\n        } else {\n          status = 'lost';\n        }\n        await got.patch(`${API}/cores/${coreId.id}`, {\n          json: {\n            last_update: lostStatus[parseInt(index, 10)],\n            status,\n          },\n          headers: {\n            'spacex-key': KEY,\n          },\n        });\n      }\n    });\n    await Promise.all(lostUpdates);\n    logger.info('Lost cores updated');\n\n    const reuseUpdates = cores.docs.map(async (core) => {\n      if (!core?.id) return;\n      const [rtlsAttempts, rtlsLandings, asdsAttempts, asdsLandings] = await Promise.all([\n        got.post(`${API}/launches/query`, {\n          json: {\n            query: {\n              upcoming: false,\n              cores: {\n                $elemMatch: {\n                  core: core.id,\n                  landing_type: 'RTLS',\n                  landing_attempt: true,\n                },\n              },\n            },\n            options: {\n              pagination: false,\n            },\n          },\n          resolveBodyOnly: true,\n          responseType: 'json',\n          throwHttpErrors: false,\n        }),\n        got.post(`${API}/launches/query`, {\n          json: {\n            query: {\n              upcoming: false,\n              cores: {\n                $elemMatch: {\n                  core: core.id,\n                  landing_type: 'RTLS',\n                  landing_attempt: true,\n                  landing_success: true,\n                },\n              },\n            },\n            options: {\n              pagination: false,\n            },\n          },\n          resolveBodyOnly: true,\n          responseType: 'json',\n          throwHttpErrors: false,\n        }),\n        got.post(`${API}/launches/query`, {\n          json: {\n            query: {\n              upcoming: false,\n              cores: {\n                $elemMatch: {\n                  core: core.id,\n                  landing_type: 'ASDS',\n                  landing_attempt: true,\n                  landing_success: true,\n                },\n              },\n            },\n            options: {\n              pagination: false,\n            },\n          },\n          resolveBodyOnly: true,\n          responseType: 'json',\n          throwHttpErrors: false,\n        }),\n        got.post(`${API}/launches/query`, {\n          json: {\n            query: {\n              upcoming: false,\n              cores: {\n                $elemMatch: {\n                  core: core.id,\n                  landing_type: 'ASDS',\n                  landing_attempt: true,\n                  landing_success: true,\n                },\n              },\n            },\n            options: {\n              pagination: false,\n            },\n          },\n          resolveBodyOnly: true,\n          responseType: 'json',\n          throwHttpErrors: false,\n        }),\n      ]);\n      await got.patch(`${API}/cores/${core.id}`, {\n        json: {\n          reuse_count: (core.launches.length > 0) ? core.launches.length - 1 : 0,\n          rtls_attempts: rtlsAttempts.totalDocs,\n          rtls_landings: rtlsLandings.totalDocs,\n          asds_attempts: asdsAttempts.totalDocs,\n          asds_landings: asdsLandings.totalDocs,\n        },\n        headers: {\n          'spacex-key': KEY,\n        },\n      });\n    });\n\n    await Promise.all(reuseUpdates);\n    logger.info('Core reuse updated');\n\n    if (HEALTHCHECK) {\n      await got(HEALTHCHECK);\n    }\n  } catch (error) {\n    console.log(`Cores Error: ${error.message}`);\n  }\n};\n"
  },
  {
    "path": "jobs/landpads.js",
    "content": "import got from 'got';\nimport { logger } from '../middleware/index.js';\n\nconst API = process.env.SPACEX_API;\nconst KEY = process.env.SPACEX_KEY;\nconst HEALTHCHECK = process.env.LANDPADS_HEALTHCHECK;\n\n/**\n * Update landpad attempts/successes\n * @return {Promise<void>}\n */\nexport default async () => {\n  try {\n    const landpads = await got.post(`${API}/landpads/query`, {\n      json: {\n        options: {\n          pagination: false,\n        },\n      },\n      resolveBodyOnly: true,\n      responseType: 'json',\n    });\n\n    const updates = landpads.docs.map(async (landpad) => {\n      const [attempts, successes] = await Promise.all([\n        got.post(`${API}/launches/query`, {\n          json: {\n            query: {\n              cores: {\n                $elemMatch: {\n                  landpad: landpad.id,\n                  landing_attempt: true,\n                },\n              },\n              upcoming: false,\n              success: true,\n            },\n            options: {\n              pagination: false,\n            },\n          },\n          resolveBodyOnly: true,\n          responseType: 'json',\n        }),\n        got.post(`${API}/launches/query`, {\n          json: {\n            query: {\n              cores: {\n                $elemMatch: {\n                  landpad: landpad.id,\n                  landing_attempt: true,\n                  landing_success: true,\n                },\n              },\n              upcoming: false,\n              success: true,\n            },\n            options: {\n              pagination: false,\n            },\n          },\n          resolveBodyOnly: true,\n          responseType: 'json',\n        }),\n      ]);\n\n      await got.patch(`${API}/landpads/${landpad.id}`, {\n        json: {\n          landing_attempts: attempts.totalDocs,\n          landing_successes: successes.totalDocs,\n        },\n        headers: {\n          'spacex-key': KEY,\n        },\n      });\n    });\n\n    await Promise.all(updates);\n\n    logger.info('Landpads updated');\n\n    if (HEALTHCHECK) {\n      await got(HEALTHCHECK);\n    }\n  } catch (error) {\n    console.log(`Landpads Error: ${error.message}`);\n  }\n};\n"
  },
  {
    "path": "jobs/launch-library.js",
    "content": "import got from 'got';\nimport moment from 'moment-timezone';\nimport { fail, success } from '../lib/healthchecks/index.js';\nimport { logger } from '../middleware/index.js';\n\nconst {\n  SPACEX_KEY,\n  LAUNCH_LIBRARY_HEALTHCHECK,\n  SPACEX_API: API,\n} = process.env;\nconst LAUNCH_LIBRARY_API = 'https://ll.thespacedevs.com/2.2.0/launch/upcoming';\n\n/**\n * Attach Launch Library v2 launch id's to upcoming launches\n * @return {Promise<void>}\n */\nexport default async () => {\n  try {\n    const log = {\n      name: 'launch-library',\n      updated: false,\n    };\n    const upcomingLaunches = await got.post(`${API}/launches/query`, {\n      json: {\n        query: {\n          upcoming: true,\n        },\n        options: {\n          sort: {\n            flight_number: 'asc',\n          },\n          limit: 1,\n        },\n      },\n      resolveBodyOnly: true,\n      responseType: 'json',\n      throwHttpErrors: false,\n    });\n    if (upcomingLaunches.docs.length === 1) {\n      const llLaunches = await got(LAUNCH_LIBRARY_API, {\n        searchParams: {\n          lsp__name: 'SpaceX',\n          ordering: 'net',\n          format: 'json',\n        },\n        responseType: 'json',\n        throwHttpErrors: false,\n      });\n      if (llLaunches.statusCode === 200 && llLaunches.body.results.length) {\n        const upcomingLaunch = upcomingLaunches.docs[0];\n        const dates = llLaunches.body.results.map((result) => ({\n          llDate: result.net,\n          llId: result.id,\n        }));\n        const diffs = dates.map((date) => ({\n          diff: moment(upcomingLaunch.date_utc).diff(moment(date.llDate)),\n          llId: date.llId,\n        }));\n        // Sort the date diffs by closeness to zero\n        const close = diffs.reduce((a, b) => (Math.abs(b.diff - 0) < Math.abs(a.diff - 0) ? b : a));\n        await got.patch(`${API}/launches/${upcomingLaunch.id}`, {\n          json: {\n            launch_library_id: close.llId,\n          },\n          headers: {\n            'spacex-key': SPACEX_KEY,\n          },\n        });\n        log.spacexdataName = upcomingLaunch.name;\n        log.launch_library_id = close.llId;\n        log.updated = true;\n      }\n    }\n    await success(LAUNCH_LIBRARY_HEALTHCHECK, log);\n    logger.info(log);\n  } catch (error) {\n    const formatted = {\n      name: 'launch-library',\n      error: error.message,\n      stack: error.stack,\n    };\n    await fail(LAUNCH_LIBRARY_HEALTHCHECK, formatted);\n    logger.error(formatted);\n  }\n};\n"
  },
  {
    "path": "jobs/launches.js",
    "content": "import _ from 'lodash';\nimport got from 'got';\nimport { logger } from '../middleware/index.js';\n\nconst API = process.env.SPACEX_API;\nconst KEY = process.env.SPACEX_KEY;\nconst HEALTHCHECK = process.env.LAUNCHES_HEALTHCHECK;\n\n/**\n * Update launch arrays\n * @return {Promise<void>}\n */\nexport default async () => {\n  try {\n    const launches = await got.post(`${API}/launches/query`, {\n      json: {\n        query: {\n          upcoming: false,\n        },\n        options: {\n          sort: {\n            flight_number: 'asc',\n          },\n          pagination: false,\n        },\n      },\n      resolveBodyOnly: true,\n      responseType: 'json',\n    });\n\n    const results = {\n      capsule: false,\n      core: false,\n      crew: false,\n      landpad: false,\n      launchpad: false,\n      payload: false,\n      ship: false,\n      rockets: false,\n    };\n\n    // Update capsule launches\n    const capsules = await got.post(`${API}/capsules/query`, {\n      json: {\n        options: {\n          pagination: false,\n        },\n      },\n      resolveBodyOnly: true,\n      responseType: 'json',\n    });\n\n    const capsuleLaunches = capsules.docs.map(async (capsule) => {\n      const launchIds = launches.docs\n        .filter((launch) => launch.capsules.includes(capsule.id))\n        .map(({ id }) => id);\n\n      await got.patch(`${API}/capsules/${capsule.id}`, {\n        json: {\n          launches: launchIds,\n        },\n        headers: {\n          'spacex-key': KEY,\n        },\n      });\n      results.capsule = true;\n    });\n    await Promise.all(capsuleLaunches);\n\n    // Update core launches\n    const cores = await got.post(`${API}/cores/query`, {\n      json: {\n        options: {\n          pagination: false,\n        },\n      },\n      resolveBodyOnly: true,\n      responseType: 'json',\n    });\n\n    const coreLaunches = cores.docs.map(async (core) => {\n      const launchIds = launches.docs\n        .filter((launch) => launch.cores.find((c) => c.core === core.id))\n        .map(({ id }) => id);\n\n      await got.patch(`${API}/cores/${core.id}`, {\n        json: {\n          launches: launchIds,\n        },\n        headers: {\n          'spacex-key': KEY,\n        },\n      });\n      results.core = true;\n    });\n    await Promise.all(coreLaunches);\n\n    // Update crew launches\n    const crewMembers = await got.post(`${API}/crew/query`, {\n      json: {\n        options: {\n          pagination: false,\n        },\n      },\n      resolveBodyOnly: true,\n      responseType: 'json',\n    });\n\n    const crewLaunches = crewMembers.docs.map(async (crew) => {\n      const launchIds = launches.docs\n        .filter((launch) => launch.crew.includes(crew.id))\n        .map(({ id }) => id);\n\n      await got.patch(`${API}/crew/${crew.id}`, {\n        json: {\n          launches: launchIds,\n        },\n        headers: {\n          'spacex-key': KEY,\n        },\n      });\n      results.crew = true;\n    });\n    await Promise.all(crewLaunches);\n\n    // Update landpad launches\n    const landpads = await got.post(`${API}/landpads/query`, {\n      json: {\n        options: {\n          pagination: false,\n        },\n      },\n      resolveBodyOnly: true,\n      responseType: 'json',\n    });\n\n    const landpadLaunches = landpads.docs.map(async (landpad) => {\n      const launchIds = launches.docs\n        .filter((launch) => launch.cores.find((c) => c.landpad === landpad.id))\n        .map(({ id }) => id);\n\n      await got.patch(`${API}/landpads/${landpad.id}`, {\n        json: {\n          launches: launchIds,\n        },\n        headers: {\n          'spacex-key': KEY,\n        },\n      });\n      results.landpad = true;\n    });\n    await Promise.all(landpadLaunches);\n\n    // Update launchpad launches\n    const launchpads = await got.post(`${API}/launchpads/query`, {\n      json: {\n        options: {\n          pagination: false,\n        },\n      },\n      resolveBodyOnly: true,\n      responseType: 'json',\n    });\n\n    const launchpadLaunches = launchpads.docs.map(async (launchpad) => {\n      const launchIds = launches.docs\n        .filter((launch) => launch.launchpad === launchpad.id)\n        .map(({ id }) => id);\n\n      await got.patch(`${API}/launchpads/${launchpad.id}`, {\n        json: {\n          launches: launchIds,\n        },\n        headers: {\n          'spacex-key': KEY,\n        },\n      });\n      results.launchpad = true;\n    });\n    await Promise.all(launchpadLaunches);\n\n    // Update payload launches\n    const payloads = await got.post(`${API}/payloads/query`, {\n      json: {\n        options: {\n          pagination: false,\n        },\n      },\n      resolveBodyOnly: true,\n      responseType: 'json',\n    });\n\n    const payloadLaunches = payloads.docs.map(async (payload) => {\n      const launchId = _.find(launches.docs, (launch) => launch.payloads.includes(payload.id));\n      if (launchId?.id) {\n        await got.patch(`${API}/payloads/${payload.id}`, {\n          json: {\n            launch: launchId.id,\n          },\n          headers: {\n            'spacex-key': KEY,\n          },\n        });\n        results.payload = true;\n      }\n    });\n    await Promise.all(payloadLaunches);\n\n    // Update ship launches\n    const ships = await got.post(`${API}/ships/query`, {\n      json: {\n        options: {\n          pagination: false,\n        },\n      },\n      resolveBodyOnly: true,\n      responseType: 'json',\n    });\n\n    const shipLaunches = ships.docs.map(async (ship) => {\n      const launchIds = launches.docs\n        .filter((launch) => launch.ships.includes(ship.id))\n        .map(({ id }) => id);\n\n      await got.patch(`${API}/ships/${ship.id}`, {\n        json: {\n          launches: launchIds,\n        },\n        headers: {\n          'spacex-key': KEY,\n        },\n      });\n      results.ship = true;\n    });\n    await Promise.all(shipLaunches);\n\n    // Update rocket success percentage\n    const rockets = await got.post(`${API}/rockets/query`, {\n      json: {\n        options: {\n          pagination: false,\n        },\n      },\n      resolveBodyOnly: true,\n      responseType: 'json',\n    });\n\n    const rocketSuccess = rockets.docs.map(async (rocket) => {\n      const successes = launches?.docs\n        .filter((l) => (l.rocket === rocket.id) && (l.success === true))?.length ?? 0;\n      const attempts = launches?.docs\n        .filter((l) => l.rocket === rocket.id)?.length ?? 0;\n      if (attempts > 0) {\n        const successRate = parseInt(Math.round((successes / attempts) * 100), 10);\n        await got.patch(`${API}/rockets/${rocket.id}`, {\n          json: {\n            success_rate_pct: successRate,\n          },\n          headers: {\n            'spacex-key': KEY,\n          },\n        });\n      }\n      results.rockets = true;\n    });\n    await Promise.all(rocketSuccess);\n\n    logger.info(results);\n\n    if (HEALTHCHECK) {\n      await got(HEALTHCHECK);\n    }\n  } catch (error) {\n    console.log(`Launches Error: ${error.message}`);\n  }\n};\n"
  },
  {
    "path": "jobs/launchpads.js",
    "content": "import got from 'got';\nimport { logger } from '../middleware/index.js';\n\nconst API = process.env.SPACEX_API;\nconst KEY = process.env.SPACEX_KEY;\nconst HEALTHCHECK = process.env.LAUNCHPADS_HEALTHCHECK;\n\n/**\n * Update launchpad attempts/successes\n * @return {Promise<void>}\n */\nexport default async () => {\n  try {\n    const launchpads = await got.post(`${API}/launchpads/query`, {\n      json: {\n        options: {\n          pagination: false,\n        },\n      },\n      resolveBodyOnly: true,\n      responseType: 'json',\n    });\n\n    const updates = launchpads.docs.map(async (launchpad) => {\n      const [attempts, successes] = await Promise.all([\n        got.post(`${API}/launches/query`, {\n          json: {\n            query: {\n              launchpad: launchpad.id,\n              upcoming: false,\n            },\n            options: {\n              pagination: false,\n            },\n          },\n          resolveBodyOnly: true,\n          responseType: 'json',\n        }),\n        got.post(`${API}/launches/query`, {\n          json: {\n            query: {\n              launchpad: launchpad.id,\n              upcoming: false,\n              success: true,\n            },\n            options: {\n              pagination: false,\n            },\n          },\n          resolveBodyOnly: true,\n          responseType: 'json',\n        }),\n      ]);\n\n      await got.patch(`${API}/launchpads/${launchpad.id}`, {\n        json: {\n          launch_attempts: attempts.totalDocs,\n          launch_successes: successes.totalDocs,\n        },\n        headers: {\n          'spacex-key': KEY,\n        },\n      });\n    });\n\n    await Promise.all(updates);\n\n    logger.info('Launchpads updated');\n\n    if (HEALTHCHECK) {\n      await got(HEALTHCHECK);\n    }\n  } catch (error) {\n    console.log(`Launchpads Error: ${error.message}`);\n  }\n};\n"
  },
  {
    "path": "jobs/payloads.js",
    "content": "import got from 'got';\nimport { CookieJar } from 'tough-cookie';\nimport { logger } from '../middleware/index.js';\n\nconst API = process.env.SPACEX_API;\nconst KEY = process.env.SPACEX_KEY;\nconst HEALTHCHECK = process.env.PAYLOADS_HEALTHCHECK;\n\n/**\n * Update payload orbit params\n * @return {Promise<void>}\n */\nexport default async () => {\n  try {\n    const cookieJar = new CookieJar();\n    const [payloads] = await Promise.all([\n      got.post(`${API}/payloads/query`, {\n        json: {\n          query: {},\n          options: {\n            pagination: false,\n          },\n        },\n        resolveBodyOnly: true,\n        responseType: 'json',\n      }),\n      got.post('https://www.space-track.org/ajaxauth/login', {\n        form: {\n          identity: process.env.SPACEX_TRACK_LOGIN,\n          password: process.env.SPACEX_TRACK_PASSWORD,\n        },\n        cookieJar,\n      }),\n    ]);\n\n    const data = await got('https://www.space-track.org/basicspacedata/query/class/tle_latest/ORDINAL/1/orderby/NORAD_CAT_ID/epoch/>now-45/format/json', {\n      resolveBodyOnly: true,\n      responseType: 'json',\n      cookieJar,\n    });\n\n    const updates = payloads.docs.map(async (payload) => {\n      const noradId = payload.norad_ids.shift() ?? null;\n      const specificOrbit = data.find((sat) => parseInt(sat.NORAD_CAT_ID, 10) === noradId);\n      if (specificOrbit) {\n        await got.patch(`${API}/payloads/${payload.id}`, {\n          json: {\n            epoch: new Date(Date.parse(specificOrbit.EPOCH)).toISOString(),\n            mean_motion: parseFloat(specificOrbit.MEAN_MOTION),\n            raan: parseFloat(specificOrbit.RA_OF_ASC_NODE),\n            arg_of_pericenter: parseFloat(specificOrbit.ARG_OF_PERICENTER),\n            mean_anomaly: parseFloat(specificOrbit.MEAN_ANOMALY),\n            semi_major_axis_km: parseFloat(specificOrbit.SEMIMAJOR_AXIS),\n            eccentricity: parseFloat(specificOrbit.ECCENTRICITY),\n            periapsis_km: parseFloat(specificOrbit.PERIGEE),\n            apoapsis_km: parseFloat(specificOrbit.APOGEE),\n            inclination_deg: parseFloat(specificOrbit.INCLINATION),\n            period_min: parseFloat(specificOrbit.PERIOD),\n          },\n          headers: {\n            'spacex-key': KEY,\n          },\n        });\n      }\n    });\n\n    await Promise.all(updates);\n\n    logger.info({\n      orbitsUpdated: true,\n    });\n\n    if (HEALTHCHECK) {\n      await got(HEALTHCHECK);\n    }\n  } catch (error) {\n    console.log(`Payloads Error: ${error.message}`);\n  }\n};\n"
  },
  {
    "path": "jobs/roadster.js",
    "content": "import got from 'got';\nimport moment from 'moment-timezone';\nimport { logger } from '../middleware/index.js';\n\nconst API = process.env.SPACEX_API;\nconst KEY = process.env.SPACEX_KEY;\nconst HEALTHCHECK = process.env.ROADSTER_HEALTHCHECK;\n\n/**\n * This script gathers tesla roadster orbital data from JPL Horizons,\n * parses the output with various regular expressions, and updates\n * the data accordingly.\n * See https://ssd-api.jpl.nasa.gov/doc/horizons.html for more information\n * @return {Promise<void>}\n */\nexport default async () => {\n  // Using date range so Horizons doesn't give us the default 10 day data\n  const today = moment().format('YYYY-MMM-DD HH:mm:ss');\n  const tomorrow = moment().add(1, 'day').format('YYYY-MMM-DD HH:mm:ss');\n\n  const ORBIT_URL = `https://ssd.jpl.nasa.gov/api/horizons.api?format=text&COMMAND='-143205'&CENTER='500@10'&EPHEM_TYPE='ELEMENTS'&START_TIME='${today}'&STOP_TIME='${tomorrow}'&STEP_SIZE='1 d'&OUT_UNITS='AU-D'&REF_PLANE='ECLIPTIC'&REF_SYSTEM='J2000'&TP_TYPE='ABSOLUTE'&ELEM_LABELS='YES'&CSV_FORMAT='NO'`;\n  const EARTH_DIST_URL = `https://ssd.jpl.nasa.gov/api/horizons.api?format=text&COMMAND='-143205'&CENTER='500@399'&START_TIME='${today}'&STOP_TIME='${tomorrow}'&STEP_SIZE='1 d'&CAL_FORMAT='CAL'&TIME_DIGITS='MINUTES'&ANG_FORMAT='HMS'&OUT_UNITS='KM-S'&RANGE_UNITS='AU'&APPARENT='AIRLESS'&SUPPRESS_RANGE_RATE='NO'&SKIP_DAYLT='NO'&EXTRA_PREC='NO'&R_T_S_ONLY='NO'&REF_SYSTEM='J2000'&CSV_FORMAT='NO'&QUANTITIES='19,20'`;\n  const MARS_DIST_URL = `https://ssd.jpl.nasa.gov/api/horizons.api?format=text&COMMAND='-143205'&CENTER='500@499'&START_TIME='${today}'&STOP_TIME='${tomorrow}'&STEP_SIZE='1 d'&CAL_FORMAT='CAL'&TIME_DIGITS='MINUTES'&ANG_FORMAT='HMS'&OUT_UNITS='KM-S'&RANGE_UNITS='AU'&APPARENT='AIRLESS'&SUPPRESS_RANGE_RATE='NO'&SKIP_DAYLT='NO'&EXTRA_PREC='NO'&R_T_S_ONLY='NO'&REF_SYSTEM='J2000'&CSV_FORMAT='NO'&QUANTITIES='19,20,22'`;\n\n  try {\n    const params = {\n      resolveBodyOnly: true,\n    };\n\n    const [orbitParams, earthDist, marsDist] = await Promise.all([\n      got(ORBIT_URL, params),\n      got(EARTH_DIST_URL, params),\n      got(MARS_DIST_URL, params),\n    ]);\n\n    /**\n     * All JPL Horizon parsing regexes from https://github.com/lnxbil/stellarium-comet-jpl\n     */\n\n    // Reading the SOE-Part into 'soe' for further processing\n    const soeReg = /\\$\\$SOE([\\s\\S]*)\\$\\$EOE/m;\n    soeReg.exec(orbitParams);\n    const orbitSoe = RegExp.$1;\n\n    // Reading Epoch - valid for CE dates.\n    const epochReg = /^[ ]*([0-9.]+)[ ]*=[ ]*A[.]D[.][ ]*/m;\n    epochReg.exec(orbitSoe);\n    const epoch = parseFloat(RegExp.$1);\n\n    // Reading Semi-major axis (A)\n    const smaReg = /A=[ ]*([0-9.E+-]+)/m;\n    smaReg.exec(orbitSoe);\n    const sma = parseFloat(RegExp.$1);\n\n    // Reading Eccentricity (EC)\n    const ecReg = /EC=[ ]*([0-9.E+-]+)/m;\n    ecReg.exec(orbitSoe);\n    const ecc = parseFloat(RegExp.$1);\n\n    // Reading Periapsis distance (QR)\n    const qrReg = /QR=[ ]*([0-9.E+-]+)/m;\n    qrReg.exec(orbitSoe);\n    const pqr = parseFloat(RegExp.$1);\n\n    // Reading Apoapsis distance (QR)\n    const adReg = /AD=[ ]*([0-9.E+-]+)/m;\n    adReg.exec(orbitSoe);\n    const aad = parseFloat(RegExp.$1);\n\n    // Reading Longitude of Ascending Node\n    const omReg = /OM=[ ]*([0-9.E+-]+)/m;\n    omReg.exec(orbitSoe);\n    const lon = parseFloat(RegExp.$1);\n\n    // Reading Argument of periapsis (W)\n    const wReg = /W\\s=[ ]*([0-9.E+-]+)/m;\n    wReg.exec(orbitSoe);\n    const aop = parseFloat(RegExp.$1);\n\n    // Reading Inclination w.r.t xy-plane\n    const inReg = /IN=[ ]*([0-9.E+-]+)/m;\n    inReg.exec(orbitSoe);\n    const inc = parseFloat(RegExp.$1);\n\n    // Reading orbital period\n    const periodReg = /PR=[ ]*([0-9.E+-]+)/m;\n    periodReg.exec(orbitSoe);\n    const period = parseFloat(RegExp.$1);\n\n    // Read SOE of earth distance + calculate distance in miles and kilometers\n    soeReg.exec(earthDist);\n    const earthSoe = RegExp.$1;\n    const strippedEarth = earthSoe.replace(/(\\r\\n\\t|\\n|\\r\\t)/gm, '');\n    const earthResult = strippedEarth.split(/\\s+/)[5];\n    const earthDistanceKm = (parseFloat(earthResult.trim()) * 149598073);\n    const earthDistanceMi = earthDistanceKm * 0.621371;\n\n    // Read SOE of mars distance + calculate distance in miles and kilometers\n    soeReg.exec(marsDist);\n    const marsSoe = RegExp.$1;\n    const strippedMars = marsSoe.replace(/(\\r\\n\\t|\\n|\\r\\t)/gm, '');\n    const marsResult = strippedMars.split(/\\s+/)[5];\n    const marsDistanceKm = (parseFloat(marsResult.trim()) * 149598073);\n    const marsDistanceMi = marsDistanceKm * 0.621371;\n\n    // Read SOE of orbital speed in KM/s + calculate kph and mph\n    const speedResult = strippedMars.split(/\\s+/)[5];\n    const orbitalSpeedKph = (parseFloat(speedResult.trim()) * 60.0 * 60.0);\n    const orbitalSpeedMph = orbitalSpeedKph * 0.621371;\n\n    const roadster = await got(`${API}/roadster`, {\n      resolveBodyOnly: true,\n      responseType: 'json',\n    });\n\n    await got.patch(`${API}/roadster/${roadster.id}`, {\n      json: {\n        epoch_jd: epoch,\n        apoapsis_au: aad,\n        periapsis_au: pqr,\n        semi_major_axis_au: sma,\n        eccentricity: ecc,\n        inclination: inc,\n        longitude: lon,\n        periapsis_arg: aop,\n        period_days: period,\n        speed_kph: orbitalSpeedKph,\n        speed_mph: orbitalSpeedMph,\n        earth_distance_km: earthDistanceKm,\n        earth_distance_mi: earthDistanceMi,\n        mars_distance_km: marsDistanceKm,\n        mars_distance_mi: marsDistanceMi,\n      },\n      headers: {\n        'spacex-key': KEY,\n      },\n    });\n\n    logger.info('Roadster updated');\n\n    if (HEALTHCHECK) {\n      await got(HEALTHCHECK);\n    }\n  } catch (error) {\n    console.log(`Roadster Error: ${error.message}`);\n  }\n};\n"
  },
  {
    "path": "jobs/starlink.js",
    "content": "import got from 'got';\nimport { CookieJar } from 'tough-cookie';\nimport Moment from 'moment-timezone';\nimport MomentRange from 'moment-range';\nimport { getSatelliteInfo } from 'tle.js';\nimport { logger } from '../middleware/index.js';\n\nconst API = process.env.SPACEX_API;\nconst KEY = process.env.SPACEX_KEY;\nconst HEALTHCHECK = process.env.STARLINK_HEALTHCHECK;\nconst moment = MomentRange.extendMoment(Moment);\n\n/**\n * Generate Starlink version from date\n * @param {Date}    date   Launch date UTC\n * @param {String}  name   Mission name\n * @return {String}\n */\nconst starlinkVersion = (date, name) => {\n  if (!date || !name) {\n    return null;\n  }\n  const missionNameVersion = name.match(/(?<version>v\\d{1,3}.\\d{1,3})/i)?.groups?.version;\n  if (missionNameVersion) {\n    return missionNameVersion;\n  }\n  const parsedDate = moment(date);\n  let version = null;\n  if (parsedDate.isAfter('2019-11-11')) {\n    version = 'v1.0';\n  } else if (parsedDate.isAfter('2019-05-24')) {\n    version = 'v0.9';\n  } else if (parsedDate.isAfter('2018-02-22')) {\n    version = 'prototype';\n  }\n  return version;\n};\n\n/**\n * Update Starlink orbits\n * @return {Promise<void>}\n */\nexport default async () => {\n  try {\n    const cookieJar = new CookieJar();\n\n    await got.post('https://www.space-track.org/ajaxauth/login', {\n      form: {\n        identity: process.env.SPACEX_TRACK_LOGIN,\n        password: process.env.SPACEX_TRACK_PASSWORD,\n      },\n      cookieJar,\n    });\n\n    const data = await got('https://www.space-track.org/basicspacedata/query/class/gp/OBJECT_NAME/~~STARLINK,~~TINTIN/orderby/NORAD_CAT_ID', {\n      responseType: 'json',\n      timeout: {\n        request: 480000, // 8 minutes\n      },\n      cookieJar,\n    });\n\n    const starlinkSats = data.body.filter((sat) => /starlink|tintin/i.test(sat.OBJECT_NAME));\n\n    const updates = starlinkSats.map(async (sat) => {\n      const date = moment.utc(sat.LAUNCH_DATE, 'YYYY-MM-DD');\n      const range = date.range('day');\n\n      const launches = await got.post(`${API}/launches/query`, {\n        json: {\n          query: {\n            date_utc: {\n              $gte: range.start.toISOString(),\n              $lte: range.end.toISOString(),\n            },\n          },\n          options: {\n            pagination: false,\n          },\n        },\n        resolveBodyOnly: true,\n        responseType: 'json',\n      });\n\n      let position;\n      if (!(sat.DECAY_DATE)) {\n        const tle = [sat.TLE_LINE1, sat.TLE_LINE2];\n        try {\n          position = await getSatelliteInfo(tle);\n        } catch (error) {\n          console.log(error);\n        }\n      }\n\n      await got.patch(`${API}/starlink/${sat.NORAD_CAT_ID}`, {\n        json: {\n          version: starlinkVersion(\n            launches?.docs[0]?.date_utc ?? null,\n            launches?.docs[0]?.name ?? null,\n          ),\n          launch: launches?.docs[0]?.id ?? null,\n          longitude: position?.lng ?? null,\n          latitude: position?.lat ?? null,\n          height_km: position?.height ?? null,\n          velocity_kms: position?.velocity ?? null,\n          spaceTrack: {\n            CCSDS_OMM_VERS: sat.CCSDS_OMM_VERS,\n            COMMENT: sat.COMMENT,\n            CREATION_DATE: sat.CREATION_DATE,\n            ORIGINATOR: sat.ORIGINATOR,\n            OBJECT_NAME: sat.OBJECT_NAME,\n            OBJECT_ID: sat.OBJECT_ID,\n            CENTER_NAME: sat.CENTER_NAME,\n            REF_FRAME: sat.REF_FRAME,\n            TIME_SYSTEM: sat.TIME_SYSTEM,\n            MEAN_ELEMENT_THEORY: sat.MEAN_ELEMENT_THEORY,\n            EPOCH: sat.EPOCH,\n            MEAN_MOTION: sat.MEAN_MOTION,\n            ECCENTRICITY: sat.ECCENTRICITY,\n            INCLINATION: sat.INCLINATION,\n            RA_OF_ASC_NODE: sat.RA_OF_ASC_NODE,\n            ARG_OF_PERICENTER: sat.ARG_OF_PERICENTER,\n            MEAN_ANOMALY: sat.MEAN_ANOMALY,\n            EPHEMERIS_TYPE: sat.EPHEMERIS_TYPE,\n            CLASSIFICATION_TYPE: sat.CLASSIFICATION_TYPE,\n            NORAD_CAT_ID: sat.NORAD_CAT_ID,\n            ELEMENT_SET_NO: sat.ELEMENT_SET_NO,\n            REV_AT_EPOCH: sat.REV_AT_EPOCH,\n            BSTAR: sat.BSTAR,\n            MEAN_MOTION_DOT: sat.MEAN_MOTION_DOT,\n            MEAN_MOTION_DDOT: sat.MEAN_MOTION_DDOT,\n            SEMIMAJOR_AXIS: sat.SEMIMAJOR_AXIS,\n            PERIOD: sat.PERIOD,\n            APOAPSIS: sat.APOAPSIS,\n            PERIAPSIS: sat.PERIAPSIS,\n            OBJECT_TYPE: sat.OBJECT_TYPE,\n            RCS_SIZE: sat.RCS_SIZE,\n            COUNTRY_CODE: sat.COUNTRY_CODE,\n            LAUNCH_DATE: sat.LAUNCH_DATE,\n            SITE: sat.SITE,\n            DECAY_DATE: sat.DECAY_DATE,\n            DECAYED: !!(sat.DECAY_DATE),\n            FILE: sat.FILE,\n            GP_ID: sat.GP_ID,\n            TLE_LINE0: sat.TLE_LINE0,\n            TLE_LINE1: sat.TLE_LINE1,\n            TLE_LINE2: sat.TLE_LINE2,\n          },\n        },\n        headers: {\n          'spacex-key': KEY,\n        },\n      });\n    });\n\n    await Promise.all(updates);\n\n    logger.info({\n      starlinkUpdated: true,\n    });\n\n    if (HEALTHCHECK) {\n      await got(HEALTHCHECK);\n    }\n  } catch (error) {\n    console.error(error);\n    console.log(`Starlink Error: ${error.message}`);\n  }\n};\n"
  },
  {
    "path": "jobs/upcoming.js",
    "content": "/* eslint-disable no-continue */\n/* eslint-disable no-restricted-syntax */\n\nimport got from 'got';\nimport { load } from 'cheerio';\nimport * as fuzz from 'fuzzball';\nimport moment from 'moment-timezone';\nimport { logger } from '../middleware/index.js';\n\nconst REDDIT_WIKI = 'https://old.reddit.com/r/spacex/wiki/launches/manifest';\nconst API = process.env.SPACEX_API;\nconst KEY = process.env.SPACEX_KEY;\nconst HEALTHCHECK = process.env.UPCOMING_HEALTHCHECK;\n\n/**\n * This script gathers dates and payload names from the subreddit launch wiki,\n * fuzzy checks them against existing upcoming mission names and updates the date if a\n * change is made in the wiki. The proper time zone is calculated from the launch site\n * id of the launch. It also corrects the flight number order based on the launch wiki order.\n * @return {Promise<void>}\n */\nexport default async () => {\n  try {\n    const flightNumbers = [];\n    const rawLaunches = await got.post(`${API}/launches/query`, {\n      json: {\n        options: {\n          pagination: false,\n          sort: {\n            flight_number: 'asc',\n          },\n        },\n      },\n      resolveBodyOnly: true,\n      responseType: 'json',\n    });\n\n    // Past launches needed to set new flight number order\n    const upcoming = rawLaunches.docs.filter((doc) => doc.upcoming === true);\n    const past = rawLaunches.docs.filter((doc) => doc.upcoming === false);\n\n    // Grab subreddit wiki\n    const rawWiki = await got(REDDIT_WIKI, {\n      resolveBodyOnly: true,\n    });\n    const $ = load(rawWiki);\n    const wiki = $('body > div.content > div > div > table:nth-child(7) > tbody').text();\n\n    if (!wiki) {\n      throw new Error(`Broken wiki selector: ${wiki}`);\n    }\n\n    const wikiRow = wiki.split('\\n').filter((v) => v !== '');\n\n    const allWikiDates = wikiRow.filter((_, index) => index % 7 === 0);\n    const wikiDates = allWikiDates.slice(0, 30).map((date) => date\n      .replace(/(?<=\\[[0-9]{2}:[0-9]{2}\\])(\\[[0-9]{1,3}\\]|\\[[0-9]{1,3}|[0-9]{1,3}\\])*/gi, '')\n      .replace(/(?<=\\s*[0-9]{4}\\s*([a-z]{3}|[a-z]{3,9})\\s*)([0-9]{1,3}\\])*/gi, '')\n      .replace(/~|(\\[|\\[\\[)[0-9]{1,3}\\]/gi, '')\n      .replace(/~|(\\[|\\])/gi, '')\n      .replace(/(early|mid|late|end|tbd|tba|net)/gi, ' ')\n      .replace(/-[0-9]{2}:[0-9]{2}/gi, ' ')\n      .replace(/(\\(|\\)|\\?)/gi, ' ') // Removes (?) from dates\n      .split('/')[0].trim());\n    const rawWikiDates = allWikiDates.slice(0, 30);\n\n    const allWikiPayloads = wikiRow.filter((_, index) => (index + 2) % 7 === 0);\n    const wikiPayloads = allWikiPayloads.slice(0, 30).map((payload) => payload.replace(/\\[[0-9]{1,3}\\]/gi, ''));\n\n    const allWikiLaunchpads = wikiRow.filter((_, index) => (index + 5) % 7 === 0);\n    const wikiLaunchpads = allWikiLaunchpads.slice(0, 30).map((launchpad) => launchpad.replace(/\\[[0-9]{1,3}\\]/gi, ''));\n\n    // Set base flight number to automatically reorder launches on the wiki\n    // If the most recent past launch is still on the wiki, don't offset the flight number\n    let baseFlightNumber;\n    if (fuzz.partial_ratio(past[past.length - 1].name, wikiPayloads[0]) === 100) {\n      baseFlightNumber = past[past.length - 1].flight_number;\n    } else {\n      baseFlightNumber = past[past.length - 1].flight_number + 1;\n    }\n\n    // Compare each mission name against entire list of wiki payloads, and fuzzy match the\n    // mission name against the wiki payload name. The partial match must be 100%, to avoid\n    // conflicts like SSO-A and SSO-B, where a really close match would produce wrong results.\n    for await (const [, launch] of upcoming.entries()) {\n      // Allow users to pause auto updates from wiki, while still preserving\n      // flight reordering feature\n      if (!launch.auto_update) {\n        continue;\n      }\n      for await (const [wikiIndex, wikiPayload] of wikiPayloads.entries()) {\n        if (fuzz.partial_ratio(launch.name, wikiPayload) === 100) {\n          // Special check for starlink / smallsat launches, because Starlink 2 and Starlink 23\n          // both pass the partial ratio check, so they are checked strictly below\n          if (/starlink/i.test(launch.name) && fuzz.ratio(launch.name, wikiPayload) !== 100) {\n            continue;\n          }\n\n          // Check and see if dates match a certain pattern depending on the length of the\n          // date given. This sets the amount of precision needed for the date.\n          // Allows for long months or short months ex: September vs Sep\n          // Allows for time with or without brackets ex: [23:45] vs 23:45\n\n          // Anything with NET in date\n          const netPattern = /^.*(net).*$/i;\n\n          // Anything with TBD/TBA in date\n          const tbdPattern = /^.*(tbd|tba).*$/i;\n\n          // 2020\n          const yearPattern = /^\\s*[0-9]{4}\\s*$/i;\n\n          // 2020 [14:10]\n          const yearHourPattern = /^\\s*[0-9]{4}\\s*(\\[?\\s*([0-9]{2}|[0-9]{1}):[0-9]{2}\\s*\\]?)\\s*$/i;\n\n          // 2020 Nov\n          const monthPattern = /^\\s*[0-9]{4}\\s*([a-z]{3}|[a-z]{3,9})\\s*$/i;\n\n          // 2020 Nov 4\n          const dayPattern = /^\\s*[0-9]{4}\\s*([a-z]{3}|[a-z]{3,9})\\s*[0-9]{1,2}\\s*$/i;\n\n          // 2020 Nov [14:10]\n          const vagueHourPattern = /^\\s*[0-9]{4}\\s*([a-z]{3}|[a-z]{3,9})\\s*(\\[?\\s*([0-9]{2}|[0-9]{1}):[0-9]{2}\\s*\\]?)\\s*$/i;\n\n          // 2020 Nov 4 [14:10]\n          const hourPattern = /^\\s*[0-9]{4}\\s*([a-z]{3}|[a-z]{3,9})\\s*[0-9]{1,2}\\s*(\\[?\\s*([0-9]{2}|[0-9]{1}):[0-9]{2}\\s*\\]?)\\s*$/i;\n\n          // 2020 Nov 4 [14:10:50]\n          const secondPattern = /^\\s*[0-9]{4}\\s*([a-z]{3}|[a-z]{3,9})\\s*[0-9]{1,2}\\s*(\\[?\\s*([0-9]{2}|[0-9]{1}):[0-9]{2}:[0-9]{2}\\s*\\]?)\\s*$/i;\n\n          let precision;\n          let wikiDate = wikiDates[parseInt(wikiIndex, 10)];\n          const rawWikiDate = rawWikiDates[parseInt(wikiIndex, 10)];\n\n          // Check if date is NET\n          const net = netPattern.test(rawWikiDate);\n\n          // Check if date contains TBD\n          const tbd = tbdPattern.test(rawWikiDate);\n\n          // Remove extra stuff humans might add\n          // NOTE: Add to this when people add unexpected things to dates in the wiki\n          const cleanedwikiDate = wikiDate;\n\n          // Set date precision\n          if (cleanedwikiDate.includes('Q')) {\n            // Quarter is first because moment.js does not make\n            // a distinction between half vs quarter. Therefore\n            // the first half starts at the beginning Q1, and the\n            // second half starts at the beginning of Q3\n            wikiDate = wikiDate.replace('Q', '');\n            precision = 'quarter';\n          } else if (cleanedwikiDate.includes('H1')) {\n            wikiDate = wikiDate.replace('H1', '1');\n            precision = 'half';\n          } else if (cleanedwikiDate.includes('H2')) {\n            wikiDate = wikiDate.replace('H2', '3');\n            precision = 'half';\n          } else if (yearPattern.test(cleanedwikiDate)) {\n            precision = 'year';\n          } else if (yearHourPattern.test(cleanedwikiDate)) {\n            precision = 'year';\n          } else if (monthPattern.test(cleanedwikiDate)) {\n            precision = 'month';\n          } else if (dayPattern.test(cleanedwikiDate)) {\n            precision = 'day';\n          } else if (vagueHourPattern.test(cleanedwikiDate)) {\n            precision = 'month';\n          } else if (hourPattern.test(cleanedwikiDate)) {\n            precision = 'hour';\n          } else if (secondPattern.test(cleanedwikiDate)) {\n            precision = 'hour';\n          } else {\n            throw new Error(`No date match: ${cleanedwikiDate}`);\n          }\n\n          // Add flight numbers to array to check for duplicates\n          flightNumbers.push(baseFlightNumber + wikiIndex);\n\n          // Wiki launchpad matchers\n          const slc40Pattern = /^SLC-40.*$/i;\n          const lc39aPattern = /^LC-39A.*$/i;\n          const slc4ePattern = /^SLC-4E.*$/i;\n          const bcPattern = /^BC.*$/i;\n          const unknownPattern = /^\\?.*$/i;\n\n          // Calculate launch site depending on wiki manifest\n          const launchpad = wikiLaunchpads[parseInt(wikiIndex, 10)];\n          console.log(launchpad);\n          let queryName;\n          if (slc40Pattern.test(launchpad)) {\n            queryName = 'CCSFS SLC 40';\n          } else if (lc39aPattern.test(launchpad)) {\n            queryName = 'KSC LC 39A';\n          } else if (slc4ePattern.test(launchpad)) {\n            queryName = 'VAFB SLC 4E';\n          } else if (bcPattern.test(launchpad)) {\n            queryName = 'STLS';\n          } else if (unknownPattern.test(launchpad)) {\n            queryName = 'CCSFS SLC 40';\n          } else {\n            throw new Error(`No launchpad match: ${launchpad}`);\n          }\n          const launchpads = await got.post(`${API}/launchpads/query`, {\n            json: {\n              query: {\n                name: queryName,\n              },\n              options: {\n                limit: 1,\n              },\n            },\n            resolveBodyOnly: true,\n            responseType: 'json',\n          });\n          const launchpadId = launchpads.docs[0].id;\n          const { timezone } = launchpads.docs[0];\n\n          // Clean wiki date, set timezone\n          const parsedDate = `${wikiDates[parseInt(wikiIndex, 10)].replace(/(-|\\[|\\]|~|early|mid|late|end|net)/gi, ' ').split('/')[0].trim()}`;\n          const time = moment(parsedDate, ['YYYY MMM HH:mm:ss', 'YYYY MMM HH:mm', 'YYYY MMM D HH:mm', 'YYYY MMM D', 'YYYY MMM', 'YYYY HH:mm', 'YYYY Q', 'YYYY']);\n          const zone = moment.tz(time, 'UTC');\n          const localTime = time.tz(timezone).format();\n\n          const rawUpdate = {\n            flight_number: (baseFlightNumber + wikiIndex),\n            date_unix: zone.unix(),\n            date_utc: zone.toISOString(),\n            date_local: localTime,\n            date_precision: precision,\n            launchpad: launchpadId,\n            tbd,\n            net,\n          };\n\n          logger.info({\n            launch: launch.name,\n            ...rawUpdate,\n          });\n\n          await got.patch(`${API}/launches/${launch.id}`, {\n            json: {\n              ...rawUpdate,\n            },\n            headers: {\n              'spacex-key': KEY,\n            },\n          });\n        }\n      }\n    }\n\n    if (HEALTHCHECK) {\n      await got(HEALTHCHECK);\n    }\n  } catch (error) {\n    console.log(`Upcoming Launch Error: ${error.message}`);\n  }\n};\n"
  },
  {
    "path": "jobs/webcast.js",
    "content": "import got from 'got';\nimport * as fuzz from 'fuzzball';\nimport Parser from 'rss-parser';\nimport { fail, success } from '../lib/healthchecks/index.js';\nimport { logger } from '../middleware/index.js';\n\nconst YOUTUBE_PREFIX = 'https://youtu.be';\nconst CHANNEL_ID = 'UCtI0Hodo5o5dUb67FeUjDeA';\nconst {\n  SPACEX_KEY,\n  WEBCAST_HEALTHCHECK,\n  SPACEX_API: API,\n} = process.env;\n\n/**\n * Check for new SpaceX webcast links\n * @return {Promise<void>}\n */\nexport default async () => {\n  try {\n    let updated = false;\n    let match = false;\n    const parser = new Parser();\n    const url = `https://www.youtube.com/feeds/videos.xml?channel_id=${CHANNEL_ID}`;\n    const rssResult = await got(url, {\n      resolveBodyOnly: true,\n    });\n    const result = await parser.parseString(rssResult);\n    const latest = result.items[0];\n    const rssTitle = latest.title;\n    const rssYoutubeId = latest.link.split('v=')[1];\n\n    const launches = await got.post(`${API}/launches/query`, {\n      json: {\n        query: {\n          upcoming: true,\n        },\n        options: {\n          sort: {\n            flight_number: 'asc',\n          },\n          limit: 1,\n        },\n      },\n      resolveBodyOnly: true,\n      responseType: 'json',\n    });\n    const launchId = launches.docs[0].id;\n    const missionName = launches.docs[0].name;\n\n    const ratio = fuzz.ratio(rssTitle, missionName);\n    // Prevent matching on mission control audio videos\n    if (!rssTitle.includes('audio') && ratio >= 50) {\n      match = true;\n      const pastLaunches = await got.post(`${API}/launches/query`, {\n        json: {\n          query: {\n            upcoming: false,\n          },\n          options: {\n            sort: {\n              flight_number: 'desc',\n            },\n            limit: 1,\n          },\n        },\n        resolveBodyOnly: true,\n        responseType: 'json',\n      });\n      const pastYoutubeId = pastLaunches.docs[0].links.youtube_id;\n      if (rssYoutubeId !== pastYoutubeId) {\n        await got.patch(`${API}/launches/${launchId}`, {\n          json: {\n            'links.webcast': `${YOUTUBE_PREFIX}/${rssYoutubeId}`,\n            'links.youtube_id': rssYoutubeId,\n          },\n          headers: {\n            'spacex-key': SPACEX_KEY,\n          },\n        });\n        updated = true;\n      }\n    }\n    const log = {\n      name: 'webcast',\n      ratio,\n      match,\n      updated,\n      youtubeTitle: rssTitle,\n      youtubeId: rssYoutubeId,\n    };\n    await success(WEBCAST_HEALTHCHECK, log);\n    logger.info(log);\n  } catch (error) {\n    const formatted = {\n      name: 'webcast',\n      error: error.message,\n      stack: error.stack,\n    };\n    await fail(WEBCAST_HEALTHCHECK, formatted);\n    logger.error(formatted);\n  }\n};\n"
  },
  {
    "path": "jobs/worker.js",
    "content": "import { CronJob } from 'cron';\nimport dotenv from 'dotenv';\nimport { logger } from '../middleware/index.js';\nimport launches from './launches.js';\nimport payloads from './payloads.js';\nimport landpads from './landpads.js';\nimport launchpads from './launchpads.js';\nimport capsules from './capsules.js';\nimport cores from './cores.js';\nimport roadster from './roadster.js';\nimport upcoming from './upcoming.js';\nimport starlink from './starlink.js';\nimport webcast from './webcast.js';\nimport launchLibrary from './launch-library.js';\n\n// Env init\ndotenv.config();\n\n// Every 10 minutes\nconst launchesJob = new CronJob('*/10 * * * *', launches);\n\n// Every 10 minutes\nconst landpadsJob = new CronJob('*/10 * * * *', landpads);\n\n// Every 10 minutes\nconst launchpadsJob = new CronJob('*/10 * * * *', launchpads);\n\n// Every 10 minutes\nconst capsulesJob = new CronJob('*/10 * * * *', capsules);\n\n// Every 10 minutes\nconst coresJob = new CronJob('*/10 * * * *', cores);\n\n// Every 10 minutes\nconst roadsterJob = new CronJob('*/10 * * * *', roadster);\n\n// Every 10 minutes\nconst upcomingJob = new CronJob('*/10 * * * *', upcoming);\n\n// Every hour on :25\nconst payloadsJob = new CronJob('25 * * * *', payloads);\n\n// Every hour on :35\nconst starlinkJob = new CronJob('35 * * * *', starlink);\n\n// Every 5 minutes\nconst webcastJob = new CronJob('*/5 * * * *', webcast);\n\n// Every hour on :45\nconst launchLibraryJob = new CronJob('45 * * * *', launchLibrary);\n\ntry {\n  launchesJob.start();\n  payloadsJob.start();\n  landpadsJob.start();\n  launchpadsJob.start();\n  capsulesJob.start();\n  coresJob.start();\n  roadsterJob.start();\n  upcomingJob.start();\n  starlinkJob.start();\n  webcastJob.start();\n  launchLibraryJob.start();\n} catch (error) {\n  const formatted = {\n    name: 'worker',\n    error: error.message,\n    stack: error.stack,\n  };\n  logger.error(formatted);\n}\n"
  },
  {
    "path": "lib/constants.js",
    "content": "/**\n * Healthchecks.io API prefix\n */\nexport const HEALTHCHECK_PREFIX = 'https://hc-ping.com';\n\n/**\n * Default Port\n */\nexport const DEFAULT_PORT = 6673;\n"
  },
  {
    "path": "lib/healthchecks/fail.js",
    "content": "/**\n * Imports\n */\nimport got from 'got';\nimport { HEALTHCHECK_PREFIX } from '../constants.js';\n\n/**\n * Send fail signal to healthcheck.io\n *\n * @param {String} [id] UUID of healthcheck\n * @param {String|Object} [msg] Message to pass to healthcheck\n * @returns {Boolean} True if successful\n */\nexport default async (id = null, msg = {}) => {\n  if (id) {\n    const response = await got.post({\n      prefixUrl: HEALTHCHECK_PREFIX,\n      url: `${id}/fail`,\n      json: msg,\n    });\n    if (response.statusCode === 200) {\n      return true;\n    }\n  }\n  return false;\n};\n"
  },
  {
    "path": "lib/healthchecks/index.js",
    "content": "export { default as fail } from './fail.js';\nexport { default as start } from './start.js';\nexport { default as success } from './success.js';\n"
  },
  {
    "path": "lib/healthchecks/start.js",
    "content": "/**\n * Imports\n */\nimport got from 'got';\nimport { HEALTHCHECK_PREFIX } from '../constants.js';\n\n/**\n * Send start signal to healthcheck.io\n *\n * @param {String} [id] UUID of healthcheck\n * @param {String|Object} [msg] Message to pass to healthcheck\n * @returns {Boolean} True if successful\n */\nexport default async (id = null, msg = {}) => {\n  if (id) {\n    const response = await got.post({\n      prefixUrl: HEALTHCHECK_PREFIX,\n      url: `${id}/start`,\n      json: msg,\n    });\n    if (response.statusCode === 200) {\n      return true;\n    }\n  }\n  return false;\n};\n"
  },
  {
    "path": "lib/healthchecks/success.js",
    "content": "/**\n * Imports\n */\nimport got from 'got';\nimport { HEALTHCHECK_PREFIX } from '../constants.js';\n\n/**\n * Send start signal to healthcheck.io\n *\n * @param {String} id UUID of healthcheck\n * @param {String|Object} [msg]  Message to pass to healthcheck\n * @returns {Boolean} True if successful\n */\nexport default async (id = null, msg = {}) => {\n  if (id) {\n    const response = await got.post({\n      prefixUrl: HEALTHCHECK_PREFIX,\n      url: `${id}`,\n      json: msg,\n    });\n    if (response.statusCode === 200) {\n      return true;\n    }\n  }\n  return false;\n};\n"
  },
  {
    "path": "lib/utils/healthcheck.js",
    "content": "#!/usr/bin/env node\n\nimport got from 'got';\n\nconst { HEALTH_URL, SPACEX_WORKER } = process.env;\n\n(async () => {\n  try {\n    if (SPACEX_WORKER) {\n      process.exit(0);\n    }\n    await got(HEALTH_URL);\n    process.exit(0);\n  } catch (error) {\n    console.log(error.message);\n    process.exit(1);\n  }\n})();\n"
  },
  {
    "path": "middleware/auth.js",
    "content": "import mongoose from 'mongoose';\n\nconst db = mongoose.connection.useDb('auth', { useCache: true });\n\n/**\n * Authentication middleware\n */\nexport default async (ctx, next) => {\n  const key = ctx.request.headers['spacex-key'];\n  if (key) {\n    const user = await db.collection('users').findOne({ key });\n    if (user?.key === key) {\n      ctx.state.roles = user.roles;\n      await next();\n      return;\n    }\n  }\n  ctx.status = 401;\n  ctx.body = 'https://youtu.be/RfiQYRn7fBg';\n};\n"
  },
  {
    "path": "middleware/authz.js",
    "content": "/**\n * Authorization middleware\n *\n * @param   {String}   role   Role for protected route\n * @returns {void}\n */\nexport default (role) => async (ctx, next) => {\n  const { roles } = ctx.state;\n  const allowed = roles.includes(role);\n  if (allowed) {\n    await next();\n    return;\n  }\n  ctx.status = 403;\n};\n"
  },
  {
    "path": "middleware/cache.js",
    "content": "import Redis from 'ioredis';\nimport blake3 from 'blake3';\nimport logger from './logger.js';\n\nconst redis = (process.env.SPACEX_REDIS) ? new Redis(process.env.SPACEX_REDIS) : new Redis();\nlet redisAvailable = false;\n\nredis.on('error', () => {\n  redisAvailable = false;\n});\nredis.on('end', () => {\n  redisAvailable = false;\n});\nredis.on('ready', () => {\n  redisAvailable = true;\n  logger.info('Redis connected');\n});\n\n/**\n * BLAKE3 hash func for redis keys\n *\n * @param   {String}    str    String to hash\n * @returns {String}  Hashed result\n */\nconst hash = (str) => blake3.createHash().update(str).digest('hex');\n\n/**\n * Redis cache middleware\n *\n * @param   {Number}    ttl       Cache TTL in seconds\n * @returns {void}\n */\nexport default (ttl) => async (ctx, next) => {\n  if (process.env.NODE_ENV !== 'production') {\n    await next();\n    return;\n  }\n\n  if (!redisAvailable) {\n    ctx.response.set('spacex-api-cache-online', 'false');\n    await next();\n    return;\n  }\n  ctx.response.set('spacex-api-cache-online', 'true');\n\n  const { url, method } = ctx.request;\n  const key = `spacex-cache:${hash(`${method}${url}${JSON.stringify(ctx.request.body)}`)}`;\n\n  if (ttl) {\n    ctx.response.set('Cache-Control', `max-age=${ttl}`);\n  } else {\n    ctx.response.set('Cache-Control', 'no-store');\n  }\n\n  // Only allow cache on whitelist methods\n  if (!['GET', 'POST'].includes(ctx.request.method)) {\n    await next();\n    return;\n  }\n\n  let cached;\n  try {\n    cached = await redis.get(key);\n    if (cached) {\n      ctx.response.status = 200;\n      ctx.response.set('spacex-api-cache', 'HIT');\n      ctx.response.type = 'application/json';\n      ctx.response.body = cached;\n      cached = true;\n    }\n  } catch (e) {\n    cached = false;\n  }\n  if (cached) {\n    return;\n  }\n  await next();\n\n  const responseBody = JSON.stringify(ctx.response.body);\n  ctx.response.set('spacex-api-cache', 'MISS');\n\n  // Set cache\n  try {\n    if ((ctx?.response?.status !== 200) || !responseBody) {\n      return;\n    }\n    await redis.set(key, responseBody, 'EX', ttl);\n  } catch (e) {\n    console.log(`Failed to set cache: ${e.message}`);\n  }\n};\n\nexport {\n  redis,\n};\n"
  },
  {
    "path": "middleware/errors.js",
    "content": "/**\n * Error handler middleware\n *\n * @param   {Object}    ctx       Koa context\n * @param   {function}  next      Koa next function\n * @returns {void}\n */\nexport default async (ctx, next) => {\n  try {\n    await next();\n  } catch (err) {\n    if (err?.kind === 'ObjectId') {\n      err.status = 404;\n    } else {\n      ctx.status = err.status ?? 500;\n      ctx.body = err.message;\n    }\n  }\n};\n"
  },
  {
    "path": "middleware/index.js",
    "content": "export { default as auth } from './auth.js';\nexport { default as authz } from './authz.js';\nexport { default as cache } from './cache.js';\nexport { default as errors } from './errors.js';\nexport { default as responseTime } from './response-time.js';\nexport { default as logger } from './logger.js';\n"
  },
  {
    "path": "middleware/logger.js",
    "content": "import pino from 'pino';\n\nexport default pino();\n"
  },
  {
    "path": "middleware/response-time.js",
    "content": "/**\n * Return header with response time\n *\n * @param   {Object}    ctx   Koa context\n * @param   {Function}  next  Next middleware\n * @returns {Promise}\n */\nexport default async (ctx, next) => {\n  const start = Date.now();\n  await next();\n  const ms = Date.now() - start;\n  ctx.set('spacex-api-response-time', `${ms}ms`);\n};\n"
  },
  {
    "path": "models/capsules.js",
    "content": "import mongoose from 'mongoose';\nimport mongoosePaginate from 'mongoose-paginate-v2';\nimport idPlugin from 'mongoose-id';\n\nconst capsuleSchema = new mongoose.Schema({\n  serial: {\n    type: String,\n    required: true,\n    unique: true,\n  },\n  status: {\n    type: String,\n    enum: ['unknown', 'active', 'retired', 'destroyed'],\n    required: true,\n  },\n  type: {\n    type: String,\n    enum: ['Dragon 1.0', 'Dragon 1.1', 'Dragon 2.0'],\n    required: true,\n  },\n  dragon: {\n    type: mongoose.ObjectId,\n    ref: 'Dragon',\n  },\n  reuse_count: {\n    type: Number,\n    default: 0,\n  },\n  water_landings: {\n    type: Number,\n    default: 0,\n  },\n  land_landings: {\n    type: Number,\n    default: 0,\n  },\n  last_update: {\n    type: String,\n    default: null,\n  },\n  launches: [{\n    type: mongoose.ObjectId,\n    ref: 'Launch',\n  }],\n}, { autoCreate: true });\n\nconst index = {\n  serial: 'text',\n  last_update: 'text',\n};\ncapsuleSchema.index(index);\n\ncapsuleSchema.plugin(mongoosePaginate);\ncapsuleSchema.plugin(idPlugin);\n\nconst Capsule = mongoose.model('Capsule', capsuleSchema);\n\nexport default Capsule;\n"
  },
  {
    "path": "models/company.js",
    "content": "import mongoose from 'mongoose';\nimport idPlugin from 'mongoose-id';\n\nconst companySchema = new mongoose.Schema({\n  name: {\n    type: String,\n  },\n  founder: {\n    type: String,\n  },\n  founded: {\n    type: Number,\n  },\n  employees: {\n    type: Number,\n  },\n  vehicles: {\n    type: Number,\n  },\n  launch_sites: {\n    type: Number,\n  },\n  test_sites: {\n    type: Number,\n  },\n  ceo: {\n    type: String,\n  },\n  cto: {\n    type: String,\n  },\n  coo: {\n    type: String,\n  },\n  cto_propulsion: {\n    type: String,\n  },\n  valuation: {\n    type: Number,\n  },\n  headquarters: {\n    address: {\n      type: String,\n    },\n    city: {\n      type: String,\n    },\n    state: {\n      type: String,\n    },\n  },\n  links: {\n    website: {\n      type: String,\n    },\n    flickr: {\n      type: String,\n    },\n    twitter: {\n      type: String,\n    },\n    elon_twitter: {\n      type: String,\n    },\n  },\n  summary: {\n    type: String,\n  },\n}, { autoCreate: true });\n\ncompanySchema.plugin(idPlugin);\n\nconst Company = mongoose.model('Company', companySchema);\n\nexport default Company;\n"
  },
  {
    "path": "models/cores.js",
    "content": "import mongoose from 'mongoose';\nimport mongoosePaginate from 'mongoose-paginate-v2';\nimport idPlugin from 'mongoose-id';\n\nconst coreSchema = new mongoose.Schema({\n  serial: {\n    type: String,\n    unique: true,\n    required: true,\n  },\n  block: {\n    type: Number,\n    default: null,\n  },\n  status: {\n    type: String,\n    enum: ['active', 'inactive', 'unknown', 'expended', 'lost', 'retired'],\n    required: true,\n  },\n  reuse_count: {\n    type: Number,\n    default: 0,\n  },\n  rtls_attempts: {\n    type: Number,\n    default: 0,\n  },\n  rtls_landings: {\n    type: Number,\n    default: 0,\n  },\n  asds_attempts: {\n    type: Number,\n    default: 0,\n  },\n  asds_landings: {\n    type: Number,\n    default: 0,\n  },\n  last_update: {\n    type: String,\n    default: null,\n  },\n  launches: [{\n    type: mongoose.ObjectId,\n    ref: 'Launch',\n  }],\n}, { autoCreate: true });\n\nconst index = {\n  serial: 'text',\n  last_update: 'text',\n};\ncoreSchema.index(index);\n\ncoreSchema.plugin(mongoosePaginate);\ncoreSchema.plugin(idPlugin);\n\nconst Core = mongoose.model('Core', coreSchema);\n\nexport default Core;\n"
  },
  {
    "path": "models/crew.js",
    "content": "import mongoose from 'mongoose';\nimport mongoosePaginate from 'mongoose-paginate-v2';\nimport idPlugin from 'mongoose-id';\n\nconst crewSchema = new mongoose.Schema({\n  name: {\n    type: String,\n    default: null,\n  },\n  status: {\n    type: String,\n    required: true,\n    enum: ['active', 'inactive', 'retired', 'unknown'],\n  },\n  agency: {\n    type: String,\n    default: null,\n  },\n  image: {\n    type: String,\n    default: null,\n  },\n  wikipedia: {\n    type: String,\n    default: null,\n  },\n  launches: [{\n    type: mongoose.ObjectId,\n    ref: 'Launch',\n  }],\n}, { autoCreate: true });\n\nconst index = {\n  name: 'text',\n};\ncrewSchema.index(index);\n\ncrewSchema.plugin(mongoosePaginate);\ncrewSchema.plugin(idPlugin);\n\nconst Crew = mongoose.model('Crew', crewSchema);\n\nexport default Crew;\n"
  },
  {
    "path": "models/dragons.js",
    "content": "import mongoose from 'mongoose';\nimport mongoosePaginate from 'mongoose-paginate-v2';\nimport idPlugin from 'mongoose-id';\n\nconst dragonSchema = new mongoose.Schema({\n  name: {\n    type: String,\n    unique: true,\n    required: true,\n  },\n  type: {\n    type: String,\n    required: true,\n  },\n  active: {\n    type: Boolean,\n    required: true,\n  },\n  crew_capacity: {\n    type: Number,\n    required: true,\n  },\n  sidewall_angle_deg: {\n    type: Number,\n    required: true,\n  },\n  orbit_duration_yr: {\n    type: Number,\n    required: true,\n  },\n  dry_mass_kg: {\n    type: Number,\n    required: true,\n  },\n  dry_mass_lb: {\n    type: Number,\n    required: true,\n  },\n  first_flight: {\n    type: String,\n    default: null,\n  },\n  heat_shield: {\n    material: {\n      type: String,\n      required: true,\n    },\n    size_meters: {\n      type: Number,\n      required: true,\n    },\n    temp_degrees: {\n      type: Number,\n    },\n    dev_partner: {\n      type: String,\n    },\n  },\n  thrusters: {\n    type: mongoose.Mixed,\n  },\n  launch_payload_mass: {\n    kg: {\n      type: Number,\n    },\n    lb: {\n      type: Number,\n    },\n  },\n  launch_payload_vol: {\n    cubic_meters: {\n      type: Number,\n    },\n    cubic_feet: {\n      type: Number,\n    },\n  },\n  return_payload_mass: {\n    kg: {\n      type: Number,\n    },\n    lb: {\n      type: Number,\n    },\n  },\n  return_payload_vol: {\n    cubic_meters: {\n      type: Number,\n    },\n    cubic_feet: {\n      type: Number,\n    },\n  },\n  pressurized_capsule: {\n    payload_volume: {\n      cubic_meters: {\n        type: Number,\n      },\n      cubic_feet: {\n        type: Number,\n      },\n    },\n  },\n  trunk: {\n    trunk_volume: {\n      cubic_meters: {\n        type: Number,\n      },\n      cubic_feet: {\n        type: Number,\n      },\n    },\n    cargo: {\n      solar_array: {\n        type: Number,\n      },\n      unpressurized_cargo: {\n        type: Boolean,\n      },\n    },\n  },\n  height_w_trunk: {\n    meters: {\n      type: Number,\n    },\n    feet: {\n      type: Number,\n    },\n  },\n  diameter: {\n    meters: {\n      type: Number,\n    },\n    feet: {\n      type: Number,\n    },\n  },\n  flickr_images: {\n    type: [\n      String,\n    ],\n  },\n  wikipedia: {\n    type: String,\n  },\n  description: {\n    type: String,\n  },\n}, { autoCreate: true });\n\nconst index = {\n  name: 'text',\n};\ndragonSchema.index(index);\n\ndragonSchema.plugin(mongoosePaginate);\ndragonSchema.plugin(idPlugin);\n\nconst Dragon = mongoose.model('Dragon', dragonSchema);\n\nexport default Dragon;\n"
  },
  {
    "path": "models/history.js",
    "content": "import mongoose from 'mongoose';\nimport mongoosePaginate from 'mongoose-paginate-v2';\nimport idPlugin from 'mongoose-id';\n\nconst historySchema = new mongoose.Schema({\n  title: {\n    type: String,\n    default: null,\n  },\n  event_date_utc: {\n    type: String,\n    default: null,\n  },\n  event_date_unix: {\n    type: Number,\n    default: null,\n  },\n  details: {\n    type: String,\n    default: null,\n  },\n  links: {\n    article: {\n      type: String,\n      default: null,\n    },\n  },\n}, { autoCreate: true });\n\nconst index = {\n  title: 'text',\n  details: 'text',\n};\nhistorySchema.index(index);\n\nhistorySchema.plugin(mongoosePaginate);\nhistorySchema.plugin(idPlugin);\n\nconst History = mongoose.model('History', historySchema);\n\nexport default History;\n"
  },
  {
    "path": "models/index.js",
    "content": "export { default as Capsule } from './capsules.js';\nexport { default as Company } from './company.js';\nexport { default as Core } from './cores.js';\nexport { default as Crew } from './crew.js';\nexport { default as Dragon } from './dragons.js';\nexport { default as History } from './history.js';\nexport { default as Landpad } from './landpads.js';\nexport { default as Launch } from './launches.js';\nexport { default as Launchpad } from './launchpads.js';\nexport { default as Payload } from './payloads.js';\nexport { default as Roadster } from './roadster.js';\nexport { default as Rocket } from './rockets.js';\nexport { default as Ship } from './ships.js';\nexport { default as Starlink } from './starlink.js';\nexport { default as User } from './users.js';\n"
  },
  {
    "path": "models/landpads.js",
    "content": "import mongoose from 'mongoose';\nimport mongoosePaginate from 'mongoose-paginate-v2';\nimport idPlugin from 'mongoose-id';\n\nconst landpadSchema = new mongoose.Schema({\n  name: {\n    type: String,\n    default: null,\n  },\n  full_name: {\n    type: String,\n    default: null,\n  },\n  status: {\n    type: String,\n    enum: ['active', 'inactive', 'unknown', 'retired', 'lost', 'under construction'],\n    required: true,\n  },\n  type: {\n    type: String,\n    default: null,\n  },\n  locality: {\n    type: String,\n    default: null,\n  },\n  region: {\n    type: String,\n    default: null,\n  },\n  latitude: {\n    type: Number,\n    default: null,\n  },\n  longitude: {\n    type: Number,\n    default: null,\n  },\n  landing_attempts: {\n    type: Number,\n    default: 0,\n  },\n  landing_successes: {\n    type: Number,\n    default: 0,\n  },\n  wikipedia: {\n    type: String,\n    default: null,\n  },\n  details: {\n    type: String,\n    default: null,\n  },\n  launches: [{\n    type: mongoose.ObjectId,\n    ref: 'Launch',\n  }],\n  images: {\n    large: [String],\n  },\n}, { autoCreate: true });\n\nconst index = {\n  name: 'text',\n  full_name: 'text',\n  details: 'text',\n};\nlandpadSchema.index(index);\n\nlandpadSchema.plugin(mongoosePaginate);\nlandpadSchema.plugin(idPlugin);\n\nconst Landpad = mongoose.model('Landpad', landpadSchema);\n\nexport default Landpad;\n"
  },
  {
    "path": "models/launches.js",
    "content": "import mongoose from 'mongoose';\nimport mongoosePaginate from 'mongoose-paginate-v2';\nimport idPlugin from 'mongoose-id';\n\nconst launchSchema = new mongoose.Schema({\n  flight_number: {\n    type: Number,\n    required: true,\n  },\n  name: {\n    type: String,\n    unique: true,\n    required: true,\n  },\n  date_utc: {\n    type: String,\n    required: true,\n  },\n  date_unix: {\n    type: Number,\n    required: true,\n  },\n  date_local: {\n    type: String,\n    required: true,\n  },\n  date_precision: {\n    type: String,\n    required: true,\n    enum: ['half', 'quarter', 'year', 'month', 'day', 'hour'],\n  },\n  static_fire_date_utc: {\n    type: String,\n    default: null,\n  },\n  static_fire_date_unix: {\n    type: Number,\n    default: null,\n  },\n  tbd: {\n    type: Boolean,\n    default: false,\n  },\n  net: {\n    type: Boolean,\n    default: false,\n  },\n  window: {\n    type: Number,\n    default: null,\n  },\n  rocket: {\n    type: mongoose.ObjectId,\n    ref: 'Rocket',\n    default: null,\n  },\n  success: {\n    type: Boolean,\n    default: null,\n  },\n  failures: [\n    {\n      _id: false,\n      time: {\n        type: Number,\n      },\n      altitude: {\n        type: Number,\n      },\n      reason: {\n        type: String,\n      },\n    },\n  ],\n  upcoming: {\n    type: Boolean,\n    required: true,\n  },\n  details: {\n    type: String,\n    default: null,\n  },\n  fairings: {\n    reused: {\n      type: Boolean,\n      default: null,\n    },\n    recovery_attempt: {\n      type: Boolean,\n      default: null,\n    },\n    recovered: {\n      type: Boolean,\n      default: null,\n    },\n    ships: [{\n      type: mongoose.ObjectId,\n      ref: 'Ship',\n    }],\n  },\n  crew: [{\n    _id: false,\n    crew: {\n      type: mongoose.ObjectId,\n      ref: 'Crew',\n      default: null,\n    },\n    role: {\n      type: String,\n      default: null,\n    },\n  }],\n  ships: [{\n    type: mongoose.ObjectId,\n    ref: 'Ship',\n  }],\n  capsules: [{\n    type: mongoose.ObjectId,\n    ref: 'Capsule',\n  }],\n  payloads: [{\n    type: mongoose.ObjectId,\n    ref: 'Payload',\n  }],\n  launchpad: {\n    type: mongoose.ObjectId,\n    ref: 'Launchpad',\n    default: null,\n  },\n  cores: [{\n    _id: false,\n    core: {\n      type: mongoose.ObjectId,\n      ref: 'Core',\n      default: null,\n    },\n    flight: {\n      type: Number,\n      default: null,\n    },\n    gridfins: {\n      type: Boolean,\n      default: null,\n    },\n    legs: {\n      type: Boolean,\n      default: null,\n    },\n    reused: {\n      type: Boolean,\n      default: null,\n    },\n    landing_attempt: {\n      type: Boolean,\n      default: null,\n    },\n    landing_success: {\n      type: Boolean,\n      default: null,\n    },\n    landing_type: {\n      type: String,\n      default: null,\n    },\n    landpad: {\n      type: mongoose.ObjectId,\n      ref: 'Landpad',\n      default: null,\n    },\n  }],\n  links: {\n    patch: {\n      small: {\n        type: String,\n        default: null,\n      },\n      large: {\n        type: String,\n        default: null,\n      },\n    },\n    reddit: {\n      campaign: {\n        type: String,\n        default: null,\n      },\n      launch: {\n        type: String,\n        default: null,\n      },\n      media: {\n        type: String,\n        default: null,\n      },\n      recovery: {\n        type: String,\n        default: null,\n      },\n    },\n    flickr: {\n      small: [String],\n      original: [String],\n    },\n    presskit: {\n      type: String,\n      default: null,\n    },\n    webcast: {\n      type: String,\n      default: null,\n    },\n    youtube_id: {\n      type: String,\n      default: null,\n    },\n    article: {\n      type: String,\n      default: null,\n    },\n    wikipedia: {\n      type: String,\n      default: null,\n    },\n  },\n  auto_update: {\n    type: Boolean,\n    default: true,\n  },\n  launch_library_id: {\n    type: String,\n    default: null,\n  },\n}, { autoCreate: true });\n\nconst index = {\n  name: 'text',\n  details: 'text',\n};\nlaunchSchema.index(index);\n\nlaunchSchema.plugin(mongoosePaginate);\nlaunchSchema.plugin(idPlugin);\n\nconst Launch = mongoose.model('Launch', launchSchema);\n\nexport default Launch;\n"
  },
  {
    "path": "models/launchpads.js",
    "content": "import mongoose from 'mongoose';\nimport mongoosePaginate from 'mongoose-paginate-v2';\nimport idPlugin from 'mongoose-id';\n\nconst launchpadSchema = new mongoose.Schema({\n  name: {\n    type: String,\n    default: null,\n  },\n  full_name: {\n    type: String,\n    default: null,\n  },\n  status: {\n    type: String,\n    enum: ['active', 'inactive', 'unknown', 'retired', 'lost', 'under construction'],\n    required: true,\n  },\n  locality: {\n    type: String,\n    default: null,\n  },\n  region: {\n    type: String,\n    default: null,\n  },\n  timezone: {\n    type: String,\n    default: null,\n  },\n  latitude: {\n    type: Number,\n    default: null,\n  },\n  longitude: {\n    type: Number,\n    default: null,\n  },\n  launch_attempts: {\n    type: Number,\n    default: 0,\n  },\n  launch_successes: {\n    type: Number,\n    default: 0,\n  },\n  rockets: [{\n    type: mongoose.ObjectId,\n    ref: 'Rocket',\n  }],\n  launches: [{\n    type: mongoose.ObjectId,\n    ref: 'Launch',\n  }],\n  details: {\n    type: String,\n    default: null,\n  },\n  images: {\n    large: [String],\n  },\n}, { autoCreate: true });\n\nconst index = {\n  name: 'text',\n  full_name: 'text',\n  details: 'text',\n};\nlaunchpadSchema.index(index);\n\nlaunchpadSchema.plugin(mongoosePaginate);\nlaunchpadSchema.plugin(idPlugin);\n\nconst Launchpad = mongoose.model('Launchpad', launchpadSchema);\n\nexport default Launchpad;\n"
  },
  {
    "path": "models/payloads.js",
    "content": "import mongoose from 'mongoose';\nimport mongoosePaginate from 'mongoose-paginate-v2';\nimport idPlugin from 'mongoose-id';\n\nconst payloadSchema = new mongoose.Schema({\n  name: {\n    type: String,\n    default: null,\n    unique: true,\n  },\n  type: {\n    type: String,\n    default: null,\n  },\n  reused: {\n    type: Boolean,\n    default: false,\n  },\n  launch: {\n    type: mongoose.ObjectId,\n    ref: 'Launch',\n    default: null,\n  },\n  customers: [String],\n  norad_ids: [Number],\n  nationalities: [String],\n  manufacturers: [String],\n  mass_kg: {\n    type: Number,\n    default: null,\n  },\n  mass_lbs: {\n    type: Number,\n    default: null,\n  },\n  orbit: {\n    type: String,\n    default: null,\n  },\n  reference_system: {\n    type: String,\n    default: null,\n  },\n  regime: {\n    type: String,\n    default: null,\n  },\n  longitude: {\n    type: Number,\n    default: null,\n  },\n  semi_major_axis_km: {\n    type: Number,\n    default: null,\n  },\n  eccentricity: {\n    type: Number,\n    default: null,\n  },\n  periapsis_km: {\n    type: Number,\n    default: null,\n  },\n  apoapsis_km: {\n    type: Number,\n    default: null,\n  },\n  inclination_deg: {\n    type: Number,\n    default: null,\n  },\n  period_min: {\n    type: Number,\n    default: null,\n  },\n  lifespan_years: {\n    type: Number,\n    default: null,\n  },\n  epoch: {\n    type: String,\n    default: null,\n  },\n  mean_motion: {\n    type: Number,\n    default: null,\n  },\n  raan: {\n    type: Number,\n    default: null,\n  },\n  arg_of_pericenter: {\n    type: Number,\n    default: null,\n  },\n  mean_anomaly: {\n    type: Number,\n    default: null,\n  },\n  dragon: {\n    capsule: {\n      type: mongoose.ObjectId,\n      ref: 'Capsule',\n      default: null,\n    },\n    mass_returned_kg: {\n      type: Number,\n      default: null,\n    },\n    mass_returned_lbs: {\n      type: Number,\n      default: null,\n    },\n    flight_time_sec: {\n      type: Number,\n      default: null,\n    },\n    manifest: {\n      type: String,\n      default: null,\n    },\n    water_landing: {\n      type: Boolean,\n      default: null,\n    },\n    land_landing: {\n      type: Boolean,\n      default: null,\n    },\n  },\n}, { autoCreate: true });\n\nconst index = {\n  name: 'text',\n};\npayloadSchema.index(index);\n\npayloadSchema.plugin(mongoosePaginate);\npayloadSchema.plugin(idPlugin);\n\nconst Payload = mongoose.model('Payload', payloadSchema);\n\nexport default Payload;\n"
  },
  {
    "path": "models/roadster.js",
    "content": "import mongoose from 'mongoose';\nimport idPlugin from 'mongoose-id';\n\nconst roadsterSchema = new mongoose.Schema({\n  name: {\n    type: String,\n  },\n  launch_date_utc: {\n    type: String,\n  },\n  launch_date_unix: {\n    type: Number,\n  },\n  launch_mass_kg: {\n    type: Number,\n  },\n  launch_mass_lbs: {\n    type: Number,\n  },\n  norad_id: {\n    type: Number,\n  },\n  epoch_jd: {\n    type: Number,\n  },\n  orbit_type: {\n    type: String,\n  },\n  apoapsis_au: {\n    type: Number,\n  },\n  periapsis_au: {\n    type: Number,\n  },\n  semi_major_axis_au: {\n    type: Number,\n  },\n  eccentricity: {\n    type: Number,\n  },\n  inclination: {\n    type: Number,\n  },\n  longitude: {\n    type: Number,\n  },\n  periapsis_arg: {\n    type: Number,\n  },\n  period_days: {\n    type: Number,\n  },\n  speed_kph: {\n    type: Number,\n  },\n  speed_mph: {\n    type: Number,\n  },\n  earth_distance_km: {\n    type: Number,\n  },\n  earth_distance_mi: {\n    type: Number,\n  },\n  mars_distance_km: {\n    type: Number,\n  },\n  mars_distance_mi: {\n    type: Number,\n  },\n  flickr_images: [String],\n  wikipedia: {\n    type: String,\n  },\n  video: {\n    type: String,\n  },\n  details: {\n    type: String,\n  },\n}, { autoCreate: true });\n\nroadsterSchema.plugin(idPlugin);\n\nconst Roadster = mongoose.model('Roadster', roadsterSchema);\n\nexport default Roadster;\n"
  },
  {
    "path": "models/rockets.js",
    "content": "import mongoose from 'mongoose';\nimport mongoosePaginate from 'mongoose-paginate-v2';\nimport idPlugin from 'mongoose-id';\n\nconst rocketSchema = new mongoose.Schema({\n  name: {\n    type: String,\n  },\n  type: {\n    type: String,\n  },\n  active: {\n    type: Boolean,\n  },\n  stages: {\n    type: Number,\n  },\n  boosters: {\n    type: Number,\n  },\n  cost_per_launch: {\n    type: Number,\n  },\n  success_rate_pct: {\n    type: Number,\n  },\n  first_flight: {\n    type: String,\n  },\n  country: {\n    type: String,\n  },\n  company: {\n    type: String,\n  },\n  height: {\n    meters: {\n      type: Number,\n    },\n    feet: {\n      type: Number,\n    },\n  },\n  diameter: {\n    meters: {\n      type: Number,\n    },\n    feet: {\n      type: Number,\n    },\n  },\n  mass: {\n    kg: {\n      type: Number,\n    },\n    lb: {\n      type: Number,\n    },\n  },\n  payload_weights: {\n    type: [\n      mongoose.Mixed,\n    ],\n  },\n  first_stage: {\n    reusable: {\n      type: Boolean,\n    },\n    engines: {\n      type: Number,\n    },\n    fuel_amount_tons: {\n      type: Number,\n    },\n    burn_time_sec: {\n      type: Number,\n    },\n    thrust_sea_level: {\n      kN: {\n        type: Number,\n      },\n      lbf: {\n        type: Number,\n      },\n    },\n    thrust_vacuum: {\n      kN: {\n        type: Number,\n      },\n      lbf: {\n        type: Number,\n      },\n    },\n  },\n  second_stage: {\n    reusable: {\n      type: Boolean,\n    },\n    engines: {\n      type: Number,\n    },\n    fuel_amount_tons: {\n      type: Number,\n    },\n    burn_time_sec: {\n      type: Number,\n    },\n    thrust: {\n      kN: {\n        type: Number,\n      },\n      lbf: {\n        type: Number,\n      },\n    },\n    payloads: {\n      option_1: {\n        type: String,\n      },\n      composite_fairing: {\n        height: {\n          meters: {\n            type: Number,\n          },\n          feet: {\n            type: Number,\n          },\n        },\n        diameter: {\n          meters: {\n            type: Number,\n          },\n          feet: {\n            type: Number,\n          },\n        },\n      },\n    },\n  },\n  engines: {\n    number: {\n      type: Number,\n    },\n    type: {\n      type: String,\n    },\n    version: {\n      type: String,\n    },\n    layout: {\n      type: String,\n    },\n    isp: {\n      sea_level: {\n        type: Number,\n      },\n      vacuum: {\n        type: Number,\n      },\n    },\n    engine_loss_max: {\n      type: Number,\n    },\n    propellant_1: {\n      type: String,\n    },\n    propellant_2: {\n      type: String,\n    },\n    thrust_sea_level: {\n      kN: {\n        type: Number,\n      },\n      lbf: {\n        type: Number,\n      },\n    },\n    thrust_vacuum: {\n      kN: {\n        type: Number,\n      },\n      lbf: {\n        type: Number,\n      },\n    },\n    thrust_to_weight: {\n      type: Number,\n    },\n  },\n  landing_legs: {\n    number: {\n      type: Number,\n    },\n    material: {\n      type: mongoose.Mixed,\n    },\n  },\n  flickr_images: {\n    type: [\n      String,\n    ],\n  },\n  wikipedia: {\n    type: String,\n  },\n  description: {\n    type: String,\n  },\n}, { autoCreate: true });\n\nconst index = {\n  name: 'text',\n};\nrocketSchema.index(index);\n\nrocketSchema.plugin(mongoosePaginate);\nrocketSchema.plugin(idPlugin);\n\nconst Rocket = mongoose.model('Rocket', rocketSchema);\n\nexport default Rocket;\n"
  },
  {
    "path": "models/ships.js",
    "content": "import mongoose from 'mongoose';\nimport mongoosePaginate from 'mongoose-paginate-v2';\nimport idPlugin from 'mongoose-id';\n\nconst shipSchema = new mongoose.Schema({\n  name: {\n    type: String,\n    unique: true,\n    required: true,\n  },\n  legacy_id: {\n    type: String,\n    default: null,\n  },\n  model: {\n    type: String,\n    default: null,\n  },\n  type: {\n    type: String,\n    default: null,\n  },\n  roles: [\n    String,\n  ],\n  active: {\n    type: Boolean,\n    required: true,\n  },\n  imo: {\n    type: Number,\n    default: null,\n  },\n  mmsi: {\n    type: Number,\n    default: null,\n  },\n  abs: {\n    type: Number,\n    default: null,\n  },\n  class: {\n    type: Number,\n    default: null,\n  },\n  mass_kg: {\n    type: Number,\n    default: null,\n  },\n  mass_lbs: {\n    type: Number,\n    default: null,\n  },\n  year_built: {\n    type: Number,\n    default: null,\n  },\n  home_port: {\n    type: String,\n    default: null,\n  },\n  status: {\n    type: String,\n    default: null,\n  },\n  speed_kn: {\n    type: Number,\n    default: null,\n  },\n  course_deg: {\n    type: Number,\n    default: null,\n  },\n  latitude: {\n    type: Number,\n    default: null,\n  },\n  longitude: {\n    type: Number,\n    default: null,\n  },\n  last_ais_update: {\n    type: String,\n    default: null,\n  },\n  link: {\n    type: String,\n    default: null,\n  },\n  image: {\n    type: String,\n    default: null,\n  },\n  launches: [{\n    type: mongoose.ObjectId,\n    ref: 'Launch',\n  }],\n}, { autoCreate: true });\n\nconst index = {\n  name: 'text',\n};\nshipSchema.index(index);\n\nshipSchema.plugin(mongoosePaginate);\nshipSchema.plugin(idPlugin);\n\nconst Ship = mongoose.model('Ship', shipSchema);\n\nexport default Ship;\n"
  },
  {
    "path": "models/starlink.js",
    "content": "import mongoose from 'mongoose';\nimport mongoosePaginate from 'mongoose-paginate-v2';\nimport idPlugin from 'mongoose-id';\n\nconst starlinkSchema = new mongoose.Schema({\n  version: {\n    type: String,\n    default: null,\n  },\n  launch: {\n    type: mongoose.ObjectId,\n    ref: 'Launch',\n    default: null,\n  },\n  longitude: {\n    type: Number,\n    default: null,\n  },\n  latitude: {\n    type: Number,\n    default: null,\n  },\n  height_km: {\n    type: Number,\n    default: null,\n  },\n  velocity_kms: {\n    type: Number,\n    default: null,\n  },\n  spaceTrack: {\n    CCSDS_OMM_VERS: {\n      type: String,\n      default: null,\n    },\n    COMMENT: {\n      type: String,\n      default: null,\n    },\n    CREATION_DATE: {\n      type: String,\n      default: null,\n    },\n    ORIGINATOR: {\n      type: String,\n      default: null,\n    },\n    OBJECT_NAME: {\n      type: String,\n      default: null,\n    },\n    OBJECT_ID: {\n      type: String,\n      default: null,\n    },\n    CENTER_NAME: {\n      type: String,\n      default: null,\n    },\n    REF_FRAME: {\n      type: String,\n      default: null,\n    },\n    TIME_SYSTEM: {\n      type: String,\n      default: null,\n    },\n    MEAN_ELEMENT_THEORY: {\n      type: String,\n      default: null,\n    },\n    EPOCH: {\n      type: String,\n      default: null,\n    },\n    MEAN_MOTION: {\n      type: Number,\n      default: null,\n    },\n    ECCENTRICITY: {\n      type: Number,\n      default: null,\n    },\n    INCLINATION: {\n      type: Number,\n      default: null,\n    },\n    RA_OF_ASC_NODE: {\n      type: Number,\n      default: null,\n    },\n    ARG_OF_PERICENTER: {\n      type: Number,\n      default: null,\n    },\n    MEAN_ANOMALY: {\n      type: Number,\n      default: null,\n    },\n    EPHEMERIS_TYPE: {\n      type: Number,\n      default: null,\n    },\n    CLASSIFICATION_TYPE: {\n      type: String,\n      default: null,\n    },\n    NORAD_CAT_ID: {\n      type: Number,\n      default: null,\n    },\n    ELEMENT_SET_NO: {\n      type: Number,\n      default: null,\n    },\n    REV_AT_EPOCH: {\n      type: Number,\n      default: null,\n    },\n    BSTAR: {\n      type: Number,\n      default: null,\n    },\n    MEAN_MOTION_DOT: {\n      type: Number,\n      default: null,\n    },\n    MEAN_MOTION_DDOT: {\n      type: Number,\n      default: null,\n    },\n    SEMIMAJOR_AXIS: {\n      type: Number,\n      default: null,\n    },\n    PERIOD: {\n      type: Number,\n      default: null,\n    },\n    APOAPSIS: {\n      type: Number,\n      default: null,\n    },\n    PERIAPSIS: {\n      type: Number,\n      default: null,\n    },\n    OBJECT_TYPE: {\n      type: String,\n      default: null,\n    },\n    RCS_SIZE: {\n      type: String,\n      default: null,\n    },\n    COUNTRY_CODE: {\n      type: String,\n      default: null,\n    },\n    LAUNCH_DATE: {\n      type: String,\n      default: null,\n    },\n    SITE: {\n      type: String,\n      default: null,\n    },\n    DECAY_DATE: {\n      type: String,\n      default: null,\n    },\n    DECAYED: {\n      type: Number,\n      default: null,\n    },\n    FILE: {\n      type: Number,\n      default: null,\n    },\n    GP_ID: {\n      type: Number,\n      default: null,\n    },\n    TLE_LINE0: {\n      type: String,\n      default: null,\n    },\n    TLE_LINE1: {\n      type: String,\n      default: null,\n    },\n    TLE_LINE2: {\n      type: String,\n      default: null,\n    },\n  },\n}, { autoCreate: true });\n\nstarlinkSchema.plugin(mongoosePaginate);\nstarlinkSchema.plugin(idPlugin);\n\nconst Starlink = mongoose.model('Starlink', starlinkSchema);\n\nexport default Starlink;\n"
  },
  {
    "path": "models/users.js",
    "content": "import mongoose from 'mongoose';\nimport mongoosePaginate from 'mongoose-paginate-v2';\nimport idPlugin from 'mongoose-id';\n\nconst userSchema = new mongoose.Schema({\n  name: {\n    type: String,\n    default: null,\n  },\n  key: {\n    type: String,\n    required: true,\n  },\n  role: {\n    type: String,\n    required: true,\n    enum: ['superuser', 'user', 'create', 'update', 'delete'],\n  },\n}, { autoCreate: true });\n\nuserSchema.plugin(mongoosePaginate);\nuserSchema.plugin(idPlugin);\n\nconst User = mongoose.model('User', userSchema);\n\nexport default User;\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"spacex-api\",\n  \"version\": \"4.0.0\",\n  \"description\": \"Open Source REST API for data about SpaceX\",\n  \"main\": \"server.js\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"test\": \"npm run lint && npm run check-dependencies && jest --silent --verbose\",\n    \"start\": \"node server.js\",\n    \"worker\": \"node jobs/worker.js\",\n    \"lint\": \"eslint .\",\n    \"check-dependencies\": \"npx depcheck --ignores=\\\"pino-pretty\\\"\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/r-spacex/SpaceX-API\"\n  },\n  \"keywords\": [\n    \"spacex\",\n    \"space\",\n    \"rocket\",\n    \"rest-api\",\n    \"mongodb\",\n    \"koa\"\n  ],\n  \"author\": \"Jake Meyer <jakewmeyer@gmail.com>\",\n  \"license\": \"Apache-2.0\",\n  \"bugs\": {\n    \"url\": \"https://github.com/r-spacex/SpaceX-API/issues\"\n  },\n  \"homepage\": \"https://github.com/r-spacex/SpaceX-API\",\n  \"dependencies\": {\n    \"blake3\": \"^2.1.7\",\n    \"cheerio\": \"^1.0.0-rc.12\",\n    \"cron\": \"^2.1.0\",\n    \"dotenv\": \"^16.0.1\",\n    \"fuzzball\": \"^2.1.2\",\n    \"got\": \"^12.3.1\",\n    \"ioredis\": \"^5.2.3\",\n    \"koa\": \"^2.13.4\",\n    \"koa-bodyparser\": \"^4.3.0\",\n    \"koa-conditional-get\": \"^3.0.0\",\n    \"koa-etag\": \"^4.0.0\",\n    \"koa-helmet\": \"^6.1.0\",\n    \"koa-router\": \"^12.0.0\",\n    \"koa2-cors\": \"^2.0.6\",\n    \"lodash\": \"^4.17.21\",\n    \"moment-range\": \"^4.0.2\",\n    \"moment-timezone\": \"^0.5.37\",\n    \"mongoose\": \"^6.5.3\",\n    \"mongoose-id\": \"^0.1.3\",\n    \"mongoose-paginate-v2\": \"^1.7.0\",\n    \"pino\": \"^8.4.2\",\n    \"rss-parser\": \"^3.12.0\",\n    \"tle.js\": \"^4.7.1\",\n    \"tough-cookie\": \"^4.1.2\"\n  },\n  \"devDependencies\": {\n    \"@typescript-eslint/eslint-plugin\": \"^5.35.1\",\n    \"@typescript-eslint/parser\": \"^5.35.1\",\n    \"eslint\": \"^8.23.0\",\n    \"eslint-config-airbnb-base\": \"^15.0.0\",\n    \"eslint-plugin-import\": \"^2.26.0\",\n    \"eslint-plugin-jest\": \"^26.8.7\",\n    \"jest\": \"^29.0.1\"\n  },\n  \"engines\": {\n    \"node\": \">=14.16\"\n  },\n  \"jest\": {\n    \"testEnvironment\": \"node\"\n  },\n  \"volta\": {\n    \"node\": \"18.7.0\"\n  }\n}\n"
  },
  {
    "path": "routes/admin/index.js",
    "content": "export default [\n  import('./v4/index.js'),\n];\n"
  },
  {
    "path": "routes/admin/v4/index.js",
    "content": "import Router from 'koa-router';\nimport { auth, authz, cache } from '../../../middleware/index.js';\n\nconst router = new Router({\n  prefix: '/(v4|latest)/admin',\n});\n\n// Clear redis cache\nrouter.delete('/cache', auth, authz('cache:clear'), async (ctx) => {\n  try {\n    await cache.redis.flushall();\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Healthcheck\nrouter.get('/health', async (ctx) => {\n  ctx.status = 200;\n});\n\nexport default router;\n"
  },
  {
    "path": "routes/capsules/index.js",
    "content": "export default [\n  import('./v4/index.js'),\n];\n"
  },
  {
    "path": "routes/capsules/v4/index.js",
    "content": "import Router from 'koa-router';\nimport { Capsule } from '../../../models/index.js';\nimport { auth, authz, cache } from '../../../middleware/index.js';\n\nconst router = new Router({\n  prefix: '/(v4|latest)/capsules',\n});\n\n// Get all capsules\nrouter.get('/', cache(300), async (ctx) => {\n  try {\n    const result = await Capsule.find({});\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Get one capsule\nrouter.get('/:id', cache(300), async (ctx) => {\n  const result = await Capsule.findById(ctx.params.id);\n  if (!result) {\n    ctx.throw(404);\n  }\n  ctx.status = 200;\n  ctx.body = result;\n});\n\n// Query capsules\nrouter.post('/query', cache(300), async (ctx) => {\n  const { query = {}, options = {} } = ctx.request.body;\n  try {\n    const result = await Capsule.paginate(query, options);\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Create capsule\nrouter.post('/', auth, authz('capsule:create'), async (ctx) => {\n  try {\n    const capsule = new Capsule(ctx.request.body);\n    await capsule.save();\n    ctx.status = 201;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Update capsule\nrouter.patch('/:id', auth, authz('capsule:update'), async (ctx) => {\n  try {\n    await Capsule.findByIdAndUpdate(ctx.params.id, ctx.request.body, { runValidators: true });\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Delete capsule\nrouter.delete('/:id', auth, authz('capsule:delete'), async (ctx) => {\n  try {\n    await Capsule.findByIdAndDelete(ctx.params.id);\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\nexport default router;\n"
  },
  {
    "path": "routes/company/index.js",
    "content": "export default [\n  import('./v4/index.js'),\n];\n"
  },
  {
    "path": "routes/company/v4/index.js",
    "content": "import Router from 'koa-router';\nimport { Company } from '../../../models/index.js';\nimport { auth, authz, cache } from '../../../middleware/index.js';\n\nconst router = new Router({\n  prefix: '/(v4|latest)/company',\n});\n\n// Get company info\nrouter.get('/', cache(86400), async (ctx) => {\n  try {\n    const result = await Company.findOne({});\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Update company info\nrouter.patch('/:id', auth, authz('company:update'), async (ctx) => {\n  try {\n    await Company.findByIdAndUpdate(ctx.params.id, ctx.request.body, { runValidators: true });\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\nexport default router;\n"
  },
  {
    "path": "routes/cores/index.js",
    "content": "export default [\n  import('./v4/index.js'),\n];\n"
  },
  {
    "path": "routes/cores/v4/index.js",
    "content": "import Router from 'koa-router';\nimport { Core } from '../../../models/index.js';\nimport { auth, authz, cache } from '../../../middleware/index.js';\n\nconst router = new Router({\n  prefix: '/(v4|latest)/cores',\n});\n\n// Get all cores\nrouter.get('/', cache(300), async (ctx) => {\n  try {\n    const result = await Core.find({});\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Get one core\nrouter.get('/:id', cache(300), async (ctx) => {\n  const result = await Core.findById(ctx.params.id);\n  if (!result) {\n    ctx.throw(404);\n  }\n  ctx.status = 200;\n  ctx.body = result;\n});\n\n// Query cores\nrouter.post('/query', cache(300), async (ctx) => {\n  const { query = {}, options = {} } = ctx.request.body;\n  try {\n    const result = await Core.paginate(query, options);\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Create core\nrouter.post('/', auth, authz('core:create'), async (ctx) => {\n  try {\n    const core = new Core(ctx.request.body);\n    await core.save();\n    ctx.status = 201;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Update core\nrouter.patch('/:id', auth, authz('core:update'), async (ctx) => {\n  try {\n    await Core.findByIdAndUpdate(ctx.params.id, ctx.request.body, { runValidators: true });\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Delete core\nrouter.delete('/:id', auth, authz('core:delete'), async (ctx) => {\n  try {\n    await Core.findByIdAndDelete(ctx.params.id);\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\nexport default router;\n"
  },
  {
    "path": "routes/crew/index.js",
    "content": "export default [\n  import('./v4/index.js'),\n];\n"
  },
  {
    "path": "routes/crew/v4/index.js",
    "content": "import Router from 'koa-router';\nimport { Crew } from '../../../models/index.js';\nimport { auth, authz, cache } from '../../../middleware/index.js';\n\nconst router = new Router({\n  prefix: '/(v4|latest)/crew',\n});\n\n// Get all crew\nrouter.get('/', cache(300), async (ctx) => {\n  try {\n    const result = await Crew.find({});\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Get one crew member\nrouter.get('/:id', cache(300), async (ctx) => {\n  const result = await Crew.findById(ctx.params.id);\n  if (!result) {\n    ctx.throw(404);\n  }\n  ctx.status = 200;\n  ctx.body = result;\n});\n\n// Query crew members\nrouter.post('/query', cache(300), async (ctx) => {\n  const { query = {}, options = {} } = ctx.request.body;\n  try {\n    const result = await Crew.paginate(query, options);\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Create crew member\nrouter.post('/', auth, authz('crew:create'), async (ctx) => {\n  try {\n    const crew = new Crew(ctx.request.body);\n    await crew.save();\n    ctx.status = 201;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Update crew member\nrouter.patch('/:id', auth, authz('crew:update'), async (ctx) => {\n  try {\n    await Crew.findByIdAndUpdate(ctx.params.id, ctx.request.body, { runValidators: true });\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Delete crew member\nrouter.delete('/:id', auth, authz('crew:delete'), async (ctx) => {\n  try {\n    await Crew.findByIdAndDelete(ctx.params.id);\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\nexport default router;\n"
  },
  {
    "path": "routes/dragons/index.js",
    "content": "export default [\n  import('./v4/index.js'),\n];\n"
  },
  {
    "path": "routes/dragons/v4/index.js",
    "content": "import Router from 'koa-router';\nimport { Dragon } from '../../../models/index.js';\nimport { auth, authz, cache } from '../../../middleware/index.js';\n\nconst router = new Router({\n  prefix: '/(v4|latest)/dragons',\n});\n\n// Get all dragons\nrouter.get('/', cache(86400), async (ctx) => {\n  try {\n    const result = await Dragon.find({}, null, { sort: { name: 'asc' } });\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Get one dragon\nrouter.get('/:id', cache(86400), async (ctx) => {\n  const result = await Dragon.findById(ctx.params.id);\n  if (!result) {\n    ctx.throw(404);\n  }\n  ctx.status = 200;\n  ctx.body = result;\n});\n\n// Query dragons\nrouter.post('/query', cache(86400), async (ctx) => {\n  const { query = {}, options = {} } = ctx.request.body;\n  try {\n    const result = await Dragon.paginate(query, options);\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Create a dragon\nrouter.post('/', auth, authz('dragon:create'), async (ctx) => {\n  try {\n    const dragon = new Dragon(ctx.request.body);\n    await dragon.save();\n    ctx.status = 201;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Update a dragon\nrouter.patch('/:id', auth, authz('dragon:update'), async (ctx) => {\n  try {\n    await Dragon.findByIdAndUpdate(ctx.params.id, ctx.request.body, { runValidators: true });\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Delete dragon\nrouter.delete('/:id', auth, authz('dragon:delete'), async (ctx) => {\n  try {\n    await Dragon.findByIdAndDelete(ctx.params.id);\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\nexport default router;\n"
  },
  {
    "path": "routes/history/index.js",
    "content": "export default [\n  import('./v4/index.js'),\n];\n"
  },
  {
    "path": "routes/history/v4/index.js",
    "content": "import Router from 'koa-router';\nimport { History } from '../../../models/index.js';\nimport { auth, authz, cache } from '../../../middleware/index.js';\n\nconst router = new Router({\n  prefix: '/(v4|latest)/history',\n});\n\n// Get all history events\nrouter.get('/', cache(300), async (ctx) => {\n  try {\n    const result = await History.find({});\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Get one history event\nrouter.get('/:id', cache(300), async (ctx) => {\n  const result = await History.findById(ctx.params.id);\n  if (!result) {\n    ctx.throw(404);\n  }\n  ctx.status = 200;\n  ctx.body = result;\n});\n\n// Query history events\nrouter.post('/query', cache(300), async (ctx) => {\n  const { query = {}, options = {} } = ctx.request.body;\n  try {\n    const result = await History.paginate(query, options);\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Create a history event\nrouter.post('/', auth, authz('history:create'), async (ctx) => {\n  try {\n    const history = new History(ctx.request.body);\n    await history.save();\n    ctx.status = 201;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Update a history event\nrouter.patch('/:id', auth, authz('history:update'), async (ctx) => {\n  try {\n    await History.findByIdAndUpdate(ctx.params.id, ctx.request.body, { runValidators: true });\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Delete history event\nrouter.delete('/:id', auth, authz('history:delete'), async (ctx) => {\n  try {\n    await History.findByIdAndDelete(ctx.params.id);\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\nexport default router;\n"
  },
  {
    "path": "routes/index.js",
    "content": "/* eslint-disable no-restricted-syntax */\nimport Router from 'koa-router';\n\nconst FOLDERS = await Promise.all([\n  import('./admin/index.js'),\n  import('./capsules/index.js'),\n  import('./company/index.js'),\n  import('./cores/index.js'),\n  import('./crew/index.js'),\n  import('./dragons/index.js'),\n  import('./history/index.js'),\n  import('./landpads/index.js'),\n  import('./launches/index.js'),\n  import('./launchpads/index.js'),\n  import('./payloads/index.js'),\n  import('./roadster/index.js'),\n  import('./rockets/index.js'),\n  import('./ships/index.js'),\n  import('./starlink/index.js'),\n  import('./users/index.js'),\n]);\n\nconst ROUTER = new Router();\n\n// Register all routes + all versions\nexport default async () => {\n  for await (const routeFolder of FOLDERS) {\n    if (routeFolder?.default) {\n      for await (const version of routeFolder.default) {\n        ROUTER.use(version?.default?.routes());\n      }\n    }\n  }\n  return ROUTER.routes();\n};\n"
  },
  {
    "path": "routes/landpads/index.js",
    "content": "export default [\n  import('./v4/index.js'),\n];\n"
  },
  {
    "path": "routes/landpads/v4/index.js",
    "content": "import Router from 'koa-router';\nimport { Landpad } from '../../../models/index.js';\nimport { auth, authz, cache } from '../../../middleware/index.js';\n\nconst router = new Router({\n  prefix: '/(v4|latest)/landpads',\n});\n\n// Get all landpads\nrouter.get('/', cache(300), async (ctx) => {\n  try {\n    const result = await Landpad.find({});\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Get one landpad\nrouter.get('/:id', cache(300), async (ctx) => {\n  const result = await Landpad.findById(ctx.params.id);\n  if (!result) {\n    ctx.throw(404);\n  }\n  ctx.status = 200;\n  ctx.body = result;\n});\n\n// Query landpads\nrouter.post('/query', cache(300), async (ctx) => {\n  const { query = {}, options = {} } = ctx.request.body;\n  try {\n    const result = await Landpad.paginate(query, options);\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Create a landpad\nrouter.post('/', auth, authz('landpad:create'), async (ctx) => {\n  try {\n    const landpad = new Landpad(ctx.request.body);\n    await landpad.save();\n    ctx.status = 201;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Update a landpad\nrouter.patch('/:id', auth, authz('landpad:update'), async (ctx) => {\n  try {\n    await Landpad.findByIdAndUpdate(ctx.params.id, ctx.request.body, { runValidators: true });\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Delete landpad\nrouter.delete('/:id', auth, authz('landpad:delete'), async (ctx) => {\n  try {\n    await Landpad.findByIdAndDelete(ctx.params.id);\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\nexport default router;\n"
  },
  {
    "path": "routes/launches/index.js",
    "content": "export default [\n  import('./v4/index.js'),\n  import('./v5/index.js'),\n];\n"
  },
  {
    "path": "routes/launches/v4/_transform-query.js",
    "content": "import _ from 'lodash';\n\n/**\n * Transform V4 query into V5 query\n *\n * @param   {Object}    ctx       Koa context\n * @param   {function}  next      Koa next function\n * @returns {void}\n */\nexport default async (query) => {\n  const transformed = query;\n\n  if (query?.options?.populate) {\n    if (Array.isArray(query.options.populate)) {\n      query.options.populate.forEach((item, index) => {\n        if (_.isObject(item)) {\n          // Index is not user specified\n          transformed.options.populate[index].path = transformed.options.populate?.[index]?.path?.replace('crew', 'crew.crew');\n        }\n      });\n    }\n    if (_.isObject(query.options.populate) && !Array.isArray(query.options.populate)) {\n      transformed.options.populate.path = transformed.options.populate.path?.replace('crew', 'crew.crew');\n    }\n    if (_.isString(query.options.populate)) {\n      transformed.options.populate = transformed.options.populate?.replace('crew', 'crew.crew');\n    }\n  }\n\n  return transformed;\n};\n"
  },
  {
    "path": "routes/launches/v4/_transform-response.js",
    "content": "/* eslint-disable no-underscore-dangle */\nconst buildCrew = (launch) => {\n  if (Array.isArray(launch?.crew) && launch.crew.length === 0) {\n    return [];\n  }\n  return launch.crew.map((crew) => {\n    if (crew?.crew) {\n      return crew?.crew;\n    }\n    return crew;\n  });\n};\n\nexport default async (payload) => {\n  if (Array.isArray(payload)) {\n    return payload.map((launch) => {\n      if (Array.isArray(launch?.crew)) {\n        return {\n          ...launch.toObject(),\n          crew: buildCrew(launch.toObject()),\n        };\n      }\n      return {\n        ...launch.toObject(),\n      };\n    });\n  }\n  if (Array.isArray(payload?.docs)) {\n    const docs = payload.docs.map((launch) => {\n      if (Array.isArray(launch?.crew)) {\n        return {\n          ...launch.toObject(),\n          crew: buildCrew(launch.toObject()),\n        };\n      }\n      return {\n        ...launch.toObject(),\n      };\n    });\n    return {\n      ...payload,\n      docs,\n    };\n  }\n  return {\n    ...payload.toObject(),\n    crew: buildCrew(payload.toObject()),\n  };\n};\n"
  },
  {
    "path": "routes/launches/v4/index.js",
    "content": "import Router from 'koa-router';\nimport { Launch } from '../../../models/index.js';\nimport { auth, authz, cache } from '../../../middleware/index.js';\nimport transformResponse from './_transform-response.js';\nimport transformQuery from './_transform-query.js';\n\nconst router = new Router({\n  prefix: '/v4/launches',\n});\n\n//\n// Convenience Endpoints\n//\n\n// Get past launches\nrouter.get('/past', cache(20), async (ctx) => {\n  try {\n    const result = await Launch.find({\n      upcoming: false,\n    }, null, {\n      sort: {\n        flight_number: 'asc',\n      },\n    });\n    ctx.status = 200;\n    ctx.body = await transformResponse(result);\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Get upcoming launches\nrouter.get('/upcoming', cache(20), async (ctx) => {\n  try {\n    const result = await Launch.find({\n      upcoming: true,\n    }, null, {\n      sort: {\n        flight_number: 'asc',\n      },\n    });\n    ctx.status = 200;\n    ctx.body = await transformResponse(result);\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Get latest launch\nrouter.get('/latest', cache(20), async (ctx) => {\n  try {\n    const result = await Launch.findOne({\n      upcoming: false,\n    }, null, {\n      sort: {\n        flight_number: 'desc',\n      },\n    });\n    ctx.status = 200;\n    ctx.body = await transformResponse(result);\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Get next launch\nrouter.get('/next', cache(20), async (ctx) => {\n  try {\n    const result = await Launch.findOne({\n      upcoming: true,\n    }, null, {\n      sort: {\n        flight_number: 'asc',\n      },\n    });\n    ctx.status = 200;\n    ctx.body = await transformResponse(result);\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n//\n// Standard Endpoints\n//\n\n// Get all launches\nrouter.get('/', cache(20), async (ctx) => {\n  try {\n    const result = await Launch.find({}, null, {\n      sort: {\n        flight_number: 'asc',\n      },\n    });\n    ctx.status = 200;\n    ctx.body = await transformResponse(result);\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Get one launch\nrouter.get('/:id', cache(20), async (ctx) => {\n  const result = await Launch.findById(ctx.params.id);\n  if (!result) {\n    ctx.throw(404);\n  }\n  ctx.status = 200;\n  ctx.body = await transformResponse(result);\n});\n\n// Query launches\nrouter.post('/query', cache(20), async (ctx) => {\n  const { query = {}, options = {} } = await transformQuery(ctx.request.body);\n  try {\n    const result = await Launch.paginate(query, options);\n    ctx.status = 200;\n    ctx.body = await transformResponse(result);\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Create a launch\nrouter.post('/', auth, authz('launch:create'), async (ctx) => {\n  try {\n    const launch = new Launch(ctx.request.body);\n    await launch.save();\n    ctx.status = 201;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Update a launch\nrouter.patch('/:id', auth, authz('launch:update'), async (ctx) => {\n  try {\n    await Launch.findByIdAndUpdate(ctx.params.id, ctx.request.body, {\n      runValidators: true,\n    });\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Delete a launch\nrouter.delete('/:id', auth, authz('launch:delete'), async (ctx) => {\n  try {\n    await Launch.findByIdAndDelete(ctx.params.id);\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\nexport default router;\n"
  },
  {
    "path": "routes/launches/v5/index.js",
    "content": "import Router from 'koa-router';\nimport { Launch } from '../../../models/index.js';\nimport { auth, authz, cache } from '../../../middleware/index.js';\n\nconst router = new Router({\n  prefix: '/(v5|latest)/launches',\n});\n\n//\n// Convenience Endpoints\n//\n\n// Get past launches\nrouter.get('/past', cache(20), async (ctx) => {\n  try {\n    const result = await Launch.find({\n      upcoming: false,\n    }, null, {\n      sort: {\n        flight_number: 'asc',\n      },\n    });\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Get upcoming launches\nrouter.get('/upcoming', cache(20), async (ctx) => {\n  try {\n    const result = await Launch.find({\n      upcoming: true,\n    }, null, {\n      sort: {\n        flight_number: 'asc',\n      },\n    });\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Get latest launch\nrouter.get('/latest', cache(20), async (ctx) => {\n  try {\n    const result = await Launch.findOne({\n      upcoming: false,\n    }, null, {\n      sort: {\n        flight_number: 'desc',\n      },\n    });\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Get next launch\nrouter.get('/next', cache(20), async (ctx) => {\n  try {\n    const result = await Launch.findOne({\n      upcoming: true,\n    }, null, {\n      sort: {\n        flight_number: 'asc',\n      },\n    });\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n//\n// Standard Endpoints\n//\n\n// Get all launches\nrouter.get('/', cache(20), async (ctx) => {\n  try {\n    const result = await Launch.find({}, null, {\n      sort: {\n        flight_number: 'asc',\n      },\n    });\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Get one launch\nrouter.get('/:id', cache(20), async (ctx) => {\n  const result = await Launch.findById(ctx.params.id);\n  if (!result) {\n    ctx.throw(404);\n  }\n  ctx.status = 200;\n  ctx.body = result;\n});\n\n// Query launches\nrouter.post('/query', cache(20), async (ctx) => {\n  const { query = {}, options = {} } = ctx.request.body;\n  try {\n    const result = await Launch.paginate(query, options);\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Create a launch\nrouter.post('/', auth, authz('launch:create'), async (ctx) => {\n  try {\n    const launch = new Launch(ctx.request.body);\n    await launch.save();\n    ctx.status = 201;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Update a launch\nrouter.patch('/:id', auth, authz('launch:update'), async (ctx) => {\n  try {\n    await Launch.findByIdAndUpdate(ctx.params.id, ctx.request.body, {\n      runValidators: true,\n    });\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Delete a launch\nrouter.delete('/:id', auth, authz('launch:delete'), async (ctx) => {\n  try {\n    await Launch.findByIdAndDelete(ctx.params.id);\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\nexport default router;\n"
  },
  {
    "path": "routes/launchpads/index.js",
    "content": "export default [\n  import('./v4/index.js'),\n];\n"
  },
  {
    "path": "routes/launchpads/v4/index.js",
    "content": "import Router from 'koa-router';\nimport { Launchpad } from '../../../models/index.js';\nimport { auth, authz, cache } from '../../../middleware/index.js';\n\nconst router = new Router({\n  prefix: '/(v4|latest)/launchpads',\n});\n\n// Get all launchpads\nrouter.get('/', cache(300), async (ctx) => {\n  try {\n    const result = await Launchpad.find({});\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Get one launchpad\nrouter.get('/:id', cache(300), async (ctx) => {\n  const result = await Launchpad.findById(ctx.params.id);\n  if (!result) {\n    ctx.throw(404);\n  }\n  ctx.status = 200;\n  ctx.body = result;\n});\n\n// Query launchpads\nrouter.post('/query', cache(300), async (ctx) => {\n  const { query = {}, options = {} } = ctx.request.body;\n  try {\n    const result = await Launchpad.paginate(query, options);\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Create a launchpad\nrouter.post('/', auth, authz('launchpad:create'), async (ctx) => {\n  try {\n    const launchpad = new Launchpad(ctx.request.body);\n    await launchpad.save();\n    ctx.status = 201;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Update a launchpad\nrouter.patch('/:id', auth, authz('launchpad:update'), async (ctx) => {\n  try {\n    await Launchpad.findByIdAndUpdate(ctx.params.id, ctx.request.body, { runValidators: true });\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Delete a launchpad\nrouter.delete('/:id', auth, authz('launchpad:delete'), async (ctx) => {\n  try {\n    await Launchpad.findByIdAndDelete(ctx.params.id);\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\nexport default router;\n"
  },
  {
    "path": "routes/payloads/index.js",
    "content": "export default [\n  import('./v4/index.js'),\n];\n"
  },
  {
    "path": "routes/payloads/v4/index.js",
    "content": "import Router from 'koa-router';\nimport { Payload } from '../../../models/index.js';\nimport { auth, authz, cache } from '../../../middleware/index.js';\n\nconst router = new Router({\n  prefix: '/(v4|latest)/payloads',\n});\n\n// Get all payloads\nrouter.get('/', cache(300), async (ctx) => {\n  try {\n    const result = await Payload.find({});\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Get one payload\nrouter.get('/:id', cache(300), async (ctx) => {\n  const result = await Payload.findById(ctx.params.id);\n  if (!result) {\n    ctx.throw(404);\n  }\n  ctx.status = 200;\n  ctx.body = result;\n});\n\n// Query payloads\nrouter.post('/query', cache(300), async (ctx) => {\n  const { query = {}, options = {} } = ctx.request.body;\n  try {\n    const result = await Payload.paginate(query, options);\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Create a payload\nrouter.post('/', auth, authz('payload:create'), async (ctx) => {\n  try {\n    const payload = new Payload(ctx.request.body);\n    await payload.save();\n    ctx.status = 201;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Update a payload\nrouter.patch('/:id', auth, authz('payload:update'), async (ctx) => {\n  try {\n    await Payload.findByIdAndUpdate(ctx.params.id, ctx.request.body, {\n      runValidators: true,\n    });\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Delete a payload\nrouter.delete('/:id', auth, authz('payload:delete'), async (ctx) => {\n  try {\n    await Payload.findByIdAndDelete(ctx.params.id);\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\nexport default router;\n"
  },
  {
    "path": "routes/roadster/index.js",
    "content": "export default [\n  import('./v4/index.js'),\n];\n"
  },
  {
    "path": "routes/roadster/v4/index.js",
    "content": "import Router from 'koa-router';\nimport { Roadster } from '../../../models/index.js';\nimport { auth, authz, cache } from '../../../middleware/index.js';\n\nconst router = new Router({\n  prefix: '/(v4|latest)/roadster',\n});\n\n// Get roadster\nrouter.get('/', cache(300), async (ctx) => {\n  try {\n    const result = await Roadster.findOne({});\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Query roadster\nrouter.post('/query', cache(300), async (ctx) => {\n  const { query = {}, options = { select: '' } } = ctx.request.body;\n  try {\n    const result = await Roadster.findOne(query).select(options.select).exec();\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Update roadster\nrouter.patch('/:id', auth, authz('roadster:update'), async (ctx) => {\n  try {\n    await Roadster.findByIdAndUpdate(ctx.params.id, ctx.request.body, { runValidators: true });\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\nexport default router;\n"
  },
  {
    "path": "routes/rockets/index.js",
    "content": "export default [\n  import('./v4/index.js'),\n];\n"
  },
  {
    "path": "routes/rockets/v4/index.js",
    "content": "import Router from 'koa-router';\nimport { Rocket } from '../../../models/index.js';\nimport { auth, authz, cache } from '../../../middleware/index.js';\n\nconst router = new Router({\n  prefix: '/(v4|latest)/rockets',\n});\n\n// Get all rockets\nrouter.get('/', cache(86400), async (ctx) => {\n  try {\n    const result = await Rocket.find({}, null, { sort: { first_flight: 'asc' } });\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Get one rocket\nrouter.get('/:id', cache(86400), async (ctx) => {\n  const result = await Rocket.findById(ctx.params.id);\n  if (!result) {\n    ctx.throw(404);\n  }\n  ctx.status = 200;\n  ctx.body = result;\n});\n\n// Query rocket\nrouter.post('/query', cache(300), async (ctx) => {\n  const { query = {}, options = {} } = ctx.request.body;\n  try {\n    const result = await Rocket.paginate(query, options);\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Create a rocket\nrouter.post('/', auth, authz('rocket:create'), async (ctx) => {\n  try {\n    const rocket = new Rocket(ctx.request.body);\n    await rocket.save();\n    ctx.status = 201;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Update a rocket\nrouter.patch('/:id', auth, authz('rocket:update'), async (ctx) => {\n  try {\n    await Rocket.findByIdAndUpdate(ctx.params.id, ctx.request.body, { runValidators: true });\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Delete a rocket\nrouter.delete('/:id', auth, authz('rocket:delete'), async (ctx) => {\n  try {\n    await Rocket.findByIdAndDelete(ctx.params.id);\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\nexport default router;\n"
  },
  {
    "path": "routes/ships/index.js",
    "content": "export default [\n  import('./v4/index.js'),\n];\n"
  },
  {
    "path": "routes/ships/v4/index.js",
    "content": "import Router from 'koa-router';\nimport { Ship } from '../../../models/index.js';\nimport { auth, authz, cache } from '../../../middleware/index.js';\n\nconst router = new Router({\n  prefix: '/(v4|latest)/ships',\n});\n\n// Get all ships\nrouter.get('/', cache(300), async (ctx) => {\n  try {\n    const result = await Ship.find({});\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Get one ship\nrouter.get('/:id', cache(300), async (ctx) => {\n  const result = await Ship.findById(ctx.params.id);\n  if (!result) {\n    ctx.throw(404);\n  }\n  ctx.status = 200;\n  ctx.body = result;\n});\n\n// Query ships\nrouter.post('/query', cache(300), async (ctx) => {\n  const { query = {}, options = {} } = ctx.request.body;\n  try {\n    const result = await Ship.paginate(query, options);\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Create a ship\nrouter.post('/', auth, authz('ship:create'), async (ctx) => {\n  try {\n    const ship = new Ship(ctx.request.body);\n    await ship.save();\n    ctx.status = 201;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Update a ship\nrouter.patch('/:id', auth, authz('ship:update'), async (ctx) => {\n  try {\n    await Ship.findByIdAndUpdate(ctx.params.id, ctx.request.body, { runValidators: true });\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Delete a ship\nrouter.delete('/:id', auth, authz('ship:delete'), async (ctx) => {\n  try {\n    await Ship.findByIdAndDelete(ctx.params.id);\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\nexport default router;\n"
  },
  {
    "path": "routes/starlink/index.js",
    "content": "export default [\n  import('./v4/index.js'),\n];\n"
  },
  {
    "path": "routes/starlink/v4/index.js",
    "content": "import Router from 'koa-router';\nimport { Starlink } from '../../../models/index.js';\nimport { auth, authz, cache } from '../../../middleware/index.js';\n\nconst router = new Router({\n  prefix: '/(v4|latest)/starlink',\n});\n\n// Get all Starlink satellites\nrouter.get('/', cache(3600), async (ctx) => {\n  try {\n    const result = await Starlink.find({});\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Get one Starlink satellite\nrouter.get('/:id', cache(3600), async (ctx) => {\n  const result = await Starlink.findById(ctx.params.id);\n  if (!result) {\n    ctx.throw(404);\n  }\n  ctx.status = 200;\n  ctx.body = result;\n});\n\n// Query Starlink satellites\nrouter.post('/query', cache(3600), async (ctx) => {\n  const { query = {}, options = {} } = ctx.request.body;\n  try {\n    const result = await Starlink.paginate(query, options);\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Create a Starlink satellite\nrouter.post('/', auth, authz('starlink:create'), async (ctx) => {\n  try {\n    const ship = new Starlink(ctx.request.body);\n    await ship.save();\n    ctx.status = 201;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Update a Starlink satellite\nrouter.patch('/:norad_id', auth, authz('starlink:update'), async (ctx) => {\n  try {\n    await Starlink.findOneAndUpdate({ 'spaceTrack.NORAD_CAT_ID': ctx.params.norad_id }, ctx.request.body, {\n      runValidators: true,\n      setDefaultsOnInsert: true,\n      upsert: true,\n    });\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Delete a Starlink satellite\nrouter.delete('/:id', auth, authz('starlink:delete'), async (ctx) => {\n  try {\n    await Starlink.findByIdAndDelete(ctx.params.id);\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\nexport default router;\n"
  },
  {
    "path": "routes/users/index.js",
    "content": "export default [\n  import('./v4/index.js'),\n];\n"
  },
  {
    "path": "routes/users/v4/index.js",
    "content": "import Router from 'koa-router';\nimport { User } from '../../../models/index.js';\nimport { auth, authz } from '../../../middleware/index.js';\n\nconst router = new Router({\n  prefix: '/(v4|latest)/users',\n});\n\n// Get all users\nrouter.get('/', auth, authz('user:list'), async (ctx) => {\n  try {\n    const result = await User.find({});\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Get one user\nrouter.get('/:id', auth, authz('user:one'), async (ctx) => {\n  const result = await User.findById(ctx.params.id);\n  if (!result) {\n    ctx.throw(404);\n  }\n  ctx.status = 200;\n  ctx.body = result;\n});\n\n// Query users\nrouter.post('/query', auth, authz('user:query'), async (ctx) => {\n  const { query = {}, options = {} } = ctx.request.body;\n  try {\n    const result = await User.paginate(query, options);\n    ctx.status = 200;\n    ctx.body = result;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Create a user\nrouter.post('/', auth, authz('user:create'), async (ctx) => {\n  try {\n    const user = new User(ctx.request.body);\n    await user.save();\n    ctx.status = 201;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Update a user\nrouter.patch('/:id', auth, authz('user:update'), async (ctx) => {\n  try {\n    await User.findByIdAndUpdate(ctx.params.id, ctx.request.body, {\n      runValidators: true,\n    });\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\n// Delete a user\nrouter.delete('/:id', auth, authz('user:delete'), async (ctx) => {\n  try {\n    await User.findByIdAndDelete(ctx.params.id);\n    ctx.status = 200;\n  } catch (error) {\n    ctx.throw(400, error.message);\n  }\n});\n\nexport default router;\n"
  },
  {
    "path": "server.js",
    "content": "import http from 'http';\nimport mongoose from 'mongoose';\nimport logger from './middleware/logger.js';\nimport { DEFAULT_PORT } from './lib/constants.js';\nimport app from './app.js';\n\nconst PORT = process.env.PORT ?? DEFAULT_PORT;\nconst SERVER = http.createServer(app.callback());\n\n// Gracefully close Mongo connection\nconst gracefulShutdown = (msg) => {\n  logger.info(`Shutdown initiated: ${msg}`);\n  mongoose.connection.close(false, () => {\n    logger.info('Mongo closed');\n    SERVER.close(() => {\n      logger.info('Shutting down...');\n      process.exit();\n    });\n  });\n};\n\n// Server start\nSERVER.listen(PORT, '0.0.0.0', () => {\n  logger.info(`Running on port: ${PORT}`);\n\n  // Handle kill commands\n  process.on('SIGTERM', gracefulShutdown);\n\n  // Handle interrupts\n  process.on('SIGINT', gracefulShutdown);\n\n  // Prevent dirty exit on uncaught exceptions:\n  process.on('uncaughtException', gracefulShutdown);\n\n  // Prevent dirty exit on unhandled promise rejection\n  process.on('unhandledRejection', gracefulShutdown);\n});\n"
  },
  {
    "path": "start.sh",
    "content": "#!/usr/bin/env sh\n\nif [ \"$SPACEX_WORKER\" = \"true\" ]; then\n  node ./jobs/worker.js\nelse\n  node ./server.js\nfi\n"
  },
  {
    "path": "tests/index.test.js",
    "content": "it('should expect true to be true', () => {\n  expect(true).toBe(true);\n});\n"
  }
]