Track usage of media files and add a job to clean them
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
c19e326bd8
commit
c9457fe0d3
@ -28,6 +28,8 @@ config :mobilizon, :instance,
|
|||||||
upload_limit: 10_000_000,
|
upload_limit: 10_000_000,
|
||||||
avatar_upload_limit: 2_000_000,
|
avatar_upload_limit: 2_000_000,
|
||||||
banner_upload_limit: 4_000_000,
|
banner_upload_limit: 4_000_000,
|
||||||
|
remove_orphan_uploads: true,
|
||||||
|
orphan_upload_grace_period_hours: 48,
|
||||||
email_from: "noreply@localhost",
|
email_from: "noreply@localhost",
|
||||||
email_reply_to: "noreply@localhost"
|
email_reply_to: "noreply@localhost"
|
||||||
|
|
||||||
@ -250,6 +252,8 @@ config :mobilizon, Oban,
|
|||||||
crontab: [
|
crontab: [
|
||||||
{"@hourly", Mobilizon.Service.Workers.BuildSiteMap, queue: :background},
|
{"@hourly", Mobilizon.Service.Workers.BuildSiteMap, queue: :background},
|
||||||
{"17 * * * *", Mobilizon.Service.Workers.RefreshGroups, queue: :background}
|
{"17 * * * *", Mobilizon.Service.Workers.RefreshGroups, queue: :background}
|
||||||
|
# To be activated in Mobilizon 1.2
|
||||||
|
# {"@hourly", Mobilizon.Service.Workers.CleanOrphanMediaWorker, queue: :background}
|
||||||
]
|
]
|
||||||
|
|
||||||
config :mobilizon, :rich_media,
|
config :mobilizon, :rich_media,
|
||||||
|
@ -212,7 +212,7 @@ import { SEARCH_PERSONS } from "../graphql/search";
|
|||||||
import { Actor, IActor, IPerson } from "../types/actor";
|
import { Actor, IActor, IPerson } from "../types/actor";
|
||||||
import Image from "./Editor/Image";
|
import Image from "./Editor/Image";
|
||||||
import MaxSize from "./Editor/MaxSize";
|
import MaxSize from "./Editor/MaxSize";
|
||||||
import { UPLOAD_PICTURE } from "../graphql/upload";
|
import { UPLOAD_MEDIA } from "../graphql/upload";
|
||||||
import { listenFileUpload } from "../utils/upload";
|
import { listenFileUpload } from "../utils/upload";
|
||||||
import { CURRENT_ACTOR_CLIENT } from "../graphql/actor";
|
import { CURRENT_ACTOR_CLIENT } from "../graphql/actor";
|
||||||
import { IComment } from "../types/comment.model";
|
import { IComment } from "../types/comment.model";
|
||||||
@ -395,7 +395,15 @@ export default class EditorComponent extends Vue {
|
|||||||
new Image(),
|
new Image(),
|
||||||
new MaxSize({ maxSize: this.maxSize }),
|
new MaxSize({ maxSize: this.maxSize }),
|
||||||
],
|
],
|
||||||
onUpdate: ({ getHTML }: { getHTML: Function }) => {
|
onUpdate: ({
|
||||||
|
getHTML,
|
||||||
|
transaction,
|
||||||
|
getJSON,
|
||||||
|
}: {
|
||||||
|
getHTML: Function;
|
||||||
|
getJSON: Function;
|
||||||
|
transaction: unknown;
|
||||||
|
}) => {
|
||||||
this.$emit("input", getHTML());
|
this.$emit("input", getHTML());
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -526,14 +534,14 @@ export default class EditorComponent extends Vue {
|
|||||||
const image = await listenFileUpload();
|
const image = await listenFileUpload();
|
||||||
try {
|
try {
|
||||||
const { data } = await this.$apollo.mutate({
|
const { data } = await this.$apollo.mutate({
|
||||||
mutation: UPLOAD_PICTURE,
|
mutation: UPLOAD_MEDIA,
|
||||||
variables: {
|
variables: {
|
||||||
file: image,
|
file: image,
|
||||||
name: image.name,
|
name: image.name,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (data.uploadPicture && data.uploadPicture.url) {
|
if (data.uploadMedia && data.uploadMedia.url) {
|
||||||
command({ src: data.uploadPicture.url });
|
command({ src: data.uploadMedia.url, "data-media-id": data.uploadMedia.id });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { Node } from "tiptap";
|
import { Node } from "tiptap";
|
||||||
import { UPLOAD_PICTURE } from "@/graphql/upload";
|
import { UPLOAD_MEDIA } from "@/graphql/upload";
|
||||||
import apolloProvider from "@/vue-apollo";
|
import apolloProvider from "@/vue-apollo";
|
||||||
import ApolloClient from "apollo-client";
|
import ApolloClient from "apollo-client";
|
||||||
import { NormalizedCacheObject } from "apollo-cache-inmemory";
|
import { NormalizedCacheObject } from "apollo-cache-inmemory";
|
||||||
@ -27,16 +28,18 @@ export default class Image extends Node {
|
|||||||
title: {
|
title: {
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
|
"data-media-id": {},
|
||||||
},
|
},
|
||||||
group: "inline",
|
group: "inline",
|
||||||
draggable: true,
|
draggable: true,
|
||||||
parseDOM: [
|
parseDOM: [
|
||||||
{
|
{
|
||||||
tag: "img[src]",
|
tag: "img",
|
||||||
getAttrs: (dom: any) => ({
|
getAttrs: (dom: any) => ({
|
||||||
src: dom.getAttribute("src"),
|
src: dom.getAttribute("src"),
|
||||||
title: dom.getAttribute("title"),
|
title: dom.getAttribute("title"),
|
||||||
alt: dom.getAttribute("alt"),
|
alt: dom.getAttribute("alt"),
|
||||||
|
"data-media-id": dom.getAttribute("data-media-id"),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -92,13 +95,16 @@ export default class Image extends Node {
|
|||||||
try {
|
try {
|
||||||
images.forEach(async (image) => {
|
images.forEach(async (image) => {
|
||||||
const { data } = await client.mutate({
|
const { data } = await client.mutate({
|
||||||
mutation: UPLOAD_PICTURE,
|
mutation: UPLOAD_MEDIA,
|
||||||
variables: {
|
variables: {
|
||||||
file: image,
|
file: image,
|
||||||
name: image.name,
|
name: image.name,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const node = schema.nodes.image.create({ src: data.uploadPicture.url });
|
const node = schema.nodes.image.create({
|
||||||
|
src: data.uploadMedia.url,
|
||||||
|
"data-media-id": data.uploadMedia.id,
|
||||||
|
});
|
||||||
const transaction = view.state.tr.insert(coordinates.pos, node);
|
const transaction = view.state.tr.insert(coordinates.pos, node);
|
||||||
view.dispatch(transaction);
|
view.dispatch(transaction);
|
||||||
});
|
});
|
||||||
|
@ -60,14 +60,14 @@ figure.image {
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { IPicture } from "@/types/picture.model";
|
import { IMedia } from "@/types/media.model";
|
||||||
import { Component, Model, Prop, Vue, Watch } from "vue-property-decorator";
|
import { Component, Model, Prop, Vue, Watch } from "vue-property-decorator";
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class PictureUpload extends Vue {
|
export default class PictureUpload extends Vue {
|
||||||
@Model("change", { type: File }) readonly pictureFile!: File;
|
@Model("change", { type: File }) readonly pictureFile!: File;
|
||||||
|
|
||||||
@Prop({ type: Object, required: false }) defaultImage!: IPicture;
|
@Prop({ type: Object, required: false }) defaultImage!: IMedia;
|
||||||
|
|
||||||
@Prop({ type: String, required: false, default: "image/gif,image/png,image/jpeg,image/webp" })
|
@Prop({ type: String, required: false, default: "image/gif,image/png,image/jpeg,image/webp" })
|
||||||
accept!: string;
|
accept!: string;
|
||||||
@ -100,7 +100,7 @@ export default class PictureUpload extends Vue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Watch("defaultImage")
|
@Watch("defaultImage")
|
||||||
onDefaultImageChange(defaultImage: IPicture): void {
|
onDefaultImageChange(defaultImage: IMedia): void {
|
||||||
console.log("onDefaultImageChange", defaultImage);
|
console.log("onDefaultImageChange", defaultImage);
|
||||||
this.imageSrc = defaultImage ? defaultImage.url : null;
|
this.imageSrc = defaultImage ? defaultImage.url : null;
|
||||||
}
|
}
|
||||||
|
@ -421,7 +421,7 @@ export const CREATE_PERSON = gql`
|
|||||||
$preferredUsername: String!
|
$preferredUsername: String!
|
||||||
$name: String!
|
$name: String!
|
||||||
$summary: String
|
$summary: String
|
||||||
$avatar: PictureInput
|
$avatar: MediaInput
|
||||||
) {
|
) {
|
||||||
createPerson(
|
createPerson(
|
||||||
preferredUsername: $preferredUsername
|
preferredUsername: $preferredUsername
|
||||||
@ -442,7 +442,7 @@ export const CREATE_PERSON = gql`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export const UPDATE_PERSON = gql`
|
export const UPDATE_PERSON = gql`
|
||||||
mutation UpdatePerson($id: ID!, $name: String, $summary: String, $avatar: PictureInput) {
|
mutation UpdatePerson($id: ID!, $name: String, $summary: String, $avatar: MediaInput) {
|
||||||
updatePerson(id: $id, name: $name, summary: $summary, avatar: $avatar) {
|
updatePerson(id: $id, name: $name, summary: $summary, avatar: $avatar) {
|
||||||
id
|
id
|
||||||
preferredUsername
|
preferredUsername
|
||||||
|
@ -244,7 +244,7 @@ export const CREATE_EVENT = gql`
|
|||||||
$joinOptions: EventJoinOptions,
|
$joinOptions: EventJoinOptions,
|
||||||
$draft: Boolean,
|
$draft: Boolean,
|
||||||
$tags: [String],
|
$tags: [String],
|
||||||
$picture: PictureInput,
|
$picture: MediaInput,
|
||||||
$onlineAddress: String,
|
$onlineAddress: String,
|
||||||
$phoneAddress: String,
|
$phoneAddress: String,
|
||||||
$category: String,
|
$category: String,
|
||||||
@ -355,7 +355,7 @@ export const EDIT_EVENT = gql`
|
|||||||
$joinOptions: EventJoinOptions,
|
$joinOptions: EventJoinOptions,
|
||||||
$draft: Boolean,
|
$draft: Boolean,
|
||||||
$tags: [String],
|
$tags: [String],
|
||||||
$picture: PictureInput,
|
$picture: MediaInput,
|
||||||
$onlineAddress: String,
|
$onlineAddress: String,
|
||||||
$phoneAddress: String,
|
$phoneAddress: String,
|
||||||
$organizerActorId: ID,
|
$organizerActorId: ID,
|
||||||
|
@ -227,8 +227,8 @@ export const CREATE_GROUP = gql`
|
|||||||
$preferredUsername: String!
|
$preferredUsername: String!
|
||||||
$name: String!
|
$name: String!
|
||||||
$summary: String
|
$summary: String
|
||||||
$avatar: PictureInput
|
$avatar: MediaInput
|
||||||
$banner: PictureInput
|
$banner: MediaInput
|
||||||
) {
|
) {
|
||||||
createGroup(
|
createGroup(
|
||||||
preferredUsername: $preferredUsername
|
preferredUsername: $preferredUsername
|
||||||
@ -259,8 +259,8 @@ export const UPDATE_GROUP = gql`
|
|||||||
$id: ID!
|
$id: ID!
|
||||||
$name: String
|
$name: String
|
||||||
$summary: String
|
$summary: String
|
||||||
$avatar: PictureInput
|
$avatar: MediaInput
|
||||||
$banner: PictureInput
|
$banner: MediaInput
|
||||||
$visibility: GroupVisibility
|
$visibility: GroupVisibility
|
||||||
$openness: Openness
|
$openness: Openness
|
||||||
$physicalAddress: AddressInput
|
$physicalAddress: AddressInput
|
||||||
|
@ -119,7 +119,7 @@ export const CREATE_POST = gql`
|
|||||||
$visibility: PostVisibility
|
$visibility: PostVisibility
|
||||||
$draft: Boolean
|
$draft: Boolean
|
||||||
$tags: [String]
|
$tags: [String]
|
||||||
$picture: PictureInput
|
$picture: MediaInput
|
||||||
) {
|
) {
|
||||||
createPost(
|
createPost(
|
||||||
title: $title
|
title: $title
|
||||||
@ -145,7 +145,7 @@ export const UPDATE_POST = gql`
|
|||||||
$visibility: PostVisibility
|
$visibility: PostVisibility
|
||||||
$draft: Boolean
|
$draft: Boolean
|
||||||
$tags: [String]
|
$tags: [String]
|
||||||
$picture: PictureInput
|
$picture: MediaInput
|
||||||
) {
|
) {
|
||||||
updatePost(
|
updatePost(
|
||||||
id: $id
|
id: $id
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import gql from "graphql-tag";
|
import gql from "graphql-tag";
|
||||||
|
|
||||||
export const UPLOAD_PICTURE = gql`
|
export const UPLOAD_MEDIA = gql`
|
||||||
mutation UploadPicture($file: Upload!, $alt: String, $name: String!) {
|
mutation UploadMedia($file: Upload!, $alt: String, $name: String!) {
|
||||||
uploadPicture(file: $file, alt: $alt, name: $name) {
|
uploadMedia(file: $file, alt: $alt, name: $name) {
|
||||||
url
|
url
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const REMOVE_PICTURE = gql`
|
export const REMOVE_MEDIA = gql`
|
||||||
mutation RemovePicture($id: ID!) {
|
mutation RemoveMedia($id: ID!) {
|
||||||
removePicture(id: $id) {
|
removeMedia(id: $id) {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { IPicture } from "@/types/picture.model";
|
import { IMedia } from "@/types/media.model";
|
||||||
|
|
||||||
export enum ActorType {
|
export enum ActorType {
|
||||||
PERSON = "PERSON",
|
PERSON = "PERSON",
|
||||||
@ -17,17 +17,17 @@ export interface IActor {
|
|||||||
summary: string;
|
summary: string;
|
||||||
preferredUsername: string;
|
preferredUsername: string;
|
||||||
suspended: boolean;
|
suspended: boolean;
|
||||||
avatar?: IPicture | null;
|
avatar?: IMedia | null;
|
||||||
banner?: IPicture | null;
|
banner?: IMedia | null;
|
||||||
type: ActorType;
|
type: ActorType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Actor implements IActor {
|
export class Actor implements IActor {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
|
||||||
avatar: IPicture | null = null;
|
avatar: IMedia | null = null;
|
||||||
|
|
||||||
banner: IPicture | null = null;
|
banner: IMedia | null = null;
|
||||||
|
|
||||||
domain: string | null = null;
|
domain: string | null = null;
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Address, IAddress } from "@/types/address.model";
|
import { Address, IAddress } from "@/types/address.model";
|
||||||
import { ITag } from "@/types/tag.model";
|
import { ITag } from "@/types/tag.model";
|
||||||
import { IPicture } from "@/types/picture.model";
|
import { IMedia } from "@/types/media.model";
|
||||||
import { IComment } from "@/types/comment.model";
|
import { IComment } from "@/types/comment.model";
|
||||||
import { Paginate } from "@/types/paginate";
|
import { Paginate } from "@/types/paginate";
|
||||||
import { Actor, Group, IActor, IGroup, IPerson } from "./actor";
|
import { Actor, Group, IActor, IGroup, IPerson } from "./actor";
|
||||||
@ -69,7 +69,7 @@ interface IEventEditJSON {
|
|||||||
visibility: EventVisibility;
|
visibility: EventVisibility;
|
||||||
joinOptions: EventJoinOptions;
|
joinOptions: EventJoinOptions;
|
||||||
draft: boolean;
|
draft: boolean;
|
||||||
picture?: IPicture | { pictureId: string } | null;
|
picture?: IMedia | { mediaId: string } | null;
|
||||||
attributedToId: string | null;
|
attributedToId: string | null;
|
||||||
onlineAddress?: string;
|
onlineAddress?: string;
|
||||||
phoneAddress?: string;
|
phoneAddress?: string;
|
||||||
@ -96,7 +96,7 @@ export interface IEvent {
|
|||||||
joinOptions: EventJoinOptions;
|
joinOptions: EventJoinOptions;
|
||||||
draft: boolean;
|
draft: boolean;
|
||||||
|
|
||||||
picture: IPicture | null;
|
picture: IMedia | null;
|
||||||
|
|
||||||
organizerActor?: IActor;
|
organizerActor?: IActor;
|
||||||
attributedTo?: IGroup;
|
attributedTo?: IGroup;
|
||||||
@ -142,7 +142,7 @@ export class EventModel implements IEvent {
|
|||||||
|
|
||||||
physicalAddress?: IAddress;
|
physicalAddress?: IAddress;
|
||||||
|
|
||||||
picture: IPicture | null = null;
|
picture: IMedia | null = null;
|
||||||
|
|
||||||
visibility = EventVisibility.PUBLIC;
|
visibility = EventVisibility.PUBLIC;
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
export interface IPicture {
|
export interface IMedia {
|
||||||
id: string;
|
id: string;
|
||||||
url: string;
|
url: string;
|
||||||
name: string;
|
name: string;
|
||||||
alt: string;
|
alt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IPictureUpload {
|
export interface IMediaUpload {
|
||||||
file: File;
|
file: File;
|
||||||
name: string;
|
name: string;
|
||||||
alt: string | null;
|
alt: string | null;
|
@ -1,5 +1,5 @@
|
|||||||
import { ITag } from "./tag.model";
|
import { ITag } from "./tag.model";
|
||||||
import { IPicture } from "./picture.model";
|
import { IMedia } from "./media.model";
|
||||||
import { IActor } from "./actor";
|
import { IActor } from "./actor";
|
||||||
|
|
||||||
export enum PostVisibility {
|
export enum PostVisibility {
|
||||||
@ -17,7 +17,7 @@ export interface IPost {
|
|||||||
title: string;
|
title: string;
|
||||||
body: string;
|
body: string;
|
||||||
tags?: ITag[];
|
tags?: ITag[];
|
||||||
picture?: IPicture | null;
|
picture?: IMedia | null;
|
||||||
draft: boolean;
|
draft: boolean;
|
||||||
visibility: PostVisibility;
|
visibility: PostVisibility;
|
||||||
author?: IActor;
|
author?: IActor;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { IPicture } from "@/types/picture.model";
|
import { IMedia } from "@/types/media.model";
|
||||||
|
|
||||||
export async function buildFileFromIPicture(obj: IPicture | null | undefined): Promise<File | null> {
|
export async function buildFileFromIMedia(obj: IMedia | null | undefined): Promise<File | null> {
|
||||||
if (!obj) return Promise.resolve(null);
|
if (!obj) return Promise.resolve(null);
|
||||||
|
|
||||||
const response = await fetch(obj.url);
|
const response = await fetch(obj.url);
|
||||||
@ -14,7 +14,7 @@ export function buildFileVariable(file: File | null, name: string, alt?: string)
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
[name]: {
|
[name]: {
|
||||||
picture: {
|
media: {
|
||||||
name: file.name,
|
name: file.name,
|
||||||
alt: alt || file.name,
|
alt: alt || file.name,
|
||||||
file,
|
file,
|
||||||
|
@ -124,7 +124,6 @@ h1 {
|
|||||||
<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 { IPicture } from "@/types/picture.model";
|
|
||||||
import {
|
import {
|
||||||
CREATE_PERSON,
|
CREATE_PERSON,
|
||||||
CURRENT_ACTOR_CLIENT,
|
CURRENT_ACTOR_CLIENT,
|
||||||
@ -137,7 +136,7 @@ import { IPerson, Person } from "../../../types/actor";
|
|||||||
import PictureUpload from "../../../components/PictureUpload.vue";
|
import PictureUpload from "../../../components/PictureUpload.vue";
|
||||||
import { MOBILIZON_INSTANCE_HOST } from "../../../api/_entrypoint";
|
import { MOBILIZON_INSTANCE_HOST } from "../../../api/_entrypoint";
|
||||||
import RouteName from "../../../router/name";
|
import RouteName from "../../../router/name";
|
||||||
import { buildFileFromIPicture, buildFileVariable } from "../../../utils/image";
|
import { buildFileVariable } from "../../../utils/image";
|
||||||
import { changeIdentity } from "../../../utils/auth";
|
import { changeIdentity } from "../../../utils/auth";
|
||||||
import identityEditionMixin from "../../../mixins/identityEdition";
|
import identityEditionMixin from "../../../mixins/identityEdition";
|
||||||
|
|
||||||
|
@ -377,7 +377,7 @@ import {
|
|||||||
import { IPerson, Person, displayNameAndUsername } from "../../types/actor";
|
import { IPerson, Person, displayNameAndUsername } from "../../types/actor";
|
||||||
import { TAGS } from "../../graphql/tags";
|
import { TAGS } from "../../graphql/tags";
|
||||||
import { ITag } from "../../types/tag.model";
|
import { ITag } from "../../types/tag.model";
|
||||||
import { buildFileFromIPicture, buildFileVariable, readFileAsync } from "../../utils/image";
|
import { buildFileFromIMedia, buildFileVariable, readFileAsync } from "../../utils/image";
|
||||||
import RouteName from "../../router/name";
|
import RouteName from "../../router/name";
|
||||||
import "intersection-observer";
|
import "intersection-observer";
|
||||||
import { CONFIG } from "../../graphql/config";
|
import { CONFIG } from "../../graphql/config";
|
||||||
@ -517,7 +517,7 @@ export default class EditEvent extends Vue {
|
|||||||
);
|
);
|
||||||
this.observer.observe(this.$refs.bottomObserver as Element);
|
this.observer.observe(this.$refs.bottomObserver as Element);
|
||||||
|
|
||||||
this.pictureFile = await buildFileFromIPicture(this.event.picture);
|
this.pictureFile = await buildFileFromIMedia(this.event.picture);
|
||||||
this.limitedPlaces = this.event.options.maximumAttendeeCapacity > 0;
|
this.limitedPlaces = this.event.options.maximumAttendeeCapacity > 0;
|
||||||
if (!(this.isUpdate || this.isDuplicate)) {
|
if (!(this.isUpdate || this.isDuplicate)) {
|
||||||
this.initializeEvent();
|
this.initializeEvent();
|
||||||
@ -775,11 +775,11 @@ export default class EditEvent extends Vue {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (this.event.picture && this.pictureFile) {
|
if (this.event.picture && this.pictureFile) {
|
||||||
const oldPictureFile = (await buildFileFromIPicture(this.event.picture)) as File;
|
const oldPictureFile = (await buildFileFromIMedia(this.event.picture)) as File;
|
||||||
const oldPictureFileContent = await readFileAsync(oldPictureFile);
|
const oldPictureFileContent = await readFileAsync(oldPictureFile);
|
||||||
const newPictureFileContent = await readFileAsync(this.pictureFile as File);
|
const newPictureFileContent = await readFileAsync(this.pictureFile as File);
|
||||||
if (oldPictureFileContent === newPictureFileContent) {
|
if (oldPictureFileContent === newPictureFileContent) {
|
||||||
res.picture = { pictureId: this.event.picture.id };
|
res.picture = { mediaId: this.event.picture.id };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -246,7 +246,7 @@ export default class GroupSettings extends mixins(GroupMixin) {
|
|||||||
if (this.avatarFile) {
|
if (this.avatarFile) {
|
||||||
avatarObj = {
|
avatarObj = {
|
||||||
avatar: {
|
avatar: {
|
||||||
picture: {
|
media: {
|
||||||
name: this.avatarFile.name,
|
name: this.avatarFile.name,
|
||||||
alt: `${this.group.preferredUsername}'s avatar`,
|
alt: `${this.group.preferredUsername}'s avatar`,
|
||||||
file: this.avatarFile,
|
file: this.avatarFile,
|
||||||
@ -258,7 +258,7 @@ export default class GroupSettings extends mixins(GroupMixin) {
|
|||||||
if (this.bannerFile) {
|
if (this.bannerFile) {
|
||||||
bannerObj = {
|
bannerObj = {
|
||||||
banner: {
|
banner: {
|
||||||
picture: {
|
media: {
|
||||||
name: this.bannerFile.name,
|
name: this.bannerFile.name,
|
||||||
alt: `${this.group.preferredUsername}'s banner`,
|
alt: `${this.group.preferredUsername}'s banner`,
|
||||||
file: this.bannerFile,
|
file: this.bannerFile,
|
||||||
|
@ -103,7 +103,7 @@
|
|||||||
import { Component, Prop } from "vue-property-decorator";
|
import { Component, Prop } from "vue-property-decorator";
|
||||||
import { mixins } from "vue-class-component";
|
import { mixins } from "vue-class-component";
|
||||||
import { FETCH_GROUP } from "@/graphql/group";
|
import { FETCH_GROUP } from "@/graphql/group";
|
||||||
import { buildFileFromIPicture, readFileAsync } from "@/utils/image";
|
import { buildFileFromIMedia, readFileAsync } from "@/utils/image";
|
||||||
import GroupMixin from "@/mixins/group";
|
import GroupMixin from "@/mixins/group";
|
||||||
import { TAGS } from "../../graphql/tags";
|
import { TAGS } from "../../graphql/tags";
|
||||||
import { CONFIG } from "../../graphql/config";
|
import { CONFIG } from "../../graphql/config";
|
||||||
@ -188,7 +188,7 @@ export default class EditPost extends mixins(GroupMixin) {
|
|||||||
errors: Record<string, unknown> = {};
|
errors: Record<string, unknown> = {};
|
||||||
|
|
||||||
async mounted(): Promise<void> {
|
async mounted(): Promise<void> {
|
||||||
this.pictureFile = await buildFileFromIPicture(this.post.picture);
|
this.pictureFile = await buildFileFromIMedia(this.post.picture);
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line consistent-return
|
// eslint-disable-next-line consistent-return
|
||||||
@ -277,11 +277,11 @@ export default class EditPost extends mixins(GroupMixin) {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (this.post.picture) {
|
if (this.post.picture) {
|
||||||
const oldPictureFile = (await buildFileFromIPicture(this.post.picture)) as File;
|
const oldPictureFile = (await buildFileFromIMedia(this.post.picture)) as File;
|
||||||
const oldPictureFileContent = await readFileAsync(oldPictureFile);
|
const oldPictureFileContent = await readFileAsync(oldPictureFile);
|
||||||
const newPictureFileContent = await readFileAsync(this.pictureFile as File);
|
const newPictureFileContent = await readFileAsync(this.pictureFile as File);
|
||||||
if (oldPictureFileContent === newPictureFileContent) {
|
if (oldPictureFileContent === newPictureFileContent) {
|
||||||
obj.picture = { pictureId: this.post.picture.id };
|
obj.picture = { mediaId: this.post.picture.id };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -10,7 +10,7 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
|
|||||||
|
|
||||||
alias Mobilizon.Actors
|
alias Mobilizon.Actors
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Media.Picture
|
alias Mobilizon.Medias.Media
|
||||||
|
|
||||||
alias Mobilizon.Federation.ActivityPub
|
alias Mobilizon.Federation.ActivityPub
|
||||||
alias Mobilizon.Federation.ActivityPub.{Activity, Federator, Relay}
|
alias Mobilizon.Federation.ActivityPub.{Activity, Federator, Relay}
|
||||||
@ -333,50 +333,50 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
|
|||||||
Return AS Link data from
|
Return AS Link data from
|
||||||
|
|
||||||
* a `Plug.Upload` struct, stored an returned
|
* a `Plug.Upload` struct, stored an returned
|
||||||
* a `Picture`, directly returned
|
* a `Media`, directly returned
|
||||||
* a map containing picture information, stored, saved and returned
|
* a map containing media information, stored, saved and returned
|
||||||
|
|
||||||
Save picture data from %Plug.Upload{} and return AS Link data.
|
Save media data from %Plug.Upload{} and return AS Link data.
|
||||||
"""
|
"""
|
||||||
def make_picture_data(%Plug.Upload{} = picture, opts) do
|
def make_media_data(%Plug.Upload{} = media, opts) do
|
||||||
case Mobilizon.Web.Upload.store(picture, opts) do
|
case Mobilizon.Web.Upload.store(media, opts) do
|
||||||
{:ok, picture} ->
|
{:ok, media} ->
|
||||||
picture
|
media
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_picture_data(%Picture{} = picture) do
|
def make_media_data(%Media{} = media) do
|
||||||
Converter.Picture.model_to_as(picture)
|
Converter.Media.model_to_as(media)
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_picture_data(picture) when is_map(picture) do
|
def make_media_data(media) when is_map(media) do
|
||||||
with {:ok, %{"url" => [%{"href" => url, "mediaType" => content_type}], "size" => size}} <-
|
with {:ok, %{"url" => [%{"href" => url, "mediaType" => content_type}], "size" => size}} <-
|
||||||
Mobilizon.Web.Upload.store(picture.file),
|
Mobilizon.Web.Upload.store(media.file),
|
||||||
{:picture_exists, nil} <- {:picture_exists, Mobilizon.Media.get_picture_by_url(url)},
|
{:media_exists, nil} <- {:media_exists, Mobilizon.Medias.get_media_by_url(url)},
|
||||||
{:ok, %Picture{file: _file} = picture} <-
|
{:ok, %Media{file: _file} = media} <-
|
||||||
Mobilizon.Media.create_picture(%{
|
Mobilizon.Medias.create_media(%{
|
||||||
"file" => %{
|
"file" => %{
|
||||||
"url" => url,
|
"url" => url,
|
||||||
"name" => picture.name,
|
"name" => media.name,
|
||||||
"content_type" => content_type,
|
"content_type" => content_type,
|
||||||
"size" => size
|
"size" => size
|
||||||
},
|
},
|
||||||
"actor_id" => picture.actor_id
|
"actor_id" => media.actor_id
|
||||||
}) do
|
}) do
|
||||||
Converter.Picture.model_to_as(picture)
|
Converter.Media.model_to_as(media)
|
||||||
else
|
else
|
||||||
{:picture_exists, %Picture{file: _file} = picture} ->
|
{:media_exists, %Media{file: _file} = media} ->
|
||||||
Converter.Picture.model_to_as(picture)
|
Converter.Media.model_to_as(media)
|
||||||
|
|
||||||
err ->
|
err ->
|
||||||
err
|
err
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def make_picture_data(nil), do: nil
|
def make_media_data(nil), do: nil
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Make announce activity data for the given actor and object
|
Make announce activity data for the given actor and object
|
||||||
|
@ -10,11 +10,11 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
|||||||
alias Mobilizon.Addresses
|
alias Mobilizon.Addresses
|
||||||
alias Mobilizon.Addresses.Address
|
alias Mobilizon.Addresses.Address
|
||||||
alias Mobilizon.Events.Event, as: EventModel
|
alias Mobilizon.Events.Event, as: EventModel
|
||||||
alias Mobilizon.Media.Picture
|
alias Mobilizon.Medias.Media
|
||||||
|
|
||||||
alias Mobilizon.Federation.ActivityStream.{Converter, Convertible}
|
alias Mobilizon.Federation.ActivityStream.{Converter, Convertible}
|
||||||
alias Mobilizon.Federation.ActivityStream.Converter.Address, as: AddressConverter
|
alias Mobilizon.Federation.ActivityStream.Converter.Address, as: AddressConverter
|
||||||
alias Mobilizon.Federation.ActivityStream.Converter.Picture, as: PictureConverter
|
alias Mobilizon.Federation.ActivityStream.Converter.Media, as: MediaConverter
|
||||||
|
|
||||||
import Mobilizon.Federation.ActivityStream.Converter.Utils,
|
import Mobilizon.Federation.ActivityStream.Converter.Utils,
|
||||||
only: [
|
only: [
|
||||||
@ -55,10 +55,10 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
|||||||
|
|
||||||
picture_id =
|
picture_id =
|
||||||
with true <- length(attachments) > 0,
|
with true <- length(attachments) > 0,
|
||||||
{:ok, %Picture{id: picture_id}} <-
|
{:ok, %Media{id: picture_id}} <-
|
||||||
attachments
|
attachments
|
||||||
|> hd()
|
|> hd()
|
||||||
|> PictureConverter.find_or_create_picture(actor_id) do
|
|> MediaConverter.find_or_create_media(actor_id) do
|
||||||
picture_id
|
picture_id
|
||||||
else
|
else
|
||||||
_err ->
|
_err ->
|
||||||
@ -239,7 +239,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Event do
|
|||||||
res,
|
res,
|
||||||
"attachment",
|
"attachment",
|
||||||
[],
|
[],
|
||||||
&(&1 ++ [PictureConverter.model_to_as(event.picture)])
|
&(&1 ++ [MediaConverter.model_to_as(event.picture)])
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
63
lib/federation/activity_stream/converter/media.ex
Normal file
63
lib/federation/activity_stream/converter/media.ex
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
defmodule Mobilizon.Federation.ActivityStream.Converter.Media do
|
||||||
|
@moduledoc """
|
||||||
|
Media converter.
|
||||||
|
|
||||||
|
This module allows to convert events from ActivityStream format to our own
|
||||||
|
internal one, and back.
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias Mobilizon.Medias
|
||||||
|
alias Mobilizon.Medias.Media, as: MediaModel
|
||||||
|
|
||||||
|
alias Mobilizon.Web.Upload
|
||||||
|
|
||||||
|
@http_options [
|
||||||
|
ssl: [{:versions, [:"tlsv1.2"]}]
|
||||||
|
]
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Convert a media struct to an ActivityStream representation.
|
||||||
|
"""
|
||||||
|
@spec model_to_as(MediaModel.t()) :: map
|
||||||
|
def model_to_as(%MediaModel{file: file}) do
|
||||||
|
%{
|
||||||
|
"type" => "Document",
|
||||||
|
"mediaType" => file.content_type,
|
||||||
|
"url" => file.url,
|
||||||
|
"name" => file.name
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Save media data from raw data and return AS Link data.
|
||||||
|
"""
|
||||||
|
def find_or_create_media(%{"type" => "Link", "href" => url}, actor_id),
|
||||||
|
do: find_or_create_media(url, actor_id)
|
||||||
|
|
||||||
|
def find_or_create_media(
|
||||||
|
%{"type" => "Document", "url" => media_url, "name" => name},
|
||||||
|
actor_id
|
||||||
|
)
|
||||||
|
when is_bitstring(media_url) do
|
||||||
|
with {:ok, %{body: body}} <- Tesla.get(media_url, opts: @http_options),
|
||||||
|
{:ok, %{name: name, url: url, content_type: content_type, size: size}} <-
|
||||||
|
Upload.store(%{body: body, name: name}),
|
||||||
|
{:media_exists, nil} <- {:media_exists, Medias.get_media_by_url(url)} do
|
||||||
|
Medias.create_media(%{
|
||||||
|
"file" => %{
|
||||||
|
"url" => url,
|
||||||
|
"name" => name,
|
||||||
|
"content_type" => content_type,
|
||||||
|
"size" => size
|
||||||
|
},
|
||||||
|
"actor_id" => actor_id
|
||||||
|
})
|
||||||
|
else
|
||||||
|
{:media_exists, %MediaModel{file: _file} = media} ->
|
||||||
|
{:ok, media}
|
||||||
|
|
||||||
|
err ->
|
||||||
|
err
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -1,63 +0,0 @@
|
|||||||
defmodule Mobilizon.Federation.ActivityStream.Converter.Picture do
|
|
||||||
@moduledoc """
|
|
||||||
Picture converter.
|
|
||||||
|
|
||||||
This module allows to convert events from ActivityStream format to our own
|
|
||||||
internal one, and back.
|
|
||||||
"""
|
|
||||||
|
|
||||||
alias Mobilizon.Media
|
|
||||||
alias Mobilizon.Media.Picture, as: PictureModel
|
|
||||||
|
|
||||||
alias Mobilizon.Web.Upload
|
|
||||||
|
|
||||||
@http_options [
|
|
||||||
ssl: [{:versions, [:"tlsv1.2"]}]
|
|
||||||
]
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Convert a picture struct to an ActivityStream representation.
|
|
||||||
"""
|
|
||||||
@spec model_to_as(PictureModel.t()) :: map
|
|
||||||
def model_to_as(%PictureModel{file: file}) do
|
|
||||||
%{
|
|
||||||
"type" => "Document",
|
|
||||||
"mediaType" => file.content_type,
|
|
||||||
"url" => file.url,
|
|
||||||
"name" => file.name
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Save picture data from raw data and return AS Link data.
|
|
||||||
"""
|
|
||||||
def find_or_create_picture(%{"type" => "Link", "href" => url}, actor_id),
|
|
||||||
do: find_or_create_picture(url, actor_id)
|
|
||||||
|
|
||||||
def find_or_create_picture(
|
|
||||||
%{"type" => "Document", "url" => picture_url, "name" => name},
|
|
||||||
actor_id
|
|
||||||
)
|
|
||||||
when is_bitstring(picture_url) do
|
|
||||||
with {:ok, %{body: body}} <- Tesla.get(picture_url, opts: @http_options),
|
|
||||||
{:ok, %{name: name, url: url, content_type: content_type, size: size}} <-
|
|
||||||
Upload.store(%{body: body, name: name}),
|
|
||||||
{:picture_exists, nil} <- {:picture_exists, Media.get_picture_by_url(url)} do
|
|
||||||
Media.create_picture(%{
|
|
||||||
"file" => %{
|
|
||||||
"url" => url,
|
|
||||||
"name" => name,
|
|
||||||
"content_type" => content_type,
|
|
||||||
"size" => size
|
|
||||||
},
|
|
||||||
"actor_id" => actor_id
|
|
||||||
})
|
|
||||||
else
|
|
||||||
{:picture_exists, %PictureModel{file: _file} = picture} ->
|
|
||||||
{:ok, picture}
|
|
||||||
|
|
||||||
err ->
|
|
||||||
err
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,34 +1,59 @@
|
|||||||
defmodule Mobilizon.GraphQL.API.Comments do
|
defmodule Mobilizon.GraphQL.API.Comments do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
API for Comments.
|
API for discussions and comments.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Discussions.Comment
|
alias Mobilizon.Discussions.Comment
|
||||||
alias Mobilizon.Federation.ActivityPub
|
alias Mobilizon.Federation.ActivityPub
|
||||||
alias Mobilizon.Federation.ActivityPub.Activity
|
alias Mobilizon.Federation.ActivityPub.Activity
|
||||||
|
alias Mobilizon.GraphQL.API.Utils
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Create a comment
|
Create a comment
|
||||||
|
|
||||||
Creates a comment from an actor
|
|
||||||
"""
|
"""
|
||||||
@spec create_comment(map) :: {:ok, Activity.t(), Comment.t()} | any
|
@spec create_comment(map) :: {:ok, Activity.t(), Comment.t()} | any
|
||||||
def create_comment(args) do
|
def create_comment(args) do
|
||||||
|
args = extract_pictures_from_comment_body(args)
|
||||||
ActivityPub.create(:comment, args, true)
|
ActivityPub.create(:comment, args, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Updates a comment
|
||||||
|
"""
|
||||||
|
@spec update_comment(Comment.t(), map()) :: {:ok, Activity.t(), Comment.t()} | any
|
||||||
def update_comment(%Comment{} = comment, args) do
|
def update_comment(%Comment{} = comment, args) do
|
||||||
|
args = extract_pictures_from_comment_body(args)
|
||||||
ActivityPub.update(comment, args, true)
|
ActivityPub.update(comment, args, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Deletes a comment
|
Deletes a comment
|
||||||
|
|
||||||
Deletes a comment from an actor
|
|
||||||
"""
|
"""
|
||||||
@spec delete_comment(Comment.t(), Actor.t()) :: {:ok, Activity.t(), Comment.t()} | any
|
@spec delete_comment(Comment.t(), Actor.t()) :: {:ok, Activity.t(), Comment.t()} | any
|
||||||
def delete_comment(%Comment{} = comment, %Actor{} = actor) do
|
def delete_comment(%Comment{} = comment, %Actor{} = actor) do
|
||||||
ActivityPub.delete(comment, actor, true)
|
ActivityPub.delete(comment, actor, true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Creates a discussion (or reply to a discussion)
|
||||||
|
"""
|
||||||
|
@spec create_discussion(map()) :: map()
|
||||||
|
def create_discussion(args) do
|
||||||
|
args = extract_pictures_from_comment_body(args)
|
||||||
|
|
||||||
|
ActivityPub.create(
|
||||||
|
:discussion,
|
||||||
|
args,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec extract_pictures_from_comment_body(map()) :: map()
|
||||||
|
defp extract_pictures_from_comment_body(%{text: text, actor_id: actor_id} = args) do
|
||||||
|
pictures = Utils.extract_pictures_from_body(text, actor_id)
|
||||||
|
Map.put(args, :media, pictures)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp extract_pictures_from_comment_body(args), do: args
|
||||||
end
|
end
|
||||||
|
@ -8,6 +8,7 @@ defmodule Mobilizon.GraphQL.API.Events do
|
|||||||
|
|
||||||
alias Mobilizon.Federation.ActivityPub
|
alias Mobilizon.Federation.ActivityPub
|
||||||
alias Mobilizon.Federation.ActivityPub.{Activity, Utils}
|
alias Mobilizon.Federation.ActivityPub.{Activity, Utils}
|
||||||
|
alias Mobilizon.GraphQL.API.Utils, as: APIUtils
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Create an event
|
Create an event
|
||||||
@ -15,6 +16,7 @@ defmodule Mobilizon.GraphQL.API.Events do
|
|||||||
@spec create_event(map) :: {:ok, Activity.t(), Event.t()} | any
|
@spec create_event(map) :: {:ok, Activity.t(), Event.t()} | any
|
||||||
def create_event(args) do
|
def create_event(args) do
|
||||||
with organizer_actor <- Map.get(args, :organizer_actor),
|
with organizer_actor <- Map.get(args, :organizer_actor),
|
||||||
|
args <- extract_pictures_from_event_body(args, organizer_actor),
|
||||||
args <-
|
args <-
|
||||||
Map.update(args, :picture, nil, fn picture ->
|
Map.update(args, :picture, nil, fn picture ->
|
||||||
process_picture(picture, organizer_actor)
|
process_picture(picture, organizer_actor)
|
||||||
@ -30,6 +32,7 @@ defmodule Mobilizon.GraphQL.API.Events do
|
|||||||
@spec update_event(map, Event.t()) :: {:ok, Activity.t(), Event.t()} | any
|
@spec update_event(map, Event.t()) :: {:ok, Activity.t(), Event.t()} | any
|
||||||
def update_event(args, %Event{} = event) do
|
def update_event(args, %Event{} = event) do
|
||||||
with organizer_actor <- Map.get(args, :organizer_actor),
|
with organizer_actor <- Map.get(args, :organizer_actor),
|
||||||
|
args <- extract_pictures_from_event_body(args, organizer_actor),
|
||||||
args <-
|
args <-
|
||||||
Map.update(args, :picture, nil, fn picture ->
|
Map.update(args, :picture, nil, fn picture ->
|
||||||
process_picture(picture, organizer_actor)
|
process_picture(picture, organizer_actor)
|
||||||
@ -40,23 +43,32 @@ defmodule Mobilizon.GraphQL.API.Events do
|
|||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Trigger the deletion of an event
|
Trigger the deletion of an event
|
||||||
|
|
||||||
If the event is deleted by
|
|
||||||
"""
|
"""
|
||||||
def delete_event(%Event{} = event, %Actor{} = actor, federate \\ true) do
|
def delete_event(%Event{} = event, %Actor{} = actor, federate \\ true) do
|
||||||
ActivityPub.delete(event, actor, federate)
|
ActivityPub.delete(event, actor, federate)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp process_picture(nil, _), do: nil
|
defp process_picture(nil, _), do: nil
|
||||||
defp process_picture(%{picture_id: _picture_id} = args, _), do: args
|
defp process_picture(%{media_id: _picture_id} = args, _), do: args
|
||||||
|
|
||||||
defp process_picture(%{picture: picture}, %Actor{id: actor_id}) do
|
defp process_picture(%{media: media}, %Actor{id: actor_id}) do
|
||||||
%{
|
%{
|
||||||
file:
|
file:
|
||||||
picture
|
media
|
||||||
|> Map.get(:file)
|
|> Map.get(:file)
|
||||||
|> Utils.make_picture_data(description: Map.get(picture, :name)),
|
|> Utils.make_media_data(description: Map.get(media, :name)),
|
||||||
actor_id: actor_id
|
actor_id: actor_id
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec extract_pictures_from_event_body(map(), Actor.t()) :: map()
|
||||||
|
defp extract_pictures_from_event_body(
|
||||||
|
%{description: description} = args,
|
||||||
|
%Actor{id: organizer_actor_id}
|
||||||
|
) do
|
||||||
|
pictures = APIUtils.extract_pictures_from_body(description, organizer_actor_id)
|
||||||
|
Map.put(args, :media, pictures)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp extract_pictures_from_event_body(args, _), do: args
|
||||||
end
|
end
|
||||||
|
@ -3,7 +3,8 @@ defmodule Mobilizon.GraphQL.API.Utils do
|
|||||||
Utils for API.
|
Utils for API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
alias Mobilizon.Config
|
alias Mobilizon.{Config, Medias}
|
||||||
|
alias Mobilizon.Medias.Media
|
||||||
alias Mobilizon.Service.Formatter
|
alias Mobilizon.Service.Formatter
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
@ -40,4 +41,41 @@ defmodule Mobilizon.GraphQL.API.Utils do
|
|||||||
{:error, "Comment must be up to #{max_size} characters"}
|
{:error, "Comment must be up to #{max_size} characters"}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Use the data-media-id attributes to extract media from body text
|
||||||
|
"""
|
||||||
|
@spec extract_pictures_from_body(String.t(), integer() | String.t()) :: list(Media.t())
|
||||||
|
def extract_pictures_from_body(body, actor_id) do
|
||||||
|
body
|
||||||
|
|> do_extract_pictures_from_body()
|
||||||
|
|> Enum.map(&fetch_picture(&1, actor_id))
|
||||||
|
|> Enum.filter(& &1)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec do_extract_pictures_from_body(String.t()) :: list(String.t())
|
||||||
|
defp do_extract_pictures_from_body(body) when is_nil(body) or body == "", do: []
|
||||||
|
|
||||||
|
defp do_extract_pictures_from_body(body) do
|
||||||
|
{:ok, document} = Floki.parse_document(body)
|
||||||
|
|
||||||
|
document
|
||||||
|
|> Floki.attribute("img", "data-media-id")
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec fetch_picture(String.t() | integer(), String.t() | integer()) :: Media.t() | nil
|
||||||
|
defp fetch_picture(id, actor_id) do
|
||||||
|
with %Media{actor_id: media_actor_id} = media <- Medias.get_media(id),
|
||||||
|
{:owns_media, true} <-
|
||||||
|
{:owns_media, check_actor_owns_media?(actor_id, media_actor_id)} do
|
||||||
|
media
|
||||||
|
else
|
||||||
|
_ -> nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec check_actor_owns_media?(integer() | String.t(), integer() | String.t()) :: boolean()
|
||||||
|
defp check_actor_owns_media?(actor_id, media_actor_id) do
|
||||||
|
actor_id == media_actor_id || Mobilizon.Actors.is_member?(media_actor_id, actor_id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
@ -7,6 +7,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
|
|||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Discussions.{Comment, Discussion}
|
alias Mobilizon.Discussions.{Comment, Discussion}
|
||||||
alias Mobilizon.Federation.ActivityPub
|
alias Mobilizon.Federation.ActivityPub
|
||||||
|
alias Mobilizon.GraphQL.API.Comments
|
||||||
alias Mobilizon.Storage.Page
|
alias Mobilizon.Storage.Page
|
||||||
alias Mobilizon.Users.User
|
alias Mobilizon.Users.User
|
||||||
import Mobilizon.Web.Gettext
|
import Mobilizon.Web.Gettext
|
||||||
@ -94,17 +95,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
|
|||||||
with {:actor, %Actor{id: creator_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
|
with {:actor, %Actor{id: creator_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
|
||||||
{:member, true} <- {:member, Actors.is_member?(creator_id, group_id)},
|
{:member, true} <- {:member, Actors.is_member?(creator_id, group_id)},
|
||||||
{:ok, _activity, %Discussion{} = discussion} <-
|
{:ok, _activity, %Discussion{} = discussion} <-
|
||||||
ActivityPub.create(
|
Comments.create_discussion(%{
|
||||||
:discussion,
|
|
||||||
%{
|
|
||||||
title: title,
|
title: title,
|
||||||
text: text,
|
text: text,
|
||||||
actor_id: group_id,
|
actor_id: group_id,
|
||||||
creator_id: creator_id,
|
creator_id: creator_id,
|
||||||
attributed_to_id: group_id
|
attributed_to_id: group_id
|
||||||
},
|
}) do
|
||||||
true
|
|
||||||
) do
|
|
||||||
{:ok, discussion}
|
{:ok, discussion}
|
||||||
else
|
else
|
||||||
{:member, false} ->
|
{:member, false} ->
|
||||||
@ -134,9 +131,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
|
|||||||
{:no_discussion, Discussions.get_discussion(discussion_id)},
|
{:no_discussion, Discussions.get_discussion(discussion_id)},
|
||||||
{:member, true} <- {:member, Actors.is_member?(creator_id, actor_id)},
|
{:member, true} <- {:member, Actors.is_member?(creator_id, actor_id)},
|
||||||
{:ok, _activity, %Discussion{} = discussion} <-
|
{:ok, _activity, %Discussion{} = discussion} <-
|
||||||
ActivityPub.create(
|
Comments.create_discussion(%{
|
||||||
:discussion,
|
|
||||||
%{
|
|
||||||
text: text,
|
text: text,
|
||||||
discussion_id: discussion_id,
|
discussion_id: discussion_id,
|
||||||
actor_id: creator_id,
|
actor_id: creator_id,
|
||||||
@ -144,9 +139,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
|
|||||||
in_reply_to_comment_id: last_comment_id,
|
in_reply_to_comment_id: last_comment_id,
|
||||||
origin_comment_id:
|
origin_comment_id:
|
||||||
origin_comment_id || previous_in_reply_to_comment_id || last_comment_id
|
origin_comment_id || previous_in_reply_to_comment_id || last_comment_id
|
||||||
},
|
}) do
|
||||||
true
|
|
||||||
) do
|
|
||||||
{:ok, discussion}
|
{:ok, discussion}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -96,8 +96,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
|
|||||||
# TODO Move me to somewhere cleaner
|
# TODO Move me to somewhere cleaner
|
||||||
defp save_attached_pictures(args) do
|
defp save_attached_pictures(args) do
|
||||||
Enum.reduce([:avatar, :banner], args, fn key, args ->
|
Enum.reduce([:avatar, :banner], args, fn key, args ->
|
||||||
if Map.has_key?(args, key) && !is_nil(args[key][:picture]) do
|
if Map.has_key?(args, key) && !is_nil(args[key][:media]) do
|
||||||
pic = args[key][:picture]
|
pic = args[key][:media]
|
||||||
|
|
||||||
with {:ok, %{name: name, url: url, content_type: content_type, size: _size}} <-
|
with {:ok, %{name: name, url: url, content_type: content_type, size: _size}} <-
|
||||||
Upload.store(pic.file, type: key, description: pic.alt) do
|
Upload.store(pic.file, type: key, description: pic.alt) do
|
||||||
|
@ -1,50 +1,47 @@
|
|||||||
defmodule Mobilizon.GraphQL.Resolvers.Picture do
|
defmodule Mobilizon.GraphQL.Resolvers.Media do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Handles the picture-related GraphQL calls
|
Handles the media-related GraphQL calls
|
||||||
"""
|
"""
|
||||||
|
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.{Media, Users}
|
alias Mobilizon.{Medias, Users}
|
||||||
alias Mobilizon.Media.Picture
|
alias Mobilizon.Medias.Media
|
||||||
alias Mobilizon.Users.User
|
alias Mobilizon.Users.User
|
||||||
import Mobilizon.Web.Gettext
|
import Mobilizon.Web.Gettext
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Get picture for an event
|
Get media for an event
|
||||||
|
|
||||||
See Mobilizon.Web.Resolvers.Event.create_event/3
|
See Mobilizon.Web.Resolvers.Event.create_event/3
|
||||||
"""
|
"""
|
||||||
def picture(%{picture_id: picture_id} = _parent, _args, _resolution) do
|
def media(%{picture_id: media_id} = _parent, _args, _resolution) do
|
||||||
with {:ok, picture} <- do_fetch_picture(picture_id), do: {:ok, picture}
|
with {:ok, media} <- do_fetch_media(media_id), do: {:ok, media}
|
||||||
end
|
end
|
||||||
|
|
||||||
def picture(%{picture: picture} = _parent, _args, _resolution), do: {:ok, picture}
|
def media(%{picture: media} = _parent, _args, _resolution), do: {:ok, media}
|
||||||
def picture(_parent, %{id: picture_id}, _resolution), do: do_fetch_picture(picture_id)
|
def media(_parent, %{id: media_id}, _resolution), do: do_fetch_media(media_id)
|
||||||
def picture(_parent, _args, _resolution), do: {:ok, nil}
|
def media(_parent, _args, _resolution), do: {:ok, nil}
|
||||||
|
|
||||||
@spec do_fetch_picture(nil) :: {:error, nil}
|
def medias(%{media: medias}, _args, _resolution) do
|
||||||
defp do_fetch_picture(nil), do: {:error, nil}
|
{:ok, Enum.map(medias, &transform_media/1)}
|
||||||
|
end
|
||||||
|
|
||||||
@spec do_fetch_picture(String.t()) :: {:ok, Picture.t()} | {:error, :not_found}
|
@spec do_fetch_media(nil) :: {:error, nil}
|
||||||
defp do_fetch_picture(picture_id) do
|
defp do_fetch_media(nil), do: {:error, nil}
|
||||||
case Media.get_picture(picture_id) do
|
|
||||||
%Picture{id: id, file: file} ->
|
@spec do_fetch_media(String.t()) :: {:ok, Media.t()} | {:error, :not_found}
|
||||||
{:ok,
|
defp do_fetch_media(media_id) do
|
||||||
%{
|
case Medias.get_media(media_id) do
|
||||||
name: file.name,
|
%Media{} = media ->
|
||||||
url: file.url,
|
{:ok, transform_media(media)}
|
||||||
id: id,
|
|
||||||
content_type: file.content_type,
|
|
||||||
size: file.size
|
|
||||||
}}
|
|
||||||
|
|
||||||
nil ->
|
nil ->
|
||||||
{:error, :not_found}
|
{:error, :not_found}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec upload_picture(map, map, map) :: {:ok, Picture.t()} | {:error, any}
|
@spec upload_media(map, map, map) :: {:ok, Media.t()} | {:error, any}
|
||||||
def upload_picture(
|
def upload_media(
|
||||||
_parent,
|
_parent,
|
||||||
%{file: %Plug.Upload{} = file} = args,
|
%{file: %Plug.Upload{} = file} = args,
|
||||||
%{context: %{current_user: %User{} = user}}
|
%{context: %{current_user: %User{} = user}}
|
||||||
@ -57,16 +54,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Picture do
|
|||||||
|> Map.put(:url, url)
|
|> Map.put(:url, url)
|
||||||
|> Map.put(:size, size)
|
|> Map.put(:size, size)
|
||||||
|> Map.put(:content_type, content_type),
|
|> Map.put(:content_type, content_type),
|
||||||
{:ok, picture = %Picture{}} <-
|
{:ok, media = %Media{}} <-
|
||||||
Media.create_picture(%{"file" => args, "actor_id" => actor_id}) do
|
Medias.create_media(%{"file" => args, "actor_id" => actor_id}) do
|
||||||
{:ok,
|
{:ok, transform_media(media)}
|
||||||
%{
|
|
||||||
name: picture.file.name,
|
|
||||||
url: picture.file.url,
|
|
||||||
id: picture.id,
|
|
||||||
content_type: picture.file.content_type,
|
|
||||||
size: picture.file.size
|
|
||||||
}}
|
|
||||||
else
|
else
|
||||||
{:error, :mime_type_not_allowed} ->
|
{:error, :mime_type_not_allowed} ->
|
||||||
{:error, dgettext("errors", "File doesn't have an allowed MIME type.")}
|
{:error, dgettext("errors", "File doesn't have an allowed MIME type.")}
|
||||||
@ -76,28 +66,28 @@ defmodule Mobilizon.GraphQL.Resolvers.Picture do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def upload_picture(_parent, _args, _resolution), do: {:error, :unauthenticated}
|
def upload_media(_parent, _args, _resolution), do: {:error, :unauthenticated}
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Remove a picture that the user owns
|
Remove a media that the user owns
|
||||||
"""
|
"""
|
||||||
@spec remove_picture(map(), map(), map()) ::
|
@spec remove_media(map(), map(), map()) ::
|
||||||
{:ok, Picture.t()}
|
{:ok, Media.t()}
|
||||||
| {:error, :unauthorized}
|
| {:error, :unauthorized}
|
||||||
| {:error, :unauthenticated}
|
| {:error, :unauthenticated}
|
||||||
| {:error, :not_found}
|
| {:error, :not_found}
|
||||||
def remove_picture(_parent, %{id: picture_id}, %{context: %{current_user: %User{} = user}}) do
|
def remove_media(_parent, %{id: media_id}, %{context: %{current_user: %User{} = user}}) do
|
||||||
with {:picture, %Picture{actor_id: actor_id} = picture} <-
|
with {:media, %Media{actor_id: actor_id} = media} <-
|
||||||
{:picture, Media.get_picture(picture_id)},
|
{:media, Medias.get_media(media_id)},
|
||||||
{:is_owned, %Actor{} = _actor} <- User.owns_actor(user, actor_id) do
|
{:is_owned, %Actor{} = _actor} <- User.owns_actor(user, actor_id) do
|
||||||
Media.delete_picture(picture)
|
Medias.delete_media(media)
|
||||||
else
|
else
|
||||||
{:picture, nil} -> {:error, :not_found}
|
{:media, nil} -> {:error, :not_found}
|
||||||
{:is_owned, _} -> {:error, :unauthorized}
|
{:is_owned, _} -> {:error, :unauthorized}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_picture(_parent, _args, _resolution), do: {:error, :unauthenticated}
|
def remove_media(_parent, _args, _resolution), do: {:error, :unauthenticated}
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Return the total media size for an actor
|
Return the total media size for an actor
|
||||||
@ -108,7 +98,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Picture do
|
|||||||
context: %{current_user: %User{} = user}
|
context: %{current_user: %User{} = user}
|
||||||
}) do
|
}) do
|
||||||
if can_get_actor_size?(user, actor_id) do
|
if can_get_actor_size?(user, actor_id) do
|
||||||
{:ok, Media.media_size_for_actor(actor_id)}
|
{:ok, Medias.media_size_for_actor(actor_id)}
|
||||||
else
|
else
|
||||||
{:error, :unauthorized}
|
{:error, :unauthorized}
|
||||||
end
|
end
|
||||||
@ -125,7 +115,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Picture do
|
|||||||
context: %{current_user: %User{} = logged_user}
|
context: %{current_user: %User{} = logged_user}
|
||||||
}) do
|
}) do
|
||||||
if can_get_user_size?(logged_user, user_id) do
|
if can_get_user_size?(logged_user, user_id) do
|
||||||
{:ok, Media.media_size_for_user(user_id)}
|
{:ok, Medias.media_size_for_user(user_id)}
|
||||||
else
|
else
|
||||||
{:error, :unauthorized}
|
{:error, :unauthorized}
|
||||||
end
|
end
|
||||||
@ -133,6 +123,17 @@ defmodule Mobilizon.GraphQL.Resolvers.Picture do
|
|||||||
|
|
||||||
def user_size(_parent, _args, _resolution), do: {:error, :unauthenticated}
|
def user_size(_parent, _args, _resolution), do: {:error, :unauthenticated}
|
||||||
|
|
||||||
|
@spec transform_media(Media.t()) :: map()
|
||||||
|
defp transform_media(%Media{id: id, file: file}) do
|
||||||
|
%{
|
||||||
|
name: file.name,
|
||||||
|
url: file.url,
|
||||||
|
id: id,
|
||||||
|
content_type: file.content_type,
|
||||||
|
size: file.size
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
@spec can_get_user_size?(User.t(), integer()) :: boolean()
|
@spec can_get_user_size?(User.t(), integer()) :: boolean()
|
||||||
defp can_get_actor_size?(%User{role: role} = user, actor_id) do
|
defp can_get_actor_size?(%User{role: role} = user, actor_id) do
|
||||||
role in [:moderator, :administrator] || owns_actor?(User.owns_actor(user, actor_id))
|
role in [:moderator, :administrator] || owns_actor?(User.owns_actor(user, actor_id))
|
@ -13,6 +13,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
|
|||||||
import Mobilizon.Web.Gettext
|
import Mobilizon.Web.Gettext
|
||||||
|
|
||||||
alias Mobilizon.Federation.ActivityPub
|
alias Mobilizon.Federation.ActivityPub
|
||||||
|
require Logger
|
||||||
|
|
||||||
alias Mobilizon.Web.{MediaProxy, Upload}
|
alias Mobilizon.Web.{MediaProxy, Upload}
|
||||||
|
|
||||||
@ -137,6 +138,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
|
|||||||
%{id: id} = args,
|
%{id: id} = args,
|
||||||
%{context: %{current_user: user}} = _resolution
|
%{context: %{current_user: user}} = _resolution
|
||||||
) do
|
) do
|
||||||
|
require Logger
|
||||||
args = Map.put(args, :user_id, user.id)
|
args = Map.put(args, :user_id, user.id)
|
||||||
|
|
||||||
with {:find_actor, %Actor{} = actor} <-
|
with {:find_actor, %Actor{} = actor} <-
|
||||||
@ -198,11 +200,11 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
|
|||||||
|
|
||||||
defp save_attached_pictures(args) do
|
defp save_attached_pictures(args) do
|
||||||
Enum.reduce([:avatar, :banner], args, fn key, args ->
|
Enum.reduce([:avatar, :banner], args, fn key, args ->
|
||||||
if Map.has_key?(args, key) && !is_nil(args[key][:picture]) do
|
if Map.has_key?(args, key) && !is_nil(args[key][:media]) do
|
||||||
pic = args[key][:picture]
|
media = args[key][:media]
|
||||||
|
|
||||||
with {:ok, %{name: name, url: url, content_type: content_type, size: _size}} <-
|
with {:ok, %{name: name, url: url, content_type: content_type, size: _size}} <-
|
||||||
Upload.store(pic.file, type: key, description: pic.alt) do
|
Upload.store(media.file, type: key, description: media.alt) do
|
||||||
Map.put(args, key, %{"name" => name, "url" => url, "mediaType" => content_type})
|
Map.put(args, key, %{"name" => name, "url" => url, "mediaType" => content_type})
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
@ -116,6 +116,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
|
|||||||
Map.update(args, :picture, nil, fn picture ->
|
Map.update(args, :picture, nil, fn picture ->
|
||||||
process_picture(picture, group)
|
process_picture(picture, group)
|
||||||
end),
|
end),
|
||||||
|
args <- extract_pictures_from_post_body(args, actor_id),
|
||||||
{:ok, _, %Post{} = post} <-
|
{:ok, _, %Post{} = post} <-
|
||||||
ActivityPub.create(
|
ActivityPub.create(
|
||||||
:post,
|
:post,
|
||||||
@ -156,6 +157,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
|
|||||||
Map.update(args, :picture, nil, fn picture ->
|
Map.update(args, :picture, nil, fn picture ->
|
||||||
process_picture(picture, group)
|
process_picture(picture, group)
|
||||||
end),
|
end),
|
||||||
|
args <- extract_pictures_from_post_body(args, actor_id),
|
||||||
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
|
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
|
||||||
{:ok, _, %Post{} = post} <-
|
{:ok, _, %Post{} = post} <-
|
||||||
ActivityPub.update(post, args, true, %{"actor" => actor_url}) do
|
ActivityPub.update(post, args, true, %{"actor" => actor_url}) do
|
||||||
@ -210,15 +212,23 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
|
|||||||
end
|
end
|
||||||
|
|
||||||
defp process_picture(nil, _), do: nil
|
defp process_picture(nil, _), do: nil
|
||||||
defp process_picture(%{picture_id: _picture_id} = args, _), do: args
|
defp process_picture(%{media_id: _picture_id} = args, _), do: args
|
||||||
|
|
||||||
defp process_picture(%{picture: picture}, %Actor{id: actor_id}) do
|
defp process_picture(%{media: media}, %Actor{id: actor_id}) do
|
||||||
%{
|
%{
|
||||||
file:
|
file:
|
||||||
picture
|
media
|
||||||
|> Map.get(:file)
|
|> Map.get(:file)
|
||||||
|> Utils.make_picture_data(description: Map.get(picture, :name)),
|
|> Utils.make_media_data(description: Map.get(media, :name)),
|
||||||
actor_id: actor_id
|
actor_id: actor_id
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec extract_pictures_from_post_body(map(), String.t()) :: map()
|
||||||
|
defp extract_pictures_from_post_body(%{body: body} = args, actor_id) do
|
||||||
|
pictures = Mobilizon.GraphQL.API.Utils.extract_pictures_from_body(body, actor_id)
|
||||||
|
Map.put(args, :media, pictures)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp extract_pictures_from_post_body(args, _actor_id), do: args
|
||||||
end
|
end
|
||||||
|
@ -529,7 +529,7 @@ defmodule Mobilizon.GraphQL.Resolvers.User do
|
|||||||
context: %{current_user: %User{id: logged_in_user_id}}
|
context: %{current_user: %User{id: logged_in_user_id}}
|
||||||
})
|
})
|
||||||
when user_id == logged_in_user_id do
|
when user_id == logged_in_user_id do
|
||||||
%{elements: elements, total: total} = Mobilizon.Media.pictures_for_user(user_id, page, limit)
|
%{elements: elements, total: total} = Mobilizon.Medias.medias_for_user(user_id, page, limit)
|
||||||
|
|
||||||
{:ok,
|
{:ok,
|
||||||
%{
|
%{
|
||||||
|
@ -30,7 +30,7 @@ defmodule Mobilizon.GraphQL.Schema do
|
|||||||
import_types(Schema.Custom.Point)
|
import_types(Schema.Custom.Point)
|
||||||
|
|
||||||
import_types(Schema.UserType)
|
import_types(Schema.UserType)
|
||||||
import_types(Schema.PictureType)
|
import_types(Schema.MediaType)
|
||||||
import_types(Schema.ActorInterface)
|
import_types(Schema.ActorInterface)
|
||||||
import_types(Schema.Actors.PersonType)
|
import_types(Schema.Actors.PersonType)
|
||||||
import_types(Schema.Actors.GroupType)
|
import_types(Schema.Actors.GroupType)
|
||||||
@ -145,7 +145,7 @@ defmodule Mobilizon.GraphQL.Schema do
|
|||||||
import_fields(:tag_queries)
|
import_fields(:tag_queries)
|
||||||
import_fields(:address_queries)
|
import_fields(:address_queries)
|
||||||
import_fields(:config_queries)
|
import_fields(:config_queries)
|
||||||
import_fields(:picture_queries)
|
import_fields(:media_queries)
|
||||||
import_fields(:report_queries)
|
import_fields(:report_queries)
|
||||||
import_fields(:admin_queries)
|
import_fields(:admin_queries)
|
||||||
import_fields(:todo_list_queries)
|
import_fields(:todo_list_queries)
|
||||||
@ -168,7 +168,7 @@ defmodule Mobilizon.GraphQL.Schema do
|
|||||||
import_fields(:participant_mutations)
|
import_fields(:participant_mutations)
|
||||||
import_fields(:member_mutations)
|
import_fields(:member_mutations)
|
||||||
import_fields(:feed_token_mutations)
|
import_fields(:feed_token_mutations)
|
||||||
import_fields(:picture_mutations)
|
import_fields(:media_mutations)
|
||||||
import_fields(:report_mutations)
|
import_fields(:report_mutations)
|
||||||
import_fields(:admin_mutations)
|
import_fields(:admin_mutations)
|
||||||
import_fields(:todo_list_mutations)
|
import_fields(:todo_list_mutations)
|
||||||
|
@ -28,8 +28,8 @@ defmodule Mobilizon.GraphQL.Schema.ActorInterface do
|
|||||||
|
|
||||||
field(:suspended, :boolean, description: "If the actor is suspended")
|
field(:suspended, :boolean, description: "If the actor is suspended")
|
||||||
|
|
||||||
field(:avatar, :picture, description: "The actor's avatar picture")
|
field(:avatar, :media, description: "The actor's avatar media")
|
||||||
field(:banner, :picture, description: "The actor's banner picture")
|
field(:banner, :media, description: "The actor's banner media")
|
||||||
|
|
||||||
# These one should have a privacy setting
|
# These one should have a privacy setting
|
||||||
field(:following, list_of(:follower), description: "List of followings")
|
field(:following, list_of(:follower), description: "List of followings")
|
||||||
|
@ -3,7 +3,7 @@ defmodule Mobilizon.GraphQL.Schema.Actors.ApplicationType do
|
|||||||
Schema representation for Group.
|
Schema representation for Group.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
alias Mobilizon.GraphQL.Resolvers.Picture
|
alias Mobilizon.GraphQL.Resolvers.Media
|
||||||
use Absinthe.Schema.Notation
|
use Absinthe.Schema.Notation
|
||||||
|
|
||||||
@desc """
|
@desc """
|
||||||
@ -27,8 +27,8 @@ defmodule Mobilizon.GraphQL.Schema.Actors.ApplicationType do
|
|||||||
|
|
||||||
field(:suspended, :boolean, description: "If the actor is suspended")
|
field(:suspended, :boolean, description: "If the actor is suspended")
|
||||||
|
|
||||||
field(:avatar, :picture, description: "The actor's avatar picture")
|
field(:avatar, :media, description: "The actor's avatar media")
|
||||||
field(:banner, :picture, description: "The actor's banner picture")
|
field(:banner, :media, description: "The actor's banner media")
|
||||||
|
|
||||||
# These one should have a privacy setting
|
# These one should have a privacy setting
|
||||||
field(:following, list_of(:follower), description: "List of followings")
|
field(:following, list_of(:follower), description: "List of followings")
|
||||||
@ -37,7 +37,7 @@ defmodule Mobilizon.GraphQL.Schema.Actors.ApplicationType do
|
|||||||
field(:followingCount, :integer, description: "Number of actors following this actor")
|
field(:followingCount, :integer, description: "Number of actors following this actor")
|
||||||
|
|
||||||
field(:media_size, :integer,
|
field(:media_size, :integer,
|
||||||
resolve: &Picture.actor_size/3,
|
resolve: &Media.actor_size/3,
|
||||||
description: "The total size of the media from this actor"
|
description: "The total size of the media from this actor"
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
@ -8,7 +8,7 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
|
|||||||
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
|
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
|
||||||
|
|
||||||
alias Mobilizon.Addresses
|
alias Mobilizon.Addresses
|
||||||
alias Mobilizon.GraphQL.Resolvers.{Discussion, Group, Member, Picture, Post, Resource, Todos}
|
alias Mobilizon.GraphQL.Resolvers.{Discussion, Group, Media, Member, Post, Resource, Todos}
|
||||||
alias Mobilizon.GraphQL.Schema
|
alias Mobilizon.GraphQL.Schema
|
||||||
|
|
||||||
import_types(Schema.Actors.MemberType)
|
import_types(Schema.Actors.MemberType)
|
||||||
@ -38,8 +38,8 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
|
|||||||
|
|
||||||
field(:suspended, :boolean, description: "If the actor is suspended")
|
field(:suspended, :boolean, description: "If the actor is suspended")
|
||||||
|
|
||||||
field(:avatar, :picture, description: "The actor's avatar picture")
|
field(:avatar, :media, description: "The actor's avatar media")
|
||||||
field(:banner, :picture, description: "The actor's banner picture")
|
field(:banner, :media, description: "The actor's banner media")
|
||||||
|
|
||||||
field(:physical_address, :address,
|
field(:physical_address, :address,
|
||||||
resolve: dataloader(Addresses),
|
resolve: dataloader(Addresses),
|
||||||
@ -53,7 +53,7 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
|
|||||||
field(:followingCount, :integer, description: "Number of actors following this actor")
|
field(:followingCount, :integer, description: "Number of actors following this actor")
|
||||||
|
|
||||||
field(:media_size, :integer,
|
field(:media_size, :integer,
|
||||||
resolve: &Picture.actor_size/3,
|
resolve: &Media.actor_size/3,
|
||||||
description: "The total size of the media from this actor"
|
description: "The total size of the media from this actor"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -198,14 +198,14 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
|
|||||||
default_value: :public
|
default_value: :public
|
||||||
)
|
)
|
||||||
|
|
||||||
arg(:avatar, :picture_input,
|
arg(:avatar, :media_input,
|
||||||
description:
|
description:
|
||||||
"The avatar for the group, either as an object or directly the ID of an existing Picture"
|
"The avatar for the group, either as an object or directly the ID of an existing media"
|
||||||
)
|
)
|
||||||
|
|
||||||
arg(:banner, :picture_input,
|
arg(:banner, :media_input,
|
||||||
description:
|
description:
|
||||||
"The banner for the group, either as an object or directly the ID of an existing Picture"
|
"The banner for the group, either as an object or directly the ID of an existing media"
|
||||||
)
|
)
|
||||||
|
|
||||||
arg(:physical_address, :address_input, description: "The physical address for the group")
|
arg(:physical_address, :address_input, description: "The physical address for the group")
|
||||||
@ -226,14 +226,14 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
|
|||||||
description: "Whether the group can be join freely, with approval or is invite-only."
|
description: "Whether the group can be join freely, with approval or is invite-only."
|
||||||
)
|
)
|
||||||
|
|
||||||
arg(:avatar, :picture_input,
|
arg(:avatar, :media_input,
|
||||||
description:
|
description:
|
||||||
"The avatar for the group, either as an object or directly the ID of an existing Picture"
|
"The avatar for the group, either as an object or directly the ID of an existing media"
|
||||||
)
|
)
|
||||||
|
|
||||||
arg(:banner, :picture_input,
|
arg(:banner, :media_input,
|
||||||
description:
|
description:
|
||||||
"The banner for the group, either as an object or directly the ID of an existing Picture"
|
"The banner for the group, either as an object or directly the ID of an existing media"
|
||||||
)
|
)
|
||||||
|
|
||||||
arg(:physical_address, :address_input, description: "The physical address for the group")
|
arg(:physical_address, :address_input, description: "The physical address for the group")
|
||||||
|
@ -7,7 +7,7 @@ defmodule Mobilizon.GraphQL.Schema.Actors.PersonType do
|
|||||||
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
|
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
|
||||||
|
|
||||||
alias Mobilizon.Events
|
alias Mobilizon.Events
|
||||||
alias Mobilizon.GraphQL.Resolvers.{Person, Picture}
|
alias Mobilizon.GraphQL.Resolvers.{Media, Person}
|
||||||
alias Mobilizon.GraphQL.Schema
|
alias Mobilizon.GraphQL.Schema
|
||||||
|
|
||||||
import_types(Schema.Events.FeedTokenType)
|
import_types(Schema.Events.FeedTokenType)
|
||||||
@ -40,8 +40,8 @@ defmodule Mobilizon.GraphQL.Schema.Actors.PersonType do
|
|||||||
|
|
||||||
field(:suspended, :boolean, description: "If the actor is suspended")
|
field(:suspended, :boolean, description: "If the actor is suspended")
|
||||||
|
|
||||||
field(:avatar, :picture, description: "The actor's avatar picture")
|
field(:avatar, :media, description: "The actor's avatar media")
|
||||||
field(:banner, :picture, description: "The actor's banner picture")
|
field(:banner, :media, description: "The actor's banner media")
|
||||||
|
|
||||||
# These one should have a privacy setting
|
# These one should have a privacy setting
|
||||||
field(:following, list_of(:follower), description: "List of followings")
|
field(:following, list_of(:follower), description: "List of followings")
|
||||||
@ -50,7 +50,7 @@ defmodule Mobilizon.GraphQL.Schema.Actors.PersonType do
|
|||||||
field(:followingCount, :integer, description: "Number of actors following this actor")
|
field(:followingCount, :integer, description: "Number of actors following this actor")
|
||||||
|
|
||||||
field(:media_size, :integer,
|
field(:media_size, :integer,
|
||||||
resolve: &Picture.actor_size/3,
|
resolve: &Media.actor_size/3,
|
||||||
description: "The total size of the media from this actor"
|
description: "The total size of the media from this actor"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -150,14 +150,14 @@ defmodule Mobilizon.GraphQL.Schema.Actors.PersonType do
|
|||||||
|
|
||||||
arg(:summary, :string, description: "The summary for the new profile", default_value: "")
|
arg(:summary, :string, description: "The summary for the new profile", default_value: "")
|
||||||
|
|
||||||
arg(:avatar, :picture_input,
|
arg(:avatar, :media_input,
|
||||||
description:
|
description:
|
||||||
"The avatar for the profile, either as an object or directly the ID of an existing Picture"
|
"The avatar for the profile, either as an object or directly the ID of an existing media"
|
||||||
)
|
)
|
||||||
|
|
||||||
arg(:banner, :picture_input,
|
arg(:banner, :media_input,
|
||||||
description:
|
description:
|
||||||
"The banner for the profile, either as an object or directly the ID of an existing Picture"
|
"The banner for the profile, either as an object or directly the ID of an existing media"
|
||||||
)
|
)
|
||||||
|
|
||||||
resolve(&Person.create_person/3)
|
resolve(&Person.create_person/3)
|
||||||
@ -171,14 +171,14 @@ defmodule Mobilizon.GraphQL.Schema.Actors.PersonType do
|
|||||||
|
|
||||||
arg(:summary, :string, description: "The summary for this profile")
|
arg(:summary, :string, description: "The summary for this profile")
|
||||||
|
|
||||||
arg(:avatar, :picture_input,
|
arg(:avatar, :media_input,
|
||||||
description:
|
description:
|
||||||
"The avatar for the profile, either as an object or directly the ID of an existing Picture"
|
"The avatar for the profile, either as an object or directly the ID of an existing media"
|
||||||
)
|
)
|
||||||
|
|
||||||
arg(:banner, :picture_input,
|
arg(:banner, :media_input,
|
||||||
description:
|
description:
|
||||||
"The banner for the profile, either as an object or directly the ID of an existing Picture"
|
"The banner for the profile, either as an object or directly the ID of an existing media"
|
||||||
)
|
)
|
||||||
|
|
||||||
resolve(&Person.update_person/3)
|
resolve(&Person.update_person/3)
|
||||||
@ -200,14 +200,14 @@ defmodule Mobilizon.GraphQL.Schema.Actors.PersonType do
|
|||||||
arg(:summary, :string, description: "The summary for the new profile", default_value: "")
|
arg(:summary, :string, description: "The summary for the new profile", default_value: "")
|
||||||
arg(:email, non_null(:string), description: "The email from the user previously created")
|
arg(:email, non_null(:string), description: "The email from the user previously created")
|
||||||
|
|
||||||
arg(:avatar, :picture_input,
|
arg(:avatar, :media_input,
|
||||||
description:
|
description:
|
||||||
"The avatar for the profile, either as an object or directly the ID of an existing Picture"
|
"The avatar for the profile, either as an object or directly the ID of an existing media"
|
||||||
)
|
)
|
||||||
|
|
||||||
arg(:banner, :picture_input,
|
arg(:banner, :media_input,
|
||||||
description:
|
description:
|
||||||
"The banner for the profile, either as an object or directly the ID of an existing Picture"
|
"The banner for the profile, either as an object or directly the ID of an existing media"
|
||||||
)
|
)
|
||||||
|
|
||||||
resolve(&Person.register_person/3)
|
resolve(&Person.register_person/3)
|
||||||
|
@ -43,7 +43,6 @@ defmodule Mobilizon.GraphQL.Schema.AddressType do
|
|||||||
An address input
|
An address input
|
||||||
"""
|
"""
|
||||||
input_object :address_input do
|
input_object :address_input do
|
||||||
# Either a full picture object
|
|
||||||
field(:geom, :point, description: "The geocoordinates for the point where this address is")
|
field(:geom, :point, description: "The geocoordinates for the point where this address is")
|
||||||
field(:street, :string, description: "The address's street name (with number)")
|
field(:street, :string, description: "The address's street name (with number)")
|
||||||
field(:locality, :string, description: "The address's locality")
|
field(:locality, :string, description: "The address's locality")
|
||||||
|
@ -8,7 +8,7 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
|
|||||||
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
|
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
|
||||||
|
|
||||||
alias Mobilizon.{Actors, Addresses, Discussions}
|
alias Mobilizon.{Actors, Addresses, Discussions}
|
||||||
alias Mobilizon.GraphQL.Resolvers.{Event, Picture, Tag}
|
alias Mobilizon.GraphQL.Resolvers.{Event, Media, Tag}
|
||||||
alias Mobilizon.GraphQL.Schema
|
alias Mobilizon.GraphQL.Schema
|
||||||
|
|
||||||
import_types(Schema.AddressType)
|
import_types(Schema.AddressType)
|
||||||
@ -31,9 +31,14 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
|
|||||||
field(:visibility, :event_visibility, description: "The event's visibility")
|
field(:visibility, :event_visibility, description: "The event's visibility")
|
||||||
field(:join_options, :event_join_options, description: "The event's visibility")
|
field(:join_options, :event_join_options, description: "The event's visibility")
|
||||||
|
|
||||||
field(:picture, :picture,
|
field(:picture, :media,
|
||||||
description: "The event's picture",
|
description: "The event's picture",
|
||||||
resolve: &Picture.picture/3
|
resolve: &Media.media/3
|
||||||
|
)
|
||||||
|
|
||||||
|
field(:media, list_of(:media),
|
||||||
|
description: "The event's media",
|
||||||
|
resolve: &Media.medias/3
|
||||||
)
|
)
|
||||||
|
|
||||||
field(:publish_at, :datetime, description: "When the event was published")
|
field(:publish_at, :datetime, description: "When the event was published")
|
||||||
@ -328,9 +333,9 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
|
|||||||
description: "The list of tags associated to the event"
|
description: "The list of tags associated to the event"
|
||||||
)
|
)
|
||||||
|
|
||||||
arg(:picture, :picture_input,
|
arg(:picture, :media_input,
|
||||||
description:
|
description:
|
||||||
"The picture for the event, either as an object or directly the ID of an existing Picture"
|
"The picture for the event, either as an object or directly the ID of an existing media"
|
||||||
)
|
)
|
||||||
|
|
||||||
arg(:publish_at, :datetime, description: "Datetime when the event was published")
|
arg(:publish_at, :datetime, description: "Datetime when the event was published")
|
||||||
@ -379,9 +384,9 @@ defmodule Mobilizon.GraphQL.Schema.EventType do
|
|||||||
|
|
||||||
arg(:tags, list_of(:string), description: "The list of tags associated to the event")
|
arg(:tags, list_of(:string), description: "The list of tags associated to the event")
|
||||||
|
|
||||||
arg(:picture, :picture_input,
|
arg(:picture, :media_input,
|
||||||
description:
|
description:
|
||||||
"The picture for the event, either as an object or directly the ID of an existing Picture"
|
"The picture for the event, either as an object or directly the ID of an existing media"
|
||||||
)
|
)
|
||||||
|
|
||||||
arg(:online_address, :string, description: "Online address of the event")
|
arg(:online_address, :string, description: "Online address of the event")
|
||||||
|
68
lib/graphql/schema/media.ex
Normal file
68
lib/graphql/schema/media.ex
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
defmodule Mobilizon.GraphQL.Schema.MediaType do
|
||||||
|
@moduledoc """
|
||||||
|
Schema representation for Medias
|
||||||
|
"""
|
||||||
|
use Absinthe.Schema.Notation
|
||||||
|
|
||||||
|
alias Mobilizon.GraphQL.Resolvers.Media
|
||||||
|
|
||||||
|
@desc "A media"
|
||||||
|
object :media do
|
||||||
|
field(:id, :id, description: "The media's ID")
|
||||||
|
field(:alt, :string, description: "The media's alternative text")
|
||||||
|
field(:name, :string, description: "The media's name")
|
||||||
|
field(:url, :string, description: "The media's full URL")
|
||||||
|
field(:content_type, :string, description: "The media's detected content type")
|
||||||
|
field(:size, :integer, description: "The media's size")
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc """
|
||||||
|
A paginated list of medias
|
||||||
|
"""
|
||||||
|
object :paginated_media_list do
|
||||||
|
field(:elements, list_of(:media), description: "The list of medias")
|
||||||
|
field(:total, :integer, description: "The total number of medias in the list")
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc "An attached media or a link to a media"
|
||||||
|
input_object :media_input do
|
||||||
|
# Either a full media object
|
||||||
|
field(:media, :media_input_object, description: "A full media attached")
|
||||||
|
# Or directly the ID of an existing media
|
||||||
|
field(:media_id, :id, description: "The ID of an existing media")
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc "An attached media"
|
||||||
|
input_object :media_input_object do
|
||||||
|
field(:name, non_null(:string), description: "The media's name")
|
||||||
|
field(:alt, :string, description: "The media's alternative text")
|
||||||
|
field(:file, non_null(:upload), description: "The media file")
|
||||||
|
field(:actor_id, :id, description: "The media owner")
|
||||||
|
end
|
||||||
|
|
||||||
|
object :media_queries do
|
||||||
|
@desc "Get a media"
|
||||||
|
field :media, :media do
|
||||||
|
arg(:id, non_null(:id), description: "The media ID")
|
||||||
|
resolve(&Media.media/3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
object :media_mutations do
|
||||||
|
@desc "Upload a media"
|
||||||
|
field :upload_media, :media do
|
||||||
|
arg(:name, non_null(:string), description: "The media's name")
|
||||||
|
arg(:alt, :string, description: "The media's alternative text")
|
||||||
|
arg(:file, non_null(:upload), description: "The media file")
|
||||||
|
resolve(&Media.upload_media/3)
|
||||||
|
end
|
||||||
|
|
||||||
|
@desc """
|
||||||
|
Remove a media
|
||||||
|
"""
|
||||||
|
field :remove_media, :deleted_object do
|
||||||
|
arg(:id, non_null(:id), description: "The media's ID")
|
||||||
|
resolve(&Media.remove_media/3)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -1,68 +0,0 @@
|
|||||||
defmodule Mobilizon.GraphQL.Schema.PictureType do
|
|
||||||
@moduledoc """
|
|
||||||
Schema representation for Pictures
|
|
||||||
"""
|
|
||||||
use Absinthe.Schema.Notation
|
|
||||||
|
|
||||||
alias Mobilizon.GraphQL.Resolvers.Picture
|
|
||||||
|
|
||||||
@desc "A picture"
|
|
||||||
object :picture do
|
|
||||||
field(:id, :id, description: "The picture's ID")
|
|
||||||
field(:alt, :string, description: "The picture's alternative text")
|
|
||||||
field(:name, :string, description: "The picture's name")
|
|
||||||
field(:url, :string, description: "The picture's full URL")
|
|
||||||
field(:content_type, :string, description: "The picture's detected content type")
|
|
||||||
field(:size, :integer, description: "The picture's size")
|
|
||||||
end
|
|
||||||
|
|
||||||
@desc """
|
|
||||||
A paginated list of pictures
|
|
||||||
"""
|
|
||||||
object :paginated_picture_list do
|
|
||||||
field(:elements, list_of(:picture), description: "The list of pictures")
|
|
||||||
field(:total, :integer, description: "The total number of pictures in the list")
|
|
||||||
end
|
|
||||||
|
|
||||||
@desc "An attached picture or a link to a picture"
|
|
||||||
input_object :picture_input do
|
|
||||||
# Either a full picture object
|
|
||||||
field(:picture, :picture_input_object, description: "A full picture attached")
|
|
||||||
# Or directly the ID of an existing picture
|
|
||||||
field(:picture_id, :id, description: "The ID of an existing picture")
|
|
||||||
end
|
|
||||||
|
|
||||||
@desc "An attached picture"
|
|
||||||
input_object :picture_input_object do
|
|
||||||
field(:name, non_null(:string), description: "The picture's name")
|
|
||||||
field(:alt, :string, description: "The picture's alternative text")
|
|
||||||
field(:file, non_null(:upload), description: "The picture file")
|
|
||||||
field(:actor_id, :id, description: "The picture owner")
|
|
||||||
end
|
|
||||||
|
|
||||||
object :picture_queries do
|
|
||||||
@desc "Get a picture"
|
|
||||||
field :picture, :picture do
|
|
||||||
arg(:id, non_null(:id), description: "The picture ID")
|
|
||||||
resolve(&Picture.picture/3)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
object :picture_mutations do
|
|
||||||
@desc "Upload a picture"
|
|
||||||
field :upload_picture, :picture do
|
|
||||||
arg(:name, non_null(:string), description: "The picture's name")
|
|
||||||
arg(:alt, :string, description: "The picture's alternative text")
|
|
||||||
arg(:file, non_null(:upload), description: "The picture file")
|
|
||||||
resolve(&Picture.upload_picture/3)
|
|
||||||
end
|
|
||||||
|
|
||||||
@desc """
|
|
||||||
Remove a picture
|
|
||||||
"""
|
|
||||||
field :remove_picture, :deleted_object do
|
|
||||||
arg(:id, non_null(:id), description: "The picture's ID")
|
|
||||||
resolve(&Picture.remove_picture/3)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
@ -3,7 +3,7 @@ defmodule Mobilizon.GraphQL.Schema.PostType do
|
|||||||
Schema representation for Posts
|
Schema representation for Posts
|
||||||
"""
|
"""
|
||||||
use Absinthe.Schema.Notation
|
use Absinthe.Schema.Notation
|
||||||
alias Mobilizon.GraphQL.Resolvers.{Picture, Post, Tag}
|
alias Mobilizon.GraphQL.Resolvers.{Media, Post, Tag}
|
||||||
|
|
||||||
@desc "A post"
|
@desc "A post"
|
||||||
object :post do
|
object :post do
|
||||||
@ -25,9 +25,9 @@ defmodule Mobilizon.GraphQL.Schema.PostType do
|
|||||||
description: "The post's tags"
|
description: "The post's tags"
|
||||||
)
|
)
|
||||||
|
|
||||||
field(:picture, :picture,
|
field(:picture, :media,
|
||||||
description: "The posts's picture",
|
description: "The posts's media",
|
||||||
resolve: &Picture.picture/3
|
resolve: &Media.media/3
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -76,9 +76,9 @@ defmodule Mobilizon.GraphQL.Schema.PostType do
|
|||||||
description: "The list of tags associated to the post"
|
description: "The list of tags associated to the post"
|
||||||
)
|
)
|
||||||
|
|
||||||
arg(:picture, :picture_input,
|
arg(:picture, :media_input,
|
||||||
description:
|
description:
|
||||||
"The banner for the post, either as an object or directly the ID of an existing Picture"
|
"The banner for the post, either as an object or directly the ID of an existing media"
|
||||||
)
|
)
|
||||||
|
|
||||||
resolve(&Post.create_post/3)
|
resolve(&Post.create_post/3)
|
||||||
@ -99,9 +99,9 @@ defmodule Mobilizon.GraphQL.Schema.PostType do
|
|||||||
|
|
||||||
arg(:tags, list_of(:string), description: "The list of tags associated to the post")
|
arg(:tags, list_of(:string), description: "The list of tags associated to the post")
|
||||||
|
|
||||||
arg(:picture, :picture_input,
|
arg(:picture, :media_input,
|
||||||
description:
|
description:
|
||||||
"The banner for the post, either as an object or directly the ID of an existing Picture"
|
"The banner for the post, either as an object or directly the ID of an existing media"
|
||||||
)
|
)
|
||||||
|
|
||||||
resolve(&Post.update_post/3)
|
resolve(&Post.update_post/3)
|
||||||
|
@ -7,7 +7,7 @@ defmodule Mobilizon.GraphQL.Schema.UserType do
|
|||||||
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
|
import Absinthe.Resolution.Helpers, only: [dataloader: 1]
|
||||||
|
|
||||||
alias Mobilizon.Events
|
alias Mobilizon.Events
|
||||||
alias Mobilizon.GraphQL.Resolvers.{Picture, User}
|
alias Mobilizon.GraphQL.Resolvers.{Media, User}
|
||||||
alias Mobilizon.GraphQL.Schema
|
alias Mobilizon.GraphQL.Schema
|
||||||
|
|
||||||
import_types(Schema.SortType)
|
import_types(Schema.SortType)
|
||||||
@ -111,7 +111,7 @@ defmodule Mobilizon.GraphQL.Schema.UserType do
|
|||||||
description: "The IP adress the user's currently signed-in with"
|
description: "The IP adress the user's currently signed-in with"
|
||||||
)
|
)
|
||||||
|
|
||||||
field(:media, :paginated_picture_list, description: "The user's media objects") do
|
field(:media, :paginated_media_list, description: "The user's media objects") do
|
||||||
arg(:page, :integer,
|
arg(:page, :integer,
|
||||||
default_value: 1,
|
default_value: 1,
|
||||||
description: "The page in the paginated user media list"
|
description: "The page in the paginated user media list"
|
||||||
@ -122,7 +122,7 @@ defmodule Mobilizon.GraphQL.Schema.UserType do
|
|||||||
end
|
end
|
||||||
|
|
||||||
field(:media_size, :integer,
|
field(:media_size, :integer,
|
||||||
resolve: &Picture.user_size/3,
|
resolve: &Media.user_size/3,
|
||||||
description: "The total size of all the media from this user (from all their actors)"
|
description: "The total size of all the media from this user (from all their actors)"
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
@ -47,12 +47,14 @@ defmodule Mix.Tasks.Mobilizon.Common do
|
|||||||
else: shell_prompt(message, "Continue?") in ~w(Yn Y y)
|
else: shell_prompt(message, "Continue?") in ~w(Yn Y y)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec shell_info(String.t()) :: :ok
|
||||||
def shell_info(message) do
|
def shell_info(message) do
|
||||||
if mix_shell?(),
|
if mix_shell?(),
|
||||||
do: Mix.shell().info(message),
|
do: Mix.shell().info(message),
|
||||||
else: IO.puts(message)
|
else: IO.puts(message)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@spec shell_error(String.t()) :: :ok
|
||||||
def shell_error(message) do
|
def shell_error(message) do
|
||||||
if mix_shell?(),
|
if mix_shell?(),
|
||||||
do: Mix.shell().error(message),
|
do: Mix.shell().error(message),
|
||||||
|
23
lib/mix/tasks/mobilizon/maintenance.ex
Normal file
23
lib/mix/tasks/mobilizon/maintenance.ex
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
defmodule Mix.Tasks.Mobilizon.Maintenance do
|
||||||
|
@moduledoc """
|
||||||
|
Tasks to maintain mobilizon
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Mix.Task
|
||||||
|
|
||||||
|
alias Mix.Tasks
|
||||||
|
import Mix.Tasks.Mobilizon.Common
|
||||||
|
|
||||||
|
@shortdoc "List common Mobilizon maintenance tasks"
|
||||||
|
|
||||||
|
@impl Mix.Task
|
||||||
|
def run(_) do
|
||||||
|
shell_info("\nAvailable tasks:")
|
||||||
|
|
||||||
|
if mix_shell?() do
|
||||||
|
Tasks.Help.run(["--search", "mobilizon.maintenance."])
|
||||||
|
else
|
||||||
|
show_subtasks_for_module(__MODULE__)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -0,0 +1,107 @@
|
|||||||
|
defmodule Mix.Tasks.Mobilizon.Maintenance.FixUnattachedMediaInBody do
|
||||||
|
@moduledoc """
|
||||||
|
Task to reattach media files that were added in event, post or comment bodies without being attached to their entities.
|
||||||
|
|
||||||
|
This task should only be run once.
|
||||||
|
"""
|
||||||
|
use Mix.Task
|
||||||
|
|
||||||
|
import Mix.Tasks.Mobilizon.Common
|
||||||
|
alias Mobilizon.{Discussions, Events, Medias, Posts}
|
||||||
|
alias Mobilizon.Discussions.Comment
|
||||||
|
alias Mobilizon.Events.Event
|
||||||
|
alias Mobilizon.Posts.Post
|
||||||
|
alias Mobilizon.Storage.Repo
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@preferred_cli_env "prod"
|
||||||
|
|
||||||
|
# TODO: Remove me in Mobilizon 1.2
|
||||||
|
|
||||||
|
@shortdoc "Reattaches inline media from events and posts"
|
||||||
|
def run([]) do
|
||||||
|
start_mobilizon()
|
||||||
|
|
||||||
|
shell_info("Going to extract pictures from events")
|
||||||
|
extract_inline_pictures_from_bodies(Event)
|
||||||
|
shell_info("Going to extract pictures from posts")
|
||||||
|
extract_inline_pictures_from_bodies(Post)
|
||||||
|
shell_info("Going to extract pictures from comments")
|
||||||
|
extract_inline_pictures_from_bodies(Comment)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp extract_inline_pictures_from_bodies(entity) do
|
||||||
|
Repo.transaction(
|
||||||
|
fn ->
|
||||||
|
entity
|
||||||
|
|> Repo.stream()
|
||||||
|
|> Stream.map(&extract_pictures(&1))
|
||||||
|
|> Stream.map(fn {entity, pics} -> save_entity(entity, pics) end)
|
||||||
|
|> Stream.run()
|
||||||
|
end,
|
||||||
|
timeout: :infinity
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp extract_pictures(entity) do
|
||||||
|
extracted_pictures = entity |> get_body() |> parse_body() |> get_media_entities_from_urls()
|
||||||
|
|
||||||
|
attached_picture = entity |> get_picture() |> get_media_entity_from_media_id()
|
||||||
|
attached_pictures = [attached_picture] |> Enum.filter(& &1)
|
||||||
|
|
||||||
|
{entity, extracted_pictures ++ attached_pictures}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_body(%Event{description: description}), do: description
|
||||||
|
defp get_body(%Post{body: body}), do: body
|
||||||
|
defp get_body(%Comment{text: text}), do: text
|
||||||
|
|
||||||
|
defp get_picture(%Event{picture_id: picture_id}), do: picture_id
|
||||||
|
defp get_picture(%Post{picture_id: picture_id}), do: picture_id
|
||||||
|
defp get_picture(%Comment{}), do: nil
|
||||||
|
|
||||||
|
defp parse_body(nil), do: []
|
||||||
|
|
||||||
|
defp parse_body(body) do
|
||||||
|
with res <- Regex.scan(~r/<img\s[^>]*?src\s*=\s*['\"]([^'\"]*?)['\"][^>]*?>/, body),
|
||||||
|
res <- Enum.map(res, fn [_, res] -> res end) do
|
||||||
|
res
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_media_entities_from_urls(media_urls) do
|
||||||
|
media_urls
|
||||||
|
|> Enum.map(fn media_url ->
|
||||||
|
# We prefer orphan media, but fallback on already attached media just in case
|
||||||
|
Medias.get_unattached_media_by_url(media_url) || Medias.get_media_by_url(media_url)
|
||||||
|
end)
|
||||||
|
|> Enum.filter(& &1)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp get_media_entity_from_media_id(nil), do: nil
|
||||||
|
|
||||||
|
defp get_media_entity_from_media_id(media_id) do
|
||||||
|
Medias.get_media(media_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp save_entity(%Event{} = _event, []), do: :ok
|
||||||
|
|
||||||
|
defp save_entity(%Event{} = event, media) do
|
||||||
|
event = Repo.preload(event, [:contacts, :media])
|
||||||
|
Events.update_event(event, %{media: media})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp save_entity(%Post{} = _post, []), do: :ok
|
||||||
|
|
||||||
|
defp save_entity(%Post{} = post, media) do
|
||||||
|
post = Repo.preload(post, [:media])
|
||||||
|
Posts.update_post(post, %{media: media})
|
||||||
|
end
|
||||||
|
|
||||||
|
defp save_entity(%Comment{} = _comment, []), do: :ok
|
||||||
|
|
||||||
|
defp save_entity(%Comment{} = comment, media) do
|
||||||
|
comment = Repo.preload(comment, [:media])
|
||||||
|
Discussions.update_comment(comment, %{media: media})
|
||||||
|
end
|
||||||
|
end
|
23
lib/mix/tasks/mobilizon/media.ex
Normal file
23
lib/mix/tasks/mobilizon/media.ex
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
defmodule Mix.Tasks.Mobilizon.Media do
|
||||||
|
@moduledoc """
|
||||||
|
Tasks to manage media
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Mix.Task
|
||||||
|
|
||||||
|
alias Mix.Tasks
|
||||||
|
import Mix.Tasks.Mobilizon.Common
|
||||||
|
|
||||||
|
@shortdoc "Manages Mobilizon media"
|
||||||
|
|
||||||
|
@impl Mix.Task
|
||||||
|
def run(_) do
|
||||||
|
shell_info("\nAvailable tasks:")
|
||||||
|
|
||||||
|
if mix_shell?() do
|
||||||
|
Tasks.Help.run(["--search", "mobilizon.media."])
|
||||||
|
else
|
||||||
|
show_subtasks_for_module(__MODULE__)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
87
lib/mix/tasks/mobilizon/media/clean_orphan.ex
Normal file
87
lib/mix/tasks/mobilizon/media/clean_orphan.ex
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
defmodule Mix.Tasks.Mobilizon.Media.CleanOrphan do
|
||||||
|
@moduledoc """
|
||||||
|
Task to accept an instance follow request
|
||||||
|
"""
|
||||||
|
use Mix.Task
|
||||||
|
import Mix.Tasks.Mobilizon.Common
|
||||||
|
alias Mobilizon.Service.CleanOrphanMedia
|
||||||
|
|
||||||
|
@shortdoc "Clean orphan media"
|
||||||
|
|
||||||
|
@grace_period Mobilizon.Config.get([:instance, :orphan_upload_grace_period_hours], 48)
|
||||||
|
|
||||||
|
@impl Mix.Task
|
||||||
|
def run(options) do
|
||||||
|
{options, [], []} =
|
||||||
|
OptionParser.parse(
|
||||||
|
options,
|
||||||
|
strict: [
|
||||||
|
dry_run: :boolean,
|
||||||
|
days: :integer,
|
||||||
|
verbose: :boolean
|
||||||
|
],
|
||||||
|
aliases: [
|
||||||
|
d: :days,
|
||||||
|
v: :verbose
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
dry_run = Keyword.get(options, :dry_run, false)
|
||||||
|
grace_period = Keyword.get(options, :days)
|
||||||
|
grace_period = if is_nil(grace_period), do: @grace_period, else: grace_period * 24
|
||||||
|
verbose = Keyword.get(options, :verbose, false)
|
||||||
|
|
||||||
|
start_mobilizon()
|
||||||
|
|
||||||
|
case CleanOrphanMedia.clean(dry_run: dry_run, grace_period: grace_period) do
|
||||||
|
{:ok, medias} ->
|
||||||
|
if length(medias) > 0 do
|
||||||
|
if dry_run or verbose do
|
||||||
|
details(medias, dry_run, verbose)
|
||||||
|
end
|
||||||
|
|
||||||
|
result(dry_run, length(medias))
|
||||||
|
else
|
||||||
|
empty_result(dry_run)
|
||||||
|
end
|
||||||
|
|
||||||
|
:ok
|
||||||
|
|
||||||
|
_err ->
|
||||||
|
shell_error("Error while cleaning orphan media files")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec details(list(Media.t()), boolean(), boolean()) :: :ok
|
||||||
|
defp details(medias, dry_run, verbose) do
|
||||||
|
cond do
|
||||||
|
dry_run ->
|
||||||
|
shell_info("List of files that would have been deleted")
|
||||||
|
|
||||||
|
verbose ->
|
||||||
|
shell_info("List of files that have been deleted")
|
||||||
|
end
|
||||||
|
|
||||||
|
Enum.each(medias, fn media ->
|
||||||
|
shell_info("ID: #{media.id}, Actor: #{media.actor_id}, URL: #{media.file.url}")
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec result(boolean(), boolean()) :: :ok
|
||||||
|
defp result(dry_run, nb_medias) do
|
||||||
|
if dry_run do
|
||||||
|
shell_info("#{nb_medias} files would have been deleted")
|
||||||
|
else
|
||||||
|
shell_info("#{nb_medias} files have been deleted")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec empty_result(boolean()) :: :ok
|
||||||
|
defp empty_result(dry_run) do
|
||||||
|
if dry_run do
|
||||||
|
shell_info("No files would have been deleted")
|
||||||
|
else
|
||||||
|
shell_info("No files were deleted")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -58,7 +58,11 @@ defmodule Mobilizon do
|
|||||||
cachex_spec(:statistics, 10, 60, 60),
|
cachex_spec(:statistics, 10, 60, 60),
|
||||||
cachex_spec(:config, 10, 60, 60),
|
cachex_spec(:config, 10, 60, 60),
|
||||||
cachex_spec(:rich_media_cache, 10, 60, 60),
|
cachex_spec(:rich_media_cache, 10, 60, 60),
|
||||||
cachex_spec(:activity_pub, 2500, 3, 15)
|
cachex_spec(:activity_pub, 2500, 3, 15),
|
||||||
|
%{
|
||||||
|
id: :cache_key_value,
|
||||||
|
start: {Cachex, :start_link, [:key_value]}
|
||||||
|
}
|
||||||
] ++
|
] ++
|
||||||
task_children(@env)
|
task_children(@env)
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ defmodule Mobilizon.Actors.Actor do
|
|||||||
alias Mobilizon.Addresses.Address
|
alias Mobilizon.Addresses.Address
|
||||||
alias Mobilizon.Discussions.Comment
|
alias Mobilizon.Discussions.Comment
|
||||||
alias Mobilizon.Events.{Event, FeedToken}
|
alias Mobilizon.Events.{Event, FeedToken}
|
||||||
alias Mobilizon.Media.File
|
alias Mobilizon.Medias.File
|
||||||
alias Mobilizon.Reports.{Note, Report}
|
alias Mobilizon.Reports.{Note, Report}
|
||||||
alias Mobilizon.Users.User
|
alias Mobilizon.Users.User
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ defmodule Mobilizon.Actors do
|
|||||||
alias Mobilizon.Addresses.Address
|
alias Mobilizon.Addresses.Address
|
||||||
alias Mobilizon.{Crypto, Events}
|
alias Mobilizon.{Crypto, Events}
|
||||||
alias Mobilizon.Federation.ActivityPub
|
alias Mobilizon.Federation.ActivityPub
|
||||||
alias Mobilizon.Media.File
|
alias Mobilizon.Medias.File
|
||||||
alias Mobilizon.Service.Workers
|
alias Mobilizon.Service.Workers
|
||||||
alias Mobilizon.Storage.{Page, Repo}
|
alias Mobilizon.Storage.{Page, Repo}
|
||||||
alias Mobilizon.Users
|
alias Mobilizon.Users
|
||||||
@ -285,7 +285,7 @@ defmodule Mobilizon.Actors do
|
|||||||
# if is_nil(file) do
|
# if is_nil(file) do
|
||||||
# nil
|
# nil
|
||||||
# else
|
# else
|
||||||
# struct(Mobilizon.Media.File, file)
|
# struct(Mobilizon.Medias.File, file)
|
||||||
# end
|
# end
|
||||||
# end
|
# end
|
||||||
|
|
||||||
@ -1673,7 +1673,8 @@ defmodule Mobilizon.Actors do
|
|||||||
:attributed_to,
|
:attributed_to,
|
||||||
:tags,
|
:tags,
|
||||||
:physical_address,
|
:physical_address,
|
||||||
:contacts
|
:contacts,
|
||||||
|
:media
|
||||||
])
|
])
|
||||||
|
|
||||||
ActivityPub.delete(event, actor, false)
|
ActivityPub.delete(event, actor, false)
|
||||||
|
@ -11,6 +11,7 @@ defmodule Mobilizon.Discussions.Comment do
|
|||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Discussions.{Comment, CommentVisibility, Discussion}
|
alias Mobilizon.Discussions.{Comment, CommentVisibility, Discussion}
|
||||||
alias Mobilizon.Events.{Event, Tag}
|
alias Mobilizon.Events.{Event, Tag}
|
||||||
|
alias Mobilizon.Medias.Media
|
||||||
alias Mobilizon.Mention
|
alias Mobilizon.Mention
|
||||||
|
|
||||||
alias Mobilizon.Web.Endpoint
|
alias Mobilizon.Web.Endpoint
|
||||||
@ -27,6 +28,7 @@ defmodule Mobilizon.Discussions.Comment do
|
|||||||
event: Event.t(),
|
event: Event.t(),
|
||||||
tags: [Tag.t()],
|
tags: [Tag.t()],
|
||||||
mentions: [Mention.t()],
|
mentions: [Mention.t()],
|
||||||
|
media: [Media.t()],
|
||||||
in_reply_to_comment: t,
|
in_reply_to_comment: t,
|
||||||
origin_comment: t
|
origin_comment: t
|
||||||
}
|
}
|
||||||
@ -66,6 +68,7 @@ defmodule Mobilizon.Discussions.Comment do
|
|||||||
has_many(:replies, Comment, foreign_key: :in_reply_to_comment_id)
|
has_many(:replies, Comment, foreign_key: :in_reply_to_comment_id)
|
||||||
many_to_many(:tags, Tag, join_through: "comments_tags", on_replace: :delete)
|
many_to_many(:tags, Tag, join_through: "comments_tags", on_replace: :delete)
|
||||||
has_many(:mentions, Mention)
|
has_many(:mentions, Mention)
|
||||||
|
many_to_many(:media, Media, join_through: "comments_medias", on_replace: :delete)
|
||||||
|
|
||||||
timestamps(type: :utc_datetime)
|
timestamps(type: :utc_datetime)
|
||||||
end
|
end
|
||||||
@ -120,6 +123,7 @@ defmodule Mobilizon.Discussions.Comment do
|
|||||||
|> maybe_add_published_at()
|
|> maybe_add_published_at()
|
||||||
|> maybe_generate_uuid()
|
|> maybe_generate_uuid()
|
||||||
|> maybe_generate_url()
|
|> maybe_generate_url()
|
||||||
|
|> put_assoc(:media, Map.get(attrs, :media, []))
|
||||||
|> put_tags(attrs)
|
|> put_tags(attrs)
|
||||||
|> put_mentions(attrs)
|
|> put_mentions(attrs)
|
||||||
end
|
end
|
||||||
|
@ -10,7 +10,7 @@ defmodule Mobilizon.Events.Event do
|
|||||||
alias Ecto.Changeset
|
alias Ecto.Changeset
|
||||||
|
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.{Addresses, Events, Media, Mention}
|
alias Mobilizon.{Addresses, Events, Medias, Mention}
|
||||||
alias Mobilizon.Addresses.Address
|
alias Mobilizon.Addresses.Address
|
||||||
|
|
||||||
alias Mobilizon.Discussions.Comment
|
alias Mobilizon.Discussions.Comment
|
||||||
@ -27,7 +27,7 @@ defmodule Mobilizon.Events.Event do
|
|||||||
Track
|
Track
|
||||||
}
|
}
|
||||||
|
|
||||||
alias Mobilizon.Media.Picture
|
alias Mobilizon.Medias.Media
|
||||||
alias Mobilizon.Storage.Repo
|
alias Mobilizon.Storage.Repo
|
||||||
|
|
||||||
alias Mobilizon.Web.Endpoint
|
alias Mobilizon.Web.Endpoint
|
||||||
@ -54,7 +54,8 @@ defmodule Mobilizon.Events.Event do
|
|||||||
organizer_actor: Actor.t(),
|
organizer_actor: Actor.t(),
|
||||||
attributed_to: Actor.t(),
|
attributed_to: Actor.t(),
|
||||||
physical_address: Address.t(),
|
physical_address: Address.t(),
|
||||||
picture: Picture.t(),
|
picture: Media.t(),
|
||||||
|
media: [Media.t()],
|
||||||
tracks: [Track.t()],
|
tracks: [Track.t()],
|
||||||
sessions: [Session.t()],
|
sessions: [Session.t()],
|
||||||
mentions: [Mention.t()],
|
mentions: [Mention.t()],
|
||||||
@ -110,7 +111,7 @@ defmodule Mobilizon.Events.Event do
|
|||||||
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)
|
||||||
belongs_to(:physical_address, Address, on_replace: :nilify)
|
belongs_to(:physical_address, Address, on_replace: :nilify)
|
||||||
belongs_to(:picture, Picture, on_replace: :update)
|
belongs_to(:picture, Media, on_replace: :update)
|
||||||
has_many(:tracks, Track)
|
has_many(:tracks, Track)
|
||||||
has_many(:sessions, Session)
|
has_many(:sessions, Session)
|
||||||
has_many(:mentions, Mention)
|
has_many(:mentions, Mention)
|
||||||
@ -118,6 +119,7 @@ defmodule Mobilizon.Events.Event do
|
|||||||
many_to_many(:contacts, Actor, join_through: "event_contacts", on_replace: :delete)
|
many_to_many(:contacts, Actor, join_through: "event_contacts", on_replace: :delete)
|
||||||
many_to_many(:tags, Tag, join_through: "events_tags", on_replace: :delete)
|
many_to_many(:tags, Tag, join_through: "events_tags", on_replace: :delete)
|
||||||
many_to_many(:participants, Actor, join_through: Participant)
|
many_to_many(:participants, Actor, join_through: Participant)
|
||||||
|
many_to_many(:media, Media, join_through: "events_medias", on_replace: :delete)
|
||||||
|
|
||||||
timestamps(type: :utc_datetime)
|
timestamps(type: :utc_datetime)
|
||||||
end
|
end
|
||||||
@ -150,6 +152,7 @@ defmodule Mobilizon.Events.Event do
|
|||||||
changeset
|
changeset
|
||||||
|> cast_embed(:options)
|
|> cast_embed(:options)
|
||||||
|> put_assoc(:contacts, Map.get(attrs, :contacts, []))
|
|> put_assoc(:contacts, Map.get(attrs, :contacts, []))
|
||||||
|
|> put_assoc(:media, Map.get(attrs, :media, []))
|
||||||
|> put_tags(attrs)
|
|> put_tags(attrs)
|
||||||
|> put_address(attrs)
|
|> put_address(attrs)
|
||||||
|> put_picture(attrs)
|
|> put_picture(attrs)
|
||||||
@ -241,9 +244,9 @@ defmodule Mobilizon.Events.Event do
|
|||||||
|
|
||||||
# In case the provided picture is an existing one
|
# In case the provided picture is an existing one
|
||||||
@spec put_picture(Changeset.t(), map) :: Changeset.t()
|
@spec put_picture(Changeset.t(), map) :: Changeset.t()
|
||||||
defp put_picture(%Changeset{} = changeset, %{picture: %{picture_id: id} = _picture}) do
|
defp put_picture(%Changeset{} = changeset, %{picture: %{media_id: id} = _picture}) do
|
||||||
case Media.get_picture!(id) do
|
case Medias.get_media!(id) do
|
||||||
%Picture{} = picture ->
|
%Media{} = picture ->
|
||||||
put_assoc(changeset, :picture, picture)
|
put_assoc(changeset, :picture, picture)
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
|
@ -84,7 +84,8 @@ defmodule Mobilizon.Events do
|
|||||||
:participants,
|
:participants,
|
||||||
:physical_address,
|
:physical_address,
|
||||||
:picture,
|
:picture,
|
||||||
:contacts
|
:contacts,
|
||||||
|
:media
|
||||||
]
|
]
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
@ -295,7 +296,7 @@ defmodule Mobilizon.Events do
|
|||||||
@spec update_event(Event.t(), map) :: {:ok, Event.t()} | {:error, Changeset.t()}
|
@spec update_event(Event.t(), map) :: {:ok, Event.t()} | {:error, Changeset.t()}
|
||||||
def update_event(%Event{draft: old_draft} = old_event, attrs) do
|
def update_event(%Event{draft: old_draft} = old_event, attrs) do
|
||||||
with %Changeset{changes: changes} = changeset <-
|
with %Changeset{changes: changes} = changeset <-
|
||||||
Event.update_changeset(Repo.preload(old_event, :tags), attrs),
|
Event.update_changeset(Repo.preload(old_event, [:tags, :media]), attrs),
|
||||||
{:ok, %{update: %Event{} = new_event}} <-
|
{:ok, %{update: %Event{} = new_event}} <-
|
||||||
Multi.new()
|
Multi.new()
|
||||||
|> Multi.update(:update, changeset)
|
|> Multi.update(:update, changeset)
|
||||||
|
@ -1,150 +0,0 @@
|
|||||||
defmodule Mobilizon.Media do
|
|
||||||
@moduledoc """
|
|
||||||
The Media context.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import Ecto.Query
|
|
||||||
|
|
||||||
alias Ecto.Multi
|
|
||||||
|
|
||||||
alias Mobilizon.Actors.Actor
|
|
||||||
alias Mobilizon.Media.{File, Picture}
|
|
||||||
alias Mobilizon.Storage.{Page, Repo}
|
|
||||||
alias Mobilizon.Users.User
|
|
||||||
|
|
||||||
alias Mobilizon.Web.Upload
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Gets a single picture.
|
|
||||||
"""
|
|
||||||
@spec get_picture(integer | String.t()) :: Picture.t() | nil
|
|
||||||
def get_picture(id), do: Repo.get(Picture, id)
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Gets a single picture.
|
|
||||||
Raises `Ecto.NoResultsError` if the picture does not exist.
|
|
||||||
"""
|
|
||||||
@spec get_picture!(integer | String.t()) :: Picture.t()
|
|
||||||
def get_picture!(id), do: Repo.get!(Picture, id)
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Get a picture by its URL.
|
|
||||||
"""
|
|
||||||
@spec get_picture_by_url(String.t()) :: Picture.t() | nil
|
|
||||||
def get_picture_by_url(url) do
|
|
||||||
url
|
|
||||||
|> picture_by_url_query()
|
|
||||||
|> Repo.one()
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
List the paginated picture for an actor
|
|
||||||
"""
|
|
||||||
@spec pictures_for_actor(integer | String.t(), integer | nil, integer | nil) :: Page.t()
|
|
||||||
def pictures_for_actor(actor_id, page, limit) do
|
|
||||||
actor_id
|
|
||||||
|> pictures_for_actor_query()
|
|
||||||
|> Page.build_page(page, limit)
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
List the paginated picture for user
|
|
||||||
"""
|
|
||||||
@spec pictures_for_user(integer | String.t(), integer | nil, integer | nil) :: Page.t()
|
|
||||||
def pictures_for_user(user_id, page, limit) do
|
|
||||||
user_id
|
|
||||||
|> pictures_for_user_query()
|
|
||||||
|> Page.build_page(page, limit)
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Calculate the sum of media size used by the user
|
|
||||||
"""
|
|
||||||
@spec media_size_for_actor(integer | String.t()) :: integer()
|
|
||||||
def media_size_for_actor(actor_id) do
|
|
||||||
actor_id
|
|
||||||
|> pictures_for_actor_query()
|
|
||||||
|> select([:file])
|
|
||||||
|> Repo.all()
|
|
||||||
|> Enum.map(& &1.file.size)
|
|
||||||
|> Enum.sum()
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Calculate the sum of media size used by the user
|
|
||||||
"""
|
|
||||||
@spec media_size_for_user(integer | String.t()) :: integer()
|
|
||||||
def media_size_for_user(user_id) do
|
|
||||||
user_id
|
|
||||||
|> pictures_for_user_query()
|
|
||||||
|> select([:file])
|
|
||||||
|> Repo.all()
|
|
||||||
|> Enum.map(& &1.file.size)
|
|
||||||
|> Enum.sum()
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Creates a picture.
|
|
||||||
"""
|
|
||||||
@spec create_picture(map) :: {:ok, Picture.t()} | {:error, Ecto.Changeset.t()}
|
|
||||||
def create_picture(attrs \\ %{}) do
|
|
||||||
%Picture{}
|
|
||||||
|> Picture.changeset(attrs)
|
|
||||||
|> Repo.insert()
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Updates a picture.
|
|
||||||
"""
|
|
||||||
@spec update_picture(Picture.t(), map) :: {:ok, Picture.t()} | {:error, Ecto.Changeset.t()}
|
|
||||||
def update_picture(%Picture{} = picture, attrs) do
|
|
||||||
picture
|
|
||||||
|> Picture.changeset(attrs)
|
|
||||||
|> Repo.update()
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc """
|
|
||||||
Deletes a picture.
|
|
||||||
"""
|
|
||||||
@spec delete_picture(Picture.t()) :: {:ok, Picture.t()} | {:error, Ecto.Changeset.t()}
|
|
||||||
def delete_picture(%Picture{} = picture) do
|
|
||||||
transaction =
|
|
||||||
Multi.new()
|
|
||||||
|> Multi.delete(:picture, picture)
|
|
||||||
|> Multi.run(:remove, fn _repo, %{picture: %Picture{file: %File{url: url}}} ->
|
|
||||||
Upload.remove(url)
|
|
||||||
end)
|
|
||||||
|> Repo.transaction()
|
|
||||||
|
|
||||||
case transaction do
|
|
||||||
{:ok, %{picture: %Picture{} = picture}} ->
|
|
||||||
{:ok, picture}
|
|
||||||
|
|
||||||
{:error, :remove, error, _} ->
|
|
||||||
{:error, error}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec picture_by_url_query(String.t()) :: Ecto.Query.t()
|
|
||||||
defp picture_by_url_query(url) do
|
|
||||||
from(
|
|
||||||
p in Picture,
|
|
||||||
where: fragment("? @> ?", p.file, ~s|{"url": "#{url}"}|)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec pictures_for_actor_query(integer() | String.t()) :: Ecto.Query.t()
|
|
||||||
defp pictures_for_actor_query(actor_id) do
|
|
||||||
Picture
|
|
||||||
|> join(:inner, [p], a in Actor, on: p.actor_id == a.id)
|
|
||||||
|> where([_p, a], a.id == ^actor_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec pictures_for_user_query(integer() | String.t()) :: Ecto.Query.t()
|
|
||||||
defp pictures_for_user_query(user_id) do
|
|
||||||
Picture
|
|
||||||
|> join(:inner, [p], a in Actor, on: p.actor_id == a.id)
|
|
||||||
|> join(:inner, [_p, a], u in User, on: a.user_id == u.id)
|
|
||||||
|> where([_p, _a, u], u.id == ^user_id)
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,32 +0,0 @@
|
|||||||
defmodule Mobilizon.Media.Picture do
|
|
||||||
@moduledoc """
|
|
||||||
Represents a picture entity.
|
|
||||||
"""
|
|
||||||
|
|
||||||
use Ecto.Schema
|
|
||||||
|
|
||||||
import Ecto.Changeset, only: [cast: 3, cast_embed: 2]
|
|
||||||
|
|
||||||
alias Mobilizon.Actors.Actor
|
|
||||||
alias Mobilizon.Media.File
|
|
||||||
|
|
||||||
@type t :: %__MODULE__{
|
|
||||||
file: File.t(),
|
|
||||||
actor: Actor.t()
|
|
||||||
}
|
|
||||||
|
|
||||||
schema "pictures" do
|
|
||||||
embeds_one(:file, File, on_replace: :update)
|
|
||||||
belongs_to(:actor, Actor)
|
|
||||||
|
|
||||||
timestamps()
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc false
|
|
||||||
@spec changeset(t, map) :: Ecto.Changeset.t()
|
|
||||||
def changeset(%__MODULE__{} = picture, attrs) do
|
|
||||||
picture
|
|
||||||
|> cast(attrs, [:actor_id])
|
|
||||||
|> cast_embed(:file)
|
|
||||||
end
|
|
||||||
end
|
|
@ -1,4 +1,4 @@
|
|||||||
defmodule Mobilizon.Media.File do
|
defmodule Mobilizon.Medias.File do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Represents a file entity.
|
Represents a file entity.
|
||||||
"""
|
"""
|
40
lib/mobilizon/medias/media.ex
Normal file
40
lib/mobilizon/medias/media.ex
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
defmodule Mobilizon.Medias.Media do
|
||||||
|
@moduledoc """
|
||||||
|
Represents a media entity.
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
import Ecto.Changeset, only: [cast: 3, cast_embed: 2]
|
||||||
|
|
||||||
|
alias Mobilizon.Actors.Actor
|
||||||
|
alias Mobilizon.Discussions.Comment
|
||||||
|
alias Mobilizon.Events.Event
|
||||||
|
alias Mobilizon.Medias.File
|
||||||
|
alias Mobilizon.Posts.Post
|
||||||
|
|
||||||
|
@type t :: %__MODULE__{
|
||||||
|
file: File.t(),
|
||||||
|
actor: Actor.t()
|
||||||
|
}
|
||||||
|
|
||||||
|
schema "medias" do
|
||||||
|
embeds_one(:file, File, on_replace: :update)
|
||||||
|
belongs_to(:actor, Actor)
|
||||||
|
has_many(:event_picture, Event, foreign_key: :picture_id)
|
||||||
|
many_to_many(:events, Event, join_through: "events_medias")
|
||||||
|
has_many(:posts_picture, Post, foreign_key: :picture_id)
|
||||||
|
many_to_many(:posts, Post, join_through: "posts_medias")
|
||||||
|
many_to_many(:comments, Comment, join_through: "comments_medias")
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc false
|
||||||
|
@spec changeset(t, map) :: Ecto.Changeset.t()
|
||||||
|
def changeset(%__MODULE__{} = media, attrs) do
|
||||||
|
media
|
||||||
|
|> cast(attrs, [:actor_id])
|
||||||
|
|> cast_embed(:file)
|
||||||
|
end
|
||||||
|
end
|
184
lib/mobilizon/medias/medias.ex
Normal file
184
lib/mobilizon/medias/medias.ex
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
defmodule Mobilizon.Medias do
|
||||||
|
@moduledoc """
|
||||||
|
The Media context.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
alias Ecto.Multi
|
||||||
|
|
||||||
|
alias Mobilizon.Actors.Actor
|
||||||
|
alias Mobilizon.Medias.{File, Media}
|
||||||
|
alias Mobilizon.Storage.{Page, Repo}
|
||||||
|
alias Mobilizon.Users.User
|
||||||
|
|
||||||
|
alias Mobilizon.Web.Upload
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gets a single media.
|
||||||
|
"""
|
||||||
|
@spec get_media(integer | String.t()) :: Media.t() | nil
|
||||||
|
def get_media(id), do: Repo.get(Media, id)
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gets a single media.
|
||||||
|
Raises `Ecto.NoResultsError` if the media does not exist.
|
||||||
|
"""
|
||||||
|
@spec get_media!(integer | String.t()) :: Media.t()
|
||||||
|
def get_media!(id), do: Repo.get!(Media, id)
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Get a media by its URL.
|
||||||
|
"""
|
||||||
|
@spec get_media_by_url(String.t()) :: Media.t() | nil
|
||||||
|
def get_media_by_url(url) do
|
||||||
|
url
|
||||||
|
|> media_by_url_query()
|
||||||
|
|> limit(1)
|
||||||
|
|> Repo.one()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Get an unattached media by it's URL
|
||||||
|
"""
|
||||||
|
def get_unattached_media_by_url(url) do
|
||||||
|
url
|
||||||
|
|> media_by_url_query()
|
||||||
|
|> join(:left, [m], e in assoc(m, :events))
|
||||||
|
|> join(:left, [m], ep in assoc(m, :event_picture))
|
||||||
|
|> join(:left, [m], p in assoc(m, :posts))
|
||||||
|
|> join(:left, [m], pp in assoc(m, :posts_picture))
|
||||||
|
|> join(:left, [m], c in assoc(m, :comments))
|
||||||
|
|> where([_m, e], is_nil(e.id))
|
||||||
|
|> where([_m, _e, ep], is_nil(ep.id))
|
||||||
|
|> where([_m, _e, _ep, p], is_nil(p.id))
|
||||||
|
|> where([_m, _e, _ep, _p, pp], is_nil(pp.id))
|
||||||
|
|> where([_m, _e, _ep, _p, _pp, c], is_nil(c.id))
|
||||||
|
|> limit(1)
|
||||||
|
|> Repo.one()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
List the paginated media for an actor
|
||||||
|
"""
|
||||||
|
@spec medias_for_actor(integer | String.t(), integer | nil, integer | nil) :: Page.t()
|
||||||
|
def medias_for_actor(actor_id, page, limit) do
|
||||||
|
actor_id
|
||||||
|
|> medias_for_actor_query()
|
||||||
|
|> Page.build_page(page, limit)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
List the paginated media for user
|
||||||
|
"""
|
||||||
|
@spec medias_for_user(integer | String.t(), integer | nil, integer | nil) :: Page.t()
|
||||||
|
def medias_for_user(user_id, page, limit) do
|
||||||
|
user_id
|
||||||
|
|> medias_for_user_query()
|
||||||
|
|> Page.build_page(page, limit)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Calculate the sum of media size used by the user
|
||||||
|
"""
|
||||||
|
@spec media_size_for_actor(integer | String.t()) :: integer()
|
||||||
|
def media_size_for_actor(actor_id) do
|
||||||
|
actor_id
|
||||||
|
|> medias_for_actor_query()
|
||||||
|
|> select([:file])
|
||||||
|
|> Repo.all()
|
||||||
|
|> Enum.map(& &1.file.size)
|
||||||
|
|> Enum.filter(& &1)
|
||||||
|
|> Enum.sum()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Calculate the sum of media size used by the user
|
||||||
|
"""
|
||||||
|
@spec media_size_for_user(integer | String.t()) :: integer()
|
||||||
|
def media_size_for_user(user_id) do
|
||||||
|
user_id
|
||||||
|
|> medias_for_user_query()
|
||||||
|
|> select([:file])
|
||||||
|
|> Repo.all()
|
||||||
|
|> Enum.map(& &1.file.size)
|
||||||
|
|> Enum.sum()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Creates a media.
|
||||||
|
"""
|
||||||
|
@spec create_media(map) :: {:ok, Media.t()} | {:error, Ecto.Changeset.t()}
|
||||||
|
def create_media(attrs \\ %{}) do
|
||||||
|
%Media{}
|
||||||
|
|> Media.changeset(attrs)
|
||||||
|
|> Repo.insert()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Updates a media.
|
||||||
|
"""
|
||||||
|
@spec update_media(Media.t(), map) :: {:ok, Media.t()} | {:error, Ecto.Changeset.t()}
|
||||||
|
def update_media(%Media{} = media, attrs) do
|
||||||
|
media
|
||||||
|
|> Media.changeset(attrs)
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Deletes a media.
|
||||||
|
"""
|
||||||
|
@spec delete_media(Media.t()) :: {:ok, Media.t()} | {:error, Ecto.Changeset.t()}
|
||||||
|
def delete_media(%Media{} = media, opts \\ []) do
|
||||||
|
transaction =
|
||||||
|
Multi.new()
|
||||||
|
|> Multi.delete(:media, media)
|
||||||
|
|> Multi.run(:remove, fn _repo, %{media: %Media{file: %File{url: url}} = media} ->
|
||||||
|
case Upload.remove(url) do
|
||||||
|
{:error, err} ->
|
||||||
|
if err =~ "doesn't exist" and Keyword.get(opts, :ignore_file_not_found, false) do
|
||||||
|
Logger.info("Deleting media and ignoring absent file.")
|
||||||
|
{:ok, media}
|
||||||
|
else
|
||||||
|
{:error, err}
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, media} ->
|
||||||
|
{:ok, media}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|> Repo.transaction()
|
||||||
|
|
||||||
|
case transaction do
|
||||||
|
{:ok, %{media: %Media{} = media}} ->
|
||||||
|
{:ok, media}
|
||||||
|
|
||||||
|
{:error, :remove, error, _} ->
|
||||||
|
{:error, error}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec media_by_url_query(String.t()) :: Ecto.Query.t()
|
||||||
|
defp media_by_url_query(url) do
|
||||||
|
from(
|
||||||
|
p in Media,
|
||||||
|
where: fragment("? @> ?", p.file, ~s|{"url": "#{url}"}|)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec medias_for_actor_query(integer() | String.t()) :: Ecto.Query.t()
|
||||||
|
defp medias_for_actor_query(actor_id) do
|
||||||
|
Media
|
||||||
|
|> join(:inner, [p], a in Actor, on: p.actor_id == a.id)
|
||||||
|
|> where([_p, a], a.id == ^actor_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec medias_for_user_query(integer() | String.t()) :: Ecto.Query.t()
|
||||||
|
defp medias_for_user_query(user_id) do
|
||||||
|
Media
|
||||||
|
|> join(:inner, [p], a in Actor, on: p.actor_id == a.id)
|
||||||
|
|> join(:inner, [_p, a], u in User, on: a.user_id == u.id)
|
||||||
|
|> where([_p, _a, u], u.id == ^user_id)
|
||||||
|
end
|
||||||
|
end
|
@ -22,8 +22,8 @@ defmodule Mobilizon.Posts.Post do
|
|||||||
alias Ecto.Changeset
|
alias Ecto.Changeset
|
||||||
alias Mobilizon.Actors.Actor
|
alias Mobilizon.Actors.Actor
|
||||||
alias Mobilizon.Events.Tag
|
alias Mobilizon.Events.Tag
|
||||||
alias Mobilizon.Media
|
alias Mobilizon.Medias
|
||||||
alias Mobilizon.Media.Picture
|
alias Mobilizon.Medias.Media
|
||||||
alias Mobilizon.Posts.Post.TitleSlug
|
alias Mobilizon.Posts.Post.TitleSlug
|
||||||
alias Mobilizon.Posts.PostVisibility
|
alias Mobilizon.Posts.PostVisibility
|
||||||
alias Mobilizon.Web.Endpoint
|
alias Mobilizon.Web.Endpoint
|
||||||
@ -41,7 +41,8 @@ defmodule Mobilizon.Posts.Post do
|
|||||||
publish_at: DateTime.t(),
|
publish_at: DateTime.t(),
|
||||||
author: Actor.t(),
|
author: Actor.t(),
|
||||||
attributed_to: Actor.t(),
|
attributed_to: Actor.t(),
|
||||||
picture: Picture.t(),
|
picture: Media.t(),
|
||||||
|
media: [Media.t()],
|
||||||
tags: [Tag.t()]
|
tags: [Tag.t()]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,6 +61,7 @@ defmodule Mobilizon.Posts.Post do
|
|||||||
belongs_to(:attributed_to, Actor)
|
belongs_to(:attributed_to, Actor)
|
||||||
belongs_to(:picture, Picture, on_replace: :update)
|
belongs_to(:picture, Picture, on_replace: :update)
|
||||||
many_to_many(:tags, Tag, join_through: "posts_tags", on_replace: :delete)
|
many_to_many(:tags, Tag, join_through: "posts_tags", on_replace: :delete)
|
||||||
|
many_to_many(:media, Media, join_through: "posts_medias", on_replace: :delete)
|
||||||
|
|
||||||
timestamps(type: :utc_datetime)
|
timestamps(type: :utc_datetime)
|
||||||
end
|
end
|
||||||
@ -82,6 +84,7 @@ defmodule Mobilizon.Posts.Post do
|
|||||||
post
|
post
|
||||||
|> cast(attrs, @attrs)
|
|> cast(attrs, @attrs)
|
||||||
|> maybe_generate_id()
|
|> maybe_generate_id()
|
||||||
|
|> put_assoc(:media, Map.get(attrs, :media, []))
|
||||||
|> put_tags(attrs)
|
|> put_tags(attrs)
|
||||||
|> maybe_put_publish_date()
|
|> maybe_put_publish_date()
|
||||||
|> put_picture(attrs)
|
|> put_picture(attrs)
|
||||||
@ -146,8 +149,8 @@ defmodule Mobilizon.Posts.Post do
|
|||||||
# In case the provided picture is an existing one
|
# In case the provided picture is an existing one
|
||||||
@spec put_picture(Changeset.t(), map) :: Changeset.t()
|
@spec put_picture(Changeset.t(), map) :: Changeset.t()
|
||||||
defp put_picture(%Changeset{} = changeset, %{picture: %{picture_id: id} = _picture}) do
|
defp put_picture(%Changeset{} = changeset, %{picture: %{picture_id: id} = _picture}) do
|
||||||
case Media.get_picture!(id) do
|
case Medias.get_media!(id) do
|
||||||
%Picture{} = picture ->
|
%Media{} = picture ->
|
||||||
put_assoc(changeset, :picture, picture)
|
put_assoc(changeset, :picture, picture)
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
|
@ -103,7 +103,7 @@ defmodule Mobilizon.Posts do
|
|||||||
@spec update_post(Post.t(), map) :: {:ok, Post.t()} | {:error, Ecto.Changeset.t()}
|
@spec update_post(Post.t(), map) :: {:ok, Post.t()} | {:error, Ecto.Changeset.t()}
|
||||||
def update_post(%Post{} = post, attrs) do
|
def update_post(%Post{} = post, attrs) do
|
||||||
post
|
post
|
||||||
|> Repo.preload(:tags)
|
|> Repo.preload([:tags, :media])
|
||||||
|> Post.changeset(attrs)
|
|> Post.changeset(attrs)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
end
|
||||||
|
60
lib/service/clean_orphan_media.ex
Normal file
60
lib/service/clean_orphan_media.ex
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
defmodule Mobilizon.Service.CleanOrphanMedia do
|
||||||
|
@moduledoc """
|
||||||
|
Service to clean orphan media
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias Mobilizon.Actors.Actor
|
||||||
|
alias Mobilizon.Medias
|
||||||
|
alias Mobilizon.Medias.Media
|
||||||
|
alias Mobilizon.Storage.Repo
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
@grace_period Mobilizon.Config.get([:instance, :orphan_upload_grace_period_hours], 48)
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Clean orphan media
|
||||||
|
|
||||||
|
Remove media that is not attached to an entity, such as media uploads that were never used in entities.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
* `grace_period` how old in hours can the media be before it's taken into account for deletion
|
||||||
|
* `dry_run` just return the media that would have been deleted, don't actually delete it
|
||||||
|
"""
|
||||||
|
@spec clean(Keyword.t()) :: {:ok, list(Media.t())} | {:error, String.t()}
|
||||||
|
def clean(opts \\ []) do
|
||||||
|
medias = find_media(opts)
|
||||||
|
|
||||||
|
if Keyword.get(opts, :dry_run, false) do
|
||||||
|
{:ok, medias}
|
||||||
|
else
|
||||||
|
Enum.each(medias, fn media ->
|
||||||
|
Medias.delete_media(media, ignore_file_not_found: true)
|
||||||
|
end)
|
||||||
|
|
||||||
|
{:ok, medias}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec find_media(Keyword.t()) :: list(Media.t())
|
||||||
|
defp find_media(opts) do
|
||||||
|
grace_period = Keyword.get(opts, :grace_period, @grace_period)
|
||||||
|
expiration_date = DateTime.add(DateTime.utc_now(), grace_period * -3600)
|
||||||
|
|
||||||
|
Media
|
||||||
|
|> where([m], m.inserted_at < ^expiration_date)
|
||||||
|
|> join(:inner, [m], a in Actor)
|
||||||
|
|> where([_m, a], is_nil(a.domain))
|
||||||
|
|> join(:left, [m], e in assoc(m, :events))
|
||||||
|
|> join(:left, [m], ep in assoc(m, :event_picture))
|
||||||
|
|> join(:left, [m], p in assoc(m, :posts))
|
||||||
|
|> join(:left, [m], pp in assoc(m, :posts_picture))
|
||||||
|
|> join(:left, [m], c in assoc(m, :comments))
|
||||||
|
|> where([_m, _a, e], is_nil(e.id))
|
||||||
|
|> where([_m, _a, _e, ep], is_nil(ep.id))
|
||||||
|
|> where([_m, _a, _e, _ep, p], is_nil(p.id))
|
||||||
|
|> where([_m, _a, _e, _ep, _p, pp], is_nil(pp.id))
|
||||||
|
|> where([_m, _a, _e, _ep, _p, _pp, c], is_nil(c.id))
|
||||||
|
|> distinct(true)
|
||||||
|
|> Repo.all()
|
||||||
|
end
|
||||||
|
end
|
31
lib/service/workers/clean_orphan_media_worker.ex
Normal file
31
lib/service/workers/clean_orphan_media_worker.ex
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
defmodule Mobilizon.Service.Workers.CleanOrphanMediaWorker do
|
||||||
|
@moduledoc """
|
||||||
|
Worker to clean orphan media
|
||||||
|
"""
|
||||||
|
|
||||||
|
use Oban.Worker, queue: "background"
|
||||||
|
alias Mobilizon.Service.CleanOrphanMedia
|
||||||
|
|
||||||
|
@grace_period Mobilizon.Config.get([:instance, :orphan_upload_grace_period_hours], 48)
|
||||||
|
|
||||||
|
@impl Oban.Worker
|
||||||
|
def perform(%Job{}) do
|
||||||
|
if Mobilizon.Config.get!([:instance, :remove_orphan_uploads]) and should_perform?() do
|
||||||
|
CleanOrphanMedia.clean()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec should_perform? :: boolean()
|
||||||
|
defp should_perform? do
|
||||||
|
case Cachex.get(:key_value, "last_media_cleanup") do
|
||||||
|
{:ok, %DateTime{} = last_media_cleanup} ->
|
||||||
|
DateTime.compare(
|
||||||
|
last_media_cleanup,
|
||||||
|
DateTime.add(DateTime.utc_now(), @grace_period * -3600)
|
||||||
|
) == :lt
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -72,7 +72,10 @@ defmodule Mobilizon.Web.Plugs.UploadedMedia do
|
|||||||
conn
|
conn
|
||||||
else
|
else
|
||||||
conn
|
conn
|
||||||
|> send_resp(404, "Not found")
|
|> delete_resp_header("content-disposition")
|
||||||
|
|> put_status(404)
|
||||||
|
|> Phoenix.Controller.put_view(Mobilizon.Web.ErrorView)
|
||||||
|
|> Phoenix.Controller.render("404.html")
|
||||||
|> halt()
|
|> halt()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
defmodule Mobilizon.Web.Upload.Filter.Optimize do
|
defmodule Mobilizon.Web.Upload.Filter.Optimize do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Handle picture optimizations
|
Handle media optimizations
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@behaviour Mobilizon.Web.Upload.Filter
|
@behaviour Mobilizon.Web.Upload.Filter
|
||||||
|
12
mix.exs
12
mix.exs
@ -236,9 +236,9 @@ defmodule Mobilizon.Mixfile do
|
|||||||
Mobilizon.Events.Tag.TitleSlug,
|
Mobilizon.Events.Tag.TitleSlug,
|
||||||
Mobilizon.Events.Tag.TitleSlug.Type,
|
Mobilizon.Events.Tag.TitleSlug.Type,
|
||||||
Mobilizon.Events.TagRelation,
|
Mobilizon.Events.TagRelation,
|
||||||
Mobilizon.Media,
|
Mobilizon.Medias,
|
||||||
Mobilizon.Media.File,
|
Mobilizon.Medias.File,
|
||||||
Mobilizon.Media.Picture,
|
Mobilizon.Medias.Media,
|
||||||
Mobilizon.Mention,
|
Mobilizon.Mention,
|
||||||
Mobilizon.Reports,
|
Mobilizon.Reports,
|
||||||
Mobilizon.Reports.Note,
|
Mobilizon.Reports.Note,
|
||||||
@ -328,7 +328,7 @@ defmodule Mobilizon.Mixfile do
|
|||||||
Mobilizon.GraphQL.Resolvers.Group,
|
Mobilizon.GraphQL.Resolvers.Group,
|
||||||
Mobilizon.GraphQL.Resolvers.Member,
|
Mobilizon.GraphQL.Resolvers.Member,
|
||||||
Mobilizon.GraphQL.Resolvers.Person,
|
Mobilizon.GraphQL.Resolvers.Person,
|
||||||
Mobilizon.GraphQL.Resolvers.Picture,
|
Mobilizon.GraphQL.Resolvers.Media,
|
||||||
Mobilizon.GraphQL.Resolvers.Report,
|
Mobilizon.GraphQL.Resolvers.Report,
|
||||||
Mobilizon.GraphQL.Resolvers.Search,
|
Mobilizon.GraphQL.Resolvers.Search,
|
||||||
Mobilizon.GraphQL.Resolvers.Tag,
|
Mobilizon.GraphQL.Resolvers.Tag,
|
||||||
@ -347,7 +347,7 @@ defmodule Mobilizon.Mixfile do
|
|||||||
Mobilizon.GraphQL.Schema.EventType,
|
Mobilizon.GraphQL.Schema.EventType,
|
||||||
Mobilizon.GraphQL.Schema.Events.FeedTokenType,
|
Mobilizon.GraphQL.Schema.Events.FeedTokenType,
|
||||||
Mobilizon.GraphQL.Schema.Events.ParticipantType,
|
Mobilizon.GraphQL.Schema.Events.ParticipantType,
|
||||||
Mobilizon.GraphQL.Schema.PictureType,
|
Mobilizon.GraphQL.Schema.MediaType,
|
||||||
Mobilizon.GraphQL.Schema.ReportType,
|
Mobilizon.GraphQL.Schema.ReportType,
|
||||||
Mobilizon.GraphQL.Schema.SearchType,
|
Mobilizon.GraphQL.Schema.SearchType,
|
||||||
Mobilizon.GraphQL.Schema.SortType,
|
Mobilizon.GraphQL.Schema.SortType,
|
||||||
@ -374,7 +374,7 @@ defmodule Mobilizon.Mixfile do
|
|||||||
Mobilizon.Federation.ActivityStream.Converter.Flag,
|
Mobilizon.Federation.ActivityStream.Converter.Flag,
|
||||||
Mobilizon.Federation.ActivityStream.Converter.Follower,
|
Mobilizon.Federation.ActivityStream.Converter.Follower,
|
||||||
Mobilizon.Federation.ActivityStream.Converter.Participant,
|
Mobilizon.Federation.ActivityStream.Converter.Participant,
|
||||||
Mobilizon.Federation.ActivityStream.Converter.Picture,
|
Mobilizon.Federation.ActivityStream.Converter.Media,
|
||||||
Mobilizon.Federation.ActivityStream.Converter.Tombstone,
|
Mobilizon.Federation.ActivityStream.Converter.Tombstone,
|
||||||
Mobilizon.Federation.ActivityStream.Converter.Utils,
|
Mobilizon.Federation.ActivityStream.Converter.Utils,
|
||||||
Mobilizon.Federation.HTTPSignatures.Signature,
|
Mobilizon.Federation.HTTPSignatures.Signature,
|
||||||
|
22
priv/repo/migrations/20201124094655_add_media_tables.exs
Normal file
22
priv/repo/migrations/20201124094655_add_media_tables.exs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
defmodule Mobilizon.Storage.Repo.Migrations.AddMediaTables do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def change do
|
||||||
|
rename(table(:pictures), to: table(:medias))
|
||||||
|
|
||||||
|
create table(:events_medias, primary_key: false) do
|
||||||
|
add(:event_id, references(:events, on_delete: :delete_all), primary_key: true)
|
||||||
|
add(:media_id, references(:medias, on_delete: :delete_all), primary_key: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
create table(:posts_medias, primary_key: false) do
|
||||||
|
add(:post_id, references(:posts, on_delete: :delete_all, type: :uuid), primary_key: true)
|
||||||
|
add(:media_id, references(:medias, on_delete: :delete_all), primary_key: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
create table(:comments_medias, primary_key: false) do
|
||||||
|
add(:comment_id, references(:comments, on_delete: :delete_all), primary_key: true)
|
||||||
|
add(:media_id, references(:medias, on_delete: :delete_all), primary_key: true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -526,7 +526,7 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||||||
organizer_actor_id: "#{actor.id}",
|
organizer_actor_id: "#{actor.id}",
|
||||||
category: "birthday",
|
category: "birthday",
|
||||||
picture: {
|
picture: {
|
||||||
picture: {
|
media: {
|
||||||
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",
|
||||||
@ -569,13 +569,13 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||||||
actor: actor,
|
actor: actor,
|
||||||
user: user
|
user: user
|
||||||
} do
|
} do
|
||||||
picture = %{name: "my pic", alt: "represents something", file: "picture.png"}
|
media = %{name: "my pic", alt: "represents something", file: "picture.png"}
|
||||||
|
|
||||||
mutation = """
|
mutation = """
|
||||||
mutation { uploadPicture(
|
mutation { uploadMedia (
|
||||||
name: "#{picture.name}",
|
name: "#{media.name}",
|
||||||
alt: "#{picture.alt}",
|
alt: "#{media.alt}",
|
||||||
file: "#{picture.file}"
|
file: "#{media.file}"
|
||||||
) {
|
) {
|
||||||
id,
|
id,
|
||||||
url,
|
url,
|
||||||
@ -586,9 +586,9 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||||||
|
|
||||||
map = %{
|
map = %{
|
||||||
"query" => mutation,
|
"query" => mutation,
|
||||||
picture.file => %Plug.Upload{
|
media.file => %Plug.Upload{
|
||||||
path: "test/fixtures/picture.png",
|
path: "test/fixtures/picture.png",
|
||||||
filename: picture.file
|
filename: media.file
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -601,8 +601,8 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||||||
map
|
map
|
||||||
)
|
)
|
||||||
|
|
||||||
assert json_response(res, 200)["data"]["uploadPicture"]["name"] == picture.name
|
assert json_response(res, 200)["data"]["uploadMedia"]["name"] == media.name
|
||||||
picture_id = json_response(res, 200)["data"]["uploadPicture"]["id"]
|
media_id = json_response(res, 200)["data"]["uploadMedia"]["id"]
|
||||||
|
|
||||||
mutation = """
|
mutation = """
|
||||||
mutation {
|
mutation {
|
||||||
@ -615,7 +615,7 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||||||
organizer_actor_id: "#{actor.id}",
|
organizer_actor_id: "#{actor.id}",
|
||||||
category: "birthday",
|
category: "birthday",
|
||||||
picture: {
|
picture: {
|
||||||
picture_id: "#{picture_id}"
|
media_id: "#{media_id}"
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
title,
|
title,
|
||||||
@ -635,7 +635,7 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||||||
|
|
||||||
assert json_response(res, 200)["data"]["createEvent"]["title"] == "come to my event"
|
assert json_response(res, 200)["data"]["createEvent"]["title"] == "come to my event"
|
||||||
|
|
||||||
assert json_response(res, 200)["data"]["createEvent"]["picture"]["name"] == picture.name
|
assert json_response(res, 200)["data"]["createEvent"]["picture"]["name"] == media.name
|
||||||
|
|
||||||
assert json_response(res, 200)["data"]["createEvent"]["picture"]["url"]
|
assert json_response(res, 200)["data"]["createEvent"]["picture"]["url"]
|
||||||
end
|
end
|
||||||
@ -943,7 +943,7 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||||||
event_id: #{event.id},
|
event_id: #{event.id},
|
||||||
category: "birthday",
|
category: "birthday",
|
||||||
picture: {
|
picture: {
|
||||||
picture: {
|
media: {
|
||||||
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",
|
||||||
@ -1349,7 +1349,7 @@ defmodule Mobilizon.Web.Resolvers.EventTest do
|
|||||||
test "delete_event/3 should check the event can be deleted by the user", %{
|
test "delete_event/3 should check the event can be deleted by the user", %{
|
||||||
conn: conn,
|
conn: conn,
|
||||||
user: user,
|
user: user,
|
||||||
actor: actor
|
actor: _actor
|
||||||
} do
|
} do
|
||||||
actor2 = insert(:actor)
|
actor2 = insert(:actor)
|
||||||
event = insert(:event, organizer_actor: actor2)
|
event = insert(:event, organizer_actor: actor2)
|
||||||
|
@ -218,8 +218,8 @@ defmodule Mobilizon.Web.Resolvers.GroupTest do
|
|||||||
$id: ID!
|
$id: ID!
|
||||||
$name: String
|
$name: String
|
||||||
$summary: String
|
$summary: String
|
||||||
$avatar: PictureInput
|
$avatar: MediaInput
|
||||||
$banner: PictureInput
|
$banner: MediaInput
|
||||||
$visibility: GroupVisibility
|
$visibility: GroupVisibility
|
||||||
$physicalAddress: AddressInput
|
$physicalAddress: AddressInput
|
||||||
) {
|
) {
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
defmodule Mobilizon.GraphQL.Resolvers.PictureTest do
|
defmodule Mobilizon.GraphQL.Resolvers.MediaTest do
|
||||||
use Mobilizon.Web.ConnCase
|
use Mobilizon.Web.ConnCase
|
||||||
use Bamboo.Test
|
use Bamboo.Test
|
||||||
|
|
||||||
import Mobilizon.Factory
|
import Mobilizon.Factory
|
||||||
|
|
||||||
alias Mobilizon.Media.Picture
|
alias Mobilizon.Medias.Media
|
||||||
|
|
||||||
alias Mobilizon.GraphQL.AbsintheHelpers
|
alias Mobilizon.GraphQL.AbsintheHelpers
|
||||||
|
|
||||||
alias Mobilizon.Web.Endpoint
|
alias Mobilizon.Web.Endpoint
|
||||||
|
|
||||||
@default_picture_details %{name: "my pic", alt: "represents something", file: "picture.png"}
|
@default_media_details %{name: "my pic", alt: "represents something", file: "picture.png"}
|
||||||
@default_picture_path "test/fixtures/picture.png"
|
@default_media_path "test/fixtures/picture.png"
|
||||||
|
|
||||||
setup %{conn: conn} do
|
setup %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
@ -20,9 +20,9 @@ defmodule Mobilizon.GraphQL.Resolvers.PictureTest do
|
|||||||
{:ok, conn: conn, user: user, actor: actor}
|
{:ok, conn: conn, user: user, actor: actor}
|
||||||
end
|
end
|
||||||
|
|
||||||
@picture_query """
|
@media_query """
|
||||||
query Picture($id: ID!) {
|
query Media($id: ID!) {
|
||||||
picture(id: $id) {
|
media(id: $id) {
|
||||||
id
|
id
|
||||||
name,
|
name,
|
||||||
alt,
|
alt,
|
||||||
@ -33,9 +33,9 @@ defmodule Mobilizon.GraphQL.Resolvers.PictureTest do
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@upload_picture_mutation """
|
@upload_media_mutation """
|
||||||
mutation UploadPicture($name: String!, $alt: String, $file: Upload!) {
|
mutation UploadMedia($name: String!, $alt: String, $file: Upload!) {
|
||||||
uploadPicture(
|
uploadMedia(
|
||||||
name: $name
|
name: $name
|
||||||
alt: $alt
|
alt: $alt
|
||||||
file: $file
|
file: $file
|
||||||
@ -48,44 +48,44 @@ defmodule Mobilizon.GraphQL.Resolvers.PictureTest do
|
|||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
describe "Resolver: Get picture" do
|
describe "Resolver: Get media" do
|
||||||
test "picture/3 returns the information on a picture", %{conn: conn} do
|
test "media/3 returns the information on a media", %{conn: conn} do
|
||||||
%Picture{id: id} = picture = insert(:picture)
|
%Media{id: id} = media = insert(:media)
|
||||||
|
|
||||||
res =
|
res =
|
||||||
conn
|
conn
|
||||||
|> AbsintheHelpers.graphql_query(query: @picture_query, variables: %{id: id})
|
|> AbsintheHelpers.graphql_query(query: @media_query, variables: %{id: id})
|
||||||
|
|
||||||
assert res["data"]["picture"]["name"] == picture.file.name
|
assert res["data"]["media"]["name"] == media.file.name
|
||||||
|
|
||||||
assert res["data"]["picture"]["content_type"] ==
|
assert res["data"]["media"]["content_type"] ==
|
||||||
picture.file.content_type
|
media.file.content_type
|
||||||
|
|
||||||
assert res["data"]["picture"]["size"] == 13_120
|
assert res["data"]["media"]["size"] == 13_120
|
||||||
|
|
||||||
assert res["data"]["picture"]["url"] =~ Endpoint.url()
|
assert res["data"]["media"]["url"] =~ Endpoint.url()
|
||||||
end
|
end
|
||||||
|
|
||||||
test "picture/3 returns nothing on a non-existent picture", %{conn: conn} do
|
test "media/3 returns nothing on a non-existent media", %{conn: conn} do
|
||||||
res =
|
res =
|
||||||
conn
|
conn
|
||||||
|> AbsintheHelpers.graphql_query(query: @picture_query, variables: %{id: 3})
|
|> AbsintheHelpers.graphql_query(query: @media_query, variables: %{id: 3})
|
||||||
|
|
||||||
assert hd(res["errors"])["message"] == "Resource not found"
|
assert hd(res["errors"])["message"] == "Resource not found"
|
||||||
assert hd(res["errors"])["status_code"] == 404
|
assert hd(res["errors"])["status_code"] == 404
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "Resolver: Upload picture" do
|
describe "Resolver: Upload media" do
|
||||||
test "upload_picture/3 uploads a new picture", %{conn: conn, user: user} do
|
test "upload_media/3 uploads a new media", %{conn: conn, user: user} do
|
||||||
picture = %{name: "my pic", alt: "represents something", file: "picture.png"}
|
media = %{name: "my pic", alt: "represents something", file: "picture.png"}
|
||||||
|
|
||||||
map = %{
|
map = %{
|
||||||
"query" => @upload_picture_mutation,
|
"query" => @upload_media_mutation,
|
||||||
"variables" => picture,
|
"variables" => media,
|
||||||
picture.file => %Plug.Upload{
|
media.file => %Plug.Upload{
|
||||||
path: "test/fixtures/picture.png",
|
path: "test/fixtures/picture.png",
|
||||||
filename: picture.file
|
filename: media.file
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,21 +99,21 @@ defmodule Mobilizon.GraphQL.Resolvers.PictureTest do
|
|||||||
)
|
)
|
||||||
|> json_response(200)
|
|> json_response(200)
|
||||||
|
|
||||||
assert res["data"]["uploadPicture"]["name"] == picture.name
|
assert res["data"]["uploadMedia"]["name"] == media.name
|
||||||
assert res["data"]["uploadPicture"]["content_type"] == "image/png"
|
assert res["data"]["uploadMedia"]["content_type"] == "image/png"
|
||||||
assert res["data"]["uploadPicture"]["size"] == 10_097
|
assert res["data"]["uploadMedia"]["size"] == 10_097
|
||||||
assert res["data"]["uploadPicture"]["url"]
|
assert res["data"]["uploadMedia"]["url"]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "upload_picture/3 forbids uploading if no auth", %{conn: conn} do
|
test "upload_media/3 forbids uploading if no auth", %{conn: conn} do
|
||||||
picture = %{name: "my pic", alt: "represents something", file: "picture.png"}
|
media = %{name: "my pic", alt: "represents something", file: "picture.png"}
|
||||||
|
|
||||||
map = %{
|
map = %{
|
||||||
"query" => @upload_picture_mutation,
|
"query" => @upload_media_mutation,
|
||||||
"variables" => picture,
|
"variables" => media,
|
||||||
picture.file => %Plug.Upload{
|
media.file => %Plug.Upload{
|
||||||
path: "test/fixtures/picture.png",
|
path: "test/fixtures/picture.png",
|
||||||
filename: picture.file
|
filename: media.file
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,43 +130,43 @@ defmodule Mobilizon.GraphQL.Resolvers.PictureTest do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "Resolver: Remove picture" do
|
describe "Resolver: Remove media" do
|
||||||
@remove_picture_mutation """
|
@remove_media_mutation """
|
||||||
mutation RemovePicture($id: ID!) {
|
mutation RemoveMedia($id: ID!) {
|
||||||
removePicture(id: $id) {
|
removeMedia(id: $id) {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
test "Removes a previously uploaded picture", %{conn: conn, user: user, actor: actor} do
|
test "Removes a previously uploaded media", %{conn: conn, user: user, actor: actor} do
|
||||||
%Picture{id: picture_id} = insert(:picture, actor: actor)
|
%Media{id: media_id} = insert(:media, actor: actor)
|
||||||
|
|
||||||
res =
|
res =
|
||||||
conn
|
conn
|
||||||
|> auth_conn(user)
|
|> auth_conn(user)
|
||||||
|> AbsintheHelpers.graphql_query(
|
|> AbsintheHelpers.graphql_query(
|
||||||
query: @remove_picture_mutation,
|
query: @remove_media_mutation,
|
||||||
variables: %{id: picture_id}
|
variables: %{id: media_id}
|
||||||
)
|
)
|
||||||
|
|
||||||
assert is_nil(res["errors"])
|
assert is_nil(res["errors"])
|
||||||
assert res["data"]["removePicture"]["id"] == to_string(picture_id)
|
assert res["data"]["removeMedia"]["id"] == to_string(media_id)
|
||||||
|
|
||||||
res =
|
res =
|
||||||
conn
|
conn
|
||||||
|> AbsintheHelpers.graphql_query(query: @picture_query, variables: %{id: picture_id})
|
|> AbsintheHelpers.graphql_query(query: @media_query, variables: %{id: media_id})
|
||||||
|
|
||||||
assert hd(res["errors"])["message"] == "Resource not found"
|
assert hd(res["errors"])["message"] == "Resource not found"
|
||||||
assert hd(res["errors"])["status_code"] == 404
|
assert hd(res["errors"])["status_code"] == 404
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Removes nothing if picture is not found", %{conn: conn, user: user} do
|
test "Removes nothing if media is not found", %{conn: conn, user: user} do
|
||||||
res =
|
res =
|
||||||
conn
|
conn
|
||||||
|> auth_conn(user)
|
|> auth_conn(user)
|
||||||
|> AbsintheHelpers.graphql_query(
|
|> AbsintheHelpers.graphql_query(
|
||||||
query: @remove_picture_mutation,
|
query: @remove_media_mutation,
|
||||||
variables: %{id: 400}
|
variables: %{id: 400}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -174,14 +174,14 @@ defmodule Mobilizon.GraphQL.Resolvers.PictureTest do
|
|||||||
assert hd(res["errors"])["status_code"] == 404
|
assert hd(res["errors"])["status_code"] == 404
|
||||||
end
|
end
|
||||||
|
|
||||||
test "Removes nothing if picture if not logged-in", %{conn: conn, actor: actor} do
|
test "Removes nothing if media if not logged-in", %{conn: conn, actor: actor} do
|
||||||
%Picture{id: picture_id} = insert(:picture, actor: actor)
|
%Media{id: media_id} = insert(:media, actor: actor)
|
||||||
|
|
||||||
res =
|
res =
|
||||||
conn
|
conn
|
||||||
|> AbsintheHelpers.graphql_query(
|
|> AbsintheHelpers.graphql_query(
|
||||||
query: @remove_picture_mutation,
|
query: @remove_media_mutation,
|
||||||
variables: %{id: picture_id}
|
variables: %{id: media_id}
|
||||||
)
|
)
|
||||||
|
|
||||||
assert hd(res["errors"])["message"] == "You need to be logged in"
|
assert hd(res["errors"])["message"] == "You need to be logged in"
|
||||||
@ -210,8 +210,8 @@ defmodule Mobilizon.GraphQL.Resolvers.PictureTest do
|
|||||||
|
|
||||||
assert res["data"]["loggedPerson"]["mediaSize"] == 0
|
assert res["data"]["loggedPerson"]["mediaSize"] == 0
|
||||||
|
|
||||||
res = upload_picture(conn, user)
|
res = upload_media(conn, user)
|
||||||
assert res["data"]["uploadPicture"]["size"] == 10_097
|
assert res["data"]["uploadMedia"]["size"] == 10_097
|
||||||
|
|
||||||
res =
|
res =
|
||||||
conn
|
conn
|
||||||
@ -221,14 +221,14 @@ defmodule Mobilizon.GraphQL.Resolvers.PictureTest do
|
|||||||
assert res["data"]["loggedPerson"]["mediaSize"] == 10_097
|
assert res["data"]["loggedPerson"]["mediaSize"] == 10_097
|
||||||
|
|
||||||
res =
|
res =
|
||||||
upload_picture(
|
upload_media(
|
||||||
conn,
|
conn,
|
||||||
user,
|
user,
|
||||||
"test/fixtures/image.jpg",
|
"test/fixtures/image.jpg",
|
||||||
Map.put(@default_picture_details, :file, "image.jpg")
|
Map.put(@default_media_details, :file, "image.jpg")
|
||||||
)
|
)
|
||||||
|
|
||||||
assert res["data"]["uploadPicture"]["size"] == 13_227
|
assert res["data"]["uploadMedia"]["size"] == 13_227
|
||||||
|
|
||||||
res =
|
res =
|
||||||
conn
|
conn
|
||||||
@ -266,7 +266,7 @@ defmodule Mobilizon.GraphQL.Resolvers.PictureTest do
|
|||||||
assert is_nil(res["errors"])
|
assert is_nil(res["errors"])
|
||||||
assert hd(res["data"]["persons"]["elements"])["mediaSize"] == 0
|
assert hd(res["data"]["persons"]["elements"])["mediaSize"] == 0
|
||||||
|
|
||||||
upload_picture(conn, user)
|
upload_media(conn, user)
|
||||||
|
|
||||||
res =
|
res =
|
||||||
conn
|
conn
|
||||||
@ -355,8 +355,8 @@ defmodule Mobilizon.GraphQL.Resolvers.PictureTest do
|
|||||||
assert res["errors"] == nil
|
assert res["errors"] == nil
|
||||||
assert res["data"]["loggedUser"]["mediaSize"] == 0
|
assert res["data"]["loggedUser"]["mediaSize"] == 0
|
||||||
|
|
||||||
res = upload_picture(conn, user)
|
res = upload_media(conn, user)
|
||||||
assert res["data"]["uploadPicture"]["size"] == 10_097
|
assert res["data"]["uploadMedia"]["size"] == 10_097
|
||||||
|
|
||||||
res =
|
res =
|
||||||
conn
|
conn
|
||||||
@ -366,14 +366,14 @@ defmodule Mobilizon.GraphQL.Resolvers.PictureTest do
|
|||||||
assert res["data"]["loggedUser"]["mediaSize"] == 10_097
|
assert res["data"]["loggedUser"]["mediaSize"] == 10_097
|
||||||
|
|
||||||
res =
|
res =
|
||||||
upload_picture(
|
upload_media(
|
||||||
conn,
|
conn,
|
||||||
user,
|
user,
|
||||||
"test/fixtures/image.jpg",
|
"test/fixtures/image.jpg",
|
||||||
Map.put(@default_picture_details, :file, "image.jpg")
|
Map.put(@default_media_details, :file, "image.jpg")
|
||||||
)
|
)
|
||||||
|
|
||||||
assert res["data"]["uploadPicture"]["size"] == 13_227
|
assert res["data"]["uploadMedia"]["size"] == 13_227
|
||||||
|
|
||||||
res =
|
res =
|
||||||
conn
|
conn
|
||||||
@ -393,14 +393,14 @@ defmodule Mobilizon.GraphQL.Resolvers.PictureTest do
|
|||||||
assert is_nil(res["errors"])
|
assert is_nil(res["errors"])
|
||||||
|
|
||||||
res =
|
res =
|
||||||
upload_picture(
|
upload_media(
|
||||||
conn,
|
conn,
|
||||||
user,
|
user,
|
||||||
"test/fixtures/image.jpg",
|
"test/fixtures/image.jpg",
|
||||||
Map.put(@default_picture_details, :file, "image.jpg")
|
Map.put(@default_media_details, :file, "image.jpg")
|
||||||
)
|
)
|
||||||
|
|
||||||
assert res["data"]["uploadPicture"]["size"] == 13_227
|
assert res["data"]["uploadMedia"]["size"] == 13_227
|
||||||
|
|
||||||
res =
|
res =
|
||||||
conn
|
conn
|
||||||
@ -438,9 +438,9 @@ defmodule Mobilizon.GraphQL.Resolvers.PictureTest do
|
|||||||
assert is_nil(res["errors"])
|
assert is_nil(res["errors"])
|
||||||
assert hd(res["data"]["users"]["elements"])["mediaSize"] == 0
|
assert hd(res["data"]["users"]["elements"])["mediaSize"] == 0
|
||||||
|
|
||||||
res = upload_picture(conn, user)
|
res = upload_media(conn, user)
|
||||||
assert is_nil(res["errors"])
|
assert is_nil(res["errors"])
|
||||||
assert res["data"]["uploadPicture"]["size"] == 10_097
|
assert res["data"]["uploadMedia"]["size"] == 10_097
|
||||||
|
|
||||||
res =
|
res =
|
||||||
conn
|
conn
|
||||||
@ -463,19 +463,19 @@ defmodule Mobilizon.GraphQL.Resolvers.PictureTest do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec upload_picture(Plug.Conn.t(), Mobilizon.Users.User.t(), String.t(), map()) :: map()
|
@spec upload_media(Plug.Conn.t(), Mobilizon.Users.User.t(), String.t(), map()) :: map()
|
||||||
defp upload_picture(
|
defp upload_media(
|
||||||
conn,
|
conn,
|
||||||
user,
|
user,
|
||||||
picture_path \\ @default_picture_path,
|
media_path \\ @default_media_path,
|
||||||
picture_details \\ @default_picture_details
|
media_details \\ @default_media_details
|
||||||
) do
|
) do
|
||||||
map = %{
|
map = %{
|
||||||
"query" => @upload_picture_mutation,
|
"query" => @upload_media_mutation,
|
||||||
"variables" => picture_details,
|
"variables" => media_details,
|
||||||
picture_details.file => %Plug.Upload{
|
media_details.file => %Plug.Upload{
|
||||||
path: picture_path,
|
path: media_path,
|
||||||
filename: picture_details.file
|
filename: media_details.file
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -205,7 +205,7 @@ defmodule Mobilizon.GraphQL.Resolvers.PersonTest do
|
|||||||
name: "secret person",
|
name: "secret person",
|
||||||
summary: "no-one will know who I am",
|
summary: "no-one will know who I am",
|
||||||
banner: {
|
banner: {
|
||||||
picture: {
|
media: {
|
||||||
file: "landscape.jpg",
|
file: "landscape.jpg",
|
||||||
name: "irish landscape",
|
name: "irish landscape",
|
||||||
alt: "The beautiful atlantic way"
|
alt: "The beautiful atlantic way"
|
||||||
@ -274,7 +274,7 @@ defmodule Mobilizon.GraphQL.Resolvers.PersonTest do
|
|||||||
name: "riri updated",
|
name: "riri updated",
|
||||||
summary: "summary updated",
|
summary: "summary updated",
|
||||||
banner: {
|
banner: {
|
||||||
picture: {
|
media: {
|
||||||
file: "landscape.jpg",
|
file: "landscape.jpg",
|
||||||
name: "irish landscape",
|
name: "irish landscape",
|
||||||
alt: "The beautiful atlantic way"
|
alt: "The beautiful atlantic way"
|
||||||
|
@ -9,7 +9,7 @@ defmodule Mobilizon.ActorsTest do
|
|||||||
alias Mobilizon.Actors.{Actor, Bot, Follower, Member}
|
alias Mobilizon.Actors.{Actor, Bot, Follower, Member}
|
||||||
alias Mobilizon.Discussions.Comment
|
alias Mobilizon.Discussions.Comment
|
||||||
alias Mobilizon.Events.Event
|
alias Mobilizon.Events.Event
|
||||||
alias Mobilizon.Media.File, as: FileModel
|
alias Mobilizon.Medias.File, as: FileModel
|
||||||
alias Mobilizon.Service.Workers
|
alias Mobilizon.Service.Workers
|
||||||
alias Mobilizon.Storage.Page
|
alias Mobilizon.Storage.Page
|
||||||
|
|
||||||
|
@ -58,7 +58,8 @@ defmodule Mobilizon.DiscussionsTest do
|
|||||||
%Comment{} = comment = insert(:comment)
|
%Comment{} = comment = insert(:comment)
|
||||||
assert {:error, %Ecto.Changeset{}} = Discussions.update_comment(comment, @invalid_attrs)
|
assert {:error, %Ecto.Changeset{}} = Discussions.update_comment(comment, @invalid_attrs)
|
||||||
%Comment{} = comment_fetched = Discussions.get_comment!(comment.id)
|
%Comment{} = comment_fetched = Discussions.get_comment!(comment.id)
|
||||||
assert comment = comment_fetched
|
assert comment.text == comment_fetched.text
|
||||||
|
assert comment.url == comment_fetched.url
|
||||||
end
|
end
|
||||||
|
|
||||||
test "delete_comment/1 deletes the comment" do
|
test "delete_comment/1 deletes the comment" do
|
||||||
|
@ -292,7 +292,7 @@ defmodule Mobilizon.EventsTest do
|
|||||||
tag1: %Tag{id: tag1_id} = tag1,
|
tag1: %Tag{id: tag1_id} = tag1,
|
||||||
tag2: %Tag{id: tag2_id} = tag2
|
tag2: %Tag{id: tag2_id} = tag2
|
||||||
} do
|
} do
|
||||||
assert {:ok, %TagRelation{} = tag_relation} =
|
assert {:ok, %TagRelation{}} =
|
||||||
Events.create_tag_relation(%{tag_id: tag1_id, link_id: tag2_id})
|
Events.create_tag_relation(%{tag_id: tag1_id, link_id: tag2_id})
|
||||||
|
|
||||||
assert Events.are_tags_linked(tag1, tag2)
|
assert Events.are_tags_linked(tag1, tag2)
|
||||||
|
@ -3,13 +3,13 @@ defmodule Mobilizon.MediaTest do
|
|||||||
|
|
||||||
import Mobilizon.Factory
|
import Mobilizon.Factory
|
||||||
|
|
||||||
alias Mobilizon.{Config, Media}
|
alias Mobilizon.{Config, Medias}
|
||||||
|
|
||||||
alias Mobilizon.Web.Upload.Uploader
|
alias Mobilizon.Web.Upload.Uploader
|
||||||
|
|
||||||
describe "media" do
|
describe "media" do
|
||||||
setup [:ensure_local_uploader]
|
setup [:ensure_local_uploader]
|
||||||
alias Mobilizon.Media.Picture
|
alias Mobilizon.Medias.Media
|
||||||
|
|
||||||
@valid_attrs %{
|
@valid_attrs %{
|
||||||
file: %{
|
file: %{
|
||||||
@ -24,39 +24,42 @@ defmodule Mobilizon.MediaTest do
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
test "get_picture!/1 returns the picture with given id" do
|
test "get_media!/1 returns the media with given id" do
|
||||||
picture = insert(:picture)
|
media = insert(:media)
|
||||||
assert Media.get_picture!(picture.id).id == picture.id
|
assert Medias.get_media!(media.id).id == media.id
|
||||||
end
|
end
|
||||||
|
|
||||||
test "create_picture/1 with valid data creates a picture" do
|
test "create_media/1 with valid data creates a media" do
|
||||||
assert {:ok, %Picture{} = picture} =
|
assert {:ok, %Media{} = media} =
|
||||||
Media.create_picture(Map.put(@valid_attrs, :actor_id, insert(:actor).id))
|
Medias.create_media(Map.put(@valid_attrs, :actor_id, insert(:actor).id))
|
||||||
|
|
||||||
assert picture.file.name == "something old"
|
assert media.file.name == "something old"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "update_picture/2 with valid data updates the picture" do
|
test "update_media/2 with valid data updates the media" do
|
||||||
picture = insert(:picture)
|
media = insert(:media)
|
||||||
|
|
||||||
assert {:ok, %Picture{} = picture} =
|
assert {:ok, %Media{} = media} =
|
||||||
Media.update_picture(picture, Map.put(@update_attrs, :actor_id, insert(:actor).id))
|
Medias.update_media(
|
||||||
|
media,
|
||||||
|
Map.put(@update_attrs, :actor_id, insert(:actor).id)
|
||||||
|
)
|
||||||
|
|
||||||
assert picture.file.name == "something new"
|
assert media.file.name == "something new"
|
||||||
end
|
end
|
||||||
|
|
||||||
test "delete_picture/1 deletes the picture" do
|
test "delete_media/1 deletes the media" do
|
||||||
picture = insert(:picture)
|
media = insert(:media)
|
||||||
|
|
||||||
%URI{path: "/media/" <> path} = URI.parse(picture.file.url)
|
%URI{path: "/media/" <> path} = URI.parse(media.file.url)
|
||||||
|
|
||||||
assert File.exists?(
|
assert File.exists?(
|
||||||
Config.get!([Uploader.Local, :uploads]) <>
|
Config.get!([Uploader.Local, :uploads]) <>
|
||||||
"/" <> path
|
"/" <> path
|
||||||
)
|
)
|
||||||
|
|
||||||
assert {:ok, %Picture{}} = Media.delete_picture(picture)
|
assert {:ok, %Media{}} = Medias.delete_media(media)
|
||||||
assert_raise Ecto.NoResultsError, fn -> Media.get_picture!(picture.id) end
|
assert_raise Ecto.NoResultsError, fn -> Medias.get_media!(media.id) end
|
||||||
|
|
||||||
refute File.exists?(
|
refute File.exists?(
|
||||||
Config.get!([Uploader.Local, :uploads]) <>
|
Config.get!([Uploader.Local, :uploads]) <>
|
||||||
|
@ -70,7 +70,7 @@ defmodule Mobilizon.PostsTest do
|
|||||||
%Post{} = post = insert(:post)
|
%Post{} = post = insert(:post)
|
||||||
assert {:error, %Ecto.Changeset{}} = Posts.update_post(post, @invalid_attrs)
|
assert {:error, %Ecto.Changeset{}} = Posts.update_post(post, @invalid_attrs)
|
||||||
%Post{} = post_fetched = Posts.get_post(post.id)
|
%Post{} = post_fetched = Posts.get_post(post.id)
|
||||||
assert post = post_fetched
|
assert post.body == post_fetched.body
|
||||||
end
|
end
|
||||||
|
|
||||||
test "delete_post/1 deletes the post" do
|
test "delete_post/1 deletes the post" do
|
||||||
|
56
test/service/clean_orphan_media_test.exs
Normal file
56
test/service/clean_orphan_media_test.exs
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
defmodule Mix.Tasks.Mobilizon.Media.CleanOrphanTest do
|
||||||
|
use Mobilizon.DataCase
|
||||||
|
|
||||||
|
import Mobilizon.Factory
|
||||||
|
|
||||||
|
alias Mobilizon.Medias
|
||||||
|
alias Mobilizon.Medias.Media
|
||||||
|
alias Mobilizon.Service.CleanOrphanMedia
|
||||||
|
|
||||||
|
describe "clean orphan media" do
|
||||||
|
test "with default values" do
|
||||||
|
{:ok, old, _} = DateTime.from_iso8601("2020-11-20T17:35:23+01:00")
|
||||||
|
%Media{id: media_id} = insert(:media, inserted_at: old)
|
||||||
|
%Media{id: media_2_id} = insert(:media)
|
||||||
|
|
||||||
|
refute is_nil(Medias.get_media(media_id))
|
||||||
|
refute is_nil(Medias.get_media(media_2_id))
|
||||||
|
|
||||||
|
assert {:ok, [found_media]} = CleanOrphanMedia.clean()
|
||||||
|
assert found_media.id == media_id
|
||||||
|
|
||||||
|
assert is_nil(Medias.get_media(media_id))
|
||||||
|
refute is_nil(Medias.get_media(media_2_id))
|
||||||
|
end
|
||||||
|
|
||||||
|
test "as dry-run" do
|
||||||
|
{:ok, old, _} = DateTime.from_iso8601("2020-11-20T17:35:23+01:00")
|
||||||
|
%Media{id: media_id} = insert(:media, inserted_at: old)
|
||||||
|
%Media{id: media_2_id} = insert(:media)
|
||||||
|
|
||||||
|
refute is_nil(Medias.get_media(media_id))
|
||||||
|
refute is_nil(Medias.get_media(media_2_id))
|
||||||
|
|
||||||
|
assert {:ok, [found_media]} = CleanOrphanMedia.clean(dry_run: true)
|
||||||
|
assert found_media.id == media_id
|
||||||
|
|
||||||
|
refute is_nil(Medias.get_media(media_id))
|
||||||
|
refute is_nil(Medias.get_media(media_2_id))
|
||||||
|
end
|
||||||
|
|
||||||
|
test "with custom grace period" do
|
||||||
|
date = DateTime.utc_now() |> DateTime.add(24 * -3600)
|
||||||
|
%Media{id: media_id} = insert(:media, inserted_at: date)
|
||||||
|
%Media{id: media_2_id} = insert(:media)
|
||||||
|
|
||||||
|
refute is_nil(Medias.get_media(media_id))
|
||||||
|
refute is_nil(Medias.get_media(media_2_id))
|
||||||
|
|
||||||
|
assert {:ok, [found_media]} = CleanOrphanMedia.clean(grace_period: 12)
|
||||||
|
assert found_media.id == media_id
|
||||||
|
|
||||||
|
assert is_nil(Medias.get_media(media_id))
|
||||||
|
refute is_nil(Medias.get_media(media_2_id))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
@ -148,6 +148,7 @@ defmodule Mobilizon.Factory do
|
|||||||
event: build(:event),
|
event: build(:event),
|
||||||
uuid: uuid,
|
uuid: uuid,
|
||||||
mentions: [],
|
mentions: [],
|
||||||
|
media: [],
|
||||||
attributed_to: nil,
|
attributed_to: nil,
|
||||||
local: true,
|
local: true,
|
||||||
deleted_at: nil,
|
deleted_at: nil,
|
||||||
@ -179,13 +180,14 @@ defmodule Mobilizon.Factory do
|
|||||||
local: true,
|
local: true,
|
||||||
publish_at: DateTime.utc_now(),
|
publish_at: DateTime.utc_now(),
|
||||||
url: Routes.page_url(Endpoint, :event, uuid),
|
url: Routes.page_url(Endpoint, :event, uuid),
|
||||||
picture: insert(:picture),
|
picture: insert(:media),
|
||||||
uuid: uuid,
|
uuid: uuid,
|
||||||
join_options: :free,
|
join_options: :free,
|
||||||
options: %{},
|
options: %{},
|
||||||
participant_stats: %{},
|
participant_stats: %{},
|
||||||
status: :confirmed,
|
status: :confirmed,
|
||||||
contacts: []
|
contacts: [],
|
||||||
|
media: []
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -269,16 +271,16 @@ defmodule Mobilizon.Factory do
|
|||||||
size: 13_227
|
size: 13_227
|
||||||
} = data
|
} = data
|
||||||
|
|
||||||
%Mobilizon.Media.File{
|
%Mobilizon.Medias.File{
|
||||||
name: "My Picture",
|
name: "My Media",
|
||||||
url: url,
|
url: url,
|
||||||
content_type: "image/png",
|
content_type: "image/png",
|
||||||
size: 13_120
|
size: 13_120
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def picture_factory do
|
def media_factory do
|
||||||
%Mobilizon.Media.Picture{
|
%Mobilizon.Medias.Media{
|
||||||
file: build(:file),
|
file: build(:file),
|
||||||
actor: build(:actor)
|
actor: build(:actor)
|
||||||
}
|
}
|
||||||
@ -372,6 +374,7 @@ defmodule Mobilizon.Factory do
|
|||||||
tags: build_list(3, :tag),
|
tags: build_list(3, :tag),
|
||||||
visibility: :public,
|
visibility: :public,
|
||||||
publish_at: DateTime.utc_now(),
|
publish_at: DateTime.utc_now(),
|
||||||
|
media: [],
|
||||||
url: Routes.page_url(Endpoint, :post, uuid)
|
url: Routes.page_url(Endpoint, :post, uuid)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
124
test/tasks/media/clean_orphan_test.exs
Normal file
124
test/tasks/media/clean_orphan_test.exs
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
defmodule Mix.Tasks.Mobilizon.Media.CleanOrphanTest do
|
||||||
|
use Mobilizon.DataCase
|
||||||
|
import Mock
|
||||||
|
import Mobilizon.Factory
|
||||||
|
|
||||||
|
alias Mix.Tasks.Mobilizon.Media.CleanOrphan
|
||||||
|
alias Mobilizon.Service.CleanOrphanMedia
|
||||||
|
|
||||||
|
Mix.shell(Mix.Shell.Process)
|
||||||
|
|
||||||
|
describe "with default options" do
|
||||||
|
test "nothing returned" do
|
||||||
|
with_mock CleanOrphanMedia, clean: fn [dry_run: false, grace_period: 48] -> {:ok, []} end do
|
||||||
|
CleanOrphan.run([])
|
||||||
|
assert_received {:mix_shell, :info, [output_received]}
|
||||||
|
assert output_received == "No files were deleted"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "media returned" do
|
||||||
|
media1 = insert(:media)
|
||||||
|
media2 = insert(:media)
|
||||||
|
|
||||||
|
with_mock CleanOrphanMedia,
|
||||||
|
clean: fn [dry_run: false, grace_period: 48] -> {:ok, [media1, media2]} end do
|
||||||
|
CleanOrphan.run([])
|
||||||
|
assert_received {:mix_shell, :info, [output_received]}
|
||||||
|
assert output_received == "2 files have been deleted"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "with dry-run option" do
|
||||||
|
test "with nothing returned" do
|
||||||
|
with_mock CleanOrphanMedia,
|
||||||
|
clean: fn [dry_run: true, grace_period: 48] -> {:ok, []} end do
|
||||||
|
CleanOrphan.run(["--dry-run"])
|
||||||
|
assert_received {:mix_shell, :info, [output_received]}
|
||||||
|
assert output_received == "No files would have been deleted"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "with media returned" do
|
||||||
|
media1 = insert(:media)
|
||||||
|
media2 = insert(:media)
|
||||||
|
|
||||||
|
with_mock CleanOrphanMedia,
|
||||||
|
clean: fn [dry_run: true, grace_period: 48] -> {:ok, [media1, media2]} end do
|
||||||
|
CleanOrphan.run(["--dry-run"])
|
||||||
|
assert_received {:mix_shell, :info, [output_received]}
|
||||||
|
assert output_received == "List of files that would have been deleted"
|
||||||
|
assert_received {:mix_shell, :info, [output_received]}
|
||||||
|
|
||||||
|
assert output_received ==
|
||||||
|
"ID: #{media1.id}, Actor: #{media1.actor_id}, URL: #{media1.file.url}"
|
||||||
|
|
||||||
|
assert_received {:mix_shell, :info, [output_received]}
|
||||||
|
|
||||||
|
assert output_received ==
|
||||||
|
"ID: #{media2.id}, Actor: #{media2.actor_id}, URL: #{media2.file.url}"
|
||||||
|
|
||||||
|
assert_received {:mix_shell, :info, [output_received]}
|
||||||
|
assert output_received == "2 files would have been deleted"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "with verbose option" do
|
||||||
|
test "with nothing returned" do
|
||||||
|
with_mock CleanOrphanMedia,
|
||||||
|
clean: fn [dry_run: false, grace_period: 48] -> {:ok, []} end do
|
||||||
|
CleanOrphan.run(["--verbose"])
|
||||||
|
assert_received {:mix_shell, :info, [output_received]}
|
||||||
|
assert output_received == "No files were deleted"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "with media returned" do
|
||||||
|
media1 = insert(:media)
|
||||||
|
media2 = insert(:media)
|
||||||
|
|
||||||
|
with_mock CleanOrphanMedia,
|
||||||
|
clean: fn [dry_run: false, grace_period: 48] -> {:ok, [media1, media2]} end do
|
||||||
|
CleanOrphan.run(["--verbose"])
|
||||||
|
assert_received {:mix_shell, :info, [output_received]}
|
||||||
|
assert output_received == "List of files that have been deleted"
|
||||||
|
assert_received {:mix_shell, :info, [output_received]}
|
||||||
|
|
||||||
|
assert output_received ==
|
||||||
|
"ID: #{media1.id}, Actor: #{media1.actor_id}, URL: #{media1.file.url}"
|
||||||
|
|
||||||
|
assert_received {:mix_shell, :info, [output_received]}
|
||||||
|
|
||||||
|
assert output_received ==
|
||||||
|
"ID: #{media2.id}, Actor: #{media2.actor_id}, URL: #{media2.file.url}"
|
||||||
|
|
||||||
|
assert_received {:mix_shell, :info, [output_received]}
|
||||||
|
assert output_received == "2 files have been deleted"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "with days option" do
|
||||||
|
test "with nothing returned" do
|
||||||
|
with_mock CleanOrphanMedia,
|
||||||
|
clean: fn [dry_run: false, grace_period: 120] -> {:ok, []} end do
|
||||||
|
CleanOrphan.run(["--days", "5"])
|
||||||
|
assert_received {:mix_shell, :info, [output_received]}
|
||||||
|
assert output_received == "No files were deleted"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "returns an error" do
|
||||||
|
test "for some reason" do
|
||||||
|
with_mock CleanOrphanMedia,
|
||||||
|
clean: fn [dry_run: false, grace_period: 48] -> {:error, "Some error"} end do
|
||||||
|
CleanOrphan.run([])
|
||||||
|
assert_received {:mix_shell, :error, [output_received]}
|
||||||
|
assert output_received == "Error while cleaning orphan media files"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user