├── 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 |
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 | [](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 |
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 |
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 |
--------------------------------------------------------------------------------