diff --git a/assets/components/errors/not-found.vue b/assets/components/errors/not-found.vue deleted file mode 100644 index 97b225e3..00000000 --- a/assets/components/errors/not-found.vue +++ /dev/null @@ -1,5 +0,0 @@ - diff --git a/assets/components/home/home.vue b/assets/components/home/home.vue index 22e45e65..313c91b7 100644 --- a/assets/components/home/home.vue +++ b/assets/components/home/home.vue @@ -65,112 +65,7 @@ :key="release.id" class="scene" > - - - {{ release.site.name }} - - {{ formatDate(release.date, 'MMM D, YYYY') }} - - {{ `(${formatDate(release.dateAdded, 'MMM D, YYYY')})` }}` - - - - - - - -
No thumbnail available
-
-
- -
- -

{{ release.title }}

-
- - - - - - - - -
+ @@ -178,15 +73,17 @@ @@ -252,145 +147,4 @@ export default { grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); grid-gap: 1rem; } - -.scene { - display: flex; - flex-direction: column; - box-sizing: border-box; - padding: 0 0 .5rem 0;; - border-radius: .25rem; - overflow: hidden; - box-shadow: 0 0 3px rgba(0, 0, 0, .25); -} - -.scene-banner { - position: relative; - margin: 0 0 .5rem 0; -} - -.scene-thumbnail { - width: 100%; - height: 200px; - display: flex; - justify-content: center; - align-items: center; - object-fit: cover; - background-position: center; - background-size: cover; - background-color: $shadow-hint; - color: $shadow; - text-shadow: 1px 1px 0 $highlight; -} - -.scene-row { - display: flex; - justify-content: space-between; - align-items: center; - box-sizing: border-box; - padding: 0 .5rem; - margin: 0 0 .25rem 0; -} - -.scene-details { - width: 100%; - display: flex; - align-items: center; - justify-content: space-between; - position: absolute; -} - -.scene-site, -.scene-date { - color: #fff; - background: rgba(0, 0, 0, .5); - font-size: .8rem; - padding: .25rem; - text-decoration: none; -} - -.scene-site { - border-radius: 0 0 .25rem 0; - font-weight: bold; -} - -.scene-date { - border-radius: 0 0 0 .25rem; -} - -.scene-info { - flex-grow: 1; -} - -.scene-link { - text-decoration: none; -} - -.scene-title { - color: $text; - margin: 0; - font-size: 1rem; - word-wrap: break-word; - overflow: hidden; - max-height: 3rem; - line-height: 1.5rem; -} - -.scene-network { - color: #555; - margin: 0 .25rem 0 0; - font-size: .8rem; -} - -.scene-actors { - word-wrap: break-word; - overflow: hidden; - max-height: 2.5rem; - line-height: 1.25rem; -} - -.scene-tags { - word-wrap: break-word; - overflow: hidden; - max-height: 2.5rem; - line-height: 1.25rem; -} - -.scene-actor, -.scene-tag { - margin: 0 .25rem 0 0; -} - -.scene-actor { - font-size: .9rem; -} - -.scene-tag { - font-size: .75rem; -} - -.scene-actor:not(:last-of-type)::after, -.scene-tag:not(:last-child):after { - content: ","; -} - -.actor-link, -.tag-link { - text-decoration: none; - - &:hover { - color: $primary; - } -} - -.actor-link { - color: $link; -} - -.tag-link { - color: $shadow-strong; -} - -.thumbnail { - width: 300px; -} diff --git a/assets/components/release/release.vue b/assets/components/release/release.vue index b5a001fc..9eec9318 100644 --- a/assets/components/release/release.vue +++ b/assets/components/release/release.vue @@ -78,17 +78,14 @@ {{ release.network.name }}: - {{ release.network.name }}: - {{ Math.floor(release.duration / 3600) }}: {{ Math.floor((release.duration % 3600) / 60).toString().padStart(2, '0') }}: {{ (release.duration % 60).toString().padStart(2, '0') }} diff --git a/assets/css/_theme.scss b/assets/css/_theme.scss index d0fc10d1..9c8e6743 100644 --- a/assets/css/_theme.scss +++ b/assets/css/_theme.scss @@ -1,6 +1,7 @@ /* $primary: #ff886c; */ $primary: #ff6c88; +$background: #fff; $text: #222; $text-contrast: #fff; diff --git a/assets/css/style.scss b/assets/css/style.scss index 2d6d2a33..6820f9d3 100644 --- a/assets/css/style.scss +++ b/assets/css/style.scss @@ -26,3 +26,11 @@ body { color: $primary; margin: 0 0 1rem 0; } + +.icon.icon-href { + fill: $shadow; + + :hover { + fill: $primary; + } +} diff --git a/assets/js/releases/state.js b/assets/js/releases/state.js index 427473f0..ff8b4c56 100644 --- a/assets/js/releases/state.js +++ b/assets/js/releases/state.js @@ -1,4 +1 @@ -export default { - authenticated: false, - user: null, -}; +export default {}; diff --git a/assets/js/router.js b/assets/js/router.js index 1fa4b894..8beb25ac 100644 --- a/assets/js/router.js +++ b/assets/js/router.js @@ -3,8 +3,10 @@ import VueRouter from 'vue-router'; import Home from '../components/home/home.vue'; import Release from '../components/release/release.vue'; +import Site from '../components/site/site.vue'; +import Network from '../components/network/network.vue'; import Actor from '../components/actor/actor.vue'; -import NotFound from '../components/errors/not-found.vue'; +import NotFound from '../components/errors/404.vue'; Vue.use(VueRouter); @@ -29,6 +31,16 @@ const routes = [ component: Actor, name: 'actor', }, + { + path: '/site/:siteSlug', + component: Site, + name: 'site', + }, + { + path: '/network/:networkSlug', + component: Network, + name: 'network', + }, { path: '*', component: NotFound, diff --git a/assets/js/store.js b/assets/js/store.js index ed3317ce..755a9856 100644 --- a/assets/js/store.js +++ b/assets/js/store.js @@ -3,6 +3,7 @@ import Vuex from 'vuex'; import initAuthStore from './auth/auth'; import initReleasesStore from './releases/releases'; +import initNetworksStore from './networks/networks'; function initStore(router) { Vue.use(Vuex); @@ -11,6 +12,7 @@ function initStore(router) { store.registerModule('auth', initAuthStore(store, router)); store.registerModule('releases', initReleasesStore(store, router)); + store.registerModule('networks', initNetworksStore(store, router)); return store; } diff --git a/config/default.js b/config/default.js index 8dced402..79d9acf2 100644 --- a/config/default.js +++ b/config/default.js @@ -79,7 +79,7 @@ module.exports = { width: 30, }, ], - photoPath: '/mnt/stor/Pictures/traxxx', + photoPath: './', filename: { dateFormat: 'DD-MM-YYYY', actorsJoin: ', ', diff --git a/public/css/style.css b/public/css/style.css index 553d9409..30d3c2fe 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -1,3 +1,123 @@ +/* $primary: #ff886c; */ +.tile[data-v-3abcf101] { + display: flex; + flex-direction: column; + box-sizing: border-box; + padding: 0 0 .5rem 0; + border-radius: .25rem; + overflow: hidden; + box-shadow: 0 0 3px rgba(0, 0, 0, 0.25); + height: 100%; +} +.banner[data-v-3abcf101] { + position: relative; + margin: 0 0 .5rem 0; +} +.thumbnail[data-v-3abcf101] { + width: 100%; + height: 200px; + display: flex; + justify-content: center; + align-items: center; + -o-object-fit: cover; + object-fit: cover; + background-position: center; + background-size: cover; + background-color: rgba(0, 0, 0, 0.1); + color: rgba(0, 0, 0, 0.5); + text-shadow: 1px 1px 0 rgba(255, 255, 255, 0.5); +} +.row[data-v-3abcf101] { + display: flex; + justify-content: space-between; + align-items: center; + box-sizing: border-box; + padding: 0 .5rem; + margin: 0 0 .25rem 0; +} +.details[data-v-3abcf101] { + width: 100%; + display: flex; + align-items: center; + justify-content: space-between; + position: absolute; +} +.site[data-v-3abcf101], +.date[data-v-3abcf101] { + color: #fff; + background: rgba(0, 0, 0, 0.5); + font-size: .8rem; + padding: .25rem; + text-decoration: none; +} +.site[data-v-3abcf101] { + border-radius: 0 0 .25rem 0; + font-weight: bold; +} +.date[data-v-3abcf101] { + border-radius: 0 0 0 .25rem; +} +.info[data-v-3abcf101] { + flex-grow: 1; +} +.link[data-v-3abcf101] { + text-decoration: none; +} +.title[data-v-3abcf101] { + color: #222; + margin: 0; + font-size: 1rem; + word-wrap: break-word; + overflow: hidden; + max-height: 3rem; + line-height: 1.5rem; +} +.network[data-v-3abcf101] { + color: #555; + margin: 0 .25rem 0 0; + font-size: .8rem; +} +.actors[data-v-3abcf101] { + word-wrap: break-word; + overflow: hidden; + max-height: 2.5rem; + line-height: 1.25rem; +} +.tags[data-v-3abcf101] { + word-wrap: break-word; + overflow: hidden; + max-height: 2.5rem; + line-height: 1.25rem; +} +.actor[data-v-3abcf101], +.tag[data-v-3abcf101] { + margin: 0 .25rem 0 0; +} +.actor[data-v-3abcf101] { + font-size: .9rem; +} +.tag[data-v-3abcf101] { + font-size: .75rem; +} +.actor[data-v-3abcf101]:not(:last-of-type)::after, +.tag[data-v-3abcf101]:not(:last-child):after { + content: ","; +} +.actor-link[data-v-3abcf101], +.tag-link[data-v-3abcf101] { + text-decoration: none; +} +.actor-link[data-v-3abcf101]:hover, + .tag-link[data-v-3abcf101]:hover { + color: #ff6c88; +} +.actor-link[data-v-3abcf101] { + color: #cc4466; +} +.tag-link[data-v-3abcf101] { + color: rgba(0, 0, 0, 0.7); +} + /* $primary: #ff886c; */ .filters-bar[data-v-5533e378] { display: block; @@ -38,126 +158,6 @@ grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); grid-gap: 1rem; } -.scene[data-v-5533e378] { - display: flex; - flex-direction: column; - box-sizing: border-box; - padding: 0 0 .5rem 0; - border-radius: .25rem; - overflow: hidden; - box-shadow: 0 0 3px rgba(0, 0, 0, 0.25); -} -.scene-banner[data-v-5533e378] { - position: relative; - margin: 0 0 .5rem 0; -} -.scene-thumbnail[data-v-5533e378] { - width: 100%; - height: 200px; - display: flex; - justify-content: center; - align-items: center; - -o-object-fit: cover; - object-fit: cover; - background-position: center; - background-size: cover; - background-color: rgba(0, 0, 0, 0.1); - color: rgba(0, 0, 0, 0.5); - text-shadow: 1px 1px 0 rgba(255, 255, 255, 0.5); -} -.scene-row[data-v-5533e378] { - display: flex; - justify-content: space-between; - align-items: center; - box-sizing: border-box; - padding: 0 .5rem; - margin: 0 0 .25rem 0; -} -.scene-details[data-v-5533e378] { - width: 100%; - display: flex; - align-items: center; - justify-content: space-between; - position: absolute; -} -.scene-site[data-v-5533e378], -.scene-date[data-v-5533e378] { - color: #fff; - background: rgba(0, 0, 0, 0.5); - font-size: .8rem; - padding: .25rem; - text-decoration: none; -} -.scene-site[data-v-5533e378] { - border-radius: 0 0 .25rem 0; - font-weight: bold; -} -.scene-date[data-v-5533e378] { - border-radius: 0 0 0 .25rem; -} -.scene-info[data-v-5533e378] { - flex-grow: 1; -} -.scene-link[data-v-5533e378] { - text-decoration: none; -} -.scene-title[data-v-5533e378] { - color: #222; - margin: 0; - font-size: 1rem; - word-wrap: break-word; - overflow: hidden; - max-height: 3rem; - line-height: 1.5rem; -} -.scene-network[data-v-5533e378] { - color: #555; - margin: 0 .25rem 0 0; - font-size: .8rem; -} -.scene-actors[data-v-5533e378] { - word-wrap: break-word; - overflow: hidden; - max-height: 2.5rem; - line-height: 1.25rem; -} -.scene-tags[data-v-5533e378] { - word-wrap: break-word; - overflow: hidden; - max-height: 2.5rem; - line-height: 1.25rem; -} -.scene-actor[data-v-5533e378], -.scene-tag[data-v-5533e378] { - margin: 0 .25rem 0 0; -} -.scene-actor[data-v-5533e378] { - font-size: .9rem; -} -.scene-tag[data-v-5533e378] { - font-size: .75rem; -} -.scene-actor[data-v-5533e378]:not(:last-of-type)::after, -.scene-tag[data-v-5533e378]:not(:last-child):after { - content: ","; -} -.actor-link[data-v-5533e378], -.tag-link[data-v-5533e378] { - text-decoration: none; -} -.actor-link[data-v-5533e378]:hover, - .tag-link[data-v-5533e378]:hover { - color: #ff6c88; -} -.actor-link[data-v-5533e378] { - color: #cc4466; -} -.tag-link[data-v-5533e378] { - color: rgba(0, 0, 0, 0.7); -} -.thumbnail[data-v-5533e378] { - width: 300px; -} /* $primary: #ff886c; */ .banner[data-v-2bc41e74] { @@ -226,6 +226,52 @@ color: #ff6c88; } +/* $primary: #ff886c; */ +.network[data-v-757c14c2] { + display: flex; + padding: 1rem; + overflow: hidden; +} +.header[data-v-757c14c2] { + display: flex; + justify-content: space-between; +} +.title[data-v-757c14c2] { + display: inline-block; + margin: 0 .5rem 0 0; +} +.heading[data-v-757c14c2] { + padding: 0; + margin: 1rem 0; +} +.logo[data-v-757c14c2] { + height: 3rem; +} +.scenes[data-v-757c14c2] { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr)); + grid-gap: 1rem; +} + +/* $primary: #ff886c; */ +.errorpage[data-v-29109daf] { + background: #fff; + color: #ff6c88; + height: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: 2rem; +} +.error[data-v-29109daf] { + margin: 0; +} +.home[data-v-29109daf] { + color: rgba(0, 0, 0, 0.5); + margin: 3rem 0; +} + /* $primary: #ff886c; */ .noselect { user-select: none; @@ -254,6 +300,11 @@ body { color: #ff6c88; margin: 0 0 1rem 0; } +.icon.icon-href { + fill: rgba(0, 0, 0, 0.5); } + .icon.icon-href :hover { + fill: #ff6c88; } + /* $primary: #ff886c; */ .header[data-v-10b7ec04] { color: #fff; diff --git a/src/releases.js b/src/releases.js index 877f8e8b..4996fb1e 100644 --- a/src/releases.js +++ b/src/releases.js @@ -41,18 +41,21 @@ async function curateRelease(release) { site: { id: release.site_id, name: release.site_name, + slug: release.site_slug, url: release.site_url, }, studio: release.studio_id ? { id: release.studio_id, name: release.studio_name, + slug: release.studio_slug, url: release.studio_url, } : null, network: { id: release.network_id, name: release.network_name, + slug: release.network_slug, url: release.network_url, }, }; @@ -63,14 +66,48 @@ function curateReleases(releases) { } async function fetchReleases(releaseId) { - // const thumbnails = await fs.readdir(path.join(config.thumbnailPath, release.site.id.toString(), release.id.toString())); - const releases = await knex('releases') .where(releaseId ? { 'releases.id': releaseId } : {}) .select( - 'releases.*', 'sites.name as site_name', 'sites.url as site_url', 'sites.network_id', - 'studios.name as studio_name', 'studios.url as studio_url', - 'networks.name as network_name', 'networks.url as network_url', + 'releases.*', 'sites.name as site_name', 'sites.slug as site_slug', 'sites.url as site_url', 'sites.network_id', + 'studios.name as studio_name', 'sites.slug as site_slug', 'studios.url as studio_url', + 'networks.name as network_name', 'networks.slug as network_slug', 'networks.url as network_url', + ) + .leftJoin('sites', 'releases.site_id', 'sites.id') + .leftJoin('studios', 'releases.studio_id', 'studios.id') + .leftJoin('networks', 'sites.network_id', 'networks.id') + .orderBy([{ column: 'date', order: 'desc' }, { column: 'created_at', order: 'desc' }]) + .limit(100); + + return curateReleases(releases); +} + +async function fetchSiteReleases(siteId, siteSlug) { + const releases = await knex('releases') + .where({ 'sites.id': siteId }) + .orWhere({ 'sites.slug': siteSlug }) + .select( + 'releases.*', 'sites.name as site_name', 'sites.slug as site_slug', 'sites.url as site_url', 'sites.network_id', + 'studios.name as studio_name', 'sites.slug as site_slug', 'studios.url as studio_url', + 'networks.name as network_name', 'networks.slug as network_slug', 'networks.url as network_url', + ) + .leftJoin('sites', 'releases.site_id', 'sites.id') + .leftJoin('studios', 'releases.studio_id', 'studios.id') + .leftJoin('networks', 'sites.network_id', 'networks.id') + .orderBy([{ column: 'date', order: 'desc' }, { column: 'created_at', order: 'desc' }]) + .limit(100); + + return curateReleases(releases); +} + +async function fetchNetworkReleases(networkId, networkSlug) { + const releases = await knex('releases') + .where({ 'networks.id': networkId }) + .orWhere({ 'networks.slug': networkSlug }) + .select( + 'releases.*', 'sites.name as site_name', 'sites.slug as site_slug', 'sites.url as site_url', 'sites.network_id', + 'studios.name as studio_name', 'sites.slug as site_slug', 'studios.url as studio_url', + 'networks.name as network_name', 'networks.slug as network_slug', 'networks.url as network_url', ) .leftJoin('sites', 'releases.site_id', 'sites.id') .leftJoin('studios', 'releases.studio_id', 'studios.id') @@ -83,4 +120,6 @@ async function fetchReleases(releaseId) { module.exports = { fetchReleases, + fetchSiteReleases, + fetchNetworkReleases, }; diff --git a/src/web/releases.js b/src/web/releases.js index 44dcab07..9012d6e2 100644 --- a/src/web/releases.js +++ b/src/web/releases.js @@ -1,6 +1,6 @@ 'use strict'; -const { fetchReleases } = require('../releases'); +const { fetchReleases, fetchNetworkReleases, fetchSiteReleases } = require('../releases'); async function fetchReleasesApi(req, res) { const releases = await fetchReleases(req.params.releaseId); @@ -8,6 +8,26 @@ async function fetchReleasesApi(req, res) { res.send(releases); } +async function fetchNetworkReleasesApi(req, res) { + const networkId = typeof req.params.networkId === 'number' ? req.params.networkId : null; + const networkSlug = typeof req.params.networkId === 'string' ? req.params.networkId : null; + + const releases = await fetchNetworkReleases(networkId, networkSlug); + + res.send(releases); +} + +async function fetchSiteReleasesApi(req, res) { + const siteId = typeof req.params.siteId === 'number' ? req.params.siteId : null; + const siteSlug = typeof req.params.siteId === 'string' ? req.params.siteId : null; + + const releases = await fetchSiteReleases(siteId, siteSlug); + + res.send(releases); +} + module.exports = { fetchReleases: fetchReleasesApi, + fetchNetworkReleases: fetchNetworkReleasesApi, + fetchSiteReleases: fetchSiteReleasesApi, }; diff --git a/src/web/server.js b/src/web/server.js index 2fc9e8eb..caba17b8 100644 --- a/src/web/server.js +++ b/src/web/server.js @@ -6,7 +6,9 @@ const express = require('express'); const Router = require('express-promise-router'); const bodyParser = require('body-parser'); -const { fetchReleases } = require('./releases'); +const { fetchReleases, fetchNetworkReleases, fetchSiteReleases } = require('./releases'); +const { fetchNetworks, fetchNetworksFromReleases } = require('./networks'); +const { fetchSites } = require('./sites'); function initServer() { const app = express(); @@ -18,6 +20,15 @@ function initServer() { router.get('/api/releases', fetchReleases); router.get('/api/releases/:releaseId', fetchReleases); + router.get('/api/releases/networks', fetchNetworksFromReleases); + + router.get('/api/networks', fetchNetworks); + router.get('/api/networks/:networkId', fetchNetworks); + router.get('/api/networks/:networkId/releases', fetchNetworkReleases); + + router.get('/api/sites', fetchSites); + router.get('/api/sites/:siteId', fetchSites); + router.get('/api/sites/:siteId/releases', fetchSiteReleases); router.get('*', (req, res) => { res.sendFile(path.join(__dirname, '../../public/index.html'));