[
  {
    "path": ".gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nlib-cov\n\n# Coverage directory used by tools like istanbul\ncoverage\n\n# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)\n.grunt\n\n# node-waf configuration\n.lock-wscript\n\n# Compiled binary addons (https://nodejs.org/api/addons.html)\nbuild/Release\n\n# Dependency directory\nnode_modules\n\n# Optional npm cache directory\n.npm\n\n# Optional REPL history\n.node_repl_history\npackage-lock.json\n.nyc_output/\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\nnode_js:\n  - \"node\"\nenv:\n  global:\n    - DATABASE_URL=postgres://postgres:@localhost/codeface\n    - NODE_ENV=TEST\nservices:\n  - postgresql\nafter_success:\n  - bash <(curl -s https://codecov.io/bash)\n"
  },
  {
    "path": "FAQ.md",
    "content": "\n## Why PostgreSQL and _Not_ MySQL?\n\nThe _good_ news is that almost all of your PostgreSQL knowledge\nis _directly_ transferable to [MySQL](https://en.wikipedia.org/wiki/MySQL).\nSince _both_ use SQL as the language\nfor interacting with the database,\nthe time you invest in learning PostgreSQL\nand building SQL skills is a hugely valuable.\n\nLearning how to _run_ means you also know how to _walk_.\nPostgreSQL might _feel_ \"more difficult\"\nin the same way that , but the principals are all the same.\nJust stick with it and keep asking questions until it all \"makes sense\".\nIf you need to _apply_ your SQL skills to MySQL, MS SQL or MariaDB,\nit will only take you a few minutes to adapt to it.\n\nIt's very much like riding a bicycle.\nOnce you know how to balance, pedal and steer,\nyour skills transfer to other bicycles.\n\n\n\nThe _reason_ MySQL is still _hugely_ popular\ncan be summarised by _one_ word:\n[***WordPress***](https://en.wikipedia.org/wiki/WordPress).\n\nOver **30%** of the 10 million most popular websites use WordPress.\nWordPress runs on the \"LAMP\" (_Linux Apache **MySQL** PHP_) stack,\nwhich means that people are using MySQL by _`default`_\nnot _conscious enlightenment_.\n+ https://www.whoishostingthis.com/compare/wordpress/stats\n+ https://w3techs.com/technologies/overview/content_management/all\n\n\n## Why _Not_ Use WordPress?\n\nWordPress is _unquestionably_ a good CMS and blogging platform\nthat helps millions of people/businesses publish online.\nSadly, it's not secure by _default_ and when a vulnerability is discovered,\nit gets exploited en-mass very quickly.\nYes, WordPress can be\n[\"Hardened\"](https://codex.wordpress.org/Hardening_WordPress)\nbut that is _usually_ not the _first_ thing on people's todo list\nwhen launching a website or blog.\nThe result is that _thousands_ of WordPress websites get hacked\neach time a patch is released e.g:\nhttps://www.zdnet.com/article/thousands-of-wordpress-sites-backdoored-with-malicious-code\nand it creates a maintenance headache\nfor the person/people _responsible_ for the site.\nWe're not saying you (_or anyone else_) should not use WordPress,\njust make sure you follow the the latest \"best practice\" if you do.\n(_We have been \"burned\" by it through no fault of our own...\nand would not touch it again with a barge pole!\nThere are **much** more **secure** and **performant** options!_)\n\n### What About NoSQL Databases/Datastores Like ElasticSearch and Redis?\n\n@dwyl we are _huge_ fans of _special-purpose_ data storage/retrieval systems.\nWe have used _several_ NoSQL databases including CouchDB, ElasticSearch,\nMongoDB, Neo4J and Redis.\nOf these we _recommend_ ElasticSearch for full-text search\nand Redis for in-momory datasets and caching. see:\n\n+ [github.com/dwyl/learn-**elasticsearch**](https://github.com/dwyl/learn-elasticsearch)\n+ [github.com/dwyl/learn-**redis**](https://github.com/dwyl/learn-redis)\n\nHowever as a \"primary\" datastore with a robust query language,\nwe feel PostgreSQL is the _clear_ winner as a \"first\" database.\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n\n# Learn PostgreSQL\n\nLearn how to use PostgreSQL\nand Structured Query Language (SQL) to store\nand query your data.\n\n<br />\n\n<a href=\"https://www.postgresql.org/about/\">\n  <img src=\"https://user-images.githubusercontent.com/194400/52590350-fa116100-2e38-11e9-9303-c38819493a4e.png\" width=\"700\">\n</a>\n\n<br />\n\n[![Build Status](https://img.shields.io/travis/dwyl/learn-postgresql/master.svg?style=flat-square)](https://travis-ci.org/dwyl/learn-postgresql)\n[![codecov.io](https://img.shields.io/codecov/c/github/dwyl/learn-postgresql/master.svg?style=flat-square)](https://codecov.io/github/dwyl/learn-postgresql?branch=master)\n[![Dependencies: None!](https://david-dm.org/dwyl/learn-postgresql/status.svg?style=flat-square)](https://david-dm.org/dwyl/learn-postgresql)\n[![devDependencies Status](https://david-dm.org/dwyl/learn-postgresql/dev-status.svg?style=flat-square)](https://david-dm.org/dwyl/learn-postgresql?type=dev)\n[![contributions welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat-square)](https://github.com/dwyl/learn-postgresql/issues)\n<!-- uncomment when ready\n[![HitCount](https://hits.dwyl.io/dwyl/learn-postgresql.svg)](https://hits.dwyl.io/dwyl/learn-postgresql)\n-->\n\n</div>\n\n\n# _Why_?\n\nHelping people store, retrieve and derive insights from data\nis the essence of _all_ software applications. <br />\n\n## SQL is _Everywhere_\n\nLike it or not, Relational Databases store\n_most_ of the world's structured data\nand Structured Query Language (SQL)\nis _by far_ the most frequent way of retrieving the data.<br />\n\nAccording to the most _recent_ surveys/statistics,\nSQL _still_ dominates the world of databases.\n\nhttps://insights.stackoverflow.com/survey/2018/#technology-databases\n![stackoverflow-survey-2018-databases](https://user-images.githubusercontent.com/194400/52594468-80cb3b80-2e43-11e9-867a-eeb4eea9a322.png)\n\n\nhttps://db-engines.com/en/ranking\n![dbms-ranking](https://user-images.githubusercontent.com/194400/52594416-64c79a00-2e43-11e9-8a61-02af22554802.png)\n\n> _**Note**: you should never adopt a technology\nbased on it's **current popularity**,\nalso be ware of_\n[\"_argumentum ad populum_\"](https://en.wikipedia.org/wiki/Argumentum_ad_populum)\n(\"_it's popular therefore you should use it_\").\n_Always pick the **appropriate tool** for the job\nbased on the requirements, constraints and/or availability\n(both of \"skill\" on your existing team or in the wider community).\nWe include these stats to explain that **relational databases**\nare **still** the most widely used **by far** and so\nlearning SQL skills is a very **wise investment**\nboth as an **individual** and for your **team** or **organisation**._\n\n\n## PostgreSQL is _Easy_ to Learn and it Runs _Everywhere_!\n\nGetting started with PostgreSQL is _easy_,\n(_just follow the steps in this guide and try out the example queries!_) <br />\nWhen you are ready to _deploy_ your app, you are in safe hands,\nPostgreSQL runs _everywhere_:\n\n+ **Travis-CI** (free) Integration Testing:\nhttps://docs.travis-ci.com/user/database-setup/#postgresql\n+ **Heroku** PostgreSQL (_free for MVP: 10k rows_): https://www.heroku.com/postgres\n+ AWS RDS Postgres (_good value + high performance_):\nhttps://aws.amazon.com/rds/postgresql/\n+ Google Cloud SQL: https://cloud.google.com/sql/\n+ DigitalOcean: https://www.digitalocean.com/products/managed-databases/\n+ Linode:\nhttps://www.linode.com/docs/databases/postgresql/create-a-highly-available-postgresql-cluster-using-patroni-and-haproxy/\n+ Azure: https://azure.microsoft.com/en-us/services/postgresql/\n  + Citus: https://techcrunch.com/2019/01/24/microsoft-acquires-citus-data\n+ Self-managed high availability cluster: https://github.com/sorintlab/stolon\n\n# _Who_?\n\n_Everyone_ building _any_ application that stores data should learn SQL.\nSQL is _ubiquitous_ in every field/industry and the sooner you learn/master it,\nthe higher your life-time return on time investment.\n\nLearning how to use a relational database is a foundational skill\nfor all of computer science and application development.\n\nBeing _proficient_ in SQL will open the door to Data Science with\n[SQL-on-Hadoop](https://mapr.com/why-hadoop/sql-hadoop/sql-hadoop-details/)\n[Apache Spark](https://en.wikipedia.org/wiki/Apache_Spark#Spark_SQL),\nGoogle [BigQuery](https://en.wikipedia.org/wiki/BigQuery),\n[Oracle](https://en.wikipedia.org/wiki/Oracle_Corporation#Controversies)\nand [Teradata](https://en.wikipedia.org/wiki/Teradata).\nIn short, get _really_ good at SQL! It's _very_ useful.\n\n# _What_?\n\nThis tutorial covers 5 areas:\n\n1. _What_ is PostgreSQL?\n2. _How_ do I get _started_ with PostgreSQL? (_a fully functioning example!_)\n3. What is Structured Query Language (SQL)? (_lots of example queries!_)\n4. _How_ do I write my _own_ SQL Queries?\n5. _How_ do I deploy my own PostgreSQL-based Application?\n\nOnce you have covered these areas,\nyou will _know_ if PostgreSQL\nis \"right\" for your needs,\nor if you need to keep looking for a different way\nto store data.\n\nLet's dive in!\n\n## 1. What is PostgreSQL?\n\nPostgreSQL (_often shortened to simply \"**Postgres**\"_)\nis an advanced\n**R**elational **D**ataBase **M**anagement **S**ystem (\"RDBMS\"),\nthat lets you _efficiently_ and _securely_ store _any_ type of data.\nWe will _explain_ \"Relational Database\" in the context\nof our _example_ below,\nso don't worry if it sounds like a buzzword soup.\n\nPostgres has an emphasis on standards compliance and extensibility\nwhich means there are many plugins you can use to enhance it\nlike [PostGIS](https://postgis.net) for mapping applications\nand entire projects built on top of it like\n[TimescaleDB](https://www.timescale.com/)\n(_a time-series database perfect for analytics_)\nand [AgensGraph](https://bitnine.net/)\n(_a graph database, great for modelling networks e.g a \"social graph\"_).\n\n\nStructured Query Language (SQL)\nis the preferred means of interacting with data at any scale. <br />\n\n> The _only_ reason MySQL is still more widely used than Postgres\ncan be summarised in *one word*: **WordPress**.\n> WordPress has a firm grip on the CMS-based website market\nand it shows no sign of slowing down.\n> If your goal is to build CMS-based websites,\nor the company you _already_ work for uses\n[WordPress](https://www.cvedetails.com/vendor/2337/Wordpress.html),\nyou should go for it!\n> If you prefer a more _general_ introduction to SQL,\n> follow _this_ tutorial!\n> The knowledge you will gain by learning Postgres is 95%+\n\"_transferable_\" to other SQL databases so don't worry about\nthe differences between MySQL and Postgres for now.\nIf you're curious, read: https://hackr.io/blog/postgresql-vs-mysql\n\n\n<!-- I'm undecided on the following section ...\nI don't want to be seen to be \"bashing\" MongoDB ...\n\nUnless you work for [MongoDB](https://en.wikipedia.org/wiki/MongoDB#Security)\n(_or another [NoSQL](https://en.wikipedia.org/wiki/NoSQL) database company_)\nthe chance that you will encounter a Relational Database\nand thus benefit from knowing Structured Query Language (SQL)\nin your _career_ as a software engineer tends toward 100%.\n\nIf you are _tempted_ to use MongoDB as your _primary_ data store,\nread this:\nhttps://www.theguardian.com/info/2018/nov/30/bye-bye-mongo-hello-postgres\nMongoDB is _meant_ to be a \"document-oriented database\",\nso it should be _perfect_ for a CMS (_document content_).\nAnd yet, after years of _trying_ to make it work,\nthe _highly_ competent engineers at The Guardian\ndecided it wasn't worth the hassle and switched to PostgreSQL to _great_ result!\nRead the Hacker News thread: https://news.ycombinator.com/item?id=18717168\n\nIf you are _tempted_ by MongoDB because of the \"***MEAN***\" **stack**,\nby all means dive into trying it. We have been there and seen the appeal.\ne.g: https://github.com/dwyl/mongo-search ...\n(_We learned the \"hard way\"\nthat Searching MongoDB was slow and inconsistent,\nPostgreSQL has **much better** full-text search\nor use ElasticSearch!_)\nHowever we _urge_ you to understand\nthat the _benefit_ of getting _started_ fast\n(_because your MongoDB records don't have a pre-defined schema_)\nwill _quickly_ wear off when queries become convoluted and _slow_\n(_because there are no **real** way to do multiple \"JOINs\"\nand index optimisation is laborious_).\n\nAnother important factor\n(_for us @dwyl because we are\n  [security](https://github.com/dwyl/learn-security/)-conscious_)\nis the fact that MongoDB is _insecure_ by **`default`** see:\nhttps://en.wikipedia.org/wiki/MongoDB#Security ...\nWe cannot stand the idea of insecurely storing _anyone's_ data,\nand PostgreSQL makes it _easy_ to add advanced security, access controls\ntable-level permissions and field-level data encryption.\n\n-->\n\n# _How_?\n\n### Installation\n\nBefore you get started with using PostgreSQL, you'll have to install it.\nFollow these steps to get started:\n\n#### MacOS\n\n1. There are a couple of ways to install PostgreSQL. One of the easier ways to\nget started is with Postgres.app. Navigate to https://postgresapp.com/ and then\nclick \"Download\":\n![download](https://cloud.githubusercontent.com/assets/12450298/19641848/6d3cfa4a-99da-11e6-858f-3ff2ada026be.png)\n\n2. Once it's finished downloading, double click on the file to unzip then move\nthe PostgreSQL elephant icon into your `applications` folder. Double click the\nicon to launch the application.\n\n3. You should now see a new window launched with a list of servers to the left side of the window \n(if it's a fresh install, you should see one named `PostgreSQL XX`). \nIf it shows anything else or an error props up, make sure you don't have any other instances of Postgres on your computer and reinstall. \nTo fully reinstall follow [these steps](https://postgresapp.com/documentation/install.html) to delete data directories and preferences. \nClick on the button 'Initialize' (or 'Start' if you had already installed previously).\n<img width=\"718\" alt=\"download\" src=\"https://user-images.githubusercontent.com/17494745/195095742-e6838922-b17a-495d-b922-71f1ddfcd581.png\">\n\n4. Run `sudo mkdir -p /etc/paths.d && echo /Applications/Postgres.app/Contents/Versions/latest/bin | sudo tee /etc/paths.d/postgresapp` \n(found [here](https://postgresapp.com/documentation/install.html)) to use `psql` in the terminal. \nClose and open the terminal.\n\n5. Postgres.app will by default create a role and database that matches your current macOS username. You can connect straight away by running `psql`.\n\n6. You should then see something in your terminal that looks like this (with your macOS username in front of the prompt rather than 'postgres'):\n\n![terminal](https://cloud.githubusercontent.com/assets/12450298/19642816/f8ac0c66-99de-11e6-87e2-db55e6abc27b.png)\n\n7. You should now be all set up to start using PostgreSQL. For documentation on\ncommand line tools etc see https://postgresapp.com/documentation/\n\n#### Ubuntu\n\nDigital Ocean have got a great article on [getting started with postgres]( https://www.digitalocean.com/community/tutorials/how-to-install-and-use-postgresql-on-ubuntu-16-04). A quick summary is below.\n\n##### Installation\n\n```\nsudo apt-get update\nsudo apt-get install postgresql postgresql-contrib\n```\n\nBy default the only role created is the default 'postgres', so PostgreSQL will only respond to connections from an Ubuntu user called 'postgres'. We need to pretend to be that user and create a role matching our actual Ubuntu username:\n\n```\nsudo -u postgres createuser --interactive\n```\n\nThis command means 'run the command `createuser --interactive` as the user called \"postgres\"'.\n\nWhen asked for the name of the role enter your Ubuntu username. If you're not sure, open a new Terminal tab and run `whoami`.\n\nWhen asked if you want to make the role a superuser, type 'y'.\n\nWe now need to create the database matching the role name, as PostgreSQL expects this. Run:\n\n```\nsudo -u postgres createdb [your user name]\n```\n\nYou can now connect to PostgreSQL by running `psql`.\n\n### Create your first PostgreSQL database\n\n1. To start PostgreSQL, type this command into the terminal:  \n`psql`  \n\n2. Next type this command into the PostgreSQL interface:  \n`CREATE DATABASE test;`  \n**NOTE:** Don't forget the semi-colon. If you do, useful error messages won't\nshow up.\n\n3. To check that our database has been created, type `\\l` into the psql prompt.\nYou should see something like this in your terminal:\n![test db](https://cloud.githubusercontent.com/assets/12450298/19650613/ce278678-9a01-11e6-89ad-b124c0adcfe5.png)\n\n### Create new users for your database\n\n1. If you closed the PostgreSQL server, start it again with:  \n` psql`  \n\n2. To create a new user, type the following into the psql prompt:  \n    ```sql\n    CREATE USER testuser;\n    ```\n\n3. Check that your user has been created. Type `\\du` into the prompt. You should\nsee something like this:\n![user](https://cloud.githubusercontent.com/assets/12450298/19650852/9c340708-9a02-11e6-8f06-75f1e30a86b3.png)\nUsers can be given certain permissions to access any given database you have\ncreated.\n\n4. Next we need to give our user permissions to access the test database we\ncreated above. Enter the following command into the `psql` prompt:  \n    ```sql\n    GRANT ALL PRIVILEGES ON DATABASE test TO testuser;\n    ```\n\n\n### PostGIS - Spacial and Geographic objects for PostgreSQL\n\n#### PostGIS Installation\nIf you've installed Postgres App as in the example above, you can easily\nextend it to include PostGIS. Follow these steps to begin using PostGIS:\n\n1. Ensure that you're logged in as a user OTHER THAN `postgres`. Follow the\nsteps above to enable your default user to be able to access the `psql` prompt.\n(_[installation step 7](#installation)_)\n\n2. Type the following into the `psql` prompt to add the extension:  \n`CREATE EXTENSION postgis;`\n\n#### PostGIS Distance between two sets of coordinates\n\nAfter you've extended PostgreSQL with PostGIS you can begin to use it. Type\nthe following command into the `psql` command line:  \n\n```sql\nSELECT ST_Distance(gg1, gg2) As spheroid_dist\nFROM (SELECT\n\tST_GeogFromText('SRID=4326;POINT(-72.1235 42.3521)') As gg1,\n\tST_GeogFromText('SRID=4326;POINT(-72.1235 43.1111)') As gg2\n\t) As foo  ;\n```\n\nThis should return `spheroid_dist` along with a value in meters. The\nexample above returns: `84315.42034614` which is rougly 84.3km between the two\npoints.\n\n### Commands\nOnce you are serving the database from your computer\n\n- To change db\n`\\connect database_name;`\n\n- To see the tables in the database\n`\\d;`\n\n- To select (and show in terminal) all tables\n`SELECT * FROM table_name`\n\n\n- To make a table\n`CREATE TABLE table_name (col_name1, col_name2)`\n\n- To add a row\n`INSERT INTO table_name ( col_name )\nVALUES ( col_value)`\ncol_name only require if only some of the cols are being filled out\n\n- To edit a column to a table \n`ALTER TABLE table_name\n  ALTER COLUMN column_name SET DEFAULT expression`\n\n- To add a column to a table \n`ALTER TABLE table_name\n  ADD COLUMN column_name data_type`\n\n- To find the number of instances where the word “Day” is present in the title of a table\n`SELECT count(title) FROM table_name WHERE title LIKE '%Day%’;`\n\n- To delete a row in a table\n`DELETE FROM table_name\n  WHERE column_name = ‘hello';`\n\n\nPostgresql follows the SQL convention of calling relations TABLES, attributes COLUMNs and tuples ROWS\n\n**Transaction**\nAll or nothing, if something fails the other commands are rolled back like nothing happened\n\n**Reference**\nWhen a table is being created you can reference a column in another table to make sure any value which is added to that column exists in the referenced table.\n\n```sql\nCREATE TABLE cities (\n  name text NOT NULL,\n  postal_code varchar(9) CHECK (postal_code <> ''),\n  country_code char(2) REFERENCES countries,\n  PRIMARY KEY (country_code, postal_code)\n);\n```\n\n`<>` means not equal\n\n\n**Join reads**\nYou can join tables together when reading them,\n\n**Inner Join**\nJoins together two tables by specifying a column in each to join them by i.e.\n\n```sql\nSELECT cities.*, country_name\n  FROM cities INNER JOIN countries\n  ON cities.country_code = countries.country_code;\n```\n\nThis will select all of the columns in both the countries\nand cities tables the data, the rows are matched up by `country_code`.\n\n**Grouping**\nYou can put rows into groups where the group is defined by a shared value in a particular column.\n\n```sql\nSELECT venue_id, count(*)\n  FROM events\n  GROUP BY venue_id;\n```\n\nThis will group the rows together by the venue_id,\ncount is then performed on each of the groups.\n\n### Learning Resources\n\n+ Node-hero: https://blog.risingstack.com/node-js-database-tutorial\n+ Pluralsight postgres getting started:\n  https://www.pluralsight.com/courses/postgresql-getting-started\n+ Tech Republic Postgres setup:\n  https://www.techrepublic.com/article/diy-a-postgresql-database-server-setup-anyone-can-handle/\n+ PostGIS installation: https://postgis.net/install\n+ PostGIS docs: https://postgis.net/docs/manual-2.3\n+ SQl Tutorials: https://www.scaler.com/topics/sql/\n+ PostGIS ST_Distance: https://postgis.net/docs/ST_Distance.html\n+ Foreign Key Constraints:\n  + https://en.wikipedia.org/wiki/Foreign_key\n  + https://www.postgresqltutorial.com/postgresql-tutorial/postgresql-foreign-key/\n  + https://tableplus.io/blog/2018/08/postgresql-how-to-add-a-foreign-key.html\n+ Graphical Interface (GUI) tools:\nhttps://wiki.postgresql.org/wiki/Community_Guide_to_PostgreSQL_GUI_Tools\n"
  },
  {
    "path": "client/index.html",
    "content": "<html>\n  <head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n    <title>Time MVP (Title Gets Over-ridden with Clock ;-)</title>\n    <link rel=\"shortcut icon\" type=\"image/png\"\n    href=\"https://cloud.githubusercontent.com/assets/194400/25605640/15c23162-2f04-11e7-8371-228cf5bf61e2.png\"/>\n    <link rel=\"stylesheet\" href=\"https://unpkg.com/tachyons@4.6.1/css/tachyons.min.css\"/>\n    <link rel=\"stylesheet\" href=\"//maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css\">\n  </head>\n\n  <body>\n    <main id=\"main\">\n      <h1>Hello World 123!</h1>\n    </main>\n\n  <script src='https://rawgit.com/dwyl/faster/master/lib/client.js'></script>\n  </body>\n</html>\n"
  },
  {
    "path": "install.md",
    "content": "# DBeaver\n\nSee: https://github.com/dwyl/learn-postgresql/issues/43#issuecomment-469000357\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"learn-postgresql\",\n  \"version\": \"1.0.0\",\n  \"description\": \"PostgreSQL Tutorial\",\n  \"main\": \"index.js\",\n  \"directories\": {\n    \"test\": \"test\"\n  },\n  \"scripts\": {\n    \"drop\": \"psql -U postgres -c 'DROP DATABASE IF EXISTS codeface;'\",\n    \"create\": \"psql -U postgres -c 'CREATE DATABASE codeface;'\",\n    \"schema\": \"psql -U postgres -d codeface -a -f schema.sql\",\n    \"recreate\": \"npm run drop && npm run create && npm run schema\",\n    \"test\": \"nyc tap ./test/*.test.js | tap-nyc\",\n    \"quick\": \"tap ./test/*.test.js\",\n    \"start\": \"node server/server.js\",\n    \"faster\": \"./node_modules/faster/bin/faster.js\",\n    \"postinstall\": \"npm run recreate\",\n    \"open-cov\": \"open ./coverage/lcov-report/index.html\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/dwyl/learn-postgresql.git\"\n  },\n  \"keywords\": [\n    \"PostgreSQL\",\n    \"Postgres\",\n    \"Beginners\",\n    \"Tutorial\"\n  ],\n  \"author\": \"dwyl & friends\",\n  \"license\": \"GPL-2.0\",\n  \"bugs\": {\n    \"url\": \"https://github.com/dwyl/learn-postgresql/issues\"\n  },\n  \"homepage\": \"https://github.com/dwyl/learn-postgresql#readme\",\n  \"dependencies\": {\n    \"github-scraper\": \"^6.7.0\",\n    \"pg\": \"^7.8.1\"\n  },\n  \"devDependencies\": {\n    \"faster\": \"^3.5.1\",\n    \"nyc\": \"^13.1.0\",\n    \"supertest\": \"^4.0.2\",\n    \"tap\": \"^12.6.1\",\n    \"tap-nyc\": \"^1.0.3\"\n  },\n  \"nyc\": {\n    \"check-coverage\": true,\n    \"lines\": 100,\n    \"statements\": 100,\n    \"functions\": 100,\n    \"branches\": 100,\n    \"include\": [\n      \"server/*.js\"\n    ],\n    \"exclude\": [\n      \"test/*.test.js\"\n    ],\n    \"reporter\": [\n      \"lcov\",\n      \"text-summary\"\n    ],\n    \"cacheDirectories\": [\n      \"node_modules\"\n    ],\n    \"all\": true,\n    \"report-dir\": \"./coverage\"\n  }\n}\n"
  },
  {
    "path": "query.sql",
    "content": "SELECT\n next_page,\n COUNT (next_page) AS c\nFROM\n logs\nWHERE next_page IS NOT null\nAND next_page NOT IN (\n    SELECT path\n    FROM logs\n    WHERE path IS NOT NULL\n)\nGROUP BY\n next_page\nORDER BY\n c ASC\nLIMIT 1;\n"
  },
  {
    "path": "schema.sql",
    "content": "CREATE TABLE IF NOT EXISTS \"people\" (\n  \"inserted_at\" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n\t\"id\" SERIAL PRIMARY KEY,\n\t\"name\" VARCHAR(50) DEFAULT NULL,\n\t\"username\" VARCHAR(50) NOT NULL,\n\t\"bio\" VARCHAR(255) DEFAULT NULL,\n\t\"worksfor\" VARCHAR(50) DEFAULT NULL,\n\t\"uid\" INT NOT NULL, -- the person's GitHub uid e.g: 4185328\n\t\"location\" VARCHAR(100) DEFAULT NULL,\n\t\"website\" VARCHAR(255) DEFAULT NULL,\n\t\"stars\" INT DEFAULT 0,\n\t\"followers\" INT DEFAULT 0,\n\t\"following\" INT DEFAULT 0,\n\t\"contribs\" INT DEFAULT 0,\n\t\"recent_activity\" INT DEFAULT 0\n);\n\nCREATE TABLE IF NOT EXISTS \"orgs\" (\n\t\"inserted_at\" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n\t\"id\" SERIAL PRIMARY KEY,\n\t\"name\" VARCHAR(50) DEFAULT NULL,\n\t\"url\" VARCHAR(50),\n\t\"description\" VARCHAR(255) DEFAULT NULL,\n\t\"location\" VARCHAR(50) DEFAULT NULL,\n\t\"website\" VARCHAR(255) DEFAULT NULL,\n\t\"email\" VARCHAR(255) DEFAULT NULL,\n\t\"pcount\" INT DEFAULT 0,\n\t\"uid\" INT NOT NULL\n);\n\nCREATE TABLE IF NOT EXISTS \"repos\" (\n\t\"inserted_at\" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n\t\"id\" SERIAL PRIMARY KEY,\n\t\"url\" VARCHAR(255) NOT NULL, -- know what the char limit is for a repo name?\n\t\"description\" TEXT DEFAULT NULL,\n\t\"website\" VARCHAR(255) DEFAULT NULL,\n\t\"watchers\" INT DEFAULT 0,\n\t\"stars\" INT DEFAULT 0,\n\t\"forks\" INT DEFAULT 0,\n\t\"commits\" INT DEFAULT 0,\n\t\"langs\" VARCHAR(255) DEFAULT NULL,\n\t\"tags\" TEXT DEFAULT NULL,\n\t\"person_id\" INT REFERENCES people (id), -- can be NULL if repo belongs to org.\n\t\"org_id\" INT REFERENCES orgs (id) -- this can be NULL if repo is personal.\n);\n\nCREATE TABLE IF NOT EXISTS \"logs\" (\n\t\"id\" SERIAL PRIMARY KEY,\n\t\"inserted_at\" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n\t\"url\" VARCHAR(255) NOT NULL,\n\t\"next_page\" VARCHAR(255) DEFAULT NULL\n);\n\nCREATE TABLE IF NOT EXISTS \"relationships\" (\n\t\"inserted_at\" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n\t\"id\" SERIAL PRIMARY KEY,\n\t\"person_id\" INT REFERENCES people (id) DEFAULT NULL,\n\t\"leader_id\" INT REFERENCES people (id) DEFAULT NULL,\n\t\"org_id\" INT REFERENCES orgs (id) DEFAULT NULL,\n\t\"repo_id\" INT REFERENCES repos (id) DEFAULT NULL\n);\n"
  },
  {
    "path": "server/bot.js",
    "content": "const db = require('./db');\nconst utils = require('./utils');\nconst gs = require('github-scraper');\n\nfunction fetch (path, callback) {\n  gs(path, function(error, data) {\n    if (error) { // don't bother trying to save data if an error occurred\n      utils.log_error(error, data, new Error().stack); // get exact stack trace.\n      return utils.exec_cb(callback, error, data);\n    }\n    console.log('data.type:', data.type);\n    switch (data.type) {\n      case 'org':\n        db.insert_org(data, callback);\n        break;\n      case 'profile':\n        db.insert_person(data, callback);\n        break;\n      case 'repo':\n        db.insert_repo(data, callback);\n        break;\n      case 'followers': // multiple cases same outcome.\n      case 'following':\n      case 'people':\n      case 'stars':\n        fetch_list_of_profiles_slowly(data, callback);\n        break;\n    }\n  });\n}\n\n/**\n * fetch_list_of_profiles_slowly does what it's name suggests.\n * attempting to fetch GitHub profiles too quickly results in errors.\n * @param {object} data - should contain url and entries (a list of people).\n * @param {function} next - the function executed once profiles are saved.\n * @param {function} callback - the callback function to be executed if any.\n */\nfunction fetch_list_of_profiles_slowly (data, callback) {\n  const len = data.entries.length;\n\n  data.entries.forEach((u, i) => { // poor person's \"async parallel\":\n\n    setTimeout(function delayed_request () { // delay requests to avoid errors\n\n      gs(u.username, function process (error, profile) {\n        utils.log_error(error, profile, new Error().stack);\n\n        db.insert_person(profile, function (err2, data2) {\n\n          if (i == len - 1) { // only insert relationships once people records\n            return db.insert_relationships(data, callback); // once per batch.\n          }  // e.g: db.insert_stars(data, callback) in the case of 'stars' page\n        });\n      });\n    }, i * 1000); // timer gets longer as i increases to avoid flooding!\n  });\n}\n\nmodule.exports = {\n  fetch: fetch\n}\n"
  },
  {
    "path": "server/db.js",
    "content": "/* istanbul ignore next */\nprocess.env.DATABASE_URL = process.env.DATABASE_URL\n  || \"postgres://postgres:@localhost/codeface\";\nconst pg = require('pg');\nconst PG_CLIENT = new pg.Client(process.env.DATABASE_URL);\nconst utils = require('./utils');\n\nconsole.log('db.js:L7: PG_CLIENT._connecting:', PG_CLIENT._connecting, // debug\n  '| PG_CLIENT._connected:', PG_CLIENT._connected);\n\n// auto-start pg connection when module is required so startup is faster!\nconnect(function (err, data) {\n  console.log('db.js:L12: PG_CLIENT._connected:', PG_CLIENT._connected);\n  console.log('- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -');\n})\n\n/**\n * connnect ensures that a postgres connection is available before continuing\n * @param {function} callback - function called once connection is confirmed.\n */\nfunction connect (callback) {\n  // console.log('L45: PG_CLIENT._connecting:', PG_CLIENT._connecting,\n  //   '| PG_CLIENT._connected:', PG_CLIENT._connected);\n  if (PG_CLIENT && !PG_CLIENT._connected && !PG_CLIENT._connecting) {\n    PG_CLIENT.connect(function (error, data) {\n      utils.log_error(error, data, new Error().stack);\n      return utils.exec_cb(callback, error, PG_CLIENT);\n    });\n  } else {\n    return utils.exec_cb(callback, null, PG_CLIENT);\n  }\n}\n\n/**\n * end used in testing to end/close the Postgres connection:\n * @param {function} callback - callback function to be executed on success.\n */\nfunction end (callback) {\n  /* istanbul ignore else */\n  if(PG_CLIENT && PG_CLIENT._connected && !PG_CLIENT._connecting) {\n    PG_CLIENT.end(() => {\n      return utils.exec_cb (callback, null, PG_CLIENT);\n    });\n  }\n}\n\n/**\n * insert_person saves a person's data to the people table.\n * @param {object} data - a valid JSON object containing data to be inserted.\n * @param {function} callback - callback function to be executed on success.\n */\nfunction insert_person (data, callback) {\n  connect( function insert_person_after_connected () {\n    const { name, username, bio, worksfor, location, website, uid,\n      stars, followers, following, contribs } = data;\n    const recent_activity = utils.recent_activity(data);\n    const query = `INSERT INTO people (name, username, bio, worksfor, location,\n      website, uid, stars, followers, following, contribs, recent_activity)\n      VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`;\n    const values = [name, username, bio, worksfor, location, website, uid,\n      stars, followers, following, contribs, recent_activity];\n\n    PG_CLIENT.query(query, values, function(error, result) {\n      utils.log_error(error, data, new Error().stack);\n      return insert_next_page (data, callback);\n    });\n  });\n}\n\n/**\n * select_person gets the person from people table.\n * @param {string} username - username of the person e.g: 'iteles'\n * @param {function} callback - callback function to be executed on success.\n */\nfunction select_person (username, callback) {\n  connect( function select_person_after_connected () {\n    const query = `SELECT * FROM people WHERE username = $1\n      ORDER BY id ASC LIMIT 1`;\n    PG_CLIENT.query(query, [username], function(error, result) {\n      utils.log_error(error, result, new Error().stack);\n      return utils.exec_cb(callback, error, result);\n    });\n  });\n}\n\n/**\n * insert_org saves an org's data to the orgs table.\n *\n */\nfunction insert_org (data, callback) {\n  connect( function insert_org_after_connected () {\n    const { url,name,description,location,website,email,pcount,uid } = data;\n    const query = `INSERT INTO orgs\n    (url, name, description, location, website, email, pcount, uid)\n    VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`;\n    const values = [url,name,description,location,website,email,pcount,uid];\n\n    PG_CLIENT.query(query, values, function(error, result) {\n      utils.log_error(error, data, new Error().stack);\n      return insert_next_page (data, callback);\n    });\n  });\n}\n\n/**\n * select_org retrieves the org for a given url.\n * @param {string} url - url of the repo (e.g: /dwyl)\n * @param {function} callback - callback function to be executed on success.\n */\nfunction select_org (url, callback) {\n  connect( function select_repo_after_connected () {\n    const query = `SELECT * FROM orgs WHERE url = $1 ORDER BY id ASC LIMIT 1`;\n    console.log(query, url);\n    PG_CLIENT.query(query, [url], function(error, result) {\n      utils.log_error(error, result, new Error().stack);\n      return utils.exec_cb(callback, error, result);\n    });\n  });\n}\n\n/**\n * insert_repo saves an repo's stats to the repos table.\n * @param {object} data - a valid JSON object containing data to be inserted.\n * @param {function} callback - callback function to be executed on success.\n */\nfunction insert_repo (data, callback) {\n  connect( function insert_repo_after_connected () {\n    const { url, description, website, tags, langs,\n      watchers, stars, forks, commits} = data;\n    const query = `INSERT INTO repos\n    (url, description, website, tags, langs, watchers, stars, forks, commits)\n    VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`;\n    const values = [url, description, website, tags, langs.join(','),\n      watchers, stars, forks, commits];\n\n    PG_CLIENT.query(query, values, function(error, result) {\n      utils.log_error(error, data, new Error().stack);\n      return insert_next_page (data, callback);\n    });\n  });\n}\n\n/**\n * select_repo retrieves the repo for a given url.\n * @param {string} url - url of the repo (e.g: /dwyl/start-here)\n * @param {function} callback - callback function to be executed on success.\n */\nfunction select_repo (url, callback) {\n  connect( function select_repo_after_connected () {\n    const query = `SELECT * FROM repos WHERE url = $1 ORDER BY id ASC LIMIT 1`;\n    url = url.replace('/stargazers', '');\n    PG_CLIENT.query(query, [url], function(error, result) {\n      utils.log_error(error, result, new Error().stack);\n      return utils.exec_cb(callback, error, result);\n    });\n  });\n}\n\n/**\n * insert_relationship saves the list of people who related to another record.\n * @param {object} data - a valid JSON object containing data to be inserted.\n * @param {function} callback - callback function to be executed on success.\n */\nfunction insert_relationships (data, callback) {\n  let fields, rel_id, url, username;\n  const len = data.entries.length - 1;\n\n  function insert_rows () { // inner function has access to outer variables\n    data.entries.forEach((p, i) => { // poor person's \"async parallel\":\n      const username = p.username;\n      // console.log('username:', username);\n      select_person(username, function(error1, result1) {\n        // console.log('L251 > result1: ', result1.rows[0]);\n        const person_id = result1.rows[0].id;\n        const query = `INSERT INTO relationships (${fields}) VALUES ($1, $2)`\n        const values = [person_id, rel_id];\n        // console.log('query:', query, 'values:', values);\n        PG_CLIENT.query(query, values, function(error2, result2) {\n          utils.log_error(error2, result2, new Error().stack);\n\n          if(i === len) {\n            return insert_next_page(data, callback);\n          }\n        });\n      });\n    }); // END data.entries.forEach\n  }\n  // there are three types of relationships, we switch based on data.type\n  switch (data.type) {\n    case 'stars':\n      fields = 'person_id, repo_id';\n      select_repo(data.url, function (error, result) {\n        rel_id = result.rows[0].id;\n        insert_rows();\n      }); // END select_repo\n      break;\n    case 'people': // this is a list of members of an organisation\n      fields = 'person_id, org_id';\n      url =  '/' + data.url.split('/')[2];// /orgs/dwyl/people > /dwyl\n      select_org(url, function (error, result) {\n        rel_id = result.rows[0].id;\n        insert_rows();\n      }); // END select_org\n      break;\n    case 'followers': // this is a list of followers/following\n      fields = 'person_id, leader_id';\n      username =  data.url.split('/')[1]; // /dwylbot/followers > dwylbot\n      // console.log('username', username);\n      // list of followers:\n      select_person(username, function (error, result) {\n        rel_id = result.rows[0].id;\n        insert_rows();\n      }); // END select_org\n      break;\n    case 'following': // pay attention to the subtle difference in fields order\n      fields = 'leader_id, person_id';\n      username =  data.url.split('/')[1]; // /dwylbot/following > dwylbot\n      // console.log('username', username);\n      // list of followers:\n      select_person(username, function (error, result) {\n        rel_id = result.rows[0].id;\n        insert_rows();\n      }); // END select_org\n      break;\n  }\n}\n\n/**\n * insert_log_item does exactly what it's name suggests inserts a log enty\n * @param {String} url - the current url (page) being viewed.\n * @param {String} next_page - the next page to be fetched.\n * @param {function} callback - callback function to be executed on success.\n */\nfunction insert_log_item (url, next_page, callback) {\n  connect( function () {\n    const query = `INSERT INTO logs (url, next_page) VALUES ($1, $2)`;\n    const values = [url, next_page]\n    PG_CLIENT.query(query, values, function(error, data) {\n      utils.log_error(error, data, new Error().stack);\n      return utils.exec_cb(callback, error, data);\n    });\n  });\n}\n\nfunction profile_next_page(urls, username) {\n  urls.push(username + '/followers');\n  urls.push(username + '/following');\n  urls.push(username + '?tab=repositories');\n  return urls;\n}\n\n/**\n * insert_next_page inserts the list of next pages to be crawled.\n * @param {Object} data - a valid JSON object containing data to be inserted.\n * @param {function} callback - callback function to be executed on success.\n */\nfunction insert_next_page (data, callback) {\n  let urls = []\n  switch (data.type) {\n    case 'org':\n      // console.log('data.name', data.name);\n      urls = data.entries.map((e) => e.url);\n      urls.push('orgs/' + data.name + '/people'); // list of PUBLIC org members.\n      urls.push(data.next_page); // if it exists.\n      break;\n    case 'profile':\n      urls = data.pinned.map((e) => e.url);\n      const orgs = Object.keys(data.orgs);\n      orgs.forEach(org => urls.push(org));\n      urls = profile_next_page(urls, data.username);\n      break;\n    case 'repo':\n      urls.push(data.url + '/stargazers');\n      break;\n    case 'followers':\n    case 'following':\n    case 'people':\n    case 'stars':\n      urls.push(data.next_page);\n      data.entries.forEach((e) => { urls = profile_next_page(urls, e.username)})\n      break;\n  }\n  let len = urls.length;\n  urls.filter((e) => e !== null) // filter out blanks (if next_page is null)\n  .forEach((next_page, i) => { // poor person's \"async parallel\":\n    insert_log_item(data.url, next_page, (error, data2) => {\n      if(--len == 0) {\n        return utils.exec_cb(callback, null, data);\n      }\n    })\n  });\n}\n\n/**\n * select_next_page get the next url (page) to crawl\n */\nfunction select_next_page (callback) {\n  connect( function () {\n    const query = `SELECT next_page, COUNT (next_page) AS c\n    FROM logs\n    WHERE next_page IS NOT null\n    AND next_page NOT IN (\n      SELECT url\n      FROM logs\n      WHERE url IS NOT NULL\n    )\n    GROUP BY next_page\n    ORDER BY c ASC\n    LIMIT 1;`;\n    // console.log('L82: query:', query);\n    PG_CLIENT.query(query, function(error, data) {\n      utils.log_error(error, data, new Error().stack);\n      return utils.exec_cb(callback, error, data);\n    });\n  });\n}\n\nmodule.exports = {\n  connect: connect,\n  end: end,\n  insert_log_item: insert_log_item,\n  select_next_page: select_next_page,\n  insert_person: insert_person,\n  select_person: select_person,\n  insert_org: insert_org,\n  select_org: select_org,\n  insert_repo: insert_repo,\n  select_repo: select_repo,\n  insert_relationships: insert_relationships,\n  PG_CLIENT: PG_CLIENT\n}\n"
  },
  {
    "path": "server/lanip.js",
    "content": "// see: https://stackoverflow.com/questions/10750303\nvar os = require('os');\nvar interfaces = os.networkInterfaces();\nvar ip = [];\nfor (var k in interfaces) {\n  for (var k2 in interfaces[k]) {\n    var address = interfaces[k][k2];\n    if (address.family === 'IPv4' && !address.internal) {\n      ip.push(address.address);\n    }\n  }\n}\nmodule.exports = ip[0];\n"
  },
  {
    "path": "server/request_handlers.js",
    "content": "var fs = require('fs');\nvar path = require('path');\n// var db = require('./db.js');\nvar index = path.resolve(__dirname, '../client/index.html');\nvar app = path.resolve(__dirname, '../client/app.js');\n\nfunction serve_index(req, res) {\n  return fs.readFile(index, function (err, data) {\n    res.writeHead(200, {'Content-Type': 'text/html'});\n    res.end(data);\n  });\n}\n\n// function serve_app(req, res) {\n//   return fs.readFile(app, function (err, data) {\n//     res.writeHead(200, {'Content-Type': 'text/javascript'});\n//     res.end(data);\n//   });\n// }\n\nmodule.exports = {\n  serve_index: serve_index,\n  // serve_app: serve_app,\n  // serve_static: serve_static,\n  // handle_post: handle_post,\n  // handle_email_verification_request: handle_email_verification_request\n}\n"
  },
  {
    "path": "server/server.js",
    "content": "process.env.PORT = process.env.PORT || 4000;\nconst http = require('http');\nconst handlers = require('./request_handlers.js');\n\nconst server = http.createServer(function run (req, res) { // can you make simplify it? ;-)\n  console.log(req.method, ':', req.url);    // absolute minimum request logging\n  var url = req.url.split('?')[0];          // strip query params for routing\n  switch (url) {\n    // case '/elmo.js':                        // not \"DRY\" ... #helpwanted!\n    //   handlers.serve_static(req, res);\n    //   break;\n    // case '/app.js':                         // serve the client application\n    //   handlers.serve_app(req, res);\n    //   break;\n    // case '/save':                           // save state to server\n    //   handlers.handle_post(req, res);\n    //   break;\n    default:                                // serve the application\n      handlers.serve_index(req, res);\n      break;\n  }\n}).listen(process.env.PORT); // start the server with the command: npm run dev\n\n// url used in tests:\nserver.url = \"http://\" + require('./lanip.js') + \":\" + process.env.PORT;\n// show local LAN IP address in console so we can connect to the app on mobile:\nconsole.info(\"GOTO:\", server.url);\n\nmodule.exports = server;\n"
  },
  {
    "path": "server/utils.js",
    "content": "const BG = '\\x1b[44m\\x1b[33m\\x1b[1m';\nconst RESET = '\\x1b[0m'; // see: https://stackoverflow.com/a/41407246/1148249\n\n/**\n * log_error is a basic error logger function which logs errors when present.\n * the beauty of centralising error logging in your apps is that it makes it\n * easy to change the logging to use a logging *service* or tool later\n * without having to change all instances of your logger.\n * @param {Object|String} error - the error reported\n * @param {Object} data - any data being passed back to the calling function.\n * @param {String} stack - the call stack where log_error was called from.\n * @example\n * utils.log_error(error, data, new Error().stack); //\n */\nfunction log_error (error, data, stack) {\n  stack = stack || 'remember to include `stack` (third param) in log_error!'\n  if (error) {\n    console.error(\n      BG,\n      'ERROR:', error, stack.toString(),\n      RESET\n    ); // .split('\\n')[1]\n  }\n  return;\n}\n\n/**\n * exec_cb runs a callback if it's a function avoids type error if not a func.\n * @param {function} callback - the callback function to be executed if any.\n * @param {Object|String} error - the error reported\n * @param {Object} data - any data being passed back to the calling function.\n */\nfunction exec_cb (callback, error, data) {\n  log_error(error, data, new Error().stack);\n  if (callback && typeof callback === 'function') {\n    return callback(error, data);\n  } // if callback is undefine or not a function do nothing!\n  return;\n}\n\n/**\n * recent_activity returns the count of recent activity for a profile\n */\nfunction recent_activity(json) {\n  const DAYS = 14;\n  const keys = Object.keys(json[\"contrib_matrix\"]);\n  const len = keys.length - 1;\n  const latest_contribs = keys.slice(- DAYS);\n  return latest_contribs.reduce((sum, k) => {\n    return sum + json[\"contrib_matrix\"][k]['count']\n  }, 0);\n}\n\nmodule.exports = {\n  log_error: log_error,\n  exec_cb: exec_cb,\n  recent_activity: recent_activity\n}\n"
  },
  {
    "path": "test/bot.test.js",
    "content": "const tap = require('tap');\nconst bot = require('../server/bot');\nconst db = require('../server/db');\nconst seed = Math.floor(Math.random() * Math.floor(100000));\n\ntap.test('crawl non-existent page to test 404', function (t) {\n  bot.fetch('/totesamaze' + seed, function(err, data) {\n    t.equal(err, 404, 'err: ' + err + ' (as expected ;-)');\n    t.end()\n  });\n});\n\ntap.test('crawl @dwyl org', function (t) {\n  // we must TRUNCATE the orgs table when running tests:\n  db.PG_CLIENT.query('TRUNCATE TABLE orgs CASCADE', function (err0, result0) {\n    t.equal(err0, null, 'no error running \"TRUNCATE TABLE orgs\"');\n    t.equal(result0.command, 'TRUNCATE', 'orgs table successfully truncated');\n\n    bot.fetch('dwyl', function(err, data) {\n      require('./fixtures/make-fixture')('org.json', data); // keep up-to-date\n      t.end();\n    });\n  });\n});\n\ntap.test('crawl @iteles person profile', function (t) {\n  db.PG_CLIENT.query('TRUNCATE TABLE people CASCADE', function (err0, result0) {\n    t.equal(err0, null, 'no error running \"TRUNCATE TABLE people\"');\n    t.equal(result0.command, 'TRUNCATE', 'people table successfully truncated');\n\n    bot.fetch('iteles', function(err, data) {\n      // delete(data.contrib_matrix); // TMI!\n      require('./fixtures/make-fixture')('person.json', data);\n      t.end()\n    });\n\n  }); // end TRUNCATE\n});\n\ntap.test('crawl dwyl/todo-list-javascript-tutorial', function (t) {\n  db.PG_CLIENT.query('TRUNCATE TABLE repos CASCADE', function (err0, result0) {\n    t.equal(err0, null, 'no error running \"TRUNCATE TABLE repos\"');\n    t.equal(result0.command, 'TRUNCATE', 'repos table successfully truncated');\n\n    bot.fetch('dwyl/todo-list-javascript-tutorial', function(err, data) {\n      require('./fixtures/make-fixture')('repo.json', data);\n      t.end()\n    });\n  }); // end TRUNCATE\n});\n\ntap.test('crawl dwyl/health', function (t) {\n  // db.PG_CLIENT.query('TRUNCATE TABLE repos CASCADE', function (err0, result0) {\n    // t.equal(err0, null, 'no error running \"TRUNCATE TABLE repos\"');\n    // t.equal(result0.command, 'TRUNCATE', 'repos table successfully truncated');\n\n    bot.fetch('dwyl/health', function(err, data) {\n      require('./fixtures/make-fixture')('repo.json', data);\n\n      const select = 'SELECT * FROM repos ORDER by id DESC LIMIT 1';\n      db.PG_CLIENT.query(select, function(err, result) {\n        t.equal(result.rows[0].url, data.url, 'repo.url ' + data.url);\n        t.end();\n      });\n    });\n  // }); // end TRUNCATE\n});\n\ntap.test('crawl /dwyl/health/stargazers', function (t) {\n  bot.fetch('/dwyl/health/stargazers', function(err, data) {\n    require('./fixtures/make-fixture')('stargazers.json', data);\n    t.end()\n  });\n});\n\ntap.test('crawl org members /orgs/SafeLives/people (3?)', function (t) {\n  bot.fetch('/SafeLives', function(err1, data1) { // first store the org\n    bot.fetch('/orgs/SafeLives/people', function(err, data) {\n      require('./fixtures/make-fixture')('members.json', data);\n      // console.log(data);\n      t.equal(data.entries.length, 3, '/orgs/SafeLives/people has 3 people.');\n      t.end()\n    });\n  });\n});\n\ntap.test('crawl /dwylbot/followers (expect 1)', function (t) {\n  bot.fetch('/dwylbot', function(err1, data1) { // first fetch the profile\n    bot.fetch('/dwylbot/followers', function(err, data) {\n      require('./fixtures/make-fixture')('followers.json', data);\n      // console.log(data);\n      t.equal(data.entries.length, 4, '/dwylbot/following is following Simon.');\n      t.end()\n    });\n  });\n});\n\n\ntap.test('crawl /dwylbot/following (expect 1)', function (t) {\n  bot.fetch('/dwylbot', function(err1, data1) { // first fetch the profile\n    bot.fetch('/dwylbot/following', function(err, data) {\n      require('./fixtures/make-fixture')('following.json', data);\n      // console.log(data);\n      t.equal(data.entries.length, 1, '/dwylbot/following is following Simon.');\n      t.end()\n    });\n  });\n});\n\ntap.test('db.end() close database connection so tests can finish', function(t) {\n  db.end(function(err, data) {\n    t.equal(db.PG_CLIENT._ending, true,\n        'db.PG_CLIENT._ending: ' + db.PG_CLIENT._ending);\n    t.end();\n  });\n});\n"
  },
  {
    "path": "test/db.test.js",
    "content": "process.env.DATABASE_URL = process.env.DATABASE_URL\n  || \"postgres://postgres:@localhost/codeface\";\n\nconst tap = require('tap'); // see: github.com/dwyl/learn-tape\nconst db = require('../server/db');\n\nconst seed = Math.floor(Math.random() * Math.floor(100000));\nconst url = '/dwyl';\n\ntap.test('db.select_next_page selects next_page to be viewed', function(t) {\n  db.PG_CLIENT.query('TRUNCATE TABLE logs', function (err0, result0) {\n    t.equal(err0, null, 'no error running \"TRUNCATE TABLE logs\"');\n    t.equal(result0.command, 'TRUNCATE', 'logs table successfully truncated');\n\n    db.insert_log_item(url, url + seed, function (err, result) {\n      const select = 'SELECT * FROM logs ORDER by id DESC LIMIT 1';\n      db.PG_CLIENT.query(select, function(err, result) {\n        // console.log(result);\n        t.equal(result.rows[0].url, url, 'logs.url is ' + url);\n        t.end();\n      });\n    });\n  });\n});\n\ntap.test('db.select_next_page selects next_page to be viewed', function(t) {\n  db.select_next_page(function (err, result) {\n    t.equal(result.rows[0].next_page, url + seed,\n      'next_page is: ' + result.rows[0].next_page);\n    t.end();\n  });\n});\n\n\ntap.test('insert_person insert test/fixtures/person.json data', function(t) {\n  const person = require('./fixtures/person.json');\n  db.insert_person(person, function (err, result) {\n    db.select_person(person.username, function(err, result) {\n      t.equal(result.rows[0].name, person.name, 'person.name ' + person.name);\n      t.end();\n    });\n  });\n});\n\ntap.test('insert_org', function(t) {\n  const org = require('./fixtures/org.json');\n  // given that we have a uniqueness constraint on the name and uid fields\n  // we must TRUNCATE the orgs table when running tests:\n  db.PG_CLIENT.query('TRUNCATE TABLE orgs CASCADE', function (err2, result2) {\n\n    db.insert_org(org, function (err, result) {\n      const select = 'SELECT * FROM orgs ORDER by id DESC LIMIT 1';\n      db.PG_CLIENT.query(select, function(err, result) {\n        t.equal(result.rows[0].uid, org.uid, 'org.uid ' + org.uid);\n        t.equal(result.rows[0].name, org.name, 'org.name ' + org.name);\n        t.end();\n      });\n    });\n  });\n});\n\ntap.test('select_repo', function(t) {\n  const repo = require('./fixtures/repo.json');\n  db.insert_repo(repo, function (err, result) {\n    db.select_repo(repo.url, function (err1, result1) {\n      t.equal(result1.rows[0].url, repo.url, 'repo.url ' + repo.url);\n      t.end();\n    });\n  });\n});\n\ntap.test('insert_relationships', function(t) {\n  const stars = require('./fixtures/stargazers.json');\n  db.insert_relationships(stars, function (err0, result0) { // insert all \"stars\"\n\n    const repo_url = stars.url.replace('/stargazers', ''); // e.g: /dwyl/health\n\n    db.select_repo(repo_url, function (err1, data1) {\n\n      const repo_id = data1.rows[0].id;\n      console.log('repo_id:', repo_id);\n      const username = stars.entries[0].username; // e.g: SimonLab\n      console.log('username:', username);\n\n      db.select_person(username, function (err2, data2) {\n        const person_id = data2.rows[0].id;\n        const select = `SELECT * FROM relationships\n          WHERE person_id = $1 AND repo_id = $2\n          ORDER by inserted_at DESC LIMIT 1`;\n\n        db.PG_CLIENT.query(select, [person_id, repo_id], function(err, result) {\n          t.equal(result.rowCount, 1, '\"stars\" relationship inserted');\n          t.end();\n        });\n      });\n    });\n  });\n});\n\ntap.test('db.end() close database connection so tests can finish', function(t) {\n  db.end(function(err, data) {\n    t.equal(db.PG_CLIENT._ending, true,\n        'db.PG_CLIENT._ending: ' + db.PG_CLIENT._ending);\n    t.end();\n  });\n});\n"
  },
  {
    "path": "test/fixtures/followers.json",
    "content": "{\n  \"entries\": [\n    {\n      \"avatar\": \"https://avatars1.githubusercontent.com/u/5723781?s=88&v=4\",\n      \"uid\": 5723781,\n      \"username\": \"melomg\"\n    },\n    {\n      \"avatar\": \"https://avatars0.githubusercontent.com/u/15983736?s=88&v=4\",\n      \"uid\": 15983736,\n      \"username\": \"samhstn\"\n    },\n    {\n      \"avatar\": \"https://avatars1.githubusercontent.com/u/6057298?s=88&v=4\",\n      \"uid\": 6057298,\n      \"username\": \"SimonLab\"\n    },\n    {\n      \"avatar\": \"https://avatars1.githubusercontent.com/u/772937?s=88&v=4\",\n      \"uid\": 772937,\n      \"username\": \"ryanpcmcquen\"\n    }\n  ],\n  \"url\": \"/dwylbot/followers\",\n  \"type\": \"followers\"\n}"
  },
  {
    "path": "test/fixtures/following.json",
    "content": "{\n  \"entries\": [\n    {\n      \"avatar\": \"https://avatars1.githubusercontent.com/u/6057298?s=88&v=4\",\n      \"uid\": 6057298,\n      \"username\": \"SimonLab\"\n    }\n  ],\n  \"url\": \"/dwylbot/following\",\n  \"type\": \"following\"\n}"
  },
  {
    "path": "test/fixtures/make-fixture.js",
    "content": "const fs = require('fs');\nconst path = require('path');\n\nmodule.exports = function (filename, data) {\n  filename = path.resolve('./test/fixtures/' + filename);\n  fs.writeFileSync(filename, JSON.stringify(data, null, 2), 'utf8');\n}\n"
  },
  {
    "path": "test/fixtures/members.json",
    "content": "{\n  \"entries\": [\n    {\n      \"avatar\": \"https://avatars0.githubusercontent.com/u/7805691?s=96&v=4\",\n      \"uid\": 7805691,\n      \"username\": \"harrygfox\"\n    },\n    {\n      \"avatar\": \"https://avatars1.githubusercontent.com/u/4185328?s=96&v=4\",\n      \"uid\": 4185328,\n      \"username\": \"iteles\"\n    },\n    {\n      \"avatar\": \"https://avatars1.githubusercontent.com/u/6057298?s=96&v=4\",\n      \"uid\": 6057298,\n      \"username\": \"SimonLab\"\n    }\n  ],\n  \"url\": \"/orgs/SafeLives/people\",\n  \"type\": \"people\"\n}"
  },
  {
    "path": "test/fixtures/org.json",
    "content": "{\n  \"url\": \"/dwyl\",\n  \"type\": \"org\",\n  \"name\": \"dwyl\",\n  \"description\": \"Start here: https://github.com/dwyl/start-here\",\n  \"location\": \"London, UK\",\n  \"website\": \"https://dwyl.com\",\n  \"email\": \"hello+github@dwyl.com\",\n  \"pcount\": 171,\n  \"avatar\": \"https://avatars1.githubusercontent.com/u/11708465?s=200&v=4\",\n  \"uid\": 11708465,\n  \"entries\": [\n    {\n      \"name\": \"learn-postgresql\",\n      \"lang\": \"\",\n      \"url\": \"/dwyl/learn-postgresql\",\n      \"description\": \"🐘 Learn how to use PostgreSQL and Structured Query Language (SQL) to store and query your relational data. 🔍\",\n      \"updated\": \"2019-04-15T13:09:30Z\"\n    },\n    {\n      \"name\": \"learn-json-web-tokens\",\n      \"lang\": \"JavaScript\",\n      \"url\": \"/dwyl/learn-json-web-tokens\",\n      \"description\": \"🔐 Learn how to use JSON Web Token (JWT) to secure your next Web App! (Tutorial/Example with Tests!!)\",\n      \"updated\": \"2019-04-15T10:42:01Z\"\n    },\n    {\n      \"name\": \"learn-hapi\",\n      \"lang\": \"HTML\",\n      \"url\": \"/dwyl/learn-hapi\",\n      \"description\": \"☀️ Learn to use Hapi.js (Node.js) web framework to build scalable apps in less time\",\n      \"updated\": \"2019-04-13T14:23:26Z\"\n    },\n    {\n      \"name\": \"aws-sdk-mock\",\n      \"lang\": \"JavaScript\",\n      \"url\": \"/dwyl/aws-sdk-mock\",\n      \"description\": \"🌈 AWSomocks for Javascript/Node.js aws-sdk tested, documented & maintained. Contributions welcome!\",\n      \"updated\": \"2019-04-12T08:36:15Z\"\n    },\n    {\n      \"name\": \"learn-elixir\",\n      \"lang\": \"Elixir\",\n      \"url\": \"/dwyl/learn-elixir\",\n      \"description\": \"💧 Learn the Elixir programming language to build functional, fast, scalable and maintainable web applications!\",\n      \"updated\": \"2019-04-08T07:58:17Z\"\n    },\n    {\n      \"name\": \"hapi-error\",\n      \"lang\": \"JavaScript\",\n      \"url\": \"/dwyl/hapi-error\",\n      \"description\": \"☔️ Intercept errors in your Hapi Web App/API and send a *useful* message to the client OR redirect to the desired endpoint.\",\n      \"updated\": \"2019-04-07T12:52:07Z\"\n    },\n    {\n      \"name\": \"home\",\n      \"lang\": \"\",\n      \"url\": \"/dwyl/home\",\n      \"description\": \"🏡 👩‍💻 💡 home is where you can [learn to] build the future surrounded by like-minded creative, friendly and [intrinsically] motivated people focussed on health, fitness and making things people and the world need!\",\n      \"updated\": \"2019-03-30T22:05:51Z\"\n    },\n    {\n      \"name\": \"elixir-dojo\",\n      \"lang\": \"Elixir\",\n      \"url\": \"/dwyl/elixir-dojo\",\n      \"description\": \"Identify card hands\",\n      \"updated\": \"2019-03-30T18:58:53Z\"\n    },\n    {\n      \"name\": \"learn-tape\",\n      \"lang\": \"JavaScript\",\n      \"url\": \"/dwyl/learn-tape\",\n      \"description\": \"✅ Learn how to use Tape for JavaScript/Node.js Test Driven Development (TDD) - Ten-Minute Testing Tutorial\",\n      \"updated\": \"2019-03-30T13:31:23Z\"\n    },\n    {\n      \"name\": \"learn-travis\",\n      \"lang\": \"JavaScript\",\n      \"url\": \"/dwyl/learn-travis\",\n      \"description\": \"😎 A quick Travis CI (Continuous Integration) Tutorial for Node.js developers\",\n      \"updated\": \"2019-03-30T13:15:15Z\"\n    },\n    {\n      \"name\": \"ordem\",\n      \"lang\": \"JavaScript\",\n      \"url\": \"/dwyl/ordem\",\n      \"description\": \"🏁 ultra-simple ordered task runner for Node.js and Browser. Run your asynchronous functions predictably in series.\",\n      \"updated\": \"2019-03-29T20:26:27Z\"\n    },\n    {\n      \"name\": \"learn-elm-architecture-in-javascript\",\n      \"lang\": \"JavaScript\",\n      \"url\": \"/dwyl/learn-elm-architecture-in-javascript\",\n      \"description\": \"🦄 Learn how to build web apps using the Elm Architecture in \\\"vanilla\\\" JavaScript (step-by-step TDD tutorial)!\",\n      \"updated\": \"2019-03-29T09:20:26Z\"\n    },\n    {\n      \"name\": \"todo-list-javascript-tutorial\",\n      \"lang\": \"JavaScript\",\n      \"url\": \"/dwyl/todo-list-javascript-tutorial\",\n      \"description\": \"✅ A step-by-step complete beginner example/tutorial for building a Todo List App (TodoMVC) from scratch in JavaScript following Test Driven Development (TDD) best practice. 🌱\",\n      \"updated\": \"2019-03-28T21:13:46Z\"\n    },\n    {\n      \"name\": \"process-handbook\",\n      \"lang\": \"\",\n      \"url\": \"/dwyl/process-handbook\",\n      \"description\": \"📗 Contains our processes, questions and journey to creating ateam\",\n      \"updated\": \"2019-03-27T21:49:19Z\"\n    },\n    {\n      \"name\": \"auth\",\n      \"lang\": \"Elixir\",\n      \"url\": \"/dwyl/auth\",\n      \"description\": \"🚪 🔐 A Complete Authentication Solution for Elixir/Phoenix Web Apps/APIs (Documented, Tested & Maintained)\",\n      \"updated\": \"2019-03-26T11:50:37Z\"\n    },\n    {\n      \"name\": \"learn-phoenix-framework\",\n      \"lang\": \"Elixir\",\n      \"url\": \"/dwyl/learn-phoenix-framework\",\n      \"description\": \"🔥 Phoenix is the web framework without compromise on speed, reliability or maintainability! Don't settle for less. 🚀\",\n      \"updated\": \"2019-03-26T11:49:30Z\"\n    },\n    {\n      \"name\": \"phoenix-ecto-append-only-log-example\",\n      \"lang\": \"Elixir\",\n      \"url\": \"/dwyl/phoenix-ecto-append-only-log-example\",\n      \"description\": \"📝 A step-by-step example/tutorial showing how to build a Phoenix (Elixir) App where all data is immutable (append only). Precursor to Blockchain, IPFS or Solid!\",\n      \"updated\": \"2019-03-25T05:50:07Z\"\n    },\n    {\n      \"name\": \"hapi-auth-jwt2\",\n      \"lang\": \"JavaScript\",\n      \"url\": \"/dwyl/hapi-auth-jwt2\",\n      \"description\": \"🔒 Secure Hapi.js authentication plugin using JSON Web Tokens (JWT) in Headers, Query or Cookies\",\n      \"updated\": \"2019-03-24T19:51:17Z\"\n    },\n    {\n      \"name\": \"learn-docker\",\n      \"lang\": \"Dockerfile\",\n      \"url\": \"/dwyl/learn-docker\",\n      \"description\": \"🚢 Learn how to use docker.io containers to consistently deploy your apps on any infrastructure.\",\n      \"updated\": \"2019-03-21T12:12:23Z\"\n    },\n    {\n      \"name\": \"sendemail\",\n      \"lang\": \"JavaScript\",\n      \"url\": \"/dwyl/sendemail\",\n      \"description\": \"✉️ Simplifies reliably sending emails from your node.js apps using AWS Simple Email Service (SES)\",\n      \"updated\": \"2019-03-20T15:35:36Z\"\n    },\n    {\n      \"name\": \"learn-heroku\",\n      \"lang\": \"HTML\",\n      \"url\": \"/dwyl/learn-heroku\",\n      \"description\": \"🏁 Learn how to deploy your web application to Heroku from scratch step-by-step in 7 minutes!\",\n      \"updated\": \"2019-03-14T11:37:37Z\"\n    },\n    {\n      \"name\": \"learn-wireframing\",\n      \"lang\": \"\",\n      \"url\": \"/dwyl/learn-wireframing\",\n      \"description\": \"💡 📰 Learn how to share your UX ideas with your team and the world so you can test hypotheses fast!\",\n      \"updated\": \"2019-03-13T20:44:36Z\"\n    },\n    {\n      \"name\": \"english-words\",\n      \"lang\": \"Python\",\n      \"url\": \"/dwyl/english-words\",\n      \"description\": \"📝 A text file containing 479k English words for all your dictionary/word-based projects e.g: auto-completion / autosuggestion\",\n      \"updated\": \"2019-03-13T20:35:44Z\"\n    },\n    {\n      \"name\": \"alog\",\n      \"lang\": \"Elixir\",\n      \"url\": \"/dwyl/alog\",\n      \"description\": \"🌲 alog (Append-only Log) is an easy way to start using the Lambda/Kappa architecture in your Elixir/Phoenix Apps while still using PostgreSQL (with Ecto).\",\n      \"updated\": \"2019-03-13T11:06:36Z\"\n    },\n    {\n      \"name\": \"phoenix-uk-postcode-finder-example\",\n      \"lang\": \"Elixir\",\n      \"url\": \"/dwyl/phoenix-uk-postcode-finder-example\",\n      \"description\": \"📍An example/tutorial application showing how to rapidly find your nearest X by typing your postcode.\",\n      \"updated\": \"2019-03-12T21:40:10Z\"\n    },\n    {\n      \"name\": \"learn-elm\",\n      \"lang\": \"HTML\",\n      \"url\": \"/dwyl/learn-elm\",\n      \"description\": \"🌈 discover why people are switching to Elm and how you can get started today!\",\n      \"updated\": \"2019-03-11T13:58:23Z\"\n    },\n    {\n      \"name\": \"technical-glossary\",\n      \"lang\": \"\",\n      \"url\": \"/dwyl/technical-glossary\",\n      \"description\": \"📝 A technical glossary for key words and terms to help anyone learn and understand concepts and prepare for a career as a creative technologist! 😕 > 🤔 > 💡 > 😊 🎉 🚀\",\n      \"updated\": \"2019-03-08T14:48:31Z\"\n    },\n    {\n      \"name\": \"learn-purescript\",\n      \"lang\": \"\",\n      \"url\": \"/dwyl/learn-purescript\",\n      \"description\": \"🚧 Learn to use Purescript to make your JavaScript Apps more reliable.\",\n      \"updated\": \"2019-03-08T14:35:47Z\"\n    },\n    {\n      \"name\": \"goodparts\",\n      \"lang\": \"JavaScript\",\n      \"url\": \"/dwyl/goodparts\",\n      \"description\": \"🙈 An ESLint Style that only allows JavaScript the Good Parts (and \\\"Better Parts\\\") in your code.\",\n      \"updated\": \"2019-03-06T14:45:23Z\"\n    },\n    {\n      \"name\": \"product-owner-guide\",\n      \"lang\": \"\",\n      \"url\": \"/dwyl/product-owner-guide\",\n      \"description\": \"🚀 A rough guide for people working with dwyl as Product Owners\",\n      \"updated\": \"2019-03-05T15:01:05Z\"\n    }\n  ],\n  \"next_page\": \"/dwyl?page=2\"\n}"
  },
  {
    "path": "test/fixtures/person.json",
    "content": "{\n  \"url\": \"/iteles\",\n  \"type\": \"profile\",\n  \"username\": \"iteles\",\n  \"bio\": \"Co-founder @dwyl | Head cheerleader @foundersandcoders\",\n  \"avatar\": \"https://avatars1.githubusercontent.com/u/4185328?s=400&v=4\",\n  \"uid\": 4185328,\n  \"repos\": 28,\n  \"projects\": 0,\n  \"stars\": 453,\n  \"followers\": 341,\n  \"following\": 75,\n  \"pinned\": [\n    {\n      \"url\": \"/dwyl/start-here\"\n    },\n    {\n      \"url\": \"/dwyl/learn-tdd\"\n    },\n    {\n      \"url\": \"/dwyl/learn-elm-architecture-in-javascript\"\n    },\n    {\n      \"url\": \"/dwyl/tachyons-bootstrap\"\n    },\n    {\n      \"url\": \"/dwyl/learn-ab-and-multivariate-testing\"\n    },\n    {\n      \"url\": \"/dwyl/learn-elixir\"\n    }\n  ],\n  \"worksfor\": \"@dwyl\",\n  \"location\": \"London, UK\",\n  \"name\": \"Ines Teles Correia\",\n  \"website\": \"https://www.twitter.com/iteles\",\n  \"contribs\": 869,\n  \"contrib_matrix\": {\n    \"2018-04-15\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"13\",\n      \"y\": \"0\"\n    },\n    \"2018-04-16\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"13\",\n      \"y\": \"12\"\n    },\n    \"2018-04-17\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"13\",\n      \"y\": \"24\"\n    },\n    \"2018-04-18\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"13\",\n      \"y\": \"36\"\n    },\n    \"2018-04-19\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"13\",\n      \"y\": \"48\"\n    },\n    \"2018-04-20\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"13\",\n      \"y\": \"60\"\n    },\n    \"2018-04-21\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"13\",\n      \"y\": \"72\"\n    },\n    \"2018-04-22\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"12\",\n      \"y\": \"0\"\n    },\n    \"2018-04-23\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"12\",\n      \"y\": \"12\"\n    },\n    \"2018-04-24\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"12\",\n      \"y\": \"24\"\n    },\n    \"2018-04-25\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"12\",\n      \"y\": \"36\"\n    },\n    \"2018-04-26\": {\n      \"fill\": \"#239a3b\",\n      \"count\": 7,\n      \"x\": \"12\",\n      \"y\": \"48\"\n    },\n    \"2018-04-27\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"12\",\n      \"y\": \"60\"\n    },\n    \"2018-04-28\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"12\",\n      \"y\": \"72\"\n    },\n    \"2018-04-29\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"11\",\n      \"y\": \"0\"\n    },\n    \"2018-04-30\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"11\",\n      \"y\": \"12\"\n    },\n    \"2018-05-01\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"11\",\n      \"y\": \"24\"\n    },\n    \"2018-05-02\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"11\",\n      \"y\": \"36\"\n    },\n    \"2018-05-03\": {\n      \"fill\": \"#196127\",\n      \"count\": 13,\n      \"x\": \"11\",\n      \"y\": \"48\"\n    },\n    \"2018-05-04\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"11\",\n      \"y\": \"60\"\n    },\n    \"2018-05-05\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"11\",\n      \"y\": \"72\"\n    },\n    \"2018-05-06\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"10\",\n      \"y\": \"0\"\n    },\n    \"2018-05-07\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"10\",\n      \"y\": \"12\"\n    },\n    \"2018-05-08\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"10\",\n      \"y\": \"24\"\n    },\n    \"2018-05-09\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"10\",\n      \"y\": \"36\"\n    },\n    \"2018-05-10\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"10\",\n      \"y\": \"48\"\n    },\n    \"2018-05-11\": {\n      \"fill\": \"#196127\",\n      \"count\": 15,\n      \"x\": \"10\",\n      \"y\": \"60\"\n    },\n    \"2018-05-12\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"10\",\n      \"y\": \"72\"\n    },\n    \"2018-05-13\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"9\",\n      \"y\": \"0\"\n    },\n    \"2018-05-14\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"9\",\n      \"y\": \"12\"\n    },\n    \"2018-05-15\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"9\",\n      \"y\": \"24\"\n    },\n    \"2018-05-16\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"9\",\n      \"y\": \"36\"\n    },\n    \"2018-05-17\": {\n      \"fill\": \"#196127\",\n      \"count\": 9,\n      \"x\": \"9\",\n      \"y\": \"48\"\n    },\n    \"2018-05-18\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"9\",\n      \"y\": \"60\"\n    },\n    \"2018-05-19\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 5,\n      \"x\": \"9\",\n      \"y\": \"72\"\n    },\n    \"2018-05-20\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"8\",\n      \"y\": \"0\"\n    },\n    \"2018-05-21\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"8\",\n      \"y\": \"12\"\n    },\n    \"2018-05-22\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"8\",\n      \"y\": \"24\"\n    },\n    \"2018-05-23\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"8\",\n      \"y\": \"36\"\n    },\n    \"2018-05-24\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"8\",\n      \"y\": \"48\"\n    },\n    \"2018-05-25\": {\n      \"fill\": \"#239a3b\",\n      \"count\": 8,\n      \"x\": \"8\",\n      \"y\": \"60\"\n    },\n    \"2018-05-26\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"8\",\n      \"y\": \"72\"\n    },\n    \"2018-05-27\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"7\",\n      \"y\": \"0\"\n    },\n    \"2018-05-28\": {\n      \"fill\": \"#239a3b\",\n      \"count\": 6,\n      \"x\": \"7\",\n      \"y\": \"12\"\n    },\n    \"2018-05-29\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"7\",\n      \"y\": \"24\"\n    },\n    \"2018-05-30\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"7\",\n      \"y\": \"36\"\n    },\n    \"2018-05-31\": {\n      \"fill\": \"#196127\",\n      \"count\": 10,\n      \"x\": \"7\",\n      \"y\": \"48\"\n    },\n    \"2018-06-01\": {\n      \"fill\": \"#239a3b\",\n      \"count\": 6,\n      \"x\": \"7\",\n      \"y\": \"60\"\n    },\n    \"2018-06-02\": {\n      \"fill\": \"#239a3b\",\n      \"count\": 6,\n      \"x\": \"7\",\n      \"y\": \"72\"\n    },\n    \"2018-06-03\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"6\",\n      \"y\": \"0\"\n    },\n    \"2018-06-04\": {\n      \"fill\": \"#239a3b\",\n      \"count\": 8,\n      \"x\": \"6\",\n      \"y\": \"12\"\n    },\n    \"2018-06-05\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"6\",\n      \"y\": \"24\"\n    },\n    \"2018-06-06\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 5,\n      \"x\": \"6\",\n      \"y\": \"36\"\n    },\n    \"2018-06-07\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"6\",\n      \"y\": \"48\"\n    },\n    \"2018-06-08\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"6\",\n      \"y\": \"60\"\n    },\n    \"2018-06-09\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"6\",\n      \"y\": \"72\"\n    },\n    \"2018-06-10\": {\n      \"fill\": \"#239a3b\",\n      \"count\": 6,\n      \"x\": \"5\",\n      \"y\": \"0\"\n    },\n    \"2018-06-11\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"5\",\n      \"y\": \"12\"\n    },\n    \"2018-06-12\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"5\",\n      \"y\": \"24\"\n    },\n    \"2018-06-13\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"5\",\n      \"y\": \"36\"\n    },\n    \"2018-06-14\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"5\",\n      \"y\": \"48\"\n    },\n    \"2018-06-15\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"5\",\n      \"y\": \"60\"\n    },\n    \"2018-06-16\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"5\",\n      \"y\": \"72\"\n    },\n    \"2018-06-17\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"4\",\n      \"y\": \"0\"\n    },\n    \"2018-06-18\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"4\",\n      \"y\": \"12\"\n    },\n    \"2018-06-19\": {\n      \"fill\": \"#239a3b\",\n      \"count\": 7,\n      \"x\": \"4\",\n      \"y\": \"24\"\n    },\n    \"2018-06-20\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"4\",\n      \"y\": \"36\"\n    },\n    \"2018-06-21\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 5,\n      \"x\": \"4\",\n      \"y\": \"48\"\n    },\n    \"2018-06-22\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"4\",\n      \"y\": \"60\"\n    },\n    \"2018-06-23\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"4\",\n      \"y\": \"72\"\n    },\n    \"2018-06-24\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"3\",\n      \"y\": \"0\"\n    },\n    \"2018-06-25\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"3\",\n      \"y\": \"12\"\n    },\n    \"2018-06-26\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"3\",\n      \"y\": \"24\"\n    },\n    \"2018-06-27\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"3\",\n      \"y\": \"36\"\n    },\n    \"2018-06-28\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"3\",\n      \"y\": \"48\"\n    },\n    \"2018-06-29\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"3\",\n      \"y\": \"60\"\n    },\n    \"2018-06-30\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"3\",\n      \"y\": \"72\"\n    },\n    \"2018-07-01\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"2\",\n      \"y\": \"0\"\n    },\n    \"2018-07-02\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"2\",\n      \"y\": \"12\"\n    },\n    \"2018-07-03\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"2\",\n      \"y\": \"24\"\n    },\n    \"2018-07-04\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"2\",\n      \"y\": \"36\"\n    },\n    \"2018-07-05\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"2\",\n      \"y\": \"48\"\n    },\n    \"2018-07-06\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"2\",\n      \"y\": \"60\"\n    },\n    \"2018-07-07\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"2\",\n      \"y\": \"72\"\n    },\n    \"2018-07-08\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"1\",\n      \"y\": \"0\"\n    },\n    \"2018-07-09\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"1\",\n      \"y\": \"12\"\n    },\n    \"2018-07-10\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"1\",\n      \"y\": \"24\"\n    },\n    \"2018-07-11\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"1\",\n      \"y\": \"36\"\n    },\n    \"2018-07-12\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 5,\n      \"x\": \"1\",\n      \"y\": \"48\"\n    },\n    \"2018-07-13\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"1\",\n      \"y\": \"60\"\n    },\n    \"2018-07-14\": {\n      \"fill\": \"#196127\",\n      \"count\": 9,\n      \"x\": \"1\",\n      \"y\": \"72\"\n    },\n    \"2018-07-15\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"0\",\n      \"y\": \"0\"\n    },\n    \"2018-07-16\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"0\",\n      \"y\": \"12\"\n    },\n    \"2018-07-17\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"0\",\n      \"y\": \"24\"\n    },\n    \"2018-07-18\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"0\",\n      \"y\": \"36\"\n    },\n    \"2018-07-19\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"0\",\n      \"y\": \"48\"\n    },\n    \"2018-07-20\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"0\",\n      \"y\": \"60\"\n    },\n    \"2018-07-21\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"0\",\n      \"y\": \"72\"\n    },\n    \"2018-07-22\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-1\",\n      \"y\": \"0\"\n    },\n    \"2018-07-23\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-1\",\n      \"y\": \"12\"\n    },\n    \"2018-07-24\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-1\",\n      \"y\": \"24\"\n    },\n    \"2018-07-25\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-1\",\n      \"y\": \"36\"\n    },\n    \"2018-07-26\": {\n      \"fill\": \"#239a3b\",\n      \"count\": 6,\n      \"x\": \"-1\",\n      \"y\": \"48\"\n    },\n    \"2018-07-27\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-1\",\n      \"y\": \"60\"\n    },\n    \"2018-07-28\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"-1\",\n      \"y\": \"72\"\n    },\n    \"2018-07-29\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-2\",\n      \"y\": \"0\"\n    },\n    \"2018-07-30\": {\n      \"fill\": \"#239a3b\",\n      \"count\": 7,\n      \"x\": \"-2\",\n      \"y\": \"12\"\n    },\n    \"2018-07-31\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-2\",\n      \"y\": \"24\"\n    },\n    \"2018-08-01\": {\n      \"fill\": \"#239a3b\",\n      \"count\": 6,\n      \"x\": \"-2\",\n      \"y\": \"36\"\n    },\n    \"2018-08-02\": {\n      \"fill\": \"#239a3b\",\n      \"count\": 6,\n      \"x\": \"-2\",\n      \"y\": \"48\"\n    },\n    \"2018-08-03\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-2\",\n      \"y\": \"60\"\n    },\n    \"2018-08-04\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-2\",\n      \"y\": \"72\"\n    },\n    \"2018-08-05\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-3\",\n      \"y\": \"0\"\n    },\n    \"2018-08-06\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-3\",\n      \"y\": \"12\"\n    },\n    \"2018-08-07\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-3\",\n      \"y\": \"24\"\n    },\n    \"2018-08-08\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-3\",\n      \"y\": \"36\"\n    },\n    \"2018-08-09\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 5,\n      \"x\": \"-3\",\n      \"y\": \"48\"\n    },\n    \"2018-08-10\": {\n      \"fill\": \"#239a3b\",\n      \"count\": 6,\n      \"x\": \"-3\",\n      \"y\": \"60\"\n    },\n    \"2018-08-11\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-3\",\n      \"y\": \"72\"\n    },\n    \"2018-08-12\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 5,\n      \"x\": \"-4\",\n      \"y\": \"0\"\n    },\n    \"2018-08-13\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"-4\",\n      \"y\": \"12\"\n    },\n    \"2018-08-14\": {\n      \"fill\": \"#239a3b\",\n      \"count\": 7,\n      \"x\": \"-4\",\n      \"y\": \"24\"\n    },\n    \"2018-08-15\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-4\",\n      \"y\": \"36\"\n    },\n    \"2018-08-16\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-4\",\n      \"y\": \"48\"\n    },\n    \"2018-08-17\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-4\",\n      \"y\": \"60\"\n    },\n    \"2018-08-18\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-4\",\n      \"y\": \"72\"\n    },\n    \"2018-08-19\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-5\",\n      \"y\": \"0\"\n    },\n    \"2018-08-20\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-5\",\n      \"y\": \"12\"\n    },\n    \"2018-08-21\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-5\",\n      \"y\": \"24\"\n    },\n    \"2018-08-22\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-5\",\n      \"y\": \"36\"\n    },\n    \"2018-08-23\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"-5\",\n      \"y\": \"48\"\n    },\n    \"2018-08-24\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"-5\",\n      \"y\": \"60\"\n    },\n    \"2018-08-25\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-5\",\n      \"y\": \"72\"\n    },\n    \"2018-08-26\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-6\",\n      \"y\": \"0\"\n    },\n    \"2018-08-27\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-6\",\n      \"y\": \"12\"\n    },\n    \"2018-08-28\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-6\",\n      \"y\": \"24\"\n    },\n    \"2018-08-29\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-6\",\n      \"y\": \"36\"\n    },\n    \"2018-08-30\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-6\",\n      \"y\": \"48\"\n    },\n    \"2018-08-31\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-6\",\n      \"y\": \"60\"\n    },\n    \"2018-09-01\": {\n      \"fill\": \"#239a3b\",\n      \"count\": 8,\n      \"x\": \"-6\",\n      \"y\": \"72\"\n    },\n    \"2018-09-02\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-7\",\n      \"y\": \"0\"\n    },\n    \"2018-09-03\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"-7\",\n      \"y\": \"12\"\n    },\n    \"2018-09-04\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"-7\",\n      \"y\": \"24\"\n    },\n    \"2018-09-05\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"-7\",\n      \"y\": \"36\"\n    },\n    \"2018-09-06\": {\n      \"fill\": \"#196127\",\n      \"count\": 9,\n      \"x\": \"-7\",\n      \"y\": \"48\"\n    },\n    \"2018-09-07\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-7\",\n      \"y\": \"60\"\n    },\n    \"2018-09-08\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 5,\n      \"x\": \"-7\",\n      \"y\": \"72\"\n    },\n    \"2018-09-09\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-8\",\n      \"y\": \"0\"\n    },\n    \"2018-09-10\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-8\",\n      \"y\": \"12\"\n    },\n    \"2018-09-11\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-8\",\n      \"y\": \"24\"\n    },\n    \"2018-09-12\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"-8\",\n      \"y\": \"36\"\n    },\n    \"2018-09-13\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-8\",\n      \"y\": \"48\"\n    },\n    \"2018-09-14\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-8\",\n      \"y\": \"60\"\n    },\n    \"2018-09-15\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-8\",\n      \"y\": \"72\"\n    },\n    \"2018-09-16\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-9\",\n      \"y\": \"0\"\n    },\n    \"2018-09-17\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-9\",\n      \"y\": \"12\"\n    },\n    \"2018-09-18\": {\n      \"fill\": \"#196127\",\n      \"count\": 9,\n      \"x\": \"-9\",\n      \"y\": \"24\"\n    },\n    \"2018-09-19\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-9\",\n      \"y\": \"36\"\n    },\n    \"2018-09-20\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-9\",\n      \"y\": \"48\"\n    },\n    \"2018-09-21\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-9\",\n      \"y\": \"60\"\n    },\n    \"2018-09-22\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-9\",\n      \"y\": \"72\"\n    },\n    \"2018-09-23\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-10\",\n      \"y\": \"0\"\n    },\n    \"2018-09-24\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-10\",\n      \"y\": \"12\"\n    },\n    \"2018-09-25\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-10\",\n      \"y\": \"24\"\n    },\n    \"2018-09-26\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"-10\",\n      \"y\": \"36\"\n    },\n    \"2018-09-27\": {\n      \"fill\": \"#239a3b\",\n      \"count\": 7,\n      \"x\": \"-10\",\n      \"y\": \"48\"\n    },\n    \"2018-09-28\": {\n      \"fill\": \"#196127\",\n      \"count\": 9,\n      \"x\": \"-10\",\n      \"y\": \"60\"\n    },\n    \"2018-09-29\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-10\",\n      \"y\": \"72\"\n    },\n    \"2018-09-30\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 5,\n      \"x\": \"-11\",\n      \"y\": \"0\"\n    },\n    \"2018-10-01\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-11\",\n      \"y\": \"12\"\n    },\n    \"2018-10-02\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-11\",\n      \"y\": \"24\"\n    },\n    \"2018-10-03\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-11\",\n      \"y\": \"36\"\n    },\n    \"2018-10-04\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-11\",\n      \"y\": \"48\"\n    },\n    \"2018-10-05\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"-11\",\n      \"y\": \"60\"\n    },\n    \"2018-10-06\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-11\",\n      \"y\": \"72\"\n    },\n    \"2018-10-07\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-12\",\n      \"y\": \"0\"\n    },\n    \"2018-10-08\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-12\",\n      \"y\": \"12\"\n    },\n    \"2018-10-09\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"-12\",\n      \"y\": \"24\"\n    },\n    \"2018-10-10\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-12\",\n      \"y\": \"36\"\n    },\n    \"2018-10-11\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-12\",\n      \"y\": \"48\"\n    },\n    \"2018-10-12\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"-12\",\n      \"y\": \"60\"\n    },\n    \"2018-10-13\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-12\",\n      \"y\": \"72\"\n    },\n    \"2018-10-14\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-13\",\n      \"y\": \"0\"\n    },\n    \"2018-10-15\": {\n      \"fill\": \"#196127\",\n      \"count\": 9,\n      \"x\": \"-13\",\n      \"y\": \"12\"\n    },\n    \"2018-10-16\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"-13\",\n      \"y\": \"24\"\n    },\n    \"2018-10-17\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"-13\",\n      \"y\": \"36\"\n    },\n    \"2018-10-18\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-13\",\n      \"y\": \"48\"\n    },\n    \"2018-10-19\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-13\",\n      \"y\": \"60\"\n    },\n    \"2018-10-20\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-13\",\n      \"y\": \"72\"\n    },\n    \"2018-10-21\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"-14\",\n      \"y\": \"0\"\n    },\n    \"2018-10-22\": {\n      \"fill\": \"#239a3b\",\n      \"count\": 8,\n      \"x\": \"-14\",\n      \"y\": \"12\"\n    },\n    \"2018-10-23\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"-14\",\n      \"y\": \"24\"\n    },\n    \"2018-10-24\": {\n      \"fill\": \"#239a3b\",\n      \"count\": 6,\n      \"x\": \"-14\",\n      \"y\": \"36\"\n    },\n    \"2018-10-25\": {\n      \"fill\": \"#239a3b\",\n      \"count\": 6,\n      \"x\": \"-14\",\n      \"y\": \"48\"\n    },\n    \"2018-10-26\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-14\",\n      \"y\": \"60\"\n    },\n    \"2018-10-27\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-14\",\n      \"y\": \"72\"\n    },\n    \"2018-10-28\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-15\",\n      \"y\": \"0\"\n    },\n    \"2018-10-29\": {\n      \"fill\": \"#196127\",\n      \"count\": 16,\n      \"x\": \"-15\",\n      \"y\": \"12\"\n    },\n    \"2018-10-30\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-15\",\n      \"y\": \"24\"\n    },\n    \"2018-10-31\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-15\",\n      \"y\": \"36\"\n    },\n    \"2018-11-01\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 5,\n      \"x\": \"-15\",\n      \"y\": \"48\"\n    },\n    \"2018-11-02\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-15\",\n      \"y\": \"60\"\n    },\n    \"2018-11-03\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"-15\",\n      \"y\": \"72\"\n    },\n    \"2018-11-04\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-16\",\n      \"y\": \"0\"\n    },\n    \"2018-11-05\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-16\",\n      \"y\": \"12\"\n    },\n    \"2018-11-06\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-16\",\n      \"y\": \"24\"\n    },\n    \"2018-11-07\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"-16\",\n      \"y\": \"36\"\n    },\n    \"2018-11-08\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-16\",\n      \"y\": \"48\"\n    },\n    \"2018-11-09\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"-16\",\n      \"y\": \"60\"\n    },\n    \"2018-11-10\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-16\",\n      \"y\": \"72\"\n    },\n    \"2018-11-11\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"-17\",\n      \"y\": \"0\"\n    },\n    \"2018-11-12\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-17\",\n      \"y\": \"12\"\n    },\n    \"2018-11-13\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-17\",\n      \"y\": \"24\"\n    },\n    \"2018-11-14\": {\n      \"fill\": \"#239a3b\",\n      \"count\": 8,\n      \"x\": \"-17\",\n      \"y\": \"36\"\n    },\n    \"2018-11-15\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"-17\",\n      \"y\": \"48\"\n    },\n    \"2018-11-16\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"-17\",\n      \"y\": \"60\"\n    },\n    \"2018-11-17\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"-17\",\n      \"y\": \"72\"\n    },\n    \"2018-11-18\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-18\",\n      \"y\": \"0\"\n    },\n    \"2018-11-19\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"-18\",\n      \"y\": \"12\"\n    },\n    \"2018-11-20\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"-18\",\n      \"y\": \"24\"\n    },\n    \"2018-11-21\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-18\",\n      \"y\": \"36\"\n    },\n    \"2018-11-22\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-18\",\n      \"y\": \"48\"\n    },\n    \"2018-11-23\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"-18\",\n      \"y\": \"60\"\n    },\n    \"2018-11-24\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"-18\",\n      \"y\": \"72\"\n    },\n    \"2018-11-25\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-19\",\n      \"y\": \"0\"\n    },\n    \"2018-11-26\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"-19\",\n      \"y\": \"12\"\n    },\n    \"2018-11-27\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-19\",\n      \"y\": \"24\"\n    },\n    \"2018-11-28\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 5,\n      \"x\": \"-19\",\n      \"y\": \"36\"\n    },\n    \"2018-11-29\": {\n      \"fill\": \"#196127\",\n      \"count\": 9,\n      \"x\": \"-19\",\n      \"y\": \"48\"\n    },\n    \"2018-11-30\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-19\",\n      \"y\": \"60\"\n    },\n    \"2018-12-01\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-19\",\n      \"y\": \"72\"\n    },\n    \"2018-12-02\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 5,\n      \"x\": \"-20\",\n      \"y\": \"0\"\n    },\n    \"2018-12-03\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-20\",\n      \"y\": \"12\"\n    },\n    \"2018-12-04\": {\n      \"fill\": \"#239a3b\",\n      \"count\": 6,\n      \"x\": \"-20\",\n      \"y\": \"24\"\n    },\n    \"2018-12-05\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"-20\",\n      \"y\": \"36\"\n    },\n    \"2018-12-06\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"-20\",\n      \"y\": \"48\"\n    },\n    \"2018-12-07\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-20\",\n      \"y\": \"60\"\n    },\n    \"2018-12-08\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"-20\",\n      \"y\": \"72\"\n    },\n    \"2018-12-09\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-21\",\n      \"y\": \"0\"\n    },\n    \"2018-12-10\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"-21\",\n      \"y\": \"12\"\n    },\n    \"2018-12-11\": {\n      \"fill\": \"#239a3b\",\n      \"count\": 6,\n      \"x\": \"-21\",\n      \"y\": \"24\"\n    },\n    \"2018-12-12\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-21\",\n      \"y\": \"36\"\n    },\n    \"2018-12-13\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-21\",\n      \"y\": \"48\"\n    },\n    \"2018-12-14\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"-21\",\n      \"y\": \"60\"\n    },\n    \"2018-12-15\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-21\",\n      \"y\": \"72\"\n    },\n    \"2018-12-16\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-22\",\n      \"y\": \"0\"\n    },\n    \"2018-12-17\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-22\",\n      \"y\": \"12\"\n    },\n    \"2018-12-18\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-22\",\n      \"y\": \"24\"\n    },\n    \"2018-12-19\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-22\",\n      \"y\": \"36\"\n    },\n    \"2018-12-20\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-22\",\n      \"y\": \"48\"\n    },\n    \"2018-12-21\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-22\",\n      \"y\": \"60\"\n    },\n    \"2018-12-22\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-22\",\n      \"y\": \"72\"\n    },\n    \"2018-12-23\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-23\",\n      \"y\": \"0\"\n    },\n    \"2018-12-24\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-23\",\n      \"y\": \"12\"\n    },\n    \"2018-12-25\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-23\",\n      \"y\": \"24\"\n    },\n    \"2018-12-26\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-23\",\n      \"y\": \"36\"\n    },\n    \"2018-12-27\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-23\",\n      \"y\": \"48\"\n    },\n    \"2018-12-28\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-23\",\n      \"y\": \"60\"\n    },\n    \"2018-12-29\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-23\",\n      \"y\": \"72\"\n    },\n    \"2018-12-30\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-24\",\n      \"y\": \"0\"\n    },\n    \"2018-12-31\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-24\",\n      \"y\": \"12\"\n    },\n    \"2019-01-01\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"-24\",\n      \"y\": \"24\"\n    },\n    \"2019-01-02\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"-24\",\n      \"y\": \"36\"\n    },\n    \"2019-01-03\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-24\",\n      \"y\": \"48\"\n    },\n    \"2019-01-04\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 5,\n      \"x\": \"-24\",\n      \"y\": \"60\"\n    },\n    \"2019-01-05\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-24\",\n      \"y\": \"72\"\n    },\n    \"2019-01-06\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-25\",\n      \"y\": \"0\"\n    },\n    \"2019-01-07\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-25\",\n      \"y\": \"12\"\n    },\n    \"2019-01-08\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 5,\n      \"x\": \"-25\",\n      \"y\": \"24\"\n    },\n    \"2019-01-09\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-25\",\n      \"y\": \"36\"\n    },\n    \"2019-01-10\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-25\",\n      \"y\": \"48\"\n    },\n    \"2019-01-11\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-25\",\n      \"y\": \"60\"\n    },\n    \"2019-01-12\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-25\",\n      \"y\": \"72\"\n    },\n    \"2019-01-13\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-26\",\n      \"y\": \"0\"\n    },\n    \"2019-01-14\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-26\",\n      \"y\": \"12\"\n    },\n    \"2019-01-15\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-26\",\n      \"y\": \"24\"\n    },\n    \"2019-01-16\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-26\",\n      \"y\": \"36\"\n    },\n    \"2019-01-17\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-26\",\n      \"y\": \"48\"\n    },\n    \"2019-01-18\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"-26\",\n      \"y\": \"60\"\n    },\n    \"2019-01-19\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-26\",\n      \"y\": \"72\"\n    },\n    \"2019-01-20\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-27\",\n      \"y\": \"0\"\n    },\n    \"2019-01-21\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-27\",\n      \"y\": \"12\"\n    },\n    \"2019-01-22\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"-27\",\n      \"y\": \"24\"\n    },\n    \"2019-01-23\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 5,\n      \"x\": \"-27\",\n      \"y\": \"36\"\n    },\n    \"2019-01-24\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"-27\",\n      \"y\": \"48\"\n    },\n    \"2019-01-25\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-27\",\n      \"y\": \"60\"\n    },\n    \"2019-01-26\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-27\",\n      \"y\": \"72\"\n    },\n    \"2019-01-27\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-28\",\n      \"y\": \"0\"\n    },\n    \"2019-01-28\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"-28\",\n      \"y\": \"12\"\n    },\n    \"2019-01-29\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-28\",\n      \"y\": \"24\"\n    },\n    \"2019-01-30\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-28\",\n      \"y\": \"36\"\n    },\n    \"2019-01-31\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 5,\n      \"x\": \"-28\",\n      \"y\": \"48\"\n    },\n    \"2019-02-01\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-28\",\n      \"y\": \"60\"\n    },\n    \"2019-02-02\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-28\",\n      \"y\": \"72\"\n    },\n    \"2019-02-03\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-29\",\n      \"y\": \"0\"\n    },\n    \"2019-02-04\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-29\",\n      \"y\": \"12\"\n    },\n    \"2019-02-05\": {\n      \"fill\": \"#239a3b\",\n      \"count\": 7,\n      \"x\": \"-29\",\n      \"y\": \"24\"\n    },\n    \"2019-02-06\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-29\",\n      \"y\": \"36\"\n    },\n    \"2019-02-07\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-29\",\n      \"y\": \"48\"\n    },\n    \"2019-02-08\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-29\",\n      \"y\": \"60\"\n    },\n    \"2019-02-09\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-29\",\n      \"y\": \"72\"\n    },\n    \"2019-02-10\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-30\",\n      \"y\": \"0\"\n    },\n    \"2019-02-11\": {\n      \"fill\": \"#239a3b\",\n      \"count\": 8,\n      \"x\": \"-30\",\n      \"y\": \"12\"\n    },\n    \"2019-02-12\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"-30\",\n      \"y\": \"24\"\n    },\n    \"2019-02-13\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-30\",\n      \"y\": \"36\"\n    },\n    \"2019-02-14\": {\n      \"fill\": \"#239a3b\",\n      \"count\": 6,\n      \"x\": \"-30\",\n      \"y\": \"48\"\n    },\n    \"2019-02-15\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-30\",\n      \"y\": \"60\"\n    },\n    \"2019-02-16\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-30\",\n      \"y\": \"72\"\n    },\n    \"2019-02-17\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-31\",\n      \"y\": \"0\"\n    },\n    \"2019-02-18\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-31\",\n      \"y\": \"12\"\n    },\n    \"2019-02-19\": {\n      \"fill\": \"#196127\",\n      \"count\": 10,\n      \"x\": \"-31\",\n      \"y\": \"24\"\n    },\n    \"2019-02-20\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-31\",\n      \"y\": \"36\"\n    },\n    \"2019-02-21\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-31\",\n      \"y\": \"48\"\n    },\n    \"2019-02-22\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-31\",\n      \"y\": \"60\"\n    },\n    \"2019-02-23\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"-31\",\n      \"y\": \"72\"\n    },\n    \"2019-02-24\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"-32\",\n      \"y\": \"0\"\n    },\n    \"2019-02-25\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-32\",\n      \"y\": \"12\"\n    },\n    \"2019-02-26\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-32\",\n      \"y\": \"24\"\n    },\n    \"2019-02-27\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-32\",\n      \"y\": \"36\"\n    },\n    \"2019-02-28\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-32\",\n      \"y\": \"48\"\n    },\n    \"2019-03-01\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-32\",\n      \"y\": \"60\"\n    },\n    \"2019-03-02\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-32\",\n      \"y\": \"72\"\n    },\n    \"2019-03-03\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-33\",\n      \"y\": \"0\"\n    },\n    \"2019-03-04\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-33\",\n      \"y\": \"12\"\n    },\n    \"2019-03-05\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-33\",\n      \"y\": \"24\"\n    },\n    \"2019-03-06\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-33\",\n      \"y\": \"36\"\n    },\n    \"2019-03-07\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"-33\",\n      \"y\": \"48\"\n    },\n    \"2019-03-08\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-33\",\n      \"y\": \"60\"\n    },\n    \"2019-03-09\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-33\",\n      \"y\": \"72\"\n    },\n    \"2019-03-10\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-34\",\n      \"y\": \"0\"\n    },\n    \"2019-03-11\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-34\",\n      \"y\": \"12\"\n    },\n    \"2019-03-12\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-34\",\n      \"y\": \"24\"\n    },\n    \"2019-03-13\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-34\",\n      \"y\": \"36\"\n    },\n    \"2019-03-14\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-34\",\n      \"y\": \"48\"\n    },\n    \"2019-03-15\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-34\",\n      \"y\": \"60\"\n    },\n    \"2019-03-16\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-34\",\n      \"y\": \"72\"\n    },\n    \"2019-03-17\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-35\",\n      \"y\": \"0\"\n    },\n    \"2019-03-18\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-35\",\n      \"y\": \"12\"\n    },\n    \"2019-03-19\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-35\",\n      \"y\": \"24\"\n    },\n    \"2019-03-20\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-35\",\n      \"y\": \"36\"\n    },\n    \"2019-03-21\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-35\",\n      \"y\": \"48\"\n    },\n    \"2019-03-22\": {\n      \"fill\": \"#196127\",\n      \"count\": 11,\n      \"x\": \"-35\",\n      \"y\": \"60\"\n    },\n    \"2019-03-23\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-35\",\n      \"y\": \"72\"\n    },\n    \"2019-03-24\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-36\",\n      \"y\": \"0\"\n    },\n    \"2019-03-25\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-36\",\n      \"y\": \"12\"\n    },\n    \"2019-03-26\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-36\",\n      \"y\": \"24\"\n    },\n    \"2019-03-27\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-36\",\n      \"y\": \"36\"\n    },\n    \"2019-03-28\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"-36\",\n      \"y\": \"48\"\n    },\n    \"2019-03-29\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 5,\n      \"x\": \"-36\",\n      \"y\": \"60\"\n    },\n    \"2019-03-30\": {\n      \"fill\": \"#196127\",\n      \"count\": 10,\n      \"x\": \"-36\",\n      \"y\": \"72\"\n    },\n    \"2019-03-31\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 3,\n      \"x\": \"-37\",\n      \"y\": \"0\"\n    },\n    \"2019-04-01\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-37\",\n      \"y\": \"12\"\n    },\n    \"2019-04-02\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-37\",\n      \"y\": \"24\"\n    },\n    \"2019-04-03\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-37\",\n      \"y\": \"36\"\n    },\n    \"2019-04-04\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-37\",\n      \"y\": \"48\"\n    },\n    \"2019-04-05\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-37\",\n      \"y\": \"60\"\n    },\n    \"2019-04-06\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-37\",\n      \"y\": \"72\"\n    },\n    \"2019-04-07\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-38\",\n      \"y\": \"0\"\n    },\n    \"2019-04-08\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-38\",\n      \"y\": \"12\"\n    },\n    \"2019-04-09\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-38\",\n      \"y\": \"24\"\n    },\n    \"2019-04-10\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 2,\n      \"x\": \"-38\",\n      \"y\": \"36\"\n    },\n    \"2019-04-11\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-38\",\n      \"y\": \"48\"\n    },\n    \"2019-04-12\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 5,\n      \"x\": \"-38\",\n      \"y\": \"60\"\n    },\n    \"2019-04-13\": {\n      \"fill\": \"#ebedf0\",\n      \"count\": 0,\n      \"x\": \"-38\",\n      \"y\": \"72\"\n    },\n    \"2019-04-14\": {\n      \"fill\": \"#c6e48b\",\n      \"count\": 1,\n      \"x\": \"-39\",\n      \"y\": \"0\"\n    },\n    \"2019-04-15\": {\n      \"fill\": \"#7bc96f\",\n      \"count\": 4,\n      \"x\": \"-39\",\n      \"y\": \"12\"\n    }\n  },\n  \"orgs\": {\n    \"bowlingjs\": \"https://avatars3.githubusercontent.com/u/8825909?s=70&v=4\",\n    \"foundersandcoders\": \"https://avatars3.githubusercontent.com/u/9970257?s=70&v=4\",\n    \"docdis\": \"https://avatars0.githubusercontent.com/u/10836426?s=70&v=4\",\n    \"dwyl\": \"https://avatars2.githubusercontent.com/u/11708465?s=70&v=4\",\n    \"ladiesofcode\": \"https://avatars0.githubusercontent.com/u/16606192?s=70&v=4\",\n    \"TheScienceMuseum\": \"https://avatars0.githubusercontent.com/u/16609662?s=70&v=4\",\n    \"SafeLives\": \"https://avatars2.githubusercontent.com/u/20841400?s=70&v=4\"\n  }\n}"
  },
  {
    "path": "test/fixtures/repo.json",
    "content": "{\n  \"url\": \"/dwyl/health\",\n  \"type\": \"repo\",\n  \"description\": \"🍏 🍋 🍓 🍐 🍌 🍍 🍉 🍒\",\n  \"website\": \"\",\n  \"tags\": \"read, the, issues\",\n  \"watchers\": 3,\n  \"stars\": 6,\n  \"forks\": 0,\n  \"commits\": 1,\n  \"branches\": 1,\n  \"releases\": 0,\n  \"langs\": []\n}"
  },
  {
    "path": "test/fixtures/stargazers.json",
    "content": "{\n  \"entries\": [\n    {\n      \"avatar\": \"https://avatars1.githubusercontent.com/u/6057298?s=88&v=4\",\n      \"uid\": 6057298,\n      \"username\": \"SimonLab\"\n    },\n    {\n      \"avatar\": \"https://avatars1.githubusercontent.com/u/11595920?s=88&v=4\",\n      \"uid\": 11595920,\n      \"username\": \"rub1e\"\n    },\n    {\n      \"avatar\": \"https://avatars1.githubusercontent.com/u/12380455?s=88&v=4\",\n      \"uid\": 12380455,\n      \"username\": \"bradreeder\"\n    },\n    {\n      \"avatar\": \"https://avatars3.githubusercontent.com/u/4200487?s=88&v=4\",\n      \"uid\": 4200487,\n      \"username\": \"JoseCage\"\n    },\n    {\n      \"avatar\": \"https://avatars1.githubusercontent.com/u/4185328?s=88&v=4\",\n      \"uid\": 4185328,\n      \"username\": \"iteles\"\n    },\n    {\n      \"avatar\": \"https://avatars3.githubusercontent.com/u/5038030?s=88&v=4\",\n      \"uid\": 5038030,\n      \"username\": \"tunnckoCore\"\n    }\n  ],\n  \"url\": \"/dwyl/health/stargazers\",\n  \"type\": \"stars\"\n}"
  },
  {
    "path": "test/server.test.js",
    "content": "const test = require('tap').test;\nconst server = require('../server/server.js');\nconst request = require('supertest')(server.url);\n\ntest('connect to server', function (t) {\n  request.get('/')\n    .expect(200)\n    .end(function(err, res) {\n      if (err) throw err;\n      server.close(function () { console.log('Server closed!'); });\n      t.end()\n    });\n});\n"
  },
  {
    "path": "test/utils.test.js",
    "content": "const tap = require('tap'); // see: github.com/dwyl/learn-tape\nconst utils = require('../server/utils');\n\ntap.test('utils.log_error', function testfn (t) {\n  const error = 'DON\\'T PANIC! This is only a utils.log_error test execution ☔️'\n  utils.log_error(error, { \"hello\": \"world\"}, new Error().stack);\n  utils.log_error(error, { \"hello\": \"world\"});\n  t.end();\n});\n\ntap.test('utils.exec_cb', function(t) {\n  const error = 'DON\\'T PANIC! This is only a utils.exec_cb test ☔️ '\n  // call without params:\n  utils.exec_cb(); // no expectation but also no error!\n  utils.exec_cb(function callback (e, data) {\n    t.equal(e, error, 'woohoo our exec_cb works as expected!');\n    t.equal(data, 'hai', 'exec_cb simply executes the callback');\n    t.end();\n  }, error, 'hai');\n});\n\ntap.test('utils.recent_activity', function(t) {\n  const person = require('./fixtures/person.json');\n  const recent_activity = utils.recent_activity(person);\n  t.ok(recent_activity > 0, 'recent_activity: ' + recent_activity)\n  t.end();\n});\n"
  },
  {
    "path": "tutorial.md",
    "content": "# Why?\n\nThis is a self-contained tutorial for people learning\nPostgreSQL for the _first_ time.\n\n\n\n## People\n\n\npsql -U postgres -d codeface -a -f schema.sql\n"
  }
]