diff --git a/.gitignore b/.gitignore index 2c09e1644..e8659535c 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ erl_crash.dump # secrets files as long as you replace their contents by environment # variables. /config/*.secret.exs +/config/releases.exs /setup_db.psql @@ -39,3 +40,5 @@ release/ *.po~ .weblate docker/production/.env +test-junit-report.xml +js/junit.xml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dad82f161..7beac896a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,13 +1,18 @@ image: tcitworld/mobilizon-ci stages: + - install - check + - build-js - test - - deploy - docker + - package + - upload + - deploy variables: MIX_ENV: "test" + YARN_CACHE_FOLDER: "js/.yarn" # DB Variables for Postgres / Postgis POSTGRES_DB: mobilizon_test POSTGRES_USER: postgres @@ -20,95 +25,130 @@ variables: MOBILIZON_DATABASE_HOST: $POSTGRES_HOST GEOLITE_CITIES_PATH: "/usr/share/GeoIP/GeoLite2-City.mmdb" MOBILIZON_INSTANCE_REGISTRATIONS_OPEN: "true" + # Release elements + PACKAGE_REGISTRY_URL: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${CI_PROJECT_NAME}" + ARCH: "amd64" cache: - key: ${CI_COMMIT_REF_SLUG} + key: "${CI_COMMIT_REF_SLUG}-${CI_COMMIT_SHORT_SHA}" paths: - ~/.cache/Cypress - - _build/ - - deps/ - - js/node_modules - cache/Cypress + - deps/ + - _build/ + - js/node_modules + - js/.yarn -lint: +# Installed dependencies are cached across the pipeline +# So there is no need to reinstall them all the time +# It saves minutes during a pipeline build time +install: + stage: install + script: + - yarn --cwd "js" install --frozen-lockfile + - mix deps.get + - mix compile + +lint-elixir: stage: check + before_script: + - mix deps.get script: - export EXITVALUE=0 - - mix deps.get - - mix credo --strict -a || export EXITVALUE=1 - mix format --check-formatted --dry-run || export EXITVALUE=1 - - cd js - - yarn install - - yarn run lint || export EXITVALUE=1 - - yarn run prettier -c . || export EXITVALUE=1 - - yarn run build:assets - - cd ../ + - mix credo --strict -a || export EXITVALUE=1 + - mix sobelow --config || export EXITVALUE=1 - exit $EXITVALUE + +lint-front: + image: node:14 + stage: check + before_script: + - export EXITVALUE=0 + - yarn --cwd "js" install --frozen-lockfile + script: + - yarn --cwd "js" run lint || export EXITVALUE=1 + - yarn --cwd "js" run prettier -c . || export EXITVALUE=1 + - exit $EXITVALUE + +build-frontend: + stage: build-js + image: node:14 + before_script: + - apt update + - apt install -y --no-install-recommends python build-essential webp imagemagick gifsicle jpegoptim optipng pngquant + script: + - yarn --cwd "js" install --frozen-lockfile + - yarn --cwd "js" run build artifacts: - expire_in: 1 day - when: on_success + expire_in: 5 days paths: - priv/static + needs: + - lint-front deps: stage: check + before_script: + - mix deps.get script: - export EXITVALUE=0 - - mix deps.get - mix hex.outdated || export EXITVALUE=1 - - cd js - - yarn outdated || export EXITVALUE=1 + - yarn --cwd "js" outdated || export EXITVALUE=1 - exit $EXITVALUE allow_failure: true + needs: + - install exunit: stage: test services: - name: mdillon/postgis:11 alias: postgres + variables: + MIX_ENV: test before_script: - - cd js - - yarn install - - yarn run build:assets - - cd ../ - mix deps.get - - MIX_ENV=test mix ecto.create - - MIX_ENV=test mix ecto.migrate - dependencies: - - lint + - mix ecto.create + - mix ecto.migrate script: - mix coveralls + artifacts: + when: always + reports: + junit: + - test-junit-report.xml + expire_in: 30 days jest: stage: test + needs: + - lint-front before_script: - - cd js - - yarn install - dependencies: - - lint + - yarn --cwd "js" install --frozen-lockfile script: - - yarn run test:unit --no-color + - yarn --cwd "js" run test:unit --no-color --ci --reporters=default --reporters=jest-junit artifacts: when: always paths: - js/coverage + reports: + junit: + - js/junit.xml expire_in: 30 days + # cypress: # stage: test # services: # - name: mdillon/postgis:11 # alias: postgres +# variables: +# MIX_ENV=e2e # script: -# - mix deps.get -# - cd js -# - yarn install -# - npx cypress install # just to be sure -# - yarn run build -# - cd ../ -# - MIX_ENV=e2e mix ecto.create -# - MIX_ENV=e2e mix ecto.migrate -# - MIX_ENV=e2e mix run priv/repo/e2e.seed.exs -# - MIX_ENV=e2e mix phx.server & +# - mix ecto.create +# - mix ecto.migrate +# - mix run priv/repo/e2e.seed.exs +# - mix phx.server & # - cd js # - npx wait-on http://localhost:4000 # - if [ -z "$CYPRESS_KEY" ]; then npx cypress run; else npx cypress run --record --parallel --key $CYPRESS_KEY; fi @@ -118,25 +158,20 @@ jest: # - js/tests/e2e/screenshots/**/*.png # - js/tests/e2e/videos/**/*.mp4 -# pages: -# stage: deploy -# script: -# # - mkdir public -# # Mobilizon documentation is now on https://framagit.org/framasoft/joinmobilizon/documentation -# # Mix docs disabled because of https://github.com/elixir-lang/ex_doc/issues/1172 -# # - mix deps.get -# # - mix docs -# # - mv doc public/backend -# #- cd js -# #- yarn install -# #- yarn run styleguide:build -# #- mv styleguide ../public/frontend -# only: -# - master -# artifacts: -# expire_in: 1 hour -# paths: -# - public +pages: + stage: deploy + script: + - mkdir public + - mix docs + - mv doc public/backend + # #- yarn run --cwd "js" styleguide:build + # #- mv js/styleguide public/frontend + rules: + - if: '$CI_COMMIT_BRANCH == "master"' + artifacts: + expire_in: 1 hour + paths: + - public .docker: &docker stage: docker @@ -152,14 +187,71 @@ jest: build-docker-master: <<: *docker - only: - - schedules + rules: + - if: '$CI_PROJECT_NAMESPACE != "framasoft"' + when: never + - if: '$CI_PIPELINE_SOURCE == "schedule"' variables: DOCKER_IMAGE_NAME: framasoft/mobilizon:master build-docker-tag: <<: *docker - only: - - tags + rules: + - if: '$CI_PROJECT_NAMESPACE != "framasoft"' + when: never + - if: $CI_COMMIT_TAG variables: DOCKER_IMAGE_NAME: framasoft/mobilizon:$CI_COMMIT_TAG + +package-app: + stage: package + before_script: + - apt update + - apt install -y --no-install-recommends build-essential git cmake + variables: + MIX_ENV: "prod" + script: + - mix local.hex --force + - mix local.rebar --force + - cp docker/production/releases.exs ./config/ + - mix phx.digest + - mix release + artifacts: + expire_in: 2 days + paths: + - _build/prod/rel + +release-upload: + stage: upload + image: curlimages/curl:latest + rules: + - if: $CI_COMMIT_TAG + script: | + APP_VERSION="${CI_COMMIT_TAG}" + APP_ASSET="${CI_PROJECT_NAME}_${APP_VERSION}_${ARCH}.tar.gz" + + echo "Artifact: ${APP_ASSET}" + tar czf ${APP_ASSET} -C _build/prod/rel mobilizon + ls -al ${APP_ASSET} + + curl --silent --show-error --header "JOB-TOKEN: ${CI_JOB_TOKEN}" --upload-file "${APP_ASSET}" ${PACKAGE_REGISTRY_URL}/${APP_VERSION}/${APP_ASSET} + artifacts: + expire_in: 1 day + when: on_success + paths: + - mobilizon_*.tar.gz + +release-create: + stage: deploy + image: registry.gitlab.com/gitlab-org/release-cli:latest + rules: + - if: $CI_COMMIT_TAG + dependencies: [] + cache: {} + script: | + APP_VERSION="${CI_COMMIT_TAG}" + APP_ASSET="${CI_PROJECT_NAME}_${APP_VERSION}_${ARCH}.tar.gz" + + release-cli create --name "$CI_PROJECT_TITLE v$CI_COMMIT_TAG" \ + --tag-name "$CI_COMMIT_TAG" \ + --assets-link "{\"name\":\"${APP_ASSET}\",\"url\":\"${PACKAGE_REGISTRY_URL}/${APP_VERSION}/${APP_ASSET}\"}" diff --git a/.sobelow-conf b/.sobelow-conf new file mode 100644 index 000000000..fc0940908 --- /dev/null +++ b/.sobelow-conf @@ -0,0 +1,12 @@ +[ + verbose: true, + private: false, + skip: true, + router: "lib/web/router.ex", + exit: "low", + format: "txt", + out: "", + threshold: "medium", + ignore: ["Config.HTTPS", "Config.CSP"], + ignore_files: ["config/dev.1.secret.exs", "config/dev.2.secret.exs", "config/dev.3.secret.exs", "config/dev.secret.exs", "config/e2e.secret.exs", "config/prod.secret.exs", "config/test.secret.exs"] +] diff --git a/.sobelow-skips b/.sobelow-skips new file mode 100644 index 000000000..9e3133f2f --- /dev/null +++ b/.sobelow-skips @@ -0,0 +1,2 @@ + +752C0E897CA81ACD81F4BB215FA5F8E4 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f230c294..d9f71341a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,59 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 1.0.5 - 27-01-2020 + +### Fixed + +- Fixed duplicate entries in search with empty search query + +## 1.0.4 - 26-01-2020 + +### Added + +- **Added interface to approve/reject group follow requests** +- **Added UI for group public RSS (Atom) / ICS feeds** +- **Attach ICS files representing the event to notifications and participations emails** +- Add initial support to build Elixir releases +- Add some CSP & other security headers + +### Changed + +- Added `
` to allowed HTML tags +- Events are now correctly ordered by their beginning date on search and group page +- Improve resource metadata parsing by restricting OGP/Twitter metadata to an allowed list of attributes +- Reverse proxy pictures from resource metadata (favicons & such) + +### Fixed + +- **Fixed group remote subscription** +- Upgrade PWA support library to avoid a call to Google CDN +- Fixed group avatar & banner upload +- Fixed some events not showing on homepage +- Fixed the `next` and `prev` attribute not being present in `CollectionPage` ActivityPub Collections +- Added a text to explain that group discussions are restricted to members on discussion list page +- Fixed ICS export timezone issues +- Fixed remote interactions when the content was not local to the instance +- Fixed a federation issue with group member removal +- Hide event organiser profile through the GraphQL API when a group is the organizer +- Fix an issue where the event form datepickers where displayed under the address map + +### Translations + +- Bengali (New!) +- Catalan +- Finnish +- French +- Galician +- German +- Italian +- Norwegian +- Polish +- Portuguese (New!) +- Slovenian (New!) +- Spanish +- Swedish + ## 1.0.3 - 18-12-2020 **This release adds new migrations, be sure to run them before restarting Mobilizon** diff --git a/Dockerfile b/Dockerfile index 9c73fff55..01cde6868 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,6 @@ FROM bitwalker/alpine-elixir:latest -RUN apk add inotify-tools postgresql-client yarn file -RUN apk add --no-cache make gcc libc-dev argon2 imagemagick +RUN apk add --no-cache inotify-tools postgresql-client yarn file make gcc libc-dev argon2 imagemagick cmake build-base libwebp-tools bash ncurses RUN mix local.hex --force && mix local.rebar --force diff --git a/config/config.exs b/config/config.exs index 072708ea7..0bc22d4bf 100644 --- a/config/config.exs +++ b/config/config.exs @@ -81,6 +81,20 @@ config :mobilizon, Mobilizon.Web.Upload, config :mobilizon, Mobilizon.Web.Upload.Uploader.Local, uploads: "uploads" +config :mobilizon, :media_proxy, + enabled: true, + proxy_opts: [ + redirect_on_failure: false, + max_body_length: 25 * 1_048_576, + # Note: max_read_duration defaults to Mobilizon.Web.ReverseProxy.max_read_duration_default/1 + max_read_duration: 30_000, + http: [ + follow_redirect: true, + pool: :media + ] + ], + whitelist: [] + config :mobilizon, Mobilizon.Web.Email.Mailer, adapter: Bamboo.SMTPAdapter, server: "localhost", @@ -215,6 +229,24 @@ config :mobilizon, :maps, type: :openstreetmap ] +config :mobilizon, :http_security, + enabled: true, + sts: false, + sts_max_age: 31_536_000, + csp_policy: [ + script_src: [], + style_src: [], + connect_src: [], + font_src: [], + img_src: ["*.tile.openstreetmap.org"], + manifest_src: [], + media_src: [], + object_src: [], + frame_src: [], + frame_ancestors: [] + ], + referrer_policy: "same-origin" + config :mobilizon, :anonymous, participation: [ allowed: true, diff --git a/config/test.exs b/config/test.exs index 8c4ac30d3..38f132bd4 100644 --- a/config/test.exs +++ b/config/test.exs @@ -59,6 +59,8 @@ config :mobilizon, Mobilizon.Web.Auth.Guardian, secret_key: "some secret" config :mobilizon, :activitypub, sign_object_fetches: false +config :junit_formatter, report_dir: "." + if System.get_env("DOCKER", "false") == "false" && File.exists?("./config/test.secret.exs") do import_config "test.secret.exs" end diff --git a/docker-compose.yml b/docker-compose.yml index 8e12be6be..8726415ba 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ services: postgres: container_name: mobilizon_db restart: unless-stopped - image: mdillon/postgis:11 + image: postgis/postgis:13-3.0 environment: POSTGRES_PASSWORD: postgres POSTGRES_DB: mobilizon_dev diff --git a/docker/production/releases.exs b/docker/production/releases.exs index d3284e0f9..b4cd218c2 100644 --- a/docker/production/releases.exs +++ b/docker/production/releases.exs @@ -5,7 +5,7 @@ import Config config :mobilizon, Mobilizon.Web.Endpoint, server: true, url: [host: System.get_env("MOBILIZON_INSTANCE_HOST", "mobilizon.lan")], - http: [port: 4000], + http: [port: System.get_env("MOBILIZON_INSTANCE_PORT", "4000")], secret_key_base: System.get_env("MOBILIZON_INSTANCE_SECRET_KEY_BASE", "changethis") config :mobilizon, Mobilizon.Web.Auth.Guardian, diff --git a/js/jest.config.js b/js/jest.config.js index b10500065..a87688fe7 100644 --- a/js/jest.config.js +++ b/js/jest.config.js @@ -7,6 +7,7 @@ module.exports = { "!get_union_json.ts", ], coverageReporters: ["html", "text", "text-summary"], + reporters: ["default", "jest-junit"], // The following should fix the issue with svgs and ?inline loader (see Logo.vue), but doesn't work // // transform: { diff --git a/js/package.json b/js/package.json index 67cfc5aa6..9aea87136 100644 --- a/js/package.json +++ b/js/package.json @@ -1,15 +1,15 @@ { "name": "mobilizon", - "version": "1.0.3", + "version": "1.0.5", "private": true, "scripts": { "serve": "vue-cli-service serve", - "build:assets": "vue-cli-service build --modern", - "build:pictures": "bash ./scripts/build/pictures.sh", "build": "yarn run build:assets && yarn run build:pictures", "test:unit": "LANG=en_US.UTF-8 LANGUAGE=en_US:en LC_ALL=en_US.UTF-8 vue-cli-service test:unit", "test:e2e": "vue-cli-service test:e2e", - "lint": "vue-cli-service lint" + "lint": "vue-cli-service lint", + "build:assets": "vue-cli-service build --modern", + "build:pictures": "bash ./scripts/build/pictures.sh" }, "dependencies": { "@absinthe/socket": "^0.2.1", @@ -39,10 +39,10 @@ "phoenix": "^1.4.11", "register-service-worker": "^1.7.1", "tippy.js": "^6.2.3", - "tiptap": "^1.26.0", - "tiptap-extensions": "^1.29.1", + "tiptap": "^1.32.0", + "tiptap-extensions": "^1.34.0", "unfetch": "^4.2.0", - "v-tooltip": "2.0.2", + "v-tooltip": "^2.1.2", "vue": "^2.6.11", "vue-apollo": "^3.0.3", "vue-class-component": "^7.2.3", @@ -52,7 +52,7 @@ "vue-router": "^3.1.6", "vue-scrollto": "^2.17.1", "vue2-leaflet": "^2.0.3", - "vuedraggable": "2.23.2" + "vuedraggable": "^2.24.3" }, "devDependencies": { "@types/jest": "^26.0.18", @@ -65,16 +65,16 @@ "@types/prosemirror-state": "^1.2.4", "@types/prosemirror-view": "^1.11.4", "@types/vuedraggable": "^2.23.0", - "@typescript-eslint/eslint-plugin": "^4.0.1", - "@typescript-eslint/parser": "^4.0.1", - "@vue/cli-plugin-babel": "~4.5.9", - "@vue/cli-plugin-e2e-cypress": "~4.5.9", - "@vue/cli-plugin-eslint": "~4.5.9", - "@vue/cli-plugin-pwa": "~4.5.9", - "@vue/cli-plugin-router": "~4.5.9", - "@vue/cli-plugin-typescript": "~4.5.9", - "@vue/cli-plugin-unit-jest": "~4.5.0", - "@vue/cli-service": "~4.5.9", + "@typescript-eslint/eslint-plugin": "^4.14.1", + "@typescript-eslint/parser": "^4.14.1", + "@vue/cli-plugin-babel": "~4.5.11", + "@vue/cli-plugin-e2e-cypress": "~4.5.11", + "@vue/cli-plugin-eslint": "~4.5.11", + "@vue/cli-plugin-pwa": "~4.5.11", + "@vue/cli-plugin-router": "~4.5.11", + "@vue/cli-plugin-typescript": "~4.5.11", + "@vue/cli-plugin-unit-jest": "~4.5.11", + "@vue/cli-service": "~4.5.11", "@vue/eslint-config-prettier": "^6.0.0", "@vue/eslint-config-typescript": "^7.0.0", "@vue/test-utils": "^1.1.0", @@ -82,7 +82,8 @@ "eslint-config-prettier": "^7.0.0", "eslint-plugin-prettier": "^3.1.3", "eslint-plugin-vue": "^7.0.0", - "mock-apollo-client": "^0.4", + "jest-junit": "^12.0.0", + "mock-apollo-client": "^0.5", "prettier": "2.2.1", "prettier-eslint": "^12.0.0", "sass": "^1.29.0", @@ -94,7 +95,6 @@ "webpack-cli": "^3.3" }, "resolutions": { - "prosemirror-model": "1.9.1", - "prosemirror-state": "1.3.3" + "workbox-webpack-plugin": "5.1.3" } } diff --git a/js/src/components/Group/JoinGroupWithAccount.vue b/js/src/components/Group/JoinGroupWithAccount.vue index e7df3567c..85b51bae4 100644 --- a/js/src/components/Group/JoinGroupWithAccount.vue +++ b/js/src/components/Group/JoinGroupWithAccount.vue @@ -1,5 +1,6 @@ @@ -34,6 +52,12 @@ import RouteName from "../router/name"; return true; } }, + error({ graphQLErrors, networkError }) { + if (networkError) { + this.errors = [networkError.message]; + } + this.errors = graphQLErrors.map((error) => error.message); + }, async result({ data: { interact } }) { switch (interact.__typename) { case "Group": @@ -49,7 +73,7 @@ import RouteName from "../router/name"; }); break; default: - this.error = this.$t("This URL is not supported"); + this.error = [this.$t("This URL is not supported")]; } // await this.$router.replace({ // name: RouteName.EVENT, @@ -62,7 +86,7 @@ import RouteName from "../router/name"; export default class Interact extends Vue { interact!: IEvent | IGroup; - error!: string; + errors: string[] = []; }