diff --git a/assets/components/actors/actor.vue b/assets/components/actors/actor.vue index 22bdcdb0..08938563 100644 --- a/assets/components/actors/actor.vue +++ b/assets/components/actors/actor.vue @@ -210,7 +210,7 @@ v-if="actor.hasTattoos" class="bio-item tattoos hideable" > - Tattoos + Tattoos
+ +
- -
@@ -595,15 +595,16 @@ export default { .description { margin: 0; - padding: 0 2rem 0 0; + padding: 0 1rem; + border-left: solid 3px var(--lighten-hint); line-height: 1.5; font-size: .9rem; } .description-logo { display: block; - width: 15rem; - max-height: 2rem; + width: 12rem; + max-height: 1.5rem; margin: .5rem 0 1.5rem 0; object-fit: contain; object-position: 0 50%; @@ -623,9 +624,9 @@ export default { .photos-container { min-width: 15rem; box-sizing: border-box; - border-right: solid 1px $shadow-hint; - padding: 1rem 1.5rem 1rem 1rem; - margin: 0 .5rem 0 0; + border-left: solid 1px $shadow-hint; + padding: 1rem 1rem 1rem 1.5rem; + margin: 0 0 0 .5rem; } .photos.compact { diff --git a/assets/img/icons/anchor.svg b/assets/img/icons/anchor.svg new file mode 100644 index 00000000..4e4542b9 --- /dev/null +++ b/assets/img/icons/anchor.svg @@ -0,0 +1,5 @@ + + +anchor + + diff --git a/assets/img/icons/lotus.svg b/assets/img/icons/lotus.svg new file mode 100755 index 00000000..0dabe206 --- /dev/null +++ b/assets/img/icons/lotus.svg @@ -0,0 +1,77 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/assets/img/icons/tattoo.svg b/assets/img/icons/tattoo.svg new file mode 100644 index 00000000..dcd7f7df --- /dev/null +++ b/assets/img/icons/tattoo.svg @@ -0,0 +1,21 @@ + + heart7 + + + + + + + + + + + + + + + + + + + diff --git a/public/img/logos/freeones/freeoneslegacy.png b/public/img/logos/freeones/freeoneslegacy.png index 6900e730..7c623fd8 100644 Binary files a/public/img/logos/freeones/freeoneslegacy.png and b/public/img/logos/freeones/freeoneslegacy.png differ diff --git a/public/img/logos/freeones/lazy/freeones.png b/public/img/logos/freeones/lazy/freeones.png index f0264c08..d1ddb077 100644 Binary files a/public/img/logos/freeones/lazy/freeones.png and b/public/img/logos/freeones/lazy/freeones.png differ diff --git a/public/img/logos/freeones/lazy/freeoneslegacy.png b/public/img/logos/freeones/lazy/freeoneslegacy.png index 73510f48..2d7cf5bc 100644 Binary files a/public/img/logos/freeones/lazy/freeoneslegacy.png and b/public/img/logos/freeones/lazy/freeoneslegacy.png differ diff --git a/public/img/logos/freeones/lazy/network.png b/public/img/logos/freeones/lazy/network.png index 9dc83830..d9148c62 100644 Binary files a/public/img/logos/freeones/lazy/network.png and b/public/img/logos/freeones/lazy/network.png differ diff --git a/public/img/logos/freeones/thumbs/freeones.png b/public/img/logos/freeones/thumbs/freeones.png index 143a88c1..58a14674 100644 Binary files a/public/img/logos/freeones/thumbs/freeones.png and b/public/img/logos/freeones/thumbs/freeones.png differ diff --git a/public/img/logos/freeones/thumbs/freeoneslegacy.png b/public/img/logos/freeones/thumbs/freeoneslegacy.png index a1aa3cbd..bfd1e530 100644 Binary files a/public/img/logos/freeones/thumbs/freeoneslegacy.png and b/public/img/logos/freeones/thumbs/freeoneslegacy.png differ diff --git a/public/img/logos/freeones/thumbs/network.png b/public/img/logos/freeones/thumbs/network.png index 0e99048e..13cb6436 100644 Binary files a/public/img/logos/freeones/thumbs/network.png and b/public/img/logos/freeones/thumbs/network.png differ diff --git a/src/actors.js b/src/actors.js index 1d9ac36c..8faa6cf7 100644 --- a/src/actors.js +++ b/src/actors.js @@ -130,6 +130,73 @@ function toBaseActors(actorsOrNames, release) { }); } +function curateActor(actor) { + if (!actor) { + return null; + } + + const curatedActor = { + id: actor.id, + name: actor.name, + slug: actor.slug, + gender: actor.gender, + alias: actor.alias && { + id: actor.alias.id, + name: actor.alias.name, + gender: actor.alias.gender, + }, + network: actor.network && { + id: actor.network.id, + name: actor.network.name, + slug: actor.network.slug, + }, + dateOfBirth: actor.date_of_birth, + dateOfDeath: actor.date_of_death, + cup: actor.cup, + bust: actor.bust, + waist: actor.waist, + hip: actor.hip, + naturalBoobs: actor.natural_boobs, + height: actor.height, + weight: actor.weight, + eyes: actor.eyes, + hair: actor.hair, + hasTattoos: actor.has_tattoos, + hasPiercings: actor.has_piercings, + tattoos: actor.tattoos, + piercings: actor.piercings, + description: actor.description, + origin: actor.birth_country && { + country: { + alpha2: actor.birth_country.alpha2, + name: actor.birth_country.name, + alias: actor.birth_country.alias, + }, + state: actor.birth_state, + city: actor.birth_city, + }, + residence: actor.residence_country && { + country: { + alpha2: actor.residence_country.alpha2, + name: actor.residence_country.name, + alias: actor.residence_country.alias, + }, + state: actor.residence_state, + city: actor.residence_city, + }, + avatar: actor.avatar && { + id: actor.avatar.id, + path: actor.avatar.path, + width: actor.avatar.width, + height: actor.avatar.height, + size: actor.avatar.size, + source: actor.avatar.source, + }, + }; + + return curatedActor; +} + function curateActorEntry(baseActor, batchId) { return { name: baseActor.name, @@ -196,7 +263,7 @@ async function curateProfile(profile) { update: profile.update, }; - curatedProfile.description = domPurify.sanitize(profile.description.replace(/\s+/g, ' '), { ALLOWED_TAGS: [] }).trim() || null; + curatedProfile.description = domPurify.sanitize(profile.description?.replace(/\s+/g, ' '), { ALLOWED_TAGS: [] }).trim() || null; const hasher = curatedProfile.description && blake2 .createHash('blake2b') @@ -632,7 +699,36 @@ async function associateActors(releases, batchId) { return actors; } +async function fetchActor(actorId) { + const actor = await knex('actors') + .select(knex.raw(` + actors.*, + row_to_json(networks) as network, + row_to_json(actor_alias) as alias, + row_to_json(birth_country) as birth_country, + row_to_json(residence_country) as residence_country, + row_to_json(media) as avatar + `)) + .modify((queryBuilder) => { + if (Number.isNaN(Number(actorId))) { + queryBuilder.where('actors.slug', actorId); + return; + } + + queryBuilder.where('actors.id', actorId); + }) + .leftJoin('actors as actor_alias', 'actor_alias.id', 'actors.alias_for') + .leftJoin('networks', 'networks.id', 'actors.network_id') + .leftJoin('countries as birth_country', 'birth_country.alpha2', 'actors.birth_country_alpha2') + .leftJoin('countries as residence_country', 'residence_country.alpha2', 'actors.residence_country_alpha2') + .leftJoin('media', 'media.id', 'actors.avatar_media_id') + .first(); + + return curateActor(actor); +} + module.exports = { associateActors, + fetchActor, scrapeActors, }; diff --git a/src/app.js b/src/app.js index adeaf70c..741e5344 100644 --- a/src/app.js +++ b/src/app.js @@ -24,6 +24,7 @@ async function init() { if (argv.actorScenes) { const actorReleases = actors.map(actor => actor.releases).flat().filter(Boolean); + console.log(actors, actorReleases); await storeReleases(actorReleases); } diff --git a/src/releases.js b/src/releases.js index d5fe5089..b8e7df8a 100644 --- a/src/releases.js +++ b/src/releases.js @@ -2,19 +2,121 @@ const knex = require('./knex'); -async function fetchReleases(limit = 100) { - const releases = await knex('releases').limit(limit); +function curateRelease(release, withMedia = false) { + if (!release) { + return null; + } - return releases; + const network = release.site_network || release.network; + + return { + id: release.id, + entryId: release.entry_id, + shootId: release.shoot_id, + title: release.title, + url: release.url, + date: release.date, + description: release.description, + duration: release.duration, + site: release.site && { + id: release.site.id, + name: release.site.name, + slug: release.site.slug, + }, + network: network && { + id: network.id, + name: network.name, + slug: network.slug, + }, + actors: (release.actors || []).map(actor => ({ + id: actor.id, + name: actor.name, + gender: actor.gender, + })), + ...(withMedia && { + poster: release.poster ? { + id: release.poster.id, + path: release.poster.path, + width: release.poster.width, + height: release.poster.height, + size: release.poster.size, + source: release.poster.source, + } : null, + photos: (release.photos || []).map(photo => ({ + id: photo.id, + path: photo.path, + width: photo.width, + height: photo.height, + size: photo.size, + source: photo.source, + })), + }), + createdAt: release.created_at, + }; +} + +function withRelations(queryBuilder, withMedia = false) { + queryBuilder + .select(knex.raw(` + releases.id, releases.entry_id, releases.shoot_id, releases.title, releases.url, releases.date, releases.description, releases.duration, releases.created_at, + row_to_json(sites) as site, + row_to_json(networks) as network, + row_to_json(site_networks) as site_network, + json_agg(DISTINCT actors) as actors + `)) + .leftJoin('sites', 'sites.id', 'releases.site_id') + .leftJoin('networks', 'networks.id', 'releases.network_id') + .leftJoin('networks as site_networks', 'site_networks.id', 'sites.network_id') + .leftJoin('releases_actors', 'releases_actors.release_id', 'releases.id') + .leftJoin('actors', 'actors.id', 'releases_actors.actor_id') + .groupBy(knex.raw(` + releases.id, releases.entry_id, releases.shoot_id, releases.title, releases.url, releases.date, releases.description, releases.duration, releases.created_at, + sites.id, networks.id, site_networks.id + `)); + + if (withMedia) { + queryBuilder + .select(knex.raw(` + row_to_json(posters) as poster, + json_agg(DISTINCT photos) as photos + `)) + .leftJoin('releases_posters', 'releases_posters.release_id', 'releases.id') + .leftJoin('media as posters', 'posters.id', 'releases_posters.media_id') + .leftJoin('releases_photos', 'releases_photos.release_id', 'releases.id') + .leftJoin('media as photos', 'photos.id', 'releases_photos.media_id') + .groupBy('posters.id'); + } +} + +async function fetchRelease(releaseId) { + const release = await knex('releases') + .where('releases.id', releaseId) + .modify(withRelations, true) + .first(); + + return curateRelease(release, true); +} + +async function fetchReleases(limit = 100) { + const releases = await knex('releases') + .modify(withRelations) + .limit(Math.min(limit, 1000)); + + return releases.map(release => curateRelease(release)); } async function searchReleases(query, limit = 100) { - const releases = await knex.raw('SELECT * FROM search_releases(?) LIMIT ?;', [query, limit]); + // const releases = await knex.raw('SELECT * FROM search_releases(?) LIMIT ?;', [query, limit]); + const releases = await knex + .from(knex.raw('search_releases(?) as releases', [query])) + .modify(withRelations) + .limit(Math.min(limit, 1000)); - return releases.rows; + return releases.map(release => curateRelease(release)); } module.exports = { + fetchRelease, fetchReleases, searchReleases, }; diff --git a/src/utils/argv-include.js b/src/utils/argv-include.js index a955bec6..72fd0d3e 100644 --- a/src/utils/argv-include.js +++ b/src/utils/argv-include.js @@ -7,8 +7,8 @@ function include(argv) { photos: argv.media && argv.photos, poster: argv.media && argv.posters, posters: argv.media && argv.posters, - releases: argv.withReleases, - scenes: argv.withReleases, + releases: argv.withScenes, + scenes: argv.withScenes, teaser: argv.media && argv.videos && argv.teasers, teasers: argv.media && argv.videos && argv.teasers, trailer: argv.media && argv.videos && argv.trailers, diff --git a/src/web/actors.js b/src/web/actors.js index b6cace5c..2dd61957 100644 --- a/src/web/actors.js +++ b/src/web/actors.js @@ -1,31 +1,18 @@ 'use strict'; -const { fetchActors } = require('../actors'); +const { fetchActor } = require('../actors'); -async function fetchActorsApi(req, res) { - const actorId = typeof req.params.actorId === 'number' ? req.params.actorId : null; - const actorSlug = typeof req.params.actorId === 'string' ? req.params.actorId : null; +async function fetchActorApi(req, res) { + const actor = await fetchActor(req.params.actorId); - if (actorId || actorSlug) { - const actors = await fetchActors({ - id: actorId, - slug: actorSlug, - }); - - if (actors.length > 0) { - res.send(actors[0]); - return; - } - - res.status(404).send(); + if (actor) { + res.send({ actor }); return; } - const actors = await fetchActors(null, req.query.limit); - - res.send(actors); + res.status(404).send({ actor: null }); } module.exports = { - fetchActors: fetchActorsApi, + fetchActor: fetchActorApi, }; diff --git a/src/web/releases.js b/src/web/releases.js index 68e1f70f..b6094cea 100644 --- a/src/web/releases.js +++ b/src/web/releases.js @@ -1,6 +1,17 @@ 'use strict'; -const { fetchReleases, searchReleases } = require('../releases'); +const { fetchRelease, fetchReleases, searchReleases } = require('../releases'); + +async function fetchReleaseApi(req, res) { + const release = await fetchRelease(req.params.releaseId); + + if (release) { + res.send({ release }); + return; + } + + res.status(404).send({ release: null }); +} async function fetchReleasesApi(req, res) { const query = req.query.query || req.query.q; @@ -9,9 +20,10 @@ async function fetchReleasesApi(req, res) { ? await searchReleases(query, req.query.limit) : await fetchReleases(req.query.limit); - res.send(releases); + res.send({ releases }); } module.exports = { + fetchRelease: fetchReleaseApi, fetchReleases: fetchReleasesApi, }; diff --git a/src/web/server.js b/src/web/server.js index 48024c62..a53b2440 100644 --- a/src/web/server.js +++ b/src/web/server.js @@ -15,9 +15,12 @@ const logger = require('../logger')(__filename); const { ActorPlugins, SitePlugins, ReleasePlugins } = require('./plugins/plugins'); const { + fetchRelease, fetchReleases, } = require('./releases'); +const { fetchActor } = require('./actors'); + function initServer() { const app = express(); const router = Router(); @@ -61,6 +64,9 @@ function initServer() { router.use(bodyParser.json({ strict: false })); router.get('/api/releases', fetchReleases); + router.get('/api/releases/:releaseId', fetchRelease); + + router.get('/api/actors/:actorId', fetchActor); router.get('*', (req, res) => { res.render(path.join(__dirname, '../../assets/index.ejs'), {