Experimenting using GraphQL in favor of REST.

This commit is contained in:
ThePendulum 2019-12-15 05:42:51 +01:00
parent 36c5fa3b52
commit 7ba716cd6f
21 changed files with 1021 additions and 113 deletions

View File

@ -3,7 +3,8 @@
<Header />
<div class="content">
<router-view />
<!-- key forces rerender when new and old path use same component -->
<router-view :key="$route.fullPath" />
</div>
</div>
</template>

View File

@ -108,6 +108,22 @@
</ul>
</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
v-if="release.tags.length > 0"
class="row"
@ -196,8 +212,10 @@
</template>
<script>
import Actor from '../tile/actor.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() {
return this.release && this.release.title;
@ -211,6 +229,8 @@ export default {
components: {
Actor,
Banner,
Releases,
Release,
},
data() {
return {

View File

@ -56,6 +56,7 @@ export default {
}
.tiles {
width: 100%;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(20rem, .33fr));
grid-gap: 1rem;

View File

@ -35,9 +35,9 @@
<span
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"
>@ {{ actor.ageThen }}</span>
>{{ actor.ageThen }}</span>
</span>
<span
v-if="actor.origin"
@ -114,6 +114,7 @@ export default {
color: $text-contrast;
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
box-sizing: border-box;
padding: .5rem;

View File

@ -1,5 +1,8 @@
<template>
<div class="tile">
<div
class="tile"
:class="{ movie: release.type === 'movie' }"
>
<span class="banner">
<span class="details">
<router-link
@ -39,7 +42,7 @@
</span>
<router-link
:to="`/scene/${release.id}`"
:to="`/${release.type || 'scene'}/${release.id}`"
class="link"
>
<img
@ -66,14 +69,19 @@
<div class="info">
<router-link
:to="`/scene/${release.id}`"
:to="`/${release.type || 'scene'}/${release.id}`"
class="row link"
>
<h3
v-tooltip.top="release.title"
:title="release.title"
class="title"
>{{ release.title }}</h3>
>
<Icon
v-if="release.type === 'movie'"
icon="film"
/>{{ release.title }}
</h3>
</router-link>
<span class="row">
@ -212,13 +220,19 @@ export default {
}
.title {
color: $text;
display: flex;
align-items: center;
margin: 0 .25rem .25rem 0;
color: $text;
font-size: 1rem;
max-height: 3rem;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
.icon {
margin: 0 .25rem 0 0;
}
}
.network {

5
assets/img/film.svg Normal file
View File

@ -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

5
assets/img/film2.svg Normal file
View File

@ -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

5
assets/img/film3.svg Normal file
View File

@ -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

View File

@ -21,7 +21,7 @@ async function get(endpoint, query = {}) {
async function post(endpoint, data) {
const res = await fetch(`${config.api.url}${endpoint}`, {
method: 'GET',
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json',
@ -39,4 +39,34 @@ async function post(endpoint, data) {
throw new Error(errorMsg);
}
export { get, post };
async function graphql(operationName, query, variables = null) {
const res = await fetch('/graphql', {
method: 'POST',
mode: 'cors',
headers: {
'Content-Type': 'application/json',
},
credentials: 'same-origin',
body: JSON.stringify({
operationName,
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,
};

View File

@ -1,14 +1,97 @@
import { get } from '../api';
import { get, graphql } from '../api';
function initReleasesActions(store, _router) {
function initReleasesActions(_store, _router) {
async function fetchReleases({ _commit }) {
/*
const releases = await get('/releases', {
filter: store.state.ui.filter,
after: store.getters.after,
before: store.getters.before,
});
*/
return releases;
const { releases } = await graphql('MyQuery', `
query MyQuery {
releases(orderBy: DATE_DESC) {
id
title
description
date
duration
createdAt
shootId
url
actors: actorsAssociateds {
actor {
id
name
slug
origin: countryByBirthCountryAlpha2 {
alpha2
name
}
avatar: actorsMediasByTargetId(condition: { role: "avatar" }) {
id
thumbnail
path
mime
}
}
}
poster: releasesMediasByTargetId(condition: { role: "poster" }) {
mime
id
index
path
thumbnail
role
}
photos: releasesMediasByTargetId(condition: { role: "photos" }) {
mime
id
index
path
thumbnail
role
}
tags: releasesTagsByTargetId {
tag {
name
priority
slug
id
}
}
site {
id
name
slug
url
network {
id
name
slug
url
}
}
}
}
`);
const curatedReleases = releases.map(release => ({
...release,
actors: release.actors.map(({ actor }) => ({
...actor,
avatar: actor.avatar[0],
})),
poster: release.poster[0],
network: release.site.network,
tags: release.tags.map(({ tag }) => tag),
}));
console.log(curatedReleases);
return curatedReleases;
}
async function fetchReleaseById({ _commit }, releaseId) {

View File

@ -200,7 +200,7 @@ exports.up = knex => Promise.resolve()
table.integer('duration')
.unsigned();
table.integer('parent', 16)
table.integer('parent_id', 16)
.references('id')
.inTable('releases');
@ -220,6 +220,8 @@ exports.up = knex => Promise.resolve()
table.string('domain');
table.integer('target_id', 16);
table.json('target');
table.string('role');
table.string('quality', 6);
@ -286,9 +288,29 @@ exports.up = knex => Promise.resolve()
table.integer('target_id', 16);
table.unique(['domain', 'tag_id', 'target_id']);
}));
}))
.then(() => knex.raw(`
CREATE VIEW releases_media AS SELECT * FROM public.media WHERE domain = 'releases';
CREATE VIEW actors_media AS SELECT * FROM public.media WHERE domain = 'actors';
CREATE VIEW tags_media AS SELECT * FROM public.media WHERE domain = 'media';
CREATE VIEW releases_tags AS SELECT * FROM public.tags_associated WHERE domain = 'releases';
COMMENT ON VIEW releases_media IS E'@foreignKey (target_id) references releases (id)|@fieldName releaseId';
COMMENT ON VIEW actors_media IS E'@foreignKey (target_id) references actors (id)|@fieldName actorId';
COMMENT ON VIEW tags_media IS E'@foreignKey (target_id) references tags (id)|@fieldName tagMediaId';
COMMENT ON VIEW releases_tags IS E'@foreignKey (target_id) references releases (id)\n@foreignKey (tag_id) references tags (id)';
`));
exports.down = knex => Promise.resolve()
.then(() => knex.raw(`
DROP VIEW releases_media;
DROP VIEW actors_media;
DROP VIEW tags_media;
DROP VIEW releases_tags;
`))
.then(() => knex.schema.dropTable('tags_associated'))
.then(() => knex.schema.dropTable('directors_associated'))
.then(() => knex.schema.dropTable('actors_associated'))

526
package-lock.json generated
View File

@ -1422,6 +1422,16 @@
"to-fast-properties": "^2.0.0"
}
},
"@graphile-contrib/pg-simplify-inflector": {
"version": "5.0.0-beta.1",
"resolved": "https://registry.npmjs.org/@graphile-contrib/pg-simplify-inflector/-/pg-simplify-inflector-5.0.0-beta.1.tgz",
"integrity": "sha512-oE1znqxBgGl6WK7t8sO4okQEqlP+GZ7ZYT4m2EB38GGbT3YuzjHTI8TcxJtDZsLPG3kPvZvB4Uc34Kb0/NGUVQ=="
},
"@graphile/lru": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/@graphile/lru/-/lru-4.5.0.tgz",
"integrity": "sha512-OoIgewLowjegJzz3tpcRE5LpQKqsap1ETFaZtC1r9p36h5ieUJnWTxCTpB7XIsU9muMTt7MMt8x0E5apS47QIQ=="
},
"@tensorflow/tfjs": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-1.4.0.tgz",
@ -1526,11 +1536,115 @@
}
}
},
"@types/accepts": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz",
"integrity": "sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==",
"requires": {
"@types/node": "*"
}
},
"@types/bluebird": {
"version": "3.5.29",
"resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.29.tgz",
"integrity": "sha512-kmVtnxTuUuhCET669irqQmPAez4KFnFVKvpleVRyfC3g+SHD1hIkFZcWLim9BVcwUBLO59o8VZE4yGCmTif8Yw=="
},
"@types/body-parser": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.1.tgz",
"integrity": "sha512-RoX2EZjMiFMjZh9lmYrwgoP9RTpAjSHiJxdp4oidAQVO02T7HER3xj9UKue5534ULWeqVEkujhWcyvUce+d68w==",
"requires": {
"@types/connect": "*",
"@types/node": "*"
}
},
"@types/connect": {
"version": "3.4.32",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.32.tgz",
"integrity": "sha512-4r8qa0quOvh7lGD0pre62CAb1oni1OO6ecJLGCezTmhQ8Fz50Arx9RUszryR8KlgK6avuSXvviL6yWyViQABOg==",
"requires": {
"@types/node": "*"
}
},
"@types/cookies": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/@types/cookies/-/cookies-0.7.4.tgz",
"integrity": "sha512-oTGtMzZZAVuEjTwCjIh8T8FrC8n/uwy+PG0yTvQcdZ7etoel7C7/3MSd7qrukENTgQtotG7gvBlBojuVs7X5rw==",
"requires": {
"@types/connect": "*",
"@types/express": "*",
"@types/keygrip": "*",
"@types/node": "*"
}
},
"@types/express": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.2.tgz",
"integrity": "sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==",
"requires": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "*",
"@types/serve-static": "*"
}
},
"@types/express-serve-static-core": {
"version": "4.17.0",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.0.tgz",
"integrity": "sha512-Xnub7w57uvcBqFdIGoRg1KhNOeEj0vB6ykUM7uFWyxvbdE89GFyqgmUcanAriMr4YOxNFZBAWkfcWIb4WBPt3g==",
"requires": {
"@types/node": "*",
"@types/range-parser": "*"
}
},
"@types/http-assert": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.1.tgz",
"integrity": "sha512-PGAK759pxyfXE78NbKxyfRcWYA/KwW17X290cNev/qAsn9eQIxkH4shoNBafH37wewhDG/0p1cHPbK6+SzZjWQ=="
},
"@types/json5": {
"version": "0.0.30",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.30.tgz",
"integrity": "sha512-sqm9g7mHlPY/43fcSNrCYfOeX9zkTTK+euO5E6+CVijSMm5tTjkVdwdqRkY3ljjIAf8679vps5jKUoJBCLsMDA=="
},
"@types/jsonwebtoken": {
"version": "8.3.5",
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.3.5.tgz",
"integrity": "sha512-VGM1gb+LwsQ5EPevvbvdnKncajBdYqNcrvixBif1BsiDQiSF1q+j4bBTvKC6Bt9n2kqNSx+yNTY2TVJ360E7EQ==",
"requires": {
"@types/node": "*"
}
},
"@types/keygrip": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.1.tgz",
"integrity": "sha1-/1QEYtL7TQqIRBzq8n0oewHD2Hg="
},
"@types/koa": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.11.0.tgz",
"integrity": "sha512-Hgx/1/rVlJvqYBrdeCsS7PDiR2qbxlMt1RnmNWD4Uxi5FF9nwkYqIldo7urjc+dfNpk+2NRGcnAYd4L5xEhCcQ==",
"requires": {
"@types/accepts": "*",
"@types/cookies": "*",
"@types/http-assert": "*",
"@types/keygrip": "*",
"@types/koa-compose": "*",
"@types/node": "*"
}
},
"@types/koa-compose": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/@types/koa-compose/-/koa-compose-3.2.5.tgz",
"integrity": "sha512-B8nG/OoE1ORZqCkBVsup/AKcvjdgoHnfi4pZMn5UwAPCbhk/96xyv284eBYW8JlQbQ7zDmnpFr68I/40mFoIBQ==",
"requires": {
"@types/koa": "*"
}
},
"@types/mime": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz",
"integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw=="
},
"@types/node": {
"version": "12.12.14",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.14.tgz",
@ -1549,11 +1663,39 @@
"resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.3.0.tgz",
"integrity": "sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q=="
},
"@types/pg": {
"version": "7.11.2",
"resolved": "https://registry.npmjs.org/@types/pg/-/pg-7.11.2.tgz",
"integrity": "sha512-4+rj7fnidA77jFURNanuPPc1HrQv+RkhI6s+K18G9zOKbOUUpChA/rbNMqFukNuZ89LoIt/I9dAlxf329TjCNw==",
"requires": {
"@types/node": "*",
"@types/pg-types": "*"
}
},
"@types/pg-types": {
"version": "1.11.5",
"resolved": "https://registry.npmjs.org/@types/pg-types/-/pg-types-1.11.5.tgz",
"integrity": "sha512-L8ogeT6vDzT1vxlW3KITTCt+BVXXVkLXfZ/XNm6UqbcJgxf+KPO7yjWx7dQQE8RW07KopL10x2gNMs41+IkMGQ=="
},
"@types/range-parser": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz",
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA=="
},
"@types/seedrandom": {
"version": "2.4.27",
"resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.27.tgz",
"integrity": "sha1-nbVjk33YaRX2kJK8QyWdL0hXjkE="
},
"@types/serve-static": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz",
"integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==",
"requires": {
"@types/express-serve-static-core": "*",
"@types/mime": "*"
}
},
"@types/webgl-ext": {
"version": "0.0.30",
"resolved": "https://registry.npmjs.org/@types/webgl-ext/-/webgl-ext-0.0.30.tgz",
@ -1564,6 +1706,14 @@
"resolved": "https://registry.npmjs.org/@types/webgl2/-/webgl2-0.0.4.tgz",
"integrity": "sha512-PACt1xdErJbMUOUweSrbVM7gSIYm1vTncW2hF6Os/EeWi6TXYAYMPp+8v6rzHmypE5gHrxaxZNXgMkJVIdZpHw=="
},
"@types/ws": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-6.0.4.tgz",
"integrity": "sha512-PpPrX7SZW9re6+Ha8ojZG4Se8AZXgf0GK6zmfqEuCsY49LFDNXO3SByp44X3dFEqtB73lkCDAdUazhAjVPiNwg==",
"requires": {
"@types/node": "*"
}
},
"@vue/component-compiler-utils": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.1.0.tgz",
@ -2691,6 +2841,11 @@
"integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==",
"dev": true
},
"backo2": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
"integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@ -3089,6 +3244,11 @@
"isarray": "^1.0.0"
}
},
"buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk="
},
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
@ -4204,6 +4364,14 @@
"safer-buffer": "^2.1.0"
}
},
"ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"requires": {
"safe-buffer": "^5.0.1"
}
},
"editorconfig": {
"version": "0.15.3",
"resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz",
@ -4740,6 +4908,11 @@
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
},
"eventemitter3": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
"integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q=="
},
"events": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz",
@ -6208,6 +6381,147 @@
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz",
"integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA=="
},
"graphile-build": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/graphile-build/-/graphile-build-4.5.0.tgz",
"integrity": "sha512-U/1K4nHWp3MGJ7kwTwIYBZnO3ZDlufmHHpdd1t4uFA46OtxdP7RG7KnXyol/XB8hfTfqSVGabaZP8lr+NVyQPA==",
"requires": {
"@graphile/lru": "4.5.0",
"chalk": "^2.4.2",
"debug": "^4.1.1",
"graphql-parse-resolve-info": "4.5.0",
"iterall": "^1.2.2",
"lodash": ">=4 <5",
"lru-cache": "^5.0.0",
"pluralize": "^7.0.0",
"semver": "^6.0.0"
},
"dependencies": {
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"requires": {
"ms": "^2.1.1"
}
},
"lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"requires": {
"yallist": "^3.0.2"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"semver": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
},
"yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
}
}
},
"graphile-build-pg": {
"version": "4.5.3",
"resolved": "https://registry.npmjs.org/graphile-build-pg/-/graphile-build-pg-4.5.3.tgz",
"integrity": "sha512-Sp3DC/PC9c8+dQkQ6FM5F2TwByWQIMX3TKpxm63SePFJfPdq2Lbl4mOjBxJykYf4Fh094MVAIz4Alz5a1yRBKw==",
"requires": {
"@graphile/lru": "4.5.0",
"chalk": "^2.4.2",
"debug": "^4.1.1",
"graphile-build": "4.5.0",
"graphql-iso-date": "^3.6.0",
"jsonwebtoken": "^8.5.1",
"lodash": ">=4 <5",
"lru-cache": ">=4 <5",
"pg-sql2": "4.5.0",
"postgres-interval": "^1.2.0"
},
"dependencies": {
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"graphile-utils": {
"version": "4.5.6",
"resolved": "https://registry.npmjs.org/graphile-utils/-/graphile-utils-4.5.6.tgz",
"integrity": "sha512-Tt4SBVxfS3wbewUhQiN1oXg4KjPVi2a9GOjD8AXWlQfKin25TH2dDRyJRALM/blVuSyBlzn9OqPnvsbM3NqXpQ==",
"requires": {
"debug": "^4.1.1",
"graphql": ">=0.9 <0.14 || ^14.0.2"
},
"dependencies": {
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"graphql": {
"version": "14.5.8",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-14.5.8.tgz",
"integrity": "sha512-MMwmi0zlVLQKLdGiMfWkgQD7dY/TUKt4L+zgJ/aR0Howebod3aNgP5JkgvAULiR2HPVZaP2VEElqtdidHweLkg==",
"requires": {
"iterall": "^1.2.2"
}
},
"graphql-iso-date": {
"version": "3.6.1",
"resolved": "https://registry.npmjs.org/graphql-iso-date/-/graphql-iso-date-3.6.1.tgz",
"integrity": "sha512-AwFGIuYMJQXOEAgRlJlFL4H1ncFM8n8XmoVDTNypNOZyQ8LFDG2ppMFlsS862BSTCDcSUfHp8PD3/uJhv7t59Q=="
},
"graphql-parse-resolve-info": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/graphql-parse-resolve-info/-/graphql-parse-resolve-info-4.5.0.tgz",
"integrity": "sha512-51eKipZcj9AQWKLtstAmqys3GmOHUoUwFN/ALe1s4k2his0sP4ASri/ZdqcweLRnno4cDPqp2dcWnJ/Pe2a2rQ==",
"requires": {
"debug": "^4.1.1"
},
"dependencies": {
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
@ -7000,6 +7314,11 @@
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
},
"iterall": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/iterall/-/iterall-1.2.2.tgz",
"integrity": "sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA=="
},
"js-base64": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz",
@ -7143,6 +7462,30 @@
"graceful-fs": "^4.1.6"
}
},
"jsonwebtoken": {
"version": "8.5.1",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
"integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
"requires": {
"jws": "^3.2.2",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1",
"semver": "^5.6.0"
},
"dependencies": {
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"jsprim": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
@ -7164,6 +7507,25 @@
"object.assign": "^4.1.0"
}
},
"jwa": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
"requires": {
"buffer-equal-constant-time": "1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"requires": {
"jwa": "^1.4.1",
"safe-buffer": "^5.0.1"
}
},
"keypress": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/keypress/-/keypress-0.2.1.tgz",
@ -7402,6 +7764,41 @@
"resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz",
"integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI="
},
"lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha1-YLuYqHy5I8aMoeUTJUgzFISfVT8="
},
"lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY="
},
"lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha1-YZwK89A/iwTDH1iChAt3sRzWg0M="
},
"lodash.isnumber": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha1-POdoEMWSjQM1IwGsKHMX8RwLH/w="
},
"lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
},
"lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
},
"lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w="
},
"lodash.sortby": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
@ -8890,6 +9287,31 @@
"resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.7.tgz",
"integrity": "sha512-UiJyO5B9zZpu32GSlP0tXy8J2NsJ9EFGFfz5v6PSbdz/1hBLX1rNiiy5+mAm5iJJYwfCv4A0EBcQLGWwjbpzZw=="
},
"pg-sql2": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/pg-sql2/-/pg-sql2-4.5.0.tgz",
"integrity": "sha512-kmqhJgKbOWsVzKjhzsfglZagyrV5fXkrHeY33WdeSsxhegZI1Evpa/lQzG1uQj2ZfLPIePV7RyoaZH8eVHRd3A==",
"requires": {
"@graphile/lru": "4.5.0",
"@types/pg": ">=6 <8",
"debug": ">=3 <5"
},
"dependencies": {
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"pg-types": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
@ -8948,6 +9370,11 @@
"find-up": "^1.0.0"
}
},
"pluralize": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz",
"integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow=="
},
"pn": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz",
@ -9108,6 +9535,75 @@
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
"dev": true
},
"postgraphile": {
"version": "4.5.5",
"resolved": "https://registry.npmjs.org/postgraphile/-/postgraphile-4.5.5.tgz",
"integrity": "sha512-JjUcZIabf8rMUwBlvDCp2VOciJcrT/AnE4ksOiYDiv//sWj4twt6QR1T7+Zw7+jA8x1DxCKeIdCKCk7EO5H/4Q==",
"requires": {
"@graphile/lru": "4.5.0",
"@types/json5": "^0.0.30",
"@types/jsonwebtoken": "^8.3.2",
"@types/koa": "^2.0.44",
"@types/pg": "^7.4.10",
"@types/ws": "^6.0.1",
"body-parser": "^1.15.2",
"chalk": "^2.4.2",
"commander": "^2.19.0",
"debug": "^4.1.1",
"finalhandler": "^1.0.6",
"graphile-utils": "4.5.6",
"graphql": "^0.6.0 || ^0.7.0 || ^0.8.0-b || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.2",
"http-errors": "^1.5.1",
"iterall": "^1.0.2",
"json5": "^2.1.1",
"jsonwebtoken": "^8.0.0",
"parseurl": "^1.3.2",
"pg": ">=6.1.0 <8",
"pg-connection-string": "^2.0.0",
"pg-sql2": "4.5.0",
"postgraphile-core": "4.5.3",
"subscriptions-transport-ws": "^0.9.15",
"tslib": "^1.5.0",
"ws": "^6.1.3"
},
"dependencies": {
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"ws": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
"integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
"requires": {
"async-limiter": "~1.0.0"
}
}
}
},
"postgraphile-core": {
"version": "4.5.3",
"resolved": "https://registry.npmjs.org/postgraphile-core/-/postgraphile-core-4.5.3.tgz",
"integrity": "sha512-DmE9JdQEMMJH0W+FVRGL/pO9zZzanuXE49+V+s2agLNjNlDFRFCNKnlUI2sA0PKTNJXilVAPQFpBifn1t74moA==",
"requires": {
"graphile-build": "4.5.0",
"graphile-build-pg": "4.5.3"
}
},
"postgraphile-plugin-connection-filter": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/postgraphile-plugin-connection-filter/-/postgraphile-plugin-connection-filter-1.1.3.tgz",
"integrity": "sha512-QWn2lDQ3d8M0iiQVZtozIcJmL3b2A3L6DnMD2e8EOOVqxxAy9Kt3b7k4a+bH46rz4Eh6NuDtHWb0PZnxx41kVg=="
},
"postgres-array": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
@ -11038,6 +11534,28 @@
"schema-utils": "^1.0.0"
}
},
"subscriptions-transport-ws": {
"version": "0.9.16",
"resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.16.tgz",
"integrity": "sha512-pQdoU7nC+EpStXnCfh/+ho0zE0Z+ma+i7xvj7bkXKb1dvYHSZxgRPaU6spRP+Bjzow67c/rRDoix5RT0uU9omw==",
"requires": {
"backo2": "^1.0.2",
"eventemitter3": "^3.1.0",
"iterall": "^1.2.1",
"symbol-observable": "^1.0.4",
"ws": "^5.2.0"
},
"dependencies": {
"ws": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz",
"integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==",
"requires": {
"async-limiter": "~1.0.0"
}
}
}
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@ -11046,6 +11564,11 @@
"has-flag": "^3.0.0"
}
},
"symbol-observable": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
"integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
},
"symbol-tree": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
@ -11417,8 +11940,7 @@
"tslib": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
"integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==",
"dev": true
"integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ=="
},
"tty-browserify": {
"version": "0.0.0",

View File

@ -67,6 +67,7 @@
"webpack-cli": "^3.3.10"
},
"dependencies": {
"@graphile-contrib/pg-simplify-inflector": "^5.0.0-beta.1",
"@tensorflow/tfjs-node": "^1.4.0",
"babel-polyfill": "^6.26.0",
"bhttp": "^1.2.4",
@ -90,6 +91,8 @@
"moment": "^2.24.0",
"opn": "^5.5.0",
"pg": "^7.14.0",
"postgraphile": "^4.5.5",
"postgraphile-plugin-connection-filter": "^1.1.3",
"prop-types": "^15.7.2",
"react": "^16.12.0",
"react-dom": "^16.12.0",

View File

@ -188,14 +188,21 @@
text-decoration: none;
}
.title[data-v-3abcf101] {
color: #222;
display: -webkit-box;
display: flex;
-webkit-box-align: center;
align-items: center;
margin: 0 .25rem .25rem 0;
color: #222;
font-size: 1rem;
max-height: 3rem;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.title .icon[data-v-3abcf101] {
margin: 0 .25rem 0 0;
}
.network[data-v-3abcf101] {
color: #555;
margin: 0 .25rem 0 0;
@ -255,6 +262,7 @@
text-transform: capitalize;
}
.tiles[data-v-22ffe3e4] {
width: 100%;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(20rem, 0.33fr));
grid-gap: 1rem;
@ -270,6 +278,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; */
.actor[data-v-6989dc6f] {
width: 10rem;
@ -317,6 +350,8 @@
width: 100%;
display: -webkit-box;
display: flex;
-webkit-box-align: center;
align-items: center;
-webkit-box-pack: justify;
justify-content: space-between;
box-sizing: border-box;
@ -330,31 +365,6 @@
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; */
.column[data-v-d4b03dc2] {
width: 1200px;

View File

@ -235,6 +235,7 @@ function getMedia(tagsMap) {
mime: 'image/jpeg',
index,
domain: file.domain || 'tags',
target: { [file.domain || 'tags']: file.target_id },
role: file.role || 'photo',
}));
}

View File

@ -12,13 +12,13 @@ const { scrapeActors, scrapeBasicActors } = require('./actors');
async function init() {
if (argv.scene) {
await Promise.map(argv.scene, async url => scrapeRelease(url, null, false, false), {
await Promise.map(argv.scene, async url => scrapeRelease(url, null, false, 'scene'), {
concurrency: 5,
});
}
if (argv.movie) {
await Promise.map(argv.movie, async url => scrapeRelease(url, null, false, true), {
await Promise.map(argv.movie, async url => scrapeRelease(url, null, false, 'movie'), {
concurrency: 5,
});
}

View File

@ -15,8 +15,45 @@ const {
} = require('./media');
const { fetchSites, findSiteByUrl } = require('./sites');
async function curateRelease(release) {
const [actors, tags, media] = await Promise.all([
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 curateMovie(release, deep = true) {
const [scenes, actors, tags, media] = await Promise.all([
knex('releases')
.where('releases.parent_id', release.id)
.modify(commonQuery, {}),
knex('actors_associated')
.select(
'actors.id', 'actors.name', 'actors.gender', 'actors.slug', 'actors.birthdate',
@ -49,8 +86,9 @@ async function curateRelease(release) {
.orderBy(['role', 'index']),
]);
return {
const curatedRelease = {
id: release.id,
type: release.type,
title: release.title,
date: release.date,
dateAdded: release.created_at,
@ -108,6 +146,126 @@ async function curateRelease(release) {
url: release.network_url,
},
};
if (scenes && scenes.length > 0 && deep) {
curatedRelease.scenes = await Promise.map(scenes, scene => curateRelease(scene, false));
}
return curatedRelease;
}
async function curateScene(release, deep = true) {
const [movie, actors, tags, media] = await Promise.all([
knex('releases')
.where('releases.id', release.parent_id)
.modify(commonQuery, {})
.first(),
knex('actors_associated')
.select(
'actors.id', 'actors.name', 'actors.gender', 'actors.slug', 'actors.birthdate',
'birth_countries.alpha2 as birth_country_alpha2', 'birth_countries.name as birth_country_name', 'birth_countries.alias as birth_country_alias',
'media.thumbnail as avatar',
)
.where({ release_id: release.id })
.leftJoin('actors', 'actors.id', 'actors_associated.actor_id')
.leftJoin('countries as birth_countries', 'actors.birth_country_alpha2', 'birth_countries.alpha2')
.leftJoin('media', (builder) => {
builder
.on('media.target_id', 'actors.id')
.andOnVal('media.domain', 'actors')
.andOnVal('media.index', '0');
})
.orderBy('actors.gender'),
knex('tags_associated')
.select('tags.name', 'tags.slug')
.where({
domain: 'releases',
target_id: release.id,
})
.leftJoin('tags', 'tags.id', 'tags_associated.tag_id')
.orderBy('tags.priority', 'desc'),
knex('media')
.where({
target_id: release.id,
domain: 'releases',
})
.orderBy(['role', 'index']),
]);
const curatedRelease = {
id: release.id,
type: release.type,
title: release.title,
date: release.date,
dateAdded: release.created_at,
description: release.description,
url: release.url,
shootId: release.shoot_id,
entryId: release.entry_id,
actors: actors.map(actor => ({
id: actor.id,
slug: actor.slug,
name: actor.name,
gender: actor.gender,
birthdate: actor.birthdate,
age: moment().diff(actor.birthdate, 'years'),
ageThen: moment(release.date).diff(actor.birthdate, 'years'),
avatar: actor.avatar,
origin: actor.birth_country_alpha2
? {
country: {
name: actor.birth_country_alias,
alpha2: actor.birth_country_alpha2,
},
}
: null,
})),
director: release.director,
tags,
duration: release.duration,
photos: media.filter(item => item.role === 'photo'),
poster: media.filter(item => item.role === 'poster')[0],
covers: media.filter(item => item.role === 'cover'),
trailer: media.filter(item => item.role === 'trailer')[0],
site: {
id: release.site_id,
name: release.site_name,
independent: release.site_parameters
? (JSON.parse(release.site_parameters).independent || false)
: false,
slug: release.site_slug,
url: release.site_url,
},
studio: release.studio_id
? {
id: release.studio_id,
name: release.studio_name,
slug: release.studio_slug,
url: release.studio_url,
}
: null,
network: {
id: release.network_id,
name: release.network_name,
description: release.network_description,
slug: release.network_slug,
url: release.network_url,
},
};
if (movie && deep) {
curatedRelease.movie = await curateMovie(movie, false);
}
return curatedRelease;
}
async function curateRelease(release) {
if (release.type === 'movie') {
return curateMovie(release);
}
return curateScene(release);
}
function curateReleases(releases) {
@ -129,12 +287,14 @@ async function getChannelSite(release) {
}
}
async function curateScrapedRelease(release) {
async function curateReleaseEntry(release) {
const curatedRelease = {
site_id: release.site.id,
studio_id: release.studio ? release.studio.id : null,
shoot_id: release.shootId || null,
entry_id: release.entryId || null,
parent_id: release.parentId,
type: release.type,
url: release.url,
title: release.title,
date: release.date,
@ -159,40 +319,6 @@ async function curateScrapedRelease(release) {
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 = {}) {
const releases = await knex('releases')
.modify(commonQuery, options)
@ -244,6 +370,40 @@ async function fetchTagReleases(queryObject, options = {}) {
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) {
const subpath = `${release.site.network.slug}/${release.site.slug}/${release.id}/`;
const identifier = `"${release.title}" (${releaseId})`;
@ -279,7 +439,7 @@ async function storeReleaseAssets(release, releaseId) {
async function storeRelease(release) {
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) {
return existingRelease.id;
@ -339,22 +499,8 @@ async function storeReleases(releases) {
concurrency: 10,
}).filter(release => release);
const actors = storedReleases.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;
}, {});
const actors = accumulateActors(storedReleases);
const movies = accumulateMovies(storedReleases);
await Promise.all([
associateActors(actors, storedReleases),
@ -363,7 +509,11 @@ async function storeReleases(releases) {
}),
]);
return storedReleases;
return {
releases: storedReleases,
actors,
movies,
};
}
module.exports = {

View File

@ -28,7 +28,7 @@ async function findSite(url, release) {
return null;
}
async function scrapeRelease(url, release, deep = false, isMovie = false) {
async function scrapeRelease(url, release, deep = true, type = 'scene') {
const site = await findSite(url, release);
if (!site) {
@ -41,21 +41,32 @@ async function scrapeRelease(url, release, deep = false, isMovie = false) {
throw new Error('Could not find scraper for URL');
}
if (!isMovie && !scraper.fetchScene) {
if (type === 'scene' && !scraper.fetchScene) {
throw new Error(`The '${site.name}'-scraper cannot fetch individual scenes`);
}
if (isMovie && !scraper.fetchMovie) {
if (type === 'movie' && !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);
const scrapedRelease = type === 'scene'
? await scraper.fetchScene(url, site, release)
: await scraper.fetchMovie(url, site, release);
const curatedRelease = { ...scrapedRelease, type };
if (!deep && argv.save) {
// don't store release when called by site scraper
const [storedRelease] = await storeReleases([scrapedRelease]);
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: [storedRelease] } = await storeReleases([curatedRelease]);
if (storedRelease) {
console.log(`http://${config.web.host}:${config.web.port}/scene/${storedRelease.id}`);

View File

@ -70,7 +70,7 @@ async function deepFetchReleases(baseReleases) {
return Promise.map(baseReleases, async (release) => {
if (release.url) {
try {
const fullRelease = await scrapeRelease(release.url, release, true);
const fullRelease = await scrapeRelease(release.url, release, true, 'scene');
return {
...release,
@ -114,7 +114,7 @@ async function scrapeSiteReleases(scraper, site) {
async function scrapeReleases() {
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];
if (!scraper) {
@ -143,7 +143,8 @@ async function scrapeReleases() {
});
if (argv.save) {
await storeReleases(scrapedReleases.flat(2));
const { movies } = await storeReleases(scrapedNetworks.flat(2));
console.log(movies);
}
}

View File

@ -27,7 +27,7 @@ async function getPhotos(albumUrl) {
const lastPhotoPage = Array.from(document.querySelectorAll('.preview-image-container a')).slice(-1)[0].href;
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`)}`;
return getPhoto(pageUrl);

View File

@ -3,9 +3,13 @@
const path = require('path');
const config = require('config');
const express = require('express');
const { postgraphile } = require('postgraphile');
const Router = require('express-promise-router');
const bodyParser = require('body-parser');
const ConnectionFilterPlugin = require('postgraphile-plugin-connection-filter');
const PgSimplifyInflectorPlugin = require('@graphile-contrib/pg-simplify-inflector');
const {
fetchReleases,
fetchReleaseById,
@ -28,6 +32,25 @@ function initServer() {
const app = express();
const router = Router();
const connectionString = `postgres://${config.database.user}:${config.database.password}@${config.database.host}:5432/${config.database.database}`;
app.use(postgraphile(
connectionString,
'public',
{
// watchPg: true,
dynamicJson: true,
graphiql: true,
enhanceGraphiql: true,
allowExplain: () => true,
simpleCollections: 'only',
graphileBuildOptions: {
pgOmitListSuffix: true,
},
appendPlugins: [PgSimplifyInflectorPlugin, ConnectionFilterPlugin],
},
));
router.use('/media', express.static(config.media.path));
router.use(express.static('public'));