merge docs into main repo (close #397) (#398)

This commit is contained in:
Shahidh K Muhammed 2018-09-11 16:41:24 +05:30 committed by GitHub
parent 9c7c650d2b
commit d3d9845497
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
240 changed files with 63928 additions and 0 deletions

View File

@ -2,6 +2,7 @@
LICENSE
scripts/*
assets/*
docs/*
.circleci/*
.ciignore
.gitignore

57
docs/.gitignore vendored Normal file
View File

@ -0,0 +1,57 @@
sample.py
sample.json
*.sw*
*.pyc
# sphinx build folder
_build
# for folks using pipenv
Pipfile
Pipfile.lock
# Compiled source #
###################
.DS_Store
*.com
*.class
*.dll
*.exe
*.o
*.so
# Packages #
############
# it's better to unpack these files and commit the raw source
# git has its own built in compression methods
*.7z
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip
# Logs and databases #
######################
*.log
*.sqlite
# OS generated files #
######################
.DS_Store?
ehthumbs.db
Icon?
Thumbs.db
# Editor backup files #
#######################
*~
# Virtual Environment
venv
.venv
# Jetbrains folder
.idea

6
docs/404.rst Normal file
View File

@ -0,0 +1,6 @@
.. title:: 404 - Page Not Found
404 - Page Not Found
---------------------
**Hey! It seems the page you are looking for doesn't exist or has been moved.**

52
docs/CONTRIBUTING.md Normal file
View File

@ -0,0 +1,52 @@
# Contributing
[Sphinx](http://www.sphinx-doc.org/en/master/documentation) files are written in
the RST markup language. Here is a [guide to RST markup
language](http://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html).
## Requirements
- [Python 3](https://www.python.org/downloads/)
- [Pip for Python 3](https://pip.pypa.io/en/stable/installing/)
## Steps
- Fork the repo and clone it:
```bash
git clone https://github.com/<your-username>/graphql-engine
```
- Move to `docs` folder and checkout to a new branch:
```bash
cd docs
git checkout -b <new-branch-name>
```
- Install dependencies (Sphinx, beautifulsoup4, algoliasearch, etc.)
```
pip3 install -r requirements.txt
```
- For development, live reload and auto build while you're editing and saving
files:
```bash
make livehtml
```
- Make the required changes.
- (Optional) Build docs to produce HTML files and verify:
```
ENV=<development|production> make html-images
```
* The generated docs are in `_build/html`.
* View the built files by running a webserver. egg:
```
cd _build/html && http-server
```
or
```
cd _build/html && python3 -m http.server 8080
```
- Commit the changes. Follow common guidelines for commit messages at [main
contributing guide](../CONTRIBUTING.md#common-guidelines).
- Push the changes to your fork and submit a pull request.
**Note:** The search is powered by [Algolia](https://www.algolia.com/) and is updated on every deployment. Your local
changes will not reflect in search results.

21
docs/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Hasura Technologies Private Limited.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

264
docs/Makefile Normal file
View File

@ -0,0 +1,264 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR ?= _build
BUILDVERSION ?= "x.y"
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " applehelp to make an Apple Help Book"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " epub3 to make an epub3"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
@echo " coverage to run coverage check of the documentation (if enabled)"
@echo " dummy to check syntax errors of document sources"
.PHONY: clean
clean:
rm -rf $(BUILDDIR)/*
.PHONY: release-images
release-images:
cp -r img/* $(BUILDDIR)/html/_images/
@echo "Image copy finished. The images are in $(BUILDDIR)/html/_images"
.PHONY: html
html:
$(SPHINXBUILD) -a -b html $(ALLSPHINXOPTS) -D version=$(BUILDVERSION) -D release=$(BUILDVERSION) -A latest_docs_version="1.0" $(BUILDDIR)/html
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
.PHONY: html-images
html-images: html release-images
.PHONY: build-release
build-release: clean
script/generate-images.sh
# Function to check whether a particular variable is set or not
check_defined = \
$(strip $(foreach 1,$1, \
$(call __check_defined,$1,$(strip $(value 2)))))
__check_defined = \
$(if $(value $1),, \
$(error Undefined $1$(if $2, ($2))))
.PHONY: algolia_index
algolia_index:
# Checking if all the variables are available
$(call check_defined, ALGOLIA_APPLICATION_ID)
$(call check_defined, ALGOLIA_ADMIN_KEY)
$(call check_defined, ALGOLIA_INDEX_NAME)
export ALGOLIA_APPLICATION_ID=${ALGOLIA_APPLICATION_ID} ALGOLIA_ADMIN_KEY=${ALGOLIA_ADMIN_KEY} ALGOLIA_INDEX_NAME=${ALGOLIA_INDEX_NAME}
python ./algolia_index/algolia_index.py _build/algolia_index/index.json
.PHONY: dirhtml
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
.PHONY: singlehtml
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
.PHONY: pickle
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
.PHONY: json
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
.PHONY: htmlhelp
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
.PHONY: qthelp
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/HasuraPlatform.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/HasuraPlatform.qhc"
.PHONY: applehelp
applehelp:
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
@echo
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
@echo "N.B. You won't be able to view it unless you put it in" \
"~/Library/Documentation/Help or install it in your application" \
"bundle."
.PHONY: devhelp
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/HasuraPlatform"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/HasuraPlatform"
@echo "# devhelp"
.PHONY: epub
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
.PHONY: epub3
epub3:
$(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
@echo
@echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
.PHONY: latex
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
.PHONY: latexpdf
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
.PHONY: latexpdfja
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
.PHONY: text
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
.PHONY: man
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
.PHONY: texinfo
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
.PHONY: info
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
.PHONY: gettext
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
.PHONY: changes
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
.PHONY: linkcheck
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
.PHONY: doctest
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
.PHONY: coverage
coverage:
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
@echo "Testing of coverage in the sources finished, look at the " \
"results in $(BUILDDIR)/coverage/python.txt."
.PHONY: xml
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
.PHONY: pseudoxml
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
.PHONY: dummy
dummy:
$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
@echo
@echo "Build finished. Dummy builder generates no files."
watch:
inotifywait -q -m --recursive -e modify -e move -e create -e delete --exclude '($(BUILDDIR)|.git)' . | while read -r CHANGE; do $(MAKE) html; done
livehtml: html-images
sphinx-autobuild -b html -i "$(BUILDDIR)/*" $(ALLSPHINXOPTS) $(BUILDDIR)/html --ignore ".git/*"

13
docs/README.md Normal file
View File

@ -0,0 +1,13 @@
# Hasura GraphQL Engine Docs
The documentation accompanying Hasura GraphQL Engine: written using
[Sphinx](http://www.sphinx-doc.org/en/master/) and deployed to
[docs.hasura.io](https://docs.hasura.io).
## Contributing
Checkout the [contributing](CONTRIBUTING.md) guide for more details.
## License
The source code in this directory are under [MIT License](LICENSE).

329
docs/_ext/djangodocs.py Normal file
View File

@ -0,0 +1,329 @@
"""
Sphinx plugins for Django documentation.
"""
import json
import os
import re
from docutils import nodes
from docutils.parsers.rst import Directive, directives
from sphinx import addnodes
from sphinx.builders.html import StandaloneHTMLBuilder
from sphinx.domains.std import Cmdoption
from sphinx.util.console import bold
from sphinx.util.nodes import set_source_info
try:
from sphinx.writers.html import SmartyPantsHTMLTranslator as HTMLTranslator
except ImportError: # Sphinx 1.6+
from sphinx.writers.html import HTMLTranslator
# RE for option descriptions without a '--' prefix
simple_option_desc_re = re.compile(
r'([-_a-zA-Z0-9]+)(\s*.*?)(?=,\s+(?:/|-|--)|$)')
def setup(app):
app.add_crossref_type(
directivename="setting",
rolename="setting",
indextemplate="pair: %s; setting",
)
app.add_crossref_type(
directivename="templatetag",
rolename="ttag",
indextemplate="pair: %s; template tag"
)
app.add_crossref_type(
directivename="templatefilter",
rolename="tfilter",
indextemplate="pair: %s; template filter"
)
app.add_crossref_type(
directivename="fieldlookup",
rolename="lookup",
indextemplate="pair: %s; field lookup type",
)
app.add_description_unit(
directivename="django-admin",
rolename="djadmin",
indextemplate="pair: %s; django-admin command",
parse_node=parse_django_admin_node,
)
app.add_directive('django-admin-option', Cmdoption)
app.add_config_value('django_next_version', '0.0', True)
# app.add_directive('versionadded', VersionDirective)
# app.add_directive('versionchanged', VersionDirective)
app.add_builder(DjangoStandaloneHTMLBuilder)
# register the snippet directive
app.add_directive('snippet', SnippetWithFilename)
# register a node for snippet directive so that the xml parser
# knows how to handle the enter/exit parsing event
app.add_node(snippet_with_filename,
html=(visit_snippet, depart_snippet_literal),
latex=(visit_snippet_latex, depart_snippet_latex),
man=(visit_snippet_literal, depart_snippet_literal),
text=(visit_snippet_literal, depart_snippet_literal),
texinfo=(visit_snippet_literal, depart_snippet_literal))
app.set_translator('djangohtml', DjangoHTMLTranslator)
app.set_translator('json', DjangoHTMLTranslator)
return {'parallel_read_safe': True}
class snippet_with_filename(nodes.literal_block):
"""
Subclass the literal_block to override the visit/depart event handlers
"""
pass
def visit_snippet_literal(self, node):
"""
default literal block handler
"""
self.visit_literal_block(node)
def depart_snippet_literal(self, node):
"""
default literal block handler
"""
self.depart_literal_block(node)
def visit_snippet(self, node):
"""
HTML document generator visit handler
"""
lang = self.highlightlang
linenos = node.rawsource.count('\n') >= self.highlightlinenothreshold - 1
fname = node['filename']
highlight_args = node.get('highlight_args', {})
if 'language' in node:
# code-block directives
lang = node['language']
highlight_args['force'] = True
if 'linenos' in node:
linenos = node['linenos']
def warner(msg):
self.builder.warn(msg, (self.builder.current_docname, node.line))
highlighted = self.highlighter.highlight_block(node.rawsource, lang,
warn=warner,
linenos=linenos,
**highlight_args)
starttag = self.starttag(node, 'div', suffix='',
CLASS='highlight-%s snippet' % lang)
self.body.append(starttag)
self.body.append('<div class="snippet-filename">%s</div>\n''' % (fname,))
self.body.append(highlighted)
self.body.append('</div>\n')
raise nodes.SkipNode
def visit_snippet_latex(self, node):
"""
Latex document generator visit handler
"""
code = node.rawsource.rstrip('\n')
lang = self.hlsettingstack[-1][0]
linenos = code.count('\n') >= self.hlsettingstack[-1][1] - 1
fname = node['filename']
highlight_args = node.get('highlight_args', {})
if 'language' in node:
# code-block directives
lang = node['language']
highlight_args['force'] = True
if 'linenos' in node:
linenos = node['linenos']
def warner(msg):
self.builder.warn(msg, (self.curfilestack[-1], node.line))
hlcode = self.highlighter.highlight_block(code, lang, warn=warner,
linenos=linenos,
**highlight_args)
self.body.append(
'\n{\\colorbox[rgb]{0.9,0.9,0.9}'
'{\\makebox[\\textwidth][l]'
'{\\small\\texttt{%s}}}}\n' % (
# Some filenames have '_', which is special in latex.
fname.replace('_', r'\_'),
)
)
if self.table:
hlcode = hlcode.replace('\\begin{Verbatim}',
'\\begin{OriginalVerbatim}')
self.table.has_problematic = True
self.table.has_verbatim = True
hlcode = hlcode.rstrip()[:-14] # strip \end{Verbatim}
hlcode = hlcode.rstrip() + '\n'
self.body.append('\n' + hlcode + '\\end{%sVerbatim}\n' %
(self.table and 'Original' or ''))
# Prevent rawsource from appearing in output a second time.
raise nodes.SkipNode
def depart_snippet_latex(self, node):
"""
Latex document generator depart handler.
"""
pass
class SnippetWithFilename(Directive):
"""
The 'snippet' directive that allows to add the filename (optional)
of a code snippet in the document. This is modeled after CodeBlock.
"""
has_content = True
optional_arguments = 1
option_spec = {'filename': directives.unchanged_required}
def run(self):
code = '\n'.join(self.content)
literal = snippet_with_filename(code, code)
if self.arguments:
literal['language'] = self.arguments[0]
literal['filename'] = self.options['filename']
set_source_info(self, literal)
return [literal]
class VersionDirective(Directive):
has_content = True
required_arguments = 1
optional_arguments = 1
final_argument_whitespace = True
option_spec = {}
def run(self):
if len(self.arguments) > 1:
msg = """Only one argument accepted for directive '{directive_name}::'.
Comments should be provided as content,
not as an extra argument.""".format(directive_name=self.name)
raise self.error(msg)
env = self.state.document.settings.env
ret = []
node = addnodes.versionmodified()
ret.append(node)
if self.arguments[0] == env.config.django_next_version:
node['version'] = "Development version"
else:
node['version'] = self.arguments[0]
node['type'] = self.name
if self.content:
self.state.nested_parse(self.content, self.content_offset, node)
env.note_versionchange(node['type'], node['version'], node, self.lineno)
return ret
class DjangoHTMLTranslator(HTMLTranslator):
"""
Django-specific reST to HTML tweaks.
"""
# Don't use border=1, which docutils does by default.
def visit_table(self, node):
self.context.append(self.compact_p)
self.compact_p = True
self._table_row_index = 0 # Needed by Sphinx
self.body.append(self.starttag(node, 'table', CLASS='docutils'))
def depart_table(self, node):
self.compact_p = self.context.pop()
self.body.append('</table>\n')
def visit_desc_parameterlist(self, node):
self.body.append('(') # by default sphinx puts <big> around the "("
self.first_param = 1
self.optional_param_level = 0
self.param_separator = node.child_text_separator
self.required_params_left = sum(isinstance(c, addnodes.desc_parameter) for c in node.children)
def depart_desc_parameterlist(self, node):
self.body.append(')')
#
# Turn the "new in version" stuff (versionadded/versionchanged) into a
# better callout -- the Sphinx default is just a little span,
# which is a bit less obvious that I'd like.
#
# FIXME: these messages are all hardcoded in English. We need to change
# that to accommodate other language docs, but I can't work out how to make
# that work.
#
version_text = {
'versionchanged': 'Changed in Django %s',
'versionadded': 'New in Django %s',
}
def visit_versionmodified(self, node):
self.body.append(
self.starttag(node, 'div', CLASS=node['type'])
)
version_text = self.version_text.get(node['type'])
if version_text:
title = "%s%s" % (
version_text % node['version'],
":" if len(node) else "."
)
self.body.append('<span class="title">%s</span> ' % title)
def depart_versionmodified(self, node):
self.body.append("</div>\n")
# Give each section a unique ID -- nice for custom CSS hooks
def visit_section(self, node):
old_ids = node.get('ids', [])
node['ids'] = ['s-' + i for i in old_ids]
node['ids'].extend(old_ids)
super().visit_section(node)
node['ids'] = old_ids
def parse_django_admin_node(env, sig, signode):
command = sig.split(' ')[0]
env.ref_context['std:program'] = command
title = "django-admin %s" % sig
signode += addnodes.desc_name(title, title)
return command
class DjangoStandaloneHTMLBuilder(StandaloneHTMLBuilder):
"""
Subclass to add some extra things we need.
"""
name = 'djangohtml'
def finish(self):
super().finish()
self.info(bold("writing templatebuiltins.js..."))
xrefs = self.env.domaindata["std"]["objects"]
templatebuiltins = {
"ttags": [
n for ((t, n), (k, a)) in xrefs.items()
if t == "templatetag" and k == "ref/templates/builtins"
],
"tfilters": [
n for ((t, n), (k, a)) in xrefs.items()
if t == "templatefilter" and k == "ref/templates/builtins"
],
}
outfilename = os.path.join(self.outdir, "templatebuiltins.js")
with open(outfilename, 'w') as fp:
fp.write('var django_template_builtins = ')
json.dump(templatebuiltins, fp)
fp.write(';\n')

95
docs/_ext/fulltoc.py Normal file
View File

@ -0,0 +1,95 @@
# -*- encoding: utf-8 -*-
#
# Copyright © 2012 New Dream Network, LLC (DreamHost)
#
# Author: Doug Hellmann <doug.hellmann@dreamhost.com>
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from sphinx import addnodes
def html_page_context(app, pagename, templatename, context, doctree):
"""Event handler for the html-page-context signal.
Modifies the context directly.
- Replaces the 'toc' value created by the HTML builder with one
that shows all document titles and the local table of contents.
- Sets display_toc to True so the table of contents is always
displayed, even on empty pages.
- Replaces the 'toctree' function with one that uses the entire
document structure, ignores the maxdepth argument, and uses
only prune and collapse.
"""
rendered_toc = get_rendered_toctree(app.builder, pagename)
context['toc_full'] = rendered_toc
context['display_toc'] = True # force toctree to display
if "toctree" not in context:
# json builder doesn't use toctree func, so nothing to replace
return
def make_toctree(collapse=True, maxdepth=-1, includehidden=True):
return get_rendered_toctree(app.builder,
pagename,
prune=False,
collapse=collapse,
)
context['toctree'] = make_toctree
def get_rendered_toctree(builder, docname, prune=False, collapse=True):
"""Build the toctree relative to the named document,
with the given parameters, and then return the rendered
HTML fragment.
"""
fulltoc = build_full_toctree(builder,
docname,
prune=prune,
collapse=collapse,
)
rendered_toc = builder.render_partial(fulltoc)['fragment']
return rendered_toc
def build_full_toctree(builder, docname, prune, collapse):
"""Return a single toctree starting from docname containing all
sub-document doctrees.
"""
env = builder.env
doctree = env.get_doctree(env.config.master_doc)
toctrees = []
for toctreenode in doctree.traverse(addnodes.toctree):
toctree = env.resolve_toctree(docname, builder, toctreenode,
collapse=collapse,
prune=prune,
includehidden=True,
)
if toctree is not None:
toctrees.append(toctree)
if not toctrees:
return None
result = toctrees[0]
for toctree in toctrees[1:]:
if toctree:
result.extend(toctree.children)
env.resolve_references(result, docname, builder)
return result
def setup(app):
app.connect('html-page-context', html_page_context)

140
docs/_ext/generate_index.py Normal file
View File

@ -0,0 +1,140 @@
# -*- coding: utf-8 -*-
from sphinx import addnodes
"""
``generate_index``: A callable yielding the global TOC tree that contains
list of all the content below the specified page. ``generate_index`` need
pagename specifing as like as ``{{ generate_index(pagename) }}`` and
optional keyword arguments are available:
* maxdepth (defaults to the max depth selected in the toctree directive):
the maximum depth of the tree; set it to -1 to allow unlimited depth
"""
import os
import json
from bs4 import BeautifulSoup
import datetime
import calendar
import re
import xml.etree.ElementTree as ET
indexObjs = []
def check_directory(path):
directory = os.path.dirname(path)
try:
if not os.path.exists(directory):
os.makedirs(directory)
except OSError as e:
raise
def on_finish_building(app, exception):
current_version = app.env.config["version"]
if "latest_docs_version" in app.env.config["html_context"].keys():
latest_version = app.env.config["html_context"]["latest_docs_version"]
else:
latest_version = "dev"
base_domain = app.env.config["html_context"]["SITEMAP_DOMAIN"]
index_file_path = "./_build/algolia_index/index.json"
sitemap_path = "./_build/sitemap/sitemap_" + current_version + ".xml"
check_directory(index_file_path)
check_directory(sitemap_path)
f = open(index_file_path, 'w+')
root = ET.Element("urlset")
root.set("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9")
for link in indexObjs:
url = ET.SubElement(root, "url")
ET.SubElement(url, "loc").text = base_domain + str(current_version) + "/" + link["url"]
ET.SubElement(url, "changefreq").text = "daily"
ET.SubElement(url, "priority").text = "1" if (current_version == latest_version) else "0.5"
ET.ElementTree(root).write(sitemap_path)
f.write(json.dumps(indexObjs))
def generate_index_file(app, pagename, templatename, context, doctree):
# If the page name is not part of the below list and is present in toc-tree
if (pagename not in ['manual/index', 'index', 'search', 'genindex']
and not (pagename.startswith("ref/") or pagename.startswith("tutorials/") or pagename.startswith("guides/"))
and re.search('<a[^>]*class="[^"]*current[^"]*"[^>]*>', context['toc_full'])):
title = ''
keyword = ''
description = ''
tags_val = ''
content = ''
image = ''
created_val = 0
if 'title' in context:
title = context['title']
if 'metatags' in context:
metatags = context['metatags']
if len(metatags) > 0:
soup = BeautifulSoup(metatags, 'html.parser')
descriptions = soup.findAll("meta", {"name": "description"})
keywords = soup.findAll("meta", {"name": "keywords"})
tags = soup.findAll("meta", {"name": "content-tags"})
created_at = soup.findAll("meta", {"name": "created-on"})
if len(descriptions) > 0:
description = descriptions[0]['content']
if len(keywords) > 0:
keyword = keywords[0]['content']
if len(tags) > 0:
tags_val = tags[0]['content']
if len(created_at) > 0:
created_val = created_at[0]['content']
created_val = datetime.datetime.strptime(created_val, "%Y-%m-%dT%H:%M:%S.%fZ")
created_val = calendar.timegm(created_val.utctimetuple())
else:
created_val = 0
if 'body' in context:
body = context['body']
soup = BeautifulSoup(body, 'html.parser')
content = soup.get_text()
imgs = soup.findAll("img", {"class": "featured-image"})
if len(imgs) > 0:
image = imgs[0]['src'].split('/')[-1]
url = pagename + '.html'
category = pagename.split('/')[0]
index_obj = {
"title": title,
"content": content,
"url": url,
"category": category,
"image": image,
"description": description,
"keywords": keyword,
"tags": tags_val,
"created_at": created_val
}
indexObjs.append(index_obj)
else:
print('\t\t\t\t\t\t\t\t\t\t\t\tIGNORED FOR INDEXING')
def setup(app):
app.connect('build-finished', on_finish_building)
app.connect('html-page-context', generate_index_file)

109
docs/_ext/global_tabs.py Normal file
View File

@ -0,0 +1,109 @@
import fett
from docutils import statemachine
from docutils.utils.error_reporting import ErrorString
from sphinx.util.compat import Directive
import yaml
# List of tabs ( ID, Display Name)
TABS_RAW = [
('linux', 'Linux'),
('mac', 'Mac'),
('windows', 'Windows'),
]
TABS_IDS = [tab[0] for tab in TABS_RAW]
TABS_DISPLAY = [tab[1] for tab in TABS_RAW]
class GlobalTabsDirective(Directive):
has_content = True
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = True
TABS_TEMPLATE = '''
.. raw:: html
<div class="global-tabs">
<ul class="tab-strip tab-strip--singleton" role="tablist">
{{ for tab in tabs sortTabs }}
<li class="tab-strip__element" data-tabid="{{ tab.id }}" role="tab" aria-selected="{{ if i zero }}true{{ else }}false{{ end }}">{{ tab.name }}</li>
{{ end }}
</ul>
<div class="tabs__content" role="tabpanel">
{{ for tab in tabs sortTabs}}
<div class="tabpanel-{{ tab.id }}">
{{ tab.content }}
.. raw:: html
</div>
{{ end }}
</div>
</div>
'''
def run(self):
contents = '\n'.join(self.content)
try:
data = yaml.safe_load(contents)
except yaml.YAMLError as error:
raise self.severe(u'Error parsing YAML:\n{}.'.format(ErrorString(error)))
raw_template = fett.Template(self.TABS_TEMPLATE)
try:
rendered_template = raw_template.render(data)
except Exception as error:
raise self.severe('Failed to render template: {}'.format(ErrorString(error)))
rendered_lines = statemachine.string2lines(rendered_template, 4, convert_whitespace=1)
self.state_machine.insert_input(rendered_lines, '')
return []
def setup(app):
app.add_directive('global-tabs', GlobalTabsDirective)
return {'parallel_read_safe': True,
'parallel_write_safe': True}
def numberOfTabs(tabData):
return len(TABS_RAW)
fett.Template.FILTERS['numberOfTabs'] = numberOfTabs
def getTabNames(tabData):
for tab in tabData:
index = TABS_IDS.index(tab['id'])
tab['name'] = TABS_DISPLAY[index]
return tabData
fett.Template.FILTERS['getTabNames'] = getTabNames
def sortTabs(tabData):
# Create a list for the sorted data
sorted_tabs = [None] * len(TABS_RAW)
for tab in tabData:
index = TABS_IDS.index(tab['id'])
tab['name'] = TABS_DISPLAY[index]
sorted_tabs[index] = tab
# Fill in any missing tabs with empty content
for index in range(len(sorted_tabs)):
if sorted_tabs[index] is None:
sorted_tabs[index] = {'id': TABS_IDS[index], 'name': TABS_DISPLAY[index], 'content': ''}
return sorted_tabs
fett.Template.FILTERS['sortTabs'] = sortTabs

55
docs/_ext/graphiql.py Normal file
View File

@ -0,0 +1,55 @@
import fett
from docutils import statemachine
from docutils.utils.error_reporting import ErrorString
from sphinx.util.compat import Directive
class GraphiQLDirective(Directive):
has_content = False
required_arguments = 0
optional_arguments = 0
final_argument_whitespace = True
option_spec = {"query": str, "response": str, "endpoint": str, "view_only": str}
GRAPHIQL_TEMPLATE = '''
.. raw:: html
<div class="graphiql {{ if view_only }}view-only{{end}}">
.. code-block:: graphql
{{ query }}
.. raw:: html
<div class="endpoint">
{{ endpoint }}
</div>
<div class="query">
{{ query }}
</div>
<div class="response">
{{ response }}
</div>
</div>
'''
def run(self):
raw_template = fett.Template(self.GRAPHIQL_TEMPLATE)
try:
rendered_template = raw_template.render(self.options)
except Exception as error:
raise self.severe('Failed to render template: {}'.format(ErrorString(error)))
rendered_lines = statemachine.string2lines(rendered_template, 4, convert_whitespace=1)
self.state_machine.insert_input(rendered_lines, '')
return []
def setup(app):
app.add_directive('graphiql', GraphiQLDirective)
return {'parallel_read_safe': True,
'parallel_write_safe': True}

View File

@ -0,0 +1,47 @@
# -*- coding: utf-8 -*-
"""
pygments.lexers.graphql
~~~~~~~~~~~~~~~~~~~~
Lexers for GraphQL formats.
:copyright: Copyright 2017 by Martin Zlámal.
:license: BSD, see LICENSE for details.
"""
from pygments.lexer import RegexLexer
from pygments.token import *
__all__ = ['GraphqlLexer']
class GraphqlLexer(RegexLexer):
"""
Lexer for GraphQL.
"""
name = 'GraphQL'
aliases = ['graphql', 'gql']
filenames = ['*.graphql', '*.gql']
mimetypes = ['application/graphql']
tokens = {
'root': [
(r'#.*', Comment.Singline),
(r'\.\.\.', Operator),
(r'"([^\\"]|\\")*"', String.Double),
(r'(-?0|-?[1-9][0-9]*)(\.[0-9]+[eE][+-]?[0-9]+|\.[0-9]+|[eE][+-]?[0-9]+)', Number.Float),
(r'(-?0|-?[1-9][0-9]*)', Number.Integer),
(r'\$+[_A-Za-z][_0-9A-Za-z]*', Name.Variable),
(r'[_A-Za-z][_0-9A-Za-z]+\s?:', Text),
(r'(type|query|fragment|mutation|@[a-z]+|on|true|false|null)\b', Keyword.Type),
(r'[!$():=@\[\]{|}]+?', Punctuation),
(r'[_A-Za-z][_0-9A-Za-z]*', Keyword),
(r'(\s|,)', Text),
]
}
def setup(app):
from sphinx.highlighting import lexers
lexers['graphql'] = GraphqlLexer()

53
docs/_ext/lexer_jsx.py Normal file
View File

@ -0,0 +1,53 @@
import re
from pygments.lexer import bygroups, include, default
from pygments.lexers.javascript import JavascriptLexer
from pygments.token import Name, Operator, Punctuation, String, Text
# Use same tokens as `JavascriptLexer`, but with tags and attributes support
TOKENS = JavascriptLexer.tokens
TOKENS.update({
'jsx': [
(r'(<)([\w]+)',
bygroups(Punctuation, Name.Tag), 'tag'),
(r'(<)(/)([\w]+)(>)',
bygroups(Punctuation, Punctuation, Name.Tag,
Punctuation)),
],
'tag': [
(r'\s+', Text),
(r'([\w]+\s*)(=)(\s*)', bygroups(Name.Attribute, Operator, Text), 'attr'),
(r'[{}]+', Punctuation),
(r'[\w\.]+', Name.Attribute),
(r'(/?)(\s*)(>)', bygroups(Punctuation, Text, Punctuation), '#pop'),
],
'attr': [
('{', Punctuation, 'expression'),
('".*?"', String, '#pop'),
("'.*?'", String, '#pop'),
default ('#pop')
],
'expression': [
('{', Punctuation, '#push'),
('}', Punctuation, '#pop'),
include('root')
]
})
TOKENS['root'].insert(0, include('jsx'))
class JsxLexer(JavascriptLexer):
name = 'react'
aliases = ['jsx', 'react']
filenames = ['*.jsx', '*.react']
mimetypes = ['text/jsx', 'text/typescript-jsx']
flags = re.MULTILINE | re.DOTALL | re.UNICODE
tokens = TOKENS
def setup(app):
from sphinx.highlighting import lexers
lexers['jsx'] = JsxLexer()

View File

@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
from sphinx import addnodes
"""
``local_toctree``: A callable yielding the global TOC tree that contains
list of all the content below the specified page. ``local_toctree`` need
pagename specifing as like as ``{{ local_toctree(pagename) }}`` and
optional keyword arguments are available:
* maxdepth (defaults to the max depth selected in the toctree directive):
the maximum depth of the tree; set it to -1 to allow unlimited depth
"""
def init_local_toctree(app):
def _get_local_toctree(docname, **kwds):
doctree = app.env.get_doctree(docname)
if 'maxdepth' not in kwds:
kwds['maxdepth'] = 0
toctrees = []
for toctreenode in doctree.traverse(addnodes.toctree):
toctree = app.env.resolve_toctree(
docname, app.builder, toctreenode, **kwds)
toctrees.append(toctree)
if not toctrees:
return None
result = toctrees[0]
for toctree in toctrees[1:]:
result.extend(toctree.children)
return app.builder.render_partial(result)['fragment']
ctx = app.env.config['html_context']
if 'local_toctree' not in ctx:
ctx['local_toctree'] = _get_local_toctree
def setup(app):
app.connect('builder-inited', init_local_toctree)

772
docs/_static/djangosite.css vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,68 @@
/*Display and hide the appropriate tab content*/
.tab {
display: none;
}
.tab-active {
display: block;
}
/*Clear the padding above the tabs*/
.tab-strip__element {
padding-top: 0;
}
.tab-strip {
list-style: none;
padding-left: 0;
margin-left: 0 !important;
}
/*Styles for inactive & unhovered tabs*/
.tab-strip__element {
background-color: #f4f6f6;
border-radius: 4px 4px 0 0;
border: 0.5px solid #babdbe;
color: #babdbe;
display: table-cell;
font-weight: bold;
height: 34px;
padding: 0 !important;
text-align: center;
vertical-align: middle;
width: 1%;
}
/*Styles for active tabs*/
.tab-strip__element[aria-selected=true],
.tab-strip__element[aria-selected=true]:focus,
.tab-strip__element[aria-selected=true]:hover {
color: #6ba442;
font-weight: bold;
background-color: #fff;
border: 0.5px solid #babdbe;
border-bottom-color: transparent;
}
/*White background on hover*/
.tab-strip__element:hover {
background: #fff;
}
/*Caret styling*/
.tab-strip__element[aria-selected=true] > .caret,
.tab-strip__element[aria-selected=true]:focus > .caret,
.tab-strip__element[aria-selected=true]:hover > .caret {
border-top-color: #6ba442;
border-bottom-color: #6ba442;
}
.tab-strip__element[aria-selected=true] > .caret,
.tab-strip__element[aria-selected=true]:hover > .caret {
border-top-color: #babdbe;
border-bottom-color: #babdbe;
}
.tab-strip [aria-selected]:hover {
cursor: pointer;
}

112
docs/_static/global_tabs/global_tabs.js vendored Normal file
View File

@ -0,0 +1,112 @@
/**
* Show the appropriate tab content and hide other tab's content
* @param {string} currentAttrValue The currently selected tab ID.
* @returns {void}
*/
function showHideTabContent(currentAttrValue) {
$('.tabs__content').children().
hide();
$(`.global-tabs .tabpanel-${currentAttrValue}`).show();
}
class TabsSingleton {
constructor(key) {
this.key = key;
this.tabStrip = document.querySelector('.tab-strip--singleton');
}
get tabPref() {
return window.localStorage.getItem(this.key);
}
set tabPref(value) {
window.localStorage.setItem(this.key, value);
}
/**
* Return the first singleton tab ID on the page.
* @returns {string} The first singleton tab ID found.
*/
getFirstTab() {
const tabsElement = this.tabStrip.querySelector('.tab-strip__element[aria-selected=true]');
if (!tabsElement) { return null; }
return tabsElement.getAttribute('data-tabid');
}
setup() {
if (!this.tabStrip) { return; }
this.hideTabBars();
for (const element of this.tabStrip.querySelectorAll('[data-tabid]')) {
element.onclick = (e) => {
// Get the tab ID of the clicked tab
const currentAttrValue = e.target.getAttribute('data-tabid');
// Check to make sure value is not null, i.e., don't do anything on "other"
if (currentAttrValue) {
// Save the users preference and re-render
this.tabPref = currentAttrValue;
this.update();
e.preventDefault();
}
};
}
this.update();
}
update() {
if (!this.tabStrip) { return; }
let tabPref = this.tabPref;
if (!tabPref) {
tabPref = this.getFirstTab();
} else if (!this.tabStrip.querySelector(`[data-tabid="${tabPref}"]`)) {
// Confirm a tab for their tabPref exists at the top of the page
tabPref = this.getFirstTab();
}
if (!tabPref) { return; }
// Show the appropriate tab content and mark the tab as active
showHideTabContent(tabPref);
this.showHideSelectedTab(tabPref);
}
/**
* Marks the selected tab as active
* @param {string} currentAttrValue The currently selected tab ID.
* @returns {void}
*/
showHideSelectedTab(currentAttrValue) {
// Get the <a>, <li> and <ul> of the selected tab
const tabLink = $(this.tabStrip.querySelector(`[data-tabid="${currentAttrValue}"]`));
// Set a tab to active
tabLink.
attr('aria-selected', true).
siblings().
attr('aria-selected', false);
}
/**
* Show only the first set of tabs at the top of the page.
* @returns {void}
*/
hideTabBars() {
const tabBars = $('.tab-strip--singleton');
const mainTabBar = tabBars.first();
// Remove any additional tab bars
tabBars.slice(1).
detach();
// Position the main tab bar after the page title
mainTabBar.
detach().
insertAfter('h1').
first();
}
}
(new TabsSingleton('tabPref')).setup();

26
docs/_static/graphiql/LICENSE vendored Normal file
View File

@ -0,0 +1,26 @@
LICENSE AGREEMENT For GraphiQL software
Facebook, Inc. (“Facebook”) owns all right, title and interest, including all
intellectual property and other proprietary rights, in and to the GraphiQL
software. Subject to your compliance with these terms, you are hereby granted a
non-exclusive, worldwide, royalty-free copyright license to (1) use and copy the
GraphiQL software; and (2) reproduce and distribute the GraphiQL software as
part of your own software (“Your Software”). Facebook reserves all rights not
expressly granted to you in this license agreement.
THE SOFTWARE AND DOCUMENTATION, IF ANY, ARE PROVIDED "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES (INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE) ARE DISCLAIMED. IN NO
EVENT SHALL FACEBOOK OR ITS AFFILIATES, OFFICES, DIRECTORS OR EMPLOYEES BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
THE USE OF THE SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
You will include in Your Software (e.g., in the file(s), documentation or other
materials accompanying your software): (1) the disclaimer set forth above; (2)
this sentence; and (3) the following copyright notice:
Copyright (c) 2015, Facebook, Inc. All rights reserved.

1741
docs/_static/graphiql/graphiql.css vendored Normal file

File diff suppressed because it is too large Load Diff

46455
docs/_static/graphiql/graphiql.js vendored Normal file

File diff suppressed because one or more lines are too long

1
docs/_static/graphiql/graphiql.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1386
docs/_static/hasura-custom.css vendored Normal file

File diff suppressed because it is too large Load Diff

1311
docs/_static/jquery-ui.css vendored Normal file

File diff suppressed because it is too large Load Diff

15
docs/_static/react/react-dom.min.js vendored Normal file

File diff suppressed because one or more lines are too long

12
docs/_static/react/react.min.js vendored Normal file

File diff suppressed because one or more lines are too long

25
docs/_static/vendor.js vendored Normal file

File diff suppressed because one or more lines are too long

224
docs/_theme/djangodocs/basic/layout.html vendored Normal file
View File

@ -0,0 +1,224 @@
{#
basic/layout.html
~~~~~~~~~~~~~~~~~
Master layout template for Sphinx themes.
:copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
#}
{%- block doctype -%}{%- if html5_doctype %}
<!DOCTYPE html>
{%- else %}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
{%- endif %}{%- endblock %}
{%- set reldelim1 = reldelim1 is not defined and ' &#187;' or reldelim1 %}
{%- set reldelim2 = reldelim2 is not defined and ' |' or reldelim2 %}
{%- set render_sidebar = (not embedded) and (not theme_nosidebar|tobool) and
(sidebars != []) %}
{%- set url_root = pathto('', 1) %}
{# XXX necessary? #}
{%- if url_root == '#' %}{% set url_root = '' %}{% endif %}
{%- if not embedded and docstitle %}
{%- set titlesuffix = " | "|safe + docstitle|e %}
{%- else %}
{%- set titlesuffix = "" %}
{%- endif %}
{%- macro relbar() %}
<div class="related" role="navigation" aria-label="related navigation">
<h3>{{ _('Navigation') }}</h3>
<ul>
{%- for rellink in rellinks %}
<li class="right" {% if loop.first %}style="margin-right: 10px"{% endif %}>
<a href="{{ pathto(rellink[0]) }}" title="{{ rellink[1]|striptags|e }}"
{{ accesskey(rellink[2]) }}>{{ rellink[3] }}</a>
{%- if not loop.first %}{{ reldelim2 }}{% endif %}</li>
{%- endfor %}
{%- block rootrellink %}
<li class="nav-item nav-item-0"><a href="{{ pathto(master_doc) }}">{{ shorttitle|e }}</a>{{ reldelim1 }}</li>
{%- endblock %}
{%- for parent in parents %}
<li class="nav-item nav-item-{{ loop.index }}"><a href="{{ parent.link|e }}" {% if loop.last %}{{ accesskey("U") }}{% endif %}>{{ parent.title }}</a>{{ reldelim1 }}</li>
{%- endfor %}
{%- block relbaritems %} {% endblock %}
</ul>
</div>
{%- endmacro %}
{%- macro sidebar() %}
{%- if render_sidebar %}
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
{%- block sidebarlogo %}
{%- if logo %}
<p class="logo"><a href="{{ pathto(master_doc) }}">
<img class="logo" src="{{ pathto('_static/' + logo, 1) }}" alt="Logo"/>
</a></p>
{%- endif %}
{%- endblock %}
{%- if sidebars != None %}
{#- new style sidebar: explicitly include/exclude templates #}
{%- for sidebartemplate in sidebars %}
{%- include sidebartemplate %}
{%- endfor %}
{%- else %}
{#- old style sidebars: using blocks -- should be deprecated #}
{%- block sidebartoc %}
{%- include "localtoc.html" %}
{%- endblock %}
{%- block sidebarrel %}
{%- include "relations.html" %}
{%- endblock %}
{%- block sidebarsourcelink %}
{%- include "sourcelink.html" %}
{%- endblock %}
{%- if customsidebar %}
{%- include customsidebar %}
{%- endif %}
{%- block sidebarsearch %}
{%- include "searchbox.html" %}
{%- endblock %}
{%- endif %}
</div>
</div>
{%- endif %}
{%- endmacro %}
{%- macro script() %}
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT: '{{ url_root }}',
VERSION: '{{ release|e }}',
COLLAPSE_INDEX: false,
FILE_SUFFIX: '{{ '' if no_search_suffix else file_suffix }}',
HAS_SOURCE: {{ has_source|lower }},
SOURCELINK_SUFFIX: '{{ sourcelink_suffix }}'
};
</script>
{%- for scriptfile in script_files %}
<script type="text/javascript" src="{{ pathto(scriptfile, 1) }}"></script>
{%- endfor %}
{%- endmacro %}
{%- macro css() %}
<link rel="stylesheet" href="{{ pathto('_static/' + style, 1) }}" type="text/css" />
<link rel="stylesheet" href="{{ pathto('_static/pygments.css', 1) }}" type="text/css" />
{%- for css in css_files %}
{%- if css|attr("rel") %}
<link rel="{{ css.rel }}" href="{{ pathto(css.filename, 1) }}" type="text/css"{% if css.title is not none %} title="{{ css.title }}"{% endif %} />
{%- else %}
<link rel="stylesheet" href="{{ pathto(css, 1) }}" type="text/css" />
{%- endif %}
{%- endfor %}
{%- endmacro %}
{%- if html_tag %}
{{ html_tag }}
{%- else %}
<html xmlns="http://www.w3.org/1999/xhtml"{% if language is not none %} lang="{{ language }}"{% endif %}>
{%- endif %}
<head>
{%- if not html5_doctype %}
<meta http-equiv="X-UA-Compatible" content="IE=Edge" />
{%- endif %}
{%- if use_meta_charset or html5_doctype %}
<meta charset="{{ encoding }}" />
{%- else %}
<meta http-equiv="Content-Type" content="text/html; charset={{ encoding }}" />
{%- endif %}
{{- metatags }}
{%- if pagename == "404" %}
<base href="/{{ version }}/{{ pagename }}">
{% endif %}
{%- block htmltitle %}
<title>{{ title|striptags|e }}{{ titlesuffix }}</title>
{%- endblock %}
{%- block csss %}
{{- css() }}
{%- endblock %}
{%- if not embedded %}
{%- block scripts %}
{{- script() }}
{%- endblock %}
{%- if use_opensearch %}
<link rel="search" type="application/opensearchdescription+xml"
title="{% trans docstitle=docstitle|e %}Search within {{ docstitle }}{% endtrans %}"
href="{{ pathto('_static/opensearch.xml', 1) }}"/>
{%- endif %}
{%- if favicon %}
<link rel="shortcut icon" href="{{ pathto('_static/' + favicon, 1) }}"/>
{%- endif %}
{%- endif %}
{%- block linktags %}
{%- if hasdoc('about') %}
<link rel="author" title="{{ _('About these documents') }}" href="{{ pathto('about') }}" />
{%- endif %}
{%- if hasdoc('genindex') %}
<link rel="index" title="{{ _('Index') }}" href="{{ pathto('genindex') }}" />
{%- endif %}
{%- if hasdoc('search') %}
<link rel="search" title="{{ _('Search') }}" href="{{ pathto('search') }}" />
{%- endif %}
{%- if hasdoc('copyright') %}
<link rel="copyright" title="{{ _('Copyright') }}" href="{{ pathto('copyright') }}" />
{%- endif %}
{%- if next %}
<link rel="next" title="{{ next.title|striptags|e }}" href="{{ next.link|e }}" />
{%- endif %}
{%- if prev %}
<link rel="prev" title="{{ prev.title|striptags|e }}" href="{{ prev.link|e }}" />
{%- endif %}
{%- endblock %}
{%- block extrahead %} {% endblock %}
</head>
{%- block body_tag %}<body>{% endblock %}
{%- block header %}{% endblock %}
{%- block relbar1 %}{{ relbar() }}{% endblock %}
{%- block content %}
{%- block sidebar1 %} {# possible location for sidebar #} {% endblock %}
<div class="document">
{%- block document %}
<div class="documentwrapper">
{%- if render_sidebar %}
<div class="bodywrapper">
{%- endif %}
<div class="body" role="main">
{% block body %} {% endblock %}
</div>
{%- if render_sidebar %}
</div>
{%- endif %}
</div>
{%- endblock %}
{%- block sidebar2 %}{{ sidebar() }}{% endblock %}
<div class="clearer"></div>
</div>
{%- endblock %}
{%- block relbar2 %}{{ relbar() }}{% endblock %}
{%- block footer %}
<div class="footer" role="contentinfo">
{%- if show_copyright %}
{%- if hasdoc('copyright') %}
{% trans path=pathto('copyright'), copyright=copyright|e %}&#169; <a href="{{ path }}">Copyright</a> {{ copyright }}.{% endtrans %}
{%- else %}
{% trans copyright=copyright|e %}&#169; Copyright {{ copyright }}.{% endtrans %}
{%- endif %}
{%- endif %}
{%- if last_updated %}
{% trans last_updated=last_updated|e %}Last updated on {{ last_updated }}.{% endtrans %}
{%- endif %}
{%- if show_sphinx %}
{% trans sphinx_version=sphinx_version|e %}Created using <a href="http://sphinx-doc.org/">Sphinx</a> {{ sphinx_version }}.{% endtrans %}
{%- endif %}
</div>
{%- endblock %}
</body>
</html>

4
docs/_theme/djangodocs/genindex.html vendored Normal file
View File

@ -0,0 +1,4 @@
{% extends "basic/genindex.html" %}
{% block bodyclass %}{% endblock %}
{% block sidebarwrapper %}{% endblock %}

655
docs/_theme/djangodocs/layout.html vendored Normal file
View File

@ -0,0 +1,655 @@
{% extends "basic/layout.html" %}
{% set css_files = css_files + ['_static/hasura-custom.css', '_static/djangosite.css'] %}
{%- macro secondnav() %}
{%- if prev %}
&laquo; <a href="{{ prev.link|e }}" title="{{ prev.title|e }}">previous</a>
{{ reldelim2 }}
{%- endif %}
{%- if next %}
<a href="{{ next.link|e }}" title="{{ next.title|e }}">next</a> &raquo;
{%- endif %}
{%- endmacro %}
{% block extrahead %}
<!--<meta name="viewport" content="user-scalable=no">-->
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
{% endblock %}
{% block document %}
<div id="custom-doc" class="{% block bodyclass %}{{ 'yui-t6' if pagename != 'index' else '' }}{% endblock %}">
<div class="main_container_wrapper">
{% include 'pages/loading.html' %}
<div id="" class="content_wrapper">
{%- if pagename == 'index' %}
{% include 'pages/landing.html' %}
{%- else %}
{% set rootpage = 'common' %}
{% set pagenameSplit = pagename.split('/') %}
{% block sidebarwrapper %}
<div class="yui-b mobile-hide" id="sidebar">
{%- if last_updated %}
<span><b>Last updated:</b> {{ last_updated }}</span>
{%- endif %}
{{ sidebar() }}
</div>
{% endblock %}
<div class="content_inner_wrapper">
<div role="main" parent={{ rootpage }}>
<div id="docs-content">
<!-- div class="media_wrapper details_media">
<div class="float_right wd_100">
<div class="share_wrapper">
<button class="share_btn green_btn">
<span>Share</span>
<i class="fa fa-share" aria-hidden="true"></i>
</button>
</div>
</div>
</div -->
<div class="mobile-logo mobile-only">
<a href="https://{{ BASE_DOMAIN }}/" target="_blank">
<div class="img_wrapper inline-block">
<img class="responsive logo_img no-shadow" src="{{ pathto('_images/layout/logo2.svg', 1) }}" />
</div>
</a>
<a href="https://docs.{{ BASE_DOMAIN }}" class="docs_label mobile-logo-docs">DOCS&nbsp;&nbsp;v{{version}}</a>
</div>
<div class="search_head_wrapper">
<div class="inline-block mobile-only" id="nav_tree_icon">
<span class="fa fa-bars"></span>
</div>
<div class="inline-block input_search_box">
<span class="fa fa-search search_icon"></span>
<input type="text" class="search_element" placeholder="Search docs..." />
</div>
<div class="header_links inline-block">
<div class="buttons">
<div class="inline-block">
<!-- a target="_blank" href="https://dashboard.{{ BASE_DOMAIN }}/login" -->
<button class="indiv_btns black supportBtn"> Support </button>
<!--/ a -->
</div>
</div>
</div>
</div>
<div id="{{ pagename|replace('/', '-') }}">
{% block body %}{% endblock %}
</div>
</div>
<div class="nav">{{ secondnav() }}</div>
<div class="feedback_wrapper">
<div class="feedback">
Was this page helpful?
<!-- inline svgs to allow color manipulation on hover -->
<div class="display_inl actions thumb_up">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="21px" height="21px" viewBox="0 0 561 561" style="enable-background:new 0 0 561 561;" xml:space="preserve">
<g>
<g id="thumb-up">
<path d="M0,535.5h102v-306H0V535.5z M561,255c0-28.05-22.95-51-51-51H349.35l25.5-117.3c0-2.55,0-5.1,0-7.65 c0-10.2-5.1-20.4-10.199-28.05L336.6,25.5L168.3,193.8c-10.2,7.65-15.3,20.4-15.3,35.7v255c0,28.05,22.95,51,51,51h229.5 c20.4,0,38.25-12.75,45.9-30.6l76.5-181.051c2.55-5.1,2.55-12.75,2.55-17.85v-51H561C561,257.55,561,255,561,255z" fill="#787878"/>
</g>
</g>
</svg>
</div>
<div class="display_inl actions thumb_down">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" width="21px" height="21px" viewBox="0 0 561 561" style="enable-background:new 0 0 561 561;" xml:space="preserve">
<g>
<g id="thumb-down">
<path d="M357,25.5H127.5c-20.4,0-38.25,12.75-45.9,30.6L5.1,237.15C2.55,242.25,0,247.35,0,255v48.45l0,0V306 c0,28.05,22.95,51,51,51h160.65l-25.5,117.3c0,2.55,0,5.101,0,7.65c0,10.2,5.1,20.399,10.2,28.05l28.05,25.5l168.3-168.3 c10.2-10.2,15.3-22.95,15.3-35.7v-255C408,48.45,385.05,25.5,357,25.5z M459,25.5v306h102v-306H459z" fill="#787878"/>
</g>
</g>
</svg>
</div>
</div>
<div class="detailed_feedback hide">
<div class="feedback_detail_msg"></div>
<textarea rows="4" cols="75" id="feedback_box" data-id="0"></textarea>
<br/>
<button class="feedback_btn">Submit</button>
</div>
<div class="thank_you hide">
Thank you for your feedback!
</div>
</div>
<div class="footer-hasura-custom">
Want to contribute or report missing content? Check out the <a target="_blank" href="https://github.com/hasura/graphql-engine-docs">github repo for docs</a>.<br>
Powered by <a target="_blank" href="http://www.sphinx-doc.org">Sphinx</a>.
Copyright &copy; 2018 <a target="_blank" href="https://hasura.io">Hasura</a>.
</div>
</div>
</div>
<div style="clear: both;"></div>
{%- endif %}
</div>
</div>
<div class="search_wrapper_template hide">
<div class="search_box_head">
<h3 class="head_wrapper search_heading">
<div class="inline-block">
<%= totalResults %> result<%= totalResults > 1 ? 's' : '' %> for <span class="search_query">'<%= _.escape(searchString) %>'</span>
</div>
<div class="algolia_search_attribution">
<span>
powered by
</span>
<span>
<img class="algolia_image" src="{{ pathto('_images/layout/algolia-logo.svg', 1) }}" alt="Algolia Hasura Search" />
</span>
</div>
</h3>
</div>
<%
if ( objs.length > 0 ) {
%>
<ul>
<%
_.each(objs, function(obj, index ) {
%>
<li class="search_bullets">
<a class="search_results_anchor" href="{{ pathto('', 1) }}<%= obj.url %>">
<div class="no_of_results">
<%= obj.title %>
</div>
</a>
<div class="search_results_description">
<%= obj.description.length > 200 ? obj.description.slice(0, 200 ) + ' ...' : obj.description %>
</div>
</li>
</a>
<%
});
%>
</ul>
<%
} else {
%>
<div class="no_results"> Sorry! we cannot find what you are searching for! </div>
<%
}
%>
<div class = "pagination_container wd_100" id = "pagination_container"></div>
<div style="clear: both;"></div>
</div>
<div class="search_wrapper main_wrapper hide">
<div class="wd_80">
<div class="search_head_wrapper">
<div class="inline-block input_search_box">
<span class="fa fa-search search_icon"></span>
<input type="text" class="search_element" placeholder="Search docs..." />
</div>
<div class="header_links inline-block">
<div class="buttons">
<div class="inline-block">
<!-- a target="_blank" href="https://dashboard.{{ BASE_DOMAIN }}/login" -->
<button class="indiv_btns black supportBtn"> Support </button>
<!--/ a -->
</div>
</div>
</div>
</div>
<br/>
<div class="search_wrapper_content"></div>
</div>
</div>
</div>
{% endblock %}
{% block sidebarrel %}
{% endblock %}
{# Empty some default blocks out #}
{% block relbar1 %}{% endblock %}
{% block relbar2 %}{% endblock %}
{% block sidebar1 %}{% endblock %}
{% block sidebar2 %}{% endblock %}
{% block footer %}
<!-- GA -->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-59768903-1', 'auto');
ga('send', 'pageview');
</script>
<!-- intercom -->
<script>
window.intercomSettings = {
app_id: "rucirpb3"
};
</script>
<script>(function(){var w=window;var ic=w.Intercom;if(typeof ic==="function"){ic('reattach_activator');ic('update',intercomSettings);}else{var d=document;var i=function(){i.c(arguments)};i.q=[];i.c=function(args){i.q.push(args)};w.Intercom=i;function l(){var s=d.createElement('script');s.type='text/javascript';s.async=true;s.src='https://widget.intercom.io/widget/rucirpb3';var
x=d.getElementsByTagName('script')[0];x.parentNode.insertBefore(s,x);}if(w.attachEvent){w.attachEvent('onload',l);}else{w.addEventListener('load',l,false);}}})()
</script>
<!-- utils -->
<script type="text/javascript">
// load script file
const loadScript = function(url, callback) {
var head = document.getElementsByTagName('head')[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = url;
// Then bind the event to the callback function.
// There are several events for cross browser compatibility.
script.onreadystatechange = callback;
script.onload = callback;
// Fire the loading
head.appendChild(script);
};
// load css file
const loadCss = function(url, callback) {
var head = document.getElementsByTagName('head')[0];
var link = document.createElement('link');
link.type = 'text/css';
link.rel = 'stylesheet';
link.href = url;
// Then bind the event to the callback function.
// There are several events for cross browser compatibility.
link.onreadystatechange = callback;
link.onload = callback;
// Fire the loading
head.appendChild(link);
};
// make ajax call
const promise_ajax = function(url, data, type) {
return new Promise(function(resolve, reject) {
var req = new XMLHttpRequest();
req.open(type, url, true);
req.setRequestHeader("Content-type", "application/json");
req.onload = function() {
if (req.status === 200) {
resolve(req.response);
} else {
reject(req.response);
}
};
req.onerror = function() {
reject(Error("Network Error"));
};
// Make the request
req.send(JSON.stringify(data));
});
};
// Track GA event
const trackga = function ( category, action, label, value ) {
// If ga is available
if ( ga ) {
ga('send', {
hitType: 'event',
eventCategory: category,
eventAction: action,
eventLabel: label,
eventValue: value
});
}
};
</script>
<!-- support button -->
<script type="text/javascript">
$(document).ready( function() {
const supportbtnHandler = function() {
if ( window.Intercom ) {
window.Intercom('show');
}
};
$('.supportBtn').on('click', supportbtnHandler);
});
</script>
<!-- search -->
<script type="text/javascript">
$(document).ready( function() {
var lastSearched = "";
const showContent = function() {
$('.content_wrapper').removeClass('hide');
$('.search_wrapper').addClass('hide');
};
const showSearchResults = function() {
$('.content_wrapper').addClass('hide');
$('.search_wrapper').removeClass('hide');
};
const searchFunc = function( query, callback, page, restrictAttributes, attributesToRetrieve ) {
var ALGOLIA_APPLICATION_ID = "{{ ALGOLIA_APPLICATION_ID }}";
var ALGOLIA_SEARCH_KEY = "{{ ALGOLIA_SEARCH_KEY }}";
var hitsPerPage = 100;
var page_no = parseInt(page) - 1;
var offset = (hitsPerPage * (page - 1));
var indexName = "{{ ALGOLIA_INDEX_NAME }}";
//var client = algoliasearch(APPLICATION_ID, SEARCH_ONLY_KEY);
var client = algoliasearch(ALGOLIA_APPLICATION_ID, ALGOLIA_SEARCH_KEY, {"protocol":"https:"}); // localhost
var index = client.initIndex(indexName);
var queries = {
query: query,
hitsPerPage: hitsPerPage,
page: page_no,
offset: offset,
restrictSearchableAttributes : restrictAttributes
};
if ( attributesToRetrieve ) {
queries["attributesToRetrieve"] = attributesToRetrieve;
}
index.search(queries, callback );
};
const search = function(searchTerm) {
function callback(err, content ) {
const searchHtmlTemplate = $('.search_wrapper_template').html();
const template = _.template(_.unescape(searchHtmlTemplate));
const templated_html = template({
"objs": content.hits,
"searchString": content.query,
"totalResults": content.nbHits
});
$(".search_wrapper_content").html(templated_html);
hideLoading();
showSearchResults();
trackga('docs', 'search', content.query, content.nbHits);
}
showLoading();
searchFunc(searchTerm, callback, 1, [], ['url', 'description', 'title', 'image', 'tags']);
};
const handleQueryParams = function() {
const re = /query=(.*)$/;
const match = re.exec(window.location.search);
var searchTerm = '';
if ( match ) {
searchTerm = decodeURIComponent(match[1]);
if (searchTerm) {
search(searchTerm);
} else {
showContent();
}
} else {
showContent();
}
$('.search_element').val(searchTerm);
lastSearched = searchTerm;
};
const doSearch = function(value) {
if (lastSearched !== value) {
window.history.pushState({"url": (window.location.pathname + '?query=' + value + '')}, 'Search Page', '?query=' + value + '');
handleQueryParams();
}
};
const inputSearchHandler = function(e) {
// Execute on enter only
if (e.keyCode === 13) {
doSearch(e.target.value)
}
};
$('.search_element').on('keyup', inputSearchHandler);
const iconSearchHandler = function(e) {
doSearch(e.target.nextSibling.nextSibling.value)
};
$('.fa-search').on('click', iconSearchHandler);
// handle state
window.onpopstate = function() {
handleQueryParams();
};
if (window.location.search.length > 0) {
handleQueryParams();
}
});
</script>
<!-- mobile sidebar -->
<script type="text/javascript">
$(document).ready( function() {
const navTreeHandler = function(e) {
const sidebar = $('#sidebar');
const background = $('.content_inner_wrapper');
const body = $('body');
if (sidebar.hasClass('mobile-hide')) {
sidebar.removeClass('mobile-hide');
background.addClass('no_scroll');
body.addClass('no_scroll');
} else {
sidebar.addClass('mobile-hide');
background.removeClass('no_scroll');
body.removeClass('no_scroll');
}
};
$('#nav_tree_icon').on('click', navTreeHandler);
});
</script>
<!-- feedback -->
<script type="text/javascript">
$(document).ready( function() {
var baseDomain = "{{ BASE_DOMAIN }}";
const feedbackUrl = baseDomain ? 'https://data.' + baseDomain + '/v1/query' : 'https://data.hasura-stg.hasura-app.io/v1/query';
const sendFeedback = function(feedback, feedbackDetailMsg) {
const insertQuery = {
'type': 'insert',
'args': {
'table': 'docs_feedback',
'objects': [
{
'page': window.location.pathname,
'feedback': feedback
}
],
'returning': ['id']
}
};
promise_ajax(feedbackUrl, insertQuery, 'POST')
.then( function(response) {
const data = JSON.parse(response);
$('#feedback_box').data('id', data['returning'][0]['id']);
$('.feedback_detail_msg').html(feedbackDetailMsg);
$('.feedback').addClass('hide');
$('.detailed_feedback').removeClass('hide');
})
.catch( function(err) {
alert('Error capturing feedback');
console.log(err);
});
};
const handleThumbUpClick = function(e) {
sendFeedback('positive', 'Great to hear that. Would you like to share any other feedback:');
};
$('.thumb_up').on('click', handleThumbUpClick);
const handleThumbDownClick = function(e) {
sendFeedback('negative', 'Sorry to hear that. Could you please tell us what you were looking for:');
};
$('.thumb_down').on('click', handleThumbDownClick);
const handleFeedbackBtnClick = function(e) {
const feedbackBox = $('#feedback_box');
const feedbackId = feedbackBox.data('id');
const feedbackContent = feedbackBox.val();
const updateQuery = {
'type': 'update',
'args': {
'table': 'docs_feedback',
'$set': {
'feedback_content': feedbackContent
},
'where': {'id': feedbackId}
}
};
promise_ajax(feedbackUrl, updateQuery, 'POST')
.then( function(response) {
$('.detailed_feedback').addClass('hide');
$('.thank_you').removeClass('hide');
})
.catch( function(err) {
alert('Error capturing feedback');
console.log(err);
});
};
$('.feedback_btn').on('click', handleFeedbackBtnClick);
});
</script>
<!-- graphiql -->
<script type="text/javascript">
$(document).ready( function() {
// graphql query fetcher
const graphQLFetcher = function(endpoint) {
endpoint = endpoint || "{{ GRAPHIQL_DEFAULT_ENDPOINT }}";
return function(graphQLParams) {
const params = {
method: 'post',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(graphQLParams),
credentials: 'include'
};
return fetch(endpoint, params)
.then(function (response) {
return response.text();
})
.then(function (responseBody) {
try {
return JSON.parse(responseBody);
} catch (error) {
return responseBody;
}
});
}
};
// create GraphiQL components and embed into HTML
const setupGraphiQL = function() {
if (typeof(React) === 'undefined' || typeof(ReactDOM) === 'undefined' || typeof(GraphiQL) === 'undefined') {
return;
}
const targets = document.getElementsByClassName('graphiql');
for (let i = 0; i < targets.length; i++) {
const target = targets[i];
const endpoint = target.getElementsByClassName("endpoint")[0].innerHTML.trim();
const query = target.getElementsByClassName("query")[0].innerHTML.trim();
const response = target.getElementsByClassName("response")[0].innerHTML.trim();
const graphiQLElement = React.createElement(GraphiQL, {
fetcher: graphQLFetcher(endpoint),
schema: null, // TODO: pass undefined if introspection supported
query: query,
response: response
});
ReactDOM.render(graphiQLElement, target);
}
};
// if graphiql elements present, load react/graphiql js files and setup graphiql
if ($('.graphiql').length > 0) {
const loadingStatus = {'react': false, 'reactDom': false, 'graphiQL': false};
const checkLoading = function(loadedFile) {
return function () {
loadingStatus[loadedFile] = true;
// return if all files not yet loaded
for (var file in loadingStatus) {
if (!loadingStatus[file]) {
return;
}
}
setupGraphiQL();
}
};
loadCss("{{ pathto('_static/graphiql/graphiql.css', 1) }}", function(){});
loadScript("{{ pathto('_static/react/react.min.js', 1) }}", checkLoading('react'));
loadScript("{{ pathto('_static/react/react-dom.min.js', 1) }}", checkLoading('reactDom'));
loadScript("{{ pathto('_static/graphiql/graphiql.min.js', 1) }}", checkLoading('graphiQL'));
}
});
</script>
<!-- global_tabs -->
<script type="text/javascript">
$(document).ready( function() {
if ($('.global-tabs').length > 0) {
loadCss("{{ pathto('_static/global_tabs/global_tabs.css', 1) }}", function(){});
loadScript("{{ pathto('_static/global_tabs/global_tabs.js', 1) }}", function(){});
}
});
</script>
<!-- replace latest release tag -->
<script type="text/javascript">
$(document).ready( function() {
// if latest release tag elements present, replace with latest release tag
if ($('.latest-release-tag').length > 0) {
const releaseUrl = 'https://releases.hasura.io/graphql-engine?agent=docs.hasura.io';
promise_ajax(releaseUrl, null, 'GET')
.then(function (response) {
const data = JSON.parse(response);
const latest_version = data.latest;
const targets = document.getElementsByClassName('latest-release-tag');
for (let i = 0; i < targets.length; i++) {
const target = targets[i];
target.innerHTML = latest_version;
}
})
.catch(function (err) {
console.log(err);
});
}
});
</script>
{% endblock %}

23
docs/_theme/djangodocs/localtoc.html vendored Normal file
View File

@ -0,0 +1,23 @@
{%- if display_toc %}
<div class="header_main_logo inline-block mobile-hide">
<a href="https://{{ BASE_DOMAIN }}/" target="_blank">
<div class="img_wrapper inline-block">
<img class="responsive logo_img" src="{{ pathto('_images/layout/logo1.svg', 1) }}" />
</div>
</a>
<a class="docs_label" href="{{ pathto('', 1) }}">
<div class="inline-block hero"> docs </div>
</a>
<a class="version_txt">
<select value="{{ version }}" onchange="location = this.value;" class="selected">
{%- if version == '1.0' %}
<option class="option_val" value="https://docs.{{ BASE_DOMAIN }}/1.0" selected="selected">v1.0</option>
{%- else -%}
<option class="option_val" value="https://docs.{{ BASE_DOMAIN }}/1.0">v1.0</option>
{% endif %}
</select>
</a>
</div>
{{ toc_full }}
{%- endif %}

3
docs/_theme/djangodocs/modindex.html vendored Normal file
View File

@ -0,0 +1,3 @@
{% extends "basic/modindex.html" %}
{% block bodyclass %}{% endblock %}
{% block sidebarwrapper %}{% endblock %}

View File

@ -0,0 +1,33 @@
<div class="landing_page_wrapper main_wrapper">
<div class="wd_80">
<div class="hcl_half">
<div class="box_wrapper">
<div class="box_head_wrapper">
<div class="box_head">
<img src="{{ pathto('_images/landing/graphql.svg', 1) }}" alt="GraphQL Engine"/>
<h3 class="head_wrapper">1. The Hasura GraphQL Engine</h3>
</div>
<div class="view_all_wrapper small_content">
</div>
</div>
<div class="small_content space_wrapper text_left">
This guide covers all Hasura GraphQL Engine concepts and features.
<br/> <br/>
</div>
<div class="sign_in_wrapper space_wrapper">
<div class="get_start">
<img src="{{ pathto('_images/landing/manual.svg', 1) }}" alt="Manual"/>
</div>
<a href="{{ pathto('graphql/manual/index.html', 1) }}">
<div class="docs_link body_content">
Open manual
<img src="{{ pathto('_images/landing/right-arrow.svg', 1) }}" alt="Right Arrow" />
</div>
</a>
</div>
</div>
</div>
<div style="clear: both;"></div>
</div>
<div style="clear: both;"></div>
</div>

View File

@ -0,0 +1,16 @@
<div class="noprojects_overley hide">
<div class="loader"></div>
<div class="subhead_wrapper_loader body_content_position"></div>
<div class="overley_wrapper"></div>
</div>
<script type="text/javascript">
const showLoading = function() {
$('.noprojects_overley').removeClass('hide');
};
const hideLoading = function() {
$('.noprojects_overley').addClass('hide');
};
</script>

1
docs/_theme/djangodocs/search.html vendored Normal file
View File

@ -0,0 +1 @@

0
docs/_theme/djangodocs/searchbox.html vendored Normal file
View File

View File

@ -0,0 +1,3 @@
@import url(reset-fonts-grids.css);
@import url(djangodocs.css);
@import url(homepage.css);

View File

@ -0,0 +1,167 @@
/*** setup ***/
html { background:#092e20;}
body { font:12px/1.5 Verdana,sans-serif; background:#092e20; color: white;}
.footer-hasura-custom {
font-size: 14px;
padding: 30px;
font-family: sans-serif;
font-weight: 300;
}
#custom-doc {
width: 100%;
margin:auto;
text-align:left;
margin-top:0;
min-width: auto;
}
#hd {
padding: 12px 20px;
background: #559e73;
}
#bd { background:#234F32; }
#ft { color:#487858; font-size:90%; padding-bottom: 2em; }
/*** links ***/
a {text-decoration: none;}
a img {border: none;}
#bd a:link, #bd a:visited { color:#ab5603; text-decoration:underline; }
#bd #sidebar a:link, #bd #sidebar a:visited { color:#ffc757; text-decoration:none; }
a:hover { color:#ffe761; }
#bd a:hover { background-color:#E0FFB8; color:#234f32; text-decoration:none; }
#bd #sidebar a:hover { color:#ffe761; background:none; }
h2 a, h3 a, h4 a { text-decoration:none !important; }
a.reference em { font-style: normal; }
/* a:link, a:visited { color:#ffc757; } */
/*** sidebar **/
#sidebar div.sphinxsidebarwrapper { font-size:92%; margin-right: 14px; }
#sidebar a { font-size: 0.9em; }
#sidebar ul ul { margin-top:0; margin-bottom:0; }
#sidebar li { margin-top: 0.2em; margin-bottom: 0.2em; }
/***
#sidebar h3, #sidebar h4 { color: white; font-size: 125%; }
#sidebar a { color: white; }
***/
/*** nav ***/
/* div.nav { margin: 0; font-size: 11px; text-align: right; color: #487858;} */
#hd div.nav { margin-top: -27px; }
#ft div.nav {
padding: 20px 40px;
}
#hd h1 a {
font-family: Roboto;
font-weight: 300;
color: #fff;
}
#global-nav { padding: 20px; color:#263E2B; }
#global-nav .ui-widget { display: inline-block; }
/*** content ***/
#yui-main div.yui-b { position: relative; }
#yui-main div.yui-b { margin: 0 0 0 20px; background: white; color: black; padding: 0.3em 2em 1em 2em; }
/*** basic styles ***/
dd { margin-left:15px; }
h1,h2,h3,h4 { margin-top:1em; font-family:"Trebuchet MS",sans-serif; font-weight:normal; }
h1 { font-size:218%; margin-top:0.6em; margin-bottom:.4em; line-height:1.1em; }
h2 { font-size:175%; margin-bottom:.6em; line-height:1.2em; color:#092e20; }
h3 { font-size:150%; font-weight:bold; margin-bottom:.2em; color:#487858; }
h4 { font-size:125%; font-weight:bold; margin-top:1.5em; margin-bottom:3px; }
div.figure { text-align: center; }
div.figure p.caption { font-size:1em; margin-top:0; margin-bottom:1.5em; color: #555;}
hr { color:#ccc; background-color:#ccc; height:1px; border:0; }
p, ul, dl { margin-top:.6em; margin-bottom:1em; padding-bottom: 0.1em;}
#yui-main div.yui-b img { max-width: 50em; margin-left: auto; margin-right: auto; display: block; }
caption { font-size:1em; font-weight:bold; margin-top:0.5em; margin-bottom:0.5em; margin-left: 2px; text-align: center; }
blockquote { padding: 0 1em; margin: 1em 0; font:125%/1.2em "Trebuchet MS", sans-serif; color:#234f32; border-left:2px solid #94da3a; }
strong { font-weight: bold; }
em { font-style: italic; }
ins { font-weight: bold; text-decoration: none; }
/*** lists ***/
ul { padding-left:30px; }
ol { padding-left:30px; }
ol.arabic li { list-style-type: decimal; }
ul li { list-style-type:square; margin-bottom:.4em; }
ul ul li { list-style-type:disc; }
ul ul ul li { list-style-type:circle; }
ol li { margin-bottom: .4em; }
ul ul { padding-left:1.2em; }
ul ul ul { padding-left:1em; }
ul.linklist, ul.toc { padding-left:0; }
ul.toc ul { margin-left:.6em; }
ul.toc ul li { list-style-type:square; }
ul.toc ul ul li { list-style-type:disc; }
ul.linklist li, ul.toc li { list-style-type:none; }
dt { font-weight:bold; margin-top:.5em; font-size:1.1em; }
dd { margin-bottom:.8em; }
ol.toc { margin-bottom: 2em; }
ol.toc li { font-size:125%; padding: .5em; line-height:1.2em; clear: right; }
ol.toc li.b { background-color: #E0FFB8; }
ol.toc li a:hover { background-color: transparent !important; text-decoration: underline !important; }
ol.toc span.release-date { color:#487858; float: right; font-size: 85%; padding-right: .5em; }
ol.toc span.comment-count { font-size: 75%; color: #999; }
/*** tables ***/
table { color:#000; margin-bottom: 1em; width: 100%; }
table.docutils td p { margin-top:0; margin-bottom:.5em; }
table.docutils td, table.docutils th { border-bottom:1px solid #dfdfdf; padding:10px 10px;}
table.docutils thead th { border-bottom:2px solid #dfdfdf; text-align:left; font-weight: bold; white-space: nowrap; }
table.docutils thead th p { margin: 0; padding: 0; }
table.docutils { border-collapse:collapse; border: 1px solid #CFE3DC;}
/*** code blocks
.literal { color:#234f32; white-space:nowrap; }
dt > tt.literal { white-space: normal; }
#sidebar .literal { color:white; background:transparent; font-size:11px; }
h4 .literal { color: #234f32; font-size: 13px; }
pre { font-size:small; background:#E0FFB8; border:1px solid #94da3a; border-width:1px 0; margin: 1em 0; padding: .3em .4em; overflow: hidden; line-height: 1.3em; white-space: pre-wrap;}
dt .literal, table .literal { background:none; }
#bd a.reference { text-decoration: none; }
#bd a.reference tt.literal { border-bottom: 1px #234f32 dotted; }
div.snippet-filename { color: white; background-color: #234F32; margin: 0; padding: 2px 5px; width: 100%; font-family: monospace; font-size: small; line-height: 1.3em; }
div.snippet-filename + div.highlight > pre { margin-top: 0; }
div.snippet-filename + pre { margin-top: 0; }
*/
/* Restore colors of pygments hyperlinked code */
#bd .highlight .k a:link, #bd .highlight .k a:visited { color: #000000; text-decoration: none; border-bottom: 1px dotted #000000; }
#bd .highlight .nf a:link, #bd .highlight .nf a:visited { color: #990000; text-decoration: none; border-bottom: 1px dotted #990000; }
/*** notes & admonitions ***/
/*
.note, .admonition { padding:.8em 1em .8em; margin: 1em 0; border:1px solid #94da3a; }
.admonition-title { font-weight:bold; margin-top:0 !important; margin-bottom:0 !important;}
.admonition .last { margin-bottom:0 !important; }
.note, .admonition { padding-left:65px; background:url(docicons-note.png) .8em .8em no-repeat;}
div.admonition-philosophy { padding-left:65px; background:url(docicons-philosophy.png) .8em .8em no-repeat;}
div.admonition-behind-the-scenes { padding-left:65px; background:url(docicons-behindscenes.png) .8em .8em no-repeat;}
.admonition.warning { background:url(docicons-warning.png) .8em .8em no-repeat; border:1px solid #ffc83c;}
*/
/*** versionadded/changes ***/
div.versionadded, div.versionchanged { }
div.versionadded span.title, div.versionchanged span.title, span.versionmodified { font-weight: bold; }
div.versionadded, div.versionchanged, div.deprecated { color:#555; }
/*** p-links ***/
a.headerlink { color: #c60f0f; font-size: 0.8em; padding: 0 4px 0 4px; text-decoration: none; visibility: hidden; }
h1:hover > a.headerlink, h2:hover > a.headerlink, h3:hover > a.headerlink, h4:hover > a.headerlink, h5:hover > a.headerlink, h6:hover > a.headerlink, dt:hover > a.headerlink { visibility: visible; }
/*** index ***/
table.indextable td { text-align: left; vertical-align: top;}
table.indextable dl, table.indextable dd { margin-top: 0; margin-bottom: 0; }
table.indextable tr.pcap { height: 10px; }
table.indextable tr.cap { margin-top: 10px; background-color: #f2f2f2;}
/*** page-specific overrides ***/
div#contents ul { margin-bottom: 0;}
div#contents ul li { margin-bottom: 0;}
div#contents ul ul li { margin-top: 0.3em;}
/*** IE hacks
* pre { width: 100%; }
**/

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

View File

@ -0,0 +1,22 @@
#index p.rubric { font-size:150%; font-weight:normal; margin-bottom:.2em; color:#487858; }
#index div.section dt { font-weight: normal; }
#index #s-getting-help { float: right; width: 35em; background: #E1ECE2; padding: 1em; margin: 2em 0 2em 2em; }
#index #s-getting-help h2 { margin: 0; }
#index #s-django-documentation div.section div.section h3 { margin: 0; }
#index #s-django-documentation div.section div.section { background: #E1ECE2; padding: 1em; margin: 2em 0 2em 40.3em; }
#index #s-django-documentation div.section div.section a.reference { white-space: nowrap; }
#index #s-using-django dl,
#index #s-add-on-contrib-applications dl,
#index #s-solving-specific-problems dl,
#index #s-reference dl
{ float: left; width: 41em; }
#index #s-add-on-contrib-applications,
#index #s-solving-specific-problems,
#index #s-reference,
#index #s-and-all-the-rest
{ clear: left; }

View File

@ -0,0 +1,13 @@
/*
Copyright (c) 2008, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.net/yui/license.txt
version: 2.5.1
*/
html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym {border:0;font-variant:normal;}sup {vertical-align:text-top;}sub {vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;}body {font:13px/1.231 arial,helvetica,clean,sans-serif;*font-size:small;*font:x-small;}table {font-size:inherit;font:100%;}
body{text-align:center;}#ft{clear:both;}#doc,#doc2,#doc3,#doc4,.yui-t1,.yui-t2,.yui-t3,.yui-t4,.yui-t5,.yui-t6,.yui-t7{margin:auto;text-align:left;width:57.69em;*width:56.25em;min-width:750px;}#doc2{width:73.076em;*width:71.25em;}#doc3{margin:auto 10px;width:auto;}#doc4{width:74.923em;*width:73.05em;}.yui-b{position:relative;}.yui-b{_position:static;}#yui-main .yui-b{position:static;}#yui-main{width:100%;}.yui-t1 #yui-main,.yui-t2 #yui-main,.yui-t3 #yui-main{float:right;margin-left:-25em;}.yui-t4 #yui-main,.yui-t5 #yui-main,.yui-t6 #yui-main{float:left;margin-right:-25em;}.yui-t1 .yui-b{float:left;width:12.30769em;*width:12.00em;}.yui-t1 #yui-main .yui-b{margin-left:13.30769em;*margin-left:13.05em;}.yui-t2 .yui-b{float:left;width:13.8461em;*width:13.50em;}.yui-t2 #yui-main .yui-b{margin-left:14.8461em;*margin-left:14.55em;}.yui-t3 .yui-b{float:left;width:23.0769em;*width:22.50em;}.yui-t3 #yui-main .yui-b{margin-left:24.0769em;*margin-left:23.62em;}.yui-t4 .yui-b{float:right;width:13.8456em;*width:13.50em;}.yui-t4 #yui-main .yui-b{margin-right:14.8456em;*margin-right:14.55em;}.yui-t5 .yui-b{float:right;width:18.4615em;*width:18.00em;}.yui-t5 #yui-main .yui-b{margin-right:19.4615em;*margin-right:19.125em;}
.yui-t6 .yui-b{float:left; padding-left: 40px; }
/* pre,code,kbd,samp,tt{font-family:monospace;*font-size:108%;line-height:100%;} */
.yui-t6 #yui-main .yui-b{margin-right:24.0769em;*margin-right:23.62em;}.yui-t7 #yui-main .yui-b{display:block;margin:0 0 1em 0;}#yui-main .yui-b{float:none;width:auto;}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{float:left;}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf,.yui-gc .yui-u,.yui-gd .yui-g,.yui-g .yui-gc .yui-u,.yui-ge .yui-u,.yui-ge .yui-g,.yui-gf .yui-g,.yui-gf .yui-u{float:right;}.yui-g div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first,.yui-ge div.first,.yui-gf div.first,.yui-g .yui-gc div.first,.yui-g .yui-ge div.first,.yui-gc div.first div.first{float:left;}.yui-g .yui-u,.yui-g .yui-g,.yui-g .yui-gb,.yui-g .yui-gc,.yui-g .yui-gd,.yui-g .yui-ge,.yui-g .yui-gf{width:49.1%;}.yui-gb .yui-u,.yui-g .yui-gb .yui-u,.yui-gb .yui-g,.yui-gb .yui-gb,.yui-gb .yui-gc,.yui-gb .yui-gd,.yui-gb .yui-ge,.yui-gb .yui-gf,.yui-gc .yui-u,.yui-gc .yui-g,.yui-gd .yui-u{width:32%;margin-left:1.99%;}.yui-gb .yui-u{*margin-left:1.9%;*width:31.9%;}.yui-gc div.first,.yui-gd .yui-u{width:66%;}.yui-gd div.first{width:32%;}.yui-ge div.first,.yui-gf .yui-u{width:74.2%;}.yui-ge .yui-u,.yui-gf div.first{width:24%;}.yui-g .yui-gb div.first,.yui-gb div.first,.yui-gc div.first,.yui-gd div.first{margin-left:0;}.yui-g .yui-g .yui-u,.yui-gb .yui-g .yui-u,.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u,.yui-ge .yui-g .yui-u,.yui-gf .yui-g .yui-u{width:49%;*width:48.1%;*margin-left:0;}.yui-g .yui-gb div.first,.yui-gb .yui-gb div.first{*margin-right:0;*width:32%;_width:31.7%;}.yui-g .yui-gc div.first,.yui-gd .yui-g{width:66%;}.yui-gb .yui-g div.first{*margin-right:4%;_margin-right:1.3%;}.yui-gb .yui-gc div.first,.yui-gb .yui-gd div.first{*margin-right:0;}.yui-gb .yui-gb .yui-u,.yui-gb .yui-gc .yui-u{*margin-left:1.8%;_margin-left:4%;}.yui-g .yui-gb .yui-u{_margin-left:1.0%;}.yui-gb .yui-gd .yui-u{*width:66%;_width:61.2%;}.yui-gb .yui-gd div.first{*width:31%;_width:29.5%;}.yui-g .yui-gc .yui-u,.yui-gb .yui-gc .yui-u{width:32%;_float:right;margin-right:0;_margin-left:0;}.yui-gb .yui-gc div.first{width:66%;*float:left;*margin-left:0;}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf .yui-u{margin:0;}.yui-gb .yui-gb .yui-u{_margin-left:.7%;}.yui-gb .yui-g div.first,.yui-gb .yui-gb div.first{*margin-left:0;}.yui-gc .yui-g .yui-u,.yui-gd .yui-g .yui-u{*width:48.1%;*margin-left:0;}s .yui-gb .yui-gd div.first{width:32%;}.yui-g .yui-gd div.first{_width:29.9%;}.yui-ge .yui-g{width:24%;}.yui-gf .yui-g{width:74.2%;}.yui-gb .yui-ge div.yui-u,.yui-gb .yui-gf div.yui-u{float:right;}.yui-gb .yui-ge div.first,.yui-gb .yui-gf div.first{float:left;}.yui-gb .yui-ge .yui-u,.yui-gb .yui-gf div.first{*width:24%;_width:20%;}.yui-gb .yui-ge div.first,.yui-gb .yui-gf .yui-u{*width:73.5%;_width:65.5%;}.yui-ge div.first .yui-gd .yui-u{width:65%;}.yui-ge div.first .yui-gd div.first{width:32%;}#bd:after,.yui-g:after,.yui-gb:after,.yui-gc:after,.yui-gd:after,.yui-ge:after,.yui-gf:after{content:".";display:block;height:0;clear:both;visibility:hidden;}#bd,.yui-g,.yui-gb,.yui-gc,.yui-gd,.yui-ge,.yui-gf{zoom:1;}

4
docs/_theme/djangodocs/theme.conf vendored Normal file
View File

@ -0,0 +1,4 @@
[theme]
inherit = basic
stylesheet = default.css
pygments_style = trac

View File

@ -0,0 +1,76 @@
import sys
import os
import json
import time
from algoliasearch import algoliasearch
APPLICATION_ID = os.environ["ALGOLIA_APPLICATION_ID"]
ADMIN_KEY = os.environ["ALGOLIA_ADMIN_KEY"]
ALGOLIA_INDEX_NAME = os.environ["ALGOLIA_INDEX_NAME"]
client = algoliasearch.Client(APPLICATION_ID, ADMIN_KEY)
index = client.init_index(ALGOLIA_INDEX_NAME)
def update_index(data):
index.clear_index()
print("\nINDEX CLEARED!\n")
index.add_objects(data)
print("INDEX REPOPULATED!\n")
def output_indexed_data():
res = index.browse_all({"query": ""})
count = 0
# print("INDEXED PAGES:")
for hit in res:
count += 1
# print('\t' + hit['title'] + ' (' + hit['url'] + ')')
print('\nTOTAL INDEXED: ' + str(count))
def process_data(json_data):
processed_data = []
CONTENT_MAX_LENGTH = 18000
print('TRIMMED:')
for json_obj in json_data:
if len(json_obj['content']) < CONTENT_MAX_LENGTH:
processed_data.append(json_obj)
else:
obj = json.loads(json.dumps(json_obj))
split_content = [obj['content'][i:i + CONTENT_MAX_LENGTH] for i in range(0, len(obj['content']), CONTENT_MAX_LENGTH)]
for content_piece in split_content:
obj = json.loads(json.dumps(json_obj))
obj['content'] = content_piece
processed_data.append(obj)
break # ignoring other pieces as unique title limitation
print('\t' + obj['title'] + ' (' + obj['url'] + ')')
return processed_data
def docs_index(data_source):
json_data = open(data_source, 'r')
json_d = json.loads(json_data.read())
processed_json_d = process_data(json_d)
update_index(processed_json_d)
time.sleep(2)
output_indexed_data()
if __name__ == "__main__":
if len(sys.argv[1:]) == 0:
print("INDEX FILE REQUIRED!! usage: algolia_index.py <index_file>")
exit(0)
docs_index(sys.argv[1])

419
docs/conf.py Normal file
View File

@ -0,0 +1,419 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Hasura Platform documentation build configuration file, created by
# sphinx-quickstart on Thu Jun 30 19:38:30 2016.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
import os
import sys
from os.path import abspath, dirname, join
sys.setrecursionlimit(2000)
# Monkey patching StandaloneHTMLBuilder to not include unnecessary scripts
from sphinx.builders.html import StandaloneHTMLBuilder
# from sphinx.util.osutil import relative_uri
StandaloneHTMLBuilder.script_files = ["_static/vendor.js"]
# StandaloneHTMLBuilder.imgpath = relative_uri("v0.13", '_images')
# Defaults to development
CURRENT_ENV = os.getenv("ENV") if os.getenv("ENV") else "development"
BASE_DOMAIN = os.getenv("BASE_DOMAIN", "development")
# Algolia variables
ALGOLIA_SECRETS = {
"development": {
"APPLICATION_ID": "WCBB1VVLRC",
"APPLICATION_SEARCH_KEY": "a50259f1da0f835fbb5e4d421b97b2de",
"ALGOLIA_INDEX_NAME": "stg_graphql_docs_search",
},
"production": {
"APPLICATION_ID": "WCBB1VVLRC",
"APPLICATION_SEARCH_KEY": "dea0d2c67378878fad5678396a532c95",
"ALGOLIA_INDEX_NAME": "graphql_docs_search",
}
}
ALGOLIA_APPLICATION_ID = ALGOLIA_SECRETS[CURRENT_ENV]["APPLICATION_ID"]
ALGOLIA_SEARCH_KEY = ALGOLIA_SECRETS[CURRENT_ENV]["APPLICATION_SEARCH_KEY"]
# Get from env if set
if os.getenv("ALGOLIA_APPLICATION_ID") and os.getenv("ALGOLIA_SEARCH_KEY"):
ALGOLIA_APPLICATION_ID = os.getenv("ALGOLIA_APPLICATION_ID")
ALGOLIA_SEARCH_KEY = os.getenv("ALGOLIA_SEARCH_KEY")
ALGOLIA_INDEX_NAME = ALGOLIA_SECRETS[CURRENT_ENV]["ALGOLIA_INDEX_NAME"]
# Get from env if set
if os.getenv("ALGOLIA_INDEX_NAME"):
ALGOLIA_INDEX_NAME = os.getenv("ALGOLIA_INDEX_NAME")
# GraphiQL defaults
GRAPHIQL_DEFAULT_ENDPOINT = "https://data.accouterments35.hasura-app.io/v1alpha1/graphql"
# Get from env if set
if os.getenv("GRAPHIQL_DEFAULT_ENDPOINT"):
GRAPHIQL_DEFAULT_ENDPOINT = os.getenv("GRAPHIQL_DEFAULT_ENDPOINT")
# set context
html_context = {
"SITEMAP_DOMAIN": "https://docs.hasura.io/",
"BASE_DOMAIN": "hasura.io" if BASE_DOMAIN == "production" else "hasura-stg.hasura-app.io",
"GRAPHIQL_DEFAULT_ENDPOINT": GRAPHIQL_DEFAULT_ENDPOINT,
"ALGOLIA_APPLICATION_ID": ALGOLIA_APPLICATION_ID,
"ALGOLIA_SEARCH_KEY": ALGOLIA_SEARCH_KEY,
"ALGOLIA_INDEX_NAME": ALGOLIA_INDEX_NAME
}
# End of it
# sys.path.insert(0, os.path.abspath('.'))
sys.path.append(abspath(join(dirname(__file__), "_ext")))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
# "sphinxcontrib.fulltoc",
extensions = [
"djangodocs",
"fulltoc",
"generate_index",
"sphinx.ext.intersphinx",
"sphinx.ext.viewcode",
"sphinxcontrib.swaggerdoc",
"sphinxcontrib.httpdomain",
"sphinx.ext.todo",
"global_tabs",
"sphinx_tabs.tabs",
"graphiql",
"lexer_jsx",
"lexer_graphql",
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The encoding of source files.
#
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = 'Hasura'
copyright = '2016, Hasura'
author = 'Hasura Developers'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = 'x.y'
# The full version, including alpha/beta/rc tags.
release = 'x.y'
#epilog rst lines for frequently reference links in docs
rst_epilog = """
"""
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#
# today = ''
#
# Else, today_fmt is used as the format for a strftime call.
#
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'venv', 'Thumbs.db', '.DS_Store']
# The reST default role (used for this markup: `text`) to use for all
# documents.
#
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
# keep_warnings = False
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = True
# -- Options for HTML output ----------------------------------------------
# import sphinx_rtd_theme
# html_theme = "sphinx_rtd_theme"
# html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
html_theme = "djangodocs"
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
#html_theme = 'alabaster'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = ["_theme"]
# html_title = '| Hasura ' + version + ' documentation'
# The name for this set of Sphinx documents.
# "<project> v<release> documentation" by default.
#
# html_title = 'Hasura Platform valpha'
# A shorter title for the navigation bar. Default is the same as html_title.
#
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#
# html_logo = None
# The name of an image file (relative to this directory) to use as a favicon of
# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#
html_favicon = "img/layout/favicon.ico"
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#
# html_extra_path = []
# If not None, a 'Last updated on:' timestamp is inserted at every page
# bottom, using the given strftime format.
# The empty string is equivalent to '%b %d, %Y'.
#
# html_last_updated_fmt = ''
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#
# html_additional_pages = {}
# If false, no module index is generated.
#
# html_domain_indices = True
# If false, no index is generated.
#
# html_use_index = True
# If true, the index is split into individual pages for each letter.
#
# html_split_index = False
# If true, links to the reST sources are added to the pages.
#
html_show_sourcelink = False
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#
html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#
html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = None
# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja'
# 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh'
#
# html_search_language = 'en'
# A dictionary with options for the search language support, empty by default.
# 'ja' uses this config value.
# 'zh' user can custom change `jieba` dictionary path.
#
# html_search_options = {'type': 'default'}
# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
#
# html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder.
htmlhelp_basename = 'Hasuradoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'Hasura.tex', 'Hasura Documentation',
author, 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#
# latex_use_parts = False
# If true, show page references after internal links.
#
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
#
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
#
# latex_appendices = []
# If false, no module index is generated.
#
# latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'hasura', 'Hasura Documentation',
[author], 1)
]
# If true, show URL addresses after external links.
#
# man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'Hasura', 'Hasura Documentation',
author, 'Hasura', 'Hasura documentation',
'Docs'),
]
# Documents to append as an appendix to all manuals.
#
# texinfo_appendices = []
# If false, no module index is generated.
#
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#
# texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#
# texinfo_no_detailmenu = False

View File

@ -0,0 +1,8 @@
- args:
sql: CREATE TABLE public."author"("id" serial NOT NULL, "name" text NOT NULL,
PRIMARY KEY ("id") )
type: run_sql
- args:
name: author
schema: public
type: add_existing_table_or_view

View File

@ -0,0 +1,13 @@
- args:
sql: |-
insert into author (id, name) values (1, 'Justin');
insert into author (id, name) values (2, 'Beltran');
insert into author (id, name) values (3, 'Sidney');
insert into author (id, name) values (4, 'Anjela');
insert into author (id, name) values (5, 'Amii');
insert into author (id, name) values (6, 'Corny');
insert into author (id, name) values (7, 'Berti');
insert into author (id, name) values (8, 'April');
insert into author (id, name) values (9, 'Ninnetta');
insert into author (id, name) values (10, 'Lyndsay');
type: run_sql

View File

@ -0,0 +1,10 @@
- args:
sql: CREATE TABLE public."article"("id" serial NOT NULL, "title" text NOT NULL,
"content" text NOT NULL, "rating" integer NOT NULL, "author_id" integer NOT
NULL, "is_published" boolean NOT NULL DEFAULT true, "published_on" date NOT
NULL DEFAULT CURRENT_DATE, PRIMARY KEY ("id") )
type: run_sql
- args:
name: article
schema: public
type: add_existing_table_or_view

View File

@ -0,0 +1,4 @@
- args:
sql: ALTER TABLE public."article" ADD FOREIGN KEY ("author_id") REFERENCES public."author"
("id")
type: run_sql

View File

@ -0,0 +1,8 @@
- args:
name: author
table:
name: article
schema: public
using:
foreign_key_constraint_on: author_id
type: create_object_relationship

View File

@ -0,0 +1,12 @@
- args:
name: articles
table:
name: author
schema: public
using:
foreign_key_constraint_on:
column: author_id
table:
name: article
schema: public
type: create_array_relationship

View File

@ -0,0 +1,23 @@
- args:
sql: |-
insert into article (id, title, content, rating, author_id, is_published, published_on) values (1, 'sit amet', 'Aliquam erat volutpat. In congue.', 1, 4, true, '2017-08-09');
insert into article (id, title, content, rating, author_id, is_published, published_on) values (2, 'a nibh', 'Quisque porta volutpat erat. Quisque erat eros, viverra eget, congue eget, semper rutrum, nulla. Nunc purus.', 3, 2, true, '2018-06-10');
insert into article (id, title, content, rating, author_id, is_published, published_on) values (3, 'amet justo morbi', 'Maecenas tristique, est et tempus semper, est quam pharetra magna, ac consequat metus sapien ut nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Mauris viverra diam vitae quam. Suspendisse potenti.', 4, 4, true, '2017-05-26');
insert into article (id, title, content, rating, author_id, is_published, published_on) values (4, 'vestibulum ac est', 'Integer tincidunt ante vel ipsum. Praesent blandit lacinia erat.', 2, 5, true, '2017-03-05');
insert into article (id, title, content, rating, author_id, is_published, published_on) values (5, 'ut blandit', 'Quisque erat eros, viverra eget, congue eget, semper rutrum, nulla. Nunc purus. Phasellus in felis.', 2, 5, false, '2017-01-01');
insert into article (id, title, content, rating, author_id, is_published, published_on) values (6, 'sapien ut', 'Nulla ac enim. In tempor, turpis nec euismod scelerisque, quam turpis adipiscing lorem, vitae mattis nibh ligula nec sem.', 1, 3, true, '2018-01-08');
insert into article (id, title, content, rating, author_id, is_published, published_on) values (7, 'nisl duis ac', 'Vestibulum quam sapien, varius ut, blandit non, interdum in, ante. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Duis faucibus accumsan odio.', 4, 6, true, '2016-07-09');
insert into article (id, title, content, rating, author_id, is_published, published_on) values (8, 'donec semper sapien', 'Praesent blandit. Nam nulla. Integer pede justo, lacinia eget, tincidunt eget, tempus vel, pede.', 3, 6, false, '2016-07-16');
insert into article (id, title, content, rating, author_id, is_published, published_on) values (9, 'sit amet', 'In tempor, turpis nec euismod scelerisque, quam turpis adipiscing lorem, vitae mattis nibh ligula nec sem. Duis aliquam convallis nunc. Proin at turpis a pede posuere nonummy.', 3, 2, true, '2017-05-16');
insert into article (id, title, content, rating, author_id, is_published, published_on) values (10, 'dui proin leo', 'Integer non velit. Donec diam neque, vestibulum eget, vulputate ut, ultrices vel, augue.', 3, 10, false, '2016-07-06');
insert into article (id, title, content, rating, author_id, is_published, published_on) values (11, 'turpis eget', 'Suspendisse potenti. Cras in purus eu magna vulputate luctus.', 3, 3, true, '2017-04-14');
insert into article (id, title, content, rating, author_id, is_published, published_on) values (12, 'volutpat quam pede', 'Aliquam erat volutpat. In congue.', 3, 5, true, '2016-10-24');
insert into article (id, title, content, rating, author_id, is_published, published_on) values (13, 'vulputate elementum', 'Nulla nisl. Nunc nisl.', 4, 8, true, '2018-03-10');
insert into article (id, title, content, rating, author_id, is_published, published_on) values (14, 'congue etiam justo', 'Maecenas pulvinar lobortis est. Phasellus sit amet erat. Nulla tempus.', 4, 3, false, '2017-07-02');
insert into article (id, title, content, rating, author_id, is_published, published_on) values (15, 'vel dapibus at', 'Etiam vel augue. Vestibulum rutrum rutrum neque.', 4, 1, true, '2018-01-02');
insert into article (id, title, content, rating, author_id, is_published, published_on) values (16, 'sem duis aliquam', 'Maecenas leo odio, condimentum id, luctus nec, molestie sed, justo. Pellentesque viverra pede ac diam.', 1, 1, true, '2018-02-14');
insert into article (id, title, content, rating, author_id, is_published, published_on) values (17, 'montes nascetur ridiculus', 'Nulla justo. Aliquam quis turpis eget elit sodales scelerisque. Mauris sit amet eros.', 5, 5, false, '2018-04-18');
insert into article (id, title, content, rating, author_id, is_published, published_on) values (18, 'ipsum primis in', 'Morbi vel lectus in quam fringilla rhoncus. Mauris enim leo, rhoncus sed, vestibulum sit amet, cursus id, turpis. Integer aliquet, massa id lobortis convallis, tortor risus dapibus augue, vel accumsan tellus nisi eu orci.', 2, 7, false, '2017-06-25');
insert into article (id, title, content, rating, author_id, is_published, published_on) values (19, 'pede venenatis', 'Pellentesque ultrices mattis odio. Donec vitae nisi.', 2, 5, false, '2017-01-06');
insert into article (id, title, content, rating, author_id, is_published, published_on) values (20, 'eu nibh', 'Ut at dolor quis odio consequat varius. Integer ac leo.', 2, 8, true, '2017-01-10');
type: run_sql

View File

@ -0,0 +1,9 @@
- args:
permission:
columns:
- id
- name
filter: {}
role: anonymous
table: author
type: create_select_permission

View File

@ -0,0 +1,14 @@
- args:
permission:
columns:
- id
- title
- content
- rating
- author_id
- is_published
- published_on
filter: {}
role: anonymous
table: article
type: create_select_permission

View File

@ -0,0 +1,7 @@
- args:
sql: CREATE TABLE "sql_function_table"("id" serial NOT NULL, "input" text NOT
NULL, "output" text , PRIMARY KEY ("id") )
type: run_sql
- args:
name: sql_function_table
type: add_existing_table_or_view

View File

@ -0,0 +1,12 @@
- args:
sql: |-
CREATE FUNCTION test_func() RETURNS trigger AS $emp_stamp$
BEGIN
NEW.output := UPPER(NEW.input);
RETURN NEW;
END;
$emp_stamp$ LANGUAGE plpgsql;
CREATE TRIGGER test_trigger BEFORE INSERT OR UPDATE ON sql_function_table
FOR EACH ROW EXECUTE PROCEDURE test_func();
type: run_sql

View File

@ -0,0 +1,3 @@
- args:
sql: ALTER TABLE "article" ALTER COLUMN "published_on" DROP NOT NULL;
type: run_sql

View File

@ -0,0 +1,6 @@
- args:
sql: |-
UPDATE article
SET published_on = NULL
WHERE is_published=false;
type: run_sql

View File

@ -0,0 +1,11 @@
- args:
sql: |-
CREATE VIEW author_average_rating AS
SELECT author.id, avg(article.rating)
From author, article
WHERE author.id = article.author_id
GROUP BY author.id
type: run_sql
- args:
name: author_average_rating
type: add_existing_table_or_view

View File

@ -0,0 +1,9 @@
- args:
name: avg_rating
table: author
using:
manual_configuration:
column_mapping:
id: id
remote_table: author_average_rating
type: create_object_relationship

View File

@ -0,0 +1,9 @@
- args:
permission:
columns:
- id
- avg
filter: {}
role: anonymous
table: author_average_rating
type: create_select_permission

View File

@ -0,0 +1,9 @@
- args:
sql: |-
CREATE VIEW article_safe AS
SELECT id, title, rating
FROM article;
type: run_sql
- args:
name: article_safe
type: add_existing_table_or_view

View File

@ -0,0 +1,10 @@
- args:
permission:
columns:
- id
- title
- rating
filter: {}
role: anonymous
table: article_safe
type: create_select_permission

View File

@ -0,0 +1,3 @@
- args:
sql: ALTER TABLE "article" ALTER COLUMN "rating" DROP NOT NULL;
type: run_sql

View File

@ -0,0 +1,29 @@
GraphQL API Reference
=====================
Endpoints
---------
- All GraphQL requests for queries, subscriptions and mutations are ``POST`` requests to ``/v1alpha1/graphql``.
Request types
-------------
You can make the following types of requests using the GraphQL API:
- :doc:`Query/Subscription <query>`
- :doc:`Mutation <mutation>`
Supported PostgreSQL types
--------------------------
You can refer the following to know about all PostgreSQL types supported by the Hasura GraphQL engine:
- :doc:`Supported PostgreSQL types <postgresql-types>`
.. toctree::
:maxdepth: 1
:hidden:
Query/Subscription <query>
Mutation <mutation>
Supported PostgreSQL types <postgresql-types>

View File

@ -0,0 +1,500 @@
.. title:: API Reference - Mutation
API Reference - Mutation
========================
Insert/Upsert syntax
--------------------
.. code-block:: none
mutation [<mutation-name>] {
<mutation-field-name> (
[<input-object>!]
[conflict-clause]
)
[mutation-response!]
}
.. list-table::
:header-rows: 1
* - Key
- Required
- Schema
- Description
* - mutation-name
- false
- Value
- Name mutation for observability
* - mutation-field-name
- true
- Value
- name of the auto-generated mutation field. E.g. *insert_author*
* - input-object
- true
- InputObject_
- name of the auto-generated mutation field
* - mutation-response
- true
- MutationResponse_
- Object to be returned after mutation succeeds.
* - on-conflict
- false
- ConflictClause_
- Converts *insert* to *upsert* by handling conflict
**E.g. INSERT**:
.. code-block:: graphql
mutation insert_article {
insert_article(
objects: [
{
title: "Software is gluttonous",
content: "Something happened in HP",
author_id: 3
}
]
) {
returning {
id
title
}
}
}
**E.g. UPSERT**:
.. code-block:: graphql
mutation upsert_author {
insert_author (
objects: [
{
name: "John",
id:12
}
],
on_conflict: {
constraint: author_name_key,
update_columns: [name, id]
}
) {
affected_rows
}
}
Update syntax
-------------
.. code-block:: none
mutation [<mutation-name>] {
<mutation-field-name> (
[where-argument!],
[set-argument!]
)
[mutation-response!]
}
.. list-table::
:header-rows: 1
* - Key
- Required
- Schema
- Description
* - mutation-name
- false
- Value
- Name of mutation for observability
* - mutation-field-name
- true
- Value
- name of the auto-generated update mutation field. E.g. *update_author*
* - where-argument
- true
- whereArgExp_
- selection criteria for rows to be updated
* - set-argument
- false
- setArgExp_
- Data to be updated in the table
* - inc-argument
- false
- incArgExp_
- Integer value to be incremented to Int columns in the table
* - append-argument
- false
- appendArgExp_
- JSON value to be appended to JSONB columns in the table
* - prepend-argument
- false
- prependArgExp_
- JSON value to be prepended to JSONB columns in the table
* - delete-key-argument
- false
- deleteKeyArgExp_
- key to be deleted in the value of JSONB columns in the table
* - delete-elem-argument
- false
- deleteElemArgExp_
- array element to be deleted in the value of JSONB columns in the table
* - delete-at-path-argument
- false
- deleteAtPathArgExp_
- element at path to be deleted in the value of JSONB columns in the table
* - mutation-response
- true
- MutationResponse_
- Object to be returned after mutation succeeds.
**E.g. UPDATE**:
.. code-block:: graphql
mutation update_author{
update_author(
where: {id: {_eq: 3}},
_set: {name: "Jane"}
) {
affected_rows
}
}
Delete syntax
-------------
.. code-block:: none
mutation [<mutation-name>] {
<mutation-field-name> (
[where-argument!]
)
[mutation-response!]
}
.. list-table::
:header-rows: 1
* - Key
- Required
- Schema
- Description
* - mutation-name
- false
- Value
- Name of mutation for observability
* - mutation-field-name
- true
- Value
- name of the auto-generated delete mutation field. E.g. *delete_author*
* - where-argument
- true
- whereArgExp_
- selection criteria for rows to delete
* - mutation-response
- true
- MutationResponse_
- Object to be returned after mutation succeeds.
**E.g. DELETE**:
.. code-block:: graphql
mutation delete_articles {
delete_article(
where: {author: {id: {_eq: 7}}}
) {
affected_rows
returning {
id
}
}
}
.. note::
For more examples and details of usage, please see :doc:`this <../mutations/index>`.
Syntax definitions
------------------
.. _InputObject:
Input Object
^^^^^^^^^^^^
.. code-block:: none
objects: [
{
field1: value,
field2: value,
..
},
..
]
# no nested objects
E.g.:
.. code-block:: graphql
objects: [
{
title: "Software is eating the world",
content: "This week, Hewlett-Packard...",
}
]
.. _MutationResponse:
Mutation Response
^^^^^^^^^^^^^^^^^
.. code-block:: none
{
affected_rows
returning {
response-field1
response-field2
..
}
}
E.g.:
.. code-block:: graphql
{
affected_rows
returning {
id
author_id
}
}
.. _ConflictClause:
Conflict Clause
^^^^^^^^^^^^^^^
.. code-block:: none
on_conflict: {
constraint: <unique_constraint_name>!
[update_columns: [table_column!]]
[action: [update|ignore]]
}
E.g.:
.. code-block:: graphql
on_conflict: {
constraint: author_name_key
update_columns: [name]
}
.. _whereArgExp:
``where`` argument
^^^^^^^^^^^^^^^^^^
.. parsed-literal::
where: BoolExp_
.. _BoolExp:
BoolExp
*******
.. parsed-literal::
AndExp_ | OrExp_ | NotExp_ | ColumnExp_
AndExp
######
.. parsed-literal::
{
_and: [BoolExp_]
}
OrExp
#####
.. parsed-literal::
{
_or: [BoolExp_]
}
NotExp
######
.. parsed-literal::
{
_not: BoolExp_
}
ColumnExp
#########
.. parsed-literal::
{
field-name: {Operator_: Value }
}
Operator
########
Generic operators (all column types except json, jsonb) :
- ``_eq``
- ``_ne``
- ``_in``
- ``_nin``
- ``_gt``
- ``_lt``
- ``_gte``
- ``_lte``
Operators for comparing columns (all column types except json, jsonb):
- ``_ceq``
- ``_cneq``
- ``_cgt``
- ``_clt``
- ``_cgte``
- ``_cnlte``
Text related operators :
- ``_like``
- ``_nlike``
- ``_ilike``
- ``_nilike``
- ``_similar``
- ``_nsimilar``
Checking for ``null`` values :
- ``_is_null`` (takes true/false as values)
.. _setArgExp:
``_set`` argument
^^^^^^^^^^^^^^^^^
.. code-block:: none
_set: {
field-name-1 : value,
field-name-2 : value,
..
}
.. _incArgExp:
``_inc`` argument
^^^^^^^^^^^^^^^^^
.. code-block:: none
_inc: {
field-name-1 : int-value,
field-name-2 : int-value,
..
}
.. _appendArgExp:
``_append`` argument
^^^^^^^^^^^^^^^^^^^^
.. code-block:: none
_append: {
field-name-1 : $json-variable-1,
field-name-2 : $json-variable-1,
..
}
E.g.
.. code-block:: json
{
"json-variable-1": "value",
"json-variable-2": "value"
}
.. _prependArgExp:
``_prepend`` argument
^^^^^^^^^^^^^^^^^^^^^
.. code-block:: none
_prepend: {
field-name-1 : $json-variable-1,
field-name-2 : $json-variable-1,
..
}
E.g.
.. code-block:: json
{
"json-variable-1": "value",
"json-variable-2": "value"
}
.. _deleteKeyArgExp:
``_delete_key`` argument
^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: none
_delete_key: {
field-name-1 : "key",
field-name-2 : "key",
..
}
.. _deleteElemArgExp:
``_delete_elem`` argument
^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: none
_delete_elem: {
field-name-1 : int-index,
field-name-2 : int-index,
..
}
.. _deleteAtPathArgExp:
``_delete_at_path`` argument
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. code-block:: none
_delete_at_path: {
field-name-1 : ["path-array"],
field-name-2 : ["path-array"],
..
}

View File

@ -0,0 +1,43 @@
Name,Aliases,Description,GraphQL Engine Type
bigint,int8,signed eight-byte integer,Int_
bigserial,serial8,autoincrementing eight-byte integer,Int_
bit [ (n) ],,fixed-length bit string,Implicit_
bit varying [ (n) ],varbit [ (n) ],variable-length bit string,Implicit_
boolean,bool,logical Boolean (true/false),Bool_
box,,rectangular box on a plane,Implicit_
bytea,,binary data (“byte array”),Implicit_
character [ (n) ],char [ (n) ],fixed-length character string,Char_
character varying [ (n) ],varchar [ (n) ],variable-length character string,String_
cidr,,IPv4 or IPv6 network address, Implicit_
circle,,circle on a plane,Implicit_
date,,"calendar date (year, month, day)",Date_
double precision,float8,double precision floating-point number (8 bytes),Float_
inet,,IPv4 or IPv6 host address,Implicit_
integer,"int, int4",signed four-byte integer,Int_
interval [ fields ] [ (p) ],,time span,Implicit_
json,,textual JSON data,JSON_
jsonb,,"binary JSON data, decomposed",JSONB_
line,,infinite line on a plane,Implicit_
lseg,,line segment on a plane, Implicit_
macaddr,,MAC (Media Access Control) address, Implicit_
macaddr8,,MAC (Media Access Control) address (EUI-64 format), Implicit_
money,,currency amount,Implicit_
"numeric [ (p, s) ]","decimal [ (p, s) ]",exact numeric of selectable precision, Numeric_
path,,geometric path on a plane,Implicit_
pg_lsn,,PostgreSQL Log Sequence Number,Implicit_
point,,geometric point on a plane,Implicit_
polygon,,closed geometric path on a plane,Implicit_
real,float4,single precision floating-point number (4 bytes),Float_
smallint,int2,signed two-byte integer,Int_
smallserial,serial2,autoincrementing two-byte integer,Int_
serial,serial4,autoincrementing four-byte integer,Int_
text,,variable-length character string,String_
time [ (p) ] [ without time zone ],,time of day (no time zone),Implicit_
time [ (p) ] with time zone,timetz,"time of day, including time zone",Timetz_
timestamp [ (p) ] [ without time zone ],,date and time (no time zone),Implicit_
timestamp [ (p) ] with time zone,timestamptz,"date and time, including time zone",Timestamptz_
tsquery,,text search query,Implicit_
tsvector,,text search document,Implicit_
txid_snapshot,,user-level transaction ID snapshot,Implicit_
uuid,,universally unique identifier,Implicit_
xml,,XML data,Implicit_
1 Name Aliases Description GraphQL Engine Type
2 bigint int8 signed eight-byte integer Int_
3 bigserial serial8 autoincrementing eight-byte integer Int_
4 bit [ (n) ] fixed-length bit string Implicit_
5 bit varying [ (n) ] varbit [ (n) ] variable-length bit string Implicit_
6 boolean bool logical Boolean (true/false) Bool_
7 box rectangular box on a plane Implicit_
8 bytea binary data (“byte array”) Implicit_
9 character [ (n) ] char [ (n) ] fixed-length character string Char_
10 character varying [ (n) ] varchar [ (n) ] variable-length character string String_
11 cidr IPv4 or IPv6 network address Implicit_
12 circle circle on a plane Implicit_
13 date calendar date (year, month, day) Date_
14 double precision float8 double precision floating-point number (8 bytes) Float_
15 inet IPv4 or IPv6 host address Implicit_
16 integer int, int4 signed four-byte integer Int_
17 interval [ fields ] [ (p) ] time span Implicit_
18 json textual JSON data JSON_
19 jsonb binary JSON data, decomposed JSONB_
20 line infinite line on a plane Implicit_
21 lseg line segment on a plane Implicit_
22 macaddr MAC (Media Access Control) address Implicit_
23 macaddr8 MAC (Media Access Control) address (EUI-64 format) Implicit_
24 money currency amount Implicit_
25 numeric [ (p, s) ] decimal [ (p, s) ] exact numeric of selectable precision Numeric_
26 path geometric path on a plane Implicit_
27 pg_lsn PostgreSQL Log Sequence Number Implicit_
28 point geometric point on a plane Implicit_
29 polygon closed geometric path on a plane Implicit_
30 real float4 single precision floating-point number (4 bytes) Float_
31 smallint int2 signed two-byte integer Int_
32 smallserial serial2 autoincrementing two-byte integer Int_
33 serial serial4 autoincrementing four-byte integer Int_
34 text variable-length character string String_
35 time [ (p) ] [ without time zone ] time of day (no time zone) Implicit_
36 time [ (p) ] with time zone timetz time of day, including time zone Timetz_
37 timestamp [ (p) ] [ without time zone ] date and time (no time zone) Implicit_
38 timestamp [ (p) ] with time zone timestamptz date and time, including time zone Timestamptz_
39 tsquery text search query Implicit_
40 tsvector text search document Implicit_
41 txid_snapshot user-level transaction ID snapshot Implicit_
42 uuid universally unique identifier Implicit_
43 xml XML data Implicit_

View File

@ -0,0 +1,261 @@
API Reference - Supported PostgreSQL Types
==========================================
List of PostgreSQL types supported by the Hasura GraphQL engine with their equivalent GraphQL engine types:
.. _table:
.. csv-table::
:file: pgtypes.csv
:widths: 10, 10, 25, 10
:header-rows: 1
.. _Int:
Int
---
GraphQL default scalar with name **Int**.
E.g.
.. code-block:: graphql
objects: [
{
id: 1,
int_col: 27
}
]
.. _Float:
Float
-----
GraphQL custom scalar type with name **float8**.
E.g.
.. code-block:: graphql
objects: [
{
id: 1,
float_col: 0.8
}
]
.. _Numeric:
Numeric
-------
GraphQL custom scalar type with name **numeric**.
E.g.
.. code-block:: graphql
objects: [
{
id: 1,
numeric_col: 0.00000008
}
]
.. _Bool:
Bool
----
GraphQL default Scalar with name **Boolean**. The **Boolean** scalar type represents ``true`` or ``false``.
E.g.
.. code-block:: graphql
objects: [
{
id: 1,
is_published: true
}
]
.. _Char:
Char
----
GraphQL custom scalar with name **character**. It is a ``String`` with single character.
E.g.
.. code-block:: graphql
objects: [
{
id: 1,
char_column: "a"
}
]
.. _String:
String
------
GraphQL default scalar with name **String**. The **String** scalar type represents textual data, represented as UTF-8 character sequences.
The String type is most often used by GraphQL to represent free-form human-readable text.
E.g.
.. code-block:: graphql
objects: [
{
id: 1,
name: "Raven"
}
]
.. _Date:
Date
----
GraphQL custom scalar with name **date**. Date (no time of day). Allowed values are yyyy-mm-dd
E.g.
.. code-block:: graphql
objects: [
{
id: 1,
date: "1996-03-15"
}
]
.. _Timetz:
Time with time zone
-------------------
Graphql custom scalar type with name **timetz**. Time of day only, with time zone. Allowed values should be of ISO8601 format.
Eg. 17:30:15Z, 17:30:15+05:30, 17:30:15.234890+05:30
E.g.
.. code-block:: graphql
objects: [
{
id: 1,
time: "17:30:15+05:30"
}
]
.. _Timestamptz:
Timestamp with time zone
------------------------
Graphql custom scalar type with name **timestamptz**. Both date and time, with time zone. Allowed values should be of ISO8601 format.
Eg. 2016-07-20T17:30:15Z, 2016-07-20T17:30:15+05:30, 2016-07-20T17:30:15.234890+05:30
E.g.
.. code-block:: graphql
objects: [
{
id: 1,
timestamptz_col: "2016-07-20T17:30:15+05:30"
}
]
.. _JSON:
JSON
----
GraphQL custom scalar type with name **json**. It is a stringified json value.
E.g.
.. code-block:: graphql
objects: [
{
id: 1,
json_col: "{ \'name\': \'raven\' }"
}
]
.. _JSONB:
JSONB
-----
GraphQL custom scalar type with name **jsonb**. Value should be given through a variable of type **jsonb**.
E.g.
.. code-block:: graphql
mutation insert_test($value : jsonb) {
insert_test(
objects: [
{
id: 1,
jsonb_col: $value
}
]
) {
affected_rows
returning{
id
details
}
}
}
variable:-
.. code-block:: json
{
"value": {
"name": "raven"
}
}
.. _Implicit:
Implicitly Supported types
--------------------------
All ``Implicit`` types in above table_ are implicitly supported by GraphQL Engine. You have to provide the value in
**String**.
E.g. For time without time zone type
In ISO 8601 format
.. code-block:: graphql
objects: [
{
id: 1,
time_col: "04:05:06.789"
}
]
E.g. For macaddr type
.. code-block:: graphql
objects: [
{
id: 1,
macaddr_col: "08:00:2b:01:02:03"
}
]
.. Note::
You can learn more about PostgreSQL data types `here <https://www.postgresql.org/docs/current/static/datatype.html>`__

View File

@ -0,0 +1,233 @@
.. title:: API Reference - Query/Subscription
API Reference - Query/Subscription
==================================
Query/Subscription syntax
-------------------------
.. code-block:: none
query|subscription [<op-name>] {
object [([argument])]{
object-fields
}
}
.. list-table::
:header-rows: 1
* - Key
- Required
- Schema
- Description
* - op-name
- false
- Value
- Name query/subscription for observability
* - object
- true
- Object_
- Name of the table/object
* - argument
- false
- Argument_
- one or more of filter criteria, instructions for sort order or pagination
**E.g. QUERY**:
.. code-block:: graphql
query {
author(where: {articles: {rating: {_gte: 4}}} order_by: name_asc) {
id
name
}
}
**E.g. SUBSCRIPTION**:
.. code-block:: graphql
subscription {
author(where: {articles: rating: {_gte: 4}}} order_by: name_asc) {
id
name
}
}
.. note::
For more examples and details of usage, please see :doc:`this <../queries/index>`.
Syntax definitions
------------------
.. _Object:
Object
^^^^^^
.. code-block:: none
object-name {
field1
field2
..
nested object1
nested object2
..
}
E.g.
.. code-block:: graphql
author {
id # scalar field
name # scalar field
article { # nested object
title
}
}
.. _Argument:
Argument
^^^^^^^^
.. parsed-literal::
WhereExp_ | OrderByExp_ | PaginationExp_
.. _WhereExp:
WhereExp
********
.. parsed-literal::
where: BoolExp_
.. _BoolExp:
BoolExp
"""""""
.. parsed-literal::
AndExp_ | OrExp_ | NotExp_ | ColumnExp_
AndExp
######
.. parsed-literal::
{
_and: [BoolExp_]
}
OrExp
#####
.. parsed-literal::
{
_or: [BoolExp_]
}
NotExp
######
.. parsed-literal::
{
_not: BoolExp_
}
ColumnExp
#########
.. parsed-literal::
{
field-name : {Operator_: Value }
}
Operator
########
Generic operators (all column types except json, jsonb) :
- ``_eq``
- ``_ne``
- ``_in``
- ``_nin``
- ``_gt``
- ``_lt``
- ``_gte``
- ``_lte``
Operators for comparing columns (all column types except json, jsonb):
- ``_ceq``
- ``_cneq``
- ``_cgt``
- ``_clt``
- ``_cgte``
- ``_cnlte``
Text related operators :
- ``_like``
- ``_nlike``
- ``_ilike``
- ``_nilike``
- ``_similar``
- ``_nsimilar``
Checking for ``null`` values :
- ``_is_null`` (takes true/false as values)
.. _OrderByExp:
OrderByExp
**********
.. parsed-literal::
order_by: (object-field + OrderByOperator_ | [object-field + OrderByOperator_])
E.g.
.. code-block:: graphql
order_by: name_asc
or
.. code-block:: graphql
order_by: [name_asc, id_desc]
.. _OrderByOperator:
OrderByOperator
"""""""""""""""
- ``_asc``
- ``_desc``
- ``_asc_nulls_first``
- ``_desc_nulls_first``
.. _PaginationExp:
PaginationExp
*************
.. parsed-literal::
limit: Integer [offset: Integer]

View File

@ -0,0 +1,118 @@
Access control basics
=====================
In this section, we're going to setup a simple access control rule for restricting querying on a table.
We're working with a simple author table where users have some information stored about themselves in the
``author`` table.
Create a author table
---------------------
Head to your console and create the ``author`` table the following columns:
+----------+--------+
| id | integer|
+----------+--------+
| name | text |
+----------+--------+
.. image:: ../../../img/graphql/manual/auth/author-table.png
Insert some sample data into the table:
+-------------+----------+
| **id** | **name** |
+-------------+----------+
| 1 | john |
+-------------+----------+
| 2 | shruti |
+-------------+----------+
| 3 | celine |
+-------------+----------+
| 4 | raj |
+-------------+----------+
Try out a query
---------------
.. code-block:: graphql
query {
author {
id
name
}
}
You'll see that this results in a response that contains all the authors because by default the GraphQL query is
accepted with admin permissions.
.. image:: ../../../img/graphql/manual/auth/fetch-authors.png
Add a simple access control rule for a logged in user
-----------------------------------------------------
Let's say for our app, logged in users are only allowed to fetch their own data.
Let's add a **select** permission for the **user** role on the ``author`` table:
.. image:: ../../../img/graphql/manual/auth/author-select-perms.png
.. list-table::
:header-rows: 1
:widths: 15 20 25 40
* - Table
- Definition
- Condition
- Representation
* - author
- user's own row
- ``id`` in the row is equal to ``user-id`` from the request session
-
.. code-block:: json
{
"id": {
"_eq": "X-Hasura-User-Id"
}
}
Now, let's make the same query as above but include the 2 dynamic authorization variables via request headers.
``X-Hasura-Role`` and ``X-Hasura-User-Id`` which will automatically get used according to the permission rule we set up.
.. image:: ../../../img/graphql/manual/auth/query-with-perms.png
You can notice above, how the same query now only includes the right slice of data.
.. _restrict_columns:
Restrict access to certain columns
----------------------------------
We can restrict the columns of a table that a particular role has access to.
Head to the ``Permissions`` tab of the table and edit the ``Select`` permissions for the role:
.. image:: ../../../img/graphql/manual/auth/restrict-columns.png
.. _limit_rows:
Limit number of rows returned in a single request
-------------------------------------------------
We can set a hard limit on the maximum number of rows that will be returned in a single request for a table for a
particular role.
Head to the ``Permissions`` tab of the table and edit the ``Select`` permissions for the role:
.. image:: ../../../img/graphql/manual/auth/limit-results.png
Next, learn more about how permissions work
-------------------------------------------
Next: :doc:`Roles and dynamic variables <roles-variables>`

View File

@ -0,0 +1,90 @@
Common roles and auth examples
==============================
This is a guide to help you set up a basic authorization architecture for your GraphQL fields.
Here are some examples of common use-cases.
Anonymous (not logged in) users
-------------------------------
- Create a role called ``anonymous`` (this value is up to you, you could even name the role ``public``).
- Generally, you wouldn't add insert, update or delete permissions.
- For the select permission condition setup a valid condition depending on your data model. For example, ``is_published: {_eq: true}``.
- If you don't have a condition, then just set the permission to ``Without any checks`` represented by a ``{}``.
- Choose the right set of columns that will get exposed in the GraphQL schema as fields. Take care of not exposing sensitive information.
.. image:: ../../../img/graphql/manual/auth/anonymous-role-examples.png
:class: no-shadow
Logged-in users
---------------
- Create a role called ``user``
- Access control rules in this case are usually dependent on a ``user_id`` or a ``owner_id`` column in your data model
- Setup a permission for insert/select/update/delete that uses said column. Eg: ``author_id: {_eq: "X-Hasura-User-Id"}`` for an article table.
- Note that the ``X-Hasura-User-Id`` is a :doc:`dynamic session variable<./roles-variables>` that comes in from your :doc:`auth webhook's<./webhook>` response, or as a request as a header if you're testing.
.. image:: ../../../img/graphql/manual/auth/user-select-graphiql.png
:class: no-shadow
Managers of an organisation in a multi-tenant app
-------------------------------------------------
Suppose you have a multi-tenant application where managers of a particular organisation can see all of the data that belongs to the organisation. In this case, your data models will probably have an ``org_id`` column that denotes the organisation either in the same table or via a related table.
- Create a role called ``manager``
- Create a permission for select, which has the condition: ``org_id: {_eq: "X-Hasura-Org-Id"}``.
- ``X-Hasura-Org-Id`` is a :doc:`dynamic variable<./roles-variables>` that is returned by your :doc:`auth webhook <./webhook>` for an incoming GraphQL request
.. image:: ../../../img/graphql/manual/auth/org-manager-graphiql.png
:class: no-shadow
Collaborators of an article
---------------------------
Let's say the "ownership" or "visibility" information for a data model (table) is not present as a column in the table, but in a different related table. In this case, let's say there is an ``article`` table and a ``collaborator`` table that has ``article_id, collaborator_id`` columns.
- Create a relationship called collaborators from the article table.
- Array relationship (article has array of collaborators): ``article :: id → collaborator :: article_id``
- Create a role called ``collaborator``
- Create a select permission on the ``article`` table, which has the condition: ``collaborators: {collaborator_id: {_eq: "X-Hasura-User_id"}}``
- This reads as: Allow the role collaborator to select if ``article.collaborators`` has a ``collaborator_id`` equal to that of ``X-Hasura-User-Id``
.. image:: ../../../img/graphql/manual/auth/collaborator-relationship.png
:class: no-shadow
Role based schemas
------------------
For every role that you create, Hasura automatically publishes a different GraphQL schema that represents the right queries, fields and mutations that are available to that role.
Case 1: Logged-in users and anonymous users can access the same GraphQL fields
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In simple use-cases logged-in users and anonymous users might be able to fetch different rows (let's say because of a ``is_public`` flag) but have access to the same fields.
- ``anonymous`` role has a ``{is_public: {_eq: true}}`` select condition
- This reads: Allow anyone to access rows that are marked public.
- ``user`` role has a ``_or: [{is_public: {_eq: true}}, {owner_id: {_eq: "X-Hasura-User-Id"}}]``
- This reads: Allow users to access any rows that are public, or that are owned by them
Case 2: Logged-in users and anonymous users have access to different fields
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In this case, anonymous users might have access only to a subset of fields while logged-in users can access all the fields for data that they own.
- ``anonymous`` role has a ``{is_public: {_eq: true}}`` select condition, and only the right columns are allowed to be selected.
- This reads: Allow anyone to access rows that are marked public.
- ``user`` role has a ``{owner_id: {_eq: "X-Hasura-User-Id"}}`` and all the columns are marked as selected.
- This reads: Allow users to that are owned by them

View File

@ -0,0 +1,65 @@
Authorization modes
===================
You can run Hasura's GraphQL Engine in three modes:
1. No Authentication mode
^^^^^^^^^^^^^^^^^^^^^^^^^
- When ``--access-key`` and ``--auth-hook`` are not set
- It is useful when you're developing . It is not recomended to use in production but however you can have proxy gateway that will set (``X-Hasura-Access-Key``) header and other required ``X-Hasura-*`` headers.
Run server in this mode using following docker command.
.. code-block:: bash
docker run --name hasura-graphql-engine -p 9000:9000 \
--link hasura-postgres:postgres \
-d hasura/graphql-engine:latest graphql-engine \
--database-url \
postgres://postgres:mysecretpassword@postgres:5432/postgres \
serve --server-port 9000 --cors-domain "*"
2. Access key mode
^^^^^^^^^^^^^^^^^^
- When only ``--access-key`` is set. See :doc:`GraphQL Server Options <../deployment/options>`
- Server authenticates based on ``X-Hasura-Access-Key`` header and expects all other required ``X-Hasura-*`` headers.
Run server in this mode using following docker command.
.. code-block:: bash
docker run --name hasura-graphql-engine -p 9000:9000 \
--link hasura-postgres:postgres \
-d hasura/graphql-engine:latest graphql-engine \
--database-url \
postgres://postgres:mysecretpassword@postgres:5432/postgres \
serve --server-port 9000 --access-key myAccKey \
--cors-domain "*"
3. Access key and Authorization webhook mode
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
- When both ``--access-key`` and ``--auth-hook`` are set
- This mode is useful in production. When server founds ``X-Hasura-Access-Key`` header it ignores webhook and expects all other required ``X-Hasura*`` headers
- If ``X-Hasura-Access-key`` header not found then server authenticaters through webhook. See :doc:`Authorization
Webhook <webhook>`
Run server in this mode using following docker command.
.. code-block:: bash
docker run --name hasura-graphql-engine -p 9000:9000 \
--link hasura-postgres:postgres \
-d hasura/graphql-engine:latest graphql-engine \
--database-url \
postgres://postgres:mysecretpassword@postgres:5432/postgres \
serve --server-port 9000 --access-key myAccKey \
--auth-hook http://myAuthhook/ --cors-domain "*"

View File

@ -0,0 +1,36 @@
Authentication / Access control
===============================
Hasura helps you define granular access controls for every field in your GraphQL schema, basically every table or
view in your Postgres schema. These access control rules can use dynamic variables that come in with every request.
.. image:: ../../../img/graphql/manual/auth/hasura-perms.png
**While developing**, you can send variables as request headers directly.
.. image:: ../../../img/graphql/manual/auth/dev-mode-auth.png
However, **in production**, when your application is deployed, your app can't send these authorization variables
directly!
Your app will likely only send an authorization token or cookie provided by your app's authentication
system to Hasura. In this case, Hasura will make a request to a webhook set up by you with the request headers your
app has sent (authorization tokens, cookies etc). The webhook should then return the variables required as context for
the access control rules. Alternatively, your app can send JWT tokens to Hasura which can be then decoded by Hasura to
get the variables required for the access control rules.
See :doc:`webhook` or :doc:`jwt` for more details.
Next, let's setup some :doc:`basic access control rules <basics>`.
See:
----
.. toctree::
:maxdepth: 1
basics
roles-variables
Permissions examples <common-roles-auth-examples>
webhook
webhook-examples
jwt

View File

@ -0,0 +1,199 @@
Authorization using JWT
=======================
You can configure JWT authorization mode (see :doc:`GraphQL server options
<../deployment/graphql-engine-flags/reference>`) to authorize all incoming
requests to Hasura GraphQL engine server.
The idea is - Your auth server will return JWT tokens, which is decoded and
verified by GraphQL engine to authorize and get metadata about the request
(``x-hasura-*`` values).
.. image:: ../../../img/graphql/manual/auth/jwt-auth.png
The JWT is decoded, the signature is verified and then it is asserted that the
current role of the user is in the list of allowed roles. If the authorization
passes, then all of the ``x-hasura-*`` values in the claim is used for the
permissions system.
.. note::
Configuring JWT requires Hasura to run with an access key (``--access-key``).
- The authorization is **enforced** when ``X-Hasura-Access-Key`` header is
**not found** in the request.
- The authorization is **skipped** when ``X-Hasura-Access-Key`` header **is
found** in the request.
.. :doc:`Read more<config>`.
TL;DR
-----
1. The JWT must contain: ``x-hasura-default-role``, ``x-hasura-allowed-roles``
in a custom namespace in the claims.
2. Other optional ``x-hasura-*`` fields (required as per your defined
permissions)
3. You can send ``x-hasura-role`` as header in the request to indicate a
different role.
4. Send the JWT via ``Authorization: Bearer <JWT>`` header.
The Spec
--------
When your auth server generates the JWT, the custom claims in the JWT **must contain**
the following:
1. A ``x-hasura-default-role`` field : indicating the default role of that user
2. A ``x-hasura-allowed-roles`` field : a list of allowed roles for the user
The claims in the JWT, can have other ``x-hasura-*`` fields where their values
can only be strings. You can use these ``x-hasura-*`` fields in your
permissions.
Now, the JWT should be sent by the client to Hasura GraphQL engine via the
``Authorization: Bearer <JWT>`` header.
Example JWT claim:
.. code-block:: json
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022,
"https://hasura.io/jwt/claims": {
"x-hasura-allowed-roles": ["editor","user", "mod"],
"x-hasura-default-role": "user",
"x-hasura-user-id": "1234567890",
"x-hasura-org-id": "123",
"x-hasura-custom": "custom-value"
}
}
This contains standard (``sub``, ``iat`` etc.) and custom (``name``, ``admin``
etc.) JWT claims, as well as Hasura specific claims inside a custom namespace
(or key) i.e ``https://hasura.io/jwt/claims``.
The ``https://hasura.io/jwt/claims`` is the custom namespace where all Hasura
specific claims have to be present. This value can be configured in the JWT
config while starting the server.
**Note**: ``x-hasura-default-role`` and ``x-hasura-allowed-roles`` are
mandatory, while rest of them are optional.
.. note::
All ``x-hasura-*`` values should be ``String``, they will be converted to the
right type automatically.
The default role can be overriden by ``x-hasura-role`` header, while making a
request.
.. code-block:: http
POST /v1alpha1/graphql HTTP/1.1
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWI...
X-Hasura-Role: editor
...
Configuring JWT mode
--------------------
You can enable JWT mode by using the ``--jwt-secret`` flag or
``HASURA_GRAPHQL_JWT_SECRET`` environment variable; the value of which is a
JSON.
The JSON is:
.. code-block:: json
{
"type": "<standard-JWT-algorithms>",
"key": "<the-key>",
"claims_namespace": "<optional-key-name-in-claims>"
}
``type``
^^^^^^^^
Valid values are : ``HS256``, ``HS384``, ``HS512``, ``RS256``,
``RS384``, ``RS512``. (see https://jwt.io).
``HS*`` is for HMAC-SHA based algorithms. ``RS*`` is for RSA based signing. For
example, if your auth server is using HMAC-SHA256 for signing the JWTs, then
use ``HS256``. If it is using RSA with 512-bit keys, then use ``RS512``. EC
public keys are not yet supported.
``key``
^^^^^^^
- Incase of symmetric key, the key as it is. (HMAC based keys).
- Incase of asymmetric keys, only the public key, in a PEM encoded string or as
a X509 certificate.
``claims_namespace``
^^^^^^^^^^^^^^^^^^^^
This is an optional field. You can specify the key name
inside which the Hasura specific claims will be present. E.g - ``https://mydomain.com/claims``.
**Default value** is: ``https://hasura.io/jwt/claims``.
Examples
^^^^^^^^
HMAC-SHA based
+++++++++++++++
Your auth server is using HMAC-SHA alogrithms to sign JWTs, and is using a
256-bit key. Then the JWT config will look like:
.. code-block:: json
{
"type":"HS256",
"key": "3EK6FD+o0+c7tzBNVfjpMkNDi2yARAAKzQlk8O2IKoxQu4nF7EdAh8s3TwpHwrdWT6R"
}
The ``key`` is the actual shared secret. Which is used by your auth server as well.
RSA based
+++++++++
Let's say your auth server is using RSA to sign JWTs, and is using a 512-bit
key. Then, the JWT config needs to have the only the public key, in PEM format
(not OpenSSH format):
.. code-block:: json
{
"type":"RS512",
"key": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugd\nUWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQs\nHUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5D\no2kQ+X5xK9cipRgEKwIDAQAB\n-----END PUBLIC KEY-----\n"
}
Running with JWT
^^^^^^^^^^^^^^^^
Using the flag:
.. code-block:: shell
$ docker run -p 8080:8080 \
hasura/graphql-engine:latest \
graphql-engine \
--database-url postgres://username:password@hostname:port/dbname \
serve \
--access-key mysecretkey \
--jwt-secret '{"type":"HS256", "key": "3EK6FD+o0+c7tzBNVfjpMkNDi2yARAAKzQlk8O2IKoxQu4nF7EdAh8s3TwpHwrdWT6R"}'
Using env vars:
.. code-block:: shell
$ docker run -p 8080:8080 \
-e HASURA_GRAPHQL_ACCESS_KEY="mysecretkey" \
-e HASURA_GRAPHQL_JWT_SECRET='{"type":"RS512", "key": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugd\nUWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQs\nHUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5D\no2kQ+X5xK9cipRgEKwIDAQAB\n-----END PUBLIC KEY-----\n"}' \
hasura/graphql-engine:latest \
graphql-engine \
--database-url postgres://username:password@hostname:port/dbname \
serve

View File

@ -0,0 +1,121 @@
Roles & Session variables
=========================
The permissions system offered by Hasura GraphQL engine is extremely flexible and is built to capture complex
use-cases conveniently.
Roles
-----
Every table/view can have permission rules that are grouped together by multiple roles.
By default, there is an ``admin`` role that can perform any operation or any table.
You can create your own roles that make it easy for you to group permissions together.
Examples:
+-----------+-----------------------------------+
| user | A logged-in user |
+-----------+-----------------------------------+
| anonymous | A not logged-in user |
+-----------+-----------------------------------+
| manager | A user that has access to other |
| | user's data |
+-----------+-----------------------------------+
You can then create permissions for each of these roles:
Examples:
+-----------+-----------------------------------+
| user | CRUD on data that belongs to them |
+-----------+-----------------------------------+
| anonymous | Only read from some tables/views |
+-----------+-----------------------------------+
Dynamic session variables
-------------------------
When you create a permission, or an access control rule, the permission rule itself needs access to some variables
that are derived from the request itself. Let's refer to these as *session variables*.
For example: If a user makes a request, the session token maps to a ``user-id``. This ``user-id`` can be used in
a permission to represent that inserts into a table are only allowed if the ``user_id`` column has a value equal to that
of ``user-id``, the session variable.
When you are constructing permission rules, however, there might be several variables that represent the business logic
of having access to data. For example, if you have a SaaS application, you might restrict access based on a ``client_id``
variable. If you want to provide different levels of access on different devices you might restrict access based on a
``device_type`` variable.
Hasura allows you to create permission rules that can use any dynamic variable that is a property of the request.
All your dynamic variables must follow the naming convention ``X-Hasura-*``.
Examples:
.. list-table::
:header-rows: 1
:widths: 20 10 20 50
* - Example
- Role
- Condition
- Permission expression
* - Allow access to user's own row
- ``user``
- ``user_id`` column is equal to ``session-user-id`` from a request
-
.. code-block:: json
{
"user_id": {
"_eq": "X-Hasura-User-Id"
}
}
* - Allow project admins access to anything that belongs to the project
- ``project-admin``
- ``project_id`` column is equal to ``project-id`` of the "session user"
-
.. code-block:: json
{
"project_id": {
"_eq": "X-Hasura-Project-Id"
}
}
Indicating roles and session-variables in a GraphQL request
-----------------------------------------------------------
Now that we have these roles and permission rules that use session-variables set up, how do we actually use them
when we make GraphQL requests from an app or from a different service?
Option 1: Development & Testing
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
While you're developing or testing, just indicate your role and your session variables by passing headers along with the request:
.. image:: ../../../img/graphql/manual/auth/dev-mode-role-header.png
If you've enabled Hasura GraphQL engine with an access key, make sure you add the ACCESS_KEY header as well.
This will allow you to use Hasura GraphQL engine as if you were in development/testing mode. This is useful when
you want to test against a Hasura GraphQL engine instance that is already serving a production application.
.. image:: ../../../img/graphql/manual/auth/dev-mode-role-header-access-key.png
Option 2: In production, from apps
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you're making GraphQL queries from your apps, you will not (should not) be sending session variables directly from your app
because anyone can spoof the role and values of the variables and get access to whatever data they want.
In this case, you should configure a webhook that will return an object containing the role and session variables given the
session token (authorization token, JWT, cookie etc.) that your app normally uses.
Read more about :doc:`configuring webhook authentication for Hasura<webhook>`.
There are 4 different types of operations for which you can define permissions on a table, ie: Select, Insert,
Update and Delete.

View File

@ -0,0 +1,23 @@
Auth webhook samples
====================
We have put together a `GitHub Node.js repo <https://github.com/hasura/sample-auth-webhook>`__ that has some sample auth
webhooks configured.
You can deploy these samples using `glitch <https://glitch.com/>`__:
.. image:: https://raw.githubusercontent.com/hasura/sample-auth-webhook/master/assets/deploy-glitch.png
:width: 200px
:alt: deploy_auth_webhook_with_glitch
:class: no-shadow
:target: http://glitch.com/edit/#!/import/github/hasura/sample-auth-webhook
Once deployed, you can use any of the following endpoints as your auth webhook in the GraphQL engine:
- ``/simple/webhook`` (`View source <https://github.com/hasura/sample-auth-webhook/blob/master/server.js#L25>`__)
- ``/firebase/webhook`` (`View source <https://github.com/hasura/sample-auth-webhook/tree/master/firebase>`__)
- ``/auth0/webhook`` (`View source <https://github.com/hasura/sample-auth-webhook/tree/master/auth0>`__)
.. note::
If you are using ``auth0`` or ``firebase`` you will have to set the associated environment variables.

View File

@ -0,0 +1,68 @@
Authorization using webhooks
============================
You can configure a webhook (see :doc:`GraphQL server options <../deployment/graphql-engine-flags/reference>`) to
authenticate all incoming requests to Hasura GraphQL engine server.
.. image:: ../../../img/graphql/manual/auth/webhook-auth.png
.. note::
Configuring webhook requires Hasura to run with an access key (``--access-key``).
.. :doc:`Read more<config>`.
- The configured webhook is **called** when ``X-Hasura-Access-Key`` header is not found in the request.
- The configured webhook is **ignored** when ``X-Hasura-Access-Key`` header is found in the request.
Spec for the webhook
--------------------
Request
^^^^^^^
Hasura will send ``GET`` request to you webhook with **all headers it received from client**
.. code-block:: http
GET https://<your-custom-webhook>/ HTTP/1.1
<Header-Key>: <Header-Value>
Response
^^^^^^^^
Success
+++++++
To allow the GraphQL request to go through, your webhook must return a ``200`` status code.
You should send the ``X-Hasura-*`` "session variables" your permission rules in Hasura.
.. code-block:: http
HTTP/1.1 200 OK
Content-Type: application/json
{
"X-Hasura-User-Id": "25",
"X-Hasura-Role": "user",
"X-Hasura-Is-Owner": "true",
"X-Hasura-Custom": "custom value"
}
.. note::
All values should be ``String``, they will be converted to the right type automatically.
Failure
+++++++
If you want to deny the GraphQL request return a ``401 Unauthorized`` exception.
.. code-block:: http
HTTP/1.1 401 Unauthorized
.. note::
Anything other than ``200`` or ``401`` response from webhook then server raises ``500 Internal Server Error``
exception
See:
----
- :doc:`Auth webhook samples <webhook-examples>`

View File

@ -0,0 +1,94 @@
Run Hasura GraphQL Engine using Docker
======================================
This guide assumes that you already have Postgres running and helps you set up the Hasura GraphQL engine using Docker
and connect it to your Postgres database.
**Prerequisites**:
- `Docker <https://docs.docker.com/install/>`_
Step 1: Get the docker run bash script
--------------------------------------
The `hasura/graphql-engine-install-manifests <https://github.com/hasura/graphql-engine-install-manifests>`_ repo
contains all installation manifests required to deploy Hasura anywhere. Get the docker run bash script from there:
.. code-block:: bash
$ wget https://raw.githubusercontent.com/hasura/graphql-engine-install-manifests/master/docker-run/docker-run.sh
Step 2: Run the hasura docker container
---------------------------------------
Check the sample docker run command in ``docker-run.sh``.
Edit the ``--database-url`` flag command, so that you can connect to your Postgres instance.
.. code-block:: bash
:emphasize-lines: 5
#! /bin/bash
docker run -d -p 8080:8080 \
hasura/graphql-engine:latest \
graphql-engine \
--database-url postgres://username:password@hostname:port/dbname \
serve --enable-console
Examples of ``database-url``:
- ``postgres://admin:password@localhost:5432/my-db``
- ``postgres://admin:@localhost:5432/my-db`` *(if there is no password)*
.. admonition:: Postgres on localhost
If your Postgres database is running on ``localhost``, add the ``--net=host`` flag to allow the Docker container to
access the host's network. This is what your command should look like:
.. code-block:: bash
:emphasize-lines: 2
#! /bin/bash
docker run -d --net=host \
hasura/graphql-engine:latest \
graphql-engine \
--database-url postgres://username:password@hostname:port/dbname \
serve --enable-console
Check if everything is running well:
.. code-block:: bash
$ docker ps
CONTAINER ID IMAGE ... CREATED STATUS PORTS ...
097f58433a2b hasura/graphql-engine ... 1m ago Up 1m 8080->8080/tcp ...
Step 3: Open the hasura console
-------------------------------
Head to http://localhost:8080/console to open the Hasura console.
Step 4: Track existing tables and relationships
-----------------------------------------------
On the console page, you'll see your existing tables/view as "Untracked tables/views" in the console. Click the
``Add all`` button to enable GraphQL APIs over them.
.. image:: ../../../../img/graphql/manual/getting-started/TrackTable.jpg
Advanced:
---------
- :doc:`Securing your GraphQL endpoint <securing-graphql-endpoint>`
- :doc:`Updating GraphQL engine <updating>`
- :doc:`Setting up migrations <../../migrations/index>`
.. toctree::
:titlesonly:
:hidden:
Securing your GraphQL endpoint <securing-graphql-endpoint>
Updating GraphQL engine <updating>

View File

@ -0,0 +1,25 @@
Securing the GraphQL endpoint (Docker)
======================================
To make sure that your GraphQL endpoint and the Hasura console are not publicly accessible, you need to
configure an access key.
Run the docker command with an access-key flag
----------------------------------------------
.. code-block:: bash
:emphasize-lines: 7
#! /bin/bash
docker run -p 8080:8080 \
hasura/graphql-engine:latest \
graphql-engine \
--database-url postgres://username:password@hostname:port/dbname \
serve \
--access-key mysecretkey
.. note::
If you're looking at adding authentication and access control to your GraphQL API then head
to :doc:`Authentication / access control <../../auth/index>`.

View File

@ -0,0 +1,34 @@
Updating Hasura GraphQL engine running with Docker
==================================================
This guide will help you update Hasura GraphQL engine running with Docker. This guide assumes that you already have
Hasura GraphQL engine running with Docker.
1) Check the latest release version
-----------------------------------
The current latest version is:
.. raw:: html
<code>hasura/graphql-engine:<span class="latest-release-tag">latest</span></code>
All the versions can be found at: https://github.com/hasura/graphql-engine/releases
2) Update the Docker image
--------------------------
In the ``docker run`` command or the ``docker-compose`` command that you're running, update the image tag to this
latest version.
For example, if you had:
.. raw:: html
<code>docker run hasura/graphql-engine:v1.0.0-alpha01 ...</code>
you should change it to:
.. raw:: html
<code>docker run hasura/graphql-engine:<span class="latest-release-tag">latest</span> ...</code>

View File

@ -0,0 +1,106 @@
GraphQL engine server config examples
=====================================
The following are a few configuration use cases:
- :ref:`add-access-key`
- :ref:`cli-with-access-key`
- :ref:`configure-cors`
.. _add-access-key:
Add an access key
-----------------
To add an access-key to Hasura, pass the ``--access-key`` flag with a secret
generated by you.
Run server in this mode using following docker command:
.. code-block:: bash
docker run -P -d hasura/graphql-engine:latest graphql-engine \
--database-url postgres://username:password@host:5432/dbname \
serve \
--access-key XXXXXXXXXXXXXXXX
Typically, you will also have a webhook for authentication:
.. code-block:: bash
docker run -P -d hasura/graphql-engine:latest graphql-engine \
--database-url postgres://username:password@host:5432/dbname \
serve \
--access-key XXXXXXXXXXXXXXXX
--auth-hook https://myauth.mywebsite.com/user/session-info
In addition to flags, the GraphQL Engine also accepts Environment variables.
In the above case, for adding an access key you will use the ``HASURA_GRAPHQL_ACCESS_KEY``
and for the webhook, you will use the ``HASURA_GRAPHQL_AUTH_HOOK`` environment variables.
.. _cli-with-access-key:
Using CLI commands with access key
----------------------------------
When you start the GraphQL Engine with an access key, CLI commands will also
need this access key to contact APIs. It can be set in ``config.yaml`` or as an
environment variable or as a flag to the command. For example, let's look at the
case of the ``console`` command:
In the ``my-project/config.yaml`` file, set a new key ``access_key``:
.. code-block:: yaml
# config.yaml
endpoint: https://my-graphql-endpoint.com
access_key: XXXXXXXXXXXXXXXX
The console can now contact the GraphQL APIs with the specified access key.
.. note::
If you're setting ``access_key`` in ``config.yaml`` please make sure you do
not check this file into a public repository.
An alternate and safe way is to pass the access key value to the command
as an environment variable:
.. code-block:: bash
export HASURA_GRAPHQL_ACCESS_KEY=xxxxx
hasura console
# OR in a single line
HASURA_GRAPHQL_ACCESS_KEY=xxxxx hasura console
You can also set the access key using a flag to the commad:
.. code-block:: bash
hasura console --access-key=XXXXXXXXXXXX
.. note::
The order of precedence for access key and endpoint is as follows:
CLI flag > Environment variable > Config file
.. _configure-cors:
Configure CORS
--------------
By default, all CORS requests are allowed. To run Hasura with more restrictive CORS settings, use the ``--cors-domain`` flag.
For example:
.. code-block:: bash
docker run -P -d hasura/graphql-engine:latest graphql-engine \
--database-url postgres://username:password@host:5432/dbname \
serve \
--access-key XXXXXXXXXXXXXXXX
--cors-domain https://mywebsite.com:8090

View File

@ -0,0 +1,18 @@
GraphQL engine server configuration
===================================
Hasura supports various flags and options for customising how you are running the GraphQL engine in your environment.
See the :doc:`server flag reference <reference>` for all the available flags.
The following are a few configuration use cases:
- :ref:`add-access-key`
- :ref:`cli-with-access-key`
- :ref:`configure-cors`
.. toctree::
:hidden:
Server flags reference <reference>
Server config examples <config-examples>

View File

@ -0,0 +1,90 @@
GraphQL engine server flags reference
=====================================
Every GraphQL engine command is structured as:
.. code-block:: bash
graphql-engine <server-flags> serve <command-flags>
Server flags
^^^^^^^^^^^^
For ``graphql-engine`` command these are the flags available
.. code-block:: none
--database-url Postgres database URL
<postgres/postgresql>://<user>:<password>@<host>:<port>/<db-name>
Example: postgres://admin:mypass@mydomain.com:5432/mydb
Or either you can specify following options
--host Postgres server host
-p, --port Postgres server port
-u, --user Database user name
-p, --password Password of the user
-d, --dbname Database name to connect to
Command flags
^^^^^^^^^^^^^
For ``serve`` subcommand these are the flags available
.. code-block:: none
--server-port Port on which graphql-engine should be served (default: 8080)
--access-key Secret access key, required to access this instance.
If specified client needs to send 'X-Hasura-Access-Key'
header
--cors-domain The domain, including sheme and port, to allow CORS for
--disable-cors Disable CORS handling
--auth-hook The authentication webhook, required to authenticate
incoming request
--jwt-secret The JSON containing type and the JWK used for
verifying. e.g: `{"type": "HS256", "key":
"<your-hmac-shared-secret>"}`,`{"type": "RS256",
"key": "<your-PEM-RSA-public-key>"}
-s, --stripes Number of stripes
-c, --connections Number of connections that need to be opened to Postgres
--timeout Each connection's idle time before it is closed
-i, --tx-iso Transaction isolation. read-commited / repeatable-read /
serializable
--root-dir This static dir is served at / and takes precedence over
all routes
--enable-console Enable API console. It is served at '/' and '/console'
Default environment variables
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
You can use environment variables to configure defaults instead of using flags:
For example:
.. code-block:: bash
HASURA_GRAPHQL_DATABASE_URL=postgres://user:pass@host:5432/dbname graphql-engine serve
These are the environment variables which are available:
.. code-block:: none
HASURA_GRAPHQL_DATABASE_URL Postgres database URL
<postgres/postgresql>://<user>:<password>@<host>:<port>/<db-
name>
Example: postgres://admin:mypass@mydomain.com:5432/mydb
HASURA_GRAPHQL_ACCESS_KEY Secret access key, required to access this instance.
If specified client needs to send 'X-Hasura-Access-Key'
header
HASURA_GRAPHQL_AUTH_HOOK The authentication webhook, required to authenticate
incoming request
HASURA_GRAPHQL_CORS_DOMAIN The domain, including sheme and port, to allow CORS for
.. note::
When the equivalent flags for environment variables are used, the flags will take precedence.

View File

@ -0,0 +1,105 @@
Run Hasura GraphQL Engine on Heroku
===================================
This guide will help you get Hasura GraphQL engine running as a "git push to deploy" app on
`Heroku <https://www.heroku.com/platform>`_ and connecting it to a `Heroku Postgres <https://www.heroku.com/postgres>`_
instance. If you want a simple, quick deployment on Heroku, follow this :doc:`Heroku quickstart
guide <../../getting-started/heroku-simple>`.
Clone the Hasura GraphQL engine Heroku app
------------------------------------------
The Hasura app with Heroku buildpack/configuration is available at:
https://github.com/hasura/graphql-engine-heroku
DATABASE_URL settings
^^^^^^^^^^^^^^^^^^^^^
Edit the command in the ``Dockerfile`` to change which database Hasura GraphQL engine connects to.
By default, it connects to the primary database in your app which is available at ``DATABASE_URL``.
.. code-block:: dockerfile
:emphasize-lines: 6
FROM hasura/graphql-engine:latest
# Change $DATABASE_URL to your Heroku Postgres URL if you're not using
# the primary Postgres instance in your app
CMD graphql-engine \
--database-url $DATABASE_URL \
serve \
--server-port $PORT \
--enable-console
Read about more configuration options :doc:`here <../graphql-engine-flags/reference>`.
Deploying
---------
These are some sample deployment instructions while creating a new app.
Step 1: Create app with ``--stack=container``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Use the `Heroku CLI <https://devcenter.heroku.com/articles/heroku-cli>`_ to create a new Heroku app. Let's call
the app ``graphql-on-postgres``.
.. code-block:: bash
# Replace graphql-on-postgres with whatever you'd like your app to be called
$ heroku create graphql-on-postgres --stack=container
Creating ⬢ graphql-on-postgres... done, stack is container
https://graphql-on-postgres.herokuapp.com/ | https://git.heroku.com/graphql-on-postgres.git
**Note**:
- ``HEROKU_GIT_REMOTE``: `https://git.heroku.com/graphql-on-postgres.git`
- ``HEROKU_APP_URL``: `https://graphql-on-postgres.herokuapp.com/`
Step 2: Create the Heroku Postgres Addon
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Create the Postgres addon in your Heroku app.
.. code-block:: bash
$ heroku addons:create heroku-postgresql:hobby-dev -a graphql-on-postgres
Creating heroku-postgresql:hobby-dev on ⬢ graphql-on-postgres... free
Database has been created and is available
! This database is empty. If upgrading, you can transfer
! data from another database with pg:copy
Created postgresql-angular-20334 as DATABASE_URL
Use heroku addons:docs heroku-postgresql to view documentation
Step 3: git push to deploy
^^^^^^^^^^^^^^^^^^^^^^^^^^
Remember to change ``HEROKU_GIT_REMOTE`` to your git remote below. In our case:
``https://git.heroku.com/graphql-on-postgres.git``
.. code-block:: bash
$ git init && git add .
$ git commit -m "first commit"
$ git remote add heroku HEROKU_GIT_REMOTE
$ git push heroku master
Visit ``https://graphql-on-postgres.herokuapp.com`` (replace ``graphql-on-postgres`` with your app name) and
you should see the Hasura console.
Advanced:
---------
- :doc:`Securing your GraphQL endpoint <securing-graphql-endpoint>`
- :doc:`using-existing-heroku-database`
- :doc:`Updating GraphQL engine <updating>`
- :doc:`Setting up migrations <../../migrations/index>`
.. toctree::
:titlesonly:
:hidden:
Securing your GraphQL endpoint <securing-graphql-endpoint>
using-existing-heroku-database
Updating GraphQL engine <updating>

View File

@ -0,0 +1,34 @@
Securing the GraphQL endpoint (Heroku)
======================================
To make sure that your GraphQL endpoint and the Hasura console are not publicly accessible, you need to
configure an access key.
Add the HASURA_GRAPHQL_ACCESS_KEY env var
-----------------------------------------
Head to the config-vars URL on your Heroku dashboard and set the ``HASURA_GRAPHQL_ACCESS_KEY`` environment variable.
.. image:: ../../../../img/graphql/manual/deployment/secure-heroku.png
Setting this environment variable will automatically restart the dyno. Now when you access your console, you'll be
prompted for the access key.
.. image:: ../../../../img/graphql/manual/deployment/access-key-console.png
(optional) Use the access key with the CLI
------------------------------------------
In case you're using the CLI to open the Hasura console, use the ``access-key`` flag when you open the console:
.. code-block:: bash
hasura console --access-key=mysecretkey
.. note::
If you're looking at adding authentication and access control to your GraphQL API then head
to :doc:`Authentication / access control <../../auth/index>`.

View File

@ -0,0 +1,59 @@
Updating Hasura GraphQL engine on Heroku
========================================
This guide will help you update Hasura GraphQL engine running on Heroku. This guide assumes that you already have
Hasura GraphQL engine running on Heroku.
The current latest version is:
.. raw:: html
<code>hasura/graphql-engine:<span class="latest-release-tag">latest</span></code>
Follow these steps to update Hasura GraphQL engine to the lastest version:
1) Clone the Hasura GraphQL engine Heroku app
---------------------------------------------
The Hasura app with Heroku buildpack/configuration is available at:
https://github.com/hasura/graphql-engine-heroku
If you already have this, then pull the latest changes which will have the updated GraphQL engine docker image.
2) Attach your Heroku app
-------------------------
Let's say your Heroku app is called ``hasura-heroku`` and is running on ``https://hasura-heroku.herokuapp.com``.
Use the `Heroku CLI <https://devcenter.heroku.com/articles/heroku-cli>`_ to configure the git repo you cloned in Step 1
to be able to push to this app.
.. code-block:: bash
# Replace hasura-heroku with your Heroku app's name
$ heroku git:remote -a hasura-heroku
3) Git push to deploy the latest Hasura GraphQL engine
------------------------------------------------------
When you ``git push`` to deploy, the Heroku app will get updated with the latest changes:
.. code-block:: bash
$ git push heroku master
Deploy a specific version of Hasura GraphQL engine
--------------------------------------------------
Head to the ``Dockerfile`` in the git repo you cloned in Step 1.
Change the ``FROM`` line to the specific version you want. A list of all releases can be found
at https://github.com/hasura/graphql-engine/releases
.. code-block:: Dockerfile
:emphasize-lines: 1
FROM hasura/graphql-engine:v1.0.0-alpha01
...
...
Change ``v1.0.0-alpha01`` to ``v1.0.0-alpha02`` for example, and then ``git push heroku master`` to deploy.

View File

@ -0,0 +1,45 @@
Using existing Heroku database
==============================
Let's say you have an existing `Heroku Postgres <https://www.heroku.com/postgres>`_ database with data in it, and you'd
like add GraphQL on it.
.. note::
In case you're exposing an existing database (esp. if it is production), please configure an access key
for the console and the GraphQL endpoint.
Step 0: Deploy Hasura on Heroku
-------------------------------
Deploy Hasura on Heroku by clicking on this button:
.. image:: https://camo.githubusercontent.com/83b0e95b38892b49184e07ad572c94c8038323fb/68747470733a2f2f7777772e6865726f6b7563646e2e636f6d2f6465706c6f792f627574746f6e2e737667
:width: 200px
:alt: heroku_deploy_button
:class: no-shadow
:target: https://heroku.com/deploy?template=https://github.com/hasura/graphql-engine-heroku
Follow the Heroku instructions to deploy, check if the Hasura console loads up when you **View app** and then head
to the **Manage App** screen on your Heroku dashboard.
Step 1: Remove the existing Postgres addon in the app
-----------------------------------------------------
Head to your Heroku dashboard and delete the Postgres addon:
.. image:: ../../../../img/graphql/manual/deployment/remove-heroku-postgres-addon.png
Step 2: Configure the DATABASE_URL environment variable
-------------------------------------------------------
Now configure the ``DATABASE_URL`` with your existing Heroku Postgres database URL.
.. image:: ../../../../img/graphql/manual/deployment/heroku-database-url-access.png
Step 3: Track tables and relationships
--------------------------------------
Wait for the GraphQL engine to restart and you'll see your existing tables as "untracked tables" in the console.
.. image:: ../../../../img/graphql/manual/getting-started/TrackTable.jpg

View File

@ -0,0 +1,36 @@
Deploying Hasura GraphQL Engine
===============================
.. note::
This section talks in depth about deploying the Hasura GraphQL engine for a **production like environment**.
If you would simply like to take the Hasura GraphQL engine for a quick spin, choose from our
:doc:`Getting started guides <../getting-started/index>`.
The Hasura GraphQL engine is a binary that is shipped as a Docker container.
Choose from the following guides to deploy the Hasura GraphQL engine and connect it to a Postgres database:
- :doc:`Deploy using Heroku <heroku/index>`
- :doc:`Deploy using Docker <docker/index>`
- :doc:`Deploy using Kubernetes <kubernetes/index>`
By default, Hasura GraphQL engine runs in a very permissive mode for easier development. Check out the below pages
to configure Hasura GraphQL engine for your production environment:
- :doc:`securing-graphql-endpoint`
- :doc:`postgres-permissions`
- :doc:`GraphQL engine server configuration <graphql-engine-flags/index>`
.. toctree::
:maxdepth: 1
:titlesonly:
:hidden:
Using Heroku <heroku/index>
Using Docker <docker/index>
Using Kubernetes <kubernetes/index>
securing-graphql-endpoint
postgres-permissions
GraphQL engine server configuration <graphql-engine-flags/index>
Updating GraphQL engine <updating>

View File

@ -0,0 +1,84 @@
Run Hasura GraphQL Engine on Kubernetes
=======================================
This guide assumes that you already have Postgres running and helps you set up the Hasura GraphQL engine on Kubernetes
and connect it to your Postgres database.
Step 1: Get the Kubernetes deployment and service files
-------------------------------------------------------
The `hasura/graphql-engine-install-manifests <https://github.com/hasura/graphql-engine-install-manifests>`_ repo
contains all installation manifests required to deploy Hasura anywhere. Get the Kubernetes deployment and service files
from there:
.. code-block:: bash
$ wget https://raw.githubusercontent.com/hasura/graphql-engine-install-manifests/master/kubernetes/deployment.yaml
$ wget https://raw.githubusercontent.com/hasura/graphql-engine-install-manifests/master/kubernetes/svc.yaml
Step 2: Set the Postgres database url
-------------------------------------
Edit ``deployment.yaml`` and set the right database url:
.. code-block:: yaml
:emphasize-lines: 4
...
env:
- name: HASURA_GRAPHQL_DATABASE_URL
value: postgres://username:password@hostname:port/dbname
...
Examples of ``database-url``:
- ``postgres://admin:password@localhost:5432/my-db``
- ``postgres://admin:@localhost:5432/my-db`` *(if there is no password)*
Step 3: Create the Kubernetes deployment and service
----------------------------------------------------
.. code-block:: bash
$ kubectl create -f deployment.yaml
$ kubectl create -f svc.yaml
Step 4: Open the hasura console
-------------------------------
The above creates a LoadBalancer type service with port 80. So you should be able to access the console at the
external IP.
For example, using docker-for-desktop on mac:
.. code-block:: bash
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
hasura LoadBalancer 10.96.214.240 localhost 80:30303/TCP 4m
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 8m
Head to: http://localhost and the console should load!
Step 5: Track existing tables and relationships
-----------------------------------------------
On the console page, you'll see your existing tables/views as "Untracked tables/views" in the console. Click the
``Add all`` button to enable GraphQL APIs over them.
.. image:: ../../../../img/graphql/manual/getting-started/TrackTable.jpg
Advanced:
---------
- :doc:`Securing your GraphQL endpoint <securing-graphql-endpoint>`
- :doc:`Updating GraphQL engine <updating>`
- :doc:`Setting up migrations <../../migrations/index>`
.. toctree::
:titlesonly:
:hidden:
Securing your GraphQL endpoint <securing-graphql-endpoint>
Updating GraphQL engine <updating>

View File

@ -0,0 +1,47 @@
Securing the GraphQL endpoint (Kubernetes)
==========================================
To make sure that your GraphQL endpoint and the Hasura console are not publicly accessible, you need to
configure an access key.
Add the HASURA_GRAPHQL_ACCESS_KEY env var
-----------------------------------------
Update the ``deployment.yaml`` to set the ``HASURA_GRAPHQL_ACCESS_KEY`` environment variable.
.. code-block:: yaml
:emphasize-lines: 10,11
...
spec:
containers:
...
command: ["graphql-engine"]
args: ["serve", "--enable-console"]
env:
- name: HASURA_GRAPHQL_DATABASE_URL
value: postgres://username:password@hostname:port/dbname
- name: HASURA_GRAPHQL_ACCESS_KEY
value: mysecretkey
ports:
- containerPort: 8080
protocol: TCP
resources: {}
(optional) Use the access key with the CLI
------------------------------------------
In case you're using the CLI to open the Hasura console, use the ``access-key`` flag when you open the console:
.. code-block:: bash
hasura console --access-key=mysecretkey
.. note::
If you're looking at adding authentication and access control to your GraphQL API then head
to :doc:`Authentication / access control <../../auth/index>`.

View File

@ -0,0 +1,47 @@
Updating Hasura GraphQL engine running on Kubernetes
====================================================
This guide will help you update Hasura GraphQL engine running on Kubernetes. This guide assumes that you already have
Hasura GraphQL engine running on Kubernetes.
1) Check the latest release version
-----------------------------------
The current latest version is:
.. raw:: html
<code>hasura/graphql-engine:<span class="latest-release-tag">latest</span></code>
All the versions can be found at: https://github.com/hasura/graphql-engine/releases
2) Update the container image
-----------------------------
In the ``deployment.yaml`` file, update the image tag to this latest version.
For example, if you had:
.. raw:: html
<code>
containers:<br>
- image: hasura/graphql-engine:v1.0.0-alpha01
</code>
you should change it to:
.. raw:: html
<code>
containers:<br>
- image: hasura/graphql-engine:<span class="latest-release-tag">latest</span>
</code>
3) Rollout the change
---------------------
.. code-block:: bash
$ kubectl replace -f deployment.yaml

View File

@ -0,0 +1,56 @@
Postgres permissions
====================
If you're running in a controlled environment, you might need to configure Hasura GraphQL engine to use a
specific Postgres user that your DBA gives you.
Hasura GraphQL engine needs access to your Postgres database with the following permissions:
- (required) Read & write access on 2 schemas: ``hdb_catalog`` and ``hdb_views``.
- (required) Read access to the ``information_schema`` and ``pg_catalog`` schemas, to query for list of tables.
- (required) Read access to the schemas (public or otherwise) if you only want to support queries.
- (optional) Write access to the schemas if you want to support mutations as well
- (optional) To create tables and views via the Hasura console (the admin UI) you'll need the privilege to create
tables/views. This might not be required when you're working with an existing database
Here's a sample SQL block that you can run on your database to create the right credentials:
.. code-block:: sql
-- We will create a separate user and grant permissions on hasura-specific
-- schemas and information_schema and pg_catalog
-- These permissions/grants are required for Hasura to work properly.
-- create a separate user for hasura
CREATE USER hasurauser WITH PASSWORD 'hasurauser';
-- create pgcrypto extension, required for UUID
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- create the schemas required by the hasura system
-- NOTE: If you are starting from scratch: drop the below schemas first, if they exist.
CREATE SCHEMA IF NOT EXISTS hdb_catalog;
CREATE SCHEMA IF NOT EXISTS hdb_views;
-- grant all privileges on system schemas
GRANT ALL PRIVILEGES ON SCHEMA hdb_catalog TO hasurauser;
GRANT ALL PRIVILEGES ON SCHEMA hdb_views TO hasurauser;
-- grant select permissions on information_schema and pg_catalog. This is
-- required for hasura to query list of available tables
GRANT SELECT ON ALL TABLES IN SCHEMA information_schema TO hasurauser;
GRANT SELECT ON ALL TABLES IN SCHEMA pg_catalog TO hasurauser;
-- Below permissions are optional. This is dependent on what access to your
-- tables/schemas - you want give to hasura. If you want expose the public
-- schema for GraphQL query then give permissions on public schema to the
-- hasura user.
-- grant all privileges on all tables in the public schema. This can be customised:
-- For example, if you only want to use GraphQL regular queries and not mutations,
-- then you can: GRANT SELECT ON ALL TABLES...
GRANT ALL ON ALL TABLES IN SCHEMA public TO hasurauser;
GRANT ALL ON ALL SEQUENCES IN SCHEMA public TO hasurauser;
-- Similarly repeat this for other schemas, if you have.

View File

@ -0,0 +1,18 @@
Securing the GraphQL endpoint
=============================
To make sure that your GraphQL endpoint and the Hasura console are not publicly accessible, you need to
configure an access key.
Depending on your deployment method, follow one of these guides to configure an access key, and prevent public
access to your GraphQL endpoint and the Hasura console:
- :doc:`For Heroku <heroku/securing-graphql-endpoint>`
- :doc:`For Docker <docker/securing-graphql-endpoint>`
- :doc:`For Kubernetes <kubernetes/securing-graphql-endpoint>`
.. note::
If you're looking at adding authentication and access control to your GraphQL API then head
to :doc:`Authentication / access control <../auth/index>`.

View File

@ -0,0 +1,15 @@
Updating Hasura GraphQL engine
==============================
The Hasura GraphQL engine runs off a Docker image and updates are as simple as changing the image tag.
The current latest version is:
.. raw:: html
<code>hasura/graphql-engine:<span class="latest-release-tag">latest</span></code>
Please follow the appropriate guide to update the GraphQL engine version you're running:
- :doc:`Updating on Heroku <./heroku/updating>`
- :doc:`Updating on Docker <./docker/updating>`

View File

@ -0,0 +1,135 @@
Hasura GraphQL engine internals
===============================
Hasura GraphQL engine uses a set of internal tables to manage the state of the database and the
GraphQL schema. It uses the data in these tables to generate the GraphQL API which then can be accessed
from different clients.
Hasura GraphQL engine when initialized, creates a schema called ``hdb_catalog`` in the Postgres database and
initializes a few tables under it as described below.
hdb_catalog
-----------
The schema created by Hasura GraphQL engine where it manages its internal state. Whenever a
table/permission/relationship is created/updated using the Hasura console or Hasura CLI. Hasura GraphQL engine
captures that information and stores it in the corresponding tables.
The following tables are used by Hasura GraphQL engine:
hdb_table
^^^^^^^^^
This table stores information about all the tables/views which are created/tracked using the Hasura console or
Hasura CLI.
Schema
""""""
.. image:: ../../../img/graphql/manual/engine-internals/hdb_table.jpg
:scale: 50%
:alt: hdb_table schema
Column Definitions
""""""""""""""""""
- **table_schema**:
Captures information about the schema under which a table/view is tracked.
- **table_name**:
Captures name of the tracked table/view.
- **is_system_defined**:
Used by GraphQL engine for internal purpose.
If it is true, then the table/view is created by GraphQL engine for internal purpose. If it is false, then the
table/view is created by the end user.
hdb_relationship
^^^^^^^^^^^^^^^^
This table stores information about the relationships created for tables/views using the Hasura console or
Hasura CLI.
Schema
""""""
.. image:: ../../../img/graphql/manual/engine-internals/hdb_relationship.jpg
:scale: 50%
:alt: hdb_relationship schema
Column Definitions
""""""""""""""""""
- **table_schema**:
Captures information about the schema under which a relationship is created
- **table_name**:
Captures name of the table/view under which a relationship is created.
- **rel_name**:
Captures name of the relationship.
- **rel_type**:
Captures the relationship type (object/array).
- **rel_def**:
Captures information about how the relationship is defined.
For example, if it is defined using a foreign-key constraint on column ``user_id``:
.. code-block:: json
{
"foreign_key_constraint_on": "user_id"
}
- **comment**:
Captures the comment for the relationship.
- **is_system_defined**:
If it is true, then the relationship is created by GraphQL engine for internal purpose. If it is false, then
the relationship is created by the end user.
hdb_permission
^^^^^^^^^^^^^^
This table stores information about the access control rules on tables/views.
Schema
""""""
.. image:: ../../../img/graphql/manual/engine-internals/hdb_permission.jpg
:scale: 50%
:alt: hdb_permission schema
Column Definitions
""""""""""""""""""
- **table_schema**:
Captures information about the schema under which a permission is created
- **table_name**:
Captures name of the table/view under which a permission is created.
- **role_name**:
Captures name of the role for which this permission will be applicable.
- **perm_type**:
Captures the permission type (insert/select/update/delete).
- **perm_def**:
Captures information about how the permission is defined.
Whenever a query is made with the above role for the above table GraphQL Engine
will first validate the requested columns with the columns which the user has access to using the ``columns`` key.
Once the query is validated the appropriate results are returned after applying the filter defined in the ``filter``
key.
For example:
.. code-block:: json
{
"columns": ["id", "name"],
"filter": {
"id": {
"_eq": "X-HASURA-USER-ID"
}
}
}
- **comment**:
Captures the comment for the permission.
- **is_system_defined**:
If it is true, then the permission is created by GraphQL engine for internal purpose. If it is false, then the
permission is created by the end user.

View File

@ -0,0 +1,17 @@
Creating an event trigger
=========================
Event triggers can be created using the Hasura console.
Open the Hasura console, head to the ``Events`` tab and click on the ``Create trigger`` button to open up the
interface below to create an event trigger:
.. image:: ../../../img/graphql/manual/event-triggers/create-event-trigger-annotations.png
Retry configuration
-------------------
Retry configuration is available in the "Advanced settings" when you create a trigger.
1. ``num_retries``: This is the number of times a failed invocation is retried. The default value is **0**.
2. ``interval_sec``: The number of seconds after which a failed invocation for an event, is retried. The default value
is **10**.

View File

@ -0,0 +1,19 @@
Event triggers
==============
Hasura can be used to create event triggers on tables in the Postgres database. Event triggers reliably capture
events happening on the specified tables and then call configured webhooks to carry out some business logic.
.. image:: ../../../img/graphql/manual/event-triggers/database-event-triggers.png
See:
^^^^
.. toctree::
:maxdepth: 2
:titlesonly:
create-trigger
payload
serverless
samples

View File

@ -0,0 +1,147 @@
Event trigger payload
=====================
The following is the payload and delivery mechanism of an event to the webhook when an event trigger is invoked.
HTTP request method
-------------------
Delivered over ``HTTP POST`` with the following headers:
.. code-block:: none
Content-Type: application/json
JSON payload
------------
.. code-block:: none
{
"event": {
"op": "<op-name>",
"data": {
"old": <column-values>,
"new": <column-values>
}
},
"created_at": "<timestamp>",
"id": "<uuid>",
"trigger": {
"name": "<name-of-trigger>",
"id": "<uuid>"
},
"table": {
"schema": "<schema-name>",
"name": "<table-name>"
}
}
.. list-table::
:header-rows: 1
* - Key
- Type
- Description
* - op-name
- OpName_
- Name of the operation. Can only be "INSERT", "UPDATE" or "DELETE"
* - column-values
- Object_
- Key-value pairs of column name and their values of the table
* - timestamp
- String
- Timestamp value
* - uuid
- String
- A UUID value
* - name-of-trigger
- String
- Name of the trigger
* - schema-name
- String
- Name of the postgres schema where the table is
* - table-name
- String
- Name of the table
**In case of**:
- INSERT
- ``event.data.old`` will be ``null``
- ``event.data.new`` will contain the insert row
- UPDATE
- ``event.data.old`` will be values before the update
- ``event.data.new`` will contain the values after the update
- DELETE
- ``event.data.old`` will contain the row that is deleted
- ``event.data.new`` will be ``null``
**For example**:
.. code-block:: json
{
"id": "85558393-c75d-4d2f-9c15-e80591b83894",
"created_at": "2018-09-05T07:14:21.601701Z",
"trigger": {
"name": "test_trigger",
"id": "37b7f91a-b3a5-4b85-be59-e5920d72f6aa"
},
"table": {
"schema": "public",
"name": "users"
},
"event": {
"op": "INSERT",
"data": {
"old": null,
"new": {
"id":"42",
"name": "john doe"
}
}
}
}
Syntax definitions
------------------
.. _Object:
Object
^^^^^^
.. code-block:: none
{
"column1": "value1",
"column2": "value2",
..
}
.. _OpName:
OpName
^^^^^^
.. parsed-literal::
"INSERT" | "UPDATE" | "DELETE"
Webhook response structure
--------------------------
A ``2xx`` response status code is deemed to be a successful invocation of the webhook. Any other response status will be
deemed as an unsucessful invocation which may cause retries as per the retry configuration.
It is also recommended that you return a JSON object in your webhook response.

Some files were not shown because too many files have changed in this diff Show More