forked from potsda.mn/mobilizon
Merge branch 'attach-picture-entities' into 'master'
Attach actor to pictures entity Closes #129 See merge request framasoft/mobilizon!147
This commit is contained in:
commit
4434459e59
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="editor">
|
<div class="editor" id="tiptab-editor" :data-actor-id="loggedPerson && loggedPerson.id">
|
||||||
<editor-menu-bar :editor="editor" v-slot="{ commands, isActive, focused }">
|
<editor-menu-bar :editor="editor" v-slot="{ commands, isActive, focused }">
|
||||||
<div class="menubar bar-is-hidden" :class="{ 'is-focused': focused }">
|
<div class="menubar bar-is-hidden" :class="{ 'is-focused': focused }">
|
||||||
|
|
||||||
|
@ -172,16 +172,25 @@ import {
|
||||||
} from 'tiptap-extensions';
|
} from 'tiptap-extensions';
|
||||||
import tippy, { Instance } from 'tippy.js';
|
import tippy, { Instance } from 'tippy.js';
|
||||||
import { SEARCH_PERSONS } from '@/graphql/search';
|
import { SEARCH_PERSONS } from '@/graphql/search';
|
||||||
import { IActor } from '@/types/actor';
|
import { IActor, IPerson } from '@/types/actor';
|
||||||
import Image from '@/components/Editor/Image';
|
import Image from '@/components/Editor/Image';
|
||||||
import { UPLOAD_PICTURE } from '@/graphql/upload';
|
import { UPLOAD_PICTURE } from '@/graphql/upload';
|
||||||
import { listenFileUpload } from '@/utils/upload';
|
import { listenFileUpload } from '@/utils/upload';
|
||||||
|
import { LOGGED_PERSON } from '@/graphql/actor';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: { EditorContent, EditorMenuBar, EditorMenuBubble },
|
components: { EditorContent, EditorMenuBar, EditorMenuBubble },
|
||||||
|
apollo: {
|
||||||
|
loggedPerson: {
|
||||||
|
query: LOGGED_PERSON,
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
export default class CreateEvent extends Vue {
|
export default class CreateEvent extends Vue {
|
||||||
@Prop({ required: true }) value!: String;
|
@Prop({ required: true }) value!: String;
|
||||||
|
|
||||||
|
loggedPerson!: IPerson;
|
||||||
|
|
||||||
editor: Editor = null;
|
editor: Editor = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -429,6 +438,7 @@ export default class CreateEvent extends Vue {
|
||||||
variables: {
|
variables: {
|
||||||
file: image,
|
file: image,
|
||||||
name: image.name,
|
name: image.name,
|
||||||
|
actorId: this.loggedPerson.id,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (data.uploadPicture && data.uploadPicture.url) {
|
if (data.uploadPicture && data.uploadPicture.url) {
|
||||||
|
|
|
@ -71,11 +71,14 @@ export default class Image extends Node {
|
||||||
const { schema } = view.state;
|
const { schema } = view.state;
|
||||||
const coordinates = view.posAtCoords({ left: event.clientX, top: event.clientY });
|
const coordinates = view.posAtCoords({ left: event.clientX, top: event.clientY });
|
||||||
const client = apolloProvider.defaultClient as ApolloClient<InMemoryCache>;
|
const client = apolloProvider.defaultClient as ApolloClient<InMemoryCache>;
|
||||||
|
const editorElem = document.getElementById('tiptab-editor');
|
||||||
|
const actorId = editorElem && editorElem.dataset.actorId;
|
||||||
|
|
||||||
for (const image of images) {
|
for (const image of images) {
|
||||||
const { data } = await client.mutate({
|
const { data } = await client.mutate({
|
||||||
mutation: UPLOAD_PICTURE,
|
mutation: UPLOAD_PICTURE,
|
||||||
variables: {
|
variables: {
|
||||||
|
actorId,
|
||||||
file: image,
|
file: image,
|
||||||
name: image.name,
|
name: image.name,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import gql from 'graphql-tag';
|
import gql from 'graphql-tag';
|
||||||
|
|
||||||
export const UPLOAD_PICTURE = gql`
|
export const UPLOAD_PICTURE = gql`
|
||||||
mutation UploadPicture($file: Upload!, $alt: String, $name: String!){
|
mutation UploadPicture($file: Upload!, $alt: String, $name: String!, $actorId: ID!){
|
||||||
uploadPicture(file: $file, alt: $alt, name: $name) {
|
uploadPicture(file: $file, alt: $alt, name: $name, actorId: $actorId) {
|
||||||
url,
|
url,
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,11 @@ defmodule Mobilizon.Media.Picture do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
alias Mobilizon.Media.File
|
alias Mobilizon.Media.File
|
||||||
|
alias Mobilizon.Actors.Actor
|
||||||
|
|
||||||
schema "pictures" do
|
schema "pictures" do
|
||||||
embeds_one(:file, File, on_replace: :update)
|
embeds_one(:file, File, on_replace: :update)
|
||||||
|
belongs_to(:actor, Actor)
|
||||||
|
|
||||||
timestamps()
|
timestamps()
|
||||||
end
|
end
|
||||||
|
@ -15,7 +17,7 @@ defmodule Mobilizon.Media.Picture do
|
||||||
@doc false
|
@doc false
|
||||||
def changeset(picture, attrs) do
|
def changeset(picture, attrs) do
|
||||||
picture
|
picture
|
||||||
|> cast(attrs, [])
|
|> cast(attrs, [:actor_id])
|
||||||
|> cast_embed(:file)
|
|> cast_embed(:file)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -149,6 +149,18 @@ defmodule Mobilizon.Users.User do
|
||||||
def is_confirmed(%User{confirmed_at: nil} = _user), do: {:error, :unconfirmed}
|
def is_confirmed(%User{confirmed_at: nil} = _user), do: {:error, :unconfirmed}
|
||||||
def is_confirmed(%User{} = user), do: {:ok, user}
|
def is_confirmed(%User{} = user), do: {:ok, user}
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns whether an user owns an actor
|
||||||
|
"""
|
||||||
|
@spec owns_actor(struct(), String.t()) :: {:is_owned, false} | {:is_owned, true, Actor.t()}
|
||||||
|
def owns_actor(%User{} = user, actor_id) when is_binary(actor_id) do
|
||||||
|
case Integer.parse(actor_id) do
|
||||||
|
{actor_id, ""} -> owns_actor(user, actor_id)
|
||||||
|
_ -> {:is_owned, false}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec owns_actor(struct(), integer()) :: {:is_owned, false} | {:is_owned, true, Actor.t()}
|
||||||
def owns_actor(%User{actors: actors}, actor_id) do
|
def owns_actor(%User{actors: actors}, actor_id) do
|
||||||
case Enum.find(actors, fn a -> a.id == actor_id end) do
|
case Enum.find(actors, fn a -> a.id == actor_id end) do
|
||||||
nil -> {:is_owned, false}
|
nil -> {:is_owned, false}
|
||||||
|
|
|
@ -4,6 +4,7 @@ defmodule MobilizonWeb.Resolvers.Picture do
|
||||||
"""
|
"""
|
||||||
alias Mobilizon.Media
|
alias Mobilizon.Media
|
||||||
alias Mobilizon.Media.Picture
|
alias Mobilizon.Media.Picture
|
||||||
|
alias Mobilizon.Users.User
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Get picture for an event's pic
|
Get picture for an event's pic
|
||||||
|
@ -43,16 +44,21 @@ defmodule MobilizonWeb.Resolvers.Picture do
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec upload_picture(map(), map(), map()) :: {:ok, Picture.t()} | {:error, any()}
|
@spec upload_picture(map(), map(), map()) :: {:ok, Picture.t()} | {:error, any()}
|
||||||
def upload_picture(_parent, %{file: %Plug.Upload{} = file} = args, %{
|
def upload_picture(_parent, %{file: %Plug.Upload{} = file, actor_id: actor_id} = args, %{
|
||||||
context: %{
|
context: %{
|
||||||
current_user: _user
|
current_user: user
|
||||||
}
|
}
|
||||||
}) do
|
}) do
|
||||||
with {:ok, %{"url" => [%{"href" => url}]}} <- MobilizonWeb.Upload.store(file),
|
with {:is_owned, true, _actor} <- User.owns_actor(user, actor_id),
|
||||||
|
{:ok, %{"url" => [%{"href" => url}]}} <- MobilizonWeb.Upload.store(file),
|
||||||
args <- Map.put(args, :url, url),
|
args <- Map.put(args, :url, url),
|
||||||
{:ok, picture = %Picture{}} <- Media.create_picture(%{"file" => args}) do
|
{:ok, picture = %Picture{}} <-
|
||||||
|
Media.create_picture(%{"file" => args, "actor_id" => actor_id}) do
|
||||||
{:ok, %{name: picture.file.name, url: picture.file.url, id: picture.id}}
|
{:ok, %{name: picture.file.name, url: picture.file.url, id: picture.id}}
|
||||||
else
|
else
|
||||||
|
{:is_owned, false} ->
|
||||||
|
{:error, "Actor id is not owned by authenticated user"}
|
||||||
|
|
||||||
err ->
|
err ->
|
||||||
{:error, err}
|
{:error, err}
|
||||||
end
|
end
|
||||||
|
|
|
@ -31,7 +31,7 @@ defmodule MobilizonWeb.Router do
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline :browser do
|
pipeline :browser do
|
||||||
plug(Plug.Static, at: "/", from: "priv/static")
|
plug(Plug.Static, at: "/", from: "priv/static/js")
|
||||||
plug(:accepts, ["html"])
|
plug(:accepts, ["html"])
|
||||||
plug(:fetch_session)
|
plug(:fetch_session)
|
||||||
plug(:fetch_flash)
|
plug(:fetch_flash)
|
||||||
|
|
|
@ -26,6 +26,7 @@ defmodule MobilizonWeb.Schema.PictureType do
|
||||||
field(:name, non_null(:string))
|
field(:name, non_null(:string))
|
||||||
field(:alt, :string)
|
field(:alt, :string)
|
||||||
field(:file, non_null(:upload))
|
field(:file, non_null(:upload))
|
||||||
|
field(:actor_id, :id)
|
||||||
end
|
end
|
||||||
|
|
||||||
object :picture_queries do
|
object :picture_queries do
|
||||||
|
@ -42,6 +43,7 @@ defmodule MobilizonWeb.Schema.PictureType do
|
||||||
arg(:name, non_null(:string))
|
arg(:name, non_null(:string))
|
||||||
arg(:alt, :string)
|
arg(:alt, :string)
|
||||||
arg(:file, non_null(:upload))
|
arg(:file, non_null(:upload))
|
||||||
|
arg(:actor_id, non_null(:id))
|
||||||
resolve(&Picture.upload_picture/3)
|
resolve(&Picture.upload_picture/3)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -176,6 +176,9 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||||
# Repo.one(query)
|
# Repo.one(query)
|
||||||
# end
|
# end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Save picture data from %Plug.Upload{} and return AS Link data.
|
||||||
|
"""
|
||||||
def make_picture_data(%Plug.Upload{} = picture) do
|
def make_picture_data(%Plug.Upload{} = picture) do
|
||||||
with {:ok, picture} <- MobilizonWeb.Upload.store(picture) do
|
with {:ok, picture} <- MobilizonWeb.Upload.store(picture) do
|
||||||
picture
|
picture
|
||||||
|
@ -184,6 +187,10 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Convert a picture model into an AS Link representation
|
||||||
|
"""
|
||||||
|
# TODO: Move me to Mobilizon.Service.ActivityPub.Converters
|
||||||
def make_picture_data(%Picture{file: file} = _picture) do
|
def make_picture_data(%Picture{file: file} = _picture) do
|
||||||
%{
|
%{
|
||||||
"type" => "Document",
|
"type" => "Document",
|
||||||
|
@ -198,6 +205,9 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Save picture data from raw data and return AS Link data.
|
||||||
|
"""
|
||||||
def make_picture_data(%{picture: picture}) do
|
def make_picture_data(%{picture: picture}) do
|
||||||
with {:ok, %{"url" => [%{"href" => url}]}} <- MobilizonWeb.Upload.store(picture.file),
|
with {:ok, %{"url" => [%{"href" => url}]}} <- MobilizonWeb.Upload.store(picture.file),
|
||||||
{:ok, %Picture{file: _file} = pic} <-
|
{:ok, %Picture{file: _file} = pic} <-
|
||||||
|
@ -205,7 +215,8 @@ defmodule Mobilizon.Service.ActivityPub.Utils do
|
||||||
"file" => %{
|
"file" => %{
|
||||||
"url" => url,
|
"url" => url,
|
||||||
"name" => picture.name
|
"name" => picture.name
|
||||||
}
|
},
|
||||||
|
"actor_id" => picture.actor_id
|
||||||
}) do
|
}) do
|
||||||
make_picture_data(pic)
|
make_picture_data(pic)
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
defmodule :"Elixir.Mobilizon.Repo.Migrations.Attach-pictures-to-actors" do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
alter table(:pictures) do
|
||||||
|
add(:actor_id, references(:actors, on_delete: :delete_all), null: false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
1320
schema.graphql
1320
schema.graphql
File diff suppressed because it is too large
Load diff
|
@ -22,17 +22,22 @@ defmodule Mobilizon.MediaTest do
|
||||||
|
|
||||||
test "get_picture!/1 returns the picture with given id" do
|
test "get_picture!/1 returns the picture with given id" do
|
||||||
picture = insert(:picture)
|
picture = insert(:picture)
|
||||||
assert Media.get_picture!(picture.id) == picture
|
assert Media.get_picture!(picture.id).id == picture.id
|
||||||
end
|
end
|
||||||
|
|
||||||
test "create_picture/1 with valid data creates a picture" do
|
test "create_picture/1 with valid data creates a picture" do
|
||||||
assert {:ok, %Picture{} = picture} = Media.create_picture(@valid_attrs)
|
assert {:ok, %Picture{} = picture} =
|
||||||
|
Media.create_picture(Map.put(@valid_attrs, :actor_id, insert(:actor).id))
|
||||||
|
|
||||||
assert picture.file.name == "something old"
|
assert picture.file.name == "something old"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "update_picture/2 with valid data updates the picture" do
|
test "update_picture/2 with valid data updates the picture" do
|
||||||
picture = insert(:picture)
|
picture = insert(:picture)
|
||||||
assert {:ok, %Picture{} = picture} = Media.update_picture(picture, @update_attrs)
|
|
||||||
|
assert {:ok, %Picture{} = picture} =
|
||||||
|
Media.update_picture(picture, Map.put(@update_attrs, :actor_id, insert(:actor).id))
|
||||||
|
|
||||||
assert picture.file.name == "something new"
|
assert picture.file.name == "something new"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -103,7 +103,8 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
|
||||||
picture: {
|
picture: {
|
||||||
name: "picture for my event",
|
name: "picture for my event",
|
||||||
alt: "A very sunny landscape",
|
alt: "A very sunny landscape",
|
||||||
file: "event.jpg"
|
file: "event.jpg",
|
||||||
|
actor_id: #{actor.id}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
@ -148,7 +149,8 @@ defmodule MobilizonWeb.Resolvers.EventResolverTest do
|
||||||
mutation { uploadPicture(
|
mutation { uploadPicture(
|
||||||
name: "#{picture.name}",
|
name: "#{picture.name}",
|
||||||
alt: "#{picture.alt}",
|
alt: "#{picture.alt}",
|
||||||
file: "#{picture.file}"
|
file: "#{picture.file}",
|
||||||
|
actor_id: #{actor.id}
|
||||||
) {
|
) {
|
||||||
id,
|
id,
|
||||||
url,
|
url,
|
||||||
|
|
|
@ -7,8 +7,9 @@ defmodule MobilizonWeb.Resolvers.PictureResolverTest do
|
||||||
|
|
||||||
setup %{conn: conn} do
|
setup %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
actor = insert(:actor, user: user)
|
||||||
|
|
||||||
{:ok, conn: conn, user: user}
|
{:ok, conn: conn, user: user, actor: actor}
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "Resolver: Get picture" do
|
describe "Resolver: Get picture" do
|
||||||
|
@ -56,14 +57,15 @@ defmodule MobilizonWeb.Resolvers.PictureResolverTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "Resolver: Upload picture" do
|
describe "Resolver: Upload picture" do
|
||||||
test "upload_picture/3 uploads a new picture", %{conn: conn, user: user} do
|
test "upload_picture/3 uploads a new picture", %{conn: conn, user: user, actor: actor} do
|
||||||
picture = %{name: "my pic", alt: "represents something", file: "picture.png"}
|
picture = %{name: "my pic", alt: "represents something", file: "picture.png"}
|
||||||
|
|
||||||
mutation = """
|
mutation = """
|
||||||
mutation { uploadPicture(
|
mutation { uploadPicture(
|
||||||
name: "#{picture.name}",
|
name: "#{picture.name}",
|
||||||
alt: "#{picture.alt}",
|
alt: "#{picture.alt}",
|
||||||
file: "#{picture.file}"
|
file: "#{picture.file}",
|
||||||
|
actor_id: #{actor.id}
|
||||||
) {
|
) {
|
||||||
url,
|
url,
|
||||||
name
|
name
|
||||||
|
@ -92,14 +94,15 @@ defmodule MobilizonWeb.Resolvers.PictureResolverTest do
|
||||||
assert json_response(res, 200)["data"]["uploadPicture"]["url"]
|
assert json_response(res, 200)["data"]["uploadPicture"]["url"]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "upload_picture/3 forbids uploading if no auth", %{conn: conn} do
|
test "upload_picture/3 forbids uploading if no auth", %{conn: conn, actor: actor} do
|
||||||
picture = %{name: "my pic", alt: "represents something", file: "picture.png"}
|
picture = %{name: "my pic", alt: "represents something", file: "picture.png"}
|
||||||
|
|
||||||
mutation = """
|
mutation = """
|
||||||
mutation { uploadPicture(
|
mutation { uploadPicture(
|
||||||
name: "#{picture.name}",
|
name: "#{picture.name}",
|
||||||
alt: "#{picture.alt}",
|
alt: "#{picture.alt}",
|
||||||
file: "#{picture.file}"
|
file: "#{picture.file}",
|
||||||
|
actor_id: #{actor.id}
|
||||||
) {
|
) {
|
||||||
url,
|
url,
|
||||||
name
|
name
|
||||||
|
|
|
@ -180,7 +180,8 @@ defmodule Mobilizon.Factory do
|
||||||
|
|
||||||
def picture_factory do
|
def picture_factory do
|
||||||
%Mobilizon.Media.Picture{
|
%Mobilizon.Media.Picture{
|
||||||
file: build(:file)
|
file: build(:file),
|
||||||
|
actor: build(:actor)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue