├── .babelrc ├── .eslintrc ├── .gitignore ├── LICENSE ├── README.md ├── docs └── example-query.png ├── example ├── index.js └── static │ └── index.html ├── package.json ├── src ├── adapters │ ├── Adapter.js │ ├── connectedVehicleStatus.js │ └── electricVehicleStatus.js ├── authContext.js ├── index.js ├── resolvers │ ├── index.js │ └── vehicle.js ├── scopes.js └── typeDefs │ ├── index.js │ └── vehicle.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env" 4 | ], 5 | "plugins": [ 6 | ["@babel/plugin-transform-runtime", 7 | { 8 | "regenerator": true 9 | } 10 | ] 11 | ], 12 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@callstack" 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | build-example -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Callstack 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # benz-ql 2 | 3 | An Apollo GraphQL server for the [Mercedes-Benz REST APIs](https://developer.mercedes-benz.com/). Allows to query them via a single endpoint, using GraphQL. 4 | 5 | ```graphql 6 | query { 7 | getVehicle(id: "1234567890ABCD1234") { 8 | licenseplate 9 | stateofcharge { 10 | value 11 | } 12 | location { 13 | longitude { 14 | value 15 | } 16 | } 17 | } 18 | } 19 | ``` 20 | 21 | ## Try it now 22 | 23 | You can explore the query response by playing around with the [online playground](https://benz-ql.herokuapp.com/). 24 | 25 | ## Getting started 26 | 27 | To get started, install the package from the registry: 28 | 29 | ```bash 30 | $ yarn add benz-ql 31 | ``` 32 | 33 | ## Configuraton 34 | 35 | ### Setting up the server 36 | 37 | This package can be used with different server frameworks supported by Apollo. In this example, we will be using Express. 38 | 39 | ```js 40 | import express from "express"; 41 | import { ApolloServer } from "apollo-server-express"; 42 | import benzQL, { scopes } from "benz-ql"; 43 | 44 | const app = express(); 45 | const PORT = 3000; 46 | const server = new ApolloServer(benzQL(scopes.SANDBOX)); // chose your environment - SANDBOX or PROD 47 | 48 | server.applyMiddleware({ app, path: "/benz-ql" }); 49 | 50 | app.listen(PORT, () => { 51 | console.log(`App listen on ${PORT}`); 52 | console.log(`Benz-ql avaliable on ${server.graphqlPath}`); 53 | }); 54 | ``` 55 | 56 | ### Connecting from the client 57 | 58 | Once you set up a development server and start it successfuly, you can connect with it by using GraphQL client of your choice. In this section, we are using a `apollo-boost` package in context of a React Native application. 59 | 60 | > Note: You will need a token for accessing Mercedes APIs. Please consult [official documentation](https://developer.mercedes-benz.com/apis) for steps to do so. 61 | 62 | Below example demonstrates accessing battery level from the API, including potential error handling. 63 | 64 | ```js 65 | import React from "react"; 66 | import { View, Text } from "react-native"; 67 | import ApolloClient, { gql } from "apollo-boost"; 68 | 69 | const client = new ApolloClient({ 70 | uri: "http://localhost:3000/benz-ql", 71 | headers: { 72 | authorization: "a1b2c3d4-a1b2-a1b2-a1b2-a1b2c3d4e5f6" 73 | } 74 | }); 75 | 76 | class Home extends React.Component { 77 | state = { batteryStatus: "unknown" }; 78 | 79 | async componentDidMount() { 80 | const { data, error } = await client.query({ 81 | query: gql` 82 | query { 83 | getVehicle(id: "1234567890ABCD1234") { 84 | stateofcharge { 85 | value 86 | } 87 | } 88 | } 89 | ` 90 | }); 91 | 92 | if (!error) { 93 | this.setState({ 94 | batteryStatus: data.getVehicle.stateofcharge.value 95 | }); 96 | } 97 | } 98 | 99 | render() { 100 | const { batteryStatus } = this.state; 101 | return ( 102 | 103 | Battery Status: {batteryStatus} 104 | 105 | ); 106 | } 107 | } 108 | ``` 109 | 110 | ## Contributing 111 | 112 | ### Run example server 113 | 114 | This project comes with a development server that is useful for debugging and developing the library. In order to get started, follow the steps below: 115 | 116 | 1. Clone this repo 117 | 2. `yarn install` 118 | 3. `cd example` 119 | 4. `node index.js` 120 | 5. Open [http://localhost:3000](http://localhost:3000) 121 | 122 | > Note: Example server uses sandbox environment 123 | 124 | ## Coverage table 125 | 126 | Here is the list of supported APIs. Contributions are welcome to support the remaining list. 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 |
API NameAPI ResourceStatus
Connected Vehicle (experimental)Vehiclesimplemented
Tiresimplemented
Doorsimplemented
Locationimplemented
Odometerimplemented
Fuelimplemented
State of Chargeimplemented
Car ConfiguratorReferencespending
Configurationspending
Imagespending
Saved configurationspending
DealerDealer searchpending
Referencespending
Electric Vehicle StatusContainer Electric Vehicle Statuspending
Resourcespending
State of charge resourcepending
Range electric resourcepending
Fuel StatusContainer Fuel Statuspending
Resourcespending
Tank level resourcepending
Range liquid resourcepending
Pay As You Drive InsuranceContainer Pay As You Drive Insurancepending
Resourcespending
Odometer resourcepending
Remote Diagnostic SupportResourcespending
Electronical Control Units (ECU's)pending
Diagnostic Trouble Codes (DTC's)pending
Diagnostic Trouble Code (DTC) Snapshotspending
Vehicle ImagesVehicle Images Basicpending
Vehicle Images 360pending
Vehicle Lock StatusResourcespending
Door Lock Status Resourcepending
Door Lock Deck Lid Status Resourcepending
Door Lock Gas Status Resourcepending
Position Heading Resourcepending
Vehicle StatusContainer Vehicle Statuspending
Resourcespending
Decklid resourcepending
Front left door resourcepending
Front right door resourcepending
Rear left door resourcepending
Rear right door resourcepending
Interior front light resourcepending
Interior rear light resourcepending
Light switch position resourcepending
Front left reading lamp resourcepending
Front right reading lamp resourcepending
Convertible (roof top) resourcepending
Sunroof resourcepending
Front left windows resourcepending
Front right windows resourcepending
Rear left windows resourcepending
Rear right windows resourcepending
357 | 358 | ## Made with ❤️ at Callstack 359 | 360 | This is an open source project and will always remain free to use. If you think it's cool, please star it 🌟. [Callstack](https://callstack.com) is a group of React and React Native geeks, contact us at [hello@callstack.com](mailto:hello@callstack.com) if you need any help with these or just want to say hi! 361 | -------------------------------------------------------------------------------- /docs/example-query.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/callstack/benz-ql/9ae8859c63eaf49a1f4f8b0d9f4923eca28411f6/docs/example-query.png -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | import express from 'express'; 3 | import { ApolloServer } from 'apollo-server-express'; 4 | import fs from 'fs'; 5 | import path from 'path'; 6 | 7 | import benzQL, { scopes } from '../src/index.js'; 8 | 9 | const app = express(); 10 | const PORT = process.env.PORT || 3000; 11 | const server = new ApolloServer({ 12 | ...benzQL(scopes.SANDBOX), 13 | introspection: true, 14 | }); 15 | 16 | server.applyMiddleware({ app, path: '/benz-ql' }); 17 | 18 | app.use('*', (req, res) => { 19 | const stream = fs.createReadStream(path.join(__dirname, 'static/index.html')); 20 | stream.pipe(res); 21 | }); 22 | 23 | app.listen(PORT, () => { 24 | console.log(`App listen on ${PORT}`); 25 | console.log(`Benz-ql avaliable on ${server.graphqlPath}`); 26 | }); 27 | -------------------------------------------------------------------------------- /example/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | GraphQL Playground 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 46 | 47 |
Loading 48 | GraphQL Playground 49 |
50 |
51 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "benz-ql", 3 | "version": "1.0.0", 4 | "description": "GraphQL wrapper over Mercedess-Benz REST API.", 5 | "main": "index.js", 6 | "license": "MIT", 7 | "scripts": { 8 | "lint": "eslint ./src/*.js", 9 | "lintFix": "eslint ./src/*.js --fix", 10 | "dev": "nodemon --exec babel-node example/index.js", 11 | "build-example": "mkdir build-example && babel src -d build-example/src && babel example -d build-example/example && cp -r example/static build-example/example", 12 | "start": "node build-example/example/index.js", 13 | "heroku-postbuild": "yarn build-example && yarn add express apollo-server-express" 14 | }, 15 | "dependencies": { 16 | "@babel/runtime": "^7.5.5", 17 | "graphql": "^14.4.2", 18 | "graphql-tag": "^2.10.1", 19 | "node-fetch": "^2.6.0", 20 | "express": "^4.17.1", 21 | "apollo-server-express": "^2.8.2" 22 | }, 23 | "devDependencies": { 24 | "@babel/cli": "^7.0.0", 25 | "@babel/core": "^7.5.5", 26 | "@babel/node": "^7.0.0", 27 | "@babel/plugin-transform-runtime": "^7.5.5", 28 | "@babel/preset-env": "^7.0.0", 29 | "@callstack/eslint-config": "^7.0.0", 30 | "eslint": "^6.2.1", 31 | "nodemon": "^1.19.1" 32 | }, 33 | "engines": { 34 | "node": "10.x", 35 | "yarn": "1.x" 36 | } 37 | } -------------------------------------------------------------------------------- /src/adapters/Adapter.js: -------------------------------------------------------------------------------- 1 | import nodeFetch from 'node-fetch'; 2 | import scopes from '../scopes'; 3 | 4 | class Adapter { 5 | constructor(sandboxUrl, prodUrl) { 6 | this.scope = scopes.SANDBOX; 7 | this.token = ''; 8 | this.SANDBOX_URL = sandboxUrl; 9 | this.PROD_URL = prodUrl; 10 | } 11 | 12 | getUrl() { 13 | if (this.scope === scopes.PROD) { 14 | return this.PROD_URL; 15 | } 16 | return this.SANDBOX_URL; 17 | } 18 | 19 | performRequest(url) { 20 | return nodeFetch(this.getUrl() + url, { 21 | headers: { 22 | authorization: `Bearer ${this.token}`, 23 | }, 24 | }).then(res => res.json()); 25 | } 26 | 27 | withScope(scope) { 28 | this.scope = scope; 29 | return this; 30 | } 31 | 32 | withToken(token) { 33 | this.token = token; 34 | return this; 35 | } 36 | } 37 | 38 | export default Adapter; 39 | -------------------------------------------------------------------------------- /src/adapters/connectedVehicleStatus.js: -------------------------------------------------------------------------------- 1 | import Adapter from './Adapter'; 2 | 3 | const SANDBOX_URL = 4 | 'https://api.mercedes-benz.com/experimental/connectedvehicle_tryout/v1'; 5 | const PROD_URL = 6 | 'https://api.mercedes-benz.com/experimental/connectedvehicle/v1'; 7 | 8 | class ElectricVehicleStatus extends Adapter { 9 | constructor(sandboxUrl, prodUrl) { 10 | super(sandboxUrl, prodUrl); 11 | } 12 | 13 | async get_allvehicles() { 14 | return this.performRequest(`/vehicles`); 15 | } 16 | 17 | async get_vehicleinfo(vehicleID) { 18 | return this.performRequest(`/vehicles/${vehicleID}`); 19 | } 20 | 21 | async get_stateofcharge(vehicleID) { 22 | return this.performRequest(`/vehicles/${vehicleID}/stateofcharge`); 23 | } 24 | 25 | async get_location(vehicleID) { 26 | return this.performRequest(`/vehicles/${vehicleID}/location`); 27 | } 28 | 29 | async get_tires(vehicleID) { 30 | return this.performRequest(`/vehicles/${vehicleID}/tires`); 31 | } 32 | 33 | async get_doors(vehicleID) { 34 | return this.performRequest(`/vehicles/${vehicleID}/doors`); 35 | } 36 | 37 | async get_odometer(vehicleID) { 38 | return this.performRequest(`/vehicles/${vehicleID}/odometer`); 39 | } 40 | 41 | async get_fuel(vehicleID) { 42 | return this.performRequest(`/vehicles/${vehicleID}/fuel`); 43 | } 44 | } 45 | 46 | export default new ElectricVehicleStatus(SANDBOX_URL, PROD_URL); 47 | -------------------------------------------------------------------------------- /src/adapters/electricVehicleStatus.js: -------------------------------------------------------------------------------- 1 | import Adapter from './Adapter'; 2 | 3 | const SANDBOX_URL = 'https://api.mercedes-benz.com/vehicledata_tryout/v1'; 4 | const PROD_URL = 'https://api.mercedes-benz.com/vehicledata/v1'; 5 | 6 | class ElectricVehicleStatus extends Adapter { 7 | constructor(sandboxUrl, prodUrl) { 8 | super(sandboxUrl, prodUrl); 9 | } 10 | 11 | async get_soc(vehicleID) { 12 | return this.performRequest(`/vehicles/${vehicleID}/resources/soc`); 13 | } 14 | 15 | async get_rangeelectric(vehicleID) { 16 | return this.performRequest( 17 | `/vehicles/${vehicleID}/resources/rangeelectric` 18 | ); 19 | } 20 | } 21 | 22 | export default new ElectricVehicleStatus(SANDBOX_URL, PROD_URL); 23 | -------------------------------------------------------------------------------- /src/authContext.js: -------------------------------------------------------------------------------- 1 | export default ({ req }) => ({ 2 | authToken: req.headers.authorization, 3 | }); 4 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import typeDefs from './typeDefs'; 2 | import resolvers from './resolvers'; 3 | import authContext from './authContext'; 4 | import _scopes from './scopes'; 5 | 6 | export const scopes = _scopes; 7 | 8 | export default (scope = _scopes.PROD) => ({ 9 | typeDefs: typeDefs, 10 | resolvers: resolvers(scope), 11 | context: authContext, 12 | }); 13 | -------------------------------------------------------------------------------- /src/resolvers/index.js: -------------------------------------------------------------------------------- 1 | import vehicleResolver from './vehicle'; 2 | import scopes from '../scopes'; 3 | 4 | export default (scope = scopes.PROD) => [vehicleResolver(scope)]; 5 | -------------------------------------------------------------------------------- /src/resolvers/vehicle.js: -------------------------------------------------------------------------------- 1 | import ConnectedVehicleStatus from '../adapters/connectedVehicleStatus'; 2 | 3 | export default scope => { 4 | const ConnectedVehicleStatusAdapter = ConnectedVehicleStatus.withScope(scope); 5 | return { 6 | Query: { 7 | getVehicle: async (parent, { id: vehicleID }, { authToken }) => { 8 | const data = await ConnectedVehicleStatusAdapter.withToken( 9 | authToken 10 | ).get_vehicleinfo(vehicleID); 11 | return data; 12 | }, 13 | vehicles: async (_, __, { authToken }) => { 14 | const data = await ConnectedVehicleStatusAdapter.withToken( 15 | authToken 16 | ).get_allvehicles(); 17 | return data; 18 | }, 19 | }, 20 | Vehicle: { 21 | stateofcharge: async ({ id: vehicleID }, _, { authToken }) => { 22 | const { stateofcharge } = await ConnectedVehicleStatusAdapter.withToken( 23 | authToken 24 | ).get_stateofcharge(vehicleID); 25 | return stateofcharge; 26 | }, 27 | location: async ({ id: vehicleID }, _, { authToken }) => { 28 | const location = await ConnectedVehicleStatusAdapter.withToken( 29 | authToken 30 | ).get_location(vehicleID); 31 | return location; 32 | }, 33 | tires: async ({ id: vehicleID }, _, { authToken }) => { 34 | const tires = await ConnectedVehicleStatusAdapter.withToken( 35 | authToken 36 | ).get_tires(vehicleID); 37 | return tires; 38 | }, 39 | doors: async ({ id: vehicleID }, _, { authToken }) => { 40 | const doors = await ConnectedVehicleStatusAdapter.withToken( 41 | authToken 42 | ).get_doors(vehicleID); 43 | return doors; 44 | }, 45 | odometer: async ({ id: vehicleID }, _, { authToken }) => { 46 | const odometer = await ConnectedVehicleStatusAdapter.withToken( 47 | authToken 48 | ).get_odometer(vehicleID); 49 | return odometer; 50 | }, 51 | fuel: async ({ id: vehicleID }, _, { authToken }) => { 52 | const fuel = await ConnectedVehicleStatusAdapter.withToken( 53 | authToken 54 | ).get_fuel(vehicleID); 55 | return fuel; 56 | }, 57 | }, 58 | }; 59 | }; 60 | -------------------------------------------------------------------------------- /src/scopes.js: -------------------------------------------------------------------------------- 1 | export default { 2 | PROD: 'PROD', 3 | SANDBOX: 'SANDBOX', 4 | }; 5 | -------------------------------------------------------------------------------- /src/typeDefs/index.js: -------------------------------------------------------------------------------- 1 | import vehicleSchema from './vehicle.js'; 2 | 3 | const linkSchema = ` 4 | type Query { 5 | _: Boolean 6 | } 7 | `; 8 | 9 | export default [linkSchema, vehicleSchema]; 10 | -------------------------------------------------------------------------------- /src/typeDefs/vehicle.js: -------------------------------------------------------------------------------- 1 | import gql from 'graphql-tag'; 2 | 3 | export default gql` 4 | type Vehicle { 5 | id: String! 6 | stateofcharge: StateOfCharge! 7 | location: Location! 8 | licenseplate: String 9 | salesdesignation: String 10 | finorvin: String 11 | nickname: String 12 | modelyear: Int 13 | colorname: String 14 | fueltype: Int 15 | powerhp: Int 16 | powerkw: Int 17 | numberofdoors: Int 18 | numberofseats: Int 19 | tires: Tires 20 | doors: Doors 21 | odometer: Odometer 22 | fuel: Fuel 23 | } 24 | 25 | type StateOfCharge { 26 | value: Int! 27 | retrievalstatus: String! 28 | timestamp: Int! 29 | unit: String! 30 | } 31 | 32 | type Location { 33 | latitude: CoordInfo! 34 | longitude: CoordInfo! 35 | heading: CoordInfo! 36 | } 37 | 38 | type CoordInfo { 39 | value: Float! 40 | retrievalstatus: String! 41 | timestamp: Int! 42 | } 43 | 44 | type Tires { 45 | tirepressurefrontleft: Tire! 46 | tirepressurefrontright: Tire! 47 | tirepressurerearleft: Tire! 48 | tirepressurerearright: Tire! 49 | } 50 | 51 | type Tire { 52 | value: Int! 53 | retrievalstatus: String! 54 | timestamp: Int! 55 | unit: String! 56 | } 57 | 58 | type VehicleListItem { 59 | id: String 60 | licenseplate: String 61 | finorvin: String 62 | } 63 | 64 | type Doors { 65 | doorstatusfrontleft: DoorStatus 66 | doorstatusfrontright: DoorStatus 67 | doorstatusrearleft: DoorStatus 68 | doorstatusrearright: DoorStatus 69 | doorlockstatusfrontleft: DoorStatus 70 | doorlockstatusfrontright: DoorStatus 71 | doorlockstatusrearleft: DoorStatus 72 | doorlockstatusrearright: DoorStatus 73 | doorlockstatusdecklid: DoorStatus 74 | doorlockstatusgas: DoorStatus 75 | doorlockstatusvehicle: DoorStatus 76 | } 77 | 78 | type DoorStatus { 79 | value: String! 80 | retrievalstatus: String! 81 | timestamp: Int! 82 | } 83 | 84 | type Odometer { 85 | odometer: DistanceData! 86 | distancesincereset: DistanceData! 87 | distancesincestart: DistanceData! 88 | } 89 | 90 | type DistanceData { 91 | value: Int! 92 | retrievalstatus: String! 93 | timestamp: Int! 94 | unit: String! 95 | } 96 | type Fuel { 97 | fuellevelpercent: FuelLevelPercent! 98 | } 99 | 100 | type FuelLevelPercent { 101 | value: Int! 102 | retrievalstatus: String! 103 | timestamp: Int! 104 | unit: String! 105 | } 106 | 107 | extend type Query { 108 | getVehicle(id: String!): Vehicle 109 | vehicles: [VehicleListItem!]! 110 | } 111 | `; 112 | --------------------------------------------------------------------------------