├── .gitignore ├── README.md ├── accounts ├── .env.sample ├── package-lock.json ├── package.json └── src │ ├── config │ └── auth0.js │ ├── graphql │ ├── dataLoaders.js │ ├── dataSources │ │ └── AccountsDataSource.js │ ├── resolvers.js │ └── schema.graphql │ ├── index.js │ └── utils │ ├── authenticateUser.js │ └── getToken.js ├── auth-proxy ├── .env.sample ├── package-lock.json ├── package.json └── src │ └── index.js ├── bookmarks ├── .env.sample ├── package-lock.json ├── package.json └── src │ ├── config │ └── mongoose.js │ ├── graphql │ ├── dataSources │ │ └── BookmarksDataSource.js │ ├── resolvers.js │ └── schema.graphql │ ├── index.js │ └── models │ └── Bookmark.js ├── gateway ├── .env.sample ├── package-lock.json ├── package.json └── src │ ├── config │ ├── apollo.js │ └── app.js │ └── index.js ├── profiles ├── .env.sample ├── package-lock.json ├── package.json └── src │ ├── config │ └── mongoose.js │ ├── graphql │ ├── dataLoaders.js │ ├── dataSources │ │ └── ProfilesDataSource.js │ ├── resolvers.js │ └── schema.graphql │ ├── index.js │ └── models │ └── Profile.js ├── router ├── .env.sample ├── docker-compose.yaml └── router.yaml ├── shared ├── package-lock.json ├── package.json └── src │ ├── directives │ ├── authDirectives.graphql │ └── authDirectives.js │ ├── index.js │ ├── scalars │ ├── DateTypeType.js │ └── URLType.js │ └── utils │ ├── Pagination.js │ └── restoreReferenceResolvers.js └── workflows ├── .env.sample ├── package-lock.json ├── package.json └── src ├── graphql ├── client.js ├── dataSources │ └── WorkflowsDataSource.js ├── operations.js ├── resolvers.js └── schema.graphql ├── index.js ├── temporal ├── activities.js ├── worker.js └── workflows.js └── utils └── Auth0Client.js /.gitignore: -------------------------------------------------------------------------------- 1 | # editor 2 | 3 | .vscode 4 | 5 | # dependencies 6 | 7 | node_modules 8 | 9 | # environment 10 | 11 | .env 12 | .env.local 13 | .env.development.local 14 | .env.test.local 15 | .env.production.local 16 | 17 | # logs 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | # misc 24 | 25 | .DS_Store 26 | .DS_Store? 27 | ._* 28 | .Spotlight-V100 29 | .Trashes 30 | ehthumbs.db 31 | *[Tt]humbs.db -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advanced GraphQL with Apollo (Source Code) 2 | 3 | This repo contains the completed files for the federated GraphQL API built throughout the second edition of the _Advanced GraphQL with Apollo_ book from 8-Bit Press. 4 | 5 | **[Get the book package here.](https://github.com/8bitpress/advanced-graphql-v2)** 6 | 7 | Happy coding! 8 | 9 | --- 10 | 11 | Copyright © 2022 8-Bit Press Inc. 12 | -------------------------------------------------------------------------------- /accounts/.env.sample: -------------------------------------------------------------------------------- 1 | AUTH0_AUDIENCE=http://localhost:4000/ 2 | AUTH0_CLIENT_ID_GRAPHQL= 3 | AUTH0_CLIENT_SECRET_GRAPHQL= 4 | AUTH0_DOMAIN= 5 | 6 | AUTH0_CLIENT_ID_MGMT_API= 7 | AUTH0_CLIENT_SECRET_MGMT_API= 8 | 9 | NODE_ENV=development 10 | PORT=4001 -------------------------------------------------------------------------------- /accounts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "marked-accounts", 3 | "version": "1.0.0", 4 | "description": "The server for the accounts service in the Marked app.", 5 | "main": "src/index.js", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "nodemon -r dotenv/config -e env,graphql,js ./src/index.js" 9 | }, 10 | "keywords": [], 11 | "author": "mandiwise", 12 | "license": "MIT", 13 | "dependencies": { 14 | "@apollo/subgraph": "^2.0.3", 15 | "apollo-datasource": "^3.3.1", 16 | "apollo-server": "^3.7.0", 17 | "auth0": "^2.40.0", 18 | "dataloader": "^2.1.0", 19 | "dotenv": "^16.0.0", 20 | "graphql": "^16.5.0", 21 | "request": "^2.88.2" 22 | }, 23 | "devDependencies": { 24 | "nodemon": "^2.0.15" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /accounts/src/config/auth0.js: -------------------------------------------------------------------------------- 1 | import { ManagementClient } from "auth0"; 2 | 3 | const auth0 = new ManagementClient({ 4 | domain: process.env.AUTH0_DOMAIN, 5 | clientId: process.env.AUTH0_CLIENT_ID_MGMT_API, 6 | clientSecret: process.env.AUTH0_CLIENT_SECRET_MGMT_API 7 | }); 8 | 9 | export default auth0; 10 | -------------------------------------------------------------------------------- /accounts/src/graphql/dataLoaders.js: -------------------------------------------------------------------------------- 1 | import DataLoader from "dataloader"; 2 | 3 | import auth0 from "../config/auth0.js"; 4 | 5 | function initDataLoaders() { 6 | const accountLoader = new DataLoader(async keys => { 7 | const q = keys.map(key => `user_id:${key}`).join(" OR "); 8 | const accounts = await auth0.getUsers({ search_engine: "v3", q }); 9 | 10 | return keys.map(key => accounts.find(account => account.user_id === key)); 11 | }); 12 | 13 | return { accountLoader }; 14 | } 15 | 16 | export default initDataLoaders; 17 | -------------------------------------------------------------------------------- /accounts/src/graphql/dataSources/AccountsDataSource.js: -------------------------------------------------------------------------------- 1 | import { DataSource } from "apollo-datasource"; 2 | import { UserInputError } from "apollo-server"; 3 | 4 | import getToken from "../../utils/getToken.js"; 5 | 6 | class AccountsDataSource extends DataSource { 7 | constructor({ auth0 }) { 8 | super(); 9 | this.auth0 = auth0; 10 | } 11 | 12 | initialize(config) { 13 | this.context = config.context; 14 | } 15 | 16 | // CREATE 17 | 18 | createAccount(email, password) { 19 | return this.auth0.createUser({ 20 | connection: "Username-Password-Authentication", 21 | email, 22 | password 23 | }); 24 | } 25 | 26 | // READ 27 | 28 | getAccountById(id) { 29 | return this.context.loaders.accountLoader.load(id); 30 | } 31 | 32 | getAccounts() { 33 | return this.auth0.getUsers(); 34 | } 35 | 36 | // UPDATE 37 | 38 | updateAccountEmail(id, email) { 39 | return this.auth0.updateUser({ id }, { email }); 40 | } 41 | 42 | async updateAccountPassword(id, newPassword, password) { 43 | const user = await this.auth0.getUser({ id }); 44 | 45 | try { 46 | await getToken(user.email, password); 47 | } catch { 48 | throw new UserInputError("Email or existing password is incorrect."); 49 | } 50 | 51 | return this.auth0.updateUser({ id }, { password: newPassword }); 52 | } 53 | 54 | // DELETE 55 | 56 | async deleteAccount(id) { 57 | try { 58 | await this.auth0.deleteUser({ id }); 59 | return true; 60 | } catch { 61 | return false; 62 | } 63 | } 64 | } 65 | 66 | export default AccountsDataSource; 67 | -------------------------------------------------------------------------------- /accounts/src/graphql/resolvers.js: -------------------------------------------------------------------------------- 1 | import { ApolloError } from "apollo-server"; 2 | 3 | import { DateTimeType } from "../../../shared/src/index.js"; 4 | 5 | const resolvers = { 6 | DateTime: DateTimeType, 7 | 8 | Account: { 9 | __resolveReference(reference, { dataSources, user }) { 10 | if (user?.sub) { 11 | return dataSources.accountsAPI.getAccountById(reference.id); 12 | } 13 | throw new ApolloError("Not authorized!"); 14 | }, 15 | id(account) { 16 | return account.user_id; 17 | }, 18 | createdAt(account) { 19 | return account.created_at; 20 | } 21 | }, 22 | 23 | Query: { 24 | account(root, { id }, { dataSources }) { 25 | return dataSources.accountsAPI.getAccountById(id); 26 | }, 27 | accounts(root, args, { dataSources }) { 28 | return dataSources.accountsAPI.getAccounts(); 29 | }, 30 | viewer(root, args, { dataSources, user }) { 31 | if (user?.sub) { 32 | return dataSources.accountsAPI.getAccountById(user.sub); 33 | } 34 | return null; 35 | } 36 | }, 37 | 38 | Mutation: { 39 | createAccount(root, { input: { email, password } }, { dataSources }) { 40 | return dataSources.accountsAPI.createAccount(email, password); 41 | }, 42 | deleteAccount(root, { id }, { dataSources }) { 43 | return dataSources.accountsAPI.deleteAccount(id); 44 | }, 45 | updateAccountEmail(root, { input: { id, email } }, { dataSources }) { 46 | return dataSources.accountsAPI.updateAccountEmail(id, email); 47 | }, 48 | updateAccountPassword( 49 | root, 50 | { input: { id, newPassword, password } }, 51 | { dataSources } 52 | ) { 53 | return dataSources.accountsAPI.updateAccountPassword( 54 | id, 55 | newPassword, 56 | password 57 | ); 58 | } 59 | } 60 | }; 61 | 62 | export default resolvers; 63 | -------------------------------------------------------------------------------- /accounts/src/graphql/schema.graphql: -------------------------------------------------------------------------------- 1 | extend schema 2 | @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"]) 3 | 4 | # SCALARS 5 | 6 | scalar DateTime 7 | 8 | # OBJECTS 9 | 10 | """ 11 | An account is a unique Auth0 user. 12 | """ 13 | type Account @key(fields: "id") { 14 | "The unique ID associated with the account." 15 | id: ID! 16 | "The date and time the account was created." 17 | createdAt: DateTime! 18 | "The email associated with the account (must be unique)." 19 | email: String! 20 | } 21 | 22 | # INPUTS 23 | 24 | """ 25 | Provides data to create a new account. 26 | """ 27 | input CreateAccountInput { 28 | "The new account's email (must be unique)." 29 | email: String! 30 | "The new account's password." 31 | password: String! 32 | } 33 | 34 | """ 35 | Provides data to update an existing account's email. 36 | """ 37 | input UpdateAccountEmailInput { 38 | "The unique ID associated with the account." 39 | id: ID! 40 | "The updated account email." 41 | email: String! 42 | } 43 | 44 | """ 45 | Provides data to update an existing account's password. A current password and new password are required to update a password. 46 | """ 47 | input UpdateAccountPasswordInput { 48 | "The unique ID associated with the account." 49 | id: ID! 50 | "The updated account password." 51 | newPassword: String! 52 | "The existing account password." 53 | password: String! 54 | } 55 | 56 | # ROOT 57 | 58 | type Query { 59 | "Retrieves a single account by ID." 60 | account(id: ID!): Account! @private 61 | "Retrieves a list of accounts." 62 | accounts: [Account] @private 63 | "Retrieves the account of the currently logged-in user." 64 | viewer: Account 65 | } 66 | 67 | type Mutation { 68 | "Creates a new account." 69 | createAccount(input: CreateAccountInput!): Account! 70 | "Deletes an account." 71 | deleteAccount(id: ID!): Boolean! @scope(permissions: ["delete:accounts"]) 72 | "Updates an account's email." 73 | updateAccountEmail(input: UpdateAccountEmailInput!): Account! 74 | @owner(argumentName: "input.id") 75 | "Updates an account's password." 76 | updateAccountPassword(input: UpdateAccountPasswordInput!): Account! 77 | @owner(argumentName: "input.id") 78 | } 79 | -------------------------------------------------------------------------------- /accounts/src/index.js: -------------------------------------------------------------------------------- 1 | import { dirname, resolve } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { readFileSync } from "fs"; 4 | 5 | import { ApolloServer, gql } from "apollo-server"; 6 | import { buildSubgraphSchema } from "@apollo/subgraph"; 7 | 8 | import { 9 | authDirectives, 10 | restoreReferenceResolvers 11 | } from "../../shared/src/index.js"; 12 | import AccountsDataSource from "./graphql/dataSources/AccountsDataSource.js"; 13 | import auth0 from "./config/auth0.js"; 14 | import initDataLoaders from "./graphql/dataLoaders.js"; 15 | import resolvers from "./graphql/resolvers.js"; 16 | 17 | const __dirname = dirname(fileURLToPath(import.meta.url)); 18 | const port = process.env.PORT; 19 | 20 | const { authDirectivesTypeDefs, authDirectivesTransformer } = authDirectives(); 21 | const subgraphTypeDefs = readFileSync( 22 | resolve(__dirname, "./graphql/schema.graphql"), 23 | "utf-8" 24 | ); 25 | const typeDefs = gql(`${subgraphTypeDefs}\n${authDirectivesTypeDefs}`); 26 | let subgraphSchema = buildSubgraphSchema({ typeDefs, resolvers }); 27 | subgraphSchema = authDirectivesTransformer(subgraphSchema); 28 | restoreReferenceResolvers(subgraphSchema, resolvers); 29 | 30 | const server = new ApolloServer({ 31 | schema: subgraphSchema, 32 | context: ({ req }) => { 33 | const user = req.headers.user ? JSON.parse(req.headers.user) : null; 34 | return { user, loaders: initDataLoaders() }; 35 | }, 36 | dataSources: () => { 37 | return { 38 | accountsAPI: new AccountsDataSource({ auth0 }) 39 | }; 40 | } 41 | }); 42 | 43 | const { url } = await server.listen({ port }); 44 | console.log(`Accounts service ready at ${url}`); 45 | -------------------------------------------------------------------------------- /accounts/src/utils/authenticateUser.js: -------------------------------------------------------------------------------- 1 | import getToken from "./getToken.js"; 2 | 3 | (async function () { 4 | const [email, password] = process.argv.slice(2); 5 | const access_token = await getToken(email, password).catch(error => { 6 | console.log(error); 7 | }); 8 | console.log(access_token); 9 | })(); 10 | -------------------------------------------------------------------------------- /accounts/src/utils/getToken.js: -------------------------------------------------------------------------------- 1 | import util from "util"; 2 | 3 | import request from "request"; 4 | 5 | const requestPromise = util.promisify(request); 6 | 7 | async function getToken(username, password) { 8 | const options = { 9 | method: "POST", 10 | url: `https://${process.env.AUTH0_DOMAIN}/oauth/token`, 11 | headers: { "content-type": "application/x-www-form-urlencoded" }, 12 | form: { 13 | audience: process.env.AUTH0_AUDIENCE, 14 | client_id: process.env.AUTH0_CLIENT_ID_GRAPHQL, 15 | client_secret: process.env.AUTH0_CLIENT_SECRET_GRAPHQL, 16 | grant_type: "http://auth0.com/oauth/grant-type/password-realm", 17 | password, 18 | realm: "Username-Password-Authentication", 19 | scope: "openid", 20 | username 21 | } 22 | }; 23 | 24 | const response = await requestPromise(options).catch(error => { 25 | throw new Error(error); 26 | }); 27 | const body = JSON.parse(response.body); 28 | const { access_token } = body; 29 | 30 | if (!access_token) { 31 | throw new Error(body.error_description || "Cannot retrieve access token."); 32 | } 33 | 34 | return access_token; 35 | } 36 | 37 | export default getToken; 38 | -------------------------------------------------------------------------------- /auth-proxy/.env.sample: -------------------------------------------------------------------------------- 1 | AUTH0_AUDIENCE=http://localhost:4000/ 2 | AUTH0_ISSUER= 3 | 4 | ROUTER_ENDPOINT=http://localhost:5000 5 | 6 | NODE_ENV=development 7 | PORT=4000 -------------------------------------------------------------------------------- /auth-proxy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auth-proxy", 3 | "version": "1.0.0", 4 | "description": "A proxy server to handle authentication in front of Apollo Router.", 5 | "main": "src/index.js", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "nodemon -r dotenv/config -e env,js ./src/index.js" 9 | }, 10 | "keywords": [], 11 | "author": "mandiwise", 12 | "license": "MIT", 13 | "dependencies": { 14 | "cors": "^2.8.5", 15 | "dotenv": "^16.0.0", 16 | "express": "^4.17.3", 17 | "express-jwt": "^6.1.1", 18 | "http-proxy-middleware": "^2.0.6", 19 | "jwks-rsa": "^2.0.5" 20 | }, 21 | "devDependencies": { 22 | "nodemon": "^2.0.15" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /auth-proxy/src/index.js: -------------------------------------------------------------------------------- 1 | import { createProxyMiddleware } from "http-proxy-middleware"; 2 | import cors from "cors"; 3 | import express from "express"; 4 | import jwt from "express-jwt"; 5 | import jwksClient from "jwks-rsa"; 6 | 7 | const port = process.env.PORT; 8 | const app = express(); 9 | 10 | app.use( 11 | cors({ 12 | origin: ["https://studio.apollographql.com"] 13 | }) 14 | ); 15 | 16 | const jwtCheck = jwt({ 17 | secret: jwksClient.expressJwtSecret({ 18 | cache: true, 19 | rateLimit: true, 20 | jwksRequestsPerMinute: 5, 21 | jwksUri: `${process.env.AUTH0_ISSUER}.well-known/jwks.json` 22 | }), 23 | audience: process.env.AUTH0_AUDIENCE, 24 | issuer: process.env.AUTH0_ISSUER, 25 | algorithms: ["RS256"], 26 | credentialsRequired: false 27 | }); 28 | 29 | app.use(jwtCheck, (err, req, res, next) => { 30 | if (err.code === "invalid_token") { 31 | return next(); 32 | } 33 | return next(err); 34 | }); 35 | 36 | app.use( 37 | createProxyMiddleware({ 38 | target: process.env.ROUTER_ENDPOINT, 39 | changeOrigin: true, 40 | onProxyReq(proxyReq, req) { 41 | proxyReq.setHeader("user", req.user ? JSON.stringify(req.user) : null); 42 | } 43 | }) 44 | ); 45 | 46 | app.listen(port, () => { 47 | console.log(`Authentication proxy ready at http://localhost:${port}`); 48 | }); 49 | -------------------------------------------------------------------------------- /bookmarks/.env.sample: -------------------------------------------------------------------------------- 1 | MONGODB_URL=mongodb://127.0.0.1:27017/marked-bookmarks 2 | 3 | NODE_ENV=development 4 | PORT=4003 -------------------------------------------------------------------------------- /bookmarks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "marked-bookmarks", 3 | "version": "1.0.0", 4 | "description": "The server for the bookmarks service in the Marked app.", 5 | "main": "src/index.js", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "nodemon -r dotenv/config -e env,graphql,js ./src/index.js" 9 | }, 10 | "keywords": [], 11 | "author": "mandiwise", 12 | "license": "MIT", 13 | "dependencies": { 14 | "@apollo/subgraph": "^2.0.3", 15 | "apollo-datasource": "^3.3.1", 16 | "apollo-server": "^3.7.0", 17 | "dotenv": "^16.0.0", 18 | "graphql": "^16.5.0", 19 | "mongoose": "^6.3.0" 20 | }, 21 | "devDependencies": { 22 | "nodemon": "^2.0.15" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /bookmarks/src/config/mongoose.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | function initMongoose() { 4 | const connectionUrl = process.env.MONGODB_URL; 5 | mongoose.connect(connectionUrl); 6 | 7 | mongoose.connection.on("connected", () => { 8 | console.log(`Mongoose default connection ready at ${connectionUrl}`); 9 | }); 10 | 11 | mongoose.connection.on("error", error => { 12 | console.log("Mongoose default connection error:", error); 13 | }); 14 | } 15 | 16 | export default initMongoose; 17 | -------------------------------------------------------------------------------- /bookmarks/src/graphql/dataSources/BookmarksDataSource.js: -------------------------------------------------------------------------------- 1 | import { DataSource } from "apollo-datasource"; 2 | import { UserInputError } from "apollo-server"; 3 | 4 | import { Pagination } from "../../../../shared/src/index.js"; 5 | 6 | class BookmarksDataSource extends DataSource { 7 | constructor({ Bookmark }) { 8 | super(); 9 | this.Bookmark = Bookmark; 10 | this.pagination = new Pagination(Bookmark); 11 | } 12 | 13 | // UTILS 14 | 15 | _formatTags(tags) { 16 | return tags.map(tag => tag.replace(/\s+/g, "-").toLowerCase()); 17 | } 18 | 19 | _getBookmarkSort(sortEnum) { 20 | let sort = {}; 21 | const sortArgs = sortEnum.split("_"); 22 | const direction = sortArgs.pop(); 23 | const field = sortArgs 24 | .map(arg => arg.toLowerCase()) 25 | .map((arg, i) => 26 | i === 0 ? arg : arg.charAt(0).toUpperCase() + arg.slice(1) 27 | ) 28 | .join(""); 29 | sort[field] = direction === "DESC" ? -1 : 1; 30 | 31 | return sort; 32 | } 33 | 34 | // CREATE 35 | 36 | async createBookmark(bookmark) { 37 | const existingBookmarkForUrl = await this.Bookmark.findOne({ 38 | ownerAccountId: bookmark.ownerAccountId, 39 | url: bookmark.url 40 | }).exec(); 41 | 42 | console.log(existingBookmarkForUrl); 43 | 44 | if (existingBookmarkForUrl) { 45 | throw new UserInputError("A bookmark for the URL already exists."); 46 | } 47 | 48 | if (bookmark.tags) { 49 | const formattedTags = this._formatTags(bookmark.tags); 50 | bookmark.tags = formattedTags; 51 | } 52 | 53 | const newBookmark = new this.Bookmark(bookmark); 54 | return newBookmark.save(); 55 | } 56 | 57 | // READ 58 | 59 | getBookmarkById(id, userId) { 60 | return this.Bookmark.findOne({ 61 | $or: [ 62 | { _id: id, private: false }, 63 | { _id: id, ownerAccountId: userId } 64 | ] 65 | }).exec(); 66 | } 67 | 68 | async getRecommendedBookmarks(accountId, interests, { after, first }) { 69 | const sort = { score: { $meta: "textScore" }, _id: -1 }; 70 | const searchString = interests 71 | .map(interest => 72 | interest.includes("-") 73 | ? `\\"${interest.split("-").join(" ")}\\"` 74 | : interest 75 | ) 76 | .join(" "); 77 | const filter = { 78 | $and: [ 79 | { 80 | $text: { $search: searchString }, 81 | ownerAccountId: { $ne: accountId }, 82 | private: false 83 | } 84 | ] 85 | }; 86 | const queryArgs = { after, first, filter, sort }; 87 | const edges = await this.pagination.getEdges(queryArgs); 88 | const pageInfo = await this.pagination.getPageInfo(edges, queryArgs); 89 | 90 | return { edges, pageInfo }; 91 | } 92 | 93 | async getUserBookmarks( 94 | accountId, 95 | userId, 96 | { after, before, first, last, orderBy } 97 | ) { 98 | const sort = this._getBookmarkSort(orderBy); 99 | const filter = { 100 | $or: [ 101 | { private: false, ownerAccountId: accountId }, 102 | { 103 | $and: [ 104 | { ownerAccountId: accountId }, 105 | { $expr: { $eq: ["$ownerAccountId", userId] } } 106 | ] 107 | } 108 | ] 109 | }; 110 | const queryArgs = { after, before, first, last, filter, sort }; 111 | const edges = await this.pagination.getEdges(queryArgs); 112 | const pageInfo = await this.pagination.getPageInfo(edges, queryArgs); 113 | 114 | return { edges, pageInfo }; 115 | } 116 | 117 | async searchBookmarks(userId, { after, first, searchString }) { 118 | const sort = { score: { $meta: "textScore" }, _id: -1 }; 119 | const filter = { 120 | $and: [ 121 | { 122 | $text: { $search: searchString }, 123 | $or: [{ private: false }, { ownerAccountId: userId }] 124 | } 125 | ] 126 | }; 127 | const queryArgs = { after, first, filter, sort }; 128 | const edges = await this.pagination.getEdges(queryArgs); 129 | const pageInfo = await this.pagination.getPageInfo(edges, queryArgs); 130 | 131 | return { edges, pageInfo }; 132 | } 133 | 134 | // UPDATE 135 | 136 | async updateBookmark( 137 | id, 138 | userId, 139 | { ownerAccountId: _, ...updatedBookmarkData } 140 | ) { 141 | if ( 142 | !updatedBookmarkData || 143 | (updatedBookmarkData && Object.keys(updatedBookmarkData).length === 0) 144 | ) { 145 | throw new UserInputError("You must supply some bookmark data to update."); 146 | } 147 | 148 | if (updatedBookmarkData) { 149 | const formattedTags = this._formatTags(updatedBookmarkData.tags); 150 | updatedBookmarkData.tags = formattedTags; 151 | } 152 | 153 | return this.Bookmark.findOneAndUpdate( 154 | { _id: id, ownerAccountId: userId }, 155 | updatedBookmarkData, 156 | { 157 | new: true 158 | } 159 | ); 160 | } 161 | 162 | // DELETE 163 | 164 | async deleteAllUserBookmarks(ownerAccountId) { 165 | try { 166 | await this.Bookmark.deleteMany({ ownerAccountId }).exec(); 167 | return true; 168 | } catch { 169 | return false; 170 | } 171 | } 172 | 173 | async deleteBookmark(id, userId) { 174 | try { 175 | const deletedBookmark = await this.Bookmark.findOneAndDelete({ 176 | _id: id, 177 | ownerAccountId: userId 178 | }).exec(); 179 | 180 | return deletedBookmark ? true : false; 181 | } catch { 182 | return false; 183 | } 184 | } 185 | } 186 | 187 | export default BookmarksDataSource; 188 | -------------------------------------------------------------------------------- /bookmarks/src/graphql/resolvers.js: -------------------------------------------------------------------------------- 1 | import { UserInputError } from "apollo-server"; 2 | 3 | import { DateTimeType, URLType } from "../../../shared/src/index.js"; 4 | 5 | const resolvers = { 6 | DateTime: DateTimeType, 7 | URL: URLType, 8 | 9 | Bookmark: { 10 | id(bookmark) { 11 | return bookmark._id; 12 | }, 13 | owner(bookmark) { 14 | return { account: { id: bookmark.ownerAccountId } }; 15 | } 16 | }, 17 | 18 | Profile: { 19 | bookmarks(profile, args, { dataSources, user }) { 20 | const userId = user?.sub ? user.sub : null; 21 | 22 | return dataSources.bookmarksAPI.getUserBookmarks( 23 | profile.account.id, 24 | userId, 25 | args 26 | ); 27 | }, 28 | recommendedBookmarks({ account, interests }, args, { dataSources }) { 29 | return dataSources.bookmarksAPI.getRecommendedBookmarks( 30 | account.id, 31 | interests, 32 | args 33 | ); 34 | } 35 | }, 36 | 37 | Query: { 38 | async bookmark(root, { id }, { dataSources, user }) { 39 | const userId = user?.sub ? user.sub : null; 40 | const bookmark = await dataSources.bookmarksAPI.getBookmarkById( 41 | id, 42 | userId 43 | ); 44 | 45 | if (!bookmark) { 46 | throw new UserInputError("Bookmark not available."); 47 | } 48 | 49 | return bookmark; 50 | }, 51 | searchBookmarks(root, { after, first, query }, { dataSources, user }) { 52 | const userId = user?.sub ? user.sub : null; 53 | 54 | return dataSources.bookmarksAPI.searchBookmarks(userId, { 55 | after, 56 | first, 57 | searchString: query 58 | }); 59 | } 60 | }, 61 | 62 | Mutation: { 63 | createBookmark(root, { input }, { dataSources }) { 64 | return dataSources.bookmarksAPI.createBookmark(input); 65 | }, 66 | deleteAllUserBookmarks(root, { ownerAccountId }, { dataSources }) { 67 | return dataSources.bookmarksAPI.deleteAllUserBookmarks(ownerAccountId); 68 | }, 69 | deleteBookmark(root, { input: { id } }, { dataSources, user }) { 70 | const userId = user?.sub ? user.sub : null; 71 | 72 | return dataSources.bookmarksAPI.deleteBookmark(id, userId); 73 | }, 74 | async updateBookmark( 75 | root, 76 | { input: { id, ...rest } }, 77 | { dataSources, user } 78 | ) { 79 | const userId = user?.sub ? user.sub : null; 80 | const bookmark = await dataSources.bookmarksAPI.updateBookmark( 81 | id, 82 | userId, 83 | rest 84 | ); 85 | 86 | if (!bookmark) { 87 | throw new UserInputError("Bookmark cannot be updated."); 88 | } 89 | 90 | return bookmark; 91 | } 92 | } 93 | }; 94 | 95 | export default resolvers; 96 | -------------------------------------------------------------------------------- /bookmarks/src/graphql/schema.graphql: -------------------------------------------------------------------------------- 1 | extend schema 2 | @link(url: "https://specs.apollo.dev/federation/v2.0", 3 | import: ["@external", "@key", "@requires", "@shareable"]) 4 | 5 | # SCALARS 6 | 7 | """ 8 | An ISO 8601-encoded UTC date string. 9 | """ 10 | scalar DateTime 11 | 12 | """ 13 | A well-formatted URL string. 14 | """ 15 | scalar URL 16 | 17 | # ENUMS 18 | 19 | """ 20 | Sorting options for bookmark connections. 21 | """ 22 | enum BookmarkOrderBy { 23 | "Order bookmarks ascending by creation time." 24 | CREATED_AT_ASC 25 | "Order bookmarks descending by creation time." 26 | CREATED_AT_DESC 27 | } 28 | 29 | # OBJECTS 30 | 31 | """ 32 | An account is a unique Auth0 user. 33 | """ 34 | type Account @key(fields: "id") { 35 | id: ID! 36 | } 37 | 38 | """ 39 | A bookmark contains content authored by a user. 40 | """ 41 | type Bookmark { 42 | "The unique ID of the bookmark." 43 | id: ID! 44 | "The date and time the bookmark was created." 45 | createdAt: DateTime! 46 | "The profile of the user who authored the bookmark." 47 | owner: Profile! 48 | "Whether a bookmark has been marked as private." 49 | private: Boolean! 50 | "User-applied tags for the bookmark." 51 | tags: [String] 52 | "A title to describe the bookmarked content." 53 | title: String 54 | "The URL of the page to be bookmarked." 55 | url: URL! 56 | } 57 | 58 | """ 59 | A list of bookmark edges with pagination information. 60 | """ 61 | type BookmarkConnection { 62 | "A list of bookmark edges." 63 | edges: [BookmarkEdge] 64 | "Information to assist with pagination." 65 | pageInfo: PageInfo! 66 | } 67 | 68 | """ 69 | A single bookmark node with its cursor. 70 | """ 71 | type BookmarkEdge { 72 | "A cursor for use in pagination." 73 | cursor: ID! 74 | "A bookmark at the end of an edge." 75 | node: Bookmark! 76 | } 77 | 78 | """ 79 | Information about pagination in a connection. 80 | """ 81 | type PageInfo @shareable { 82 | "The cursor to continue from when paginating forward." 83 | endCursor: String 84 | "Whether there are more items when paginating forward." 85 | hasNextPage: Boolean! 86 | "Whether there are more items when paginating backward." 87 | hasPreviousPage: Boolean! 88 | "The cursor to continue from them paginating backward." 89 | startCursor: String 90 | } 91 | 92 | """ 93 | A profile contains metadata about a specific user. 94 | """ 95 | type Profile @key(fields: "account { id }") { 96 | account: Account 97 | interests: [String] @external 98 | """ 99 | A list of bookmarks created by the user. 100 | 101 | Private bookmarks will be hidden for non-owners. 102 | """ 103 | bookmarks( 104 | after: String 105 | before: String 106 | first: Int 107 | last: Int 108 | orderBy: BookmarkOrderBy = CREATED_AT_DESC 109 | ): BookmarkConnection 110 | """ 111 | A list of recommended bookmarks for a user, based on their interests. 112 | """ 113 | recommendedBookmarks(after: String, first: Int): BookmarkConnection 114 | @requires(fields: "interests") 115 | } 116 | 117 | # INPUTS 118 | 119 | """ 120 | Provides data to create a bookmark. 121 | """ 122 | input CreateBookmarkInput { 123 | "The Auth0 ID of the user who created the bookmark." 124 | ownerAccountId: ID! 125 | "Whether a bookmark should be marked as private." 126 | private: Boolean! 127 | "User-applied tags for the bookmark." 128 | tags: [String] 129 | "The title of the page to be bookmarked." 130 | title: String 131 | "The URL of the page to be bookmarked." 132 | url: URL! 133 | } 134 | 135 | """ 136 | Provides data to delete a bookmark. 137 | """ 138 | input DeleteBookmarkInput { 139 | "The unique ID of the bookmark." 140 | id: ID! 141 | "The Auth0 ID of the user who created the bookmark." 142 | ownerAccountId: ID! 143 | } 144 | 145 | """ 146 | Provides data to update an existing bookmark. 147 | """ 148 | input UpdateBookmarkInput { 149 | "The unique ID of the bookmark." 150 | id: ID! 151 | "The Auth0 ID of the user who created the bookmark." 152 | ownerAccountId: ID! 153 | "The updated privacy setting for the bookmark." 154 | private: Boolean 155 | "An updated user-applied tags for the bookmark." 156 | tags: [String] 157 | "The updated title to describe the bookmarked content." 158 | title: String 159 | "The updated URL of the bookmarked page." 160 | url: URL 161 | } 162 | 163 | # ROOT 164 | 165 | type Query { 166 | "Retrieves a single bookmark by ID." 167 | bookmark(id: ID!): Bookmark! 168 | "Provides a search string to query relevant bookmarks." 169 | searchBookmarks( 170 | after: String 171 | first: Int 172 | "The text string to search for in bookmark title." 173 | query: String! 174 | ): BookmarkConnection 175 | } 176 | 177 | type Mutation { 178 | "Creates a new bookmark." 179 | createBookmark(input: CreateBookmarkInput!): Bookmark! 180 | @owner(argumentName: "input.ownerAccountId") 181 | "Deletes all of a user's bookmarks." 182 | deleteAllUserBookmarks(ownerAccountId: ID!): Boolean! 183 | @scope(permissions: ["delete:bookmarks"]) 184 | "Deletes a bookmark." 185 | deleteBookmark(input: DeleteBookmarkInput!): Boolean! 186 | @owner(argumentName: "input.ownerAccountId") 187 | "Updates a bookmark." 188 | updateBookmark(input: UpdateBookmarkInput!): Bookmark! 189 | @owner(argumentName: "input.ownerAccountId") 190 | } 191 | -------------------------------------------------------------------------------- /bookmarks/src/index.js: -------------------------------------------------------------------------------- 1 | import { dirname, resolve } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { readFileSync } from "fs"; 4 | 5 | import { ApolloServer, gql } from "apollo-server"; 6 | import { buildSubgraphSchema } from "@apollo/subgraph"; 7 | 8 | import { 9 | authDirectives, 10 | restoreReferenceResolvers 11 | } from "../../shared/src/index.js"; 12 | import Bookmark from "./models/Bookmark.js"; 13 | import BookmarksDataSource from "./graphql/dataSources/BookmarksDataSource.js"; 14 | import initMongoose from "./config/mongoose.js"; 15 | import resolvers from "./graphql/resolvers.js"; 16 | 17 | const __dirname = dirname(fileURLToPath(import.meta.url)); 18 | const port = process.env.PORT; 19 | 20 | const { authDirectivesTypeDefs, authDirectivesTransformer } = authDirectives(); 21 | const subgraphTypeDefs = readFileSync( 22 | resolve(__dirname, "./graphql/schema.graphql"), 23 | "utf-8" 24 | ); 25 | const typeDefs = gql(`${subgraphTypeDefs}\n${authDirectivesTypeDefs}`); 26 | let subgraphSchema = buildSubgraphSchema({ typeDefs, resolvers }); 27 | subgraphSchema = authDirectivesTransformer(subgraphSchema); 28 | restoreReferenceResolvers(subgraphSchema, resolvers); 29 | 30 | const server = new ApolloServer({ 31 | schema: subgraphSchema, 32 | context: ({ req }) => { 33 | const user = req.headers.user ? JSON.parse(req.headers.user) : null; 34 | return { user }; 35 | }, 36 | dataSources: () => { 37 | return { 38 | bookmarksAPI: new BookmarksDataSource({ Bookmark }) 39 | }; 40 | } 41 | }); 42 | 43 | initMongoose(); 44 | 45 | server.listen({ port }).then(({ url }) => { 46 | console.log(`Bookmarks service ready at ${url}`); 47 | }); 48 | -------------------------------------------------------------------------------- /bookmarks/src/models/Bookmark.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const bookmarkSchema = new mongoose.Schema({ 4 | createdAt: { 5 | type: Date, 6 | default: Date.now, 7 | required: true 8 | }, 9 | ownerAccountId: { 10 | type: String, 11 | required: true 12 | }, 13 | private: { 14 | type: Boolean, 15 | default: false, 16 | required: true 17 | }, 18 | tags: [ 19 | { 20 | type: String 21 | } 22 | ], 23 | title: { 24 | type: String 25 | }, 26 | url: { 27 | type: String, 28 | required: true 29 | } 30 | }); 31 | 32 | bookmarkSchema.index({ title: "text" }); 33 | 34 | const Bookmark = mongoose.model("Bookmark", bookmarkSchema); 35 | 36 | export default Bookmark; 37 | -------------------------------------------------------------------------------- /gateway/.env.sample: -------------------------------------------------------------------------------- 1 | APOLLO_KEY= 2 | APOLLO_GRAPH_REF= 3 | 4 | AUTH0_AUDIENCE=http://localhost:4000/ 5 | AUTH0_ISSUER= 6 | 7 | NODE_ENV=development 8 | PORT=4000 9 | 10 | ACCOUNTS_ENDPOINT=http://localhost:4001 11 | PROFILES_ENDPOINT=http://localhost:4002 12 | BOOKMARKS_ENDPOINT=http://localhost:4003 13 | WORKFLOWS_ENDPOINT=http://localhost:4004 -------------------------------------------------------------------------------- /gateway/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "marked-gateway", 3 | "version": "1.0.0", 4 | "description": "The server for the gateway service in the Marked app.", 5 | "main": "src/index.js", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "nodemon -r dotenv/config -e env,js ./src/index.js" 9 | }, 10 | "keywords": [], 11 | "author": "mandiwise", 12 | "license": "MIT", 13 | "dependencies": { 14 | "@apollo/gateway": "^2.0.1", 15 | "apollo-server-express": "^3.6.7", 16 | "dotenv": "^16.0.0", 17 | "express": "^4.17.3", 18 | "express-jwt": "^6.1.1", 19 | "graphql": "^16.3.0", 20 | "graphql-depth-limit": "^1.1.0", 21 | "jwks-rsa": "^2.0.5" 22 | }, 23 | "devDependencies": { 24 | "nodemon": "^2.0.15" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /gateway/src/config/apollo.js: -------------------------------------------------------------------------------- 1 | import { 2 | ApolloGateway, 3 | IntrospectAndCompose, 4 | RemoteGraphQLDataSource 5 | } from "@apollo/gateway"; 6 | import { ApolloError, ApolloServer } from "apollo-server-express"; 7 | import { ApolloServerPluginDrainHttpServer } from "apollo-server-core"; 8 | import depthLimit from "graphql-depth-limit"; 9 | 10 | function initGateway(httpServer) { 11 | const gateway = new ApolloGateway({ 12 | // This is only used when running the gateway in unmanaged mode 13 | // supergraphSdl: new IntrospectAndCompose({ 14 | // subgraphs: [ 15 | // { name: "accounts", url: process.env.ACCOUNTS_ENDPOINT }, 16 | // { name: "profiles", url: process.env.PROFILES_ENDPOINT }, 17 | // { name: "bookmarks", url: process.env.BOOKMARKS_ENDPOINT }, 18 | // { name: "workflows", url: process.env.WORKFLOWS_ENDPOINT } 19 | // ], 20 | // pollIntervalInMs: 1000 21 | // }), 22 | buildService({ url }) { 23 | return new RemoteGraphQLDataSource({ 24 | apq: true, 25 | url, 26 | willSendRequest({ request, context }) { 27 | request.http.headers.set( 28 | "user", 29 | context.user ? JSON.stringify(context.user) : null 30 | ); 31 | } 32 | }); 33 | } 34 | }); 35 | 36 | return new ApolloServer({ 37 | gateway, 38 | plugins: [ApolloServerPluginDrainHttpServer({ httpServer })], 39 | context: ({ req }) => { 40 | const user = req.user || null; 41 | return { user }; 42 | }, 43 | validationRules: [depthLimit(10)], 44 | formatError: err => { 45 | if ( 46 | err.message.includes("Did you mean") && 47 | process.env.NODE_ENV !== "development" 48 | ) { 49 | return new ApolloError("Internal server error"); 50 | } 51 | 52 | return err; 53 | } 54 | }); 55 | } 56 | 57 | export default initGateway; 58 | -------------------------------------------------------------------------------- /gateway/src/config/app.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import jwt from "express-jwt"; 3 | import jwksClient from "jwks-rsa"; 4 | 5 | const app = express(); 6 | 7 | const jwtCheck = jwt({ 8 | secret: jwksClient.expressJwtSecret({ 9 | cache: true, 10 | rateLimit: true, 11 | jwksRequestsPerMinute: 5, 12 | jwksUri: `${process.env.AUTH0_ISSUER}.well-known/jwks.json` 13 | }), 14 | audience: process.env.AUTH0_AUDIENCE, 15 | issuer: process.env.AUTH0_ISSUER, 16 | algorithms: ["RS256"], 17 | credentialsRequired: false 18 | }); 19 | 20 | app.use(jwtCheck, (err, req, res, next) => { 21 | if (err.code === "invalid_token") { 22 | return next(); 23 | } 24 | return next(err); 25 | }); 26 | 27 | export default app; 28 | -------------------------------------------------------------------------------- /gateway/src/index.js: -------------------------------------------------------------------------------- 1 | import http from "http"; 2 | 3 | import app from "./config/app.js"; 4 | import initGateway from "./config/apollo.js"; 5 | 6 | const port = process.env.PORT; 7 | const httpServer = http.createServer(app); 8 | const gateway = initGateway(httpServer); 9 | 10 | await gateway.start(); 11 | gateway.applyMiddleware({ app, path: "/" }); 12 | 13 | await new Promise(resolve => httpServer.listen({ port }, resolve)); 14 | console.log(`Gateway ready at http://localhost:${port}${gateway.graphqlPath}`); 15 | -------------------------------------------------------------------------------- /profiles/.env.sample: -------------------------------------------------------------------------------- 1 | MONGODB_URL=mongodb://127.0.0.1:27017/marked-profiles 2 | 3 | NODE_ENV=development 4 | PORT=4002 -------------------------------------------------------------------------------- /profiles/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "marked-profiles", 3 | "version": "1.0.0", 4 | "description": "The server for the profiles service in the Marked app.", 5 | "main": "src/index.js", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "nodemon -r dotenv/config -e env,graphql,js ./src/index.js" 9 | }, 10 | "keywords": [], 11 | "author": "mandiwise", 12 | "license": "MIT", 13 | "dependencies": { 14 | "@apollo/subgraph": "^2.0.3", 15 | "apollo-datasource": "^3.3.1", 16 | "apollo-server": "^3.7.0", 17 | "dataloader": "^2.1.0", 18 | "dotenv": "^16.0.0", 19 | "graphql": "^16.5.0", 20 | "mongoose": "^6.3.0" 21 | }, 22 | "devDependencies": { 23 | "nodemon": "^2.0.15" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /profiles/src/config/mongoose.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | function initMongoose() { 4 | const connectionUrl = process.env.MONGODB_URL; 5 | mongoose.connect(connectionUrl); 6 | 7 | mongoose.connection.on("connected", () => { 8 | console.log(`Mongoose default connection ready at ${connectionUrl}`); 9 | }); 10 | 11 | mongoose.connection.on("error", error => { 12 | console.log("Mongoose default connection error:", error); 13 | }); 14 | } 15 | 16 | export default initMongoose; 17 | -------------------------------------------------------------------------------- /profiles/src/graphql/dataLoaders.js: -------------------------------------------------------------------------------- 1 | import DataLoader from "dataloader"; 2 | 3 | import Profile from "../models/Profile.js"; 4 | 5 | function initDataLoaders() { 6 | const profileLoader = new DataLoader(async keys => { 7 | const fieldName = Object.keys(keys[0])[0]; 8 | const fieldValues = keys.map(key => key[fieldName]); 9 | const uniqueFieldValues = [...new Set(fieldValues)]; 10 | const profiles = await Profile.find({ 11 | [fieldName]: { $in: uniqueFieldValues } 12 | }).exec(); 13 | 14 | return keys.map(key => 15 | profiles.find(profile => key[fieldName] === profile[fieldName].toString()) 16 | ); 17 | }); 18 | 19 | return { profileLoader }; 20 | } 21 | 22 | export default initDataLoaders; 23 | -------------------------------------------------------------------------------- /profiles/src/graphql/dataSources/ProfilesDataSource.js: -------------------------------------------------------------------------------- 1 | import { DataSource } from "apollo-datasource"; 2 | import { UserInputError } from "apollo-server"; 3 | 4 | import { Pagination } from "../../../../shared/src/index.js"; 5 | 6 | class ProfilesDataSource extends DataSource { 7 | constructor({ Profile }) { 8 | super(); 9 | this.Profile = Profile; 10 | this.pagination = new Pagination(Profile); 11 | } 12 | 13 | initialize(config) { 14 | this.context = config.context; 15 | } 16 | 17 | // UTILS 18 | 19 | _formatTags(tags) { 20 | return tags.map(tag => tag.replace(/\s+/g, "-").toLowerCase()); 21 | } 22 | 23 | _getProfileSort(sortEnum) { 24 | let sort = {}; 25 | const sortArgs = sortEnum.split("_"); 26 | const direction = sortArgs.pop(); 27 | const field = sortArgs 28 | .map(arg => arg.toLowerCase()) 29 | .map((arg, i) => 30 | i === 0 ? arg : arg.charAt(0).toUpperCase() + arg.slice(1) 31 | ) 32 | .join(""); 33 | sort[field] = direction === "DESC" ? -1 : 1; 34 | 35 | return sort; 36 | } 37 | 38 | // CREATE 39 | 40 | createProfile(profile) { 41 | if (profile.interests) { 42 | const formattedTags = this._formatTags(profile.interests); 43 | profile.interests = formattedTags; 44 | } 45 | 46 | const newProfile = new this.Profile(profile); 47 | return newProfile.save(); 48 | } 49 | 50 | // READ 51 | 52 | async checkViewerHasInNetwork(viewerAccountId, accountId) { 53 | const viewerProfile = await this.Profile.findOne({ 54 | accountId: viewerAccountId 55 | }) 56 | .select("network") 57 | .exec(); 58 | 59 | return viewerProfile.network.includes(accountId); 60 | } 61 | 62 | async getNetworkProfiles({ after, before, first, last, orderBy, network }) { 63 | const sort = this._getProfileSort(orderBy); 64 | const filter = { accountId: { $in: network } }; 65 | const queryArgs = { after, before, first, last, filter, sort }; 66 | const edges = await this.pagination.getEdges(queryArgs); 67 | const pageInfo = await this.pagination.getPageInfo(edges, queryArgs); 68 | 69 | return { edges, pageInfo }; 70 | } 71 | 72 | getProfile(filter) { 73 | return this.context.loaders.profileLoader.load(filter); 74 | } 75 | 76 | getProfileById(id) { 77 | return this.context.loaders.profileLoader.load({ _id: id }); 78 | } 79 | 80 | async getProfiles({ after, before, first, last, orderBy }) { 81 | const sort = this._getProfileSort(orderBy); 82 | const queryArgs = { after, before, first, last, sort }; 83 | const edges = await this.pagination.getEdges(queryArgs); 84 | const pageInfo = await this.pagination.getPageInfo(edges, queryArgs); 85 | 86 | return { edges, pageInfo }; 87 | } 88 | 89 | async searchProfiles({ after, first, searchString }) { 90 | const sort = { score: { $meta: "textScore" }, _id: -1 }; 91 | const filter = { $text: { $search: searchString } }; 92 | const queryArgs = { after, first, filter, sort }; 93 | const edges = await this.pagination.getEdges(queryArgs); 94 | const pageInfo = await this.pagination.getPageInfo(edges, queryArgs); 95 | 96 | return { edges, pageInfo }; 97 | } 98 | 99 | // UPDATE 100 | 101 | async addToNetwork(accountId, accountIdToFollow) { 102 | if (accountId === accountIdToFollow) { 103 | throw new UserInputError("User cannot be added to their own network."); 104 | } 105 | 106 | return await this.Profile.findOneAndUpdate( 107 | { accountId }, 108 | { $addToSet: { network: accountIdToFollow } }, 109 | { new: true } 110 | ); 111 | } 112 | 113 | async removeFromNetwork(accountId, accountIdToFollow) { 114 | return await this.Profile.findOneAndUpdate( 115 | { accountId }, 116 | { $pull: { network: accountIdToFollow } }, 117 | { new: true } 118 | ); 119 | } 120 | 121 | async removeUserFromNetworks(accountId) { 122 | try { 123 | await this.Profile.updateMany( 124 | { network: { $in: [accountId] } }, 125 | { $pull: { network: accountId } } 126 | ).exec(); 127 | return true; 128 | } catch { 129 | return false; 130 | } 131 | } 132 | 133 | async updateProfile(accountId, updatedProfileData) { 134 | if ( 135 | !updatedProfileData || 136 | (updatedProfileData && Object.keys(updatedProfileData).length === 0) 137 | ) { 138 | throw new UserInputError("You must supply some profile data to update."); 139 | } 140 | 141 | if (updatedProfileData.interests) { 142 | const formattedTags = this._formatTags(updatedProfileData.interests); 143 | updatedProfileData.interests = formattedTags; 144 | } 145 | 146 | return await this.Profile.findOneAndUpdate( 147 | { accountId }, 148 | updatedProfileData, 149 | { 150 | new: true 151 | } 152 | ).exec(); 153 | } 154 | 155 | // DELETE 156 | 157 | async deleteProfile(accountId) { 158 | try { 159 | await this.Profile.findOneAndDelete({ 160 | accountId 161 | }).exec(); 162 | return true; 163 | } catch { 164 | return false; 165 | } 166 | } 167 | } 168 | 169 | export default ProfilesDataSource; 170 | -------------------------------------------------------------------------------- /profiles/src/graphql/resolvers.js: -------------------------------------------------------------------------------- 1 | import { ApolloError, UserInputError } from "apollo-server"; 2 | 3 | import { DateTimeType } from "../../../shared/src/index.js"; 4 | 5 | const resolvers = { 6 | DateTime: DateTimeType, 7 | 8 | Account: { 9 | profile(account, args, { dataSources }) { 10 | return dataSources.profilesAPI.getProfile({ 11 | accountId: account.id 12 | }); 13 | } 14 | }, 15 | 16 | Profile: { 17 | __resolveReference(reference, { dataSources, user }) { 18 | if (user?.sub) { 19 | return reference.id 20 | ? dataSources.profilesAPI.getProfileById(reference.id) 21 | : dataSources.profilesAPI.getProfile({ 22 | accountId: reference.account.id 23 | }); 24 | } 25 | throw new ApolloError("Not authorized!"); 26 | }, 27 | account(profile) { 28 | return { id: profile.accountId }; 29 | }, 30 | network(profile, args, { dataSources }) { 31 | return dataSources.profilesAPI.getNetworkProfiles({ 32 | ...args, 33 | network: profile.network 34 | }); 35 | }, 36 | id(profile) { 37 | return profile._id; 38 | }, 39 | isInNetwork(profile, args, { dataSources, user }) { 40 | return dataSources.profilesAPI.checkViewerHasInNetwork( 41 | user.sub, 42 | profile.accountId 43 | ); 44 | } 45 | }, 46 | 47 | Query: { 48 | async profile(root, { username }, { dataSources }) { 49 | const profile = await dataSources.profilesAPI.getProfile({ username }); 50 | 51 | if (!profile) { 52 | throw new UserInputError("Profile not available."); 53 | } 54 | return profile; 55 | }, 56 | profiles(root, args, { dataSources }) { 57 | return dataSources.profilesAPI.getProfiles(args); 58 | }, 59 | searchProfiles(root, { query }, { dataSources }) { 60 | return dataSources.profilesAPI.searchProfiles(query); 61 | }, 62 | searchProfiles(parent, { after, first, query }, { dataSources }) { 63 | return dataSources.profilesAPI.searchProfiles({ 64 | after, 65 | first, 66 | searchString: query 67 | }); 68 | } 69 | }, 70 | 71 | Mutation: { 72 | addToNetwork( 73 | root, 74 | { input: { accountId, networkMemberId } }, 75 | { dataSources } 76 | ) { 77 | return dataSources.profilesAPI.addToNetwork(accountId, networkMemberId); 78 | }, 79 | createProfile(root, { input }, { dataSources }) { 80 | return dataSources.profilesAPI.createProfile(input); 81 | }, 82 | deleteProfile(root, { accountId }, { dataSources }) { 83 | return dataSources.profilesAPI.deleteProfile(accountId); 84 | }, 85 | removeFromNetwork( 86 | root, 87 | { input: { accountId, networkMemberId } }, 88 | { dataSources } 89 | ) { 90 | return dataSources.profilesAPI.removeFromNetwork( 91 | accountId, 92 | networkMemberId 93 | ); 94 | }, 95 | removeUserFromNetworks(root, { accountId }, { dataSources }) { 96 | return dataSources.profilesAPI.removeUserFromNetworks(accountId); 97 | }, 98 | updateProfile(root, { input: { accountId, ...rest } }, { dataSources }) { 99 | return dataSources.profilesAPI.updateProfile(accountId, rest); 100 | } 101 | } 102 | }; 103 | 104 | export default resolvers; 105 | -------------------------------------------------------------------------------- /profiles/src/graphql/schema.graphql: -------------------------------------------------------------------------------- 1 | extend schema 2 | @link(url: "https://specs.apollo.dev/federation/v2.0", 3 | import: ["@key", "@shareable"]) 4 | 5 | # SCALARS 6 | 7 | """ 8 | An ISO 8601-encoded UTC date string. 9 | """ 10 | scalar DateTime 11 | 12 | # ENUMS 13 | 14 | """ 15 | Sorting options for profile connections. 16 | """ 17 | enum ProfileOrderBy { 18 | "Order profiles ascending by creation time." 19 | CREATED_AT_ASC 20 | "Order profiles descending by creation time." 21 | CREATED_AT_DESC 22 | "Order profiles ascending by username." 23 | USERNAME_ASC 24 | "Order profiles descending by username." 25 | USERNAME_DESC 26 | } 27 | 28 | # OBJECTS 29 | 30 | """ 31 | An account is a unique Auth0 user. 32 | """ 33 | type Account @key(fields: "id") { 34 | id: ID! 35 | "Metadata about the user that owns the account." 36 | profile: Profile 37 | } 38 | 39 | """ 40 | Information about pagination in a connection. 41 | """ 42 | type PageInfo @shareable { 43 | "The cursor to continue from when paginating forward." 44 | endCursor: String 45 | "Whether there are more items when paginating forward." 46 | hasNextPage: Boolean! 47 | "Whether there are more items when paginating backward." 48 | hasPreviousPage: Boolean! 49 | "The cursor to continue from them paginating backward." 50 | startCursor: String 51 | } 52 | 53 | """ 54 | A profile contains metadata about a specific user. 55 | """ 56 | type Profile @key(fields: "id") @key(fields: "account { id }") { 57 | "The unique ID of the user's profile." 58 | id: ID! 59 | "The date and time the profile was created." 60 | createdAt: DateTime! 61 | "The Auth0 account tied to this profile." 62 | account: Account! 63 | "The full name of the user." 64 | fullName: String 65 | "A tag-like list of topics of interest to the user." 66 | interests: [String] 67 | "Whether the currently authenticated user has another user in their network." 68 | isInNetwork: Boolean 69 | "Other users that have been added to the user's network." 70 | network ( 71 | first: Int 72 | after: String 73 | last: Int 74 | before: String 75 | orderBy: ProfileOrderBy = USERNAME_ASC 76 | ): ProfileConnection 77 | "The unique username of the user." 78 | username: String! 79 | } 80 | 81 | """ 82 | A list of profile edges with pagination information. 83 | """ 84 | type ProfileConnection { 85 | "A list of profile edges." 86 | edges: [ProfileEdge] 87 | "Information to assist with pagination." 88 | pageInfo: PageInfo! 89 | } 90 | 91 | """ 92 | A single profile node with its cursor. 93 | """ 94 | type ProfileEdge { 95 | "A cursor for use in pagination." 96 | cursor: ID! 97 | "A profile at the end of an edge." 98 | node: Profile! 99 | } 100 | 101 | # INPUTS 102 | 103 | """ 104 | Provides data to create a new user profile. 105 | """ 106 | input CreateProfileInput { 107 | "The new user's unique Auth0 ID." 108 | accountId: ID! 109 | "The new user's full name." 110 | fullName: String 111 | "A tag-like list of topics of interest to the user." 112 | interests: [String] 113 | "The new user's username (must be unique)." 114 | username: String! 115 | } 116 | 117 | """ 118 | Provides the unique ID of an existing profile to add or remove from a network. 119 | """ 120 | input NetworkMemberInput { 121 | "The unique Auth0 ID of the user that is updating their network." 122 | accountId: ID! 123 | "The unique profile ID of the user to be added or removed from a network." 124 | networkMemberId: ID! 125 | } 126 | 127 | """ 128 | Provides data to update an existing profile. 129 | """ 130 | input UpdateProfileInput { 131 | "The new user's unique Auth0 ID." 132 | accountId: ID! 133 | "The updated full name of the user." 134 | fullName: String 135 | "An updated list of tag-like topics of interest to the user." 136 | interests: [String] 137 | "The updated unique username of the user." 138 | username: String 139 | } 140 | 141 | # ROOT 142 | 143 | type Query { 144 | "Retrieves a single profile by username." 145 | profile(username: String!): Profile! @private 146 | "Retrieves a list of profiles." 147 | profiles( 148 | after: String 149 | before: String 150 | first: Int 151 | last: Int 152 | orderBy: ProfileOrderBy = USERNAME_ASC 153 | ): ProfileConnection @private 154 | "Performs a search of user profiles. Results are available in descending order by relevance only." 155 | searchProfiles( 156 | after: String 157 | first: Int 158 | "The text string to search for in usernames or full names." 159 | query: String! 160 | ): ProfileConnection @private 161 | } 162 | 163 | type Mutation { 164 | "Allows one user to add another user to their network." 165 | addToNetwork(input: NetworkMemberInput!): Profile! 166 | @owner(argumentName: "input.accountId") 167 | "Creates a new profile tied to an Auth0 account." 168 | createProfile(input: CreateProfileInput!): Profile! 169 | @owner(argumentName: "input.accountId") 170 | "Deletes a user profile." 171 | deleteProfile(accountId: ID!): Boolean! 172 | @scope(permissions: ["delete:profiles"]) 173 | "Allows one user to remove another user from their network." 174 | removeFromNetwork(input: NetworkMemberInput!): Profile! 175 | @owner(argumentName: "input.accountId") 176 | "Remove user from other users' networks" 177 | removeUserFromNetworks(accountId: ID!): Boolean! 178 | @scope(permissions: ["update:profiles"]) 179 | "Updates a user's profile details." 180 | updateProfile(input: UpdateProfileInput!): Profile! 181 | @owner(argumentName: "input.accountId") 182 | } 183 | -------------------------------------------------------------------------------- /profiles/src/index.js: -------------------------------------------------------------------------------- 1 | import { dirname, resolve } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { readFileSync } from "fs"; 4 | 5 | import { ApolloServer, gql } from "apollo-server"; 6 | import { buildSubgraphSchema } from "@apollo/subgraph"; 7 | 8 | import { 9 | authDirectives, 10 | restoreReferenceResolvers 11 | } from "../../shared/src/index.js"; 12 | import initDataLoaders from "./graphql/dataLoaders.js"; 13 | import initMongoose from "./config/mongoose.js"; 14 | import Profile from "./models/Profile.js"; 15 | import ProfilesDataSource from "./graphql/dataSources/ProfilesDataSource.js"; 16 | import resolvers from "./graphql/resolvers.js"; 17 | 18 | const __dirname = dirname(fileURLToPath(import.meta.url)); 19 | const port = process.env.PORT; 20 | 21 | const { authDirectivesTypeDefs, authDirectivesTransformer } = authDirectives(); 22 | const subgraphTypeDefs = readFileSync( 23 | resolve(__dirname, "./graphql/schema.graphql"), 24 | "utf-8" 25 | ); 26 | const typeDefs = gql(`${subgraphTypeDefs}\n${authDirectivesTypeDefs}`); 27 | let subgraphSchema = buildSubgraphSchema({ typeDefs, resolvers }); 28 | subgraphSchema = authDirectivesTransformer(subgraphSchema); 29 | restoreReferenceResolvers(subgraphSchema, resolvers); 30 | 31 | const server = new ApolloServer({ 32 | schema: subgraphSchema, 33 | context: ({ req }) => { 34 | const user = req.headers.user ? JSON.parse(req.headers.user) : null; 35 | return { user, loaders: initDataLoaders() }; 36 | }, 37 | dataSources: () => { 38 | return { 39 | profilesAPI: new ProfilesDataSource({ Profile }) 40 | }; 41 | } 42 | }); 43 | 44 | initMongoose(); 45 | 46 | server.listen({ port }).then(({ url }) => { 47 | console.log(`Profiles service ready at ${url}`); 48 | }); 49 | -------------------------------------------------------------------------------- /profiles/src/models/Profile.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const profileSchema = new mongoose.Schema({ 4 | accountId: { 5 | type: String, 6 | required: true, 7 | unique: true 8 | }, 9 | createdAt: { 10 | type: Date, 11 | default: Date.now, 12 | required: true 13 | }, 14 | fullName: { 15 | type: String, 16 | trim: true 17 | }, 18 | interests: [String], 19 | network: [String], 20 | username: { 21 | type: String, 22 | required: true, 23 | trim: true, 24 | unique: true 25 | } 26 | }); 27 | 28 | profileSchema.index({ fullName: "text", username: "text" }); 29 | 30 | const Profile = mongoose.model("Profile", profileSchema); 31 | 32 | export default Profile; 33 | -------------------------------------------------------------------------------- /router/.env.sample: -------------------------------------------------------------------------------- 1 | APOLLO_KEY= 2 | APOLLO_GRAPH_REF= 3 | APOLLO_ROUTER_HOT_RELOAD=true -------------------------------------------------------------------------------- /router/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | apollo-router: 3 | image: ghcr.io/apollographql/router:v0.9.2 4 | container_name: apollo-router 5 | env_file: 6 | - ./.env 7 | volumes: 8 | - ./router.yaml:/dist/config/router.yaml 9 | ports: 10 | - "5000:5000" 11 | -------------------------------------------------------------------------------- /router/router.yaml: -------------------------------------------------------------------------------- 1 | server: 2 | listen: 0.0.0.0:5000 3 | cors: 4 | origins: 5 | - https://studio.apollographql.com 6 | allow_headers: [Content-Type, Authorization] 7 | headers: 8 | all: 9 | - propagate: 10 | named: "user" 11 | override_subgraph_url: 12 | accounts: http://host.docker.internal:4001 13 | profiles: http://host.docker.internal:4002 14 | bookmarks: http://host.docker.internal:4003 15 | workflows: http://host.docker.internal:4004 16 | -------------------------------------------------------------------------------- /shared/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "marked-shared", 3 | "version": "1.0.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "marked-shared", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@graphql-tools/utils": "^8.6.7", 13 | "apollo-server": "^3.6.7", 14 | "graphql": "^16.3.0", 15 | "lodash-es": "^4.17.21", 16 | "validator": "^13.7.0" 17 | } 18 | }, 19 | "node_modules/@apollo/protobufjs": { 20 | "version": "1.2.2", 21 | "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.2.2.tgz", 22 | "integrity": "sha512-vF+zxhPiLtkwxONs6YanSt1EpwpGilThpneExUN5K3tCymuxNnVq2yojTvnpRjv2QfsEIt/n7ozPIIzBLwGIDQ==", 23 | "hasInstallScript": true, 24 | "dependencies": { 25 | "@protobufjs/aspromise": "^1.1.2", 26 | "@protobufjs/base64": "^1.1.2", 27 | "@protobufjs/codegen": "^2.0.4", 28 | "@protobufjs/eventemitter": "^1.1.0", 29 | "@protobufjs/fetch": "^1.1.0", 30 | "@protobufjs/float": "^1.0.2", 31 | "@protobufjs/inquire": "^1.1.0", 32 | "@protobufjs/path": "^1.1.2", 33 | "@protobufjs/pool": "^1.1.0", 34 | "@protobufjs/utf8": "^1.1.0", 35 | "@types/long": "^4.0.0", 36 | "@types/node": "^10.1.0", 37 | "long": "^4.0.0" 38 | }, 39 | "bin": { 40 | "apollo-pbjs": "bin/pbjs", 41 | "apollo-pbts": "bin/pbts" 42 | } 43 | }, 44 | "node_modules/@apollographql/apollo-tools": { 45 | "version": "0.5.3", 46 | "resolved": "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.5.3.tgz", 47 | "integrity": "sha512-VcsXHfTFoCodDAgJZxN04GdFK1kqOhZQnQY/9Fa147P+I8xfvOSz5d+lKAPB+hwSgBNyd7ncAKGIs4+utbL+yA==", 48 | "engines": { 49 | "node": ">=8", 50 | "npm": ">=6" 51 | }, 52 | "peerDependencies": { 53 | "graphql": "^14.2.1 || ^15.0.0 || ^16.0.0" 54 | } 55 | }, 56 | "node_modules/@apollographql/graphql-playground-html": { 57 | "version": "1.6.29", 58 | "resolved": "https://registry.npmjs.org/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.29.tgz", 59 | "integrity": "sha512-xCcXpoz52rI4ksJSdOCxeOCn2DLocxwHf9dVT/Q90Pte1LX+LY+91SFtJF3KXVHH8kEin+g1KKCQPKBjZJfWNA==", 60 | "dependencies": { 61 | "xss": "^1.0.8" 62 | } 63 | }, 64 | "node_modules/@graphql-tools/merge": { 65 | "version": "8.2.7", 66 | "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.2.7.tgz", 67 | "integrity": "sha512-rKxjNogqu1UYAG/y5FOb6lJsmSQbWA+jq4inWjNEVX54VGGE7/WGnmPaqcsyomNOfS3vIRS6NnG+DxiQSqetjg==", 68 | "dependencies": { 69 | "@graphql-tools/utils": "8.6.6", 70 | "tslib": "~2.3.0" 71 | }, 72 | "peerDependencies": { 73 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" 74 | } 75 | }, 76 | "node_modules/@graphql-tools/merge/node_modules/@graphql-tools/utils": { 77 | "version": "8.6.6", 78 | "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.6.6.tgz", 79 | "integrity": "sha512-wjY2ljKLCnnbRrDNPPgPNqCujou0LFSOWcxAjV6DYUlfFWTsAEvlYmsmY4T+K12wI/fnqoJ2bUwIlap1plFDMg==", 80 | "dependencies": { 81 | "tslib": "~2.3.0" 82 | }, 83 | "peerDependencies": { 84 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" 85 | } 86 | }, 87 | "node_modules/@graphql-tools/mock": { 88 | "version": "8.6.5", 89 | "resolved": "https://registry.npmjs.org/@graphql-tools/mock/-/mock-8.6.5.tgz", 90 | "integrity": "sha512-2Uz2qerJVU7wLDQseWNmgCUwiLgM3DfFJEPeQRC1s6oQELstKZHSW8Fb45mmjt37fyKqUdwPUT65PwzK03CK/Q==", 91 | "dependencies": { 92 | "@graphql-tools/schema": "8.3.7", 93 | "@graphql-tools/utils": "8.6.6", 94 | "fast-json-stable-stringify": "^2.1.0", 95 | "tslib": "~2.3.0" 96 | }, 97 | "peerDependencies": { 98 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" 99 | } 100 | }, 101 | "node_modules/@graphql-tools/mock/node_modules/@graphql-tools/utils": { 102 | "version": "8.6.6", 103 | "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.6.6.tgz", 104 | "integrity": "sha512-wjY2ljKLCnnbRrDNPPgPNqCujou0LFSOWcxAjV6DYUlfFWTsAEvlYmsmY4T+K12wI/fnqoJ2bUwIlap1plFDMg==", 105 | "dependencies": { 106 | "tslib": "~2.3.0" 107 | }, 108 | "peerDependencies": { 109 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" 110 | } 111 | }, 112 | "node_modules/@graphql-tools/schema": { 113 | "version": "8.3.7", 114 | "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-8.3.7.tgz", 115 | "integrity": "sha512-7byr9J6rfMPFPfiR4u65dy20xHATTvbgOY7KYd1sYPnMKKfRZe0tUgpnE+noXcfob7N8s366WaVh7bEoztQMwg==", 116 | "dependencies": { 117 | "@graphql-tools/merge": "8.2.7", 118 | "@graphql-tools/utils": "8.6.6", 119 | "tslib": "~2.3.0", 120 | "value-or-promise": "1.0.11" 121 | }, 122 | "peerDependencies": { 123 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" 124 | } 125 | }, 126 | "node_modules/@graphql-tools/schema/node_modules/@graphql-tools/utils": { 127 | "version": "8.6.6", 128 | "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.6.6.tgz", 129 | "integrity": "sha512-wjY2ljKLCnnbRrDNPPgPNqCujou0LFSOWcxAjV6DYUlfFWTsAEvlYmsmY4T+K12wI/fnqoJ2bUwIlap1plFDMg==", 130 | "dependencies": { 131 | "tslib": "~2.3.0" 132 | }, 133 | "peerDependencies": { 134 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" 135 | } 136 | }, 137 | "node_modules/@graphql-tools/utils": { 138 | "version": "8.6.7", 139 | "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.6.7.tgz", 140 | "integrity": "sha512-Qi3EN95Rt3hb8CyDKpPKFWOPrnc00P18cpVTXEgtKxetSP39beJBeEEtLB0R53eP/6IolsyTZOTgkET1EaERaw==", 141 | "dependencies": { 142 | "tslib": "~2.3.0" 143 | }, 144 | "peerDependencies": { 145 | "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" 146 | } 147 | }, 148 | "node_modules/@josephg/resolvable": { 149 | "version": "1.0.1", 150 | "resolved": "https://registry.npmjs.org/@josephg/resolvable/-/resolvable-1.0.1.tgz", 151 | "integrity": "sha512-CtzORUwWTTOTqfVtHaKRJ0I1kNQd1bpn3sUh8I3nJDVY+5/M/Oe1DnEWzPQvqq/xPIIkzzzIP7mfCoAjFRvDhg==" 152 | }, 153 | "node_modules/@protobufjs/aspromise": { 154 | "version": "1.1.2", 155 | "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", 156 | "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" 157 | }, 158 | "node_modules/@protobufjs/base64": { 159 | "version": "1.1.2", 160 | "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", 161 | "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" 162 | }, 163 | "node_modules/@protobufjs/codegen": { 164 | "version": "2.0.4", 165 | "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", 166 | "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" 167 | }, 168 | "node_modules/@protobufjs/eventemitter": { 169 | "version": "1.1.0", 170 | "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", 171 | "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" 172 | }, 173 | "node_modules/@protobufjs/fetch": { 174 | "version": "1.1.0", 175 | "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", 176 | "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", 177 | "dependencies": { 178 | "@protobufjs/aspromise": "^1.1.1", 179 | "@protobufjs/inquire": "^1.1.0" 180 | } 181 | }, 182 | "node_modules/@protobufjs/float": { 183 | "version": "1.0.2", 184 | "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", 185 | "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" 186 | }, 187 | "node_modules/@protobufjs/inquire": { 188 | "version": "1.1.0", 189 | "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", 190 | "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" 191 | }, 192 | "node_modules/@protobufjs/path": { 193 | "version": "1.1.2", 194 | "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", 195 | "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" 196 | }, 197 | "node_modules/@protobufjs/pool": { 198 | "version": "1.1.0", 199 | "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", 200 | "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" 201 | }, 202 | "node_modules/@protobufjs/utf8": { 203 | "version": "1.1.0", 204 | "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", 205 | "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" 206 | }, 207 | "node_modules/@types/accepts": { 208 | "version": "1.3.5", 209 | "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz", 210 | "integrity": "sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==", 211 | "dependencies": { 212 | "@types/node": "*" 213 | } 214 | }, 215 | "node_modules/@types/body-parser": { 216 | "version": "1.19.2", 217 | "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", 218 | "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", 219 | "dependencies": { 220 | "@types/connect": "*", 221 | "@types/node": "*" 222 | } 223 | }, 224 | "node_modules/@types/connect": { 225 | "version": "3.4.35", 226 | "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", 227 | "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", 228 | "dependencies": { 229 | "@types/node": "*" 230 | } 231 | }, 232 | "node_modules/@types/cors": { 233 | "version": "2.8.12", 234 | "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", 235 | "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" 236 | }, 237 | "node_modules/@types/express": { 238 | "version": "4.17.13", 239 | "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", 240 | "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", 241 | "dependencies": { 242 | "@types/body-parser": "*", 243 | "@types/express-serve-static-core": "^4.17.18", 244 | "@types/qs": "*", 245 | "@types/serve-static": "*" 246 | } 247 | }, 248 | "node_modules/@types/express-serve-static-core": { 249 | "version": "4.17.28", 250 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", 251 | "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", 252 | "dependencies": { 253 | "@types/node": "*", 254 | "@types/qs": "*", 255 | "@types/range-parser": "*" 256 | } 257 | }, 258 | "node_modules/@types/long": { 259 | "version": "4.0.1", 260 | "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", 261 | "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" 262 | }, 263 | "node_modules/@types/mime": { 264 | "version": "1.3.2", 265 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", 266 | "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" 267 | }, 268 | "node_modules/@types/node": { 269 | "version": "10.17.60", 270 | "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", 271 | "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==" 272 | }, 273 | "node_modules/@types/qs": { 274 | "version": "6.9.7", 275 | "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", 276 | "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" 277 | }, 278 | "node_modules/@types/range-parser": { 279 | "version": "1.2.4", 280 | "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", 281 | "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" 282 | }, 283 | "node_modules/@types/serve-static": { 284 | "version": "1.13.10", 285 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", 286 | "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", 287 | "dependencies": { 288 | "@types/mime": "^1", 289 | "@types/node": "*" 290 | } 291 | }, 292 | "node_modules/accepts": { 293 | "version": "1.3.8", 294 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 295 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 296 | "dependencies": { 297 | "mime-types": "~2.1.34", 298 | "negotiator": "0.6.3" 299 | }, 300 | "engines": { 301 | "node": ">= 0.6" 302 | } 303 | }, 304 | "node_modules/apollo-datasource": { 305 | "version": "3.3.1", 306 | "resolved": "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-3.3.1.tgz", 307 | "integrity": "sha512-Z3a8rEUXVPIZ1p8xrFL8bcNhWmhOmovgDArvwIwmJOBnh093ZpRfO+ESJEDAN4KswmyzCLDAwjsW4zQOONdRUw==", 308 | "dependencies": { 309 | "apollo-server-caching": "^3.3.0", 310 | "apollo-server-env": "^4.2.1" 311 | }, 312 | "engines": { 313 | "node": ">=12.0" 314 | } 315 | }, 316 | "node_modules/apollo-reporting-protobuf": { 317 | "version": "3.3.1", 318 | "resolved": "https://registry.npmjs.org/apollo-reporting-protobuf/-/apollo-reporting-protobuf-3.3.1.tgz", 319 | "integrity": "sha512-tyvj3Vj71TCh6c8PtdHOLgHHBSJ05DF/A/Po3q8yfHTBkOPcOJZE/GGN/PT/pwKg7HHxKcAeHDw7+xciVvGx0w==", 320 | "dependencies": { 321 | "@apollo/protobufjs": "1.2.2" 322 | } 323 | }, 324 | "node_modules/apollo-server": { 325 | "version": "3.6.7", 326 | "resolved": "https://registry.npmjs.org/apollo-server/-/apollo-server-3.6.7.tgz", 327 | "integrity": "sha512-WERZqaVCkZJMoOc9y692NribgNtKbHDjOwiAmgXI2WBlON2oUvCwgxPvsMg+bXVpQx4itrMyj31a2N6BeKmbmQ==", 328 | "dependencies": { 329 | "apollo-server-core": "^3.6.7", 330 | "apollo-server-express": "^3.6.7", 331 | "express": "^4.17.1" 332 | }, 333 | "peerDependencies": { 334 | "graphql": "^15.3.0 || ^16.0.0" 335 | } 336 | }, 337 | "node_modules/apollo-server-caching": { 338 | "version": "3.3.0", 339 | "resolved": "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-3.3.0.tgz", 340 | "integrity": "sha512-Wgcb0ArjZ5DjQ7ID+tvxUcZ7Yxdbk5l1MxZL8D8gkyjooOkhPNzjRVQ7ubPoXqO54PrOMOTm1ejVhsF+AfIirQ==", 341 | "dependencies": { 342 | "lru-cache": "^6.0.0" 343 | }, 344 | "engines": { 345 | "node": ">=12.0" 346 | } 347 | }, 348 | "node_modules/apollo-server-core": { 349 | "version": "3.6.7", 350 | "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-3.6.7.tgz", 351 | "integrity": "sha512-OnZ9vu7LrYy2rvEu+nbgqucw6VyTSIPAEjK87c4rkzlVOxpwtGUaQ4FMWD9zBIj7yLq9q22b638E8LdYoaTAjQ==", 352 | "dependencies": { 353 | "@apollographql/apollo-tools": "^0.5.3", 354 | "@apollographql/graphql-playground-html": "1.6.29", 355 | "@graphql-tools/mock": "^8.1.2", 356 | "@graphql-tools/schema": "^8.0.0", 357 | "@josephg/resolvable": "^1.0.0", 358 | "apollo-datasource": "^3.3.1", 359 | "apollo-reporting-protobuf": "^3.3.1", 360 | "apollo-server-caching": "^3.3.0", 361 | "apollo-server-env": "^4.2.1", 362 | "apollo-server-errors": "^3.3.1", 363 | "apollo-server-plugin-base": "^3.5.2", 364 | "apollo-server-types": "^3.5.2", 365 | "async-retry": "^1.2.1", 366 | "fast-json-stable-stringify": "^2.1.0", 367 | "graphql-tag": "^2.11.0", 368 | "lodash.sortby": "^4.7.0", 369 | "loglevel": "^1.6.8", 370 | "lru-cache": "^6.0.0", 371 | "sha.js": "^2.4.11", 372 | "uuid": "^8.0.0" 373 | }, 374 | "engines": { 375 | "node": ">=12.0" 376 | }, 377 | "peerDependencies": { 378 | "graphql": "^15.3.0 || ^16.0.0" 379 | } 380 | }, 381 | "node_modules/apollo-server-env": { 382 | "version": "4.2.1", 383 | "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-4.2.1.tgz", 384 | "integrity": "sha512-vm/7c7ld+zFMxibzqZ7SSa5tBENc4B0uye9LTfjJwGoQFY5xsUPH5FpO5j0bMUDZ8YYNbrF9SNtzc5Cngcr90g==", 385 | "dependencies": { 386 | "node-fetch": "^2.6.7" 387 | }, 388 | "engines": { 389 | "node": ">=12.0" 390 | } 391 | }, 392 | "node_modules/apollo-server-errors": { 393 | "version": "3.3.1", 394 | "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-3.3.1.tgz", 395 | "integrity": "sha512-xnZJ5QWs6FixHICXHxUfm+ZWqqxrNuPlQ+kj5m6RtEgIpekOPssH/SD9gf2B4HuWV0QozorrygwZnux8POvyPA==", 396 | "engines": { 397 | "node": ">=12.0" 398 | }, 399 | "peerDependencies": { 400 | "graphql": "^15.3.0 || ^16.0.0" 401 | } 402 | }, 403 | "node_modules/apollo-server-express": { 404 | "version": "3.6.7", 405 | "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-3.6.7.tgz", 406 | "integrity": "sha512-B4gH5j8t3XxTCIa9bl7Iq/F3YFzMxX/LV4Sc+L/3xkHm648u576G5Lkskl8HsoTGSzzyeVcVsPDoYHiBjCAN0Q==", 407 | "dependencies": { 408 | "@types/accepts": "^1.3.5", 409 | "@types/body-parser": "1.19.2", 410 | "@types/cors": "2.8.12", 411 | "@types/express": "4.17.13", 412 | "@types/express-serve-static-core": "4.17.28", 413 | "accepts": "^1.3.5", 414 | "apollo-server-core": "^3.6.7", 415 | "apollo-server-types": "^3.5.2", 416 | "body-parser": "^1.19.0", 417 | "cors": "^2.8.5", 418 | "parseurl": "^1.3.3" 419 | }, 420 | "engines": { 421 | "node": ">=12.0" 422 | }, 423 | "peerDependencies": { 424 | "express": "^4.17.1", 425 | "graphql": "^15.3.0 || ^16.0.0" 426 | } 427 | }, 428 | "node_modules/apollo-server-plugin-base": { 429 | "version": "3.5.2", 430 | "resolved": "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-3.5.2.tgz", 431 | "integrity": "sha512-SwIf1waDmNDb0kmn57QR++InwK6Iv/X2slpm/aFIoqFBe91r6uJfakJvQZuh8dLEgk68gxqFsT8zHRpxBclE+g==", 432 | "dependencies": { 433 | "apollo-server-types": "^3.5.2" 434 | }, 435 | "engines": { 436 | "node": ">=12.0" 437 | }, 438 | "peerDependencies": { 439 | "graphql": "^15.3.0 || ^16.0.0" 440 | } 441 | }, 442 | "node_modules/apollo-server-types": { 443 | "version": "3.5.2", 444 | "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-3.5.2.tgz", 445 | "integrity": "sha512-vhcbIWsBkoNibABOym4AAPBoNDjokhjUQokKYdwZMeqrb850PMQdNJFrGyXT5onP408Ghv4O8PfgBuPQmeJhVQ==", 446 | "dependencies": { 447 | "apollo-reporting-protobuf": "^3.3.1", 448 | "apollo-server-caching": "^3.3.0", 449 | "apollo-server-env": "^4.2.1" 450 | }, 451 | "engines": { 452 | "node": ">=12.0" 453 | }, 454 | "peerDependencies": { 455 | "graphql": "^15.3.0 || ^16.0.0" 456 | } 457 | }, 458 | "node_modules/array-flatten": { 459 | "version": "1.1.1", 460 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 461 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 462 | }, 463 | "node_modules/async-retry": { 464 | "version": "1.3.3", 465 | "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", 466 | "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", 467 | "dependencies": { 468 | "retry": "0.13.1" 469 | } 470 | }, 471 | "node_modules/body-parser": { 472 | "version": "1.20.0", 473 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", 474 | "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", 475 | "dependencies": { 476 | "bytes": "3.1.2", 477 | "content-type": "~1.0.4", 478 | "debug": "2.6.9", 479 | "depd": "2.0.0", 480 | "destroy": "1.2.0", 481 | "http-errors": "2.0.0", 482 | "iconv-lite": "0.4.24", 483 | "on-finished": "2.4.1", 484 | "qs": "6.10.3", 485 | "raw-body": "2.5.1", 486 | "type-is": "~1.6.18", 487 | "unpipe": "1.0.0" 488 | }, 489 | "engines": { 490 | "node": ">= 0.8", 491 | "npm": "1.2.8000 || >= 1.4.16" 492 | } 493 | }, 494 | "node_modules/bytes": { 495 | "version": "3.1.2", 496 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 497 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 498 | "engines": { 499 | "node": ">= 0.8" 500 | } 501 | }, 502 | "node_modules/call-bind": { 503 | "version": "1.0.2", 504 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 505 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 506 | "dependencies": { 507 | "function-bind": "^1.1.1", 508 | "get-intrinsic": "^1.0.2" 509 | }, 510 | "funding": { 511 | "url": "https://github.com/sponsors/ljharb" 512 | } 513 | }, 514 | "node_modules/commander": { 515 | "version": "2.20.3", 516 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 517 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" 518 | }, 519 | "node_modules/content-disposition": { 520 | "version": "0.5.4", 521 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 522 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 523 | "dependencies": { 524 | "safe-buffer": "5.2.1" 525 | }, 526 | "engines": { 527 | "node": ">= 0.6" 528 | } 529 | }, 530 | "node_modules/content-type": { 531 | "version": "1.0.4", 532 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 533 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", 534 | "engines": { 535 | "node": ">= 0.6" 536 | } 537 | }, 538 | "node_modules/cookie": { 539 | "version": "0.4.2", 540 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", 541 | "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", 542 | "engines": { 543 | "node": ">= 0.6" 544 | } 545 | }, 546 | "node_modules/cookie-signature": { 547 | "version": "1.0.6", 548 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 549 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 550 | }, 551 | "node_modules/cors": { 552 | "version": "2.8.5", 553 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 554 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 555 | "dependencies": { 556 | "object-assign": "^4", 557 | "vary": "^1" 558 | }, 559 | "engines": { 560 | "node": ">= 0.10" 561 | } 562 | }, 563 | "node_modules/cssfilter": { 564 | "version": "0.0.10", 565 | "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", 566 | "integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=" 567 | }, 568 | "node_modules/debug": { 569 | "version": "2.6.9", 570 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 571 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 572 | "dependencies": { 573 | "ms": "2.0.0" 574 | } 575 | }, 576 | "node_modules/depd": { 577 | "version": "2.0.0", 578 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 579 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 580 | "engines": { 581 | "node": ">= 0.8" 582 | } 583 | }, 584 | "node_modules/destroy": { 585 | "version": "1.2.0", 586 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 587 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", 588 | "engines": { 589 | "node": ">= 0.8", 590 | "npm": "1.2.8000 || >= 1.4.16" 591 | } 592 | }, 593 | "node_modules/ee-first": { 594 | "version": "1.1.1", 595 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 596 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 597 | }, 598 | "node_modules/encodeurl": { 599 | "version": "1.0.2", 600 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 601 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", 602 | "engines": { 603 | "node": ">= 0.8" 604 | } 605 | }, 606 | "node_modules/escape-html": { 607 | "version": "1.0.3", 608 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 609 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 610 | }, 611 | "node_modules/etag": { 612 | "version": "1.8.1", 613 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 614 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", 615 | "engines": { 616 | "node": ">= 0.6" 617 | } 618 | }, 619 | "node_modules/express": { 620 | "version": "4.17.3", 621 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", 622 | "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", 623 | "dependencies": { 624 | "accepts": "~1.3.8", 625 | "array-flatten": "1.1.1", 626 | "body-parser": "1.19.2", 627 | "content-disposition": "0.5.4", 628 | "content-type": "~1.0.4", 629 | "cookie": "0.4.2", 630 | "cookie-signature": "1.0.6", 631 | "debug": "2.6.9", 632 | "depd": "~1.1.2", 633 | "encodeurl": "~1.0.2", 634 | "escape-html": "~1.0.3", 635 | "etag": "~1.8.1", 636 | "finalhandler": "~1.1.2", 637 | "fresh": "0.5.2", 638 | "merge-descriptors": "1.0.1", 639 | "methods": "~1.1.2", 640 | "on-finished": "~2.3.0", 641 | "parseurl": "~1.3.3", 642 | "path-to-regexp": "0.1.7", 643 | "proxy-addr": "~2.0.7", 644 | "qs": "6.9.7", 645 | "range-parser": "~1.2.1", 646 | "safe-buffer": "5.2.1", 647 | "send": "0.17.2", 648 | "serve-static": "1.14.2", 649 | "setprototypeof": "1.2.0", 650 | "statuses": "~1.5.0", 651 | "type-is": "~1.6.18", 652 | "utils-merge": "1.0.1", 653 | "vary": "~1.1.2" 654 | }, 655 | "engines": { 656 | "node": ">= 0.10.0" 657 | } 658 | }, 659 | "node_modules/express/node_modules/body-parser": { 660 | "version": "1.19.2", 661 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", 662 | "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", 663 | "dependencies": { 664 | "bytes": "3.1.2", 665 | "content-type": "~1.0.4", 666 | "debug": "2.6.9", 667 | "depd": "~1.1.2", 668 | "http-errors": "1.8.1", 669 | "iconv-lite": "0.4.24", 670 | "on-finished": "~2.3.0", 671 | "qs": "6.9.7", 672 | "raw-body": "2.4.3", 673 | "type-is": "~1.6.18" 674 | }, 675 | "engines": { 676 | "node": ">= 0.8" 677 | } 678 | }, 679 | "node_modules/express/node_modules/depd": { 680 | "version": "1.1.2", 681 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 682 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", 683 | "engines": { 684 | "node": ">= 0.6" 685 | } 686 | }, 687 | "node_modules/express/node_modules/http-errors": { 688 | "version": "1.8.1", 689 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", 690 | "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", 691 | "dependencies": { 692 | "depd": "~1.1.2", 693 | "inherits": "2.0.4", 694 | "setprototypeof": "1.2.0", 695 | "statuses": ">= 1.5.0 < 2", 696 | "toidentifier": "1.0.1" 697 | }, 698 | "engines": { 699 | "node": ">= 0.6" 700 | } 701 | }, 702 | "node_modules/express/node_modules/on-finished": { 703 | "version": "2.3.0", 704 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 705 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 706 | "dependencies": { 707 | "ee-first": "1.1.1" 708 | }, 709 | "engines": { 710 | "node": ">= 0.8" 711 | } 712 | }, 713 | "node_modules/express/node_modules/qs": { 714 | "version": "6.9.7", 715 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", 716 | "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", 717 | "engines": { 718 | "node": ">=0.6" 719 | }, 720 | "funding": { 721 | "url": "https://github.com/sponsors/ljharb" 722 | } 723 | }, 724 | "node_modules/express/node_modules/raw-body": { 725 | "version": "2.4.3", 726 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", 727 | "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", 728 | "dependencies": { 729 | "bytes": "3.1.2", 730 | "http-errors": "1.8.1", 731 | "iconv-lite": "0.4.24", 732 | "unpipe": "1.0.0" 733 | }, 734 | "engines": { 735 | "node": ">= 0.8" 736 | } 737 | }, 738 | "node_modules/fast-json-stable-stringify": { 739 | "version": "2.1.0", 740 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 741 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" 742 | }, 743 | "node_modules/finalhandler": { 744 | "version": "1.1.2", 745 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 746 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 747 | "dependencies": { 748 | "debug": "2.6.9", 749 | "encodeurl": "~1.0.2", 750 | "escape-html": "~1.0.3", 751 | "on-finished": "~2.3.0", 752 | "parseurl": "~1.3.3", 753 | "statuses": "~1.5.0", 754 | "unpipe": "~1.0.0" 755 | }, 756 | "engines": { 757 | "node": ">= 0.8" 758 | } 759 | }, 760 | "node_modules/finalhandler/node_modules/on-finished": { 761 | "version": "2.3.0", 762 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 763 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 764 | "dependencies": { 765 | "ee-first": "1.1.1" 766 | }, 767 | "engines": { 768 | "node": ">= 0.8" 769 | } 770 | }, 771 | "node_modules/forwarded": { 772 | "version": "0.2.0", 773 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 774 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 775 | "engines": { 776 | "node": ">= 0.6" 777 | } 778 | }, 779 | "node_modules/fresh": { 780 | "version": "0.5.2", 781 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 782 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", 783 | "engines": { 784 | "node": ">= 0.6" 785 | } 786 | }, 787 | "node_modules/function-bind": { 788 | "version": "1.1.1", 789 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 790 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 791 | }, 792 | "node_modules/get-intrinsic": { 793 | "version": "1.1.1", 794 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", 795 | "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", 796 | "dependencies": { 797 | "function-bind": "^1.1.1", 798 | "has": "^1.0.3", 799 | "has-symbols": "^1.0.1" 800 | }, 801 | "funding": { 802 | "url": "https://github.com/sponsors/ljharb" 803 | } 804 | }, 805 | "node_modules/graphql": { 806 | "version": "16.3.0", 807 | "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.3.0.tgz", 808 | "integrity": "sha512-xm+ANmA16BzCT5pLjuXySbQVFwH3oJctUVdy81w1sV0vBU0KgDdBGtxQOUd5zqOBk/JayAFeG8Dlmeq74rjm/A==", 809 | "engines": { 810 | "node": "^12.22.0 || ^14.16.0 || >=16.0.0" 811 | } 812 | }, 813 | "node_modules/graphql-tag": { 814 | "version": "2.12.6", 815 | "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz", 816 | "integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==", 817 | "dependencies": { 818 | "tslib": "^2.1.0" 819 | }, 820 | "engines": { 821 | "node": ">=10" 822 | }, 823 | "peerDependencies": { 824 | "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" 825 | } 826 | }, 827 | "node_modules/has": { 828 | "version": "1.0.3", 829 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 830 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 831 | "dependencies": { 832 | "function-bind": "^1.1.1" 833 | }, 834 | "engines": { 835 | "node": ">= 0.4.0" 836 | } 837 | }, 838 | "node_modules/has-symbols": { 839 | "version": "1.0.3", 840 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 841 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 842 | "engines": { 843 | "node": ">= 0.4" 844 | }, 845 | "funding": { 846 | "url": "https://github.com/sponsors/ljharb" 847 | } 848 | }, 849 | "node_modules/http-errors": { 850 | "version": "2.0.0", 851 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 852 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 853 | "dependencies": { 854 | "depd": "2.0.0", 855 | "inherits": "2.0.4", 856 | "setprototypeof": "1.2.0", 857 | "statuses": "2.0.1", 858 | "toidentifier": "1.0.1" 859 | }, 860 | "engines": { 861 | "node": ">= 0.8" 862 | } 863 | }, 864 | "node_modules/http-errors/node_modules/statuses": { 865 | "version": "2.0.1", 866 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 867 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 868 | "engines": { 869 | "node": ">= 0.8" 870 | } 871 | }, 872 | "node_modules/iconv-lite": { 873 | "version": "0.4.24", 874 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 875 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 876 | "dependencies": { 877 | "safer-buffer": ">= 2.1.2 < 3" 878 | }, 879 | "engines": { 880 | "node": ">=0.10.0" 881 | } 882 | }, 883 | "node_modules/inherits": { 884 | "version": "2.0.4", 885 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 886 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 887 | }, 888 | "node_modules/ipaddr.js": { 889 | "version": "1.9.1", 890 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 891 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 892 | "engines": { 893 | "node": ">= 0.10" 894 | } 895 | }, 896 | "node_modules/lodash-es": { 897 | "version": "4.17.21", 898 | "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", 899 | "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" 900 | }, 901 | "node_modules/lodash.sortby": { 902 | "version": "4.7.0", 903 | "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", 904 | "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" 905 | }, 906 | "node_modules/loglevel": { 907 | "version": "1.8.0", 908 | "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz", 909 | "integrity": "sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA==", 910 | "engines": { 911 | "node": ">= 0.6.0" 912 | }, 913 | "funding": { 914 | "type": "tidelift", 915 | "url": "https://tidelift.com/funding/github/npm/loglevel" 916 | } 917 | }, 918 | "node_modules/long": { 919 | "version": "4.0.0", 920 | "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", 921 | "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" 922 | }, 923 | "node_modules/lru-cache": { 924 | "version": "6.0.0", 925 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 926 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 927 | "dependencies": { 928 | "yallist": "^4.0.0" 929 | }, 930 | "engines": { 931 | "node": ">=10" 932 | } 933 | }, 934 | "node_modules/media-typer": { 935 | "version": "0.3.0", 936 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 937 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", 938 | "engines": { 939 | "node": ">= 0.6" 940 | } 941 | }, 942 | "node_modules/merge-descriptors": { 943 | "version": "1.0.1", 944 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 945 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 946 | }, 947 | "node_modules/methods": { 948 | "version": "1.1.2", 949 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 950 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", 951 | "engines": { 952 | "node": ">= 0.6" 953 | } 954 | }, 955 | "node_modules/mime": { 956 | "version": "1.6.0", 957 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 958 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", 959 | "bin": { 960 | "mime": "cli.js" 961 | }, 962 | "engines": { 963 | "node": ">=4" 964 | } 965 | }, 966 | "node_modules/mime-db": { 967 | "version": "1.52.0", 968 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 969 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 970 | "engines": { 971 | "node": ">= 0.6" 972 | } 973 | }, 974 | "node_modules/mime-types": { 975 | "version": "2.1.35", 976 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 977 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 978 | "dependencies": { 979 | "mime-db": "1.52.0" 980 | }, 981 | "engines": { 982 | "node": ">= 0.6" 983 | } 984 | }, 985 | "node_modules/ms": { 986 | "version": "2.0.0", 987 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 988 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 989 | }, 990 | "node_modules/negotiator": { 991 | "version": "0.6.3", 992 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 993 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", 994 | "engines": { 995 | "node": ">= 0.6" 996 | } 997 | }, 998 | "node_modules/node-fetch": { 999 | "version": "2.6.7", 1000 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", 1001 | "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", 1002 | "dependencies": { 1003 | "whatwg-url": "^5.0.0" 1004 | }, 1005 | "engines": { 1006 | "node": "4.x || >=6.0.0" 1007 | }, 1008 | "peerDependencies": { 1009 | "encoding": "^0.1.0" 1010 | }, 1011 | "peerDependenciesMeta": { 1012 | "encoding": { 1013 | "optional": true 1014 | } 1015 | } 1016 | }, 1017 | "node_modules/object-assign": { 1018 | "version": "4.1.1", 1019 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1020 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 1021 | "engines": { 1022 | "node": ">=0.10.0" 1023 | } 1024 | }, 1025 | "node_modules/object-inspect": { 1026 | "version": "1.12.0", 1027 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", 1028 | "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", 1029 | "funding": { 1030 | "url": "https://github.com/sponsors/ljharb" 1031 | } 1032 | }, 1033 | "node_modules/on-finished": { 1034 | "version": "2.4.1", 1035 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 1036 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 1037 | "dependencies": { 1038 | "ee-first": "1.1.1" 1039 | }, 1040 | "engines": { 1041 | "node": ">= 0.8" 1042 | } 1043 | }, 1044 | "node_modules/parseurl": { 1045 | "version": "1.3.3", 1046 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1047 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 1048 | "engines": { 1049 | "node": ">= 0.8" 1050 | } 1051 | }, 1052 | "node_modules/path-to-regexp": { 1053 | "version": "0.1.7", 1054 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 1055 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 1056 | }, 1057 | "node_modules/proxy-addr": { 1058 | "version": "2.0.7", 1059 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 1060 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 1061 | "dependencies": { 1062 | "forwarded": "0.2.0", 1063 | "ipaddr.js": "1.9.1" 1064 | }, 1065 | "engines": { 1066 | "node": ">= 0.10" 1067 | } 1068 | }, 1069 | "node_modules/qs": { 1070 | "version": "6.10.3", 1071 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", 1072 | "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", 1073 | "dependencies": { 1074 | "side-channel": "^1.0.4" 1075 | }, 1076 | "engines": { 1077 | "node": ">=0.6" 1078 | }, 1079 | "funding": { 1080 | "url": "https://github.com/sponsors/ljharb" 1081 | } 1082 | }, 1083 | "node_modules/range-parser": { 1084 | "version": "1.2.1", 1085 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1086 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 1087 | "engines": { 1088 | "node": ">= 0.6" 1089 | } 1090 | }, 1091 | "node_modules/raw-body": { 1092 | "version": "2.5.1", 1093 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", 1094 | "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", 1095 | "dependencies": { 1096 | "bytes": "3.1.2", 1097 | "http-errors": "2.0.0", 1098 | "iconv-lite": "0.4.24", 1099 | "unpipe": "1.0.0" 1100 | }, 1101 | "engines": { 1102 | "node": ">= 0.8" 1103 | } 1104 | }, 1105 | "node_modules/retry": { 1106 | "version": "0.13.1", 1107 | "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", 1108 | "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", 1109 | "engines": { 1110 | "node": ">= 4" 1111 | } 1112 | }, 1113 | "node_modules/safe-buffer": { 1114 | "version": "5.2.1", 1115 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1116 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1117 | "funding": [ 1118 | { 1119 | "type": "github", 1120 | "url": "https://github.com/sponsors/feross" 1121 | }, 1122 | { 1123 | "type": "patreon", 1124 | "url": "https://www.patreon.com/feross" 1125 | }, 1126 | { 1127 | "type": "consulting", 1128 | "url": "https://feross.org/support" 1129 | } 1130 | ] 1131 | }, 1132 | "node_modules/safer-buffer": { 1133 | "version": "2.1.2", 1134 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1135 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1136 | }, 1137 | "node_modules/send": { 1138 | "version": "0.17.2", 1139 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", 1140 | "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", 1141 | "dependencies": { 1142 | "debug": "2.6.9", 1143 | "depd": "~1.1.2", 1144 | "destroy": "~1.0.4", 1145 | "encodeurl": "~1.0.2", 1146 | "escape-html": "~1.0.3", 1147 | "etag": "~1.8.1", 1148 | "fresh": "0.5.2", 1149 | "http-errors": "1.8.1", 1150 | "mime": "1.6.0", 1151 | "ms": "2.1.3", 1152 | "on-finished": "~2.3.0", 1153 | "range-parser": "~1.2.1", 1154 | "statuses": "~1.5.0" 1155 | }, 1156 | "engines": { 1157 | "node": ">= 0.8.0" 1158 | } 1159 | }, 1160 | "node_modules/send/node_modules/depd": { 1161 | "version": "1.1.2", 1162 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 1163 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", 1164 | "engines": { 1165 | "node": ">= 0.6" 1166 | } 1167 | }, 1168 | "node_modules/send/node_modules/destroy": { 1169 | "version": "1.0.4", 1170 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 1171 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 1172 | }, 1173 | "node_modules/send/node_modules/http-errors": { 1174 | "version": "1.8.1", 1175 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", 1176 | "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", 1177 | "dependencies": { 1178 | "depd": "~1.1.2", 1179 | "inherits": "2.0.4", 1180 | "setprototypeof": "1.2.0", 1181 | "statuses": ">= 1.5.0 < 2", 1182 | "toidentifier": "1.0.1" 1183 | }, 1184 | "engines": { 1185 | "node": ">= 0.6" 1186 | } 1187 | }, 1188 | "node_modules/send/node_modules/ms": { 1189 | "version": "2.1.3", 1190 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1191 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 1192 | }, 1193 | "node_modules/send/node_modules/on-finished": { 1194 | "version": "2.3.0", 1195 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 1196 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 1197 | "dependencies": { 1198 | "ee-first": "1.1.1" 1199 | }, 1200 | "engines": { 1201 | "node": ">= 0.8" 1202 | } 1203 | }, 1204 | "node_modules/serve-static": { 1205 | "version": "1.14.2", 1206 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", 1207 | "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", 1208 | "dependencies": { 1209 | "encodeurl": "~1.0.2", 1210 | "escape-html": "~1.0.3", 1211 | "parseurl": "~1.3.3", 1212 | "send": "0.17.2" 1213 | }, 1214 | "engines": { 1215 | "node": ">= 0.8.0" 1216 | } 1217 | }, 1218 | "node_modules/setprototypeof": { 1219 | "version": "1.2.0", 1220 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 1221 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 1222 | }, 1223 | "node_modules/sha.js": { 1224 | "version": "2.4.11", 1225 | "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", 1226 | "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", 1227 | "dependencies": { 1228 | "inherits": "^2.0.1", 1229 | "safe-buffer": "^5.0.1" 1230 | }, 1231 | "bin": { 1232 | "sha.js": "bin.js" 1233 | } 1234 | }, 1235 | "node_modules/side-channel": { 1236 | "version": "1.0.4", 1237 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 1238 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 1239 | "dependencies": { 1240 | "call-bind": "^1.0.0", 1241 | "get-intrinsic": "^1.0.2", 1242 | "object-inspect": "^1.9.0" 1243 | }, 1244 | "funding": { 1245 | "url": "https://github.com/sponsors/ljharb" 1246 | } 1247 | }, 1248 | "node_modules/statuses": { 1249 | "version": "1.5.0", 1250 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 1251 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", 1252 | "engines": { 1253 | "node": ">= 0.6" 1254 | } 1255 | }, 1256 | "node_modules/toidentifier": { 1257 | "version": "1.0.1", 1258 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1259 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 1260 | "engines": { 1261 | "node": ">=0.6" 1262 | } 1263 | }, 1264 | "node_modules/tr46": { 1265 | "version": "0.0.3", 1266 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 1267 | "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" 1268 | }, 1269 | "node_modules/tslib": { 1270 | "version": "2.3.1", 1271 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", 1272 | "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" 1273 | }, 1274 | "node_modules/type-is": { 1275 | "version": "1.6.18", 1276 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 1277 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 1278 | "dependencies": { 1279 | "media-typer": "0.3.0", 1280 | "mime-types": "~2.1.24" 1281 | }, 1282 | "engines": { 1283 | "node": ">= 0.6" 1284 | } 1285 | }, 1286 | "node_modules/unpipe": { 1287 | "version": "1.0.0", 1288 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1289 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", 1290 | "engines": { 1291 | "node": ">= 0.8" 1292 | } 1293 | }, 1294 | "node_modules/utils-merge": { 1295 | "version": "1.0.1", 1296 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 1297 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", 1298 | "engines": { 1299 | "node": ">= 0.4.0" 1300 | } 1301 | }, 1302 | "node_modules/uuid": { 1303 | "version": "8.3.2", 1304 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", 1305 | "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", 1306 | "bin": { 1307 | "uuid": "dist/bin/uuid" 1308 | } 1309 | }, 1310 | "node_modules/validator": { 1311 | "version": "13.7.0", 1312 | "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", 1313 | "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==", 1314 | "engines": { 1315 | "node": ">= 0.10" 1316 | } 1317 | }, 1318 | "node_modules/value-or-promise": { 1319 | "version": "1.0.11", 1320 | "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.11.tgz", 1321 | "integrity": "sha512-41BrgH+dIbCFXClcSapVs5M6GkENd3gQOJpEfPDNa71LsUGMXDL0jMWpI/Rh7WhX+Aalfz2TTS3Zt5pUsbnhLg==", 1322 | "engines": { 1323 | "node": ">=12" 1324 | } 1325 | }, 1326 | "node_modules/vary": { 1327 | "version": "1.1.2", 1328 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1329 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", 1330 | "engines": { 1331 | "node": ">= 0.8" 1332 | } 1333 | }, 1334 | "node_modules/webidl-conversions": { 1335 | "version": "3.0.1", 1336 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 1337 | "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" 1338 | }, 1339 | "node_modules/whatwg-url": { 1340 | "version": "5.0.0", 1341 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 1342 | "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", 1343 | "dependencies": { 1344 | "tr46": "~0.0.3", 1345 | "webidl-conversions": "^3.0.0" 1346 | } 1347 | }, 1348 | "node_modules/xss": { 1349 | "version": "1.0.11", 1350 | "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.11.tgz", 1351 | "integrity": "sha512-EimjrjThZeK2MO7WKR9mN5ZC1CSqivSl55wvUK5EtU6acf0rzEE1pN+9ZDrFXJ82BRp3JL38pPE6S4o/rpp1zQ==", 1352 | "dependencies": { 1353 | "commander": "^2.20.3", 1354 | "cssfilter": "0.0.10" 1355 | }, 1356 | "bin": { 1357 | "xss": "bin/xss" 1358 | }, 1359 | "engines": { 1360 | "node": ">= 0.10.0" 1361 | } 1362 | }, 1363 | "node_modules/yallist": { 1364 | "version": "4.0.0", 1365 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 1366 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 1367 | } 1368 | }, 1369 | "dependencies": { 1370 | "@apollo/protobufjs": { 1371 | "version": "1.2.2", 1372 | "resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.2.2.tgz", 1373 | "integrity": "sha512-vF+zxhPiLtkwxONs6YanSt1EpwpGilThpneExUN5K3tCymuxNnVq2yojTvnpRjv2QfsEIt/n7ozPIIzBLwGIDQ==", 1374 | "requires": { 1375 | "@protobufjs/aspromise": "^1.1.2", 1376 | "@protobufjs/base64": "^1.1.2", 1377 | "@protobufjs/codegen": "^2.0.4", 1378 | "@protobufjs/eventemitter": "^1.1.0", 1379 | "@protobufjs/fetch": "^1.1.0", 1380 | "@protobufjs/float": "^1.0.2", 1381 | "@protobufjs/inquire": "^1.1.0", 1382 | "@protobufjs/path": "^1.1.2", 1383 | "@protobufjs/pool": "^1.1.0", 1384 | "@protobufjs/utf8": "^1.1.0", 1385 | "@types/long": "^4.0.0", 1386 | "@types/node": "^10.1.0", 1387 | "long": "^4.0.0" 1388 | } 1389 | }, 1390 | "@apollographql/apollo-tools": { 1391 | "version": "0.5.3", 1392 | "resolved": "https://registry.npmjs.org/@apollographql/apollo-tools/-/apollo-tools-0.5.3.tgz", 1393 | "integrity": "sha512-VcsXHfTFoCodDAgJZxN04GdFK1kqOhZQnQY/9Fa147P+I8xfvOSz5d+lKAPB+hwSgBNyd7ncAKGIs4+utbL+yA==", 1394 | "requires": {} 1395 | }, 1396 | "@apollographql/graphql-playground-html": { 1397 | "version": "1.6.29", 1398 | "resolved": "https://registry.npmjs.org/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.29.tgz", 1399 | "integrity": "sha512-xCcXpoz52rI4ksJSdOCxeOCn2DLocxwHf9dVT/Q90Pte1LX+LY+91SFtJF3KXVHH8kEin+g1KKCQPKBjZJfWNA==", 1400 | "requires": { 1401 | "xss": "^1.0.8" 1402 | } 1403 | }, 1404 | "@graphql-tools/merge": { 1405 | "version": "8.2.7", 1406 | "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-8.2.7.tgz", 1407 | "integrity": "sha512-rKxjNogqu1UYAG/y5FOb6lJsmSQbWA+jq4inWjNEVX54VGGE7/WGnmPaqcsyomNOfS3vIRS6NnG+DxiQSqetjg==", 1408 | "requires": { 1409 | "@graphql-tools/utils": "8.6.6", 1410 | "tslib": "~2.3.0" 1411 | }, 1412 | "dependencies": { 1413 | "@graphql-tools/utils": { 1414 | "version": "8.6.6", 1415 | "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.6.6.tgz", 1416 | "integrity": "sha512-wjY2ljKLCnnbRrDNPPgPNqCujou0LFSOWcxAjV6DYUlfFWTsAEvlYmsmY4T+K12wI/fnqoJ2bUwIlap1plFDMg==", 1417 | "requires": { 1418 | "tslib": "~2.3.0" 1419 | } 1420 | } 1421 | } 1422 | }, 1423 | "@graphql-tools/mock": { 1424 | "version": "8.6.5", 1425 | "resolved": "https://registry.npmjs.org/@graphql-tools/mock/-/mock-8.6.5.tgz", 1426 | "integrity": "sha512-2Uz2qerJVU7wLDQseWNmgCUwiLgM3DfFJEPeQRC1s6oQELstKZHSW8Fb45mmjt37fyKqUdwPUT65PwzK03CK/Q==", 1427 | "requires": { 1428 | "@graphql-tools/schema": "8.3.7", 1429 | "@graphql-tools/utils": "8.6.6", 1430 | "fast-json-stable-stringify": "^2.1.0", 1431 | "tslib": "~2.3.0" 1432 | }, 1433 | "dependencies": { 1434 | "@graphql-tools/utils": { 1435 | "version": "8.6.6", 1436 | "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.6.6.tgz", 1437 | "integrity": "sha512-wjY2ljKLCnnbRrDNPPgPNqCujou0LFSOWcxAjV6DYUlfFWTsAEvlYmsmY4T+K12wI/fnqoJ2bUwIlap1plFDMg==", 1438 | "requires": { 1439 | "tslib": "~2.3.0" 1440 | } 1441 | } 1442 | } 1443 | }, 1444 | "@graphql-tools/schema": { 1445 | "version": "8.3.7", 1446 | "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-8.3.7.tgz", 1447 | "integrity": "sha512-7byr9J6rfMPFPfiR4u65dy20xHATTvbgOY7KYd1sYPnMKKfRZe0tUgpnE+noXcfob7N8s366WaVh7bEoztQMwg==", 1448 | "requires": { 1449 | "@graphql-tools/merge": "8.2.7", 1450 | "@graphql-tools/utils": "8.6.6", 1451 | "tslib": "~2.3.0", 1452 | "value-or-promise": "1.0.11" 1453 | }, 1454 | "dependencies": { 1455 | "@graphql-tools/utils": { 1456 | "version": "8.6.6", 1457 | "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.6.6.tgz", 1458 | "integrity": "sha512-wjY2ljKLCnnbRrDNPPgPNqCujou0LFSOWcxAjV6DYUlfFWTsAEvlYmsmY4T+K12wI/fnqoJ2bUwIlap1plFDMg==", 1459 | "requires": { 1460 | "tslib": "~2.3.0" 1461 | } 1462 | } 1463 | } 1464 | }, 1465 | "@graphql-tools/utils": { 1466 | "version": "8.6.7", 1467 | "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-8.6.7.tgz", 1468 | "integrity": "sha512-Qi3EN95Rt3hb8CyDKpPKFWOPrnc00P18cpVTXEgtKxetSP39beJBeEEtLB0R53eP/6IolsyTZOTgkET1EaERaw==", 1469 | "requires": { 1470 | "tslib": "~2.3.0" 1471 | } 1472 | }, 1473 | "@josephg/resolvable": { 1474 | "version": "1.0.1", 1475 | "resolved": "https://registry.npmjs.org/@josephg/resolvable/-/resolvable-1.0.1.tgz", 1476 | "integrity": "sha512-CtzORUwWTTOTqfVtHaKRJ0I1kNQd1bpn3sUh8I3nJDVY+5/M/Oe1DnEWzPQvqq/xPIIkzzzIP7mfCoAjFRvDhg==" 1477 | }, 1478 | "@protobufjs/aspromise": { 1479 | "version": "1.1.2", 1480 | "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", 1481 | "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" 1482 | }, 1483 | "@protobufjs/base64": { 1484 | "version": "1.1.2", 1485 | "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", 1486 | "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" 1487 | }, 1488 | "@protobufjs/codegen": { 1489 | "version": "2.0.4", 1490 | "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", 1491 | "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" 1492 | }, 1493 | "@protobufjs/eventemitter": { 1494 | "version": "1.1.0", 1495 | "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", 1496 | "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" 1497 | }, 1498 | "@protobufjs/fetch": { 1499 | "version": "1.1.0", 1500 | "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", 1501 | "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", 1502 | "requires": { 1503 | "@protobufjs/aspromise": "^1.1.1", 1504 | "@protobufjs/inquire": "^1.1.0" 1505 | } 1506 | }, 1507 | "@protobufjs/float": { 1508 | "version": "1.0.2", 1509 | "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", 1510 | "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" 1511 | }, 1512 | "@protobufjs/inquire": { 1513 | "version": "1.1.0", 1514 | "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", 1515 | "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" 1516 | }, 1517 | "@protobufjs/path": { 1518 | "version": "1.1.2", 1519 | "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", 1520 | "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" 1521 | }, 1522 | "@protobufjs/pool": { 1523 | "version": "1.1.0", 1524 | "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", 1525 | "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" 1526 | }, 1527 | "@protobufjs/utf8": { 1528 | "version": "1.1.0", 1529 | "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", 1530 | "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" 1531 | }, 1532 | "@types/accepts": { 1533 | "version": "1.3.5", 1534 | "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz", 1535 | "integrity": "sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==", 1536 | "requires": { 1537 | "@types/node": "*" 1538 | } 1539 | }, 1540 | "@types/body-parser": { 1541 | "version": "1.19.2", 1542 | "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", 1543 | "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", 1544 | "requires": { 1545 | "@types/connect": "*", 1546 | "@types/node": "*" 1547 | } 1548 | }, 1549 | "@types/connect": { 1550 | "version": "3.4.35", 1551 | "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", 1552 | "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", 1553 | "requires": { 1554 | "@types/node": "*" 1555 | } 1556 | }, 1557 | "@types/cors": { 1558 | "version": "2.8.12", 1559 | "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", 1560 | "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==" 1561 | }, 1562 | "@types/express": { 1563 | "version": "4.17.13", 1564 | "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", 1565 | "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", 1566 | "requires": { 1567 | "@types/body-parser": "*", 1568 | "@types/express-serve-static-core": "^4.17.18", 1569 | "@types/qs": "*", 1570 | "@types/serve-static": "*" 1571 | } 1572 | }, 1573 | "@types/express-serve-static-core": { 1574 | "version": "4.17.28", 1575 | "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", 1576 | "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", 1577 | "requires": { 1578 | "@types/node": "*", 1579 | "@types/qs": "*", 1580 | "@types/range-parser": "*" 1581 | } 1582 | }, 1583 | "@types/long": { 1584 | "version": "4.0.1", 1585 | "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", 1586 | "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" 1587 | }, 1588 | "@types/mime": { 1589 | "version": "1.3.2", 1590 | "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", 1591 | "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" 1592 | }, 1593 | "@types/node": { 1594 | "version": "10.17.60", 1595 | "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", 1596 | "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==" 1597 | }, 1598 | "@types/qs": { 1599 | "version": "6.9.7", 1600 | "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", 1601 | "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" 1602 | }, 1603 | "@types/range-parser": { 1604 | "version": "1.2.4", 1605 | "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", 1606 | "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" 1607 | }, 1608 | "@types/serve-static": { 1609 | "version": "1.13.10", 1610 | "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", 1611 | "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", 1612 | "requires": { 1613 | "@types/mime": "^1", 1614 | "@types/node": "*" 1615 | } 1616 | }, 1617 | "accepts": { 1618 | "version": "1.3.8", 1619 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", 1620 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", 1621 | "requires": { 1622 | "mime-types": "~2.1.34", 1623 | "negotiator": "0.6.3" 1624 | } 1625 | }, 1626 | "apollo-datasource": { 1627 | "version": "3.3.1", 1628 | "resolved": "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-3.3.1.tgz", 1629 | "integrity": "sha512-Z3a8rEUXVPIZ1p8xrFL8bcNhWmhOmovgDArvwIwmJOBnh093ZpRfO+ESJEDAN4KswmyzCLDAwjsW4zQOONdRUw==", 1630 | "requires": { 1631 | "apollo-server-caching": "^3.3.0", 1632 | "apollo-server-env": "^4.2.1" 1633 | } 1634 | }, 1635 | "apollo-reporting-protobuf": { 1636 | "version": "3.3.1", 1637 | "resolved": "https://registry.npmjs.org/apollo-reporting-protobuf/-/apollo-reporting-protobuf-3.3.1.tgz", 1638 | "integrity": "sha512-tyvj3Vj71TCh6c8PtdHOLgHHBSJ05DF/A/Po3q8yfHTBkOPcOJZE/GGN/PT/pwKg7HHxKcAeHDw7+xciVvGx0w==", 1639 | "requires": { 1640 | "@apollo/protobufjs": "1.2.2" 1641 | } 1642 | }, 1643 | "apollo-server": { 1644 | "version": "3.6.7", 1645 | "resolved": "https://registry.npmjs.org/apollo-server/-/apollo-server-3.6.7.tgz", 1646 | "integrity": "sha512-WERZqaVCkZJMoOc9y692NribgNtKbHDjOwiAmgXI2WBlON2oUvCwgxPvsMg+bXVpQx4itrMyj31a2N6BeKmbmQ==", 1647 | "requires": { 1648 | "apollo-server-core": "^3.6.7", 1649 | "apollo-server-express": "^3.6.7", 1650 | "express": "^4.17.1" 1651 | } 1652 | }, 1653 | "apollo-server-caching": { 1654 | "version": "3.3.0", 1655 | "resolved": "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-3.3.0.tgz", 1656 | "integrity": "sha512-Wgcb0ArjZ5DjQ7ID+tvxUcZ7Yxdbk5l1MxZL8D8gkyjooOkhPNzjRVQ7ubPoXqO54PrOMOTm1ejVhsF+AfIirQ==", 1657 | "requires": { 1658 | "lru-cache": "^6.0.0" 1659 | } 1660 | }, 1661 | "apollo-server-core": { 1662 | "version": "3.6.7", 1663 | "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-3.6.7.tgz", 1664 | "integrity": "sha512-OnZ9vu7LrYy2rvEu+nbgqucw6VyTSIPAEjK87c4rkzlVOxpwtGUaQ4FMWD9zBIj7yLq9q22b638E8LdYoaTAjQ==", 1665 | "requires": { 1666 | "@apollographql/apollo-tools": "^0.5.3", 1667 | "@apollographql/graphql-playground-html": "1.6.29", 1668 | "@graphql-tools/mock": "^8.1.2", 1669 | "@graphql-tools/schema": "^8.0.0", 1670 | "@josephg/resolvable": "^1.0.0", 1671 | "apollo-datasource": "^3.3.1", 1672 | "apollo-reporting-protobuf": "^3.3.1", 1673 | "apollo-server-caching": "^3.3.0", 1674 | "apollo-server-env": "^4.2.1", 1675 | "apollo-server-errors": "^3.3.1", 1676 | "apollo-server-plugin-base": "^3.5.2", 1677 | "apollo-server-types": "^3.5.2", 1678 | "async-retry": "^1.2.1", 1679 | "fast-json-stable-stringify": "^2.1.0", 1680 | "graphql-tag": "^2.11.0", 1681 | "lodash.sortby": "^4.7.0", 1682 | "loglevel": "^1.6.8", 1683 | "lru-cache": "^6.0.0", 1684 | "sha.js": "^2.4.11", 1685 | "uuid": "^8.0.0" 1686 | } 1687 | }, 1688 | "apollo-server-env": { 1689 | "version": "4.2.1", 1690 | "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-4.2.1.tgz", 1691 | "integrity": "sha512-vm/7c7ld+zFMxibzqZ7SSa5tBENc4B0uye9LTfjJwGoQFY5xsUPH5FpO5j0bMUDZ8YYNbrF9SNtzc5Cngcr90g==", 1692 | "requires": { 1693 | "node-fetch": "^2.6.7" 1694 | } 1695 | }, 1696 | "apollo-server-errors": { 1697 | "version": "3.3.1", 1698 | "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-3.3.1.tgz", 1699 | "integrity": "sha512-xnZJ5QWs6FixHICXHxUfm+ZWqqxrNuPlQ+kj5m6RtEgIpekOPssH/SD9gf2B4HuWV0QozorrygwZnux8POvyPA==", 1700 | "requires": {} 1701 | }, 1702 | "apollo-server-express": { 1703 | "version": "3.6.7", 1704 | "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-3.6.7.tgz", 1705 | "integrity": "sha512-B4gH5j8t3XxTCIa9bl7Iq/F3YFzMxX/LV4Sc+L/3xkHm648u576G5Lkskl8HsoTGSzzyeVcVsPDoYHiBjCAN0Q==", 1706 | "requires": { 1707 | "@types/accepts": "^1.3.5", 1708 | "@types/body-parser": "1.19.2", 1709 | "@types/cors": "2.8.12", 1710 | "@types/express": "4.17.13", 1711 | "@types/express-serve-static-core": "4.17.28", 1712 | "accepts": "^1.3.5", 1713 | "apollo-server-core": "^3.6.7", 1714 | "apollo-server-types": "^3.5.2", 1715 | "body-parser": "^1.19.0", 1716 | "cors": "^2.8.5", 1717 | "parseurl": "^1.3.3" 1718 | } 1719 | }, 1720 | "apollo-server-plugin-base": { 1721 | "version": "3.5.2", 1722 | "resolved": "https://registry.npmjs.org/apollo-server-plugin-base/-/apollo-server-plugin-base-3.5.2.tgz", 1723 | "integrity": "sha512-SwIf1waDmNDb0kmn57QR++InwK6Iv/X2slpm/aFIoqFBe91r6uJfakJvQZuh8dLEgk68gxqFsT8zHRpxBclE+g==", 1724 | "requires": { 1725 | "apollo-server-types": "^3.5.2" 1726 | } 1727 | }, 1728 | "apollo-server-types": { 1729 | "version": "3.5.2", 1730 | "resolved": "https://registry.npmjs.org/apollo-server-types/-/apollo-server-types-3.5.2.tgz", 1731 | "integrity": "sha512-vhcbIWsBkoNibABOym4AAPBoNDjokhjUQokKYdwZMeqrb850PMQdNJFrGyXT5onP408Ghv4O8PfgBuPQmeJhVQ==", 1732 | "requires": { 1733 | "apollo-reporting-protobuf": "^3.3.1", 1734 | "apollo-server-caching": "^3.3.0", 1735 | "apollo-server-env": "^4.2.1" 1736 | } 1737 | }, 1738 | "array-flatten": { 1739 | "version": "1.1.1", 1740 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 1741 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 1742 | }, 1743 | "async-retry": { 1744 | "version": "1.3.3", 1745 | "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", 1746 | "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", 1747 | "requires": { 1748 | "retry": "0.13.1" 1749 | } 1750 | }, 1751 | "body-parser": { 1752 | "version": "1.20.0", 1753 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", 1754 | "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", 1755 | "requires": { 1756 | "bytes": "3.1.2", 1757 | "content-type": "~1.0.4", 1758 | "debug": "2.6.9", 1759 | "depd": "2.0.0", 1760 | "destroy": "1.2.0", 1761 | "http-errors": "2.0.0", 1762 | "iconv-lite": "0.4.24", 1763 | "on-finished": "2.4.1", 1764 | "qs": "6.10.3", 1765 | "raw-body": "2.5.1", 1766 | "type-is": "~1.6.18", 1767 | "unpipe": "1.0.0" 1768 | } 1769 | }, 1770 | "bytes": { 1771 | "version": "3.1.2", 1772 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 1773 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" 1774 | }, 1775 | "call-bind": { 1776 | "version": "1.0.2", 1777 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 1778 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 1779 | "requires": { 1780 | "function-bind": "^1.1.1", 1781 | "get-intrinsic": "^1.0.2" 1782 | } 1783 | }, 1784 | "commander": { 1785 | "version": "2.20.3", 1786 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 1787 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" 1788 | }, 1789 | "content-disposition": { 1790 | "version": "0.5.4", 1791 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", 1792 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", 1793 | "requires": { 1794 | "safe-buffer": "5.2.1" 1795 | } 1796 | }, 1797 | "content-type": { 1798 | "version": "1.0.4", 1799 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 1800 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 1801 | }, 1802 | "cookie": { 1803 | "version": "0.4.2", 1804 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", 1805 | "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" 1806 | }, 1807 | "cookie-signature": { 1808 | "version": "1.0.6", 1809 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 1810 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 1811 | }, 1812 | "cors": { 1813 | "version": "2.8.5", 1814 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", 1815 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", 1816 | "requires": { 1817 | "object-assign": "^4", 1818 | "vary": "^1" 1819 | } 1820 | }, 1821 | "cssfilter": { 1822 | "version": "0.0.10", 1823 | "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", 1824 | "integrity": "sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=" 1825 | }, 1826 | "debug": { 1827 | "version": "2.6.9", 1828 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 1829 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 1830 | "requires": { 1831 | "ms": "2.0.0" 1832 | } 1833 | }, 1834 | "depd": { 1835 | "version": "2.0.0", 1836 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 1837 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" 1838 | }, 1839 | "destroy": { 1840 | "version": "1.2.0", 1841 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", 1842 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" 1843 | }, 1844 | "ee-first": { 1845 | "version": "1.1.1", 1846 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 1847 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 1848 | }, 1849 | "encodeurl": { 1850 | "version": "1.0.2", 1851 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 1852 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 1853 | }, 1854 | "escape-html": { 1855 | "version": "1.0.3", 1856 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 1857 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 1858 | }, 1859 | "etag": { 1860 | "version": "1.8.1", 1861 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 1862 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 1863 | }, 1864 | "express": { 1865 | "version": "4.17.3", 1866 | "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", 1867 | "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", 1868 | "requires": { 1869 | "accepts": "~1.3.8", 1870 | "array-flatten": "1.1.1", 1871 | "body-parser": "1.19.2", 1872 | "content-disposition": "0.5.4", 1873 | "content-type": "~1.0.4", 1874 | "cookie": "0.4.2", 1875 | "cookie-signature": "1.0.6", 1876 | "debug": "2.6.9", 1877 | "depd": "~1.1.2", 1878 | "encodeurl": "~1.0.2", 1879 | "escape-html": "~1.0.3", 1880 | "etag": "~1.8.1", 1881 | "finalhandler": "~1.1.2", 1882 | "fresh": "0.5.2", 1883 | "merge-descriptors": "1.0.1", 1884 | "methods": "~1.1.2", 1885 | "on-finished": "~2.3.0", 1886 | "parseurl": "~1.3.3", 1887 | "path-to-regexp": "0.1.7", 1888 | "proxy-addr": "~2.0.7", 1889 | "qs": "6.9.7", 1890 | "range-parser": "~1.2.1", 1891 | "safe-buffer": "5.2.1", 1892 | "send": "0.17.2", 1893 | "serve-static": "1.14.2", 1894 | "setprototypeof": "1.2.0", 1895 | "statuses": "~1.5.0", 1896 | "type-is": "~1.6.18", 1897 | "utils-merge": "1.0.1", 1898 | "vary": "~1.1.2" 1899 | }, 1900 | "dependencies": { 1901 | "body-parser": { 1902 | "version": "1.19.2", 1903 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", 1904 | "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", 1905 | "requires": { 1906 | "bytes": "3.1.2", 1907 | "content-type": "~1.0.4", 1908 | "debug": "2.6.9", 1909 | "depd": "~1.1.2", 1910 | "http-errors": "1.8.1", 1911 | "iconv-lite": "0.4.24", 1912 | "on-finished": "~2.3.0", 1913 | "qs": "6.9.7", 1914 | "raw-body": "2.4.3", 1915 | "type-is": "~1.6.18" 1916 | } 1917 | }, 1918 | "depd": { 1919 | "version": "1.1.2", 1920 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 1921 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 1922 | }, 1923 | "http-errors": { 1924 | "version": "1.8.1", 1925 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", 1926 | "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", 1927 | "requires": { 1928 | "depd": "~1.1.2", 1929 | "inherits": "2.0.4", 1930 | "setprototypeof": "1.2.0", 1931 | "statuses": ">= 1.5.0 < 2", 1932 | "toidentifier": "1.0.1" 1933 | } 1934 | }, 1935 | "on-finished": { 1936 | "version": "2.3.0", 1937 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 1938 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 1939 | "requires": { 1940 | "ee-first": "1.1.1" 1941 | } 1942 | }, 1943 | "qs": { 1944 | "version": "6.9.7", 1945 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", 1946 | "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==" 1947 | }, 1948 | "raw-body": { 1949 | "version": "2.4.3", 1950 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", 1951 | "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", 1952 | "requires": { 1953 | "bytes": "3.1.2", 1954 | "http-errors": "1.8.1", 1955 | "iconv-lite": "0.4.24", 1956 | "unpipe": "1.0.0" 1957 | } 1958 | } 1959 | } 1960 | }, 1961 | "fast-json-stable-stringify": { 1962 | "version": "2.1.0", 1963 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 1964 | "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" 1965 | }, 1966 | "finalhandler": { 1967 | "version": "1.1.2", 1968 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", 1969 | "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", 1970 | "requires": { 1971 | "debug": "2.6.9", 1972 | "encodeurl": "~1.0.2", 1973 | "escape-html": "~1.0.3", 1974 | "on-finished": "~2.3.0", 1975 | "parseurl": "~1.3.3", 1976 | "statuses": "~1.5.0", 1977 | "unpipe": "~1.0.0" 1978 | }, 1979 | "dependencies": { 1980 | "on-finished": { 1981 | "version": "2.3.0", 1982 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 1983 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 1984 | "requires": { 1985 | "ee-first": "1.1.1" 1986 | } 1987 | } 1988 | } 1989 | }, 1990 | "forwarded": { 1991 | "version": "0.2.0", 1992 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 1993 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" 1994 | }, 1995 | "fresh": { 1996 | "version": "0.5.2", 1997 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 1998 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 1999 | }, 2000 | "function-bind": { 2001 | "version": "1.1.1", 2002 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 2003 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" 2004 | }, 2005 | "get-intrinsic": { 2006 | "version": "1.1.1", 2007 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", 2008 | "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", 2009 | "requires": { 2010 | "function-bind": "^1.1.1", 2011 | "has": "^1.0.3", 2012 | "has-symbols": "^1.0.1" 2013 | } 2014 | }, 2015 | "graphql": { 2016 | "version": "16.3.0", 2017 | "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.3.0.tgz", 2018 | "integrity": "sha512-xm+ANmA16BzCT5pLjuXySbQVFwH3oJctUVdy81w1sV0vBU0KgDdBGtxQOUd5zqOBk/JayAFeG8Dlmeq74rjm/A==" 2019 | }, 2020 | "graphql-tag": { 2021 | "version": "2.12.6", 2022 | "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz", 2023 | "integrity": "sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==", 2024 | "requires": { 2025 | "tslib": "^2.1.0" 2026 | } 2027 | }, 2028 | "has": { 2029 | "version": "1.0.3", 2030 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 2031 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 2032 | "requires": { 2033 | "function-bind": "^1.1.1" 2034 | } 2035 | }, 2036 | "has-symbols": { 2037 | "version": "1.0.3", 2038 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 2039 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" 2040 | }, 2041 | "http-errors": { 2042 | "version": "2.0.0", 2043 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 2044 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 2045 | "requires": { 2046 | "depd": "2.0.0", 2047 | "inherits": "2.0.4", 2048 | "setprototypeof": "1.2.0", 2049 | "statuses": "2.0.1", 2050 | "toidentifier": "1.0.1" 2051 | }, 2052 | "dependencies": { 2053 | "statuses": { 2054 | "version": "2.0.1", 2055 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 2056 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" 2057 | } 2058 | } 2059 | }, 2060 | "iconv-lite": { 2061 | "version": "0.4.24", 2062 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 2063 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 2064 | "requires": { 2065 | "safer-buffer": ">= 2.1.2 < 3" 2066 | } 2067 | }, 2068 | "inherits": { 2069 | "version": "2.0.4", 2070 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 2071 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 2072 | }, 2073 | "ipaddr.js": { 2074 | "version": "1.9.1", 2075 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 2076 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" 2077 | }, 2078 | "lodash-es": { 2079 | "version": "4.17.21", 2080 | "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", 2081 | "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" 2082 | }, 2083 | "lodash.sortby": { 2084 | "version": "4.7.0", 2085 | "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", 2086 | "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" 2087 | }, 2088 | "loglevel": { 2089 | "version": "1.8.0", 2090 | "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz", 2091 | "integrity": "sha512-G6A/nJLRgWOuuwdNuA6koovfEV1YpqqAG4pRUlFaz3jj2QNZ8M4vBqnVA+HBTmU/AMNUtlOsMmSpF6NyOjztbA==" 2092 | }, 2093 | "long": { 2094 | "version": "4.0.0", 2095 | "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", 2096 | "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" 2097 | }, 2098 | "lru-cache": { 2099 | "version": "6.0.0", 2100 | "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", 2101 | "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", 2102 | "requires": { 2103 | "yallist": "^4.0.0" 2104 | } 2105 | }, 2106 | "media-typer": { 2107 | "version": "0.3.0", 2108 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 2109 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 2110 | }, 2111 | "merge-descriptors": { 2112 | "version": "1.0.1", 2113 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 2114 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 2115 | }, 2116 | "methods": { 2117 | "version": "1.1.2", 2118 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 2119 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 2120 | }, 2121 | "mime": { 2122 | "version": "1.6.0", 2123 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", 2124 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" 2125 | }, 2126 | "mime-db": { 2127 | "version": "1.52.0", 2128 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 2129 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" 2130 | }, 2131 | "mime-types": { 2132 | "version": "2.1.35", 2133 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 2134 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 2135 | "requires": { 2136 | "mime-db": "1.52.0" 2137 | } 2138 | }, 2139 | "ms": { 2140 | "version": "2.0.0", 2141 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 2142 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 2143 | }, 2144 | "negotiator": { 2145 | "version": "0.6.3", 2146 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", 2147 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" 2148 | }, 2149 | "node-fetch": { 2150 | "version": "2.6.7", 2151 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", 2152 | "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", 2153 | "requires": { 2154 | "whatwg-url": "^5.0.0" 2155 | } 2156 | }, 2157 | "object-assign": { 2158 | "version": "4.1.1", 2159 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 2160 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" 2161 | }, 2162 | "object-inspect": { 2163 | "version": "1.12.0", 2164 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", 2165 | "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==" 2166 | }, 2167 | "on-finished": { 2168 | "version": "2.4.1", 2169 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 2170 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 2171 | "requires": { 2172 | "ee-first": "1.1.1" 2173 | } 2174 | }, 2175 | "parseurl": { 2176 | "version": "1.3.3", 2177 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 2178 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" 2179 | }, 2180 | "path-to-regexp": { 2181 | "version": "0.1.7", 2182 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 2183 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 2184 | }, 2185 | "proxy-addr": { 2186 | "version": "2.0.7", 2187 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 2188 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 2189 | "requires": { 2190 | "forwarded": "0.2.0", 2191 | "ipaddr.js": "1.9.1" 2192 | } 2193 | }, 2194 | "qs": { 2195 | "version": "6.10.3", 2196 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", 2197 | "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", 2198 | "requires": { 2199 | "side-channel": "^1.0.4" 2200 | } 2201 | }, 2202 | "range-parser": { 2203 | "version": "1.2.1", 2204 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 2205 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" 2206 | }, 2207 | "raw-body": { 2208 | "version": "2.5.1", 2209 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", 2210 | "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", 2211 | "requires": { 2212 | "bytes": "3.1.2", 2213 | "http-errors": "2.0.0", 2214 | "iconv-lite": "0.4.24", 2215 | "unpipe": "1.0.0" 2216 | } 2217 | }, 2218 | "retry": { 2219 | "version": "0.13.1", 2220 | "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", 2221 | "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==" 2222 | }, 2223 | "safe-buffer": { 2224 | "version": "5.2.1", 2225 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 2226 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" 2227 | }, 2228 | "safer-buffer": { 2229 | "version": "2.1.2", 2230 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 2231 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 2232 | }, 2233 | "send": { 2234 | "version": "0.17.2", 2235 | "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", 2236 | "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", 2237 | "requires": { 2238 | "debug": "2.6.9", 2239 | "depd": "~1.1.2", 2240 | "destroy": "~1.0.4", 2241 | "encodeurl": "~1.0.2", 2242 | "escape-html": "~1.0.3", 2243 | "etag": "~1.8.1", 2244 | "fresh": "0.5.2", 2245 | "http-errors": "1.8.1", 2246 | "mime": "1.6.0", 2247 | "ms": "2.1.3", 2248 | "on-finished": "~2.3.0", 2249 | "range-parser": "~1.2.1", 2250 | "statuses": "~1.5.0" 2251 | }, 2252 | "dependencies": { 2253 | "depd": { 2254 | "version": "1.1.2", 2255 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 2256 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 2257 | }, 2258 | "destroy": { 2259 | "version": "1.0.4", 2260 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 2261 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 2262 | }, 2263 | "http-errors": { 2264 | "version": "1.8.1", 2265 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", 2266 | "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", 2267 | "requires": { 2268 | "depd": "~1.1.2", 2269 | "inherits": "2.0.4", 2270 | "setprototypeof": "1.2.0", 2271 | "statuses": ">= 1.5.0 < 2", 2272 | "toidentifier": "1.0.1" 2273 | } 2274 | }, 2275 | "ms": { 2276 | "version": "2.1.3", 2277 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 2278 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 2279 | }, 2280 | "on-finished": { 2281 | "version": "2.3.0", 2282 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 2283 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 2284 | "requires": { 2285 | "ee-first": "1.1.1" 2286 | } 2287 | } 2288 | } 2289 | }, 2290 | "serve-static": { 2291 | "version": "1.14.2", 2292 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", 2293 | "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", 2294 | "requires": { 2295 | "encodeurl": "~1.0.2", 2296 | "escape-html": "~1.0.3", 2297 | "parseurl": "~1.3.3", 2298 | "send": "0.17.2" 2299 | } 2300 | }, 2301 | "setprototypeof": { 2302 | "version": "1.2.0", 2303 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 2304 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 2305 | }, 2306 | "sha.js": { 2307 | "version": "2.4.11", 2308 | "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", 2309 | "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", 2310 | "requires": { 2311 | "inherits": "^2.0.1", 2312 | "safe-buffer": "^5.0.1" 2313 | } 2314 | }, 2315 | "side-channel": { 2316 | "version": "1.0.4", 2317 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 2318 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 2319 | "requires": { 2320 | "call-bind": "^1.0.0", 2321 | "get-intrinsic": "^1.0.2", 2322 | "object-inspect": "^1.9.0" 2323 | } 2324 | }, 2325 | "statuses": { 2326 | "version": "1.5.0", 2327 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", 2328 | "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" 2329 | }, 2330 | "toidentifier": { 2331 | "version": "1.0.1", 2332 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 2333 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" 2334 | }, 2335 | "tr46": { 2336 | "version": "0.0.3", 2337 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 2338 | "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" 2339 | }, 2340 | "tslib": { 2341 | "version": "2.3.1", 2342 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", 2343 | "integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" 2344 | }, 2345 | "type-is": { 2346 | "version": "1.6.18", 2347 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", 2348 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", 2349 | "requires": { 2350 | "media-typer": "0.3.0", 2351 | "mime-types": "~2.1.24" 2352 | } 2353 | }, 2354 | "unpipe": { 2355 | "version": "1.0.0", 2356 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 2357 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 2358 | }, 2359 | "utils-merge": { 2360 | "version": "1.0.1", 2361 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 2362 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 2363 | }, 2364 | "uuid": { 2365 | "version": "8.3.2", 2366 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", 2367 | "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" 2368 | }, 2369 | "validator": { 2370 | "version": "13.7.0", 2371 | "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", 2372 | "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==" 2373 | }, 2374 | "value-or-promise": { 2375 | "version": "1.0.11", 2376 | "resolved": "https://registry.npmjs.org/value-or-promise/-/value-or-promise-1.0.11.tgz", 2377 | "integrity": "sha512-41BrgH+dIbCFXClcSapVs5M6GkENd3gQOJpEfPDNa71LsUGMXDL0jMWpI/Rh7WhX+Aalfz2TTS3Zt5pUsbnhLg==" 2378 | }, 2379 | "vary": { 2380 | "version": "1.1.2", 2381 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 2382 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 2383 | }, 2384 | "webidl-conversions": { 2385 | "version": "3.0.1", 2386 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 2387 | "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" 2388 | }, 2389 | "whatwg-url": { 2390 | "version": "5.0.0", 2391 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 2392 | "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", 2393 | "requires": { 2394 | "tr46": "~0.0.3", 2395 | "webidl-conversions": "^3.0.0" 2396 | } 2397 | }, 2398 | "xss": { 2399 | "version": "1.0.11", 2400 | "resolved": "https://registry.npmjs.org/xss/-/xss-1.0.11.tgz", 2401 | "integrity": "sha512-EimjrjThZeK2MO7WKR9mN5ZC1CSqivSl55wvUK5EtU6acf0rzEE1pN+9ZDrFXJ82BRp3JL38pPE6S4o/rpp1zQ==", 2402 | "requires": { 2403 | "commander": "^2.20.3", 2404 | "cssfilter": "0.0.10" 2405 | } 2406 | }, 2407 | "yallist": { 2408 | "version": "4.0.0", 2409 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 2410 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 2411 | } 2412 | } 2413 | } 2414 | -------------------------------------------------------------------------------- /shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "marked-shared", 3 | "version": "1.0.0", 4 | "description": "The shared dependencies for the the Marked app.", 5 | "type": "module", 6 | "main": "src/index.js", 7 | "keywords": [], 8 | "author": "mandiwise", 9 | "license": "MIT", 10 | "dependencies": { 11 | "@graphql-tools/utils": "^8.6.7", 12 | "apollo-server": "^3.6.7", 13 | "graphql": "^16.3.0", 14 | "lodash-es": "^4.17.21", 15 | "validator": "^13.7.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /shared/src/directives/authDirectives.graphql: -------------------------------------------------------------------------------- 1 | """ 2 | Require a user to be authenticated to use the field. 3 | """ 4 | directive @private on FIELD_DEFINITION 5 | 6 | """ 7 | Require an authenticated user to be the owner of the resource to use the field. 8 | """ 9 | directive @owner( 10 | """ 11 | Name of the argument to check against the access token's `sub` claim. 12 | 13 | Dot notation is allowed for nested fields. 14 | """ 15 | argumentName: String! 16 | ) on FIELD_DEFINITION 17 | 18 | """ 19 | An authenticated user must have one of these access token permissions to use the field. 20 | """ 21 | directive @scope(permissions: [String!]!) on FIELD_DEFINITION 22 | -------------------------------------------------------------------------------- /shared/src/directives/authDirectives.js: -------------------------------------------------------------------------------- 1 | import { dirname, resolve } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { readFileSync } from "fs"; 4 | 5 | import { ApolloError } from "apollo-server"; 6 | import { defaultFieldResolver } from "graphql"; 7 | import { getDirectives, MapperKind, mapSchema } from "@graphql-tools/utils"; 8 | import { get } from "lodash-es"; 9 | 10 | function authDirectives() { 11 | const __dirname = dirname(fileURLToPath(import.meta.url)); 12 | 13 | return { 14 | authDirectivesTypeDefs: readFileSync( 15 | resolve(__dirname, "./authDirectives.graphql"), 16 | "utf-8" 17 | ), 18 | authDirectivesTransformer: schema => 19 | mapSchema(schema, { 20 | [MapperKind.OBJECT_FIELD]: fieldConfig => { 21 | const fieldDirectives = getDirectives(schema, fieldConfig); 22 | const privateDirective = fieldDirectives.find( 23 | dir => dir.name === "private" 24 | ); 25 | const ownerDirective = fieldDirectives.find( 26 | dir => dir.name === "owner" 27 | ); 28 | const scopeDirective = fieldDirectives.find( 29 | dir => dir.name === "scope" 30 | ); 31 | 32 | const { resolve = defaultFieldResolver } = fieldConfig; 33 | 34 | if (privateDirective || ownerDirective || scopeDirective) { 35 | fieldConfig.resolve = function (source, args, context, info) { 36 | const privateAuthorized = privateDirective && context.user?.sub; 37 | const ownerArgAuthorized = 38 | ownerDirective && 39 | context.user?.sub && 40 | get(args, ownerDirective.args.argumentName) === 41 | context.user.sub; 42 | 43 | let scopeAuthorized = false; 44 | if (scopeDirective && context.user?.scope) { 45 | const tokenPermissions = context.user.scope.split(" "); 46 | scopeAuthorized = scopeDirective.args.permissions.every(scope => 47 | tokenPermissions.includes(scope) 48 | ); 49 | } 50 | 51 | if ( 52 | (privateDirective && !privateAuthorized) || 53 | (ownerDirective && !ownerArgAuthorized) || 54 | (scopeDirective && !scopeAuthorized) 55 | ) { 56 | throw new ApolloError("Not authorized!"); 57 | } 58 | 59 | return resolve(source, args, context, info); 60 | }; 61 | 62 | return fieldConfig; 63 | } 64 | } 65 | }) 66 | }; 67 | } 68 | 69 | export default authDirectives; 70 | -------------------------------------------------------------------------------- /shared/src/index.js: -------------------------------------------------------------------------------- 1 | import authDirectives from "./directives/authDirectives.js"; 2 | import DateTimeType from "./scalars/DateTypeType.js"; 3 | import Pagination from "./utils/Pagination.js"; 4 | import restoreReferenceResolvers from "./utils/restoreReferenceResolvers.js"; 5 | import URLType from "./scalars/URLType.js"; 6 | 7 | export { 8 | authDirectives, 9 | DateTimeType, 10 | Pagination, 11 | restoreReferenceResolvers, 12 | URLType 13 | }; 14 | -------------------------------------------------------------------------------- /shared/src/scalars/DateTypeType.js: -------------------------------------------------------------------------------- 1 | import { ApolloError } from "apollo-server"; 2 | import { GraphQLScalarType } from "graphql"; 3 | import validator from "validator"; 4 | 5 | const DateTimeType = new GraphQLScalarType({ 6 | name: "DateTime", 7 | description: "An ISO 8601-encoded UTC date string.", 8 | parseValue: value => { 9 | if (validator.isISO8601(value)) { 10 | return value; 11 | } 12 | throw new ApolloError("DateTime must be a valid ISO 8601 Date string"); 13 | }, 14 | serialize: value => { 15 | if (typeof value !== "string") { 16 | value = value.toISOString(); 17 | } 18 | 19 | if (validator.isISO8601(value)) { 20 | return value; 21 | } 22 | throw new ApolloError("DateTime must be a valid ISO 8601 Date string"); 23 | }, 24 | parseLiteral: ast => { 25 | if (validator.isISO8601(ast.value)) { 26 | return ast.value; 27 | } 28 | throw new ApolloError("DateTime must be a valid ISO 8601 Date string"); 29 | } 30 | }); 31 | 32 | export default DateTimeType; 33 | -------------------------------------------------------------------------------- /shared/src/scalars/URLType.js: -------------------------------------------------------------------------------- 1 | import { ApolloError } from "apollo-server"; 2 | import { GraphQLScalarType } from "graphql"; 3 | import validator from "validator"; 4 | 5 | const URLType = new GraphQLScalarType({ 6 | name: "URL", 7 | description: "A well-formed URL string.", 8 | parseValue: value => { 9 | if (validator.isURL(value)) { 10 | return value; 11 | } 12 | throw new ApolloError("String must be a valid URL including a protocol"); 13 | }, 14 | serialize: value => { 15 | if (validator.isURL(value)) { 16 | return value; 17 | } 18 | throw new ApolloError("String must be a valid URL including a protocol"); 19 | }, 20 | parseLiteral: ast => { 21 | if (validator.isURL(ast.value)) { 22 | return ast.value; 23 | } 24 | throw new ApolloError("String must be a valid URL including a protocol"); 25 | } 26 | }); 27 | 28 | export default URLType; 29 | -------------------------------------------------------------------------------- /shared/src/utils/Pagination.js: -------------------------------------------------------------------------------- 1 | import { UserInputError } from "apollo-server"; 2 | 3 | class Pagination { 4 | constructor(Model) { 5 | this.Model = Model; 6 | } 7 | 8 | // Get documents and cast them into the correct edge/node shape 9 | async getEdges(queryArgs) { 10 | const { after, before, first, last, filter = {}, sort = {} } = queryArgs; 11 | const isSearch = this._isSearchQuery(sort); 12 | let edges; 13 | 14 | if (!first && !last) { 15 | throw new UserInputError( 16 | "Provide a `first` or `last` value to paginate this connection." 17 | ); 18 | } else if (first && last) { 19 | throw new UserInputError( 20 | "Passing `first` and `last` arguments is not supported with this connection." 21 | ); 22 | } else if (first < 0 || last < 0) { 23 | throw new UserInputError( 24 | "Minimum record request for `first` and `last` arguments is 0." 25 | ); 26 | } else if (first > 100 || last > 100) { 27 | throw new UserInputError( 28 | "Maximum record request for `first` and `last` arguments is 100." 29 | ); 30 | } else if (!first && isSearch) { 31 | throw new UserInputError("Search queries may only be paginated forward."); 32 | } 33 | 34 | if (isSearch) { 35 | const operator = this._getOperator(sort); 36 | const pipeline = await this._getSearchPipeline( 37 | after, 38 | filter, 39 | first, 40 | operator, 41 | sort 42 | ); 43 | 44 | const docs = await this.Model.aggregate(pipeline); 45 | 46 | edges = docs.length 47 | ? docs.map(doc => ({ node: doc, cursor: doc._id })) 48 | : []; 49 | } else if (first) { 50 | const operator = this._getOperator(sort); 51 | const queryDoc = after 52 | ? await this._getFilterWithCursor(after, filter, operator, sort) 53 | : filter; 54 | 55 | const docs = await this.Model.find(queryDoc) 56 | .sort(sort) 57 | .limit(first) 58 | .exec(); 59 | 60 | edges = docs.length 61 | ? docs.map(doc => ({ cursor: doc._id, node: doc })) 62 | : []; 63 | } else { 64 | const reverseSort = this._reverseSortDirection(sort); 65 | const operator = this._getOperator(reverseSort); 66 | 67 | const queryDoc = before 68 | ? await this._getFilterWithCursor(before, filter, operator, reverseSort) 69 | : filter; 70 | 71 | const docs = await this.Model.find(queryDoc) 72 | .sort(reverseSort) 73 | .limit(last) 74 | .exec(); 75 | 76 | edges = docs.length 77 | ? docs.map(doc => ({ node: doc, cursor: doc._id })).reverse() 78 | : []; 79 | } 80 | 81 | return edges; 82 | } 83 | 84 | // Get pagination information 85 | async getPageInfo(edges, queryArgs) { 86 | if (edges.length) { 87 | const { filter = {}, sort = {} } = queryArgs; 88 | const startCursor = this._getStartCursor(edges); 89 | const endCursor = this._getEndCursor(edges); 90 | const hasNextPage = await this._getHasNextPage(endCursor, filter, sort); 91 | const hasPreviousPage = await this._getHasPreviousPage( 92 | startCursor, 93 | filter, 94 | sort 95 | ); 96 | 97 | return { 98 | hasNextPage, 99 | hasPreviousPage, 100 | startCursor, 101 | endCursor 102 | }; 103 | } 104 | 105 | return { 106 | hasNextPage: false, 107 | hasPreviousPage: false, 108 | startCursor: null, 109 | endCursor: null 110 | }; 111 | } 112 | 113 | // Add the cursor ID with the correct comparison operator to the query 114 | // filter 115 | async _getFilterWithCursor(fromCursorId, filter, operator, sort) { 116 | let filterWithCursor = { $and: [filter] }; 117 | const fieldArr = Object.keys(sort); 118 | const field = fieldArr.length ? fieldArr[0] : "_id"; 119 | const fromDoc = await this.Model.findOne({ _id: fromCursorId }) 120 | .select(field) 121 | .exec(); 122 | 123 | if (!fromDoc) { 124 | throw new UserInputError(`No record found for ID '${fromCursorId}'`); 125 | } 126 | 127 | filterWithCursor.$and.push({ 128 | [field]: { [operator]: fromDoc[field] } 129 | }); 130 | 131 | return filterWithCursor; 132 | } 133 | 134 | // Create the aggregation pipeline to paginate a full-text search 135 | async _getSearchPipeline(fromCursorId, filter, first, operator, sort) { 136 | const textSearchPipeline = [ 137 | { $match: filter }, 138 | { $addFields: { score: { $meta: "textScore" } } }, 139 | { $sort: sort } 140 | ]; 141 | 142 | if (fromCursorId) { 143 | const fromDoc = await this.Model.findOne({ 144 | ...filter, 145 | _id: fromCursorId 146 | }) 147 | .select({ score: { $meta: "textScore" } }) 148 | .exec(); 149 | 150 | if (!fromDoc) { 151 | throw new UserInputError(`No record found for ID '${fromCursorId}'`); 152 | } 153 | 154 | textSearchPipeline.push({ 155 | $match: { 156 | $or: [ 157 | { score: { [operator]: fromDoc._doc.score } }, 158 | { 159 | score: { $eq: fromDoc._doc.score }, 160 | _id: { [operator]: fromCursorId } 161 | } 162 | ] 163 | } 164 | }); 165 | } 166 | 167 | textSearchPipeline.push({ $limit: first }); 168 | 169 | return textSearchPipeline; 170 | } 171 | 172 | // Reverse the sort direction when queries need to look in the opposite 173 | // direction of the set sort order (e.g. next/previous page checks) 174 | _reverseSortDirection(sort) { 175 | const fieldArr = Object.keys(sort); 176 | 177 | if (fieldArr.length === 0) { 178 | return { $natural: -1 }; 179 | } 180 | 181 | const field = fieldArr[0]; 182 | return { [field]: sort[field] * -1 }; 183 | } 184 | 185 | // Get the correct comparison operator based on the sort order 186 | _getOperator(sort, options = {}) { 187 | const orderArr = Object.values(sort); 188 | const checkPreviousTextScore = options?.checkPreviousTextScore 189 | ? options.checkPreviousTextScore 190 | : false; 191 | let operator; 192 | 193 | if (this._isSearchQuery(sort)) { 194 | operator = checkPreviousTextScore ? "$gt" : "$lt"; 195 | } else { 196 | operator = orderArr.length && orderArr[0] === -1 ? "$lt" : "$gt"; 197 | // Could use an array method here like 'includes' or 'some' 198 | } 199 | 200 | return operator; 201 | } 202 | 203 | // Determine if a query is a full-text search based on the sort 204 | // expression 205 | _isSearchQuery(sort) { 206 | const fieldArr = Object.keys(sort); 207 | return fieldArr.length && fieldArr[0] === "score"; 208 | } 209 | 210 | // Check if a next page of results is available 211 | async _getHasNextPage(endCursor, filter, sort) { 212 | const isSearch = this._isSearchQuery(sort); 213 | const operator = this._getOperator(sort); 214 | let nextPage; 215 | 216 | if (isSearch) { 217 | const pipeline = await this._getSearchPipeline( 218 | endCursor, 219 | filter, 220 | 1, 221 | operator, 222 | sort 223 | ); 224 | 225 | const result = await this.Model.aggregate(pipeline); 226 | nextPage = result.length; 227 | } else { 228 | const queryDoc = await this._getFilterWithCursor( 229 | endCursor, 230 | filter, 231 | operator, 232 | sort 233 | ); 234 | 235 | nextPage = await this.Model.findOne(queryDoc).select("_id").sort(sort); 236 | } 237 | 238 | return Boolean(nextPage); 239 | } 240 | 241 | // Check if a previous page of results is available 242 | async _getHasPreviousPage(startCursor, filter, sort) { 243 | const isSearch = this._isSearchQuery(sort); 244 | let prevPage; 245 | 246 | if (isSearch) { 247 | const operator = this._getOperator(sort, { 248 | checkPreviousTextScore: true 249 | }); 250 | 251 | const pipeline = await this._getSearchPipeline( 252 | startCursor, 253 | filter, 254 | 1, 255 | operator, 256 | sort 257 | ); 258 | 259 | const result = await this.Model.aggregate(pipeline); 260 | prevPage = result.length; 261 | } else { 262 | const reverseSort = this._reverseSortDirection(sort); 263 | const operator = this._getOperator(reverseSort); 264 | const queryDoc = await this._getFilterWithCursor( 265 | startCursor, 266 | filter, 267 | operator, 268 | reverseSort 269 | ); 270 | 271 | prevPage = await this.Model.findOne(queryDoc) 272 | .select("_id") 273 | .sort(reverseSort); 274 | } 275 | 276 | return Boolean(prevPage); 277 | } 278 | 279 | // Get the ID of the first document in the paging window 280 | _getStartCursor(edges) { 281 | if (!edges.length) { 282 | return null; 283 | } 284 | 285 | return edges[0].cursor; 286 | } 287 | 288 | // Get the ID of the last document in the paging window 289 | _getEndCursor(edges) { 290 | if (!edges.length) { 291 | return null; 292 | } 293 | 294 | return edges[edges.length - 1].cursor; 295 | } 296 | } 297 | 298 | export default Pagination; 299 | -------------------------------------------------------------------------------- /shared/src/utils/restoreReferenceResolvers.js: -------------------------------------------------------------------------------- 1 | const APOLLO_RESOLVE_REFERENCE_FIELD_NAME = "__resolveReference"; 2 | const APOLLO_FIELD_NAME_PREFIX = "__"; 3 | 4 | function restoreReferenceResolvers( 5 | schema, 6 | resolvers, 7 | apolloFields = [APOLLO_RESOLVE_REFERENCE_FIELD_NAME] 8 | ) { 9 | const apolloFieldsSet = new Set(apolloFields); 10 | 11 | const typeMap = schema.getTypeMap(); 12 | 13 | for (const [name, type] of Object.entries(typeMap)) { 14 | const typeResolvers = resolvers[name]; 15 | 16 | if (typeResolvers) { 17 | const apolloResolverFieldNames = Object.keys(typeResolvers).filter( 18 | fieldName => apolloFieldsSet.has(fieldName) 19 | ); 20 | 21 | for (const apolloResolverFieldName of apolloResolverFieldNames) { 22 | const trimmedName = apolloResolverFieldName.substring( 23 | APOLLO_FIELD_NAME_PREFIX.length 24 | ); 25 | 26 | const apolloResolver = typeResolvers[apolloResolverFieldName]; 27 | type[trimmedName] = apolloResolver; 28 | } 29 | } 30 | } 31 | } 32 | 33 | export default restoreReferenceResolvers; 34 | -------------------------------------------------------------------------------- /workflows/.env.sample: -------------------------------------------------------------------------------- 1 | AUTH0_AUDIENCE=http://localhost:4000/ 2 | AUTH0_CLIENT_ID_WORKFLOWS= 3 | AUTH0_CLIENT_SECRET_WORKFLOWS= 4 | AUTH0_DOMAIN= 5 | 6 | GATEWAY_ENDPOINT=http://localhost:4000/ 7 | 8 | NODE_ENV=development 9 | PORT=4004 -------------------------------------------------------------------------------- /workflows/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "marked-workflows", 3 | "version": "1.0.0", 4 | "description": "The server for the workflows service in the Marked app.", 5 | "main": "src/index.js", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "nodemon -r dotenv/config -e env,graphql,js ./src/index.js" 9 | }, 10 | "keywords": [], 11 | "author": "mandiwise", 12 | "license": "MIT", 13 | "dependencies": { 14 | "@apollo/client": "^3.6.1", 15 | "@apollo/subgraph": "^2.0.1", 16 | "apollo-datasource": "^3.3.1", 17 | "apollo-server": "^3.6.7", 18 | "dotenv": "^16.0.0", 19 | "graphql": "^16.5.0", 20 | "jwt-decode": "^3.1.2", 21 | "mongoose": "^6.3.0", 22 | "node-fetch": "^3.2.4", 23 | "temporalio": "^0.21.1" 24 | }, 25 | "devDependencies": { 26 | "nodemon": "^2.0.15" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /workflows/src/graphql/client.js: -------------------------------------------------------------------------------- 1 | import { ApolloClient, InMemoryCache } from "@apollo/client/core/core.cjs"; 2 | import { ApolloLink } from "@apollo/client/link/core/core.cjs"; 3 | import { HttpLink } from "@apollo/client/link/http/HttpLink.js"; 4 | import { onError } from "@apollo/client/link/error/error.cjs"; 5 | import { setContext } from "@apollo/client/link/context/context.cjs"; 6 | import fetch from "node-fetch"; 7 | 8 | function createAuthenticatedApolloClient(uri, getToken) { 9 | if (!uri) { 10 | throw new Error( 11 | "Cannot make request to GraphQL API, missing `uri` argument" 12 | ); 13 | } 14 | 15 | const authLink = setContext(async (request, { headers }) => { 16 | let accessToken; 17 | 18 | if (getToken) { 19 | accessToken = await getToken(); 20 | } 21 | 22 | return { 23 | headers: { 24 | ...headers, 25 | ...(accessToken && { Authorization: `Bearer ${accessToken}` }) 26 | } 27 | }; 28 | }); 29 | 30 | const errorLink = onError(({ graphQLErrors, networkError }) => { 31 | if (graphQLErrors) { 32 | graphQLErrors.forEach(({ extensions: { serviceName }, message, path }) => 33 | console.error( 34 | `[GraphQL error]: Message: ${message}, Service: ${serviceName}, Path: ${path[0]}` 35 | ) 36 | ); 37 | } 38 | if (networkError) { 39 | console.error(`[Network error]: ${networkError}`); 40 | } 41 | }); 42 | 43 | return new ApolloClient({ 44 | cache: new InMemoryCache(), 45 | link: ApolloLink.from([errorLink, authLink, new HttpLink({ fetch, uri })]), 46 | name: "Workflows Subgraph", 47 | version: "1.0" 48 | }); 49 | } 50 | 51 | export default createAuthenticatedApolloClient; 52 | -------------------------------------------------------------------------------- /workflows/src/graphql/dataSources/WorkflowsDataSource.js: -------------------------------------------------------------------------------- 1 | import { Connection, WorkflowClient } from "@temporalio/client"; 2 | import { DataSource } from "apollo-datasource"; 3 | 4 | import { DeleteAllUserData } from "../../temporal/workflows.js"; 5 | 6 | class WorkflowsDataSource extends DataSource { 7 | constructor() { 8 | super(); 9 | const connection = new Connection(); 10 | this.client = new WorkflowClient(connection.service); 11 | } 12 | 13 | // DELETE 14 | 15 | async deleteAllUserData(accountId) { 16 | try { 17 | const handle = await this.client.start(DeleteAllUserData, { 18 | taskQueue: "marked-app", 19 | workflowId: `delete-user-data-${accountId}`, 20 | args: [accountId] 21 | }); 22 | await handle.result(); 23 | return true; 24 | } catch { 25 | return false; 26 | } 27 | } 28 | } 29 | 30 | export default WorkflowsDataSource; 31 | -------------------------------------------------------------------------------- /workflows/src/graphql/operations.js: -------------------------------------------------------------------------------- 1 | import { gql } from "apollo-server"; 2 | 3 | export const DeleteAccount = gql` 4 | mutation DeleteAccount($id: ID!) { 5 | deleteAccount(id: $id) 6 | } 7 | `; 8 | 9 | export const DeleteProfile = gql` 10 | mutation DeleteProfile($accountId: ID!) { 11 | deleteProfile(accountId: $accountId) 12 | } 13 | `; 14 | 15 | export const DeleteAllUserBookmarks = gql` 16 | mutation DeleteAllUserBookmarks($ownerAccountId: ID!) { 17 | deleteAllUserBookmarks(ownerAccountId: $ownerAccountId) 18 | } 19 | `; 20 | 21 | export const RemoveUserFromNetworks = gql` 22 | mutation RemoveUserFromNetworks($accountId: ID!) { 23 | removeUserFromNetworks(accountId: $accountId) 24 | } 25 | `; 26 | -------------------------------------------------------------------------------- /workflows/src/graphql/resolvers.js: -------------------------------------------------------------------------------- 1 | const resolvers = { 2 | Mutation: { 3 | deleteAllUserData(root, { accountId }, { dataSources }) { 4 | return dataSources.workflowsAPI.deleteAllUserData(accountId); 5 | } 6 | } 7 | }; 8 | 9 | export default resolvers; 10 | -------------------------------------------------------------------------------- /workflows/src/graphql/schema.graphql: -------------------------------------------------------------------------------- 1 | extend schema @link(url: "https://specs.apollo.dev/federation/v2.0") 2 | 3 | # ROOT 4 | 5 | type Mutation { 6 | "Deletes all user account, profile, and bookmark data." 7 | deleteAllUserData(accountId: ID!): Boolean! @owner(argumentName: "accountId") 8 | } 9 | -------------------------------------------------------------------------------- /workflows/src/index.js: -------------------------------------------------------------------------------- 1 | import { dirname, resolve } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { readFileSync } from "fs"; 4 | 5 | import { ApolloServer, gql } from "apollo-server"; 6 | import { buildSubgraphSchema } from "@apollo/subgraph"; 7 | 8 | import { 9 | authDirectives, 10 | restoreReferenceResolvers 11 | } from "../../shared/src/index.js"; 12 | import initTemporalWorker from "./temporal/worker.js"; 13 | import resolvers from "./graphql/resolvers.js"; 14 | import WorkflowsDataSource from "./graphql/dataSources/WorkflowsDataSource.js"; 15 | 16 | const __dirname = dirname(fileURLToPath(import.meta.url)); 17 | const port = process.env.PORT; 18 | 19 | const { authDirectivesTypeDefs, authDirectivesTransformer } = authDirectives(); 20 | const subgraphTypeDefs = readFileSync( 21 | resolve(__dirname, "./graphql/schema.graphql"), 22 | "utf-8" 23 | ); 24 | const typeDefs = gql(`${subgraphTypeDefs}\n${authDirectivesTypeDefs}`); 25 | let subgraphSchema = buildSubgraphSchema({ typeDefs, resolvers }); 26 | subgraphSchema = authDirectivesTransformer(subgraphSchema); 27 | restoreReferenceResolvers(subgraphSchema, resolvers); 28 | 29 | const server = new ApolloServer({ 30 | schema: subgraphSchema, 31 | context: ({ req }) => { 32 | const user = req.headers.user ? JSON.parse(req.headers.user) : null; 33 | return { user }; 34 | }, 35 | dataSources: () => { 36 | return { 37 | workflowsAPI: new WorkflowsDataSource() 38 | }; 39 | } 40 | }); 41 | 42 | initTemporalWorker().catch(error => { 43 | console.error(error); 44 | process.exit(1); 45 | }); 46 | 47 | server.listen({ port }).then(({ url }) => { 48 | console.log(`Workflows service ready at ${url}`); 49 | }); 50 | -------------------------------------------------------------------------------- /workflows/src/temporal/activities.js: -------------------------------------------------------------------------------- 1 | import { 2 | DeleteAccount, 3 | DeleteProfile, 4 | DeleteAllUserBookmarks, 5 | RemoveUserFromNetworks 6 | } from "../graphql/operations.js"; 7 | import Auth0Client from "../utils/Auth0Client.js"; 8 | import createAuthenticatedApolloClient from "../graphql/client.js"; 9 | 10 | const { getToken } = new Auth0Client({ 11 | audience: process.env.AUTH0_AUDIENCE, 12 | clientId: process.env.AUTH0_CLIENT_ID_WORKFLOWS, 13 | clientSecret: process.env.AUTH0_CLIENT_SECRET_WORKFLOWS, 14 | domain: process.env.AUTH0_DOMAIN 15 | }); 16 | 17 | const apolloClient = createAuthenticatedApolloClient( 18 | process.env.GATEWAY_ENDPOINT, 19 | getToken 20 | ); 21 | 22 | export async function deleteAccount(id) { 23 | const response = await apolloClient.mutate({ 24 | mutation: DeleteAccount, 25 | variables: { id } 26 | }); 27 | 28 | if (response.error) { 29 | throw new Error(response.error); 30 | } else { 31 | return true; 32 | } 33 | } 34 | 35 | export async function deleteProfile(accountId) { 36 | const response = await apolloClient.mutate({ 37 | mutation: DeleteProfile, 38 | variables: { accountId } 39 | }); 40 | 41 | if (response.error) { 42 | throw new Error(response.error); 43 | } else { 44 | return true; 45 | } 46 | } 47 | 48 | export async function removeUserFromNetworks(accountId) { 49 | const response = await apolloClient.mutate({ 50 | mutation: RemoveUserFromNetworks, 51 | variables: { accountId } 52 | }); 53 | 54 | if (response.error) { 55 | throw new Error(response.error); 56 | } else { 57 | return true; 58 | } 59 | } 60 | 61 | export async function deleteAllUserBookmarks(ownerAccountId) { 62 | const response = await apolloClient.mutate({ 63 | mutation: DeleteAllUserBookmarks, 64 | variables: { ownerAccountId } 65 | }); 66 | 67 | if (response.error) { 68 | throw new Error(response.error); 69 | } else { 70 | return true; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /workflows/src/temporal/worker.js: -------------------------------------------------------------------------------- 1 | import { Worker } from "@temporalio/worker"; 2 | import { URL } from "url"; 3 | import * as activities from "./activities.js"; 4 | 5 | async function initTemporalWorker() { 6 | const worker = await Worker.create({ 7 | workflowsPath: new URL("./workflows.js", import.meta.url).pathname, 8 | activities, 9 | taskQueue: "marked-app" 10 | }); 11 | 12 | await worker.run(); 13 | } 14 | 15 | export default initTemporalWorker; 16 | -------------------------------------------------------------------------------- /workflows/src/temporal/workflows.js: -------------------------------------------------------------------------------- 1 | import { proxyActivities } from "@temporalio/workflow"; 2 | 3 | const { 4 | deleteAccount, 5 | deleteProfile, 6 | deleteAllUserBookmarks, 7 | removeUserFromNetworks 8 | } = proxyActivities({ 9 | startToCloseTimeout: "1 minute" 10 | }); 11 | 12 | export async function DeleteAllUserData(accountId) { 13 | await deleteAccount(accountId); 14 | await deleteProfile(accountId); 15 | await deleteAllUserBookmarks(accountId); 16 | await removeUserFromNetworks(accountId); 17 | } 18 | -------------------------------------------------------------------------------- /workflows/src/utils/Auth0Client.js: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import jwtDecode from "jwt-decode"; 3 | 4 | class Auth0Client { 5 | constructor({ audience, clientId, clientSecret, domain }) { 6 | this.audience = audience; 7 | this.clientId = clientId; 8 | this.clientSecret = clientSecret; 9 | this.domain = domain; 10 | this.cache = {}; 11 | } 12 | 13 | getToken = async () => { 14 | const clientId = this.clientId; 15 | const cachedToken = this.cache[clientId]; 16 | 17 | if (cachedToken) { 18 | const decodedToken = jwtDecode(cachedToken); 19 | const expiresAt = decodedToken.exp * 1000; 20 | const isAuthenticated = expiresAt 21 | ? new Date().getTime() < expiresAt 22 | : false; 23 | 24 | if (isAuthenticated) { 25 | return cachedToken; 26 | } 27 | } 28 | 29 | const options = { 30 | method: "POST", 31 | headers: { 32 | "cache-control": "no-cache", 33 | "content-type": "application/json" 34 | }, 35 | body: JSON.stringify({ 36 | audience: this.audience, 37 | client_id: this.clientId, 38 | client_secret: this.clientSecret, 39 | grant_type: "client_credentials" 40 | }) 41 | }; 42 | 43 | const response = await fetch(`https://${this.domain}/oauth/token`, options); 44 | const body = await response.json(); 45 | const { access_token } = body; 46 | 47 | if (!access_token) { 48 | throw new Error( 49 | body.error_description || "Cannot retrieve access token." 50 | ); 51 | } 52 | 53 | this.cache[clientId] = access_token; 54 | 55 | return access_token; 56 | }; 57 | } 58 | 59 | export default Auth0Client; 60 | --------------------------------------------------------------------------------