kopia lustrzana https://github.com/nextcloud/social
Add media api
See https://docs.joinmastodon.org/methods/statuses/media/ Signed-off-by: Carl Schwan <carl@carlschwan.eu>pull/1439/head
rodzic
e821566eca
commit
9f49b14657
|
@ -81,6 +81,10 @@ return [
|
||||||
['name' => 'Api#savedSearches', 'url' => '/api/saved_searches/list.json', 'verb' => 'GET'],
|
['name' => 'Api#savedSearches', 'url' => '/api/saved_searches/list.json', 'verb' => 'GET'],
|
||||||
['name' => 'Api#timelines', 'url' => '/api/v1/timelines/{timeline}/', 'verb' => 'GET'],
|
['name' => 'Api#timelines', 'url' => '/api/v1/timelines/{timeline}/', 'verb' => 'GET'],
|
||||||
['name' => 'Api#notifications', 'url' => '/api/v1/notifications', 'verb' => 'GET'],
|
['name' => 'Api#notifications', 'url' => '/api/v1/notifications', 'verb' => 'GET'],
|
||||||
|
['name' => 'MediaApi#uploadMedia', 'url' => '/api/v1/media', 'verb' => 'POST'],
|
||||||
|
['name' => 'MediaApi#updateMedia', 'url' => '/api/v1/media/{id}', 'verb' => 'PUT'],
|
||||||
|
['name' => 'MediaApi#deleteMedia', 'url' => '/api/v1/media/{id}', 'verb' => 'DELETE'],
|
||||||
|
['name' => 'MediaApi#getMedia', 'url' => '/media/{shortcode}.{extension}', 'verb' => 'GET'],
|
||||||
|
|
||||||
// Api for local front-end
|
// Api for local front-end
|
||||||
// TODO: front-end should be using the new ApiController
|
// TODO: front-end should be using the new ApiController
|
||||||
|
|
|
@ -10,19 +10,22 @@ namespace OCA\Social\Controller;
|
||||||
use OCA\Social\Entity\MediaAttachment;
|
use OCA\Social\Entity\MediaAttachment;
|
||||||
use OCA\Social\Service\AccountFinder;
|
use OCA\Social\Service\AccountFinder;
|
||||||
use OCP\AppFramework\Http;
|
use OCP\AppFramework\Http;
|
||||||
|
use OCP\AppFramework\Http\Response;
|
||||||
use OCP\AppFramework\Http\DataResponse;
|
use OCP\AppFramework\Http\DataResponse;
|
||||||
|
use OCP\AppFramework\Http\DataDownloadResponse;
|
||||||
|
use OCP\AppFramework\Http\NotFoundResponse;
|
||||||
use OCP\DB\ORM\IEntityManager;
|
use OCP\DB\ORM\IEntityManager;
|
||||||
use OCP\Files\IAppData;
|
use OCP\Files\IAppData;
|
||||||
use OCP\Files\NotFoundException;
|
use OCP\Files\NotFoundException;
|
||||||
use OCP\IL10N;
|
use OCP\IL10N;
|
||||||
use OCP\AppFramework\Controller;
|
use OCP\AppFramework\Controller;
|
||||||
use OCP\AppFramework\Http\DataResponse;
|
|
||||||
use OCP\Files\IMimeTypeDetector;
|
use OCP\Files\IMimeTypeDetector;
|
||||||
use OCP\Image;
|
use OCP\Image;
|
||||||
use OCP\IRequest;
|
use OCP\IRequest;
|
||||||
use OCP\IURLGenerator;
|
use OCP\IURLGenerator;
|
||||||
use OCP\IUserSession;
|
use OCP\IUserSession;
|
||||||
use OCP\Util;
|
use OCP\Util;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
class MediaApiController extends Controller {
|
class MediaApiController extends Controller {
|
||||||
|
|
||||||
|
@ -34,6 +37,18 @@ class MediaApiController extends Controller {
|
||||||
private IEntityManager $entityManager;
|
private IEntityManager $entityManager;
|
||||||
private IURLGenerator $generator;
|
private IURLGenerator $generator;
|
||||||
|
|
||||||
|
public const IMAGE_MIME_TYPES = [
|
||||||
|
'image/png',
|
||||||
|
'image/jpeg',
|
||||||
|
'image/jpg',
|
||||||
|
'image/gif',
|
||||||
|
'image/x-xbitmap',
|
||||||
|
'image/x-ms-bmp',
|
||||||
|
'image/bmp',
|
||||||
|
'image/svg+xml',
|
||||||
|
'image/webp',
|
||||||
|
];
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
string $appName,
|
string $appName,
|
||||||
IRequest $request,
|
IRequest $request,
|
||||||
|
@ -43,7 +58,8 @@ class MediaApiController extends Controller {
|
||||||
IUserSession $userSession,
|
IUserSession $userSession,
|
||||||
AccountFinder $accountFinder,
|
AccountFinder $accountFinder,
|
||||||
IEntityManager $entityManager,
|
IEntityManager $entityManager,
|
||||||
IURLGenerator $generator
|
IURLGenerator $generator,
|
||||||
|
LoggerInterface $logger
|
||||||
) {
|
) {
|
||||||
parent::__construct($appName, $request);
|
parent::__construct($appName, $request);
|
||||||
$this->l10n = $l10n;
|
$this->l10n = $l10n;
|
||||||
|
@ -53,6 +69,7 @@ class MediaApiController extends Controller {
|
||||||
$this->accountFinder = $accountFinder;
|
$this->accountFinder = $accountFinder;
|
||||||
$this->entityManager = $entityManager;
|
$this->entityManager = $entityManager;
|
||||||
$this->generator = $generator;
|
$this->generator = $generator;
|
||||||
|
$this->logger = $logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -60,7 +77,7 @@ class MediaApiController extends Controller {
|
||||||
*
|
*
|
||||||
* @NoAdminRequired
|
* @NoAdminRequired
|
||||||
*/
|
*/
|
||||||
public function uploadMedia(string $description, string $focus = ''): DataResponse {
|
public function uploadMedia(?string $description, ?string $focus = ''): DataResponse {
|
||||||
try {
|
try {
|
||||||
$file = $this->getUploadedFile('file');
|
$file = $this->getUploadedFile('file');
|
||||||
if (!isset($file['tmp_name'], $file['name'], $file['type'])) {
|
if (!isset($file['tmp_name'], $file['name'], $file['type'])) {
|
||||||
|
@ -90,10 +107,10 @@ class MediaApiController extends Controller {
|
||||||
"aspect" => $image->width() / $image->height(),
|
"aspect" => $image->width() / $image->height(),
|
||||||
];
|
];
|
||||||
|
|
||||||
$attachment = new MediaAttachment();
|
$attachment = MediaAttachment::create();
|
||||||
$attachment->setMimetype($file['type']);
|
$attachment->setMimetype($file['type']);
|
||||||
$attachment->setAccount($account);
|
$attachment->setAccount($account);
|
||||||
$attachment->setDescription($description);
|
$attachment->setDescription($description ?? '');
|
||||||
$attachment->setMeta($meta);
|
$attachment->setMeta($meta);
|
||||||
$this->entityManager->persist($attachment);
|
$this->entityManager->persist($attachment);
|
||||||
$this->entityManager->flush();
|
$this->entityManager->flush();
|
||||||
|
@ -103,10 +120,39 @@ class MediaApiController extends Controller {
|
||||||
} catch (NotFoundException $e) {
|
} catch (NotFoundException $e) {
|
||||||
$folder = $this->appData->newFolder('media-attachments');
|
$folder = $this->appData->newFolder('media-attachments');
|
||||||
}
|
}
|
||||||
|
assert($attachment->getId() !== '');
|
||||||
$folder->newFile($attachment->getId(), $image->data());
|
$folder->newFile($attachment->getId(), $image->data());
|
||||||
|
|
||||||
return new DataResponse($attachment->toMastodonApi($this->generator));
|
return new DataResponse($attachment->toMastodonApi($this->generator));
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
|
$this->logger->error($e->getMessage(), ['exception' => $e]);
|
||||||
|
return new DataResponse([
|
||||||
|
"error" => "Validation failed: File content type is invalid, File is invalid",
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*/
|
||||||
|
public function updateMedia(string $id, ?string $description, ?string $focus = ''): Response {
|
||||||
|
try {
|
||||||
|
$account = $this->accountFinder->getCurrentAccount($this->userSession->getUser());
|
||||||
|
$attachementRepository = $this->entityManager->getRepository(MediaAttachment::class);
|
||||||
|
$attachement = $attachementRepository->findOneBy([
|
||||||
|
'id' => $id,
|
||||||
|
]);
|
||||||
|
if ($attachement->getAccount()->getId() !== $account->getId()) {
|
||||||
|
throw new NotFoundResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
$attachement->setDescription($description ?? '');
|
||||||
|
$this->entityManager->persist($attachement);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
|
||||||
|
return new DataResponse($attachement->toMastodonApi($this->generator));
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$this->logger->error($e->getMessage(), ['exception' => $e]);
|
||||||
return new DataResponse([
|
return new DataResponse([
|
||||||
"error" => "Validation failed: File content type is invalid, File is invalid",
|
"error" => "Validation failed: File content type is invalid, File is invalid",
|
||||||
], 500);
|
], 500);
|
||||||
|
@ -151,4 +197,60 @@ class MediaApiController extends Controller {
|
||||||
}
|
}
|
||||||
return $file;
|
return $file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*/
|
||||||
|
public function getMedia(string $shortcode, string $extension): DataDownloadResponse {
|
||||||
|
try {
|
||||||
|
$folder = $this->appData->getFolder('media-attachments');
|
||||||
|
} catch (NotFoundException $e) {
|
||||||
|
$folder = $this->appData->newFolder('media-attachments');
|
||||||
|
}
|
||||||
|
$attachementRepository = $this->entityManager->getRepository(MediaAttachment::class);
|
||||||
|
$attachement = $attachementRepository->findOneBy([
|
||||||
|
'shortcode' => $shortcode,
|
||||||
|
]);
|
||||||
|
$file = $folder->getFile($attachement->getId());
|
||||||
|
return new DataDownloadResponse(
|
||||||
|
$file->getContent(),
|
||||||
|
(string) Http::STATUS_OK,
|
||||||
|
$this->getSecureMimeType($file->getMimeType())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*/
|
||||||
|
public function deleteMedia(string $id): DataResponse {
|
||||||
|
try {
|
||||||
|
$folder = $this->appData->getFolder('media-attachments');
|
||||||
|
} catch (NotFoundException $e) {
|
||||||
|
$folder = $this->appData->newFolder('media-attachments');
|
||||||
|
}
|
||||||
|
$attachementRepository = $this->entityManager->getRepository(MediaAttachment::class);
|
||||||
|
$attachement = $attachementRepository->findOneBy([
|
||||||
|
'id' => $id,
|
||||||
|
]);
|
||||||
|
$file = $folder->getFile($attachement->getId());
|
||||||
|
$file->delete();
|
||||||
|
$this->entityManager->remove($attachement);
|
||||||
|
$this->entityManager->flush();
|
||||||
|
return new DataResponse(['removed']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allow all supported mimetypes
|
||||||
|
* Use mimetype detector for the other ones
|
||||||
|
*
|
||||||
|
* @param string $mimetype
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function getSecureMimeType(string $mimetype): string {
|
||||||
|
if (in_array($mimetype, self::IMAGE_MIME_TYPES)) {
|
||||||
|
return $mimetype;
|
||||||
|
}
|
||||||
|
return $this->mimeTypeDetector->getSecureMimeType($mimetype);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ class MediaAttachment {
|
||||||
* @ORM\Column(type="bigint")
|
* @ORM\Column(type="bigint")
|
||||||
* @ORM\GeneratedValue
|
* @ORM\GeneratedValue
|
||||||
*/
|
*/
|
||||||
private string $id = '-1';
|
private ?string $id = '-1';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\ManyToOne
|
* @ORM\ManyToOne
|
||||||
|
@ -81,7 +81,7 @@ class MediaAttachment {
|
||||||
/**
|
/**
|
||||||
* @ORM\Column(type="text")
|
* @ORM\Column(type="text")
|
||||||
*/
|
*/
|
||||||
private ?string $description = null;
|
private string $description = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ORM\Column
|
* @ORM\Column
|
||||||
|
@ -101,18 +101,27 @@ class MediaAttachment {
|
||||||
/**
|
/**
|
||||||
* @ORM\Column
|
* @ORM\Column
|
||||||
*/
|
*/
|
||||||
private ?string $blurhash = null;
|
private string $blurhash = '';
|
||||||
|
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
$this->updatedAt = new \DateTime();
|
$this->updatedAt = new \DateTime();
|
||||||
$this->createdAt = new \DateTime();
|
$this->createdAt = new \DateTime();
|
||||||
|
$this->meta = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
static public function create(): self {
|
||||||
|
$attachement = new MediaAttachment();
|
||||||
|
$length = 14;
|
||||||
|
$length = ($length < 4) ? 4 : $length;
|
||||||
|
$attachement->setShortcode(bin2hex(random_bytes(($length - ($length % 2)) / 2)));
|
||||||
|
return $attachement;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getId(): string {
|
public function getId(): string {
|
||||||
return $this->id;
|
return $this->id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setId(string $id): void {
|
public function setId(?string $id): void {
|
||||||
$this->id = $id;
|
$this->id = $id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -381,13 +381,6 @@ export default {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
formData.append('file', event.target.files[0])
|
formData.append('file', event.target.files[0])
|
||||||
this.$store.dispatch('uploadAttachement', formData)
|
this.$store.dispatch('uploadAttachement', formData)
|
||||||
|
|
||||||
const previewUrl = URL.createObjectURL(event.target.files[0])
|
|
||||||
this.previewUrls.push({
|
|
||||||
description: '',
|
|
||||||
url: previewUrl,
|
|
||||||
result: event.target.files[0],
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
removeAttachment(idx) {
|
removeAttachment(idx) {
|
||||||
this.previewUrls.splice(idx, 1)
|
this.previewUrls.splice(idx, 1)
|
||||||
|
|
|
@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="preview-grid">
|
<div class="preview-grid">
|
||||||
<PreviewGridItem v-for="(item, index) in miniatures" :key="index" :preview="item" :index="index" @delete="deletePreview" />
|
<PreviewGridItem v-for="(item, index) in draft.attachements" :key="index" :preview="item" :index="index" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -27,6 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
<script>
|
<script>
|
||||||
import PreviewGridItem from './PreviewGridItem'
|
import PreviewGridItem from './PreviewGridItem'
|
||||||
import FileUpload from 'vue-material-design-icons/FileUpload'
|
import FileUpload from 'vue-material-design-icons/FileUpload'
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'PreviewGrid',
|
name: 'PreviewGrid',
|
||||||
|
@ -34,6 +35,11 @@ export default {
|
||||||
PreviewGridItem,
|
PreviewGridItem,
|
||||||
FileUpload,
|
FileUpload,
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
'draft': state => state.timeline.draft,
|
||||||
|
}),
|
||||||
|
},
|
||||||
props: {
|
props: {
|
||||||
uploadProgress: {
|
uploadProgress: {
|
||||||
type: Number,
|
type: Number,
|
||||||
|
@ -48,12 +54,6 @@ export default {
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
deletePreview(index) {
|
|
||||||
console.debug("rjeoijreo")
|
|
||||||
this.miniatures.splice(index, 1)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
<div class="preview-item-wrapper">
|
<div class="preview-item-wrapper">
|
||||||
<div class="preview-item" :style="backgroundStyle">
|
<div class="preview-item" :style="backgroundStyle">
|
||||||
<div class="preview-item__actions">
|
<div class="preview-item__actions">
|
||||||
<Button type="tertiary-no-background" @click="$emit('delete', index)">
|
<Button type="tertiary-no-background" @click="deletePreview">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<Close :size="16" fillColor="white" />
|
<Close :size="16" fillColor="white" />
|
||||||
</template>
|
</template>
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
<label :for="`image-description-${index}`">
|
<label :for="`image-description-${index}`">
|
||||||
{{ t('social', 'Describe for the visually impaired') }}
|
{{ t('social', 'Describe for the visually impaired') }}
|
||||||
</label>
|
</label>
|
||||||
<textarea :id="`image-description-${index}`" v-model="preview.description">
|
<textarea :id="`image-description-${index}`" v-model="internalDescription">
|
||||||
</textarea>
|
</textarea>
|
||||||
<Button type="primary" @click="closeModal">{{ t('social', 'Close') }}</Button>
|
<Button type="primary" @click="closeModal">{{ t('social', 'Close') }}</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -51,14 +51,27 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
modal: false,
|
modal: false,
|
||||||
|
internalDescription: '',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
this.internalDescription = this.preview.description
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
deletePreview() {
|
||||||
|
this.$store.dispatch('deleteAttachement', {
|
||||||
|
id: this.preview.id,
|
||||||
|
})
|
||||||
|
},
|
||||||
showModal() {
|
showModal() {
|
||||||
this.modal = true
|
this.modal = true
|
||||||
},
|
},
|
||||||
closeModal() {
|
closeModal() {
|
||||||
this.modal = false
|
this.modal = false
|
||||||
|
this.$store.dispatch('updateAttachement', {
|
||||||
|
id: this.preview.id,
|
||||||
|
description: this.internalDescription,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
@ -74,7 +87,7 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
backgroundStyle() {
|
backgroundStyle() {
|
||||||
return {
|
return {
|
||||||
backgroundImage: `url("${this.preview.url}")`,
|
backgroundImage: `url("${this.preview.preview_url}")`,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -53,7 +53,9 @@ const state = {
|
||||||
* @member {boolean}
|
* @member {boolean}
|
||||||
*/
|
*/
|
||||||
composerDisplayStatus: false,
|
composerDisplayStatus: false,
|
||||||
draft: null,
|
draft: {
|
||||||
|
attachements: []
|
||||||
|
},
|
||||||
}
|
}
|
||||||
const mutations = {
|
const mutations = {
|
||||||
addToTimeline(state, data) {
|
addToTimeline(state, data) {
|
||||||
|
@ -112,7 +114,22 @@ const mutations = {
|
||||||
if (typeof parentAnnounce.id !== 'undefined') {
|
if (typeof parentAnnounce.id !== 'undefined') {
|
||||||
Vue.set(state.timeline[parentAnnounce.id].cache[parentAnnounce.object].object.action.values, 'boosted', false)
|
Vue.set(state.timeline[parentAnnounce.id].cache[parentAnnounce.object].object.action.values, 'boosted', false)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
addAttachement(state, {id, description, url, preview_url}) {
|
||||||
|
state.draft.attachements.push({id, description, url, preview_url})
|
||||||
|
},
|
||||||
|
updateAttachement(state, {id, description, url, preview_url}) {
|
||||||
|
const index = state.draft.attachements.findIndex(item => {
|
||||||
|
return id === item.id
|
||||||
|
})
|
||||||
|
state.draft.attachements.splice(index, 1, {id, description, url, preview_url})
|
||||||
|
},
|
||||||
|
deleteAttachement(state, {id}) {
|
||||||
|
const index = state.draft.attachements.findIndex(item => {
|
||||||
|
return id === item.id
|
||||||
|
})
|
||||||
|
state.draft.attachements.splice(index, 1)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
const getters = {
|
const getters = {
|
||||||
getComposerDisplayStatus(state) {
|
getComposerDisplayStatus(state) {
|
||||||
|
@ -146,7 +163,7 @@ const actions = {
|
||||||
context.commit('setAccount', account)
|
context.commit('setAccount', account)
|
||||||
},
|
},
|
||||||
async uploadAttachement(context, formData) {
|
async uploadAttachement(context, formData) {
|
||||||
const res = await axios.post(generateUrl('apps/social/api/v1/media', formData, {
|
const res = await axios.post(generateUrl('apps/social/api/v1/media'), formData, {
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'multipart/form-data'
|
'Content-Type': 'multipart/form-data'
|
||||||
},
|
},
|
||||||
|
@ -158,9 +175,23 @@ const actions = {
|
||||||
preview_url: res.data.preview_url,
|
preview_url: res.data.preview_url,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
async uploadAttachement() {
|
async updateAttachement(context, {id, description}) {
|
||||||
|
const res = await axios.put(generateUrl('apps/social/api/v1/media/' + id), {
|
||||||
},
|
description,
|
||||||
|
})
|
||||||
|
context.commit('updateAttachement', {
|
||||||
|
id: res.data.id,
|
||||||
|
description: res.data.description,
|
||||||
|
url: res.data.url,
|
||||||
|
preview_url: res.data.preview_url,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async deleteAttachement(context, {id}) {
|
||||||
|
const res = await axios.delete(generateUrl('apps/social/api/v1/media/' + id))
|
||||||
|
context.commit('deleteAttachement', {
|
||||||
|
id: res.data.id,
|
||||||
|
})
|
||||||
|
},
|
||||||
post(context, post) {
|
post(context, post) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
axios.post(generateUrl('apps/social/api/v1/post'), { data: post }).then((response) => {
|
axios.post(generateUrl('apps/social/api/v1/post'), { data: post }).then((response) => {
|
||||||
|
|
Ładowanie…
Reference in New Issue