diff options
| -rw-r--r-- | src/api/routes/albums/albumGET.js | 11 | ||||
| -rw-r--r-- | src/api/routes/albums/link/linkPOST.js | 3 | ||||
| -rw-r--r-- | src/api/utils/Util.js | 4 | ||||
| -rw-r--r-- | src/site/components/grid/Grid.vue | 77 | ||||
| -rw-r--r-- | src/site/router/index.js | 1 | ||||
| -rw-r--r-- | src/site/views/PublicAlbum.vue | 105 | ||||
| -rw-r--r-- | src/site/views/dashboard/Albums.vue | 24 |
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' }; }, |