Compare commits
18 Commits
36c5fa3b52
...
e2c2c9b4f0
Author | SHA1 | Date |
---|---|---|
|
e2c2c9b4f0 | |
|
2271577874 | |
|
30cf597ec9 | |
|
72b175e9e2 | |
|
70e27a6cd9 | |
|
e77dbca954 | |
|
5a6bf2b42f | |
|
1c43884102 | |
|
31aee71edb | |
|
9b17add4e2 | |
|
3845c3f52d | |
|
6950a76cb5 | |
|
f4c2e6c08c | |
|
577c03f9b7 | |
|
ce92d13327 | |
|
13b45e1709 | |
|
07a6c77ce2 | |
|
7ba716cd6f |
|
@ -3,7 +3,7 @@
|
||||||
v-if="actor"
|
v-if="actor"
|
||||||
class="content actor"
|
class="content actor"
|
||||||
>
|
>
|
||||||
<FilterBar :fetch-releases="fetchReleases" />
|
<FilterBar :fetch-releases="fetchActor" />
|
||||||
|
|
||||||
<div class="actor-inner">
|
<div class="actor-inner">
|
||||||
<div class="profile">
|
<div class="profile">
|
||||||
|
@ -136,8 +136,8 @@
|
||||||
>
|
>
|
||||||
<dfn class="bio-label"><Icon icon="height" />Height</dfn>
|
<dfn class="bio-label"><Icon icon="height" />Height</dfn>
|
||||||
<span>
|
<span>
|
||||||
<span class="height-metric">{{ actor.height }} cm</span>
|
<span class="height-metric">{{ actor.height.metric }} cm</span>
|
||||||
<span class="height-imperial">{{ imperialHeight.feet }}' {{ imperialHeight.inches }}"</span>
|
<span class="height-imperial">{{ actor.height.imperial }}</span>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
@ -148,8 +148,8 @@
|
||||||
<dfn class="bio-label"><Icon icon="scale" />Weight</dfn>
|
<dfn class="bio-label"><Icon icon="scale" />Weight</dfn>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
<span class="weight-metric">{{ actor.weight }} kg</span>
|
<span class="weight-metric">{{ actor.weight.metric }} kg</span>
|
||||||
<span class="weight-imperial">{{ imperialWeight }} lbs</span>
|
<span class="weight-imperial">{{ actor.weight.imperial }} lbs</span>
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
@ -232,29 +232,19 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Releases :releases="releases" />
|
<Releases :releases="actor.releases" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { cmToFeetInches, kgToLbs } from '../../../src/utils/convert';
|
|
||||||
|
|
||||||
import Photos from './photos.vue';
|
import Photos from './photos.vue';
|
||||||
import FilterBar from '../header/filter-bar.vue';
|
import FilterBar from '../header/filter-bar.vue';
|
||||||
import Releases from '../releases/releases.vue';
|
import Releases from '../releases/releases.vue';
|
||||||
|
|
||||||
async function fetchReleases() {
|
async function fetchActor() {
|
||||||
this.releases = await this.$store.dispatch('fetchActorReleases', this.$route.params.actorSlug);
|
this.actor = await this.$store.dispatch('fetchActors', { actorSlug: this.$route.params.actorSlug });
|
||||||
}
|
|
||||||
|
|
||||||
function imperialHeight() {
|
|
||||||
return cmToFeetInches(this.actor.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
function imperialWeight() {
|
|
||||||
return kgToLbs(this.actor.weight);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollPhotos(event) {
|
function scrollPhotos(event) {
|
||||||
|
@ -262,10 +252,7 @@ function scrollPhotos(event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function mounted() {
|
async function mounted() {
|
||||||
[this.actor] = await Promise.all([
|
this.fetchActor();
|
||||||
this.$store.dispatch('fetchActors', { actorId: this.$route.params.actorSlug }),
|
|
||||||
this.fetchReleases(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (this.actor) {
|
if (this.actor) {
|
||||||
this.pageTitle = this.actor.name;
|
this.pageTitle = this.actor.name;
|
||||||
|
@ -286,13 +273,9 @@ export default {
|
||||||
expanded: false,
|
expanded: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
imperialHeight,
|
|
||||||
imperialWeight,
|
|
||||||
},
|
|
||||||
mounted,
|
mounted,
|
||||||
methods: {
|
methods: {
|
||||||
fetchReleases,
|
fetchActor,
|
||||||
scrollPhotos,
|
scrollPhotos,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
<Header />
|
<Header />
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<router-view />
|
<!-- key forces rerender when new and old path use same component -->
|
||||||
|
<router-view :key="$route.fullPath" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,20 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="filter-bar noselect">
|
<div class="filter-bar noselect">
|
||||||
<span>
|
<span>
|
||||||
<label class="range">
|
|
||||||
<input
|
|
||||||
:id="`${_uid}-all`"
|
|
||||||
:checked="range === 'all'"
|
|
||||||
type="radio"
|
|
||||||
class="range-input"
|
|
||||||
@click="setRange('all')"
|
|
||||||
>
|
|
||||||
<label
|
|
||||||
:for="`${_uid}-all`"
|
|
||||||
class="range-button"
|
|
||||||
>All</label>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<label class="range">
|
<label class="range">
|
||||||
<input
|
<input
|
||||||
:id="`${_uid}-new`"
|
:id="`${_uid}-new`"
|
||||||
|
@ -42,6 +28,20 @@
|
||||||
class="range-button"
|
class="range-button"
|
||||||
>Upcoming</label>
|
>Upcoming</label>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
<label class="range">
|
||||||
|
<input
|
||||||
|
:id="`${_uid}-all`"
|
||||||
|
:checked="range === 'all'"
|
||||||
|
type="radio"
|
||||||
|
class="range-input"
|
||||||
|
@click="setRange('all')"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
:for="`${_uid}-all`"
|
||||||
|
class="range-button"
|
||||||
|
>All</label>
|
||||||
|
</label>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span>
|
<span>
|
||||||
|
|
|
@ -15,7 +15,7 @@ import FilterBar from '../header/filter-bar.vue';
|
||||||
import Releases from '../releases/releases.vue';
|
import Releases from '../releases/releases.vue';
|
||||||
|
|
||||||
async function fetchReleases() {
|
async function fetchReleases() {
|
||||||
this.releases = await this.$store.dispatch('fetchReleases');
|
this.releases = await this.$store.dispatch('fetchReleases', { limit: 100 });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function mounted() {
|
async function mounted() {
|
||||||
|
|
|
@ -1,11 +1,35 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="network"
|
v-if="network"
|
||||||
class="content network"
|
class="content"
|
||||||
>
|
>
|
||||||
<FilterBar :fetch-releases="fetchReleases" />
|
<FilterBar :fetch-releases="fetchNetwork" />
|
||||||
|
|
||||||
|
<div class="network">
|
||||||
|
<div class="sidebar">
|
||||||
|
<a
|
||||||
|
:href="network.url"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="title"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
:src="`/img/logos/${network.slug}/network.png`"
|
||||||
|
class="logo"
|
||||||
|
>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<p
|
||||||
|
v-if="network.description"
|
||||||
|
class="description"
|
||||||
|
>{{ network.description }}</p>
|
||||||
|
|
||||||
|
<Sites
|
||||||
|
v-if="sites.length"
|
||||||
|
:sites="sites"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="content-inner">
|
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<a
|
<a
|
||||||
:href="network.url"
|
:href="network.url"
|
||||||
|
@ -17,34 +41,18 @@
|
||||||
:src="`/img/logos/${network.slug}/network.png`"
|
:src="`/img/logos/${network.slug}/network.png`"
|
||||||
class="logo"
|
class="logo"
|
||||||
>
|
>
|
||||||
|
|
||||||
<Icon
|
|
||||||
v-if="network.url"
|
|
||||||
icon="new-tab"
|
|
||||||
class="icon-href"
|
|
||||||
/>
|
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<p class="description">{{ network.description }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<template v-if="sites.length">
|
<div class="content-inner">
|
||||||
<h3 class="heading">Sites</h3>
|
<Sites
|
||||||
|
v-if="sites.length"
|
||||||
|
:sites="sites"
|
||||||
|
class="compact"
|
||||||
|
/>
|
||||||
|
|
||||||
<ul class="nolist sites">
|
<Releases :releases="releases" />
|
||||||
<li
|
</div>
|
||||||
v-for="site in sites"
|
|
||||||
:key="`site-${site.id}`"
|
|
||||||
>
|
|
||||||
<SiteTile :site="site" />
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<Releases
|
|
||||||
:releases="releases"
|
|
||||||
:context="network.name"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -52,22 +60,20 @@
|
||||||
<script>
|
<script>
|
||||||
import FilterBar from '../header/filter-bar.vue';
|
import FilterBar from '../header/filter-bar.vue';
|
||||||
import Releases from '../releases/releases.vue';
|
import Releases from '../releases/releases.vue';
|
||||||
import SiteTile from '../tile/site.vue';
|
import Sites from '../sites/sites.vue';
|
||||||
|
|
||||||
async function fetchReleases() {
|
async function fetchNetwork() {
|
||||||
this.releases = await this.$store.dispatch('fetchNetworkReleases', this.$route.params.networkSlug);
|
this.network = await this.$store.dispatch('fetchNetworks', this.$route.params.networkSlug);
|
||||||
}
|
|
||||||
|
|
||||||
async function mounted() {
|
|
||||||
[[this.network]] = await Promise.all([
|
|
||||||
this.$store.dispatch('fetchNetworks', this.$route.params.networkSlug),
|
|
||||||
this.fetchReleases(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
this.sites = this.network.sites
|
this.sites = this.network.sites
|
||||||
.filter(site => !site.independent)
|
.filter(site => !site.independent)
|
||||||
.sort(({ name: nameA }, { name: nameB }) => nameA.localeCompare(nameB));
|
.sort(({ name: nameA }, { name: nameB }) => nameA.localeCompare(nameB));
|
||||||
|
|
||||||
|
this.releases = this.network.sites.map(site => site.releases).flat();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function mounted() {
|
||||||
|
await this.fetchNetwork();
|
||||||
this.pageTitle = this.network.name;
|
this.pageTitle = this.network.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,64 +81,108 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
FilterBar,
|
FilterBar,
|
||||||
Releases,
|
Releases,
|
||||||
SiteTile,
|
Sites,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
network: null,
|
network: null,
|
||||||
sites: null,
|
sites: null,
|
||||||
releases: null,
|
releases: [],
|
||||||
pageTitle: null,
|
pageTitle: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
mounted,
|
mounted,
|
||||||
methods: {
|
methods: {
|
||||||
fetchReleases,
|
fetchNetwork,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
@import 'theme';
|
@import 'theme';
|
||||||
|
|
||||||
.header {
|
@media(max-width: $breakpoint3) {
|
||||||
display: flex;
|
.releases .tiles {
|
||||||
flex-wrap: wrap;
|
grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
|
||||||
justify-content: space-between;
|
}
|
||||||
align-items: top;
|
}
|
||||||
margin: 0 0 2rem 0;
|
</style>
|
||||||
}
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
.title {
|
@import 'theme';
|
||||||
display: inline-flex;
|
|
||||||
align-items: top;
|
.network {
|
||||||
margin: 0 1rem 0 0;
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
&:hover .icon {
|
flex-grow: 1;
|
||||||
fill: $primary;
|
justify-content: stretch;
|
||||||
}
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.sidebar {
|
||||||
width: 20rem;
|
height: 100%;
|
||||||
max-height: 8rem;
|
width: 18rem;
|
||||||
object-fit: contain;
|
display: flex;
|
||||||
margin: 0 .5rem 0 0;
|
flex-direction: column;
|
||||||
}
|
flex-shrink: 0;
|
||||||
|
color: $text-contrast;
|
||||||
.sites {
|
border-right: solid 1px $shadow-hint;
|
||||||
display: grid;
|
overflow: hidden;
|
||||||
grid-gap: 1rem;
|
}
|
||||||
margin: 0 0 2rem 0;
|
|
||||||
}
|
.sidebar .title {
|
||||||
|
border-bottom: solid 1px $shadow-hint;
|
||||||
.sites {
|
}
|
||||||
grid-template-columns: repeat(auto-fit, 15rem);
|
|
||||||
}
|
.logo {
|
||||||
|
width: 100%;
|
||||||
@media(max-width: $breakpoint) {
|
max-height: 8rem;
|
||||||
.sites {
|
display: flex;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
|
justify-content: center;
|
||||||
|
object-fit: contain;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 0;
|
||||||
|
filter: $logo-shadow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sites.compact {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
width: 100%;
|
||||||
|
height: 4rem;
|
||||||
|
display: none;
|
||||||
|
justify-content: center;
|
||||||
|
border-bottom: solid 1px $shadow-hint;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media(max-width: $breakpoint) {
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sites.compact {
|
||||||
|
display: flex;
|
||||||
|
padding: 0 1rem 1rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.network {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
max-width: 20rem;
|
||||||
|
height: 100%;
|
||||||
|
padding: .5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
display: none;
|
||||||
|
height: auto;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -108,6 +108,22 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="release.scenes && release.scenes.length > 0">
|
||||||
|
<h3>Scenes</h3>
|
||||||
|
|
||||||
|
<Releases
|
||||||
|
v-if="release.scenes && release.scenes.length > 0"
|
||||||
|
:releases="release.scenes"
|
||||||
|
class="row"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="release.movie">
|
||||||
|
<h3>Movie</h3>
|
||||||
|
|
||||||
|
<Release :release="release.movie" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="release.tags.length > 0"
|
v-if="release.tags.length > 0"
|
||||||
class="row"
|
class="row"
|
||||||
|
@ -196,8 +212,10 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Actor from '../tile/actor.vue';
|
|
||||||
import Banner from './banner.vue';
|
import Banner from './banner.vue';
|
||||||
|
import Actor from '../tile/actor.vue';
|
||||||
|
import Release from '../tile/release.vue';
|
||||||
|
import Releases from './releases.vue';
|
||||||
|
|
||||||
function pageTitle() {
|
function pageTitle() {
|
||||||
return this.release && this.release.title;
|
return this.release && this.release.title;
|
||||||
|
@ -211,6 +229,8 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
Actor,
|
Actor,
|
||||||
Banner,
|
Banner,
|
||||||
|
Releases,
|
||||||
|
Release,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -293,7 +313,7 @@ export default {
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
filter: $logo-outline;
|
filter: $logo-shadow;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo-site {
|
.logo-site {
|
||||||
|
|
|
@ -56,6 +56,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.tiles {
|
.tiles {
|
||||||
|
width: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(20rem, .33fr));
|
grid-template-columns: repeat(auto-fit, minmax(20rem, .33fr));
|
||||||
grid-gap: 1rem;
|
grid-gap: 1rem;
|
||||||
|
|
|
@ -1,157 +0,0 @@
|
||||||
<template>
|
|
||||||
<div
|
|
||||||
v-if="site"
|
|
||||||
class="content site"
|
|
||||||
>
|
|
||||||
<FilterBar :fetch-releases="fetchReleases" />
|
|
||||||
|
|
||||||
<div class="content-inner">
|
|
||||||
<div class="header">
|
|
||||||
<a
|
|
||||||
v-if="site.url"
|
|
||||||
:href="site.url"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
class="title"
|
|
||||||
>
|
|
||||||
<object
|
|
||||||
:data="`/img/logos/${site.network.slug}/${site.slug}.png`"
|
|
||||||
:title="site.name"
|
|
||||||
type="image/png"
|
|
||||||
class="logo"
|
|
||||||
><h2>{{ site.name }}</h2></object>
|
|
||||||
|
|
||||||
<Icon
|
|
||||||
icon="new-tab"
|
|
||||||
class="icon-href"
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<span class="link">
|
|
||||||
<span class="networklogo-container">
|
|
||||||
Part of
|
|
||||||
<a
|
|
||||||
:href="`/network/${site.network.slug}`"
|
|
||||||
class="networklogo-link"
|
|
||||||
>
|
|
||||||
<object
|
|
||||||
:data="`/img/logos/${site.network.slug}/network.png`"
|
|
||||||
:title="site.network.name"
|
|
||||||
type="image/png"
|
|
||||||
class="networklogo"
|
|
||||||
>{{ site.network.name }}</object>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="description">{{ site.description }}</p>
|
|
||||||
|
|
||||||
<Releases
|
|
||||||
:releases="releases"
|
|
||||||
:context="site.name"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import FilterBar from '../header/filter-bar.vue';
|
|
||||||
import Releases from '../releases/releases.vue';
|
|
||||||
|
|
||||||
async function fetchReleases() {
|
|
||||||
this.releases = await this.$store.dispatch('fetchSiteReleases', this.$route.params.siteSlug);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function mounted() {
|
|
||||||
[[this.site]] = await Promise.all([
|
|
||||||
this.$store.dispatch('fetchSites', this.$route.params.siteSlug),
|
|
||||||
this.fetchReleases(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
this.pageTitle = this.site.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: {
|
|
||||||
FilterBar,
|
|
||||||
Releases,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
site: null,
|
|
||||||
releases: null,
|
|
||||||
pageTitle: null,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted,
|
|
||||||
methods: {
|
|
||||||
fetchReleases,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
@import 'theme';
|
|
||||||
|
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: top;
|
|
||||||
margin: 0 1rem 0 0;
|
|
||||||
|
|
||||||
&:hover .icon {
|
|
||||||
fill: $primary;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.heading {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0 0 1rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link {
|
|
||||||
display: flex;
|
|
||||||
flex-shrink: 0;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
width: 20rem;
|
|
||||||
max-height: 8rem;
|
|
||||||
object-fit: contain;
|
|
||||||
margin: 0 .5rem 1rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.networklogo-container {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.networklogo {
|
|
||||||
color: $text;
|
|
||||||
width: 15rem;
|
|
||||||
max-height: 6rem;
|
|
||||||
font-weight: bold;
|
|
||||||
object-fit: contain;
|
|
||||||
object-position: 100% 0;
|
|
||||||
margin: 0 0 0 .5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sites,
|
|
||||||
.scenes {
|
|
||||||
display: grid;
|
|
||||||
grid-gap: 1rem;
|
|
||||||
margin: 0 0 1rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sites {
|
|
||||||
grid-template-columns: repeat(auto-fit, 15rem);
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -16,6 +16,7 @@
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
:src="`/img/${tag.poster.thumbnail}`"
|
:src="`/img/${tag.poster.thumbnail}`"
|
||||||
|
:alt="tag.poster.comment"
|
||||||
class="poster"
|
class="poster"
|
||||||
>
|
>
|
||||||
</a>
|
</a>
|
||||||
|
@ -43,6 +44,7 @@
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
:src="`/img/${photo.thumbnail}`"
|
:src="`/img/${photo.thumbnail}`"
|
||||||
|
:alt="photo.comment"
|
||||||
class="photo"
|
class="photo"
|
||||||
>
|
>
|
||||||
</a>
|
</a>
|
||||||
|
@ -50,7 +52,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content-inner">
|
<div class="content-inner">
|
||||||
<Releases :releases="releases" />
|
<Releases :releases="tag.releases" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -68,17 +70,13 @@ import Releases from '../releases/releases.vue';
|
||||||
const converter = new Converter();
|
const converter = new Converter();
|
||||||
|
|
||||||
async function fetchReleases() {
|
async function fetchReleases() {
|
||||||
this.releases = await this.$store.dispatch('fetchTagReleases', this.$route.params.tagSlug);
|
this.tag = await this.$store.dispatch('fetchTags', { tagSlug: this.$route.params.tagSlug });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function mounted() {
|
async function mounted() {
|
||||||
[this.tag] = await Promise.all([
|
this.tag = await this.$store.dispatch('fetchTags', { tagSlug: this.$route.params.tagSlug });
|
||||||
this.$store.dispatch('fetchTags', { tagId: this.$route.params.tagSlug }),
|
|
||||||
this.fetchReleases(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
this.description = converter.makeHtml(escapeHtml(this.tag.description));
|
|
||||||
|
|
||||||
|
this.description = this.tag.description && converter.makeHtml(escapeHtml(this.tag.description));
|
||||||
this.pageTitle = this.tag.name;
|
this.pageTitle = this.tag.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,6 +88,7 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
tag: null,
|
tag: null,
|
||||||
|
description: null,
|
||||||
releases: null,
|
releases: null,
|
||||||
pageTitle: null,
|
pageTitle: null,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="tags">
|
<div class="tags">
|
||||||
<h3>Ethnicity</h3>
|
<h3>Oral</h3>
|
||||||
|
|
||||||
<div class="tiles">
|
<div class="tiles">
|
||||||
<Tag
|
<Tag
|
||||||
v-for="tag in tags.ethnicity"
|
v-for="tag in tags.oral"
|
||||||
:key="`tag-${tag.id}`"
|
:key="`tag-${tag.id}`"
|
||||||
:tag="tag"
|
:tag="tag"
|
||||||
/>
|
/>
|
||||||
|
@ -30,6 +30,16 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h3>Ethnicity</h3>
|
||||||
|
|
||||||
|
<div class="tiles">
|
||||||
|
<Tag
|
||||||
|
v-for="tag in tags.ethnicity"
|
||||||
|
:key="`tag-${tag.id}`"
|
||||||
|
:tag="tag"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h3>Finish</h3>
|
<h3>Finish</h3>
|
||||||
|
|
||||||
<div class="tiles">
|
<div class="tiles">
|
||||||
|
@ -39,6 +49,16 @@
|
||||||
:tag="tag"
|
:tag="tag"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<h3>Misc</h3>
|
||||||
|
|
||||||
|
<div class="tiles">
|
||||||
|
<Tag
|
||||||
|
v-for="tag in tags.misc.concat(tags.body)"
|
||||||
|
:key="`tag-${tag.id}`"
|
||||||
|
:tag="tag"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -47,41 +67,55 @@ import Tag from '../tile/tag.vue';
|
||||||
|
|
||||||
async function mounted() {
|
async function mounted() {
|
||||||
const tags = await this.$store.dispatch('fetchTags', {
|
const tags = await this.$store.dispatch('fetchTags', {
|
||||||
slug: [
|
slugs: [
|
||||||
'airtight',
|
'airtight',
|
||||||
'anal',
|
'anal',
|
||||||
|
'anal-creampie',
|
||||||
|
'asian',
|
||||||
|
'ass-eating',
|
||||||
|
'ass-to-mouth',
|
||||||
|
'blowbang',
|
||||||
|
'blowjob',
|
||||||
|
'bukkake',
|
||||||
|
'caucasian',
|
||||||
|
'creampie',
|
||||||
|
'da-tp',
|
||||||
|
'deepthroat',
|
||||||
'double-anal',
|
'double-anal',
|
||||||
|
'double-blowjob',
|
||||||
'double-penetration',
|
'double-penetration',
|
||||||
'double-vaginal',
|
'double-vaginal',
|
||||||
'da-tp',
|
|
||||||
'dv-tp',
|
'dv-tp',
|
||||||
'triple-anal',
|
|
||||||
'blowbang',
|
|
||||||
'gangbang',
|
|
||||||
'mff',
|
|
||||||
'mfm',
|
|
||||||
'orgy',
|
|
||||||
'asian',
|
|
||||||
'caucasian',
|
|
||||||
'ebony',
|
'ebony',
|
||||||
|
'facefuck',
|
||||||
|
'facial',
|
||||||
|
'gangbang',
|
||||||
|
'gapes',
|
||||||
'interracial',
|
'interracial',
|
||||||
'latina',
|
'latina',
|
||||||
'anal-creampie',
|
'mff',
|
||||||
'bukkake',
|
'mfm',
|
||||||
'creampie',
|
|
||||||
'facial',
|
|
||||||
'oral-creampie',
|
'oral-creampie',
|
||||||
|
'orgy',
|
||||||
|
'pussy-eating',
|
||||||
'swallowing',
|
'swallowing',
|
||||||
|
'tattoo',
|
||||||
|
'trainbang',
|
||||||
|
'triple-anal',
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
this.tags = tags.reduce((acc, tag) => {
|
this.tags = tags.reduce((acc, tag) => {
|
||||||
|
if (!tag.group) {
|
||||||
|
return { ...acc, misc: [...acc.misc, tag] };
|
||||||
|
}
|
||||||
|
|
||||||
if (acc[tag.group.slug]) {
|
if (acc[tag.group.slug]) {
|
||||||
return { ...acc, [tag.group.slug]: [...acc[tag.group.slug], tag] };
|
return { ...acc, [tag.group.slug]: [...acc[tag.group.slug], tag] };
|
||||||
}
|
}
|
||||||
|
|
||||||
return { ...acc, [tag.group.slug]: [tag] };
|
return { ...acc, [tag.group.slug]: [tag] };
|
||||||
}, {});
|
}, { misc: [] });
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -27,17 +27,18 @@
|
||||||
v-if="actor.age || actor.origin"
|
v-if="actor.age || actor.origin"
|
||||||
class="details"
|
class="details"
|
||||||
>
|
>
|
||||||
<span v-if="actor.age">
|
<span>
|
||||||
<span
|
<span
|
||||||
|
v-if="actor.age"
|
||||||
v-tooltip="`Born on ${formatDate(actor.birthdate, 'MMMM D, YYYY')}`"
|
v-tooltip="`Born on ${formatDate(actor.birthdate, 'MMMM D, YYYY')}`"
|
||||||
class="age"
|
class="age"
|
||||||
>{{ actor.age }}</span>
|
>{{ actor.age }}</span>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
v-if="actor.ageThen && actor.ageThen < actor.age"
|
v-if="actor.ageThen && actor.ageThen < actor.age"
|
||||||
v-tooltip="'Age at scene date'"
|
v-tooltip="`${actor.ageThen} years old on release date`"
|
||||||
class="age-then"
|
class="age-then"
|
||||||
>@ {{ actor.ageThen }}</span>
|
>{{ actor.ageThen }}</span>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
v-if="actor.origin"
|
v-if="actor.origin"
|
||||||
|
@ -114,6 +115,7 @@ export default {
|
||||||
color: $text-contrast;
|
color: $text-contrast;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding: .5rem;
|
padding: .5rem;
|
||||||
|
|
|
@ -52,7 +52,7 @@ export default {
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
filter: $logo-outline;
|
filter: $logo-shadow;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="tile">
|
<div
|
||||||
|
class="tile"
|
||||||
|
:class="{ movie: release.type === 'movie' }"
|
||||||
|
>
|
||||||
<span class="banner">
|
<span class="banner">
|
||||||
<span class="details">
|
<span class="details">
|
||||||
<router-link
|
<router-link
|
||||||
|
@ -39,7 +42,7 @@
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<router-link
|
<router-link
|
||||||
:to="`/scene/${release.id}`"
|
:to="`/${release.type || 'scene'}/${release.id}`"
|
||||||
class="link"
|
class="link"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
|
@ -66,14 +69,19 @@
|
||||||
|
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<router-link
|
<router-link
|
||||||
:to="`/scene/${release.id}`"
|
:to="`/${release.type || 'scene'}/${release.id}`"
|
||||||
class="row link"
|
class="row link"
|
||||||
>
|
>
|
||||||
<h3
|
<h3
|
||||||
v-tooltip.top="release.title"
|
v-tooltip.top="release.title"
|
||||||
:title="release.title"
|
:title="release.title"
|
||||||
class="title"
|
class="title"
|
||||||
>{{ release.title }}</h3>
|
>
|
||||||
|
<Icon
|
||||||
|
v-if="release.type === 'movie'"
|
||||||
|
icon="film"
|
||||||
|
/>{{ release.title }}
|
||||||
|
</h3>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<span class="row">
|
<span class="row">
|
||||||
|
@ -212,13 +220,19 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
color: $text;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
margin: 0 .25rem .25rem 0;
|
margin: 0 .25rem .25rem 0;
|
||||||
|
color: $text;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
max-height: 3rem;
|
max-height: 3rem;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin: 0 .25rem 0 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.network {
|
.network {
|
||||||
|
@ -235,9 +249,8 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.tags {
|
.tags {
|
||||||
max-height: 2.5rem;
|
max-height: .5rem;
|
||||||
padding: .25rem .5rem 1rem .5rem;
|
padding: .25rem .5rem 1rem .5rem;
|
||||||
line-height: 1.5rem;
|
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,11 +4,11 @@
|
||||||
:title="site.name"
|
:title="site.name"
|
||||||
class="tile"
|
class="tile"
|
||||||
>
|
>
|
||||||
<object
|
<img
|
||||||
:data="`/img/logos/${site.network.slug}/${site.slug}.png`"
|
:src="`/img/logos/${site.network.slug}/${site.slug}.png`"
|
||||||
type="image/png"
|
:alt="site.name"
|
||||||
class="logo"
|
class="logo"
|
||||||
>{{ site.name }}</object>
|
>
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ export default {
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
filter: $logo-outline;
|
filter: $logo-shadow;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
|
|
|
@ -4,14 +4,14 @@
|
||||||
:title="tag.name"
|
:title="tag.name"
|
||||||
class="tile"
|
class="tile"
|
||||||
>
|
>
|
||||||
<span class="title">{{ tag.name }}</span>
|
|
||||||
|
|
||||||
<img
|
<img
|
||||||
v-if="tag.poster"
|
v-if="tag.poster"
|
||||||
:src="`/img/${tag.poster.thumbnail}`"
|
:src="`/img/${tag.poster.thumbnail}`"
|
||||||
:alt="tag.name"
|
:alt="tag.name"
|
||||||
class="poster"
|
class="poster"
|
||||||
>
|
>
|
||||||
|
|
||||||
|
<span class="title">{{ tag.name }}</span>
|
||||||
</a>
|
</a>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,8 @@ $highlight-strong: rgba(255, 255, 255, .7);
|
||||||
$highlight-weak: rgba(255, 255, 255, .2);
|
$highlight-weak: rgba(255, 255, 255, .2);
|
||||||
$highlight-hint: rgba(255, 255, 255, .075);
|
$highlight-hint: rgba(255, 255, 255, .075);
|
||||||
|
|
||||||
$logo-outline: drop-shadow(1px 0 0 $shadow-weak) drop-shadow(-1px 0 0 $shadow-weak) drop-shadow(0 1px 0 $shadow-weak) drop-shadow(0 -1px 0 $shadow-weak);
|
$logo-shadow: drop-shadow(1px 0 0 $shadow-weak) drop-shadow(-1px 0 0 $shadow-weak) drop-shadow(0 1px 0 $shadow-weak) drop-shadow(0 -1px 0 $shadow-weak);
|
||||||
|
$logo-highlight: drop-shadow(1px 0 0 $highlight-weak) drop-shadow(-1px 0 0 $highlight-weak) drop-shadow(0 1px 0 $highlight-weak) drop-shadow(0 -1px 0 $highlight-weak);
|
||||||
|
|
||||||
$profile: #222;
|
$profile: #222;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
<!-- Generated by IcoMoon.io -->
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<title>film</title>
|
||||||
|
<path d="M0 0v16h16v-16h-16zM10 1h2v2h-2v-2zM7 1h2v2h-2v-2zM4 1h2v2h-2v-2zM1 1h2v2h-2v-2zM1 4h6v8h-6v-8zM3 15h-2v-2h2v2zM6 15h-2v-2h2v2zM9 15h-2v-2h2v2zM12 15h-2v-2h2v2zM15 15h-2v-2h2v2zM15 12h-7v-8h7v8zM15 3h-2v-2h2v2z"></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 387 B |
|
@ -0,0 +1,5 @@
|
||||||
|
<!-- Generated by IcoMoon.io -->
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<title>film2</title>
|
||||||
|
<path d="M0 1v14h16v-14h-16zM3 14h-2v-2h2v2zM3 11h-2v-2h2v2zM3 7h-2v-2h2v2zM3 4h-2v-2h2v2zM12 14h-8v-5h8v5zM12 7h-8v-5h8v5zM15 14h-2v-2h2v2zM15 11h-2v-2h2v2zM15 7h-2v-2h2v2zM15 4h-2v-2h2v2z"></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 358 B |
|
@ -0,0 +1,5 @@
|
||||||
|
<!-- Generated by IcoMoon.io -->
|
||||||
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
<title>film3</title>
|
||||||
|
<path d="M0 2v12h16v-12h-16zM3 13h-2v-2h2v2zM3 9h-2v-2h2v2zM3 5h-2v-2h2v2zM12 13h-8v-10h8v10zM15 13h-2v-2h2v2zM15 9h-2v-2h2v2zM15 5h-2v-2h2v2z"></path>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 311 B |
|
@ -1,12 +1,191 @@
|
||||||
import { get } from '../api';
|
import { graphql, get } from '../api';
|
||||||
|
import {
|
||||||
|
releasePosterFragment,
|
||||||
|
releaseActorsFragment,
|
||||||
|
releaseTagsFragment,
|
||||||
|
} from '../fragments';
|
||||||
|
import { curateRelease } from '../curate';
|
||||||
|
|
||||||
|
function curateActor(actor) {
|
||||||
|
const curatedActor = {
|
||||||
|
...actor,
|
||||||
|
height: actor.heightMetric && {
|
||||||
|
metric: actor.heightMetric,
|
||||||
|
imperial: actor.heightImperial,
|
||||||
|
},
|
||||||
|
weight: actor.weightMetric && {
|
||||||
|
metric: actor.weightMetric,
|
||||||
|
imperial: actor.weightImperial,
|
||||||
|
},
|
||||||
|
origin: actor.birthCountry && {
|
||||||
|
city: actor.birthCity,
|
||||||
|
state: actor.birthState,
|
||||||
|
country: actor.birthCountry,
|
||||||
|
},
|
||||||
|
residence: actor.residenceCountry && {
|
||||||
|
city: actor.residenceCity,
|
||||||
|
state: actor.residenceState,
|
||||||
|
country: actor.residenceCountry,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (actor.avatar) {
|
||||||
|
curatedActor.avatar = actor.avatar.media;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actor.releases) {
|
||||||
|
curatedActor.releases = actor.releases.map(release => curateRelease(release.release));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actor.photos) {
|
||||||
|
curatedActor.photos = actor.photos.map(photo => photo.media);
|
||||||
|
}
|
||||||
|
|
||||||
|
return curatedActor;
|
||||||
|
}
|
||||||
|
|
||||||
function initActorActions(store, _router) {
|
function initActorActions(store, _router) {
|
||||||
async function fetchActors({ _commit }, { actorId, limit = 100 }) {
|
async function fetchActorBySlug(actorSlug, limit = 100) {
|
||||||
if (actorId) {
|
const { actor } = await graphql(`
|
||||||
return get(`/actors/${actorId}`, { limit });
|
query Actor(
|
||||||
|
$actorSlug: String!
|
||||||
|
$limit:Int = 1000,
|
||||||
|
$after:Date = "1900-01-01",
|
||||||
|
$before:Date = "2100-01-01",
|
||||||
|
) {
|
||||||
|
actor: actorBySlug(slug: $actorSlug) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
gender
|
||||||
|
birthdate
|
||||||
|
age
|
||||||
|
ethnicity
|
||||||
|
bust
|
||||||
|
waist
|
||||||
|
hip
|
||||||
|
heightMetric: height(units:METRIC)
|
||||||
|
heightImperial: height(units:IMPERIAL)
|
||||||
|
weightMetric: weight(units:METRIC)
|
||||||
|
weightImperial: weight(units:IMPERIAL)
|
||||||
|
hasTattoos
|
||||||
|
hasPiercings
|
||||||
|
tattoos
|
||||||
|
piercings
|
||||||
|
avatar: actorsAvatarByActorId {
|
||||||
|
media {
|
||||||
|
thumbnail
|
||||||
|
path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
photos: actorsPhotos {
|
||||||
|
media {
|
||||||
|
id
|
||||||
|
thumbnail
|
||||||
|
path
|
||||||
|
index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
birthCity
|
||||||
|
birthState
|
||||||
|
birthCountry: countryByBirthCountryAlpha2 {
|
||||||
|
alpha2
|
||||||
|
name
|
||||||
|
alias
|
||||||
|
}
|
||||||
|
residenceCity
|
||||||
|
residenceState
|
||||||
|
residenceCountry: countryByResidenceCountryAlpha2 {
|
||||||
|
alpha2
|
||||||
|
name
|
||||||
|
alias
|
||||||
|
}
|
||||||
|
social: actorsSocials {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
platform
|
||||||
|
}
|
||||||
|
aliases: actorsByAliasFor {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
releases: releasesActors(
|
||||||
|
filter: {
|
||||||
|
release: {
|
||||||
|
date: {
|
||||||
|
lessThan: $before,
|
||||||
|
greaterThan: $after,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
first: $limit,
|
||||||
|
orderBy: RELEASE_BY_RELEASE_ID__DATE_DESC,
|
||||||
|
) {
|
||||||
|
release {
|
||||||
|
id
|
||||||
|
url
|
||||||
|
title
|
||||||
|
date
|
||||||
|
${releaseActorsFragment}
|
||||||
|
${releaseTagsFragment}
|
||||||
|
${releasePosterFragment}
|
||||||
|
site {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
url
|
||||||
|
network {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, {
|
||||||
|
actorSlug,
|
||||||
|
limit,
|
||||||
|
after: store.getters.after,
|
||||||
|
before: store.getters.before,
|
||||||
|
});
|
||||||
|
|
||||||
|
return curateActor(actor);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchActors({ _commit }, { actorSlug, limit = 100 }) {
|
||||||
|
if (actorSlug) {
|
||||||
|
return fetchActorBySlug(actorSlug);
|
||||||
}
|
}
|
||||||
|
|
||||||
return get('/actors', { limit });
|
const { actors } = await graphql(`
|
||||||
|
query Actors($limit:Int) {
|
||||||
|
actors(first:$limit) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
age
|
||||||
|
birthdate
|
||||||
|
avatar: actorsAvatarByActorId {
|
||||||
|
media {
|
||||||
|
thumbnail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
birthCountry: countryByBirthCountryAlpha2 {
|
||||||
|
alpha2
|
||||||
|
name
|
||||||
|
alias
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, {
|
||||||
|
limit,
|
||||||
|
});
|
||||||
|
|
||||||
|
return actors.map(actor => curateActor(actor));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchActorReleases({ _commit }, actorId) {
|
async function fetchActorReleases({ _commit }, actorId) {
|
||||||
|
|
|
@ -21,7 +21,7 @@ async function get(endpoint, query = {}) {
|
||||||
|
|
||||||
async function post(endpoint, data) {
|
async function post(endpoint, data) {
|
||||||
const res = await fetch(`${config.api.url}${endpoint}`, {
|
const res = await fetch(`${config.api.url}${endpoint}`, {
|
||||||
method: 'GET',
|
method: 'POST',
|
||||||
mode: 'cors',
|
mode: 'cors',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
@ -39,4 +39,33 @@ async function post(endpoint, data) {
|
||||||
throw new Error(errorMsg);
|
throw new Error(errorMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { get, post };
|
async function graphql(query, variables = null) {
|
||||||
|
const res = await fetch('/graphql', {
|
||||||
|
method: 'POST',
|
||||||
|
mode: 'cors',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
credentials: 'same-origin',
|
||||||
|
body: JSON.stringify({
|
||||||
|
query,
|
||||||
|
variables,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (res.ok) {
|
||||||
|
const { data } = await res.json();
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorMsg = await res.text();
|
||||||
|
|
||||||
|
throw new Error(errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
get,
|
||||||
|
post,
|
||||||
|
graphql,
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
function curateActor(actor) {
|
||||||
|
const curatedActor = {
|
||||||
|
...actor,
|
||||||
|
origin: actor.originCountry && {
|
||||||
|
country: actor.originCountry,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (actor.avatar) curatedActor.avatar = actor.avatar.media;
|
||||||
|
|
||||||
|
return curatedActor;
|
||||||
|
}
|
||||||
|
|
||||||
|
function curateRelease(release) {
|
||||||
|
const curatedRelease = {
|
||||||
|
...release,
|
||||||
|
actors: release.actors ? release.actors.map(({ actor }) => curateActor(actor)) : [],
|
||||||
|
poster: release.poster && release.poster.media,
|
||||||
|
tags: release.tags ? release.tags.map(({ tag }) => tag) : [],
|
||||||
|
network: release.site.network,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (release.photos) curatedRelease.photos = release.photos.map(({ media }) => media);
|
||||||
|
if (release.trailer) curatedRelease.trailer = release.trailer.media;
|
||||||
|
|
||||||
|
return curatedRelease;
|
||||||
|
}
|
||||||
|
|
||||||
|
function curateSite(site, network) {
|
||||||
|
const curatedSite = {
|
||||||
|
id: site.id,
|
||||||
|
name: site.name,
|
||||||
|
slug: site.slug,
|
||||||
|
url: site.url,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (site.releases) curatedSite.releases = site.releases.map(release => curateRelease(release));
|
||||||
|
if (site.network || network) curatedSite.network = site.network || network;
|
||||||
|
|
||||||
|
return curatedSite;
|
||||||
|
}
|
||||||
|
|
||||||
|
function curateNetwork(network) {
|
||||||
|
const curatedNetwork = {
|
||||||
|
id: network.id,
|
||||||
|
name: network.name,
|
||||||
|
slug: network.slug,
|
||||||
|
url: network.url,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (network.sites) {
|
||||||
|
curatedNetwork.sites = network.sites.map(site => curateSite(site, curatedNetwork));
|
||||||
|
}
|
||||||
|
|
||||||
|
return curatedNetwork;
|
||||||
|
}
|
||||||
|
|
||||||
|
function curateTag(tag) {
|
||||||
|
const curatedTag = {
|
||||||
|
...tag,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (tag.releases) curatedTag.releases = tag.releases.map(({ release }) => curateRelease(release));
|
||||||
|
if (tag.photos) curatedTag.photos = tag.photos.map(({ media }) => media);
|
||||||
|
if (tag.poster) curatedTag.poster = tag.poster.media;
|
||||||
|
|
||||||
|
return curatedTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
curateActor,
|
||||||
|
curateRelease,
|
||||||
|
curateSite,
|
||||||
|
curateNetwork,
|
||||||
|
curateTag,
|
||||||
|
};
|
|
@ -0,0 +1,154 @@
|
||||||
|
const siteFragment = `
|
||||||
|
site {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
url
|
||||||
|
network {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const sitesFragment = `
|
||||||
|
sites {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
url
|
||||||
|
network {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const releaseActorsFragment = `
|
||||||
|
actors: releasesActors(orderBy: ACTOR_BY_ACTOR_ID__GENDER_ASC) {
|
||||||
|
actor {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
birthdate
|
||||||
|
age
|
||||||
|
originCountry: countryByBirthCountryAlpha2 {
|
||||||
|
alpha2
|
||||||
|
name
|
||||||
|
alias
|
||||||
|
}
|
||||||
|
avatar: actorsAvatarByActorId {
|
||||||
|
media {
|
||||||
|
thumbnail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const releaseTagsFragment = `
|
||||||
|
tags: releasesTags(orderBy: TAG_BY_TAG_ID__PRIORITY_DESC) {
|
||||||
|
tag {
|
||||||
|
name
|
||||||
|
priority
|
||||||
|
slug
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const releasePosterFragment = `
|
||||||
|
poster: releasesPosterByReleaseId {
|
||||||
|
media {
|
||||||
|
index
|
||||||
|
path
|
||||||
|
thumbnail
|
||||||
|
comment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const releasePhotosFragment = `
|
||||||
|
photos: releasesPhotos {
|
||||||
|
media {
|
||||||
|
index
|
||||||
|
path
|
||||||
|
thumbnail
|
||||||
|
comment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const releaseTrailerFragment = `
|
||||||
|
trailer: releasesTrailerByReleaseId {
|
||||||
|
media {
|
||||||
|
index
|
||||||
|
path
|
||||||
|
thumbnail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const releasesFragment = `
|
||||||
|
releases(
|
||||||
|
filter: {
|
||||||
|
date: {
|
||||||
|
lessThan: $before,
|
||||||
|
greaterThan: $after,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
first: $limit,
|
||||||
|
orderBy: DATE_DESC,
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
date
|
||||||
|
createdAt
|
||||||
|
url
|
||||||
|
${releaseActorsFragment}
|
||||||
|
${releaseTagsFragment}
|
||||||
|
${releasePosterFragment}
|
||||||
|
${siteFragment}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const releaseFragment = `
|
||||||
|
release(id: $releaseId) {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
description
|
||||||
|
date
|
||||||
|
duration
|
||||||
|
createdAt
|
||||||
|
shootId
|
||||||
|
url
|
||||||
|
${releaseActorsFragment}
|
||||||
|
${releaseTagsFragment}
|
||||||
|
${releasePosterFragment}
|
||||||
|
${releasePhotosFragment}
|
||||||
|
${releaseTrailerFragment}
|
||||||
|
${siteFragment}
|
||||||
|
studio {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export {
|
||||||
|
releaseActorsFragment,
|
||||||
|
releaseTagsFragment,
|
||||||
|
releasePosterFragment,
|
||||||
|
releasePhotosFragment,
|
||||||
|
releaseTrailerFragment,
|
||||||
|
releasesFragment,
|
||||||
|
releaseFragment,
|
||||||
|
siteFragment,
|
||||||
|
sitesFragment,
|
||||||
|
};
|
|
@ -1,27 +1,68 @@
|
||||||
import { get } from '../api';
|
import { graphql } from '../api';
|
||||||
|
import { sitesFragment, releasesFragment } from '../fragments';
|
||||||
|
import { curateNetwork } from '../curate';
|
||||||
|
|
||||||
function initNetworksActions(store, _router) {
|
function initNetworksActions(store, _router) {
|
||||||
async function fetchNetworks({ _commit }, networkId) {
|
async function fetchNetworkBySlug(networkSlug, limit = 100) {
|
||||||
const networks = await get(`/networks/${networkId || ''}`, {
|
const { network } = await graphql(`
|
||||||
|
query Network(
|
||||||
});
|
$networkSlug: String!
|
||||||
|
$limit:Int = 1000,
|
||||||
return networks;
|
$after:Date = "1900-01-01",
|
||||||
}
|
$before:Date = "2100-01-01",
|
||||||
|
) {
|
||||||
async function fetchNetworkReleases({ _commit }, networkId) {
|
network: networkBySlug(slug: $networkSlug) {
|
||||||
const releases = await get(`/networks/${networkId}/releases`, {
|
id
|
||||||
filter: store.state.ui.filter,
|
name
|
||||||
|
slug
|
||||||
|
url
|
||||||
|
sites {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
url
|
||||||
|
${releasesFragment}
|
||||||
|
network {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, {
|
||||||
|
networkSlug,
|
||||||
|
limit,
|
||||||
after: store.getters.after,
|
after: store.getters.after,
|
||||||
before: store.getters.before,
|
before: store.getters.before,
|
||||||
});
|
});
|
||||||
|
|
||||||
return releases;
|
return curateNetwork(network);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchNetworks({ _commit }, networkSlug) {
|
||||||
|
if (networkSlug) {
|
||||||
|
return fetchNetworkBySlug(networkSlug);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { networks } = await graphql(`
|
||||||
|
query Networks {
|
||||||
|
networks {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
url
|
||||||
|
${sitesFragment}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
|
||||||
|
return networks.map(network => curateNetwork(network));
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fetchNetworks,
|
fetchNetworks,
|
||||||
fetchNetworkReleases,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,40 @@
|
||||||
import { get } from '../api';
|
import { graphql } from '../api';
|
||||||
|
import { releasesFragment, releaseFragment } from '../fragments';
|
||||||
|
import { curateRelease } from '../curate';
|
||||||
|
|
||||||
function initReleasesActions(store, _router) {
|
function initReleasesActions(store, _router) {
|
||||||
async function fetchReleases({ _commit }) {
|
async function fetchReleases({ _commit }, { limit = 100 }) {
|
||||||
const releases = await get('/releases', {
|
console.log(store.state.ui.filter, store.getters.after, store.getters.before);
|
||||||
filter: store.state.ui.filter,
|
|
||||||
|
const { releases } = await graphql(`
|
||||||
|
query Releases(
|
||||||
|
$limit:Int = 1000,
|
||||||
|
$after:Date = "1900-01-01",
|
||||||
|
$before:Date = "2100-01-01",
|
||||||
|
) {
|
||||||
|
${releasesFragment}
|
||||||
|
}
|
||||||
|
`, {
|
||||||
|
limit,
|
||||||
after: store.getters.after,
|
after: store.getters.after,
|
||||||
before: store.getters.before,
|
before: store.getters.before,
|
||||||
});
|
});
|
||||||
|
|
||||||
return releases;
|
return releases.map(release => curateRelease(release));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchReleaseById({ _commit }, releaseId) {
|
async function fetchReleaseById({ _commit }, releaseId) {
|
||||||
const release = await get(`/releases/${releaseId}`);
|
// const release = await get(`/releases/${releaseId}`);
|
||||||
|
|
||||||
return release;
|
const { release } = await graphql(`
|
||||||
|
query Release($releaseId:Int!) {
|
||||||
|
${releaseFragment}
|
||||||
|
}
|
||||||
|
`, {
|
||||||
|
releaseId: Number(releaseId),
|
||||||
|
});
|
||||||
|
|
||||||
|
return curateRelease(release);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import VueRouter from 'vue-router';
|
||||||
|
|
||||||
import Home from '../components/home/home.vue';
|
import Home from '../components/home/home.vue';
|
||||||
import Release from '../components/releases/release.vue';
|
import Release from '../components/releases/release.vue';
|
||||||
import Site from '../components/site/site.vue';
|
import Site from '../components/sites/site.vue';
|
||||||
import Network from '../components/networks/network.vue';
|
import Network from '../components/networks/network.vue';
|
||||||
import Networks from '../components/networks/networks.vue';
|
import Networks from '../components/networks/networks.vue';
|
||||||
import Actor from '../components/actors/actor.vue';
|
import Actor from '../components/actors/actor.vue';
|
||||||
|
|
|
@ -1,12 +1,69 @@
|
||||||
import { get } from '../api';
|
import { graphql } from '../api';
|
||||||
|
import { releasesFragment } from '../fragments';
|
||||||
|
import { curateSite } from '../curate';
|
||||||
|
|
||||||
function initSitesActions(store, _router) {
|
function initSitesActions(store, _router) {
|
||||||
async function fetchSites({ _commit }, siteId) {
|
async function fetchSiteBySlug(siteSlug, limit = 100) {
|
||||||
const sites = await get(`/sites/${siteId || ''}`);
|
const { site } = await graphql(`
|
||||||
|
query Site(
|
||||||
|
$siteSlug: String!,
|
||||||
|
$limit:Int = 100,
|
||||||
|
$after:Date = "1900-01-01",
|
||||||
|
$before:Date = "2100-01-01",
|
||||||
|
) {
|
||||||
|
site: siteBySlug(slug: $siteSlug) {
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
url
|
||||||
|
network {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
url
|
||||||
|
}
|
||||||
|
${releasesFragment}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, {
|
||||||
|
siteSlug,
|
||||||
|
limit,
|
||||||
|
after: store.getters.after,
|
||||||
|
before: store.getters.before,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(site);
|
||||||
|
|
||||||
|
return curateSite(site);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchSites({ _commit }, { siteSlug, limit = 100 }) {
|
||||||
|
if (siteSlug) {
|
||||||
|
return fetchSiteBySlug(siteSlug, limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { sites } = await graphql(`
|
||||||
|
query Sites(
|
||||||
|
$actorSlug: String!
|
||||||
|
$limit:Int = 100,
|
||||||
|
$after:Date = "1900-01-01",
|
||||||
|
$before:Date = "2100-01-01",
|
||||||
|
) {
|
||||||
|
site {
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, {
|
||||||
|
limit,
|
||||||
|
after: store.getters.after,
|
||||||
|
before: store.getters.before,
|
||||||
|
});
|
||||||
|
|
||||||
return sites;
|
return sites;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
async function fetchSiteReleases({ _commit }, siteId) {
|
async function fetchSiteReleases({ _commit }, siteId) {
|
||||||
const releases = await get(`/sites/${siteId}/releases`, {
|
const releases = await get(`/sites/${siteId}/releases`, {
|
||||||
filter: store.state.ui.filter,
|
filter: store.state.ui.filter,
|
||||||
|
@ -16,10 +73,11 @@ function initSitesActions(store, _router) {
|
||||||
|
|
||||||
return releases;
|
return releases;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fetchSites,
|
fetchSites,
|
||||||
fetchSiteReleases,
|
// fetchSiteReleases,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,23 +1,97 @@
|
||||||
import { get } from '../api';
|
import { graphql, get } from '../api';
|
||||||
|
import {
|
||||||
|
releasePosterFragment,
|
||||||
|
releaseActorsFragment,
|
||||||
|
releaseTagsFragment,
|
||||||
|
siteFragment,
|
||||||
|
} from '../fragments';
|
||||||
|
import { curateTag } from '../curate';
|
||||||
|
|
||||||
function initTagsActions(store, _router) {
|
function initTagsActions(store, _router) {
|
||||||
|
async function fetchTagBySlug(tagSlug) {
|
||||||
|
const { tagBySlug } = await graphql(`
|
||||||
|
query Tag($tagSlug:String!) {
|
||||||
|
tagBySlug(slug:$tagSlug) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
description
|
||||||
|
group {
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
poster: tagsPosterByTagId {
|
||||||
|
media {
|
||||||
|
id
|
||||||
|
thumbnail
|
||||||
|
path
|
||||||
|
comment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
photos: tagsPhotos {
|
||||||
|
media {
|
||||||
|
id
|
||||||
|
thumbnail
|
||||||
|
path
|
||||||
|
comment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
releases: releasesTags {
|
||||||
|
release {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
date
|
||||||
|
createdAt
|
||||||
|
url
|
||||||
|
${releaseActorsFragment}
|
||||||
|
${releaseTagsFragment}
|
||||||
|
${releasePosterFragment}
|
||||||
|
${siteFragment}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, {
|
||||||
|
tagSlug,
|
||||||
|
});
|
||||||
|
|
||||||
|
return curateTag(tagBySlug);
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchTags({ _commit }, {
|
async function fetchTags({ _commit }, {
|
||||||
tagId,
|
tagSlug,
|
||||||
limit = 100,
|
limit = 100,
|
||||||
slug,
|
slugs = [],
|
||||||
group,
|
_group,
|
||||||
priority,
|
_priority,
|
||||||
}) {
|
}) {
|
||||||
if (tagId) {
|
if (tagSlug) {
|
||||||
return get(`/tags/${tagId}`);
|
return fetchTagBySlug(tagSlug);
|
||||||
}
|
}
|
||||||
|
|
||||||
return get('/tags', {
|
const { tags } = await graphql(`
|
||||||
|
query Tags($slugs: [String!] = [], $limit: Int = 100) {
|
||||||
|
tags(filter: {slug: {in: $slugs}}, first: $limit) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
poster: tagsPosterByTagId {
|
||||||
|
media {
|
||||||
|
thumbnail
|
||||||
|
}
|
||||||
|
}
|
||||||
|
group {
|
||||||
|
name
|
||||||
|
slug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`, {
|
||||||
|
slugs,
|
||||||
limit,
|
limit,
|
||||||
slug,
|
|
||||||
priority,
|
|
||||||
group,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return tags.map(tag => curateTag(tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchTagReleases({ _commit }, tagId) {
|
async function fetchTagReleases({ _commit }, tagId) {
|
||||||
|
@ -33,6 +107,7 @@ function initTagsActions(store, _router) {
|
||||||
return {
|
return {
|
||||||
fetchTags,
|
fetchTags,
|
||||||
fetchTagReleases,
|
fetchTagReleases,
|
||||||
|
fetchTagBySlug,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,16 +2,16 @@ import dayjs from 'dayjs';
|
||||||
|
|
||||||
const dateRanges = {
|
const dateRanges = {
|
||||||
new: () => ({
|
new: () => ({
|
||||||
after: dayjs(new Date(0)).format('YYYY-MM-DD'),
|
after: '1900-01-01',
|
||||||
before: dayjs(new Date()).format('YYYY-MM-DD'),
|
before: dayjs(new Date()).add(1, 'day').format('YYYY-MM-DD'),
|
||||||
}),
|
}),
|
||||||
upcoming: () => ({
|
upcoming: () => ({
|
||||||
after: dayjs(new Date()).format('YYYY-MM-DD'),
|
after: dayjs(new Date()).format('YYYY-MM-DD'),
|
||||||
before: dayjs(new Date(2 ** 42)).format('YYYY-MM-DD'),
|
before: '2100-01-01',
|
||||||
}),
|
}),
|
||||||
all: () => ({
|
all: () => ({
|
||||||
after: dayjs(new Date(0)).format('YYYY-MM-DD'),
|
after: '1900-01-01',
|
||||||
before: dayjs(new Date(2 ** 42)).format('YYYY-MM-DD'),
|
before: '2100-01-01',
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,184 @@ exports.up = knex => Promise.resolve()
|
||||||
table.integer('priority', 2)
|
table.integer('priority', 2)
|
||||||
.defaultTo(0);
|
.defaultTo(0);
|
||||||
}))
|
}))
|
||||||
|
.then(() => knex.schema.createTable('media', (table) => {
|
||||||
|
table.increments('id', 16);
|
||||||
|
|
||||||
|
table.string('path');
|
||||||
|
table.string('thumbnail');
|
||||||
|
table.integer('index');
|
||||||
|
table.string('mime');
|
||||||
|
|
||||||
|
table.string('type');
|
||||||
|
table.string('quality', 6);
|
||||||
|
|
||||||
|
table.string('hash');
|
||||||
|
table.text('comment');
|
||||||
|
table.string('source', 1000);
|
||||||
|
|
||||||
|
table.unique('hash');
|
||||||
|
table.unique('source');
|
||||||
|
|
||||||
|
table.datetime('created_at')
|
||||||
|
.defaultTo(knex.fn.now());
|
||||||
|
}))
|
||||||
|
.then(() => knex.schema.createTable('tags_groups', (table) => {
|
||||||
|
table.increments('id', 12);
|
||||||
|
|
||||||
|
table.string('name', 32);
|
||||||
|
table.text('description');
|
||||||
|
|
||||||
|
table.string('slug', 32)
|
||||||
|
.unique();
|
||||||
|
|
||||||
|
table.datetime('created_at')
|
||||||
|
.defaultTo(knex.fn.now());
|
||||||
|
}))
|
||||||
|
.then(() => knex.schema.createTable('tags', (table) => {
|
||||||
|
table.increments('id', 12);
|
||||||
|
table.string('name');
|
||||||
|
|
||||||
|
table.text('description');
|
||||||
|
|
||||||
|
table.integer('priority', 2)
|
||||||
|
.defaultTo(0);
|
||||||
|
|
||||||
|
table.integer('group_id', 12)
|
||||||
|
.references('id')
|
||||||
|
.inTable('tags_groups');
|
||||||
|
|
||||||
|
table.integer('alias_for', 12)
|
||||||
|
.references('id')
|
||||||
|
.inTable('tags');
|
||||||
|
|
||||||
|
table.string('slug', 32)
|
||||||
|
.unique();
|
||||||
|
|
||||||
|
table.datetime('created_at')
|
||||||
|
.defaultTo(knex.fn.now());
|
||||||
|
}))
|
||||||
|
.then(() => knex.schema.createTable('tags_posters', (table) => {
|
||||||
|
table.integer('tag_id', 12)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('tags');
|
||||||
|
|
||||||
|
table.integer('media_id', 16)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('media');
|
||||||
|
|
||||||
|
table.unique('tag_id');
|
||||||
|
}))
|
||||||
|
.then(() => knex.schema.createTable('tags_photos', (table) => {
|
||||||
|
table.integer('tag_id', 12)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('tags');
|
||||||
|
|
||||||
|
table.integer('media_id', 16)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('media');
|
||||||
|
|
||||||
|
table.unique(['tag_id', 'media_id']);
|
||||||
|
}))
|
||||||
|
.then(() => knex.schema.createTable('networks', (table) => {
|
||||||
|
table.increments('id', 12);
|
||||||
|
|
||||||
|
table.string('name');
|
||||||
|
table.string('url');
|
||||||
|
table.text('description');
|
||||||
|
table.string('parameters');
|
||||||
|
|
||||||
|
table.string('slug', 32)
|
||||||
|
.unique();
|
||||||
|
|
||||||
|
table.datetime('created_at')
|
||||||
|
.defaultTo(knex.fn.now());
|
||||||
|
}))
|
||||||
|
.then(() => knex.schema.createTable('networks_social', (table) => {
|
||||||
|
table.increments('id', 16);
|
||||||
|
|
||||||
|
table.string('url');
|
||||||
|
table.string('platform');
|
||||||
|
|
||||||
|
table.integer('network_id', 12)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('networks');
|
||||||
|
|
||||||
|
table.unique(['url', 'network_id']);
|
||||||
|
|
||||||
|
table.datetime('created_at')
|
||||||
|
.defaultTo(knex.fn.now());
|
||||||
|
}))
|
||||||
|
.then(() => knex.schema.createTable('sites', (table) => {
|
||||||
|
table.increments('id', 12);
|
||||||
|
|
||||||
|
table.integer('network_id', 12)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('networks');
|
||||||
|
|
||||||
|
table.string('name');
|
||||||
|
table.string('url');
|
||||||
|
table.text('description');
|
||||||
|
table.string('parameters');
|
||||||
|
|
||||||
|
table.string('slug', 32)
|
||||||
|
.unique();
|
||||||
|
|
||||||
|
table.datetime('created_at')
|
||||||
|
.defaultTo(knex.fn.now());
|
||||||
|
}))
|
||||||
|
.then(() => knex.schema.createTable('sites_tags', (table) => {
|
||||||
|
table.integer('tag_id', 12)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('tags');
|
||||||
|
|
||||||
|
table.integer('site_id', 12)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('sites');
|
||||||
|
|
||||||
|
table.unique(['tag_id', 'site_id']);
|
||||||
|
}))
|
||||||
|
.then(() => knex.schema.createTable('sites_social', (table) => {
|
||||||
|
table.increments('id', 16);
|
||||||
|
|
||||||
|
table.string('url');
|
||||||
|
table.string('platform');
|
||||||
|
|
||||||
|
table.integer('site_id', 12)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('sites');
|
||||||
|
|
||||||
|
table.unique(['url', 'site_id']);
|
||||||
|
|
||||||
|
table.datetime('created_at')
|
||||||
|
.defaultTo(knex.fn.now());
|
||||||
|
}))
|
||||||
|
.then(() => knex.schema.createTable('studios', (table) => {
|
||||||
|
table.increments('id', 12);
|
||||||
|
|
||||||
|
table.integer('network_id', 12)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('networks');
|
||||||
|
|
||||||
|
table.string('name');
|
||||||
|
table.string('url');
|
||||||
|
table.text('description');
|
||||||
|
|
||||||
|
table.string('slug', 32)
|
||||||
|
.unique();
|
||||||
|
|
||||||
|
table.datetime('created_at')
|
||||||
|
.defaultTo(knex.fn.now());
|
||||||
|
}))
|
||||||
.then(() => knex.schema.createTable('actors', (table) => {
|
.then(() => knex.schema.createTable('actors', (table) => {
|
||||||
table.increments('id', 12);
|
table.increments('id', 12);
|
||||||
|
|
||||||
|
@ -70,6 +248,48 @@ exports.up = knex => Promise.resolve()
|
||||||
table.datetime('scraped_at');
|
table.datetime('scraped_at');
|
||||||
table.boolean('scrape_success');
|
table.boolean('scrape_success');
|
||||||
}))
|
}))
|
||||||
|
.then(() => knex.schema.createTable('actors_avatars', (table) => {
|
||||||
|
table.integer('actor_id', 12)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('actors');
|
||||||
|
|
||||||
|
table.integer('media_id', 16)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('media');
|
||||||
|
|
||||||
|
table.unique('actor_id');
|
||||||
|
}))
|
||||||
|
.then(() => knex.schema.createTable('actors_photos', (table) => {
|
||||||
|
table.integer('actor_id', 12)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('actors');
|
||||||
|
|
||||||
|
table.integer('media_id', 16)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('media');
|
||||||
|
|
||||||
|
table.unique(['actor_id', 'media_id']);
|
||||||
|
}))
|
||||||
|
.then(() => knex.schema.createTable('actors_social', (table) => {
|
||||||
|
table.increments('id', 16);
|
||||||
|
|
||||||
|
table.string('url');
|
||||||
|
table.string('platform');
|
||||||
|
|
||||||
|
table.integer('actor_id', 8)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('actors');
|
||||||
|
|
||||||
|
table.unique(['url', 'actor_id']);
|
||||||
|
|
||||||
|
table.datetime('created_at')
|
||||||
|
.defaultTo(knex.fn.now());
|
||||||
|
}))
|
||||||
.then(() => knex.schema.createTable('directors', (table) => {
|
.then(() => knex.schema.createTable('directors', (table) => {
|
||||||
table.increments('id', 12);
|
table.increments('id', 12);
|
||||||
|
|
||||||
|
@ -84,92 +304,6 @@ exports.up = knex => Promise.resolve()
|
||||||
table.datetime('created_at')
|
table.datetime('created_at')
|
||||||
.defaultTo(knex.fn.now());
|
.defaultTo(knex.fn.now());
|
||||||
}))
|
}))
|
||||||
.then(() => knex.schema.createTable('tags_groups', (table) => {
|
|
||||||
table.increments('id', 12);
|
|
||||||
|
|
||||||
table.string('name', 32);
|
|
||||||
table.text('description');
|
|
||||||
|
|
||||||
table.string('slug', 32)
|
|
||||||
.unique();
|
|
||||||
|
|
||||||
table.datetime('created_at')
|
|
||||||
.defaultTo(knex.fn.now());
|
|
||||||
}))
|
|
||||||
.then(() => knex.schema.createTable('tags', (table) => {
|
|
||||||
table.increments('id', 12);
|
|
||||||
table.string('name');
|
|
||||||
|
|
||||||
table.text('description');
|
|
||||||
|
|
||||||
table.integer('priority', 2)
|
|
||||||
.defaultTo(0);
|
|
||||||
|
|
||||||
table.integer('group_id', 12)
|
|
||||||
.references('id')
|
|
||||||
.inTable('tags_groups');
|
|
||||||
|
|
||||||
table.integer('alias_for', 12)
|
|
||||||
.references('id')
|
|
||||||
.inTable('tags');
|
|
||||||
|
|
||||||
table.string('slug', 32)
|
|
||||||
.unique();
|
|
||||||
|
|
||||||
table.datetime('created_at')
|
|
||||||
.defaultTo(knex.fn.now());
|
|
||||||
}))
|
|
||||||
.then(() => knex.schema.createTable('networks', (table) => {
|
|
||||||
table.increments('id', 12);
|
|
||||||
|
|
||||||
table.string('name');
|
|
||||||
table.string('url');
|
|
||||||
table.text('description');
|
|
||||||
table.string('parameters');
|
|
||||||
|
|
||||||
table.string('slug', 32)
|
|
||||||
.unique();
|
|
||||||
|
|
||||||
table.datetime('created_at')
|
|
||||||
.defaultTo(knex.fn.now());
|
|
||||||
}))
|
|
||||||
.then(() => knex.schema.createTable('sites', (table) => {
|
|
||||||
table.increments('id', 12);
|
|
||||||
|
|
||||||
table.integer('network_id', 12)
|
|
||||||
.notNullable()
|
|
||||||
.references('id')
|
|
||||||
.inTable('networks');
|
|
||||||
|
|
||||||
table.string('name');
|
|
||||||
table.string('url');
|
|
||||||
table.text('description');
|
|
||||||
table.string('parameters');
|
|
||||||
|
|
||||||
table.string('slug', 32)
|
|
||||||
.unique();
|
|
||||||
|
|
||||||
table.datetime('created_at')
|
|
||||||
.defaultTo(knex.fn.now());
|
|
||||||
}))
|
|
||||||
.then(() => knex.schema.createTable('studios', (table) => {
|
|
||||||
table.increments('id', 12);
|
|
||||||
|
|
||||||
table.integer('network_id', 12)
|
|
||||||
.notNullable()
|
|
||||||
.references('id')
|
|
||||||
.inTable('networks');
|
|
||||||
|
|
||||||
table.string('name');
|
|
||||||
table.string('url');
|
|
||||||
table.text('description');
|
|
||||||
|
|
||||||
table.string('slug', 32)
|
|
||||||
.unique();
|
|
||||||
|
|
||||||
table.datetime('created_at')
|
|
||||||
.defaultTo(knex.fn.now());
|
|
||||||
}))
|
|
||||||
.then(() => knex.schema.createTable('releases', (table) => {
|
.then(() => knex.schema.createTable('releases', (table) => {
|
||||||
table.increments('id', 16);
|
table.increments('id', 16);
|
||||||
|
|
||||||
|
@ -193,14 +327,10 @@ exports.up = knex => Promise.resolve()
|
||||||
table.date('date');
|
table.date('date');
|
||||||
table.text('description');
|
table.text('description');
|
||||||
|
|
||||||
table.integer('director', 12)
|
|
||||||
.references('id')
|
|
||||||
.inTable('directors');
|
|
||||||
|
|
||||||
table.integer('duration')
|
table.integer('duration')
|
||||||
.unsigned();
|
.unsigned();
|
||||||
|
|
||||||
table.integer('parent', 16)
|
table.integer('parent_id', 16)
|
||||||
.references('id')
|
.references('id')
|
||||||
.inTable('releases');
|
.inTable('releases');
|
||||||
|
|
||||||
|
@ -209,46 +339,7 @@ exports.up = knex => Promise.resolve()
|
||||||
table.datetime('created_at')
|
table.datetime('created_at')
|
||||||
.defaultTo(knex.fn.now());
|
.defaultTo(knex.fn.now());
|
||||||
}))
|
}))
|
||||||
.then(() => knex.schema.createTable('media', (table) => {
|
.then(() => knex.schema.createTable('releases_actors', (table) => {
|
||||||
table.increments('id', 16);
|
|
||||||
|
|
||||||
table.string('path');
|
|
||||||
table.string('thumbnail');
|
|
||||||
table.integer('index');
|
|
||||||
table.string('mime');
|
|
||||||
|
|
||||||
table.string('domain');
|
|
||||||
table.integer('target_id', 16);
|
|
||||||
|
|
||||||
table.string('role');
|
|
||||||
table.string('quality', 6);
|
|
||||||
|
|
||||||
table.string('hash');
|
|
||||||
table.text('comment');
|
|
||||||
table.string('source', 1000);
|
|
||||||
|
|
||||||
table.unique(['domain', 'target_id', 'role', 'hash']);
|
|
||||||
|
|
||||||
table.datetime('created_at')
|
|
||||||
.defaultTo(knex.fn.now());
|
|
||||||
}))
|
|
||||||
.then(() => knex.schema.createTable('social', (table) => {
|
|
||||||
table.increments('id', 16);
|
|
||||||
|
|
||||||
table.string('url');
|
|
||||||
table.string('platform');
|
|
||||||
|
|
||||||
table.string('domain');
|
|
||||||
table.integer('target_id', 16);
|
|
||||||
|
|
||||||
table.unique(['url', 'domain', 'target_id']);
|
|
||||||
|
|
||||||
table.datetime('created_at')
|
|
||||||
.defaultTo(knex.fn.now());
|
|
||||||
}))
|
|
||||||
.then(() => knex.schema.createTable('actors_associated', (table) => {
|
|
||||||
table.increments('id', 16);
|
|
||||||
|
|
||||||
table.integer('release_id', 16)
|
table.integer('release_id', 16)
|
||||||
.notNullable()
|
.notNullable()
|
||||||
.references('id')
|
.references('id')
|
||||||
|
@ -261,9 +352,7 @@ exports.up = knex => Promise.resolve()
|
||||||
|
|
||||||
table.unique(['release_id', 'actor_id']);
|
table.unique(['release_id', 'actor_id']);
|
||||||
}))
|
}))
|
||||||
.then(() => knex.schema.createTable('directors_associated', (table) => {
|
.then(() => knex.schema.createTable('releases_directors', (table) => {
|
||||||
table.increments('id', 16);
|
|
||||||
|
|
||||||
table.integer('release_id', 16)
|
table.integer('release_id', 16)
|
||||||
.notNullable()
|
.notNullable()
|
||||||
.references('id')
|
.references('id')
|
||||||
|
@ -276,30 +365,131 @@ exports.up = knex => Promise.resolve()
|
||||||
|
|
||||||
table.unique(['release_id', 'director_id']);
|
table.unique(['release_id', 'director_id']);
|
||||||
}))
|
}))
|
||||||
.then(() => knex.schema.createTable('tags_associated', (table) => {
|
.then(() => knex.schema.createTable('releases_posters', (table) => {
|
||||||
|
table.integer('release_id', 16)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('releases');
|
||||||
|
|
||||||
|
table.integer('media_id', 16)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('media');
|
||||||
|
|
||||||
|
table.unique('release_id');
|
||||||
|
}))
|
||||||
|
.then(() => knex.schema.createTable('releases_covers', (table) => {
|
||||||
|
table.integer('release_id', 16)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('releases');
|
||||||
|
|
||||||
|
table.integer('media_id', 16)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('media');
|
||||||
|
|
||||||
|
table.unique(['release_id', 'media_id']);
|
||||||
|
}))
|
||||||
|
.then(() => knex.schema.createTable('releases_trailers', (table) => {
|
||||||
|
table.integer('release_id', 16)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('releases');
|
||||||
|
|
||||||
|
table.integer('media_id', 16)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('media');
|
||||||
|
|
||||||
|
table.unique('release_id');
|
||||||
|
}))
|
||||||
|
.then(() => knex.schema.createTable('releases_photos', (table) => {
|
||||||
|
table.integer('release_id', 16)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('releases');
|
||||||
|
|
||||||
|
table.integer('media_id', 16)
|
||||||
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('media');
|
||||||
|
|
||||||
|
table.unique(['release_id', 'media_id']);
|
||||||
|
}))
|
||||||
|
.then(() => knex.schema.createTable('releases_tags', (table) => {
|
||||||
table.integer('tag_id', 12)
|
table.integer('tag_id', 12)
|
||||||
.notNullable()
|
.notNullable()
|
||||||
.references('id')
|
.references('id')
|
||||||
.inTable('tags');
|
.inTable('tags');
|
||||||
|
|
||||||
table.string('domain');
|
table.integer('release_id', 16)
|
||||||
table.integer('target_id', 16);
|
.notNullable()
|
||||||
|
.references('id')
|
||||||
|
.inTable('releases');
|
||||||
|
|
||||||
table.unique(['domain', 'tag_id', 'target_id']);
|
table.unique(['tag_id', 'release_id']);
|
||||||
}));
|
}))
|
||||||
|
.then(() => knex.raw(`
|
||||||
|
COMMENT ON COLUMN actors.height IS E'@omit read,update,create,delete,all,many';
|
||||||
|
COMMENT ON COLUMN actors.weight IS E'@omit read,update,create,delete,all,many';
|
||||||
|
|
||||||
exports.down = knex => Promise.resolve()
|
/*
|
||||||
.then(() => knex.schema.dropTable('tags_associated'))
|
CREATE VIEW releases_actors_sortable AS
|
||||||
.then(() => knex.schema.dropTable('directors_associated'))
|
SELECT releases_actors.*, actors.gender, actors.name, actors.birthdate FROM releases_actors
|
||||||
.then(() => knex.schema.dropTable('actors_associated'))
|
JOIN actors ON releases_actors.actor_id = actors.id;
|
||||||
.then(() => knex.schema.dropTable('tags'))
|
|
||||||
.then(() => knex.schema.dropTable('tags_groups'))
|
CREATE VIEW releases_tags_sortable AS
|
||||||
.then(() => knex.schema.dropTable('media'))
|
SELECT releases_tags.*, tags.name, tags.priority FROM releases_tags
|
||||||
.then(() => knex.schema.dropTable('social'))
|
JOIN tags ON releases_tags.tag_id = tags.id;
|
||||||
.then(() => knex.schema.dropTable('actors'))
|
|
||||||
.then(() => knex.schema.dropTable('releases'))
|
CREATE VIEW actors_releases_sortable AS
|
||||||
.then(() => knex.schema.dropTable('sites'))
|
SELECT releases_actors.*, releases.date FROM releases_actors
|
||||||
.then(() => knex.schema.dropTable('studios'))
|
JOIN releases ON releases_actors.release_id = releases.id;
|
||||||
.then(() => knex.schema.dropTable('directors'))
|
|
||||||
.then(() => knex.schema.dropTable('networks'))
|
COMMENT ON VIEW releases_actors_sortable IS E'@foreignKey (release_id) references releases (id)\n@foreignKey (actor_id) references actors (id)';
|
||||||
.then(() => knex.schema.dropTable('countries'));
|
COMMENT ON VIEW releases_tags_sortable IS E'@foreignKey (release_id) references releases (id)\n@foreignKey (tag_id) references tags (id)';
|
||||||
|
COMMENT ON VIEW actors_releases_sortable IS E'@foreignKey (release_id) references releases (id)\n@foreignKey (actor_id) references actors (id)';
|
||||||
|
|
||||||
|
/* allow conversion resolver to be added for height and weight */
|
||||||
|
CREATE FUNCTION releases_by_tag_slugs(slugs text[]) RETURNS setof releases AS $$
|
||||||
|
SELECT DISTINCT ON (releases.id) releases.* FROM releases
|
||||||
|
JOIN releases_tags ON (releases_tags.release_id = releases.id)
|
||||||
|
JOIN tags ON (releases_tags.tag_id = tags.id)
|
||||||
|
WHERE tags.slug = ANY($1);
|
||||||
|
$$ LANGUAGE sql STABLE
|
||||||
|
*/
|
||||||
|
`));
|
||||||
|
|
||||||
|
exports.down = knex => knex.raw(`
|
||||||
|
DROP FUNCTION IF EXISTS releases_by_tag_slugs;
|
||||||
|
|
||||||
|
DROP VIEW IF EXISTS releases_actors_view;
|
||||||
|
|
||||||
|
DROP TABLE IF EXISTS releases_actors CASCADE;
|
||||||
|
DROP TABLE IF EXISTS releases_directors CASCADE;
|
||||||
|
DROP TABLE IF EXISTS releases_posters CASCADE;
|
||||||
|
DROP TABLE IF EXISTS releases_photos CASCADE;
|
||||||
|
DROP TABLE IF EXISTS releases_covers CASCADE;
|
||||||
|
DROP TABLE IF EXISTS releases_trailers CASCADE;
|
||||||
|
DROP TABLE IF EXISTS releases_tags CASCADE;
|
||||||
|
DROP TABLE IF EXISTS actors_avatars CASCADE;
|
||||||
|
DROP TABLE IF EXISTS actors_photos CASCADE;
|
||||||
|
DROP TABLE IF EXISTS actors_social CASCADE;
|
||||||
|
DROP TABLE IF EXISTS sites_tags CASCADE;
|
||||||
|
DROP TABLE IF EXISTS sites_social CASCADE;
|
||||||
|
DROP TABLE IF EXISTS networks_social CASCADE;
|
||||||
|
DROP TABLE IF EXISTS tags_posters CASCADE;
|
||||||
|
DROP TABLE IF EXISTS tags_photos CASCADE;
|
||||||
|
DROP TABLE IF EXISTS releases CASCADE;
|
||||||
|
DROP TABLE IF EXISTS actors CASCADE;
|
||||||
|
DROP TABLE IF EXISTS directors CASCADE;
|
||||||
|
DROP TABLE IF EXISTS tags CASCADE;
|
||||||
|
DROP TABLE IF EXISTS tags_groups CASCADE;
|
||||||
|
DROP TABLE IF EXISTS social CASCADE;
|
||||||
|
DROP TABLE IF EXISTS sites CASCADE;
|
||||||
|
DROP TABLE IF EXISTS studios CASCADE;
|
||||||
|
DROP TABLE IF EXISTS media CASCADE;
|
||||||
|
DROP TABLE IF EXISTS countries CASCADE;
|
||||||
|
DROP TABLE IF EXISTS networks CASCADE;
|
||||||
|
`);
|
||||||
|
|
10
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "traxxx",
|
"name": "traxxx",
|
||||||
"version": "1.43.1",
|
"version": "1.44.0",
|
||||||
"description": "All the latest porn releases in one place",
|
"description": "All the latest porn releases in one place",
|
||||||
"main": "src/app.js",
|
"main": "src/app.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -39,12 +39,11 @@
|
||||||
"@babel/core": "^7.7.5",
|
"@babel/core": "^7.7.5",
|
||||||
"@babel/plugin-proposal-optional-chaining": "^7.7.5",
|
"@babel/plugin-proposal-optional-chaining": "^7.7.5",
|
||||||
"@babel/preset-env": "^7.7.6",
|
"@babel/preset-env": "^7.7.6",
|
||||||
|
"@babel/register": "^7.7.4",
|
||||||
"autoprefixer": "^9.7.3",
|
"autoprefixer": "^9.7.3",
|
||||||
"babel-cli": "^6.26.0",
|
|
||||||
"babel-eslint": "^10.0.3",
|
"babel-eslint": "^10.0.3",
|
||||||
"babel-loader": "^8.0.6",
|
"babel-loader": "^8.0.6",
|
||||||
"babel-preset-airbnb": "^3.3.2",
|
"babel-preset-airbnb": "^3.3.2",
|
||||||
"babel-register": "^6.26.0",
|
|
||||||
"css-loader": "^2.1.1",
|
"css-loader": "^2.1.1",
|
||||||
"eslint": "^5.16.0",
|
"eslint": "^5.16.0",
|
||||||
"eslint-config-airbnb": "^17.1.1",
|
"eslint-config-airbnb": "^17.1.1",
|
||||||
|
@ -67,6 +66,8 @@
|
||||||
"webpack-cli": "^3.3.10"
|
"webpack-cli": "^3.3.10"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@graphile-contrib/pg-order-by-related": "^1.0.0-beta.6",
|
||||||
|
"@graphile-contrib/pg-simplify-inflector": "^5.0.0-beta.1",
|
||||||
"@tensorflow/tfjs-node": "^1.4.0",
|
"@tensorflow/tfjs-node": "^1.4.0",
|
||||||
"babel-polyfill": "^6.26.0",
|
"babel-polyfill": "^6.26.0",
|
||||||
"bhttp": "^1.2.4",
|
"bhttp": "^1.2.4",
|
||||||
|
@ -83,6 +84,7 @@
|
||||||
"express-react-views": "^0.11.0",
|
"express-react-views": "^0.11.0",
|
||||||
"face-api.js": "^0.21.0",
|
"face-api.js": "^0.21.0",
|
||||||
"fs-extra": "^7.0.1",
|
"fs-extra": "^7.0.1",
|
||||||
|
"graphile-utils": "^4.5.6",
|
||||||
"jsdom": "^15.2.1",
|
"jsdom": "^15.2.1",
|
||||||
"knex": "^0.16.5",
|
"knex": "^0.16.5",
|
||||||
"knex-migrate": "^1.7.4",
|
"knex-migrate": "^1.7.4",
|
||||||
|
@ -90,6 +92,8 @@
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.24.0",
|
||||||
"opn": "^5.5.0",
|
"opn": "^5.5.0",
|
||||||
"pg": "^7.14.0",
|
"pg": "^7.14.0",
|
||||||
|
"postgraphile": "^4.5.5",
|
||||||
|
"postgraphile-plugin-connection-filter": "^1.1.3",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"react": "^16.12.0",
|
"react": "^16.12.0",
|
||||||
"react-dom": "^16.12.0",
|
"react-dom": "^16.12.0",
|
||||||
|
|
|
@ -188,14 +188,21 @@
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
.title[data-v-3abcf101] {
|
.title[data-v-3abcf101] {
|
||||||
color: #222;
|
display: -webkit-box;
|
||||||
|
display: flex;
|
||||||
|
-webkit-box-align: center;
|
||||||
|
align-items: center;
|
||||||
margin: 0 .25rem .25rem 0;
|
margin: 0 .25rem .25rem 0;
|
||||||
|
color: #222;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
max-height: 3rem;
|
max-height: 3rem;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
.title .icon[data-v-3abcf101] {
|
||||||
|
margin: 0 .25rem 0 0;
|
||||||
|
}
|
||||||
.network[data-v-3abcf101] {
|
.network[data-v-3abcf101] {
|
||||||
color: #555;
|
color: #555;
|
||||||
margin: 0 .25rem 0 0;
|
margin: 0 .25rem 0 0;
|
||||||
|
@ -208,9 +215,8 @@
|
||||||
line-height: 1.5rem;
|
line-height: 1.5rem;
|
||||||
}
|
}
|
||||||
.tags[data-v-3abcf101] {
|
.tags[data-v-3abcf101] {
|
||||||
max-height: 2.5rem;
|
max-height: .5rem;
|
||||||
padding: .25rem .5rem 1rem .5rem;
|
padding: .25rem .5rem 1rem .5rem;
|
||||||
line-height: 1.5rem;
|
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
}
|
}
|
||||||
|
@ -255,6 +261,7 @@
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
.tiles[data-v-22ffe3e4] {
|
.tiles[data-v-22ffe3e4] {
|
||||||
|
width: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(20rem, 0.33fr));
|
grid-template-columns: repeat(auto-fit, minmax(20rem, 0.33fr));
|
||||||
grid-gap: 1rem;
|
grid-gap: 1rem;
|
||||||
|
@ -270,6 +277,31 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* $primary: #ff886c; */
|
||||||
|
.banner[data-v-42bb19c4] {
|
||||||
|
background: #222;
|
||||||
|
flex-shrink: 0;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow-x: auto;
|
||||||
|
scrollbar-width: none;
|
||||||
|
box-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
|
||||||
|
font-size: 0;
|
||||||
|
}
|
||||||
|
.banner[data-v-42bb19c4]::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.trailer[data-v-42bb19c4] {
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 100vw;
|
||||||
|
}
|
||||||
|
.trailer-video[data-v-42bb19c4] {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
.item[data-v-42bb19c4] {
|
||||||
|
height: 18rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
/* $primary: #ff886c; */
|
/* $primary: #ff886c; */
|
||||||
.actor[data-v-6989dc6f] {
|
.actor[data-v-6989dc6f] {
|
||||||
width: 10rem;
|
width: 10rem;
|
||||||
|
@ -317,6 +349,8 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
-webkit-box-align: center;
|
||||||
|
align-items: center;
|
||||||
-webkit-box-pack: justify;
|
-webkit-box-pack: justify;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
@ -330,31 +364,6 @@
|
||||||
color: rgba(255, 255, 255, 0.5);
|
color: rgba(255, 255, 255, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* $primary: #ff886c; */
|
|
||||||
.banner[data-v-42bb19c4] {
|
|
||||||
background: #222;
|
|
||||||
flex-shrink: 0;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow-x: auto;
|
|
||||||
scrollbar-width: none;
|
|
||||||
box-shadow: 0 0 3px rgba(0, 0, 0, 0.5);
|
|
||||||
font-size: 0;
|
|
||||||
}
|
|
||||||
.banner[data-v-42bb19c4]::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.trailer[data-v-42bb19c4] {
|
|
||||||
display: inline-block;
|
|
||||||
max-width: 100vw;
|
|
||||||
}
|
|
||||||
.trailer-video[data-v-42bb19c4] {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
.item[data-v-42bb19c4] {
|
|
||||||
height: 18rem;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* $primary: #ff886c; */
|
/* $primary: #ff886c; */
|
||||||
.column[data-v-d4b03dc2] {
|
.column[data-v-d4b03dc2] {
|
||||||
width: 1200px;
|
width: 1200px;
|
||||||
|
@ -510,28 +519,28 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* $primary: #ff886c; */
|
/* $primary: #ff886c; */
|
||||||
.header[data-v-3e57cf44] {
|
.header[data-v-194630f6] {
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
-webkit-box-pack: justify;
|
-webkit-box-pack: justify;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
.title[data-v-3e57cf44] {
|
.title[data-v-194630f6] {
|
||||||
display: -webkit-inline-box;
|
display: -webkit-inline-box;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
-webkit-box-align: top;
|
-webkit-box-align: top;
|
||||||
align-items: top;
|
align-items: top;
|
||||||
margin: 0 1rem 0 0;
|
margin: 0 1rem 0 0;
|
||||||
}
|
}
|
||||||
.title:hover .icon[data-v-3e57cf44] {
|
.title:hover .icon[data-v-194630f6] {
|
||||||
fill: #ff6c88;
|
fill: #ff6c88;
|
||||||
}
|
}
|
||||||
.heading[data-v-3e57cf44] {
|
.heading[data-v-194630f6] {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0 0 1rem 0;
|
margin: 0 0 1rem 0;
|
||||||
}
|
}
|
||||||
.link[data-v-3e57cf44] {
|
.link[data-v-194630f6] {
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
@ -541,20 +550,20 @@
|
||||||
-webkit-box-align: end;
|
-webkit-box-align: end;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
.logo[data-v-3e57cf44] {
|
.logo[data-v-194630f6] {
|
||||||
width: 20rem;
|
width: 20rem;
|
||||||
max-height: 8rem;
|
max-height: 8rem;
|
||||||
-o-object-fit: contain;
|
-o-object-fit: contain;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
margin: 0 .5rem 1rem 0;
|
margin: 0 .5rem 1rem 0;
|
||||||
}
|
}
|
||||||
.networklogo-container[data-v-3e57cf44] {
|
.networklogo-container[data-v-194630f6] {
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
-webkit-box-align: center;
|
-webkit-box-align: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.networklogo[data-v-3e57cf44] {
|
.networklogo[data-v-194630f6] {
|
||||||
color: #222;
|
color: #222;
|
||||||
width: 15rem;
|
width: 15rem;
|
||||||
max-height: 6rem;
|
max-height: 6rem;
|
||||||
|
@ -565,13 +574,13 @@
|
||||||
object-position: 100% 0;
|
object-position: 100% 0;
|
||||||
margin: 0 0 0 .5rem;
|
margin: 0 0 0 .5rem;
|
||||||
}
|
}
|
||||||
.sites[data-v-3e57cf44],
|
.sites[data-v-194630f6],
|
||||||
.scenes[data-v-3e57cf44] {
|
.scenes[data-v-194630f6] {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: 1rem;
|
grid-gap: 1rem;
|
||||||
margin: 0 0 1rem 0;
|
margin: 0 0 1rem 0;
|
||||||
}
|
}
|
||||||
.sites[data-v-3e57cf44] {
|
.sites[data-v-194630f6] {
|
||||||
grid-template-columns: repeat(auto-fit, 15rem);
|
grid-template-columns: repeat(auto-fit, 15rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -623,44 +632,111 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* $primary: #ff886c; */
|
/* $primary: #ff886c; */
|
||||||
.header[data-v-e2e12602] {
|
.sites[data-v-7bebaa3e] {
|
||||||
display: -webkit-box;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
-webkit-box-pack: justify;
|
|
||||||
justify-content: space-between;
|
|
||||||
-webkit-box-align: top;
|
|
||||||
align-items: top;
|
|
||||||
margin: 0 0 2rem 0;
|
|
||||||
}
|
|
||||||
.title[data-v-e2e12602] {
|
|
||||||
display: -webkit-inline-box;
|
|
||||||
display: inline-flex;
|
|
||||||
-webkit-box-align: top;
|
|
||||||
align-items: top;
|
|
||||||
margin: 0 1rem 0 0;
|
|
||||||
}
|
|
||||||
.title:hover .icon[data-v-e2e12602] {
|
|
||||||
fill: #ff6c88;
|
|
||||||
}
|
|
||||||
.logo[data-v-e2e12602] {
|
|
||||||
width: 20rem;
|
|
||||||
max-height: 8rem;
|
|
||||||
-o-object-fit: contain;
|
|
||||||
object-fit: contain;
|
|
||||||
margin: 0 .5rem 0 0;
|
|
||||||
}
|
|
||||||
.sites[data-v-e2e12602] {
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-gap: 1rem;
|
grid-gap: 1rem;
|
||||||
margin: 0 0 2rem 0;
|
padding: 1rem;
|
||||||
|
margin: 0;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
.sites[data-v-e2e12602] {
|
.sites.compact[data-v-7bebaa3e] {
|
||||||
grid-template-columns: repeat(auto-fit, 15rem);
|
display: -webkit-box;
|
||||||
|
display: flex;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
.sites.compact .tile[data-v-7bebaa3e] {
|
||||||
|
width: 15rem;
|
||||||
|
margin: 0 1rem 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* $primary: #ff886c; */
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.releases .tiles {
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* $primary: #ff886c; */
|
||||||
|
.network[data-v-e2e12602] {
|
||||||
|
display: -webkit-box;
|
||||||
|
display: flex;
|
||||||
|
-webkit-box-orient: horizontal;
|
||||||
|
-webkit-box-direction: normal;
|
||||||
|
flex-direction: row;
|
||||||
|
-webkit-box-flex: 1;
|
||||||
|
flex-grow: 1;
|
||||||
|
-webkit-box-pack: stretch;
|
||||||
|
justify-content: stretch;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.sidebar[data-v-e2e12602] {
|
||||||
|
height: 100%;
|
||||||
|
width: 18rem;
|
||||||
|
display: -webkit-box;
|
||||||
|
display: flex;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-box-direction: normal;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: #fff;
|
||||||
|
border-right: solid 1px rgba(0, 0, 0, 0.1);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.sidebar .title[data-v-e2e12602] {
|
||||||
|
border-bottom: solid 1px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.logo[data-v-e2e12602] {
|
||||||
|
width: 100%;
|
||||||
|
max-height: 8rem;
|
||||||
|
display: -webkit-box;
|
||||||
|
display: flex;
|
||||||
|
-webkit-box-pack: center;
|
||||||
|
justify-content: center;
|
||||||
|
-o-object-fit: contain;
|
||||||
|
object-fit: contain;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 1rem;
|
||||||
|
margin: 0;
|
||||||
|
-webkit-filter: drop-shadow(1px 0 0 rgba(0, 0, 0, 0.2)) drop-shadow(-1px 0 0 rgba(0, 0, 0, 0.2)) drop-shadow(0 1px 0 rgba(0, 0, 0, 0.2)) drop-shadow(0 -1px 0 rgba(0, 0, 0, 0.2));
|
||||||
|
filter: drop-shadow(1px 0 0 rgba(0, 0, 0, 0.2)) drop-shadow(-1px 0 0 rgba(0, 0, 0, 0.2)) drop-shadow(0 1px 0 rgba(0, 0, 0, 0.2)) drop-shadow(0 -1px 0 rgba(0, 0, 0, 0.2));
|
||||||
|
}
|
||||||
|
.sites.compact[data-v-e2e12602] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.header[data-v-e2e12602] {
|
||||||
|
width: 100%;
|
||||||
|
height: 4rem;
|
||||||
|
display: none;
|
||||||
|
-webkit-box-pack: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-bottom: solid 1px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
.sites[data-v-e2e12602] {
|
.header[data-v-e2e12602] {
|
||||||
grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
|
display: -webkit-box;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.sites.compact[data-v-e2e12602] {
|
||||||
|
display: -webkit-box;
|
||||||
|
display: flex;
|
||||||
|
padding: 0 1rem 1rem 1rem;
|
||||||
|
}
|
||||||
|
.network[data-v-e2e12602] {
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-box-direction: normal;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.logo[data-v-e2e12602] {
|
||||||
|
max-width: 20rem;
|
||||||
|
height: 100%;
|
||||||
|
padding: .5rem;
|
||||||
|
}
|
||||||
|
.sidebar[data-v-e2e12602] {
|
||||||
|
display: none;
|
||||||
|
height: auto;
|
||||||
|
width: 100%;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 31 KiB |
After Width: | Height: | Size: 30 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 32 KiB |
After Width: | Height: | Size: 61 KiB |
After Width: | Height: | Size: 124 KiB |
After Width: | Height: | Size: 4.1 MiB |
After Width: | Height: | Size: 99 KiB |
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 845 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 110 KiB |
After Width: | Height: | Size: 107 KiB |
After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 684 KiB |
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 102 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 800 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 107 KiB |
After Width: | Height: | Size: 494 KiB |
After Width: | Height: | Size: 107 KiB |
After Width: | Height: | Size: 2.1 MiB |
After Width: | Height: | Size: 100 KiB |
After Width: | Height: | Size: 3.2 MiB |
After Width: | Height: | Size: 99 KiB |
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 391 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 115 KiB |
After Width: | Height: | Size: 142 KiB |
After Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 782 KiB |
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 93 KiB |
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 137 KiB |
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 108 KiB |
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 1.0 MiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 127 KiB |
After Width: | Height: | Size: 446 KiB |
After Width: | Height: | Size: 97 KiB |
After Width: | Height: | Size: 402 KiB |
After Width: | Height: | Size: 93 KiB |
After Width: | Height: | Size: 1.4 MiB |
After Width: | Height: | Size: 113 KiB |
After Width: | Height: | Size: 3.7 MiB |
After Width: | Height: | Size: 114 KiB |
|
@ -125,10 +125,4 @@ const networks = [
|
||||||
];
|
];
|
||||||
|
|
||||||
exports.seed = knex => Promise.resolve()
|
exports.seed = knex => Promise.resolve()
|
||||||
.then(async () => {
|
.then(async () => upsert('networks', networks, 'slug', knex));
|
||||||
// find network IDs
|
|
||||||
const duplicates = await knex('networks').select('*');
|
|
||||||
const duplicatesBySlug = duplicates.reduce((acc, network) => ({ ...acc, [network.slug]: network }), {});
|
|
||||||
|
|
||||||
return upsert('networks', networks, duplicatesBySlug, 'slug', knex);
|
|
||||||
});
|
|
||||||
|
|
|
@ -2428,15 +2428,10 @@ function getSites(networksMap) {
|
||||||
/* eslint-disable max-len */
|
/* eslint-disable max-len */
|
||||||
exports.seed = knex => Promise.resolve()
|
exports.seed = knex => Promise.resolve()
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
const [duplicates, networks] = await Promise.all([
|
const networks = await knex('networks').select('*');
|
||||||
knex('sites').select('*'),
|
|
||||||
knex('networks').select('*'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const duplicatesBySlug = duplicates.reduce((acc, site) => ({ ...acc, [site.slug]: site }), {});
|
|
||||||
const networksMap = networks.reduce((acc, { id, slug }) => ({ ...acc, [slug]: id }), {});
|
const networksMap = networks.reduce((acc, { id, slug }) => ({ ...acc, [slug]: id }), {});
|
||||||
|
|
||||||
const sites = getSites(networksMap);
|
const sites = getSites(networksMap);
|
||||||
|
|
||||||
return upsert('sites', sites, duplicatesBySlug, 'slug', knex);
|
return upsert('sites', sites, 'slug', knex);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const upsert = require('../src/utils/upsert');
|
const upsert = require('../src/utils/upsert');
|
||||||
|
|
||||||
function getStudios(networksMap) {
|
function getStudios(networksMap) {
|
||||||
|
@ -9,133 +7,133 @@ function getStudios(networksMap) {
|
||||||
slug: 'gonzocom',
|
slug: 'gonzocom',
|
||||||
name: 'Gonzo.com',
|
name: 'Gonzo.com',
|
||||||
url: 'https://www.legalporno.com/studios/gonzo_com',
|
url: 'https://www.legalporno.com/studios/gonzo_com',
|
||||||
network_id: networksMap['legalporno'],
|
network_id: networksMap.legalporno,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'giorgiograndi',
|
slug: 'giorgiograndi',
|
||||||
name: 'Giorgio Grandi',
|
name: 'Giorgio Grandi',
|
||||||
url: 'https://www.legalporno.com/studios/giorgio-grandi',
|
url: 'https://www.legalporno.com/studios/giorgio-grandi',
|
||||||
network_id: networksMap['legalporno'],
|
network_id: networksMap.legalporno,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'hardpornworld',
|
slug: 'hardpornworld',
|
||||||
name: 'Hard Porn World',
|
name: 'Hard Porn World',
|
||||||
url: 'https://www.legalporno.com/studios/hard-porn-world',
|
url: 'https://www.legalporno.com/studios/hard-porn-world',
|
||||||
network_id: networksMap['legalporno'],
|
network_id: networksMap.legalporno,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'interracialvision',
|
slug: 'interracialvision',
|
||||||
name: 'Interracial Vision',
|
name: 'Interracial Vision',
|
||||||
url: 'https://www.legalporno.com/studios/interracial-vision',
|
url: 'https://www.legalporno.com/studios/interracial-vision',
|
||||||
network_id: networksMap['legalporno'],
|
network_id: networksMap.legalporno,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'giorgioslab',
|
slug: 'giorgioslab',
|
||||||
name: 'Giorgio\'s Lab',
|
name: 'Giorgio\'s Lab',
|
||||||
url: 'https://www.legalporno.com/studios/giorgio--s-lab',
|
url: 'https://www.legalporno.com/studios/giorgio--s-lab',
|
||||||
network_id: networksMap['legalporno'],
|
network_id: networksMap.legalporno,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'americananal',
|
slug: 'americananal',
|
||||||
name: 'American Anal',
|
name: 'American Anal',
|
||||||
url: 'https://www.legalporno.com/studios/american-anal',
|
url: 'https://www.legalporno.com/studios/american-anal',
|
||||||
network_id: networksMap['legalporno'],
|
network_id: networksMap.legalporno,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'assablanca',
|
slug: 'assablanca',
|
||||||
name: 'Assablanca',
|
name: 'Assablanca',
|
||||||
url: 'https://www.legalporno.com/studios/assablanca',
|
url: 'https://www.legalporno.com/studios/assablanca',
|
||||||
network_id: networksMap['legalporno'],
|
network_id: networksMap.legalporno,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'focus',
|
slug: 'focus',
|
||||||
name: 'Focus',
|
name: 'Focus',
|
||||||
url: 'https://www.legalporno.com/studios/focus',
|
url: 'https://www.legalporno.com/studios/focus',
|
||||||
network_id: networksMap['legalporno'],
|
network_id: networksMap.legalporno,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'analforever',
|
slug: 'analforever',
|
||||||
name: 'Anal Forever',
|
name: 'Anal Forever',
|
||||||
url: 'https://www.legalporno.com/studios/anal-forever',
|
url: 'https://www.legalporno.com/studios/anal-forever',
|
||||||
network_id: networksMap['legalporno'],
|
network_id: networksMap.legalporno,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'gonzoinbrazil',
|
slug: 'gonzoinbrazil',
|
||||||
name: 'Gonzo in Brazil',
|
name: 'Gonzo in Brazil',
|
||||||
url: 'https://www.legalporno.com/studios/gonzo-in-brazil',
|
url: 'https://www.legalporno.com/studios/gonzo-in-brazil',
|
||||||
network_id: networksMap['legalporno'],
|
network_id: networksMap.legalporno,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'mranal',
|
slug: 'mranal',
|
||||||
name: 'Mr Anal',
|
name: 'Mr Anal',
|
||||||
url: 'https://www.legalporno.com/studios/mr-anal',
|
url: 'https://www.legalporno.com/studios/mr-anal',
|
||||||
network_id: networksMap['legalporno'],
|
network_id: networksMap.legalporno,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'tarrawhite',
|
slug: 'tarrawhite',
|
||||||
name: 'Tarra White',
|
name: 'Tarra White',
|
||||||
url: 'https://www.legalporno.com/studios/tarra-white',
|
url: 'https://www.legalporno.com/studios/tarra-white',
|
||||||
network_id: networksMap['legalporno'],
|
network_id: networksMap.legalporno,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'sineplexsos',
|
slug: 'sineplexsos',
|
||||||
name: 'Sineplex SOS',
|
name: 'Sineplex SOS',
|
||||||
url: 'https://www.legalporno.com/studios/sineplex-sos',
|
url: 'https://www.legalporno.com/studios/sineplex-sos',
|
||||||
network_id: networksMap['legalporno'],
|
network_id: networksMap.legalporno,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'fmodels',
|
slug: 'fmodels',
|
||||||
name: 'F Models',
|
name: 'F Models',
|
||||||
url: 'https://www.legalporno.com/studios/f-models',
|
url: 'https://www.legalporno.com/studios/f-models',
|
||||||
network_id: networksMap['legalporno'],
|
network_id: networksMap.legalporno,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'sineplexcz',
|
slug: 'sineplexcz',
|
||||||
name: 'Sineplex CZ',
|
name: 'Sineplex CZ',
|
||||||
url: 'https://www.legalporno.com/studios/sineplex-cz',
|
url: 'https://www.legalporno.com/studios/sineplex-cz',
|
||||||
network_id: networksMap['legalporno'],
|
network_id: networksMap.legalporno,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'gg',
|
slug: 'gg',
|
||||||
name: 'GG',
|
name: 'GG',
|
||||||
url: 'https://www.legalporno.com/studios/gg',
|
url: 'https://www.legalporno.com/studios/gg',
|
||||||
network_id: networksMap['legalporno'],
|
network_id: networksMap.legalporno,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'firstgape',
|
slug: 'firstgape',
|
||||||
name: 'First Gape',
|
name: 'First Gape',
|
||||||
url: 'https://www.legalporno.com/studios/first-gape',
|
url: 'https://www.legalporno.com/studios/first-gape',
|
||||||
network_id: networksMap['legalporno'],
|
network_id: networksMap.legalporno,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'omargalantiproductions',
|
slug: 'omargalantiproductions',
|
||||||
name: 'Omar Galanti Productions',
|
name: 'Omar Galanti Productions',
|
||||||
url: 'https://www.legalporno.com/studios/omar-galanti-productions',
|
url: 'https://www.legalporno.com/studios/omar-galanti-productions',
|
||||||
network_id: networksMap['legalporno'],
|
network_id: networksMap.legalporno,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'norestfortheass',
|
slug: 'norestfortheass',
|
||||||
name: 'No Rest For The Ass',
|
name: 'No Rest For The Ass',
|
||||||
url: 'https://www.legalporno.com/studios/no-rest-for-the-ass',
|
url: 'https://www.legalporno.com/studios/no-rest-for-the-ass',
|
||||||
network_id: networksMap['legalporno'],
|
network_id: networksMap.legalporno,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'hairygonzo',
|
slug: 'hairygonzo',
|
||||||
name: 'Hairy Gonzo',
|
name: 'Hairy Gonzo',
|
||||||
url: 'https://www.legalporno.com/studios/hairy-gonzo',
|
url: 'https://www.legalporno.com/studios/hairy-gonzo',
|
||||||
network_id: networksMap['legalporno'],
|
network_id: networksMap.legalporno,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'sineplexclassic',
|
slug: 'sineplexclassic',
|
||||||
name: 'Sineplex Classic',
|
name: 'Sineplex Classic',
|
||||||
url: 'https://www.legalporno.com/studios/sineplex-classic',
|
url: 'https://www.legalporno.com/studios/sineplex-classic',
|
||||||
network_id: networksMap['legalporno'],
|
network_id: networksMap.legalporno,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slug: 'sinemale',
|
slug: 'sinemale',
|
||||||
name: 'Sinemale',
|
name: 'Sinemale',
|
||||||
url: 'https://www.legalporno.com/studios/sinemale',
|
url: 'https://www.legalporno.com/studios/sinemale',
|
||||||
network_id: networksMap['legalporno'],
|
network_id: networksMap.legalporno,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -143,15 +141,10 @@ function getStudios(networksMap) {
|
||||||
/* eslint-disable max-len */
|
/* eslint-disable max-len */
|
||||||
exports.seed = knex => Promise.resolve()
|
exports.seed = knex => Promise.resolve()
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
const [duplicates, networks] = await Promise.all([
|
const networks = await knex('networks').select('*');
|
||||||
knex('studios').select('*'),
|
|
||||||
knex('networks').select('*'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const duplicatesBySlug = duplicates.reduce((acc, studio) => ({ ...acc, [studio.slug]: studio }), {});
|
|
||||||
const networksMap = networks.reduce((acc, { id, slug }) => ({ ...acc, [slug]: id }), {});
|
const networksMap = networks.reduce((acc, { id, slug }) => ({ ...acc, [slug]: id }), {});
|
||||||
|
|
||||||
const studios = getStudios(networksMap);
|
const studios = getStudios(networksMap);
|
||||||
|
|
||||||
return upsert('studios', studios, duplicatesBySlug, 'slug', knex);
|
return upsert('studios', studios, 'slug', knex);
|
||||||
});
|
});
|
||||||
|
|
|
@ -33,6 +33,10 @@ const groups = [
|
||||||
slug: 'location',
|
slug: 'location',
|
||||||
name: 'Location',
|
name: 'Location',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
slug: 'oral',
|
||||||
|
name: 'Oral',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
slug: 'orientation',
|
slug: 'orientation',
|
||||||
name: 'Orientation',
|
name: 'Orientation',
|
||||||
|
@ -69,7 +73,7 @@ function getTags(groupsMap) {
|
||||||
name: 'airtight',
|
name: 'airtight',
|
||||||
slug: 'airtight',
|
slug: 'airtight',
|
||||||
alias_for: null,
|
alias_for: null,
|
||||||
description: 'Stuffing one cock in her ass, one in her pussy, and one in her mouth, filling all of her penetrable holes and sealing her airtight like a figurative balloon. In other words, simultaneously getting [double penetrated](/tag/double-penetration), and giving a [blowjob](/tag/blowjob) or getting [facefucked](/tag/facefuck). Being airtight implies being [gangbanged](/tag/gangbang).',
|
description: 'Stuffing one cock in her ass, one in her pussy, and one in her mouth, filling all of her penetrable holes and sealing her airtight like a figurative balloon. In other words, simultaneously getting [double penetrated](/tag/double-penetration), and giving a [blowjob](/tag/blowjob) or getting [facefucked](/tag/facefuck). Being airtight implies being [gangbanged](/tag/gangbang).', /* eslint-disable-line max-len */
|
||||||
priority: 9,
|
priority: 9,
|
||||||
group_id: groupsMap.penetration,
|
group_id: groupsMap.penetration,
|
||||||
},
|
},
|
||||||
|
@ -136,16 +140,19 @@ function getTags(groupsMap) {
|
||||||
priority: 6,
|
priority: 6,
|
||||||
description: 'Sucking off a cock right after anal, giving your own or someone else`s asshole a second hand taste.',
|
description: 'Sucking off a cock right after anal, giving your own or someone else`s asshole a second hand taste.',
|
||||||
alias_for: null,
|
alias_for: null,
|
||||||
|
group_id: groupsMap.oral,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'ass eating',
|
name: 'ass eating',
|
||||||
slug: 'ass-eating',
|
slug: 'ass-eating',
|
||||||
alias_for: null,
|
alias_for: null,
|
||||||
|
group_id: groupsMap.oral,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'ball licking',
|
name: 'ball licking',
|
||||||
slug: 'ball-licking',
|
slug: 'ball-licking',
|
||||||
alias_for: null,
|
alias_for: null,
|
||||||
|
group_id: groupsMap.oral,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'ballerina',
|
name: 'ballerina',
|
||||||
|
@ -211,6 +218,7 @@ function getTags(groupsMap) {
|
||||||
slug: 'blowjob',
|
slug: 'blowjob',
|
||||||
priority: 7,
|
priority: 7,
|
||||||
alias_for: null,
|
alias_for: null,
|
||||||
|
group_id: groupsMap.oral,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'blowbang',
|
name: 'blowbang',
|
||||||
|
@ -319,6 +327,7 @@ function getTags(groupsMap) {
|
||||||
slug: 'deepthroat',
|
slug: 'deepthroat',
|
||||||
priority: 7,
|
priority: 7,
|
||||||
alias_for: null,
|
alias_for: null,
|
||||||
|
group_id: groupsMap.oral,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'double penetration',
|
name: 'double penetration',
|
||||||
|
@ -345,11 +354,13 @@ function getTags(groupsMap) {
|
||||||
name: 'double blowjob',
|
name: 'double blowjob',
|
||||||
slug: 'double-blowjob',
|
slug: 'double-blowjob',
|
||||||
alias_for: null,
|
alias_for: null,
|
||||||
|
group_id: groupsMap.oral,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'doggy style',
|
name: 'doggy style',
|
||||||
slug: 'doggy-style',
|
slug: 'doggy-style',
|
||||||
alias_for: null,
|
alias_for: null,
|
||||||
|
group_id: groupsMap.position,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'dress',
|
name: 'dress',
|
||||||
|
@ -379,7 +390,7 @@ function getTags(groupsMap) {
|
||||||
slug: 'facefuck',
|
slug: 'facefuck',
|
||||||
priority: 9,
|
priority: 9,
|
||||||
alias_for: null,
|
alias_for: null,
|
||||||
group_id: groupsMap.position,
|
group_id: groupsMap.oral,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'facesitting',
|
name: 'facesitting',
|
||||||
|
@ -429,7 +440,7 @@ function getTags(groupsMap) {
|
||||||
{
|
{
|
||||||
name: 'gangbang',
|
name: 'gangbang',
|
||||||
slug: 'gangbang',
|
slug: 'gangbang',
|
||||||
description: 'A group of three or more guys fucking a woman, at least two at the same time, often but not necessarily involving a [blowbang](/tag/blowbang), [double penetration](/tag/airtight) and [airtight](/tag/airtight). If she only gets fucked by one guy at a time, it might be considered a [trainbang](/tag/trainbang) instead. In a reverse gangbang, multiple women fuck one man.',
|
description: 'A group of three or more guys fucking a woman, at least two at the same time, often but not necessarily involving a [blowbang](/tag/blowbang), [double penetration](/tag/airtight) and [airtight](/tag/airtight). If she only gets fucked by one guy at a time, it might be considered a [trainbang](/tag/trainbang) instead. In a reverse gangbang, multiple women fuck one man.', /* eslint-disable-line max-len */
|
||||||
alias_for: null,
|
alias_for: null,
|
||||||
priority: 9,
|
priority: 9,
|
||||||
group_id: groupsMap.group,
|
group_id: groupsMap.group,
|
||||||
|
@ -645,6 +656,7 @@ function getTags(groupsMap) {
|
||||||
name: 'pussy eating',
|
name: 'pussy eating',
|
||||||
slug: 'pussy-eating',
|
slug: 'pussy-eating',
|
||||||
alias_for: null,
|
alias_for: null,
|
||||||
|
group_id: groupsMap.oral,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'redhead',
|
name: 'redhead',
|
||||||
|
@ -1542,35 +1554,20 @@ function getTagAliases(tagsMap) {
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.seed = knex => Promise.resolve()
|
exports.seed = knex => Promise.resolve()
|
||||||
|
.then(async () => upsert('tags_groups', groups, 'slug', knex))
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
const duplicates = await knex('tags_groups').select('*');
|
const groupEntries = await knex('tags_groups').select('*');
|
||||||
const duplicatesBySlug = duplicates.reduce((acc, group) => ({ ...acc, [group.slug]: group }), {});
|
|
||||||
|
|
||||||
return upsert('tags_groups', groups, duplicatesBySlug, 'slug', knex);
|
|
||||||
})
|
|
||||||
.then(async () => {
|
|
||||||
const [duplicates, groupEntries] = await Promise.all([
|
|
||||||
knex('tags').select('*'),
|
|
||||||
knex('tags_groups').select('*'),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const duplicatesBySlug = duplicates.reduce((acc, tag) => ({ ...acc, [tag.slug]: tag }), {});
|
|
||||||
const groupsMap = groupEntries.reduce((acc, { id, slug }) => ({ ...acc, [slug]: id }), {});
|
const groupsMap = groupEntries.reduce((acc, { id, slug }) => ({ ...acc, [slug]: id }), {});
|
||||||
|
|
||||||
const tags = getTags(groupsMap);
|
const tags = getTags(groupsMap);
|
||||||
|
|
||||||
return upsert('tags', tags, duplicatesBySlug, 'slug', knex);
|
return upsert('tags', tags, 'slug', knex);
|
||||||
})
|
})
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
const [duplicates, tags] = await Promise.all([
|
const tags = await knex('tags').select('*').where({ alias_for: null });
|
||||||
knex('tags').select('*').whereNotNull('alias_for'),
|
|
||||||
knex('tags').select('*').where({ alias_for: null }),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const duplicatesByName = duplicates.reduce((acc, tag) => ({ ...acc, [tag.name]: tag }), {});
|
|
||||||
const tagsMap = tags.reduce((acc, { id, slug }) => ({ ...acc, [slug]: id }), {});
|
const tagsMap = tags.reduce((acc, { id, slug }) => ({ ...acc, [slug]: id }), {});
|
||||||
|
|
||||||
const tagAliases = getTagAliases(tagsMap);
|
const tagAliases = getTagAliases(tagsMap);
|
||||||
|
|
||||||
return upsert('tags', tagAliases, duplicatesByName, 'name', knex);
|
return upsert('tags', tagAliases, 'name', knex);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,256 +1,296 @@
|
||||||
const upsert = require('../src/utils/upsert');
|
const upsert = require('../src/utils/upsert');
|
||||||
|
|
||||||
function getMedia(tagsMap) {
|
const tagPosters = [
|
||||||
return [
|
{
|
||||||
{
|
path: 'tags/airtight/poster.jpeg',
|
||||||
path: 'tags/airtight/poster.jpeg',
|
tagSlug: 'airtight',
|
||||||
target_id: tagsMap.airtight,
|
comment: 'Jynx Maze in "Pump My Ass Full of Cum 3" for Jules Jordan',
|
||||||
role: 'poster',
|
},
|
||||||
comment: 'Jynx Maze in "Pump My Ass Full of Cum 3" for Jules Jordan',
|
{
|
||||||
},
|
path: 'tags/anal/poster.jpeg',
|
||||||
{
|
tagSlug: 'anal',
|
||||||
path: 'tags/airtight/2.jpeg',
|
comment: 'Jynx Maze in "Anal Buffet 6" for Evil Angel',
|
||||||
target_id: tagsMap.airtight,
|
},
|
||||||
comment: 'Dakota Skye in "Dakota Goes Nuts" for ArchAngel',
|
{
|
||||||
},
|
path: 'tags/ass-to-mouth/poster.jpeg',
|
||||||
{
|
tagSlug: 'ass-to-mouth',
|
||||||
path: 'tags/airtight/1.jpeg',
|
comment: 'Alysa Gap and Logan in "Anal Buffet 4" for Evil Angel',
|
||||||
target_id: tagsMap.airtight,
|
},
|
||||||
comment: 'Chloe Amour in "DP Masters 4" for Jules Jordan',
|
{
|
||||||
},
|
path: 'tags/gapes/poster.jpeg',
|
||||||
{
|
tagSlug: 'gapes',
|
||||||
path: 'tags/airtight/0/poster.jpeg',
|
comment: 'Paulina in "Anal Buffet 4" for Evil Angel',
|
||||||
domain: 'tags',
|
},
|
||||||
target_id: tagsMap.airtight,
|
{
|
||||||
comment: 'Sheena Shaw in "Ass Worship 14" for Jules Jordan',
|
path: 'tags/da-tp/0.jpeg',
|
||||||
},
|
tagSlug: 'da-tp',
|
||||||
{
|
comment: 'Natasha Teen in LegalPorno SZ2164',
|
||||||
path: 'tags/anal/poster.jpeg',
|
},
|
||||||
target_id: tagsMap.anal,
|
{
|
||||||
role: 'poster',
|
path: 'tags/double-penetration/poster.jpeg',
|
||||||
comment: '',
|
tagSlug: 'double-penetration',
|
||||||
},
|
comment: 'Mia Malkova in "DP!" for HardX',
|
||||||
{
|
},
|
||||||
path: 'tags/double-penetration/poster.jpeg',
|
{
|
||||||
target_id: tagsMap['double-penetration'],
|
path: 'tags/double-anal/poster.jpeg',
|
||||||
role: 'poster',
|
tagSlug: 'double-anal',
|
||||||
comment: '',
|
comment: 'Haley Reed in "Young Hot Ass" for Evil Angel',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'tags/double-anal/poster.jpeg',
|
path: 'tags/double-vaginal/poster.jpeg',
|
||||||
target_id: tagsMap['double-anal'],
|
tagSlug: 'double-vaginal',
|
||||||
role: 'poster',
|
comment: '',
|
||||||
comment: '',
|
},
|
||||||
},
|
{
|
||||||
{
|
path: 'tags/dv-tp/poster.jpeg',
|
||||||
path: 'tags/double-vaginal/poster.jpeg',
|
tagSlug: 'dv-tp',
|
||||||
target_id: tagsMap['double-vaginal'],
|
comment: 'Juelz Ventura in "Gangbanged 5" for Elegant Angel',
|
||||||
role: 'poster',
|
},
|
||||||
comment: '',
|
{
|
||||||
},
|
path: 'tags/tattoo/poster.jpeg',
|
||||||
{
|
tagSlug: 'tattoo',
|
||||||
path: 'tags/da-tp/0.jpeg',
|
comment: 'Kali Roses in "Goes All In For Anal" for Hussie Pass',
|
||||||
target_id: tagsMap['da-tp'],
|
},
|
||||||
role: 'poster',
|
{
|
||||||
comment: 'Natasha Teen in LegalPorno SZ2164',
|
path: 'tags/triple-anal/poster.jpeg',
|
||||||
},
|
tagSlug: 'triple-anal',
|
||||||
{
|
comment: 'Kristy Black in SZ1986 for LegalPorno',
|
||||||
path: 'tags/da-tp/3.jpeg',
|
},
|
||||||
target_id: tagsMap['da-tp'],
|
{
|
||||||
role: 'photo',
|
path: 'tags/blowbang/poster.jpeg',
|
||||||
comment: 'Evelina Darling in GIO294',
|
tagSlug: 'blowbang',
|
||||||
},
|
comment: '',
|
||||||
{
|
},
|
||||||
path: 'tags/da-tp/1.jpeg',
|
{
|
||||||
target_id: tagsMap['da-tp'],
|
path: 'tags/gangbang/poster.jpeg',
|
||||||
role: 'photo',
|
tagSlug: 'gangbang',
|
||||||
comment: 'Francys Belle in SZ1702 for LegalPorno',
|
comment: 'Kristen Scott in "Interracial Gangbang!" for Jules Jordan',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'tags/da-tp/2.jpeg',
|
path: 'tags/mff/poster.jpeg',
|
||||||
target_id: tagsMap['da-tp'],
|
tagSlug: 'mff',
|
||||||
role: 'photo',
|
comment: '',
|
||||||
comment: 'Angel Smalls in GIO408 for LegalPorno',
|
},
|
||||||
},
|
{
|
||||||
{
|
path: 'tags/mfm/poster.jpeg',
|
||||||
path: 'tags/da-tp/4.jpeg',
|
tagSlug: 'mfm',
|
||||||
target_id: tagsMap['da-tp'],
|
comment: '',
|
||||||
role: 'photo',
|
},
|
||||||
comment: 'Ninel Mojado aka Mira Cuckold in GIO063 for LegalPorno',
|
{
|
||||||
},
|
path: 'tags/orgy/poster.jpeg',
|
||||||
{
|
tagSlug: 'orgy',
|
||||||
path: 'tags/dv-tp/poster.jpeg',
|
comment: '',
|
||||||
target_id: tagsMap['dv-tp'],
|
},
|
||||||
role: 'poster',
|
{
|
||||||
comment: 'Juelz Ventura in "Gangbanged 5" for Elegant Angel',
|
path: 'tags/asian/poster.jpeg',
|
||||||
},
|
tagSlug: 'asian',
|
||||||
{
|
comment: 'Vina Sky in "Young and Glamorous 10" for Jules Jordan',
|
||||||
path: 'tags/dv-tp/0.jpeg',
|
},
|
||||||
target_id: tagsMap['dv-tp'],
|
{
|
||||||
role: 'photo',
|
path: 'tags/caucasian/poster.jpeg',
|
||||||
comment: 'Luna Rival in LegalPorno SZ1490',
|
tagSlug: 'caucasian',
|
||||||
},
|
comment: '',
|
||||||
{
|
},
|
||||||
path: 'tags/tattoo/poster.jpeg',
|
{
|
||||||
target_id: tagsMap.tattoo,
|
path: 'tags/ebony/poster.jpeg',
|
||||||
role: 'poster',
|
tagSlug: 'ebony',
|
||||||
comment: 'Kali Roses in "Goes All In For Anal" for Hussie Pass',
|
comment: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'tags/triple-anal/poster.jpeg',
|
path: 'tags/latina/poster.jpeg',
|
||||||
target_id: tagsMap['triple-anal'],
|
tagSlug: 'latina',
|
||||||
role: 'poster',
|
comment: '',
|
||||||
comment: 'Kristy Black in SZ1986 for LegalPorno',
|
},
|
||||||
},
|
{
|
||||||
{
|
path: 'tags/interracial/poster.jpeg',
|
||||||
path: 'tags/triple-anal/1.jpeg',
|
tagSlug: 'interracial',
|
||||||
target_id: tagsMap['triple-anal'],
|
comment: '',
|
||||||
role: 'photo',
|
},
|
||||||
comment: 'Natasha Teen in SZ2098 for LegalPorno',
|
{
|
||||||
},
|
path: 'tags/facial/poster.jpeg',
|
||||||
{
|
tagSlug: 'facial',
|
||||||
path: 'tags/triple-anal/2.jpeg',
|
comment: '',
|
||||||
target_id: tagsMap['triple-anal'],
|
},
|
||||||
role: 'photo',
|
{
|
||||||
comment: 'Kira Thorn in GIO1018 for LegalPorno',
|
path: 'tags/trainbang/poster.jpeg',
|
||||||
},
|
tagSlug: 'trainbang',
|
||||||
{
|
comment: 'Kali Roses in "Passing Me Around" for Blacked',
|
||||||
path: 'tags/blowbang/poster.jpeg',
|
},
|
||||||
target_id: tagsMap.blowbang,
|
{
|
||||||
role: 'poster',
|
path: 'tags/bukkake/poster.jpeg',
|
||||||
comment: '',
|
tagSlug: 'bukkake',
|
||||||
},
|
comment: '',
|
||||||
{
|
},
|
||||||
path: 'tags/gangbang/poster.jpeg',
|
{
|
||||||
target_id: tagsMap.gangbang,
|
path: 'tags/swallowing/poster.jpeg',
|
||||||
role: 'poster',
|
tagSlug: 'swallowing',
|
||||||
comment: '',
|
comment: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'tags/gangbang/1.jpeg',
|
path: 'tags/creampie/poster.jpeg',
|
||||||
target_id: tagsMap.gangbang,
|
tagSlug: 'creampie',
|
||||||
role: 'photo',
|
comment: '',
|
||||||
comment: 'Ginger Lynn in "Gangbang Mystique", a photoset shot by Suze Randall for Puritan No. 10, 1984. This photo pushed the boundaries of pornography at the time, as depicting a woman \'fully occupied\' was unheard of.',
|
},
|
||||||
},
|
{
|
||||||
{
|
path: 'tags/anal-creampie/poster.jpeg',
|
||||||
path: 'tags/gangbang/2.jpeg',
|
tagSlug: 'anal-creampie',
|
||||||
target_id: tagsMap.gangbang,
|
comment: '',
|
||||||
role: 'photo',
|
},
|
||||||
comment: 'Riley Reid\'s double anal in "The Gangbang of Riley Reid" for Jules Jordan',
|
{
|
||||||
},
|
path: 'tags/oral-creampie/poster.jpeg',
|
||||||
{
|
tagSlug: 'oral-creampie',
|
||||||
path: 'tags/gangbang/3.jpeg',
|
comment: '',
|
||||||
target_id: tagsMap.gangbang,
|
},
|
||||||
role: 'photo',
|
]
|
||||||
comment: 'Kelsi Monroe in "Brazzers House 2, Day 2" for Brazzers',
|
.map((file, index) => ({
|
||||||
},
|
...file,
|
||||||
{
|
thumbnail: file.thumbnail || file.path.replace('.jpeg', '_thumb.jpeg'),
|
||||||
path: 'tags/mff/poster.jpeg',
|
mime: 'image/jpeg',
|
||||||
target_id: tagsMap.mff,
|
index,
|
||||||
role: 'poster',
|
}));
|
||||||
comment: '',
|
|
||||||
},
|
const tagPhotos = [
|
||||||
{
|
{
|
||||||
path: 'tags/mfm/poster.jpeg',
|
path: 'tags/airtight/3.jpeg',
|
||||||
target_id: tagsMap.mfm,
|
tagSlug: 'airtight',
|
||||||
role: 'poster',
|
comment: 'Anita Bellini in "Triple Dick Gangbang" for Hands On Hardcore (DDF Network)',
|
||||||
comment: '',
|
},
|
||||||
},
|
{
|
||||||
{
|
path: 'tags/airtight/2.jpeg',
|
||||||
path: 'tags/orgy/poster.jpeg',
|
tagSlug: 'airtight',
|
||||||
target_id: tagsMap.orgy,
|
comment: 'Dakota Skye in "Dakota Goes Nuts" for ArchAngel',
|
||||||
role: 'poster',
|
},
|
||||||
comment: '',
|
{
|
||||||
},
|
path: 'tags/airtight/1.jpeg',
|
||||||
{
|
tagSlug: 'airtight',
|
||||||
path: 'tags/asian/poster.jpeg',
|
comment: 'Chloe Amour in "DP Masters 4" for Jules Jordan',
|
||||||
target_id: tagsMap.asian,
|
},
|
||||||
role: 'poster',
|
{
|
||||||
comment: '',
|
path: 'tags/airtight/0.jpeg',
|
||||||
},
|
domain: 'tags',
|
||||||
{
|
tagSlug: 'airtight',
|
||||||
path: 'tags/caucasian/poster.jpeg',
|
comment: 'Sheena Shaw in "Ass Worship 14" for Jules Jordan',
|
||||||
target_id: tagsMap.caucasian,
|
},
|
||||||
role: 'poster',
|
{
|
||||||
comment: '',
|
path: 'tags/anal/0.jpeg',
|
||||||
},
|
tagSlug: 'anal',
|
||||||
{
|
comment: '',
|
||||||
path: 'tags/ebony/poster.jpeg',
|
},
|
||||||
target_id: tagsMap.ebony,
|
{
|
||||||
role: 'poster',
|
path: 'tags/double-anal/1.jpeg',
|
||||||
comment: '',
|
tagSlug: 'double-anal',
|
||||||
},
|
comment: 'Ria Sunn in SZ1801 for LegalPorno',
|
||||||
{
|
},
|
||||||
path: 'tags/latina/poster.jpeg',
|
{
|
||||||
target_id: tagsMap.latina,
|
path: 'tags/double-anal/0.jpeg',
|
||||||
role: 'poster',
|
tagSlug: 'double-anal',
|
||||||
comment: '',
|
comment: 'Nicole Black doing double anal during a gangbang in GIO971 for LegalPorno',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'tags/interracial/poster.jpeg',
|
path: 'tags/da-tp/3.jpeg',
|
||||||
target_id: tagsMap.interracial,
|
tagSlug: 'da-tp',
|
||||||
role: 'poster',
|
comment: 'Evelina Darling in GIO294',
|
||||||
comment: '',
|
},
|
||||||
},
|
{
|
||||||
{
|
path: 'tags/da-tp/1.jpeg',
|
||||||
path: 'tags/facial/poster.jpeg',
|
tagSlug: 'da-tp',
|
||||||
target_id: tagsMap.facial,
|
comment: 'Francys Belle in SZ1702 for LegalPorno',
|
||||||
role: 'poster',
|
},
|
||||||
comment: '',
|
{
|
||||||
},
|
path: 'tags/da-tp/2.jpeg',
|
||||||
{
|
tagSlug: 'da-tp',
|
||||||
path: 'tags/bukkake/poster.jpeg',
|
comment: 'Angel Smalls in GIO408 for LegalPorno',
|
||||||
target_id: tagsMap.bukkake,
|
},
|
||||||
role: 'poster',
|
{
|
||||||
comment: '',
|
path: 'tags/da-tp/4.jpeg',
|
||||||
},
|
tagSlug: 'da-tp',
|
||||||
{
|
comment: 'Ninel Mojado aka Mira Cuckold in GIO063 for LegalPorno',
|
||||||
path: 'tags/swallowing/poster.jpeg',
|
},
|
||||||
target_id: tagsMap.swallowing,
|
{
|
||||||
role: 'poster',
|
path: 'tags/dv-tp/0.jpeg',
|
||||||
comment: '',
|
tagSlug: 'dv-tp',
|
||||||
},
|
comment: 'Luna Rival in LegalPorno SZ1490',
|
||||||
{
|
},
|
||||||
path: 'tags/creampie/poster.jpeg',
|
{
|
||||||
target_id: tagsMap.creampie,
|
path: 'tags/double-penetration/0.jpeg',
|
||||||
role: 'poster',
|
tagSlug: 'double-penetration',
|
||||||
comment: '',
|
comment: '',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'tags/anal-creampie/poster.jpeg',
|
path: 'tags/gapes/0.jpeg',
|
||||||
target_id: tagsMap['anal-creampie'],
|
tagSlug: 'gapes',
|
||||||
role: 'poster',
|
comment: 'McKenzee Miles in "Anal Buffet 4" for Evil Angel',
|
||||||
comment: '',
|
},
|
||||||
},
|
{
|
||||||
{
|
path: 'tags/trainbang/0.jpeg',
|
||||||
path: 'tags/oral-creampie/poster.jpeg',
|
tagSlug: 'trainbang',
|
||||||
target_id: tagsMap['oral-creampie'],
|
comment: 'Nicole Black in GIO971 for LegalPorno',
|
||||||
role: 'poster',
|
},
|
||||||
comment: '',
|
{
|
||||||
},
|
path: 'tags/triple-anal/1.jpeg',
|
||||||
]
|
tagSlug: 'triple-anal',
|
||||||
.map((file, index) => ({
|
comment: 'Natasha Teen in SZ2098 for LegalPorno',
|
||||||
...file,
|
},
|
||||||
thumbnail: file.thumbnail || file.path.replace('.jpeg', '_thumb.jpeg'),
|
{
|
||||||
mime: 'image/jpeg',
|
path: 'tags/triple-anal/2.jpeg',
|
||||||
index,
|
tagSlug: 'triple-anal',
|
||||||
domain: file.domain || 'tags',
|
comment: 'Kira Thorn in GIO1018 for LegalPorno',
|
||||||
role: file.role || 'photo',
|
},
|
||||||
}));
|
{
|
||||||
}
|
path: 'tags/gangbang/1.jpeg',
|
||||||
|
tagSlug: 'gangbang',
|
||||||
|
comment: 'Ginger Lynn in "Gangbang Mystique", a photoset shot by Suze Randall for Puritan No. 10, 1984. This photo pushed the boundaries of pornography at the time, as depicting a woman \'fully occupied\' was unheard of.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'tags/gangbang/0.jpeg',
|
||||||
|
tagSlug: 'gangbang',
|
||||||
|
comment: '"4 On 1 Gangbangs" for Doghouse Digital',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'tags/gangbang/2.jpeg',
|
||||||
|
tagSlug: 'gangbang',
|
||||||
|
comment: 'Riley Reid\'s double anal in "The Gangbang of Riley Reid" for Jules Jordan',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'tags/gangbang/3.jpeg',
|
||||||
|
tagSlug: 'gangbang',
|
||||||
|
comment: 'Kelsi Monroe in "Brazzers House 2, Day 2" for Brazzers',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
.map((file, index) => ({
|
||||||
|
...file,
|
||||||
|
thumbnail: file.thumbnail || file.path.replace('.jpeg', '_thumb.jpeg'),
|
||||||
|
mime: 'image/jpeg',
|
||||||
|
index,
|
||||||
|
}));
|
||||||
|
|
||||||
/* eslint-disable max-len */
|
/* eslint-disable max-len */
|
||||||
exports.seed = knex => Promise.resolve()
|
exports.seed = knex => Promise.resolve()
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
const [duplicates, tags] = await Promise.all([
|
const tagMedia = tagPosters.concat(tagPhotos);
|
||||||
knex('media').where('domain', 'tags'),
|
|
||||||
knex('tags').where('alias_for', null),
|
const tags = await knex('tags').whereIn('slug', tagMedia.map(item => item.tagSlug));
|
||||||
|
const { inserted, updated } = await upsert('media', tagMedia.map(({
|
||||||
|
path, thumbnail, mime, index, comment,
|
||||||
|
}) => ({
|
||||||
|
path, thumbnail, mime, index, comment,
|
||||||
|
})), 'path', knex);
|
||||||
|
|
||||||
|
const tagIdsBySlug = tags.reduce((acc, tag) => ({ ...acc, [tag.slug]: tag.id }), {});
|
||||||
|
const mediaIdsByPath = inserted.concat(updated).reduce((acc, item) => ({ ...acc, [item.path]: item.id }), {});
|
||||||
|
|
||||||
|
const tagPosterEntries = tagPosters.map(poster => ({
|
||||||
|
tag_id: tagIdsBySlug[poster.tagSlug],
|
||||||
|
media_id: mediaIdsByPath[poster.path],
|
||||||
|
}));
|
||||||
|
|
||||||
|
const tagPhotoEntries = tagPhotos.map(photo => ({
|
||||||
|
tag_id: tagIdsBySlug[photo.tagSlug],
|
||||||
|
media_id: mediaIdsByPath[photo.path],
|
||||||
|
}));
|
||||||
|
|
||||||
|
return Promise.all([
|
||||||
|
upsert('tags_posters', tagPosterEntries, 'tag_id', knex),
|
||||||
|
upsert('tags_photos', tagPhotoEntries, 'tag_id', knex),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const duplicatesByPath = duplicates.reduce((acc, file) => ({ ...acc, [file.path]: file }), {});
|
|
||||||
const tagsMap = tags.reduce((acc, { id, slug }) => ({ ...acc, [slug]: id }), {});
|
|
||||||
|
|
||||||
const media = getMedia(tagsMap);
|
|
||||||
|
|
||||||
return upsert('media', media, duplicatesByPath, 'path', knex);
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1755,9 +1755,4 @@ const countries = [
|
||||||
];
|
];
|
||||||
|
|
||||||
exports.seed = knex => knex('countries')
|
exports.seed = knex => knex('countries')
|
||||||
.then(async () => {
|
.then(async () => upsert('countries', countries, 'alpha2', knex));
|
||||||
const duplicates = await knex('countries').select('*');
|
|
||||||
const duplicatesByAlpha2 = duplicates.reduce((acc, country) => ({ ...acc, [country.alpha2]: country }), {});
|
|
||||||
|
|
||||||
return upsert('countries', countries, duplicatesByAlpha2, 'alpha2', knex);
|
|
||||||
});
|
|
||||||
|
|
|
@ -17,8 +17,8 @@ async function curateActor(actor) {
|
||||||
knex('media')
|
knex('media')
|
||||||
.where({ domain: 'actors', target_id: actor.id })
|
.where({ domain: 'actors', target_id: actor.id })
|
||||||
.orderBy('index'),
|
.orderBy('index'),
|
||||||
knex('social')
|
knex('actors_social')
|
||||||
.where({ domain: 'actors', target_id: actor.id })
|
.where('actor_id', actor.id)
|
||||||
.orderBy('platform', 'desc'),
|
.orderBy('platform', 'desc'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
@ -197,8 +197,7 @@ function curateSocialEntry(url, actorId) {
|
||||||
return {
|
return {
|
||||||
url: match.url,
|
url: match.url,
|
||||||
platform: match.platform,
|
platform: match.platform,
|
||||||
domain: 'actors',
|
actor_id: actorId,
|
||||||
target_id: actorId,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,10 +206,7 @@ async function curateSocialEntries(urls, actorId) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingSocialLinks = await knex('social').where({
|
const existingSocialLinks = await knex('actors_social').where('actor_id', actorId);
|
||||||
domain: 'actors',
|
|
||||||
target_id: actorId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return urls.reduce((acc, url) => {
|
return urls.reduce((acc, url) => {
|
||||||
const socialEntry = curateSocialEntry(url, actorId);
|
const socialEntry = curateSocialEntry(url, actorId);
|
||||||
|
@ -243,7 +239,7 @@ async function fetchActors(queryObject, limit = 100) {
|
||||||
async function storeSocialLinks(urls, actorId) {
|
async function storeSocialLinks(urls, actorId) {
|
||||||
const curatedSocialEntries = await curateSocialEntries(urls, actorId);
|
const curatedSocialEntries = await curateSocialEntries(urls, actorId);
|
||||||
|
|
||||||
await knex('social').insert(curatedSocialEntries);
|
await knex('actors_social').insert(curatedSocialEntries);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function storeActor(actor, scraped = false, scrapeSuccess = false) {
|
async function storeActor(actor, scraped = false, scrapeSuccess = false) {
|
||||||
|
@ -358,7 +354,7 @@ async function scrapeActors(actorNames) {
|
||||||
updateActor(profile, true, true),
|
updateActor(profile, true, true),
|
||||||
// storeAvatars(profile, actorEntry),
|
// storeAvatars(profile, actorEntry),
|
||||||
storePhotos(profile.avatars, {
|
storePhotos(profile.avatars, {
|
||||||
domain: 'actors',
|
domain: 'actor',
|
||||||
role: 'photo',
|
role: 'photo',
|
||||||
primaryRole: 'avatar',
|
primaryRole: 'avatar',
|
||||||
targetId: actorEntry.id,
|
targetId: actorEntry.id,
|
||||||
|
@ -374,7 +370,7 @@ async function scrapeActors(actorNames) {
|
||||||
|
|
||||||
await createMediaDirectory('actors', `${newActorEntry.slug}/`);
|
await createMediaDirectory('actors', `${newActorEntry.slug}/`);
|
||||||
await storePhotos(profile.avatars, {
|
await storePhotos(profile.avatars, {
|
||||||
domain: 'actors',
|
domain: 'actor',
|
||||||
role: 'photo',
|
role: 'photo',
|
||||||
primaryRole: 'avatar',
|
primaryRole: 'avatar',
|
||||||
targetId: newActorEntry.id,
|
targetId: newActorEntry.id,
|
||||||
|
@ -399,7 +395,7 @@ async function scrapeBasicActors() {
|
||||||
async function associateActors(mappedActors, releases) {
|
async function associateActors(mappedActors, releases) {
|
||||||
const [existingActorEntries, existingAssociationEntries] = await Promise.all([
|
const [existingActorEntries, existingAssociationEntries] = await Promise.all([
|
||||||
knex('actors').whereIn('name', Object.keys(mappedActors)),
|
knex('actors').whereIn('name', Object.keys(mappedActors)),
|
||||||
knex('actors_associated').whereIn('release_id', releases.map(release => release.id)),
|
knex('releases_actors').whereIn('release_id', releases.map(release => release.id)),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const associations = await Promise.map(Object.entries(mappedActors), async ([actorName, releaseIds]) => {
|
const associations = await Promise.map(Object.entries(mappedActors), async ([actorName, releaseIds]) => {
|
||||||
|
@ -418,7 +414,7 @@ async function associateActors(mappedActors, releases) {
|
||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
knex('actors_associated').insert(associations.flat()),
|
knex('releases_actors').insert(associations.flat()),
|
||||||
scrapeBasicActors(),
|
scrapeBasicActors(),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
12
src/app.js
|
@ -1,26 +1,20 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const Promise = require('bluebird');
|
|
||||||
|
|
||||||
const argv = require('./argv');
|
const argv = require('./argv');
|
||||||
const knex = require('./knex');
|
const knex = require('./knex');
|
||||||
const initServer = require('./web/server');
|
const initServer = require('./web/server');
|
||||||
|
|
||||||
const scrapeSites = require('./scrape-sites');
|
const scrapeSites = require('./scrape-sites');
|
||||||
const scrapeRelease = require('./scrape-release');
|
const { scrapeReleases } = require('./scrape-releases');
|
||||||
const { scrapeActors, scrapeBasicActors } = require('./actors');
|
const { scrapeActors, scrapeBasicActors } = require('./actors');
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
if (argv.scene) {
|
if (argv.scene) {
|
||||||
await Promise.map(argv.scene, async url => scrapeRelease(url, null, false, false), {
|
await scrapeReleases(argv.scene, null, 'scene');
|
||||||
concurrency: 5,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argv.movie) {
|
if (argv.movie) {
|
||||||
await Promise.map(argv.movie, async url => scrapeRelease(url, null, false, true), {
|
await scrapeReleases(argv.movie, null, 'movie');
|
||||||
concurrency: 5,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argv.scrape || argv.networks || argv.sites) {
|
if (argv.scrape || argv.networks || argv.sites) {
|
||||||
|
|
196
src/media.js
|
@ -10,6 +10,7 @@ const sharp = require('sharp');
|
||||||
const blake2 = require('blake2');
|
const blake2 = require('blake2');
|
||||||
|
|
||||||
const knex = require('./knex');
|
const knex = require('./knex');
|
||||||
|
const upsert = require('./utils/upsert');
|
||||||
|
|
||||||
function getHash(buffer) {
|
function getHash(buffer) {
|
||||||
const hash = blake2.createHash('blake2b', { digestLength: 24 });
|
const hash = blake2.createHash('blake2b', { digestLength: 24 });
|
||||||
|
@ -40,6 +41,9 @@ async function createThumbnail(buffer) {
|
||||||
height: config.media.thumbnailSize,
|
height: config.media.thumbnailSize,
|
||||||
withoutEnlargement: true,
|
withoutEnlargement: true,
|
||||||
})
|
})
|
||||||
|
.jpeg({
|
||||||
|
quality: 75,
|
||||||
|
})
|
||||||
.toBuffer();
|
.toBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +54,7 @@ async function createMediaDirectory(domain, subpath) {
|
||||||
return filepath;
|
return filepath;
|
||||||
}
|
}
|
||||||
|
|
||||||
function curatePhotoEntries(files, domain = 'releases', role = 'photo', targetId) {
|
function curatePhotoEntries(files) {
|
||||||
return files.map((file, index) => ({
|
return files.map((file, index) => ({
|
||||||
path: file.filepath,
|
path: file.filepath,
|
||||||
thumbnail: file.thumbpath,
|
thumbnail: file.thumbpath,
|
||||||
|
@ -58,51 +62,33 @@ function curatePhotoEntries(files, domain = 'releases', role = 'photo', targetId
|
||||||
hash: file.hash,
|
hash: file.hash,
|
||||||
source: file.source,
|
source: file.source,
|
||||||
index,
|
index,
|
||||||
domain,
|
|
||||||
target_id: targetId,
|
|
||||||
role: file.role || role,
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// before fetching
|
async function findDuplicates(photos, identifier, prop = null, label) {
|
||||||
async function filterSourceDuplicates(photos, domains = ['releases'], roles = ['photo'], identifier) {
|
const duplicates = await knex('media')
|
||||||
const photoSourceEntries = await knex('media')
|
.whereIn(identifier, photos.flat().map(photo => (prop ? photo[prop] : photo)));
|
||||||
.whereIn('source', photos.flat())
|
|
||||||
.whereIn('domain', domains)
|
|
||||||
.whereIn('role', roles); // accept string argument
|
|
||||||
|
|
||||||
const photoSources = new Set(photoSourceEntries.map(photo => photo.source));
|
const duplicateLookup = new Set(duplicates.map(photo => photo[prop || identifier]));
|
||||||
const newPhotos = photos.filter(source => (Array.isArray(source) // fallbacks provided?
|
const originals = photos.filter(source => (Array.isArray(source) // fallbacks provided?
|
||||||
? !source.some(sourceX => photoSources.has(sourceX)) // ensure none of the sources match
|
? !source.some(sourceX => duplicateLookup.has(prop ? sourceX[prop] : sourceX)) // ensure none of the sources match
|
||||||
: !photoSources.has(source)));
|
: !duplicateLookup.has(prop ? source[prop] : source)));
|
||||||
|
|
||||||
if (photoSourceEntries.length > 0) {
|
if (duplicates.length > 0) {
|
||||||
console.log(`Ignoring ${photoSourceEntries.length} ${roles} items already present by source for ${identifier}`);
|
console.log(`${duplicates.length} media items already present by ${identifier} for ${label}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return newPhotos;
|
if (originals.length > 0) {
|
||||||
}
|
console.log(`Fetching ${originals.length} new media items for ${label}`);
|
||||||
|
|
||||||
// after fetching
|
|
||||||
async function filterHashDuplicates(files, domains = ['releases'], roles = ['photo'], identifier) {
|
|
||||||
const photoHashEntries = await knex('media')
|
|
||||||
.whereIn('hash', files.map(file => file.hash))
|
|
||||||
.whereIn('domain', [].concat(domains))
|
|
||||||
.whereIn('role', [].concat(roles)); // accept string argument
|
|
||||||
|
|
||||||
const photoHashes = new Set(photoHashEntries.map(entry => entry.hash));
|
|
||||||
|
|
||||||
if (photoHashEntries.length > 0) {
|
|
||||||
console.log(`Ignoring ${photoHashEntries.length} ${roles} items already present by hash for ${identifier}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return files.filter(file => file && !photoHashes.has(file.hash));
|
return [duplicates, originals];
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchPhoto(photoUrl, index, identifier, attempt = 1) {
|
async function fetchPhoto(photoUrl, index, label, attempt = 1) {
|
||||||
if (Array.isArray(photoUrl)) {
|
if (Array.isArray(photoUrl)) {
|
||||||
return photoUrl.reduce(async (outcome, url) => outcome.catch(async () => {
|
return photoUrl.reduce(async (outcome, url) => outcome.catch(async () => {
|
||||||
const photo = await fetchPhoto(url, index, identifier);
|
const photo = await fetchPhoto(url, index, label);
|
||||||
|
|
||||||
if (photo) {
|
if (photo) {
|
||||||
return photo;
|
return photo;
|
||||||
|
@ -133,11 +119,11 @@ async function fetchPhoto(photoUrl, index, identifier, attempt = 1) {
|
||||||
|
|
||||||
throw new Error(`Response ${res.statusCode} not OK`);
|
throw new Error(`Response ${res.statusCode} not OK`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(`Failed attempt ${attempt}/3 to fetch photo ${index + 1} for ${identifier} (${photoUrl}): ${error}`);
|
console.warn(`Failed attempt ${attempt}/3 to fetch photo ${index + 1} for ${label} (${photoUrl}): ${error}`);
|
||||||
|
|
||||||
if (attempt < 3) {
|
if (attempt < 3) {
|
||||||
await Promise.delay(1000);
|
await Promise.delay(1000);
|
||||||
return fetchPhoto(photoUrl, index, identifier, attempt + 1);
|
return fetchPhoto(photoUrl, index, label, attempt + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -145,7 +131,7 @@ async function fetchPhoto(photoUrl, index, identifier, attempt = 1) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function savePhotos(files, {
|
async function savePhotos(files, {
|
||||||
domain = 'releases',
|
domain = 'release',
|
||||||
subpath,
|
subpath,
|
||||||
role = 'photo',
|
role = 'photo',
|
||||||
naming = 'index',
|
naming = 'index',
|
||||||
|
@ -155,11 +141,11 @@ async function savePhotos(files, {
|
||||||
const thumbnail = await createThumbnail(file.photo);
|
const thumbnail = await createThumbnail(file.photo);
|
||||||
|
|
||||||
const filename = naming === 'index'
|
const filename = naming === 'index'
|
||||||
? `${file.role || role}-${index + 1}`
|
? `${file.role || role}${index + 1}`
|
||||||
: `${timestamp + index}`;
|
: `${timestamp + index}`;
|
||||||
|
|
||||||
const filepath = path.join(domain, subpath, `${filename}.${file.extension}`);
|
const filepath = path.join(`${domain}s`, subpath, `${filename}.${file.extension}`);
|
||||||
const thumbpath = path.join(domain, subpath, `${filename}_thumb.${file.extension}`);
|
const thumbpath = path.join(`${domain}s`, subpath, `${filename}_thumb.${file.extension}`);
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
fs.writeFile(path.join(config.media.path, filepath), file.photo),
|
fs.writeFile(path.join(config.media.path, filepath), file.photo),
|
||||||
|
@ -176,49 +162,28 @@ async function savePhotos(files, {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function storePhotos(photos, {
|
async function storePhotos(photos, {
|
||||||
domain = 'releases',
|
domain = 'release',
|
||||||
role = 'photo',
|
role = 'photo',
|
||||||
naming = 'index',
|
naming = 'index',
|
||||||
targetId,
|
targetId,
|
||||||
subpath,
|
subpath,
|
||||||
primaryRole, // role to assign to first photo if not already in database, used mainly for avatars
|
primaryRole, // role to assign to first photo if not already in database, used mainly for avatars
|
||||||
}, identifier) {
|
}, label) {
|
||||||
if (!photos || photos.length === 0) {
|
if (!photos || photos.length === 0) {
|
||||||
console.warn(`No ${role}s available for ${identifier}`);
|
console.warn(`No ${role}s available for ${label}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pluckedPhotos = pluckPhotos(photos);
|
const pluckedPhotos = pluckPhotos(photos);
|
||||||
const roles = primaryRole ? [role, primaryRole] : [role];
|
const [sourceDuplicates, sourceOriginals] = await findDuplicates(pluckedPhotos, 'source', null, label);
|
||||||
|
|
||||||
const newPhotos = await filterSourceDuplicates(pluckedPhotos, [domain], roles, identifier);
|
const metaFiles = await Promise.map(sourceOriginals, async (photoUrl, index) => fetchPhoto(photoUrl, index, label), {
|
||||||
|
|
||||||
if (newPhotos.length === 0) return;
|
|
||||||
|
|
||||||
console.log(`Fetching ${newPhotos.length} ${role}s for ${identifier}`);
|
|
||||||
|
|
||||||
const metaFiles = await Promise.map(newPhotos, async (photoUrl, index) => fetchPhoto(photoUrl, index, identifier), {
|
|
||||||
concurrency: 10,
|
concurrency: 10,
|
||||||
}).filter(photo => photo);
|
}).filter(photo => photo);
|
||||||
|
|
||||||
const [uniquePhotos, primaryPhoto] = await Promise.all([
|
const [hashDuplicates, hashOriginals] = await findDuplicates(metaFiles, 'hash', 'hash', label);
|
||||||
filterHashDuplicates(metaFiles, [domain], roles, identifier),
|
|
||||||
primaryRole
|
|
||||||
? await knex('media')
|
|
||||||
.where('domain', domain)
|
|
||||||
.where('target_id', targetId)
|
|
||||||
.where('role', primaryRole)
|
|
||||||
.first()
|
|
||||||
: null,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if (primaryRole && !primaryPhoto) {
|
const savedPhotos = await savePhotos(hashOriginals, {
|
||||||
console.log(`Setting first photo as ${primaryRole} for ${identifier}`);
|
|
||||||
|
|
||||||
uniquePhotos[0].role = primaryRole;
|
|
||||||
}
|
|
||||||
|
|
||||||
const savedPhotos = await savePhotos(uniquePhotos, {
|
|
||||||
domain,
|
domain,
|
||||||
role,
|
role,
|
||||||
targetId,
|
targetId,
|
||||||
|
@ -228,59 +193,102 @@ async function storePhotos(photos, {
|
||||||
|
|
||||||
const curatedPhotoEntries = curatePhotoEntries(savedPhotos, domain, role, targetId);
|
const curatedPhotoEntries = curatePhotoEntries(savedPhotos, domain, role, targetId);
|
||||||
|
|
||||||
await knex('media').insert(curatedPhotoEntries);
|
const newPhotos = await knex('media').insert(curatedPhotoEntries).returning('*');
|
||||||
|
const photoEntries = Array.isArray(newPhotos)
|
||||||
|
? [...sourceDuplicates, ...hashDuplicates, ...newPhotos]
|
||||||
|
: [...sourceDuplicates, ...hashDuplicates];
|
||||||
|
|
||||||
console.log(`Stored ${newPhotos.length} ${role}s for ${identifier}`);
|
const photoAssociations = photoEntries
|
||||||
|
.map(photoEntry => ({
|
||||||
|
[`${domain}_id`]: targetId,
|
||||||
|
media_id: photoEntry.id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (primaryRole) {
|
||||||
|
// store one photo as a 'primary' photo, such as an avatar or cover
|
||||||
|
const primaryPhoto = await knex(`${domain}s_${primaryRole}s`)
|
||||||
|
.where(`${domain}_id`, targetId)
|
||||||
|
.first();
|
||||||
|
|
||||||
|
if (primaryPhoto) {
|
||||||
|
const remainingAssociations = photoAssociations.filter(association => association.media_id === primaryPhoto.media_id);
|
||||||
|
|
||||||
|
await upsert(`${domain}s_${role}s`, remainingAssociations, [`${domain}_id`, 'media_id']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
upsert(`${domain}s_${primaryRole}s`, photoAssociations.slice(0, 1), [`${domain}_id`, 'media_id']),
|
||||||
|
upsert(`${domain}s_${role}s`, photoAssociations.slice(1), [`${domain}_id`, 'media_id']),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await upsert(`${domain}s_${role}s`, photoAssociations, [`${domain}_id`, 'media_id']);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function storeTrailer(trailers, {
|
async function storeTrailer(trailers, {
|
||||||
domain = 'releases',
|
domain = 'releases',
|
||||||
role = 'trailer',
|
|
||||||
targetId,
|
targetId,
|
||||||
subpath,
|
subpath,
|
||||||
}, identifier) {
|
}, label) {
|
||||||
// support scrapers supplying multiple qualities
|
// support scrapers supplying multiple qualities
|
||||||
const trailer = Array.isArray(trailers)
|
const trailer = Array.isArray(trailers)
|
||||||
? trailers.find(trailerX => [1080, 720].includes(trailerX.quality)) || trailers[0]
|
? trailers.find(trailerX => [1080, 720].includes(trailerX.quality)) || trailers[0]
|
||||||
: trailers;
|
: trailers;
|
||||||
|
|
||||||
if (!trailer || !trailer.src) {
|
if (!trailer || !trailer.src) {
|
||||||
console.warn(`No trailer available for ${identifier}`);
|
console.warn(`No trailer available for ${label}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Storing trailer for ${identifier}`);
|
const [sourceDuplicates, sourceOriginals] = await findDuplicates([trailer], 'source', 'src', label);
|
||||||
|
|
||||||
const { pathname } = new URL(trailer.src);
|
const metaFiles = await Promise.map(sourceOriginals, async (trailerX) => {
|
||||||
const mimetype = trailer.type || mime.getType(pathname);
|
const { pathname } = new URL(trailerX.src);
|
||||||
|
const mimetype = trailerX.type || mime.getType(pathname);
|
||||||
|
|
||||||
const res = await bhttp.get(trailer.src);
|
const res = await bhttp.get(trailerX.src);
|
||||||
const filepath = path.join('releases', subpath, `trailer${trailer.quality ? `_${trailer.quality}` : ''}.${mime.getExtension(mimetype)}`);
|
const hash = getHash(res.body);
|
||||||
|
const filepath = path.join(domain, subpath, `trailer${trailerX.quality ? `_${trailerX.quality}` : ''}.${mime.getExtension(mimetype)}`);
|
||||||
|
|
||||||
await Promise.all([
|
return {
|
||||||
fs.writeFile(path.join(config.media.path, filepath), res.body),
|
trailer: res.body,
|
||||||
knex('media').insert({
|
|
||||||
path: filepath,
|
path: filepath,
|
||||||
mime: mimetype,
|
mime: mimetype,
|
||||||
source: trailer.src,
|
source: trailerX.src,
|
||||||
domain,
|
quality: trailerX.quality || null,
|
||||||
target_id: targetId,
|
hash,
|
||||||
role,
|
};
|
||||||
quality: trailer.quality || null,
|
});
|
||||||
}),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function findAvatar(actorId, domain = 'actors') {
|
const [hashDuplicates, hashOriginals] = await findDuplicates(metaFiles, 'hash', 'hash', label);
|
||||||
return knex('media')
|
|
||||||
.where('domain', domain)
|
const newTrailers = await knex('media')
|
||||||
.where('target_id', actorId)
|
.insert(hashOriginals.map(trailerX => ({
|
||||||
.where('role', 'avatar');
|
path: trailerX.path,
|
||||||
|
mime: trailerX.mime,
|
||||||
|
source: trailerX.source,
|
||||||
|
quality: trailerX.quality,
|
||||||
|
hash: trailerX.hash,
|
||||||
|
})))
|
||||||
|
.returning('*');
|
||||||
|
|
||||||
|
await Promise.all(hashOriginals.map(trailerX => fs.writeFile(path.join(config.media.path, trailerX.path), trailerX.trailer)));
|
||||||
|
|
||||||
|
const trailerEntries = Array.isArray(newTrailers)
|
||||||
|
? [...sourceDuplicates, ...hashDuplicates, ...newTrailers]
|
||||||
|
: [...sourceDuplicates, ...hashDuplicates];
|
||||||
|
|
||||||
|
await upsert('releases_trailers', trailerEntries.map(trailerEntry => ({
|
||||||
|
release_id: targetId,
|
||||||
|
media_id: trailerEntry.id,
|
||||||
|
})), ['release_id', 'media_id']);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
createMediaDirectory,
|
createMediaDirectory,
|
||||||
findAvatar,
|
|
||||||
storePhotos,
|
storePhotos,
|
||||||
storeTrailer,
|
storeTrailer,
|
||||||
};
|
};
|
||||||
|
|
195
src/releases.js
|
@ -15,6 +15,40 @@ const {
|
||||||
} = require('./media');
|
} = require('./media');
|
||||||
const { fetchSites, findSiteByUrl } = require('./sites');
|
const { fetchSites, findSiteByUrl } = require('./sites');
|
||||||
|
|
||||||
|
function commonQuery(queryBuilder, {
|
||||||
|
filter = [],
|
||||||
|
after = new Date(0), // January 1970
|
||||||
|
before = new Date(2 ** 44), // May 2109
|
||||||
|
limit = 100,
|
||||||
|
}) {
|
||||||
|
const finalFilter = [].concat(filter); // ensure filter is array
|
||||||
|
|
||||||
|
queryBuilder
|
||||||
|
.leftJoin('sites', 'releases.site_id', 'sites.id')
|
||||||
|
.leftJoin('studios', 'releases.studio_id', 'studios.id')
|
||||||
|
.leftJoin('networks', 'sites.network_id', 'networks.id')
|
||||||
|
.select(
|
||||||
|
'releases.*',
|
||||||
|
'sites.name as site_name', 'sites.slug as site_slug', 'sites.url as site_url', 'sites.network_id', 'sites.parameters as site_parameters',
|
||||||
|
'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', 'networks.description as network_description',
|
||||||
|
)
|
||||||
|
.whereNotExists((builder) => {
|
||||||
|
// apply tag filters
|
||||||
|
builder
|
||||||
|
.select('*')
|
||||||
|
.from('tags_associated')
|
||||||
|
.leftJoin('tags', 'tags_associated.tag_id', 'tags.id')
|
||||||
|
.whereIn('tags.slug', finalFilter)
|
||||||
|
.where('tags_associated.domain', 'releases')
|
||||||
|
.whereRaw('tags_associated.target_id = releases.id');
|
||||||
|
})
|
||||||
|
.andWhere('releases.date', '>', after)
|
||||||
|
.andWhere('releases.date', '<=', before)
|
||||||
|
.orderBy([{ column: 'date', order: 'desc' }, { column: 'created_at', order: 'desc' }])
|
||||||
|
.limit(limit);
|
||||||
|
}
|
||||||
|
|
||||||
async function curateRelease(release) {
|
async function curateRelease(release) {
|
||||||
const [actors, tags, media] = await Promise.all([
|
const [actors, tags, media] = await Promise.all([
|
||||||
knex('actors_associated')
|
knex('actors_associated')
|
||||||
|
@ -49,8 +83,9 @@ async function curateRelease(release) {
|
||||||
.orderBy(['role', 'index']),
|
.orderBy(['role', 'index']),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return {
|
const curatedRelease = {
|
||||||
id: release.id,
|
id: release.id,
|
||||||
|
type: release.type,
|
||||||
title: release.title,
|
title: release.title,
|
||||||
date: release.date,
|
date: release.date,
|
||||||
dateAdded: release.created_at,
|
dateAdded: release.created_at,
|
||||||
|
@ -108,33 +143,51 @@ async function curateRelease(release) {
|
||||||
url: release.network_url,
|
url: release.network_url,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return curatedRelease;
|
||||||
}
|
}
|
||||||
|
|
||||||
function curateReleases(releases) {
|
function curateReleases(releases) {
|
||||||
return Promise.all(releases.map(async release => curateRelease(release)));
|
return Promise.all(releases.map(async release => curateRelease(release)));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getChannelSite(release) {
|
async function attachChannelSite(release) {
|
||||||
try {
|
if (!release.site.isFallback) {
|
||||||
const site = await findSiteByUrl(release.channel);
|
return release;
|
||||||
|
|
||||||
return site || null;
|
|
||||||
} catch (error) {
|
|
||||||
const [site] = await fetchSites({
|
|
||||||
name: release.channel,
|
|
||||||
slug: release.channel,
|
|
||||||
});
|
|
||||||
|
|
||||||
return site || null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!release.channel) {
|
||||||
|
throw new Error(`Unable to derive channel site from generic URL: ${release.url}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [site] = await fetchSites({
|
||||||
|
name: release.channel,
|
||||||
|
slug: release.channel,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (site) {
|
||||||
|
return {
|
||||||
|
...release,
|
||||||
|
site,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const urlSite = await findSiteByUrl(release.channel);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...release,
|
||||||
|
site: urlSite,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function curateScrapedRelease(release) {
|
async function curateReleaseEntry(release) {
|
||||||
const curatedRelease = {
|
const curatedRelease = {
|
||||||
site_id: release.site.id,
|
site_id: release.site.id,
|
||||||
studio_id: release.studio ? release.studio.id : null,
|
studio_id: release.studio ? release.studio.id : null,
|
||||||
shoot_id: release.shootId || null,
|
shoot_id: release.shootId || null,
|
||||||
entry_id: release.entryId || null,
|
entry_id: release.entryId || null,
|
||||||
|
parent_id: release.parentId,
|
||||||
|
type: release.type,
|
||||||
url: release.url,
|
url: release.url,
|
||||||
title: release.title,
|
title: release.title,
|
||||||
date: release.date,
|
date: release.date,
|
||||||
|
@ -147,52 +200,9 @@ async function curateScrapedRelease(release) {
|
||||||
deep: typeof release.deep === 'boolean' ? release.deep : false,
|
deep: typeof release.deep === 'boolean' ? release.deep : false,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (release.site.isFallback && release.channel) {
|
|
||||||
const site = await getChannelSite(release);
|
|
||||||
|
|
||||||
if (site) {
|
|
||||||
curatedRelease.site_id = site.id;
|
|
||||||
return curatedRelease;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return curatedRelease;
|
return curatedRelease;
|
||||||
}
|
}
|
||||||
|
|
||||||
function commonQuery(queryBuilder, {
|
|
||||||
filter = [],
|
|
||||||
after = new Date(0), // January 1970
|
|
||||||
before = new Date(2 ** 44), // May 2109
|
|
||||||
limit = 100,
|
|
||||||
}) {
|
|
||||||
const finalFilter = [].concat(filter); // ensure filter is array
|
|
||||||
|
|
||||||
queryBuilder
|
|
||||||
.leftJoin('sites', 'releases.site_id', 'sites.id')
|
|
||||||
.leftJoin('studios', 'releases.studio_id', 'studios.id')
|
|
||||||
.leftJoin('networks', 'sites.network_id', 'networks.id')
|
|
||||||
.select(
|
|
||||||
'releases.*',
|
|
||||||
'sites.name as site_name', 'sites.slug as site_slug', 'sites.url as site_url', 'sites.network_id', 'sites.parameters as site_parameters',
|
|
||||||
'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', 'networks.description as network_description',
|
|
||||||
)
|
|
||||||
.whereNotExists((builder) => {
|
|
||||||
// apply tag filters
|
|
||||||
builder
|
|
||||||
.select('*')
|
|
||||||
.from('tags_associated')
|
|
||||||
.leftJoin('tags', 'tags_associated.tag_id', 'tags.id')
|
|
||||||
.whereIn('tags.slug', finalFilter)
|
|
||||||
.where('tags_associated.domain', 'releases')
|
|
||||||
.whereRaw('tags_associated.target_id = releases.id');
|
|
||||||
})
|
|
||||||
.andWhere('date', '>', after)
|
|
||||||
.andWhere('date', '<=', before)
|
|
||||||
.orderBy([{ column: 'date', order: 'desc' }, { column: 'created_at', order: 'desc' }])
|
|
||||||
.limit(limit);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchReleases(queryObject = {}, options = {}) {
|
async function fetchReleases(queryObject = {}, options = {}) {
|
||||||
const releases = await knex('releases')
|
const releases = await knex('releases')
|
||||||
.modify(commonQuery, options)
|
.modify(commonQuery, options)
|
||||||
|
@ -244,6 +254,40 @@ async function fetchTagReleases(queryObject, options = {}) {
|
||||||
return curateReleases(releases);
|
return curateReleases(releases);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function accumulateActors(releases) {
|
||||||
|
return releases.reduce((acc, release) => {
|
||||||
|
if (!release.actors) return acc;
|
||||||
|
|
||||||
|
release.actors.forEach((actor) => {
|
||||||
|
const trimmedActor = actor.trim();
|
||||||
|
|
||||||
|
if (acc[trimmedActor]) {
|
||||||
|
acc[trimmedActor] = acc[trimmedActor].concat(release.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
acc[trimmedActor] = [release.id];
|
||||||
|
});
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
function accumulateMovies(releases) {
|
||||||
|
return releases.reduce((acc, release) => {
|
||||||
|
if (release.movie) {
|
||||||
|
if (acc[release.movie]) {
|
||||||
|
acc[release.movie] = acc[release.movie].concat(release.id);
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
acc[release.movie] = [release.id];
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
async function storeReleaseAssets(release, releaseId) {
|
async function storeReleaseAssets(release, releaseId) {
|
||||||
const subpath = `${release.site.network.slug}/${release.site.slug}/${release.id}/`;
|
const subpath = `${release.site.network.slug}/${release.site.slug}/${release.id}/`;
|
||||||
const identifier = `"${release.title}" (${releaseId})`;
|
const identifier = `"${release.title}" (${releaseId})`;
|
||||||
|
@ -279,7 +323,7 @@ async function storeReleaseAssets(release, releaseId) {
|
||||||
|
|
||||||
async function storeRelease(release) {
|
async function storeRelease(release) {
|
||||||
const existingRelease = await knex('releases').where('entry_id', release.entryId).first();
|
const existingRelease = await knex('releases').where('entry_id', release.entryId).first();
|
||||||
const curatedRelease = await curateScrapedRelease(release);
|
const curatedRelease = await curateReleaseEntry(release);
|
||||||
|
|
||||||
if (existingRelease && !argv.redownload) {
|
if (existingRelease && !argv.redownload) {
|
||||||
return existingRelease.id;
|
return existingRelease.id;
|
||||||
|
@ -317,18 +361,13 @@ async function storeRelease(release) {
|
||||||
|
|
||||||
async function storeReleases(releases) {
|
async function storeReleases(releases) {
|
||||||
const storedReleases = await Promise.map(releases, async (release) => {
|
const storedReleases = await Promise.map(releases, async (release) => {
|
||||||
if (release.site.isFallback && !release.channel) {
|
|
||||||
console.error(`Unable to derive channel site from generic URL: ${release.url}.`);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const releaseId = await storeRelease(release);
|
const releaseWithChannelSite = await attachChannelSite(release);
|
||||||
|
const releaseId = await storeRelease(releaseWithChannelSite);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: releaseId,
|
id: releaseId,
|
||||||
...release,
|
...releaseWithChannelSite,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
@ -339,22 +378,8 @@ async function storeReleases(releases) {
|
||||||
concurrency: 10,
|
concurrency: 10,
|
||||||
}).filter(release => release);
|
}).filter(release => release);
|
||||||
|
|
||||||
const actors = storedReleases.reduce((acc, release) => {
|
const actors = accumulateActors(storedReleases);
|
||||||
if (!release.actors) return acc;
|
const movies = accumulateMovies(storedReleases);
|
||||||
|
|
||||||
release.actors.forEach((actor) => {
|
|
||||||
const trimmedActor = actor.trim();
|
|
||||||
|
|
||||||
if (acc[trimmedActor]) {
|
|
||||||
acc[trimmedActor] = acc[trimmedActor].concat(release.id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
acc[trimmedActor] = [release.id];
|
|
||||||
});
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
associateActors(actors, storedReleases),
|
associateActors(actors, storedReleases),
|
||||||
|
@ -363,7 +388,11 @@ async function storeReleases(releases) {
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return storedReleases;
|
return {
|
||||||
|
releases: storedReleases,
|
||||||
|
actors,
|
||||||
|
movies,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
|
@ -1,68 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const config = require('config');
|
|
||||||
|
|
||||||
const argv = require('./argv');
|
|
||||||
const scrapers = require('./scrapers/scrapers');
|
|
||||||
const { storeReleases } = require('./releases');
|
|
||||||
const { findSiteByUrl } = require('./sites');
|
|
||||||
const { findNetworkByUrl } = require('./networks');
|
|
||||||
|
|
||||||
async function findSite(url, release) {
|
|
||||||
const site = (release && release.site) || await findSiteByUrl(url);
|
|
||||||
|
|
||||||
if (site) {
|
|
||||||
return site;
|
|
||||||
}
|
|
||||||
|
|
||||||
const network = await findNetworkByUrl(url);
|
|
||||||
|
|
||||||
if (network) {
|
|
||||||
return {
|
|
||||||
...network,
|
|
||||||
network,
|
|
||||||
isFallback: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function scrapeRelease(url, release, deep = false, isMovie = false) {
|
|
||||||
const site = await findSite(url, release);
|
|
||||||
|
|
||||||
if (!site) {
|
|
||||||
throw new Error('Could not find site in database');
|
|
||||||
}
|
|
||||||
|
|
||||||
const scraper = scrapers.releases[site.slug] || scrapers.releases[site.network.slug];
|
|
||||||
|
|
||||||
if (!scraper) {
|
|
||||||
throw new Error('Could not find scraper for URL');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isMovie && !scraper.fetchScene) {
|
|
||||||
throw new Error(`The '${site.name}'-scraper cannot fetch individual scenes`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isMovie && !scraper.fetchMovie) {
|
|
||||||
throw new Error(`The '${site.name}'-scraper cannot fetch individual movies`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const scrapedRelease = isMovie
|
|
||||||
? await scraper.fetchMovie(url, site, release)
|
|
||||||
: await scraper.fetchScene(url, site, release);
|
|
||||||
|
|
||||||
if (!deep && argv.save) {
|
|
||||||
// don't store release when called by site scraper
|
|
||||||
const [storedRelease] = await storeReleases([scrapedRelease]);
|
|
||||||
|
|
||||||
if (storedRelease) {
|
|
||||||
console.log(`http://${config.web.host}:${config.web.port}/scene/${storedRelease.id}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return scrapedRelease;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = scrapeRelease;
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const config = require('config');
|
||||||
|
const Promise = require('bluebird');
|
||||||
|
|
||||||
|
const argv = require('./argv');
|
||||||
|
const scrapers = require('./scrapers/scrapers');
|
||||||
|
const { storeReleases } = require('./releases');
|
||||||
|
const { findSiteByUrl } = require('./sites');
|
||||||
|
const { findNetworkByUrl } = require('./networks');
|
||||||
|
|
||||||
|
async function findSite(url, release) {
|
||||||
|
const site = (release && release.site) || await findSiteByUrl(url);
|
||||||
|
|
||||||
|
if (site) {
|
||||||
|
return site;
|
||||||
|
}
|
||||||
|
|
||||||
|
const network = await findNetworkByUrl(url);
|
||||||
|
|
||||||
|
if (network) {
|
||||||
|
return {
|
||||||
|
...network,
|
||||||
|
network,
|
||||||
|
isFallback: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function scrapeRelease(url, release, type = 'scene') {
|
||||||
|
const site = await findSite(url, release);
|
||||||
|
|
||||||
|
if (!site) {
|
||||||
|
throw new Error('Could not find site in database');
|
||||||
|
}
|
||||||
|
|
||||||
|
const scraper = scrapers.releases[site.slug] || scrapers.releases[site.network.slug];
|
||||||
|
|
||||||
|
if (!scraper) {
|
||||||
|
throw new Error('Could not find scraper for URL');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'scene' && !scraper.fetchScene) {
|
||||||
|
throw new Error(`The '${site.name}'-scraper cannot fetch individual scenes`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type === 'movie' && !scraper.fetchMovie) {
|
||||||
|
throw new Error(`The '${site.name}'-scraper cannot fetch individual movies`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrapedRelease = type === 'scene'
|
||||||
|
? await scraper.fetchScene(url, site, release)
|
||||||
|
: await scraper.fetchMovie(url, site, release);
|
||||||
|
|
||||||
|
return scrapedRelease;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function scrapeReleases(urls, release, type = 'scene') {
|
||||||
|
const scrapedReleases = await Promise.map(urls, async url => scrapeRelease(url, release, type), {
|
||||||
|
concurrency: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
const curatedReleases = scrapedReleases.map(scrapedRelease => ({ ...scrapedRelease, type }));
|
||||||
|
|
||||||
|
if (argv.save) {
|
||||||
|
/*
|
||||||
|
const movie = scrapedRelease.movie
|
||||||
|
? await scrapeRelease(scrapedRelease.movie, null, false, 'movie')
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (movie) {
|
||||||
|
const { releases: [storedMovie] } = await storeReleases([movie]);
|
||||||
|
curatedRelease.parentId = storedMovie.id;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { releases: storedReleases } = await storeReleases(curatedReleases);
|
||||||
|
|
||||||
|
if (storedReleases) {
|
||||||
|
console.log(storedReleases.map(storedRelease => `http://${config.web.host}:${config.web.port}/scene/${storedRelease.id}`).join('\n'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
scrapeRelease,
|
||||||
|
scrapeReleases,
|
||||||
|
};
|
|
@ -7,7 +7,7 @@ const argv = require('./argv');
|
||||||
const knex = require('./knex');
|
const knex = require('./knex');
|
||||||
const { fetchIncludedSites } = require('./sites');
|
const { fetchIncludedSites } = require('./sites');
|
||||||
const scrapers = require('./scrapers/scrapers');
|
const scrapers = require('./scrapers/scrapers');
|
||||||
const scrapeRelease = require('./scrape-release');
|
const { scrapeRelease } = require('./scrape-releases');
|
||||||
const { storeReleases } = require('./releases');
|
const { storeReleases } = require('./releases');
|
||||||
|
|
||||||
function getAfterDate() {
|
function getAfterDate() {
|
||||||
|
@ -70,7 +70,7 @@ async function deepFetchReleases(baseReleases) {
|
||||||
return Promise.map(baseReleases, async (release) => {
|
return Promise.map(baseReleases, async (release) => {
|
||||||
if (release.url) {
|
if (release.url) {
|
||||||
try {
|
try {
|
||||||
const fullRelease = await scrapeRelease(release.url, release, true);
|
const fullRelease = await scrapeRelease(release.url, release, 'scene');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...release,
|
...release,
|
||||||
|
@ -111,10 +111,10 @@ async function scrapeSiteReleases(scraper, site) {
|
||||||
return baseReleases;
|
return baseReleases;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function scrapeReleases() {
|
async function scrapeSites() {
|
||||||
const networks = await fetchIncludedSites();
|
const networks = await fetchIncludedSites();
|
||||||
|
|
||||||
const scrapedReleases = await Promise.map(networks, async network => Promise.map(network.sites, async (site) => {
|
const scrapedNetworks = await Promise.map(networks, async network => Promise.map(network.sites, async (site) => {
|
||||||
const scraper = scrapers.releases[site.slug] || scrapers.releases[site.network.slug];
|
const scraper = scrapers.releases[site.slug] || scrapers.releases[site.network.slug];
|
||||||
|
|
||||||
if (!scraper) {
|
if (!scraper) {
|
||||||
|
@ -143,8 +143,8 @@ async function scrapeReleases() {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (argv.save) {
|
if (argv.save) {
|
||||||
await storeReleases(scrapedReleases.flat(2));
|
await storeReleases(scrapedNetworks.flat(2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = scrapeReleases;
|
module.exports = scrapeSites;
|
||||||
|
|
|
@ -27,7 +27,7 @@ async function getPhotos(albumUrl) {
|
||||||
const lastPhotoPage = Array.from(document.querySelectorAll('.preview-image-container a')).slice(-1)[0].href;
|
const lastPhotoPage = Array.from(document.querySelectorAll('.preview-image-container a')).slice(-1)[0].href;
|
||||||
const lastPhotoIndex = parseInt(lastPhotoPage.match(/\d+.jpg/)[0], 10);
|
const lastPhotoIndex = parseInt(lastPhotoPage.match(/\d+.jpg/)[0], 10);
|
||||||
|
|
||||||
const photoUrls = await Promise.map(Array.from({ length: lastPhotoIndex }), async (index) => {
|
const photoUrls = await Promise.map(Array.from({ length: lastPhotoIndex }), async (value, index) => {
|
||||||
const pageUrl = `https://blacksonblondes.com${lastPhotoPage.replace(/\d+.jpg/, `${index.toString().padStart(3, '0')}.jpg`)}`;
|
const pageUrl = `https://blacksonblondes.com${lastPhotoPage.replace(/\d+.jpg/, `${index.toString().padStart(3, '0')}.jpg`)}`;
|
||||||
|
|
||||||
return getPhoto(pageUrl);
|
return getPhoto(pageUrl);
|
||||||
|
|
|
@ -50,7 +50,9 @@ function scrapeProfile(html, actorName) {
|
||||||
if (bio.weight) profile.weight = Number(bio.weight.split(',')[0]);
|
if (bio.weight) profile.weight = Number(bio.weight.split(',')[0]);
|
||||||
|
|
||||||
profile.social = Array.from(document.querySelectorAll('.profile-meta-item a.social-icons'), el => el.href);
|
profile.social = Array.from(document.querySelectorAll('.profile-meta-item a.social-icons'), el => el.href);
|
||||||
profile.avatar = document.querySelector('.profile-image-large img').src;
|
|
||||||
|
const avatar = document.querySelector('.profile-image-large img').src;
|
||||||
|
if (!avatar.match('placeholder')) profile.avatar = document.querySelector('.profile-image-large img').src;
|
||||||
|
|
||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ async function scrapeProfile(html, _url, actorName) {
|
||||||
const { document } = new JSDOM(html).window;
|
const { document } = new JSDOM(html).window;
|
||||||
|
|
||||||
const entries = Array.from(document.querySelectorAll('.infoPiece'), el => el.textContent.replace(/\n|\t/g, '').split(':'));
|
const entries = Array.from(document.querySelectorAll('.infoPiece'), el => el.textContent.replace(/\n|\t/g, '').split(':'));
|
||||||
const bio = entries.reduce((acc, [key, value]) => ({ ...acc, [key.trim()]: value.trim() }), {});
|
const bio = entries.reduce((acc, [key, value]) => (key ? { ...acc, [key.trim()]: value.trim() } : acc), {});
|
||||||
|
|
||||||
const profile = {
|
const profile = {
|
||||||
name: actorName,
|
name: actorName,
|
||||||
|
|
|
@ -54,13 +54,13 @@ module.exports = {
|
||||||
actors: {
|
actors: {
|
||||||
// ordered by data priority
|
// ordered by data priority
|
||||||
xempire,
|
xempire,
|
||||||
|
julesjordan,
|
||||||
brazzers,
|
brazzers,
|
||||||
legalporno,
|
legalporno,
|
||||||
pornhub,
|
pornhub,
|
||||||
freeones,
|
freeones,
|
||||||
freeonesLegacy,
|
freeonesLegacy,
|
||||||
kellymadison,
|
kellymadison,
|
||||||
julesjordan,
|
|
||||||
ddfnetwork,
|
ddfnetwork,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
12
src/tags.js
|
@ -66,20 +66,16 @@ async function associateTags(release, releaseId) {
|
||||||
? await matchTags(release.tags) // scraper returned raw tags
|
? await matchTags(release.tags) // scraper returned raw tags
|
||||||
: release.tags; // tags already matched by (outdated) scraper
|
: release.tags; // tags already matched by (outdated) scraper
|
||||||
|
|
||||||
const associationEntries = await knex('tags_associated')
|
const associationEntries = await knex('releases_tags')
|
||||||
.where({
|
.where('release_id', releaseId)
|
||||||
domain: 'releases',
|
|
||||||
target_id: releaseId,
|
|
||||||
})
|
|
||||||
.whereIn('tag_id', tags);
|
.whereIn('tag_id', tags);
|
||||||
|
|
||||||
const existingAssociations = new Set(associationEntries.map(association => association.tag_id));
|
const existingAssociations = new Set(associationEntries.map(association => association.tag_id));
|
||||||
const newAssociations = tags.filter(tagId => !existingAssociations.has(tagId));
|
const newAssociations = tags.filter(tagId => !existingAssociations.has(tagId));
|
||||||
|
|
||||||
await knex('tags_associated').insert(newAssociations.map(tagId => ({
|
await knex('releases_tags').insert(newAssociations.map(tagId => ({
|
||||||
tag_id: tagId,
|
tag_id: tagId,
|
||||||
domain: 'releases',
|
release_id: releaseId,
|
||||||
target_id: releaseId,
|
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|