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:
@@ -212,7 +212,7 @@ import { SEARCH_PERSONS } from "../graphql/search";
|
||||
import { Actor, IActor, IPerson } from "../types/actor";
|
||||
import Image from "./Editor/Image";
|
||||
import MaxSize from "./Editor/MaxSize";
|
||||
import { UPLOAD_PICTURE } from "../graphql/upload";
|
||||
import { UPLOAD_MEDIA } from "../graphql/upload";
|
||||
import { listenFileUpload } from "../utils/upload";
|
||||
import { CURRENT_ACTOR_CLIENT } from "../graphql/actor";
|
||||
import { IComment } from "../types/comment.model";
|
||||
@@ -395,7 +395,15 @@ export default class EditorComponent extends Vue {
|
||||
new Image(),
|
||||
new MaxSize({ maxSize: this.maxSize }),
|
||||
],
|
||||
onUpdate: ({ getHTML }: { getHTML: Function }) => {
|
||||
onUpdate: ({
|
||||
getHTML,
|
||||
transaction,
|
||||
getJSON,
|
||||
}: {
|
||||
getHTML: Function;
|
||||
getJSON: Function;
|
||||
transaction: unknown;
|
||||
}) => {
|
||||
this.$emit("input", getHTML());
|
||||
},
|
||||
});
|
||||
@@ -526,14 +534,14 @@ export default class EditorComponent extends Vue {
|
||||
const image = await listenFileUpload();
|
||||
try {
|
||||
const { data } = await this.$apollo.mutate({
|
||||
mutation: UPLOAD_PICTURE,
|
||||
mutation: UPLOAD_MEDIA,
|
||||
variables: {
|
||||
file: image,
|
||||
name: image.name,
|
||||
},
|
||||
});
|
||||
if (data.uploadPicture && data.uploadPicture.url) {
|
||||
command({ src: data.uploadPicture.url });
|
||||
if (data.uploadMedia && data.uploadMedia.url) {
|
||||
command({ src: data.uploadMedia.url, "data-media-id": data.uploadMedia.id });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-nocheck
|
||||
import { Node } from "tiptap";
|
||||
import { UPLOAD_PICTURE } from "@/graphql/upload";
|
||||
import { UPLOAD_MEDIA } from "@/graphql/upload";
|
||||
import apolloProvider from "@/vue-apollo";
|
||||
import ApolloClient from "apollo-client";
|
||||
import { NormalizedCacheObject } from "apollo-cache-inmemory";
|
||||
@@ -27,16 +28,18 @@ export default class Image extends Node {
|
||||
title: {
|
||||
default: null,
|
||||
},
|
||||
"data-media-id": {},
|
||||
},
|
||||
group: "inline",
|
||||
draggable: true,
|
||||
parseDOM: [
|
||||
{
|
||||
tag: "img[src]",
|
||||
tag: "img",
|
||||
getAttrs: (dom: any) => ({
|
||||
src: dom.getAttribute("src"),
|
||||
title: dom.getAttribute("title"),
|
||||
alt: dom.getAttribute("alt"),
|
||||
"data-media-id": dom.getAttribute("data-media-id"),
|
||||
}),
|
||||
},
|
||||
],
|
||||
@@ -92,13 +95,16 @@ export default class Image extends Node {
|
||||
try {
|
||||
images.forEach(async (image) => {
|
||||
const { data } = await client.mutate({
|
||||
mutation: UPLOAD_PICTURE,
|
||||
mutation: UPLOAD_MEDIA,
|
||||
variables: {
|
||||
file: image,
|
||||
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);
|
||||
view.dispatch(transaction);
|
||||
});
|
||||
|
||||
@@ -60,14 +60,14 @@ figure.image {
|
||||
</style>
|
||||
|
||||
<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";
|
||||
|
||||
@Component
|
||||
export default class PictureUpload extends Vue {
|
||||
@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" })
|
||||
accept!: string;
|
||||
@@ -100,7 +100,7 @@ export default class PictureUpload extends Vue {
|
||||
}
|
||||
|
||||
@Watch("defaultImage")
|
||||
onDefaultImageChange(defaultImage: IPicture): void {
|
||||
onDefaultImageChange(defaultImage: IMedia): void {
|
||||
console.log("onDefaultImageChange", defaultImage);
|
||||
this.imageSrc = defaultImage ? defaultImage.url : null;
|
||||
}
|
||||
|
||||
@@ -421,7 +421,7 @@ export const CREATE_PERSON = gql`
|
||||
$preferredUsername: String!
|
||||
$name: String!
|
||||
$summary: String
|
||||
$avatar: PictureInput
|
||||
$avatar: MediaInput
|
||||
) {
|
||||
createPerson(
|
||||
preferredUsername: $preferredUsername
|
||||
@@ -442,7 +442,7 @@ export const CREATE_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) {
|
||||
id
|
||||
preferredUsername
|
||||
|
||||
@@ -244,7 +244,7 @@ export const CREATE_EVENT = gql`
|
||||
$joinOptions: EventJoinOptions,
|
||||
$draft: Boolean,
|
||||
$tags: [String],
|
||||
$picture: PictureInput,
|
||||
$picture: MediaInput,
|
||||
$onlineAddress: String,
|
||||
$phoneAddress: String,
|
||||
$category: String,
|
||||
@@ -355,7 +355,7 @@ export const EDIT_EVENT = gql`
|
||||
$joinOptions: EventJoinOptions,
|
||||
$draft: Boolean,
|
||||
$tags: [String],
|
||||
$picture: PictureInput,
|
||||
$picture: MediaInput,
|
||||
$onlineAddress: String,
|
||||
$phoneAddress: String,
|
||||
$organizerActorId: ID,
|
||||
|
||||
@@ -227,8 +227,8 @@ export const CREATE_GROUP = gql`
|
||||
$preferredUsername: String!
|
||||
$name: String!
|
||||
$summary: String
|
||||
$avatar: PictureInput
|
||||
$banner: PictureInput
|
||||
$avatar: MediaInput
|
||||
$banner: MediaInput
|
||||
) {
|
||||
createGroup(
|
||||
preferredUsername: $preferredUsername
|
||||
@@ -259,8 +259,8 @@ export const UPDATE_GROUP = gql`
|
||||
$id: ID!
|
||||
$name: String
|
||||
$summary: String
|
||||
$avatar: PictureInput
|
||||
$banner: PictureInput
|
||||
$avatar: MediaInput
|
||||
$banner: MediaInput
|
||||
$visibility: GroupVisibility
|
||||
$openness: Openness
|
||||
$physicalAddress: AddressInput
|
||||
|
||||
@@ -119,7 +119,7 @@ export const CREATE_POST = gql`
|
||||
$visibility: PostVisibility
|
||||
$draft: Boolean
|
||||
$tags: [String]
|
||||
$picture: PictureInput
|
||||
$picture: MediaInput
|
||||
) {
|
||||
createPost(
|
||||
title: $title
|
||||
@@ -145,7 +145,7 @@ export const UPDATE_POST = gql`
|
||||
$visibility: PostVisibility
|
||||
$draft: Boolean
|
||||
$tags: [String]
|
||||
$picture: PictureInput
|
||||
$picture: MediaInput
|
||||
) {
|
||||
updatePost(
|
||||
id: $id
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import gql from "graphql-tag";
|
||||
|
||||
export const UPLOAD_PICTURE = gql`
|
||||
mutation UploadPicture($file: Upload!, $alt: String, $name: String!) {
|
||||
uploadPicture(file: $file, alt: $alt, name: $name) {
|
||||
export const UPLOAD_MEDIA = gql`
|
||||
mutation UploadMedia($file: Upload!, $alt: String, $name: String!) {
|
||||
uploadMedia(file: $file, alt: $alt, name: $name) {
|
||||
url
|
||||
id
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const REMOVE_PICTURE = gql`
|
||||
mutation RemovePicture($id: ID!) {
|
||||
removePicture(id: $id) {
|
||||
export const REMOVE_MEDIA = gql`
|
||||
mutation RemoveMedia($id: ID!) {
|
||||
removeMedia(id: $id) {
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IPicture } from "@/types/picture.model";
|
||||
import { IMedia } from "@/types/media.model";
|
||||
|
||||
export enum ActorType {
|
||||
PERSON = "PERSON",
|
||||
@@ -17,17 +17,17 @@ export interface IActor {
|
||||
summary: string;
|
||||
preferredUsername: string;
|
||||
suspended: boolean;
|
||||
avatar?: IPicture | null;
|
||||
banner?: IPicture | null;
|
||||
avatar?: IMedia | null;
|
||||
banner?: IMedia | null;
|
||||
type: ActorType;
|
||||
}
|
||||
|
||||
export class Actor implements IActor {
|
||||
id?: string;
|
||||
|
||||
avatar: IPicture | null = null;
|
||||
avatar: IMedia | null = null;
|
||||
|
||||
banner: IPicture | null = null;
|
||||
banner: IMedia | null = null;
|
||||
|
||||
domain: string | null = null;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Address, IAddress } from "@/types/address.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 { Paginate } from "@/types/paginate";
|
||||
import { Actor, Group, IActor, IGroup, IPerson } from "./actor";
|
||||
@@ -69,7 +69,7 @@ interface IEventEditJSON {
|
||||
visibility: EventVisibility;
|
||||
joinOptions: EventJoinOptions;
|
||||
draft: boolean;
|
||||
picture?: IPicture | { pictureId: string } | null;
|
||||
picture?: IMedia | { mediaId: string } | null;
|
||||
attributedToId: string | null;
|
||||
onlineAddress?: string;
|
||||
phoneAddress?: string;
|
||||
@@ -96,7 +96,7 @@ export interface IEvent {
|
||||
joinOptions: EventJoinOptions;
|
||||
draft: boolean;
|
||||
|
||||
picture: IPicture | null;
|
||||
picture: IMedia | null;
|
||||
|
||||
organizerActor?: IActor;
|
||||
attributedTo?: IGroup;
|
||||
@@ -142,7 +142,7 @@ export class EventModel implements IEvent {
|
||||
|
||||
physicalAddress?: IAddress;
|
||||
|
||||
picture: IPicture | null = null;
|
||||
picture: IMedia | null = null;
|
||||
|
||||
visibility = EventVisibility.PUBLIC;
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
export interface IPicture {
|
||||
export interface IMedia {
|
||||
id: string;
|
||||
url: string;
|
||||
name: string;
|
||||
alt: string;
|
||||
}
|
||||
|
||||
export interface IPictureUpload {
|
||||
export interface IMediaUpload {
|
||||
file: File;
|
||||
name: string;
|
||||
alt: string | null;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ITag } from "./tag.model";
|
||||
import { IPicture } from "./picture.model";
|
||||
import { IMedia } from "./media.model";
|
||||
import { IActor } from "./actor";
|
||||
|
||||
export enum PostVisibility {
|
||||
@@ -17,7 +17,7 @@ export interface IPost {
|
||||
title: string;
|
||||
body: string;
|
||||
tags?: ITag[];
|
||||
picture?: IPicture | null;
|
||||
picture?: IMedia | null;
|
||||
draft: boolean;
|
||||
visibility: PostVisibility;
|
||||
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);
|
||||
|
||||
const response = await fetch(obj.url);
|
||||
@@ -14,7 +14,7 @@ export function buildFileVariable(file: File | null, name: string, alt?: string)
|
||||
|
||||
return {
|
||||
[name]: {
|
||||
picture: {
|
||||
media: {
|
||||
name: file.name,
|
||||
alt: alt || file.name,
|
||||
file,
|
||||
|
||||
@@ -124,7 +124,6 @@ h1 {
|
||||
<script lang="ts">
|
||||
import { Component, Prop, Watch } from "vue-property-decorator";
|
||||
import { mixins } from "vue-class-component";
|
||||
import { IPicture } from "@/types/picture.model";
|
||||
import {
|
||||
CREATE_PERSON,
|
||||
CURRENT_ACTOR_CLIENT,
|
||||
@@ -137,7 +136,7 @@ import { IPerson, Person } from "../../../types/actor";
|
||||
import PictureUpload from "../../../components/PictureUpload.vue";
|
||||
import { MOBILIZON_INSTANCE_HOST } from "../../../api/_entrypoint";
|
||||
import RouteName from "../../../router/name";
|
||||
import { buildFileFromIPicture, buildFileVariable } from "../../../utils/image";
|
||||
import { buildFileVariable } from "../../../utils/image";
|
||||
import { changeIdentity } from "../../../utils/auth";
|
||||
import identityEditionMixin from "../../../mixins/identityEdition";
|
||||
|
||||
|
||||
@@ -377,7 +377,7 @@ import {
|
||||
import { IPerson, Person, displayNameAndUsername } from "../../types/actor";
|
||||
import { TAGS } from "../../graphql/tags";
|
||||
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 "intersection-observer";
|
||||
import { CONFIG } from "../../graphql/config";
|
||||
@@ -517,7 +517,7 @@ export default class EditEvent extends Vue {
|
||||
);
|
||||
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;
|
||||
if (!(this.isUpdate || this.isDuplicate)) {
|
||||
this.initializeEvent();
|
||||
@@ -775,11 +775,11 @@ export default class EditEvent extends Vue {
|
||||
|
||||
try {
|
||||
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 newPictureFileContent = await readFileAsync(this.pictureFile as File);
|
||||
if (oldPictureFileContent === newPictureFileContent) {
|
||||
res.picture = { pictureId: this.event.picture.id };
|
||||
res.picture = { mediaId: this.event.picture.id };
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -246,7 +246,7 @@ export default class GroupSettings extends mixins(GroupMixin) {
|
||||
if (this.avatarFile) {
|
||||
avatarObj = {
|
||||
avatar: {
|
||||
picture: {
|
||||
media: {
|
||||
name: this.avatarFile.name,
|
||||
alt: `${this.group.preferredUsername}'s avatar`,
|
||||
file: this.avatarFile,
|
||||
@@ -258,7 +258,7 @@ export default class GroupSettings extends mixins(GroupMixin) {
|
||||
if (this.bannerFile) {
|
||||
bannerObj = {
|
||||
banner: {
|
||||
picture: {
|
||||
media: {
|
||||
name: this.bannerFile.name,
|
||||
alt: `${this.group.preferredUsername}'s banner`,
|
||||
file: this.bannerFile,
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
import { Component, Prop } from "vue-property-decorator";
|
||||
import { mixins } from "vue-class-component";
|
||||
import { FETCH_GROUP } from "@/graphql/group";
|
||||
import { buildFileFromIPicture, readFileAsync } from "@/utils/image";
|
||||
import { buildFileFromIMedia, readFileAsync } from "@/utils/image";
|
||||
import GroupMixin from "@/mixins/group";
|
||||
import { TAGS } from "../../graphql/tags";
|
||||
import { CONFIG } from "../../graphql/config";
|
||||
@@ -188,7 +188,7 @@ export default class EditPost extends mixins(GroupMixin) {
|
||||
errors: Record<string, unknown> = {};
|
||||
|
||||
async mounted(): Promise<void> {
|
||||
this.pictureFile = await buildFileFromIPicture(this.post.picture);
|
||||
this.pictureFile = await buildFileFromIMedia(this.post.picture);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line consistent-return
|
||||
@@ -277,11 +277,11 @@ export default class EditPost extends mixins(GroupMixin) {
|
||||
}
|
||||
try {
|
||||
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 newPictureFileContent = await readFileAsync(this.pictureFile as File);
|
||||
if (oldPictureFileContent === newPictureFileContent) {
|
||||
obj.picture = { pictureId: this.post.picture.id };
|
||||
obj.picture = { mediaId: this.post.picture.id };
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
Reference in New Issue
Block a user