Skip to content
26 changes: 20 additions & 6 deletions client/api/asset.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
import request from './request';

const urls = {
base: repositoryId => `/repositories/${repositoryId}/assets`
base: 'assets',
repository: repositoryId => `repositories/${repositoryId}/assets`
};

function getUrl(repositoryId, key) {
function getUrl(key) {
const params = { key };
return request.get(urls.base(repositoryId), { params }).then(res => res.data.url);
return request.get(urls.base, { params }).then(res => res.data.url);
}

function upload(repositoryId, data) {
return request.post(urls.base(repositoryId), data).then(res => res.data);
function upload(data) {
return request.post(urls.base, data).then(res => res.data);
}

function getRepositoryAssetUrl(repositoryId, key) {
const params = { key };
return request.get(urls.repository(repositoryId), { params })
.then(res => res.data.url);
}

function uploadRepositoryAsset(repositoryId, data) {
return request.post(urls.repository(repositoryId), data)
.then(res => res.data);
}

export default {
getUrl,
upload
upload,
getRepositoryAssetUrl,
uploadRepositoryAsset
};
79 changes: 41 additions & 38 deletions client/components/common/FileInput.vue
Original file line number Diff line number Diff line change
@@ -1,49 +1,56 @@
<template>
<form @submit.prevent>
<v-file-input
v-if="!fileKey"
:ref="id"
@change.native="upload"
@click:append="$refs[id].$el.querySelector('input').click()"
:accept="acceptedFileTypes"
:label="label"
:placeholder="placeholder"
:outlined="outlined"
:dense="dense"
:clearable="false"
:append-icon="uploading ? 'mdi-loading mdi-spin' : 'mdi-upload'"
prepend-icon="" />
<div v-else class="mb-5 px-1 grey--text text--darken-3">
<div>{{ label }}</div>
<v-btn
@click="downloadFile(fileKey, fileName)"
text
class="grey--text text--darken-4 text-none px-0">
{{ fileName | truncate(35) }}
</v-btn>
<v-btn
@click="deleteFile({ id, fileName })"
color="grey darken-4"
icon x-small
class="ml-1">
<v-icon>mdi-close</v-icon>
</v-btn>
</div>
</form>
<upload-provider
v-slot="{ uploading, uploadFile, downloadFile, deleteFile }"
@upload="$emit('upload', $event)"
@uploading="$emit('update:uploading', $event)"
@delete="$emit('delete', $event)"
:repository-id="repositoryId">
<form @submit.prevent>
<v-file-input
v-if="!fileKey"
:ref="id"
@change.native="uploadFile"
@click:append="$refs[id].$el.querySelector('input').click()"
:accept="acceptedFileTypes"
:label="label"
:placeholder="placeholder"
:outlined="outlined"
:dense="dense"
:clearable="false"
:append-icon="uploading ? 'mdi-loading mdi-spin' : 'mdi-upload'"
prepend-icon="" />
<div v-else class="mb-5 px-1 grey--text text--darken-3">
<div>{{ label }}</div>
<v-btn
@click="downloadFile(fileKey, fileName)"
text
class="grey--text text--darken-4 text-none px-0">
{{ fileName | truncate(35) }}
</v-btn>
<v-btn
@click="deleteFile({ id, fileName })"
color="grey darken-4"
icon x-small
class="ml-1">
<v-icon>mdi-close</v-icon>
</v-btn>
</div>
</form>
</upload-provider>
</template>

<script>
import get from 'lodash/get';
import uniqueId from 'lodash/uniqueId';
import uploadMixin from '@/components/common/mixins/upload';
import UploadProvider from '@/components/common/UploadProvider';

export default {
name: 'file-input',
mixins: [uploadMixin],
props: {
id: { type: String, default: () => uniqueId('file_') },
fileKey: { type: String, default: '' },
fileName: { type: String, default: '' },
repositoryId: { type: Number, default: null },
validate: { type: Object, default: () => ({ ext: [] }) },
label: { type: String, default: 'File upload' },
placeholder: { type: String, default: 'Choose a file' },
Expand All @@ -56,10 +63,6 @@ export default {
return ext.length ? `.${ext.join(',.')}` : '';
}
},
watch: {
uploading(val) {
this.$emit('update:uploading', val);
}
}
components: { UploadProvider }
};
</script>
2 changes: 2 additions & 0 deletions client/components/common/InputAsset.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
:uploading.sync="uploading"
:validate="{ ext: extensions }"
:confirm-deletion="false"
:repository-id="repositoryId"
:label="uploadLabel"
class="upload-btn" />
<template v-if="file">
Expand Down Expand Up @@ -93,6 +94,7 @@ function isUploaded(url) {
export default {
name: 'input-asset',
props: {
repositoryId: { type: Number, default: null },
url: { type: String, default: null },
publicUrl: { type: String, default: null },
extensions: { type: Array, required: true },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
<template>
<div>
<slot v-bind="{ uploading, uploadFile, downloadFile, deleteFile }"></slot>
</div>
</template>

<script>
import downloadMixin from 'utils/downloadMixin';
import loader from '@/components/common/loader';
import { mapGetters } from 'vuex';
import { mapRequests } from '@/plugins/radio';

export default {
inject: ['$storageService'],
mixins: [downloadMixin],
props: {
repositoryId: { type: Number, default: null }
},
data: () => ({ uploading: false }),
computed: mapGetters('repository', { repositoryId: 'id' }),
methods: {
...mapRequests('app', ['showConfirmationModal']),
createFileForm(e) {
Expand All @@ -16,9 +24,13 @@ export default {
if (!file) return;
this.form.append('file', file, file.name);
},
upload: loader(function (e) {
upload(data) {
if (!this.repositoryId) return this.storageService.upload(data);
return this.$storageService.uploadRepositoryAsset(this.repositoryId, data);
},
uploadFile: loader(function (e) {
this.createFileForm(e);
return this.$storageService.upload(this.repositoryId, this.form)
return this.upload(this.form)
.then(data => {
const { name } = this.form.get('file');
this.$emit('upload', { ...data, name });
Expand All @@ -27,7 +39,9 @@ export default {
});
}, 'uploading'),
async downloadFile(key, name) {
const url = await this.$storageService.getUrl(this.repositoryId, key);
const url = this.repositoryId
? await this.$storageService.getRepositoryAssetUrl(this.repositoryId, key)
: await this.$storageService.getUrl(key);
return this.download(url, name);
},
deleteFile(item) {
Expand All @@ -37,5 +51,11 @@ export default {
action: () => this.$emit('delete', item.id, null)
});
}
},
watch: {
uploading(val) {
this.$emit('uploading', val);
}
}
};
</script>;
23 changes: 13 additions & 10 deletions client/components/common/tce-core/UploadBtn.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
<template>
<div class="file-upload">
<upload-provider
v-slot="{ uploading, downloadFile, deleteFile }"
ref="provider"
@upload="$emit('upload', $event)"
@uploading="$emit('update:uploading', $event)"
@delete="$emit('delete', $event)"
:repository-id="repositoryId"
class="file-upload">
<form @submit.prevent class="upload-form">
<validation-provider ref="validator" :rules="validate">
<input
Expand Down Expand Up @@ -32,35 +39,31 @@
<v-icon>mdi-delete</v-icon>
</v-btn>
</form>
</div>
</upload-provider>
</template>

<script>
import uniqueId from 'lodash/uniqueId';
import uploadMixin from '@/components/common/mixins/upload';
import UploadProvider from '@/components/common/UploadProvider';

export default {
name: 'upload-btn',
mixins: [uploadMixin],
props: {
id: { type: String, default: () => uniqueId('file_') },
fileName: { type: String, default: '' },
fileKey: { type: String, default: '' },
repositoryId: { type: Number, default: null },
validate: { type: Object, default: () => ({ ext: [] }) },
label: { type: String, default: 'Choose a file' },
sm: { type: Boolean, default: false }
},
methods: {
async validateAndUpload(e) {
const { valid } = await this.$refs.validator.validate(e);
if (valid) this.upload(e);
if (valid) this.$refs.provider.uploadFile(e);
}
},
watch: {
uploading(val) {
this.$emit('update:uploading', val);
}
}
components: { UploadProvider }
};
</script>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<v-toolbar-title class="pl-1">Audio Component</v-toolbar-title>
<input-asset
@input="save"
:repository-id="element.repositoryId"
:url="url"
:public-url="publicUrl"
:extensions="[
Expand Down
13 changes: 8 additions & 5 deletions client/components/content-elements/tce-image/server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const mime = require('mime-types');

const DEFAULT_IMAGE_EXTENSION = 'png';

function processImage(asset, { storage }) {
function processImage(asset, { getRepositoryStorage }) {
const image = asset.data.url;
const base64Pattern = /^data:image\/(\w+);base64,/;

Expand All @@ -22,21 +22,24 @@ function processImage(asset, { storage }) {
return Promise.resolve(asset);
}

const storage = getRepositoryStorage(asset.repositoryId);
const file = Buffer.from(image.replace(base64Pattern, ''), 'base64');
const extension = image.match(base64Pattern)[1] || DEFAULT_IMAGE_EXTENSION;
const hashString = `${asset.id}${file}`;
const hash = crypto.createHash('md5').update(hashString).digest('hex');
const storagePath = storage.getPath(asset.repositoryId);
const key = `${storagePath}/${asset.id}/${hash}.${extension}`;
const key = `${asset.id}/${hash}.${extension}`;
asset.data.url = key;
return saveFile(key, file, storage).then(() => asset);
}

function resolveImage(asset, { storage, storageProxy }) {
function resolveImage(asset, { getRepositoryStorage, storageProxy }) {
if (!asset.data || !asset.data.url) return Promise.resolve(asset);

const storage = getRepositoryStorage(asset.repositoryId);

function getUrl(key) {
asset.data.url = storageProxy.getFileUrl(key);
const fullKey = storage.getFullKey(key);
asset.data.url = storageProxy.getFileUrl(fullKey);
return asset;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<v-toolbar-title>PDF Component</v-toolbar-title>
<input-asset
@input="save"
:repository-id="element.repositoryId"
:url="url"
:public-url="publicUrl"
:extensions="['.pdf']"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<v-toolbar-title class="pl-1">Video component</v-toolbar-title>
<input-asset
@input="save"
:repository-id="element.repositoryId"
:url="url"
:public-url="publicUrl"
:extensions="['.mp4']"
Expand Down
4 changes: 0 additions & 4 deletions config/server/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@

module.exports = {
provider: process.env.STORAGE_PROVIDER,
// The path where assets will be stored inside repository/${repositoryId} folder.
// For example, if path is equal to assets,
// assets will be stored inside repository/${repositoryId}/assets folder
path: 'assets',
protocol: 'storage://',
amazon: {
key: process.env.STORAGE_KEY,
Expand Down
12 changes: 8 additions & 4 deletions server/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,22 @@ const express = require('express');
const helmet = require('helmet');
const origin = require('./shared/origin');
const path = require('path');
const storage = require('./repository/storage');
const storageProxy = require('./repository/proxy');
const serviceProvider = require('./shared/serviceProvider');
// eslint-disable-next-line require-sort/require-sort
require('express-async-errors');

/* eslint-disable require-sort/require-sort */
const auth = require('./shared/auth');
const config = require('../config/server');
const logger = require('./shared/logger')();
const storage = require('./shared/storage')(config.storage);
serviceProvider.set('storage', storage);
const storageProxy = require('./shared/storage/proxy')(config.storage.proxy);
serviceProvider.set('storageProxy', storageProxy);
const router = require('./router');
/* eslint-enable */

storageProxy.addStorage('repository', storage);
const { STORAGE_PATH } = process.env;

const app = express();
Expand All @@ -41,8 +45,8 @@ app.use(origin());
app.use(express.static(path.join(__dirname, '../dist/')));
if (STORAGE_PATH) app.use(express.static(STORAGE_PATH));
if (storageProxy.isSelfHosted) {
const { proxy: middleware } = require('./shared/storage/proxy/mw')(storage, storageProxy);
app.use(storageProxy.path, middleware);
const { getFile } = require('./shared/storage/proxy/mw');
app.use(storageProxy.path, getFile(storageProxy));
}

// Mount main router.
Expand Down
Loading