aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/api/routes/albums/albumGET.js11
-rw-r--r--src/api/routes/albums/link/linkPOST.js3
-rw-r--r--src/api/utils/Util.js4
-rw-r--r--src/site/components/grid/Grid.vue77
-rw-r--r--src/site/router/index.js1
-rw-r--r--src/site/views/PublicAlbum.vue105
-rw-r--r--src/site/views/dashboard/Albums.vue24
7 files changed, 182 insertions, 43 deletions
diff --git a/src/api/routes/albums/albumGET.js b/src/api/routes/albums/albumGET.js
index f9e5208..655db13 100644
--- a/src/api/routes/albums/albumGET.js
+++ b/src/api/routes/albums/albumGET.js
@@ -1,6 +1,7 @@
const Route = require('../../structures/Route');
const config = require('../../../../config');
const db = require('knex')(config.server.database);
+const Util = require('../../utils/Util');
class albumGET extends Route {
constructor() {
@@ -21,13 +22,19 @@ class albumGET extends Route {
if (!album) return res.status(400).json({ message: 'Album not found' });
const fileList = await db.table('albumsFiles').where('albumId', link.albumId);
- const fileIds = fileList.filter(el => el.file.fileId);
+ const fileIds = fileList.map(el => el.fileId);
const files = await db.table('files')
- .where('id', fileIds)
+ .whereIn('id', fileIds)
+ .orderBy('id', 'desc')
.select('name');
+ for (let file of files) {
+ file = Util.constructFilePublicLink(file);
+ }
return res.json({
message: 'Successfully retrieved files',
+ name: album.name,
+ downloadEnabled: link.enableDownload,
files
});
}
diff --git a/src/api/routes/albums/link/linkPOST.js b/src/api/routes/albums/link/linkPOST.js
index 26a527a..9c8c0bc 100644
--- a/src/api/routes/albums/link/linkPOST.js
+++ b/src/api/routes/albums/link/linkPOST.js
@@ -17,6 +17,9 @@ class linkPOST extends Route {
const exists = await db.table('albums').where('id', albumId).first();
if (!exists) return res.status(400).json({ message: 'Album doesn\t exist' });
+ const count = await db.table('links').where('albumId', albumId).count({ count: 'id' });
+ if (count[0].count >= config.albums.maxLinksPerAlbum) return res.status(400).json({ message: 'Maximum links per album reached' });
+
const identifier = await Util.getUniqueAlbumIdentifier();
if (!identifier) return res.status(500).json({ message: 'There was a problem allocating a link for your album' });
diff --git a/src/api/utils/Util.js b/src/api/utils/Util.js
index d8ae735..e0ad031 100644
--- a/src/api/utils/Util.js
+++ b/src/api/utils/Util.js
@@ -105,7 +105,7 @@ class Util {
static getUniqueAlbumIdentifier() {
const retry = async (i = 0) => {
const identifier = randomstring.generate({
- length: config.uploads.generatedAlbumLinkLength,
+ length: config.albums.generatedAlbumLinkLength,
capitalization: 'lowercase'
});
const exists = await db.table('links').where({ identifier }).first();
@@ -113,7 +113,7 @@ class Util {
/*
It's funny but if you do i++ the asignment never gets done resulting in an infinite loop
*/
- if (i < config.uploads.retryAlbumLinkTimes) return retry(i + 1);
+ if (i < config.albums.retryAlbumLinkTimes) return retry(i + 1);
log.error('Couldnt allocate identifier for album');
return null;
};
diff --git a/src/site/components/grid/Grid.vue b/src/site/components/grid/Grid.vue
index 09922c9..50e626b 100644
--- a/src/site/components/grid/Grid.vue
+++ b/src/site/components/grid/Grid.vue
@@ -82,36 +82,46 @@
<WaterfallItem v-for="(item, index) in files"
v-if="showWaterfall && item.thumb"
:key="index"
+ :width="width"
move-class="item-move">
- <img :src="`${item.thumb}`">
- <div :class="{ fixed }"
- class="actions">
- <b-tooltip label="Link"
- position="is-top">
- <a :href="`${item.url}`"
- target="_blank">
- <i class="icon-web-code"/>
- </a>
- </b-tooltip>
- <b-tooltip label="Albums"
- position="is-top">
- <a @click="manageAlbums(item)">
- <i class="icon-interface-window"/>
- </a>
- </b-tooltip>
- <b-tooltip label="Tags"
- position="is-top">
- <a @click="manageTags(item)">
- <i class="icon-ecommerce-tag-c"/>
- </a>
- </b-tooltip>
- <b-tooltip label="Delete"
- position="is-top">
- <a @click="deleteFile(item, index)">
- <i class="icon-editorial-trash-a-l"/>
- </a>
- </b-tooltip>
- </div>
+ <template v-if="isPublic">
+ <a :href="`${item.url}`"
+ target="_blank">
+ <img :src="`${item.thumb}`">
+ </a>
+ </template>
+ <template v-else>
+ <img :src="`${item.thumb}`">
+ <div v-if="!isPublic"
+ :class="{ fixed }"
+ class="actions">
+ <b-tooltip label="Link"
+ position="is-top">
+ <a :href="`${item.url}`"
+ target="_blank">
+ <i class="icon-web-code"/>
+ </a>
+ </b-tooltip>
+ <b-tooltip label="Albums"
+ position="is-top">
+ <a @click="manageAlbums(item)">
+ <i class="icon-interface-window"/>
+ </a>
+ </b-tooltip>
+ <b-tooltip label="Tags"
+ position="is-top">
+ <a @click="manageTags(item)">
+ <i class="icon-ecommerce-tag-c"/>
+ </a>
+ </b-tooltip>
+ <b-tooltip label="Delete"
+ position="is-top">
+ <a @click="deleteFile(item, index)">
+ <i class="icon-editorial-trash-a-l"/>
+ </a>
+ </b-tooltip>
+ </div>
+ </template>
</WaterfallItem>
</Waterfall>
</template>
@@ -132,12 +142,19 @@ export default {
fixed: {
type: Boolean,
default: false
+ },
+ isPublic: {
+ type: Boolean,
+ default: false
+ },
+ width: {
+ type: Number,
+ default: 150
}
},
data() {
return { showWaterfall: true };
},
- mounted() {},
methods: {
deleteFile(file, index) {
this.$dialog.confirm({
diff --git a/src/site/router/index.js b/src/site/router/index.js
index d1be2a6..7b9b051 100644
--- a/src/site/router/index.js
+++ b/src/site/router/index.js
@@ -12,6 +12,7 @@ const router = new Router({
{ path: '/dashboard', component: () => import('../views/Dashboard/Uploads.vue') },
{ path: '/dashboard/albums', component: () => import('../views/Dashboard/Albums.vue') },
{ path: '/dashboard/settings', component: () => import('../views/Dashboard/Settings.vue') },
+ { path: '/a/:identifier', component: () => import('../views/PublicAlbum.vue'), props: true }
// { path: '*', component: () => import('../views/NotFound.vue') }
]
});
diff --git a/src/site/views/PublicAlbum.vue b/src/site/views/PublicAlbum.vue
new file mode 100644
index 0000000..534c185
--- /dev/null
+++ b/src/site/views/PublicAlbum.vue
@@ -0,0 +1,105 @@
+<style lang="scss" scoped>
+ @import '../styles/colors.scss';
+ section { background-color: $backgroundLight1 !important; }
+
+ section.hero div.hero-body.align-top {
+ align-items: baseline;
+ flex-grow: 0;
+ padding-bottom: 0;
+ }
+
+ div.loading-container {
+ justify-content: center;
+ display: flex;
+ }
+</style>
+<style lang="scss">
+ @import '../styles/colors.scss';
+</style>
+
+<template>
+ <section class="hero is-fullheight">
+ <template v-if="files.length">
+ <div class="hero-body align-top">
+ <div class="container">
+ <h1 class="title">{{ name }}</h1>
+ <h2 class="subtitle">Serving {{ files.length }} files</h2>
+ <hr>
+ </div>
+ </div>
+ <div class="hero-body">
+ <div class="container">
+ <Grid v-if="files.length"
+ :files="files"
+ :isPublic="true"
+ :width="200"/>
+ </div>
+ </div>
+ </template>
+ <template v-else>
+ <div class="hero-body">
+ <div class="container loading-container">
+ <Loading class="square"/>
+ </div>
+ </div>
+ </template>
+ </section>
+</template>
+
+<script>
+import Grid from '../components/grid/Grid.vue';
+import Loading from '../components/loading/CubeShadow.vue';
+import axios from 'axios';
+import config from '../config.js';
+
+export default {
+ components: { Grid, Loading },
+ async getInitialData({ route, store }) {
+ try {
+ const res = await axios.get(`${config.baseURL}/album/${route.params.identifier}`);
+ return {
+ name: res.data.name,
+ downloadEnabled: res.data.downloadEnabled,
+ files: res.data.files
+ };
+ } catch (error) {
+ console.error(error);
+ return {
+ name: null,
+ downloadEnabled: false,
+ files: []
+ };
+ }
+ },
+ data() {
+ return {};
+ },
+ metaInfo() {
+ return {
+ title: `${this.name ? this.name : ''}`,
+ meta: [
+ { vmid: 'theme-color', name: 'theme-color', content: '#30a9ed' },
+ { vmid: 'twitter:card', name: 'twitter:card', content: 'summary' },
+ { vmid: 'twitter:title', name: 'twitter:title', content: `Album: ${this.name} | Files: ${this.files.length}` },
+ { vmid: 'twitter:description', name: 'twitter:description', content: 'A modern and self-hosted file upload service that can handle anything you throw at it. Fast uploads, file manager and sharing capabilities all crafted with a beautiful user experience in mind.' },
+ { vmid: 'twitter:image', name: 'twitter:image', content: `${this.files.length > 0 ? this.files[0].thumbSquare : '/public/images/share.jpg'}` },
+ { vmid: 'twitter:image:src', name: 'twitter:image:src', value: `${this.files.length > 0 ? this.files[0].thumbSquare : '/public/images/share.jpg'}` },
+
+ { vmid: 'og:url', property: 'og:url', content: `${config.URL}/a/${this.$route.params.identifier}` },
+ { vmid: 'og:title', property: 'og:title', content: `Album: ${this.name} | Files: ${this.files.length}` },
+ { vmid: 'og:description', property: 'og:description', content: 'A modern and self-hosted file upload service that can handle anything you throw at it. Fast uploads, file manager and sharing capabilities all crafted with a beautiful user experience in mind.' },
+ { vmid: 'og:image', property: 'og:image', content: `${this.files.length > 0 ? this.files[0].thumbSquare : '/public/images/share.jpg'}` },
+ { vmid: 'og:image:secure_url', property: 'og:image:secure_url', content: `${this.files.length > 0 ? this.files[0].thumbSquare : '/public/images/share.jpg'}` }
+ ]
+ };
+ },
+ mounted() {
+ this.$ga.page({
+ page: `/a/${this.$route.params.identifier}`,
+ title: `Album | ${this.name}`,
+ location: window.location.href
+ });
+ },
+ methods: {}
+};
+</script>
diff --git a/src/site/views/dashboard/Albums.vue b/src/site/views/dashboard/Albums.vue
index 2c7984c..57cd01f 100644
--- a/src/site/views/dashboard/Albums.vue
+++ b/src/site/views/dashboard/Albums.vue
@@ -211,7 +211,7 @@
<b-table-column field="identifier"
label="Link"
centered>
- <a :href="props.row.identifier"
+ <a :href="`${config.URL}/a/${props.row.identifier}`"
target="_blank">
{{ props.row.identifier }}
</a>
@@ -235,10 +235,11 @@
<b-switch :value="props.row.enabled "/>
</b-table-column>
- <b-table-column field="createdAt"
- label="Created at"
+ <b-table-column field="actions"
+ label="Actions"
centered>
- {{ props.row.createdAt }}
+ <button class="button is-danger"
+ @click="deleteLink(props.row.identifier)">Delete link</button>
</b-table-column>
</template>
<template slot="empty">
@@ -251,11 +252,11 @@
</template>
<template slot="footer">
<div class="has-text-right">
- <p class="control">
- <button :class="{ 'is-loading': album.isCreatingLink }"
- class="button is-primary"
- @click="createLink(album)">Create new link</button>
- </p>
+ <button :class="{ 'is-loading': album.isCreatingLink }"
+ class="button is-primary"
+ style="float: left"
+ @click="createLink(album)">Create new link</button>
+ {{ album.links.length }} / {{ config.maxLinksPerAlbum }} links created
</div>
</template>
</b-table>
@@ -285,6 +286,11 @@ export default {
newAlbumName: null
};
},
+ computed: {
+ config() {
+ return this.$store.state.config;
+ }
+ },
metaInfo() {
return { title: 'Uploads' };
},