Merge branch 'categories' into 'master'

Make Categories a predifined list

See merge request framasoft/mobilizon!79
This commit is contained in:
Thomas Citharel 2019-02-25 17:06:32 +01:00
commit 3507438f17
28 changed files with 90 additions and 656 deletions

View File

@ -49,12 +49,6 @@ export default class App extends Vue {
route: "GroupList", route: "GroupList",
role: null role: null
}, },
{
icon: "content_copy",
text: "Categories",
route: "CategoryList",
role: "ROLE_ADMIN"
},
{ icon: "settings", text: "Settings", role: "ROLE_USER" }, { icon: "settings", text: "Settings", role: "ROLE_USER" },
{ icon: "chat_bubble", text: "Send feedback", role: "ROLE_USER" }, { icon: "chat_bubble", text: "Send feedback", role: "ROLE_USER" },
{ icon: "help", text: "Help", role: null }, { icon: "help", text: "Help", role: null },

View File

@ -1,29 +0,0 @@
import gql from 'graphql-tag';
export const FETCH_CATEGORIES = gql`
query {
categories {
id,
title,
description,
picture {
url,
},
}
}
`;
export const CREATE_CATEGORY = gql`
mutation createCategory($title: String!, $description: String!, $picture: Upload!) {
createCategory(title: $title, description: $description, picture: $picture) {
id,
title,
description,
picture {
url,
url_thumbnail
},
},
},
`;

View File

@ -25,6 +25,7 @@ export const FETCH_EVENT = gql`
thumbnail, thumbnail,
large_image, large_image,
publish_at, publish_at,
category,
# online_address, # online_address,
# phone_address, # phone_address,
organizerActor { organizerActor {
@ -39,10 +40,7 @@ export const FETCH_EVENT = gql`
# }, # },
participants { participants {
${participantQuery} ${participantQuery}
}, }
category {
title,
},
} }
} }
`; `;
@ -75,9 +73,7 @@ export const FETCH_EVENTS = gql`
preferredUsername, preferredUsername,
name, name,
}, },
category { category,
title,
},
participants { participants {
${participantQuery} ${participantQuery}
} }
@ -112,9 +108,9 @@ export const EDIT_EVENT = gql`
$title: String!, $title: String!,
$description: String!, $description: String!,
$organizerActorId: Int!, $organizerActorId: Int!,
$categoryId: Int! $category: String!
) { ) {
EditEvent(title: $title, description: $description, organizerActorId: $organizerActorId, categoryId: $categoryId) { EditEvent(title: $title, description: $description, organizerActorId: $organizerActorId, category: $category) {
uuid uuid
} }
} }

View File

@ -1,22 +0,0 @@
import CategoryList from '@/views/Category/List.vue';
import CreateCategory from '@/views/Category/Create.vue';
export enum CategoryRouteName {
CATEGORY_LIST = 'CategoryList',
CREATE_CATEGORY = 'CreateCategory',
}
export const categoryRoutes = [
{
path: '/category',
name: CategoryRouteName.CATEGORY_LIST,
component: CategoryList,
meta: { requiredAuth: false },
},
{
path: '/category/create',
name: CategoryRouteName.CREATE_CATEGORY,
component: CreateCategory,
meta: { requiredAuth: true },
},
];

View File

@ -5,7 +5,6 @@ import Home from '@/views/Home.vue';
import { UserRouteName, userRoutes } from './user'; import { UserRouteName, userRoutes } from './user';
import { EventRouteName, eventRoutes } from '@/router/event'; import { EventRouteName, eventRoutes } from '@/router/event';
import { ActorRouteName, actorRoutes } from '@/router/actor'; import { ActorRouteName, actorRoutes } from '@/router/actor';
import { CategoryRouteName, categoryRoutes } from '@/router/category';
Vue.use(Router); Vue.use(Router);
@ -20,7 +19,6 @@ export const RouteName = {
...GlobalRouteName, ...GlobalRouteName,
...UserRouteName, ...UserRouteName,
...EventRouteName, ...EventRouteName,
...CategoryRouteName,
...ActorRouteName, ...ActorRouteName,
}; };
@ -30,7 +28,6 @@ const router = new Router({
routes: [ routes: [
...userRoutes, ...userRoutes,
...eventRoutes, ...eventRoutes,
...categoryRoutes,
...actorRoutes, ...actorRoutes,
{ {

View File

@ -27,10 +27,12 @@ export enum ParticipantRole {
CREATOR = 'creator', CREATOR = 'creator',
} }
export interface ICategory { export enum Category {
title: string; BUSINESS = 'business',
description: string; CONFERENCE = 'conference',
picture: string; BIRTHDAY = 'birthday',
DEMONSTRATION = 'demonstration',
MEETING = 'meeting',
} }
export interface IParticipant { export interface IParticipant {
@ -47,7 +49,7 @@ export interface IEvent {
title: string; title: string;
description: string; description: string;
category: ICategory; category: Category;
begins_on: Date; begins_on: Date;
ends_on: Date; ends_on: Date;

View File

@ -1,75 +0,0 @@
<template>
<section>
<h1 class="title">
<translate>Create a new category</translate>
</h1>
<div class="columns">
<form class="column" @submit="submit">
<b-field :label="$gettext('Name of the category')">
<b-input aria-required="true" required v-model="category.title"/>
</b-field>
<b-field :label="$gettext('Description')">
<b-input type="textarea" v-model="category.description"/>
</b-field>
<b-field class="file">
<b-upload v-model="file" @input="onFilePicked">
<a class="button is-primary">
<b-icon icon="upload"></b-icon>
<span>
<translate>Click to upload</translate>
</span>
</a>
</b-upload>
<span class="file-name" v-if="file">{{ this.image.name }}</span>
</b-field>
<button class="button is-primary">
<translate>Create the category</translate>
</button>
</form>
</div>
</section>
</template>
<script lang="ts">
import { CREATE_CATEGORY } from "@/graphql/category";
import { Component, Vue } from "vue-property-decorator";
import { ICategory } from "@/types/event.model";
/**
* TODO : No picture is uploaded ATM
*/
@Component
export default class CreateCategory extends Vue {
category!: ICategory;
image = {
name: ""
} as { name: string };
file: any = null;
create() {
this.$apollo
.mutate({
mutation: CREATE_CATEGORY,
variables: this.category
})
.then(data => {
console.log(data);
})
.catch(error => {
console.error(error);
});
}
// TODO : Check if we can upload as soon as file is picked and purge files not validated
onFilePicked(e) {
if (e === undefined || e.name.lastIndexOf(".") <= 0) {
console.error("File is incorrect");
}
this.image.name = e.name;
}
}
</script>

View File

@ -1,55 +0,0 @@
<template>
<section>
<h1 class="title">
<translate>Category List</translate>
</h1>
<b-loading :active.sync="$apollo.loading"></b-loading>
<div class="columns">
<div class="column card" v-for="category in categories" :key="category.id">
<div class="card-image">
<figure class="image is-4by3">
<img v-if="category.picture.url" :src="HTTP_ENDPOINT + category.picture.url">
</figure>
</div>
<div class="card-content">
<h2 class="title is-4">{{ category.title }}</h2>
<p>{{ category.description }}</p>
</div>
</div>
</div>
</section>
</template>
<script lang="ts">
import { FETCH_CATEGORIES } from "@/graphql/category";
import { Component, Vue } from "vue-property-decorator";
// TODO : remove this hardcode
@Component({
apollo: {
categories: {
query: FETCH_CATEGORIES
}
}
})
export default class List extends Vue {
categories = [];
loading = true;
HTTP_ENDPOINT = "http://localhost:4000";
deleteCategory(categoryId) {
const router = this.$router;
// FIXME: remove eventFetch
// eventFetch(`/categories/${categoryId}`, this.$store, { method: 'DELETE' })
// .then(() => {
// this.categories = this.categories.filter(category => category.id !== categoryId);
// router.push('/category');
// });
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
</style>

View File

@ -1,3 +1,4 @@
import {Category} from "../../types/event.model";
import {EventJoinOptions} from "../../types/event.model"; import {EventJoinOptions} from "../../types/event.model";
<template> <template>
<section> <section>
@ -18,8 +19,8 @@ import {EventJoinOptions} from "../../types/event.model";
<option <option
v-for="category in categories" v-for="category in categories"
:value="category" :value="category"
:key="category.title" :key="category"
>{{ category.title }}</option> >{{ $gettext(category) }}</option>
</b-select> </b-select>
</b-field> </b-field>
@ -35,27 +36,21 @@ import {EventJoinOptions} from "../../types/event.model";
// import Location from '@/components/Location'; // import Location from '@/components/Location';
import VueMarkdown from "vue-markdown"; import VueMarkdown from "vue-markdown";
import {CREATE_EVENT, EDIT_EVENT} from "@/graphql/event"; import {CREATE_EVENT, EDIT_EVENT} from "@/graphql/event";
import {FETCH_CATEGORIES} from "@/graphql/category";
import {Component, Prop, Vue} from "vue-property-decorator"; import {Component, Prop, Vue} from "vue-property-decorator";
import {EventJoinOptions, EventStatus, EventVisibility, ICategory, IEvent} from "@/types/event.model"; import {Category, EventJoinOptions, EventStatus, EventVisibility, IEvent} from "@/types/event.model";
import {LOGGED_PERSON} from "@/graphql/actor"; import {LOGGED_PERSON} from "@/graphql/actor";
import {IPerson} from "@/types/actor.model"; import {IPerson} from "@/types/actor.model";
@Component({ @Component({
components: { components: {
VueMarkdown VueMarkdown
},
apollo: {
categories: {
query: FETCH_CATEGORIES
}
} }
}) })
export default class CreateEvent extends Vue { export default class CreateEvent extends Vue {
@Prop({ required: false, type: String }) uuid!: string; @Prop({ required: false, type: String }) uuid!: string;
loggedPerson!: IPerson; loggedPerson!: IPerson;
categories: ICategory[] = []; categories: string[] = Object.keys(Category);
event!: IEvent; // FIXME: correctly type an event event!: IEvent; // FIXME: correctly type an event
// created() { // created() {
@ -77,7 +72,7 @@ export default class CreateEvent extends Vue {
description: "", description: "",
begins_on: new Date(), begins_on: new Date(),
ends_on: new Date(), ends_on: new Date(),
category: this.categories[0], category: Category.MEETING,
participants: [], participants: [],
uuid: "", uuid: "",
url: "", url: "",
@ -102,7 +97,7 @@ export default class CreateEvent extends Vue {
title: this.event.title, title: this.event.title,
description: this.event.description, description: this.event.description,
beginsOn: this.event.begins_on, beginsOn: this.event.begins_on,
category: this.event.category.title, category: this.event.category,
organizerActorId: this.event.organizerActor.id organizerActorId: this.event.organizerActor.id
} }
}) })

View File

@ -10,7 +10,7 @@
</div> </div>
<div class="card-content"> <div class="card-content">
<span>{{ event.begins_on | formatDay }}</span> <span>{{ event.begins_on | formatDay }}</span>
<span class="tag is-primary">{{ event.category.title }}</span> <span class="tag is-primary">{{ event.category }}</span>
<h1 class="title">{{ event.title }}</h1> <h1 class="title">{{ event.title }}</h1>
<router-link <router-link
:to="{name: 'Profile', params: { name: event.organizerActor.preferredUsername } }" :to="{name: 'Profile', params: { name: event.organizerActor.preferredUsername } }"

View File

@ -1,26 +0,0 @@
defmodule Mobilizon.Events.Category do
@moduledoc """
Represents a category for events
"""
use Ecto.Schema
import Ecto.Changeset
alias Mobilizon.Events.Category
use Arc.Ecto.Schema
schema "categories" do
field(:description, :string)
field(:picture, MobilizonWeb.Uploaders.Category.Type)
field(:title, :string, null: false)
timestamps()
end
@doc false
def changeset(%Category{} = category, attrs) do
category
|> cast(attrs, [:title, :description])
|> cast_attachments(attrs, [:picture])
|> validate_required([:title])
|> unique_constraint(:title)
end
end

View File

@ -19,13 +19,21 @@ defenum(Mobilizon.Events.EventStatusEnum, :event_status_type, [
:cancelled :cancelled
]) ])
defenum(Mobilizon.Event.EventCategoryEnum, :event_category_type, [
:business,
:conference,
:birthday,
:demonstration,
:meeting
])
defmodule Mobilizon.Events.Event do defmodule Mobilizon.Events.Event do
@moduledoc """ @moduledoc """
Represents an event Represents an event
""" """
use Ecto.Schema use Ecto.Schema
import Ecto.Changeset import Ecto.Changeset
alias Mobilizon.Events.{Event, Participant, Tag, Category, Session, Track} alias Mobilizon.Events.{Event, Participant, Tag, Session, Track}
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Addresses.Address alias Mobilizon.Addresses.Address
@ -45,10 +53,10 @@ defmodule Mobilizon.Events.Event do
field(:uuid, Ecto.UUID, default: Ecto.UUID.generate()) field(:uuid, Ecto.UUID, default: Ecto.UUID.generate())
field(:online_address, :string) field(:online_address, :string)
field(:phone_address, :string) field(:phone_address, :string)
field(:category, :string)
belongs_to(:organizer_actor, Actor, foreign_key: :organizer_actor_id) belongs_to(:organizer_actor, Actor, foreign_key: :organizer_actor_id)
belongs_to(:attributed_to, Actor, foreign_key: :attributed_to_id) belongs_to(:attributed_to, Actor, foreign_key: :attributed_to_id)
many_to_many(:tags, Tag, join_through: "events_tags") many_to_many(:tags, Tag, join_through: "events_tags")
belongs_to(:category, Category)
many_to_many(:participants, Actor, join_through: Participant) many_to_many(:participants, Actor, join_through: Participant)
has_many(:tracks, Track) has_many(:tracks, Track)
has_many(:sessions, Session) has_many(:sessions, Session)
@ -67,7 +75,7 @@ defmodule Mobilizon.Events.Event do
:begins_on, :begins_on,
:ends_on, :ends_on,
:organizer_actor_id, :organizer_actor_id,
:category_id, :category,
:status, :status,
:visibility, :visibility,
:thumbnail, :thumbnail,
@ -83,7 +91,7 @@ defmodule Mobilizon.Events.Event do
:title, :title,
:begins_on, :begins_on,
:organizer_actor_id, :organizer_actor_id,
:category_id, :category,
:url, :url,
:uuid :uuid
]) ])

View File

@ -27,7 +27,6 @@ defmodule Mobilizon.Events do
order_by: [desc: :id], order_by: [desc: :id],
preload: [ preload: [
:organizer_actor, :organizer_actor,
:category,
:sessions, :sessions,
:tracks, :tracks,
:tags, :tags,
@ -145,7 +144,6 @@ defmodule Mobilizon.Events do
where: e.uuid == ^uuid and e.visibility in [^:public, ^:unlisted], where: e.uuid == ^uuid and e.visibility in [^:public, ^:unlisted],
preload: [ preload: [
:organizer_actor, :organizer_actor,
:category,
:sessions, :sessions,
:tracks, :tracks,
:tags, :tags,
@ -164,7 +162,6 @@ defmodule Mobilizon.Events do
Repo.preload(event, [ Repo.preload(event, [
:organizer_actor, :organizer_actor,
:category,
:sessions, :sessions,
:tracks, :tracks,
:tags, :tags,
@ -182,7 +179,6 @@ defmodule Mobilizon.Events do
where: e.url == ^url and e.visibility in [^:public, ^:unlisted], where: e.url == ^url and e.visibility in [^:public, ^:unlisted],
preload: [ preload: [
:organizer_actor, :organizer_actor,
:category,
:sessions, :sessions,
:tracks, :tracks,
:tags, :tags,
@ -205,7 +201,6 @@ defmodule Mobilizon.Events do
where: e.url == ^url and e.visibility in [^:public, ^:unlisted], where: e.url == ^url and e.visibility in [^:public, ^:unlisted],
preload: [ preload: [
:organizer_actor, :organizer_actor,
:category,
:sessions, :sessions,
:tracks, :tracks,
:tags, :tags,
@ -350,110 +345,6 @@ defmodule Mobilizon.Events do
Event.changeset(event, %{}) Event.changeset(event, %{})
end end
alias Mobilizon.Events.Category
@doc """
Returns the list of categories.
## Examples
iex> list_categories()
[%Category{}, ...]
"""
def list_categories(page \\ nil, limit \\ nil) do
Repo.all(
Category
|> paginate(page, limit)
)
end
@doc """
Gets a single category.
Raises `Ecto.NoResultsError` if the Category does not exist.
## Examples
iex> get_category!(123)
%Category{}
iex> get_category!(456)
** (Ecto.NoResultsError)
"""
def get_category!(id), do: Repo.get!(Category, id)
@spec get_category_by_title(String.t()) :: Category.t() | nil
def get_category_by_title(title) when is_binary(title) do
Repo.get_by(Category, title: title)
end
@doc """
Creates a category.
## Examples
iex> create_category(%{field: value})
{:ok, %Category{}}
iex> create_category(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_category(attrs \\ %{}) do
%Category{}
|> Category.changeset(attrs)
|> Repo.insert()
end
@doc """
Updates a category.
## Examples
iex> update_category(category, %{field: new_value})
{:ok, %Category{}}
iex> update_category(category, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_category(%Category{} = category, attrs) do
category
|> Category.changeset(attrs)
|> Repo.update()
end
@doc """
Deletes a Category.
## Examples
iex> delete_category(category)
{:ok, %Category{}}
iex> delete_category(category)
{:error, %Ecto.Changeset{}}
"""
def delete_category(%Category{} = category) do
Repo.delete(category)
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking category changes.
## Examples
iex> change_category(category)
%Ecto.Changeset{source: %Category{}}
"""
def change_category(%Category{} = category) do
Category.changeset(category, %{})
end
alias Mobilizon.Events.Tag alias Mobilizon.Events.Tag
@doc """ @doc """

View File

@ -1,48 +0,0 @@
defmodule MobilizonWeb.Resolvers.Category do
@moduledoc """
Handles the category-related GraphQL calls
"""
require Logger
alias Mobilizon.Actors.User
###
# TODO : Refactor this into MobilizonWeb.API.Categories when a standard AS category is defined
###
def list_categories(_parent, %{page: page, limit: limit}, _resolution) do
categories =
Mobilizon.Events.list_categories(page, limit)
|> Enum.map(fn category ->
urls = MobilizonWeb.Uploaders.Category.urls({category.picture, category})
Map.put(category, :picture, %{url: urls.original, url_thumbnail: urls.thumb})
end)
{:ok, categories}
end
def create_category(_parent, %{title: title, picture: picture, description: description}, %{
context: %{current_user: %User{} = _user}
}) do
with {:ok, category} <-
Mobilizon.Events.create_category(%{
title: title,
description: description,
picture: picture
}),
urls <- MobilizonWeb.Uploaders.Category.urls({category.picture, category}) do
Logger.info("Created category " <> title)
{:ok, Map.put(category, :picture, %{url: urls.original, url_thumbnail: urls.thumb})}
else
{:error, %Ecto.Changeset{errors: errors} = _changeset} ->
# This is pretty ridiculous for changeset to error
errors =
Enum.into(errors, %{})
|> Enum.map(fn {key, {value, _}} -> Atom.to_string(key) <> ": " <> value end)
{:error, errors}
end
end
def create_category(_parent, _args, %{}) do
{:error, "You are not allowed to create a category if not connected"}
end
end

View File

@ -130,7 +130,6 @@ defmodule MobilizonWeb.Schema do
import_fields(:group_queries) import_fields(:group_queries)
import_fields(:event_queries) import_fields(:event_queries)
import_fields(:participant_queries) import_fields(:participant_queries)
import_fields(:category_queries)
import_fields(:tag_queries) import_fields(:tag_queries)
end end
@ -142,7 +141,6 @@ defmodule MobilizonWeb.Schema do
import_fields(:person_mutations) import_fields(:person_mutations)
import_fields(:group_mutations) import_fields(:group_mutations)
import_fields(:event_mutations) import_fields(:event_mutations)
import_fields(:category_mutations)
import_fields(:comment_mutations) import_fields(:comment_mutations)
import_fields(:participant_mutations) import_fields(:participant_mutations)

View File

@ -7,7 +7,6 @@ defmodule MobilizonWeb.Schema.EventType do
import Absinthe.Resolution.Helpers, only: [dataloader: 1] import Absinthe.Resolution.Helpers, only: [dataloader: 1]
import_types(MobilizonWeb.Schema.AddressType) import_types(MobilizonWeb.Schema.AddressType)
import_types(MobilizonWeb.Schema.Events.ParticipantType) import_types(MobilizonWeb.Schema.Events.ParticipantType)
import_types(MobilizonWeb.Schema.Events.CategoryType)
import_types(MobilizonWeb.Schema.TagType) import_types(MobilizonWeb.Schema.TagType)
alias MobilizonWeb.Resolvers alias MobilizonWeb.Resolvers
@ -44,7 +43,7 @@ defmodule MobilizonWeb.Schema.EventType do
description: "The event's tags" description: "The event's tags"
) )
field(:category, :category, description: "The event's category") field(:category, :string, description: "The event's category")
field(:participants, list_of(:participant), field(:participants, list_of(:participant),
resolve: &MobilizonWeb.Resolvers.Event.list_participants_for_event/3, resolve: &MobilizonWeb.Resolvers.Event.list_participants_for_event/3,

View File

@ -1,34 +0,0 @@
defmodule MobilizonWeb.Schema.Events.CategoryType do
@moduledoc """
Schema representation for Category
"""
use Absinthe.Schema.Notation
alias MobilizonWeb.Resolvers
@desc "A category"
object :category do
field(:id, :id, description: "The category's ID")
field(:description, :string, description: "The category's description")
field(:picture, :picture, description: "The category's picture")
field(:title, :string, description: "The category's title")
end
object :category_queries do
@desc "Get the list of categories"
field :categories, non_null(list_of(:category)) do
arg(:page, :integer, default_value: 1)
arg(:limit, :integer, default_value: 10)
resolve(&Resolvers.Category.list_categories/3)
end
end
object :category_mutations do
@desc "Create a category with a title, description and picture"
field :create_category, type: :category do
arg(:title, non_null(:string))
arg(:description, non_null(:string))
arg(:picture, non_null(:upload))
resolve(&Resolvers.Category.create_category/3)
end
end
end

View File

@ -30,10 +30,9 @@ defmodule MobilizonWeb.Uploaders.Category do
"#{title}_#{version}" "#{title}_#{version}"
end end
# TODO : When we're sure creating a category is secured and made possible only for admins, use category name
# Override the storage directory: # Override the storage directory:
def storage_dir(_, _) do def storage_dir(_, _) do
"uploads/categories/" "uploads/event/"
end end
# Provide a default URL if there hasn't been a file uploaded # Provide a default URL if there hasn't been a file uploaded

View File

@ -1,6 +1,5 @@
defmodule MobilizonWeb.ActivityPub.ObjectView do defmodule MobilizonWeb.ActivityPub.ObjectView do
use MobilizonWeb, :view use MobilizonWeb, :view
alias MobilizonWeb.ActivityPub.ObjectView
alias Mobilizon.Service.ActivityPub.Utils alias Mobilizon.Service.ActivityPub.Utils
def render("event.json", %{event: event}) do def render("event.json", %{event: event}) do
@ -9,7 +8,7 @@ defmodule MobilizonWeb.ActivityPub.ObjectView do
"actor" => event["actor"], "actor" => event["actor"],
"id" => event["id"], "id" => event["id"],
"name" => event["title"], "name" => event["title"],
"category" => render_one(event["category"], ObjectView, "category.json", as: :category), "category" => event["category"],
"content" => event["summary"], "content" => event["summary"],
"mediaType" => "text/html" "mediaType" => "text/html"
# "published" => Timex.format!(event.inserted_at, "{ISO:Extended}"), # "published" => Timex.format!(event.inserted_at, "{ISO:Extended}"),
@ -35,15 +34,4 @@ defmodule MobilizonWeb.ActivityPub.ObjectView do
Map.merge(comment, Utils.make_json_ld_header()) Map.merge(comment, Utils.make_json_ld_header())
end end
def render("category.json", %{category: category}) when not is_nil(category) do
%{
"identifier" => category.id,
"name" => category.title
}
end
def render("category.json", %{category: _category}) do
nil
end
end end

View File

@ -11,7 +11,7 @@ defmodule Mobilizon.Service.ActivityPub do
""" """
alias Mobilizon.Events alias Mobilizon.Events
alias Mobilizon.Events.{Event, Category, Comment} alias Mobilizon.Events.{Event, Comment}
alias Mobilizon.Service.ActivityPub.Transmogrifier alias Mobilizon.Service.ActivityPub.Transmogrifier
alias Mobilizon.Service.WebFinger alias Mobilizon.Service.WebFinger
alias Mobilizon.Activity alias Mobilizon.Activity
@ -565,23 +565,8 @@ defmodule Mobilizon.Service.ActivityPub do
# TODO : Use MobilizonWeb.API instead # TODO : Use MobilizonWeb.API instead
# TODO : refactor me and move me somewhere else! # TODO : refactor me and move me somewhere else!
# TODO : also, there should be a form of cache that allows this to be more efficient # TODO : also, there should be a form of cache that allows this to be more efficient
category =
if is_nil(ical_event.categories) do
nil
else
ical_category = ical_event.categories |> hd() |> String.downcase()
case ical_category |> Events.get_category_by_title() do # ical_event.categories should be tags
nil ->
case Events.create_category(%{"title" => ical_category}) do
{:ok, %Category{} = category} -> category
_ -> nil
end
category ->
category
end
end
{:ok, event} = {:ok, event} =
Events.create_event(%{ Events.create_event(%{
@ -591,8 +576,7 @@ defmodule Mobilizon.Service.ActivityPub do
updated_at: ical_event.stamp, updated_at: ical_event.stamp,
description: ical_event.description |> sanitize_ical_event_strings, description: ical_event.description |> sanitize_ical_event_strings,
title: ical_event.summary |> sanitize_ical_event_strings, title: ical_event.summary |> sanitize_ical_event_strings,
organizer_actor: actor, organizer_actor: actor
category: category
}) })
event_to_activity(event, false) event_to_activity(event, false)

View File

@ -117,7 +117,7 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
"description" => object["content"], "description" => object["content"],
"organizer_actor_id" => actor_id, "organizer_actor_id" => actor_id,
"begins_on" => object["begins_on"], "begins_on" => object["begins_on"],
"category_id" => Events.get_category_by_title(object["category"]).id, "category" => object["category"],
"url" => object["id"], "url" => object["id"],
"uuid" => object["uuid"] "uuid" => object["uuid"]
} }

View File

@ -71,7 +71,7 @@
"phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"phoenix_html": {:hex, :phoenix_html, "2.13.1", "fa8f034b5328e2dfa0e4131b5569379003f34bc1fafdaa84985b0b9d2f12e68b", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_html": {:hex, :phoenix_html, "2.13.1", "fa8f034b5328e2dfa0e4131b5569379003f34bc1fafdaa84985b0b9d2f12e68b", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.0", "3bb31a9fbd40ffe8652e60c8660dffd72dd231efcdf49b744fb75b9ef7db5dd2", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm"}, "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.2.0", "3bb31a9fbd40ffe8652e60c8660dffd72dd231efcdf49b744fb75b9ef7db5dd2", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.1", "6668d787e602981f24f17a5fbb69cc98f8ab085114ebfac6cc36e10a90c8e93c", [:mix], [], "hexpm"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.2", "496c303bdf1b2e98a9d26e89af5bba3ab487ba3a3735f74bf1f4064d2a845a3e", [:mix], [], "hexpm"},
"plug": {:hex, :plug, "1.7.2", "d7b7db7fbd755e8283b6c0a50be71ec0a3d67d9213d74422d9372effc8e87fd1", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}], "hexpm"}, "plug": {:hex, :plug, "1.7.2", "d7b7db7fbd755e8283b6c0a50be71ec0a3d67d9213d74422d9372effc8e87fd1", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}], "hexpm"},
"plug_cowboy": {:hex, :plug_cowboy, "2.0.1", "d798f8ee5acc86b7d42dbe4450b8b0dadf665ce588236eb0a751a132417a980e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "plug_cowboy": {:hex, :plug_cowboy, "2.0.1", "d798f8ee5acc86b7d42dbe4450b8b0dadf665ce588236eb0a751a132417a980e", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
"plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"}, "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},

View File

@ -0,0 +1,35 @@
defmodule Mobilizon.Repo.Migrations.DropCategories do
use Ecto.Migration
def up do
# The category field is a string for the time being
# while we determine the definitive minimal list of
# categories in https://framagit.org/framasoft/mobilizon/issues/30
# afterwards it will be a PostgreSQL enum and we'll
# just add new elements without being able to delete
# the previous ones
alter table(:events) do
add(:category, :string)
remove(:category_id)
end
drop(table(:categories))
end
def down do
create table(:categories) do
add(:title, :string)
add(:description, :string)
add(:picture, :string)
timestamps()
end
create(unique_index(:categories, [:title]))
alter table(:events) do
remove(:category)
add(:category_id, references(:categories, on_delete: :nothing))
end
end
end

View File

@ -11,7 +11,8 @@ defmodule Mobilizon.EventsTest do
ends_on: "2010-04-17 14:00:00Z", ends_on: "2010-04-17 14:00:00Z",
title: "some title", title: "some title",
url: "some url", url: "some url",
uuid: "b5126423-f1af-43e4-a923-002a03003ba4" uuid: "b5126423-f1af-43e4-a923-002a03003ba4",
category: "meeting"
} }
describe "events" do describe "events" do
@ -79,14 +80,12 @@ defmodule Mobilizon.EventsTest do
test "create_event/1 with valid data creates a event" do test "create_event/1 with valid data creates a event" do
actor = insert(:actor) actor = insert(:actor)
category = insert(:category)
address = insert(:address) address = insert(:address)
valid_attrs = valid_attrs =
@event_valid_attrs @event_valid_attrs
|> Map.put(:organizer_actor, actor) |> Map.put(:organizer_actor, actor)
|> Map.put(:organizer_actor_id, actor.id) |> Map.put(:organizer_actor_id, actor.id)
|> Map.put(:category_id, category.id)
|> Map.put(:address_id, address.id) |> Map.put(:address_id, address.id)
with {:ok, %Event{} = event} <- Events.create_event(valid_attrs) do with {:ok, %Event{} = event} <- Events.create_event(valid_attrs) do
@ -175,77 +174,6 @@ defmodule Mobilizon.EventsTest do
end end
end end
describe "categories" do
alias Mobilizon.Events.Category
setup do
category = insert(:category)
{:ok, category: category}
end
@valid_attrs %{
description: "some description",
picture: %Plug.Upload{
path: "test/fixtures/category_picture.png",
filename: "category_picture.png"
},
title: "some title"
}
@update_attrs %{
description: "some updated description",
picture: %Plug.Upload{
path: "test/fixtures/category_picture_updated.png",
filename: "category_picture_updated.png"
},
title: "some updated title"
}
@invalid_attrs %{description: nil, picture: nil, title: nil}
test "list_categories/0 returns all categories", %{category: category} do
assert [category.id] == Events.list_categories() |> Enum.map(& &1.id)
end
test "get_category!/1 returns the category with given id", %{category: category} do
assert Events.get_category!(category.id).id == category.id
end
test "get_category_by_title/1 return the category with given title", %{category: category} do
assert Events.get_category_by_title(category.title).id == category.id
end
test "create_category/1 with valid data creates a category" do
assert {:ok, %Category{} = category} = Events.create_category(@valid_attrs)
assert category.description == "some description"
assert category.picture.file_name == @valid_attrs.picture.filename
assert category.title == "some title"
end
test "create_category/1 with invalid data returns error changeset" do
assert {:error, %Ecto.Changeset{}} = Events.create_category(@invalid_attrs)
end
test "update_category/2 with valid data updates the category", %{category: category} do
assert {:ok, %Category{} = category} = Events.update_category(category, @update_attrs)
assert category.description == "some updated description"
assert category.picture.file_name == @update_attrs.picture.filename
assert category.title == "some updated title"
end
test "update_category/2 with invalid data returns error changeset", %{category: category} do
assert {:error, %Ecto.Changeset{}} = Events.update_category(category, @invalid_attrs)
assert category.description == Events.get_category!(category.id).description
end
test "delete_category/1 deletes the category", %{category: category} do
assert {:ok, %Category{}} = Events.delete_category(category)
assert_raise Ecto.NoResultsError, fn -> Events.get_category!(category.id) end
end
test "change_category/1 returns a category changeset", %{category: category} do
assert %Ecto.Changeset{} = Events.change_category(category)
end
end
describe "tags" do describe "tags" do
alias Mobilizon.Events.Tag alias Mobilizon.Events.Tag

View File

@ -1,77 +0,0 @@
defmodule MobilizonWeb.Resolvers.CategoryResolverTest do
use MobilizonWeb.ConnCase
alias MobilizonWeb.AbsintheHelpers
import Mobilizon.Factory
setup %{conn: conn} do
user = insert(:user)
actor = insert(:actor, user: user)
{:ok, conn: conn, actor: actor, user: user}
end
describe "Category Resolver" do
test "list_categories/3 returns the list of categories", context do
insert(:category)
insert(:category)
query = """
{
categories {
id,
title,
description,
picture {
url,
},
}
}
"""
res =
context.conn
|> get("/api", AbsintheHelpers.query_skeleton(query, "categories"))
assert json_response(res, 200)["data"]["categories"] |> length == 2
end
# We can't test an upload…yet?
# test "create_category/3 creates a category", %{conn: conn, actor: actor, user: user} do
# mutation = """
# mutation {
# createCategory(title: "my category", description: "my desc") {
# id,
# title,
# description,
# },
# }
# """
# res =
# conn
# |> auth_conn(user)
# |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
# assert json_response(res, 200)["data"]["createCategory"]["title"] == "my category"
# end
# test "create_category/3 doesn't create a category if the user isn't logged in", %{conn: conn, actor: actor} do
# mutation = """
# mutation {
# createCategory(title: "my category", description: "my desc") {
# id,
# title,
# description,
# },
# }
# """
# res =
# conn
# |> post("/api", AbsintheHelpers.mutation_skeleton(mutation))
# assert hd(json_response(res, 200)["errors"])["message"] ==
# "You are not allowed to create a category if not connected"
# end
end
end

View File

@ -9,7 +9,8 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
title: "some title", title: "some title",
begins_on: DateTime.utc_now() |> DateTime.truncate(:second), begins_on: DateTime.utc_now() |> DateTime.truncate(:second),
uuid: "b5126423-f1af-43e4-a923-002a03003ba4", uuid: "b5126423-f1af-43e4-a923-002a03003ba4",
url: "some url" url: "some url",
category: "meeting"
} }
setup %{conn: conn} do setup %{conn: conn} do
@ -21,12 +22,9 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
describe "Event Resolver" do describe "Event Resolver" do
test "find_event/3 returns an event", context do test "find_event/3 returns an event", context do
category = insert(:category)
event = event =
@event @event
|> Map.put(:organizer_actor_id, context.actor.id) |> Map.put(:organizer_actor_id, context.actor.id)
|> Map.put(:category_id, category.id)
{:ok, event} = Events.create_event(event) {:ok, event} = Events.create_event(event)
@ -61,8 +59,6 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
end end
test "create_event/3 creates an event", %{conn: conn, actor: actor, user: user} do test "create_event/3 creates an event", %{conn: conn, actor: actor, user: user} do
category = insert(:category)
mutation = """ mutation = """
mutation { mutation {
createEvent( createEvent(
@ -72,7 +68,7 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.to_iso8601() DateTime.utc_now() |> DateTime.truncate(:second) |> DateTime.to_iso8601()
}", }",
organizer_actor_id: "#{actor.id}", organizer_actor_id: "#{actor.id}",
category: "#{category.title}" category: "birthday"
) { ) {
title, title,
uuid uuid

View File

@ -9,7 +9,8 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
title: "some title", title: "some title",
begins_on: DateTime.utc_now() |> DateTime.truncate(:second), begins_on: DateTime.utc_now() |> DateTime.truncate(:second),
uuid: "b5126423-f1af-43e4-a923-002a03003ba4", uuid: "b5126423-f1af-43e4-a923-002a03003ba4",
url: "some url" url: "some url",
category: "meeting"
} }
setup %{conn: conn} do setup %{conn: conn} do
@ -313,13 +314,9 @@ defmodule MobilizonWeb.Resolvers.ParticipantResolverTest do
end end
test "list_participants_for_event/3 returns participants for an event", context do test "list_participants_for_event/3 returns participants for an event", context do
# Plain event
category = insert(:category)
event = event =
@event @event
|> Map.put(:organizer_actor_id, context.actor.id) |> Map.put(:organizer_actor_id, context.actor.id)
|> Map.put(:category_id, category.id)
{:ok, event} = Events.create_event(event) {:ok, event} = Events.create_event(event)

View File

@ -53,13 +53,6 @@ defmodule Mobilizon.Factory do
} }
end end
def category_factory do
%Mobilizon.Events.Category{
title: sequence("MyCategory"),
description: "My category desc"
}
end
def tag_factory do def tag_factory do
%Mobilizon.Events.Tag{ %Mobilizon.Events.Tag{
title: "MyTag", title: "MyTag",
@ -112,7 +105,7 @@ defmodule Mobilizon.Factory do
begins_on: start, begins_on: start,
ends_on: Timex.shift(start, hours: 2), ends_on: Timex.shift(start, hours: 2),
organizer_actor: actor, organizer_actor: actor,
category: build(:category), category: sequence("something"),
physical_address: build(:address), physical_address: build(:address),
visibility: :public, visibility: :public,
url: "#{actor.url}/#{uuid}", url: "#{actor.url}/#{uuid}",