From 4441521994559d9dfc41a737af2c91c3fe93d8d3 Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Tue, 6 Nov 2018 10:30:27 +0100 Subject: [PATCH] Move to GraphQL Signed-off-by: Thomas Citharel --- .gitignore | 4 +- .gitlab-ci.yml | 5 +- config/config.exs | 8 +- docker/tests/Dockerfile | 5 + js/.eslintrc.js | 8 +- js/.postcssrc.js | 6 +- js/Makefile | 58 + js/get_union_json.js | 38 + js/package-lock.json | 2180 ++++++++++++++++- js/package.json | 18 +- js/src/App.vue | 48 +- js/src/actions/login.js | 5 - js/src/api/eventFetch.js | 29 - js/src/auth/index.js | 120 - js/src/components/Account/Account.vue | 366 ++- js/src/components/Account/Identities.vue | 35 +- js/src/components/Account/Login.vue | 151 +- js/src/components/Account/PasswordReset.vue | 4 +- js/src/components/Account/Register.vue | 215 +- js/src/components/Account/RegisterAvatar.vue | 4 +- .../components/Account/ResendConfirmation.vue | 2 - .../components/Account/SendPasswordReset.vue | 6 +- js/src/components/Account/Validate.vue | 38 +- js/src/components/Category/Create.vue | 109 +- js/src/components/Category/List.vue | 67 +- js/src/components/Event/Create.vue | 246 +- js/src/components/Event/Edit.vue | 46 +- js/src/components/Event/Event.vue | 432 ++-- js/src/components/Event/EventList.vue | 147 +- js/src/components/Group/Create.vue | 107 +- js/src/components/Group/Group.vue | 68 +- js/src/components/Group/GroupList.vue | 78 +- js/src/components/Home.vue | 73 +- js/src/components/Location.vue | 40 +- js/src/components/NavBar.vue | 198 +- js/src/constants.js | 3 + js/src/graphql/actor.js | 39 + js/src/graphql/auth.js | 16 + js/src/graphql/category.js | 29 + js/src/graphql/event.js | 109 + js/src/graphql/fragmentTypes.json | 1 + js/src/graphql/search.js | 17 + js/src/graphql/upload.js | 10 + js/src/graphql/user.js | 28 + js/src/i18n/en.js | 15 - js/src/i18n/fr.js | 20 - js/src/i18n/index.js | 6 - js/src/i18n/locale/en_US/LC_MESSAGES/app.po | 30 + js/src/i18n/locale/fr_FR/LC_MESSAGES/app.po | 30 + js/src/i18n/locale/fr_FR/LC_MESSAGES/app.po~ | 30 + js/src/i18n/translations.json | 1 + js/src/main.js | 47 +- js/src/store/index.js | 44 - js/src/store/mutation-types.js | 4 - js/src/vue-apollo.js | 135 + js/tests/unit/.eslintrc.js | 8 +- lib/mix/tasks/create_bot.ex | 2 +- lib/mobilizon/actors/actors.ex | 134 +- lib/mobilizon/actors/service/activation.ex | 12 +- .../actors/service/reset_password.ex | 31 +- lib/mobilizon/actors/service/tools.ex | 27 + lib/mobilizon/actors/user.ex | 18 +- lib/mobilizon/email/user.ex | 4 +- lib/mobilizon/events/category.ex | 6 +- lib/mobilizon/events/event.ex | 1 - lib/mobilizon/events/events.ex | 93 +- lib/mobilizon_web/auth_pipeline.ex | 6 +- lib/mobilizon_web/context.ex | 20 + .../controllers/actor_controller.ex | 79 - .../controllers/address_controller.ex | 78 - .../controllers/bot_controller.ex | 48 - .../controllers/category_controller.ex | 46 - .../controllers/comment_controller.ex | 43 - .../controllers/event_controller.ex | 125 - .../controllers/event_request_controller.ex | 52 - .../controllers/follower_controller.ex | 43 - .../controllers/group_controller.ex | 53 - .../controllers/inboxes_controller.ex | 6 - ..._controller.ex => node_info_controller.ex} | 4 +- .../controllers/outboxes_controller.ex | 10 - .../controllers/participant_controller.ex | 18 - .../controllers/search_controller.ex | 23 - .../controllers/session_controller.ex | 56 - .../controllers/tag_controller.ex | 46 - .../controllers/track_controller.ex | 46 - .../controllers/user_controller.ex | 148 -- .../controllers/user_session_controller.ex | 41 - .../controllers/web_finger_controller.ex | 4 + lib/mobilizon_web/endpoint.ex | 8 + lib/mobilizon_web/resolvers/actor.ex | 26 + lib/mobilizon_web/resolvers/category.ex | 37 + lib/mobilizon_web/resolvers/event.ex | 54 + lib/mobilizon_web/resolvers/upload.ex | 2 + lib/mobilizon_web/resolvers/user.ex | 118 + lib/mobilizon_web/router.ex | 90 +- lib/mobilizon_web/schema.ex | 318 +++ lib/mobilizon_web/schema/custom/UUID4.ex | 36 + lib/mobilizon_web/upload_plug.ex | 15 + lib/mobilizon_web/uploaders/avatar.ex | 50 + lib/mobilizon_web/uploaders/category.ex | 49 + lib/mobilizon_web/views/actor_view.ex | 68 - lib/mobilizon_web/views/address_view.ex | 42 - lib/mobilizon_web/views/bot_view.ex | 16 - lib/mobilizon_web/views/category_view.ex | 24 - lib/mobilizon_web/views/comment_view.ex | 16 - lib/mobilizon_web/views/event_view.ex | 68 - lib/mobilizon_web/views/participant_view.ex | 19 - lib/mobilizon_web/views/search_view.ex | 16 - lib/mobilizon_web/views/session_view.ex | 29 - lib/mobilizon_web/views/tag_view.ex | 19 - lib/mobilizon_web/views/track_view.ex | 19 - lib/mobilizon_web/views/user_session_view.ex | 10 - lib/mobilizon_web/views/user_view.ex | 55 - mix.exs | 9 + mix.lock | 10 + ...021161324_add_default_actor_user_field.exs | 15 + priv/repo/seeds.exs | 2 + .../controllers/actor_controller_test.exs | 190 -- .../controllers/address_controller_test.exs | 144 -- .../controllers/bot_controller_test.exs | 94 - .../controllers/category_controller_test.exs | 105 - .../controllers/comment_controller_test.exs | 96 - .../controllers/event_controller_test.exs | 182 -- .../controllers/follower_controller_test.exs | 83 - .../controllers/session_controller_test.exs | 147 -- .../controllers/tag_controller_test.exs | 85 - .../controllers/track_controller_test.exs | 116 - .../controllers/user_controller_test.exs | 223 -- .../user_session_controller_test.exs | 1 - test/fixtures/category_picture.png | Bin 0 -> 10097 bytes test/fixtures/category_picture_updated.png | Bin 0 -> 10489 bytes .../actors/actors_test.exs | 49 +- .../addresses/addresses_test.exs | 0 .../events/events_test.exs | 30 +- .../service/activitypub/activitypub_test.exs | 0 .../service/web_finger/web_finger_test.exs | 0 .../activity_pub_controller_test.exs | 0 .../controllers/nodeinfo_controller_test.exs | 8 +- .../controllers/page_controller_test.exs | 0 .../resolvers/actor_resolver_test.exs | 78 + .../resolvers/category_resolver_test.exs | 60 + .../resolvers/event_resolver_test.exs | 167 ++ .../resolvers/user_resolver_test.exs | 239 ++ .../views/error_view_test.exs | 0 .../views/layout_view_test.exs | 0 .../views/page_view_test.exs | 0 test/support/abinthe_helpers.ex | 17 + test/support/factory.ex | 7 +- uploads/.gitkeep | 0 149 files changed, 5605 insertions(+), 4665 deletions(-) create mode 100644 docker/tests/Dockerfile create mode 100644 js/Makefile create mode 100644 js/get_union_json.js delete mode 100644 js/src/actions/login.js delete mode 100644 js/src/api/eventFetch.js delete mode 100644 js/src/auth/index.js create mode 100644 js/src/constants.js create mode 100644 js/src/graphql/actor.js create mode 100644 js/src/graphql/auth.js create mode 100644 js/src/graphql/category.js create mode 100644 js/src/graphql/event.js create mode 100644 js/src/graphql/fragmentTypes.json create mode 100644 js/src/graphql/search.js create mode 100644 js/src/graphql/upload.js create mode 100644 js/src/graphql/user.js delete mode 100644 js/src/i18n/en.js delete mode 100644 js/src/i18n/fr.js delete mode 100644 js/src/i18n/index.js create mode 100644 js/src/i18n/locale/en_US/LC_MESSAGES/app.po create mode 100644 js/src/i18n/locale/fr_FR/LC_MESSAGES/app.po create mode 100644 js/src/i18n/locale/fr_FR/LC_MESSAGES/app.po~ create mode 100644 js/src/i18n/translations.json delete mode 100644 js/src/store/index.js delete mode 100644 js/src/store/mutation-types.js create mode 100644 js/src/vue-apollo.js create mode 100644 lib/mobilizon/actors/service/tools.ex create mode 100644 lib/mobilizon_web/context.ex delete mode 100644 lib/mobilizon_web/controllers/actor_controller.ex delete mode 100644 lib/mobilizon_web/controllers/address_controller.ex delete mode 100644 lib/mobilizon_web/controllers/bot_controller.ex delete mode 100644 lib/mobilizon_web/controllers/category_controller.ex delete mode 100644 lib/mobilizon_web/controllers/comment_controller.ex delete mode 100644 lib/mobilizon_web/controllers/event_controller.ex delete mode 100644 lib/mobilizon_web/controllers/event_request_controller.ex delete mode 100644 lib/mobilizon_web/controllers/follower_controller.ex delete mode 100644 lib/mobilizon_web/controllers/group_controller.ex delete mode 100644 lib/mobilizon_web/controllers/inboxes_controller.ex rename lib/mobilizon_web/controllers/{nodeinfo_controller.ex => node_info_controller.ex} (91%) delete mode 100644 lib/mobilizon_web/controllers/outboxes_controller.ex delete mode 100644 lib/mobilizon_web/controllers/participant_controller.ex delete mode 100644 lib/mobilizon_web/controllers/search_controller.ex delete mode 100644 lib/mobilizon_web/controllers/session_controller.ex delete mode 100644 lib/mobilizon_web/controllers/tag_controller.ex delete mode 100644 lib/mobilizon_web/controllers/track_controller.ex delete mode 100644 lib/mobilizon_web/controllers/user_controller.ex delete mode 100644 lib/mobilizon_web/controllers/user_session_controller.ex create mode 100644 lib/mobilizon_web/resolvers/actor.ex create mode 100644 lib/mobilizon_web/resolvers/category.ex create mode 100644 lib/mobilizon_web/resolvers/event.ex create mode 100644 lib/mobilizon_web/resolvers/upload.ex create mode 100644 lib/mobilizon_web/resolvers/user.ex create mode 100644 lib/mobilizon_web/schema.ex create mode 100644 lib/mobilizon_web/schema/custom/UUID4.ex create mode 100644 lib/mobilizon_web/upload_plug.ex create mode 100644 lib/mobilizon_web/uploaders/avatar.ex create mode 100644 lib/mobilizon_web/uploaders/category.ex delete mode 100644 lib/mobilizon_web/views/actor_view.ex delete mode 100644 lib/mobilizon_web/views/address_view.ex delete mode 100644 lib/mobilizon_web/views/bot_view.ex delete mode 100644 lib/mobilizon_web/views/category_view.ex delete mode 100644 lib/mobilizon_web/views/comment_view.ex delete mode 100644 lib/mobilizon_web/views/event_view.ex delete mode 100644 lib/mobilizon_web/views/participant_view.ex delete mode 100644 lib/mobilizon_web/views/search_view.ex delete mode 100644 lib/mobilizon_web/views/session_view.ex delete mode 100644 lib/mobilizon_web/views/tag_view.ex delete mode 100644 lib/mobilizon_web/views/track_view.ex delete mode 100644 lib/mobilizon_web/views/user_session_view.ex delete mode 100644 lib/mobilizon_web/views/user_view.ex create mode 100644 priv/repo/migrations/20181021161324_add_default_actor_user_field.exs delete mode 100644 test/eventos_web/controllers/actor_controller_test.exs delete mode 100644 test/eventos_web/controllers/address_controller_test.exs delete mode 100644 test/eventos_web/controllers/bot_controller_test.exs delete mode 100644 test/eventos_web/controllers/category_controller_test.exs delete mode 100644 test/eventos_web/controllers/comment_controller_test.exs delete mode 100644 test/eventos_web/controllers/event_controller_test.exs delete mode 100644 test/eventos_web/controllers/follower_controller_test.exs delete mode 100644 test/eventos_web/controllers/session_controller_test.exs delete mode 100644 test/eventos_web/controllers/tag_controller_test.exs delete mode 100644 test/eventos_web/controllers/track_controller_test.exs delete mode 100644 test/eventos_web/controllers/user_controller_test.exs delete mode 100644 test/eventos_web/controllers/user_session_controller_test.exs create mode 100644 test/fixtures/category_picture.png create mode 100644 test/fixtures/category_picture_updated.png rename test/{eventos => mobilizon}/actors/actors_test.exs (91%) rename test/{eventos => mobilizon}/addresses/addresses_test.exs (100%) rename test/{eventos => mobilizon}/events/events_test.exs (95%) rename test/{eventos => mobilizon}/service/activitypub/activitypub_test.exs (100%) rename test/{eventos => mobilizon}/service/web_finger/web_finger_test.exs (100%) rename test/{eventos_web => mobilizon_web}/controllers/activity_pub_controller_test.exs (100%) rename test/{eventos_web => mobilizon_web}/controllers/nodeinfo_controller_test.exs (81%) rename test/{eventos_web => mobilizon_web}/controllers/page_controller_test.exs (100%) create mode 100644 test/mobilizon_web/resolvers/actor_resolver_test.exs create mode 100644 test/mobilizon_web/resolvers/category_resolver_test.exs create mode 100644 test/mobilizon_web/resolvers/event_resolver_test.exs create mode 100644 test/mobilizon_web/resolvers/user_resolver_test.exs rename test/{eventos_web => mobilizon_web}/views/error_view_test.exs (100%) rename test/{eventos_web => mobilizon_web}/views/layout_view_test.exs (100%) rename test/{eventos_web => mobilizon_web}/views/page_view_test.exs (100%) create mode 100644 test/support/abinthe_helpers.ex create mode 100644 uploads/.gitkeep diff --git a/.gitignore b/.gitignore index 53698e33a..d723bace9 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,6 @@ priv/static/* priv/data/* !priv/data/.gitkeep .vscode/ -cover/ \ No newline at end of file +cover/ +uploads/* +!uploads/.gitkeep \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3c60b54a9..48412af92 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,4 +1,4 @@ -image: elixir:1.7 +image: tcitworld/mobilizon-ci services: - name: mdillon/postgis:10 @@ -20,8 +20,7 @@ cache: - .rebar3 before_script: - - apt-get update - - apt-get install -y build-essential postgresql-client git + - cd js && npm install && npm run build && cd ../ - mix local.rebar --force - mix local.hex --force - mix deps.get diff --git a/config/config.exs b/config/config.exs index 272085e17..169e9ec85 100644 --- a/config/config.exs +++ b/config/config.exs @@ -47,7 +47,7 @@ config :guardian, Guardian.DB, # default schema_name: "guardian_tokens", # store all token types if not set - token_types: ["refresh_token"], + # token_types: ["refresh_token"], # default: 60 minutes sweep_interval: 60 @@ -59,3 +59,9 @@ config :geolix, source: System.get_env("GEOLITE_CITIES_PATH") || "priv/data/GeoLite2-City.mmdb" } ] + +config :arc, + storage: Arc.Storage.Local + +config :email_checker, + validations: [EmailChecker.Check.Format] diff --git a/docker/tests/Dockerfile b/docker/tests/Dockerfile new file mode 100644 index 000000000..cab195811 --- /dev/null +++ b/docker/tests/Dockerfile @@ -0,0 +1,5 @@ +FROM elixir:latest + +RUN apt-get update -yq && apt-get install -yq build-essential inotify-tools postgresql-client git curl gnupg +RUN curl -sL https://deb.nodesource.com/setup_8.x | bash && apt-get install nodejs -yq +RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \ No newline at end of file diff --git a/js/.eslintrc.js b/js/.eslintrc.js index 7d28e5d9e..081185d34 100644 --- a/js/.eslintrc.js +++ b/js/.eslintrc.js @@ -1,7 +1,7 @@ module.exports = { root: true, - 'extends': [ + extends: [ 'plugin:vue/essential', - '@vue/airbnb' - ] -} \ No newline at end of file + '@vue/airbnb', + ], +}; diff --git a/js/.postcssrc.js b/js/.postcssrc.js index 100cc0124..a47ef4f95 100644 --- a/js/.postcssrc.js +++ b/js/.postcssrc.js @@ -1,5 +1,5 @@ module.exports = { plugins: { - autoprefixer: {} - } -} \ No newline at end of file + autoprefixer: {}, + }, +}; diff --git a/js/Makefile b/js/Makefile new file mode 100644 index 000000000..315d4ce25 --- /dev/null +++ b/js/Makefile @@ -0,0 +1,58 @@ +# On OSX the PATH variable isn't exported unless "SHELL" is also set, see: http://stackoverflow.com/a/25506676 +SHELL = /bin/bash +NODE_BINDIR = ./node_modules/.bin +export PATH := $(NODE_BINDIR):$(PATH) + +# Where to find input files (it can be multiple paths). +INPUT_FILES = ./src + +# Where to write the files generated by this makefile. +OUTPUT_DIR = ./src/i18n + +# Available locales for the app. +LOCALES = en_US fr_FR + +# Name of the generated .po files for each available locale. +LOCALE_FILES ?= $(patsubst %,$(OUTPUT_DIR)/locale/%/LC_MESSAGES/app.po,$(LOCALES)) + +GETTEXT_HTML_SOURCES = $(shell find $(INPUT_FILES) -name '*.vue' -o -name '*.html' 2> /dev/null) +GETTEXT_JS_SOURCES = $(shell find $(INPUT_FILES) -name '*.vue' -o -name '*.js') + +# Makefile Targets +.PHONY: clean makemessages translations + +clean: + rm -f /tmp/template.pot $(OUTPUT_DIR)/translations.json + +makemessages: /tmp/template.pot + +translations: ./$(OUTPUT_DIR)/translations.json + +# Create a main .pot template, then generate .po files for each available language. +# Thanx to Systematic: https://github.com/Polyconseil/systematic/blob/866d5a/mk/main.mk#L167-L183 +/tmp/template.pot: $(GETTEXT_HTML_SOURCES) +# `dir` is a Makefile built-in expansion function which extracts the directory-part of `$@`. +# `$@` is a Makefile automatic variable: the file name of the target of the rule. +# => `mkdir -p /tmp/` + mkdir -p $(dir $@) + which gettext-extract +# Extract gettext strings from templates files and create a POT dictionary template. + gettext-extract --attribute v-translate --quiet --output $@ $(GETTEXT_HTML_SOURCES) +# Extract gettext strings from JavaScript files. + xgettext --language=JavaScript --keyword=npgettext:1c,2,3 \ + --from-code=utf-8 --join-existing --no-wrap \ + --package-name=$(shell node -e "console.log(require('./package.json').name);") \ + --package-version=$(shell node -e "console.log(require('./package.json').version);") \ + --output $@ $(GETTEXT_JS_SOURCES) +# Generate .po files for each available language. + @for lang in $(LOCALES); do \ + export PO_FILE=$(OUTPUT_DIR)/locale/$$lang/LC_MESSAGES/app.po; \ + echo "msgmerge --update $$PO_FILE $@"; \ + mkdir -p $$(dirname $$PO_FILE); \ + [ -f $$PO_FILE ] && msgmerge --lang=$$lang --update $$PO_FILE $@ || msginit --no-translator --locale=$$lang --input=$@ --output-file=$$PO_FILE; \ + msgattrib --no-wrap --no-obsolete -o $$PO_FILE $$PO_FILE; \ + done; + +$(OUTPUT_DIR)/translations.json: clean /tmp/template.pot + mkdir -p $(OUTPUT_DIR) + gettext-compile --output $@ $(LOCALE_FILES) diff --git a/js/get_union_json.js b/js/get_union_json.js new file mode 100644 index 000000000..4a7b88979 --- /dev/null +++ b/js/get_union_json.js @@ -0,0 +1,38 @@ +const fetch = require('node-fetch'); +const fs = require('fs'); + +fetch(`http://localhost:4000/graphiql`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + variables: {}, + query: ` + { + __schema { + types { + kind + name + possibleTypes { + name + } + } + } + } + `, + }), +}) + .then(result => result.json()) + .then(result => { + // here we're filtering out any type information unrelated to unions or interfaces + const filteredData = result.data.__schema.types.filter( + type => type.possibleTypes !== null, + ); + result.data.__schema.types = filteredData; + fs.writeFile('./fragmentTypes.json', JSON.stringify(result.data), err => { + if (err) { + console.error('Error writing fragmentTypes file', err); + } else { + console.log('Fragment types successfully extracted!'); + } + }); + }); diff --git a/js/package-lock.json b/js/package-lock.json index e0b3195b8..3c2f9cf2a 100644 --- a/js/package-lock.json +++ b/js/package-lock.json @@ -4,6 +4,23 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@apollographql/apollo-upload-server": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@apollographql/apollo-upload-server/-/apollo-upload-server-5.0.3.tgz", + "integrity": "sha512-tGAp3ULNyoA8b5o9LsU2Lq6SwgVPUOKAqKywu2liEtTvrFSGPrObwanhYwArq3GPeOqp2bi+JknSJCIU3oQN1Q==", + "dev": true, + "requires": { + "@babel/runtime-corejs2": "^7.0.0-rc.1", + "busboy": "^0.2.14", + "object-path": "^0.11.4" + } + }, + "@apollographql/graphql-playground-html": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@apollographql/graphql-playground-html/-/graphql-playground-html-1.6.0.tgz", + "integrity": "sha512-QAZIFrfVRkjvMkUHIQKZXZ3La0V5t12w5PWrhihYEabHwzIZV/txQd/kSYHgYPXC4s5OURxsXZop9f0BzI2QIQ==", + "dev": true + }, "@babel/code-frame": { "version": "7.0.0-beta.47", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.47.tgz", @@ -728,6 +745,24 @@ "regenerator-runtime": "^0.11.1" } }, + "@babel/runtime-corejs2": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.1.2.tgz", + "integrity": "sha512-drxaPByExlcRDKW4ZLubUO4ZkI8/8ax9k9wve1aEthdLKFzjB7XRkOQ0xoTIWGxqdDnWDElkjYq77bt7yrcYJQ==", + "dev": true, + "requires": { + "core-js": "^2.5.7", + "regenerator-runtime": "^0.12.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz", + "integrity": "sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==", + "dev": true + } + } + }, "@babel/template": { "version": "7.0.0-beta.47", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.47.tgz", @@ -796,6 +831,202 @@ "integrity": "sha512-yprFYuno9FtNsSHVlSWd+nRlmGoAbqbeCwOryP6sC/zoCjhpArcRMYp19EvpSUSizJAlsXEwJv+wcWS9XaXdMw==", "dev": true }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=", + "dev": true + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "dev": true + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "dev": true + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=", + "dev": true + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", + "dev": true, + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=", + "dev": true + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=", + "dev": true + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=", + "dev": true + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=", + "dev": true + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=", + "dev": true + }, + "@types/accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/async": { + "version": "2.0.49", + "resolved": "https://registry.npmjs.org/@types/async/-/async-2.0.49.tgz", + "integrity": "sha512-Benr3i5odUkvpFkOpzGqrltGdbSs+EVCkEBGXbuR7uT0VzhXKIkhem6PDzHdx5EonA+rfbB3QvP6aDOw5+zp5Q==", + "optional": true + }, + "@types/babel-types": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.4.tgz", + "integrity": "sha512-WiZhq3SVJHFRgRYLXvpf65XnV6ipVHhnNaNvE8yCimejrGglkg38kEj0JcizqwSHxmPSjcTlig/6JouxLGEhGw==" + }, + "@types/babylon": { + "version": "6.16.3", + "resolved": "https://registry.npmjs.org/@types/babylon/-/babylon-6.16.3.tgz", + "integrity": "sha512-lyJ8sW1PbY3uwuvpOBZ9zMYKshMnQpXmeDHh8dj9j2nJm/xrW0FgB5gLSYOArj5X0IfaXnmhFoJnhS4KbqIMug==", + "requires": { + "@types/babel-types": "*" + } + }, + "@types/body-parser": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.0.tgz", + "integrity": "sha512-a2+YeUjPkztKJu5aIF2yArYFQQp8d51wZ7DavSHjFuY1mqVgidGyzEQ41JIVNy82fXj8yPgy2vJmfIywgESW6w==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.32", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz", + "integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/cors": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.4.tgz", + "integrity": "sha512-ipZjBVsm2tF/n8qFGOuGBkUij9X9ZswVi9G3bx/6dz7POpVa6gVHcj1wsX/LVEn9MMF41fxK/PnZPPoTD1UFPw==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/events": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", + "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==", + "dev": true + }, + "@types/express": { + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.16.0.tgz", + "integrity": "sha512-TtPEYumsmSTtTetAPXlJVf3kEqb6wZK0bZojpJQrnD/djV4q1oB6QQ8aKvKqwNPACoe02GNiy5zDzcYivR5Z2w==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.16.0", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.16.0.tgz", + "integrity": "sha512-lTeoCu5NxJU4OD9moCgm0ESZzweAx0YqsAcab6OB0EB3+As1OaHtKnaGJvcngQxYsi9UNv0abn4/DRavrRxt4w==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/node": "*", + "@types/range-parser": "*" + } + }, + "@types/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.0.tgz", + "integrity": "sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q==", + "dev": true + }, + "@types/mime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.0.tgz", + "integrity": "sha512-A2TAGbTFdBw9azHbpVd+/FkdW2T6msN1uct1O9bH3vTerEHKZhTXJUQXy+hNq1B0RagfU8U+KBdqiZpxjhOUQA==", + "dev": true + }, + "@types/node": { + "version": "10.11.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.11.6.tgz", + "integrity": "sha512-fnA7yvqg3oKQDb3skBif9w5RRKVKAaeKeNuLzZL37XcSiWL4IoSXQnnbchR3UnBu2EMLHBip7ZVEkqoIVBP8QQ==" + }, + "@types/range-parser": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.2.tgz", + "integrity": "sha512-HtKGu+qG1NPvYe1z7ezLsyIaXYyi8SoAVqWDZgDQ8dLrsZvSzUNCwZyfX33uhWxL/SU0ZDQZ3nwZ0nimt507Kw==", + "dev": true + }, + "@types/serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-/BZ4QRLpH/bNYgZgwhKEh+5AsboDBcUdlBYgzoLX0fpj3Y2gp6EApyOlM3bK53wQS/OE1SrdSYBAbux2D1528Q==", + "dev": true, + "requires": { + "@types/express-serve-static-core": "*", + "@types/mime": "*" + } + }, + "@types/ws": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-5.1.2.tgz", + "integrity": "sha512-NkTXUKTYdXdnPE2aUUbGOXE1XfMK527SCvU/9bj86kyFF6kZ9ZnOQ3mK5jADn98Y2vEUD/7wKDgZa7Qst2wYOg==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/node": "*" + } + }, + "@types/zen-observable": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.0.tgz", + "integrity": "sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg==" + }, "@vue/babel-preset-app": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@vue/babel-preset-app/-/babel-preset-app-3.0.5.tgz", @@ -1302,14 +1533,28 @@ "acorn": { "version": "5.7.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", - "dev": true + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==" + }, + "acorn-bigint": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/acorn-bigint/-/acorn-bigint-0.2.0.tgz", + "integrity": "sha1-D0WlKQU3eZo7BwhWiaGGiBy1N4Q=", + "requires": { + "acorn": "^5.2.1" + } + }, + "acorn-class-fields": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/acorn-class-fields/-/acorn-class-fields-0.1.2.tgz", + "integrity": "sha1-IHgvMEr0Ilf+/1vUpcM1KRRzv1g=", + "requires": { + "acorn": "^5.3.0" + } }, "acorn-dynamic-import": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz", "integrity": "sha512-zVWV8Z8lislJoOKKqdNMOB+s6+XV5WERty8MnKBeFgwA+19XJjJHs2RP5dzM57FftIs+jQnRToLiWazKr6sSWg==", - "dev": true, "requires": { "acorn": "^5.0.0" } @@ -1332,6 +1577,22 @@ } } }, + "acorn-import-meta": { + "version": "0.2.1", + "resolved": "http://registry.npmjs.org/acorn-import-meta/-/acorn-import-meta-0.2.1.tgz", + "integrity": "sha512-+KB5Q0P0Q/XpsPHgnLx4XbCGqMogw4yiJJjYsbzPCNrE/IoX+c6J4C+BFcwdWh3CD1zLzMxPITN1jzHd+NiS3w==", + "requires": { + "acorn": "^5.4.1" + } + }, + "acorn-json-superset": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/acorn-json-superset/-/acorn-json-superset-0.1.1.tgz", + "integrity": "sha512-fhvg6mWlulil3spkNL0UQtym0pLAaKsKWmDGuTKlP5PVQwv9DlR1avvnnwl2YT9A61AH5j0idgv5/h9Rdkaqyg==", + "requires": { + "acorn": "^5.4.1" + } + }, "acorn-jsx": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", @@ -1349,6 +1610,46 @@ } } }, + "acorn-numeric-separator": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/acorn-numeric-separator/-/acorn-numeric-separator-0.1.1.tgz", + "integrity": "sha1-qkVaHZWuiHIx3pfgaBq74osGXo0=", + "requires": { + "acorn": "^5.2.1" + } + }, + "acorn-optional-catch-binding": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/acorn-optional-catch-binding/-/acorn-optional-catch-binding-0.1.1.tgz", + "integrity": "sha512-LJn5iDpAU1Zah1sdG2pY4rwv7kSe7ykbKpYrwbw5Igfn3OgPyjSD5f0JPboA1xITYpENS9rtNgN7PaAtTsvI/g==", + "requires": { + "acorn": "^5.2.1" + } + }, + "acorn-private-methods": { + "version": "0.1.1", + "resolved": "http://registry.npmjs.org/acorn-private-methods/-/acorn-private-methods-0.1.1.tgz", + "integrity": "sha1-MsE88k0Fvxyb4EkUtBSRxZ11oZU=", + "requires": { + "acorn": "^5.4.0" + } + }, + "acorn-stage3": { + "version": "0.6.0", + "resolved": "http://registry.npmjs.org/acorn-stage3/-/acorn-stage3-0.6.0.tgz", + "integrity": "sha512-/CZrHonJfg5OSTkZ71w4L4JnpsqZyDIXaSot5gUpQriTUavjiuAjkJBxxNGtxTlGBVtOBtYwzqxLDUSOD3amDQ==", + "requires": { + "acorn": "^5.5.0", + "acorn-bigint": "^0.2.0", + "acorn-class-fields": "^0.1.1", + "acorn-dynamic-import": "^3.0.0", + "acorn-import-meta": "^0.2.1", + "acorn-json-superset": "^0.1.0", + "acorn-numeric-separator": "^0.1.1", + "acorn-optional-catch-binding": "^0.1.0", + "acorn-private-methods": "^0.1.1" + } + }, "acorn-walk": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.0.tgz", @@ -1403,6 +1704,16 @@ "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", "dev": true }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "requires": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + } + }, "alphanum-sort": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", @@ -1415,6 +1726,15 @@ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", "dev": true }, + "ansi-align": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", + "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", + "dev": true, + "requires": { + "string-width": "^2.0.0" + } + }, "ansi-colors": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.1.0.tgz", @@ -1443,7 +1763,6 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -1749,6 +2068,370 @@ } } }, + "apollo-absinthe-upload-link": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/apollo-absinthe-upload-link/-/apollo-absinthe-upload-link-1.4.0.tgz", + "integrity": "sha512-fZfxTGTPzOP51TdeiBSHgXyc1wFTjBTIDGJA4+c5zQNAHqmcwhZZxeb0dDP1veZ1XSaExFrjB39oZHwN75R0Yg==", + "requires": { + "apollo-client": "^2.0.4", + "apollo-link": "^1.0.7", + "apollo-link-http": "^1.3.2", + "apollo-link-http-common": "^0.2.4", + "graphql": "0.11.3", + "rxjs": "~6.2.2" + }, + "dependencies": { + "graphql": { + "version": "0.11.3", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-0.11.3.tgz", + "integrity": "sha512-/WGU673BlQaCw3gDECmqL5+l8kg4Sm3XHvuzPtZLiI/y8Xm7fKq+C0mtJmAImuHJvTBsMtCuX/R9BUQqPm4tzw==", + "requires": { + "iterall": "^1.1.0" + } + } + } + }, + "apollo-cache": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/apollo-cache/-/apollo-cache-1.1.18.tgz", + "integrity": "sha512-XZ17PxZw+FE75SKZcymsIqA5EetcmLxoHKXqvkGmWi4WZpVDX/hUoPTIUbRsGmHOfZfOokFviDOzOSnEdQo1Mw==", + "requires": { + "apollo-utilities": "^1.0.22" + }, + "dependencies": { + "apollo-utilities": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.0.23.tgz", + "integrity": "sha512-RW65kZZxSEysvzm5YBrNU6SvE+Z62VQ+lpxYX/3d9pZYsMBkaMdfr3OnUwGKt/MqUsDfUb/LLoXpMRhKukMpeQ==", + "requires": { + "fast-json-stable-stringify": "^2.0.0" + } + } + } + }, + "apollo-cache-control": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/apollo-cache-control/-/apollo-cache-control-0.2.5.tgz", + "integrity": "sha512-xEDrUvo3U2mQKSzA8NzQmgeqK4ytwFnTGl2YKGKPfG0+r8fZdswKp6CDBue29KLO8KkSuqW/hntveWrAdK51FQ==", + "dev": true, + "requires": { + "apollo-server-env": "^2.0.3", + "graphql-extensions": "^0.2.1" + } + }, + "apollo-cache-inmemory": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/apollo-cache-inmemory/-/apollo-cache-inmemory-1.3.6.tgz", + "integrity": "sha512-ARSMDNFAPS1xLx79PrOFGd+iEFsC/pVusRbirA+p0AHVJSWyZdhW2fNy7RyFA+2pLbCFFgOgS2SoCq+JFu1RTA==", + "requires": { + "apollo-cache": "^1.1.18", + "apollo-utilities": "^1.0.22", + "optimism": "^0.6.6" + }, + "dependencies": { + "apollo-utilities": { + "version": "1.0.23", + "resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.0.23.tgz", + "integrity": "sha512-RW65kZZxSEysvzm5YBrNU6SvE+Z62VQ+lpxYX/3d9pZYsMBkaMdfr3OnUwGKt/MqUsDfUb/LLoXpMRhKukMpeQ==", + "requires": { + "fast-json-stable-stringify": "^2.0.0" + } + } + } + }, + "apollo-client": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/apollo-client/-/apollo-client-2.4.3.tgz", + "integrity": "sha512-ALfqkvnv2ZXrk08rqegFDkgRNVh7G/hz+jK+dblCKjcbgUmBduSHsnIfc335povbZTLPjtb0/z0iMECJu3FvZw==", + "requires": { + "@types/async": "2.0.49", + "@types/zen-observable": "^0.8.0", + "apollo-cache": "1.1.18", + "apollo-link": "^1.0.0", + "apollo-link-dedup": "^1.0.0", + "apollo-utilities": "1.0.22", + "symbol-observable": "^1.0.2", + "zen-observable": "^0.8.0" + }, + "dependencies": { + "apollo-cache": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/apollo-cache/-/apollo-cache-1.1.18.tgz", + "integrity": "sha512-XZ17PxZw+FE75SKZcymsIqA5EetcmLxoHKXqvkGmWi4WZpVDX/hUoPTIUbRsGmHOfZfOokFviDOzOSnEdQo1Mw==", + "requires": { + "apollo-utilities": "^1.0.22" + } + }, + "apollo-utilities": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.0.22.tgz", + "integrity": "sha512-6+78Woy+js6LrMrI/TS3Lbdti7D8LhgZnVANSGfsPqjVlEkMnl0p7XSfpEFfX69hDN04wf43+BnqgS2cgJkiMA==", + "requires": { + "fast-json-stable-stringify": "^2.0.0" + } + } + } + }, + "apollo-datasource": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/apollo-datasource/-/apollo-datasource-0.1.3.tgz", + "integrity": "sha512-yEGEe5Cjzqqu5ml1VV3O8+C+thzdknZri9Ny0P3daTGNO+45J3vBOMcmaANeeI2+OOeWxdqUNa5aPOx/35kniw==", + "dev": true, + "requires": { + "apollo-server-caching": "0.1.2", + "apollo-server-env": "2.0.3" + } + }, + "apollo-engine-reporting": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/apollo-engine-reporting/-/apollo-engine-reporting-0.0.6.tgz", + "integrity": "sha512-JmfNJ9v3QEJQ8ZhLfCKEDiww53n5kj5HarP85p8LreoYNojbvcWN8Qm6RgvSG5N/ZJrAYHeTRQbSxm1vWwGubw==", + "dev": true, + "requires": { + "apollo-engine-reporting-protobuf": "^0.0.1", + "apollo-server-env": "^2.0.3", + "async-retry": "^1.2.1", + "graphql-extensions": "^0.2.1", + "lodash": "^4.17.10" + } + }, + "apollo-engine-reporting-protobuf": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/apollo-engine-reporting-protobuf/-/apollo-engine-reporting-protobuf-0.0.1.tgz", + "integrity": "sha512-AySoDgog2p1Nph44FyyqaU4AfRZOXx8XZxRsVHvYY4dHlrMmDDhhjfF3Jswa7Wr8X/ivvx3xA0jimRn6rsG8Ew==", + "dev": true, + "requires": { + "protobufjs": "^6.8.6" + } + }, + "apollo-link": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/apollo-link/-/apollo-link-1.2.3.tgz", + "integrity": "sha512-iL9yS2OfxYhigme5bpTbmRyC+Htt6tyo2fRMHT3K1XRL/C5IQDDz37OjpPy4ndx7WInSvfSZaaOTKFja9VWqSw==", + "requires": { + "apollo-utilities": "^1.0.0", + "zen-observable-ts": "^0.8.10" + } + }, + "apollo-link-context": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/apollo-link-context/-/apollo-link-context-1.0.9.tgz", + "integrity": "sha512-gcC1WH7mTyNtS0bF4fPijepXqnERwZjm1JCkuOT6ADBTpDTXIqK+Ec+/QkVawDO26EV9OX5ujWe4kI1qC6T6tA==", + "dev": true, + "requires": { + "apollo-link": "^1.2.3" + } + }, + "apollo-link-dedup": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/apollo-link-dedup/-/apollo-link-dedup-1.0.10.tgz", + "integrity": "sha512-tpUI9lMZsidxdNygSY1FxflXEkUZnvKRkMUsXXuQUNoSLeNtEvUX7QtKRAl4k9ubLl8JKKc9X3L3onAFeGTK8w==", + "requires": { + "apollo-link": "^1.2.3" + } + }, + "apollo-link-http": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/apollo-link-http/-/apollo-link-http-1.5.5.tgz", + "integrity": "sha512-C5N6N/mRwmepvtzO27dgMEU3MMtRKSqcljBkYNZmWwH11BxkUQ5imBLPM3V4QJXNE7NFuAQAB5PeUd4ligivTQ==", + "requires": { + "apollo-link": "^1.2.3", + "apollo-link-http-common": "^0.2.5" + } + }, + "apollo-link-http-common": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/apollo-link-http-common/-/apollo-link-http-common-0.2.5.tgz", + "integrity": "sha512-6FV1wr5AqAyJ64Em1dq5hhGgiyxZE383VJQmhIoDVc3MyNcFL92TkhxREOs4rnH2a9X2iJMko7nodHSGLC6d8w==", + "requires": { + "apollo-link": "^1.2.3" + } + }, + "apollo-link-persisted-queries": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/apollo-link-persisted-queries/-/apollo-link-persisted-queries-0.2.1.tgz", + "integrity": "sha512-OxBum5e5vn8XBBEURXpoYstwcKNtK/p3K3bAQ5yGjj7IyzpLmBcKLzfjk3wAnEyJJYbOUXIvPg7XnxQbcIlNGA==", + "dev": true, + "requires": { + "apollo-link": "^1.2.1", + "hash.js": "^1.1.3" + } + }, + "apollo-link-state": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/apollo-link-state/-/apollo-link-state-0.4.2.tgz", + "integrity": "sha512-xMPcAfuiPVYXaLwC6oJFIZrKgV3GmdO31Ag2eufRoXpvT0AfJZjdaPB4450Nu9TslHRePN9A3quxNueILlQxlw==", + "dev": true, + "requires": { + "apollo-utilities": "^1.0.8", + "graphql-anywhere": "^4.1.0-alpha.0" + } + }, + "apollo-link-ws": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/apollo-link-ws/-/apollo-link-ws-1.0.9.tgz", + "integrity": "sha512-CtKwLE61bCJTW5jrucOMm5PubeAlCl/9i45pg4GKKlAbl0zR4i2dww8TJkYoIY6iCyj4qjKW/uqGD6v5/aVwhg==", + "dev": true, + "requires": { + "apollo-link": "^1.2.3" + } + }, + "apollo-server-caching": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/apollo-server-caching/-/apollo-server-caching-0.1.2.tgz", + "integrity": "sha512-jBRnsTgXN0m8yVpumoelaUq9mXR7YpJ3EE+y/alI7zgXY+0qFDqksRApU8dEfg3q6qUnO7rFxRhdG5eyc0+1ig==", + "dev": true, + "requires": { + "lru-cache": "^4.1.3" + } + }, + "apollo-server-core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/apollo-server-core/-/apollo-server-core-2.1.0.tgz", + "integrity": "sha512-D1Tw0o3NzCQ2KGM8EWh9AHELHmn/SE361dtlqJxkbelxXqAkCIGIFywF30h+0ezhMbgbO7eqBBJfvRilF/oJHA==", + "dev": true, + "requires": { + "@apollographql/apollo-upload-server": "^5.0.3", + "@types/ws": "^5.1.2", + "apollo-cache-control": "^0.2.5", + "apollo-datasource": "^0.1.3", + "apollo-engine-reporting": "^0.0.6", + "apollo-server-caching": "^0.1.2", + "apollo-server-env": "^2.0.3", + "apollo-server-errors": "^2.0.2", + "apollo-tracing": "^0.2.5", + "graphql-extensions": "^0.2.1", + "graphql-subscriptions": "^0.5.8", + "graphql-tag": "^2.9.2", + "graphql-tools": "^3.0.4", + "hash.js": "^1.1.3", + "lodash": "^4.17.10", + "subscriptions-transport-ws": "^0.9.11", + "ws": "^5.2.0" + }, + "dependencies": { + "graphql-subscriptions": { + "version": "0.5.8", + "resolved": "http://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-0.5.8.tgz", + "integrity": "sha512-0CaZnXKBw2pwnIbvmVckby5Ge5e2ecmjofhYCdyeACbCly2j3WXDP/pl+s+Dqd2GQFC7y99NB+53jrt55CKxYQ==", + "dev": true, + "requires": { + "iterall": "^1.2.1" + } + } + } + }, + "apollo-server-env": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/apollo-server-env/-/apollo-server-env-2.0.3.tgz", + "integrity": "sha512-uIfKFH8n8xKO0eLb9Fa79+s2DdMuVethgznvW6SrOYq5VzgkIIobqKEuZPKa5wObw9CkCyju/+Sr7b7WWMFxUQ==", + "dev": true, + "requires": { + "node-fetch": "^2.1.2", + "util.promisify": "^1.0.0" + } + }, + "apollo-server-errors": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/apollo-server-errors/-/apollo-server-errors-2.0.2.tgz", + "integrity": "sha512-zyWDqAVDCkj9espVsoUpZr9PwDznM8UW6fBfhV+i1br//s2AQb07N6ektZ9pRIEvkhykDZW+8tQbDwAO0vUROg==", + "dev": true + }, + "apollo-server-express": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/apollo-server-express/-/apollo-server-express-2.1.0.tgz", + "integrity": "sha512-jLFIz1VLduMA/rme4OAy3IPeoaMEZOPoQXpio8AhfjIqCijRPPfoWJ2QMqz56C/g3vas7rZtgcVOrHpjBKudjw==", + "dev": true, + "requires": { + "@apollographql/apollo-upload-server": "^5.0.3", + "@apollographql/graphql-playground-html": "^1.6.0", + "@types/accepts": "^1.3.5", + "@types/body-parser": "1.17.0", + "@types/cors": "^2.8.4", + "@types/express": "4.16.0", + "accepts": "^1.3.5", + "apollo-server-core": "^2.1.0", + "body-parser": "^1.18.3", + "cors": "^2.8.4", + "graphql-subscriptions": "^0.5.8", + "graphql-tools": "^3.0.4", + "type-is": "^1.6.16" + }, + "dependencies": { + "body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "dev": true, + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "graphql-subscriptions": { + "version": "0.5.8", + "resolved": "http://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-0.5.8.tgz", + "integrity": "sha512-0CaZnXKBw2pwnIbvmVckby5Ge5e2ecmjofhYCdyeACbCly2j3WXDP/pl+s+Dqd2GQFC7y99NB+53jrt55CKxYQ==", + "dev": true, + "requires": { + "iterall": "^1.2.1" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "apollo-tracing": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/apollo-tracing/-/apollo-tracing-0.2.5.tgz", + "integrity": "sha512-DZO7pfL5LATHeJdVFoTZ/N3HwA+IMf1YnIt5K+uMQW+/MrRgYOtTszUv5tYX2cUIqHYHcbdDaBQUuIXwSpaV2Q==", + "dev": true, + "requires": { + "apollo-server-env": "^2.0.3", + "graphql-extensions": "^0.2.1" + } + }, + "apollo-upload-client": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/apollo-upload-client/-/apollo-upload-client-9.0.0.tgz", + "integrity": "sha512-Uio1f5T+RMRvP7IHNkFwCDpWhIFjbhhg9UKKyGBS6a8i0sU0Hv70FcSFTpYEeYfGTDiRBe+4FpUdIxD1A6rmkg==", + "dev": true, + "requires": { + "apollo-link": "^1.2.3", + "apollo-link-http-common": "^0.2.5", + "extract-files": "^4.0.0" + } + }, + "apollo-utilities": { + "version": "1.0.21", + "resolved": "https://registry.npmjs.org/apollo-utilities/-/apollo-utilities-1.0.21.tgz", + "integrity": "sha512-ZcxELlEl+sDCYBgEMdNXJAsZtRVm8wk4HIA58bMsqYfd1DSAJQEtZ93F0GZgYNAGy3QyaoBeZtbb0/01++G8JQ==", + "requires": { + "fast-json-stable-stringify": "^2.0.0", + "fclone": "^1.0.11" + } + }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", @@ -1869,6 +2552,11 @@ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, "asn1": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", @@ -1960,6 +2648,15 @@ "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", "dev": true }, + "async-retry": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.2.3.tgz", + "integrity": "sha512-tfDb02Th6CE6pJUF2gjW5ZVjsgwlucVXOEQMvEX9JgSJMs9gAX+Nz3xRuJBKuUYjTSYORqvDBORdAQ3LU59g7Q==", + "dev": true, + "requires": { + "retry": "0.12.0" + } + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -2262,18 +2959,41 @@ "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, "requires": { "core-js": "^2.4.0", "regenerator-runtime": "^0.11.0" } }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + }, + "dependencies": { + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" + } + } + }, "babylon": { "version": "7.0.0-beta.47", "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.47.tgz", "integrity": "sha512-+rq2cr4GDhtToEzKFD6KZZMDBXhjFAr9JjPw9pAppZACeEWqNM294j+NdBzkSHYXwzzBmVjZ3nEVJlOhbR2gOQ==", "dev": true }, + "backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", + "dev": true + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -2404,8 +3124,7 @@ "bluebird": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.2.tgz", - "integrity": "sha512-dhHTWMI7kMx5whMQntl7Vr9C6BvV10lFXDAasnqnrMYhXVCzzk6IO9Fo2L75jXHT07WrOngL1WDXOp+yYS91Yg==", - "dev": true + "integrity": "sha512-dhHTWMI7kMx5whMQntl7Vr9C6BvV10lFXDAasnqnrMYhXVCzzk6IO9Fo2L75jXHT07WrOngL1WDXOp+yYS91Yg==" }, "bn.js": { "version": "4.11.8", @@ -2523,8 +3242,22 @@ "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", - "dev": true + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, + "boxen": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-1.3.0.tgz", + "integrity": "sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==", + "dev": true, + "requires": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + } }, "brace-expansion": { "version": "1.1.11", @@ -2687,6 +3420,42 @@ "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", "dev": true }, + "busboy": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", + "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", + "dev": true, + "requires": { + "dicer": "0.2.5", + "readable-stream": "1.1.x" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, "bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", @@ -2854,6 +3623,12 @@ "integrity": "sha512-4NI3s4Y6ROm+SgZN5sLUG4k7nVWQnedis3c/RWkynV5G6cHSY7+a8fwFyn2yoBDE3E6VswhTNNwR3PvzGqlTkg==", "dev": true }, + "capture-stack-trace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", + "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==", + "dev": true + }, "case-sensitive-paths-webpack-plugin": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.1.2.tgz", @@ -2866,6 +3641,15 @@ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", "dev": true }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "requires": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + } + }, "chai": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", @@ -2917,13 +3701,20 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, + "character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=", + "requires": { + "is-regex": "^1.0.3" + } + }, "chardet": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", @@ -2947,6 +3738,80 @@ "integrity": "sha512-YbulWHdfP99UfZ73NcUDlNJhEIDgm9Doq9GhpyXbF+7Aegi3CVV7qqMCKTTqJxlvEvnQBp9IA+dxsGN6xK/nSg==", "dev": true }, + "cheerio": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz", + "integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=", + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash": "^4.15.0", + "parse5": "^3.0.1" + }, + "dependencies": { + "css-select": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "htmlparser2": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.0.tgz", + "integrity": "sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ==", + "requires": { + "domelementtype": "^1.3.0", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.0.6" + } + }, + "parse5": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", + "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", + "requires": { + "@types/node": "*" + } + }, + "readable-stream": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.0.6.tgz", + "integrity": "sha512-9E1oLoOWfhSXHGv6QlwXJim7uNzd9EVlWK+21tCU9Ju/kR0/p2AZYPz4qSchgO8PlLIH4FpZYfzwS+rEksZjIg==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "chokidar": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", @@ -3149,7 +4014,6 @@ "version": "4.2.1", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", - "dev": true, "requires": { "source-map": "~0.6.0" }, @@ -3157,11 +4021,16 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, + "cli-boxes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", + "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=", + "dev": true + }, "cli-cursor": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", @@ -3312,7 +4181,6 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, "requires": { "color-name": "1.1.3" } @@ -3320,8 +4188,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "color-string": { "version": "1.5.3", @@ -3442,6 +4309,20 @@ "typedarray": "^0.0.6" } }, + "configstore": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", + "integrity": "sha512-vtv5HtGjcYUgFrXc6Kx747B83MRRVS5R1VTEQoXvuP+kMI+if6uywV0nDGoiydJRy4yk7h9od5Og0kxx4zUXmw==", + "dev": true, + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, "connect-history-api-fallback": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.5.0.tgz", @@ -3467,11 +4348,28 @@ "version": "0.15.1", "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz", "integrity": "sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==", - "dev": true, "requires": { "bluebird": "^3.1.1" } }, + "constantinople": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.1.2.tgz", + "integrity": "sha512-yePcBqEFhLOqSBtwYOGGS1exHo/s1xjekXiinh4itpNQGCu4KA1euPh1fg07N2wMITZXQkBz75Ntdt1ctGZouw==", + "requires": { + "@types/babel-types": "^7.0.0", + "@types/babylon": "^6.16.2", + "babel-types": "^6.26.0", + "babylon": "^6.18.0" + }, + "dependencies": { + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==" + } + } + }, "constants-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", @@ -3587,8 +4485,7 @@ "core-js": { "version": "2.5.7", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", - "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", - "dev": true + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==" }, "core-util-is": { "version": "1.0.2", @@ -3596,6 +4493,16 @@ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, + "cors": { + "version": "2.8.4", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.4.tgz", + "integrity": "sha1-K9OB8usgECAQXNUOpZ2mMJBpRoY=", + "dev": true, + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "cosmiconfig": { "version": "5.0.6", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.6.tgz", @@ -3629,6 +4536,15 @@ "elliptic": "^6.0.0" } }, + "create-error-class": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", + "dev": true, + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, "create-hash": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", @@ -3693,6 +4609,12 @@ "randomfill": "^1.0.3" } }, + "crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", + "dev": true + }, "css-color-names": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", @@ -3836,8 +4758,7 @@ "css-what": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", - "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=", - "dev": true + "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=" }, "cssesc": { "version": "0.1.0", @@ -4021,8 +4942,7 @@ "de-indent": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", - "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=", - "dev": true + "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=" }, "debug": { "version": "3.2.5", @@ -4036,8 +4956,7 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "decode-uri-component": { "version": "0.2.0", @@ -4060,6 +4979,12 @@ "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", "dev": true }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -4196,6 +5121,12 @@ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", "dev": true }, + "deprecated-decorator": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz", + "integrity": "sha1-AJZjF7ehL+kvPMgx91g68ym4bDc=", + "dev": true + }, "des.js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", @@ -4218,6 +5149,42 @@ "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", "dev": true }, + "dicer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", + "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", + "dev": true, + "requires": { + "readable-stream": "1.1.x", + "streamsearch": "0.1.2" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, "diff": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", @@ -4287,6 +5254,11 @@ "esutils": "^2.0.2" } }, + "doctypes": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", + "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" + }, "dom-converter": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", @@ -4300,7 +5272,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", - "dev": true, "requires": { "domelementtype": "~1.1.1", "entities": "~1.1.1" @@ -4309,8 +5280,7 @@ "domelementtype": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", - "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", - "dev": true + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" } } }, @@ -4323,8 +5293,7 @@ "domelementtype": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz", - "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=", - "dev": true + "integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=" }, "domexception": { "version": "1.0.1", @@ -4358,7 +5327,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", - "dev": true, "requires": { "is-obj": "^1.0.0" } @@ -4384,6 +5352,12 @@ "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=", "dev": true }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", + "dev": true + }, "duplexify": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz", @@ -4402,6 +5376,71 @@ "integrity": "sha1-EskbMIWjfwuqM26UhurEv5Tj54g=", "dev": true }, + "easygettext": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/easygettext/-/easygettext-2.7.0.tgz", + "integrity": "sha512-BaoyxsZtre7Ndvgz3utjrE/6Yo8Txsc4m33ehQ0pBNX3HjcjGQozDhnpqSRhaeD8PQAk0Rgq3vhI+YJvQu0vUQ==", + "requires": { + "@vue/component-compiler-utils": "^1.2.1", + "acorn": "^5.5.3", + "acorn-stage3": "^0.6.0", + "cheerio": "^1.0.0-rc.2", + "minimist": "^1.2.0", + "pofile": "^1.0.10", + "pug": "^2.0.3", + "vue-template-compiler": "^2.5.16" + }, + "dependencies": { + "@vue/component-compiler-utils": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-1.3.1.tgz", + "integrity": "sha512-IyjJW6ToMitgAhp3xh22QiEW8JvHfLyzlyY/J+GjJ71miod9tNsy6xT2ckm/VirlhPMfeM43kgYZe34jhmmzpw==", + "requires": { + "consolidate": "^0.15.1", + "hash-sum": "^1.0.2", + "lru-cache": "^4.1.2", + "merge-source-map": "^1.1.0", + "postcss": "^6.0.20", + "postcss-selector-parser": "^3.1.1", + "prettier": "^1.13.0", + "source-map": "^0.5.6", + "vue-template-es2015-compiler": "^1.6.0" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "postcss": { + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.4.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + } + } + }, + "postcss-selector-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", + "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", + "requires": { + "dot-prop": "^4.1.1", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + } + } + }, "ecc-jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", @@ -4542,8 +5581,7 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { "version": "1.11.0", @@ -4901,6 +5939,12 @@ "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", "dev": true }, + "esm": { + "version": "3.0.84", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.0.84.tgz", + "integrity": "sha512-SzSGoZc17S7P+12R9cg21Bdb7eybX25RnIeRZ80xZs+VZ3kdQKzqTp2k4hZJjR7p9l0186TTXSgrxzlMDBktlw==", + "dev": true + }, "espree": { "version": "3.5.4", "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", @@ -4944,8 +5988,7 @@ "esutils": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" }, "etag": { "version": "1.8.1", @@ -4959,6 +6002,22 @@ "integrity": "sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ==", "dev": true }, + "event-stream": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.6.tgz", + "integrity": "sha512-dGXNg4F/FgVzlApjzItL+7naHutA3fDqbV/zAZqDDlXTjiMnQmZKu+prImWKszeBM5UQeGvAl3u1wBiKeDh61g==", + "dev": true, + "requires": { + "duplexer": "^0.1.1", + "flatmap-stream": "^0.1.0", + "from": "^0.1.7", + "map-stream": "0.0.7", + "pause-stream": "^0.0.11", + "split": "^1.0.1", + "stream-combiner": "^0.2.2", + "through": "^2.3.8" + } + }, "eventemitter3": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz", @@ -5143,6 +6202,12 @@ "is-extglob": "^1.0.0" } }, + "extract-files": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/extract-files/-/extract-files-4.0.0.tgz", + "integrity": "sha512-beLL9RAMrdAU1myviggeJH6nlsfLS5Ei1fUX7ETw8GUsr6VHtZVW8stz686ConpZzncFitbDG1II/m32gc3sXQ==", + "dev": true + }, "extract-zip": { "version": "1.6.7", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.6.7.tgz", @@ -5528,8 +6593,7 @@ "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, "fast-levenshtein": { "version": "2.0.6", @@ -5552,6 +6616,11 @@ "websocket-driver": ">=0.5.1" } }, + "fclone": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz", + "integrity": "sha1-EOhdo4v+p/xZk0HClu4ddyZu5kA=" + }, "fd-slicer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.0.1.tgz", @@ -5740,6 +6809,12 @@ } } }, + "flatmap-stream": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/flatmap-stream/-/flatmap-stream-0.1.1.tgz", + "integrity": "sha512-lAq4tLbm3sidmdCN8G3ExaxH7cUCtP5mgDvrYowsx84dcYkJJ4I28N7gkxA6+YlSXzaGLJYIDEi9WGfXzMiXdw==", + "dev": true + }, "flatten": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/flatten/-/flatten-1.0.2.tgz", @@ -5882,6 +6957,12 @@ } } }, + "from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", + "dev": true + }, "from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", @@ -6501,8 +7582,7 @@ "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "functional-red-black-tree": { "version": "1.0.1", @@ -6681,6 +7761,15 @@ "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", "dev": true }, + "global-dirs": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", + "dev": true, + "requires": { + "ini": "^1.3.4" + } + }, "globals": { "version": "11.8.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.8.0.tgz", @@ -6719,6 +7808,25 @@ "minimatch": "~3.0.2" } }, + "got": { + "version": "6.7.1", + "resolved": "http://registry.npmjs.org/got/-/got-6.7.1.tgz", + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", + "dev": true, + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + } + }, "graceful-fs": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", @@ -6731,6 +7839,60 @@ "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", "dev": true }, + "graphql": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/graphql/-/graphql-14.0.2.tgz", + "integrity": "sha512-gUC4YYsaiSJT1h40krG3J+USGlwhzNTXSb4IOZljn9ag5Tj+RkoXrWp+Kh7WyE3t1NCfab5kzCuxBIvOMERMXw==", + "dev": true, + "requires": { + "iterall": "^1.2.2" + } + }, + "graphql-anywhere": { + "version": "4.1.19", + "resolved": "https://registry.npmjs.org/graphql-anywhere/-/graphql-anywhere-4.1.19.tgz", + "integrity": "sha512-mQlvbECzYPBcgBC9JsdM4v+DSvNmcIP+8Vwr+ij3gktbaLSE0Iza30mztuz40Jlf7ooMs+0emBZucNjLzqz7tA==", + "dev": true, + "requires": { + "apollo-utilities": "^1.0.21" + } + }, + "graphql-extensions": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/graphql-extensions/-/graphql-extensions-0.2.1.tgz", + "integrity": "sha512-/1FTPSWSffDjlRyMAV2UwQhojLmca9aQD0ieo1IYiqT5SE+uOWi4r83QF1CoER0sREIsH3s+nTmdH3cvQVG3MA==", + "dev": true, + "requires": { + "apollo-server-env": "^2.0.3" + } + }, + "graphql-subscriptions": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-1.0.0.tgz", + "integrity": "sha512-+ytmryoHF1LVf58NKEaNPRUzYyXplm120ntxfPcgOBC7TnK7Tv/4VRHeh4FAR9iL+O1bqhZs4nkibxQ+OA5cDQ==", + "dev": true, + "requires": { + "iterall": "^1.2.1" + } + }, + "graphql-tag": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.10.0.tgz", + "integrity": "sha512-9FD6cw976TLLf9WYIUPCaaTpniawIjHWZSwIRZSjrfufJamcXbVVYfN2TWvJYbw0Xf2JjYbl1/f2+wDnBVw3/w==" + }, + "graphql-tools": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/graphql-tools/-/graphql-tools-3.1.1.tgz", + "integrity": "sha512-yHvPkweUB0+Q/GWH5wIG60bpt8CTwBklCSzQdEHmRUgAdEQKxw+9B7zB3dG7wB3Ym7M7lfrS4Ej+jtDZfA2UXg==", + "dev": true, + "requires": { + "apollo-link": "^1.2.2", + "apollo-utilities": "^1.0.1", + "deprecated-decorator": "^0.1.6", + "iterall": "^1.1.3", + "uuid": "^3.1.0" + } + }, "growl": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", @@ -6773,7 +7935,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -6790,8 +7951,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-symbols": { "version": "1.0.0", @@ -6878,8 +8038,7 @@ "hash-sum": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", - "integrity": "sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=", - "dev": true + "integrity": "sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=" }, "hash.js": { "version": "1.1.5", @@ -6894,8 +8053,7 @@ "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=" }, "hex-color-regex": { "version": "1.1.0", @@ -7562,6 +8720,17 @@ "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", "dev": true }, + "ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", + "dev": true + }, + "immutable-tuple": { + "version": "0.4.8", + "resolved": "https://registry.npmjs.org/immutable-tuple/-/immutable-tuple-0.4.8.tgz", + "integrity": "sha512-1m29EVSrF+LJJAyVo1v12NsIalVKjyi4HNQVQDBx+LNCIuRXnfeMCHuLao5CyN1m3Sn0T63U5JEkmPArPCipQA==" + }, "import-cwd": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", @@ -7588,6 +8757,12 @@ } } }, + "import-lazy": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=", + "dev": true + }, "import-local": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", @@ -7676,8 +8851,7 @@ "indexes-of": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", - "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=", - "dev": true + "integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc=" }, "indexof": { "version": "0.0.1", @@ -7698,7 +8872,12 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true }, "inquirer": { @@ -7918,6 +9097,22 @@ "is-primitive": "^2.0.0" } }, + "is-expression": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-3.0.0.tgz", + "integrity": "sha1-Oayqa+f9HzRx3ELHQW5hwkMXrJ8=", + "requires": { + "acorn": "~4.0.2", + "object-assign": "^4.0.1" + }, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" + } + } + }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -7954,6 +9149,22 @@ "is-extglob": "^1.0.0" } }, + "is-installed-globally": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", + "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", + "dev": true, + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + } + }, + "is-npm": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=", + "dev": true + }, "is-number": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", @@ -7966,8 +9177,7 @@ "is-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" }, "is-path-cwd": { "version": "1.0.0", @@ -8025,14 +9235,18 @@ "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" + }, + "is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", "dev": true }, "is-regex": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "dev": true, "requires": { "has": "^1.0.1" } @@ -8058,6 +9272,12 @@ "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", "dev": true }, + "is-retry-allowed": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", + "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=", + "dev": true + }, "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", @@ -8159,6 +9379,11 @@ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "dev": true }, + "iterall": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/iterall/-/iterall-1.2.2.tgz", + "integrity": "sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA==" + }, "javascript-stringify": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-1.6.0.tgz", @@ -8197,6 +9422,11 @@ "easy-stack": "^1.0.0" } }, + "js-stringify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", + "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds=" + }, "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", @@ -8352,6 +9582,15 @@ "verror": "1.10.0" } }, + "jstransformer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=", + "requires": { + "is-promise": "^2.0.0", + "promise": "^7.0.1" + } + }, "katex": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/katex/-/katex-0.6.0.tgz", @@ -8376,11 +9615,19 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } }, + "latest-version": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", + "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", + "dev": true, + "requires": { + "package-json": "^4.0.0" + } + }, "launch-editor": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.2.1.tgz", @@ -8400,6 +9647,11 @@ "launch-editor": "^2.2.1" } }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" + }, "lcid": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", @@ -8534,8 +9786,7 @@ "lodash": { "version": "4.17.10", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", - "dev": true + "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" }, "lodash._arraycopy": { "version": "3.0.0", @@ -8812,6 +10063,17 @@ "integrity": "sha1-4PyVEztu8nbNyIh82vJKpvFW+Po=", "dev": true }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "dev": true + }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -8837,11 +10099,16 @@ "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", "dev": true }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", + "dev": true + }, "lru-cache": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", - "dev": true, "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" @@ -8856,6 +10123,12 @@ "pify": "^3.0.0" } }, + "make-error": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "dev": true + }, "map-age-cleaner": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.2.tgz", @@ -8877,6 +10150,12 @@ "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", "dev": true }, + "map-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz", + "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=", + "dev": true + }, "map-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", @@ -9076,7 +10355,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", - "dev": true, "requires": { "source-map": "^0.6.1" }, @@ -9084,8 +10362,7 @@ "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" } } }, @@ -9688,6 +10965,12 @@ "lower-case": "^1.1.1" } }, + "node-fetch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.2.0.tgz", + "integrity": "sha512-OayFWziIxiHY8bCUyLX6sTpDH8Jsbp4FfYd1j1f7vZyfgkcOnAyM4oQR16f8a0s7Gl/viMGRey8eScYk4V4EZA==", + "dev": true + }, "node-forge": { "version": "0.7.5", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz", @@ -9971,6 +11254,24 @@ } } }, + "nodemon": { + "version": "1.18.4", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.18.4.tgz", + "integrity": "sha512-hyK6vl65IPnky/ee+D3IWvVGgJa/m3No2/Xc/3wanS6Ce1MWjCzH6NnhPJ/vZM+6JFym16jtHx51lmCMB9HDtg==", + "dev": true, + "requires": { + "chokidar": "^2.0.2", + "debug": "^3.1.0", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.0.4", + "pstree.remy": "^1.1.0", + "semver": "^5.5.0", + "supports-color": "^5.2.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.2", + "update-notifier": "^2.3.0" + } + }, "nodent-runtime": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/nodent-runtime/-/nodent-runtime-3.2.1.tgz", @@ -10044,7 +11345,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", - "dev": true, "requires": { "boolbase": "~1.0.0" } @@ -10076,8 +11376,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-copy": { "version": "0.1.0", @@ -10113,6 +11412,12 @@ "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==", "dev": true }, + "object-path": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.4.tgz", + "integrity": "sha1-NwrnUvvzfePqcKhhwju6iRVpGUk=", + "dev": true + }, "object-visit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", @@ -10245,6 +11550,14 @@ "is-wsl": "^1.1.0" } }, + "optimism": { + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/optimism/-/optimism-0.6.6.tgz", + "integrity": "sha512-1Y6LY7pYbXP5y6yeXYfXhxJi9hsxYAZWpt7bBp4seAwfcYtaN7tq9wot/pdrhyI809/K4gDm3BcZcEkmwGevjg==", + "requires": { + "immutable-tuple": "^0.4.4" + } + }, "optimist": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", @@ -10470,6 +11783,18 @@ } } }, + "package-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", + "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", + "dev": true, + "requires": { + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + } + }, "pako": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", @@ -10587,8 +11912,7 @@ "path-parse": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, "path-to-regexp": { "version": "0.1.7", @@ -10611,6 +11935,15 @@ "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", "dev": true }, + "pause-stream": { + "version": "0.0.11", + "resolved": "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "dev": true, + "requires": { + "through": "~2.3" + } + }, "pbkdf2": { "version": "3.0.17", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", @@ -10678,6 +12011,11 @@ "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", "dev": true }, + "pofile": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pofile/-/pofile-1.0.11.tgz", + "integrity": "sha512-Vy9eH1dRD9wHjYt/QqXcTz+RnX/zg53xK+KljFSX30PvdDMb2z+c6uDUeblUGqqJgz3QFsdlA0IJvHziPmWtQg==" + }, "portfinder": { "version": "1.0.17", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.17.tgz", @@ -11325,6 +12663,12 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true + }, "preserve": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", @@ -11334,8 +12678,7 @@ "prettier": { "version": "1.13.7", "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.13.7.tgz", - "integrity": "sha512-KIU72UmYPGk4MujZGYMFwinB7lOf2LsDNGSOC8ufevsrPLISrZbNJlWstRi3m0AMuszbH+EFSQ/r6w56RSPK6w==", - "dev": true + "integrity": "sha512-KIU72UmYPGk4MujZGYMFwinB7lOf2LsDNGSOC8ufevsrPLISrZbNJlWstRi3m0AMuszbH+EFSQ/r6w56RSPK6w==" }, "pretty-bytes": { "version": "4.0.2", @@ -11377,12 +12720,41 @@ "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", "dev": true }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "~2.0.3" + } + }, "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", "dev": true }, + "protobufjs": { + "version": "6.8.8", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz", + "integrity": "sha512-AAmHtD5pXgZfi7GMpllpO3q1Xw1OYldr+dMUlAnffGTAhqkg72WdmSY71uKBF/JuyiKs8psYbtKrhi0ASCD8qw==", + "dev": true, + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.0", + "@types/node": "^10.1.0", + "long": "^4.0.0" + } + }, "proxy-addr": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", @@ -11438,11 +12810,19 @@ "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", "dev": true }, + "ps-tree": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.1.0.tgz", + "integrity": "sha1-tCGyQUDWID8e08dplrRCewjowBQ=", + "dev": true, + "requires": { + "event-stream": "~3.3.0" + } + }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { "version": "1.1.29", @@ -11450,6 +12830,15 @@ "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==", "dev": true }, + "pstree.remy": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.0.tgz", + "integrity": "sha512-q5I5vLRMVtdWa8n/3UEzZX7Lfghzrg9eG2IKk2ENLSofKRCXVqMvMUHxCKgXNaqH/8ebhBxrqftHWnyTFweJ5Q==", + "dev": true, + "requires": { + "ps-tree": "^1.1.0" + } + }, "public-encrypt": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", @@ -11464,6 +12853,163 @@ "safe-buffer": "^5.1.2" } }, + "pug": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pug/-/pug-2.0.3.tgz", + "integrity": "sha1-ccuoJTfJWl6rftBGluQiH1Oqh44=", + "requires": { + "pug-code-gen": "^2.0.1", + "pug-filters": "^3.1.0", + "pug-lexer": "^4.0.0", + "pug-linker": "^3.0.5", + "pug-load": "^2.0.11", + "pug-parser": "^5.0.0", + "pug-runtime": "^2.0.4", + "pug-strip-comments": "^1.0.3" + } + }, + "pug-attrs": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-2.0.3.tgz", + "integrity": "sha1-owlflw5kFR972tlX7vVftdeQXRU=", + "requires": { + "constantinople": "^3.0.1", + "js-stringify": "^1.0.1", + "pug-runtime": "^2.0.4" + } + }, + "pug-code-gen": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-2.0.1.tgz", + "integrity": "sha1-CVHsgyJddNjPxHan+Zolm199BQw=", + "requires": { + "constantinople": "^3.0.1", + "doctypes": "^1.1.0", + "js-stringify": "^1.0.1", + "pug-attrs": "^2.0.3", + "pug-error": "^1.3.2", + "pug-runtime": "^2.0.4", + "void-elements": "^2.0.1", + "with": "^5.0.0" + } + }, + "pug-error": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-1.3.2.tgz", + "integrity": "sha1-U659nSm7A89WRJOgJhCfVMR/XyY=" + }, + "pug-filters": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-3.1.0.tgz", + "integrity": "sha1-JxZVVbwEwjbkqisDZiRt+gIbYm4=", + "requires": { + "clean-css": "^4.1.11", + "constantinople": "^3.0.1", + "jstransformer": "1.0.0", + "pug-error": "^1.3.2", + "pug-walk": "^1.1.7", + "resolve": "^1.1.6", + "uglify-js": "^2.6.1" + }, + "dependencies": { + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" + }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "requires": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + } + }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + } + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" + }, + "yargs": { + "version": "3.10.0", + "resolved": "http://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "requires": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + } + } + }, + "pug-lexer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-4.0.0.tgz", + "integrity": "sha1-IQwYRX7y4XYCQnQMXmR715TOwng=", + "requires": { + "character-parser": "^2.1.1", + "is-expression": "^3.0.0", + "pug-error": "^1.3.2" + } + }, + "pug-linker": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-3.0.5.tgz", + "integrity": "sha1-npp65ABWgtAn3uuWsAD4juuDoC8=", + "requires": { + "pug-error": "^1.3.2", + "pug-walk": "^1.1.7" + } + }, + "pug-load": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-2.0.11.tgz", + "integrity": "sha1-5kjlftET/iwfRdV4WOorrWvAFSc=", + "requires": { + "object-assign": "^4.1.0", + "pug-walk": "^1.1.7" + } + }, + "pug-parser": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-5.0.0.tgz", + "integrity": "sha1-45Stmz/KkxI5QK/4hcBuRKt+aOQ=", + "requires": { + "pug-error": "^1.3.2", + "token-stream": "0.0.1" + } + }, + "pug-runtime": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-2.0.4.tgz", + "integrity": "sha1-4XjhvaaKsujArPybztLFT9iM61g=" + }, + "pug-strip-comments": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-1.0.3.tgz", + "integrity": "sha1-8VWVkiBu3G+FMQ2s9K+0igJa9Z8=", + "requires": { + "pug-error": "^1.3.2" + } + }, + "pug-walk": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-1.1.7.tgz", + "integrity": "sha1-wA1cUSi6xYBr7BXSt+fNq+QlMfM=" + }, "pump": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", @@ -11583,6 +13129,26 @@ "unpipe": "1.0.0" } }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, "read-pkg": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-4.0.1.tgz", @@ -12042,8 +13608,7 @@ "regenerator-runtime": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" }, "regenerator-transform": { "version": "0.12.4", @@ -12098,6 +13663,25 @@ "resolved": "https://registry.npmjs.org/register-service-worker/-/register-service-worker-1.5.2.tgz", "integrity": "sha512-XNqSZHJsFGnvEGkg/2IrCp6G8Ya3qLj4mq0bSHil/dfdO82LOxGnMnJjAD9MYCvf/8cDCO8pL+1i65yzmP7rPQ==" }, + "registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "dev": true, + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", + "dev": true, + "requires": { + "rc": "^1.0.1" + } + }, "regjsgen": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.4.0.tgz", @@ -12188,8 +13772,7 @@ "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" }, "repeating": { "version": "2.0.1", @@ -12347,7 +13930,6 @@ "version": "1.8.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", - "dev": true, "requires": { "path-parse": "^1.0.5" } @@ -12397,6 +13979,12 @@ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, + "retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", + "dev": true + }, "rgb-regex": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", @@ -12409,6 +13997,14 @@ "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=", "dev": true }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "requires": { + "align-text": "^0.1.1" + } + }, "rimraf": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", @@ -12461,11 +14057,18 @@ "rx-lite": "*" } }, + "rxjs": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.2.2.tgz", + "integrity": "sha512-0MI8+mkKAXZUF9vMrEoPnaoHkfzBPP4IGwUYRJhIRJF6/w3uByO1e91bEHn8zd43RdkTMKiooYKmwz7RH6zfOQ==", + "requires": { + "tslib": "^1.9.0" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -12697,6 +14300,15 @@ "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", "dev": true }, + "semver-diff": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", + "dev": true, + "requires": { + "semver": "^5.0.3" + } + }, "send": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", @@ -13149,8 +14761,7 @@ "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, "source-map-resolve": { "version": "0.5.2", @@ -13284,6 +14895,15 @@ } } }, + "split": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", + "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "dev": true, + "requires": { + "through": "2" + } + }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -13387,6 +15007,16 @@ "readable-stream": "^2.0.2" } }, + "stream-combiner": { + "version": "0.2.2", + "resolved": "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz", + "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=", + "dev": true, + "requires": { + "duplexer": "~0.1.1", + "through": "~2.3.4" + } + }, "stream-each": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", @@ -13416,6 +15046,12 @@ "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", "dev": true }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=", + "dev": true + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -13452,7 +15088,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -13557,11 +15192,23 @@ } } }, + "subscriptions-transport-ws": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.15.tgz", + "integrity": "sha512-f9eBfWdHsePQV67QIX+VRhf++dn1adyC/PZHP6XI5AfKnZ4n0FW+v5omxwdHVpd4xq2ZijaHEcmlQrhBY79ZWQ==", + "dev": true, + "requires": { + "backo2": "^1.0.2", + "eventemitter3": "^3.1.0", + "iterall": "^1.2.1", + "symbol-observable": "^1.0.4", + "ws": "^5.2.0" + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -13588,6 +15235,11 @@ "util.promisify": "~1.0.0" } }, + "symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" + }, "symbol-tree": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", @@ -13625,6 +15277,43 @@ "inherits": "2" } }, + "term-size": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz", + "integrity": "sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=", + "dev": true, + "requires": { + "execa": "^0.7.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + } + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -13653,6 +15342,11 @@ } } }, + "throttle-debounce": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-2.0.1.tgz", + "integrity": "sha512-Sr6jZBlWShsAaSXKyNXyNicOrJW/KtkDqIEwHt4wYwWA2wa/q67Luhqoujg48V8hTk60wB56tYrJJn6jc2R7VA==" + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -13681,6 +15375,12 @@ "integrity": "sha1-qGLgGOP7HqLsP85dVWBc9X8kc3E=", "dev": true }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", + "dev": true + }, "timers-browserify": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", @@ -13759,6 +15459,11 @@ } } }, + "token-stream": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-0.0.1.tgz", + "integrity": "sha1-zu78cXp2xDFvEm0LnbqlXX598Bo=" + }, "topo": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.0.tgz", @@ -13774,6 +15479,26 @@ "integrity": "sha1-LmhELZ9k7HILjMieZEOsbKqVACk=", "dev": true }, + "touch": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", + "integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==", + "dev": true, + "requires": { + "nopt": "~1.0.10" + }, + "dependencies": { + "nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", + "dev": true, + "requires": { + "abbrev": "1" + } + } + } + }, "tough-cookie": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", @@ -13842,11 +15567,40 @@ "integrity": "sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==", "dev": true }, + "ts-node": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz", + "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==", + "dev": true, + "requires": { + "arrify": "^1.0.0", + "buffer-from": "^1.1.0", + "diff": "^3.1.0", + "make-error": "^1.1.1", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "source-map-support": "^0.5.6", + "yn": "^2.0.0" + }, + "dependencies": { + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "minimist": { + "version": "1.2.0", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "dev": true + } + } + }, "tslib": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", - "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", - "dev": true + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" }, "tty-browserify": { "version": "0.0.0", @@ -13930,6 +15684,12 @@ } } }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "optional": true + }, "uglifyjs-webpack-plugin": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.3.0.tgz", @@ -13976,6 +15736,32 @@ "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", "dev": true }, + "undefsafe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz", + "integrity": "sha1-Il9rngM3Zj4Njnz9aG/Cg2zKznY=", + "dev": true, + "requires": { + "debug": "^2.2.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", @@ -14042,8 +15828,7 @@ "uniq": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", - "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", - "dev": true + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=" }, "uniqs": { "version": "2.0.0", @@ -14069,6 +15854,15 @@ "imurmurhash": "^0.1.4" } }, + "unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "dev": true, + "requires": { + "crypto-random-string": "^1.0.0" + } + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -14138,12 +15932,36 @@ } } }, + "unzip-response": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", + "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", + "dev": true + }, "upath": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", "dev": true }, + "update-notifier": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", + "integrity": "sha512-gwMdhgJHGuj/+wHJJs9e6PcCszpxR1b236igrOkUofGhqJuG+amlIKwApH1IW1WWl7ovZxsX49lMBWLxSdm5Dw==", + "dev": true, + "requires": { + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, "upper-case": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", @@ -14255,6 +16073,15 @@ "requires-port": "^1.0.0" } }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "dev": true, + "requires": { + "prepend-http": "^1.0.1" + } + }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -14281,8 +16108,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "util.promisify": { "version": "1.0.0", @@ -14354,11 +16180,89 @@ "indexof": "0.0.1" } }, + "void-elements": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", + "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=" + }, "vue": { "version": "2.5.17", "resolved": "https://registry.npmjs.org/vue/-/vue-2.5.17.tgz", "integrity": "sha512-mFbcWoDIJi0w0Za4emyLiW72Jae0yjANHbCVquMKijcavBGypqlF7zHRgMa5k4sesdv7hv2rB4JPdZfR+TPfhQ==" }, + "vue-apollo": { + "version": "3.0.0-beta.25", + "resolved": "https://registry.npmjs.org/vue-apollo/-/vue-apollo-3.0.0-beta.25.tgz", + "integrity": "sha512-M7/l3h0NlFvaZ/s/wrtRiOt3xXMbaNNuteGaCY+U5D0ABrQqvCgy5mayIZHurQxbloluNkbCt18wRKAgJTAuKA==", + "requires": { + "chalk": "^2.4.1", + "throttle-debounce": "^2.0.0" + } + }, + "vue-cli-plugin-apollo": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/vue-cli-plugin-apollo/-/vue-cli-plugin-apollo-0.17.1.tgz", + "integrity": "sha512-/lCV/SIrtV+mJJLM7XSMw5zl40bMSxQUfRfJwQIMUt4Vbh51/XNUDsOBiSZ79M+cfU6j6pfdKz+LdeNBaHW7Rw==", + "dev": true, + "requires": { + "apollo-cache-inmemory": "^1.2.10", + "apollo-client": "^2.4.2", + "apollo-link": "^1.2.3", + "apollo-link-context": "^1.0.9", + "apollo-link-persisted-queries": "^0.2.1", + "apollo-link-state": "^0.4.2", + "apollo-link-ws": "^1.0.9", + "apollo-server-express": "^2.1.0", + "apollo-upload-client": "^9.0.0", + "apollo-utilities": "^1.0.21", + "chalk": "^2.4.1", + "deepmerge": "^2.2.1", + "esm": "^3.0.84", + "execa": "^1.0.0", + "express": "^4.16.3", + "graphql": "^14.0.2", + "graphql-subscriptions": "^1.0.0", + "nodemon": "^1.18.4", + "subscriptions-transport-ws": "^0.9.15", + "ts-node": "^7.0.1" + }, + "dependencies": { + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, "vue-eslint-parser": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-2.0.3.tgz", @@ -14373,6 +16277,11 @@ "lodash": "^4.17.4" } }, + "vue-gettext": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/vue-gettext/-/vue-gettext-2.1.1.tgz", + "integrity": "sha512-vbKhl7VGlVtTElNIdvpC4GKTRH1CwZmzhELcP6jS/UaFcF+yZvS5bIIAiPCQzMEU5dpAI0hIgJAxAKOMIg7UXw==" + }, "vue-gravatar": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/vue-gravatar/-/vue-gravatar-1.2.1.tgz", @@ -14439,7 +16348,6 @@ "version": "2.5.17", "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.5.17.tgz", "integrity": "sha512-63uI4syCwtGR5IJvZM0LN5tVsahrelomHtCxvRkZPJ/Tf3ADm1U1wG6KWycK3qCfqR+ygM5vewUvmJ0REAYksg==", - "dev": true, "requires": { "de-indent": "^1.0.2", "he": "^1.1.0" @@ -14448,13 +16356,12 @@ "vue-template-es2015-compiler": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.6.0.tgz", - "integrity": "sha512-x3LV3wdmmERhVCYy3quqA57NJW7F3i6faas++pJQWtknWT+n7k30F4TVdHvCLn48peTJFRvCpxs3UuFPqgeELg==", - "dev": true + "integrity": "sha512-x3LV3wdmmERhVCYy3quqA57NJW7F3i6faas++pJQWtknWT+n7k30F4TVdHvCLn48peTJFRvCpxs3UuFPqgeELg==" }, "vuetify": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-1.2.7.tgz", - "integrity": "sha512-humc4uXzIiD91lf8DDec4jPNdqM9PDFE5DgZ8YOj5WyZ/1FLkhgX+EeMyms5ZgGbMwUJvXmqZm1QWbfCa+1vVQ==" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-1.3.1.tgz", + "integrity": "sha512-n2JOEm4T0C4pHzaUSgV1+rAh/cTm2g89YIbLMyfwgencYON0kmr2ulY7vSVsBJGqv5ODDccbCzboXkfEXPYS5Q==" }, "vuetify-google-autocomplete": { "version": "2.0.0-beta.5", @@ -14466,11 +16373,6 @@ "resolved": "https://registry.npmjs.org/vuex/-/vuex-3.0.1.tgz", "integrity": "sha512-wLoqz0B7DSZtgbWL1ShIBBCjv22GV5U+vcBFox658g6V0s4wZV9P4YjCNyoHSyIBpj1f29JBoNQIqD82cR4O3w==" }, - "vuex-i18n": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/vuex-i18n/-/vuex-i18n-1.10.5.tgz", - "integrity": "sha1-Y16iIE4Ko/j9US8Pq39rmU0/Zmw=" - }, "w3c-hr-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", @@ -15286,6 +17188,51 @@ "string-width": "^1.0.2 || 2" } }, + "widest-line": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.0.tgz", + "integrity": "sha1-AUKk6KJD+IgsAjOqDgKBqnYVInM=", + "dev": true, + "requires": { + "string-width": "^2.1.1" + } + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=" + }, + "with": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/with/-/with-5.1.1.tgz", + "integrity": "sha1-+k2qktrzLE6pTtRTyB8EaGtXXf4=", + "requires": { + "acorn": "^3.1.0", + "acorn-globals": "^3.0.0" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "http://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=" + }, + "acorn-globals": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-3.1.0.tgz", + "integrity": "sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8=", + "requires": { + "acorn": "^4.0.4" + }, + "dependencies": { + "acorn": { + "version": "4.0.13", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" + } + } + } + } + }, "wordwrap": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", @@ -15540,6 +17487,17 @@ "mkdirp": "^0.5.1" } }, + "write-file-atomic": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", + "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, "ws": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", @@ -15549,6 +17507,12 @@ "async-limiter": "~1.0.0" } }, + "xdg-basedir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-3.0.0.tgz", + "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", + "dev": true + }, "xml-name-validator": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", @@ -15576,8 +17540,7 @@ "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" }, "yargs": { "version": "11.1.0", @@ -15617,6 +17580,12 @@ "fd-slicer": "~1.0.1" } }, + "yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", + "dev": true + }, "yorkie": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/yorkie/-/yorkie-2.0.0.tgz", @@ -15668,6 +17637,19 @@ "dev": true } } + }, + "zen-observable": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/zen-observable/-/zen-observable-0.8.9.tgz", + "integrity": "sha512-Y9kPzjGvIZ5jchSlqlCpBW3I82zBBL4z+ulXDRVA1NwsKzjt5kwAi+gOYIy0htNkfuehGZZtP5mRXHRV6TjDWw==" + }, + "zen-observable-ts": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/zen-observable-ts/-/zen-observable-ts-0.8.10.tgz", + "integrity": "sha512-5vqMtRggU/2GhePC9OU4sYEWOdvmayp2k3gjPf4F0mXwB3CSbbNznfDUvDJx9O2ZTa1EIXdJhPchQveFKwNXPQ==", + "requires": { + "zen-observable": "^0.8.0" + } } } } diff --git a/js/package.json b/js/package.json index 1072b6ca5..d7418fad8 100644 --- a/js/package.json +++ b/js/package.json @@ -6,22 +6,29 @@ "serve": "vue-cli-service serve", "build": "vue-cli-service build --modern", "lint": "vue-cli-service lint", - "test:unit": "vue-cli-service test:unit", - "test:e2e": "vue-cli-service test:e2e" + "test:e2e": "vue-cli-service test:e2e", + "test:unit": "vue-cli-service test:unit" }, "dependencies": { + "apollo-absinthe-upload-link": "^1.4.0", + "apollo-cache-inmemory": "^1.3.6", + "apollo-link": "^1.2.3", + "apollo-link-http": "^1.5.5", + "easygettext": "^2.7.0", + "graphql-tag": "^2.9.0", "material-design-icons": "^3.0.1", "moment": "^2.22.2", "ngeohash": "^0.6.0", "register-service-worker": "^1.4.1", "vue": "^2.5.17", + "vue-apollo": "^3.0.0-beta.25", + "vue-gettext": "^2.1.1", "vue-gravatar": "^1.2.1", "vue-markdown": "^2.2.4", "vue-router": "^3.0.1", - "vuetify": "^1.2.7", + "vuetify": "^1.3.1", "vuetify-google-autocomplete": "^2.0.0-beta.5", - "vuex": "^3.0.1", - "vuex-i18n": "^1.10.5" + "vuex": "^3.0.1" }, "devDependencies": { "@vue/cli-plugin-babel": "^3.0.5", @@ -36,6 +43,7 @@ "dotenv-webpack": "^1.5.7", "node-sass": "^4.9.3", "sass-loader": "^7.1.0", + "vue-cli-plugin-apollo": "^0.17.1", "vue-template-compiler": "^2.5.17" }, "browserslist": [ diff --git a/js/src/App.vue b/js/src/App.vue index 70cb7fd6f..7c235cb08 100644 --- a/js/src/App.vue +++ b/js/src/App.vue @@ -12,24 +12,24 @@ - + - - + {{ this.displayed_name }} - + - © Thomas Citharel {{ new Date().getFullYear() }} - Made with Elixir, Phoenix & VueJS & Vuetify with some love and some weeks + © The Mobilizon Contributors %{date} - Made with Elixir, Phoenix & VueJS & Vuetify with some love and some weeks + diff --git a/js/src/actions/login.js b/js/src/actions/login.js deleted file mode 100644 index 67f96f27f..000000000 --- a/js/src/actions/login.js +++ /dev/null @@ -1,5 +0,0 @@ -export default { - login(state, user) { - state.user = user.user; - }, -}; diff --git a/js/src/api/eventFetch.js b/js/src/api/eventFetch.js deleted file mode 100644 index 2fe6a7180..000000000 --- a/js/src/api/eventFetch.js +++ /dev/null @@ -1,29 +0,0 @@ -import { API_ORIGIN, API_PATH } from './_entrypoint'; - -const jsonLdMimeType = 'application/json'; - -export default function eventFetch(url, store, optionsarg = {}) { - const options = optionsarg; - if (typeof options.headers === 'undefined') { - options.headers = new Headers(); - } - if (options.headers.get('Accept') === null) { - options.headers.set('Accept', jsonLdMimeType); - } - - if (options.body !== 'undefined' && !(options.body instanceof FormData) && options.headers.get('Content-Type') === null) { - options.headers.set('Content-Type', jsonLdMimeType); - } - - if (store.state.user) { - options.headers.set('Authorization', `Bearer ${localStorage.getItem('token')}`); - } - - const link = url.includes(API_PATH) ? API_ORIGIN + url : API_ORIGIN + API_PATH + url; - - return fetch(link, options).then((response) => { - if (response.ok) return response; - - throw response.text(); - }); -} diff --git a/js/src/auth/index.js b/js/src/auth/index.js deleted file mode 100644 index babf71353..000000000 --- a/js/src/auth/index.js +++ /dev/null @@ -1,120 +0,0 @@ -import { API_ORIGIN, API_PATH } from '../api/_entrypoint'; -import { LOGIN_USER, LOAD_USER, CHANGE_ACTOR } from '../store/mutation-types'; - -// URL and endpoint constants -const LOGIN_URL = `${API_ORIGIN}${API_PATH}/login`; -const SIGNUP_URL = `${API_ORIGIN}${API_PATH}/users/`; -const CHECK_AUTH = `${API_ORIGIN}${API_PATH}/user/`; -const REFRESH_TOKEN = `${API_ORIGIN}${API_PATH}/token/refresh`; - -export default { - - // Send a request to the login URL and save the returned JWT - login(creds, success, error) { - fetch(LOGIN_URL, { method: 'POST', body: creds, headers: { 'Content-Type': 'application/json' } }) - .then((response) => { - if (response.status === 200) { - return response.json(); - } - throw response.json(); - }) - .then((data) => { - localStorage.setItem('token', data.token); - // localStorage.setItem('refresh_token', data.refresh_token); - return success(data); - }) - .catch(err => error(err)); - }, - - signup(creds, success, error) { - fetch(SIGNUP_URL, { method: 'POST', body: creds, headers: { 'Content-Type': 'application/json' } }) - .then((response) => { - if (response.status === 200) { - return response.json(); - } - throw response.json(); - }) - .then((data) => { - localStorage.setItem('token', data.token); - // localStorage.setItem('refresh_token', data.refresh_token); - - return success(data); - }).catch(err => error(err)); - }, - refreshToken(store, successHandler, errorHandler) { - const refreshToken = localStorage.getItem('refresh_token'); - console.log('We are refreshing the jwt token'); - fetch(REFRESH_TOKEN, { method: 'POST', body: JSON.stringify({ refresh_token: refreshToken }), headers: { 'Content-Type': 'application/json' } }) - .then((response) => { - if (response.ok) { - return response.json(); - } - return errorHandler('Error while authenticating'); - }) - .then((response) => { - console.log('We have a new token'); - this.authenticated = true; - store.commit(LOGIN_USER, response); - localStorage.setItem('token', response.token); - console.log("Let's try to auth again"); - successHandler(); - }); - }, - - // To log out, we just need to remove the token - logout(store) { - localStorage.removeItem('refresh_token'); - localStorage.removeItem('token'); - this.authenticated = false; - store.commit('LOGOUT_USER'); - }, - - jwt_decode(token) { - const base64Url = token.split('.')[1]; - const base64 = base64Url.replace('-', '+').replace('_', '/'); - return JSON.parse(window.atob(base64)); - }, - - getTokenExpirationDate(encodedToken) { - const token = this.jwt_decode(encodedToken); - if (!token.exp) { return null; } - - const date = new Date(0); - date.setUTCSeconds(token.exp); - - return date; - }, - - isTokenExpired(token) { - const expirationDate = this.getTokenExpirationDate(token); - return expirationDate < new Date(); - }, - - getUser(store, successHandler, errorHandler) { - console.log('We are checking the auth'); - this.token = localStorage.getItem('token'); - const options = {}; - options.headers = new Headers(); - options.headers.set('Authorization', `Bearer ${this.token}`); - fetch(CHECK_AUTH, options) - .then((response) => { - if (response.ok) { - return response.json(); - } - return errorHandler('Error while authenticating'); - }).then((response) => { - this.authenticated = true; - console.log(response); - store.commit(LOAD_USER, response.data); - store.commit(CHANGE_ACTOR, response.data.actors[0]); - return successHandler(); - }); - }, - - // The object to be passed as a header for authenticated requests - getAuthHeader() { - return { - Authorization: `Bearer ${localStorage.getItem('access_token')}`, - }; - }, -}; diff --git a/js/src/components/Account/Account.vue b/js/src/components/Account/Account.vue index 6df8d1f25..bcd4a0fcc 100644 --- a/js/src/components/Account/Account.vue +++ b/js/src/components/Account/Account.vue @@ -1,216 +1,210 @@ diff --git a/js/src/components/Account/Identities.vue b/js/src/components/Account/Identities.vue index f4edd957f..1e65e5437 100644 --- a/js/src/components/Account/Identities.vue +++ b/js/src/components/Account/Identities.vue @@ -15,7 +15,7 @@ @click="$router.push({ name: 'Account', params: { name: actor.username } })" > - star + star @@ -67,29 +67,26 @@ diff --git a/js/src/components/Account/Login.vue b/js/src/components/Account/Login.vue index b0230d6c0..c730c3915 100644 --- a/js/src/components/Account/Login.vue +++ b/js/src/components/Account/Login.vue @@ -60,83 +60,88 @@ diff --git a/js/src/components/Account/PasswordReset.vue b/js/src/components/Account/PasswordReset.vue index 214987075..939cc4e66 100644 --- a/js/src/components/Account/PasswordReset.vue +++ b/js/src/components/Account/PasswordReset.vue @@ -37,8 +37,6 @@ \ No newline at end of file + diff --git a/js/src/constants.js b/js/src/constants.js new file mode 100644 index 000000000..32e85cdff --- /dev/null +++ b/js/src/constants.js @@ -0,0 +1,3 @@ +export const AUTH_TOKEN = 'auth-token'; +export const AUTH_USER_ID = 'auth-user-id'; +export const AUTH_USER_ACTOR = 'auth-user-actor'; diff --git a/js/src/graphql/actor.js b/js/src/graphql/actor.js new file mode 100644 index 000000000..45c54ca99 --- /dev/null +++ b/js/src/graphql/actor.js @@ -0,0 +1,39 @@ +import gql from 'graphql-tag'; + +export const FETCH_ACTOR = gql` +query($name:String!) { + actor(preferredUsername: $name) { + url, + outboxUrl, + inboxUrl, + followingUrl, + followersUrl, + sharedInboxUrl, + name, + domain, + summary, + preferredUsername, + suspended, + avatarUrl, + bannerUrl, + organizedEvents { + uuid, + title, + description, + organizer_actor { + avatarUrl, + preferred_username, + name, + } + }, + } +} +`; + +export const LOGGED_ACTOR = gql` +query { + loggedActor { + avatarUrl, + preferredUsername, + } +}`; diff --git a/js/src/graphql/auth.js b/js/src/graphql/auth.js new file mode 100644 index 000000000..277343ec7 --- /dev/null +++ b/js/src/graphql/auth.js @@ -0,0 +1,16 @@ +import gql from 'graphql-tag'; + +export const LOGIN = gql` +mutation Login($email: String!, $password: String!) { + login(email: $email, password: $password) { + token, + user { + id, + }, + actor { + avatarUrl, + preferredUsername, + } + }, +} +`; diff --git a/js/src/graphql/category.js b/js/src/graphql/category.js new file mode 100644 index 000000000..3966ff137 --- /dev/null +++ b/js/src/graphql/category.js @@ -0,0 +1,29 @@ +import gql from 'graphql-tag'; + +export const FETCH_CATEGORIES = gql` +query { + categories { + id, + title, + description, + picture { + url, + }, + } +} +`; + +export const CREATE_CATEGORY = gql` + mutation createCategory($title: String!, $description: String!, $picture: Upload!) { + createCategory(title: $title, description: $description, picture: $picture) { + id, + title, + description, + picture { + url, + url_thumbnail + }, + }, + }, + +`; diff --git a/js/src/graphql/event.js b/js/src/graphql/event.js new file mode 100644 index 000000000..d965da74c --- /dev/null +++ b/js/src/graphql/event.js @@ -0,0 +1,109 @@ +import gql from 'graphql-tag'; + +export const FETCH_EVENT = gql` + query($uuid:UUID!) { + event(uuid: $uuid) { + uuid, + url, + local, + title, + description, + begins_on, + ends_on, + state, + status, + public, + thumbnail, + large_image, + publish_at, + # address_type, + online_address, + phone, + organizerActor { + avatarUrl, + preferredUsername, + name, + }, + attributedTo { + avatarUrl, + preferredUsername, + name, + }, + participants { + actor { + avatarUrl, + preferredUsername, + name, + }, + role, + }, + category { + title, + }, + } + } +`; + +export const FETCH_EVENTS = gql` + query { + events { + uuid, + url, + local, + title, + description, + begins_on, + ends_on, + state, + status, + public, + thumbnail, + large_image, + publish_at, + # address_type, + online_address, + phone, + organizerActor { + avatarUrl, + preferredUsername, + name, + }, + attributedTo { + avatarUrl, + preferredUsername, + name, + }, + category { + title, + }, + } + } +`; + +export const CREATE_EVENT = gql` + mutation CreateEvent( + $title: String!, + $description: String!, + $organizerActorId: Int!, + $categoryId: Int!, + $beginsOn: DateTime!, + $addressType: AddressType!, + ) { + createEvent(title: $title, description: $description, beginsOn: $beginsOn, organizerActorId: $organizerActorId, categoryId: $categoryId, addressType: $addressType) { + uuid + } +} +`; + +export const EDIT_EVENT = gql` + mutation EditEvent( + $title: String!, + $description: String!, + $organizerActorId: Int!, + $categoryId: Int!, + ) { + EditEvent(title: $title, description: $description, organizerActorId: $organizerActorId, categoryId: $categoryId) { + uuid + } +} +`; diff --git a/js/src/graphql/fragmentTypes.json b/js/src/graphql/fragmentTypes.json new file mode 100644 index 000000000..dc7e18159 --- /dev/null +++ b/js/src/graphql/fragmentTypes.json @@ -0,0 +1 @@ +{"__schema":{"types":[{"possibleTypes":[{"name":"Event"},{"name":"Actor"}],"name":"SearchResult","kind":"UNION"}]}} \ No newline at end of file diff --git a/js/src/graphql/search.js b/js/src/graphql/search.js new file mode 100644 index 000000000..6d479b919 --- /dev/null +++ b/js/src/graphql/search.js @@ -0,0 +1,17 @@ +import gql from 'graphql-tag'; + +export const SEARCH = gql` +query SearchEvents($searchText: String!) { + search(search: $searchText) { + ...on Event { + title, + uuid, + __typename + }, + ...on Actor { + preferredUsername, + __typename + } + } +} +`; \ No newline at end of file diff --git a/js/src/graphql/upload.js b/js/src/graphql/upload.js new file mode 100644 index 000000000..138b5d2d3 --- /dev/null +++ b/js/src/graphql/upload.js @@ -0,0 +1,10 @@ +import gql from 'graphql-tag'; + +export const UPLOAD_PICTURE = gql` + mutation { + uploadPicture(file: "file") { + url, + url_thumbnail + } + } +`; diff --git a/js/src/graphql/user.js b/js/src/graphql/user.js new file mode 100644 index 000000000..b489b8206 --- /dev/null +++ b/js/src/graphql/user.js @@ -0,0 +1,28 @@ +import gql from 'graphql-tag'; + +export const CREATE_USER = gql` +mutation CreateUser($email: String!, $username: String!, $password: String!) { + createUser(email: $email, username: $username, password: $password) { + preferredUsername, + user { + email, + confirmationSentAt + } + } +} +`; + +export const VALIDATE_USER = gql` +mutation ValidateUser($token: String!) { + validateUser(token: $token) { + token, + user { + id, + }, + actor { + avatarUrl, + preferredUsername, + } + } +} +`; diff --git a/js/src/i18n/en.js b/js/src/i18n/en.js deleted file mode 100644 index 6948a1f5f..000000000 --- a/js/src/i18n/en.js +++ /dev/null @@ -1,15 +0,0 @@ -export default { - home: { - welcome: 'Welcome on Mobilizon, {username}', - welcome_off: 'Welcome on Mobilizon', - events: 'Events', - groups: 'Groups', - login: 'Login', - register: 'Register', - }, - event: { - list: { - title: "Your event list", - }, - }, -}; diff --git a/js/src/i18n/fr.js b/js/src/i18n/fr.js deleted file mode 100644 index 25b6fbcc6..000000000 --- a/js/src/i18n/fr.js +++ /dev/null @@ -1,20 +0,0 @@ -export default { - home: { - welcome: 'Bienvenue sur Mobilizon, {username}!', - welcome_off: 'Bienvenue sur Mobilizon', - events: 'Événements', - groups: 'Groupes', - login: 'Se connecter', - register: "S'inscrire", - }, - event: { - list: { - title: "Votre liste d'événements", - }, - }, - session: { - error: { - bad_login: 'Erreur lors de la connexion : Votre nom d\'utilisateur ou votre mot de passe est incorrect', - }, - }, -}; diff --git a/js/src/i18n/index.js b/js/src/i18n/index.js deleted file mode 100644 index d20a035c0..000000000 --- a/js/src/i18n/index.js +++ /dev/null @@ -1,6 +0,0 @@ -import en from './en'; -import fr from './fr'; - -export default { - en, fr, -}; diff --git a/js/src/i18n/locale/en_US/LC_MESSAGES/app.po b/js/src/i18n/locale/en_US/LC_MESSAGES/app.po new file mode 100644 index 000000000..b8924e022 --- /dev/null +++ b/js/src/i18n/locale/en_US/LC_MESSAGES/app.po @@ -0,0 +1,30 @@ +# English translations for mobilizon package. +# Copyright (C) 2018 THE mobilizon'S COPYRIGHT HOLDER +# This file is distributed under the same license as the mobilizon package. +# Automatically generated, 2018. +# +msgid "" +msgstr "" +"Project-Id-Version: mobilizon 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-10-24 16:25+0200\n" +"PO-Revision-Date: 2018-10-24 16:25+0200\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: en_US\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: src/components/Account/Register.vue:70 +msgid "A validation email was sent to %{email}" +msgstr "A validation email was sent to %{email}" + +#: src/components/Account/Register.vue:71 +msgid "Before you can login, you need to click on the link inside it to validate your account" +msgstr "Before you can login, you need to click on the link inside it to validate your account" + +#: src/components/Home.vue:14 +msgid "Register" +msgstr "Register" diff --git a/js/src/i18n/locale/fr_FR/LC_MESSAGES/app.po b/js/src/i18n/locale/fr_FR/LC_MESSAGES/app.po new file mode 100644 index 000000000..45b560750 --- /dev/null +++ b/js/src/i18n/locale/fr_FR/LC_MESSAGES/app.po @@ -0,0 +1,30 @@ +# French translations for mobilizon package. +# Copyright (C) 2018 THE mobilizon'S COPYRIGHT HOLDER +# This file is distributed under the same license as the mobilizon package. +# Automatically generated, 2018. +# +msgid "" +msgstr "" +"Project-Id-Version: mobilizon 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-10-24 16:25+0200\n" +"PO-Revision-Date: 2018-10-24 16:25+0200\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: fr_FR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: src/components/Account/Register.vue:70 +msgid "A validation email was sent to %{email}" +msgstr "" + +#: src/components/Account/Register.vue:71 +msgid "Before you can login, you need to click on the link inside it to validate your account" +msgstr "" + +#: src/components/Home.vue:14 +msgid "Register" +msgstr "S'inscrire" diff --git a/js/src/i18n/locale/fr_FR/LC_MESSAGES/app.po~ b/js/src/i18n/locale/fr_FR/LC_MESSAGES/app.po~ new file mode 100644 index 000000000..131065ac7 --- /dev/null +++ b/js/src/i18n/locale/fr_FR/LC_MESSAGES/app.po~ @@ -0,0 +1,30 @@ +# French translations for mobilizon package. +# Copyright (C) 2018 THE mobilizon'S COPYRIGHT HOLDER +# This file is distributed under the same license as the mobilizon package. +# Automatically generated, 2018. +# +msgid "" +msgstr "" +"Project-Id-Version: mobilizon 0.1.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-10-24 16:25+0200\n" +"PO-Revision-Date: 2018-10-24 16:25+0200\n" +"Last-Translator: Automatically generated\n" +"Language-Team: none\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +#: src/components/Account/Register.vue:70 +msgid "A validation email was sent to %{email}" +msgstr "" + +#: src/components/Account/Register.vue:71 +msgid "Before you can login, you need to click on the link inside it to validate your account" +msgstr "" + +#: src/components/Home.vue:14 +msgid "Register" +msgstr "S'inscrire" diff --git a/js/src/i18n/translations.json b/js/src/i18n/translations.json new file mode 100644 index 000000000..03e0894c3 --- /dev/null +++ b/js/src/i18n/translations.json @@ -0,0 +1 @@ +{"en_US":{"A validation email was sent to %{email}":"A validation email was sent to %{email}","Before you can login, you need to click on the link inside it to validate your account":"Before you can login, you need to click on the link inside it to validate your account","Register":"Register"},"fr_FR":{"Register":"S'inscrire"}} \ No newline at end of file diff --git a/js/src/main.js b/js/src/main.js index d7f3406ea..59584c5a6 100644 --- a/js/src/main.js +++ b/js/src/main.js @@ -5,60 +5,37 @@ import Vue from 'vue'; import VueMarkdown from 'vue-markdown'; import Vuetify from 'vuetify'; import moment from 'moment'; -import VuexI18n from 'vuex-i18n'; +import GetTextPlugin from 'vue-gettext'; import 'material-design-icons/iconfont/material-icons.css'; import 'vuetify/dist/vuetify.min.css'; -import App from './App.vue'; -import router from './router'; -import store from './store'; -import translations from './i18n'; -import auth from './auth'; +import App from '@/App.vue'; +import router from '@/router'; +// import store from './store'; +import translations from '@/i18n/translations.json'; +import { createProvider } from './vue-apollo'; Vue.config.productionTip = false; Vue.use(VueMarkdown); Vue.use(Vuetify); -let language = window.navigator.userLanguage || window.navigator.language; +const language = window.navigator.userLanguage || window.navigator.language; moment.locale(language); Vue.filter('formatDate', value => (value ? moment(String(value)).format('LLLL') : null)); Vue.filter('formatDay', value => (value ? moment(String(value)).format('LL') : null)); -if (!(language in translations)) { - [language] = language.split('-', 1); -} - -Vue.use(VuexI18n.plugin, store); - -Object.entries(translations).forEach((key) => { - Vue.i18n.add(key[0], key[1]); +Vue.use(GetTextPlugin, { + translations, + defaultLanguage: 'en_US', }); -Vue.i18n.set(language); -Vue.i18n.fallback('en'); - -router.beforeEach((to, from, next) => { - if (to.matched.some(record => record.meta.requiredAuth) && !store.state.user) { - next({ - name: 'Login', - query: { redirect: to.fullPath }, - }); - } else { - next(); - } -}); - -auth.getUser(store, () => {}, (error) => { - console.warn(error); -}); - -console.log('store', store); +Vue.config.language = language.replace('-', '_'); /* eslint-disable no-new */ new Vue({ el: '#app', router, - store, template: '', + apolloProvider: createProvider(), components: { App }, }); diff --git a/js/src/store/index.js b/js/src/store/index.js deleted file mode 100644 index 8adce48ce..000000000 --- a/js/src/store/index.js +++ /dev/null @@ -1,44 +0,0 @@ -import Vue from 'vue'; -import Vuex from 'vuex'; -import { LOGIN_USER, LOGOUT_USER, LOAD_USER, CHANGE_ACTOR } from './mutation-types'; - -const state = { - isLogged: !!localStorage.getItem('token'), - user: false, - actor: false, - defaultActor: localStorage.getItem('defaultActor') || null, -}; - -/* eslint-disable */ -const mutations = { - [LOGIN_USER](state, user) { - state.isLogged = true; - state.user = user; - }, - - [LOAD_USER](state, user) { - state.user = user; - }, - - [LOGOUT_USER](state) { - state.isLogged = false; - state.user = null; - }, - - [CHANGE_ACTOR](state, actor) { - state.actor = actor; - state.defaultActor = actor.username; - } -}; -/* eslint-enable */ - -Vue.use(Vuex); -const store = new Vuex.Store({ state, mutations }); - -store.subscribe((mutation, localState) => { - if (mutation === CHANGE_ACTOR) { - localStorage.setItem('defaultActor', localState.actor.username); - } -}); - -export default store; diff --git a/js/src/store/mutation-types.js b/js/src/store/mutation-types.js deleted file mode 100644 index 8f2bec90e..000000000 --- a/js/src/store/mutation-types.js +++ /dev/null @@ -1,4 +0,0 @@ -export const LOGIN_USER = 'LOGIN_USER'; -export const LOAD_USER = 'LOAD_USER'; -export const LOGOUT_USER = 'LOGOUT_USER'; -export const CHANGE_ACTOR = 'CHANGE_ACTOR'; diff --git a/js/src/vue-apollo.js b/js/src/vue-apollo.js new file mode 100644 index 000000000..7caf0a69b --- /dev/null +++ b/js/src/vue-apollo.js @@ -0,0 +1,135 @@ +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import { ApolloLink } from 'apollo-link'; +import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'; +import { createLink } from 'apollo-absinthe-upload-link'; +import { createApolloClient, restartWebsockets } from 'vue-cli-plugin-apollo/graphql-client'; +import { AUTH_TOKEN } from './constants'; + +// Install the vue plugin +Vue.use(VueApollo); + +// Http endpoint +const httpEndpoint = process.env.VUE_APP_GRAPHQL_HTTP || 'http://localhost:4000/api'; + +const fragmentMatcher = new IntrospectionFragmentMatcher({ + introspectionQueryResultData: { + __schema: { + types: [ + { + kind: 'UNION', + name: 'SearchResult', + possibleTypes: [ + { name: 'Event' }, + { name: 'Actor' }, + ], + }, // this is an example, put your INTERFACE and UNION kinds here! + ], + }, + }, +}); + +const cache = new InMemoryCache({ fragmentMatcher }); + + +const authMiddleware = new ApolloLink((operation, forward) => { + // add the authorization to the headers + const token = localStorage.getItem(AUTH_TOKEN); + operation.setContext({ + headers: { + authorization: token ? `Bearer ${token}` : null, + }, + }); + + return forward(operation); +}); + +const uploadLink = createLink({ + uri: httpEndpoint, +}); + +// const link = ApolloLink.from([ +// uploadLink, +// authMiddleware, +// HttpLink, +// ]); + +const link = authMiddleware.concat(uploadLink); + +// Config +const defaultOptions = { + // You can use `https` for secure connection (recommended in production) + httpEndpoint, + // You can use `wss` for secure connection (recommended in production) + // Use `null` to disable subscriptions + // wsEndpoint: process.env.VUE_APP_GRAPHQL_WS || 'ws://localhost:4000/graphql', + // LocalStorage token + tokenName: AUTH_TOKEN, + // Enable Automatic Query persisting with Apollo Engine + persisting: false, + // Use websockets for everything (no HTTP) + // You need to pass a `wsEndpoint` for this to work + websocketsOnly: false, + // Is being rendered on the server? + ssr: false, + cache, + link, + defaultHttpLink: false, +}; + +// Call this in the Vue app file +export function createProvider(options = {}) { + // Create apollo client + const { apolloClient, wsClient } = createApolloClient({ + ...defaultOptions, + ...options, + }); + apolloClient.wsClient = wsClient; + + // Create vue apollo provider + const apolloProvider = new VueApollo({ + defaultClient: apolloClient, + link, + cache, + connectToDevTools: true, + defaultOptions: { + $query: { + // fetchPolicy: 'cache-and-network', + }, + }, + errorHandler(error) { + // eslint-disable-next-line no-console + console.log('%cError', 'background: red; color: white; padding: 2px 4px; border-radius: 3px; font-weight: bold;', error.message); + }, + }); + + return apolloProvider; +} + +// Manually call this when user log in +export async function onLogin(apolloClient, token) { + if (typeof localStorage !== 'undefined' && token) { + localStorage.setItem(AUTH_TOKEN, token); + } + if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient); + try { + await apolloClient.resetStore(); + } catch (e) { + // eslint-disable-next-line no-console + console.log('%cError on cache reset (login)', 'color: orange;', e.message); + } +} + +// Manually call this when user log out +export async function onLogout(apolloClient) { + if (typeof localStorage !== 'undefined') { + localStorage.removeItem(AUTH_TOKEN); + } + if (apolloClient.wsClient) restartWebsockets(apolloClient.wsClient); + try { + await apolloClient.resetStore(); + } catch (e) { + // eslint-disable-next-line no-console + console.log('%cError on cache reset (logout)', 'color: orange;', e.message); + } +} diff --git a/js/tests/unit/.eslintrc.js b/js/tests/unit/.eslintrc.js index 74fe62701..00c5cf0bd 100644 --- a/js/tests/unit/.eslintrc.js +++ b/js/tests/unit/.eslintrc.js @@ -1,8 +1,8 @@ module.exports = { env: { - mocha: true + mocha: true, }, rules: { - 'import/no-extraneous-dependencies': 'off' - } -} \ No newline at end of file + 'import/no-extraneous-dependencies': 'off', + }, +}; diff --git a/lib/mix/tasks/create_bot.ex b/lib/mix/tasks/create_bot.ex index ca61ed0c4..1fe7066f8 100644 --- a/lib/mix/tasks/create_bot.ex +++ b/lib/mix/tasks/create_bot.ex @@ -14,7 +14,7 @@ defmodule Mix.Tasks.CreateBot do def run([email, name, summary, type, url]) do Mix.Task.run("app.start") - with {:ok, %User{} = user} <- Actors.find_by_email(email), + with {:ok, %User{} = user} <- Actors.get_user_by_email(email, true), actor <- Actors.register_bot_account(%{name: name, summary: summary}), {:ok, %Bot{} = bot} <- Actors.create_bot(%{ diff --git a/lib/mobilizon/actors/actors.ex b/lib/mobilizon/actors/actors.ex index 4c2d6ca94..6dfb773fa 100644 --- a/lib/mobilizon/actors/actors.ex +++ b/lib/mobilizon/actors/actors.ex @@ -11,6 +11,14 @@ defmodule Mobilizon.Actors do alias Mobilizon.Service.ActivityPub + def data() do + Dataloader.Ecto.new(Repo, query: &query/2) + end + + def query(queryable, _params) do + queryable + end + @doc """ Returns the list of actors. @@ -42,6 +50,28 @@ defmodule Mobilizon.Actors do Repo.get!(Actor, id) end + @doc """ + Returns the associated actor for an user, either the default set one or the first found + """ + @spec get_actor_for_user(%Mobilizon.Actors.User{}) :: %Mobilizon.Actors.Actor{} + def get_actor_for_user(%Mobilizon.Actors.User{} = user) do + case user.default_actor_id do + nil -> get_first_actor_for_user(user) + actor_id -> get_actor!(actor_id) + end + end + + @doc """ + Returns the first actor found for an user + + Useful when the user has not defined default actor + + Raises `Ecto.NoResultsError` if no Actor is found for this ID + """ + defp get_first_actor_for_user(%Mobilizon.Actors.User{id: id} = _user) do + Repo.one!(from(a in Actor, where: a.user_id == ^id)) + end + def get_actor_with_everything!(id) do actor = Repo.get!(Actor, id) Repo.preload(actor, :organized_events) @@ -162,10 +192,13 @@ defmodule Mobilizon.Actors do Repo.all(User) end - def list_users_with_actors do - users = Repo.all(User) - Repo.preload(users, :actors) - end + @doc """ + List users with their associated actors. No reason for that, so removed + """ + # def list_users_with_actors do + # users = Repo.all(User) + # Repo.preload(users, :actors) + # end defp blank?(""), do: nil defp blank?(n), do: n @@ -226,6 +259,14 @@ defmodule Mobilizon.Actors do Repo.preload(user, :actors) end + @spec get_user_with_actor(integer()) :: %User{} + def get_user_with_actor(id) do + case Repo.get(User, id) do + nil -> {:error, "User with ID #{id} not found"} + user -> {:ok, Repo.preload(user, :actors)} + end + end + def get_actor_by_url(url) do Repo.get_by(Actor, url: url) end @@ -297,10 +338,17 @@ defmodule Mobilizon.Actors do @doc """ Find actors by their name or displayed name """ - def find_actors_by_username_or_name(username) do + def find_actors_by_username_or_name(username, page \\ 1, limit \\ 10) + def find_actors_by_username_or_name("", page, limit), do: [] + + def find_actors_by_username_or_name(username, page, limit) do + start = (page - 1) * limit + Repo.all( from( a in Actor, + limit: ^limit, + offset: ^start, where: ilike(a.preferred_username, ^like_sanitize(username)) or ilike(a.name, ^like_sanitize(username)) @@ -340,19 +388,6 @@ defmodule Mobilizon.Actors do end end - @doc """ - Get an user by email - """ - def find_by_email(email) do - case Repo.preload(Repo.get_by(User, email: email), :actors) do - nil -> - {:error, nil} - - user -> - {:ok, user} - end - end - @doc """ Authenticate user """ @@ -390,31 +425,37 @@ defmodule Mobilizon.Actors do nil end - actor = - Mobilizon.Actors.Actor.registration_changeset(%Mobilizon.Actors.Actor{}, %{ - preferred_username: username, - domain: nil, - keys: pem, - avatar_url: avatar - }) - - user = - Mobilizon.Actors.User.registration_changeset(%Mobilizon.Actors.User{}, %{ - email: email, - password: password - }) - - actor_with_user = Ecto.Changeset.put_assoc(actor, :user, user) - - try do - Mobilizon.Repo.insert!(actor_with_user) - find_by_email(email) - rescue - e in Ecto.InvalidChangesetError -> - {:error, e.changeset} + with actor_changeset <- + Mobilizon.Actors.Actor.registration_changeset(%Mobilizon.Actors.Actor{}, %{ + preferred_username: username, + domain: nil, + keys: pem, + avatar_url: avatar + }), + {:ok, %Mobilizon.Actors.Actor{id: id} = actor} <- Mobilizon.Repo.insert(actor_changeset), + user_changeset <- + Mobilizon.Actors.User.registration_changeset(%Mobilizon.Actors.User{}, %{ + email: email, + password: password, + default_actor_id: id + }), + {:ok, %Mobilizon.Actors.User{} = user} <- Mobilizon.Repo.insert(user_changeset) do + {:ok, Map.put(actor, :user, user)} + else + {:error, %Ecto.Changeset{} = changeset} -> + handle_actor_user_changeset(changeset) end end + defp handle_actor_user_changeset(changeset) do + changeset = + Ecto.Changeset.traverse_errors(changeset, fn + {msg, opts} -> msg + msg -> msg + end) + {:error, hd(Map.get(changeset, :email))} + end + def register_bot_account(%{name: name, summary: summary}) do key = :public_key.generate_key({:rsa, 2048, 65_537}) entry = :public_key.pem_entry_encode(:RSAPrivateKey, key) @@ -466,9 +507,16 @@ defmodule Mobilizon.Actors do iex> get_user_by_email(user, wrong_email) {:error, nil} """ - def get_user_by_email(email) do - case Repo.get_by(User, email: email) do - nil -> {:error, nil} + def get_user_by_email(email, activated \\ nil) do + query = + case activated do + nil -> from(u in User, where: u.email == ^email) + true -> from(u in User, where: u.email == ^email and not is_nil(u.confirmed_at)) + false -> from(u in User, where: u.email == ^email and is_nil(u.confirmed_at)) + end + + case Repo.one(query) do + nil -> {:error, :user_not_found} user -> {:ok, user} end end diff --git a/lib/mobilizon/actors/service/activation.ex b/lib/mobilizon/actors/service/activation.ex index 6487184d8..24ac0d2c7 100644 --- a/lib/mobilizon/actors/service/activation.ex +++ b/lib/mobilizon/actors/service/activation.ex @@ -3,6 +3,7 @@ defmodule Mobilizon.Actors.Service.Activation do alias Mobilizon.{Mailer, Repo, Actors.User, Actors} alias Mobilizon.Email.User, as: UserEmail + alias Mobilizon.Actors.Service.Tools require Logger @@ -15,7 +16,8 @@ defmodule Mobilizon.Actors.Service.Activation do "confirmation_sent_at" => nil, "confirmation_token" => nil }) do - {:ok, Repo.preload(user, :actors)} + Logger.info("User #{user.email} has been confirmed") + {:ok, user} else _err -> {:error, "Invalid token"} @@ -23,8 +25,12 @@ defmodule Mobilizon.Actors.Service.Activation do end def resend_confirmation_email(%User{} = user, locale \\ "en") do - {:ok, user} = Actors.update_user(user, %{"confirmation_sent_at" => DateTime.utc_now()}) - send_confirmation_email(user, locale) + with :ok <- Tools.we_can_send_email(user, :confirmation_sent_at), + {:ok, user} <- Actors.update_user(user, %{"confirmation_sent_at" => DateTime.utc_now()}) do + send_confirmation_email(user, locale) + Logger.info("Sent confirmation email again to #{user.email}") + {:ok, user.email} + end end def send_confirmation_email(%User{} = user, locale \\ "en") do diff --git a/lib/mobilizon/actors/service/reset_password.ex b/lib/mobilizon/actors/service/reset_password.ex index 8521c6cce..6c41e74fd 100644 --- a/lib/mobilizon/actors/service/reset_password.ex +++ b/lib/mobilizon/actors/service/reset_password.ex @@ -5,6 +5,7 @@ defmodule Mobilizon.Actors.Service.ResetPassword do alias Mobilizon.{Mailer, Repo, Actors.User} alias Mobilizon.Email.User, as: UserEmail + alias Mobilizon.Actors.Service.Tools @doc """ Check that the provided token is correct and update provided password @@ -20,7 +21,7 @@ defmodule Mobilizon.Actors.Service.ResetPassword do "reset_password_token" => nil }) ) do - {:ok, Repo.preload(user, :actors)} + {:ok, user} else err -> {:error, :invalid_token} @@ -32,11 +33,11 @@ defmodule Mobilizon.Actors.Service.ResetPassword do """ @spec send_password_reset_email(User.t(), String.t()) :: tuple def send_password_reset_email(%User{} = user, locale \\ "en") do - with :ok <- we_can_send_email(user), + with :ok <- Tools.we_can_send_email(user, :reset_password_sent_at), {:ok, %User{} = user_updated} <- Repo.update( User.send_password_reset_changeset(user, %{ - "reset_password_token" => random_string(30), + "reset_password_token" => Tools.random_string(30), "reset_password_sent_at" => DateTime.utc_now() }) ) do @@ -50,28 +51,4 @@ defmodule Mobilizon.Actors.Service.ResetPassword do {:error, reason} -> {:error, reason} end end - - @spec random_string(integer) :: String.t() - defp random_string(length) do - length - |> :crypto.strong_rand_bytes() - |> Base.url_encode64() - end - - @spec we_can_send_email(User.t()) :: boolean - defp we_can_send_email(%User{} = user) do - case user.reset_password_sent_at do - nil -> - :ok - - _ -> - case Timex.before?(Timex.shift(user.reset_password_sent_at, hours: 1), DateTime.utc_now()) do - true -> - :ok - - false -> - {:error, :email_too_soon} - end - end - end end diff --git a/lib/mobilizon/actors/service/tools.ex b/lib/mobilizon/actors/service/tools.ex new file mode 100644 index 000000000..52634254d --- /dev/null +++ b/lib/mobilizon/actors/service/tools.ex @@ -0,0 +1,27 @@ +defmodule Mobilizon.Actors.Service.Tools do + alias Mobilizon.Actors.User + + @spec we_can_send_email(User.t()) :: boolean + def we_can_send_email(%User{} = user, key \\ :reset_password_sent_at) do + case Map.get(user, key) do + nil -> + :ok + + _ -> + case Timex.before?(Timex.shift(Map.get(user, key), hours: 1), DateTime.utc_now()) do + true -> + :ok + + false -> + {:error, :email_too_soon} + end + end + end + + @spec random_string(integer) :: String.t() + def random_string(length) do + length + |> :crypto.strong_rand_bytes() + |> Base.url_encode64() + end +end diff --git a/lib/mobilizon/actors/user.ex b/lib/mobilizon/actors/user.ex index b0fc5c6e9..a10b673c4 100644 --- a/lib/mobilizon/actors/user.ex +++ b/lib/mobilizon/actors/user.ex @@ -12,6 +12,7 @@ defmodule Mobilizon.Actors.User do field(:password, :string, virtual: true) field(:role, :integer, default: 0) has_many(:actors, Actor) + field(:default_actor_id, :integer) field(:confirmed_at, :utc_datetime) field(:confirmation_sent_at, :utc_datetime) field(:confirmation_token, :string) @@ -27,6 +28,7 @@ defmodule Mobilizon.Actors.User do |> cast(attrs, [ :email, :role, + :default_actor_id, :password_hash, :confirmed_at, :confirmation_sent_at, @@ -49,7 +51,8 @@ defmodule Mobilizon.Actors.User do struct |> changeset(params) |> cast(params, ~w(password)a, []) - |> validate_required([:email, :password]) + |> validate_required([:email, :password, :default_actor_id]) + |> validate_email() |> validate_length( :password, min: 6, @@ -92,6 +95,19 @@ defmodule Mobilizon.Actors.User do end end + defp validate_email(changeset) do + case changeset do + %Ecto.Changeset{valid?: true, changes: %{email: email}} -> + case EmailChecker.valid?(email) do + false -> add_error(changeset, :email, "Email doesn't fit required format") + _ -> changeset + end + + _ -> + changeset + end + end + defp random_string(length) do length |> :crypto.strong_rand_bytes() diff --git a/lib/mobilizon/email/user.ex b/lib/mobilizon/email/user.ex index d4882e294..35dc8be5e 100644 --- a/lib/mobilizon/email/user.ex +++ b/lib/mobilizon/email/user.ex @@ -16,7 +16,7 @@ defmodule Mobilizon.Email.User do base_email() |> to(user.email) |> subject( - gettext("Peakweaver: Confirmation instructions for %{instance}", instance: instance_url) + gettext("Mobilizon: Confirmation instructions for %{instance}", instance: instance_url) ) |> put_header("Reply-To", get_config(:reply_to)) |> assign(:token, user.confirmation_token) @@ -32,7 +32,7 @@ defmodule Mobilizon.Email.User do |> to(user.email) |> subject( gettext( - "Peakweaver: Reset your password on %{instance} instructions", + "Mobilizon: Reset your password on %{instance} instructions", instance: instance_url ) ) diff --git a/lib/mobilizon/events/category.ex b/lib/mobilizon/events/category.ex index c69218665..a9afeea2f 100644 --- a/lib/mobilizon/events/category.ex +++ b/lib/mobilizon/events/category.ex @@ -5,10 +5,11 @@ defmodule Mobilizon.Events.Category do use Ecto.Schema import Ecto.Changeset alias Mobilizon.Events.Category + use Arc.Ecto.Schema schema "categories" do field(:description, :string) - field(:picture, :string) + field(:picture, MobilizonWeb.Uploaders.Category.Type) field(:title, :string, null: false) timestamps() @@ -17,7 +18,8 @@ defmodule Mobilizon.Events.Category do @doc false def changeset(%Category{} = category, attrs) do category - |> cast(attrs, [:title, :description, :picture]) + |> cast(attrs, [:title, :description]) + |> cast_attachments(attrs, [:picture]) |> validate_required([:title]) |> unique_constraint(:title) end diff --git a/lib/mobilizon/events/event.ex b/lib/mobilizon/events/event.ex index 804dcffaf..10ee6fb03 100644 --- a/lib/mobilizon/events/event.ex +++ b/lib/mobilizon/events/event.ex @@ -86,7 +86,6 @@ defmodule Mobilizon.Events.Event do |> validate_required([ :title, :begins_on, - :ends_on, :organizer_actor_id, :category_id, :url, diff --git a/lib/mobilizon/events/events.ex b/lib/mobilizon/events/events.ex index bd110dbfa..cf3164bf9 100644 --- a/lib/mobilizon/events/events.ex +++ b/lib/mobilizon/events/events.ex @@ -6,23 +6,16 @@ defmodule Mobilizon.Events do import Ecto.Query, warn: false alias Mobilizon.Repo - alias Mobilizon.Events.Event - alias Mobilizon.Events.Comment + alias Mobilizon.Events.{Event, Comment, Participant} alias Mobilizon.Actors.Actor alias Mobilizon.Addresses.Address - @doc """ - Returns the list of events. + def data() do + Dataloader.Ecto.new(Mobilizon.Repo, query: &query/2) + end - ## Examples - - iex> list_events() - [%Event{}, ...] - - """ - def list_events do - events = Repo.all(Event) - Repo.preload(events, [:organizer_actor]) + def query(queryable, _params) do + queryable end def get_events_for_actor(%Actor{id: actor_id} = _actor, page \\ 1, limit \\ 10) do @@ -179,15 +172,47 @@ defmodule Mobilizon.Events do ]) end + @doc """ + Returns the list of events. + + ## Examples + + iex> list_events() + [%Event{}, ...] + + """ + def list_events(page \\ 1, limit \\ 10) do + start = (page - 1) * limit + + query = + from(e in Event, + limit: ^limit, + offset: ^start, + preload: [:organizer_actor] + ) + + Repo.all(query) + end + @doc """ Find events by name """ - def find_events_by_name(name) when name == "", do: [] + def find_events_by_name(name, page \\ 1, limit \\ 10) + def find_events_by_name("", page, limit), do: list_events(page, limit) - def find_events_by_name(name) do + def find_events_by_name(name, page, limit) do name = String.trim(name) - events = Repo.all(from(a in Event, where: ilike(a.title, ^like_sanitize(name)))) - Repo.preload(events, [:organizer_actor]) + start = (page - 1) * limit + + query = + from(e in Event, + limit: ^limit, + offset: ^start, + where: ilike(e.title, ^like_sanitize(name)), + preload: [:organizer_actor] + ) + + Repo.all(query) end @doc """ @@ -210,9 +235,16 @@ defmodule Mobilizon.Events do """ def create_event(attrs \\ %{}) do - case %Event{} |> Event.changeset(attrs) |> Repo.insert() do - {:ok, %Event{} = event} -> {:ok, Repo.preload(event, [:organizer_actor])} - err -> err + with {:ok, %Event{} = event} <- %Event{} |> Event.changeset(attrs) |> Repo.insert(), + {:ok, %Participant{} = _participant} <- + %Participant{} + |> Participant.changeset(%{ + actor_id: attrs.organizer_actor_id, + role: 4, + event_id: event.id + }) + |> Repo.insert() do + {:ok, Repo.preload(event, [:organizer_actor])} end end @@ -475,6 +507,27 @@ defmodule Mobilizon.Events do Repo.all(Participant) end + @doc """ + Returns the list of participants for an event. + + ## Examples + + iex> list_participants_for_event(someuuid) + [%Participant{}, ...] + + """ + def list_participants_for_event(uuid) do + Repo.all( + from( + p in Participant, + join: e in Event, + on: p.event_id == e.id, + where: e.uuid == ^uuid, + preload: [:actor] + ) + ) + end + @doc """ Gets a single participant. diff --git a/lib/mobilizon_web/auth_pipeline.ex b/lib/mobilizon_web/auth_pipeline.ex index 5ddcce967..1f2a753a0 100644 --- a/lib/mobilizon_web/auth_pipeline.ex +++ b/lib/mobilizon_web/auth_pipeline.ex @@ -8,7 +8,7 @@ defmodule MobilizonWeb.AuthPipeline do module: MobilizonWeb.Guardian, error_handler: MobilizonWeb.AuthErrorHandler - plug(Guardian.Plug.VerifyHeader, claims: %{"typ" => "access"}) - plug(Guardian.Plug.EnsureAuthenticated) - plug(Guardian.Plug.LoadResource, ensure: true) + plug(Guardian.Plug.VerifyHeader, realm: "Bearer") + plug(Guardian.Plug.LoadResource, allow_blank: true) + plug(MobilizonWeb.Context) end diff --git a/lib/mobilizon_web/context.ex b/lib/mobilizon_web/context.ex new file mode 100644 index 000000000..04884dc59 --- /dev/null +++ b/lib/mobilizon_web/context.ex @@ -0,0 +1,20 @@ +defmodule MobilizonWeb.Context do + @behaviour Plug + + import Plug.Conn + require Logger + + def init(opts) do + opts + end + + def call(conn, _) do + case Guardian.Plug.current_resource(conn) do + nil -> + conn + + user -> + put_private(conn, :absinthe, %{context: %{current_user: user}}) + end + end +end diff --git a/lib/mobilizon_web/controllers/actor_controller.ex b/lib/mobilizon_web/controllers/actor_controller.ex deleted file mode 100644 index a933905d2..000000000 --- a/lib/mobilizon_web/controllers/actor_controller.ex +++ /dev/null @@ -1,79 +0,0 @@ -defmodule MobilizonWeb.ActorController do - @moduledoc """ - Controller for Actors - """ - use MobilizonWeb, :controller - - alias Mobilizon.Actors - alias Mobilizon.Actors.{Actor, User} - alias Mobilizon.Service.ActivityPub - - action_fallback(MobilizonWeb.FallbackController) - - def index(conn, _params) do - actors = Actors.list_actors() - render(conn, "index.json", actors: actors) - end - - def create(conn, %{"actor" => actor_params}) do - with %User{} = user <- Guardian.Plug.current_resource(conn), - actor_params <- Map.put(actor_params, "user_id", user.id), - actor_params <- Map.put(actor_params, "keys", keys_for_account()), - {:ok, %Actor{} = actor} <- Actors.create_actor(actor_params) do - conn - |> put_status(:created) - |> put_resp_header("location", actor_path(conn, :show, actor.preferred_username)) - |> render("show_basic.json", actor: actor) - end - end - - defp keys_for_account() do - key = :public_key.generate_key({:rsa, 2048, 65_537}) - entry = :public_key.pem_entry_encode(:RSAPrivateKey, key) - - [entry] - |> :public_key.pem_encode() - |> String.trim_trailing() - end - - def show(conn, %{"name" => name}) do - with %Actor{} = actor <- Actors.get_actor_by_name_with_everything(name) do - render(conn, "show.json", actor: actor) - else - nil -> - send_resp(conn, :not_found, "") - end - end - - @email_regex ~r/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/ - def search(conn, %{"name" => name}) do - # find already saved accounts - case Actors.search(name) do - {:ok, actors} -> - render(conn, "index.json", actors: actors) - - {:error, err} -> - json(conn, err) - end - end - - def update(conn, %{"name" => name, "actor" => actor_params}) do - actor = Actors.get_local_actor_by_name(name) - - with {:ok, %Actor{} = actor} <- Actors.update_actor(actor, actor_params) do - render(conn, "show_basic.json", actor: actor) - end - end - - # def delete(conn, %{"id" => id_str}) do - # {id, _} = Integer.parse(id_str) - # if Guardian.Plug.current_resource(conn).actor.id == id do - # actor = Actors.get_actor!(id) - # with {:ok, %Actor{}} <- Actors.delete_actor(actor) do - # send_resp(conn, :no_content, "") - # end - # else - # send_resp(conn, 401, "") - # end - # end -end diff --git a/lib/mobilizon_web/controllers/address_controller.ex b/lib/mobilizon_web/controllers/address_controller.ex deleted file mode 100644 index 006e2acdb..000000000 --- a/lib/mobilizon_web/controllers/address_controller.ex +++ /dev/null @@ -1,78 +0,0 @@ -defmodule MobilizonWeb.AddressController do - @moduledoc """ - A controller for addresses - """ - - use MobilizonWeb, :controller - - alias Mobilizon.Addresses - alias Mobilizon.Addresses.Address - - action_fallback(MobilizonWeb.FallbackController) - - def index(conn, _params) do - addresses = Addresses.list_addresses() - render(conn, "index.json", addresses: addresses) - end - - def create(conn, %{"address" => address_params}) do - with {:ok, geom} <- Addresses.process_geom(address_params["geom"]) do - address_params = %{address_params | "geom" => geom} - - with {:ok, %Address{} = address} <- Addresses.create_address(address_params) do - conn - |> put_status(:created) - |> put_resp_header("location", address_path(conn, :show, address)) - |> render("show.json", address: address) - end - end - end - - def process_geom(%{"type" => type, "data" => data}) do - import Logger - Logger.debug("Process geom") - Logger.debug(inspect(data)) - Logger.debug(inspect(type)) - types = [:point] - - unless is_atom(type) do - type = String.to_existing_atom(type) - end - - case type do - :point -> - %Geo.Point{coordinates: {data["latitude"], data["longitude"]}, srid: 4326} - - nil -> - nil - end - end - - def process_geom(nil) do - nil - end - - def show(conn, %{"id" => id}) do - address = Addresses.get_address!(id) - render(conn, "show.json", address: address) - end - - def update(conn, %{"id" => id, "address" => address_params}) do - with {:ok, geom} <- Addresses.process_geom(address_params["geom"]) do - address = Addresses.get_address!(id) - address_params = %{address_params | "geom" => geom} - - with {:ok, %Address{} = address} <- Addresses.update_address(address, address_params) do - render(conn, "show.json", address: address) - end - end - end - - def delete(conn, %{"id" => id}) do - address = Addresses.get_address!(id) - - with {:ok, %Address{}} <- Addresses.delete_address(address) do - send_resp(conn, :no_content, "") - end - end -end diff --git a/lib/mobilizon_web/controllers/bot_controller.ex b/lib/mobilizon_web/controllers/bot_controller.ex deleted file mode 100644 index b817422d0..000000000 --- a/lib/mobilizon_web/controllers/bot_controller.ex +++ /dev/null @@ -1,48 +0,0 @@ -defmodule MobilizonWeb.BotController do - use MobilizonWeb, :controller - - alias Mobilizon.Actors - alias Mobilizon.Actors.{Bot, Actor} - - action_fallback(MobilizonWeb.FallbackController) - - def index(conn, _params) do - bots = Actors.list_bots() - render(conn, "index.json", bots: bots) - end - - def create(conn, %{"bot" => bot_params}) do - with user <- Guardian.Plug.current_resource(conn), - bot_params <- Map.put(bot_params, "user_id", user.id), - %Actor{} = actor <- - Actors.register_bot_account(%{name: bot_params["name"], summary: bot_params["summary"]}), - bot_params <- Map.put(bot_params, "actor_id", actor.id), - {:ok, %Bot{} = bot} <- Actors.create_bot(bot_params) do - conn - |> put_status(:created) - |> put_resp_header("location", bot_path(conn, :show, bot)) - |> render("show.json", bot: bot) - end - end - - def show(conn, %{"id" => id}) do - bot = Actors.get_bot!(id) - render(conn, "show.json", bot: bot) - end - - def update(conn, %{"id" => id, "bot" => bot_params}) do - bot = Actors.get_bot!(id) - - with {:ok, %Bot{} = bot} <- Actors.update_bot(bot, bot_params) do - render(conn, "show.json", bot: bot) - end - end - - def delete(conn, %{"id" => id}) do - bot = Actors.get_bot!(id) - - with {:ok, %Bot{}} <- Actors.delete_bot(bot) do - send_resp(conn, :no_content, "") - end - end -end diff --git a/lib/mobilizon_web/controllers/category_controller.ex b/lib/mobilizon_web/controllers/category_controller.ex deleted file mode 100644 index 2fd05587a..000000000 --- a/lib/mobilizon_web/controllers/category_controller.ex +++ /dev/null @@ -1,46 +0,0 @@ -defmodule MobilizonWeb.CategoryController do - @moduledoc """ - Controller for Categories - """ - use MobilizonWeb, :controller - - alias Mobilizon.Events - alias Mobilizon.Events.Category - - action_fallback(MobilizonWeb.FallbackController) - - def index(conn, _params) do - categories = Events.list_categories() - render(conn, "index.json", categories: categories) - end - - def create(conn, %{"category" => category_params}) do - with {:ok, %Category{} = category} <- Events.create_category(category_params) do - conn - |> put_status(:created) - |> put_resp_header("location", category_path(conn, :show, category)) - |> render("show.json", category: category) - end - end - - def show(conn, %{"id" => id}) do - category = Events.get_category!(id) - render(conn, "show.json", category: category) - end - - def update(conn, %{"id" => id, "category" => category_params}) do - category = Events.get_category!(id) - - with {:ok, %Category{} = category} <- Events.update_category(category, category_params) do - render(conn, "show.json", category: category) - end - end - - def delete(conn, %{"id" => id}) do - category = Events.get_category!(id) - - with {:ok, %Category{}} <- Events.delete_category(category) do - send_resp(conn, :no_content, "") - end - end -end diff --git a/lib/mobilizon_web/controllers/comment_controller.ex b/lib/mobilizon_web/controllers/comment_controller.ex deleted file mode 100644 index 11f4bbc12..000000000 --- a/lib/mobilizon_web/controllers/comment_controller.ex +++ /dev/null @@ -1,43 +0,0 @@ -defmodule MobilizonWeb.CommentController do - use MobilizonWeb, :controller - - alias Mobilizon.Events - alias Mobilizon.Events.Comment - - action_fallback(MobilizonWeb.FallbackController) - - def index(conn, _params) do - comments = Events.list_comments() - render(conn, "index.json", comments: comments) - end - - def create(conn, %{"comment" => comment_params}) do - with {:ok, %Comment{} = comment} <- Events.create_comment(comment_params) do - conn - |> put_status(:created) - |> put_resp_header("location", comment_path(conn, :show, comment)) - |> render("show.json", comment: comment) - end - end - - def show(conn, %{"uuid" => uuid}) do - comment = Events.get_comment_with_uuid!(uuid) - render(conn, "show.json", comment: comment) - end - - def update(conn, %{"uuid" => uuid, "comment" => comment_params}) do - comment = Events.get_comment_with_uuid!(uuid) - - with {:ok, %Comment{} = comment} <- Events.update_comment(comment, comment_params) do - render(conn, "show.json", comment: comment) - end - end - - def delete(conn, %{"uuid" => uuid}) do - comment = Events.get_comment_with_uuid!(uuid) - - with {:ok, %Comment{}} <- Events.delete_comment(comment) do - send_resp(conn, :no_content, "") - end - end -end diff --git a/lib/mobilizon_web/controllers/event_controller.ex b/lib/mobilizon_web/controllers/event_controller.ex deleted file mode 100644 index 5dd7f808d..000000000 --- a/lib/mobilizon_web/controllers/event_controller.ex +++ /dev/null @@ -1,125 +0,0 @@ -defmodule MobilizonWeb.EventController do - @moduledoc """ - Controller for Events - """ - use MobilizonWeb, :controller - - alias Mobilizon.Events - alias Mobilizon.Events.Event - alias Mobilizon.Export.ICalendar - - require Logger - - action_fallback(MobilizonWeb.FallbackController) - - def index(conn, _params) do - ip = "88.161.154.97" - Logger.debug(inspect(Geolix.lookup(ip), pretty: true)) - - with %{ - city: %Geolix.Adapter.MMDB2.Result.City{ - city: city, - country: country, - location: %Geolix.Adapter.MMDB2.Record.Location{ - latitude: latitude, - longitude: longitude - } - } - } <- Geolix.lookup(ip) do - distance = - case city do - nil -> 500_000 - _ -> 50_000 - end - - events = Events.find_close_events(longitude, latitude, distance) - - render( - conn, - "index.json", - events: events, - coord: %{longitude: longitude, latitude: latitude, distance: distance}, - city: city, - country: country - ) - end - end - - def index_all(conn, _params) do - events = Events.list_events() - render(conn, "index_all.json", events: events) - end - - def create(conn, %{"event" => event_params}) do - event_params = process_event_address(event_params) - Logger.debug("creating event with") - Logger.debug(inspect(event_params)) - - with {:ok, %Event{} = event} <- Events.create_event(event_params) do - conn - |> put_status(:created) - |> put_resp_header("location", event_path(conn, :show, event.uuid)) - |> render("show_simple.json", event: event) - end - end - - defp process_event_address(event) do - cond do - Map.has_key?(event, "address_type") && event["address_type"] !== :physical -> - event - - Map.has_key?(event, "physical_address") -> - address = event["physical_address"] - geom = MobilizonWeb.AddressController.process_geom(address["geom"]) - - address = - case geom do - nil -> - address - - _ -> - %{address | "geom" => geom} - end - - %{event | "physical_address" => address} - - true -> - event - end - end - - def search(conn, %{"name" => name}) do - events = Events.find_events_by_name(name) - render(conn, "index.json", events: events) - end - - def show(conn, %{"uuid" => uuid}) do - case Events.get_event_full_by_uuid(uuid) do - nil -> - send_resp(conn, 404, "") - - event -> - render(conn, "show.json", event: event) - end - end - - def export_to_ics(conn, %{"uuid" => uuid}) do - event = uuid |> Events.get_event_full_by_uuid() |> ICalendar.export_event() - send_resp(conn, 200, event) - end - - def update(conn, %{"uuid" => uuid, "event" => event_params}) do - event = Events.get_event_full_by_uuid(uuid) - - with {:ok, %Event{} = event} <- Events.update_event(event, event_params) do - render(conn, "show_simple.json", event: event) - end - end - - def delete(conn, %{"uuid" => uuid}) do - with event <- Events.get_event_by_uuid(uuid), - {:ok, %Event{}} <- Events.delete_event(event) do - send_resp(conn, :no_content, "") - end - end -end diff --git a/lib/mobilizon_web/controllers/event_request_controller.ex b/lib/mobilizon_web/controllers/event_request_controller.ex deleted file mode 100644 index a0486c169..000000000 --- a/lib/mobilizon_web/controllers/event_request_controller.ex +++ /dev/null @@ -1,52 +0,0 @@ -# defmodule MobilizonWeb.EventRequestController do -# @moduledoc """ -# Controller for Event requests -# """ -# use MobilizonWeb, :controller -# -# alias Mobilizon.Events -# alias Mobilizon.Events.{Event, Request} -# -# action_fallback MobilizonWeb.FallbackController -# -# def index_for_user(conn, _params) do -# actor = Guardian.Plug.current_resource(conn).actor -# requests = Events.list_requests_for_actor(actor) -# render(conn, "index.json", requests: requests) -# end -# -# def create(conn, %{"request" => request_params}) do -# request_params = Map.put(request_params, "actor_id", Guardian.Plug.current_resource(conn).actor.id) -# with {:ok, %Request{} = request} <- Events.create_request(request_params) do -# conn -# |> put_status(:created) -# |> put_resp_header("location", event_request_path(conn, :show, request)) -# |> render("show.json", request: request) -# end -# end -# -# def create_for_event(conn, %{"request" => request_params, "id" => event_id}) do -# request_params = Map.put(request_params, "event_id", event_id) -# create(conn, request_params) -# end -# -# def show(conn, %{"id" => id}) do -# request = Events.get_request!(id) -# render(conn, "show.json", request: request) -# end -# -# def update(conn, %{"id" => id, "request" => request_params}) do -# request = Events.get_request!(id) -# -# with {:ok, %Request{} = request} <- Events.update_request(request, request_params) do -# render(conn, "show.json", request: request) -# end -# end -# -# def delete(conn, %{"id" => id}) do -# request = Events.get_request!(id) -# with {:ok, %Request{}} <- Events.delete_request(request) do -# send_resp(conn, :no_content, "") -# end -# end -# end diff --git a/lib/mobilizon_web/controllers/follower_controller.ex b/lib/mobilizon_web/controllers/follower_controller.ex deleted file mode 100644 index 01c625a63..000000000 --- a/lib/mobilizon_web/controllers/follower_controller.ex +++ /dev/null @@ -1,43 +0,0 @@ -defmodule MobilizonWeb.FollowerController do - use MobilizonWeb, :controller - - alias Mobilizon.Actors - alias Mobilizon.Actors.Follower - - action_fallback(MobilizonWeb.FallbackController) - - def index(conn, _params) do - followers = Actors.list_followers() - render(conn, "index.json", followers: followers) - end - - def create(conn, %{"follower" => follower_params}) do - with {:ok, %Follower{} = follower} <- Actors.create_follower(follower_params) do - conn - |> put_status(:created) - |> put_resp_header("location", follower_path(conn, :show, follower)) - |> render("show.json", follower: follower) - end - end - - def show(conn, %{"id" => id}) do - follower = Actors.get_follower!(id) - render(conn, "show.json", follower: follower) - end - - def update(conn, %{"id" => id, "follower" => follower_params}) do - follower = Actors.get_follower!(id) - - with {:ok, %Follower{} = follower} <- Actors.update_follower(follower, follower_params) do - render(conn, "show.json", follower: follower) - end - end - - def delete(conn, %{"id" => id}) do - follower = Actors.get_follower!(id) - - with {:ok, %Follower{}} <- Actors.delete_follower(follower) do - send_resp(conn, :no_content, "") - end - end -end diff --git a/lib/mobilizon_web/controllers/group_controller.ex b/lib/mobilizon_web/controllers/group_controller.ex deleted file mode 100644 index 95d14f54b..000000000 --- a/lib/mobilizon_web/controllers/group_controller.ex +++ /dev/null @@ -1,53 +0,0 @@ -defmodule MobilizonWeb.GroupController do - @moduledoc """ - Controller for Groups - """ - use MobilizonWeb, :controller - - alias Mobilizon.Actors - alias Mobilizon.Actors.{Actor, Member} - - action_fallback(MobilizonWeb.FallbackController) - - def index(conn, _params) do - groups = Actors.list_groups() - render(conn, MobilizonWeb.ActorView, "index.json", actors: groups) - end - - def create(conn, %{"group" => group_params}) do - with {:ok, %Actor{} = group} <- Actors.create_group(group_params), - {:ok, %Member{} = member} <- - Actors.create_member(%{ - "parent_id" => group.id, - "actor_id" => Actors.get_local_actor_by_name(group_params["actor_admin"]).id, - "role" => 2 - }) do - conn - |> put_status(:created) - |> put_resp_header("location", actor_path(conn, :show, group)) - |> render(MobilizonWeb.ActorView, "actor_basic.json", actor: group) - end - end - - def join(conn, %{"name" => group_name, "actor_name" => actor_name}) do - with %Actor{} = group <- Actors.get_group_by_name(group_name), - %Actor{} = actor <- Actors.get_local_actor_by_name(actor_name), - {:ok, %Member{} = member} <- - Actors.create_member(%{"parent_id" => group.id, "actor_id" => actor.id}) do - conn - |> put_status(:created) - |> render(MobilizonWeb.MemberView, "member.json", member: member) - else - nil -> - conn - |> put_status(:not_found) - |> render(MobilizonWeb.ErrorView, "not_found.json", - details: "group or actor doesn't exist" - ) - - err -> - require Logger - Logger.debug(inspect(err)) - end - end -end diff --git a/lib/mobilizon_web/controllers/inboxes_controller.ex b/lib/mobilizon_web/controllers/inboxes_controller.ex deleted file mode 100644 index 9c3ca2d97..000000000 --- a/lib/mobilizon_web/controllers/inboxes_controller.ex +++ /dev/null @@ -1,6 +0,0 @@ -defmodule MobilizonWeb.InboxesController do - use MobilizonWeb, :controller - - def create(conn) do - end -end diff --git a/lib/mobilizon_web/controllers/nodeinfo_controller.ex b/lib/mobilizon_web/controllers/node_info_controller.ex similarity index 91% rename from lib/mobilizon_web/controllers/nodeinfo_controller.ex rename to lib/mobilizon_web/controllers/node_info_controller.ex index 885d15b6a..7427f49f6 100644 --- a/lib/mobilizon_web/controllers/nodeinfo_controller.ex +++ b/lib/mobilizon_web/controllers/node_info_controller.ex @@ -1,4 +1,4 @@ -defmodule MobilizonWeb.NodeinfoController do +defmodule MobilizonWeb.NodeInfoController do use MobilizonWeb, :controller alias MobilizonWeb @@ -11,7 +11,7 @@ defmodule MobilizonWeb.NodeinfoController do links: [ %{ rel: "http://nodeinfo.diaspora.software/ns/schema/2.0", - href: MobilizonWeb.Router.Helpers.nodeinfo_url(MobilizonWeb.Endpoint, :nodeinfo, "2.0") + href: MobilizonWeb.Router.Helpers.node_info_url(MobilizonWeb.Endpoint, :nodeinfo, "2.0") } ] } diff --git a/lib/mobilizon_web/controllers/outboxes_controller.ex b/lib/mobilizon_web/controllers/outboxes_controller.ex deleted file mode 100644 index 806338aaf..000000000 --- a/lib/mobilizon_web/controllers/outboxes_controller.ex +++ /dev/null @@ -1,10 +0,0 @@ -defmodule MobilizonWeb.OutboxesController do - use MobilizonWeb, :controller - - def show(conn) do - actor = Guardian.Plug.current_resource(conn).actor - events = actor.events - - render(conn, "index.json", events: events) - end -end diff --git a/lib/mobilizon_web/controllers/participant_controller.ex b/lib/mobilizon_web/controllers/participant_controller.ex deleted file mode 100644 index 0bb64a3f3..000000000 --- a/lib/mobilizon_web/controllers/participant_controller.ex +++ /dev/null @@ -1,18 +0,0 @@ -defmodule MobilizonWeb.ParticipantController do - @moduledoc """ - Controller for participants to an event - """ - use MobilizonWeb, :controller - - alias Mobilizon.Events - - def join(conn, %{"uuid" => uuid}) do - with event <- Events.get_event_by_uuid(uuid), - %{actor: actor} <- Guardian.Plug.current_resource(conn) do - participant = - Events.create_participant(%{"event_id" => event.id, "actor_id" => actor.id, "role" => 1}) - - render(conn, "participant.json", %{participant: participant}) - end - end -end diff --git a/lib/mobilizon_web/controllers/search_controller.ex b/lib/mobilizon_web/controllers/search_controller.ex deleted file mode 100644 index 7667a4a2f..000000000 --- a/lib/mobilizon_web/controllers/search_controller.ex +++ /dev/null @@ -1,23 +0,0 @@ -defmodule MobilizonWeb.SearchController do - @moduledoc """ - Controller for Search - """ - use MobilizonWeb, :controller - - alias Mobilizon.Events - alias Mobilizon.Actors - - action_fallback(MobilizonWeb.FallbackController) - - def search(conn, %{"name" => name}) do - events = Events.find_events_by_name(name) - # find already saved accounts - case Actors.search(name) do - {:ok, actors} -> - render(conn, "search.json", events: events, actors: actors) - - {:error, err} -> - json(conn, err) - end - end -end diff --git a/lib/mobilizon_web/controllers/session_controller.ex b/lib/mobilizon_web/controllers/session_controller.ex deleted file mode 100644 index c70ba3fa8..000000000 --- a/lib/mobilizon_web/controllers/session_controller.ex +++ /dev/null @@ -1,56 +0,0 @@ -defmodule MobilizonWeb.SessionController do - @moduledoc """ - Controller for (event) Sessions - """ - use MobilizonWeb, :controller - - alias Mobilizon.Events - alias Mobilizon.Events.Session - - action_fallback(MobilizonWeb.FallbackController) - - def index(conn, _params) do - sessions = Events.list_sessions() - render(conn, "index.json", sessions: sessions) - end - - def create(conn, %{"session" => session_params}) do - with {:ok, %Session{} = session} <- Events.create_session(session_params) do - conn - |> put_status(:created) - |> put_resp_header("location", session_path(conn, :show, session)) - |> render("show.json", session: session) - end - end - - def show(conn, %{"id" => id}) do - session = Events.get_session!(id) - render(conn, "show.json", session: session) - end - - def show_sessions_for_event(conn, %{"uuid" => event_uuid}) do - sessions = Events.list_sessions_for_event(event_uuid) - render(conn, "index.json", sessions: sessions) - end - - def show_sessions_for_track(conn, %{"id" => track}) do - sessions = Events.list_sessions_for_track(track) - render(conn, "index.json", sessions: sessions) - end - - def update(conn, %{"id" => id, "session" => session_params}) do - session = Events.get_session!(id) - - with {:ok, %Session{} = session} <- Events.update_session(session, session_params) do - render(conn, "show.json", session: session) - end - end - - def delete(conn, %{"id" => id}) do - session = Events.get_session!(id) - - with {:ok, %Session{}} <- Events.delete_session(session) do - send_resp(conn, :no_content, "") - end - end -end diff --git a/lib/mobilizon_web/controllers/tag_controller.ex b/lib/mobilizon_web/controllers/tag_controller.ex deleted file mode 100644 index c1a5fbe8d..000000000 --- a/lib/mobilizon_web/controllers/tag_controller.ex +++ /dev/null @@ -1,46 +0,0 @@ -defmodule MobilizonWeb.TagController do - @moduledoc """ - Controller for Tags - """ - use MobilizonWeb, :controller - - alias Mobilizon.Events - alias Mobilizon.Events.Tag - - action_fallback(MobilizonWeb.FallbackController) - - def index(conn, _params) do - tags = Events.list_tags() - render(conn, "index.json", tags: tags) - end - - def create(conn, %{"tag" => tag_params}) do - with {:ok, %Tag{} = tag} <- Events.create_tag(tag_params) do - conn - |> put_status(:created) - |> put_resp_header("location", tag_path(conn, :show, tag)) - |> render("show.json", tag: tag) - end - end - - def show(conn, %{"id" => id}) do - tag = Events.get_tag!(id) - render(conn, "show.json", tag: tag) - end - - def update(conn, %{"id" => id, "tag" => tag_params}) do - tag = Events.get_tag!(id) - - with {:ok, %Tag{} = tag} <- Events.update_tag(tag, tag_params) do - render(conn, "show.json", tag: tag) - end - end - - def delete(conn, %{"id" => id}) do - tag = Events.get_tag!(id) - - with {:ok, %Tag{}} <- Events.delete_tag(tag) do - send_resp(conn, :no_content, "") - end - end -end diff --git a/lib/mobilizon_web/controllers/track_controller.ex b/lib/mobilizon_web/controllers/track_controller.ex deleted file mode 100644 index bf4b6fdfc..000000000 --- a/lib/mobilizon_web/controllers/track_controller.ex +++ /dev/null @@ -1,46 +0,0 @@ -defmodule MobilizonWeb.TrackController do - @moduledoc """ - Controller for Tracks - """ - use MobilizonWeb, :controller - - alias Mobilizon.Events - alias Mobilizon.Events.Track - - action_fallback(MobilizonWeb.FallbackController) - - def index(conn, _params) do - tracks = Events.list_tracks() - render(conn, "index.json", tracks: tracks) - end - - def create(conn, %{"track" => track_params}) do - with {:ok, %Track{} = track} <- Events.create_track(track_params) do - conn - |> put_status(:created) - |> put_resp_header("location", track_path(conn, :show, track)) - |> render("show.json", track: track) - end - end - - def show(conn, %{"id" => id}) do - track = Events.get_track!(id) - render(conn, "show.json", track: track) - end - - def update(conn, %{"id" => id, "track" => track_params}) do - track = Events.get_track!(id) - - with {:ok, %Track{} = track} <- Events.update_track(track, track_params) do - render(conn, "show.json", track: track) - end - end - - def delete(conn, %{"id" => id}) do - track = Events.get_track!(id) - - with {:ok, %Track{}} <- Events.delete_track(track) do - send_resp(conn, :no_content, "") - end - end -end diff --git a/lib/mobilizon_web/controllers/user_controller.ex b/lib/mobilizon_web/controllers/user_controller.ex deleted file mode 100644 index 3a6848254..000000000 --- a/lib/mobilizon_web/controllers/user_controller.ex +++ /dev/null @@ -1,148 +0,0 @@ -defmodule MobilizonWeb.UserController do - @moduledoc """ - Controller for Users - """ - use MobilizonWeb, :controller - - alias Mobilizon.Actors - alias Mobilizon.Actors.User - alias Mobilizon.Repo - alias Mobilizon.Actors.Service.{Activation, ResetPassword} - - action_fallback(MobilizonWeb.FallbackController) - - def index(conn, _params) do - users = Actors.list_users_with_actors() - render(conn, "index.json", users: users) - end - - def register(conn, %{"username" => username, "email" => email, "password" => password}) do - with {:ok, %User{} = user} <- - Actors.register(%{email: email, password: password, username: username}) do - Activation.send_confirmation_email(user, "locale") - - conn - |> put_status(:created) - |> render("confirmation.json", %{user: user}) - end - end - - def validate(conn, %{"token" => token}) do - with {:ok, %User{} = user} <- Activation.check_confirmation_token(token) do - {:ok, token, _claims} = MobilizonWeb.Guardian.encode_and_sign(user) - - conn - |> put_resp_header("location", user_path(conn, :show_current_actor)) - |> render("show_with_token.json", %{user: user, token: token}) - else - {:error, msg} -> - conn - |> put_status(:not_found) - |> json(%{"error" => msg}) - end - end - - @time_before_resend 3600 - def resend_confirmation(conn, %{"email" => email}) do - with {:ok, %User{} = user} <- Actors.find_by_email(email), - false <- is_nil(user.confirmation_token), - true <- - Timex.before?( - Timex.shift(user.confirmation_sent_at, seconds: @time_before_resend), - DateTime.utc_now() - ) do - Activation.resend_confirmation_email(user) - render(conn, "confirmation.json", %{user: user}) - else - {:error, :not_found} -> - conn - |> put_status(:not_found) - |> json(%{"error" => "Unable to find an user with this email"}) - - _ -> - conn - |> put_status(:not_found) - |> json(%{ - "error" => - "Unable to resend the validation token. Please wait a while before you can ask for resending token" - }) - end - end - - def send_reset_password(conn, %{"email" => email}) do - with {:ok, %User{} = user} <- Actors.find_by_email(email), - {:ok, _} <- ResetPassword.send_password_reset_email(user) do - render(conn, "password_reset.json", %{user: user}) - else - {:error, nil} -> - conn - |> put_status(:not_found) - |> json(%{"errors" => "Unable to find an user with this email"}) - - {:error, :email_too_soon} -> - conn - |> put_status(:not_found) - |> json(%{"errors" => "You requested a new reset password too early"}) - end - end - - def reset_password(conn, %{"password" => password, "token" => token}) do - with {:ok, %User{} = user} <- ResetPassword.check_reset_password_token(password, token) do - {:ok, token, _claims} = MobilizonWeb.Guardian.encode_and_sign(user) - render(conn, "show_with_token.json", %{user: user, token: token}) - else - {:error, :invalid_token} -> - conn - |> put_status(:not_found) - |> json(%{"errors" => %{"token" => ["Wrong token for password reset"]}}) - - {:error, %Ecto.Changeset{} = changeset} -> - conn - |> put_status(:unprocessable_entity) - |> render(MobilizonWeb.ChangesetView, "error.json", changeset: changeset) - end - end - - def show_current_actor(conn, _params) do - user = - conn - |> Guardian.Plug.current_resource() - |> Repo.preload(:actors) - - render(conn, "show_simple.json", user: user) - end - - # defp handle_changeset_errors(errors) do - # errors - # |> Enum.map(fn {field, detail} -> - # "#{field} " <> render_detail(detail) - # end) - # |> Enum.join() - # end - - # defp render_detail({message, values}) do - # Enum.reduce(values, message, fn {k, v}, acc -> - # String.replace(acc, "%{#{k}}", to_string(v)) - # end) - # end - - # defp render_detail(message) do - # message - # end - - def update(conn, %{"id" => id, "user" => user_params}) do - user = Actors.get_user!(id) - - with {:ok, %User{} = user} <- Actors.update_user(user, user_params) do - render(conn, "show.json", user: user) - end - end - - def delete(conn, %{"id" => id}) do - user = Actors.get_user!(id) - - with {:ok, %User{}} <- Actors.delete_user(user) do - send_resp(conn, :no_content, "") - end - end -end diff --git a/lib/mobilizon_web/controllers/user_session_controller.ex b/lib/mobilizon_web/controllers/user_session_controller.ex deleted file mode 100644 index 4b4df85a6..000000000 --- a/lib/mobilizon_web/controllers/user_session_controller.ex +++ /dev/null @@ -1,41 +0,0 @@ -defmodule MobilizonWeb.UserSessionController do - @moduledoc """ - Controller for user sessions - """ - use MobilizonWeb, :controller - alias Mobilizon.Actors.User - alias Mobilizon.Actors - - def sign_in(conn, %{"email" => email, "password" => password}) do - with {:ok, %User{} = user} <- Actors.find_by_email(email), - {:ok, %User{} = _user} <- User.is_confirmed(user), - {:ok, token, _claims} <- Actors.authenticate(%{user: user, password: password}) do - # Render the token - render(conn, "token.json", %{token: token, user: user}) - else - {:error, :not_found} -> - conn - |> put_status(401) - |> json(%{"error_msg" => "No such user", "display_error" => "session.error.bad_login"}) - - {:error, :unconfirmed} -> - conn - |> put_status(401) - |> json(%{ - "error_msg" => "User is not activated", - "display_error" => "session.error.not_activated" - }) - - {:error, :unauthorized} -> - conn - |> put_status(401) - |> json(%{"error_msg" => "Bad login", "display_error" => "session.error.bad_login"}) - end - end - - def sign_out(conn, _params) do - conn - |> MobilizonWeb.Guardian.Plug.sign_out() - |> send_resp(204, "") - end -end diff --git a/lib/mobilizon_web/controllers/web_finger_controller.ex b/lib/mobilizon_web/controllers/web_finger_controller.ex index 50b7846a9..ffbf7db9c 100644 --- a/lib/mobilizon_web/controllers/web_finger_controller.ex +++ b/lib/mobilizon_web/controllers/web_finger_controller.ex @@ -18,4 +18,8 @@ defmodule MobilizonWeb.WebFingerController do _e -> send_resp(conn, 404, "Couldn't find user") end end + + def webfinger(conn, _) do + send_resp(conn, 400, "No query provided") + end end diff --git a/lib/mobilizon_web/endpoint.ex b/lib/mobilizon_web/endpoint.ex index fa16cba5a..3276de4e8 100644 --- a/lib/mobilizon_web/endpoint.ex +++ b/lib/mobilizon_web/endpoint.ex @@ -10,6 +10,14 @@ defmodule MobilizonWeb.Endpoint do # # You should set gzip to true if you are running phoenix.digest # when deploying your static files in production. + + plug( + Plug.Static, + at: "/uploads", + from: "./uploads", + gzip: false + ) + plug( Plug.Static, at: "/", diff --git a/lib/mobilizon_web/resolvers/actor.ex b/lib/mobilizon_web/resolvers/actor.ex new file mode 100644 index 000000000..5129e911b --- /dev/null +++ b/lib/mobilizon_web/resolvers/actor.ex @@ -0,0 +1,26 @@ +defmodule MobilizonWeb.Resolvers.Actor do + alias Mobilizon.Actors.Actor, as: ActorSchema + alias Mobilizon.Actors.User + alias Mobilizon.Actors + + def find_actor(_parent, %{preferred_username: name}, _resolution) do + case Actors.get_actor_by_name_with_everything(name) do + nil -> + {:error, "Actor with name #{name} not found"} + + actor -> + {:ok, actor} + end + end + + @doc """ + Returns the current actor for the currently logged-in user + """ + def get_current_actor(_parent, _args, %{context: %{current_user: user}}) do + {:ok, Actors.get_actor_for_user(user)} + end + + def get_current_actor(_parent, _args, _resolution) do + {:error, "You need to be logged-in to view current actor"} + end +end diff --git a/lib/mobilizon_web/resolvers/category.ex b/lib/mobilizon_web/resolvers/category.ex new file mode 100644 index 000000000..d3cadbfcb --- /dev/null +++ b/lib/mobilizon_web/resolvers/category.ex @@ -0,0 +1,37 @@ +defmodule MobilizonWeb.Resolvers.Category do + require Logger + + def list_categories(_parent, _args, _resolution) do + categories = + Mobilizon.Events.list_categories() + |> Enum.map(fn category -> + urls = MobilizonWeb.Uploaders.Category.urls({category.picture, category}) + Map.put(category, :picture, %{url: urls.original, url_thumbnail: urls.thumb}) + end) + + {:ok, categories} + end + + def create_category(_parent, %{title: title, picture: picture, description: description}, %{ + context: %{current_user: user} + }) do + with {:ok, category} <- + Mobilizon.Events.create_category(%{ + title: title, + description: description, + picture: picture + }), + urls <- MobilizonWeb.Uploaders.Category.urls({category.picture, category}) do + Logger.info("Created category " <> title) + {:ok, Map.put(category, :picture, %{url: urls.original, url_thumbnail: urls.thumb})} + else + {:error, %Ecto.Changeset{errors: errors} = _changeset} -> + # This is pretty ridiculous for changeset to error + errors = + Enum.into(errors, %{}) + |> Enum.map(fn {key, {value, _}} -> Atom.to_string(key) <> ": " <> value end) + + {:error, errors} + end + end +end diff --git a/lib/mobilizon_web/resolvers/event.ex b/lib/mobilizon_web/resolvers/event.ex new file mode 100644 index 000000000..09b0e6565 --- /dev/null +++ b/lib/mobilizon_web/resolvers/event.ex @@ -0,0 +1,54 @@ +defmodule MobilizonWeb.Resolvers.Event do + def list_events(_parent, _args, _resolution) do + {:ok, Mobilizon.Events.list_events()} + end + + def find_event(_parent, %{uuid: uuid}, _resolution) do + case Mobilizon.Events.get_event_full_by_uuid(uuid) do + nil -> + {:error, "Event with UUID #{uuid} not found"} + + event -> + {:ok, event} + end + end + + def list_participants_for_event(_parent, %{uuid: uuid}, _resolution) do + {:ok, Mobilizon.Events.list_participants_for_event(uuid)} + end + + @doc """ + Search events by title + """ + def search_events(_parent, %{search: search, page: page, limit: limit}, _resolution) do + {:ok, Mobilizon.Events.find_events_by_name(search, page, limit)} + end + + @doc """ + Search events and actors by title + """ + def search_events_and_actors(_parent, %{search: search, page: page, limit: limit}, _resolution) do + found = + Mobilizon.Events.find_events_by_name(search, page, limit) ++ + Mobilizon.Actors.find_actors_by_username_or_name(search, page, limit) + + require Logger + Logger.debug(inspect(found)) + {:ok, found} + end + + @doc """ + List participants for event (through an event request) + """ + def list_participants_for_event(%{uuid: uuid}, _args, _resolution) do + {:ok, Mobilizon.Events.list_participants_for_event(uuid)} + end + + def create_event(_parent, args, %{context: %{current_user: user}}) do + Mobilizon.Events.create_event(args) + end + + def create_event(_parent, _args, _resolution) do + {:error, "You need to be logged-in to create events"} + end +end diff --git a/lib/mobilizon_web/resolvers/upload.ex b/lib/mobilizon_web/resolvers/upload.ex new file mode 100644 index 000000000..8d50226a6 --- /dev/null +++ b/lib/mobilizon_web/resolvers/upload.ex @@ -0,0 +1,2 @@ +defmodule MobilizonWeb.Resolvers.Upload do +end diff --git a/lib/mobilizon_web/resolvers/user.ex b/lib/mobilizon_web/resolvers/user.ex new file mode 100644 index 000000000..213d2aaf2 --- /dev/null +++ b/lib/mobilizon_web/resolvers/user.ex @@ -0,0 +1,118 @@ +defmodule MobilizonWeb.Resolvers.User do + alias Mobilizon.Actors.{User, Actor} + alias Mobilizon.Actors + + @doc """ + Find an user by it's ID + """ + def find_user(_parent, %{id: id}, _resolution) do + Actors.get_user_with_actor(id) + end + + @doc """ + Return current logged-in user + """ + def get_current_user(_parent, _args, %{context: %{current_user: user}}) do + {:ok, user} + end + + def get_current_user(_parent, _args, _resolution) do + {:error, "You need to be logged-in to view current user"} + end + + @desc """ + Login an user. Returns a token and the user + """ + def login_user(_parent, %{email: email, password: password}, _resolution) do + with {:ok, %User{} = user} <- Actors.get_user_by_email(email, true), + {:ok, token, _} <- Actors.authenticate(%{user: user, password: password}), + %Actor{} = actor <- Actors.get_actor_for_user(user) do + {:ok, %{token: token, user: user, actor: actor}} + else + {:error, :user_not_found} -> + {:error, "User with email not found"} + + {:error, :unauthorized} -> + {:error, "Impossible to authenticate"} + end + end + + @desc """ + Register an user : + - create the user + - create the actor + - set the user's default_actor to the newly created actor + - send a validation email to the user + """ + @spec create_user_actor(any(), map(), any()) :: tuple() + def create_user_actor(_parent, args, _resolution) do + with {:ok, %Actor{user: user} = actor} <- Actors.register(args) do + Mobilizon.Actors.Service.Activation.send_confirmation_email(user) + {:ok, actor} + end + end + + @doc """ + Validate an user, get it's actor and a token + """ + def validate_user(_parent, %{token: token}, _resolution) do + with {:ok, %User{} = user} <- + Mobilizon.Actors.Service.Activation.check_confirmation_token(token), + %Actor{} = actor <- Actors.get_actor_for_user(user), + {:ok, token, _} <- MobilizonWeb.Guardian.encode_and_sign(user) do + {:ok, %{token: token, user: user, actor: actor}} + end + end + + @doc """ + Send the confirmation email again. + We only do this to accounts unconfirmed + """ + def resend_confirmation_email(_parent, %{email: email, locale: locale}, _resolution) do + with {:ok, user} <- Actors.get_user_by_email(email, false), + {:ok, email} <- Mobilizon.Actors.Service.Activation.resend_confirmation_email(user, locale) do + {:ok, email} + else + {:error, :user_not_found} -> + {:error, "No user to validate with this email was found"} + {:error, :email_too_soon} -> + {:error, "You requested again a confirmation email too soon"} + end + end + + @doc """ + Send an email to reset the password from an user + """ + def send_reset_password(_parent, %{email: email, locale: locale}, _resolution) do + with {:ok, user} <- Actors.get_user_by_email(email, false), + {:ok, email} <- Mobilizon.Actors.Service.ResetPassword.send_password_reset_email(user, locale) do + {:ok, email} + else + {:error, :user_not_found} -> + {:error, "No user to validate with this email was found"} + {:error, :email_too_soon} -> + {:error, "You requested again a confirmation email too soon"} + end + end + + @doc """ + Reset the password from an user + """ + def reset_password(_parent, %{password: password, token: token}, _resolution) do + with {:ok, %User{} = user} <- + Mobilizon.Actors.Service.ResetPassword.check_reset_password_token(password, token), + %Actor{} = actor <- Actors.get_actor_for_user(user), + {:ok, token, _} <- MobilizonWeb.Guardian.encode_and_sign(user) do + {:ok, %{token: token, user: user, actor: actor}} + end + end + + @desc "Change an user default actor" + def change_default_actor(_parent, %{preferred_username: username}, %{ + context: %{current_user: user} + }) do + with %Actor{id: id} <- Actors.get_local_actor_by_name(username) do + Actors.update_user(user, %{default_actor_id: id}) + end + end +end diff --git a/lib/mobilizon_web/router.ex b/lib/mobilizon_web/router.ex index cb7c235ae..7707dc3b2 100644 --- a/lib/mobilizon_web/router.ex +++ b/lib/mobilizon_web/router.ex @@ -4,8 +4,9 @@ defmodule MobilizonWeb.Router do """ use MobilizonWeb, :router - pipeline :api do + pipeline :graphql do plug(:accepts, ["json"]) + plug(MobilizonWeb.AuthPipeline) end pipeline :well_known do @@ -17,11 +18,6 @@ defmodule MobilizonWeb.Router do plug(MobilizonWeb.HTTPSignaturePlug) end - pipeline :api_auth do - plug(:accepts, ["json"]) - plug(MobilizonWeb.AuthPipeline) - end - pipeline :browser do plug(:accepts, ["html"]) plug(:fetch_session) @@ -34,90 +30,21 @@ defmodule MobilizonWeb.Router do plug(:accepts, ["html", "application/json"]) end - scope "/api", MobilizonWeb do - pipe_through(:api) + scope "/api" do + pipe_through(:graphql) - scope "/v1" do - post("/users", UserController, :register) - get("/users/validate/:token", UserController, :validate) - post("/users/resend", UserController, :resend_confirmation) - - post("/users/password-reset/send", UserController, :send_reset_password) - post("/users/password-reset/post", UserController, :reset_password) - - post("/login", UserSessionController, :sign_in) - get("/groups", GroupController, :index) - get("/events", EventController, :index) - get("/events/all", EventController, :index_all) - get("/events/search/:name", EventController, :search) - get("/events/:uuid/ics", EventController, :export_to_ics) - get("/events/:uuid/tracks", TrackController, :show_tracks_for_event) - get("/events/:uuid/sessions", SessionController, :show_sessions_for_event) - get("/events/:uuid", EventController, :show) - get("/comments/:uuid", CommentController, :show) - get("/bots/:id", BotController, :show) - get("/bots", BotController, :index) - - get("/actors", ActorController, :index) - get("/actors/search/:name", ActorController, :search) - get("/actors/:name", ActorController, :show) - - resources("/followers", FollowerController, except: [:new, :edit]) - - resources("/tags", TagController, only: [:index, :show]) - resources("/categories", CategoryController, only: [:index, :show]) - resources("/sessions", SessionController, only: [:index, :show]) - resources("/tracks", TrackController, only: [:index, :show]) - resources("/addresses", AddressController, only: [:index, :show]) - - get("/search/:name", SearchController, :search) - - scope "/nodeinfo" do - pipe_through(:nodeinfo) - - get("/:version", NodeinfoController, :nodeinfo) - end - end + forward("/", Absinthe.Plug, schema: MobilizonWeb.Schema) end - # Authentificated API - scope "/api", MobilizonWeb do - pipe_through(:api_auth) - - scope "/v1" do - get("/user", UserController, :show_current_actor) - post("/sign-out", UserSessionController, :sign_out) - resources("/users", UserController, except: [:new, :edit, :show]) - post("/actors", ActorController, :create) - patch("/actors/:name", ActorController, :update) - post("/events", EventController, :create) - patch("/events/:uuid", EventController, :update) - put("/events/:uuid", EventController, :update) - delete("/events/:uuid", EventController, :delete) - post("/events/:uuid/join", ParticipantController, :join) - post("/comments", CommentController, :create) - patch("/comments/:uuid", CommentController, :update) - put("/comments/:uuid", CommentController, :update) - delete("/comments/:uuid", CommentController, :delete) - resources("/bots", BotController, except: [:new, :edit, :show, :index]) - post("/groups", GroupController, :create) - post("/groups/:name/join", GroupController, :join) - resources("/members", MemberController) - resources("/sessions", SessionController, except: [:index, :show]) - resources("/tracks", TrackController, except: [:index, :show]) - get("/tracks/:id/sessions", SessionController, :show_sessions_for_track) - resources("/categories", CategoryController) - resources("/tags", TagController) - resources("/addresses", AddressController, except: [:index, :show]) - end - end + forward("/graphiql", Absinthe.Plug.GraphiQL, schema: MobilizonWeb.Schema) scope "/.well-known", MobilizonWeb do pipe_through(:well_known) get("/host-meta", WebFingerController, :host_meta) get("/webfinger", WebFingerController, :webfinger) - get("/nodeinfo", NodeinfoController, :schemas) + get("/nodeinfo", NodeInfoController, :schemas) + get("/nodeinfo/:version", NodeInfoController, :nodeinfo) end scope "/", MobilizonWeb do @@ -141,6 +68,7 @@ defmodule MobilizonWeb.Router do scope "/", MobilizonWeb do pipe_through(:browser) + forward("/uploads", UploadPlug) get("/*path", PageController, :index) end end diff --git a/lib/mobilizon_web/schema.ex b/lib/mobilizon_web/schema.ex new file mode 100644 index 000000000..e60067c2d --- /dev/null +++ b/lib/mobilizon_web/schema.ex @@ -0,0 +1,318 @@ +defmodule MobilizonWeb.Schema do + use Absinthe.Schema + + import Absinthe.Resolution.Helpers, only: [dataloader: 1] + alias Mobilizon.{Actors, Events} + alias Mobilizon.Actors.Actor + alias Mobilizon.Events.Event + + import_types(MobilizonWeb.Schema.Custom.UUID) + import_types(Absinthe.Type.Custom) + import_types(Absinthe.Plug.Types) + + # import_types(MobilizonWeb.Schema.EventTypes) + # import_types(MobilizonWeb.Schema.ActorTypes) + + alias MobilizonWeb.Resolvers + + @desc "An ActivityPub actor" + object :actor do + field(:url, :string) + field(:outbox_url, :string) + field(:inbox_url, :string) + field(:following_url, :string) + field(:followers_url, :string) + field(:shared_inbox_url, :string) + field(:type, :actor_type) + field(:name, :string) + field(:domain, :string) + field(:summary, :string) + field(:preferred_username, :string) + field(:keys, :string) + field(:manually_approves_followers, :boolean) + field(:suspended, :boolean) + field(:avatar_url, :string) + field(:banner_url, :string) + # field(:followers, list_of(:follower)) + field :organized_events, list_of(:event) do + resolve(dataloader(Events)) + end + + # field(:memberships, list_of(:member)) + field(:user, :user) + end + + enum :actor_type do + value(:Person) + value(:Application) + value(:Group) + value(:Organization) + value(:Service) + end + + @desc "A local user of Mobilizon" + object :user do + field(:id, non_null(:id)) + field(:email, non_null(:string)) + # , resolve: dataloader(:actors)) + field(:actors, non_null(list_of(:actor))) + field(:default_actor_id, non_null(:integer)) + field(:confirmed_at, :datetime) + field(:confirmation_sent_at, :datetime) + field(:confirmation_token, :string) + field(:reset_password_sent_at, :datetime) + field(:reset_password_token, :string) + end + + @desc "A JWT and the associated user ID" + object :login do + field(:token, non_null(:string)) + field(:user, non_null(:user)) + field(:actor, non_null(:actor)) + end + + @desc "An event" + object :event do + field(:uuid, :uuid) + field(:url, :string) + field(:local, :boolean) + field(:title, :string) + field(:description, :string) + field(:begins_on, :datetime) + field(:ends_on, :datetime) + field(:state, :integer) + field(:status, :integer) + field(:public, :boolean) + field(:thumbnail, :string) + field(:large_image, :string) + field(:publish_at, :datetime) + field(:address_type, :address_type) + field(:online_address, :string) + field(:phone, :string) + + field :organizer_actor, :actor do + resolve(dataloader(Actors)) + end + + field(:attributed_to, :actor) + # field(:tags, list_of(:tag)) + field(:category, :category) + + field(:participants, list_of(:participant), + resolve: &Resolvers.Event.list_participants_for_event/3 + ) + + # field(:tracks, list_of(:track)) + # field(:sessions, list_of(:session)) + # field(:physical_address, :address) + + field(:updated_at, :datetime) + field(:created_at, :datetime) + end + + @desc "Represents a participant to an event" + object :participant do + # field(:event, :event, resolve: dataloader(Events)) + # , resolve: dataloader(Actors) + field(:actor, :actor) + field(:role, :integer) + end + + enum :address_type do + value(:physical) + value(:url) + value(:phone) + value(:other) + end + + @desc "A category" + object :category do + field(:id, :id) + field(:description, :string) + field(:picture, :picture) + field(:title, :string) + field(:updated_at, :datetime) + field(:created_at, :datetime) + end + + @desc "A picture" + object :picture do + field(:url, :string) + field(:url_thumbnail, :string) + end + + @desc "A search result" + union :search_result do + types([:event, :actor]) + + resolve_type(fn + %Actor{}, _ -> + :actor + + %Event{}, _ -> + :event + end) + end + + def context(ctx) do + loader = + Dataloader.new() + |> Dataloader.add_source(Actors, Actors.data()) + |> Dataloader.add_source(Events, Events.data()) + + Map.put(ctx, :loader, loader) + end + + def plugins do + [Absinthe.Middleware.Dataloader | Absinthe.Plugin.defaults()] + end + + query do + @desc "Get all events" + field :events, list_of(:event) do + resolve(&Resolvers.Event.list_events/3) + end + + @desc "Search through events and actors" + field :search, list_of(:search_result) do + arg(:search, non_null(:string)) + arg(:page, :integer, default_value: 1) + arg(:limit, :integer, default_value: 10) + resolve(&Resolvers.Event.search_events_and_actors/3) + end + + @desc "Get an event by uuid" + field :event, :event do + arg(:uuid, non_null(:uuid)) + resolve(&Resolvers.Event.find_event/3) + end + + @desc "Get all participants for an event uuid" + field :participants, list_of(:participant) do + arg(:uuid, non_null(:uuid)) + resolve(&Resolvers.Event.list_participants_for_event/3) + end + + @desc "Get an user" + field :user, :user do + arg(:id, non_null(:id)) + resolve(&Resolvers.User.find_user/3) + end + + @desc "Get the current user" + field :logged_user, :user do + resolve(&Resolvers.User.get_current_user/3) + end + + @desc "Get the current actor for the logged-in user" + field :logged_actor, :actor do + resolve(&Resolvers.Actor.get_current_actor/3) + end + + @desc "Get an actor" + field :actor, :actor do + arg(:preferred_username, non_null(:string)) + resolve(&Resolvers.Actor.find_actor/3) + end + + @doc """ + Get the list of categories + """ + field :categories, list_of(:category) do + resolve(&Resolvers.Category.list_categories/3) + end + end + + mutation do + @desc "Create an event" + field :create_event, type: :event do + arg(:title, non_null(:string)) + arg(:description, non_null(:string)) + arg(:begins_on, non_null(:datetime)) + arg(:ends_on, :datetime) + arg(:state, :integer) + arg(:status, :integer) + arg(:public, :boolean) + arg(:thumbnail, :string) + arg(:large_image, :string) + arg(:publish_at, :datetime) + arg(:address_type, non_null(:address_type)) + arg(:online_address, :string) + arg(:phone, :string) + arg(:organizer_actor_id, non_null(:integer)) + arg(:category_id, non_null(:integer)) + + resolve(&Resolvers.Event.create_event/3) + end + + @doc """ + Create a category with a title, description and picture + """ + field :create_category, type: :category do + arg(:title, non_null(:string)) + arg(:description, non_null(:string)) + arg(:picture, non_null(:upload)) + resolve(&Resolvers.Category.create_category/3) + end + + @desc "Create an user (returns an actor)" + field :create_user, type: :actor do + arg(:email, non_null(:string)) + arg(:password, non_null(:string)) + arg(:username, non_null(:string)) + + resolve(&Resolvers.User.create_user_actor/3) + end + + @desc "Validate an user after registration" + field :validate_user, type: :login do + arg(:token, non_null(:string)) + resolve(&Resolvers.User.validate_user/3) + end + + @desc "Resend registration confirmation token" + field :resend_confirmation_email, type: :string do + arg(:email, non_null(:string)) + arg(:locale, :string, default_value: "en") + resolve(&Resolvers.User.resend_confirmation_email/3) + end + + @doc """ + Send a link through email to reset user password + """ + field :send_reset_password, type: :string do + arg(:email, non_null(:string)) + arg(:locale, :string, default_value: "en") + resolve(&Resolvers.User.send_reset_password/3) + end + + @doc """ + Reset user password + """ + field :reset_password, type: :login do + arg(:token, non_null(:string)) + arg(:password, non_null(:string)) + arg(:locale, :string, default_value: "en") + resolve(&Resolvers.User.reset_password/3) + end + + @desc "Login an user" + field :login, :login do + arg(:email, non_null(:string)) + arg(:password, non_null(:string)) + resolve(&Resolvers.User.login_user/3) + end + + @desc "Change default actor for user" + field :change_default_actor, :user do + arg(:preferred_username, non_null(:string)) + resolve(&Resolvers.User.change_default_actor/3) + end + + @desc "Upload a picture" + field :upload_picture, :picture do + arg(:file, non_null(:upload)) + resolve(&Resolvers.Upload.upload_picture/3) + end + end +end diff --git a/lib/mobilizon_web/schema/custom/UUID4.ex b/lib/mobilizon_web/schema/custom/UUID4.ex new file mode 100644 index 000000000..4540e3344 --- /dev/null +++ b/lib/mobilizon_web/schema/custom/UUID4.ex @@ -0,0 +1,36 @@ +defmodule MobilizonWeb.Schema.Custom.UUID do + @moduledoc """ + The UUID4 scalar type allows UUID compliant strings to be passed in and out. + Requires `{ :ecto, ">= 0.0.0" }` package: https://github.com/elixir-ecto/ecto + """ + use Absinthe.Schema.Notation + + alias Ecto.UUID + + scalar :uuid, name: "UUID" do + description(""" + The `UUID` scalar type represents UUID4 compliant string data, represented as UTF-8 + character sequences. The UUID4 type is most often used to represent unique + human-readable ID strings. + """) + + serialize(&encode/1) + parse(&decode/1) + end + + @spec decode(Absinthe.Blueprint.Input.String.t()) :: {:ok, term()} | :error + @spec decode(Absinthe.Blueprint.Input.Null.t()) :: {:ok, nil} + defp decode(%Absinthe.Blueprint.Input.String{value: value}) do + UUID.cast(value) + end + + defp decode(%Absinthe.Blueprint.Input.Null{}) do + {:ok, nil} + end + + defp decode(_) do + :error + end + + defp encode(value), do: value +end diff --git a/lib/mobilizon_web/upload_plug.ex b/lib/mobilizon_web/upload_plug.ex new file mode 100644 index 000000000..e22fe4405 --- /dev/null +++ b/lib/mobilizon_web/upload_plug.ex @@ -0,0 +1,15 @@ +defmodule MobilizonWeb.UploadPlug do + use Plug.Builder + + plug(Plug.Static, + at: "/", + from: {:mobilizon, "./uploads"} + ) + + # only: ~w(images robots.txt) + plug(:not_found) + + def not_found(conn, _) do + send_resp(conn, 404, "not found") + end +end diff --git a/lib/mobilizon_web/uploaders/avatar.ex b/lib/mobilizon_web/uploaders/avatar.ex new file mode 100644 index 000000000..8583122d0 --- /dev/null +++ b/lib/mobilizon_web/uploaders/avatar.ex @@ -0,0 +1,50 @@ +defmodule MobilizonWeb.Uploaders.Avatar do + use Arc.Definition + + # Include ecto support (requires package arc_ecto installed): + # use Arc.Ecto.Definition + + @versions [:original] + + # To add a thumbnail version: + # @versions [:original, :thumb] + + # Override the bucket on a per definition basis: + # def bucket do + # :custom_bucket_name + # end + + # Whitelist file extensions: + # def validate({file, _}) do + # ~w(.jpg .jpeg .gif .png) |> Enum.member?(Path.extname(file.file_name)) + # end + + # Define a thumbnail transformation: + # def transform(:thumb, _) do + # {:convert, "-strip -thumbnail 250x250^ -gravity center -extent 250x250 -format png", :png} + # end + + # Override the persisted filenames: + # def filename(version, _) do + # version + # end + + # Override the storage directory: + # def storage_dir(version, {file, scope}) do + # "uploads/user/avatars/#{scope.id}" + # end + + # Provide a default URL if there hasn't been a file uploaded + # def default_url(version, scope) do + # "/images/avatars/default_#{version}.png" + # end + + # Specify custom headers for s3 objects + # Available options are [:cache_control, :content_disposition, + # :content_encoding, :content_length, :content_type, + # :expect, :expires, :storage_class, :website_redirect_location] + # + # def s3_object_headers(version, {file, scope}) do + # [content_type: MIME.from_path(file.file_name)] + # end +end diff --git a/lib/mobilizon_web/uploaders/category.ex b/lib/mobilizon_web/uploaders/category.ex new file mode 100644 index 000000000..9e9fd7977 --- /dev/null +++ b/lib/mobilizon_web/uploaders/category.ex @@ -0,0 +1,49 @@ +defmodule MobilizonWeb.Uploaders.Category do + use Arc.Definition + use Arc.Ecto.Definition + + # To add a thumbnail version: + @versions [:original, :thumb] + @extension_whitelist ~w(.jpg .jpeg .gif .png) + + # Override the bucket on a per definition basis: + # def bucket do + # :custom_bucket_name + # end + + # Whitelist file extensions: + def validate({file, _}) do + file_extension = file.file_name |> Path.extname() |> String.downcase() + Enum.member?(@extension_whitelist, file_extension) + end + + # Define a thumbnail transformation: + def transform(:thumb, _) do + {:convert, "-strip -thumbnail 250x250^ -gravity center -extent 250x250 -format png", :png} + end + + # Override the persisted filenames: + def filename(version, {file, %{title: title}}) do + "#{title}_#{version}" + end + + # TODO : When we're sure creating a category is secured and made possible only for admins, use category name + # Override the storage directory: + def storage_dir(_, _) do + "uploads/categories/" + end + + # Provide a default URL if there hasn't been a file uploaded + # def default_url(version, scope) do + # "/images/avatars/default_#{version}.png" + # end + + # Specify custom headers for s3 objects + # Available options are [:cache_control, :content_disposition, + # :content_encoding, :content_length, :content_type, + # :expect, :expires, :storage_class, :website_redirect_location] + # + # def s3_object_headers(version, {file, scope}) do + # [content_type: MIME.from_path(file.file_name)] + # end +end diff --git a/lib/mobilizon_web/views/actor_view.ex b/lib/mobilizon_web/views/actor_view.ex deleted file mode 100644 index 1c7749891..000000000 --- a/lib/mobilizon_web/views/actor_view.ex +++ /dev/null @@ -1,68 +0,0 @@ -defmodule MobilizonWeb.ActorView do - @moduledoc """ - View for Actors - """ - use MobilizonWeb, :view - alias MobilizonWeb.{ActorView, EventView, MemberView} - alias Mobilizon.Actors - - def render("index.json", %{actors: actors}) do - %{data: render_many(actors, ActorView, "actor_basic.json")} - end - - def render("show.json", %{actor: actor}) do - %{data: render_one(actor, ActorView, "actor.json")} - end - - def render("show_basic.json", %{actor: actor}) do - %{data: render_one(actor, ActorView, "actor_basic.json")} - end - - def render("actor_basic.json", %{actor: actor}) do - %{ - id: actor.id, - username: actor.preferred_username, - domain: actor.domain, - display_name: actor.name, - description: actor.summary, - type: actor.type, - # public_key: actor.public_key, - suspended: actor.suspended, - url: actor.url, - avatar: actor.avatar_url - } - end - - def render("actor.json", %{actor: actor}) do - output = %{ - id: actor.id, - username: actor.preferred_username, - domain: actor.domain, - display_name: actor.name, - description: actor.summary, - type: actor.type, - # public_key: actor.public_key, - suspended: actor.suspended, - url: actor.url, - avatar: actor.avatar_url, - banner: actor.banner_url, - organized_events: render_many(actor.organized_events, EventView, "event_for_actor.json") - } - - import Logger - Logger.debug(inspect(actor.type)) - - if actor.type == :Group do - Logger.debug("I'm a group !") - - Map.put( - output, - :members, - render_many(Actors.members_for_group(actor), MemberView, "member.json") - ) - else - Logger.debug("not a group") - output - end - end -end diff --git a/lib/mobilizon_web/views/address_view.ex b/lib/mobilizon_web/views/address_view.ex deleted file mode 100644 index acef9e450..000000000 --- a/lib/mobilizon_web/views/address_view.ex +++ /dev/null @@ -1,42 +0,0 @@ -defmodule MobilizonWeb.AddressView do - @moduledoc """ - View for addresses - """ - - use MobilizonWeb, :view - alias MobilizonWeb.AddressView - - def render("index.json", %{addresses: addresses}) do - %{data: render_many(addresses, AddressView, "address.json")} - end - - def render("show.json", %{address: address}) do - %{data: render_one(address, AddressView, "address.json")} - end - - def render("address.json", %{address: address}) do - %{ - id: address.id, - description: address.description, - floor: address.floor, - addressCountry: address.addressCountry, - addressLocality: address.addressLocality, - addressRegion: address.addressRegion, - postalCode: address.postalCode, - streetAddress: address.streetAddress, - geom: render_one(address.geom, AddressView, "geom.json") - } - end - - def render("geom.json", %{address: %Geo.Point{} = point}) do - [lat, lon] = Tuple.to_list(point.coordinates) - - %{ - type: "point", - data: %{ - latitude: lat, - longitude: lon - } - } - end -end diff --git a/lib/mobilizon_web/views/bot_view.ex b/lib/mobilizon_web/views/bot_view.ex deleted file mode 100644 index eebd6948e..000000000 --- a/lib/mobilizon_web/views/bot_view.ex +++ /dev/null @@ -1,16 +0,0 @@ -defmodule MobilizonWeb.BotView do - use MobilizonWeb, :view - alias MobilizonWeb.BotView - - def render("index.json", %{bots: bots}) do - %{data: render_many(bots, BotView, "bot.json")} - end - - def render("show.json", %{bot: bot}) do - %{data: render_one(bot, BotView, "bot.json")} - end - - def render("bot.json", %{bot: bot}) do - %{id: bot.id, source: bot.source, type: bot.type} - end -end diff --git a/lib/mobilizon_web/views/category_view.ex b/lib/mobilizon_web/views/category_view.ex deleted file mode 100644 index 3e5a70bba..000000000 --- a/lib/mobilizon_web/views/category_view.ex +++ /dev/null @@ -1,24 +0,0 @@ -defmodule MobilizonWeb.CategoryView do - @moduledoc """ - View for Categories - """ - use MobilizonWeb, :view - alias MobilizonWeb.CategoryView - - def render("index.json", %{categories: categories}) do - %{data: render_many(categories, CategoryView, "category.json")} - end - - def render("show.json", %{category: category}) do - %{data: render_one(category, CategoryView, "category.json")} - end - - def render("category.json", %{category: category}) do - %{ - id: category.id, - title: category.title, - description: category.description, - picture: category.picture - } - end -end diff --git a/lib/mobilizon_web/views/comment_view.ex b/lib/mobilizon_web/views/comment_view.ex deleted file mode 100644 index d06222ec0..000000000 --- a/lib/mobilizon_web/views/comment_view.ex +++ /dev/null @@ -1,16 +0,0 @@ -defmodule MobilizonWeb.CommentView do - use MobilizonWeb, :view - alias MobilizonWeb.CommentView - - def render("index.json", %{comments: comments}) do - %{data: render_many(comments, CommentView, "comment.json")} - end - - def render("show.json", %{comment: comment}) do - %{data: render_one(comment, CommentView, "comment.json")} - end - - def render("comment.json", %{comment: comment}) do - %{id: comment.id, uuid: comment.uuid, url: comment.url, text: comment.text} - end -end diff --git a/lib/mobilizon_web/views/event_view.ex b/lib/mobilizon_web/views/event_view.ex deleted file mode 100644 index c01050a9c..000000000 --- a/lib/mobilizon_web/views/event_view.ex +++ /dev/null @@ -1,68 +0,0 @@ -defmodule MobilizonWeb.EventView do - @moduledoc """ - View for Events - """ - use MobilizonWeb, :view - alias MobilizonWeb.{EventView, ActorView, GroupView, AddressView} - - def render("index.json", %{events: events, coord: coord, city: city, country: country}) do - %{ - data: render_many(events, EventView, "event_simple.json"), - coord: coord, - city: city, - country: country - } - end - - def render("index_all.json", %{events: events}) do - %{ - data: render_many(events, EventView, "event_simple.json") - } - end - - def render("show_simple.json", %{event: event}) do - %{data: render_one(event, EventView, "event_simple.json")} - end - - def render("show.json", %{event: event}) do - %{data: render_one(event, EventView, "event.json")} - end - - def render("event_for_actor.json", %{event: event}) do - %{id: event.id, title: event.title, uuid: event.uuid} - end - - def render("event_simple.json", %{event: event}) do - %{ - id: event.id, - title: event.title, - description: event.description, - begins_on: event.begins_on, - ends_on: event.ends_on, - uuid: event.uuid, - organizer: %{ - username: event.organizer_actor.preferred_username, - display_name: event.organizer_actor.name, - avatar: event.organizer_actor.avatar_url - }, - type: "Event", - address_type: event.address_type - } - end - - def render("event.json", %{event: event}) do - %{ - id: event.id, - title: event.title, - description: event.description, - begins_on: event.begins_on, - ends_on: event.ends_on, - uuid: event.uuid, - organizer: render_one(event.organizer_actor, ActorView, "actor_basic.json"), - participants: render_many(event.participants, ActorView, "actor_basic.json"), - physical_address: render_one(event.physical_address, AddressView, "address.json"), - type: "Event", - address_type: event.address_type - } - end -end diff --git a/lib/mobilizon_web/views/participant_view.ex b/lib/mobilizon_web/views/participant_view.ex deleted file mode 100644 index 69921be4f..000000000 --- a/lib/mobilizon_web/views/participant_view.ex +++ /dev/null @@ -1,19 +0,0 @@ -defmodule MobilizonWeb.ParticipantView do - @moduledoc """ - View for Participants - """ - use MobilizonWeb, :view - alias MobilizonWeb.ParticipantView - - def render("index.json", %{participants: participants}) do - %{data: render_many(participants, ParticipantView, "participant.json")} - end - - def render("show.json", %{participant: participant}) do - %{data: render_one(participant, ParticipantView, "participant.json")} - end - - def render("participant.json", %{participant: participant}) do - %{id: participant.id, role: participant.role} - end -end diff --git a/lib/mobilizon_web/views/search_view.ex b/lib/mobilizon_web/views/search_view.ex deleted file mode 100644 index 6085c240e..000000000 --- a/lib/mobilizon_web/views/search_view.ex +++ /dev/null @@ -1,16 +0,0 @@ -defmodule MobilizonWeb.SearchView do - @moduledoc """ - View for Events - """ - use MobilizonWeb, :view - alias MobilizonWeb.{EventView, ActorView, GroupView, AddressView} - - def render("search.json", %{events: events, actors: actors}) do - %{ - data: %{ - events: render_many(events, EventView, "event_simple.json"), - actors: render_many(actors, ActorView, "actor_basic.json") - } - } - end -end diff --git a/lib/mobilizon_web/views/session_view.ex b/lib/mobilizon_web/views/session_view.ex deleted file mode 100644 index ac3808525..000000000 --- a/lib/mobilizon_web/views/session_view.ex +++ /dev/null @@ -1,29 +0,0 @@ -defmodule MobilizonWeb.SessionView do - @moduledoc """ - View for event Sessions - """ - use MobilizonWeb, :view - alias MobilizonWeb.SessionView - - def render("index.json", %{sessions: sessions}) do - %{data: render_many(sessions, SessionView, "session.json")} - end - - def render("show.json", %{session: session}) do - %{data: render_one(session, SessionView, "session.json")} - end - - def render("session.json", %{session: session}) do - %{ - id: session.id, - title: session.title, - subtitle: session.subtitle, - short_abstract: session.short_abstract, - long_abstract: session.long_abstract, - language: session.language, - slides_url: session.slides_url, - videos_urls: session.videos_urls, - audios_urls: session.audios_urls - } - end -end diff --git a/lib/mobilizon_web/views/tag_view.ex b/lib/mobilizon_web/views/tag_view.ex deleted file mode 100644 index 17f218a63..000000000 --- a/lib/mobilizon_web/views/tag_view.ex +++ /dev/null @@ -1,19 +0,0 @@ -defmodule MobilizonWeb.TagView do - @moduledoc """ - View for Tags - """ - use MobilizonWeb, :view - alias MobilizonWeb.TagView - - def render("index.json", %{tags: tags}) do - %{data: render_many(tags, TagView, "tag.json")} - end - - def render("show.json", %{tag: tag}) do - %{data: render_one(tag, TagView, "tag.json")} - end - - def render("tag.json", %{tag: tag}) do - %{id: tag.id, title: tag.title} - end -end diff --git a/lib/mobilizon_web/views/track_view.ex b/lib/mobilizon_web/views/track_view.ex deleted file mode 100644 index 941db2605..000000000 --- a/lib/mobilizon_web/views/track_view.ex +++ /dev/null @@ -1,19 +0,0 @@ -defmodule MobilizonWeb.TrackView do - @moduledoc """ - View for Tracks - """ - use MobilizonWeb, :view - alias MobilizonWeb.TrackView - - def render("index.json", %{tracks: tracks}) do - %{data: render_many(tracks, TrackView, "track.json")} - end - - def render("show.json", %{track: track}) do - %{data: render_one(track, TrackView, "track.json")} - end - - def render("track.json", %{track: track}) do - %{id: track.id, name: track.name, description: track.description, color: track.color} - end -end diff --git a/lib/mobilizon_web/views/user_session_view.ex b/lib/mobilizon_web/views/user_session_view.ex deleted file mode 100644 index f2c98e30d..000000000 --- a/lib/mobilizon_web/views/user_session_view.ex +++ /dev/null @@ -1,10 +0,0 @@ -defmodule MobilizonWeb.UserSessionView do - @moduledoc """ - View for user Sessions - """ - use MobilizonWeb, :view - - def render("token.json", %{token: token, user: user}) do - %{token: token, user: render_one(user, MobilizonWeb.UserView, "user_simple.json")} - end -end diff --git a/lib/mobilizon_web/views/user_view.ex b/lib/mobilizon_web/views/user_view.ex deleted file mode 100644 index 828a3255f..000000000 --- a/lib/mobilizon_web/views/user_view.ex +++ /dev/null @@ -1,55 +0,0 @@ -defmodule MobilizonWeb.UserView do - @moduledoc """ - View for Users - """ - use MobilizonWeb, :view - alias MobilizonWeb.UserView - alias MobilizonWeb.ActorView - - def render("index.json", %{users: users}) do - %{data: render_many(users, UserView, "user_simple.json")} - end - - def render("show.json", %{user: user}) do - %{data: render_one(user, UserView, "user.json")} - end - - def render("show_simple.json", %{user: user}) do - %{data: render_one(user, UserView, "user_simple.json")} - end - - def render("show_with_token.json", %{user: user, token: token}) do - %{ - user: render_one(user, UserView, "user_simple.json"), - token: token - } - end - - def render("user_simple.json", %{user: user}) do - %{ - id: user.id, - role: user.role, - actors: render_many(user.actors, ActorView, "actor_basic.json") - } - end - - def render("user.json", %{user: user}) do - %{id: user.id, role: user.role, actors: render_many(user.actors, ActorView, "actor.json")} - end - - def render("user_private.json", %{user: user}) do - %{id: user.id, email: user.email, role: user.role} - end - - def render("confirmation.json", %{user: user}) do - %{ - email: user.email - } - end - - def render("password_reset.json", %{user: user}) do - %{ - email: user.email - } - end -end diff --git a/mix.exs b/mix.exs index d2dccfd8b..f16a3806a 100644 --- a/mix.exs +++ b/mix.exs @@ -75,6 +75,15 @@ defmodule Mobilizon.Mixfile do {:bamboo, "~> 1.0"}, {:bamboo_smtp, "~> 1.5.0"}, {:geolix, "~> 0.16"}, + {:absinthe, "~> 1.4.0"}, + {:absinthe_phoenix, "~> 1.4.0"}, + {:absinthe_plug, "~> 1.4.0"}, + {:poison, "~> 3.1"}, + {:absinthe_ecto, "~> 0.1.3"}, + {:dataloader, "~> 1.0"}, + {:arc, "~> 0.11.0"}, + {:arc_ecto, "~> 0.11.0"}, + {:email_checker, "~> 0.1.2"}, # Dev and test dependencies {:phoenix_live_reload, "~> 1.0", only: :dev}, {:ex_machina, "~> 2.2", only: [:dev, :test]}, diff --git a/mix.lock b/mix.lock index 4213f6f21..35b88b924 100644 --- a/mix.lock +++ b/mix.lock @@ -1,4 +1,10 @@ %{ + "absinthe": {:hex, :absinthe, "1.4.13", "81eb2ff41f1b62cd6e992955f62c22c042d1079b7936c27f5f7c2c806b8fc436", [:mix], [{:dataloader, "~> 1.0.0", [hex: :dataloader, repo: "hexpm", optional: true]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, + "absinthe_ecto": {:hex, :absinthe_ecto, "0.1.3", "420b68129e79fe4571a4838904ba03e282330d335da47729ad52ffd7b8c5fcb1", [:mix], [{:absinthe, "~> 1.3.0 or ~> 1.4.0", [hex: :absinthe, repo: "hexpm", optional: false]}, {:ecto, ">= 0.0.0", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm"}, + "absinthe_phoenix": {:hex, :absinthe_phoenix, "1.4.3", "cea34e7ebbc9a252038c1f1164878ee86bcb108905fe462be77efacda15c1e70", [:mix], [{:absinthe, "~> 1.4.0", [hex: :absinthe, repo: "hexpm", optional: false]}, {:absinthe_plug, "~> 1.4.0", [hex: :absinthe_plug, repo: "hexpm", optional: false]}, {:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.2", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.10.5 or ~> 2.11", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:poison, "~> 2.0 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, + "absinthe_plug": {:hex, :absinthe_plug, "1.4.5", "f63d52a76c870cd5f11d4bed8f61351ab5c5f572c5eb0479a0137f9f730ba33d", [:mix], [{:absinthe, "~> 1.4.11", [hex: :absinthe, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.2 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, + "arc": {:hex, :arc, "0.11.0", "ac7a0cc03035317b6fef9fe94c97d7d9bd183a3e7ce1606aa0c175cfa8d1ba6d", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:ex_aws_s3, "~> 2.0", [hex: :ex_aws_s3, repo: "hexpm", optional: true]}, {:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.1", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm"}, + "arc_ecto": {:hex, :arc_ecto, "0.11.0", "41f19944df3804b49c7bf511dfbeffe09b5b500892ed70d062d891bc891de589", [:mix], [{:arc, "~> 0.11.0", [hex: :arc, repo: "hexpm", optional: false]}, {:ecto, "~> 2.1", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm"}, "argon2_elixir": {:hex, :argon2_elixir, "1.3.1", "02a3d55a2670d25df25d75adcef2d74662c72bbc85aba17ca0ea585764b59ef4", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm"}, "bamboo": {:hex, :bamboo, "1.0.0", "446525f74eb59022ef58bc82f6c91c8e4c5a1469ab42a7f9b37c17262f872ef0", [:mix], [{:hackney, "~> 1.12.1", [hex: :hackney, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, "bamboo_smtp": {:hex, :bamboo_smtp, "1.5.0", "ebc4deb64a0ff88d05edc1e5f6fd77aea563cdbeac3fcb277666af96dff309e3", [:mix], [{:bamboo, "~> 1.0.0", [hex: :bamboo, repo: "hexpm", optional: false]}, {:gen_smtp, "~> 0.12.0", [hex: :gen_smtp, repo: "hexpm", optional: false]}], "hexpm"}, @@ -12,6 +18,7 @@ "cowboy": {:hex, :cowboy, "1.1.2", "61ac29ea970389a88eca5a65601460162d370a70018afe6f949a29dca91f3bb0", [:rebar3], [{:cowlib, "~> 1.0.2", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3.2", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"}, "cowlib": {:hex, :cowlib, "1.0.2", "9d769a1d062c9c3ac753096f868ca121e2730b9a377de23dec0f7e08b1df84ee", [:make], [], "hexpm"}, "credo": {:hex, :credo, "0.9.3", "76fa3e9e497ab282e0cf64b98a624aa11da702854c52c82db1bf24e54ab7c97a", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:poison, ">= 0.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, + "dataloader": {:hex, :dataloader, "1.0.4", "7c2345c53c9e5b61420013fc53c8463ba347a938b61f66677eb47d9c4a53ac5d", [:mix], [{:ecto, ">= 0.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, "db_connection": {:hex, :db_connection, "1.1.3", "89b30ca1ef0a3b469b1c779579590688561d586694a3ce8792985d4d7e575a61", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}, {:poolboy, "~> 1.5", [hex: :poolboy, repo: "hexpm", optional: true]}, {:sbroker, "~> 1.0", [hex: :sbroker, repo: "hexpm", optional: true]}], "hexpm"}, "decimal": {:hex, :decimal, "1.5.0", "b0433a36d0e2430e3d50291b1c65f53c37d56f83665b43d79963684865beab68", [:mix], [], "hexpm"}, "earmark": {:hex, :earmark, "1.2.6", "b6da42b3831458d3ecc57314dff3051b080b9b2be88c2e5aa41cd642a5b044ed", [:mix], [], "hexpm"}, @@ -19,6 +26,7 @@ "ecto_autoslug_field": {:hex, :ecto_autoslug_field, "0.5.1", "c8a160fa6e5e0002740fe1c500bcc27d10bdb073a93715ce8a01b7af8a290777", [:mix], [{:ecto, ">= 2.1.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:slugger, ">= 0.2.0", [hex: :slugger, repo: "hexpm", optional: false]}], "hexpm"}, "ecto_enum": {:hex, :ecto_enum, "1.1.0", "d44fe2ce6e1c0e907e7c3b6456a69e0f1d662348d8b4e2a662ba312223d8ff62", [:mix], [{:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm"}, "elixir_make": {:hex, :elixir_make, "0.4.2", "332c649d08c18bc1ecc73b1befc68c647136de4f340b548844efc796405743bf", [:mix], [], "hexpm"}, + "email_checker": {:hex, :email_checker, "0.1.2", "05b3121c71b69f1ab5df7d8b4844046898bf218031998ef53f20c6b8bfd219e9", [:mix], [{:socket, "~> 0.3.1", [hex: :socket, repo: "hexpm", optional: false]}], "hexpm"}, "ex_crypto": {:hex, :ex_crypto, "0.9.0", "e04a831034c4d0a43fb2858f696d6b5ae0f87f07dedca3452912fd3cb5ee3ca2", [:mix], [{:poison, ">= 2.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, "ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, "ex_ical": {:hex, :ex_ical, "0.2.0", "4b928b554614704016cc0c9ee226eb854da9327a1cc460457621ceacb1ac29a6", [:mix], [{:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm"}, @@ -44,6 +52,7 @@ "jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, repo: "hexpm", optional: false]}], "hexpm"}, "json_ld": {:hex, :json_ld, "0.3.0", "92f508ca831b9e4530e3e6c950976fdafcf26323e6817c325b3e1ee78affc4bd", [:mix], [{:jason, "~> 1.1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:rdf, "~> 0.5", [hex: :rdf, repo: "hexpm", optional: false]}], "hexpm"}, "jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], [], "hexpm"}, + "kronky": {:hex, :kronky, "0.5.0", "b2038c267f02b297044cb574f542fa96763278a88b32a97d0c37bde95c63c13b", [:mix], [{:absinthe, "~> 1.3", [hex: :absinthe, repo: "hexpm", optional: false]}, {:ecto, ">= 2.1.4", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm"}, "littlefinger": {:hex, :littlefinger, "0.1.0", "5d3720bebd65d6a2051c31ca45f28b2d452d25aeeb8adb0a8f87013868bb0e7e", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:poison, "~> 3.1", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm"}, "makeup": {:hex, :makeup, "0.5.5", "9e08dfc45280c5684d771ad58159f718a7b5788596099bdfb0284597d368a882", [:mix], [{:nimble_parsec, "~> 0.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, "makeup_elixir": {:hex, :makeup_elixir, "0.10.0", "0f09c2ddf352887a956d84f8f7e702111122ca32fbbc84c2f0569b8b65cbf7fa", [:mix], [{:makeup, "~> 0.5.5", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm"}, @@ -67,6 +76,7 @@ "rdf": {:hex, :rdf, "0.5.1", "b59eaf5df3d77c6c3bb35efdb61f30ba8a1321c03206449ea71fb58670e94f1d", [:mix], [{:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm"}, "rsa_ex": {:hex, :rsa_ex, "0.4.0", "e28dd7dc5236e156df434af0e4aa822384c8866c928e17b785d4edb7c253b558", [:mix], [], "hexpm"}, "slugger": {:hex, :slugger, "0.3.0", "efc667ab99eee19a48913ccf3d038b1fb9f165fa4fbf093be898b8099e61b6ed", [:mix], [], "hexpm"}, + "socket": {:hex, :socket, "0.3.13", "98a2ab20ce17f95fb512c5cadddba32b57273e0d2dba2d2e5f976c5969d0c632", [:mix], [], "hexpm"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], [], "hexpm"}, "timex": {:hex, :timex, "3.4.1", "e63fc1a37453035e534c3febfe9b6b9e18583ec7b37fd9c390efdef97397d70b", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"}, "timex_ecto": {:hex, :timex_ecto, "3.3.0", "d5bdef09928e7a60f10a0baa47ce653f29b43d6fee87b30b236b216d0e36b98d", [:mix], [{:ecto, "~> 2.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm"}, diff --git a/priv/repo/migrations/20181021161324_add_default_actor_user_field.exs b/priv/repo/migrations/20181021161324_add_default_actor_user_field.exs new file mode 100644 index 000000000..9deb73aea --- /dev/null +++ b/priv/repo/migrations/20181021161324_add_default_actor_user_field.exs @@ -0,0 +1,15 @@ +defmodule Mobilizon.Repo.Migrations.AddDefaultActorUserField do + use Ecto.Migration + + def up do + alter table(:users) do + add :default_actor_id, :integer + end + end + + def down do + alter table(:users) do + remove :default_actor_id + end + end +end diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index 4a0412f95..16117576e 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -24,6 +24,8 @@ actor2 = insert(:actor, user: user) # Make actor organize an event event = insert(:event, organizer_actor: actor) +participant = insert(:participant, actor: actor, event: event) + # Insert a group group = insert(:actor, type: :Group) diff --git a/test/eventos_web/controllers/actor_controller_test.exs b/test/eventos_web/controllers/actor_controller_test.exs deleted file mode 100644 index 4cfa21f8d..000000000 --- a/test/eventos_web/controllers/actor_controller_test.exs +++ /dev/null @@ -1,190 +0,0 @@ -defmodule MobilizonWeb.ActorControllerTest do - use MobilizonWeb.ConnCase - - import Mobilizon.Factory - - alias Mobilizon.Actors - - setup %{conn: conn} do - user = insert(:user) - actor = insert(:actor, user: user) - {:ok, conn: conn, user: user, actor: actor} - end - - @create_attrs %{ - preferred_username: "otheridentity", - summary: "This is my other identity" - } - - describe "index" do - test "lists all actors", %{conn: conn, user: user, actor: actor} do - conn = get(conn, actor_path(conn, :index)) - assert hd(json_response(conn, 200)["data"])["username"] == actor.preferred_username - end - end - - describe "create actor" do - test "from an existing user", %{conn: conn, user: user} do - conn = auth_conn(conn, user) - conn = post(conn, actor_path(conn, :create), actor: @create_attrs) - assert json_response(conn, 201)["data"]["username"] == @create_attrs.preferred_username - end - end - - describe "show actor" do - test "show existing actor", %{conn: conn, actor: actor} do - actor_id = actor.id - conn = get(conn, actor_path(conn, :show, actor.preferred_username)) - assert %{"data" => %{"id" => actor_id}} = json_response(conn, 200) - end - - test "show non-existing actor", %{conn: conn, actor: actor} do - actor_id = actor.id - conn = get(conn, actor_path(conn, :show, "nonexisting")) - assert "" == response(conn, 404) - end - end - - describe "search for actors" do - test "search for existing actors", %{conn: conn, actor: actor} do - actor_username = actor.preferred_username - conn = get(conn, actor_path(conn, :search, actor_username)) - assert %{"data" => [%{"username" => actor_username}]} = json_response(conn, 200) - end - - test "search for existing actors with similar username", %{conn: conn, actor: actor} do - actor_username = actor.preferred_username - conn = get(conn, actor_path(conn, :search, "thom")) - assert %{"data" => [%{"username" => actor_username}]} = json_response(conn, 200) - end - - test "search for nothing", %{conn: conn, actor: actor} do - actor_username = actor.preferred_username - conn = get(conn, actor_path(conn, :search, "nothing")) - assert %{"data" => []} = json_response(conn, 200) - end - end - - describe "update actor" do - test "update actor with valid attrs", %{conn: conn, user: user, actor: actor} do - conn = auth_conn(conn, user) - - conn = - patch(conn, actor_path(conn, :update, actor.preferred_username), %{ - "actor" => %{"name" => "glouglou"} - }) - - assert %{"data" => %{"display_name" => "glouglou"}} = json_response(conn, 200) - end - - test "update actor with invalid attrs", %{conn: conn, user: user, actor: actor} do - conn = auth_conn(conn, user) - - conn = - patch(conn, actor_path(conn, :update, actor.preferred_username), %{ - "actor" => %{"preferred_username" => nil} - }) - - assert json_response(conn, 422)["errors"] != %{} - end - end - - ### - # Not possible atm - ### - # describe "delete actor" do - # setup [:create_actor] - # - # test "deletes own actor", %{conn: conn, user: user} do - # conn = auth_conn(conn, user) - # conn = delete conn, actor_path(conn, :delete, user.actor) - # assert response(conn, 204) - # assert_error_sent 404, fn -> - # get conn, actor_path(conn, :show, user.actor) - # end - # end - # - # test "deletes other actor", %{conn: conn, actor: actor, user: user} do - # conn = auth_conn(conn, user) - # conn = delete conn, actor_path(conn, :delete, actor) - # assert response(conn, 401) - # conn = get conn, actor_path(conn, :show, actor) - # assert response(conn, 200) - # end - # end - - @create_group_attrs %{ - preferred_username: "mygroup", - summary: "This is my awesome group", - name: "My Group" - } - - describe "index groups" do - test "lists all actor groups", %{conn: conn} do - conn = get(conn, group_path(conn, :index)) - assert json_response(conn, 200)["data"] == [] - end - - test "after creating a group", %{conn: conn, user: user, actor: actor} do - # create group - conn = auth_conn(conn, user) - create_group_attrs = Map.put(@create_group_attrs, :actor_admin, actor.preferred_username) - conn = post(conn, group_path(conn, :create), group: create_group_attrs) - - group_res = json_response(conn, 201) - assert group_res["username"] == @create_group_attrs.preferred_username - - conn = get(conn, group_path(conn, :index)) - assert json_response(conn, 200)["data"] == [group_res] - end - end - - describe "create group" do - test "with valid attributes", %{conn: conn, user: user, actor: actor} do - conn = auth_conn(conn, user) - create_group_attrs = Map.put(@create_group_attrs, :actor_admin, actor.preferred_username) - conn = post(conn, group_path(conn, :create), group: create_group_attrs) - - assert json_response(conn, 201)["username"] == @create_group_attrs.preferred_username - end - end - - describe "join group" do - setup %{conn: conn} do - user = insert(:user) - actor = insert(:actor, user: user) - group = insert(:group, preferred_username: "mygroup") - {:ok, conn: conn, user: user, actor: actor, group: group} - end - - test "from valid account", %{conn: conn, user: user, actor: actor, group: group} do - conn = auth_conn(conn, user) - - conn = - post(conn, group_path(conn, :join, group.preferred_username), %{ - "actor_name" => actor.preferred_username - }) - - resp = json_response(conn, 201) - - assert resp = %{ - "actor" => %{"username" => actor.preferred_username}, - "group" => %{"username" => group.preferred_username}, - "role" => 0 - } - end - - test "join non existent group", %{conn: conn, user: user, actor: actor} do - conn = auth_conn(conn, user) - - conn = - post(conn, group_path(conn, :join, "mygroup@nonexistent.tld"), %{ - "actor_name" => actor.preferred_username - }) - - resp = json_response(conn, 404) - - assert resp = %{msg: "Resource not found", details: "group or actor doesn't exist"} - end - end -end diff --git a/test/eventos_web/controllers/address_controller_test.exs b/test/eventos_web/controllers/address_controller_test.exs deleted file mode 100644 index 01d66e009..000000000 --- a/test/eventos_web/controllers/address_controller_test.exs +++ /dev/null @@ -1,144 +0,0 @@ -defmodule MobilizonWeb.AddressControllerTest do - use MobilizonWeb.ConnCase - - import Mobilizon.Factory - - alias Mobilizon.Addresses - alias Mobilizon.Addresses.Address - - @create_attrs %{ - addressCountry: "some addressCountry", - addressLocality: "some addressLocality", - addressRegion: "some addressRegion", - description: "some description", - floor: "some floor", - postalCode: "some postalCode", - streetAddress: "some streetAddress", - geom: %{type: :point, data: %{latitude: -20, longitude: 30}} - } - @update_attrs %{ - addressCountry: "some updated addressCountry", - addressLocality: "some updated addressLocality", - addressRegion: "some updated addressRegion", - description: "some updated description", - floor: "some updated floor", - postalCode: "some updated postalCode", - streetAddress: "some updated streetAddress", - geom: %{type: :point, data: %{latitude: -40, longitude: 40}} - } - @invalid_attrs %{ - addressCountry: nil, - addressLocality: nil, - addressRegion: nil, - description: nil, - floor: nil, - postalCode: nil, - streetAddress: nil, - geom: %{type: "oh no", data: %{latitude: nil, longitude: nil}} - } - - def fixture(:address) do - {:ok, address} = Addresses.create_address(@create_attrs) - address - end - - setup %{conn: conn} do - user = insert(:user) - actor = insert(:actor, user: user) - {:ok, conn: conn, user: user} - end - - describe "index" do - test "lists all addresses", %{conn: conn, user: user} do - conn = auth_conn(conn, user) - conn = get(conn, address_path(conn, :index)) - assert json_response(conn, 200)["data"] == [] - end - end - - describe "create address" do - test "renders address when data is valid", %{conn: conn, user: user} do - conn = auth_conn(conn, user) - conn = post(conn, address_path(conn, :create), address: @create_attrs) - assert %{"id" => id} = json_response(conn, 201)["data"] - - conn = get(conn, address_path(conn, :show, id)) - - assert json_response(conn, 200)["data"] == %{ - "id" => id, - "addressCountry" => "some addressCountry", - "addressLocality" => "some addressLocality", - "addressRegion" => "some addressRegion", - "description" => "some description", - "floor" => "some floor", - "postalCode" => "some postalCode", - "streetAddress" => "some streetAddress", - "geom" => %{ - "data" => %{"latitude" => -20.0, "longitude" => 30.0}, - "type" => "point" - } - } - end - - test "renders errors when data is invalid", %{conn: conn, user: user} do - conn = auth_conn(conn, user) - conn = post(conn, address_path(conn, :create), address: @invalid_attrs) - assert json_response(conn, 422)["errors"] != %{} - end - end - - describe "update address" do - setup [:create_address] - - test "renders address when data is valid", %{ - conn: conn, - address: %Address{id: id} = address, - user: user - } do - conn = auth_conn(conn, user) - conn = put(conn, address_path(conn, :update, address), address: @update_attrs) - assert %{"id" => ^id} = json_response(conn, 200)["data"] - - conn = get(conn, address_path(conn, :show, id)) - - assert json_response(conn, 200)["data"] == %{ - "id" => id, - "addressCountry" => "some updated addressCountry", - "addressLocality" => "some updated addressLocality", - "addressRegion" => "some updated addressRegion", - "description" => "some updated description", - "floor" => "some updated floor", - "postalCode" => "some updated postalCode", - "streetAddress" => "some updated streetAddress", - "geom" => %{ - "data" => %{"latitude" => -40.0, "longitude" => 40.0}, - "type" => "point" - } - } - end - - test "renders errors when data is invalid", %{conn: conn, address: address, user: user} do - conn = auth_conn(conn, user) - conn = put(conn, address_path(conn, :update, address), address: @invalid_attrs) - assert json_response(conn, 422)["errors"] != %{} - end - end - - describe "delete address" do - setup [:create_address] - - test "deletes chosen address", %{conn: conn, address: address, user: user} do - conn = auth_conn(conn, user) - conn = delete(conn, address_path(conn, :delete, address)) - assert response(conn, 204) - - assert_error_sent(404, fn -> - get(conn, address_path(conn, :show, address)) - end) - end - end - - defp create_address(_) do - {:ok, address: insert(:address)} - end -end diff --git a/test/eventos_web/controllers/bot_controller_test.exs b/test/eventos_web/controllers/bot_controller_test.exs deleted file mode 100644 index a136a80a7..000000000 --- a/test/eventos_web/controllers/bot_controller_test.exs +++ /dev/null @@ -1,94 +0,0 @@ -defmodule MobilizonWeb.BotControllerTest do - use MobilizonWeb.ConnCase - - import Mobilizon.Factory - - alias Mobilizon.Actors - alias Mobilizon.Actors.Bot - - @create_attrs %{source: "some source", type: "some type", name: "some name"} - @update_attrs %{ - source: "some updated source", - type: "some updated type", - name: "some updated name" - } - @invalid_attrs %{source: nil, type: nil, name: nil} - - setup %{conn: conn} do - user = insert(:user) - actor = insert(:actor, user: user) - {:ok, conn: put_req_header(conn, "accept", "application/json"), user: user} - end - - describe "index" do - test "lists all bots", %{conn: conn} do - conn = get(conn, bot_path(conn, :index)) - assert json_response(conn, 200)["data"] == [] - end - end - - describe "create bot" do - test "renders bot when data is valid", %{conn: conn, user: user} do - conn = auth_conn(conn, user) - conn = post(conn, bot_path(conn, :create), bot: @create_attrs) - assert %{"id" => id} = json_response(conn, 201)["data"] - - conn = get(conn, bot_path(conn, :show, id)) - - assert json_response(conn, 200)["data"] == %{ - "id" => id, - "source" => "some source", - "type" => "some type" - } - end - - test "renders errors when data is invalid", %{conn: conn, user: user} do - conn = auth_conn(conn, user) - conn = post(conn, bot_path(conn, :create), bot: @invalid_attrs) - assert json_response(conn, 422)["errors"] != %{} - end - end - - describe "update bot" do - setup [:create_bot] - - test "renders bot when data is valid", %{conn: conn, bot: %Bot{id: id} = bot, user: user} do - conn = auth_conn(conn, user) - conn = put(conn, bot_path(conn, :update, bot), bot: @update_attrs) - assert %{"id" => ^id} = json_response(conn, 200)["data"] - - conn = get(conn, bot_path(conn, :show, id)) - - assert json_response(conn, 200)["data"] == %{ - "id" => id, - "source" => "some updated source", - "type" => "some updated type" - } - end - - test "renders errors when data is invalid", %{conn: conn, bot: bot, user: user} do - conn = auth_conn(conn, user) - conn = put(conn, bot_path(conn, :update, bot), bot: @invalid_attrs) - assert json_response(conn, 422)["errors"] != %{} - end - end - - describe "delete bot" do - setup [:create_bot] - - test "deletes chosen bot", %{conn: conn, bot: bot, user: user} do - conn = auth_conn(conn, user) - conn = delete(conn, bot_path(conn, :delete, bot)) - assert response(conn, 204) - - assert_error_sent(404, fn -> - get(conn, bot_path(conn, :show, bot)) - end) - end - end - - defp create_bot(_) do - bot = insert(:bot) - {:ok, bot: bot} - end -end diff --git a/test/eventos_web/controllers/category_controller_test.exs b/test/eventos_web/controllers/category_controller_test.exs deleted file mode 100644 index 868782b5f..000000000 --- a/test/eventos_web/controllers/category_controller_test.exs +++ /dev/null @@ -1,105 +0,0 @@ -defmodule MobilizonWeb.CategoryControllerTest do - use MobilizonWeb.ConnCase - - import Mobilizon.Factory - - alias Mobilizon.Events - alias Mobilizon.Events.Category - - @create_attrs %{description: "some description", picture: "some picture", title: "some title"} - @update_attrs %{ - description: "some updated description", - picture: "some updated picture", - title: "some updated title" - } - @invalid_attrs %{description: nil, picture: nil, title: nil} - - def fixture(:category) do - {:ok, category} = Events.create_category(@create_attrs) - category - end - - setup %{conn: conn} do - user = insert(:user) - actor = insert(:actor, user: user) - {:ok, conn: conn, user: user} - end - - describe "index" do - test "lists all categories", %{conn: conn} do - conn = get(conn, category_path(conn, :index)) - assert json_response(conn, 200)["data"] == [] - end - end - - describe "create category" do - test "renders category when data is valid", %{conn: conn, user: user} do - conn = auth_conn(conn, user) - conn = post(conn, category_path(conn, :create), category: @create_attrs) - assert %{"id" => id} = json_response(conn, 201)["data"] - - conn = get(conn, category_path(conn, :show, id)) - - assert json_response(conn, 200)["data"] == %{ - "id" => id, - "description" => "some description", - "picture" => "some picture", - "title" => "some title" - } - end - - test "renders errors when data is invalid", %{conn: conn, user: user} do - conn = auth_conn(conn, user) - conn = post(conn, category_path(conn, :create), category: @invalid_attrs) - assert json_response(conn, 422)["errors"] != %{} - end - end - - describe "update category" do - setup [:create_category] - - test "renders category when data is valid", %{ - conn: conn, - category: %Category{id: id} = category, - user: user - } do - conn = auth_conn(conn, user) - conn = put(conn, category_path(conn, :update, category), category: @update_attrs) - assert %{"id" => ^id} = json_response(conn, 200)["data"] - - conn = get(conn, category_path(conn, :show, id)) - - assert json_response(conn, 200)["data"] == %{ - "id" => id, - "description" => "some updated description", - "picture" => "some updated picture", - "title" => "some updated title" - } - end - - test "renders errors when data is invalid", %{conn: conn, category: category, user: user} do - conn = auth_conn(conn, user) - conn = put(conn, category_path(conn, :update, category), category: @invalid_attrs) - assert json_response(conn, 422)["errors"] != %{} - end - end - - describe "delete category" do - setup [:create_category] - - test "deletes chosen category", %{conn: conn, category: category, user: user} do - conn = auth_conn(conn, user) - conn = delete(conn, category_path(conn, :delete, category)) - assert response(conn, 204) - - assert_error_sent(404, fn -> - get(conn, category_path(conn, :show, category)) - end) - end - end - - defp create_category(_) do - category = fixture(:category) - {:ok, category: category} - end -end diff --git a/test/eventos_web/controllers/comment_controller_test.exs b/test/eventos_web/controllers/comment_controller_test.exs deleted file mode 100644 index af107d08d..000000000 --- a/test/eventos_web/controllers/comment_controller_test.exs +++ /dev/null @@ -1,96 +0,0 @@ -defmodule MobilizonWeb.CommentControllerTest do - use MobilizonWeb.ConnCase - - alias Mobilizon.Events - alias Mobilizon.Events.Comment - - import Mobilizon.Factory - - @create_attrs %{text: "some text"} - @update_attrs %{text: "some updated text"} - @invalid_attrs %{text: nil, url: nil} - - setup %{conn: conn} do - user = insert(:user) - actor = insert(:actor, user: user) - {:ok, conn: put_req_header(conn, "accept", "application/json"), user: user, actor: actor} - end - - describe "create comment" do - test "renders comment when data is valid", %{conn: conn, user: user, actor: actor} do - conn = auth_conn(conn, user) - attrs = Map.merge(@create_attrs, %{actor_id: actor.id}) - conn = post(conn, comment_path(conn, :create), comment: attrs) - assert %{"uuid" => uuid, "id" => id} = json_response(conn, 201)["data"] - - conn = get(conn, comment_path(conn, :show, uuid)) - - assert json_response(conn, 200)["data"] == %{ - "id" => id, - "text" => "some text", - "uuid" => uuid, - "url" => "#{MobilizonWeb.Endpoint.url()}/comments/#{uuid}" - } - end - - test "renders errors when data is invalid", %{conn: conn, user: user} do - conn = auth_conn(conn, user) - conn = post(conn, comment_path(conn, :create), comment: @invalid_attrs) - assert json_response(conn, 422)["errors"] != %{} - end - end - - describe "update comment" do - setup [:create_comment] - - test "renders comment when data is valid", %{ - conn: conn, - comment: %Comment{id: id, uuid: uuid} = comment, - user: user, - actor: actor - } do - conn = auth_conn(conn, user) - attrs = Map.merge(@update_attrs, %{actor_id: actor.id}) - conn = put(conn, comment_path(conn, :update, uuid), comment: attrs) - assert %{"uuid" => uuid, "id" => id} = json_response(conn, 200)["data"] - - conn = get(conn, comment_path(conn, :show, uuid)) - - assert json_response(conn, 200)["data"] == %{ - "id" => id, - "text" => "some updated text", - "uuid" => uuid, - "url" => "#{MobilizonWeb.Endpoint.url()}/comments/#{uuid}" - } - end - - test "renders errors when data is invalid", %{conn: conn, comment: comment, user: user} do - conn = auth_conn(conn, user) - conn = put(conn, comment_path(conn, :update, comment.uuid), comment: @invalid_attrs) - assert json_response(conn, 422)["errors"] != %{} - end - end - - describe "delete comment" do - setup [:create_comment] - - test "deletes chosen comment", %{ - conn: conn, - comment: %Comment{uuid: uuid} = comment, - user: user - } do - conn = auth_conn(conn, user) - conn = delete(conn, comment_path(conn, :delete, uuid)) - assert response(conn, 204) - - assert_error_sent(404, fn -> - get(conn, comment_path(conn, :show, uuid)) - end) - end - end - - defp create_comment(_) do - comment = insert(:comment) - {:ok, comment: comment} - end -end diff --git a/test/eventos_web/controllers/event_controller_test.exs b/test/eventos_web/controllers/event_controller_test.exs deleted file mode 100644 index db1c688eb..000000000 --- a/test/eventos_web/controllers/event_controller_test.exs +++ /dev/null @@ -1,182 +0,0 @@ -defmodule MobilizonWeb.EventControllerTest do - use MobilizonWeb.ConnCase - import Mobilizon.Factory - - alias Mobilizon.Events - alias Mobilizon.Events.Event - alias Mobilizon.Export.ICalendar - - @create_attrs %{ - begins_on: "2010-04-17 14:00:00.000000Z", - description: "some description", - ends_on: "2010-04-17 14:00:00.000000Z", - title: "some title" - } - @update_attrs %{ - begins_on: "2011-05-18 15:01:01.000000Z", - description: "some updated description", - ends_on: "2011-05-18 15:01:01.000000Z", - title: "some updated title" - } - @invalid_attrs %{begins_on: nil, description: nil, ends_on: nil, title: nil, address_id: nil} - @create_address_attrs %{ - "addressCountry" => "some addressCountry", - "addressLocality" => "some addressLocality", - "addressRegion" => "some addressRegion", - "description" => "some description", - "floor" => "some floor", - "postalCode" => "some postalCode", - "streetAddress" => "some streetAddress", - "geom" => %{"type" => :point, "data" => %{"latitude" => -20, "longitude" => 30}} - } - - def fixture(:event) do - {:ok, event} = Events.create_event(@create_attrs) - event - end - - def address_fixture do - insert(:address) - end - - setup %{conn: conn} do - user = insert(:user) - actor = insert(:actor, user: user) - {:ok, conn: conn, user: user, actor: actor} - end - - describe "index" do - test "lists all events", %{conn: conn} do - conn = get(conn, event_path(conn, :index)) - assert json_response(conn, 200)["data"] == [] - end - end - - describe "create event" do - test "renders event when data is valid", %{conn: conn, user: user, actor: actor} do - attrs = Map.put(@create_attrs, :organizer_actor_id, actor.id) - attrs = Map.put(attrs, "physical_address", @create_address_attrs) - - category = insert(:category) - attrs = Map.put(attrs, :category_id, category.id) - conn = auth_conn(conn, user) - conn = post(conn, event_path(conn, :create), event: attrs) - assert %{"uuid" => uuid} = json_response(conn, 201)["data"] - - conn = get(conn, event_path(conn, :show, uuid)) - - assert %{ - "begins_on" => "2010-04-17T14:00:00Z", - "description" => "some description", - "ends_on" => "2010-04-17T14:00:00Z", - "title" => "some title", - "participants" => [], - "physical_address" => %{ - "addressCountry" => "some addressCountry", - "addressLocality" => "some addressLocality", - "addressRegion" => "some addressRegion", - "floor" => "some floor", - "geom" => %{ - "data" => %{"latitude" => -20.0, "longitude" => 30.0}, - "type" => "point" - }, - "postalCode" => "some postalCode", - "streetAddress" => "some streetAddress" - } - } = json_response(conn, 200)["data"] - end - - test "renders errors when data is invalid", %{conn: conn, user: user, actor: actor} do - conn = auth_conn(conn, user) - attrs = Map.put(@invalid_attrs, :organizer_actor_id, actor.id) - attrs = Map.put(attrs, :address, @create_address_attrs) - conn = post(conn, event_path(conn, :create), event: attrs) - assert json_response(conn, 422)["errors"] != %{} - end - end - - describe "export event" do - setup [:create_event] - - test "renders ics export of event", %{ - conn: conn, - event: %Event{uuid: uuid} = event, - user: user - } do - conn = auth_conn(conn, user) - conn = get(conn, event_path(conn, :export_to_ics, uuid)) - exported_event = ICalendar.export_event(event) - assert exported_event == response(conn, 200) - end - end - - describe "update event" do - setup [:create_event] - - test "renders event when data is valid", %{ - conn: conn, - event: %Event{uuid: uuid} = event, - user: user, - actor: actor - } do - conn = auth_conn(conn, user) - address = address_fixture() - attrs = Map.put(@update_attrs, :organizer_actor_id, actor.id) - attrs = Map.put(attrs, :address_id, address.id) - conn = put(conn, event_path(conn, :update, uuid), event: attrs) - assert %{"uuid" => uuid} = json_response(conn, 200)["data"] - - conn = get(conn, event_path(conn, :show, uuid)) - - assert %{ - "begins_on" => "2011-05-18T15:01:01Z", - "description" => "some updated description", - "ends_on" => "2011-05-18T15:01:01Z", - "title" => "some updated title", - "participants" => [], - "physical_address" => %{ - "addressCountry" => "My Country", - "addressLocality" => "My Locality", - "addressRegion" => "My Region", - "floor" => "Myfloor", - "geom" => %{ - "data" => %{"latitude" => 30.0, "longitude" => -90.0}, - "type" => "point" - }, - "postalCode" => "My Postal Code", - "streetAddress" => "My Street Address" - } - } = json_response(conn, 200)["data"] - end - - test "renders errors when data is invalid", %{ - conn: conn, - event: %Event{uuid: uuid} = event, - user: user, - actor: actor - } do - conn = auth_conn(conn, user) - attrs = Map.put(@invalid_attrs, :organizer_actor_id, actor.id) - conn = put(conn, event_path(conn, :update, uuid), event: attrs) - assert json_response(conn, 422)["errors"] != %{} - end - end - - describe "delete event" do - setup [:create_event] - - test "deletes chosen event", %{conn: conn, event: %Event{uuid: uuid} = event, user: user} do - conn = auth_conn(conn, user) - conn = delete(conn, event_path(conn, :delete, uuid)) - assert response(conn, 204) - conn = get(conn, event_path(conn, :show, uuid)) - assert response(conn, 404) - end - end - - defp create_event(_) do - actor = insert(:actor) - event = insert(:event, organizer_actor: actor) - {:ok, event: event, actor: actor} - end -end diff --git a/test/eventos_web/controllers/follower_controller_test.exs b/test/eventos_web/controllers/follower_controller_test.exs deleted file mode 100644 index aa994879f..000000000 --- a/test/eventos_web/controllers/follower_controller_test.exs +++ /dev/null @@ -1,83 +0,0 @@ -defmodule MobilizonWeb.FollowerControllerTest do - use MobilizonWeb.ConnCase - - alias Mobilizon.Actors - alias Mobilizon.Actors.Follower - import Mobilizon.Factory - - @create_attrs %{approved: true, score: 42} - @update_attrs %{approved: false, score: 43} - @invalid_attrs %{approved: nil, score: nil} - - setup %{conn: conn} do - actor = insert(:actor) - target_actor = insert(:actor) - - {:ok, - conn: put_req_header(conn, "accept", "application/json"), - actor: actor, - target_actor: target_actor} - end - - describe "create follower" do - test "renders follower when data is valid", %{ - conn: conn, - actor: actor, - target_actor: target_actor - } do - create_attrs = - @create_attrs - |> Map.put(:actor_id, actor.id) - |> Map.put(:target_actor_id, target_actor.id) - - conn = post(conn, follower_path(conn, :create), follower: create_attrs) - assert %{"id" => id} = json_response(conn, 201)["data"] - - conn = get(conn, follower_path(conn, :show, id)) - assert json_response(conn, 200)["data"] == %{"id" => id, "approved" => true, "score" => 42} - end - - test "renders errors when data is invalid", %{conn: conn} do - conn = post(conn, follower_path(conn, :create), follower: @invalid_attrs) - assert json_response(conn, 422)["errors"] != %{} - end - end - - describe "update follower" do - setup [:create_follower] - - test "renders follower when data is valid", %{ - conn: conn, - follower: %Follower{id: id} = follower - } do - conn = put(conn, follower_path(conn, :update, follower), follower: @update_attrs) - assert %{"id" => ^id} = json_response(conn, 200)["data"] - - conn = get(conn, follower_path(conn, :show, id)) - assert json_response(conn, 200)["data"] == %{"id" => id, "approved" => false, "score" => 43} - end - - test "renders errors when data is invalid", %{conn: conn, follower: follower} do - conn = put(conn, follower_path(conn, :update, follower), follower: @invalid_attrs) - assert json_response(conn, 422)["errors"] != %{} - end - end - - describe "delete follower" do - setup [:create_follower] - - test "deletes chosen follower", %{conn: conn, follower: follower} do - conn = delete(conn, follower_path(conn, :delete, follower)) - assert response(conn, 204) - - assert_error_sent(404, fn -> - get(conn, follower_path(conn, :show, follower)) - end) - end - end - - defp create_follower(%{actor: actor, target_actor: target_actor}) do - follower = insert(:follower, actor: actor, target_actor: target_actor) - [follower: follower] - end -end diff --git a/test/eventos_web/controllers/session_controller_test.exs b/test/eventos_web/controllers/session_controller_test.exs deleted file mode 100644 index 2baeb36de..000000000 --- a/test/eventos_web/controllers/session_controller_test.exs +++ /dev/null @@ -1,147 +0,0 @@ -defmodule MobilizonWeb.SessionControllerTest do - use MobilizonWeb.ConnCase - - import Mobilizon.Factory - - alias Mobilizon.Events - alias Mobilizon.Events.Session - - @create_attrs %{ - audios_urls: "some audios_urls", - language: "some language", - long_abstract: "some long_abstract", - short_abstract: "some short_abstract", - slides_url: "some slides_url", - subtitle: "some subtitle", - title: "some title", - videos_urls: "some videos_urls" - } - @update_attrs %{ - audios_urls: "some updated audios_urls", - language: "some updated language", - long_abstract: "some updated long_abstract", - short_abstract: "some updated short_abstract", - slides_url: "some updated slides_url", - subtitle: "some updated subtitle", - title: "some updated title", - videos_urls: "some updated videos_urls" - } - @invalid_attrs %{ - audios_urls: nil, - language: nil, - long_abstract: nil, - short_abstract: nil, - slides_url: nil, - subtitle: nil, - title: nil, - videos_urls: nil - } - - def fixture(:session) do - {:ok, session} = Events.create_session(@create_attrs) - session - end - - setup %{conn: conn} do - user = insert(:user) - actor = insert(:actor, user: user) - event = insert(:event, organizer_actor: actor) - {:ok, conn: conn, user: user, event: event} - end - - describe "index" do - test "lists all sessions", %{conn: conn} do - conn = get(conn, session_path(conn, :index)) - assert json_response(conn, 200)["data"] == [] - end - end - - describe "create session" do - test "renders session when data is valid", %{conn: conn, user: user, event: event} do - conn = auth_conn(conn, user) - event_id = event.id - attrs = Map.put(@create_attrs, :event_id, event_id) - conn = post(conn, session_path(conn, :create), session: attrs) - assert %{"id" => id} = json_response(conn, 201)["data"] - - conn = get(conn, session_path(conn, :show_sessions_for_event, event.uuid)) - assert hd(json_response(conn, 200)["data"])["id"] == id - - conn = get(conn, session_path(conn, :show, id)) - - assert json_response(conn, 200)["data"] == %{ - "id" => id, - "audios_urls" => "some audios_urls", - "language" => "some language", - "long_abstract" => "some long_abstract", - "short_abstract" => "some short_abstract", - "slides_url" => "some slides_url", - "subtitle" => "some subtitle", - "title" => "some title", - "videos_urls" => "some videos_urls" - } - end - - test "renders errors when data is invalid", %{conn: conn, user: user, event: event} do - conn = auth_conn(conn, user) - attrs = Map.put(@invalid_attrs, :event_id, event.id) - conn = post(conn, session_path(conn, :create), session: attrs) - assert json_response(conn, 422)["errors"] != %{} - end - end - - describe "update session" do - setup [:create_session] - - test "renders session when data is valid", %{ - conn: conn, - session: %Session{id: id} = session, - user: user, - event: event - } do - conn = auth_conn(conn, user) - attrs = Map.put(@update_attrs, :event_id, event.id) - conn = patch(conn, session_path(conn, :update, session), session: attrs) - assert %{"id" => ^id} = json_response(conn, 200)["data"] - - conn = get(conn, session_path(conn, :show, id)) - - assert json_response(conn, 200)["data"] == %{ - "id" => id, - "audios_urls" => "some updated audios_urls", - "language" => "some updated language", - "long_abstract" => "some updated long_abstract", - "short_abstract" => "some updated short_abstract", - "slides_url" => "some updated slides_url", - "subtitle" => "some updated subtitle", - "title" => "some updated title", - "videos_urls" => "some updated videos_urls" - } - end - - test "renders errors when data is invalid", %{conn: conn, session: session, user: user} do - conn = auth_conn(conn, user) - conn = patch(conn, session_path(conn, :update, session), session: @invalid_attrs) - assert json_response(conn, 422)["errors"] != %{} - end - end - - describe "delete session" do - setup [:create_session] - - test "deletes chosen session", %{conn: conn, session: session, user: user} do - conn = auth_conn(conn, user) - conn = delete(conn, session_path(conn, :delete, session)) - assert response(conn, 204) - - assert_error_sent(404, fn -> - get(conn, session_path(conn, :show, session)) - end) - end - end - - defp create_session(_) do - session = insert(:session) - {:ok, session: session} - end -end diff --git a/test/eventos_web/controllers/tag_controller_test.exs b/test/eventos_web/controllers/tag_controller_test.exs deleted file mode 100644 index b6b187cc9..000000000 --- a/test/eventos_web/controllers/tag_controller_test.exs +++ /dev/null @@ -1,85 +0,0 @@ -defmodule MobilizonWeb.TagControllerTest do - use MobilizonWeb.ConnCase - - import Mobilizon.Factory - - alias Mobilizon.Events - alias Mobilizon.Events.Tag - - @create_attrs %{title: "some title"} - @update_attrs %{title: "some updated title"} - @invalid_attrs %{title: nil} - - def fixture(:tag) do - {:ok, tag} = Events.create_tag(@create_attrs) - tag - end - - setup %{conn: conn} do - user = insert(:user) - actor = insert(:actor, user: user) - {:ok, conn: conn, user: user} - end - - describe "index" do - test "lists all tags", %{conn: conn} do - conn = get(conn, tag_path(conn, :index)) - assert json_response(conn, 200)["data"] == [] - end - end - - describe "create tag" do - test "renders tag when data is valid", %{conn: conn, user: user} do - conn = auth_conn(conn, user) - conn = post(conn, tag_path(conn, :create), tag: @create_attrs) - assert %{"id" => id} = json_response(conn, 201)["data"] - - conn = get(conn, tag_path(conn, :show, id)) - assert json_response(conn, 200)["data"] == %{"id" => id, "title" => "some title"} - end - - test "renders errors when data is invalid", %{conn: conn, user: user} do - conn = auth_conn(conn, user) - conn = post(conn, tag_path(conn, :create), tag: @invalid_attrs) - assert json_response(conn, 422)["errors"] != %{} - end - end - - describe "update tag" do - setup [:create_tag] - - test "renders tag when data is valid", %{conn: conn, tag: %Tag{id: id} = tag, user: user} do - conn = auth_conn(conn, user) - conn = put(conn, tag_path(conn, :update, tag), tag: @update_attrs) - assert %{"id" => ^id} = json_response(conn, 200)["data"] - - conn = get(conn, tag_path(conn, :show, id)) - assert json_response(conn, 200)["data"] == %{"id" => id, "title" => "some updated title"} - end - - test "renders errors when data is invalid", %{conn: conn, tag: tag, user: user} do - conn = auth_conn(conn, user) - conn = put(conn, tag_path(conn, :update, tag), tag: @invalid_attrs) - assert json_response(conn, 422)["errors"] != %{} - end - end - - describe "delete tag" do - setup [:create_tag] - - test "deletes chosen tag", %{conn: conn, tag: tag, user: user} do - conn = auth_conn(conn, user) - conn = delete(conn, tag_path(conn, :delete, tag)) - assert response(conn, 204) - - assert_error_sent(404, fn -> - get(conn, tag_path(conn, :show, tag)) - end) - end - end - - defp create_tag(_) do - tag = fixture(:tag) - {:ok, tag: tag} - end -end diff --git a/test/eventos_web/controllers/track_controller_test.exs b/test/eventos_web/controllers/track_controller_test.exs deleted file mode 100644 index 2065b2395..000000000 --- a/test/eventos_web/controllers/track_controller_test.exs +++ /dev/null @@ -1,116 +0,0 @@ -defmodule MobilizonWeb.TrackControllerTest do - use MobilizonWeb.ConnCase - - import Mobilizon.Factory - - alias Mobilizon.Events - alias Mobilizon.Events.Track - - @create_attrs %{color: "some color", description: "some description", name: "some name"} - @update_attrs %{ - color: "some updated color", - description: "some updated description", - name: "some updated name" - } - @invalid_attrs %{color: nil, description: nil, name: nil} - - def fixture(:track) do - {:ok, track} = Events.create_track(@create_attrs) - track - end - - setup %{conn: conn} do - user = insert(:user) - actor = insert(:actor, user: user) - event = insert(:event, organizer_actor: actor) - {:ok, conn: conn, user: user, event: event} - end - - describe "index" do - test "lists all tracks", %{conn: conn} do - conn = get(conn, track_path(conn, :index)) - assert json_response(conn, 200)["data"] == [] - end - end - - describe "create track" do - test "renders track when data is valid", %{conn: conn, user: user, event: event} do - conn = auth_conn(conn, user) - attrs = Map.put(@create_attrs, :event_id, event.id) - conn = post(conn, track_path(conn, :create), track: attrs) - assert %{"id" => id} = json_response(conn, 201)["data"] - - conn = get(conn, track_path(conn, :show, id)) - - assert json_response(conn, 200)["data"] == %{ - "id" => id, - "color" => "some color", - "description" => "some description", - "name" => "some name" - } - end - - test "renders errors when data is invalid", %{conn: conn, user: user, event: event} do - conn = auth_conn(conn, user) - attrs = Map.put(@invalid_attrs, :event_id, event.id) - conn = post(conn, track_path(conn, :create), track: attrs) - assert json_response(conn, 422)["errors"] != %{} - end - end - - describe "update track" do - setup [:create_track] - - test "renders track when data is valid", %{ - conn: conn, - track: %Track{id: id} = track, - user: user, - event: event - } do - conn = auth_conn(conn, user) - attrs = Map.put(@update_attrs, :event_id, event.id) - conn = put(conn, track_path(conn, :update, track), track: attrs) - assert %{"id" => ^id} = json_response(conn, 200)["data"] - - conn = get(conn, track_path(conn, :show, id)) - - assert json_response(conn, 200)["data"] == %{ - "id" => id, - "color" => "some updated color", - "description" => "some updated description", - "name" => "some updated name" - } - end - - test "renders errors when data is invalid", %{ - conn: conn, - track: track, - user: user, - event: event - } do - conn = auth_conn(conn, user) - attrs = Map.put(@invalid_attrs, :event_id, event.id) - conn = put(conn, track_path(conn, :update, track), track: attrs) - assert json_response(conn, 422)["errors"] != %{} - end - end - - describe "delete track" do - setup [:create_track] - - test "deletes chosen track", %{conn: conn, track: track, user: user} do - conn = auth_conn(conn, user) - conn = delete(conn, track_path(conn, :delete, track)) - assert response(conn, 204) - - assert_error_sent(404, fn -> - get(conn, track_path(conn, :show, track)) - end) - end - end - - defp create_track(_) do - track = insert(:track) - {:ok, track: track} - end -end diff --git a/test/eventos_web/controllers/user_controller_test.exs b/test/eventos_web/controllers/user_controller_test.exs deleted file mode 100644 index 383cc8e80..000000000 --- a/test/eventos_web/controllers/user_controller_test.exs +++ /dev/null @@ -1,223 +0,0 @@ -defmodule MobilizonWeb.UserControllerTest do - use MobilizonWeb.ConnCase - - import Mobilizon.Factory - - alias Mobilizon.Actors - alias Mobilizon.Actors.User - use Bamboo.Test - - @create_attrs %{email: "foo@bar.tld", password: "some password_hash", username: "some username"} - # @update_attrs %{email: "foo@fighters.tld", password: "some updated password_hash", username: "some updated username"} - @invalid_attrs %{email: "not an email", password: nil, username: nil} - - def fixture(:user) do - {:ok, user} = Actors.create_user(@create_attrs) - user - end - - setup %{conn: conn} do - user = insert(:user) - actor = insert(:actor, user: user) - {:ok, conn: conn, user: user, actor: actor} - end - - describe "index" do - test "lists all users", %{conn: conn, user: user} do - conn = auth_conn(conn, user) - conn = get(conn, user_path(conn, :index)) - assert hd(json_response(conn, 200)["data"])["id"] == user.id - end - end - - describe "create user" do - test "renders user when data is valid", %{conn: conn} do - conn = post(conn, user_path(conn, :register), @create_attrs) - assert %{"email" => "foo@bar.tld"} = json_response(conn, 201) - assert {:ok, %User{} = user} = Mobilizon.Actors.get_user_by_email(@create_attrs.email) - assert_delivered_email(Mobilizon.Email.User.confirmation_email(user)) - end - - test "renders errors when data is invalid", %{conn: conn} do - conn = post(conn, user_path(conn, :register), @invalid_attrs) - assert json_response(conn, 422)["errors"] != %{} - end - - test "renders user with avatar when email is valid", %{conn: conn} do - attrs = %{ - email: "contact@framasoft.org", - password: "some password_hash", - username: "framasoft" - } - - conn = post(conn, user_path(conn, :register), attrs) - assert %{"email" => "contact@framasoft.org"} = json_response(conn, 201) - end - end - - describe "validating user" do - test "validate user when token is valid", %{conn: conn} do - conn = post(conn, user_path(conn, :create), @create_attrs) - assert %{"email" => "foo@bar.tld"} = json_response(conn, 201) - assert {:ok, %User{} = user} = Mobilizon.Actors.get_user_by_email(@create_attrs.email) - assert_delivered_email(Mobilizon.Email.User.confirmation_email(user)) - - conn = get(conn, user_path(conn, :validate, user.confirmation_token)) - assert %{"user" => _, "token" => _} = json_response(conn, 200) - end - - test "validate user when token is invalid", %{conn: conn} do - conn = post(conn, user_path(conn, :create), @create_attrs) - assert %{"email" => "foo@bar.tld"} = json_response(conn, 201) - assert {:ok, %User{} = user} = Mobilizon.Actors.get_user_by_email(@create_attrs.email) - assert_delivered_email(Mobilizon.Email.User.confirmation_email(user)) - - conn = get(conn, user_path(conn, :validate, "toto")) - assert %{"error" => _} = json_response(conn, 404) - end - end - - describe "revalidating user" do - test "ask to resend token to user when too soon", %{conn: conn} do - conn = post(conn, user_path(conn, :create), @create_attrs) - assert %{"email" => "foo@bar.tld"} = json_response(conn, 201) - assert {:ok, %User{} = user} = Mobilizon.Actors.get_user_by_email(@create_attrs.email) - assert_delivered_email(Mobilizon.Email.User.confirmation_email(user)) - - conn = post(conn, user_path(conn, :resend_confirmation), %{"email" => @create_attrs.email}) - assert %{"error" => _} = json_response(conn, 404) - end - - test "ask to resend token to user when the time is right", %{conn: conn} do - conn = post(conn, user_path(conn, :create), @create_attrs) - - assert %{"email" => "foo@bar.tld"} = json_response(conn, 201) - assert {:ok, %User{} = user} = Mobilizon.Actors.get_user_by_email(@create_attrs.email) - assert_delivered_email(Mobilizon.Email.User.confirmation_email(user)) - - # Hammer time ! - {:ok, %User{} = user} = - Mobilizon.Actors.update_user(user, %{ - confirmation_sent_at: Timex.shift(user.confirmation_sent_at, hours: -3) - }) - - conn = post(conn, user_path(conn, :resend_confirmation), %{"email" => @create_attrs.email}) - assert_delivered_email(Mobilizon.Email.User.confirmation_email(user)) - assert %{"email" => "foo@bar.tld"} = json_response(conn, 200) - end - end - - describe "resetting user's password" do - test "ask for reset", %{conn: conn, user: user} do - user_email = user.email - - # Send reset email - conn = post(conn, user_path(conn, :send_reset_password), %{"email" => user_email}) - assert {:ok, %User{} = user} = Mobilizon.Actors.get_user_by_email(user.email) - assert_delivered_email(Mobilizon.Email.User.reset_password_email(user)) - assert %{"email" => user_email} = json_response(conn, 200) - - # Call reset route - conn = - post(conn, user_path(conn, :reset_password), %{ - "password" => "new password", - "token" => user.reset_password_token - }) - - user_id = user.id - assert %{"user" => %{"id" => user_id}} = json_response(conn, 200) - end - - test "ask twice for reset too soon", %{conn: conn, user: user} do - user_email = user.email - - # Send reset email - conn = post(conn, user_path(conn, :send_reset_password), %{"email" => user.email}) - assert {:ok, %User{} = user} = Mobilizon.Actors.get_user_by_email(user.email) - assert_delivered_email(Mobilizon.Email.User.reset_password_email(user)) - assert %{"email" => user_email} = json_response(conn, 200) - - # Send reset email again - conn = post(conn, user_path(conn, :send_reset_password), %{"email" => user.email}) - - assert %{"errors" => "You requested a new reset password too early"} = - json_response(conn, 404) - end - - test "ask twice for reset after a while", %{conn: conn, user: user} do - user_email = user.email - - # Send reset email - conn = post(conn, user_path(conn, :send_reset_password), %{"email" => user.email}) - assert {:ok, %User{} = user} = Mobilizon.Actors.get_user_by_email(user.email) - assert_delivered_email(Mobilizon.Email.User.reset_password_email(user)) - assert %{"email" => user_email} = json_response(conn, 200) - - # Hammer time ! - {:ok, %User{} = user} = - Mobilizon.Actors.update_user(user, %{ - reset_password_sent_at: Timex.shift(user.reset_password_sent_at, hours: -3) - }) - - # Send reset email again - conn = post(conn, user_path(conn, :send_reset_password), %{"email" => user.email}) - assert {:ok, %User{} = user} = Mobilizon.Actors.get_user_by_email(user.email) - assert_delivered_email(Mobilizon.Email.User.reset_password_email(user)) - assert %{"email" => user_email} = json_response(conn, 200) - end - - test "ask for reset with wrong address", %{conn: conn} do - conn = post(conn, user_path(conn, :send_reset_password), %{"email" => "yolo@coucou"}) - assert %{"errors" => "Unable to find an user with this email"} = json_response(conn, 404) - end - - test "calling reset route with wrong token", %{conn: conn} do - conn = - post(conn, user_path(conn, :reset_password), %{ - "password" => "new password", - "token" => "just wrong" - }) - - assert %{"errors" => %{"token" => ["Wrong token for password reset"]}} = - json_response(conn, 404) - end - end - - # describe "update user" do - # setup [:create_user] - # - # test "renders user when data is valid", %{conn: conn, user: %User{id: id} = user} do - # conn = auth_conn(conn, user) - # conn = put conn, user_path(conn, :update, user), user: @update_attrs - # assert %{"id" => ^id} = json_response(conn, 200)["data"] - # - # conn = get conn, user_path(conn, :show, id) - # assert json_response(conn, 200)["data"] == %{ - # "id" => id, - # "email" => "some updated email", - # "password_hash" => "some updated password_hash", - # "role" => 43} - # end - # - # test "renders errors when data is invalid", %{conn: conn, user: user} do - # conn = auth_conn(conn, user) - # conn = put conn, user_path(conn, :update, user), user: @invalid_attrs - # assert json_response(conn, 422)["errors"] != %{} - # end - # end - - describe "delete user" do - setup [:create_user] - - test "deletes chosen user", %{conn: conn, user: user} do - conn = auth_conn(conn, user) - conn = delete(conn, user_path(conn, :delete, user)) - assert response(conn, 204) - end - end - - defp create_user(_) do - user = insert(:user) - {:ok, user: user} - end -end diff --git a/test/eventos_web/controllers/user_session_controller_test.exs b/test/eventos_web/controllers/user_session_controller_test.exs deleted file mode 100644 index 8b1378917..000000000 --- a/test/eventos_web/controllers/user_session_controller_test.exs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/test/fixtures/category_picture.png b/test/fixtures/category_picture.png new file mode 100644 index 0000000000000000000000000000000000000000..d3853fefd4c063db2f13649f1959a76eb4cf4c1c GIT binary patch literal 10097 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 literal 0 HcmV?d00001 diff --git a/test/fixtures/category_picture_updated.png b/test/fixtures/category_picture_updated.png new file mode 100644 index 0000000000000000000000000000000000000000..cfcdaac9053daeec2fad4752f0d86ca5afd9c501 GIT binary patch literal 10489 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 literal 0 HcmV?d00001 diff --git a/test/eventos/actors/actors_test.exs b/test/mobilizon/actors/actors_test.exs similarity index 91% rename from test/eventos/actors/actors_test.exs rename to test/mobilizon/actors/actors_test.exs index eec7ee2f6..e7e0e53ef 100644 --- a/test/eventos/actors/actors_test.exs +++ b/test/mobilizon/actors/actors_test.exs @@ -59,6 +59,16 @@ defmodule Mobilizon.ActorsTest do assert actor_fetched = actor end + test "get_actor_for_user/1 returns the actor for an user", %{actor: %{user: user} = actor} do + assert actor = Actors.get_actor_for_user(user) + end + + test "get_actor_for_user/1 returns the actor for an user with no default actor defined" do + user = insert(:user) + actor = insert(:actor, user: user) + assert actor = Actors.get_actor_for_user(user) + end + test "get_actor_with_everything!/1 returns the actor with it's organized events", %{ actor: actor } do @@ -185,7 +195,6 @@ defmodule Mobilizon.ActorsTest do 20_890_513_599_005_517_665_557_846_902_571_022_168_782_075_040_010_449_365_706_450_877_170_130_373_892_202_874_869_873_999_284_399_697_282_332_064_948_148_602_583_340_776_692_090_472_558_740_998_357_203_838_580_321_412_679_020_304_645_826_371_196_718_081_108_049_114_160_630_664_514_340_729_769_453_281_682_773_898_619_827_376_232_969_899_348_462_205_389_310_883_299_183_817_817_999_273_916_446_620_095_414_233_374_619_948_098_516_821_650_069_821_783_810_210_582_035_456_563_335_930_330_252_551_528_035_801_173_640_288_329_718_719_895_926_309_416_142_129_926_226_047_930_429_802_084_560_488_897_717_417_403_272_782_469_039_131_379_953_278_833_320_195_233_761_955_815_307_522_871_787_339_192_744_439_894_317_730_207_141_881_699_363_391_788_150_650_217_284_777_541_358_381_165_360_697_136_307_663_640_904_621_178_632_289_787, 65537} test "test get_public_key_for_url/1 with remote actor" do - require Logger assert Actor.get_public_key_for_url(@remote_account_url) == @remote_actor_key end @@ -199,6 +208,10 @@ defmodule Mobilizon.ActorsTest do assert actor.preferred_username == "some username" end + test "create_actor/1 with empty data returns error changeset" do + assert {:error, %Ecto.Changeset{}} = Actors.create_actor() + end + test "create_actor/1 with invalid data returns error changeset" do assert {:error, %Ecto.Changeset{}} = Actors.create_actor(@invalid_attrs) end @@ -264,7 +277,7 @@ defmodule Mobilizon.ActorsTest do test "create_user/1 with valid data creates a user" do {:ok, %Actor{} = actor} = Actors.create_actor(@actor_valid_attrs) - attrs = Map.put(@valid_attrs, :actor_id, actor.id) + attrs = @valid_attrs |> Map.put(:actor_id, actor.id) |> Map.put(:default_actor_id, actor.id) assert {:ok, %User{} = user} = Actors.create_user(attrs) assert user.email == "foo@bar.tld" assert user.role == 42 @@ -302,12 +315,42 @@ defmodule Mobilizon.ActorsTest do @email "email@domain.tld" @password "password" test "authenticate/1 checks the user's password" do - {:ok, %User{} = user} = Actors.create_user(%{email: @email, password: @password}) + {:ok, %Actor{user: user} = _actor} = + Actors.register(%{email: @email, password: @password, username: "yolo"}) + assert {:ok, _, _} = Actors.authenticate(%{user: user, password: @password}) assert {:error, :unauthorized} == Actors.authenticate(%{user: user, password: "bad password"}) end + + test "get_user_by_email/1 finds an user by it's email" do + {:ok, %Actor{user: %User{email: email} = user} = _actor} = + Actors.register(%{email: @email, password: @password, username: "yolo"}) + + assert email == @email + {:ok, %User{id: id}} = Actors.get_user_by_email(@email) + assert id == user.id + assert {:error, :user_not_found} = Actors.get_user_by_email("no email") + end + + test "get_user_by_email/1 finds an activated user by it's email" do + {:ok, %Actor{user: %User{email: email} = user} = _actor} = + Actors.register(%{email: @email, password: @password, username: "yolo"}) + {:ok, %User{id: id}} = Actors.get_user_by_email(@email, false) + assert id == user.id + assert {:error, :user_not_found} = Actors.get_user_by_email(@email, true) + + Actors.update_user(user, %{ + "confirmed_at" => DateTime.utc_now(), + "confirmation_sent_at" => nil, + "confirmation_token" => nil + }) + + assert {:error, :user_not_found} = Actors.get_user_by_email(@email, false) + {:ok, %User{id: id}} = Actors.get_user_by_email(@email, true) + assert id == user.id + end end describe "groups" do diff --git a/test/eventos/addresses/addresses_test.exs b/test/mobilizon/addresses/addresses_test.exs similarity index 100% rename from test/eventos/addresses/addresses_test.exs rename to test/mobilizon/addresses/addresses_test.exs diff --git a/test/eventos/events/events_test.exs b/test/mobilizon/events/events_test.exs similarity index 95% rename from test/eventos/events/events_test.exs rename to test/mobilizon/events/events_test.exs index 9c5cd651a..46ebc8032 100644 --- a/test/eventos/events/events_test.exs +++ b/test/mobilizon/events/events_test.exs @@ -68,16 +68,18 @@ defmodule Mobilizon.EventsTest do assert Events.get_event_full!(event.id).participants == [] end - test "find_events_by_name/1 returns events for a given name", %{event: event} do - assert event.title == hd(Events.find_events_by_name(event.title)).title + test "find_events_by_name/1 returns events for a given name", %{ + event: %Event{title: title} = event + } do + assert title == hd(Events.find_events_by_name(event.title)).title - event2 = insert(:event, title: "Special event") + %Event{title: title2} = event2 = insert(:event, title: "Special event") assert event2.title == hd(Events.find_events_by_name("Special")).title - event2 = insert(:event, title: "Special event") assert event2.title == hd(Events.find_events_by_name(" Special ")).title - assert [] == Events.find_events_by_name("") + assert title = hd(Events.find_events_by_name("")).title + assert title2 = hd(tl(Events.find_events_by_name(""))).title end test "create_event/1 with valid data creates a event" do @@ -168,10 +170,20 @@ defmodule Mobilizon.EventsTest do {:ok, category: category} end - @valid_attrs %{description: "some description", picture: "some picture", title: "some title"} + @valid_attrs %{ + description: "some description", + picture: %Plug.Upload{ + path: "test/fixtures/category_picture.png", + filename: "category_picture.png" + }, + title: "some title" + } @update_attrs %{ description: "some updated description", - picture: "some updated picture", + picture: %Plug.Upload{ + path: "test/fixtures/category_picture_updated.png", + filename: "category_picture_updated.png" + }, title: "some updated title" } @invalid_attrs %{description: nil, picture: nil, title: nil} @@ -191,7 +203,7 @@ defmodule Mobilizon.EventsTest do test "create_category/1 with valid data creates a category" do assert {:ok, %Category{} = category} = Events.create_category(@valid_attrs) assert category.description == "some description" - assert category.picture == "some picture" + assert category.picture.file_name == @valid_attrs.picture.filename assert category.title == "some title" end @@ -203,7 +215,7 @@ defmodule Mobilizon.EventsTest do assert {:ok, category} = Events.update_category(category, @update_attrs) assert %Category{} = category assert category.description == "some updated description" - assert category.picture == "some updated picture" + assert category.picture.file_name == @update_attrs.picture.filename assert category.title == "some updated title" end diff --git a/test/eventos/service/activitypub/activitypub_test.exs b/test/mobilizon/service/activitypub/activitypub_test.exs similarity index 100% rename from test/eventos/service/activitypub/activitypub_test.exs rename to test/mobilizon/service/activitypub/activitypub_test.exs diff --git a/test/eventos/service/web_finger/web_finger_test.exs b/test/mobilizon/service/web_finger/web_finger_test.exs similarity index 100% rename from test/eventos/service/web_finger/web_finger_test.exs rename to test/mobilizon/service/web_finger/web_finger_test.exs diff --git a/test/eventos_web/controllers/activity_pub_controller_test.exs b/test/mobilizon_web/controllers/activity_pub_controller_test.exs similarity index 100% rename from test/eventos_web/controllers/activity_pub_controller_test.exs rename to test/mobilizon_web/controllers/activity_pub_controller_test.exs diff --git a/test/eventos_web/controllers/nodeinfo_controller_test.exs b/test/mobilizon_web/controllers/nodeinfo_controller_test.exs similarity index 81% rename from test/eventos_web/controllers/nodeinfo_controller_test.exs rename to test/mobilizon_web/controllers/nodeinfo_controller_test.exs index 96ea216f3..9443e2e00 100644 --- a/test/eventos_web/controllers/nodeinfo_controller_test.exs +++ b/test/mobilizon_web/controllers/nodeinfo_controller_test.exs @@ -1,16 +1,16 @@ -defmodule MobilizonWeb.NodeinfoControllerTest do +defmodule MobilizonWeb.NodeInfoControllerTest do use MobilizonWeb.ConnCase @instance Application.get_env(:mobilizon, :instance) test "Get node info schemas", %{conn: conn} do - conn = get(conn, nodeinfo_path(conn, :schemas)) + conn = get(conn, node_info_path(conn, :schemas)) assert json_response(conn, 200) == %{ "links" => [ %{ "href" => - MobilizonWeb.Router.Helpers.nodeinfo_url( + MobilizonWeb.Router.Helpers.node_info_url( MobilizonWeb.Endpoint, :nodeinfo, "2.0" @@ -22,7 +22,7 @@ defmodule MobilizonWeb.NodeinfoControllerTest do end test "Get node info", %{conn: conn} do - conn = get(conn, nodeinfo_path(conn, :nodeinfo, "2.0")) + conn = get(conn, node_info_path(conn, :nodeinfo, "2.0")) resp = json_response(conn, 200) diff --git a/test/eventos_web/controllers/page_controller_test.exs b/test/mobilizon_web/controllers/page_controller_test.exs similarity index 100% rename from test/eventos_web/controllers/page_controller_test.exs rename to test/mobilizon_web/controllers/page_controller_test.exs diff --git a/test/mobilizon_web/resolvers/actor_resolver_test.exs b/test/mobilizon_web/resolvers/actor_resolver_test.exs new file mode 100644 index 000000000..73ec0527b --- /dev/null +++ b/test/mobilizon_web/resolvers/actor_resolver_test.exs @@ -0,0 +1,78 @@ +defmodule MobilizonWeb.Resolvers.ActorResolverTest do + use MobilizonWeb.ConnCase + alias Mobilizon.{Events, Actors} + alias Mobilizon.Actors.Actor + alias MobilizonWeb.AbsintheHelpers + import Mobilizon.Factory + + @valid_actor_params %{email: "test@test.tld", password: "testest", username: "test"} + @non_existent_username "nonexistent" + + describe "Actor Resolver" do + test "find_actor/3 returns an actor by it's username", context do + {:ok, actor} = Actors.register(@valid_actor_params) + + query = """ + { + actor(preferredUsername: "#{actor.preferred_username}") { + preferredUsername, + } + } + """ + + res = + context.conn + |> get("/api", AbsintheHelpers.query_skeleton(query, "actor")) + + assert json_response(res, 200)["data"]["actor"]["preferredUsername"] == + actor.preferred_username + + query = """ + { + actor(preferredUsername: "#{@non_existent_username}") { + preferredUsername, + } + } + """ + + res = + context.conn + |> get("/api", AbsintheHelpers.query_skeleton(query, "actor")) + + assert json_response(res, 200)["data"]["actor"] == nil + + assert hd(json_response(res, 200)["errors"])["message"] == + "Actor with name #{@non_existent_username} not found" + end + + test "get_current_actor/3 returns the current logged-in actor", context do + {:ok, actor} = Actors.register(@valid_actor_params) + + query = """ + { + loggedActor { + avatarUrl, + preferredUsername, + } + } + """ + + res = + context.conn + |> get("/api", AbsintheHelpers.query_skeleton(query, "logged_actor")) + + assert json_response(res, 200)["data"]["loggedActor"] == nil + + assert hd(json_response(res, 200)["errors"])["message"] == + "You need to be logged-in to view current actor" + + res = + context.conn + |> auth_conn(actor.user) + |> get("/api", AbsintheHelpers.query_skeleton(query, "logged_actor")) + + assert json_response(res, 200)["data"]["loggedActor"]["preferredUsername"] == + actor.preferred_username + end + end +end diff --git a/test/mobilizon_web/resolvers/category_resolver_test.exs b/test/mobilizon_web/resolvers/category_resolver_test.exs new file mode 100644 index 000000000..e941c3c7e --- /dev/null +++ b/test/mobilizon_web/resolvers/category_resolver_test.exs @@ -0,0 +1,60 @@ +defmodule MobilizonWeb.Resolvers.CategoryResolverTest do + use MobilizonWeb.ConnCase + alias Mobilizon.Actors + alias Mobilizon.Actors.Actor + alias MobilizonWeb.AbsintheHelpers + import Mobilizon.Factory + + setup %{conn: conn} do + {:ok, %Actor{} = actor} = + Actors.register(%{email: "test@test.tld", password: "testest", username: "test"}) + + {:ok, conn: conn, actor: actor} + end + + describe "Category Resolver" do + test "list_categories/3 returns the list of categories", context do + insert(:category) + insert(:category) + + query = """ + { + categories { + id, + title, + description, + picture { + url, + }, + } + } + """ + + res = + context.conn + |> get("/api", AbsintheHelpers.query_skeleton(query, "categories")) + + assert json_response(res, 200)["data"]["categories"] |> length == 2 + end + + # We can't test an upload…yet? + # test "create_category/3 creates a category", %{conn: conn, actor: actor} do + # mutation = """ + # mutation { + # createCategory(title: "my category", description: "my desc") { + # id, + # title, + # description, + # }, + # } + # """ + + # res = + # conn + # |> auth_conn(actor.user) + # |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) + + # assert json_response(res, 200)["data"]["createCategory"]["title"] == "my category" + # end + end +end diff --git a/test/mobilizon_web/resolvers/event_resolver_test.exs b/test/mobilizon_web/resolvers/event_resolver_test.exs new file mode 100644 index 000000000..756dba5cd --- /dev/null +++ b/test/mobilizon_web/resolvers/event_resolver_test.exs @@ -0,0 +1,167 @@ +defmodule MobilizonWeb.Resolvers.EventResolverTest do + use MobilizonWeb.ConnCase + alias Mobilizon.{Events, Actors} + alias Mobilizon.Actors.Actor + alias MobilizonWeb.AbsintheHelpers + import Mobilizon.Factory + + @event %{description: "some body", title: "some title", begins_on: Ecto.DateTime.utc()} + + setup %{conn: conn} do + {:ok, %Actor{} = actor} = + Actors.register(%{email: "test@test.tld", password: "testest", username: "test"}) + + {:ok, conn: conn, actor: actor} + end + + describe "Event Resolver" do + test "find_event/3 returns an event", context do + category = insert(:category) + + event = + @event + |> Map.put(:organizer_actor_id, context.actor.id) + |> Map.put(:category_id, category.id) + + {:ok, event} = Events.create_event(event) + + query = """ + { + event(uuid: "#{event.uuid}") { + uuid, + } + } + """ + + res = + context.conn + |> get("/api", AbsintheHelpers.query_skeleton(query, "event")) + + assert json_response(res, 200)["data"]["event"]["uuid"] == to_string(event.uuid) + + query = """ + { + event(uuid: "bad uuid") { + uuid, + } + } + """ + + res = + context.conn + |> get("/api", AbsintheHelpers.query_skeleton(query, "event")) + + assert [%{"message" => "Argument \"uuid\" has invalid value \"bad uuid\"."}] = + json_response(res, 400)["errors"] + end + + test "list_participants_for_event/3 returns participants for an event", context do + # Plain event + category = insert(:category) + + event = + @event + |> Map.put(:organizer_actor_id, context.actor.id) + |> Map.put(:category_id, category.id) + + {:ok, event} = Events.create_event(event) + + query = """ + { + participants(uuid: "#{event.uuid}") { + role, + actor { + preferredUsername + } + } + } + """ + + res = + context.conn + |> get("/api", AbsintheHelpers.query_skeleton(query, "participants")) + + assert json_response(res, 200)["data"]["participants"] == [ + %{ + "actor" => %{"preferredUsername" => context.actor.preferred_username}, + "role" => 4 + } + ] + + # Adding a participant + actor2 = insert(:actor) + participant = insert(:participant, event: event, actor: actor2) + + res = + context.conn + |> get("/api", AbsintheHelpers.query_skeleton(query, "participants")) + + assert json_response(res, 200)["data"]["participants"] == [ + %{ + "actor" => %{"preferredUsername" => context.actor.preferred_username}, + "role" => 4 + }, + %{ + "actor" => %{"preferredUsername" => participant.actor.preferred_username}, + "role" => 0 + } + ] + end + + test "create_event/3 creates an event", %{conn: conn, actor: actor} do + category = insert(:category) + + mutation = """ + mutation { + createEvent( + title: "come to my event", + description: "it will be fine", + beginsOn: "#{DateTime.utc_now() |> DateTime.to_iso8601()}", + organizer_actor_id: #{actor.id}, + category_id: #{category.id}, + addressType: #{"OTHER"} + ) { + title, + uuid + } + } + """ + + res = + conn + |> auth_conn(actor.user) + |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) + + assert json_response(res, 200)["data"]["createEvent"]["title"] == "come to my event" + end + + test "search_events_and_actors/3 finds events and actors", %{conn: conn, actor: actor} do + event = insert(:event, title: "test") + + query = """ + { + search(search: "test") { + ...on Event { + title, + uuid, + __typename + }, + ...on Actor { + preferredUsername, + __typename + } + } + } + """ + + res = + conn + |> get("/api", AbsintheHelpers.query_skeleton(query, "search")) + + assert hd(json_response(res, 200)["data"]["search"])["uuid"] == to_string(event.uuid) + + assert hd(tl(json_response(res, 200)["data"]["search"]))["preferredUsername"] == + actor.preferred_username + end + end +end diff --git a/test/mobilizon_web/resolvers/user_resolver_test.exs b/test/mobilizon_web/resolvers/user_resolver_test.exs new file mode 100644 index 000000000..595583476 --- /dev/null +++ b/test/mobilizon_web/resolvers/user_resolver_test.exs @@ -0,0 +1,239 @@ +defmodule MobilizonWeb.Resolvers.UserResolverTest do + use MobilizonWeb.ConnCase + alias Mobilizon.{Events, Actors} + alias Mobilizon.Actors.{Actor, User} + alias MobilizonWeb.AbsintheHelpers + import Mobilizon.Factory + use Bamboo.Test + + @valid_actor_params %{email: "test@test.tld", password: "testest", username: "test"} + @non_existent_username "nonexistent" + + describe "User Resolver" do + test "find_user/3 returns an user by it's id", context do + user = insert(:user) + + query = """ + { + user(id: "#{user.id}") { + email, + } + } + """ + + res = + context.conn + |> get("/api", AbsintheHelpers.query_skeleton(query, "user")) + + assert json_response(res, 200)["data"]["user"]["email"] == user.email + + query = """ + { + user(id: "#{0}") { + email, + } + } + """ + + res = + context.conn + |> get("/api", AbsintheHelpers.query_skeleton(query, "user")) + + assert json_response(res, 200)["data"]["user"] == nil + assert hd(json_response(res, 200)["errors"])["message"] == "User with ID #{0} not found" + end + + test "get_current_user/3 returns the current logged-in user", context do + user = insert(:user) + + query = """ + { + loggedUser { + id + } + } + """ + + res = + context.conn + |> get("/api", AbsintheHelpers.query_skeleton(query, "logged_user")) + + assert json_response(res, 200)["data"]["loggedUser"] == nil + + assert hd(json_response(res, 200)["errors"])["message"] == + "You need to be logged-in to view current user" + + res = + context.conn + |> auth_conn(user) + |> get("/api", AbsintheHelpers.query_skeleton(query, "logged_user")) + + assert json_response(res, 200)["data"]["loggedUser"]["id"] == to_string(user.id) + end + end + + @account_creation %{email: "test@demo.tld", password: "long password", username: "test_account"} + @account_creation_bad_email %{ + email: "y@l@", + password: "long password", + username: "test_account" + } + + test "test create_user_actor/3 creates an user", context do + mutation = """ + mutation { + createUser( + email: "#{@account_creation.email}", + password: "#{@account_creation.password}", + username: "#{@account_creation.username}" + ) { + preferred_username, + user { + email + } + } + } + """ + + res = + context.conn + |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) + + assert json_response(res, 200)["data"]["createUser"]["preferred_username"] == + @account_creation.username + + assert json_response(res, 200)["data"]["createUser"]["user"]["email"] == + @account_creation.email + end + + test "test create_user_actor/3 doesn't create an user with bad email", context do + mutation = """ + mutation { + createUser( + email: "#{@account_creation_bad_email.email}", + password: "#{@account_creation.password}", + username: "#{@account_creation.username}" + ) { + preferred_username, + user { + email + } + } + } + """ + + res = + context.conn + |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) + + assert hd(json_response(res, 200)["errors"])["message"] == "Email doesn't fit required format" + end + + @valid_actor_params %{email: "test@test.tld", password: "testest", username: "test"} + test "test validate_user/3 validates an user", context do + + {:ok, actor} = Actors.register(@valid_actor_params) + + mutation = """ + mutation { + validateUser( + token: "#{actor.user.confirmation_token}" + ) { + token, + user { + id + }, + actor { + preferredUsername + } + } + } + """ + + res = + context.conn + |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) + + assert json_response(res, 200)["data"]["validateUser"]["actor"]["preferredUsername"] == @valid_actor_params.username + + + assert json_response(res, 200)["data"]["validateUser"]["user"]["id"] == + to_string(actor.user.id) + end + + test "test validate_user/3 with invalid token doesn't validate an user", context do + + {:ok, actor} = Actors.register(@valid_actor_params) + + mutation = """ + mutation { + validateUser( + token: "no pass" + ) { + token, + user { + id + }, + actor { + preferredUsername + } + } + } + """ + + res = + context.conn + |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) + + assert hd(json_response(res, 200)["errors"])["message"] == "Invalid token" + end + + test "test resend_confirmation_email/3 with valid email resends an validation email", context do + {:ok, actor} = Actors.register(@valid_actor_params) + + mutation = """ + mutation { + resendConfirmationEmail( + email: "#{actor.user.email}" + ) + } + """ + + res = + context.conn + |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) + + assert hd(json_response(res, 200)["errors"])["message"] == "You requested again a confirmation email too soon" + + # Hammer time ! + Mobilizon.Actors.update_user(actor.user, %{ + confirmation_sent_at: Timex.shift(actor.user.confirmation_sent_at, hours: -3) + }) + + + res = + context.conn + |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) + + assert json_response(res, 200)["data"]["resendConfirmationEmail"] == actor.user.email + assert_delivered_email Mobilizon.Email.User.confirmation_email(actor.user) + end + + test "test resend_confirmation_email/3 with invalid email resends an validation email", context do + {:ok, actor} = Actors.register(@valid_actor_params) + + mutation = """ + mutation { + resendConfirmationEmail( + email: "oh no" + ) + } + """ + + res = + context.conn + |> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) + + assert hd(json_response(res, 200)["errors"])["message"] == "No user to validate with this email was found" + end +end diff --git a/test/eventos_web/views/error_view_test.exs b/test/mobilizon_web/views/error_view_test.exs similarity index 100% rename from test/eventos_web/views/error_view_test.exs rename to test/mobilizon_web/views/error_view_test.exs diff --git a/test/eventos_web/views/layout_view_test.exs b/test/mobilizon_web/views/layout_view_test.exs similarity index 100% rename from test/eventos_web/views/layout_view_test.exs rename to test/mobilizon_web/views/layout_view_test.exs diff --git a/test/eventos_web/views/page_view_test.exs b/test/mobilizon_web/views/page_view_test.exs similarity index 100% rename from test/eventos_web/views/page_view_test.exs rename to test/mobilizon_web/views/page_view_test.exs diff --git a/test/support/abinthe_helpers.ex b/test/support/abinthe_helpers.ex new file mode 100644 index 000000000..396b86b9b --- /dev/null +++ b/test/support/abinthe_helpers.ex @@ -0,0 +1,17 @@ +defmodule MobilizonWeb.AbsintheHelpers do + def query_skeleton(query, query_name) do + %{ + "operationName" => "#{query_name}", + "query" => "query #{query_name} #{query}", + "variables" => "{}" + } + end + + def mutation_skeleton(query) do + %{ + "operationName" => "", + "query" => "#{query}", + "variables" => "" + } + end +end diff --git a/test/support/factory.ex b/test/support/factory.ex index 1deff9fe2..ac81c0cd4 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -82,15 +82,16 @@ defmodule Mobilizon.Factory do actor = build(:actor) %Mobilizon.Events.Event{ - title: sequence("MyEvent"), - description: "My desc", + title: sequence("Ceci est un événement"), + description: "Ceci est une description avec une première phrase assez longue, + puis sur une seconde ligne", begins_on: nil, ends_on: nil, organizer_actor: actor, category: build(:category), physical_address: build(:address), public: true, - url: "#{MobilizonWeb.Endpoint.url()}/@#{actor.url}/#{Ecto.UUID.generate()}" + url: "@#{actor.url}/#{Ecto.UUID.generate()}" } end diff --git a/uploads/.gitkeep b/uploads/.gitkeep new file mode 100644 index 000000000..e69de29bb