├── graphcool ├── .gitignore ├── .graphcoolrc ├── src │ ├── createShortLink.graphql │ ├── email-password │ │ ├── signup.graphql │ │ ├── authenticate.graphql │ │ ├── loggedInUser.graphql │ │ ├── loggedInUser.ts │ │ ├── authenticate.ts │ │ └── signup.ts │ └── createShortLink.js ├── event.json ├── package.json ├── types.graphql ├── graphcool.yml ├── yarn.lock └── package-lock.json ├── public ├── favicon.ico ├── manifest.json └── index.html ├── src ├── constants.js ├── index.css ├── AppRouter.js ├── components │ ├── Link.js │ ├── CreateShortLink.js │ ├── Signup.js │ ├── ShortLinkRedirect.js │ ├── Login.js │ └── LinkList.js ├── Home.js ├── index.js ├── logo.svg └── serviceWorker.js ├── .gitignore ├── package.json ├── LICENSE └── README.md /graphcool/.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/peterj/shortly/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /graphcool/.graphcoolrc: -------------------------------------------------------------------------------- 1 | targets: 2 | prod: shared-us-west-2/cjbqvtox232ur01419pee2xbi 3 | default: prod 4 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | const constants = { 2 | shortlyID: 'SHORTLY_ID', 3 | shortlyToken: 'SHORTLY_TOKEN' 4 | }; 5 | 6 | export default constants; 7 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | 7 | .error { 8 | font-size: 12pt; 9 | color: #c23616; 10 | } -------------------------------------------------------------------------------- /graphcool/src/createShortLink.graphql: -------------------------------------------------------------------------------- 1 | subscription { 2 | Link(filter: { mutation_in: [CREATED] }) { 3 | node { 4 | id 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /graphcool/src/email-password/signup.graphql: -------------------------------------------------------------------------------- 1 | type SignupUserPayload { 2 | id: ID! 3 | token: String! 4 | } 5 | 6 | extend type Mutation { 7 | signupUser(email: String!, password: String!): SignupUserPayload 8 | } -------------------------------------------------------------------------------- /graphcool/event.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "Link": { 4 | "node": { 5 | "id": "LINK_ID" 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /graphcool/src/email-password/authenticate.graphql: -------------------------------------------------------------------------------- 1 | type AuthenticateUserPayload { 2 | id: ID! 3 | token: String! 4 | } 5 | 6 | extend type Mutation { 7 | authenticateUser(email: String!, password: String!): AuthenticateUserPayload 8 | } 9 | -------------------------------------------------------------------------------- /graphcool/src/email-password/loggedInUser.graphql: -------------------------------------------------------------------------------- 1 | type LoggedInUserPayload { 2 | id: ID! 3 | } 4 | 5 | extend type Query { 6 | # return user data if request contains valid authentication token 7 | loggedInUser: LoggedInUserPayload 8 | } 9 | -------------------------------------------------------------------------------- /graphcool/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphcool", 3 | "version": "0.1.0", 4 | "description": "My Graphcool Service", 5 | "dependencies": { 6 | "graphcool-lib": "^0.1.4", 7 | "@types/bcryptjs": "^2.4.1", 8 | "@types/validator": "^6.3.0", 9 | "bcryptjs": "^2.4.3", 10 | "graphql-request": "^1.4.0", 11 | "validator": "^13.7.0" 12 | } 13 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /graphcool/types.graphql: -------------------------------------------------------------------------------- 1 | type Link @model { 2 | id: ID! @isUnique 3 | hash: String 4 | url: String! 5 | description: String 6 | stats: LinkStats @relation(name: "LinkOnLinkStats") 7 | dummy: String 8 | createdBy: User @relation(name: "LinkOnUser") 9 | } 10 | 11 | type LinkStats @model { 12 | id: ID! @isUnique 13 | clicks: Int 14 | link: Link @relation(name: "LinkOnLinkStats") 15 | } 16 | 17 | type User @model { 18 | # Required system field: 19 | id: ID! @isUnique # read-only (managed by Graphcool) 20 | # Optional system fields (remove if not needed): 21 | createdAt: DateTime! # read-only (managed by Graphcool) 22 | updatedAt: DateTime! # read-only (managed by Graphcool) 23 | email: String! @isUnique 24 | password: String! 25 | links: [Link!]! @relation(name: "LinkOnUser") 26 | } 27 | -------------------------------------------------------------------------------- /src/AppRouter.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Switch, BrowserRouter, Route } from 'react-router-dom'; 3 | 4 | import Home from './Home'; 5 | import Login from './components/Login'; 6 | import Signup from './components/Signup'; 7 | import ShortLinkRedirect from './components/ShortLinkRedirect'; 8 | 9 | const AppRouter = () => ( 10 | 11 | 12 | 13 | 14 | 15 | ( 18 | 19 | )} 20 | /> 21 | 22 | 23 | ); 24 | 25 | export default AppRouter; 26 | -------------------------------------------------------------------------------- /src/components/Link.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | class Link extends Component { 5 | render() { 6 | const clickCount = 7 | (this.props.link.stats && this.props.link.stats.clicks) || 0; 8 | return ( 9 |
10 |
11 | {this.props.link.description} ( 14 | {this.props.link.hash} 15 | ) --> clicks: {clickCount} 16 |
17 |
18 | ); 19 | } 20 | } 21 | 22 | Link.propTypes = { 23 | link: PropTypes.shape({ 24 | id: PropTypes.string, 25 | url: PropTypes.string, 26 | hash: PropTypes.string, 27 | description: PropTypes.string, 28 | }), 29 | }; 30 | 31 | export default Link; 32 | -------------------------------------------------------------------------------- /graphcool/graphcool.yml: -------------------------------------------------------------------------------- 1 | types: ./types.graphql 2 | 3 | functions: 4 | signup: 5 | type: resolver 6 | schema: src/email-password/signup.graphql 7 | handler: 8 | code: src/email-password/signup.ts 9 | 10 | authenticate: 11 | type: resolver 12 | schema: src/email-password/authenticate.graphql 13 | handler: 14 | code: src/email-password/authenticate.ts 15 | 16 | loggedInUser: 17 | type: resolver 18 | schema: src/email-password/loggedInUser.graphql 19 | handler: 20 | code: src/email-password/loggedInUser.ts 21 | 22 | createShortLink: 23 | type: subscription 24 | query: src/createShortLink.graphql 25 | handler: 26 | code: src/createShortLink.js 27 | 28 | permissions: 29 | - operation: "*" 30 | 31 | 32 | # Your root tokens used for functions to get full access to the API 33 | # Read more here: 34 | # https://www.graph.cool/docs/reference/auth/authentication/authentication-tokens-eip7ahqu5o 35 | # rootTokens: 36 | # - mytoken 37 | 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shortly", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "apollo-boost": "^0.3.1", 7 | "apollo-cache-inmemory": "^1.5.1", 8 | "apollo-client": "^2.5.1", 9 | "apollo-link": "^1.2.11", 10 | "apollo-link-http": "^1.5.14", 11 | "apollo-link-ws": "^1.0.17", 12 | "graphql": "^14.1.1", 13 | "graphql-tag": "^2.10.1", 14 | "prop-types": "^15.7.2", 15 | "react": "^16.8.5", 16 | "react-apollo": "^2.5.2", 17 | "react-dom": "^16.8.5", 18 | "react-router-dom": "^5.0.0", 19 | "react-scripts": "2.1.8", 20 | "subscriptions-transport-ws": "^0.9.16" 21 | }, 22 | "scripts": { 23 | "start": "react-scripts start", 24 | "build": "react-scripts build", 25 | "test": "react-scripts test", 26 | "eject": "react-scripts eject" 27 | }, 28 | "eslintConfig": { 29 | "extends": "react-app" 30 | }, 31 | "browserslist": [ 32 | ">0.2%", 33 | "not dead", 34 | "not ie <= 11", 35 | "not op_mini all" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Peter Jausovec 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Shortly 2 | 3 | Simple URL shortener built with React, Apollo and GraphQL. You can find the 4 | corresponding tutorials 5 | [here](https://medium.com/@pjausovec/building-url-shortener-using-react-apollo-and-graphql-part-i-467aef8c64ce) 6 | 7 | ### Set up Graphcool 8 | 9 | This project uses a Graphcool service. Visit [Graphcool](http://graph.cool) to 10 | sign-up and get a free account. To get started, install the Graphcool CLI first, 11 | login and then deploy the Graphcool service: 12 | 13 | ``` 14 | cd graphcool 15 | 16 | # Login to graphcool 17 | graphcool login 18 | 19 | # Deploy the graphcool service 20 | graphcool deploy 21 | 22 | # Get the API URLs 23 | graphcool info 24 | ``` 25 | 26 | Next, paste the Simple and Subscriptions API URLs to index.js. 27 | 28 | ### Run the project 29 | 30 | After you've set up Graphcool and pasted the Simple and Subscriptions API URLs 31 | to index.js, you can run `yarn start` to start the website. 32 | 33 | 34 | ## License 35 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fpeterj%2Fshortly.svg?type=large)](https://app.fossa.io/projects/git%2Bgithub.com%2Fpeterj%2Fshortly?ref=badge_large) 36 | -------------------------------------------------------------------------------- /graphcool/src/email-password/loggedInUser.ts: -------------------------------------------------------------------------------- 1 | import { fromEvent, FunctionEvent } from 'graphcool-lib' 2 | import { GraphQLClient } from 'graphql-request' 3 | 4 | interface User { 5 | id: string 6 | } 7 | 8 | export default async (event: FunctionEvent<{}>) => { 9 | console.log(event) 10 | 11 | try { 12 | // no logged in user 13 | if (!event.context.auth || !event.context.auth.nodeId) { 14 | return { data: null } 15 | } 16 | 17 | const userId = event.context.auth.nodeId 18 | const graphcool = fromEvent(event) 19 | const api = graphcool.api('simple/v1') 20 | 21 | // get user by id 22 | const user: User = await getUser(api, userId) 23 | .then(r => r.User) 24 | 25 | // no logged in user 26 | if (!user || !user.id) { 27 | return { data: null } 28 | } 29 | 30 | return { data: { id: user.id } } 31 | } catch (e) { 32 | console.log(e) 33 | return { error: 'An unexpected error occured during authentication.' } 34 | } 35 | } 36 | 37 | async function getUser(api: GraphQLClient, id: string): Promise<{ User }> { 38 | const query = ` 39 | query getUser($id: ID!) { 40 | User(id: $id) { 41 | id 42 | } 43 | } 44 | ` 45 | 46 | const variables = { 47 | id, 48 | } 49 | 50 | return api.request<{ User }>(query, variables) 51 | } -------------------------------------------------------------------------------- /graphcool/src/email-password/authenticate.ts: -------------------------------------------------------------------------------- 1 | import { fromEvent, FunctionEvent } from 'graphcool-lib' 2 | import { GraphQLClient } from 'graphql-request' 3 | import * as bcrypt from 'bcryptjs' 4 | 5 | interface User { 6 | id: string 7 | password: string 8 | } 9 | 10 | interface EventData { 11 | email: string 12 | password: string 13 | } 14 | 15 | const SALT_ROUNDS = 10 16 | 17 | export default async (event: FunctionEvent) => { 18 | console.log(event) 19 | 20 | try { 21 | const graphcool = fromEvent(event) 22 | const api = graphcool.api('simple/v1') 23 | 24 | const { email, password } = event.data 25 | 26 | // get user by email 27 | const user: User = await getUserByEmail(api, email) 28 | .then(r => r.User) 29 | 30 | // no user with this email 31 | if (!user) { 32 | return { error: 'Invalid credentials!' } 33 | } 34 | 35 | // check password 36 | const passwordIsCorrect = await bcrypt.compare(password, user.password) 37 | if (!passwordIsCorrect) { 38 | return { error: 'Invalid credentials!' } 39 | } 40 | 41 | // generate node token for existing User node 42 | const token = await graphcool.generateNodeToken(user.id, 'User') 43 | 44 | return { data: { id: user.id, token} } 45 | } catch (e) { 46 | console.log(e) 47 | return { error: 'An unexpected error occured during authentication.' } 48 | } 49 | } 50 | 51 | async function getUserByEmail(api: GraphQLClient, email: string): Promise<{ User }> { 52 | const query = ` 53 | query getUserByEmail($email: String!) { 54 | User(email: $email) { 55 | id 56 | password 57 | } 58 | } 59 | ` 60 | 61 | const variables = { 62 | email, 63 | } 64 | 65 | return api.request<{ User }>(query, variables) 66 | } 67 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 24 | React App 25 | 26 | 27 | 28 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/Home.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import LinkList from './components/LinkList'; 4 | import CreateShortLink from './components/CreateShortLink'; 5 | import gql from 'graphql-tag'; 6 | import { graphql } from 'react-apollo'; 7 | import constants from './constants'; 8 | 9 | const LOGGED_IN_USER_QUERY = gql` 10 | query CurrentUser { 11 | loggedInUser { 12 | id 13 | } 14 | } 15 | `; 16 | 17 | class Home extends Component { 18 | logout = () => { 19 | localStorage.removeItem(constants.shortlyID); 20 | localStorage.removeItem(constants.shortlyToken); 21 | this.props.history.push('/'); 22 | }; 23 | 24 | render() { 25 | if (this.props.currentUser && this.props.currentUser.loading) { 26 | return
Loading ...
; 27 | } 28 | 29 | const userId = 30 | this.props.currentUser.loggedInUser && 31 | this.props.currentUser.loggedInUser.id; 32 | 33 | if (userId) { 34 | return ( 35 |
36 | Hi user {userId} () 41 |
42 |
43 |

Create a short link

44 | 45 |
46 |
47 |

All links

48 | 49 |
50 |
51 | ); 52 | } else { 53 | return ( 54 |
55 | Please login or{' '} 56 | sign up! 57 |
58 | ); 59 | } 60 | } 61 | } 62 | 63 | export default graphql(LOGGED_IN_USER_QUERY, { name: 'currentUser' })(Home); 64 | -------------------------------------------------------------------------------- /src/components/CreateShortLink.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import gql from 'graphql-tag'; 4 | import { graphql } from 'react-apollo'; 5 | import constants from '../constants'; 6 | 7 | const CREATE_SHORT_LINK_MUTATION = gql` 8 | mutation CreateLinkMutation( 9 | $url: String! 10 | $description: String! 11 | $createdById: ID! 12 | ) { 13 | createLink( 14 | url: $url 15 | description: $description 16 | createdById: $createdById 17 | ) { 18 | id 19 | } 20 | } 21 | `; 22 | 23 | class CreateShortLink extends Component { 24 | constructor(props) { 25 | super(props); 26 | this.state = { 27 | description: '', 28 | url: '', 29 | }; 30 | } 31 | 32 | createShortLink = async () => { 33 | const { url, description } = this.state; 34 | await this.props.createShortLinkMutation({ 35 | variables: { 36 | url, 37 | description, 38 | createdById: localStorage.getItem(constants.shortlyID), 39 | }, 40 | }); 41 | }; 42 | 43 | render() { 44 | return ( 45 |
46 | this.setState({ url: e.target.value })} 52 | /> 53 | 59 | this.setState({ description: e.target.value }) 60 | } 61 | /> 62 | 63 |
64 | ); 65 | } 66 | } 67 | 68 | export default graphql(CREATE_SHORT_LINK_MUTATION, { 69 | name: 'createShortLinkMutation', 70 | })(CreateShortLink); 71 | -------------------------------------------------------------------------------- /graphcool/src/createShortLink.js: -------------------------------------------------------------------------------- 1 | const { fromEvent } = require('graphcool-lib'); 2 | 3 | const createHash = itemCount => { 4 | let hashDigits = []; 5 | // dividend is a unique integer (in our case, number of links) 6 | let dividend = itemCount + 1; 7 | let remainder = 0; 8 | while (dividend > 0) { 9 | remainder = dividend % 62; 10 | dividend = Math.floor(dividend / 62); 11 | hashDigits.unshift(remainder); 12 | } 13 | const alphabetArray = `ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789`.split( 14 | '', 15 | ); 16 | // Convert hashDigits to base62 representation 17 | let hashString = ''; 18 | let i = 0; 19 | while (hashDigits.length > i) { 20 | hashString += alphabetArray[hashDigits[i]]; 21 | i++; 22 | } 23 | return hashString; 24 | }; 25 | 26 | module.exports = async event => { 27 | // Get the data from the event - the data 28 | // is determined by the subscription. In our case, it will look like this: 29 | // event = { 30 | // "data": { 31 | // "Link": { 32 | // "node": { 33 | // "id": "LINK_ID" 34 | // } 35 | // } 36 | // } 37 | // } 38 | const { id } = event.data.Link.node; 39 | 40 | const graphcool = fromEvent(event); 41 | const api = graphcool.api('simple/v1'); 42 | 43 | // 1. Get the link count. 44 | const getLinkCountQuery = ` 45 | query GetLinkCountQuery { 46 | links: _allLinksMeta { 47 | count 48 | } 49 | }`; 50 | 51 | const linkCountQueryResult = await api.request(getLinkCountQuery); 52 | const linkCount = linkCountQueryResult.links.count; 53 | 54 | // 2. Get the hash. 55 | const hash = createHash(linkCount); 56 | 57 | // 3. Update the link with a hash. 58 | const updateLinkMutation = ` 59 | mutation ($id: ID!, $hash: String!) { 60 | updateLink(id: $id, hash: $hash) { 61 | id 62 | } 63 | }`; 64 | 65 | const variables = { id, hash }; 66 | await api.request(updateLinkMutation, variables); 67 | 68 | return { 69 | data: { 70 | success: true, 71 | }, 72 | }; 73 | }; 74 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import AppRouter from './AppRouter'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | import { ApolloClient } from 'apollo-client'; 8 | import { InMemoryCache } from 'apollo-cache-inmemory'; 9 | import { ApolloProvider } from 'react-apollo'; 10 | 11 | import { WebSocketLink } from 'apollo-link-ws'; 12 | import { split, ApolloLink } from 'apollo-link'; 13 | import { HttpLink } from 'apollo-link-http'; 14 | import { getMainDefinition } from 'apollo-utilities'; 15 | 16 | const GRAPHQL_ENDPOINT = ''; 17 | const SUBSCRIPTIONS_ENDPOINT = ''; 18 | 19 | if (!SUBSCRIPTIONS_ENDPOINT) { 20 | throw Error('Provide a GraphQL Subscriptions endpoint.'); 21 | } 22 | 23 | if (!GRAPHQL_ENDPOINT) { 24 | throw Error('Provide a GraphQL endpoint.'); 25 | } 26 | 27 | const apolloLinkWithToken = new ApolloLink((operation, forward) => { 28 | const token = localStorage.getItem('SHORTLY_TOKEN'); 29 | const authHeader = token ? `Bearer ${token}` : null; 30 | operation.setContext({ 31 | headers: { 32 | authorization: authHeader 33 | } 34 | }); 35 | return forward(operation); 36 | }); 37 | 38 | const httpLink = new HttpLink({ 39 | uri: GRAPHQL_ENDPOINT 40 | }); 41 | 42 | const httpLinkWithToken = apolloLinkWithToken.concat(httpLink); 43 | 44 | const wsLink = new WebSocketLink({ 45 | uri: SUBSCRIPTIONS_ENDPOINT, 46 | options: { 47 | reconnect: true 48 | } 49 | }); 50 | 51 | const link = split( 52 | ({ query }) => { 53 | const { kind, operation } = getMainDefinition(query); 54 | return kind === 'OperationDefinition' && operation === 'subscription'; 55 | }, 56 | wsLink, 57 | httpLinkWithToken 58 | ); 59 | 60 | const client = new ApolloClient({ 61 | link, 62 | cache: new InMemoryCache() 63 | }); 64 | 65 | const withApolloProvider = Comp => ( 66 | {Comp} 67 | ); 68 | 69 | ReactDOM.render( 70 | withApolloProvider(), 71 | document.getElementById('root') 72 | ); 73 | 74 | // If you want your app to work offline and load faster, you can change 75 | // unregister() to register() below. Note this comes with some pitfalls. 76 | // Learn more about service workers: https://bit.ly/CRA-PWA 77 | serviceWorker.unregister(); 78 | -------------------------------------------------------------------------------- /graphcool/src/email-password/signup.ts: -------------------------------------------------------------------------------- 1 | import { fromEvent, FunctionEvent } from 'graphcool-lib' 2 | import { GraphQLClient } from 'graphql-request' 3 | import * as bcrypt from 'bcryptjs' 4 | import * as validator from 'validator' 5 | 6 | interface User { 7 | id: string 8 | } 9 | 10 | interface EventData { 11 | email: string 12 | password: string 13 | } 14 | 15 | const SALT_ROUNDS = 10 16 | 17 | export default async (event: FunctionEvent) => { 18 | console.log(event) 19 | 20 | try { 21 | const graphcool = fromEvent(event) 22 | const api = graphcool.api('simple/v1') 23 | 24 | const { email, password } = event.data 25 | 26 | if (!validator.isEmail(email)) { 27 | return { error: 'Not a valid email' } 28 | } 29 | 30 | // check if user exists already 31 | const userExists: boolean = await getUser(api, email) 32 | .then(r => r.User !== null) 33 | if (userExists) { 34 | return { error: 'Email already in use' } 35 | } 36 | 37 | // create password hash 38 | const salt = bcrypt.genSaltSync(SALT_ROUNDS) 39 | const hash = await bcrypt.hash(password, salt) 40 | 41 | // create new user 42 | const userId = await createGraphcoolUser(api, email, hash) 43 | 44 | // generate node token for new User node 45 | const token = await graphcool.generateNodeToken(userId, 'User') 46 | 47 | return { data: { id: userId, token } } 48 | } catch (e) { 49 | console.log(e) 50 | return { error: 'An unexpected error occured during signup.' } 51 | } 52 | } 53 | 54 | async function getUser(api: GraphQLClient, email: string): Promise<{ User }> { 55 | const query = ` 56 | query getUser($email: String!) { 57 | User(email: $email) { 58 | id 59 | } 60 | } 61 | ` 62 | 63 | const variables = { 64 | email, 65 | } 66 | 67 | return api.request<{ User }>(query, variables) 68 | } 69 | 70 | async function createGraphcoolUser(api: GraphQLClient, email: string, password: string): Promise { 71 | const mutation = ` 72 | mutation createGraphcoolUser($email: String!, $password: String!) { 73 | createUser( 74 | email: $email, 75 | password: $password 76 | ) { 77 | id 78 | } 79 | } 80 | ` 81 | 82 | const variables = { 83 | email, 84 | password: password, 85 | } 86 | 87 | return api.request<{ createUser: User }>(mutation, variables) 88 | .then(r => r.createUser.id) 89 | } 90 | -------------------------------------------------------------------------------- /src/components/Signup.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import gql from 'graphql-tag'; 4 | import { graphql } from 'react-apollo'; 5 | import constants from '../constants'; 6 | 7 | const SIGNUP_USER_MUTATION = gql` 8 | mutation SignupUser($email: String!, $password: String!) { 9 | signupUser(email: $email, password: $password) { 10 | id 11 | token 12 | } 13 | } 14 | `; 15 | 16 | class Signup extends Component { 17 | state = { 18 | email: '', 19 | password: '', 20 | error: '' 21 | } 22 | 23 | signup = async () => { 24 | this.setState({error: ''}); 25 | 26 | const { email, password } = this.state; 27 | try { 28 | const result = await this.props.signupUserMutation({ 29 | variables: { 30 | email, 31 | password, 32 | }, 33 | }); 34 | 35 | // Store the ID and token in local storage. 36 | localStorage.setItem(constants.shortlyID, result.data.signupUser.id); 37 | localStorage.setItem(constants.shortlyToken, result.data.signupUser.token); 38 | this.props.history.push('/'); 39 | } catch (error) { 40 | this.setState({error: `Sorry, an error occured on signing up. (${error})`}) 41 | } 42 | }; 43 | 44 | render() { 45 | return ( 46 |
47 |

Join Shortly

48 | this.setState({ email: e.target.value })} 54 | /> 55 |
56 | this.setState({ password: e.target.value })} 62 | /> 63 |
64 | 65 | { this.state.error && 66 |

{this.state.error}

67 | } 68 |
69 | ); 70 | } 71 | } 72 | 73 | export default graphql(SIGNUP_USER_MUTATION, { name: 'signupUserMutation' })( 74 | Signup, 75 | ); 76 | -------------------------------------------------------------------------------- /src/components/ShortLinkRedirect.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import gql from 'graphql-tag'; 3 | import { graphql, compose } from 'react-apollo'; 4 | import PropTypes from 'prop-types'; 5 | 6 | const GET_FULL_LINK_QUERY = gql` 7 | query GetFullLink($hash: String!) { 8 | allLinks(filter: { hash: $hash }) { 9 | id 10 | url 11 | stats { 12 | id 13 | clicks 14 | } 15 | } 16 | } 17 | `; 18 | 19 | const UPDATE_CLICK_COUNT_MUTATION = gql` 20 | mutation UpdateClickCount($id: ID!, $clicks: Int!) { 21 | updateLinkStats(id: $id, clicks: $clicks) { 22 | id 23 | } 24 | } 25 | `; 26 | 27 | const CREATE_LINK_STATS_MUTATION = gql` 28 | mutation CreateLinkStats($linkId: ID!, $clicks: Int!) { 29 | createLinkStats(linkId: $linkId, clicks: $clicks) { 30 | id 31 | } 32 | } 33 | `; 34 | 35 | const ShortLinkRedirect = ({ 36 | updateClickCount, 37 | createLinkStats, 38 | hash, 39 | data: { loading, error, allLinks }, 40 | }) => { 41 | if (error) { 42 | return
Error occurred: {error}
; 43 | } 44 | 45 | if (loading) { 46 | return
Loading ...
; 47 | } 48 | 49 | if (!allLinks || allLinks.length !== 1) { 50 | return
No redirect found for '{hash}'
; 51 | } 52 | 53 | const linkInfo = allLinks[0]; 54 | 55 | if (!linkInfo.stats) { 56 | // Create new link stats 57 | createLinkStats({ 58 | variables: { 59 | linkId: linkInfo.id, 60 | clicks: 1, 61 | }, 62 | }); 63 | } else { 64 | let currentClicks = (linkInfo.stats && linkInfo.stats.clicks) || 0; 65 | 66 | // Increment the click count 67 | currentClicks++; 68 | 69 | // Update the click count. 70 | updateClickCount({ 71 | variables: { 72 | id: linkInfo.stats.id, 73 | clicks: currentClicks, 74 | }, 75 | }); 76 | } 77 | 78 | // Navigate to the full URL 79 | window.location = linkInfo.url; 80 | return null; 81 | }; 82 | 83 | ShortLinkRedirect.propTypes = { 84 | hash: PropTypes.string, 85 | }; 86 | 87 | export default compose( 88 | graphql(UPDATE_CLICK_COUNT_MUTATION, { name: 'updateClickCount' }), 89 | graphql(CREATE_LINK_STATS_MUTATION, { name: 'createLinkStats' }), 90 | graphql(GET_FULL_LINK_QUERY, { 91 | options: ({ hash }) => ({ variables: { hash } }), 92 | }), 93 | )(ShortLinkRedirect); 94 | -------------------------------------------------------------------------------- /src/components/Login.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import gql from 'graphql-tag'; 4 | import { graphql } from 'react-apollo'; 5 | import constants from '../constants'; 6 | 7 | const AUTHENTICATE_USER_MUTATION = gql` 8 | mutation AuthUser($email: String!, $password: String!) { 9 | authenticateUser(email: $email, password: $password) { 10 | id 11 | token 12 | } 13 | } 14 | `; 15 | 16 | class Login extends Component { 17 | state = { 18 | email: '', 19 | password: '', 20 | error: '' 21 | } 22 | 23 | login = async () => { 24 | this.setState({error: ''}); 25 | 26 | const { email, password } = this.state; 27 | try { 28 | const result = await this.props.authenticateUserMutation({ 29 | variables: { 30 | email, 31 | password, 32 | }, 33 | }); 34 | // Store the ID and token in local storage. 35 | localStorage.setItem(constants.shortlyID, result.data.authenticateUser.id); 36 | localStorage.setItem( 37 | constants.shortlyToken, 38 | result.data.authenticateUser.token, 39 | ); 40 | this.props.history.push('/'); 41 | } catch (error) { 42 | this.setState({error: `Sorry, an error occured on login. (${error})`}) 43 | } 44 | }; 45 | 46 | render() { 47 | return ( 48 |
49 |

Login to Shortly

50 | this.setState({ email: e.target.value })} 56 | /> 57 |
58 | this.setState({ password: e.target.value })} 64 | /> 65 |
66 | 67 | { this.state.error && 68 |

{this.state.error}

69 | } 70 |
71 | ); 72 | } 73 | } 74 | 75 | export default graphql(AUTHENTICATE_USER_MUTATION, { 76 | name: 'authenticateUserMutation', 77 | })(Login); 78 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/LinkList.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Link from './Link'; 3 | 4 | import gql from 'graphql-tag'; 5 | import { graphql } from 'react-apollo'; 6 | import constants from '../constants'; 7 | 8 | const ALL_LINKS_QUERY = gql` 9 | query AllLinksQuery($createdById: ID!) { 10 | allLinks(filter: { createdBy: { id: $createdById } }) { 11 | id 12 | url 13 | description 14 | hash 15 | stats { 16 | clicks 17 | } 18 | } 19 | } 20 | `; 21 | 22 | 23 | const LINKS_SUBSCRIPTION = gql` 24 | subscription NewLinkCreatedSubscription($createdById: ID!) { 25 | Link( 26 | filter: { 27 | mutation_in: [CREATED, UPDATED] 28 | node: { createdBy: { id: $createdById } } 29 | } 30 | ) { 31 | node { 32 | id 33 | url 34 | description 35 | hash 36 | stats { 37 | clicks 38 | } 39 | } 40 | } 41 | } 42 | `; 43 | 44 | class LinkList extends Component { 45 | componentDidMount() { 46 | this.props.allLinksQuery.subscribeToMore({ 47 | document: LINKS_SUBSCRIPTION, 48 | variables: { createdById: localStorage.getItem(constants.shortlyID) }, 49 | updateQuery: (prev, { subscriptionData }) => { 50 | if ( 51 | prev.allLinks.find( 52 | l => l.id === subscriptionData.data.Link.node.id, 53 | ) 54 | ) { 55 | return prev; 56 | } 57 | const newLinks = [ 58 | ...prev.allLinks, 59 | subscriptionData.data.Link.node, 60 | ]; 61 | const result = { 62 | ...prev, 63 | allLinks: newLinks, 64 | }; 65 | return result; 66 | }, 67 | }); 68 | } 69 | 70 | render() { 71 | if (this.props.allLinksQuery && this.props.allLinksQuery.loading) { 72 | return
Loading ...
; 73 | } 74 | 75 | if (this.props.allLinksQuery && this.props.allLinksQuery.error) { 76 | console.log(JSON.stringify(this.props.allLinksQuery)); 77 | return
Error occurred
; 78 | } 79 | 80 | const allLinks = this.props.allLinksQuery.allLinks; 81 | if (allLinks.length === 0) { 82 | return
No links...
; 83 | } 84 | 85 | return ( 86 |
87 | {allLinks.map(link => )} 88 |
89 | ); 90 | } 91 | } 92 | 93 | export default graphql(ALL_LINKS_QUERY, { 94 | name: 'allLinksQuery', 95 | options: props => ({ 96 | variables: { 97 | createdById: localStorage.getItem(constants.shortlyID), 98 | }, 99 | }), 100 | })(LinkList); 101 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.1/8 is considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then(registration => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch(error => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl) 104 | .then(response => { 105 | // Ensure service worker exists, and that we really are getting a JS file. 106 | const contentType = response.headers.get('content-type'); 107 | if ( 108 | response.status === 404 || 109 | (contentType != null && contentType.indexOf('javascript') === -1) 110 | ) { 111 | // No service worker found. Probably a different app. Reload the page. 112 | navigator.serviceWorker.ready.then(registration => { 113 | registration.unregister().then(() => { 114 | window.location.reload(); 115 | }); 116 | }); 117 | } else { 118 | // Service worker found. Proceed as normal. 119 | registerValidSW(swUrl, config); 120 | } 121 | }) 122 | .catch(() => { 123 | console.log( 124 | 'No internet connection found. App is running in offline mode.' 125 | ); 126 | }); 127 | } 128 | 129 | export function unregister() { 130 | if ('serviceWorker' in navigator) { 131 | navigator.serviceWorker.ready.then(registration => { 132 | registration.unregister(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /graphcool/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/bcryptjs@^2.4.1": 6 | version "2.4.1" 7 | resolved "https://registry.yarnpkg.com/@types/bcryptjs/-/bcryptjs-2.4.1.tgz#7fb63922b5b106edacdcfe084cd38850f78aacfc" 8 | 9 | "@types/validator@^6.3.0": 10 | version "6.3.0" 11 | resolved "https://registry.yarnpkg.com/@types/validator/-/validator-6.3.0.tgz#d7454bd67c6a933a9dbe939ae16edbf0f6894e70" 12 | 13 | apollo-fetch@^0.6.0: 14 | version "0.6.0" 15 | resolved "https://registry.yarnpkg.com/apollo-fetch/-/apollo-fetch-0.6.0.tgz#aae9b28c117af344b091ec8ba4d1a5aa0474dc5d" 16 | dependencies: 17 | isomorphic-fetch "^2.2.1" 18 | 19 | apollo-link-http@^0.7.0: 20 | version "0.7.0" 21 | resolved "https://registry.yarnpkg.com/apollo-link-http/-/apollo-link-http-0.7.0.tgz#cf75e9e2537d785deefff2cd1ae2ff0dc1c88300" 22 | dependencies: 23 | apollo-fetch "^0.6.0" 24 | graphql "^0.11.0" 25 | 26 | apollo-link@^0.7.0: 27 | version "0.7.0" 28 | resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-0.7.0.tgz#a8f09069b31821c27285584264356b1b6e6be6f2" 29 | dependencies: 30 | apollo-utilities "^0.2.0-beta.0" 31 | graphql "^0.11.3" 32 | zen-observable-ts "^0.5.0" 33 | 34 | apollo-utilities@^0.2.0-beta.0: 35 | version "0.2.0-rc.3" 36 | resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-0.2.0-rc.3.tgz#7bd93be0f587f20c5b46e21880272e305759fdc2" 37 | 38 | apollo-utilities@^1.0.1: 39 | version "1.0.3" 40 | resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.0.3.tgz#bf435277609850dd442cf1d5c2e8bc6655eaa943" 41 | 42 | bcryptjs@^2.4.3: 43 | version "2.4.3" 44 | resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" 45 | 46 | cross-fetch@1.1.1: 47 | version "1.1.1" 48 | resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-1.1.1.tgz#dede6865ae30f37eae62ac90ebb7bdac002b05a0" 49 | dependencies: 50 | node-fetch "1.7.3" 51 | whatwg-fetch "2.0.3" 52 | 53 | deprecated-decorator@^0.1.6: 54 | version "0.1.6" 55 | resolved "https://registry.yarnpkg.com/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz#00966317b7a12fe92f3cc831f7583af329b86c37" 56 | 57 | encoding@^0.1.11: 58 | version "0.1.12" 59 | resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" 60 | dependencies: 61 | iconv-lite "~0.4.13" 62 | 63 | graphcool-lib@^0.1.4: 64 | version "0.1.4" 65 | resolved "https://registry.yarnpkg.com/graphcool-lib/-/graphcool-lib-0.1.4.tgz#0028b6d9b10dbf4d4f50c0d3ce4ccfcac18acaff" 66 | dependencies: 67 | apollo-link "^0.7.0" 68 | apollo-link-http "^0.7.0" 69 | graphql "^0.11.2" 70 | graphql-request "^1.3.4" 71 | graphql-tools "^2.4.0" 72 | node-fetch "^1.7.3" 73 | source-map-support "^0.4.17" 74 | 75 | graphql-request@^1.3.4, graphql-request@^1.4.0: 76 | version "1.4.1" 77 | resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-1.4.1.tgz#0772743cfac8dfdd4d69d36106a96c9bdd191ce8" 78 | dependencies: 79 | cross-fetch "1.1.1" 80 | 81 | graphql-tools@^2.4.0: 82 | version "2.14.1" 83 | resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-2.14.1.tgz#15f96683d7f178042baddcfc17d73dcfeee67356" 84 | dependencies: 85 | apollo-utilities "^1.0.1" 86 | deprecated-decorator "^0.1.6" 87 | uuid "^3.1.0" 88 | 89 | graphql@^0.11.0, graphql@^0.11.2, graphql@^0.11.3: 90 | version "0.11.7" 91 | resolved "https://registry.yarnpkg.com/graphql/-/graphql-0.11.7.tgz#e5abaa9cb7b7cccb84e9f0836bf4370d268750c6" 92 | dependencies: 93 | iterall "1.1.3" 94 | 95 | iconv-lite@~0.4.13: 96 | version "0.4.19" 97 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" 98 | 99 | is-stream@^1.0.1: 100 | version "1.1.0" 101 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" 102 | 103 | isomorphic-fetch@^2.2.1: 104 | version "2.2.1" 105 | resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" 106 | dependencies: 107 | node-fetch "^1.0.1" 108 | whatwg-fetch ">=0.10.0" 109 | 110 | iterall@1.1.3: 111 | version "1.1.3" 112 | resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.1.3.tgz#1cbbff96204056dde6656e2ed2e2226d0e6d72c9" 113 | 114 | node-fetch@1.7.3, node-fetch@^1.0.1, node-fetch@^1.7.3: 115 | version "1.7.3" 116 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" 117 | dependencies: 118 | encoding "^0.1.11" 119 | is-stream "^1.0.1" 120 | 121 | source-map-support@^0.4.17: 122 | version "0.4.18" 123 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" 124 | dependencies: 125 | source-map "^0.5.6" 126 | 127 | source-map@^0.5.6: 128 | version "0.5.7" 129 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" 130 | 131 | uuid@^3.1.0: 132 | version "3.1.0" 133 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" 134 | 135 | validator@^13.7.0: 136 | version "13.7.0" 137 | resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857" 138 | 139 | whatwg-fetch@2.0.3, whatwg-fetch@>=0.10.0: 140 | version "2.0.3" 141 | resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" 142 | 143 | zen-observable-ts@^0.5.0: 144 | version "0.5.0" 145 | resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.5.0.tgz#c5021e7ac486fc281f6126d574673cfb6daf0069" 146 | -------------------------------------------------------------------------------- /graphcool/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphcool", 3 | "version": "0.1.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/bcryptjs": { 8 | "version": "2.4.2", 9 | "resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.2.tgz", 10 | "integrity": "sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==" 11 | }, 12 | "@types/validator": { 13 | "version": "6.3.0", 14 | "resolved": "https://registry.npmjs.org/@types/validator/-/validator-6.3.0.tgz", 15 | "integrity": "sha512-fUc+9BEr1WWW8wVRl5I/pyRDGZNe9nH06kOzlGH3eN7N8VVPW/zSrE0Vdca6G2sRCVmIN/XuOenbmldAAyTYaw==" 16 | }, 17 | "apollo-fetch": { 18 | "version": "0.6.0", 19 | "resolved": "https://registry.npmjs.org/apollo-fetch/-/apollo-fetch-0.6.0.tgz", 20 | "integrity": "sha1-qumyjBF680SwkeyLpNGlqgR03F0=", 21 | "requires": { 22 | "isomorphic-fetch": "2.2.1" 23 | } 24 | }, 25 | "apollo-link": { 26 | "version": "0.7.0", 27 | "resolved": "https://registry.npmjs.org/apollo-link/-/apollo-link-0.7.0.tgz", 28 | "integrity": "sha512-LPwygaqW57k7D5rKEdd5xZcLL7SY7MbUkvDYXfU/+el6Iq46AXfxJICXrU6UVydngEBD1DYm0t8CT4JRk0MR7g==", 29 | "requires": { 30 | "apollo-utilities": "0.2.0-rc.3", 31 | "graphql": "0.11.7", 32 | "zen-observable-ts": "0.5.0" 33 | } 34 | }, 35 | "apollo-link-http": { 36 | "version": "0.7.0", 37 | "resolved": "https://registry.npmjs.org/apollo-link-http/-/apollo-link-http-0.7.0.tgz", 38 | "integrity": "sha512-BCYxduD5oHfsiWz15pcjooyAsP4E6gmL1BH4z4ikwU6I++QhbIBt5zH+ezUQcOKcozNY7+ZfcA4oaRud9FbkbA==", 39 | "requires": { 40 | "apollo-fetch": "0.6.0", 41 | "graphql": "0.11.7" 42 | } 43 | }, 44 | "apollo-utilities": { 45 | "version": "0.2.0-rc.3", 46 | "resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-0.2.0-rc.3.tgz", 47 | "integrity": "sha512-UM5ok/DUKSgh/3T302hoPqCAhqfXdvBaQKOQJb0QUuX3qu2qVKzvwFsv/C3zWYySeCJTP9EoV2LtooIJOhSL4g==" 48 | }, 49 | "bcryptjs": { 50 | "version": "2.4.3", 51 | "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz", 52 | "integrity": "sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms=" 53 | }, 54 | "cross-fetch": { 55 | "version": "1.1.1", 56 | "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-1.1.1.tgz", 57 | "integrity": "sha512-+VJE04+UfxxmBfcnmAu/lKor53RUCx/1ilOti4p+JgrnLQ4AZZIRoe2OEd76VaHyWQmQxqKnV+TaqjHC4r0HWw==", 58 | "requires": { 59 | "node-fetch": "1.7.3", 60 | "whatwg-fetch": "2.0.3" 61 | } 62 | }, 63 | "deprecated-decorator": { 64 | "version": "0.1.6", 65 | "resolved": "https://registry.npmjs.org/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz", 66 | "integrity": "sha1-AJZjF7ehL+kvPMgx91g68ym4bDc=" 67 | }, 68 | "encoding": { 69 | "version": "0.1.12", 70 | "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", 71 | "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", 72 | "requires": { 73 | "iconv-lite": "0.4.19" 74 | } 75 | }, 76 | "graphcool-lib": { 77 | "version": "0.1.4", 78 | "resolved": "https://registry.npmjs.org/graphcool-lib/-/graphcool-lib-0.1.4.tgz", 79 | "integrity": "sha512-qUmcS+nQLPR3gTOvfQbHAdwKkwBJlUIm1SNb+5lcPkh3iIPXSISHJcpt7kECNB2s75O8shKYGMXJRd3F5S2Aaw==", 80 | "requires": { 81 | "apollo-link": "0.7.0", 82 | "apollo-link-http": "0.7.0", 83 | "graphql": "0.11.7", 84 | "graphql-request": "1.4.1", 85 | "graphql-tools": "2.14.1", 86 | "node-fetch": "1.7.3", 87 | "source-map-support": "0.4.18" 88 | } 89 | }, 90 | "graphql": { 91 | "version": "0.11.7", 92 | "resolved": "https://registry.npmjs.org/graphql/-/graphql-0.11.7.tgz", 93 | "integrity": "sha512-x7uDjyz8Jx+QPbpCFCMQ8lltnQa4p4vSYHx6ADe8rVYRTdsyhCJbvSty5DAsLVmU6cGakl+r8HQYolKHxk/tiw==", 94 | "requires": { 95 | "iterall": "1.1.3" 96 | } 97 | }, 98 | "graphql-request": { 99 | "version": "1.4.1", 100 | "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-1.4.1.tgz", 101 | "integrity": "sha1-B3J0PPrI391NadNhBqlsm90ZHOg=", 102 | "requires": { 103 | "cross-fetch": "1.1.1" 104 | } 105 | }, 106 | "graphql-tools": { 107 | "version": "2.14.1", 108 | "resolved": "https://registry.npmjs.org/graphql-tools/-/graphql-tools-2.14.1.tgz", 109 | "integrity": "sha512-p5Uvg2abjdISAlFQmwq/NZFdgQzvEgaQVQwuy2Am5VrDYQxkfHXnefYWBT0yYV6weuwymx41xdfiUYSeB+SuDQ==", 110 | "requires": { 111 | "apollo-utilities": "1.0.3", 112 | "deprecated-decorator": "0.1.6", 113 | "uuid": "3.1.0" 114 | }, 115 | "dependencies": { 116 | "apollo-utilities": { 117 | "version": "1.0.3", 118 | "resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.0.3.tgz", 119 | "integrity": "sha512-wNKf0GAXfvnmZFYVl1YIzZ6LDSUe+zo4SKd2kbzi7YquNZUuSwJnG1FfEphvfwRRTI2dnpcGZEq26I7uUBrWNw==" 120 | } 121 | } 122 | }, 123 | "iconv-lite": { 124 | "version": "0.4.19", 125 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", 126 | "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" 127 | }, 128 | "is-stream": { 129 | "version": "1.1.0", 130 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", 131 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" 132 | }, 133 | "isomorphic-fetch": { 134 | "version": "2.2.1", 135 | "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", 136 | "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", 137 | "requires": { 138 | "node-fetch": "1.7.3", 139 | "whatwg-fetch": "2.0.3" 140 | } 141 | }, 142 | "iterall": { 143 | "version": "1.1.3", 144 | "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.1.3.tgz", 145 | "integrity": "sha512-Cu/kb+4HiNSejAPhSaN1VukdNTTi/r4/e+yykqjlG/IW+1gZH5b4+Bq3whDX4tvbYugta3r8KTMUiqT3fIGxuQ==" 146 | }, 147 | "node-fetch": { 148 | "version": "1.7.3", 149 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", 150 | "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==", 151 | "requires": { 152 | "encoding": "0.1.12", 153 | "is-stream": "1.1.0" 154 | } 155 | }, 156 | "source-map": { 157 | "version": "0.5.7", 158 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", 159 | "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" 160 | }, 161 | "source-map-support": { 162 | "version": "0.4.18", 163 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", 164 | "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", 165 | "requires": { 166 | "source-map": "0.5.7" 167 | } 168 | }, 169 | "uuid": { 170 | "version": "3.1.0", 171 | "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.1.0.tgz", 172 | "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==" 173 | }, 174 | "validator": { 175 | "version": "13.7.0", 176 | "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", 177 | "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==" 178 | }, 179 | "whatwg-fetch": { 180 | "version": "2.0.3", 181 | "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz", 182 | "integrity": "sha1-nITsLc9oGH/wC8ZOEnS0QhduHIQ=" 183 | }, 184 | "zen-observable-ts": { 185 | "version": "0.5.0", 186 | "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.5.0.tgz", 187 | "integrity": "sha512-8soRu9VE2HYkAAKcDegToWzu8snITcZjujnE4SXc+7IczbHXRCFkd4Cj4DgYq88nU6SwTU5lkea7KBGNa/CaFw==" 188 | } 189 | } 190 | } 191 | --------------------------------------------------------------------------------