Compare commits
77 Commits
old
...
704a5ee8db
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
704a5ee8db | ||
|
|
cd187fac16 | ||
|
|
bb055e6ecc | ||
|
|
01b37f087f | ||
|
|
96e094ee88 | ||
|
|
85c73bad77 | ||
|
|
587c111449 | ||
|
|
43d239a6ae | ||
|
|
0fa36b17bf | ||
|
|
1a92cd79f7 | ||
|
|
527112d5da | ||
|
|
0d8c92aac9 | ||
|
|
b9556c9c86 | ||
|
|
8439631e2d | ||
|
|
cc63cc652a | ||
|
|
7c46bdd495 | ||
|
|
1d84830423 | ||
|
|
21a3bc44e6 | ||
|
|
b00b8f4a96 | ||
|
|
f1c9ac4207 | ||
|
|
0d95746689 | ||
|
|
430c7e124d | ||
|
|
153f28c494 | ||
|
|
a586413240 | ||
|
|
25e0575c2b | ||
|
|
acca75e2b5 | ||
|
|
5cbf122d6f | ||
|
|
08df432665 | ||
|
|
762b3984a3 | ||
|
|
505ff0767c | ||
|
|
9be80e2be9 | ||
|
|
e202e887f9 | ||
|
|
574c117ab0 | ||
|
|
d59a57f311 | ||
|
|
5e499c3685 | ||
|
|
17e5ce71b2 | ||
|
|
5352186319 | ||
|
|
e9ba02d65d | ||
|
|
39813d4461 | ||
|
|
829a285a2d | ||
|
|
a19a77e165 | ||
|
|
122dd3eaee | ||
|
|
18b219850e | ||
|
|
33a327a04b | ||
|
|
a46061e247 | ||
|
|
94e07ff23d | ||
|
|
4811befcf6 | ||
|
|
c455f02c66 | ||
|
|
efc5620a28 | ||
|
|
61123fdb6a | ||
|
|
3ec6911d46 | ||
|
|
2021093645 | ||
|
|
1c72dc202f | ||
|
|
1ef946fa77 | ||
|
|
3b6bbc39ff | ||
|
|
481c9feada | ||
|
|
953b3e9568 | ||
|
|
bdd2e68f49 | ||
|
|
e4cc349302 | ||
|
|
6547b93e55 | ||
|
|
bb9649d23b | ||
|
|
9e2eaef9d1 | ||
|
|
1c3ee75d3b | ||
|
|
15c9af8057 | ||
|
|
295573c1ef | ||
|
|
e93e8ace5c | ||
|
|
43af7ba777 | ||
|
|
0dad5b0d68 | ||
|
|
ae9b793318 | ||
|
|
fd8170f223 | ||
|
|
661b8b716b | ||
|
|
5ff076cac3 | ||
|
|
41c100ac4e | ||
|
|
c6e977f842 | ||
|
|
50b7f521b5 | ||
|
|
f0d0ee3acc | ||
|
|
ccb99e278c |
1
.gitignore
vendored
@@ -3,6 +3,7 @@ dist/
|
||||
log/
|
||||
media/
|
||||
html/
|
||||
tmp/*
|
||||
public/js/*
|
||||
public/css/*
|
||||
config/*
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
class="favicon"
|
||||
>
|
||||
<img
|
||||
:src="`/img/logos/${actor.entity.slug}/favicon_dark.png`"
|
||||
:src="`/img/logos/${actor.entity.slug}/favicon_light.png`"
|
||||
class="favicon-icon"
|
||||
>
|
||||
</RouterLink>
|
||||
|
||||
@@ -50,9 +50,9 @@ function ratioFilter(banner) {
|
||||
|
||||
function entityCampaign() {
|
||||
const bannerCampaigns = this.entity.campaigns
|
||||
.concat(this.entity.children?.flatMap(child => child.campaigns))
|
||||
.concat(this.entity.children?.flatMap((child) => child.campaigns))
|
||||
.concat(this.entity.parent?.campaigns)
|
||||
.filter(campaignX => campaignX && this.ratioFilter(campaignX.banner));
|
||||
.filter((campaignX) => campaignX && this.ratioFilter(campaignX.banner));
|
||||
|
||||
if (bannerCampaigns.length > 0) {
|
||||
const randomCampaign = bannerCampaigns[Math.floor(Math.random() * bannerCampaigns.length)];
|
||||
@@ -66,7 +66,7 @@ function entityCampaign() {
|
||||
}
|
||||
|
||||
function tagCampaign() {
|
||||
const campaignBanners = this.tag.banners.filter(banner => banner.campaigns.length > 0 && this.ratioFilter(banner));
|
||||
const campaignBanners = this.tag.banners.filter((banner) => banner.campaigns.length > 0 && this.ratioFilter(banner));
|
||||
const banner = campaignBanners[Math.floor(Math.random() * campaignBanners.length)];
|
||||
|
||||
if (banner?.campaigns.length > 0) {
|
||||
@@ -83,17 +83,25 @@ function tagCampaign() {
|
||||
return null;
|
||||
}
|
||||
|
||||
function campaign() {
|
||||
async function genericCampaign() {
|
||||
const randomCampaign = await this.$store.dispatch('fetchRandomCampaign', { minRatio: this.minRatio, maxRatio: this.maxRatio });
|
||||
|
||||
this.campaign = randomCampaign;
|
||||
this.$emit('campaign', randomCampaign);
|
||||
|
||||
return randomCampaign;
|
||||
}
|
||||
|
||||
async function mounted() {
|
||||
if (this.entity) {
|
||||
return this.entityCampaign();
|
||||
await this.entityCampaign();
|
||||
}
|
||||
|
||||
if (this.tag) {
|
||||
return this.tagCampaign();
|
||||
await this.tagCampaign();
|
||||
}
|
||||
|
||||
this.$emit('campaign', null); // allow parent to toggle campaigns depending on availability
|
||||
return null;
|
||||
await this.genericCampaign();
|
||||
}
|
||||
|
||||
export default {
|
||||
@@ -124,11 +132,15 @@ export default {
|
||||
},
|
||||
},
|
||||
emits: ['campaign'],
|
||||
computed: {
|
||||
campaign,
|
||||
data() {
|
||||
return {
|
||||
campaign: null,
|
||||
};
|
||||
},
|
||||
mounted,
|
||||
methods: {
|
||||
entityCampaign,
|
||||
genericCampaign,
|
||||
ratioFilter,
|
||||
tagCampaign,
|
||||
},
|
||||
@@ -139,7 +151,6 @@ export default {
|
||||
.campaign {
|
||||
height: 100%;
|
||||
display: inline-flex;
|
||||
flex-grow: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ async function setConsent(consent, includeQueer) {
|
||||
}
|
||||
|
||||
if (includeQueer) {
|
||||
this.$store.dispatch('setTagFilter', this.$store.state.ui.tagFilter.filter(tag => !['gay', 'bisexual', 'transsexual'].includes(tag)));
|
||||
this.$store.dispatch('setTagFilter', this.$store.state.ui.tagFilter.filter((tag) => !['gay', 'bisexual', 'transsexual'].includes(tag)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -107,6 +107,7 @@ export default {
|
||||
showSidebar: false,
|
||||
showWarning: localStorage.getItem('consent') !== window.env.sessionId,
|
||||
showFilters: false,
|
||||
selected: null,
|
||||
};
|
||||
},
|
||||
mounted,
|
||||
|
||||
@@ -108,6 +108,7 @@
|
||||
:fetch-releases="fetchEntity"
|
||||
:items-total="totalCount"
|
||||
:items-per-page="limit"
|
||||
:available-tags="entity.tags"
|
||||
/>
|
||||
|
||||
<div class="releases">
|
||||
|
||||
@@ -102,6 +102,8 @@ export default {
|
||||
}
|
||||
|
||||
.name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--text-light);
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
<template>
|
||||
<div class="home">
|
||||
<div class="content-inner">
|
||||
<div class="campaign-container">
|
||||
<Campaign
|
||||
:min-ratio="6"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FilterBar
|
||||
ref="filter"
|
||||
:fetch-releases="fetchReleases"
|
||||
@@ -32,6 +38,7 @@
|
||||
import FilterBar from '../filters/filter-bar.vue';
|
||||
import Releases from '../releases/releases.vue';
|
||||
import Pagination from '../pagination/pagination.vue';
|
||||
import Campaign from '../campaigns/campaign.vue';
|
||||
|
||||
async function fetchReleases(scroll = true) {
|
||||
this.done = false;
|
||||
@@ -59,6 +66,7 @@ async function mounted() {
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Campaign,
|
||||
FilterBar,
|
||||
Releases,
|
||||
Pagination,
|
||||
@@ -91,4 +99,12 @@ export default {
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.campaign-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: .75rem 1rem .25rem 1rem;
|
||||
background: var(--background-dim);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -28,7 +28,7 @@ export default {
|
||||
};
|
||||
},
|
||||
beforeMount() {
|
||||
this.svg = require(`../../img/icons/${this.icon}.svg`).default;
|
||||
this.svg = require(`../../img/icons/${this.icon}.svg`).default; // eslint-disable-line global-require, import/no-dynamic-require
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -11,8 +11,22 @@
|
||||
class="empty"
|
||||
>No results for "{{ $route.query.query }}"</span>
|
||||
|
||||
<template v-else>
|
||||
<h2 class="heading">Popular</h2>
|
||||
|
||||
<div
|
||||
class="entity-tiles"
|
||||
>
|
||||
<Entity
|
||||
v-for="entity in popularEntities"
|
||||
:key="entity.parent ? `entity-tile-${entity.parent.slug}-${entity.slug}` : `entity-tile-${entity.slug}`"
|
||||
:entity="entity"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h2 class="heading">All networks</h2>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="entity-tiles"
|
||||
>
|
||||
<Entity
|
||||
@@ -21,6 +35,7 @@
|
||||
:entity="entity"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
@@ -58,6 +73,45 @@ async function searchEntities() {
|
||||
this.done = true;
|
||||
}
|
||||
|
||||
function popularEntities() {
|
||||
const entitiesBySlug = Object.fromEntries(this.entities.map((entity) => [entity.slug, entity]));
|
||||
|
||||
return [
|
||||
'21sextury',
|
||||
'amateurallure',
|
||||
'analvids',
|
||||
'bamvisions',
|
||||
'bang',
|
||||
'bangbros',
|
||||
'blowpass',
|
||||
'brazzers',
|
||||
'burningangel',
|
||||
'digitalplayground',
|
||||
'dogfartnetwork',
|
||||
'dorcel',
|
||||
'elegantangel',
|
||||
'evilangel',
|
||||
'fakehub',
|
||||
'girlsway',
|
||||
'hookuphotshot',
|
||||
'hussiepass',
|
||||
'insex',
|
||||
'julesjordan',
|
||||
'kellymadison',
|
||||
'kink',
|
||||
'mofos',
|
||||
'naughtyamerica',
|
||||
'newsensations',
|
||||
'pervcity',
|
||||
'pornpros',
|
||||
'private',
|
||||
'realitykings',
|
||||
'twistys',
|
||||
'vixen',
|
||||
'xempire',
|
||||
].map((slug) => entitiesBySlug[slug]).filter(Boolean);
|
||||
}
|
||||
|
||||
async function mounted() {
|
||||
this.pageTitle = 'Channels';
|
||||
|
||||
@@ -82,6 +136,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
channelCount,
|
||||
popularEntities,
|
||||
},
|
||||
watch: {
|
||||
$route: fetchEntities,
|
||||
@@ -130,6 +185,10 @@ export default {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.heading {
|
||||
margin: 1rem 0 0 0;
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint2) {
|
||||
.entity-tiles {
|
||||
grid-gap: .5rem;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="media-container">
|
||||
<div
|
||||
class="media"
|
||||
:class="{ center: release.photos.length < 2, preview: !me }"
|
||||
:class="{ center: (release.photos?.length || 0) + (release.scenesPhotos?.length || 0) < 2, preview: !me }"
|
||||
>
|
||||
<div
|
||||
v-if="release.trailer || release.teaser"
|
||||
@@ -71,10 +71,13 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<template v-if="release.covers && release.covers.length > 0">
|
||||
<a
|
||||
<template v-if="release.covers?.length > 0">
|
||||
<div
|
||||
v-for="cover in release.covers"
|
||||
:key="`cover-${cover.id}`"
|
||||
class="item-container"
|
||||
>
|
||||
<a
|
||||
:href="getPath(cover)"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
@@ -89,6 +92,7 @@
|
||||
@load="$emit('load', $event)"
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div
|
||||
@@ -164,8 +168,8 @@ function poster() {
|
||||
function photos() {
|
||||
const clips = this.release.clips || [];
|
||||
const clipPostersById = clips.reduce((acc, clip) => ({ ...acc, [clip.poster.id]: clip.poster }), {});
|
||||
const uniqueClipPosters = Array.from(new Set(clips.map(clip => clip.poster.id) || [])).map(posterId => clipPostersById[posterId]);
|
||||
const photosWithClipPosters = (this.release.photos || []).concat(uniqueClipPosters);
|
||||
const uniqueClipPosters = Array.from(new Set(clips.map((clip) => clip.poster.id) || [])).map((posterId) => clipPostersById[posterId]);
|
||||
const photosWithClipPosters = (this.release.photos || []).concat(this.release.scenesPhotos || []).concat(uniqueClipPosters);
|
||||
|
||||
if (this.release.trailer || (this.release.teaser && this.release.teaser.mime !== 'image/gif')) {
|
||||
// poster will be on trailer video
|
||||
|
||||
@@ -3,12 +3,6 @@
|
||||
<div class="content-inner">
|
||||
<SearchBar :placeholder="`Search ${totalCount} movies`" />
|
||||
|
||||
<TagFilter
|
||||
class="filters-filter"
|
||||
:filter="filter"
|
||||
:available-tags="availableTags"
|
||||
/>
|
||||
|
||||
<div
|
||||
ref="tiles"
|
||||
class="tiles"
|
||||
@@ -36,7 +30,6 @@
|
||||
import MovieTile from './movie-tile.vue';
|
||||
import SearchBar from '../search/bar.vue';
|
||||
import Pagination from '../pagination/pagination.vue';
|
||||
import TagFilter from '../filters/tag-filter.vue';
|
||||
|
||||
async function fetchMovies() {
|
||||
if (this.$route.query.query) {
|
||||
@@ -80,7 +73,6 @@ export default {
|
||||
MovieTile,
|
||||
SearchBar,
|
||||
Pagination,
|
||||
TagFilter,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
@@ -21,14 +21,14 @@
|
||||
<Details :release="release" />
|
||||
|
||||
<button
|
||||
v-if="release.photos.length > 0"
|
||||
v-if="release.photos?.length > 0 || release.scenesPhotos?.length > 0"
|
||||
class="album-toggle"
|
||||
@click="$router.push({ hash: '#album' })"
|
||||
><Icon icon="grid3" />View album</button>
|
||||
|
||||
<Album
|
||||
v-if="showAlbum"
|
||||
:items="[release.poster, ...release.photos]"
|
||||
:items="[release.poster, ...(release.photos || []), ...(release.scenesPhotos || [])]"
|
||||
:title="release.title"
|
||||
:path="config.media.mediaPath"
|
||||
@close="$router.replace({ hash: undefined })"
|
||||
@@ -79,22 +79,23 @@
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="release.movies && release.movies.length > 0"
|
||||
v-if="release.movies?.length > 0 || release.series?.length > 0"
|
||||
class="row"
|
||||
>
|
||||
<span class="row-label">Part of</span>
|
||||
|
||||
<div class="movies">
|
||||
<router-link
|
||||
v-for="movie in release.movies"
|
||||
v-for="movie in [...release.movies, ...release.series]"
|
||||
:key="`movie-${movie.id}`"
|
||||
:to="{ name: 'movie', params: { releaseId: movie.id, releaseSlug: movie.slug } }"
|
||||
:to="{ name: movie.type || 'movie', params: { releaseId: movie.id, releaseSlug: movie.slug } }"
|
||||
class="movie"
|
||||
>
|
||||
<span class="movie-title">{{ movie.title }}</span>
|
||||
|
||||
<img
|
||||
v-if="movie.covers.length > 0"
|
||||
:src="getPath(movie.covers[0], 'thumbnail')"
|
||||
v-if="movie.covers.length > 0 || movie.poster"
|
||||
:src="getPath(movie.covers[0] || movie.poster, 'thumbnail')"
|
||||
class="movie-cover"
|
||||
>
|
||||
</router-link>
|
||||
@@ -202,6 +203,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="release.qualities"
|
||||
class="row"
|
||||
>
|
||||
<span class="row-label">Available qualities</span>
|
||||
|
||||
<span
|
||||
v-for="quality in release.qualities"
|
||||
:key="quality"
|
||||
class="quality"
|
||||
>{{ quality }}</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="release.comment"
|
||||
class="row"
|
||||
@@ -243,6 +257,10 @@ async function fetchRelease(scroll = true) {
|
||||
this.release = await this.$store.dispatch('fetchMovieById', this.$route.params.releaseId);
|
||||
}
|
||||
|
||||
if (this.$route.name === 'serie') {
|
||||
this.release = await this.$store.dispatch('fetchSerieById', this.$route.params.releaseId);
|
||||
}
|
||||
|
||||
if (scroll && this.$refs.content) {
|
||||
this.$refs.content.scrollTop = 0;
|
||||
}
|
||||
@@ -282,7 +300,7 @@ function pageTitle() {
|
||||
}
|
||||
|
||||
function showAlbum() {
|
||||
return this.release.photos?.length > 0 && this.$route.hash === '#album';
|
||||
return (this.release.photos?.length > 0 || this.release.scenesPhotos?.length > 0) && this.$route.hash === '#album';
|
||||
}
|
||||
|
||||
export default {
|
||||
@@ -465,6 +483,16 @@ export default {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.quality {
|
||||
&::after {
|
||||
content: 'p, ';
|
||||
}
|
||||
|
||||
&:last-child::after {
|
||||
content: 'p',
|
||||
}
|
||||
}
|
||||
|
||||
.releases {
|
||||
margin: 0 0 .5rem 0;
|
||||
}
|
||||
|
||||
@@ -87,7 +87,6 @@ const tagSlugsByCategory = {
|
||||
'titty-fucking',
|
||||
'fisting',
|
||||
'anal-fisting',
|
||||
'fisting-dp',
|
||||
],
|
||||
group: [
|
||||
'mfm',
|
||||
@@ -108,16 +107,6 @@ const tagSlugsByCategory = {
|
||||
'bukkake',
|
||||
'fake-cum',
|
||||
],
|
||||
toys: [
|
||||
'toys',
|
||||
'toy-anal',
|
||||
'toy-dp',
|
||||
'double-dildo',
|
||||
'double-dildo-blowjob',
|
||||
'double-dildo-kiss',
|
||||
'double-dildo-anal',
|
||||
'double-dildo-dp',
|
||||
],
|
||||
roleplay: [
|
||||
'family',
|
||||
'parody',
|
||||
@@ -126,6 +115,15 @@ const tagSlugsByCategory = {
|
||||
'maid',
|
||||
'nun',
|
||||
],
|
||||
extreme: [
|
||||
'dp',
|
||||
'airtight',
|
||||
'dap',
|
||||
'dvp',
|
||||
'triple-penetration',
|
||||
'tap',
|
||||
'tvp',
|
||||
],
|
||||
fetish: [
|
||||
'bdsm',
|
||||
'femdom',
|
||||
@@ -134,15 +132,15 @@ const tagSlugsByCategory = {
|
||||
'latex',
|
||||
'blindfold',
|
||||
],
|
||||
extreme: [
|
||||
'dp',
|
||||
'airtight',
|
||||
'dap',
|
||||
'dvp',
|
||||
'da-tp',
|
||||
'dv-tp',
|
||||
'tap',
|
||||
'tvp',
|
||||
toys: [
|
||||
'toys',
|
||||
'toy-anal',
|
||||
'toy-dp',
|
||||
'double-dildo',
|
||||
'double-dildo-blowjob',
|
||||
'double-dildo-kiss',
|
||||
'double-dildo-anal',
|
||||
'double-dildo-dp',
|
||||
],
|
||||
misc: [
|
||||
'gaping',
|
||||
|
||||
@@ -12,6 +12,7 @@ export default {
|
||||
selectableTags: [
|
||||
'airtight',
|
||||
'anal',
|
||||
'bdsm',
|
||||
'blowbang',
|
||||
'blowjob',
|
||||
'creampie',
|
||||
|
||||
@@ -65,19 +65,23 @@ function curateActor(actor, release) {
|
||||
return curatedActor;
|
||||
}
|
||||
|
||||
function curateRelease(release) {
|
||||
function curateRelease(release, type = 'scene') {
|
||||
const curatedRelease = {
|
||||
...release,
|
||||
type: release.type || type,
|
||||
actors: [],
|
||||
poster: release.poster && release.poster.media,
|
||||
tags: release.tags ? release.tags.map((tag) => tag.tag || tag) : [],
|
||||
};
|
||||
|
||||
if (release.scenes) curatedRelease.scenes = release.scenes.filter(Boolean).map(({ scene }) => curateRelease(scene));
|
||||
if (release.movies) curatedRelease.movies = release.movies.filter(Boolean).map(({ movie }) => curateRelease(movie));
|
||||
if (release.chapters) curatedRelease.chapters = release.chapters.filter(Boolean).map((chapter) => curateRelease(chapter));
|
||||
if (release.photos) curatedRelease.photos = release.photos.filter(Boolean).map((photo) => photo.media || photo);
|
||||
if (release.covers) curatedRelease.covers = release.covers.filter(Boolean).map(({ media }) => media);
|
||||
curatedRelease.scenes = release.scenes?.filter(Boolean).map(({ scene }) => curateRelease(scene, 'scene')) || [];
|
||||
curatedRelease.movies = release.movies?.filter(Boolean).map(({ movie }) => curateRelease(movie, 'movie')) || [];
|
||||
curatedRelease.series = release.series?.filter(Boolean).map(({ serie }) => curateRelease(serie, 'serie')) || [];
|
||||
curatedRelease.chapters = release.chapters?.filter(Boolean).map((chapter) => curateRelease(chapter)) || [];
|
||||
curatedRelease.photos = release.photos?.filter(Boolean).map((photo) => photo.media || photo) || [];
|
||||
curatedRelease.scenesPhotos = release.scenesPhotos?.filter(Boolean).map((photo) => photo.media || photo) || [];
|
||||
curatedRelease.covers = release.covers?.filter(Boolean).map(({ media }) => media) || [];
|
||||
|
||||
if (release.trailer) curatedRelease.trailer = release.trailer.media;
|
||||
if (release.teaser) curatedRelease.teaser = release.teaser.media;
|
||||
if (release.actors) curatedRelease.actors = release.actors.filter(Boolean).map((actor) => curateActor(actor.actor || actor, curatedRelease));
|
||||
@@ -105,6 +109,7 @@ function curateEntity(entity, parent, releases) {
|
||||
};
|
||||
|
||||
if (entity.tags) curatedEntity.tags = entity.tags.map(({ tag }) => tag);
|
||||
if (entity.sceneTags) curatedEntity.sceneTags = entity.sceneTags;
|
||||
|
||||
if (entity.children) {
|
||||
if (entity.children.nodes) {
|
||||
|
||||
@@ -41,6 +41,11 @@ function initEntitiesActions(store, router) {
|
||||
slug
|
||||
}
|
||||
}
|
||||
sceneTags {
|
||||
id
|
||||
name
|
||||
slug
|
||||
}
|
||||
children: childEntitiesConnection(
|
||||
orderBy: [PRIORITY_DESC, NAME_ASC],
|
||||
filter: {
|
||||
|
||||
@@ -367,6 +367,7 @@ const releaseFields = `
|
||||
date
|
||||
datePrecision
|
||||
slug
|
||||
qualities
|
||||
shootId
|
||||
productionDate
|
||||
comment
|
||||
@@ -442,6 +443,29 @@ const releasesFragment = `
|
||||
}
|
||||
`;
|
||||
|
||||
const mediaFields = `
|
||||
id
|
||||
index
|
||||
path
|
||||
thumbnail
|
||||
lazy
|
||||
isS3
|
||||
comment
|
||||
sfw: sfwMedia {
|
||||
id
|
||||
thumbnail
|
||||
lazy
|
||||
path
|
||||
comment
|
||||
}
|
||||
`;
|
||||
|
||||
const mediaFragment = `
|
||||
media {
|
||||
${mediaFields}
|
||||
}
|
||||
`;
|
||||
|
||||
const releaseFragment = `
|
||||
release(id: $releaseId) {
|
||||
id
|
||||
@@ -452,6 +476,7 @@ const releaseFragment = `
|
||||
duration
|
||||
createdAt
|
||||
shootId
|
||||
qualities
|
||||
productionDate
|
||||
createdBatchId
|
||||
productionLocation
|
||||
@@ -536,6 +561,19 @@ const releaseFragment = `
|
||||
}
|
||||
}
|
||||
}
|
||||
series: seriesScenesBySceneId {
|
||||
serie {
|
||||
id
|
||||
title
|
||||
slug
|
||||
covers: seriesCoversBySerieId(orderBy: MEDIA_BY_MEDIA_ID__INDEX_ASC) {
|
||||
${mediaFragment}
|
||||
}
|
||||
poster: seriesPosterBySerieId {
|
||||
${mediaFragment}
|
||||
}
|
||||
}
|
||||
}
|
||||
isFavorited
|
||||
isStashed(includeFavorites: false)
|
||||
stashes: stashesScenesBySceneId(
|
||||
@@ -624,6 +662,8 @@ export {
|
||||
actorFields,
|
||||
actorStashesFields,
|
||||
campaignsFragment,
|
||||
mediaFields,
|
||||
mediaFragment,
|
||||
movieFields,
|
||||
releaseActorsFragment,
|
||||
releaseFields,
|
||||
|
||||
@@ -4,6 +4,8 @@ import {
|
||||
releaseFragment,
|
||||
releaseFields,
|
||||
movieFields,
|
||||
mediaFragment,
|
||||
mediaFields,
|
||||
} from '../fragments';
|
||||
import { curateRelease } from '../curate';
|
||||
import getDateRange from '../get-date-range';
|
||||
@@ -141,16 +143,16 @@ function initReleasesActions(store, router) {
|
||||
};
|
||||
}
|
||||
|
||||
async function fetchMovieById({ _commit }, movieId) {
|
||||
async function fetchCollectionById({ _commit }, movieId, type = 'movie') {
|
||||
// const release = await get(`/releases/${releaseId}`);
|
||||
|
||||
const { movie } = await graphql(`
|
||||
const result = await graphql(`
|
||||
query Movie(
|
||||
$movieId: Int!
|
||||
$hasAuth: Boolean!
|
||||
$userId: Int
|
||||
) {
|
||||
movie(id: $movieId) {
|
||||
${type}(id: $movieId) {
|
||||
id
|
||||
title
|
||||
description
|
||||
@@ -182,7 +184,7 @@ function initReleasesActions(store, router) {
|
||||
isS3
|
||||
}
|
||||
}
|
||||
poster: moviesPoster {
|
||||
poster: ${type === 'series' ? 'seriesPosterBySerieId' : 'moviesPoster'} {
|
||||
media {
|
||||
id
|
||||
path
|
||||
@@ -195,7 +197,7 @@ function initReleasesActions(store, router) {
|
||||
isS3
|
||||
}
|
||||
}
|
||||
covers: moviesCovers(orderBy: MEDIA_BY_MEDIA_ID__INDEX_ASC) {
|
||||
covers: ${type === 'series' ? 'seriesCoversBySerieId' : 'moviesCovers'}(orderBy: MEDIA_BY_MEDIA_ID__INDEX_ASC) {
|
||||
media {
|
||||
id
|
||||
path
|
||||
@@ -208,14 +210,14 @@ function initReleasesActions(store, router) {
|
||||
isS3
|
||||
}
|
||||
}
|
||||
trailer: moviesTrailer {
|
||||
trailer: ${type === 'series' ? 'seriesTrailerBySerieId' : 'moviesTrailer'} {
|
||||
media {
|
||||
id
|
||||
path
|
||||
isS3
|
||||
}
|
||||
}
|
||||
scenes: moviesScenes {
|
||||
scenes: ${type === 'series' ? 'seriesScenesBySerieId' : 'moviesScenes'} {
|
||||
scene {
|
||||
${releaseFields}
|
||||
}
|
||||
@@ -225,25 +227,11 @@ function initReleasesActions(store, router) {
|
||||
slug
|
||||
name
|
||||
}
|
||||
photos {
|
||||
id
|
||||
index
|
||||
path
|
||||
thumbnail
|
||||
lazy
|
||||
width
|
||||
height
|
||||
thumbnailWidth
|
||||
thumbnailHeight
|
||||
isS3
|
||||
comment
|
||||
sfw: sfwMedia {
|
||||
id
|
||||
thumbnail
|
||||
lazy
|
||||
path
|
||||
comment
|
||||
photos: ${type === 'series' ? 'seriesPhotosBySerieId' : 'moviesPhotos'} {
|
||||
${mediaFragment}
|
||||
}
|
||||
scenesPhotos {
|
||||
${mediaFields}
|
||||
}
|
||||
entity {
|
||||
id
|
||||
@@ -259,7 +247,7 @@ function initReleasesActions(store, router) {
|
||||
hasLogo
|
||||
}
|
||||
}
|
||||
stashes: stashesMovies(
|
||||
stashes: ${type === 'series' ? 'stashesSeriesBySerieId' : 'stashesMovies'}(
|
||||
filter: {
|
||||
stash: {
|
||||
userId: {
|
||||
@@ -283,12 +271,20 @@ function initReleasesActions(store, router) {
|
||||
userId: store.state.auth.user?.id,
|
||||
});
|
||||
|
||||
if (!movie) {
|
||||
if (!result[type]) {
|
||||
router.replace('/not-found');
|
||||
return null;
|
||||
}
|
||||
|
||||
return curateRelease(movie);
|
||||
return curateRelease(result[type]);
|
||||
}
|
||||
|
||||
async function fetchMovieById(context, movieId) {
|
||||
return fetchCollectionById(context, movieId, 'movie');
|
||||
}
|
||||
|
||||
async function fetchSerieById(context, serieId) {
|
||||
return fetchCollectionById(context, serieId, 'series');
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -296,6 +292,7 @@ function initReleasesActions(store, router) {
|
||||
fetchReleaseById,
|
||||
fetchMovies,
|
||||
fetchMovieById,
|
||||
fetchSerieById,
|
||||
searchMovies,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -71,6 +71,11 @@ const routes = [
|
||||
component: Release,
|
||||
name: 'movie',
|
||||
},
|
||||
{
|
||||
path: '/serie/:releaseId/:releaseSlug?',
|
||||
component: Release,
|
||||
name: 'serie',
|
||||
},
|
||||
{
|
||||
path: '/actor/:actorId/:actorSlug',
|
||||
name: 'actor',
|
||||
|
||||
@@ -227,6 +227,46 @@ function initUiActions(store, _router) {
|
||||
};
|
||||
}
|
||||
|
||||
async function fetchRandomCampaign(context, { minRatio, maxRatio }) {
|
||||
const { randomCampaign } = await graphql(`
|
||||
query Campaign(
|
||||
$minRatio: BigFloat
|
||||
$maxRatio: BigFloat
|
||||
) {
|
||||
randomCampaign: getRandomCampaign(minRatio: $minRatio, maxRatio: $maxRatio) {
|
||||
url
|
||||
affiliate {
|
||||
url
|
||||
}
|
||||
banner {
|
||||
id
|
||||
type
|
||||
ratio
|
||||
entity {
|
||||
type
|
||||
slug
|
||||
parent {
|
||||
type
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
entity {
|
||||
slug
|
||||
}
|
||||
parent {
|
||||
slug
|
||||
}
|
||||
}
|
||||
}
|
||||
`, {
|
||||
minRatio,
|
||||
maxRatio,
|
||||
});
|
||||
|
||||
return randomCampaign;
|
||||
}
|
||||
|
||||
async function fetchStats() {
|
||||
const {
|
||||
scenes,
|
||||
@@ -273,6 +313,7 @@ function initUiActions(store, _router) {
|
||||
setBatch,
|
||||
setSfw,
|
||||
setTheme,
|
||||
fetchRandomCampaign,
|
||||
fetchNotifications,
|
||||
fetchStats,
|
||||
};
|
||||
|
||||
@@ -89,6 +89,10 @@ module.exports = {
|
||||
'uksinners',
|
||||
// mindgeek
|
||||
'pornhub',
|
||||
// insex
|
||||
'paintoy',
|
||||
'aganmedon',
|
||||
'sensualpain',
|
||||
],
|
||||
networks: [
|
||||
// dummy network for testing
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
exports.up = async (knex) => knex.raw(`
|
||||
CREATE OR REPLACE FUNCTION entities_scene_total(entity entities) RETURNS integer AS $$
|
||||
CREATE OR REPLACE FUNCTION entities_scene_total(entity entities) RETURNS bigint AS $$
|
||||
SELECT COUNT(id)
|
||||
FROM releases
|
||||
WHERE releases.entity_id = entity.id;
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
exports.up = async (knex) => knex.raw(`
|
||||
CREATE VIEW movies_tagged AS
|
||||
SELECT * FROM movies;
|
||||
`);
|
||||
|
||||
exports.down = async (knex) => knex.raw(`
|
||||
DROP VIEW IF EXISTS movies_tagged;
|
||||
`);
|
||||
23
migrations/20220227215315_entity_filters.js
Normal file
@@ -0,0 +1,23 @@
|
||||
exports.up = async (knex) => knex.raw(`
|
||||
CREATE FUNCTION entities_scene_tags(entity entities, selectable_tags text[]) RETURNS SETOF tags AS $$
|
||||
SELECT tags.*
|
||||
FROM releases
|
||||
LEFT JOIN
|
||||
releases_tags ON releases_tags.release_id = releases.id
|
||||
LEFT JOIN
|
||||
tags ON tags.id = releases_tags.tag_id
|
||||
WHERE
|
||||
releases.entity_id = entity.id
|
||||
AND
|
||||
CASE WHEN array_length(selectable_tags, 1) IS NOT NULL
|
||||
THEN tags.slug = ANY(selectable_tags)
|
||||
ELSE true
|
||||
END
|
||||
GROUP BY tags.id
|
||||
ORDER BY tags.name;
|
||||
$$ LANGUAGE SQL STABLE;
|
||||
`);
|
||||
|
||||
exports.down = async (knex) => knex.raw(`
|
||||
DROP FUNCTION IF EXISTS entities_scene_tags;
|
||||
`);
|
||||
215
migrations/20220304233846_series.js
Normal file
@@ -0,0 +1,215 @@
|
||||
const config = require('config');
|
||||
|
||||
exports.up = async (knex) => Promise.resolve()
|
||||
.then(() => knex.schema.createTable('series', (table) => {
|
||||
table.increments('id', 16);
|
||||
|
||||
table.integer('entity_id', 12)
|
||||
.references('id')
|
||||
.inTable('entities')
|
||||
.notNullable();
|
||||
|
||||
table.integer('studio_id', 12)
|
||||
.references('id')
|
||||
.inTable('entities');
|
||||
|
||||
table.text('entry_id');
|
||||
table.unique(['entity_id', 'entry_id']);
|
||||
|
||||
table.text('url', 1000);
|
||||
table.text('title');
|
||||
table.text('slug');
|
||||
|
||||
table.timestamp('date');
|
||||
table.index('date');
|
||||
|
||||
table.enum('date_precision', ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'])
|
||||
.defaultTo('day');
|
||||
|
||||
table.text('description');
|
||||
|
||||
table.boolean('deep');
|
||||
table.text('deep_url', 1000);
|
||||
|
||||
table.text('comment');
|
||||
|
||||
table.integer('created_batch_id', 12)
|
||||
.references('id')
|
||||
.inTable('batches')
|
||||
.onDelete('cascade');
|
||||
|
||||
table.integer('updated_batch_id', 12)
|
||||
.references('id')
|
||||
.inTable('batches')
|
||||
.onDelete('cascade');
|
||||
|
||||
table.datetime('created_at')
|
||||
.defaultTo(knex.fn.now());
|
||||
}))
|
||||
.then(() => knex.schema.createTable('series_scenes', (table) => {
|
||||
table.integer('serie_id', 16)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('series')
|
||||
.onDelete('cascade');
|
||||
|
||||
table.integer('scene_id', 16)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('releases')
|
||||
.onDelete('cascade');
|
||||
|
||||
table.unique(['serie_id', 'scene_id']);
|
||||
|
||||
table.datetime('created_at')
|
||||
.defaultTo(knex.fn.now());
|
||||
}))
|
||||
.then(() => knex.schema.createTable('series_trailers', (table) => {
|
||||
table.integer('serie_id', 16)
|
||||
.unique()
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('series')
|
||||
.onDelete('cascade');
|
||||
|
||||
table.text('media_id', 21)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('media');
|
||||
}))
|
||||
.then(() => knex.schema.createTable('series_posters', (table) => {
|
||||
table.integer('serie_id', 16)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('series')
|
||||
.onDelete('cascade');
|
||||
|
||||
table.text('media_id', 21)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('media')
|
||||
.onDelete('cascade');
|
||||
|
||||
table.unique('serie_id');
|
||||
}))
|
||||
.then(() => knex.schema.createTable('series_covers', (table) => {
|
||||
table.integer('serie_id', 16)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('series')
|
||||
.onDelete('cascade');
|
||||
|
||||
table.text('media_id', 21)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('media');
|
||||
|
||||
table.unique(['serie_id', 'media_id']);
|
||||
}))
|
||||
.then(() => knex.schema.createTable('series_search', (table) => {
|
||||
table.integer('serie_id', 16)
|
||||
.references('id')
|
||||
.inTable('series')
|
||||
.onDelete('cascade');
|
||||
}))
|
||||
.then(() => knex.schema.createTable('stashes_series', (table) => {
|
||||
table.integer('stash_id')
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('stashes')
|
||||
.onDelete('cascade');
|
||||
|
||||
table.integer('serie_id')
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('series')
|
||||
.onDelete('cascade');
|
||||
|
||||
table.unique(['stash_id', 'serie_id']);
|
||||
|
||||
table.string('comment');
|
||||
|
||||
table.datetime('created_at')
|
||||
.notNullable()
|
||||
.defaultTo(knex.fn.now());
|
||||
}))
|
||||
.then(() => knex.raw(`
|
||||
ALTER TABLE series_search ADD COLUMN document tsvector;
|
||||
|
||||
CREATE UNIQUE INDEX series_search_unique ON series_search (serie_id);
|
||||
CREATE INDEX series_search_index ON series_search USING GIN (document);
|
||||
|
||||
CREATE FUNCTION series_actors(serie series) RETURNS SETOF actors AS $$
|
||||
SELECT actors.*
|
||||
FROM series_scenes
|
||||
LEFT JOIN
|
||||
releases ON releases.id = series_scenes.scene_id
|
||||
LEFT JOIN
|
||||
releases_actors ON releases_actors.release_id = releases.id
|
||||
LEFT JOIN
|
||||
actors ON actors.id = releases_actors.actor_id
|
||||
WHERE series_scenes.serie_id = serie.id
|
||||
AND actors.id IS NOT NULL
|
||||
GROUP BY actors.id
|
||||
ORDER BY actors.name, actors.gender
|
||||
$$ LANGUAGE SQL STABLE;
|
||||
|
||||
CREATE FUNCTION series_tags(serie series) RETURNS SETOF tags AS $$
|
||||
SELECT tags.*
|
||||
FROM series_scenes
|
||||
LEFT JOIN
|
||||
releases ON releases.id = series_scenes.scene_id
|
||||
LEFT JOIN
|
||||
releases_tags ON releases_tags.release_id = releases.id
|
||||
LEFT JOIN
|
||||
tags ON tags.id = releases_tags.tag_id
|
||||
WHERE series_scenes.serie_id = serie.id
|
||||
AND tags.id IS NOT NULL
|
||||
GROUP BY tags.id
|
||||
ORDER BY tags.priority DESC
|
||||
$$ LANGUAGE SQL STABLE;
|
||||
|
||||
CREATE FUNCTION series_photos(serie series) RETURNS SETOF media AS $$
|
||||
SELECT media.*
|
||||
FROM series_scenes
|
||||
LEFT JOIN
|
||||
releases ON releases.id = series_scenes.scene_id
|
||||
INNER JOIN
|
||||
releases_photos ON releases_photos.release_id = releases.id
|
||||
LEFT JOIN
|
||||
media ON media.id = releases_photos.media_id
|
||||
WHERE series_scenes.serie_id = serie.id
|
||||
GROUP BY media.id
|
||||
ORDER BY media.index ASC
|
||||
$$ LANGUAGE SQL STABLE;
|
||||
|
||||
GRANT ALL ON ALL TABLES IN SCHEMA public TO :visitor;
|
||||
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO :visitor;
|
||||
|
||||
ALTER TABLE stashes_series ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY stashes_policy ON stashes_series
|
||||
USING (EXISTS (
|
||||
SELECT *
|
||||
FROM stashes
|
||||
WHERE stashes.id = stashes_series.stash_id
|
||||
AND (stashes.user_id = current_user_id() OR stashes.public)
|
||||
));
|
||||
`, {
|
||||
visitor: knex.raw(config.database.query.user),
|
||||
}));
|
||||
|
||||
exports.down = async (knex) => Promise.resolve()
|
||||
.then(() => knex.raw(`
|
||||
DROP FUNCTION IF EXISTS series_actors;
|
||||
DROP FUNCTION IF EXISTS series_tags;
|
||||
DROP FUNCTION IF EXISTS series_photos;
|
||||
|
||||
DROP TABLE IF EXISTS stashes_series CASCADE;
|
||||
DROP TABLE IF EXISTS series_scenes CASCADE;
|
||||
DROP TABLE IF EXISTS series_trailers CASCADE;
|
||||
DROP TABLE IF EXISTS series_posters CASCADE;
|
||||
DROP TABLE IF EXISTS series_covers CASCADE;
|
||||
DROP TABLE IF EXISTS series_search CASCADE;
|
||||
DROP TABLE IF EXISTS series CASCADE;
|
||||
`));
|
||||
49
migrations/20220327230125_movie_photos.js
Normal file
@@ -0,0 +1,49 @@
|
||||
const config = require('config');
|
||||
|
||||
exports.up = async (knex) => Promise.resolve()
|
||||
.then(() => knex.raw(`
|
||||
ALTER FUNCTION movies_photos(movie movies) RENAME TO movies_scenes_photos;
|
||||
ALTER FUNCTION series_photos(serie series) RENAME TO series_scenes_photos;
|
||||
`))
|
||||
.then(() => knex.schema.createTable('movies_photos', (table) => {
|
||||
table.integer('movie_id', 16)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('movies')
|
||||
.onDelete('cascade');
|
||||
|
||||
table.text('media_id', 21)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('media');
|
||||
|
||||
table.unique(['movie_id', 'media_id']);
|
||||
}))
|
||||
.then(() => knex.schema.createTable('series_photos', (table) => {
|
||||
table.integer('serie_id', 16)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('series')
|
||||
.onDelete('cascade');
|
||||
|
||||
table.text('media_id', 21)
|
||||
.notNullable()
|
||||
.references('id')
|
||||
.inTable('media');
|
||||
|
||||
table.unique(['serie_id', 'media_id']);
|
||||
}))
|
||||
.then(() => knex.raw(`
|
||||
GRANT ALL ON ALL TABLES IN SCHEMA public TO :visitor;
|
||||
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO :visitor;
|
||||
`, {
|
||||
visitor: knex.raw(config.database.query.user),
|
||||
}));
|
||||
|
||||
exports.down = async (knex) => knex.raw(`
|
||||
DROP TABLE IF EXISTS movies_photos CASCADE;
|
||||
DROP TABLE IF EXISTS series_photos CASCADE;
|
||||
|
||||
ALTER FUNCTION movies_scenes_photos(movie movies) RENAME TO movies_photos;
|
||||
ALTER FUNCTION series_scenes_photos(serie series) RENAME TO series_photos;
|
||||
`);
|
||||
7
migrations/20220331135618_qualities.js
Normal file
@@ -0,0 +1,7 @@
|
||||
exports.up = async (knex) => knex.schema.alterTable('releases', (table) => {
|
||||
table.specificType('qualities', 'text[]');
|
||||
});
|
||||
|
||||
exports.down = async (knex) => knex.schema.alterTable('releases', (table) => {
|
||||
table.dropColumn('qualities');
|
||||
});
|
||||
7
migrations/20220403235645_last_login.js
Normal file
@@ -0,0 +1,7 @@
|
||||
exports.up = async (knex) => knex.schema.alterTable('users', (table) => {
|
||||
table.datetime('last_login');
|
||||
});
|
||||
|
||||
exports.down = async (knex) => knex.schema.alterTable('users', (table) => {
|
||||
table.dropColumn('last_login');
|
||||
});
|
||||
25
migrations/_20220330230122_stats.js
Normal file
@@ -0,0 +1,25 @@
|
||||
exports.up = async (knex) => knex.raw(`
|
||||
CREATE MATERIALIZED VIEW entities_stats
|
||||
AS
|
||||
WITH RECURSIVE relations AS (
|
||||
SELECT entities.id, entities.parent_id, count(releases.id) AS releases_count, count(releases.id) AS total_count
|
||||
FROM entities
|
||||
LEFT JOIN releases ON releases.entity_id = entities.id
|
||||
GROUP BY entities.id
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT entities.id AS entity_id, count(releases.id) AS releases_count, count(releases.id) + relations.total_count AS total_count
|
||||
FROM entities
|
||||
INNER JOIN relations ON relations.id = entities.parent_id
|
||||
LEFT JOIN releases ON releases.entity_id = entities.id
|
||||
GROUP BY entities.id
|
||||
)
|
||||
|
||||
SELECT relations.id AS entity_id, relations.releases_count
|
||||
FROM relations;
|
||||
`);
|
||||
|
||||
exports.down = async (knex) => knex.raw(`
|
||||
DROP MATERIALIZED VIEW entities_stats;
|
||||
`);
|
||||
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "traxxx",
|
||||
"version": "1.209.4",
|
||||
"version": "1.217.3",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "traxxx",
|
||||
"version": "1.209.4",
|
||||
"version": "1.217.3",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@casl/ability": "^5.2.2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "traxxx",
|
||||
"version": "1.209.4",
|
||||
"version": "1.217.3",
|
||||
"description": "All the latest porn releases in one place",
|
||||
"main": "src/app.js",
|
||||
"scripts": {
|
||||
|
||||
|
After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 145 KiB |
|
Before Width: | Height: | Size: 191 KiB |
BIN
public/img/logos/bang/bangpodcast.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
BIN
public/img/logos/bang/lazy/bangpodcast.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
BIN
public/img/logos/bang/lazy/favicon_dark.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
public/img/logos/bang/lazy/favicon_light.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 8.1 KiB After Width: | Height: | Size: 8.1 KiB |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 5.9 KiB |
BIN
public/img/logos/bang/thumbs/bangpodcast.png
Normal file
|
After Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 9.5 KiB After Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.1 KiB |
BIN
public/img/logos/bang/thumbs/favicon_dark.png
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
public/img/logos/bang/thumbs/favicon_light.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
BIN
public/img/logos/biphoria/biphoria.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
public/img/logos/biphoria/favicon.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
public/img/logos/biphoria/favicon_dark.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
public/img/logos/biphoria/favicon_light.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
public/img/logos/biphoria/lazy/biphoria.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
public/img/logos/biphoria/lazy/favicon-dark.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
public/img/logos/biphoria/lazy/favicon-light.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
public/img/logos/biphoria/lazy/favicon.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
public/img/logos/biphoria/lazy/network.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
40
public/img/logos/biphoria/misc/biphoria-light.svg
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 8192 696.7" style="enable-background:new 0 0 8192 696.7;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M378.2,11.5c103,0,155.7,10.1,203.2,33.6c47.5,24.4,74,62.1,74,116c0,87.5-109.6,134.3-187.4,145.5v2
|
||||
c134.6,12.2,237.6,68.2,237.6,170c0,72.2-46.2,120.1-118.8,153.7C524.8,660.8,431.1,673,334.7,673H6v-28.5
|
||||
c103-6.1,113.6-14.2,113.6-124.1V164.2C119.6,54.2,109,46.1,13.9,40V11.5L378.2,11.5L378.2,11.5z M264.7,300.6h52.8
|
||||
c113.6,0,184.8-40.7,184.8-126.2c0-93.6-85.8-124.1-166.3-124.1c-34.4,0-52.8,2-60.7,5.1c-10.6,4.1-10.6,21.4-10.6,45.7
|
||||
L264.7,300.6L264.7,300.6z M264.7,337.2v181.1c0,95.6,15.8,116,100.3,114c87.1,0,176.9-37.6,176.9-142.5
|
||||
c0-103.8-97.7-152.6-232.4-152.6L264.7,337.2L264.7,337.2z"/>
|
||||
<path class="st0" d="M1438.2,561c0,74.2,7.9,79.4,89.8,83.5V673h-318.1v-28.5c81.9-4.1,89.8-9.1,89.8-83.5V331.1
|
||||
c0-75.3-7.9-79.4-89.8-83.5v-28.5h318v28.5c-81.9,4.1-89.8,8.1-89.8,83.5L1438.2,561L1438.2,561z"/>
|
||||
<path class="st0" d="M2380.7,11.5c99,0,174.2,12.2,227,40.7c56.7,30.5,89.8,75.3,89.8,143.5c0,129.2-133.4,195.3-261.4,207.6
|
||||
c-21.1,2-44.8,2-60.7,2l-88.4-18.4v133.3c0,109.9,10.6,118.1,132,124.1v28.5h-388.1v-28.4c100.3-6.1,110.9-14.2,110.9-124.1V164.2
|
||||
c0-109.9-10.6-118.1-105.6-124.1V11.5L2380.7,11.5L2380.7,11.5z M2287,347.3c21.1,6.1,46.2,12.2,80.5,12.2
|
||||
c55.4,0,169-28.5,169-162.8c0-104.8-74-146.6-184.8-146.6c-63.4,0-64.6,6.1-64.6,36.6V347.3z"/>
|
||||
<path class="st0" d="M3690.2,412.4V331c0-75.3-7.9-79.4-93.8-83.5V219h322.1v28.5c-81.9,4.1-89.8,8.1-89.8,83.5v229.9
|
||||
c0,74.2,7.9,79.4,89.8,83.5v28.5h-322.1v-28.4c85.8-4.1,93.8-9.1,93.8-83.5V453.1h-269.3V561c0,74.2,7.9,79.4,85.8,83.5V673h-315.5
|
||||
v-28.5c83.2-4.1,91.1-9.1,91.1-83.5V331.1c0-75.3-7.9-79.4-93.8-83.5v-28.5h322.1v28.5c-81.9,4.1-89.8,8.1-89.8,83.5v81.4
|
||||
L3690.2,412.4L3690.2,412.4z"/>
|
||||
<path class="st0" d="M5085.4,438.9c0,160.8-159.7,246.2-327.4,246.2c-219.1,0-330-120.1-330-228.9c0-168.9,175.5-249.3,330-249.3
|
||||
C4958.7,207,5085.4,310.7,5085.4,438.9z M4767.3,648.5c84.4,0,162.4-51.9,162.4-187.2c0-108.9-64.6-217.8-183.5-217.8
|
||||
c-87.1,0-162.4,70.2-162.4,189.3C4583.8,557,4656.4,648.5,4767.3,648.5z"/>
|
||||
<path class="st0" d="M5816.7,561c0,74.2,7.9,79.4,89.8,83.5V673h-314.1v-28.5c83.2-4.1,91.1-9.1,91.1-83.5V331.1
|
||||
c0-75.3-7.9-79.4-87.1-83.5v-28.5h330c60.7,0,110.9,6.1,149.2,23.4c36.9,17.3,66,50.9,66,95.6c0,60-55.4,97.7-128,116
|
||||
c15.8,21.4,58.1,70.2,85.8,100.7c31.7,35.6,56.7,57,72.6,68.2c18.5,12.2,43.6,23.4,66,29.5l-4,26.5h-52.8
|
||||
c-117.5-1-153.2-26.5-188.8-63.1c-31.7-32.6-66-82.5-88.4-111c-15.8-21.4-26.4-25.5-66-25.5h-21.3V561z M5816.7,447h42.3
|
||||
c29,0,62.1-3,85.8-14.2c36.9-17.3,52.8-47.9,52.8-83.5c0-65.1-58.1-94.6-124-94.6c-54.2,0-56.7,1-56.7,29.5L5816.7,447L5816.7,447z
|
||||
"/>
|
||||
<path class="st0" d="M6948,561c0,74.2,7.9,79.4,89.8,83.5V673h-318.1v-28.5c81.9-4.1,89.8-9.1,89.8-83.5V331.1
|
||||
c0-75.3-7.9-79.4-89.8-83.5v-28.5h318.1v28.5c-81.9,4.1-89.8,8.1-89.8,83.5L6948,561L6948,561z"/>
|
||||
<path class="st0" d="M7886.5,644.5l25-2c36.9-3,43.6-10.1,21.1-54l-31.7-61.1h-180.9c-5.2,11.2-19.8,43.7-31.7,73.2
|
||||
c-11.9,30.5-4,38.6,33,41.7l30.4,2v28.5h-238.9v-28.4c55.4-5.1,79.2-7.1,116.1-74.2l196.7-355.2l48.8-8.1l199.3,375.5
|
||||
c30.4,56,56.7,58,112.2,62.1V673h-299.7v-28.5H7886.5z M7734.7,488.8h147.8l-72.6-144.5h-4L7734.7,488.8z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
BIN
public/img/logos/biphoria/network.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
public/img/logos/biphoria/thumbs/biphoria.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
public/img/logos/biphoria/thumbs/favicon-dark.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
public/img/logos/biphoria/thumbs/favicon-light.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
public/img/logos/biphoria/thumbs/favicon.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
public/img/logos/biphoria/thumbs/network.png
Normal file
|
After Width: | Height: | Size: 5.2 KiB |
BIN
public/img/logos/pervcity/dpdiva.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.6 KiB |
BIN
public/img/logos/pervcity/lazy/dpdiva.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
BIN
public/img/logos/pervcity/lazy/favicon_dark.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
public/img/logos/pervcity/lazy/favicon_light.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |