Merge branch 'fixes' into 'master'

Some fixes

See merge request framasoft/mobilizon!869
This commit is contained in:
Thomas Citharel 2021-03-25 14:25:01 +00:00
commit cd7732f6a5
35 changed files with 790 additions and 501 deletions

1
.gitignore vendored
View File

@ -14,6 +14,7 @@ erl_crash.dump
# secrets files as long as you replace their contents by environment # secrets files as long as you replace their contents by environment
# variables. # variables.
/config/*.secret.exs /config/*.secret.exs
/config/runtime.exs
/setup_db.psql /setup_db.psql

View File

@ -8,5 +8,5 @@
out: "", out: "",
threshold: "medium", threshold: "medium",
ignore: ["Config.HTTPS", "Config.CSP"], 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"] 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", "config/runtime.1.secret.exs", "config/runtime.2.secret.exs", "config/runtime.3.secret.exs", "config/runtime.exs"]
] ]

View File

@ -1,4 +1,5 @@
5048AE33D6269B15E21CF28C6F545AB6 5048AE33D6269B15E21CF28C6F545AB6
752C0E897CA81ACD81F4BB215FA5F8E4 752C0E897CA81ACD81F4BB215FA5F8E4
23412CF16549E4E88366DC9DECF39071

View File

@ -59,7 +59,8 @@ config :mobilizon, Mobilizon.Web.Endpoint,
config :mime, :types, %{ config :mime, :types, %{
"application/activity+json" => ["activity-json"], "application/activity+json" => ["activity-json"],
"application/ld+json" => ["activity-json"], "application/ld+json" => ["activity-json"],
"application/jrd+json" => ["jrd-json"] "application/jrd+json" => ["jrd-json"],
"application/xrd+xml" => ["xrd-xml"]
} }
# Upload configuration # Upload configuration

View File

@ -97,20 +97,3 @@ config :mobilizon, :anonymous,
reports: [ reports: [
allowed: true allowed: true
] ]
require Logger
cond do
System.get_env("INSTANCE_CONFIG") &&
File.exists?("./config/#{System.get_env("INSTANCE_CONFIG")}") ->
import_config System.get_env("INSTANCE_CONFIG")
System.get_env("DOCKER", "false") == "false" && File.exists?("./config/dev.secret.exs") ->
import_config "dev.secret.exs"
System.get_env("DOCKER", "false") == "true" ->
Logger.info("Using environment configuration for Docker")
true ->
Logger.error("No configuration file found")
end

View File

@ -33,7 +33,7 @@
"graphql-tag": "^2.10.3", "graphql-tag": "^2.10.3",
"intersection-observer": "^0.12.0", "intersection-observer": "^0.12.0",
"leaflet": "^1.4.0", "leaflet": "^1.4.0",
"leaflet.locatecontrol": "^0.72.0", "leaflet.locatecontrol": "^0.73.0",
"lodash": "^4.17.11", "lodash": "^4.17.11",
"ngeohash": "^0.6.3", "ngeohash": "^0.6.3",
"phoenix": "^1.4.11", "phoenix": "^1.4.11",
@ -67,14 +67,14 @@
"@types/vuedraggable": "^2.23.0", "@types/vuedraggable": "^2.23.0",
"@typescript-eslint/eslint-plugin": "^4.14.1", "@typescript-eslint/eslint-plugin": "^4.14.1",
"@typescript-eslint/parser": "^4.14.1", "@typescript-eslint/parser": "^4.14.1",
"@vue/cli-plugin-babel": "~4.5.11", "@vue/cli-plugin-babel": "~4.5.12",
"@vue/cli-plugin-e2e-cypress": "~4.5.11", "@vue/cli-plugin-e2e-cypress": "~4.5.12",
"@vue/cli-plugin-eslint": "~4.5.11", "@vue/cli-plugin-eslint": "~4.5.12",
"@vue/cli-plugin-pwa": "~4.5.11", "@vue/cli-plugin-pwa": "~4.5.12",
"@vue/cli-plugin-router": "~4.5.11", "@vue/cli-plugin-router": "~4.5.12",
"@vue/cli-plugin-typescript": "~4.5.11", "@vue/cli-plugin-typescript": "~4.5.12",
"@vue/cli-plugin-unit-jest": "~4.5.11", "@vue/cli-plugin-unit-jest": "~4.5.12",
"@vue/cli-service": "~4.5.11", "@vue/cli-service": "~4.5.12",
"@vue/eslint-config-prettier": "^6.0.0", "@vue/eslint-config-prettier": "^6.0.0",
"@vue/eslint-config-typescript": "^7.0.0", "@vue/eslint-config-typescript": "^7.0.0",
"@vue/test-utils": "^1.1.0", "@vue/test-utils": "^1.1.0",

View File

@ -24,6 +24,9 @@
v-model="newComment.text" v-model="newComment.text"
/> />
</p> </p>
<p class="help is-danger" v-if="emptyCommentError">
{{ $t("Comment text can't be empty") }}
</p>
</div> </div>
<div class="send-comment"> <div class="send-comment">
<b-button <b-button
@ -125,12 +128,23 @@ export default class CommentTree extends Vue {
CommentModeration = CommentModeration; CommentModeration = CommentModeration;
emptyCommentError = false;
@Watch("currentActor") @Watch("currentActor")
watchCurrentActor(currentActor: IPerson): void { watchCurrentActor(currentActor: IPerson): void {
this.newComment.actor = currentActor; this.newComment.actor = currentActor;
} }
@Watch("newComment", { deep: true })
resetEmptyCommentError(newComment: IComment): void {
if (this.emptyCommentError) {
this.emptyCommentError = ["", "<p></p>"].includes(newComment.text);
}
}
async createCommentForEvent(comment: IComment): Promise<void> { async createCommentForEvent(comment: IComment): Promise<void> {
this.emptyCommentError = ["", "<p></p>"].includes(comment.text);
if (this.emptyCommentError) return;
try { try {
if (!comment.actor) return; if (!comment.actor) return;
await this.$apollo.mutate({ await this.$apollo.mutate({
@ -216,10 +230,13 @@ export default class CommentTree extends Vue {
// and reset the new comment field // and reset the new comment field
this.newComment = new CommentModel(); this.newComment = new CommentModel();
} catch (error) { } catch (errors) {
console.error(error); console.error(errors);
if (error.graphQLErrors && error.graphQLErrors.length > 0) { if (errors.graphQLErrors && errors.graphQLErrors.length > 0) {
this.$notifier.error(error.graphQLErrors[0].message); const error = errors.graphQLErrors[0];
if (error.field !== "text" && error.message[0] !== "can't be blank") {
this.$notifier.error(error.message);
}
} }
} }
} }

View File

@ -967,5 +967,7 @@
"You posted a comment on the event {event}.": "You posted a comment on the event {event}.", "You posted a comment on the event {event}.": "You posted a comment on the event {event}.",
"{profile} posted a comment on the event {event}.": "{profile} posted a comment on the event {event}.", "{profile} posted a comment on the event {event}.": "{profile} posted a comment on the event {event}.",
"You replied to a comment on the event {event}.": "You replied to a comment on the event {event}.", "You replied to a comment on the event {event}.": "You replied to a comment on the event {event}.",
"{profile} replied to a comment on the event {event}.": "{profile} replied to a comment on the event {event}." "{profile} replied to a comment on the event {event}.": "{profile} replied to a comment on the event {event}.",
"New post": "New post",
"Comment text can't be empty": "Comment text can't be empty"
} }

View File

@ -1061,5 +1061,7 @@
"You posted a comment on the event {event}.": "Vous avez posté un commentaire sur l'événement {event}.", "You posted a comment on the event {event}.": "Vous avez posté un commentaire sur l'événement {event}.",
"{profile} posted a comment on the event {event}.": "{profile} a posté un commentaire sur l'événement {event}.", "{profile} posted a comment on the event {event}.": "{profile} a posté un commentaire sur l'événement {event}.",
"You replied to a comment on the event {event}.": "Vous avez répondu à un commentaire sur l'événement {event}.", "You replied to a comment on the event {event}.": "Vous avez répondu à un commentaire sur l'événement {event}.",
"{profile} replied to a comment on the event {event}.": "{profile} a répondu à un commentaire sur l'événement {event}." "{profile} replied to a comment on the event {event}.": "{profile} a répondu à un commentaire sur l'événement {event}.",
"New post": "Nouveau billet",
"Comment text can't be empty": "Le texte du commentaire ne peut être vide"
} }

View File

@ -34,7 +34,7 @@ import { Component, Vue } from "vue-property-decorator";
variables() { variables() {
return { return {
id: this.currentActor.id, id: this.currentActor.id,
group: this.$route.params.preferredUsername, group: this.group.preferredUsername,
}; };
}, },
subscribeToMore: { subscribeToMore: {
@ -42,14 +42,14 @@ import { Component, Vue } from "vue-property-decorator";
variables() { variables() {
return { return {
actorId: this.currentActor.id, actorId: this.currentActor.id,
group: this.$route.params.preferredUsername, group: this.group.preferredUsername,
}; };
}, },
skip() { skip() {
return ( return (
!this.currentActor || !this.currentActor ||
!this.currentActor.id || !this.currentActor.id ||
!this.$route.params.preferredUsername !this.group.preferredUsername
); );
}, },
}, },
@ -57,7 +57,7 @@ import { Component, Vue } from "vue-property-decorator";
return ( return (
!this.currentActor || !this.currentActor ||
!this.currentActor.id || !this.currentActor.id ||
!this.$route.params.preferredUsername !this.group.preferredUsername
); );
}, },
}, },

View File

@ -3,7 +3,11 @@
<h1>{{ $t("Create a discussion") }}</h1> <h1>{{ $t("Create a discussion") }}</h1>
<form @submit.prevent="createDiscussion"> <form @submit.prevent="createDiscussion">
<b-field :label="$t('Title')"> <b-field
:label="$t('Title')"
:message="errors.title"
:type="errors.title ? 'is-danger' : undefined"
>
<b-input aria-required="true" required v-model="discussion.title" /> <b-input aria-required="true" required v-model="discussion.title" />
</b-field> </b-field>
@ -64,7 +68,10 @@ export default class CreateDiscussion extends Vue {
discussion = { title: "", text: "" }; discussion = { title: "", text: "" };
errors = { title: "" };
async createDiscussion(): Promise<void> { async createDiscussion(): Promise<void> {
this.errors = { title: "" };
try { try {
if (!this.group.id || !this.currentActor.id) return; if (!this.group.id || !this.currentActor.id) return;
const { data } = await this.$apollo.mutate({ const { data } = await this.$apollo.mutate({
@ -86,7 +93,11 @@ export default class CreateDiscussion extends Vue {
} catch (error) { } catch (error) {
console.error(error); console.error(error);
if (error.graphQLErrors && error.graphQLErrors.length > 0) { if (error.graphQLErrors && error.graphQLErrors.length > 0) {
this.$notifier.error(error.graphQLErrors[0].message); if (error.graphQLErrors[0].field == "title") {
this.errors.title = error.graphQLErrors[0].message;
} else {
this.$notifier.error(error.graphQLErrors[0].message);
}
} }
} }
} }

View File

@ -2,6 +2,43 @@
<div> <div>
<form @submit.prevent="publish(false)" v-if="isCurrentActorAGroupModerator"> <form @submit.prevent="publish(false)" v-if="isCurrentActorAGroupModerator">
<div class="container section"> <div class="container section">
<nav class="breadcrumb" aria-label="breadcrumbs" v-if="actualGroup">
<ul>
<li>
<router-link
v-if="actualGroup"
:to="{
name: RouteName.GROUP,
params: {
preferredUsername: usernameWithDomain(actualGroup),
},
}"
>{{
actualGroup.name || actualGroup.preferredUsername
}}</router-link
>
<b-skeleton v-else :animated="true"></b-skeleton>
</li>
<li>
<router-link
v-if="actualGroup"
:to="{
name: RouteName.POSTS,
params: {
preferredUsername: usernameWithDomain(actualGroup),
},
}"
>{{ $t("Posts") }}</router-link
>
<b-skeleton v-else :animated="true"></b-skeleton>
</li>
<li class="is-active">
<span v-if="preferredUsername">{{ $t("New post") }}</span>
<span v-else-if="slug">{{ $t("Edit post") }}</span>
<b-skeleton v-else :animated="true"></b-skeleton>
</li>
</ul>
</nav>
<h1 class="title" v-if="isUpdate === true"> <h1 class="title" v-if="isUpdate === true">
{{ $t("Edit post") }} {{ $t("Edit post") }}
</h1> </h1>
@ -111,7 +148,6 @@
<script lang="ts"> <script lang="ts">
import { Component, Prop, Watch } from "vue-property-decorator"; import { Component, Prop, Watch } from "vue-property-decorator";
import { mixins } from "vue-class-component"; import { mixins } from "vue-class-component";
import { FETCH_GROUP } from "@/graphql/group";
import { import {
buildFileFromIMedia, buildFileFromIMedia,
buildFileVariable, buildFileVariable,
@ -135,11 +171,24 @@ import TagInput from "../../components/Event/TagInput.vue";
import RouteName from "../../router/name"; import RouteName from "../../router/name";
import Subtitle from "../../components/Utils/Subtitle.vue"; import Subtitle from "../../components/Utils/Subtitle.vue";
import PictureUpload from "../../components/PictureUpload.vue"; import PictureUpload from "../../components/PictureUpload.vue";
import { PERSON_MEMBERSHIP_GROUP } from "@/graphql/actor";
import { FETCH_GROUP } from "@/graphql/group";
@Component({ @Component({
apollo: { apollo: {
tags: TAGS, tags: TAGS,
config: CONFIG, config: CONFIG,
group: {
query: FETCH_GROUP,
variables() {
return {
name: this.preferredUsername,
};
},
skip() {
return !this.preferredUsername;
},
},
post: { post: {
query: FETCH_POST, query: FETCH_POST,
fetchPolicy: "cache-and-network", fetchPolicy: "cache-and-network",
@ -152,15 +201,17 @@ import PictureUpload from "../../components/PictureUpload.vue";
return !this.slug; return !this.slug;
}, },
}, },
group: { person: {
query: FETCH_GROUP, query: PERSON_MEMBERSHIP_GROUP,
fetchPolicy: "cache-and-network",
variables() { variables() {
return { return {
name: this.preferredUsername, id: this.currentActor.id,
group: this.actualGroup.preferredUsername,
}; };
}, },
skip() { skip() {
return !this.preferredUsername; return !this.currentActor?.id || !this.actualGroup?.preferredUsername;
}, },
}, },
}, },
@ -206,6 +257,10 @@ export default class EditPost extends mixins(GroupMixin) {
errors: Record<string, unknown> = {}; errors: Record<string, unknown> = {};
RouteName = RouteName;
usernameWithDomain = usernameWithDomain;
async mounted(): Promise<void> { async mounted(): Promise<void> {
this.pictureFile = await buildFileFromIMedia(this.post.picture); this.pictureFile = await buildFileFromIMedia(this.post.picture);
} }
@ -331,18 +386,6 @@ export default class EditPost extends mixins(GroupMixin) {
} }
return this.group; return this.group;
} }
hasCurrentActorThisRole(givenRole: string | string[]): boolean {
const roles = Array.isArray(givenRole) ? givenRole : [givenRole];
return (
this.person &&
this.actualGroup &&
this.person.memberships.elements.some(
({ parent: { id }, role }) =>
id === this.actualGroup.id && roles.includes(role)
)
);
}
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -360,4 +403,8 @@ form {
} }
} }
} }
.breadcrumb li.is-active > span {
padding: 0 0.75em;
}
</style> </style>

View File

@ -190,6 +190,9 @@
<b-modal :active.sync="createLinkResourceModal" has-modal-card> <b-modal :active.sync="createLinkResourceModal" has-modal-card>
<div class="modal-card"> <div class="modal-card">
<section class="modal-card-body"> <section class="modal-card-body">
<b-message type="is-danger" v-if="modalError">
{{ modalError }}
</b-message>
<form @submit.prevent="createResource"> <form @submit.prevent="createResource">
<b-field :label="$t('URL')"> <b-field :label="$t('URL')">
<b-input <b-input
@ -317,6 +320,8 @@ export default class Resources extends Mixins(ResourceMixin) {
renameModal = false; renameModal = false;
modalError = "";
groupObject: Record<string, unknown> = { groupObject: Record<string, unknown> = {
name: "resources", name: "resources",
pull: "clone", pull: "clone",
@ -341,6 +346,7 @@ export default class Resources extends Mixins(ResourceMixin) {
async createResource(): Promise<void> { async createResource(): Promise<void> {
if (!this.resource.actor) return; if (!this.resource.actor) return;
this.modalError = "";
try { try {
await this.$apollo.mutate({ await this.$apollo.mutate({
mutation: CREATE_RESOURCE, mutation: CREATE_RESOURCE,
@ -364,21 +370,28 @@ export default class Resources extends Mixins(ResourceMixin) {
this.newResource.resourceUrl = ""; this.newResource.resourceUrl = "";
} catch (err) { } catch (err) {
console.error(err); console.error(err);
this.modalError = err.graphQLErrors[0].message;
} }
} }
async previewResource(): Promise<void> { async previewResource(): Promise<void> {
if (this.newResource.resourceUrl === "") return; this.modalError = "";
const { data } = await this.$apollo.mutate({ try {
mutation: PREVIEW_RESOURCE_LINK, if (this.newResource.resourceUrl === "") return;
variables: { const { data } = await this.$apollo.mutate({
resourceUrl: this.newResource.resourceUrl, mutation: PREVIEW_RESOURCE_LINK,
}, variables: {
}); resourceUrl: this.newResource.resourceUrl,
this.newResource.title = data.previewResourceLink.title; },
this.newResource.summary = data.previewResourceLink.description; });
this.newResource.metadata = data.previewResourceLink; this.newResource.title = data.previewResourceLink.title;
this.newResource.type = "link"; this.newResource.summary = data.previewResourceLink.description;
this.newResource.metadata = data.previewResourceLink;
this.newResource.type = "link";
} catch (err) {
console.error(err);
this.modalError = err.graphQLErrors[0].message;
}
} }
createSentenceForType(type: string): string { createSentenceForType(type: string): string {

View File

@ -47,6 +47,7 @@ export default class Validate extends Vue {
this.loading = false; this.loading = false;
await this.$router.push({ name: RouteName.HOME }); await this.$router.push({ name: RouteName.HOME });
} catch (err) { } catch (err) {
this.loading = false;
console.error(err); console.error(err);
this.failed = true; this.failed = true;
} }

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Discussions do
alias Mobilizon.Federation.ActivityPub.Audience alias Mobilizon.Federation.ActivityPub.Audience
alias Mobilizon.Federation.ActivityPub.Types.Entity alias Mobilizon.Federation.ActivityPub.Types.Entity
alias Mobilizon.Federation.ActivityStream.Convertible alias Mobilizon.Federation.ActivityStream.Convertible
alias Mobilizon.GraphQL.API.Utils, as: APIUtils
alias Mobilizon.Service.Activity.Discussion, as: DiscussionActivity alias Mobilizon.Service.Activity.Discussion, as: DiscussionActivity
alias Mobilizon.Web.Endpoint alias Mobilizon.Web.Endpoint
import Mobilizon.Federation.ActivityPub.Utils, only: [make_create_data: 2, make_update_data: 2] import Mobilizon.Federation.ActivityPub.Utils, only: [make_create_data: 2, make_update_data: 2]
@ -17,7 +18,8 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Discussions do
@impl Entity @impl Entity
@spec create(map(), map()) :: {:ok, map()} @spec create(map(), map()) :: {:ok, map()}
def create(%{discussion_id: discussion_id} = args, additional) when not is_nil(discussion_id) do def create(%{discussion_id: discussion_id} = args, additional) when not is_nil(discussion_id) do
with %Discussion{} = discussion <- Discussions.get_discussion(discussion_id), with args <- prepare_args(args),
%Discussion{} = discussion <- Discussions.get_discussion(discussion_id),
{:ok, %Discussion{last_comment_id: last_comment_id} = discussion} <- {:ok, %Discussion{last_comment_id: last_comment_id} = discussion} <-
Discussions.reply_to_discussion(discussion, args), Discussions.reply_to_discussion(discussion, args),
{:ok, _} <- {:ok, _} <-
@ -39,7 +41,8 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Discussions do
@impl Entity @impl Entity
@spec create(map(), map()) :: {:ok, map()} @spec create(map(), map()) :: {:ok, map()}
def create(args, additional) do def create(args, additional) do
with {:ok, %Discussion{} = discussion} <- with args <- prepare_args(args),
{:ok, %Discussion{} = discussion} <-
Discussions.create_discussion(args), Discussions.create_discussion(args),
{:ok, _} <- {:ok, _} <-
DiscussionActivity.insert_activity(discussion, subject: "discussion_created"), DiscussionActivity.insert_activity(discussion, subject: "discussion_created"),
@ -118,4 +121,18 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Discussions do
:ok :ok
end end
defp prepare_args(args) do
{text, _mentions, _tags} =
APIUtils.make_content_html(
args |> Map.get(:text, "") |> String.trim(),
# Can't put additional tags on a comment
[],
"text/html"
)
args
|> Map.update(:title, "", &String.trim/1)
|> Map.put(:text, text)
end
end end

View File

@ -42,6 +42,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Comment do
Comments.create_comment(args) do Comments.create_comment(args) do
{:ok, comment} {:ok, comment}
else else
{:error, err} ->
{:error, err}
{:allowed, false} -> {:allowed, false} ->
{:error, :unauthorized} {:error, :unauthorized}
end end

View File

@ -104,6 +104,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
}) do }) do
{:ok, discussion} {:ok, discussion}
else else
{:error, :discussion, err, _} ->
{:error, err}
{:member, false} -> {:member, false} ->
{:error, :unauthorized} {:error, :unauthorized}
end end

View File

@ -124,6 +124,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
{:ok, %Actor{} = new_person} <- Actors.new_person(args) do {:ok, %Actor{} = new_person} <- Actors.new_person(args) do
{:ok, new_person} {:ok, new_person}
else else
{:error, err} ->
{:error, err}
{:picture, {:error, :file_too_large}} -> {:picture, {:error, :file_too_large}} ->
{:error, dgettext("errors", "The provided picture is too heavy")} {:error, dgettext("errors", "The provided picture is too heavy")}
end end
@ -232,7 +235,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
This function is used to register a person afterwards the user has been created (but not activated) This function is used to register a person afterwards the user has been created (but not activated)
""" """
def register_person(_parent, args, _resolution) do def register_person(_parent, args, _resolution) do
with {:ok, %User{} = user} <- Users.get_user_by_email(args.email), # When registering, email is assumed confirmed (unlike changing email)
with {:ok, %User{} = user} <- Users.get_user_by_email(args.email, unconfirmed: false),
user_actor <- Users.get_actor_for_user(user), user_actor <- Users.get_actor_for_user(user),
no_actor <- is_nil(user_actor), no_actor <- is_nil(user_actor),
{:no_actor, true} <- {:no_actor, no_actor}, {:no_actor, true} <- {:no_actor, no_actor},
@ -364,9 +368,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
context: %{current_user: %User{id: user_id, role: role}} context: %{current_user: %User{id: user_id, role: role}}
} }
) do ) do
with true <- actor_user_id == user_id or is_moderator(role), with {:can_get_events, true} <-
{:can_get_events, actor_user_id == user_id or is_moderator(role)},
%Page{} = page <- Events.list_organized_events_for_actor(actor, page, limit) do %Page{} = page <- Events.list_organized_events_for_actor(actor, page, limit) do
{:ok, page} {:ok, page}
else
{:can_get_events, false} ->
{:error, :unauthorized}
end end
end end

View File

@ -118,6 +118,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
) do ) do
{:ok, resource} {:ok, resource}
else else
{:error, _} ->
{:error, dgettext("errors", "Error while creating resource")}
{:own_check, _} -> {:own_check, _} ->
{:error, dgettext("errors", "Parent resource doesn't belong to this group")} {:error, dgettext("errors", "Parent resource doesn't belong to this group")}
@ -201,8 +204,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Resource do
{:ok, data} when is_map(data) -> {:ok, data} when is_map(data) ->
{:ok, struct(Metadata, data)} {:ok, struct(Metadata, data)}
{:error, _err} -> {:error, :invalid_parsed_data} ->
{:error, dgettext("errors", "Unable to fetch resource details from this URL.")}
{:error, err} ->
Logger.warn("Error while fetching preview from #{inspect(resource_url)}") Logger.warn("Error while fetching preview from #{inspect(resource_url)}")
Logger.debug(inspect(err))
{:error, :unknown_resource} {:error, :unknown_resource}
end end
end end

View File

@ -7,7 +7,6 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
alias Mobilizon.{Actors, Admin, Config, Events, Users} alias Mobilizon.{Actors, Admin, Config, Events, Users}
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Crypto
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Relay alias Mobilizon.Federation.ActivityPub.Relay
alias Mobilizon.Service.Auth.Authenticator alias Mobilizon.Service.Auth.Authenticator
@ -19,8 +18,6 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
require Logger require Logger
@confirmation_token_length 30
@doc """ @doc """
Find an user by its ID Find an user by its ID
""" """
@ -183,11 +180,11 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
@doc """ @doc """
Send the confirmation email again. Send the confirmation email again.
We only do this to accounts unconfirmed We only do this to accounts not activated
""" """
def resend_confirmation_email(_parent, args, _resolution) do def resend_confirmation_email(_parent, args, _resolution) do
with {:ok, %User{locale: locale} = user} <- with {:ok, %User{locale: locale} = user} <-
Users.get_user_by_email(Map.get(args, :email), false), Users.get_user_by_email(Map.get(args, :email), activated: false, unconfirmed: false),
{:ok, email} <- {:ok, email} <-
Email.User.resend_confirmation_email(user, Map.get(args, :locale, locale)) do Email.User.resend_confirmation_email(user, Map.get(args, :locale, locale)) do
{:ok, email} {:ok, email}
@ -205,7 +202,8 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
""" """
def send_reset_password(_parent, args, _resolution) do def send_reset_password(_parent, args, _resolution) do
with email <- Map.get(args, :email), with email <- Map.get(args, :email),
{:ok, %User{locale: locale} = user} <- Users.get_user_by_email(email, true), {:ok, %User{locale: locale} = user} <-
Users.get_user_by_email(email, activated: true, unconfirmed: false),
{:can_reset_password, true} <- {:can_reset_password, true} <-
{:can_reset_password, Authenticator.can_reset_password?(user)}, {:can_reset_password, Authenticator.can_reset_password?(user)},
{:ok, %Bamboo.Email{} = _email_html} <- {:ok, %Bamboo.Email{} = _email_html} <-
@ -256,6 +254,8 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
end end
end end
def change_default_actor(_parent, _args, _resolution), do: {:error, :unauthenticated}
@doc """ @doc """
Returns the list of events for all of this user's identities are going to Returns the list of events for all of this user's identities are going to
""" """
@ -355,14 +355,7 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
{:current_password, Authenticator.login(user.email, password)}, {:current_password, Authenticator.login(user.email, password)},
{:same_email, false} <- {:same_email, new_email == old_email}, {:same_email, false} <- {:same_email, new_email == old_email},
{:email_valid, true} <- {:email_valid, Email.Checker.valid?(new_email)}, {:email_valid, true} <- {:email_valid, Email.Checker.valid?(new_email)},
{:ok, %User{} = user} <- {:ok, %User{} = user} <- Users.update_user_email(user, new_email) do
user
|> User.changeset(%{
unconfirmed_email: new_email,
confirmation_token: Crypto.random_string(@confirmation_token_length),
confirmation_sent_at: DateTime.utc_now() |> DateTime.truncate(:second)
})
|> Repo.update() do
user user
|> Email.User.send_email_reset_old_email() |> Email.User.send_email_reset_old_email()
|> Email.Mailer.deliver_later() |> Email.Mailer.deliver_later()
@ -389,17 +382,12 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
end end
def validate_email(_parent, %{token: token}, _resolution) do def validate_email(_parent, %{token: token}, _resolution) do
with %User{} = user <- Users.get_user_by_activation_token(token), with {:get, %User{} = user} <- {:get, Users.get_user_by_activation_token(token)},
{:ok, %User{} = user} <- {:ok, %User{} = user} <- Users.validate_email(user) do
user
|> User.changeset(%{
email: user.unconfirmed_email,
unconfirmed_email: nil,
confirmation_token: nil,
confirmation_sent_at: nil
})
|> Repo.update() do
{:ok, user} {:ok, user}
else
{:get, nil} ->
{:error, dgettext("errors", "Invalid activation token")}
end end
end end

View File

@ -16,7 +16,7 @@ defmodule Mix.Tasks.Mobilizon.CreateBot do
def run([email, name, summary, type, url]) do def run([email, name, summary, type, url]) do
start_mobilizon() start_mobilizon()
with {:ok, %User{} = user} <- Users.get_user_by_email(email, true), with {:ok, %User{} = user} <- Users.get_user_by_email(email, activated: true),
actor <- Actors.register_bot(%{name: name, summary: summary}), actor <- Actors.register_bot(%{name: name, summary: summary}),
{:ok, %Bot{} = bot} <- {:ok, %Bot{} = bot} <-
Actors.create_bot(%{ Actors.create_bot(%{

View File

@ -28,6 +28,7 @@ defmodule Mobilizon.Discussions.Discussion do
alias Mobilizon.Discussions.Discussion.TitleSlug alias Mobilizon.Discussions.Discussion.TitleSlug
alias Mobilizon.Web.Endpoint alias Mobilizon.Web.Endpoint
alias Mobilizon.Web.Router.Helpers, as: Routes alias Mobilizon.Web.Router.Helpers, as: Routes
import Mobilizon.Web.Gettext, only: [dgettext: 2]
@type t :: %__MODULE__{ @type t :: %__MODULE__{
creator: Actor.t(), creator: Actor.t(),
@ -63,7 +64,7 @@ defmodule Mobilizon.Discussions.Discussion do
discussion discussion
|> cast(attrs, @attrs) |> cast(attrs, @attrs)
|> maybe_generate_id() |> maybe_generate_id()
|> validate_required([:title, :id]) |> validate_required([:title, :id], message: dgettext("errors", "can't be blank"))
|> TitleSlug.maybe_generate_slug() |> TitleSlug.maybe_generate_slug()
|> TitleSlug.unique_constraint() |> TitleSlug.unique_constraint()
|> maybe_generate_url() |> maybe_generate_url()

View File

@ -138,11 +138,12 @@ defmodule Mobilizon.Posts.Post do
defp process_tag(tag), do: Tag.changeset(%Tag{}, tag) defp process_tag(tag), do: Tag.changeset(%Tag{}, tag)
defp maybe_put_publish_date(%Changeset{} = changeset) do defp maybe_put_publish_date(%Changeset{} = changeset) do
publish_at = default_publish_at =
if get_field(changeset, :draft, true) == false, if get_field(changeset, :draft, true) == false,
do: DateTime.utc_now() |> DateTime.truncate(:second), do: DateTime.utc_now() |> DateTime.truncate(:second),
else: nil else: nil
publish_at = get_change(changeset, :publish_at, default_publish_at)
put_change(changeset, :publish_at, publish_at) put_change(changeset, :publish_at, publish_at)
end end

View File

@ -10,7 +10,7 @@ defmodule Mobilizon.Users do
alias Ecto.Multi alias Ecto.Multi
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Events alias Mobilizon.{Crypto, Events}
alias Mobilizon.Events.FeedToken alias Mobilizon.Events.FeedToken
alias Mobilizon.Storage.{Page, Repo} alias Mobilizon.Storage.{Page, Repo}
alias Mobilizon.Users.{Setting, User} alias Mobilizon.Users.{Setting, User}
@ -19,6 +19,8 @@ defmodule Mobilizon.Users do
defenum(NotificationPendingNotificationDelay, none: 0, direct: 1, one_hour: 5, one_day: 10) defenum(NotificationPendingNotificationDelay, none: 0, direct: 1, one_hour: 5, one_day: 10)
@confirmation_token_length 30
@doc """ @doc """
Registers an user. Registers an user.
""" """
@ -66,10 +68,12 @@ defmodule Mobilizon.Users do
@doc """ @doc """
Gets an user by its email. Gets an user by its email.
""" """
@spec get_user_by_email(String.t(), boolean | nil) :: @spec get_user_by_email(String.t(), Keyword.t()) ::
{:ok, User.t()} | {:error, :user_not_found} {:ok, User.t()} | {:error, :user_not_found}
def get_user_by_email(email, activated \\ nil) do def get_user_by_email(email, options \\ []) do
query = user_by_email_query(email, activated) activated = Keyword.get(options, :activated, nil)
unconfirmed = Keyword.get(options, :unconfirmed, true)
query = user_by_email_query(email, activated, unconfirmed)
case Repo.one(query) do case Repo.one(query) do
nil -> nil ->
@ -83,10 +87,13 @@ defmodule Mobilizon.Users do
@doc """ @doc """
Gets an user by its email. Gets an user by its email.
""" """
@spec get_user_by_email!(String.t(), boolean | nil) :: User.t() @spec get_user_by_email!(String.t(), Keyword.t()) :: User.t()
def get_user_by_email!(email, activated \\ nil) do def get_user_by_email!(email, options \\ []) do
activated = Keyword.get(options, :activated, nil)
unconfirmed = Keyword.get(options, :unconfirmed, true)
email email
|> user_by_email_query(activated) |> user_by_email_query(activated, unconfirmed)
|> Repo.one!() |> Repo.one!()
end end
@ -123,6 +130,29 @@ defmodule Mobilizon.Users do
end end
end end
@spec update_user_email(User.t(), String.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
def update_user_email(%User{} = user, new_email) do
user
|> User.changeset(%{
unconfirmed_email: new_email,
confirmation_token: Crypto.random_string(@confirmation_token_length),
confirmation_sent_at: DateTime.utc_now() |> DateTime.truncate(:second)
})
|> Repo.update()
end
@spec validate_email(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()}
def validate_email(%User{} = user) do
user
|> User.changeset(%{
email: user.unconfirmed_email,
unconfirmed_email: nil,
confirmation_token: nil,
confirmation_sent_at: nil
})
|> Repo.update()
end
@delete_user_default_options [reserve_email: true] @delete_user_default_options [reserve_email: true]
@doc """ @doc """
@ -375,29 +405,26 @@ defmodule Mobilizon.Users do
Setting.changeset(setting, %{}) Setting.changeset(setting, %{})
end end
@spec user_by_email_query(String.t(), boolean | nil) :: Ecto.Query.t() @spec user_by_email_query(String.t(), boolean | nil, boolean()) :: Ecto.Query.t()
defp user_by_email_query(email, nil) do defp user_by_email_query(email, activated, unconfirmed) do
from(u in User, User
where: u.email == ^email or u.unconfirmed_email == ^email, |> where([u], u.email == ^email)
preload: :default_actor |> include_unconfirmed(unconfirmed, email)
) |> filter_activated(activated)
|> preload([:default_actor])
end end
defp user_by_email_query(email, true) do defp include_unconfirmed(query, false, _email), do: query
from(
u in User,
where: u.email == ^email and not is_nil(u.confirmed_at) and not u.disabled,
preload: :default_actor
)
end
defp user_by_email_query(email, false) do defp include_unconfirmed(query, true, email),
from( do: or_where(query, [u], u.unconfirmed_email == ^email)
u in User,
where: (u.email == ^email or u.unconfirmed_email == ^email) and is_nil(u.confirmed_at), defp filter_activated(query, nil), do: query
preload: :default_actor
) defp filter_activated(query, true),
end do: where(query, [u], not is_nil(u.confirmed_at) and not u.disabled)
defp filter_activated(query, false), do: where(query, [u], is_nil(u.confirmed_at))
@spec user_by_activation_token_query(String.t()) :: Ecto.Query.t() @spec user_by_activation_token_query(String.t()) :: Ecto.Query.t()
defp user_by_activation_token_query(token) do defp user_by_activation_token_query(token) do

View File

@ -86,7 +86,7 @@ defmodule Mobilizon.Service.Auth.Authenticator do
def fetch_user(nil), do: {:error, :user_not_found} def fetch_user(nil), do: {:error, :user_not_found}
def fetch_user(email) when not is_nil(email) do def fetch_user(email) when not is_nil(email) do
with {:ok, %User{} = user} <- Users.get_user_by_email(email, true) do with {:ok, %User{} = user} <- Users.get_user_by_email(email, activated: true) do
user user
end end
end end

View File

@ -57,7 +57,7 @@ defmodule Mobilizon.Service.RichMedia.Parser do
@spec parse_url(String.t(), Enum.t()) :: {:ok, map()} | {:error, any()} @spec parse_url(String.t(), Enum.t()) :: {:ok, map()} | {:error, any()}
defp parse_url(url, options \\ []) do defp parse_url(url, options \\ []) do
user_agent = Keyword.get(options, :user_agent, Config.instance_user_agent()) user_agent = Keyword.get(options, :user_agent, default_user_agent(url))
headers = [{"User-Agent", user_agent}] headers = [{"User-Agent", user_agent}]
Logger.debug("Fetching content at address #{inspect(url)}") Logger.debug("Fetching content at address #{inspect(url)}")
@ -212,7 +212,8 @@ defmodule Mobilizon.Service.RichMedia.Parser do
end end
defp check_parsed_data(data) do defp check_parsed_data(data) do
{:error, "Found metadata was invalid or incomplete: #{inspect(data)}"} Logger.debug("Found metadata was invalid or incomplete: #{inspect(data)}")
{:error, :invalid_parsed_data}
end end
defp clean_parsed_data(data) do defp clean_parsed_data(data) do
@ -293,4 +294,17 @@ defmodule Mobilizon.Service.RichMedia.Parser do
end end
defp check_remote_picture_path(data), do: data defp check_remote_picture_path(data), do: data
# Twitter requires a well-know crawler user-agent to show server-rendered data
defp default_user_agent("https://twitter.com/" <> _) do
"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
end
defp default_user_agent("https://mobile.twitter.com/" <> _) do
"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
end
defp default_user_agent(_url) do
Config.instance_user_agent()
end
end end

View File

@ -70,8 +70,14 @@ defmodule Mobilizon.Service.RichMedia.Parsers.MetaTagsParser do
defp maybe_put_description(meta, html) when meta != %{} do defp maybe_put_description(meta, html) when meta != %{} do
case get_page_description(html) do case get_page_description(html) do
"" -> meta "" ->
description -> Map.put_new(meta, :description, description) meta
descriptions when is_list(descriptions) and length(descriptions) > 0 ->
Map.put_new(meta, :description, hd(descriptions))
description ->
Map.put_new(meta, :description, description)
end end
end end

View File

@ -120,7 +120,8 @@ defmodule Mobilizon.Web.ReverseProxy do
opts opts
end end
with {:ok, code, headers, client} <- request(method, url, req_headers, hackney_opts), with {:is_url, true} <- {:is_url, valid_uri?(url)},
{:ok, code, headers, client} <- request(method, url, req_headers, hackney_opts),
:ok <- header_length_constraint(headers, Keyword.get(opts, :max_body_length)) do :ok <- header_length_constraint(headers, Keyword.get(opts, :max_body_length)) do
response(conn, client, url, code, headers, opts) response(conn, client, url, code, headers, opts)
else else
@ -129,6 +130,13 @@ defmodule Mobilizon.Web.ReverseProxy do
|> head_response(url, code, headers, opts) |> head_response(url, code, headers, opts)
|> halt() |> halt()
{:is_url, false} ->
Logger.warn("Tried to reverse proxy URL #{inspect(url)}")
conn
|> error_or_redirect(url, 500, "Request failed", opts)
|> halt()
{:error, {:invalid_http_response, code}} -> {:error, {:invalid_http_response, code}} ->
Logger.error("#{__MODULE__}: request to #{inspect(url)} failed with HTTP status #{code}") Logger.error("#{__MODULE__}: request to #{inspect(url)} failed with HTTP status #{code}")
@ -397,4 +405,10 @@ defmodule Mobilizon.Web.ReverseProxy do
def filename(url_or_path) do def filename(url_or_path) do
if path = URI.parse(url_or_path).path, do: Path.basename(path) if path = URI.parse(url_or_path).path, do: Path.basename(path)
end end
@spec valid_uri?(String.t()) :: boolean()
defp valid_uri?(url) do
uri = URI.parse(url)
uri.scheme != nil && uri.host =~ "."
end
end end

View File

@ -9,6 +9,10 @@ defmodule Mobilizon.Web.Router do
plug(Mobilizon.Web.Auth.Pipeline) plug(Mobilizon.Web.Auth.Pipeline)
end end
pipeline :host_meta do
plug(:accepts, ["xrd-xml"])
end
pipeline :well_known do pipeline :well_known do
plug(:accepts, ["json", "jrd-json"]) plug(:accepts, ["json", "jrd-json"])
end end
@ -67,9 +71,14 @@ defmodule Mobilizon.Web.Router do
end end
scope "/.well-known", Mobilizon.Web do scope "/.well-known", Mobilizon.Web do
pipe_through(:well_known) pipe_through(:host_meta)
get("/host-meta", WebFingerController, :host_meta) get("/host-meta", WebFingerController, :host_meta)
end
scope "/.well-known", Mobilizon.Web do
pipe_through(:well_known)
get("/webfinger", WebFingerController, :webfinger) get("/webfinger", WebFingerController, :webfinger)
get("/nodeinfo", NodeInfoController, :schemas) get("/nodeinfo", NodeInfoController, :schemas)
get("/nodeinfo/:version", NodeInfoController, :nodeinfo) get("/nodeinfo/:version", NodeInfoController, :nodeinfo)

View File

@ -138,7 +138,7 @@ defmodule Mobilizon.Mixfile do
{:tesla, "~> 1.4.0"}, {:tesla, "~> 1.4.0"},
{:sitemapper, "~> 0.5.0"}, {:sitemapper, "~> 0.5.0"},
{:xml_builder, "~> 2.1.1"}, {:xml_builder, "~> 2.1.1"},
{:remote_ip, "~> 0.2.0"}, {:remote_ip, "~> 1.0.0"},
{:ex_cldr_languages, "~> 0.2.1"}, {:ex_cldr_languages, "~> 0.2.1"},
{:slugger, "~> 0.3"}, {:slugger, "~> 0.3"},
{:sentry, "~> 8.0"}, {:sentry, "~> 8.0"},

View File

@ -9,8 +9,8 @@
"bamboo_smtp": {:hex, :bamboo_smtp, "4.0.0", "0cc7df161d5d440d280a6d2eb20bf80bc45ea77161728a229e5ab339dcd087cd", [:mix], [{:bamboo, "~> 2.0.0", [hex: :bamboo, repo: "hexpm", optional: false]}, {:gen_smtp, "~> 1.1.0", [hex: :gen_smtp, repo: "hexpm", optional: false]}], "hexpm", "2412015092121b9f24f3f2e654bcd98e5c5f9afb323a94f8defa22e70ba8f23d"}, "bamboo_smtp": {:hex, :bamboo_smtp, "4.0.0", "0cc7df161d5d440d280a6d2eb20bf80bc45ea77161728a229e5ab339dcd087cd", [:mix], [{:bamboo, "~> 2.0.0", [hex: :bamboo, repo: "hexpm", optional: false]}, {:gen_smtp, "~> 1.1.0", [hex: :gen_smtp, repo: "hexpm", optional: false]}], "hexpm", "2412015092121b9f24f3f2e654bcd98e5c5f9afb323a94f8defa22e70ba8f23d"},
"bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"},
"cachex": {:hex, :cachex, "3.3.0", "6f2ebb8f27491fe39121bd207c78badc499214d76c695658b19d6079beeca5c2", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "d90e5ee1dde14cef33f6b187af4335b88748b72b30c038969176cd4e6ccc31a1"}, "cachex": {:hex, :cachex, "3.3.0", "6f2ebb8f27491fe39121bd207c78badc499214d76c695658b19d6079beeca5c2", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "d90e5ee1dde14cef33f6b187af4335b88748b72b30c038969176cd4e6ccc31a1"},
"certifi": {:hex, :certifi, "2.5.3", "70bdd7e7188c804f3a30ee0e7c99655bc35d8ac41c23e12325f36ab449b70651", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "ed516acb3929b101208a9d700062d520f3953da3b6b918d866106ffa980e1c10"}, "certifi": {:hex, :certifi, "2.6.1", "dbab8e5e155a0763eea978c913ca280a6b544bfa115633fa20249c3d396d9493", [:rebar3], [], "hexpm", "524c97b4991b3849dd5c17a631223896272c6b0af446778ba4675a1dff53bb7e"},
"cldr_utils": {:hex, :cldr_utils, "2.15.0", "69a759a73376587aadde3657cd05e86324322dece89508599879b803dd8fa5c5", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "d5632a974e82bacdcb636fafc6b4660ff901f8818ffa058511584de6bfb0df2c"}, "cldr_utils": {:hex, :cldr_utils, "2.15.1", "1136987437ac4821f3ec7d46ff150f1c020a1f79730ff1252bfa7681ed577f31", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "ca3c8da0aab18b4d944541392e9548422e30854e3a9d7768dc629c8123f8efb8"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"comeonin": {:hex, :comeonin, "5.3.2", "5c2f893d05c56ae3f5e24c1b983c2d5dfb88c6d979c9287a76a7feb1e1d8d646", [:mix], [], "hexpm", "d0993402844c49539aeadb3fe46a3c9bd190f1ecf86b6f9ebd71957534c95f04"}, "comeonin": {:hex, :comeonin, "5.3.2", "5c2f893d05c56ae3f5e24c1b983c2d5dfb88c6d979c9287a76a7feb1e1d8d646", [:mix], [], "hexpm", "d0993402844c49539aeadb3fe46a3c9bd190f1ecf86b6f9ebd71957534c95f04"},
"connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"},
@ -41,7 +41,7 @@
"ex_cldr_languages": {:hex, :ex_cldr_languages, "0.2.2", "d7dab93272443b70e18e6aef0f62974418baaca3a3b31a66d51921ec1547113c", [:mix], [{:ex_cldr, "~> 2.2 and >= 2.2.1", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "d9cbf4bf643365b0042e774520ddfcbc31618efd5a5383fac98f75149961622c"}, "ex_cldr_languages": {:hex, :ex_cldr_languages, "0.2.2", "d7dab93272443b70e18e6aef0f62974418baaca3a3b31a66d51921ec1547113c", [:mix], [{:ex_cldr, "~> 2.2 and >= 2.2.1", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "d9cbf4bf643365b0042e774520ddfcbc31618efd5a5383fac98f75149961622c"},
"ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.16.1", "aea1cecbf19dfb6ac82658d05685015172866cf1a4ce0778f58157442ccf015d", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.18", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, "~> 2.8", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ba7a6f9f50aaa7759d05e508a333747cf670c823b2857780b025f3c421f4f1d3"}, "ex_cldr_numbers": {:hex, :ex_cldr_numbers, "2.16.1", "aea1cecbf19dfb6ac82658d05685015172866cf1a4ce0778f58157442ccf015d", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ex_cldr, "~> 2.18", [hex: :ex_cldr, repo: "hexpm", optional: false]}, {:ex_cldr_currencies, "~> 2.8", [hex: :ex_cldr_currencies, repo: "hexpm", optional: false]}, {:ex_doc, "~> 0.18", [hex: :ex_doc, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "ba7a6f9f50aaa7759d05e508a333747cf670c823b2857780b025f3c421f4f1d3"},
"ex_crypto": {:hex, :ex_crypto, "0.10.0", "af600a89b784b36613a989da6e998c1b200ff1214c3cfbaf8deca4aa2f0a1739", [:mix], [{:poison, ">= 2.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm", "ccc7472cfe8a0f4565f97dce7e9280119bf15a5ea51c6535e5b65f00660cde1c"}, "ex_crypto": {:hex, :ex_crypto, "0.10.0", "af600a89b784b36613a989da6e998c1b200ff1214c3cfbaf8deca4aa2f0a1739", [:mix], [{:poison, ">= 2.0.0", [hex: :poison, repo: "hexpm", optional: false]}], "hexpm", "ccc7472cfe8a0f4565f97dce7e9280119bf15a5ea51c6535e5b65f00660cde1c"},
"ex_doc": {:hex, :ex_doc, "0.23.0", "a069bc9b0bf8efe323ecde8c0d62afc13d308b1fa3d228b65bca5cf8703a529d", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f5e2c4702468b2fd11b10d39416ddadd2fcdd173ba2a0285ebd92c39827a5a16"}, "ex_doc": {:hex, :ex_doc, "0.24.1", "15673de99154f93ca7f05900e4e4155ced1ee0cd34e0caeee567900a616871a4", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "07972f17bdf7dc7b5bd76ec97b556b26178ed3f056e7ec9288eb7cea7f91cce2"},
"ex_ical": {:hex, :ex_ical, "0.2.0", "4b928b554614704016cc0c9ee226eb854da9327a1cc460457621ceacb1ac29a6", [:mix], [{:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "db76473b2ae0259e6633c6c479a5a4d8603f09497f55c88f9ef4d53d2b75befb"}, "ex_ical": {:hex, :ex_ical, "0.2.0", "4b928b554614704016cc0c9ee226eb854da9327a1cc460457621ceacb1ac29a6", [:mix], [{:timex, "~> 3.1", [hex: :timex, repo: "hexpm", optional: false]}], "hexpm", "db76473b2ae0259e6633c6c479a5a4d8603f09497f55c88f9ef4d53d2b75befb"},
"ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"}, "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"},
"ex_optimizer": {:hex, :ex_optimizer, "0.1.1", "62da37e206fc2233ff7a4e54e40eae365c40f96c81992fcd15b782eb25169b80", [:mix], [{:file_info, "~> 0.0.4", [hex: :file_info, repo: "hexpm", optional: false]}], "hexpm", "e6f5c059bcd58b66be2f6f257fdc4f69b74b0fa5c9ddd669486af012e4b52286"}, "ex_optimizer": {:hex, :ex_optimizer, "0.1.1", "62da37e206fc2233ff7a4e54e40eae365c40f96c81992fcd15b782eb25169b80", [:mix], [{:file_info, "~> 0.0.4", [hex: :file_info, repo: "hexpm", optional: false]}], "hexpm", "e6f5c059bcd58b66be2f6f257fdc4f69b74b0fa5c9ddd669486af012e4b52286"},
@ -66,7 +66,7 @@
"guardian": {:hex, :guardian, "2.1.1", "1f02b349f6ba765647cc834036a8d76fa4bd65605342fe3a031df3c99d0d411a", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "189b87ba7ce6b40d6ba029138098b96ffc4ae78f229f5b39539b9141af8bf0f8"}, "guardian": {:hex, :guardian, "2.1.1", "1f02b349f6ba765647cc834036a8d76fa4bd65605342fe3a031df3c99d0d411a", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "189b87ba7ce6b40d6ba029138098b96ffc4ae78f229f5b39539b9141af8bf0f8"},
"guardian_db": {:hex, :guardian_db, "2.1.0", "ec95a9d99cdd1e550555d09a7bb4a340d8887aad0697f594590c2fd74be02426", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:guardian, "~> 1.0 or ~> 2.0", [hex: :guardian, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "f8e7d543ac92c395f3a7fd5acbe6829faeade57d688f7562e2f0fca8f94a0d70"}, "guardian_db": {:hex, :guardian_db, "2.1.0", "ec95a9d99cdd1e550555d09a7bb4a340d8887aad0697f594590c2fd74be02426", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:guardian, "~> 1.0 or ~> 2.0", [hex: :guardian, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.13", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "f8e7d543ac92c395f3a7fd5acbe6829faeade57d688f7562e2f0fca8f94a0d70"},
"guardian_phoenix": {:hex, :guardian_phoenix, "2.0.1", "89a817265af09a6ddf7cb1e77f17ffca90cea2db10ff888375ef34502b2731b1", [:mix], [{:guardian, "~> 2.0", [hex: :guardian, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "21f439246715192b231f228680465d1ed5fbdf01555a4a3b17165532f5f9a08c"}, "guardian_phoenix": {:hex, :guardian_phoenix, "2.0.1", "89a817265af09a6ddf7cb1e77f17ffca90cea2db10ff888375ef34502b2731b1", [:mix], [{:guardian, "~> 2.0", [hex: :guardian, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.3", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "21f439246715192b231f228680465d1ed5fbdf01555a4a3b17165532f5f9a08c"},
"hackney": {:hex, :hackney, "1.17.0", "717ea195fd2f898d9fe9f1ce0afcc2621a41ecfe137fae57e7fe6e9484b9aa99", [:rebar3], [{:certifi, "~>2.5", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "64c22225f1ea8855f584720c0e5b3cd14095703af1c9fbc845ba042811dc671c"}, "hackney": {:hex, :hackney, "1.17.4", "99da4674592504d3fb0cfef0db84c3ba02b4508bae2dff8c0108baa0d6e0977c", [:rebar3], [{:certifi, "~>2.6.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "de16ff4996556c8548d512f4dbe22dd58a587bf3332e7fd362430a7ef3986b16"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
"http_sign": {:hex, :http_sign, "0.1.1", "b16edb83aa282892f3271f9a048c155e772bf36e15700ab93901484c55f8dd10", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "2d4b1c2579d85534035f12c9e1260abdf6d03a9ad4f515b2ee53b50e68c8b787"}, "http_sign": {:hex, :http_sign, "0.1.1", "b16edb83aa282892f3271f9a048c155e772bf36e15700ab93901484c55f8dd10", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "2d4b1c2579d85534035f12c9e1260abdf6d03a9ad4f515b2ee53b50e68c8b787"},
"http_signatures": {:hex, :http_signatures, "0.1.0", "4e4b501a936dbf4cb5222597038a89ea10781776770d2e185849fa829686b34c", [:mix], [], "hexpm", "f8a7b3731e3fd17d38fa6e343fcad7b03d6874a3b0a108c8568a71ed9c2cf824"}, "http_signatures": {:hex, :http_signatures, "0.1.0", "4e4b501a936dbf4cb5222597038a89ea10781776770d2e185849fa829686b34c", [:mix], [], "hexpm", "f8a7b3731e3fd17d38fa6e343fcad7b03d6874a3b0a108c8568a71ed9c2cf824"},
@ -84,6 +84,7 @@
"linkify": {:hex, :linkify, "0.5.0", "e0ea8de73ff44742d6a889721221f4c4eccaad5284957ee9832ffeb347602d54", [:mix], [], "hexpm", "4ccd958350aee7c51c89e21f05b15d30596ebbba707e051d21766be1809df2d7"}, "linkify": {:hex, :linkify, "0.5.0", "e0ea8de73ff44742d6a889721221f4c4eccaad5284957ee9832ffeb347602d54", [:mix], [], "hexpm", "4ccd958350aee7c51c89e21f05b15d30596ebbba707e051d21766be1809df2d7"},
"makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"},
"makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"}, "makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"},
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"}, "meck": {:hex, :meck, "0.8.13", "ffedb39f99b0b99703b8601c6f17c7f76313ee12de6b646e671e3188401f7866", [:rebar3], [], "hexpm", "d34f013c156db51ad57cc556891b9720e6a1c1df5fe2e15af999c84d6cebeb1a"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mime": {:hex, :mime, "1.5.0", "203ef35ef3389aae6d361918bf3f952fa17a09e8e43b5aa592b93eba05d0fb8d", [:mix], [], "hexpm", "55a94c0f552249fc1a3dd9cd2d3ab9de9d3c89b559c2bd01121f824834f24746"}, "mime": {:hex, :mime, "1.5.0", "203ef35ef3389aae6d361918bf3f952fa17a09e8e43b5aa592b93eba05d0fb8d", [:mix], [], "hexpm", "55a94c0f552249fc1a3dd9cd2d3ab9de9d3c89b559c2bd01121f824834f24746"},
@ -107,23 +108,23 @@
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
"plug": {:hex, :plug, "1.11.1", "f2992bac66fdae679453c9e86134a4201f6f43a687d8ff1cd1b2862d53c80259", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "23524e4fefbb587c11f0833b3910bfb414bf2e2534d61928e920f54e3a1b881f"}, "plug": {:hex, :plug, "1.11.1", "f2992bac66fdae679453c9e86134a4201f6f43a687d8ff1cd1b2862d53c80259", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "23524e4fefbb587c11f0833b3910bfb414bf2e2534d61928e920f54e3a1b881f"},
"plug_cowboy": {:hex, :plug_cowboy, "2.4.1", "779ba386c0915027f22e14a48919a9545714f849505fa15af2631a0d298abf0f", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d72113b6dff7b37a7d9b2a5b68892808e3a9a752f2bf7e503240945385b70507"}, "plug_cowboy": {:hex, :plug_cowboy, "2.4.1", "779ba386c0915027f22e14a48919a9545714f849505fa15af2631a0d298abf0f", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d72113b6dff7b37a7d9b2a5b68892808e3a9a752f2bf7e503240945385b70507"},
"plug_crypto": {:hex, :plug_crypto, "1.2.1", "5c854427528bf61d159855cedddffc0625e2228b5f30eff76d5a4de42d896ef4", [:mix], [], "hexpm", "6961c0e17febd9d0bfa89632d391d2545d2e0eb73768f5f50305a23961d8782c"}, "plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"},
"poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"}, "poison": {:hex, :poison, "4.0.1", "bcb755a16fac91cad79bfe9fc3585bb07b9331e50cfe3420a24bcc2d735709ae", [:mix], [], "hexpm", "ba8836feea4b394bb718a161fc59a288fe0109b5006d6bdf97b6badfcf6f0f25"},
"postgrex": {:hex, :postgrex, "0.15.8", "f5e782bbe5e8fa178d5e3cd1999c857dc48eda95f0a4d7f7bd92a50e84a0d491", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "698fbfacea34c4cf22c8281abeb5cf68d99628d541874f085520ab3b53d356fe"}, "postgrex": {:hex, :postgrex, "0.15.8", "f5e782bbe5e8fa178d5e3cd1999c857dc48eda95f0a4d7f7bd92a50e84a0d491", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "698fbfacea34c4cf22c8281abeb5cf68d99628d541874f085520ab3b53d356fe"},
"progress_bar": {:hex, :progress_bar, "2.0.1", "7b40200112ae533d5adceb80ff75fbe66dc753bca5f6c55c073bfc122d71896d", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "2519eb58a2f149a3a094e729378256d8cb6d96a259ec94841bd69fdc71f18f87"}, "progress_bar": {:hex, :progress_bar, "2.0.1", "7b40200112ae533d5adceb80ff75fbe66dc753bca5f6c55c073bfc122d71896d", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "2519eb58a2f149a3a094e729378256d8cb6d96a259ec94841bd69fdc71f18f87"},
"ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"}, "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm", "451d8527787df716d99dc36162fca05934915db0b6141bbdac2ea8d3c7afc7d7"},
"remote_ip": {:hex, :remote_ip, "0.2.1", "cd27cd8ea54ecaaf3532776ff4c5e353b3804e710302e88c01eadeaaf42e7e24", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:inet_cidr, "~> 1.0", [hex: :inet_cidr, repo: "hexpm", optional: false]}, {:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "2e7ab1a461cc3cd5719f37e116a08f45c8b8493923063631b164315d6b7ee8e0"}, "remote_ip": {:hex, :remote_ip, "1.0.0", "3d7fb45204a5704443f480cee9515e464997f52c35e0a60b6ece1f81484067ae", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "9e9fcad4e50c43b5234bb6a9629ed6ab223f3ed07147bd35470e4ee5c8caf907"},
"rsa_ex": {:hex, :rsa_ex, "0.4.0", "e28dd7dc5236e156df434af0e4aa822384c8866c928e17b785d4edb7c253b558", [:mix], [], "hexpm", "40e1f08e8401da4be59a6dd0f4da30c42d5bb01703161f0208d839d97db27f4e"}, "rsa_ex": {:hex, :rsa_ex, "0.4.0", "e28dd7dc5236e156df434af0e4aa822384c8866c928e17b785d4edb7c253b558", [:mix], [], "hexpm", "40e1f08e8401da4be59a6dd0f4da30c42d5bb01703161f0208d839d97db27f4e"},
"sentry": {:hex, :sentry, "8.0.5", "5ca922b9238a50c7258b52f47364b2d545beda5e436c7a43965b34577f1ef61f", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, "~> 2.3", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "4972839fdbf52e886d7b3e694c8adf421f764f2fa79036b88fb4742049bd4b7c"}, "sentry": {:hex, :sentry, "8.0.5", "5ca922b9238a50c7258b52f47364b2d545beda5e436c7a43965b34577f1ef61f", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, "~> 2.3", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "4972839fdbf52e886d7b3e694c8adf421f764f2fa79036b88fb4742049bd4b7c"},
"shortuuid": {:hex, :shortuuid, "2.1.2", "14dbafdb2f6c7213fdfcc05c7572384b5051a7b1621170018ad4c05504bd96c1", [:mix], [], "hexpm", "d9b0c4f37500ea5199b6275ece872e213e9f45a015caf4aa777cec84f63ad353"}, "shortuuid": {:hex, :shortuuid, "2.1.2", "14dbafdb2f6c7213fdfcc05c7572384b5051a7b1621170018ad4c05504bd96c1", [:mix], [], "hexpm", "d9b0c4f37500ea5199b6275ece872e213e9f45a015caf4aa777cec84f63ad353"},
"sitemapper": {:hex, :sitemapper, "0.5.0", "23b0bb7b3888f03d4e4e5bedb7034e6d2979e169366372d960d6f433112b9bdf", [:mix], [{:ex_aws_s3, "~> 2.0", [hex: :ex_aws_s3, repo: "hexpm", optional: true]}, {:xml_builder, "~> 2.1.1", [hex: :xml_builder, repo: "hexpm", optional: false]}], "hexpm", "be7acff8d0245aa7ca125b9c4d0751009bbbca26ef866d888fef4fdf98670e41"}, "sitemapper": {:hex, :sitemapper, "0.5.0", "23b0bb7b3888f03d4e4e5bedb7034e6d2979e169366372d960d6f433112b9bdf", [:mix], [{:ex_aws_s3, "~> 2.0", [hex: :ex_aws_s3, repo: "hexpm", optional: true]}, {:xml_builder, "~> 2.1.1", [hex: :xml_builder, repo: "hexpm", optional: false]}], "hexpm", "be7acff8d0245aa7ca125b9c4d0751009bbbca26ef866d888fef4fdf98670e41"},
"sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"}, "sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"},
"slugger": {:hex, :slugger, "0.3.0", "efc667ab99eee19a48913ccf3d038b1fb9f165fa4fbf093be898b8099e61b6ed", [:mix], [], "hexpm", "20d0ded0e712605d1eae6c5b4889581c3460d92623a930ddda91e0e609b5afba"}, "slugger": {:hex, :slugger, "0.3.0", "efc667ab99eee19a48913ccf3d038b1fb9f165fa4fbf093be898b8099e61b6ed", [:mix], [], "hexpm", "20d0ded0e712605d1eae6c5b4889581c3460d92623a930ddda91e0e609b5afba"},
"sobelow": {:hex, :sobelow, "0.11.0", "cdc17e3a9f1ea78dc55dbe0a03121cb6767fef737c6d9f1e62ee7e78730abccc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "c57807bfe6f231338b657781f89ef0320b66a0dbe779aa911d6ed27cfa14ae6e"}, "sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"}, "telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
"tesla": {:hex, :tesla, "1.4.0", "1081bef0124b8bdec1c3d330bbe91956648fb008cf0d3950a369cda466a31a87", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "bf1374a5569f5fca8e641363b63f7347d680d91388880979a33bc12a6eb3e0aa"}, "tesla": {:hex, :tesla, "1.4.0", "1081bef0124b8bdec1c3d330bbe91956648fb008cf0d3950a369cda466a31a87", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "~> 4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "bf1374a5569f5fca8e641363b63f7347d680d91388880979a33bc12a6eb3e0aa"},
"timex": {:hex, :timex, "3.6.4", "137a49450b8d1f80efff82de4b78ab9ad2e367f06346825704310599733f338b", [: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 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "f6fce3f07ab67f525043af5b1f68ed5fa12a41b9dab95a9a98bb6acfb30ecadc"}, "timex": {:hex, :timex, "3.7.3", "df8a2ea814749d700d6878ab9eacac9fdb498ecee2f507cb0002ec172bc24d0f", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8691c1d86ca3a7bc14a156e2199dc8927be95d1a8f0e3b69e4bb2d6262c53ac6"},
"tzdata": {:hex, :tzdata, "1.1.0", "72f5babaa9390d0f131465c8702fa76da0919e37ba32baa90d93c583301a8359", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "18f453739b48d3dc5bcf0e8906d2dc112bb40baafe2c707596d89f3c8dd14034"}, "tzdata": {:hex, :tzdata, "1.1.0", "72f5babaa9390d0f131465c8702fa76da0919e37ba32baa90d93c583301a8359", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "18f453739b48d3dc5bcf0e8906d2dc112bb40baafe2c707596d89f3c8dd14034"},
"ueberauth": {:hex, :ueberauth, "0.6.3", "d42ace28b870e8072cf30e32e385579c57b9cc96ec74fa1f30f30da9c14f3cc0", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "afc293d8a1140d6591b53e3eaf415ca92842cb1d32fad3c450c6f045f7f91b60"}, "ueberauth": {:hex, :ueberauth, "0.6.3", "d42ace28b870e8072cf30e32e385579c57b9cc96ec74fa1f30f30da9c14f3cc0", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "afc293d8a1140d6591b53e3eaf415ca92842cb1d32fad3c450c6f045f7f91b60"},
"ueberauth_discord": {:hex, :ueberauth_discord, "0.5.2", "afc5d68879575c365972fd4d7cf7b01c16f7d062fc6bf7e86e2595736ac41127", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.6.3", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "bbd7701d8fef02623fb106ed7c24427ed2327ef769bcb1d2eba5670e54716cdc"}, "ueberauth_discord": {:hex, :ueberauth_discord, "0.5.2", "afc5d68879575c365972fd4d7cf7b01c16f7d062fc6bf7e86e2595736ac41127", [:mix], [{:oauth2, "~> 1.0 or ~> 2.0", [hex: :oauth2, repo: "hexpm", optional: false]}, {:ueberauth, "~> 0.6.3", [hex: :ueberauth, repo: "hexpm", optional: false]}], "hexpm", "bbd7701d8fef02623fb106ed7c24427ed2327ef769bcb1d2eba5670e54716cdc"},

File diff suppressed because one or more lines are too long

View File

@ -121,6 +121,16 @@ defmodule Mobilizon.GraphQL.Resolvers.UserTest do
} }
""" """
@change_default_actor """
mutation ChangeDefaultActor($preferredUsername: String!) {
changeDefaultActor(preferredUsername: $preferredUsername) {
defaultActor {
preferredUsername
}
}
}
"""
@valid_actor_params %{email: "test@test.tld", password: "testest", username: "test"} @valid_actor_params %{email: "test@test.tld", password: "testest", username: "test"}
@valid_single_actor_params %{preferred_username: "test2", keys: "yolo"} @valid_single_actor_params %{preferred_username: "test2", keys: "yolo"}
@ -835,7 +845,7 @@ defmodule Mobilizon.GraphQL.Resolvers.UserTest do
end end
describe "Resolver: change default actor for user" do describe "Resolver: change default actor for user" do
test "test change_default_actor/3 with valid actor", context do test "test change_default_actor/3 without being logged-in", %{conn: conn} do
# Prepare user with two actors # Prepare user with two actors
user = insert(:user) user = insert(:user)
insert(:actor, user: user) insert(:actor, user: user)
@ -848,24 +858,41 @@ defmodule Mobilizon.GraphQL.Resolvers.UserTest do
assert {:ok, %User{actors: actors}} = Users.get_user_with_actors(user.id) assert {:ok, %User{actors: actors}} = Users.get_user_with_actors(user.id)
assert length(actors) == 2 assert length(actors) == 2
mutation = """ res =
mutation { conn
changeDefaultActor(preferred_username: "#{actor2.preferred_username}") { |> AbsintheHelpers.graphql_query(
default_actor { query: @change_default_actor,
preferred_username variables: %{preferredUsername: actor2.preferred_username}
} )
}
} assert hd(res["errors"])["message"] == "You need to be logged in"
""" end
test "test change_default_actor/3 with valid actor", %{conn: conn} do
# Prepare user with two actors
user = insert(:user)
insert(:actor, user: user)
assert {:ok, %User{actors: _actors}} = Users.get_user_with_actors(user.id)
actor_params = @valid_single_actor_params |> Map.put(:user_id, user.id)
assert {:ok, %Actor{} = actor2} = Actors.create_actor(actor_params)
assert {:ok, %User{actors: actors}} = Users.get_user_with_actors(user.id)
assert length(actors) == 2
res = res =
context.conn conn
|> auth_conn(user) |> auth_conn(user)
|> post("/api", AbsintheHelpers.mutation_skeleton(mutation)) |> AbsintheHelpers.graphql_query(
query: @change_default_actor,
variables: %{preferredUsername: actor2.preferred_username}
)
assert json_response(res, 200)["data"]["changeDefaultActor"]["default_actor"][ assert res["errors"] == nil
"preferred_username"
] == actor2.preferred_username assert res["data"]["changeDefaultActor"]["defaultActor"]["preferredUsername"] ==
actor2.preferred_username
end end
end end
@ -1113,6 +1140,59 @@ defmodule Mobilizon.GraphQL.Resolvers.UserTest do
assert user.unconfirmed_email == nil assert user.unconfirmed_email == nil
end end
test "change_email/3 with valid email but invalid token", %{conn: conn} do
{:ok, %User{} = user} = Users.register(%{email: @old_email, password: @password})
# Hammer time !
{:ok, %User{} = _user} =
Users.update_user(user, %{
confirmed_at: Timex.shift(user.confirmation_sent_at, hours: -3),
confirmation_sent_at: nil,
confirmation_token: nil
})
res =
conn
|> AbsintheHelpers.graphql_query(
query: @login_mutation,
variables: %{email: @old_email, password: @password}
)
login = res["data"]["login"]
assert Map.has_key?(login, "accessToken") && not is_nil(login["accessToken"])
res =
conn
|> auth_conn(user)
|> AbsintheHelpers.graphql_query(
query: @change_email_mutation,
variables: %{email: @new_email, password: @password}
)
assert res["errors"] == nil
assert res["data"]["changeEmail"]["id"] == to_string(user.id)
user = Users.get_user!(user.id)
assert user.email == @old_email
assert user.unconfirmed_email == @new_email
assert_delivered_email(Email.User.send_email_reset_old_email(user))
assert_delivered_email(Email.User.send_email_reset_new_email(user))
res =
conn
|> AbsintheHelpers.graphql_query(
query: @validate_email_mutation,
variables: %{token: "some token"}
)
assert hd(res["errors"])["message"] == "Invalid activation token"
user = Users.get_user!(user.id)
assert user.email == @old_email
assert user.unconfirmed_email == @new_email
end
test "change_email/3 with invalid password", %{conn: conn} do test "change_email/3 with invalid password", %{conn: conn} do
{:ok, %User{} = user} = Users.register(%{email: @old_email, password: @password}) {:ok, %User{} = user} = Users.register(%{email: @old_email, password: @password})

View File

@ -85,9 +85,9 @@ defmodule Mobilizon.UsersTest do
test "get_user_by_email/1 finds an activated user by its email" do test "get_user_by_email/1 finds an activated user by its email" do
{:ok, %User{} = user} = Users.register(%{email: @email, password: @password}) {:ok, %User{} = user} = Users.register(%{email: @email, password: @password})
{:ok, %User{id: id}} = Users.get_user_by_email(@email, false) {:ok, %User{id: id}} = Users.get_user_by_email(@email, activated: false)
assert id == user.id assert id == user.id
assert {:error, :user_not_found} = Users.get_user_by_email(@email, true) assert {:error, :user_not_found} = Users.get_user_by_email(@email, activated: true)
Users.update_user(user, %{ Users.update_user(user, %{
"confirmed_at" => DateTime.utc_now() |> DateTime.truncate(:second), "confirmed_at" => DateTime.utc_now() |> DateTime.truncate(:second),
@ -95,10 +95,28 @@ defmodule Mobilizon.UsersTest do
"confirmation_token" => nil "confirmation_token" => nil
}) })
assert {:error, :user_not_found} = Users.get_user_by_email(@email, false) assert {:error, :user_not_found} = Users.get_user_by_email(@email, activated: false)
{:ok, %User{id: id}} = Users.get_user_by_email(@email, true) {:ok, %User{id: id}} = Users.get_user_by_email(@email, activated: true)
assert id == user.id assert id == user.id
end end
@unconfirmed_email "unconfirmed@email.com"
test "get_user_by_email/1 finds an user by its pending email" do
{:ok, %User{} = user} = Users.register(%{email: @email, password: @password})
Users.update_user(user, %{
"confirmed_at" => DateTime.utc_now() |> DateTime.truncate(:second),
"confirmation_sent_at" => nil,
"confirmation_token" => nil
})
assert {:ok, %User{}} = Users.update_user_email(user, @unconfirmed_email)
assert {:error, :user_not_found} =
Users.get_user_by_email(@unconfirmed_email, unconfirmed: false)
assert {:ok, %User{}} = Users.get_user_by_email(@unconfirmed_email, unconfirmed: true)
end
end end
describe "user_settings" do describe "user_settings" do