Merge branch '2.0-beta.1' into 'main'

2.0-beta.1

Closes #892

See merge request framasoft/mobilizon!1102
This commit is contained in:
Thomas Citharel 2021-11-09 08:46:42 +00:00
commit 16925ebf77
32 changed files with 743 additions and 556 deletions

View File

@ -4,6 +4,85 @@ 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/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 2.0.0 - unreleased
### Added
- Added possibility to follow groups and be notified from new upcoming events
- Export list of participants to CSV, `PDF` and `ODS`
- Allow to set timezone for an event. The timezone is automatically defined from the address if one is defined. If the event timezone is different than the user's current one, a toggle is shown to switch between the two.
- Added initial support for Right To Left languages (such as arabic) and [BiDi](https://en.wikipedia.org/wiki/Bidirectional_text)
- Build releases in `arm` and `arm64` format in addition to `amd64`
- Build Docker images in `arm` and `arm64` format in addition to `amd64`
- Added possibility to indicate the event is fully online
- Added possibility to search only for online events
- Added possibility to search only in past events
- Detect event, comments and posts languages automatically. Allows setting language
- Allow to change an user's password through the users.modify mix task
- Add instance setting so that only the admin can create groups
- Add instance setting so that only groups can create events
- Added JSON-LD metadata about the event in emails
- Added a quick link to email notification settings at the bottom of emails
- Allow to access Mobilizon with a specific language directly by using `https://instance.tld/lang` where `lang` is a language supported by Mobilizon
### Changed
- Multiple UI improvements, including post, event and participation cards, discussions and emails. The « My Events » page was also redesigned to allow showing events from your groups.
- Various accessibility improvements
- Delete current actor ID as well from local storage when unlogging
- Show a default text for instance contact in default terms text when no instance contact is set
- Only show locatecontrol button in leaflet map when we can do geolocation
- Disable push column in notification settings when push is not available
- Show actual language instead of language code in Users admin view
- Empty old & new passwords fields when successful password change
- Don't link to the group page from admin when actor is suspended
- Warn participants when the event organizer is suspended (and therefore the event cancelled)
- Improve metadata on public page
- Make sure some event action pages (participate remotely or without an account) don't get indexed by search engines
- Only send `Tombstone` element in `Delete` activities, not the whole previous deleted element.
- Only add address and tags to event icalendar export if they exist
- `master` branch has been renamed to `main`
### Removed
- Support for Elixir < 1.12 and OTP < 22
### Fixed
- Fix tags autocomplete
- Fix config onboarding after LDAP initial connexion
- Fix events pagination on tags page
- Fixed deduplicated files from orphan media being deleted as well
- Fix deleting own account
- Fix search returning user profiles instead of only groups
- Fix federating geo coordinates
- Fix an issue with group activity items when moving resources
- Fix an issue with Identity Picker
- Fix an issue with TagInput
- Fix an issue when leaving a group
- Fix admin settings edition
- Fix an issue when showing public page of suspended group
- Removed non existing page (`/about/mobilizon`) from sitemap
- Fix action logs containing group suspension events
- Fixed group physical address not exposed to ActivityPub
### Security
- Fixed private messages sent as event replies from Mastodon that were shown publically as public comments. They are now discarded.
### Translations
- Czech
- Gaelic
- German
- Indonesian
- Norwegian Nynorsk
- Occitan
- Persian
- Portuguese (Brazil)
- Russian
- Slovenian
- Spanish
## 1.3.2 - 2021-08-23 ## 1.3.2 - 2021-08-23
### Fixed ### Fixed

View File

@ -1,3 +1,30 @@
# Upgrading from 1.3 to 2.0
Requirements dependencies depend on the way Mobilizon is installed.
## New dependencies requirements
### Release and Docker
You are already using latest Elixir version in the release tarball and Docker images.
### Source install
* Elixir 1.12 and Erlang OTP 22 is now required. If your distribution doesn't provide these versions, you can uninstall them and install [Elixir](https://github.com/asdf-vm/asdf-elixir) through the [ASDF tool](https://asdf-vm.com/).
## Optional dependencies
These are optional, installing them will allow Mobilizon to export to PDF and ODS as well.
### Docker
Everything is included in our Docker image.
### Release and source install
New optional Python dependencies:
* `Python` >= 3.6
* `weasyprint` for PDF export (with [a few extra dependencies](https://doc.courtbouillon.org/weasyprint/stable/first_steps.html))
* `pyexcel-ods3` for ODS export (no extra dependencies)
Both can be installed through pip.
# Upgrading from 1.0 to 1.1 # Upgrading from 1.0 to 1.1
The 1.1 version of Mobilizon brings Elixir releases support. An Elixir release is a self-contained directory that contains all of Mobilizon's code (front-end and backend), it's dependencies, as well as the Erlang Virtual Machine and runtime (only the parts you need). As long as the release has been assembled on the same OS and architecture, it can be deploy and run straight away. [Read more about releases](https://elixir-lang.org/getting-started/mix-otp/config-and-releases.html#releases). The 1.1 version of Mobilizon brings Elixir releases support. An Elixir release is a self-contained directory that contains all of Mobilizon's code (front-end and backend), it's dependencies, as well as the Erlang Virtual Machine and runtime (only the parts you need). As long as the release has been assembled on the same OS and architecture, it can be deploy and run straight away. [Read more about releases](https://elixir-lang.org/getting-started/mix-otp/config-and-releases.html#releases).

View File

@ -88,6 +88,8 @@ config :mobilizon, Mobilizon.Web.Upload,
config :mobilizon, Mobilizon.Web.Upload.Uploader.Local, uploads: "/var/lib/mobilizon/uploads" config :mobilizon, Mobilizon.Web.Upload.Uploader.Local, uploads: "/var/lib/mobilizon/uploads"
config :tz_world, data_dir: "/var/lib/mobilizon/timezones"
config :mobilizon, :media_proxy, config :mobilizon, :media_proxy,
enabled: true, enabled: true,
proxy_opts: [ proxy_opts: [

View File

@ -94,6 +94,8 @@ config :mobilizon, Mobilizon.Web.Auth.Guardian,
config :mobilizon, Mobilizon.Web.Upload.Uploader.Local, uploads: "uploads" config :mobilizon, Mobilizon.Web.Upload.Uploader.Local, uploads: "uploads"
config :tz_world, data_dir: "_build/dev/lib/tz_world/priv"
config :mobilizon, :anonymous, config :mobilizon, :anonymous,
reports: [ reports: [
allowed: true allowed: true

View File

@ -33,9 +33,6 @@ config :mobilizon, :instance,
email_from: System.get_env("MOBILIZON_INSTANCE_EMAIL", "noreply@mobilizon.lan"), email_from: System.get_env("MOBILIZON_INSTANCE_EMAIL", "noreply@mobilizon.lan"),
email_reply_to: System.get_env("MOBILIZON_REPLY_EMAIL", "noreply@mobilizon.lan") email_reply_to: System.get_env("MOBILIZON_REPLY_EMAIL", "noreply@mobilizon.lan")
config :mobilizon, Mobilizon.Web.Upload.Uploader.Local,
uploads: System.get_env("MOBILIZON_UPLOADS", "/app/uploads")
config :mobilizon, Mobilizon.Storage.Repo, config :mobilizon, Mobilizon.Storage.Repo,
adapter: Ecto.Adapters.Postgres, adapter: Ecto.Adapters.Postgres,
username: System.get_env("MOBILIZON_DATABASE_USERNAME", "username"), username: System.get_env("MOBILIZON_DATABASE_USERNAME", "username"),
@ -68,4 +65,8 @@ config :geolix,
} }
] ]
config :mobilizon, Mobilizon.Web.Upload.Uploader.Local, uploads: "/var/lib/mobilizon/uploads" config :mobilizon, Mobilizon.Web.Upload.Uploader.Local,
uploads: System.get_env("MOBILIZON_UPLOADS", "/var/lib/mobilizon/uploads")
config :tz_world,
data_dir: System.get_env("MOBILIZON_TIMEZONES_DIR", "/var/lib/mobilizon/timezones")

View File

@ -37,12 +37,3 @@ config :mobilizon, :cldr,
"ru", "ru",
"sv" "sv"
] ]
cond do
System.get_env("INSTANCE_CONFIG") &&
File.exists?("./config/#{System.get_env("INSTANCE_CONFIG")}") ->
import_config System.get_env("INSTANCE_CONFIG")
true ->
:ok
end

View File

@ -60,6 +60,8 @@ config :mobilizon, Mobilizon.Web.Upload, filters: [], link_name: false
config :mobilizon, Mobilizon.Web.Upload.Uploader.Local, uploads: "test/uploads" config :mobilizon, Mobilizon.Web.Upload.Uploader.Local, uploads: "test/uploads"
config :tz_world, data_dir: "_build/test/lib/tz_world/priv"
config :exvcr, config :exvcr,
vcr_cassette_library_dir: "test/fixtures/vcr_cassettes" vcr_cassette_library_dir: "test/fixtures/vcr_cassettes"

View File

@ -48,9 +48,11 @@ LABEL org.opencontainers.image.title="mobilizon" \
org.opencontainers.image.revision=$VCS_REF \ org.opencontainers.image.revision=$VCS_REF \
org.opencontainers.image.created=$BUILD_DATE org.opencontainers.image.created=$BUILD_DATE
RUN apk add --no-cache openssl ca-certificates ncurses-libs file postgresql-client libgcc libstdc++ imagemagick RUN apk add --no-cache openssl ca-certificates ncurses-libs file postgresql-client libgcc libstdc++ imagemagick python3 py3-pip py3-pillow py3-cffi py3-brotli gcc musl-dev python3-dev pango libxslt-dev
RUN pip install weasyprint pyexcel-ods3
RUN mkdir -p /app/uploads && chown nobody:nobody /app/uploads RUN mkdir -p /var/lib/mobilizon/uploads && chown nobody:nobody /var/lib/mobilizon/uploads
RUN mkdir -p /var/lib/mobilizon/timezones && chown nobody:nobody /var/lib/mobilizon/timezones
RUN mkdir -p /etc/mobilizon && chown nobody:nobody /etc/mobilizon RUN mkdir -p /etc/mobilizon && chown nobody:nobody /etc/mobilizon
USER nobody USER nobody

View File

@ -9,8 +9,7 @@ module.exports = {
"plugin:vue/essential", "plugin:vue/essential",
"eslint:recommended", "eslint:recommended",
"@vue/typescript/recommended", "@vue/typescript/recommended",
"@vue/prettier", "plugin:prettier/recommended",
"@vue/prettier/@typescript-eslint",
], ],
plugins: ["prettier"], plugins: ["prettier"],

View File

@ -1,6 +1,6 @@
{ {
"name": "mobilizon", "name": "mobilizon",
"version": "1.3.2", "version": "2.0.0-beta.1",
"private": true, "private": true,
"scripts": { "scripts": {
"serve": "vue-cli-service serve", "serve": "vue-cli-service serve",
@ -39,7 +39,7 @@
"core-js": "^3.6.4", "core-js": "^3.6.4",
"date-fns": "^2.16.0", "date-fns": "^2.16.0",
"date-fns-tz": "^1.1.6", "date-fns-tz": "^1.1.6",
"graphql": "^15.0.0", "graphql": "^16.0.0",
"graphql-tag": "^2.10.3", "graphql-tag": "^2.10.3",
"intersection-observer": "^0.12.0", "intersection-observer": "^0.12.0",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
@ -74,27 +74,27 @@
"@types/prosemirror-model": "^1.7.2", "@types/prosemirror-model": "^1.7.2",
"@types/prosemirror-state": "^1.2.4", "@types/prosemirror-state": "^1.2.4",
"@types/prosemirror-view": "^1.11.4", "@types/prosemirror-view": "^1.11.4",
"@typescript-eslint/eslint-plugin": "^4.18.0", "@typescript-eslint/eslint-plugin": "^5.3.0",
"@typescript-eslint/parser": "^4.18.0", "@typescript-eslint/parser": "^5.3.0",
"@vue/cli-plugin-babel": "~5.0.0-beta.7", "@vue/cli-plugin-babel": "~5.0.0-rc.0",
"@vue/cli-plugin-e2e-cypress": "~5.0.0-beta.7", "@vue/cli-plugin-e2e-cypress": "~5.0.0-rc.0",
"@vue/cli-plugin-eslint": "~5.0.0-beta.7", "@vue/cli-plugin-eslint": "~5.0.0-rc.0",
"@vue/cli-plugin-pwa": "~5.0.0-beta.7", "@vue/cli-plugin-pwa": "~5.0.0-rc.0",
"@vue/cli-plugin-router": "~5.0.0-beta.7", "@vue/cli-plugin-router": "~5.0.0-rc.0",
"@vue/cli-plugin-typescript": "~5.0.0-beta.7", "@vue/cli-plugin-typescript": "~5.0.0-rc.0",
"@vue/cli-plugin-unit-jest": "~5.0.0-beta.7", "@vue/cli-plugin-unit-jest": "~5.0.0-rc.0",
"@vue/cli-service": "~5.0.0-beta.7", "@vue/cli-service": "~5.0.0-rc.0",
"@vue/eslint-config-prettier": "^6.0.0", "@vue/eslint-config-typescript": "^9.0.0",
"@vue/eslint-config-typescript": "^7.0.0",
"@vue/test-utils": "^1.1.0", "@vue/test-utils": "^1.1.0",
"@vue/vue2-jest": "^27.0.0-alpha.2", "@vue/vue2-jest": "^27.0.0-alpha.3",
"@vue/vue3-jest": "^27.0.0-alpha.1", "@vue/vue3-jest": "^27.0.0-alpha.1",
"cypress": "^8.3.0", "cypress": "^8.3.0",
"eslint": "^7.20.0", "eslint": "^8.2.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-cypress": "^2.10.3", "eslint-plugin-cypress": "^2.10.3",
"eslint-plugin-import": "^2.20.2", "eslint-plugin-import": "^2.20.2",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^7.6.0", "eslint-plugin-vue": "^8.0.3",
"flush-promises": "^1.0.2", "flush-promises": "^1.0.2",
"jest": "^27.1.0", "jest": "^27.1.0",
"jest-junit": "^13.0.0", "jest-junit": "^13.0.0",

View File

@ -6,7 +6,7 @@
:class="{ inline, clickable: actor && actor.type === ActorType.GROUP }" :class="{ inline, clickable: actor && actor.type === ActorType.GROUP }"
> >
<slot></slot> <slot></slot>
<template slot="popover" class="popover"> <template slot="popover">
<actor-card :full="true" :actor="actor" :popover="true" /> <actor-card :full="true" :actor="actor" :popover="true" />
</template> </template>
</v-popover> </v-popover>

View File

@ -9,19 +9,19 @@
> >
<template slot="label"> <template slot="label">
{{ actualLabel }} {{ actualLabel }}
<span
class="is-size-6 has-text-weight-normal"
v-if="gettingLocation"
>{{ $t("Getting location") }}</span
>
</template>
<p class="control" v-if="canShowLocateMeButton && !gettingLocation">
<b-button <b-button
v-if="canShowLocateMeButton && !gettingLocation"
size="is-small"
icon-right="map-marker" icon-right="map-marker"
@click="locateMe" @click="locateMe"
:title="$t('Use my location')" :title="$t('Use my location')"
/> />
<span </p>
class="is-size-6 has-text-weight-normal"
v-else-if="gettingLocation"
>{{ $t("Getting location") }}</span
>
</template>
<b-autocomplete <b-autocomplete
:data="addressData" :data="addressData"
v-model="queryText" v-model="queryText"
@ -29,7 +29,7 @@
field="fullName" field="fullName"
:loading="isFetching" :loading="isFetching"
@typing="fetchAsyncData" @typing="fetchAsyncData"
icon="map-marker" :icon="canShowLocateMeButton ? null : 'map-marker'"
expanded expanded
@select="updateSelected" @select="updateSelected"
v-bind="$attrs" v-bind="$attrs"
@ -71,7 +71,10 @@
:title="$t('Clear address field')" :title="$t('Clear address field')"
/> />
</b-field> </b-field>
<div class="card" v-if="selected.originId || selected.url"> <div
class="card"
v-if="!hideSelected && (selected.originId || selected.url)"
>
<div class="card-content"> <div class="card-content">
<address-info <address-info
:address="selected" :address="selected"
@ -119,6 +122,8 @@ export default class FullAddressAutoComplete extends Mixins(
@Prop({ required: false }) userTimezone!: string; @Prop({ required: false }) userTimezone!: string;
@Prop({ required: false, default: false, type: Boolean }) disabled!: boolean; @Prop({ required: false, default: false, type: Boolean }) disabled!: boolean;
@Prop({ required: false, default: false, type: Boolean }) hideMap!: boolean; @Prop({ required: false, default: false, type: Boolean }) hideMap!: boolean;
@Prop({ required: false, default: false, type: Boolean })
hideSelected!: boolean;
addressModalActive = false; addressModalActive = false;

View File

@ -163,7 +163,7 @@ export default class Map extends Vue {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
div.map-container { div.map-container {
height: 80vh; height: 100%;
width: 100%; width: 100%;
.leaflet-map { .leaflet-map {

View File

@ -82,11 +82,7 @@
tag="span" tag="span"
@keyup.enter="toggleMenu" @keyup.enter="toggleMenu"
> >
<template <template slot="label" v-if="currentActor">
slot="label"
v-if="currentActor"
class="navbar-dropdown-profile"
>
<div class="identity-wrapper"> <div class="identity-wrapper">
<div> <div>
<figure class="image is-32x32" v-if="currentActor.avatar"> <figure class="image is-32x32" v-if="currentActor.avatar">

View File

@ -1232,5 +1232,6 @@
"View full profile": "View full profile", "View full profile": "View full profile",
"Any type": "Any type", "Any type": "Any type",
"In person": "In person", "In person": "In person",
"In the past": "In the past" "In the past": "In the past",
"Only registered users may fetch remote events from their URL.": "Only registered users may fetch remote events from their URL."
} }

View File

@ -1336,5 +1336,6 @@
"View full profile": "Voir le profil complet", "View full profile": "Voir le profil complet",
"Any type": "N'importe quel type", "Any type": "N'importe quel type",
"In person": "En personne", "In person": "En personne",
"In the past": "Dans le passé" "In the past": "Dans le passé",
"Only registered users may fetch remote events from their URL.": "Seul⋅es les utilisateur⋅ices enregistré⋅es peuvent récupérer des événements depuis leur URL."
} }

View File

@ -29,8 +29,10 @@ export default class IdentityEditionMixin extends Mixins(Vue) {
.toLocaleLowerCase() .toLocaleLowerCase()
.normalize("NFD") .normalize("NFD")
.replace(/[\u0300-\u036f]/g, "") .replace(/[\u0300-\u036f]/g, "")
.replace(/\s{2,}/, " ")
.replace(/ /g, "_") .replace(/ /g, "_")
.replace(/[^a-z0-9_]/g, ""); .replace(/[^a-z0-9_]/g, "")
.replace(/_{2,}/, "");
} }
validateUsername(): boolean { validateUsername(): boolean {

View File

@ -8,8 +8,10 @@ function convertToUsername(value: string | null): string {
.toLocaleLowerCase() .toLocaleLowerCase()
.normalize("NFD") .normalize("NFD")
.replace(/[\u0300-\u036f]/g, "") .replace(/[\u0300-\u036f]/g, "")
.replace(/\s{2,}/, " ")
.replace(/ /g, "_") .replace(/ /g, "_")
.replace(/[^a-z0-9_]/g, ""); .replace(/[^a-z0-9_]/g, "")
.replace(/_{2,}/, "");
} }
function autoUpdateUsername( function autoUpdateUsername(

View File

@ -83,7 +83,7 @@
@cancel-anonymous-participation="cancelAnonymousParticipation" @cancel-anonymous-participation="cancelAnonymousParticipation"
/> />
<div class="has-text-right"> <div class="has-text-right">
<template class="visibility" v-if="!event.draft"> <template v-if="!event.draft">
<p v-if="event.visibility === EventVisibility.PUBLIC"> <p v-if="event.visibility === EventVisibility.PUBLIC">
{{ $t("Public event") }} {{ $t("Public event") }}
<b-icon icon="earth" /> <b-icon icon="earth" />

View File

@ -34,6 +34,8 @@
ref="aac" ref="aac"
:placeholder="$t('For instance: London')" :placeholder="$t('For instance: London')"
@input="locchange" @input="locchange"
:hideMap="true"
:hideSelected="true"
/> />
<b-field <b-field
:label="$t('Radius')" :label="$t('Radius')"
@ -144,9 +146,16 @@
</b-pagination> </b-pagination>
</div> </div>
</div> </div>
<b-message v-else-if="$apollo.loading === false" type="is-danger">{{ <b-message v-else-if="$apollo.loading === false" type="is-danger">
$t("No events found") <p>{{ $t("No events found") }}</p>
}}</b-message> <p v-if="searchIsUrl && !currentUser.id">
{{
$t(
"Only registered users may fetch remote events from their URL."
)
}}
</p>
</b-message>
</b-tab-item> </b-tab-item>
<b-tab-item v-if="!tag"> <b-tab-item v-if="!tag">
<template slot="header"> <template slot="header">
@ -211,6 +220,8 @@ import MultiGroupCard from "../components/Group/MultiGroupCard.vue";
import { CONFIG } from "../graphql/config"; import { CONFIG } from "../graphql/config";
import { REVERSE_GEOCODE } from "../graphql/address"; import { REVERSE_GEOCODE } from "../graphql/address";
import debounce from "lodash/debounce"; import debounce from "lodash/debounce";
import { CURRENT_USER_CLIENT } from "@/graphql/user";
import { ICurrentUser } from "@/types/current-user.model";
interface ISearchTimeOption { interface ISearchTimeOption {
label: string; label: string;
@ -267,6 +278,7 @@ const GEOHASH_DEPTH = 9; // put enough accuracy, radius will be used anyway
this.searchGroups = data.searchGroups; this.searchGroups = data.searchGroups;
}, },
}, },
currentUser: CURRENT_USER_CLIENT,
}, },
metaInfo() { metaInfo() {
return { return {
@ -292,6 +304,8 @@ export default class Search extends Vue {
location: IAddress = new Address(); location: IAddress = new Address();
currentUser!: ICurrentUser;
dateOptions: Record<string, ISearchTimeOption> = { dateOptions: Record<string, ISearchTimeOption> = {
past: { past: {
label: this.$t("In the past") as string, label: this.$t("In the past") as string,
@ -570,6 +584,18 @@ export default class Search extends Vue {
private stringExists(value: string | null | undefined): boolean { private stringExists(value: string | null | undefined): boolean {
return this.valueExists(value) && (value as string).length > 0; return this.valueExists(value) && (value as string).length > 0;
} }
get searchIsUrl(): boolean {
let url;
if (!this.search) return false;
try {
url = new URL(this.search);
} catch (_) {
return false;
}
return url.protocol === "http:" || url.protocol === "https:";
}
} }
</script> </script>

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,7 @@ defmodule Mobilizon.GraphQL.API.Search do
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
import Mobilizon.GraphQL.Resolvers.Event.Utils
require Logger require Logger
@ -67,10 +68,14 @@ defmodule Mobilizon.GraphQL.API.Search do
term = String.trim(term) term = String.trim(term)
if is_url(term) do if is_url(term) do
# skip, if it's w not an actor # skip, if it's not an event
case process_from_url(term) do case process_from_url(term) do
%Page{total: _total, elements: [%Event{} = _event]} = page -> %Page{total: _total, elements: [%Event{} = event]} = page ->
{:ok, page} if Map.get(args, :current_user) != nil || check_event_access?(event) do
{:ok, page}
else
{:ok, %{total: 0, elements: []}}
end
_ -> _ ->
{:ok, %{total: 0, elements: []}} {:ok, %{total: 0, elements: []}}

View File

@ -117,28 +117,19 @@ defmodule Mobilizon.GraphQL.Resolvers.Event do
@spec find_event(any(), map(), Absinthe.Resolution.t()) :: @spec find_event(any(), map(), Absinthe.Resolution.t()) ::
{:ok, Event.t()} | {:error, :event_not_found} {:ok, Event.t()} | {:error, :event_not_found}
def find_event(parent, %{uuid: uuid} = args, %{context: context} = resolution) do def find_event(parent, %{uuid: uuid} = args, %{context: context} = resolution) do
with {:has_event, %Event{} = event} <- case Events.get_public_event_by_uuid_with_preload(uuid) do
{:has_event, Events.get_public_event_by_uuid_with_preload(uuid)}, %Event{} = event ->
{:access_valid, true} <- if Map.has_key?(context, :current_user) || check_event_access?(event) do
{:access_valid, Map.has_key?(context, :current_user) || check_event_access(event)} do {:ok, event}
{:ok, event} else
else {:error, :event_not_found}
{:has_event, _} -> end
_ ->
find_private_event(parent, args, resolution) find_private_event(parent, args, resolution)
{:access_valid, _} ->
{:error, :event_not_found}
end end
end end
@spec check_event_access(Event.t()) :: boolean()
defp check_event_access(%Event{local: true}), do: true
defp check_event_access(%Event{url: url}) do
relay_actor_id = Config.relay_actor_id()
Events.check_if_event_has_instance_follow(url, relay_actor_id)
end
@doc """ @doc """
List participants for event (through an event request) List participants for event (through an event request)
""" """

View File

@ -4,6 +4,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Event.Utils do
""" """
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.{Config, Events}
alias Mobilizon.Events.Event alias Mobilizon.Events.Event
alias Mobilizon.Federation.ActivityPub.Permission alias Mobilizon.Federation.ActivityPub.Permission
import Mobilizon.Service.Guards, only: [is_valid_string: 1] import Mobilizon.Service.Guards, only: [is_valid_string: 1]
@ -37,4 +38,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Event.Utils do
def can_event_be_deleted_by?(%Event{} = event, %Actor{id: actor_member_id}) do def can_event_be_deleted_by?(%Event{} = event, %Actor{id: actor_member_id}) do
Event.can_be_managed_by?(event, actor_member_id) Event.can_be_managed_by?(event, actor_member_id)
end end
@spec check_event_access?(Event.t()) :: boolean()
def check_event_access?(%Event{local: true}), do: true
def check_event_access?(%Event{url: url}) do
relay_actor_id = Config.relay_actor_id()
Events.check_if_event_has_instance_follow(url, relay_actor_id)
end
end end

View File

@ -26,7 +26,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Search do
%{page: page, limit: limit} = args, %{page: page, limit: limit} = args,
%{context: context} = _resolution %{context: context} = _resolution
) do ) do
current_actor = Map.get(context, :current_actor, nil) current_actor = Map.get(context, :current_actor)
current_actor_id = if current_actor, do: current_actor.id, else: nil current_actor_id = if current_actor, do: current_actor.id, else: nil
args = Map.put(args, :current_actor_id, current_actor_id) args = Map.put(args, :current_actor_id, current_actor_id)
Search.search_actors(args, page, limit, :Group) Search.search_actors(args, page, limit, :Group)
@ -37,7 +37,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Search do
""" """
@spec search_events(any(), map(), Absinthe.Resolution.t()) :: @spec search_events(any(), map(), Absinthe.Resolution.t()) ::
{:ok, Page.t(Event.t())} | {:error, String.t()} {:ok, Page.t(Event.t())} | {:error, String.t()}
def search_events(_parent, %{page: page, limit: limit} = args, _resolution) do def search_events(
_parent,
%{page: page, limit: limit} = args,
%{context: context} = _resolution
) do
current_user = Map.get(context, :current_user)
args = Map.put(args, :current_user, current_user)
Search.search_events(args, page, limit) Search.search_events(args, page, limit)
end end

View File

@ -0,0 +1,34 @@
# Portions of this file are derived from Pleroma:
# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Mobilizon.TzWorld.Update do
use Mix.Task
alias Mix.Tasks.TzWorld.Update, as: TzWorldUpdate
import Mix.Tasks.Mobilizon.Common
require Logger
@shortdoc "Wrapper on `tz_world.update` task."
@moduledoc """
Changes `Logger` level to `:info` before downloading.
Changes level back when downloads ends.
## Update TzWorld data
mix mobilizon.tz_world.update
"""
@impl true
def run(_args) do
start_mobilizon()
level = Logger.level()
Logger.configure(level: :info)
TzWorldUpdate.run(nil)
Logger.configure(level: level)
end
end

View File

@ -14,7 +14,6 @@ defmodule Mobilizon do
import Cachex.Spec import Cachex.Spec
alias Mix.Tasks.TzWorld.Update, as: TzWorldUpdate
alias Mobilizon.{Config, Storage, Web} alias Mobilizon.{Config, Storage, Web}
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Service.{ErrorPage, ErrorReporting} alias Mobilizon.Service.{ErrorPage, ErrorReporting}
@ -37,9 +36,6 @@ defmodule Mobilizon do
@spec start(:normal | {:takeover, node} | {:failover, node}, term) :: @spec start(:normal | {:takeover, node} | {:failover, node}, term) ::
{:ok, pid} | {:ok, pid, term} | {:error, term} {:ok, pid} | {:ok, pid, term} | {:error, term}
def start(_type, _args) do def start(_type, _args) do
# We update TzWorld at runtime so that the data is not contained in releases
TzWorldUpdate.run(nil)
children = children =
[ [
# supervisors # supervisors
@ -51,7 +47,6 @@ defmodule Mobilizon do
# workers # workers
Guardian.DB.Token.SweeperServer, Guardian.DB.Token.SweeperServer,
ActivityPub.Federator, ActivityPub.Federator,
Mobilizon.PythonWorker,
TzWorld.Backend.DetsWithIndexCache, TzWorld.Backend.DetsWithIndexCache,
cachex_spec(:feed, 2500, 60, 60, &Feed.create_cache/1), cachex_spec(:feed, 2500, 60, 60, &Feed.create_cache/1),
cachex_spec(:ics, 2500, 60, 60, &ICalendar.create_cache/1), cachex_spec(:ics, 2500, 60, 60, &ICalendar.create_cache/1),
@ -73,6 +68,13 @@ defmodule Mobilizon do
] ++ ] ++
task_children(@env) task_children(@env)
children =
if Mobilizon.PythonPort.python_exists?() do
children ++ [Mobilizon.PythonWorker]
else
children
end
ErrorReporting.configure() ErrorReporting.configure()
# Only attach the telemetry logger when we aren't in an IEx shell # Only attach the telemetry logger when we aren't in an IEx shell

View File

@ -3,10 +3,8 @@ defmodule Mobilizon.Service.Export.Participants.ODS do
Export a list of participants to ODS Export a list of participants to ODS
""" """
alias Mobilizon.Events alias Mobilizon.{Events, Export, PythonPort, PythonWorker}
alias Mobilizon.Events.Event alias Mobilizon.Events.Event
alias Mobilizon.Export
alias Mobilizon.PythonWorker
alias Mobilizon.Storage.Repo alias Mobilizon.Storage.Repo
alias Mobilizon.Web.Gettext, as: GettextBackend alias Mobilizon.Web.Gettext, as: GettextBackend
import Mobilizon.Web.Gettext, only: [gettext: 2] import Mobilizon.Web.Gettext, only: [gettext: 2]
@ -91,7 +89,7 @@ defmodule Mobilizon.Service.Export.Participants.ODS do
@spec dependencies_ok? :: boolean @spec dependencies_ok? :: boolean
def dependencies_ok? do def dependencies_ok? do
PythonWorker.has_module("pyexcel_ods3") PythonPort.python_exists?() && PythonWorker.has_module("pyexcel_ods3")
end end
@spec enabled? :: boolean @spec enabled? :: boolean

View File

@ -3,10 +3,8 @@ defmodule Mobilizon.Service.Export.Participants.PDF do
Export a list of participants to PDF Export a list of participants to PDF
""" """
alias Mobilizon.Events alias Mobilizon.{Events, Export, PythonPort, PythonWorker}
alias Mobilizon.Events.Event alias Mobilizon.Events.Event
alias Mobilizon.Export
alias Mobilizon.PythonWorker
alias Mobilizon.Storage.Repo alias Mobilizon.Storage.Repo
alias Mobilizon.Web.ExportView alias Mobilizon.Web.ExportView
alias Mobilizon.Web.Gettext, as: GettextBackend alias Mobilizon.Web.Gettext, as: GettextBackend
@ -105,7 +103,7 @@ defmodule Mobilizon.Service.Export.Participants.PDF do
@spec dependencies_ok? :: boolean @spec dependencies_ok? :: boolean
def dependencies_ok? do def dependencies_ok? do
PythonWorker.has_module("weasyprint") PythonPort.python_exists?() && PythonWorker.has_module("weasyprint")
end end
@spec enabled? :: boolean @spec enabled? :: boolean

View File

@ -5,15 +5,23 @@ defmodule Mobilizon.PythonPort do
use Export.Python use Export.Python
@python_path "/usr/bin/python3"
@doc """
Whether Python3 is installed
"""
@spec python_exists? :: boolean
def python_exists? do
File.exists?(python_path())
end
@doc """ @doc """
## Parameters ## Parameters
- path: directory to include in python path - path: directory to include in python path
""" """
@spec python_instance(String.t()) :: pid @spec python_instance(String.t()) :: pid
def python_instance(path) do def python_instance(path) do
python = "/usr/bin/python3" {:ok, pid} = Python.start(python: python_path(), python_path: path)
{:ok, pid} = Python.start(python: python, python_path: path)
pid pid
end end
@ -25,4 +33,12 @@ defmodule Mobilizon.PythonPort do
def call_python(pid, module, function, arguments \\ []) do def call_python(pid, module, function, arguments \\ []) do
Python.call(pid, module, function, arguments) Python.call(pid, module, function, arguments)
end end
@spec python_path :: String.t()
defp python_path do
case get_in(Application.get_env(:mobilizon, __MODULE__), [:path]) do
path when is_binary(path) -> path
nil -> @python_path
end
end
end end

View File

@ -1,7 +1,7 @@
defmodule Mobilizon.Mixfile do defmodule Mobilizon.Mixfile do
use Mix.Project use Mix.Project
@version "1.3.2" @version "2.0.0-beta.1"
def project do def project do
[ [

View File

@ -98,7 +98,7 @@
"mogrify": {:hex, :mogrify, "0.9.1", "a26f107c4987477769f272bd0f7e3ac4b7b75b11ba597fd001b877beffa9c068", [:mix], [], "hexpm", "134edf189337d2125c0948bf0c228fdeef975c594317452d536224069a5b7f05"}, "mogrify": {:hex, :mogrify, "0.9.1", "a26f107c4987477769f272bd0f7e3ac4b7b75b11ba597fd001b877beffa9c068", [:mix], [], "hexpm", "134edf189337d2125c0948bf0c228fdeef975c594317452d536224069a5b7f05"},
"mox": {:hex, :mox, "1.0.1", "b651bf0113265cda0ba3a827fcb691f848b683c373b77e7d7439910a8d754d6e", [:mix], [], "hexpm", "35bc0dea5499d18db4ef7fe4360067a59b06c74376eb6ab3bd67e6295b133469"}, "mox": {:hex, :mox, "1.0.1", "b651bf0113265cda0ba3a827fcb691f848b683c373b77e7d7439910a8d754d6e", [:mix], [], "hexpm", "35bc0dea5499d18db4ef7fe4360067a59b06c74376eb6ab3bd67e6295b133469"},
"nimble_csv": {:hex, :nimble_csv, "1.1.0", "b1dba4a86be9e03065c9de829050468e591f569100332db949e7ce71be0afc25", [:mix], [], "hexpm", "e986755bc302832cac429be6deda0fc9d82d3c82b47abefb68b3c17c9d949a3f"}, "nimble_csv": {:hex, :nimble_csv, "1.1.0", "b1dba4a86be9e03065c9de829050468e591f569100332db949e7ce71be0afc25", [:mix], [], "hexpm", "e986755bc302832cac429be6deda0fc9d82d3c82b47abefb68b3c17c9d949a3f"},
"nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, "nimble_parsec": {:hex, :nimble_parsec, "1.2.0", "b44d75e2a6542dcb6acf5d71c32c74ca88960421b6874777f79153bbbbd7dccc", [:mix], [], "hexpm", "52b2871a7515a5ac49b00f214e4165a40724cf99798d8e4a65e4fd64ebd002c1"},
"nimble_pool": {:hex, :nimble_pool, "0.1.0", "ffa9d5be27eee2b00b0c634eb649aa27f97b39186fec3c493716c2a33e784ec6", [:mix], [], "hexpm", "343a1eaa620ddcf3430a83f39f2af499fe2370390d4f785cd475b4df5acaf3f9"}, "nimble_pool": {:hex, :nimble_pool, "0.1.0", "ffa9d5be27eee2b00b0c634eb649aa27f97b39186fec3c493716c2a33e784ec6", [:mix], [], "hexpm", "343a1eaa620ddcf3430a83f39f2af499fe2370390d4f785cd475b4df5acaf3f9"},
"oauth2": {:hex, :oauth2, "2.0.0", "338382079fe16c514420fa218b0903f8ad2d4bfc0ad0c9f988867dfa246731b0", [:mix], [{:hackney, "~> 1.13", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "881b8364ac7385f9fddc7949379cbe3f7081da37233a1aa7aab844670a91e7e7"}, "oauth2": {:hex, :oauth2, "2.0.0", "338382079fe16c514420fa218b0903f8ad2d4bfc0ad0c9f988867dfa246731b0", [:mix], [{:hackney, "~> 1.13", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "881b8364ac7385f9fddc7949379cbe3f7081da37233a1aa7aab844670a91e7e7"},
"oauther": {:hex, :oauther, "1.3.0", "82b399607f0ca9d01c640438b34d74ebd9e4acd716508f868e864537ecdb1f76", [:mix], [], "hexpm", "78eb888ea875c72ca27b0864a6f550bc6ee84f2eeca37b093d3d833fbcaec04e"}, "oauther": {:hex, :oauther, "1.3.0", "82b399607f0ca9d01c640438b34d74ebd9e4acd716508f868e864537ecdb1f76", [:mix], [], "hexpm", "78eb888ea875c72ca27b0864a6f550bc6ee84f2eeca37b093d3d833fbcaec04e"},