Added tag page. Added default 'anal' tag to Vixen scraper for Tushy and Tushy Raw.
|
@ -113,14 +113,4 @@ export default {
|
||||||
content: ':';
|
content: ':';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenes {
|
|
||||||
display: grid;
|
|
||||||
grid-gap: 1rem;
|
|
||||||
margin: 0 0 1rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scenes {
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
.header {
|
.header {
|
||||||
color: $text-contrast;
|
color: $text-contrast;
|
||||||
background: $primary;
|
background: $primary;
|
||||||
padding: 1rem;
|
padding: .5rem 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo-link {
|
.logo-link {
|
||||||
|
|
|
@ -144,10 +144,4 @@ export default {
|
||||||
.filter {
|
.filter {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenes {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
|
|
||||||
grid-gap: 1rem;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -113,18 +113,13 @@ export default {
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sites,
|
.sites {
|
||||||
.scenes {
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: 1rem;
|
grid-gap: 1rem;
|
||||||
margin: 0 0 1rem 0;
|
margin: 0 0 1rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenes {
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
.sites {
|
.sites {
|
||||||
grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
|
grid-template-columns: repeat(auto-fit, 15rem);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -147,11 +147,7 @@ export default {
|
||||||
margin: 0 0 1rem 0;
|
margin: 0 0 1rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scenes {
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
.sites {
|
.sites {
|
||||||
grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
|
grid-template-columns: repeat(auto-fit, 15rem);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="tag"
|
||||||
|
class="content tag"
|
||||||
|
>
|
||||||
|
<div class="header">
|
||||||
|
<span>
|
||||||
|
<h2 class="title">{{ tag.name }}</h2>
|
||||||
|
|
||||||
|
<span class="description">{{ tag.description }}</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content-inner">
|
||||||
|
<h3 class="heading">Latest releases</h3>
|
||||||
|
|
||||||
|
<ul class="nolist scenes">
|
||||||
|
<li
|
||||||
|
v-for="release in releases"
|
||||||
|
:key="`release-${release.id}`"
|
||||||
|
>
|
||||||
|
<ReleaseTile :release="release" />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ReleaseTile from '../tile/release.vue';
|
||||||
|
|
||||||
|
async function mounted() {
|
||||||
|
[this.tag] = await this.$store.dispatch('fetchTags', this.$route.params.tagSlug);
|
||||||
|
this.releases = await this.$store.dispatch('fetchTagReleases', this.$route.params.tagSlug);
|
||||||
|
|
||||||
|
this.pageTitle = this.tag.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
ReleaseTile,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
tag: null,
|
||||||
|
releases: null,
|
||||||
|
pageTitle: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted,
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import 'theme';
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 .5rem 0 0;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.heading {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bio-heading {
|
||||||
|
display: inline-block;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: .5rem 0 0 0;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: ':';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -131,8 +131,11 @@ export default {
|
||||||
|
|
||||||
.thumbnail {
|
.thumbnail {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 12rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
object-position: 50% 0;
|
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-color: $shadow-hint;
|
background-color: $shadow-hint;
|
||||||
|
|
|
@ -30,7 +30,7 @@ export default {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 1rem;
|
padding: .5rem 1rem;
|
||||||
border-radius: .25rem;
|
border-radius: .25rem;
|
||||||
box-shadow: 0 0 3px rgba(0, 0, 0, .25);
|
box-shadow: 0 0 3px rgba(0, 0, 0, .25);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
/* $primary: #ff886c; */
|
/* $primary: #ff886c; */
|
||||||
|
$breakpoint: 720px;
|
||||||
$primary: #ff6c88;
|
$primary: #ff6c88;
|
||||||
|
|
||||||
$background: #fff;
|
$background: #fff;
|
||||||
|
|
|
@ -34,3 +34,15 @@ body {
|
||||||
fill: $primary;
|
fill: $primary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scenes {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(20rem, .5fr));
|
||||||
|
grid-gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(max-width: $breakpoint) {
|
||||||
|
.scenes {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(22.5rem, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import Release from '../components/release/release.vue';
|
||||||
import Site from '../components/site/site.vue';
|
import Site from '../components/site/site.vue';
|
||||||
import Network from '../components/network/network.vue';
|
import Network from '../components/network/network.vue';
|
||||||
import Actor from '../components/actor/actor.vue';
|
import Actor from '../components/actor/actor.vue';
|
||||||
|
import Tag from '../components/tag/tag.vue';
|
||||||
import NotFound from '../components/errors/404.vue';
|
import NotFound from '../components/errors/404.vue';
|
||||||
|
|
||||||
Vue.use(VueRouter);
|
Vue.use(VueRouter);
|
||||||
|
@ -19,12 +20,12 @@ const routes = [
|
||||||
{
|
{
|
||||||
path: '/scene/:releaseId',
|
path: '/scene/:releaseId',
|
||||||
component: Release,
|
component: Release,
|
||||||
name: 'release',
|
name: 'scene',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/movie/:releaseId',
|
path: '/movie/:releaseId',
|
||||||
component: Release,
|
component: Release,
|
||||||
name: 'release',
|
name: 'movie',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/actor/:actorSlug',
|
path: '/actor/:actorSlug',
|
||||||
|
@ -41,6 +42,11 @@ const routes = [
|
||||||
component: Network,
|
component: Network,
|
||||||
name: 'network',
|
name: 'network',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/tag/:tagSlug',
|
||||||
|
component: Tag,
|
||||||
|
name: 'tag',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '*',
|
path: '*',
|
||||||
component: NotFound,
|
component: NotFound,
|
||||||
|
|
|
@ -6,6 +6,7 @@ import initReleasesStore from './releases/releases';
|
||||||
import initSitesStore from './sites/sites';
|
import initSitesStore from './sites/sites';
|
||||||
import initNetworksStore from './networks/networks';
|
import initNetworksStore from './networks/networks';
|
||||||
import initActorsStore from './actors/actors';
|
import initActorsStore from './actors/actors';
|
||||||
|
import initTagsStore from './tags/tags';
|
||||||
|
|
||||||
function initStore(router) {
|
function initStore(router) {
|
||||||
Vue.use(Vuex);
|
Vue.use(Vuex);
|
||||||
|
@ -17,6 +18,7 @@ function initStore(router) {
|
||||||
store.registerModule('actors', initActorsStore(store, router));
|
store.registerModule('actors', initActorsStore(store, router));
|
||||||
store.registerModule('sites', initSitesStore(store, router));
|
store.registerModule('sites', initSitesStore(store, router));
|
||||||
store.registerModule('networks', initNetworksStore(store, router));
|
store.registerModule('networks', initNetworksStore(store, router));
|
||||||
|
store.registerModule('tags', initTagsStore(store, router));
|
||||||
|
|
||||||
return store;
|
return store;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
import { get } from '../api';
|
||||||
|
|
||||||
|
function initTagsActions(_store, _router) {
|
||||||
|
async function fetchTags({ _commit }, tagId) {
|
||||||
|
const tags = await get(`/tags/${tagId || ''}`);
|
||||||
|
|
||||||
|
return tags;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchTagReleases({ _commit }, tagId) {
|
||||||
|
const releases = await get(`/tags/${tagId}/releases`);
|
||||||
|
|
||||||
|
return releases;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
fetchTags,
|
||||||
|
fetchTagReleases,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default initTagsActions;
|
|
@ -0,0 +1 @@
|
||||||
|
export default {};
|
|
@ -0,0 +1 @@
|
||||||
|
export default {};
|
|
@ -0,0 +1,13 @@
|
||||||
|
import state from './state';
|
||||||
|
import mutations from './mutations';
|
||||||
|
import actions from './actions';
|
||||||
|
|
||||||
|
function initTagsStore(store, router) {
|
||||||
|
return {
|
||||||
|
state,
|
||||||
|
mutations,
|
||||||
|
actions: actions(store, router),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default initTagsStore;
|
|
@ -84,7 +84,7 @@ module.exports = {
|
||||||
],
|
],
|
||||||
media: {
|
media: {
|
||||||
path: './',
|
path: './',
|
||||||
thumbnailSize: 324, // width for 16:9 will be exactly 576px
|
thumbnailSize: 320, // width for 16:9 will be exactly 576px
|
||||||
},
|
},
|
||||||
filename: {
|
filename: {
|
||||||
dateFormat: 'DD-MM-YYYY',
|
dateFormat: 'DD-MM-YYYY',
|
||||||
|
|
|
@ -37,7 +37,9 @@ exports.up = knex => Promise.resolve()
|
||||||
.then(() => knex.schema.createTable('tags_groups', (table) => {
|
.then(() => knex.schema.createTable('tags_groups', (table) => {
|
||||||
table.increments('id', 12);
|
table.increments('id', 12);
|
||||||
|
|
||||||
table.string('group', 32);
|
table.string('name', 32);
|
||||||
|
table.text('description');
|
||||||
|
|
||||||
table.string('slug', 32)
|
table.string('slug', 32)
|
||||||
.unique();
|
.unique();
|
||||||
}))
|
}))
|
||||||
|
@ -45,6 +47,8 @@ exports.up = knex => Promise.resolve()
|
||||||
table.increments('id', 12);
|
table.increments('id', 12);
|
||||||
table.string('name');
|
table.string('name');
|
||||||
|
|
||||||
|
table.text('description');
|
||||||
|
|
||||||
table.integer('group_id', 12)
|
table.integer('group_id', 12)
|
||||||
.references('id')
|
.references('id')
|
||||||
.inTable('tags_groups');
|
.inTable('tags_groups');
|
||||||
|
|
|
@ -15,10 +15,12 @@
|
||||||
}
|
}
|
||||||
.thumbnail[data-v-3abcf101] {
|
.thumbnail[data-v-3abcf101] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 12rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
-o-object-fit: cover;
|
-o-object-fit: cover;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
-o-object-position: 50% 0;
|
|
||||||
object-position: 50% 0;
|
|
||||||
background-position: center;
|
background-position: center;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-color: rgba(0, 0, 0, 0.1);
|
background-color: rgba(0, 0, 0, 0.1);
|
||||||
|
@ -151,11 +153,6 @@
|
||||||
.filter[data-v-5533e378] {
|
.filter[data-v-5533e378] {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
.scenes[data-v-5533e378] {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
|
|
||||||
grid-gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* $primary: #ff886c; */
|
/* $primary: #ff886c; */
|
||||||
.banner[data-v-2bc41e74] {
|
.banner[data-v-2bc41e74] {
|
||||||
|
@ -263,11 +260,8 @@
|
||||||
grid-gap: 1rem;
|
grid-gap: 1rem;
|
||||||
margin: 0 0 1rem 0;
|
margin: 0 0 1rem 0;
|
||||||
}
|
}
|
||||||
.scenes[data-v-3e57cf44] {
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
|
|
||||||
}
|
|
||||||
.sites[data-v-3e57cf44] {
|
.sites[data-v-3e57cf44] {
|
||||||
grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
|
grid-template-columns: repeat(auto-fit, 15rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* $primary: #ff886c; */
|
/* $primary: #ff886c; */
|
||||||
|
@ -276,7 +270,7 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: 1rem;
|
padding: .5rem 1rem;
|
||||||
border-radius: .25rem;
|
border-radius: .25rem;
|
||||||
box-shadow: 0 0 3px rgba(0, 0, 0, 0.25);
|
box-shadow: 0 0 3px rgba(0, 0, 0, 0.25);
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -322,17 +316,13 @@
|
||||||
.logo[data-v-757c14c2] {
|
.logo[data-v-757c14c2] {
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
}
|
}
|
||||||
.sites[data-v-757c14c2],
|
.sites[data-v-757c14c2] {
|
||||||
.scenes[data-v-757c14c2] {
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: 1rem;
|
grid-gap: 1rem;
|
||||||
margin: 0 0 1rem 0;
|
margin: 0 0 1rem 0;
|
||||||
}
|
}
|
||||||
.scenes[data-v-757c14c2] {
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
|
|
||||||
}
|
|
||||||
.sites[data-v-757c14c2] {
|
.sites[data-v-757c14c2] {
|
||||||
grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
|
grid-template-columns: repeat(auto-fit, 15rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* $primary: #ff886c; */
|
/* $primary: #ff886c; */
|
||||||
|
@ -357,13 +347,29 @@
|
||||||
.bio-heading[data-v-677a8360]::after {
|
.bio-heading[data-v-677a8360]::after {
|
||||||
content: ':';
|
content: ':';
|
||||||
}
|
}
|
||||||
.scenes[data-v-677a8360] {
|
|
||||||
display: grid;
|
/* $primary: #ff886c; */
|
||||||
grid-gap: 1rem;
|
.header[data-v-80991bcc] {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
.title[data-v-80991bcc] {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 .5rem 0 0;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
.heading[data-v-80991bcc] {
|
||||||
|
padding: 0;
|
||||||
margin: 0 0 1rem 0;
|
margin: 0 0 1rem 0;
|
||||||
}
|
}
|
||||||
.scenes[data-v-677a8360] {
|
.bio-heading[data-v-80991bcc] {
|
||||||
grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
|
display: inline-block;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: .5rem 0 0 0;
|
||||||
|
}
|
||||||
|
.bio-heading[data-v-80991bcc]::after {
|
||||||
|
content: ':';
|
||||||
}
|
}
|
||||||
|
|
||||||
/* $primary: #ff886c; */
|
/* $primary: #ff886c; */
|
||||||
|
@ -418,11 +424,20 @@ body {
|
||||||
.icon.icon-href :hover {
|
.icon.icon-href :hover {
|
||||||
fill: #ff6c88; }
|
fill: #ff6c88; }
|
||||||
|
|
||||||
|
.scenes {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(20rem, 0.5fr));
|
||||||
|
grid-gap: 1rem; }
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
.scenes {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(22.5rem, 1fr)); } }
|
||||||
|
|
||||||
/* $primary: #ff886c; */
|
/* $primary: #ff886c; */
|
||||||
.header[data-v-10b7ec04] {
|
.header[data-v-10b7ec04] {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: #ff6c88;
|
background: #ff6c88;
|
||||||
padding: 1rem;
|
padding: .5rem 1rem;
|
||||||
}
|
}
|
||||||
.logo-link[data-v-10b7ec04] {
|
.logo-link[data-v-10b7ec04] {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
|
Before Width: | Height: | Size: 59 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 6.2 KiB |
After Width: | Height: | Size: 86 KiB |
After Width: | Height: | Size: 172 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 48 KiB |
After Width: | Height: | Size: 13 KiB |
|
@ -2,6 +2,9 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width,height=device-height,initial-scale=1,maximum-scale=1,user-scalable=no">
|
||||||
|
<meta name="theme-color" content="#ff6c88">
|
||||||
|
|
||||||
<title>traxxx</title>
|
<title>traxxx</title>
|
||||||
|
|
||||||
<script src="/js/bundle.js" defer></script>
|
<script src="/js/bundle.js" defer></script>
|
||||||
|
|
|
@ -5,47 +5,47 @@ const upsert = require('../src/utils/upsert');
|
||||||
const groups = [
|
const groups = [
|
||||||
{
|
{
|
||||||
slug: 'age',
|
slug: 'age',
|
||||||
group: 'Age',
|
name: 'Age',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'body',
|
slug: 'body',
|
||||||
group: 'Body',
|
name: 'Body',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'clothing',
|
slug: 'clothing',
|
||||||
group: 'Clothing',
|
name: 'Clothing',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'ethnicity',
|
slug: 'ethnicity',
|
||||||
group: 'Ethnicity',
|
name: 'Ethnicity',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'group',
|
slug: 'group',
|
||||||
group: 'Group sex',
|
name: 'Group sex',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'hair',
|
slug: 'hair',
|
||||||
group: 'Hair',
|
name: 'Hair',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'location',
|
slug: 'location',
|
||||||
group: 'Location',
|
name: 'Location',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'orientation',
|
slug: 'orientation',
|
||||||
group: 'Orientation',
|
name: 'Orientation',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'penetration',
|
slug: 'penetration',
|
||||||
group: 'Penetration',
|
name: 'Penetration',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'position',
|
slug: 'position',
|
||||||
group: 'Position',
|
name: 'Position',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'roleplay',
|
slug: 'roleplay',
|
||||||
group: 'Roleplay',
|
name: 'Roleplay',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -54,6 +54,7 @@ function getTags(groupsMap) {
|
||||||
{
|
{
|
||||||
name: '4K',
|
name: '4K',
|
||||||
slug: '4k',
|
slug: '4k',
|
||||||
|
description: 'Available in high quality 4K resolution.',
|
||||||
alias_for: null,
|
alias_for: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -66,6 +67,7 @@ function getTags(groupsMap) {
|
||||||
name: 'airtight',
|
name: 'airtight',
|
||||||
slug: 'airtight',
|
slug: 'airtight',
|
||||||
alias_for: null,
|
alias_for: null,
|
||||||
|
description: 'A cock in every penetrable hole (of a woman); one in the mouth, one in the vagina, and one in the asshole.',
|
||||||
group_id: groupsMap['penetration'],
|
group_id: groupsMap['penetration'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -83,15 +85,18 @@ function getTags(groupsMap) {
|
||||||
name: 'anal creampie',
|
name: 'anal creampie',
|
||||||
slug: 'anal-creampie',
|
slug: 'anal-creampie',
|
||||||
alias_for: null,
|
alias_for: null,
|
||||||
|
description: 'Ejaculating into the asshole.'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'anal',
|
name: 'anal',
|
||||||
slug: 'anal',
|
slug: 'anal',
|
||||||
|
description: 'Penetrating the asshole with a (real) dick.',
|
||||||
alias_for: null,
|
alias_for: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'anal fingering',
|
name: 'ass fingering',
|
||||||
slug: 'anal-fingering',
|
slug: 'ass-fingering',
|
||||||
|
description: 'Inserting one or multiple fingers into the asshole.',
|
||||||
alias_for: null,
|
alias_for: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -663,6 +668,12 @@ function getTags(groupsMap) {
|
||||||
slug: 'spanking',
|
slug: 'spanking',
|
||||||
alias_for: null,
|
alias_for: null,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'spooning',
|
||||||
|
slug: 'spooning',
|
||||||
|
alias_for: null,
|
||||||
|
group_id: groupsMap['position'],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'strapon',
|
name: 'strapon',
|
||||||
slug: 'strapon',
|
slug: 'strapon',
|
||||||
|
@ -827,6 +838,10 @@ function getTagAliases(tagsMap) {
|
||||||
name: 'asians',
|
name: 'asians',
|
||||||
alias_for: tagsMap['asian'],
|
alias_for: tagsMap['asian'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'anal fingering',
|
||||||
|
alias_for: tagsMap['ass-fingering'],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'ass licking',
|
name: 'ass licking',
|
||||||
alias_for: tagsMap['ass-eating'],
|
alias_for: tagsMap['ass-eating'],
|
||||||
|
@ -855,6 +870,10 @@ function getTagAliases(tagsMap) {
|
||||||
name: 'fmf',
|
name: 'fmf',
|
||||||
alias_for: tagsMap['fmf'],
|
alias_for: tagsMap['fmf'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'ffm',
|
||||||
|
alias_for: tagsMap['fmf'],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'bgb',
|
name: 'bgb',
|
||||||
alias_for: tagsMap['mfm'],
|
alias_for: tagsMap['mfm'],
|
||||||
|
@ -947,6 +966,10 @@ function getTagAliases(tagsMap) {
|
||||||
name: 'buttplug',
|
name: 'buttplug',
|
||||||
alias_for: tagsMap['anal-toys'],
|
alias_for: tagsMap['anal-toys'],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'butt plug',
|
||||||
|
alias_for: tagsMap['anal-toys'],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'caning',
|
name: 'caning',
|
||||||
alias_for: tagsMap['corporal-punishment'],
|
alias_for: tagsMap['corporal-punishment'],
|
||||||
|
|
|
@ -23,9 +23,7 @@ async function storePoster(release, releaseEntry) {
|
||||||
console.log(`Storing poster for (${release.site.name}, ${releaseEntry.id}) "${release.title}"`);
|
console.log(`Storing poster for (${release.site.name}, ${releaseEntry.id}) "${release.title}"`);
|
||||||
|
|
||||||
const res = await bhttp.get(release.poster);
|
const res = await bhttp.get(release.poster);
|
||||||
const thumbnail = await sharp(res.body)
|
const thumbnail = await sharp(res.body).resize({ height: config.media.thumbnailSize }).toBuffer();
|
||||||
.resize({ width: Math.floor((config.media.thumbnailSize / 9) * 16), height: config.media.thumbnailSize }) // ensure thumbnail is 16:9
|
|
||||||
.toBuffer();
|
|
||||||
|
|
||||||
if (res.statusCode === 200) {
|
if (res.statusCode === 200) {
|
||||||
const { pathname } = new URL(release.poster);
|
const { pathname } = new URL(release.poster);
|
||||||
|
|
|
@ -143,9 +143,32 @@ async function fetchActorReleases(actorId, actorSlug) {
|
||||||
return curateReleases(releases);
|
return curateReleases(releases);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchTagReleases(tagId, tagSlug) {
|
||||||
|
const releases = await knex('tags_associated')
|
||||||
|
.where({ 'tags.id': tagId })
|
||||||
|
.orWhere({ 'tags.slug': tagSlug })
|
||||||
|
.select(
|
||||||
|
'releases.*',
|
||||||
|
'tags.name as tag_name',
|
||||||
|
'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('releases', 'tags_associated.release_id', 'releases.id')
|
||||||
|
.leftJoin('tags', 'tags_associated.tag_id', 'tags.id')
|
||||||
|
.leftJoin('sites', 'releases.site_id', 'sites.id')
|
||||||
|
.leftJoin('studios', 'releases.studio_id', 'studios.id')
|
||||||
|
.leftJoin('networks', 'sites.network_id', 'networks.id')
|
||||||
|
.orderBy([{ column: 'releases.date', order: 'desc' }, { column: 'releases.created_at', order: 'desc' }])
|
||||||
|
.limit(100);
|
||||||
|
|
||||||
|
return curateReleases(releases);
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
fetchReleases,
|
fetchReleases,
|
||||||
fetchActorReleases,
|
fetchActorReleases,
|
||||||
fetchSiteReleases,
|
fetchSiteReleases,
|
||||||
fetchNetworkReleases,
|
fetchNetworkReleases,
|
||||||
|
fetchTagReleases,
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,6 +7,15 @@ const moment = require('moment');
|
||||||
|
|
||||||
const { matchTags } = require('../tags');
|
const { matchTags } = require('../tags');
|
||||||
|
|
||||||
|
const defaultTags = {
|
||||||
|
blacked: ['bbc'],
|
||||||
|
blackedraw: ['bbc'],
|
||||||
|
tushy: ['anal'],
|
||||||
|
tushyraw: ['anal'],
|
||||||
|
vixen: [],
|
||||||
|
deeper: [],
|
||||||
|
};
|
||||||
|
|
||||||
function scrapeLatest(html, site) {
|
function scrapeLatest(html, site) {
|
||||||
const $ = cheerio.load(html, { normalizeWhitespace: true });
|
const $ = cheerio.load(html, { normalizeWhitespace: true });
|
||||||
|
|
||||||
|
@ -73,7 +82,7 @@ async function scrapeScene(html, url, site) {
|
||||||
} = scene;
|
} = scene;
|
||||||
|
|
||||||
const date = new Date(scene.releaseDate);
|
const date = new Date(scene.releaseDate);
|
||||||
const tags = await matchTags(rawTags);
|
const tags = await matchTags([...defaultTags[site.slug], ...rawTags]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
url,
|
url,
|
||||||
|
|
53
src/tags.js
|
@ -2,6 +2,49 @@
|
||||||
|
|
||||||
const knex = require('./knex');
|
const knex = require('./knex');
|
||||||
|
|
||||||
|
async function curateTag(tag) {
|
||||||
|
const aliases = await knex('tags').where({ alias_for: tag.id });
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: tag.id,
|
||||||
|
name: tag.name,
|
||||||
|
description: tag.description,
|
||||||
|
group: {
|
||||||
|
id: tag.group_id,
|
||||||
|
name: tag.group_name,
|
||||||
|
description: tag.group_description,
|
||||||
|
slug: tag.group_slug,
|
||||||
|
},
|
||||||
|
aliases: aliases.map(({ name }) => name),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function curateTags(tags) {
|
||||||
|
return Promise.all(tags.map(async tag => curateTag(tag)));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function storeTags(release, releaseEntry) {
|
||||||
|
return knex('tags_associated').insert(release.tags.map(tagId => ({
|
||||||
|
tag_id: tagId,
|
||||||
|
release_id: releaseEntry.id,
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchTags(tagId, tagSlug) {
|
||||||
|
const tags = await knex('tags')
|
||||||
|
.where({ 'tags.id': tagId })
|
||||||
|
.orWhere({ 'tags.slug': tagSlug })
|
||||||
|
.andWhere({ 'tags.alias_for': null })
|
||||||
|
.select(
|
||||||
|
'tags.*',
|
||||||
|
'tags_groups.id as group_id', 'tags_groups.name as group_name', 'tags_groups.slug as group_slug', 'tags_groups.description as groups_description',
|
||||||
|
)
|
||||||
|
.leftJoin('tags_groups', 'tags.group_id', 'tags_groups.id')
|
||||||
|
.limit(100);
|
||||||
|
|
||||||
|
return curateTags(tags);
|
||||||
|
}
|
||||||
|
|
||||||
async function matchTags(rawTags) {
|
async function matchTags(rawTags) {
|
||||||
const tags = rawTags
|
const tags = rawTags
|
||||||
.concat(rawTags.map(tag => tag.toLowerCase()))
|
.concat(rawTags.map(tag => tag.toLowerCase()))
|
||||||
|
@ -25,14 +68,8 @@ async function matchTags(rawTags) {
|
||||||
return tagEntries;
|
return tagEntries;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function storeTags(release, releaseEntry) {
|
|
||||||
return knex('tags_associated').insert(release.tags.map(tagId => ({
|
|
||||||
tag_id: tagId,
|
|
||||||
release_id: releaseEntry.id,
|
|
||||||
})));
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
matchTags,
|
|
||||||
storeTags,
|
storeTags,
|
||||||
|
fetchTags,
|
||||||
|
matchTags,
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,6 +5,7 @@ const {
|
||||||
fetchActorReleases,
|
fetchActorReleases,
|
||||||
fetchNetworkReleases,
|
fetchNetworkReleases,
|
||||||
fetchSiteReleases,
|
fetchSiteReleases,
|
||||||
|
fetchTagReleases,
|
||||||
} = require('../releases');
|
} = require('../releases');
|
||||||
|
|
||||||
async function fetchReleasesApi(req, res) {
|
async function fetchReleasesApi(req, res) {
|
||||||
|
@ -40,9 +41,19 @@ async function fetchSiteReleasesApi(req, res) {
|
||||||
res.send(releases);
|
res.send(releases);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchTagReleasesApi(req, res) {
|
||||||
|
const tagId = typeof req.params.tagId === 'number' ? req.params.tagId : null;
|
||||||
|
const tagSlug = typeof req.params.tagId === 'string' ? req.params.tagId : null;
|
||||||
|
|
||||||
|
const releases = await fetchTagReleases(tagId, tagSlug);
|
||||||
|
|
||||||
|
res.send(releases);
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
fetchReleases: fetchReleasesApi,
|
fetchReleases: fetchReleasesApi,
|
||||||
fetchActorReleases: fetchActorReleasesApi,
|
fetchActorReleases: fetchActorReleasesApi,
|
||||||
fetchNetworkReleases: fetchNetworkReleasesApi,
|
fetchNetworkReleases: fetchNetworkReleasesApi,
|
||||||
fetchSiteReleases: fetchSiteReleasesApi,
|
fetchSiteReleases: fetchSiteReleasesApi,
|
||||||
|
fetchTagReleases: fetchTagReleasesApi,
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,6 +11,7 @@ const {
|
||||||
fetchActorReleases,
|
fetchActorReleases,
|
||||||
fetchNetworkReleases,
|
fetchNetworkReleases,
|
||||||
fetchSiteReleases,
|
fetchSiteReleases,
|
||||||
|
fetchTagReleases,
|
||||||
} = require('./releases');
|
} = require('./releases');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -20,6 +21,7 @@ const {
|
||||||
|
|
||||||
const { fetchActors } = require('./actors');
|
const { fetchActors } = require('./actors');
|
||||||
const { fetchSites } = require('./sites');
|
const { fetchSites } = require('./sites');
|
||||||
|
const { fetchTags } = require('./tags');
|
||||||
|
|
||||||
function initServer() {
|
function initServer() {
|
||||||
const app = express();
|
const app = express();
|
||||||
|
@ -50,6 +52,10 @@ function initServer() {
|
||||||
router.get('/api/sites/:siteId', fetchSites);
|
router.get('/api/sites/:siteId', fetchSites);
|
||||||
router.get('/api/sites/:siteId/releases', fetchSiteReleases);
|
router.get('/api/sites/:siteId/releases', fetchSiteReleases);
|
||||||
|
|
||||||
|
router.get('/api/tags', fetchTags);
|
||||||
|
router.get('/api/tags/:tagId', fetchTags);
|
||||||
|
router.get('/api/tags/:tagId/releases', fetchTagReleases);
|
||||||
|
|
||||||
router.get('*', (req, res) => {
|
router.get('*', (req, res) => {
|
||||||
res.sendFile(path.join(__dirname, '../../public/index.html'));
|
res.sendFile(path.join(__dirname, '../../public/index.html'));
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { fetchTags } = require('../tags');
|
||||||
|
|
||||||
|
async function fetchTagsApi(req, res) {
|
||||||
|
const tagId = typeof req.params.tagId === 'number' ? req.params.tagId : null;
|
||||||
|
const tagSlug = typeof req.params.tagId === 'string' ? req.params.tagId : null;
|
||||||
|
|
||||||
|
const tags = await fetchTags(tagId, tagSlug);
|
||||||
|
|
||||||
|
res.send(tags);
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
fetchTags: fetchTagsApi,
|
||||||
|
};
|