├── .gitignore ├── Makefile ├── README.md ├── docker-compose.yml ├── elasticsearch └── .gitkeep ├── frontend ├── .babelrc ├── .dockerignore ├── .gitignore ├── Dockerfile ├── README.md ├── elm.json ├── package-lock.json ├── package.json ├── src │ ├── Main.elm │ ├── assets │ │ └── images │ │ │ └── favicon.ico │ ├── index.html │ ├── index.js │ └── styles.scss ├── tests │ └── Example.elm └── webpack.config.js ├── hasura ├── config.yaml ├── metadata │ ├── actions.graphql │ ├── actions.yaml │ ├── allow_list.yaml │ ├── cron_triggers.yaml │ ├── databases │ │ ├── databases.yaml │ │ └── default │ │ │ ├── functions │ │ │ └── functions.yaml │ │ │ └── tables │ │ │ ├── public_post_status.yaml │ │ │ ├── public_posts.yaml │ │ │ ├── public_posts_tags.yaml │ │ │ ├── public_tags.yaml │ │ │ └── tables.yaml │ ├── inherited_roles.yaml │ ├── query_collections.yaml │ ├── remote_schemas.yaml │ ├── rest_endpoints.yaml │ └── version.yaml └── migrations │ └── default │ ├── 1620166477942_create_table_public_posts │ ├── down.sql │ └── up.sql │ ├── 1620166597095_create_table_public_tags │ ├── down.sql │ └── up.sql │ ├── 1620166708080_create_table_public_posts_tags │ ├── down.sql │ └── up.sql │ ├── 1620166889801_create_table_public_post_status │ ├── down.sql │ └── up.sql │ ├── 1620166942292_insert_into_public_post_status │ ├── down.sql │ └── up.sql │ ├── 1620166976476_insert_into_public_post_status │ ├── down.sql │ └── up.sql │ ├── 1620167041972_insert_into_public_post_status │ ├── down.sql │ └── up.sql │ ├── 1620167117091_alter_table_public_posts_add_column_status │ ├── down.sql │ └── up.sql │ ├── 1620167136786_set_fk_public_posts_status │ ├── down.sql │ └── up.sql │ ├── 1620242455446_insert_into_public_posts │ ├── down.sql │ └── up.sql │ └── 1620242497114_insert_into_public_posts │ ├── down.sql │ └── up.sql ├── nginx └── nginx.conf ├── postgres └── Dockerfile └── test.sql /.gitignore: -------------------------------------------------------------------------------- 1 | postgres/db_data 2 | elasticsearch/data 3 | .idea/ 4 | *.kate-swp 5 | nginx-le/ssl 6 | frontend-dist/ 7 | *.log 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # This file should gather/document commonly used commands and make remembering them easier. 2 | # It also serves to clean up the README. 3 | 4 | # Some default parameters 5 | c=frontend 6 | 7 | # The .PHONY rule should come before all tasks that do not have "file targets", which is all of 'm :) 8 | 9 | # Borrowed from https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html 10 | .PHONY: help 11 | help: ## Display this help section 12 | @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) 13 | .DEFAULT_GOAL := help 14 | 15 | 16 | .PHONY: jumpin 17 | jumpin: ## Get a bash shell in an already started container ('frontend' by default, pick another one with c=) 18 | docker-compose exec $(c) bash 19 | 20 | .PHONY: ps 21 | ps: ## List all containers that are part of this project 22 | docker-compose ps 23 | 24 | .PHONY: hasura-migrations-init 25 | hasura-migrations-init: ## Initialize Hasura migrations 26 | @docker-compose exec hasura bash -c "cd /tmp; hasura-cli init hasura --endpoint http://localhost:8080; cp -r /tmp/hasura/* /hasura/" 27 | @echo "When using the Makefile start conosle with: make hasura-console" 28 | 29 | .PHONY: hasura-migrations-status 30 | hasura-migrations-status: ## Check Hasura's migration status 31 | @docker-compose exec hasura bash -c "cd /hasura; hasura-cli migrate status --database-name=postgres" 32 | 33 | .PHONY: hasura-migrations-squash 34 | hasura-migrations-squash: ## Squash multiple migrations into one (currently broken) 35 | @echo "Go run it yourself (fill in the gaps and use the resulting migration version with 'make hasura-migration-apply'):" 36 | @echo 'docker-compose exec hasura bash -c "cd /hasura; hasura-cli migrate squash --name \"\" --from --database-name=postgres"' 37 | 38 | .PHONY: hasura-migrations-apply 39 | hasura-migrations-apply: ## Make Hasura apply it's migrations 40 | @echo "Go run it yourself (fill in the squased migration version):" 41 | @echo 'docker-compose exec hasura bash -c "cd /hasura; hasura-cli migrate apply --version \"\" --skip-execution --database-name=postgres"' 42 | 43 | .PHONY: hasura-export-metadata 44 | hasura-export-metadata: ## Export the metadata from Hasura 45 | @docker-compose exec hasura bash -c "cd /hasura; hasura-cli metadata export" 46 | 47 | .PHONY: hasura-console 48 | hasura-console: ## Start the Hasura console (on http://localhost:9695) 49 | @docker-compose exec hasura bash -c "apt-get install -y socat; cd /hasura; \ 50 | socat TCP-LISTEN:8080,fork TCP:hasura:8080 & \ 51 | socat TCP-LISTEN:9695,fork,reuseaddr,bind=hasura TCP:127.0.0.1:9695 & \ 52 | socat TCP-LISTEN:9693,fork,reuseaddr,bind=hasura TCP:127.0.0.1:9693 & \ 53 | hasura-cli console --log-level DEBUG --address "127.0.0.1" --no-browser || exit 1 " 54 | 55 | .PHONY: generate-elm-graphql-client 56 | generate-elm-graphql-client: ## Generate GraphQL client library 57 | @docker-compose run elm-graphql-generator \ 58 | /usr/local/bin/npx --yes --package=@dillonkearns/elm-graphql@4.2.0 \ 59 | -- elm-graphql http://hasura:8080/v1/graphql --base HasuraClient \ 60 | --header "x-hasura-admin-secret:adminsecret" \ 61 | --output /output --skip-elm-format 62 | 63 | .PHONY: frontend-prod-build 64 | frontend-prod-build: ## Create a production build 65 | @docker-compose run frontend \ 66 | npm run prod 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | low-code-backend-dockered 2 | ========================= 3 | 4 | Starter kit for rapid+typesafe web application development on open source foundations. 5 | 6 | Features: 7 | 8 | * Postgres as the primary data store. 9 | * ZomboDB adds auto-synced full text search (including fuzzy matching and suggestions) to Postgres, by bridging to ElasticSearch. 10 | * Hasura to manage permissions (and usage limits), database migrations and provide GraphQL interface. Basically removing the need for server-side controller logic. 11 | * Elm with [elm-graphql](https://github.com/jamesmacaulay/elm-graphql) to demo the joy of type safe queries in the front end. 12 | 13 | All together its a workflow that allows all effort to be focused in: 14 | 15 | * setting up database structure and permission (mostly no-code experience) 16 | * UX design and implementation (as exmaple in web UI Elm is provided) 17 | 18 | Server-side updates may change the API and therefor break your app if the exact GraphQL spec is not enforced. 19 | Using [elm-graphql](https://package.elm-lang.org/packages/dillonkearns/elm-graphql/latest) 20 | and [Hasura](https://hasura.io) we can generate an Elm GraphQL client that exactly fits your data model, 21 | and thus only allows valid queries to be expressed in Elm. 22 | After changing the database schema simply regenerate the Elm GraphQL client library code. 23 | Any errors resulting from your change are now compile errors and should show up as such in your IDE. 24 | Good stuff. 25 | 26 | Stop writing/debugging layers of error-prone boilerplate and focus on cranking out features! 27 | 28 | Highlights: 29 | 30 | * Strong typesafety from database schema, through GraphQL API to frontend (with generated GraphQL client code). 31 | * Define database schema with a UI that creates migrations! (to aid collaboration and deployments) 32 | * Hot code reloading in development mode. 33 | * Optimized (pre-gzipped) production builds, served by Nginx in production mode. 34 | * And everything runs from containers: 35 | * No need to install any tooling on the local machine except for `docker`, `docker-compose` and `make`. 36 | * All versions are pinned, so it should work anywhere (if not please raise an issue). 37 | * Uninstalls cleanly. 38 | 39 | 40 | ## Ingredients 41 | 42 | This repo contains the following parts: 43 | 44 | * Dockered Postgres database 45 | * Dockered Hasura which provides: 46 | * a GraphQL frontend to Postgres 47 | * an API gateway with (row based) authorization 48 | * a UI for schema changes that also manages migrations (see `/hasura/metadata` and `/hasura/migrations`) 49 | * a basic UI for CRUD operations on data 50 | * Dockered type-safe Elm GraphQL API client generator 51 | * Dockered Webpack based setup providing: 52 | * hot code reloading in development 53 | * production builds (with pre-gzipped artifacts) 54 | * Dockered Nginx serving pre-gzipped production builds (to show the setup on full speed) 55 | * Container orchestration with [docker-compose](https://docs.docker.com/compose) 56 | * A minimal demo Elm app that uses the generated GraphQL client (see `/frontend`) 57 | * Some migrations, seed data and basic role definitions (all Hasura managed) 58 | * A self-documenting Makefile for common tasks (run `make help`) 59 | * This README explaining what's inside and how it works 60 | 61 | 62 | ## Get going 63 | 64 | Assuming you have `docker`, `docker-compose` and `make` installed, let's go! 65 | 66 | Start by running Postgres and Hasura with: 67 | 68 | docker-compose up hasura 69 | 70 | Once the `hasura` docker service is running, start Hasura's console with: 71 | 72 | make hasura-console 73 | 74 | This console is only for local development use. To use it point your browser to: 75 | 76 | http://localhost:9695 77 | 78 | Any changes you make using the console may become migrations (in the `/hasura/migrations` directory) 79 | or may lead to changes to the metadata (in `/hasura/metadata`). 80 | By checking these folders into version control you will always keep the the schema definitions with your code. 81 | Hasura can help you in running/managing migrations. 82 | 83 | The `/hasura/migrations` and `/hasura/metadata` directories are not empty. 84 | They contain a simple schema modelling `posts` that have `tags` with a many-to-many relationship over a join table. 85 | The `posts` also have a `status` that is modelled with what Hasura calls an "enum table". 86 | 87 | The following command is used to generate the 100% type safe GraphQL client: 88 | 89 | make generate-elm-graphql-client 90 | 91 | The code will be put in `/frontend/src/HasuraClient` which is *not* checked into version control, 92 | as it can be re-generated at any time (and needs to be regenerated on schema changes) using the command above. 93 | 94 | The following command runs development-mode Webpack in the `/frontend` container: 95 | 96 | docker-compose up frontend 97 | 98 | It proxies request for the `hasura` container (`/v1/graphql`) 99 | and watches filesystem on the host for changes in the `/frontend` directory which will be hot-reloaded to the browser on: 100 | 101 | http://localhost:3000 102 | 103 | Production builds are made with: 104 | 105 | make frontend-run-prod 106 | 107 | Which compiles/copies/gzips artifacts to `/frontend/dist/`, from there the production setup may pick it up: 108 | 109 | docker-compose up nginx 110 | 111 | Point your browser to: 112 | 113 | http://localhost:3030 114 | 115 | Now check the response times and rejoice! 116 | 117 | 118 | 119 | ## Moving forward 120 | 121 | Interesting ways to extend this setup? 122 | 123 | * Add a [hasura-backend-plus](https://nhost.github.io/hasura-backend-plus) service and some basic (or not so basic) authentication/authorization system. 124 | * Add React Admin with [ra-data-hasura](https://github.com/hasura/ra-data-hasura) as a better place to manage data than through Hasura's console. 125 | * Add fulltext search with [pgGroonga](https://github.com/pgroonga/docker) and Hasura's [tracked custom queries](https://duckduckgo.com/?q=hasura+fulltext+search) 126 | * Describe how running Elm tests would work in this setup. 127 | * Use the `RemoteData` package. 128 | * Make Webpack regenerate the GraphQL client for us on change/ on production build 129 | * Make sure this works on MacOS and Windows. 130 | * Improve the performance even further 131 | * Possibly with server-side prerendering, for which we have to pick an approach: 132 | * https://discourse.elm-lang.org/t/exploration-for-server-side-rendering/4999/23 133 | * https://github.com/rogeriochaves/spades 134 | * https://github.com/dawehner/elm-static-html-lib 135 | * https://elm-pages.com/blog/static-http/ 136 | * http://pietrograndi.com/server-side-rendering-with-elm/ 137 | 138 | Collaboration is encouraged. Feel free to fork, submit PRs and/or open issues (even if it is to discuss how to improve this setup)! 139 | 140 | 141 | ## Caveats 142 | 143 | * Files on the host filesystem are owned by `root` when created from docker containers. Fix it with: `chown $USER:$USER -R .` 144 | 145 | * For integration with your IDE you may need to run some local tooling (like `node`, `elm` and `elm-format`). 146 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | services: 3 | 4 | nginx: 5 | image: nginx:latest 6 | hostname: nginx 7 | restart: always 8 | ports: 9 | - "3030:80" 10 | depends_on: 11 | - "hasura" 12 | volumes: 13 | - ./frontend/dist:/frontend-dist 14 | - ./nginx/nginx.conf:/etc/nginx/nginx.conf 15 | - ./nginx/error.log:/etc/nginx/error_log.log 16 | 17 | frontend: 18 | image: frontend 19 | build: 20 | context: ./frontend 21 | init: true 22 | ports: 23 | - "3000:3000" 24 | depends_on: 25 | - "hasura" 26 | volumes: 27 | - ./frontend:/frontend:delegated 28 | - node_modules_volume:/frontend/node_modules 29 | 30 | elm-graphql-generator: 31 | image: node:15.14.0-alpine3.13 32 | volumes: 33 | - ./frontend/src/HasuraClient:/output/HasuraClient 34 | depends_on: 35 | - "hasura" 36 | 37 | hasura: 38 | image: graphql-engine:v2.5.0 39 | ports: 40 | - "8080:8080" 41 | - "9695:9695" 42 | - "9693:9693" 43 | depends_on: 44 | - "postgres" 45 | volumes: 46 | - ./hasura:/hasura 47 | - ./hasura/migrations:/hasura-migrations 48 | - ./hasura/metadata:/hasura-metadata 49 | restart: always 50 | init: true # to speed up shutting down 51 | environment: 52 | HASURA_GRAPHQL_DATABASE_URL: postgres://postgres:postgres@postgres:5432/postgres 53 | # disable the regular console, use the CLI console instead (see Makefile) as it creates migrations 54 | HASURA_GRAPHQL_ENABLE_CONSOLE: "false" 55 | # enable debugging mode. It is recommended to disable this in production 56 | HASURA_GRAPHQL_DEV_MODE: "true" 57 | HASURA_GRAPHQL_ENABLED_LOG_TYPES: startup, http-log, webhook-log, websocket-log, query-log 58 | HASURA_GRAPHQL_UNAUTHORIZED_ROLE: "guest" 59 | HASURA_GRAPHQL_ADMIN_SECRET: "adminsecret" 60 | 61 | postgres: 62 | hostname: postgres 63 | build: postgres/. 64 | restart: always 65 | init: true # to speed up shutting down 66 | ports: 67 | - "5432:5432" 68 | volumes: 69 | - ./postgres/db_data:/var/lib/postgresql/data 70 | environment: 71 | POSTGRES_PASSWORD: postgres 72 | depends_on: 73 | - "elasticsearch" 74 | 75 | elasticsearch: 76 | image: docker.elastic.co/elasticsearch/elasticsearch:7.17.2 # only 7.x is supported by ZomboDB as of apr'22 77 | ports: 78 | - "9200:9200" 79 | - "9300:9300" 80 | volumes: 81 | - ./elasticsearch/data:/usr/share/elasticsearch/data 82 | environment: 83 | - discovery.type=single-node 84 | - xpack.security.enabled=false 85 | - "ES_JAVA_OPTS=-Xms512m -Xmx512m" 86 | ulimits: 87 | memlock: 88 | soft: -1 89 | hard: -1 90 | nofile: 91 | soft: 65536 92 | hard: 65536 93 | cap_add: 94 | - IPC_LOCK 95 | 96 | volumes: 97 | node_modules_volume: 98 | -------------------------------------------------------------------------------- /elasticsearch/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cies/low-code-backend-dockered/08d89a84776541eee60a0cd0bc599eceec9614d4/elasticsearch/.gitkeep -------------------------------------------------------------------------------- /frontend/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env" 5 | ] 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /frontend/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # generated Elm GraphQL client library 2 | src/HasuraClient 3 | 4 | # elm-package generated files 5 | elm-stuff/ 6 | 7 | # elm-repl generated files 8 | repl-temp-* 9 | 10 | # nodejs stuff 11 | build/Release 12 | node_modules/ 13 | .npm 14 | yarn.lock 15 | .node_repl_history 16 | 17 | # editor and OS stuff 18 | .DS_Store 19 | .idea 20 | ignore 21 | dist 22 | tests/VerifyExamples 23 | 24 | # logs 25 | logs 26 | *.log 27 | npm-debug.log* 28 | -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:latest 2 | EXPOSE 3000 3 | WORKDIR /frontend 4 | COPY package-lock.json package.json ./ 5 | RUN npm install --no-progress --ignore-optional 6 | RUN chmod -R 777 /home/node /root 7 | CMD npm run dev 8 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | These should be run with node/npm and the elm tool chain installed. 4 | 5 | Or from within the docker container (see `../Makefile`). 6 | 7 | ## Install 8 | 9 | ```sh 10 | npm install 11 | ``` 12 | 13 | ## Develop 14 | 15 | Start with Elm debug tool with either 16 | 17 | ```sh 18 | npm start 19 | ``` 20 | 21 | or 22 | 23 | ```sh 24 | npm start --nodebug 25 | ``` 26 | 27 | the `--nodebug` removes the Elm debug tool (useful when the model becomes too big). 28 | 29 | ## Production builds 30 | 31 | Build production assets (HTML, JS and CSS together) with: 32 | 33 | ```sh 34 | npm run prod 35 | ``` 36 | 37 | ## Static assets 38 | 39 | Just add to `src/assets/` and the production build copies them to `/dist` 40 | 41 | 42 | ## Testing 43 | 44 | Requires you have [installed elm-test globally](https://github.com/elm-community/elm-test#running-tests-locally). 45 | 46 | When you install your dependencies `elm-test init` is run. After that all you need to do to run the tests is: 47 | 48 | ```sh 49 | npm test 50 | ``` 51 | 52 | Take a look at the examples in `tests/` 53 | 54 | If you add dependencies to your main app, then run `elm-test --add-dependencies` 55 | 56 | 57 | ## Elm-analyse 58 | 59 | Elm-analyse is a "tool that allows you to analyse your Elm code, identify deficiencies and apply best practices." 60 | Its built into this starter, just run the following to see how your code is getting on: 61 | 62 | ```sh 63 | npm run analyse 64 | ``` 65 | 66 | ## ES6 67 | 68 | This starter includes [Babel](https://babeljs.io/) so you can directly use ES6 code. 69 | 70 | 71 | ## How it works 72 | 73 | `npm run dev` maps to `webpack-dev-server --hot --colors --port 3000` where 74 | 75 | - `--hot` Enable webpack's Hot Module Replacement feature 76 | - `--port 3000` - use port 3000 instead of default 8000 77 | - inline (default) a script will be inserted in your bundle to take care of reloading, and build messages will appear in the browser console. 78 | - `--colors` should show the colours created in the original Elm errors, but does not (To Fix) 79 | 80 | One alternative is to run `npx webpack-dev-server --hot --colors --host=0.0.0.0 --port 3000` which will enable your dev server to be reached from other computers on your local network 81 | 82 | -------------------------------------------------------------------------------- /frontend/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | "src" 5 | ], 6 | "elm-version": "0.19.1", 7 | "dependencies": { 8 | "direct": { 9 | "dillonkearns/elm-graphql": "5.0.5", 10 | "elm/browser": "1.0.2", 11 | "elm/core": "1.0.4", 12 | "elm/html": "1.0.0", 13 | "elm/http": "2.0.0", 14 | "elm/json": "1.1.3", 15 | "elm/parser": "1.1.0", 16 | "elm/url": "1.0.0", 17 | "hecrj/html-parser": "2.4.0", 18 | "krisajenkins/remotedata": "6.0.1" 19 | }, 20 | "indirect": { 21 | "elm/bytes": "1.0.8", 22 | "elm/file": "1.0.5", 23 | "elm/regex": "1.0.0", 24 | "elm/time": "1.0.0", 25 | "elm/virtual-dom": "1.0.2", 26 | "elm-community/list-extra": "8.3.0", 27 | "lukewestby/elm-string-interpolate": "1.0.4", 28 | "rtfeldman/elm-hex": "1.0.0" 29 | } 30 | }, 31 | "test-dependencies": { 32 | "direct": { 33 | "elm-explorations/test": "1.2.2" 34 | }, 35 | "indirect": { 36 | "elm/random": "1.0.0" 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Cies Breijs", 3 | "name": "elm-hasura-dockered", 4 | "version": "0.0.1", 5 | "description": "Starter kit combining Postgres, Hasura and Elm", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "elm-test", 9 | "start": "npm run dev", 10 | "dev": "webpack-dev-server --host 0.0.0.0 --hot --watch --colors --port 3000", 11 | "build": "webpack", 12 | "prod": "webpack -p", 13 | "serve-prod": "npx serve dist/", 14 | "analyse": "elm-analyse -s -p 3001 -o" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/cies/elm-hasura-dockered.git" 19 | }, 20 | "license": "Proprietary", 21 | "dependencies": { 22 | "@tailwindcss/custom-forms": "^0.2.1", 23 | "imagemin-webpack-plugin": "^2.4.2", 24 | "sass": "^1.32.5", 25 | "serve": "^11.3.2" 26 | }, 27 | "devDependencies": { 28 | "@babel/core": "^7.11.6", 29 | "@babel/preset-env": "^7.11.5", 30 | "autoprefixer": "^9.8.6", 31 | "babel-loader": "^8.1.0", 32 | "clean-webpack-plugin": "^3.0.0", 33 | "closure-webpack-plugin": "^2.3.0", 34 | "compression-webpack-plugin": "^6.1.1", 35 | "copy-webpack-plugin": "^6.1.1", 36 | "css-loader": "^4.3.0", 37 | "elm": "^0.19.1-3", 38 | "elm-analyse": "^0.16.5", 39 | "elm-format": "^0.8.4", 40 | "elm-hot-webpack-loader": "^1.1.7", 41 | "elm-test": "^0.19.1-revision4", 42 | "elm-webpack-loader": "^7.0.1", 43 | "file-loader": "^6.1.0", 44 | "google-closure-compiler": "^20200920.0.0", 45 | "html-webpack-plugin": "^4.5.0", 46 | "mini-css-extract-plugin": "^0.11.2", 47 | "optimize-css-assets-webpack-plugin": "^5.0.4", 48 | "resolve-url-loader": "^3.1.1", 49 | "sass-loader": "^10.0.2", 50 | "style-loader": "^1.2.1", 51 | "url-loader": "^4.1.0", 52 | "webpack": "^4.44.2", 53 | "webpack-cli": "^3.3.12", 54 | "webpack-dev-server": "^3.11.0", 55 | "webpack-merge": "^5.1.4" 56 | }, 57 | "prettier": { 58 | "tabWidth": 4 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /frontend/src/Main.elm: -------------------------------------------------------------------------------- 1 | port module Main exposing (Model, Msg(..), init, main, toJs, update, view) 2 | 3 | import Browser 4 | import Graphql.Http 5 | import Graphql.OptionalArgument as OptionalArgument exposing (OptionalArgument(..)) 6 | import Graphql.SelectionSet as SelectionSet exposing (SelectionSet) 7 | import HasuraClient.Enum.Order_by exposing (Order_by(..)) 8 | import HasuraClient.Enum.Post_status_enum exposing (Post_status_enum) 9 | import HasuraClient.InputObject exposing (buildPosts_order_by) 10 | import HasuraClient.Object 11 | import HasuraClient.Object.Posts 12 | import HasuraClient.Query exposing (PostsOptionalArguments) 13 | import Html as H 14 | import Html.Attributes as HA 15 | import Html.Events exposing (onClick) 16 | 17 | 18 | port toJs : String -> Cmd msg 19 | 20 | 21 | type alias Model = 22 | { counter : Int 23 | , posts : List Post 24 | } 25 | 26 | type alias Post = 27 | { title : String 28 | , body : String 29 | , status : Post_status_enum 30 | } 31 | 32 | 33 | type Msg 34 | = Inc 35 | | TestServer 36 | | OnServerResponse (Result (Graphql.Http.Error (List Post)) (List Post)) 37 | 38 | 39 | update : Msg -> Model -> ( Model, Cmd Msg ) 40 | update message model = 41 | case message of 42 | Inc -> ( { model | counter = model.counter + 1 }, toJs "Inc" ) 43 | 44 | TestServer -> ( model, postsQuery OnServerResponse ) 45 | 46 | OnServerResponse res -> case res of 47 | Ok r -> ( { model | posts = r }, Cmd.none ) 48 | Err _ -> ( { model | posts = [] }, Cmd.none ) 49 | 50 | 51 | -- Writing GraphQL requests with `elm-graphql` is a little tricky at first. 52 | -- But the type system and documentation are there to help. 53 | -- Read more about `elm-graphql` here: 54 | -- https://package.elm-lang.org/packages/dillonkearns/elm-graphql/latest 55 | postsQuery : (Result (Graphql.Http.Error (List Post)) (List Post) -> Msg ) -> Cmd Msg 56 | postsQuery onResponse = 57 | HasuraClient.Query.posts orderPostsById postSelection 58 | |> Graphql.Http.queryRequest "/v1/graphql" 59 | |> Graphql.Http.send onResponse 60 | 61 | -- An `order_by` query argument to: ascending on `id` 62 | orderPostsById : PostsOptionalArguments -> PostsOptionalArguments 63 | orderPostsById args = 64 | { args 65 | | order_by = Present <| 66 | [ buildPosts_order_by (\args2 -> { args2 | id = OptionalArgument.Present Asc }) ] 67 | } 68 | 69 | -- Selects and maps the fields from server-side `posts` to the `Post` record in Elm. 70 | postSelection : SelectionSet Post HasuraClient.Object.Posts 71 | postSelection = 72 | SelectionSet.map3 Post 73 | HasuraClient.Object.Posts.title 74 | HasuraClient.Object.Posts.body 75 | HasuraClient.Object.Posts.status 76 | 77 | 78 | view : Model -> H.Html Msg 79 | view model = 80 | H.div [ HA.class "container" ] 81 | [ H.h1 [] [ H.text "Fully dockered Elm-Hasura starter kit" ] 82 | , H.p [] [ H.text "Click on the buttons below to demonstrate interactivity." ] 83 | , H.div [] 84 | [ H.button 85 | [ onClick Inc ] 86 | [ H.text "+ 1" ] 87 | , H.span [] [ H.text " -> " ] 88 | , H.text <| String.fromInt model.counter 89 | ] 90 | , H.br [] [] 91 | , H.div [] 92 | [ H.button 93 | [ onClick TestServer ] 94 | [ H.text "fire GraphQL query" ] 95 | , H.div [] ( List.map viewPost model.posts ) 96 | ] 97 | , H.p [] [ H.text "Change the source code (elm/scss) and see how it reloads while retaining the state!" ] 98 | ] 99 | 100 | 101 | viewPost : Post -> H.Html Msg 102 | viewPost post = H.div [] 103 | [ H.h3 [] [H.text post.title] 104 | , H.p [] [H.text post.body] 105 | , H.br [] [] 106 | ] 107 | 108 | 109 | init : Int -> ( Model, Cmd Msg ) 110 | init flags = 111 | ( { counter = flags, posts = [] }, Cmd.none ) 112 | 113 | 114 | main : Program Int Model Msg 115 | main = 116 | Browser.document 117 | { init = init 118 | , update = update 119 | , view = 120 | \m -> 121 | { title = "Fully dockered Elm-Hasura starter kit" 122 | , body = [ view m ] 123 | } 124 | , subscriptions = \_ -> Sub.none 125 | } 126 | 127 | -------------------------------------------------------------------------------- /frontend/src/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cies/low-code-backend-dockered/08d89a84776541eee60a0cd0bc599eceec9614d4/frontend/src/assets/images/favicon.ico -------------------------------------------------------------------------------- /frontend/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | elm-hasura-dockered 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require("./styles.scss"); 4 | 5 | const {Elm} = require('./Main'); 6 | var app = Elm.Main.init({flags: 6}); 7 | 8 | app.ports.toJs.subscribe(data => { 9 | console.log(data); 10 | }) 11 | // Use ES2015 syntax and let Babel compile it for you 12 | var testFn = (inp) => { 13 | let a = inp + 1; 14 | return a; 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/styles.scss: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | background-color: #fda; 4 | font-family: sans; 5 | } 6 | -------------------------------------------------------------------------------- /frontend/tests/Example.elm: -------------------------------------------------------------------------------- 1 | module Example exposing (fuzzTest, unitTest, viewTest) 2 | 3 | import Expect exposing (Expectation) 4 | import Fuzz exposing (Fuzzer, int, list, string) 5 | import Main exposing (..) 6 | import Test exposing (..) 7 | import Test.Html.Query as Query 8 | import Test.Html.Selector exposing (tag, text) 9 | 10 | 11 | {-| See 12 | -} 13 | unitTest : Test 14 | unitTest = 15 | describe "simple unit test" 16 | [ test "Inc adds one" <| 17 | \() -> 18 | update Inc (Model 0 "") 19 | |> Tuple.first 20 | |> .counter 21 | |> Expect.equal 1 22 | ] 23 | 24 | 25 | {-| See 26 | -} 27 | fuzzTest : Test 28 | fuzzTest = 29 | describe "simple fuzz test" 30 | [ fuzz int "Inc ALWAYS adds one" <| 31 | \ct -> 32 | update Inc (Model ct "") 33 | |> Tuple.first 34 | |> .counter 35 | |> Expect.equal (ct + 1) 36 | ] 37 | 38 | 39 | {-| see 40 | -} 41 | viewTest : Test 42 | viewTest = 43 | describe "Testing view function" 44 | [ test "Button has the expected text" <| 45 | \() -> 46 | Model 0 "" 47 | |> view 48 | |> Query.fromHtml 49 | |> Query.findAll [ tag "button" ] 50 | |> Query.first 51 | |> Query.has [ text "+ 1" ] 52 | ] 53 | -------------------------------------------------------------------------------- /frontend/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const glob = require("glob"); 3 | const {merge} = require('webpack-merge'); 4 | const {CleanWebpackPlugin} = require("clean-webpack-plugin"); 5 | const CopyWebpackPlugin = require("copy-webpack-plugin"); 6 | const HTMLWebpackPlugin = require("html-webpack-plugin"); 7 | 8 | // Production JS optimization 9 | const ClosurePlugin = require("closure-webpack-plugin"); 10 | 11 | // Production CSS assets - separate, minimised 12 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 13 | const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); 14 | 15 | // Production optimized images 16 | const ImageMinPlugin = require('imagemin-webpack-plugin').default; 17 | 18 | // Production pre-compressed assets 19 | const CompressionWebpackPlugin = require("compression-webpack-plugin"); 20 | 21 | module.exports = () => ({}); 22 | 23 | var MODE = process.env.npm_lifecycle_event === "prod" ? "production" : "development"; 24 | var withDebug = !process.env["npm_config_nodebug"] && MODE === "development"; 25 | // this may help for Yarn users 26 | // var withDebug = !npmParams.includes("--nodebug"); 27 | console.log( 28 | "\x1b[36m%s\x1b[0m", 29 | `** elm-webpack-starter: mode "${MODE}", withDebug: ${withDebug}\n` 30 | ); 31 | 32 | var common = { 33 | mode: MODE, 34 | entry: "./src/index.js", 35 | output: { 36 | path: path.join(__dirname, "dist"), 37 | publicPath: "/", 38 | // FIXME webpack -p automatically adds hash when building for production 39 | filename: MODE === "production" ? "[name]-[hash].js" : "index.js" 40 | }, 41 | plugins: [ 42 | new HTMLWebpackPlugin({ 43 | // Use this template to get basic responsive meta tags 44 | template: "src/index.html", 45 | favicon: "src/assets/images/favicon.ico", 46 | // inject details of output file at end of body 47 | inject: "body" 48 | }) 49 | ], 50 | resolve: { 51 | modules: [path.join(__dirname, "src"), "node_modules"], 52 | extensions: [".js", ".elm", ".scss", ".png"] 53 | }, 54 | module: { 55 | rules: [ 56 | { 57 | test: /\.js$/, 58 | exclude: /node_modules/, 59 | use: { loader: "babel-loader" } 60 | }, 61 | { 62 | test: /\.(sa|sc|c)ss$/, 63 | exclude: [/elm-stuff/, /node_modules/], 64 | // see https://github.com/webpack-contrib/css-loader#url 65 | use: ["style-loader", "css-loader?url=false", "sass-loader"] 66 | }, 67 | { 68 | test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/, 69 | exclude: [/elm-stuff/, /node_modules/], 70 | use: { 71 | loader: "url-loader", 72 | options: { 73 | limit: 10000, 74 | mimetype: "application/font-woff" 75 | } 76 | } 77 | }, 78 | { 79 | test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, 80 | exclude: [/elm-stuff/, /node_modules/], 81 | loader: "file-loader" 82 | }, 83 | { 84 | test: /\.(jpe?g|png|gif|svg)$/i, 85 | exclude: [/elm-stuff/, /node_modules/], 86 | loader: "file-loader" 87 | } 88 | ] 89 | } 90 | }; 91 | 92 | if (MODE === "development") { 93 | module.exports = merge(common, { 94 | optimization: { 95 | // Prevents compilation errors causing the hot loader to lose state 96 | noEmitOnErrors: true 97 | }, 98 | module: { 99 | rules: [ 100 | { 101 | test: /\.elm$/, 102 | exclude: [/elm-stuff/, /node_modules/], 103 | use: [ 104 | { loader: "elm-hot-webpack-loader" }, 105 | { 106 | loader: "elm-webpack-loader", 107 | options: { 108 | // add Elm's debug overlay to output 109 | debug: withDebug 110 | } 111 | } 112 | ] 113 | } 114 | ] 115 | }, 116 | devServer: { 117 | inline: true, 118 | stats: "errors-only", 119 | contentBase: path.join(__dirname, "src/assets"), 120 | historyApiFallback: true, 121 | // feel free to delete this section if you don't need anything like this 122 | before(app) { 123 | // on port 3000 124 | app.get("/test", function (req, res) { 125 | res.json({result: "OK"}); 126 | }); 127 | }, 128 | // proxy requests to meilisearch, so it all runs from the same domain 129 | proxy: { 130 | "/v1/graphql": { 131 | target: "http://hasura:8080" 132 | } 133 | } 134 | } 135 | }); 136 | } 137 | 138 | 139 | 140 | if (MODE === "production") { 141 | module.exports = merge(common, { 142 | optimization: { 143 | splitChunks: { 144 | cacheGroups: { 145 | styles: { 146 | name: 'styles', 147 | test: /\.css$/, 148 | chunks: 'all', 149 | enforce: true 150 | } 151 | } 152 | }, 153 | minimizer: [ 154 | new ClosurePlugin( 155 | { mode: "STANDARD" }, 156 | { 157 | // compiler flags here 158 | // 159 | // for debugging help, try these: 160 | // 161 | // formatting: 'PRETTY_PRINT', 162 | // debug: true 163 | // renaming: false 164 | } 165 | ), 166 | new OptimizeCSSAssetsPlugin({}) 167 | ] 168 | }, 169 | plugins: [ 170 | // Delete everything from output-path (/dist) and report to user 171 | new CleanWebpackPlugin({ 172 | root: __dirname, 173 | exclude: [], 174 | verbose: true, 175 | dry: false 176 | }), 177 | 178 | // Copy static assets 179 | new CopyWebpackPlugin({ 180 | patterns: [ 181 | { from: "src/assets" } 182 | ] 183 | }), 184 | 185 | new MiniCssExtractPlugin({ 186 | // Options similar to the same options in webpackOptions.output 187 | // both options are optional 188 | filename: "[name]-[hash].css" 189 | }), 190 | 191 | new ImageMinPlugin({ 192 | test: /\.(jpe?g|png|gif|svg)$/i, 193 | cache: true, 194 | }), 195 | 196 | new OptimizeCSSAssetsPlugin(), 197 | 198 | new CompressionWebpackPlugin() 199 | ], 200 | module: { 201 | rules: [ 202 | { 203 | test: /\.elm$/, 204 | exclude: [/elm-stuff/, /node_modules/], 205 | use: { 206 | loader: "elm-webpack-loader", 207 | options: { optimize: true } 208 | } 209 | }, 210 | { 211 | test: /\.(sa|sc|c)ss$/, 212 | exclude: [/elm-stuff/, /node_modules/], 213 | use: [ 214 | MiniCssExtractPlugin.loader, 215 | "css-loader?url=false", 216 | "sass-loader" 217 | ] 218 | } 219 | ] 220 | } 221 | }); 222 | } 223 | -------------------------------------------------------------------------------- /hasura/config.yaml: -------------------------------------------------------------------------------- 1 | version: 3 2 | endpoint: http://localhost:8080 3 | metadata_directory: metadata 4 | actions: 5 | kind: synchronous 6 | handler_webhook_baseurl: http://localhost:3000 7 | -------------------------------------------------------------------------------- /hasura/metadata/actions.graphql: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /hasura/metadata/actions.yaml: -------------------------------------------------------------------------------- 1 | actions: [] 2 | custom_types: 3 | enums: [] 4 | input_objects: [] 5 | objects: [] 6 | scalars: [] 7 | -------------------------------------------------------------------------------- /hasura/metadata/allow_list.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /hasura/metadata/cron_triggers.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /hasura/metadata/databases/databases.yaml: -------------------------------------------------------------------------------- 1 | - name: default 2 | kind: postgres 3 | configuration: 4 | connection_info: 5 | database_url: 6 | from_env: HASURA_GRAPHQL_DATABASE_URL 7 | pool_settings: {} 8 | tables: "!include default/tables/tables.yaml" 9 | functions: "!include default/functions/functions.yaml" 10 | -------------------------------------------------------------------------------- /hasura/metadata/databases/default/functions/functions.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /hasura/metadata/databases/default/tables/public_post_status.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | name: post_status 3 | schema: public 4 | is_enum: true 5 | array_relationships: 6 | - name: posts 7 | using: 8 | foreign_key_constraint_on: 9 | column: status 10 | table: 11 | name: posts 12 | schema: public 13 | select_permissions: 14 | - permission: 15 | columns: 16 | - value 17 | - comment 18 | filter: {} 19 | role: guest 20 | -------------------------------------------------------------------------------- /hasura/metadata/databases/default/tables/public_posts.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | name: posts 3 | schema: public 4 | object_relationships: 5 | - name: post_status 6 | using: 7 | foreign_key_constraint_on: status 8 | array_relationships: 9 | - name: posts_tags 10 | using: 11 | foreign_key_constraint_on: 12 | column: post_id 13 | table: 14 | name: posts_tags 15 | schema: public 16 | select_permissions: 17 | - permission: 18 | columns: 19 | - id 20 | - created_at 21 | - updated_at 22 | - title 23 | - body 24 | - status 25 | filter: {} 26 | role: guest 27 | -------------------------------------------------------------------------------- /hasura/metadata/databases/default/tables/public_posts_tags.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | name: posts_tags 3 | schema: public 4 | object_relationships: 5 | - name: post 6 | using: 7 | foreign_key_constraint_on: post_id 8 | - name: tag 9 | using: 10 | foreign_key_constraint_on: tag_id 11 | select_permissions: 12 | - permission: 13 | columns: 14 | - post_id 15 | - tag_id 16 | filter: {} 17 | role: guest 18 | -------------------------------------------------------------------------------- /hasura/metadata/databases/default/tables/public_tags.yaml: -------------------------------------------------------------------------------- 1 | table: 2 | name: tags 3 | schema: public 4 | array_relationships: 5 | - name: posts_tags 6 | using: 7 | foreign_key_constraint_on: 8 | column: tag_id 9 | table: 10 | name: posts_tags 11 | schema: public 12 | select_permissions: 13 | - permission: 14 | columns: 15 | - id 16 | - name 17 | - comment 18 | filter: {} 19 | role: guest 20 | -------------------------------------------------------------------------------- /hasura/metadata/databases/default/tables/tables.yaml: -------------------------------------------------------------------------------- 1 | - "!include public_post_status.yaml" 2 | - "!include public_posts.yaml" 3 | - "!include public_posts_tags.yaml" 4 | - "!include public_tags.yaml" 5 | -------------------------------------------------------------------------------- /hasura/metadata/inherited_roles.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /hasura/metadata/query_collections.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /hasura/metadata/remote_schemas.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /hasura/metadata/rest_endpoints.yaml: -------------------------------------------------------------------------------- 1 | [] 2 | -------------------------------------------------------------------------------- /hasura/metadata/version.yaml: -------------------------------------------------------------------------------- 1 | version: 3 2 | -------------------------------------------------------------------------------- /hasura/migrations/default/1620166477942_create_table_public_posts/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE "public"."posts"; 2 | -------------------------------------------------------------------------------- /hasura/migrations/default/1620166477942_create_table_public_posts/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "public"."posts" ("id" serial NOT NULL, "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "title" text NOT NULL, "body" text NOT NULL, PRIMARY KEY ("id") ); 2 | CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"() 3 | RETURNS TRIGGER AS $$ 4 | DECLARE 5 | _new record; 6 | BEGIN 7 | _new := NEW; 8 | _new."updated_at" = NOW(); 9 | RETURN _new; 10 | END; 11 | $$ LANGUAGE plpgsql; 12 | CREATE TRIGGER "set_public_posts_updated_at" 13 | BEFORE UPDATE ON "public"."posts" 14 | FOR EACH ROW 15 | EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"(); 16 | COMMENT ON TRIGGER "set_public_posts_updated_at" ON "public"."posts" 17 | IS 'trigger to set value of column "updated_at" to current timestamp on row update'; 18 | -------------------------------------------------------------------------------- /hasura/migrations/default/1620166597095_create_table_public_tags/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE "public"."tags"; 2 | -------------------------------------------------------------------------------- /hasura/migrations/default/1620166597095_create_table_public_tags/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "public"."tags" ("id" serial NOT NULL, "name" text NOT NULL, "comment" text, PRIMARY KEY ("id") ); 2 | -------------------------------------------------------------------------------- /hasura/migrations/default/1620166708080_create_table_public_posts_tags/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE "public"."posts_tags"; 2 | -------------------------------------------------------------------------------- /hasura/migrations/default/1620166708080_create_table_public_posts_tags/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "public"."posts_tags" ("post_id" integer NOT NULL, "tag_id" integer NOT NULL, PRIMARY KEY ("post_id","tag_id") , FOREIGN KEY ("post_id") REFERENCES "public"."posts"("id") ON UPDATE restrict ON DELETE restrict, FOREIGN KEY ("tag_id") REFERENCES "public"."tags"("id") ON UPDATE restrict ON DELETE restrict); 2 | -------------------------------------------------------------------------------- /hasura/migrations/default/1620166889801_create_table_public_post_status/down.sql: -------------------------------------------------------------------------------- 1 | DROP TABLE "public"."post_status"; 2 | -------------------------------------------------------------------------------- /hasura/migrations/default/1620166889801_create_table_public_post_status/up.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE "public"."post_status" ("value" text NOT NULL, "comment" text NOT NULL, PRIMARY KEY ("value") ); 2 | -------------------------------------------------------------------------------- /hasura/migrations/default/1620166942292_insert_into_public_post_status/down.sql: -------------------------------------------------------------------------------- 1 | DELETE FROM "public"."post_status" WHERE "value" = 'draft'; 2 | -------------------------------------------------------------------------------- /hasura/migrations/default/1620166942292_insert_into_public_post_status/up.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO "public"."post_status"("value", "comment") VALUES ('draft', 'may be published in the future'); 2 | -------------------------------------------------------------------------------- /hasura/migrations/default/1620166976476_insert_into_public_post_status/down.sql: -------------------------------------------------------------------------------- 1 | DELETE FROM "public"."post_status" WHERE "value" = 'published'; 2 | -------------------------------------------------------------------------------- /hasura/migrations/default/1620166976476_insert_into_public_post_status/up.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO "public"."post_status"("value", "comment") VALUES ('published', 'currently available to the public'); 2 | -------------------------------------------------------------------------------- /hasura/migrations/default/1620167041972_insert_into_public_post_status/down.sql: -------------------------------------------------------------------------------- 1 | DELETE FROM "public"."post_status" WHERE "value" = 'retracted'; 2 | -------------------------------------------------------------------------------- /hasura/migrations/default/1620167041972_insert_into_public_post_status/up.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO "public"."post_status"("value", "comment") VALUES ('retracted', 'no longer available'); 2 | -------------------------------------------------------------------------------- /hasura/migrations/default/1620167117091_alter_table_public_posts_add_column_status/down.sql: -------------------------------------------------------------------------------- 1 | -- Could not auto-generate a down migration. 2 | -- Please write an appropriate down migration for the SQL below: 3 | -- alter table "public"."posts" add column "status" text 4 | not null; 5 | -------------------------------------------------------------------------------- /hasura/migrations/default/1620167117091_alter_table_public_posts_add_column_status/up.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."posts" add column "status" text 2 | not null; 3 | -------------------------------------------------------------------------------- /hasura/migrations/default/1620167136786_set_fk_public_posts_status/down.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."posts" drop constraint "posts_status_fkey"; 2 | -------------------------------------------------------------------------------- /hasura/migrations/default/1620167136786_set_fk_public_posts_status/up.sql: -------------------------------------------------------------------------------- 1 | alter table "public"."posts" 2 | add constraint "posts_status_fkey" 3 | foreign key ("status") 4 | references "public"."post_status" 5 | ("value") on update restrict on delete restrict; 6 | -------------------------------------------------------------------------------- /hasura/migrations/default/1620242455446_insert_into_public_posts/down.sql: -------------------------------------------------------------------------------- 1 | DELETE FROM "public"."posts" WHERE "id" = 1; 2 | -------------------------------------------------------------------------------- /hasura/migrations/default/1620242455446_insert_into_public_posts/up.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO "public"."posts"("id", "created_at", "updated_at", "title", "body", "status") VALUES (1, '2021-05-05T19:20:55.304589+00:00', '2021-05-05T19:20:55.304589+00:00', 'Boring title of first test post', 'The mail or post is a system for physically transporting postcards, letters, and parcels.[1] A postal service can be private or public, though many governments place restrictions on private systems. Since the mid-19th century, national postal systems have generally been established as a government monopoly, with a fee on the article prepaid. Proof of payment is usually in the form of an adhesive postage stamp, but a postage meter is also used for bulk mailing. With the advent of email, the retronym "snail mail" was coined.', 'published'); 2 | -------------------------------------------------------------------------------- /hasura/migrations/default/1620242497114_insert_into_public_posts/down.sql: -------------------------------------------------------------------------------- 1 | DELETE FROM "public"."posts" WHERE "id" = 2; 2 | -------------------------------------------------------------------------------- /hasura/migrations/default/1620242497114_insert_into_public_posts/up.sql: -------------------------------------------------------------------------------- 1 | INSERT INTO "public"."posts"("id", "created_at", "updated_at", "title", "body", "status") VALUES (2, '2021-05-05T19:21:37.00081+00:00', '2021-05-05T19:21:37.00081+00:00', 'Boring title of second test post', 'The practice of communication by written documents carried by an intermediary from one person or place to another almost certainly dates back nearly to the invention of writing. However, the development of formal postal systems occurred much later. ', 'published'); 2 | -------------------------------------------------------------------------------- /nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | worker_processes auto; 2 | 3 | events { 4 | use epoll; 5 | worker_connections 4000; 6 | } 7 | 8 | http { 9 | server_tokens off; 10 | charset utf-8; 11 | include mime.types; 12 | 13 | sendfile on; 14 | open_file_cache max=200000 inactive=20s; 15 | open_file_cache_valid 30s; 16 | open_file_cache_min_uses 2; 17 | open_file_cache_errors on; 18 | 19 | server { 20 | server_name _; 21 | listen 80 default_server; 22 | listen [::]:80 default_server; 23 | gzip on; 24 | 25 | location /v1/graphql/ { 26 | proxy_pass http://hasura:8080; 27 | proxy_redirect off; 28 | proxy_set_header Host $http_host; 29 | proxy_set_header X-Real-IP $remote_addr; 30 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 31 | 32 | proxy_hide_header Set-Cookie; # safety but not needed as hasura does not set cookies 33 | proxy_ignore_headers Set-Cookie; 34 | proxy_ignore_headers Cache-Control; 35 | add_header X-Cache-Status $upstream_cache_status; # handy instrumentation 36 | } 37 | 38 | location / { 39 | gzip_static on; 40 | root /frontend-dist/; 41 | index index.html; 42 | add_header Cache-Control public; # cache in proxies and browsers (new releases have a different hash) 43 | expires 1d; 44 | location /index.html { 45 | expires off; # we need index.html to be fresh, as it specifies the hashes of the assets 46 | } 47 | } 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /postgres/Dockerfile: -------------------------------------------------------------------------------- 1 | # Builds the ZomboDB extension for Postgres 13 running on Debian Buster 2 | # This requires lots of tools. 3 | 4 | FROM debian:bullseye 5 | 6 | # Version pinning 7 | # * ZDB_BRANCH can also be a tag 8 | # * make sure to update the versions in the target container steps by hand! 9 | ARG ZDB_VERSION=3000.0.11 10 | ARG ZDB_BRANCH=v${ZDB_VERSION} 11 | ARG PG_VERSION=14 12 | ARG CARGO_PGX_VERSION=0.4.2 13 | 14 | ARG USER=docker 15 | ARG UID=1000 16 | ARG GID=1000 17 | 18 | RUN useradd -m ${USER} --uid=${UID} 19 | 20 | ARG DEBIAN_FRONTEND=noninteractive 21 | RUN apt-get update -y -qq --fix-missing 22 | RUN apt-get install -y wget gnupg 23 | RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ bullseye-pgdg main" >> /etc/apt/sources.list.d/pgdg.list 24 | RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - 25 | RUN apt update 26 | RUN apt-get update -y --fix-missing 27 | RUN apt-get install -y curl 28 | 29 | RUN apt-get install -y gcc make build-essential libz-dev zlib1g-dev strace libssl-dev pkg-config git 30 | RUN apt-get install -y postgresql-${PG_VERSION} postgresql-server-dev-${PG_VERSION} 31 | RUN apt-get install -y ruby ruby-dev rubygems build-essential 32 | RUN gem install --no-document fpm 33 | 34 | RUN mkdir -p /usr/lib/postgresql/${PG_VERSION}/lib 35 | RUN chown 1000:1000 /usr/lib/postgresql/${PG_VERSION}/lib 36 | RUN mkdir -p /usr/share/postgresql/${PG_VERSION}/extension 37 | RUN chown 1000:1000 /usr/share/postgresql/${PG_VERSION}/extension 38 | 39 | USER ${UID}:${GID} 40 | WORKDIR /home/${USER} 41 | RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | bash -s -- -y 42 | ENV PATH="/home/${USER}/.cargo/bin:${PATH}" 43 | 44 | RUN cargo install cargo-pgx --version ${CARGO_PGX_VERSION} 45 | RUN cargo pgx init --pg${PG_VERSION}=/usr/lib/postgresql/${PG_VERSION}/bin/pg_config 46 | 47 | RUN git clone --branch=${ZDB_BRANCH} --depth=1 https://github.com/zombodb/zombodb.git 48 | WORKDIR zombodb 49 | RUN cargo pgx install --release 50 | 51 | 52 | # Then copy over the extension to the official "Postgres 13 on Debian Buster" container 53 | # We dont need thoses GBs of tooling to actually use the extension. 54 | 55 | FROM postgres:14.2-bullseye 56 | COPY --from=0 /usr/lib/postgresql/14/lib/zombodb.so /usr/lib/postgresql/14/lib/ 57 | COPY --from=0 /usr/share/postgresql/14/extension/zombodb.control /usr/share/postgresql/14/extension/ 58 | COPY --from=0 /usr/share/postgresql/14/extension/zombodb--3000.0.11.sql /usr/share/postgresql/14/extension/ 59 | 60 | -------------------------------------------------------------------------------- /test.sql: -------------------------------------------------------------------------------- 1 | CREATE EXTENSION zombodb; 2 | 3 | CREATE TABLE analyzers_test ( 4 | pkey serial8 NOT NULL PRIMARY KEY, 5 | arabic zdb.arabic, 6 | armenian zdb.armenian, 7 | -- basque zdb.basque, -- no texts of this language 8 | -- brazilian zdb.brazilian, -- no texts of this language 9 | bulgarian zdb.bulgarian, 10 | catalan zdb.catalan, 11 | chinese zdb.chinese, 12 | cjk zdb.cjk, 13 | czech zdb.czech, 14 | danish zdb.danish, 15 | dutch zdb.dutch, 16 | english zdb.english, 17 | finnish zdb.finnish, 18 | french zdb.french, 19 | -- galician zdb.galician, -- no texts of this language 20 | german zdb.german, 21 | -- greek zdb.greek, -- no texts of this language 22 | hindi zdb.hindi, 23 | hungarian zdb.hungarian, 24 | indonesian zdb.indonesian, 25 | irish zdb.irish, 26 | italian zdb.italian, 27 | -- latvian zdb.latvian, -- no texts of this language 28 | norwegian zdb.norwegian, 29 | persian zdb.persian, 30 | portuguese zdb.portuguese, 31 | romanian zdb.romanian, 32 | russian zdb.russian, 33 | -- sorani zdb.sorani, -- no texts of this language 34 | spanish zdb.spanish, 35 | swedish zdb.swedish, 36 | turkish zdb.turkish, 37 | thai zdb.thai, 38 | standard zdb.zdb_standard, 39 | whitespace zdb.whitespace 40 | ); 41 | 42 | 43 | INSERT INTO analyzers_test VALUES ( 44 | DEFAULT, 45 | 'هذا اختبار' 46 | , 'սա փորձություն է' 47 | , 'това е тест' 48 | , 'Això és un examen' 49 | , '这是一个测试' 50 | , 'これはテストです' -- cjk (japanese) 51 | , 'toto je test' 52 | , 'dette er en test' 53 | , 'dit is een test' 54 | , 'this is a test' -- english 55 | , 'Tämä on koe' 56 | , 'c''est un test' 57 | , 'das ist ein Test' -- german 58 | , 'यह एक परीक्षण है' -- hindi 59 | , 'ez egy teszt' 60 | , 'ini adalah sebuah ujian' 61 | , 'tá sé seo le tástáil' 62 | , 'questa è una prova' 63 | , 'dette er en test' -- norwegian 64 | , 'این یک امتحان است' -- persian 65 | , 'isso é um teste' 66 | , 'acesta este un test' 67 | , 'Это тест' -- russian 68 | , 'esto es un exámen' 69 | , 'detta är ett prov' 70 | , 'bu bir test' -- turkish 71 | , 'นี่คือการทดสอบ' 72 | , 'this is a test' -- zdb_standard 73 | , 'this is a test' -- whitespace 74 | ); 75 | 76 | INSERT INTO analyzers_test VALUES ( 77 | default 78 | , NULL 79 | , NULL 80 | , NULL -- bg 81 | , NULL 82 | , NULL 83 | , NULL -- cjk (japanese) 84 | , NULL 85 | , NULL 86 | , NULL 87 | , 'the quick brown fox jumped over the lazy dog' -- english 88 | , NULL 89 | , NULL 90 | , NULL -- german 91 | , NULL -- hindi 92 | , NULL 93 | , NULL 94 | , NULL 95 | , NULL 96 | , NULL -- norwegian 97 | , null -- persian 98 | , NULL 99 | , NULL 100 | , NULL -- russian 101 | , NULL 102 | , NULL 103 | , NULL -- turkish 104 | , NULL 105 | , NULL -- zdb_standard 106 | , NULL -- whitespace 107 | ); 108 | 109 | INSERT INTO analyzers_test VALUES ( 110 | default 111 | , NULL 112 | , NULL 113 | , NULL -- bg 114 | , NULL 115 | , NULL 116 | , NULL -- cjk (japanese) 117 | , NULL 118 | , NULL 119 | , 'hier probeer ik wat nederlandse tekst neer te knallen, lopen, werken, zoeken, gaan, scholen, discos, verkennen' 120 | , null -- english 121 | , NULL 122 | , NULL 123 | , NULL -- german 124 | , NULL -- hindi 125 | , NULL 126 | , NULL 127 | , NULL 128 | , NULL 129 | , NULL -- norwegian 130 | , null -- persian 131 | , NULL 132 | , NULL 133 | , NULL -- russian 134 | , NULL 135 | , NULL 136 | , NULL -- turkish 137 | , NULL 138 | , NULL -- zdb_standard 139 | , NULL -- whitespace 140 | ); 141 | 142 | INSERT INTO analyzers_test VALUES (default, null , null , NULL , null , NULL, NULL, NULL, NULL, NULL 143 | , null, NULL, NULL, NULL, NULL, NULl, NULL, NULL, NULl, NULL , null, NULL, NULL, NULL, NULL , NUll, NULL, NULL 144 | , NULL -- zdb_standard 145 | , 'hier probeer ik alleen wat nieuwe dingen om te kijken welke queries werken' ); 146 | 147 | INSERT INTO analyzers_test VALUES (default, null , null , NULL , null , NULL, NULL, NULL, NULL, NULL 148 | , null, NULL, NULL, NULL, NULL, NULl, NULL, NULL, NULl, NULL , null, NULL, NULL, NULL, NULL , NUll, NULL, NULL 149 | , 'hier probeer ik alleen wat nieuwe dingen om te kijken welke queries werken' 150 | , NULL -- whitespace 151 | ); 152 | 153 | CREATE INDEX idxanalyzers_test ON analyzers_test USING zombodb ((analyzers_test)) WITH (url='http://elasticsearch:9200/');; 154 | 155 | SELECT 156 | attname, 157 | (SELECT count(*) > 0 FROM analyzers_test WHERE analyzers_test ==> term(attname, 'test')) found 158 | FROM pgattribute 159 | WHERE attrelid = 'analyzers_test'::regclass AND attnum >= 1 AND attname <> 'pkey' 160 | ORDER BY attnum; 161 | 162 | SELECT 163 | attname, 164 | q 165 | FROM analyzers_test, (SELECT 166 | attname, 167 | (attname || ':' || ((SELECT row_to_json(analyzers_test) 168 | FROM analyzers_test) -> attname))::text q 169 | FROM pg_attribute 170 | WHERE attrelid = 'analyzers_test'::regclass AND attnum >= 1 AND attname <> 'pkey') x 171 | WHERE analyzers_test ==> q 172 | ORDER BY attname; 173 | 174 | SELECT * FROM analyzers_test WHERE analyzers_test ==> 'dinge~4'; 175 | 176 | 177 | 178 | 179 | DROP TABLE analyzers_test; 180 | 181 | --------------------------------------------------------------------------------