├── Procfile ├── package.json ├── wsgi.py ├── Pipfile ├── static ├── pilcrow.css ├── hljs-github.min.css └── style.css ├── api ├── __init__.py ├── schema.py └── routes.py ├── LICENSE ├── documentation.yaml ├── README.md ├── .gitignore ├── templates └── index.html └── Pipfile.lock /Procfile: -------------------------------------------------------------------------------- 1 | web: gunicorn wsgi:app --log-file=- 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "docusaurus-plugin-openapi": "^0.0.16" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /wsgi.py: -------------------------------------------------------------------------------- 1 | from api import create_app 2 | 3 | app = create_app() 4 | 5 | if __name__ == "__main__": 6 | app.run() 7 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | flask = "*" 8 | mtaa = "*" 9 | flask-cors = "*" 10 | flask-graphql = "*" 11 | graphene = "*" 12 | gunicorn = "*" 13 | 14 | [dev-packages] 15 | 16 | [scripts] 17 | dev = "env FLASK_APP=wsgi env FLASK_ENV=development flask run" 18 | # prod = "env FLASK_APP=wsgi env FLASK_ENV=production gunicorn --bind 0.0.0.0:5000 -w 8 wsgi:app --timeout 10000" 19 | 20 | [requires] 21 | python_version = "3.8" 22 | -------------------------------------------------------------------------------- /static/pilcrow.css: -------------------------------------------------------------------------------- 1 | h1, 2 | h2, 3 | h3, 4 | h4, 5 | h5, 6 | h6 { 7 | position: relative; 8 | } 9 | 10 | h1:hover .header-link:before, 11 | h2:hover .header-link:before, 12 | h3:hover .header-link:before, 13 | h4:hover .header-link:before, 14 | h5:hover .header-link:before, 15 | h6:hover .header-link:before { 16 | content: "\00B6";/* pilcrow */ 17 | color: #888; 18 | font-size: smaller; 19 | } 20 | 21 | .header-link { 22 | -webkit-user-select: none; 23 | -moz-user-select: none; 24 | -ms-user-select: none; 25 | user-select: none; 26 | 27 | position: absolute; 28 | top: 0; 29 | left: -0.7em; 30 | display: block; 31 | padding-right: 1em; 32 | } 33 | 34 | h1:hover .header-link, 35 | h2:hover .header-link, 36 | h3:hover .header-link, 37 | h4:hover .header-link, 38 | h5:hover .header-link, 39 | h6:hover .header-link { 40 | display: inline-block; 41 | text-decoration: none; 42 | } 43 | -------------------------------------------------------------------------------- /api/__init__.py: -------------------------------------------------------------------------------- 1 | from os import system, urandom, path, getcwd 2 | from flask import Flask 3 | from flask_cors import CORS 4 | 5 | # from flask_graphql import GraphQLView 6 | 7 | 8 | def create_app(): 9 | app = Flask( 10 | __name__, 11 | template_folder=path.join(getcwd(), "templates"), 12 | static_folder=path.join(getcwd(), "static"), 13 | ) 14 | app.config["SECRET_KEY"] = urandom(64) 15 | 16 | CORS(app) 17 | 18 | # Temporarily commenting out GraphQL till I can get it to work again 19 | # from api.schema import schema 20 | # # Optional, for adding batch query support (used in Apollo-Client) 21 | # app.add_url_rule( 22 | # '/api/graphql', 23 | # view_func=GraphQLView.as_view( 24 | # 'graphql', 25 | # schema=schema, 26 | # graphql_schema=schema, 27 | # graphiql=True, 28 | # batch=True 29 | # ) 30 | # ) 31 | 32 | with app.app_context(): 33 | from . import routes 34 | 35 | return app 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Ano Rebel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /documentation.yaml: -------------------------------------------------------------------------------- 1 | swagger: "2.0" 2 | info: 3 | description: | 4 | Mtaa API Documentation , Mtaa api is an api for 5 | accessing easy accessing locations across tanzania 6 | from regions to streets. 7 | 8 | The source code can be found at [mtaaAPI](https://github.com/HackEAC/mtaaAPI) 9 | version: 1.5 10 | title: mtaaAPI 11 | contact: 12 | email: kalebjordan.kj@gmail.com 13 | license: 14 | name: MIT 15 | url: https://github.com/HackEAC/mtaaAPI/blob/main/LICENSE 16 | 17 | paths: 18 | /api/tanzania: 19 | get: 20 | summary: Return all regions in a country (Tanzania) 21 | produces: 22 | - application/json 23 | responses: 24 | 200: 25 | description: OK 26 | examples: 27 | application/json: 28 | { "regions": ["Shinyanga", "Mara", "....", "Tabora", "Dodoma"] } 29 | /api/tanzania/{region}: 30 | get: 31 | summary: Returns all districts in a specified region 32 | produces: 33 | - application/json 34 | parameters: 35 | name: region 36 | responses: 37 | 200: 38 | description: OK 39 | examples: 40 | application/json: {} 41 | -------------------------------------------------------------------------------- /api/schema.py: -------------------------------------------------------------------------------- 1 | from graphene import ObjectType, String, Schema, List, NonNull, Field 2 | from mtaa import tanzania 3 | import mtaa 4 | 5 | 6 | class Street(ObjectType): 7 | names = List(String) 8 | 9 | 10 | class Ward(ObjectType): 11 | post_code = String(required=True) 12 | streets = List(Street) 13 | 14 | 15 | class District(ObjectType): 16 | post_code = String(required=True) 17 | wards = List(Ward) 18 | 19 | 20 | class Region(ObjectType): 21 | post_code = String(required=True) 22 | districts = List(District) 23 | 24 | 25 | class Tanzania(ObjectType): 26 | regions = List(String) 27 | districts = List(String) 28 | wards = List(String) 29 | streets = List(String) 30 | 31 | def resolve_regions(root, info): 32 | return mtaa.regions 33 | 34 | def resolve_districts(root, info): 35 | return mtaa.districts 36 | 37 | def resolve_wards(root, info): 38 | return mtaa.wards 39 | 40 | def resolve_streets(root, info): 41 | return mtaa.streets 42 | 43 | 44 | class Query(ObjectType): 45 | hello = String( 46 | required=True, 47 | # name=String(default_value='World') 48 | ) 49 | tanzania = Field(Tanzania) 50 | 51 | def resolve_hello(parent, info, name): 52 | return f'Hello, {name}!' 53 | 54 | 55 | schema = Schema(query=Query) 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mtaa API 2 | 3 | [Mtaa API](https://mtaa-api.herokuapp.com) is a simple REST(And later GraphQL) API for accessing Tanzania locations. 4 | 5 | Mtaa API is powered by [mtaa](https://github.com/Kalebu/mtaa). 6 | 7 | ## Why? 8 | 9 | Most of the time when coding, we found ourselves in need of some location data, especially for our country, [Tanzania](https://en.wikipedia.org/wiki/Tanzania). 10 | 11 | We didn't like the idea of scraping some public sites because we had the feeling that we was spending more time understanding the site and cleaning the data than focusing on my task. 12 | 13 | But we liked the idea of a public API for developers. So I decided to code a little Flask server inspired by our [tanzania-locations-db](https://github.com/HackEAC/tanzania-locations-db), [mtaa](https://github.com/Kalebu/mtaa) and here is Mtaa API. 14 | 15 | You can find it running here and are free to use it in your developments: [https://mtaa-api.herokuapp.com/api](https://mtaa-api.herokuapp.com/api). 16 | 17 | I hope you will find it useful. 18 | 19 | ## Features 20 | 21 | * No registration 22 | * Zero-config 23 | * Basic API 24 | * Cross-domain ([CORS](http://en.wikipedia.org/wiki/Cross-origin_resource_sharing)) 25 | * Supports GET and POST(GraphQL) verbs 26 | * Compatible with React, Angular, Vue, Ember, ... 27 | 28 | 29 | 30 | 31 | ## Guide 32 | 33 | For examples and more, documentation you can visit https://mtaa-api.herokuapp.com/docs -------------------------------------------------------------------------------- /static/hljs-github.min.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | github.com style (c) Vasily Polovnyov 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | color: #333; 12 | background: #f8f8f8; 13 | -webkit-text-size-adjust: none; 14 | } 15 | 16 | .hljs-comment, 17 | .diff .hljs-header, 18 | .hljs-javadoc { 19 | color: #998; 20 | font-style: italic; 21 | } 22 | 23 | .hljs-keyword, 24 | .css .rule .hljs-keyword, 25 | .hljs-winutils, 26 | .nginx .hljs-title, 27 | .hljs-subst, 28 | .hljs-request, 29 | .hljs-status { 30 | color: #333; 31 | font-weight: bold; 32 | } 33 | 34 | .hljs-number, 35 | .hljs-hexcolor, 36 | .ruby .hljs-constant { 37 | color: #008080; 38 | } 39 | 40 | .hljs-string, 41 | .hljs-tag .hljs-value, 42 | .hljs-phpdoc, 43 | .hljs-dartdoc, 44 | .tex .hljs-formula { 45 | color: #d14; 46 | } 47 | 48 | .hljs-title, 49 | .hljs-id, 50 | .scss .hljs-preprocessor { 51 | color: #900; 52 | font-weight: bold; 53 | } 54 | 55 | .hljs-list .hljs-keyword, 56 | .hljs-subst { 57 | font-weight: normal; 58 | } 59 | 60 | .hljs-class .hljs-title, 61 | .hljs-type, 62 | .vhdl .hljs-literal, 63 | .tex .hljs-command { 64 | color: #458; 65 | font-weight: bold; 66 | } 67 | 68 | .hljs-tag, 69 | .hljs-tag .hljs-title, 70 | .hljs-rules .hljs-property, 71 | .django .hljs-tag .hljs-keyword { 72 | color: #000080; 73 | font-weight: normal; 74 | } 75 | 76 | .hljs-attribute, 77 | .hljs-variable, 78 | .lisp .hljs-body { 79 | color: #008080; 80 | } 81 | 82 | .hljs-regexp { 83 | color: #009926; 84 | } 85 | 86 | .hljs-symbol, 87 | .ruby .hljs-symbol .hljs-string, 88 | .lisp .hljs-keyword, 89 | .clojure .hljs-keyword, 90 | .scheme .hljs-keyword, 91 | .tex .hljs-special, 92 | .hljs-prompt { 93 | color: #990073; 94 | } 95 | 96 | .hljs-built_in { 97 | color: #0086b3; 98 | } 99 | 100 | .hljs-preprocessor, 101 | .hljs-pragma, 102 | .hljs-pi, 103 | .hljs-doctype, 104 | .hljs-shebang, 105 | .hljs-cdata { 106 | color: #999; 107 | font-weight: bold; 108 | } 109 | 110 | .hljs-deletion { 111 | background: #fdd; 112 | } 113 | 114 | .hljs-addition { 115 | background: #dfd; 116 | } 117 | 118 | .diff .hljs-change { 119 | background: #0086b3; 120 | } 121 | 122 | .hljs-chunk { 123 | color: #aaa; 124 | } 125 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/flask 2 | # Edit at https://www.gitignore.io/?templates=flask 3 | 4 | ### Flask ### 5 | instance/* 6 | !instance/.gitignore 7 | .webassets-cache 8 | 9 | ### Flask.Python Stack ### 10 | # Byte-compiled / optimized / DLL files 11 | __pycache__/ 12 | *.py[cod] 13 | *$py.class 14 | 15 | # C extensions 16 | *.so 17 | 18 | # Distribution / packaging 19 | .Python 20 | build/ 21 | develop-eggs/ 22 | dist/ 23 | downloads/ 24 | eggs/ 25 | .eggs/ 26 | lib/ 27 | lib64/ 28 | parts/ 29 | sdist/ 30 | var/ 31 | wheels/ 32 | pip-wheel-metadata/ 33 | share/python-wheels/ 34 | *.egg-info/ 35 | .installed.cfg 36 | *.egg 37 | MANIFEST 38 | 39 | # PyInstaller 40 | # Usually these files are written by a python script from a template 41 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 42 | *.manifest 43 | *.spec 44 | 45 | # Installer logs 46 | pip-log.txt 47 | pip-delete-this-directory.txt 48 | 49 | # Unit test / coverage reports 50 | htmlcov/ 51 | .tox/ 52 | .nox/ 53 | .coverage 54 | .coverage.* 55 | .cache 56 | nosetests.xml 57 | coverage.xml 58 | *.cover 59 | .hypothesis/ 60 | .pytest_cache/ 61 | 62 | # Translations 63 | *.mo 64 | *.pot 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # pipenv 79 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 80 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 81 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 82 | # install all needed dependencies. 83 | #Pipfile.lock 84 | 85 | # celery beat schedule file 86 | celerybeat-schedule 87 | 88 | # SageMath parsed files 89 | *.sage.py 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | .spyproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | # Mr Developer 99 | .mr.developer.cfg 100 | .project 101 | .pydevproject 102 | 103 | # mkdocs documentation 104 | /site 105 | 106 | # mypy 107 | .mypy_cache/ 108 | .dmypy.json 109 | dmypy.json 110 | 111 | # Pyre type checker 112 | .pyre/ 113 | 114 | # End of https://www.gitignore.io/api/flask 115 | 116 | .vscode 117 | node_modules 118 | .idea 119 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Mtaa API 5 | 6 | 7 | 11 | 15 | 19 | 20 | 21 |

Mtaa API

22 |

23 | Mtaa API is a simple REST(And 24 | later GraphQL) API for accessing Tanzania locations. 25 |

26 |

27 | Mtaa API is powered by mtaa. 28 |

29 |

Why?

30 |

31 | Most of the time when coding, we found ourselves in need of some location 32 | data, especially for our country, 33 | Tanzania. 34 |

35 |

36 | We didn't like the idea of scraping some public sites because we had 37 | the feeling that we was spending more time understanding the site and 38 | cleaning the data than focusing on my task. 39 |

40 |

41 | But we liked the idea of a public API for developers. So I decided to code 42 | a little Flask server inspired by our 43 | tanzania-locations-db, mtaa and here is Mtaa API. 46 |

47 |

48 | You can find it running here and are free to use it in your developments: 49 | https://mtaa-api.herokuapp.com/api. 52 |

53 |

I hope you will find it useful.

54 |

Features

55 | 72 |

Guide

73 |

74 | For examples and more, documentation you can visit https://mtaa-api.herokuapp.com/docs 75 |

76 | 77 | 78 | -------------------------------------------------------------------------------- /api/routes.py: -------------------------------------------------------------------------------- 1 | import mtaa 2 | from flask import current_app as app 3 | from flask import jsonify, render_template, redirect 4 | from mtaa import tanzania 5 | from typing import Dict 6 | 7 | 8 | LEVELS = { 9 | "regions": mtaa.regions, 10 | "districts": mtaa.districts, 11 | "wards": mtaa.wards, 12 | "streets": mtaa.streets, 13 | } 14 | 15 | 16 | def get_postcode(payload, level): 17 | return getattr(payload, level, "") 18 | 19 | 20 | @app.get("/") 21 | def home(): 22 | return render_template("index.html") 23 | 24 | 25 | @app.get("/docs") 26 | def api(): 27 | return redirect("https://app.swaggerhub.com/apis/Kalebu/mtaa-api_documentation/1.0") 28 | 29 | 30 | @app.get("/api/all/") 31 | def get_all(level): 32 | return jsonify(LEVELS.get(level, {})) 33 | 34 | 35 | @app.get("/api/tanzania") 36 | def tanzan(): 37 | """ 38 | Returns a list of all the regions in Tanzania 39 | """ 40 | return jsonify({"regions": list(tanzania)}) 41 | 42 | 43 | @app.get("/api/tanzania/") 44 | def regions(region: str) -> Dict: 45 | """ 46 | Returns a list of all the districts in the given Region and the region's post code 47 | """ 48 | region: str = region.lower().capitalize() 49 | payload = tanzania.get(region) 50 | if not payload: 51 | return jsonify({}) 52 | postcode = get_postcode(payload, "post_code") 53 | return jsonify({"post_code": postcode, "districts": list(payload.districts)}) 54 | 55 | 56 | @app.get("/api/tanzania//") 57 | def districts(region: str, district: str) -> Dict: 58 | """ 59 | Returns a list of all the wards in the given District and the district's post code 60 | """ 61 | reg: str = region.lower().capitalize() 62 | dist: str = district.lower().capitalize() 63 | try: 64 | payload = tanzania.get(reg).districts.get(dist) 65 | postcode = get_postcode(payload, "district_post_code") 66 | wards = list(payload.wards) 67 | wards.remove("ward_post_code") 68 | return jsonify({"post_code": postcode, "wards": wards}) 69 | except Exception as bug: 70 | print(bug) 71 | return jsonify({}) 72 | 73 | 74 | @app.get("/api/tanzania///") 75 | def wards(region: str, district: str, ward: str) -> Dict: 76 | """ 77 | Returns a list of all the streets in the given Ward and the ward's post code, if any 78 | """ 79 | reg: str = region.lower().capitalize() 80 | dist: str = district.lower().capitalize() 81 | ward: str = ward.lower().capitalize() 82 | try: 83 | payload = tanzania.get(reg).districts.get(dist).wards.get(ward) 84 | post_code = get_postcode(payload, "ward_post_code") 85 | streets = list(payload.streets) 86 | return jsonify({"post_code": post_code, "streets": streets}) 87 | except Exception as bug: 88 | print(bug) 89 | return jsonify({}) 90 | 91 | 92 | @app.get("/api/tanzania////") 93 | def streets(region: str, district: str, ward: str, street: str) -> Dict: 94 | reg: str = region.lower().capitalize() 95 | dist: str = district.lower().capitalize() 96 | ward: str = ward.lower().capitalize() 97 | temp: str = street.lower().capitalize() 98 | try: 99 | payload = ( 100 | tanzania.get(reg).districts.get(dist).wards.get(ward).streets.get(temp) 101 | ) 102 | return jsonify({"more": payload}) 103 | except Exception as bug: 104 | print(bug) 105 | return jsonify({}) 106 | 107 | 108 | @app.errorhandler(404) 109 | def handle_404(error_message): 110 | return jsonify({"response": str(error_message)}), 404 111 | -------------------------------------------------------------------------------- /static/style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | background: #000; 3 | font-family: Georgia, Palatino, serif; 4 | color: #EEE; 5 | line-height: 1; 6 | padding: 30px; 7 | margin:auto; 8 | max-width:42em; 9 | } 10 | h1, h2, h3, h4 { 11 | font-weight: 400; 12 | } 13 | h1, h2, h3, h4, h5, p { 14 | margin-bottom: 24px; 15 | padding: 0; 16 | } 17 | h1 { 18 | font-size: 48px; 19 | } 20 | h2 { 21 | font-size: 36px; 22 | margin: 24px 0 6px; 23 | } 24 | h3 { 25 | font-size: 24px; 26 | } 27 | h4 { 28 | font-size: 21px; 29 | } 30 | h5 { 31 | font-size: 18px; 32 | } 33 | a { 34 | color: #61BFC1; 35 | margin: 0; 36 | padding: 0; 37 | text-decoration: none; 38 | vertical-align: baseline; 39 | } 40 | a:hover { 41 | text-decoration: underline; 42 | } 43 | a:visited { 44 | color: #466B6C; 45 | } 46 | ul, ol { 47 | padding: 0; 48 | margin: 0; 49 | } 50 | li { 51 | line-height: 24px; 52 | } 53 | li ul, li ul { 54 | margin-left: 24px; 55 | } 56 | p, ul, ol { 57 | font-size: 16px; 58 | line-height: 24px; 59 | max-width: 540px; 60 | } 61 | pre { 62 | padding: 0px 24px; 63 | max-width: 800px; 64 | white-space: pre-wrap; 65 | } 66 | code { 67 | font-family: Consolas, Monaco, Andale Mono, monospace; 68 | line-height: 1.5; 69 | font-size: 13px; 70 | } 71 | aside { 72 | display: block; 73 | float: right; 74 | width: 390px; 75 | } 76 | blockquote { 77 | border-left:.5em solid #eee; 78 | padding: 0 2em; 79 | margin-left:0; 80 | max-width: 476px; 81 | } 82 | blockquote cite { 83 | font-size:14px; 84 | line-height:20px; 85 | color:#bfbfbf; 86 | } 87 | blockquote cite:before { 88 | content: '\2014 \00A0'; 89 | } 90 | 91 | blockquote p { 92 | color: #666; 93 | max-width: 460px; 94 | } 95 | hr { 96 | width: 540px; 97 | text-align: left; 98 | margin: 0 auto 0 0; 99 | color: #999; 100 | } 101 | 102 | /* Code below this line is copyright Twitter Inc. */ 103 | 104 | button, 105 | input, 106 | select, 107 | textarea { 108 | font-size: 100%; 109 | margin: 0; 110 | vertical-align: baseline; 111 | *vertical-align: middle; 112 | } 113 | button, input { 114 | line-height: normal; 115 | *overflow: visible; 116 | } 117 | button::-moz-focus-inner, input::-moz-focus-inner { 118 | border: 0; 119 | padding: 0; 120 | } 121 | button, 122 | input[type="button"], 123 | input[type="reset"], 124 | input[type="submit"] { 125 | cursor: pointer; 126 | -webkit-appearance: button; 127 | } 128 | input[type=checkbox], input[type=radio] { 129 | cursor: pointer; 130 | } 131 | /* override default chrome & firefox settings */ 132 | input:not([type="image"]), textarea { 133 | -webkit-box-sizing: content-box; 134 | -moz-box-sizing: content-box; 135 | box-sizing: content-box; 136 | } 137 | 138 | input[type="search"] { 139 | -webkit-appearance: textfield; 140 | -webkit-box-sizing: content-box; 141 | -moz-box-sizing: content-box; 142 | box-sizing: content-box; 143 | } 144 | input[type="search"]::-webkit-search-decoration { 145 | -webkit-appearance: none; 146 | } 147 | label, 148 | input, 149 | select, 150 | textarea { 151 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 152 | font-size: 13px; 153 | font-weight: normal; 154 | line-height: normal; 155 | margin-bottom: 18px; 156 | } 157 | input[type=checkbox], input[type=radio] { 158 | cursor: pointer; 159 | margin-bottom: 0; 160 | } 161 | input[type=text], 162 | input[type=password], 163 | textarea, 164 | select { 165 | display: inline-block; 166 | width: 210px; 167 | padding: 4px; 168 | font-size: 13px; 169 | font-weight: normal; 170 | line-height: 18px; 171 | height: 18px; 172 | color: #808080; 173 | border: 1px solid #ccc; 174 | -webkit-border-radius: 3px; 175 | -moz-border-radius: 3px; 176 | border-radius: 3px; 177 | } 178 | select, input[type=file] { 179 | height: 27px; 180 | line-height: 27px; 181 | } 182 | textarea { 183 | height: auto; 184 | } 185 | 186 | /* grey out placeholders */ 187 | :-moz-placeholder { 188 | color: #bfbfbf; 189 | } 190 | ::-webkit-input-placeholder { 191 | color: #bfbfbf; 192 | } 193 | 194 | input[type=text], 195 | input[type=password], 196 | select, 197 | textarea { 198 | -webkit-transition: border linear 0.2s, box-shadow linear 0.2s; 199 | -moz-transition: border linear 0.2s, box-shadow linear 0.2s; 200 | transition: border linear 0.2s, box-shadow linear 0.2s; 201 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1); 202 | -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1); 203 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1); 204 | } 205 | input[type=text]:focus, input[type=password]:focus, textarea:focus { 206 | outline: none; 207 | border-color: rgba(82, 168, 236, 0.8); 208 | -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6); 209 | -moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6); 210 | box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6); 211 | } 212 | 213 | /* buttons */ 214 | button { 215 | display: inline-block; 216 | padding: 4px 14px; 217 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 218 | font-size: 13px; 219 | line-height: 18px; 220 | -webkit-border-radius: 4px; 221 | -moz-border-radius: 4px; 222 | border-radius: 4px; 223 | -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); 224 | -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); 225 | box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); 226 | background-color: #0064cd; 227 | background-repeat: repeat-x; 228 | background-image: -khtml-gradient(linear, left top, left bottom, from(#049cdb), to(#0064cd)); 229 | background-image: -moz-linear-gradient(top, #049cdb, #0064cd); 230 | background-image: -ms-linear-gradient(top, #049cdb, #0064cd); 231 | background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #049cdb), color-stop(100%, #0064cd)); 232 | background-image: -webkit-linear-gradient(top, #049cdb, #0064cd); 233 | background-image: -o-linear-gradient(top, #049cdb, #0064cd); 234 | background-image: linear-gradient(top, #049cdb, #0064cd); 235 | color: #fff; 236 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 237 | border: 1px solid #004b9a; 238 | border-bottom-color: #003f81; 239 | -webkit-transition: 0.1s linear all; 240 | -moz-transition: 0.1s linear all; 241 | transition: 0.1s linear all; 242 | border-color: #0064cd #0064cd #003f81; 243 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 244 | } 245 | button:hover { 246 | color: #fff; 247 | background-position: 0 -15px; 248 | text-decoration: none; 249 | } 250 | button:active { 251 | -webkit-box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); 252 | -moz-box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); 253 | box-shadow: inset 0 3px 7px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05); 254 | } 255 | button::-moz-focus-inner { 256 | padding: 0; 257 | border: 0; 258 | } 259 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "d79a2118953e8e24149e369f82cce688e6c53887629755375192996eefdec528" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.8" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": { 19 | "aniso8601": { 20 | "hashes": [ 21 | "sha256:513d2b6637b7853806ae79ffaca6f3e8754bdd547048f5ccc1420aec4b714f1e", 22 | "sha256:d10a4bf949f619f719b227ef5386e31f49a2b6d453004b21f02661ccc8670c7b" 23 | ], 24 | "version": "==7.0.0" 25 | }, 26 | "click": { 27 | "hashes": [ 28 | "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a", 29 | "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6" 30 | ], 31 | "markers": "python_version >= '3.6'", 32 | "version": "==8.0.1" 33 | }, 34 | "flask": { 35 | "hashes": [ 36 | "sha256:1c4c257b1892aec1398784c63791cbaa43062f1f7aeb555c4da961b20ee68f55", 37 | "sha256:a6209ca15eb63fc9385f38e452704113d679511d9574d09b2cf9183ae7d20dc9" 38 | ], 39 | "index": "pypi", 40 | "version": "==2.0.1" 41 | }, 42 | "flask-cors": { 43 | "hashes": [ 44 | "sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438", 45 | "sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de" 46 | ], 47 | "index": "pypi", 48 | "version": "==3.0.10" 49 | }, 50 | "flask-graphql": { 51 | "hashes": [ 52 | "sha256:825578c044df436cd74503a38bbd31c919a90acda5e9b6e0e45736964bc5235d" 53 | ], 54 | "index": "pypi", 55 | "version": "==2.0.1" 56 | }, 57 | "graphene": { 58 | "hashes": [ 59 | "sha256:09165f03e1591b76bf57b133482db9be6dac72c74b0a628d3c93182af9c5a896", 60 | "sha256:2cbe6d4ef15cfc7b7805e0760a0e5b80747161ce1b0f990dfdc0d2cf497c12f9" 61 | ], 62 | "index": "pypi", 63 | "version": "==2.1.8" 64 | }, 65 | "graphql-core": { 66 | "hashes": [ 67 | "sha256:44c9bac4514e5e30c5a595fac8e3c76c1975cae14db215e8174c7fe995825bad", 68 | "sha256:aac46a9ac524c9855910c14c48fc5d60474def7f99fd10245e76608eba7af746" 69 | ], 70 | "version": "==2.3.2" 71 | }, 72 | "graphql-relay": { 73 | "hashes": [ 74 | "sha256:870b6b5304123a38a0b215a79eace021acce5a466bf40cd39fa18cb8528afabb", 75 | "sha256:ac514cb86db9a43014d7e73511d521137ac12cf0101b2eaa5f0a3da2e10d913d" 76 | ], 77 | "version": "==2.0.1" 78 | }, 79 | "graphql-server-core": { 80 | "hashes": [ 81 | "sha256:04ee90da0322949f7b49ff6905688e3a21a9efbd5a7d7835997e431a0afdbd11" 82 | ], 83 | "version": "==1.2.0" 84 | }, 85 | "gunicorn": { 86 | "hashes": [ 87 | "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e", 88 | "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8" 89 | ], 90 | "index": "pypi", 91 | "version": "==20.1.0" 92 | }, 93 | "itsdangerous": { 94 | "hashes": [ 95 | "sha256:5174094b9637652bdb841a3029700391451bd092ba3db90600dea710ba28e97c", 96 | "sha256:9e724d68fc22902a1435351f84c3fb8623f303fffcc566a4cb952df8c572cff0" 97 | ], 98 | "markers": "python_version >= '3.6'", 99 | "version": "==2.0.1" 100 | }, 101 | "jinja2": { 102 | "hashes": [ 103 | "sha256:1f06f2da51e7b56b8f238affdd6b4e2c61e39598a378cc49345bc1bd42a978a4", 104 | "sha256:703f484b47a6af502e743c9122595cc812b0271f661722403114f71a79d0f5a4" 105 | ], 106 | "markers": "python_version >= '3.6'", 107 | "version": "==3.0.1" 108 | }, 109 | "markupsafe": { 110 | "hashes": [ 111 | "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298", 112 | "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64", 113 | "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b", 114 | "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567", 115 | "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff", 116 | "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74", 117 | "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35", 118 | "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26", 119 | "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7", 120 | "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75", 121 | "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f", 122 | "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135", 123 | "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8", 124 | "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a", 125 | "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914", 126 | "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18", 127 | "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8", 128 | "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2", 129 | "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d", 130 | "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b", 131 | "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f", 132 | "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb", 133 | "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833", 134 | "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415", 135 | "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902", 136 | "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9", 137 | "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d", 138 | "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066", 139 | "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f", 140 | "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5", 141 | "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94", 142 | "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509", 143 | "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51", 144 | "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872" 145 | ], 146 | "markers": "python_version >= '3.6'", 147 | "version": "==2.0.1" 148 | }, 149 | "mtaa": { 150 | "hashes": [ 151 | "sha256:7ba89c0674fee198c16a249a4318762d1839a9311ad159e3698fb5c7d28508ec", 152 | "sha256:c70acea1793883c7fe905f41be76345bee82120628d11c4278c8f524c3f7e9ce" 153 | ], 154 | "index": "pypi", 155 | "version": "==1.5" 156 | }, 157 | "promise": { 158 | "hashes": [ 159 | "sha256:dfd18337c523ba4b6a58801c164c1904a9d4d1b1747c7d5dbf45b693a49d93d0" 160 | ], 161 | "version": "==2.3" 162 | }, 163 | "rx": { 164 | "hashes": [ 165 | "sha256:13a1d8d9e252625c173dc795471e614eadfe1cf40ffc684e08b8fff0d9748c23", 166 | "sha256:7357592bc7e881a95e0c2013b73326f704953301ab551fbc8133a6fadab84105" 167 | ], 168 | "version": "==1.6.1" 169 | }, 170 | "six": { 171 | "hashes": [ 172 | "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", 173 | "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" 174 | ], 175 | "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", 176 | "version": "==1.16.0" 177 | }, 178 | "werkzeug": { 179 | "hashes": [ 180 | "sha256:1de1db30d010ff1af14a009224ec49ab2329ad2cde454c8a708130642d579c42", 181 | "sha256:6c1ec500dcdba0baa27600f6a22f6333d8b662d22027ff9f6202e3367413caa8" 182 | ], 183 | "markers": "python_version >= '3.6'", 184 | "version": "==2.0.1" 185 | } 186 | }, 187 | "develop": {} 188 | } 189 | --------------------------------------------------------------------------------