mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-09-11 10:46:25 +03:00
parent
9c7c650d2b
commit
d3d9845497
@ -2,6 +2,7 @@
|
||||
LICENSE
|
||||
scripts/*
|
||||
assets/*
|
||||
docs/*
|
||||
.circleci/*
|
||||
.ciignore
|
||||
.gitignore
|
||||
|
57
docs/.gitignore
vendored
Normal file
57
docs/.gitignore
vendored
Normal 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
6
docs/404.rst
Normal 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
52
docs/CONTRIBUTING.md
Normal 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
21
docs/LICENSE
Normal 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
264
docs/Makefile
Normal 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
13
docs/README.md
Normal 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
329
docs/_ext/djangodocs.py
Normal 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
95
docs/_ext/fulltoc.py
Normal 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
140
docs/_ext/generate_index.py
Normal 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
109
docs/_ext/global_tabs.py
Normal 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
55
docs/_ext/graphiql.py
Normal 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}
|
47
docs/_ext/lexer_graphql.py
Normal file
47
docs/_ext/lexer_graphql.py
Normal 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
53
docs/_ext/lexer_jsx.py
Normal 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()
|
38
docs/_ext/local_toctree.py
Normal file
38
docs/_ext/local_toctree.py
Normal 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
772
docs/_static/djangosite.css
vendored
Normal file
File diff suppressed because one or more lines are too long
68
docs/_static/global_tabs/global_tabs.css
vendored
Normal file
68
docs/_static/global_tabs/global_tabs.css
vendored
Normal 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
112
docs/_static/global_tabs/global_tabs.js
vendored
Normal 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
26
docs/_static/graphiql/LICENSE
vendored
Normal 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
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
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
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
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
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
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
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
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
224
docs/_theme/djangodocs/basic/layout.html
vendored
Normal 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 ' »' 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 %}© <a href="{{ path }}">Copyright</a> {{ copyright }}.{% endtrans %}
|
||||
{%- else %}
|
||||
{% trans copyright=copyright|e %}© 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
4
docs/_theme/djangodocs/genindex.html
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{% extends "basic/genindex.html" %}
|
||||
|
||||
{% block bodyclass %}{% endblock %}
|
||||
{% block sidebarwrapper %}{% endblock %}
|
655
docs/_theme/djangodocs/layout.html
vendored
Normal file
655
docs/_theme/djangodocs/layout.html
vendored
Normal 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 %}
|
||||
« <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> »
|
||||
{%- 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 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 © 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
23
docs/_theme/djangodocs/localtoc.html
vendored
Normal 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
3
docs/_theme/djangodocs/modindex.html
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{% extends "basic/modindex.html" %}
|
||||
{% block bodyclass %}{% endblock %}
|
||||
{% block sidebarwrapper %}{% endblock %}
|
33
docs/_theme/djangodocs/pages/landing.html
vendored
Normal file
33
docs/_theme/djangodocs/pages/landing.html
vendored
Normal 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>
|
16
docs/_theme/djangodocs/pages/loading.html
vendored
Normal file
16
docs/_theme/djangodocs/pages/loading.html
vendored
Normal 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
1
docs/_theme/djangodocs/search.html
vendored
Normal file
@ -0,0 +1 @@
|
||||
|
0
docs/_theme/djangodocs/searchbox.html
vendored
Normal file
0
docs/_theme/djangodocs/searchbox.html
vendored
Normal file
3
docs/_theme/djangodocs/static/default.css
vendored
Normal file
3
docs/_theme/djangodocs/static/default.css
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
@import url(reset-fonts-grids.css);
|
||||
@import url(djangodocs.css);
|
||||
@import url(homepage.css);
|
167
docs/_theme/djangodocs/static/djangodocs.css
vendored
Normal file
167
docs/_theme/djangodocs/static/djangodocs.css
vendored
Normal 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%; }
|
||||
**/
|
BIN
docs/_theme/djangodocs/static/docicons-behindscenes.png
vendored
Normal file
BIN
docs/_theme/djangodocs/static/docicons-behindscenes.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 KiB |
BIN
docs/_theme/djangodocs/static/docicons-note.png
vendored
Normal file
BIN
docs/_theme/djangodocs/static/docicons-note.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 554 B |
BIN
docs/_theme/djangodocs/static/docicons-philosophy.png
vendored
Normal file
BIN
docs/_theme/djangodocs/static/docicons-philosophy.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 789 B |
BIN
docs/_theme/djangodocs/static/docicons-warning.png
vendored
Normal file
BIN
docs/_theme/djangodocs/static/docicons-warning.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 432 B |
22
docs/_theme/djangodocs/static/homepage.css
vendored
Normal file
22
docs/_theme/djangodocs/static/homepage.css
vendored
Normal 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; }
|
13
docs/_theme/djangodocs/static/reset-fonts-grids.css
vendored
Normal file
13
docs/_theme/djangodocs/static/reset-fonts-grids.css
vendored
Normal 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
4
docs/_theme/djangodocs/theme.conf
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
[theme]
|
||||
inherit = basic
|
||||
stylesheet = default.css
|
||||
pygments_style = trac
|
76
docs/algolia_index/algolia_index.py
Normal file
76
docs/algolia_index/algolia_index.py
Normal 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
419
docs/conf.py
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -0,0 +1,4 @@
|
||||
- args:
|
||||
sql: ALTER TABLE public."article" ADD FOREIGN KEY ("author_id") REFERENCES public."author"
|
||||
("id")
|
||||
type: run_sql
|
@ -0,0 +1,8 @@
|
||||
- args:
|
||||
name: author
|
||||
table:
|
||||
name: article
|
||||
schema: public
|
||||
using:
|
||||
foreign_key_constraint_on: author_id
|
||||
type: create_object_relationship
|
@ -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
|
@ -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
|
@ -0,0 +1,9 @@
|
||||
- args:
|
||||
permission:
|
||||
columns:
|
||||
- id
|
||||
- name
|
||||
filter: {}
|
||||
role: anonymous
|
||||
table: author
|
||||
type: create_select_permission
|
@ -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
|
@ -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
|
@ -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
|
@ -0,0 +1,3 @@
|
||||
- args:
|
||||
sql: ALTER TABLE "article" ALTER COLUMN "published_on" DROP NOT NULL;
|
||||
type: run_sql
|
@ -0,0 +1,6 @@
|
||||
- args:
|
||||
sql: |-
|
||||
UPDATE article
|
||||
SET published_on = NULL
|
||||
WHERE is_published=false;
|
||||
type: run_sql
|
@ -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
|
@ -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
|
@ -0,0 +1,9 @@
|
||||
- args:
|
||||
permission:
|
||||
columns:
|
||||
- id
|
||||
- avg
|
||||
filter: {}
|
||||
role: anonymous
|
||||
table: author_average_rating
|
||||
type: create_select_permission
|
@ -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
|
@ -0,0 +1,10 @@
|
||||
- args:
|
||||
permission:
|
||||
columns:
|
||||
- id
|
||||
- title
|
||||
- rating
|
||||
filter: {}
|
||||
role: anonymous
|
||||
table: article_safe
|
||||
type: create_select_permission
|
@ -0,0 +1,3 @@
|
||||
- args:
|
||||
sql: ALTER TABLE "article" ALTER COLUMN "rating" DROP NOT NULL;
|
||||
type: run_sql
|
29
docs/graphql/manual/api-reference/index.rst
Normal file
29
docs/graphql/manual/api-reference/index.rst
Normal 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>
|
500
docs/graphql/manual/api-reference/mutation.rst
Normal file
500
docs/graphql/manual/api-reference/mutation.rst
Normal 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"],
|
||||
..
|
||||
}
|
43
docs/graphql/manual/api-reference/pgtypes.csv
Normal file
43
docs/graphql/manual/api-reference/pgtypes.csv
Normal 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_
|
|
261
docs/graphql/manual/api-reference/postgresql-types.rst
Normal file
261
docs/graphql/manual/api-reference/postgresql-types.rst
Normal 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>`__
|
||||
|
||||
|
233
docs/graphql/manual/api-reference/query.rst
Normal file
233
docs/graphql/manual/api-reference/query.rst
Normal 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]
|
||||
|
118
docs/graphql/manual/auth/basics.rst
Normal file
118
docs/graphql/manual/auth/basics.rst
Normal 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>`
|
||||
|
||||
|
90
docs/graphql/manual/auth/common-roles-auth-examples.rst
Normal file
90
docs/graphql/manual/auth/common-roles-auth-examples.rst
Normal 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
|
65
docs/graphql/manual/auth/config.rst.wip
Normal file
65
docs/graphql/manual/auth/config.rst.wip
Normal 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 "*"
|
36
docs/graphql/manual/auth/index.rst
Normal file
36
docs/graphql/manual/auth/index.rst
Normal 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
|
199
docs/graphql/manual/auth/jwt.rst
Normal file
199
docs/graphql/manual/auth/jwt.rst
Normal 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
|
121
docs/graphql/manual/auth/roles-variables.rst
Normal file
121
docs/graphql/manual/auth/roles-variables.rst
Normal 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.
|
||||
|
||||
|
23
docs/graphql/manual/auth/webhook-examples.rst
Normal file
23
docs/graphql/manual/auth/webhook-examples.rst
Normal 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.
|
68
docs/graphql/manual/auth/webhook.rst
Normal file
68
docs/graphql/manual/auth/webhook.rst
Normal 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>`
|
94
docs/graphql/manual/deployment/docker/index.rst
Normal file
94
docs/graphql/manual/deployment/docker/index.rst
Normal 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>
|
@ -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>`.
|
34
docs/graphql/manual/deployment/docker/updating.rst
Normal file
34
docs/graphql/manual/deployment/docker/updating.rst
Normal 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>
|
@ -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
|
@ -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>
|
@ -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.
|
105
docs/graphql/manual/deployment/heroku/index.rst
Normal file
105
docs/graphql/manual/deployment/heroku/index.rst
Normal 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>
|
@ -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>`.
|
59
docs/graphql/manual/deployment/heroku/updating.rst
Normal file
59
docs/graphql/manual/deployment/heroku/updating.rst
Normal 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.
|
@ -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
|
36
docs/graphql/manual/deployment/index.rst
Normal file
36
docs/graphql/manual/deployment/index.rst
Normal 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>
|
84
docs/graphql/manual/deployment/kubernetes/index.rst
Normal file
84
docs/graphql/manual/deployment/kubernetes/index.rst
Normal 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>
|
@ -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>`.
|
47
docs/graphql/manual/deployment/kubernetes/updating.rst
Normal file
47
docs/graphql/manual/deployment/kubernetes/updating.rst
Normal 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
|
||||
|
56
docs/graphql/manual/deployment/postgres-permissions.rst
Normal file
56
docs/graphql/manual/deployment/postgres-permissions.rst
Normal 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.
|
18
docs/graphql/manual/deployment/securing-graphql-endpoint.rst
Normal file
18
docs/graphql/manual/deployment/securing-graphql-endpoint.rst
Normal 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>`.
|
||||
|
15
docs/graphql/manual/deployment/updating.rst
Normal file
15
docs/graphql/manual/deployment/updating.rst
Normal 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>`
|
135
docs/graphql/manual/engine-internals/index.rst
Normal file
135
docs/graphql/manual/engine-internals/index.rst
Normal 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.
|
17
docs/graphql/manual/event-triggers/create-trigger.rst
Normal file
17
docs/graphql/manual/event-triggers/create-trigger.rst
Normal 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**.
|
19
docs/graphql/manual/event-triggers/index.rst
Normal file
19
docs/graphql/manual/event-triggers/index.rst
Normal 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
|
147
docs/graphql/manual/event-triggers/payload.rst
Normal file
147
docs/graphql/manual/event-triggers/payload.rst
Normal 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
Loading…
Reference in New Issue
Block a user