Storing directors.
|  | @ -68,6 +68,20 @@ | |||
| 				:tags="release.tags" | ||||
| 			/> | ||||
| 
 | ||||
| 			<div | ||||
| 				v-if="release.directors" | ||||
| 				class="row" | ||||
| 			> | ||||
| 				<span class="row-label">Director</span> | ||||
| 
 | ||||
| 				<router-link | ||||
| 					v-for="director in release.directors" | ||||
| 					:key="`director-${director.id}`" | ||||
| 					class="link director" | ||||
| 					:to="`/director/${director.id}/${director.slug}`" | ||||
| 				>{{ director.name }}</router-link> | ||||
| 			</div> | ||||
| 
 | ||||
| 			<div | ||||
| 				v-if="release.movies && release.movies.length > 0" | ||||
| 				class="row" | ||||
|  | @ -423,6 +437,10 @@ export default { | |||
|     color: var(--link); | ||||
|     text-decoration: none; | ||||
| 
 | ||||
| 	&.director:not(:last-child)::after { | ||||
| 		content: ', '; | ||||
| 	} | ||||
| 
 | ||||
|     &:hover { | ||||
|         color: var(--primary); | ||||
| 
 | ||||
|  |  | |||
|  | @ -75,6 +75,7 @@ function curateRelease(release) { | |||
| 	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)); | ||||
| 	if (release.directors) curatedRelease.directors = release.directors.filter(Boolean).map(director => curateActor(director.director || director, curatedRelease)); | ||||
| 	if (release.movieTags && release.movieTags.length > 0) curatedRelease.tags = release.movieTags.filter(Boolean).map(({ tag }) => tag); | ||||
| 	if (release.movieActors && release.movieActors.length > 0) curatedRelease.actors = release.movieActors.filter(Boolean).map(({ actor }) => curateActor(actor, curatedRelease)); | ||||
| 
 | ||||
|  |  | |||
|  | @ -82,6 +82,14 @@ const releaseActorsFragment = ` | |||
|     } | ||||
| `;
 | ||||
| 
 | ||||
| const releaseDirectorFragment = ` | ||||
|     directors: releasesDirectors(orderBy: ACTOR_BY_DIRECTOR_ID__NAME_ASC) { | ||||
|       director { | ||||
|           ${actorFields} | ||||
|       } | ||||
|     } | ||||
| `;
 | ||||
| 
 | ||||
| const releaseTagsFragment = ` | ||||
|     tags: releasesTags(orderBy: TAG_BY_TAG_ID__PRIORITY_DESC) { | ||||
|       tag { | ||||
|  | @ -261,6 +269,7 @@ const releaseFragment = ` | |||
| 	comment | ||||
|     url | ||||
|     ${releaseActorsFragment} | ||||
|     ${releaseDirectorFragment} | ||||
|     ${releaseTagsFragment} | ||||
|     ${releasePosterFragment} | ||||
|     ${releasePhotosFragment} | ||||
|  |  | |||
|  | @ -67,6 +67,23 @@ const routes = [ | |||
| 		component: Actor, | ||||
| 		name: 'actorRange', | ||||
| 	}, | ||||
| 	{ | ||||
| 		path: '/director/:actorId/:actorSlug', | ||||
| 		name: 'director', | ||||
| 		redirect: from => ({ | ||||
| 			name: 'directorRange', | ||||
| 			params: { | ||||
| 				...from.params, | ||||
| 				range: 'latest', | ||||
| 				pageNumber: 1, | ||||
| 			}, | ||||
| 		}), | ||||
| 	}, | ||||
| 	{ | ||||
| 		path: '/director/:actorId/:actorSlug', | ||||
| 		component: Actor, | ||||
| 		name: 'directorRange', | ||||
| 	}, | ||||
| 	{ | ||||
| 		path: '/channel/:entitySlug', | ||||
| 		redirect: from => ({ | ||||
|  |  | |||
|  | @ -599,20 +599,6 @@ exports.up = knex => Promise.resolve() | |||
| 		table.datetime('created_at') | ||||
| 			.defaultTo(knex.fn.now()); | ||||
| 	})) | ||||
| 	.then(() => knex.schema.createTable('directors', (table) => { | ||||
| 		table.increments('id', 12); | ||||
| 
 | ||||
| 		table.text('name'); | ||||
| 		table.integer('alias_for', 12) | ||||
| 			.references('id') | ||||
| 			.inTable('directors'); | ||||
| 
 | ||||
| 		table.text('slug', 32) | ||||
| 			.unique(); | ||||
| 
 | ||||
| 		table.datetime('created_at') | ||||
| 			.defaultTo(knex.fn.now()); | ||||
| 	})) | ||||
| 	.then(() => knex.schema.createTable('releases', (table) => { | ||||
| 		table.increments('id', 16); | ||||
| 
 | ||||
|  | @ -702,12 +688,21 @@ exports.up = knex => Promise.resolve() | |||
| 			.inTable('releases') | ||||
| 			.onDelete('cascade'); | ||||
| 
 | ||||
| 		table.integer('director_id', 8) | ||||
| 		table.integer('director_id', 12) | ||||
| 			.notNullable() | ||||
| 			.references('id') | ||||
| 			.inTable('directors'); | ||||
| 			.inTable('actors') | ||||
| 			.onDelete('cascade'); | ||||
| 
 | ||||
| 		table.integer('alias_id', 12) | ||||
| 			.references('id') | ||||
| 			.inTable('actors') | ||||
| 			.onDelete('cascade'); | ||||
| 
 | ||||
| 		table.unique(['release_id', 'director_id']); | ||||
| 
 | ||||
| 		table.datetime('created_at') | ||||
| 			.defaultTo(knex.fn.now()); | ||||
| 	})) | ||||
| 	.then(() => knex.schema.createTable('releases_posters', (table) => { | ||||
| 		table.integer('release_id', 16) | ||||
|  | @ -1236,7 +1231,6 @@ exports.down = (knex) => { // eslint-disable-line arrow-body-style | |||
| 		DROP TABLE IF EXISTS chapters CASCADE; | ||||
| 		DROP TABLE IF EXISTS releases CASCADE; | ||||
| 		DROP TABLE IF EXISTS actors CASCADE; | ||||
| 		DROP TABLE IF EXISTS directors CASCADE; | ||||
| 		DROP TABLE IF EXISTS tags CASCADE; | ||||
| 		DROP TABLE IF EXISTS tags_groups CASCADE; | ||||
| 		DROP TABLE IF EXISTS social CASCADE; | ||||
|  |  | |||
| Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 1.4 KiB | 
| After Width: | Height: | Size: 1.4 KiB | 
| After Width: | Height: | Size: 1.4 KiB | 
| After Width: | Height: | Size: 709 B | 
| Before Width: | Height: | Size: 640 B After Width: | Height: | Size: 640 B | 
|  | @ -2,18 +2,33 @@ | |||
|     "name": "traxxx", | ||||
|     "short_name": "traxxx", | ||||
|     "icons": [ | ||||
|         { | ||||
|             "src": "/img/favicon/android-chrome-32x32.png", | ||||
|             "sizes": "32x32", | ||||
|             "type": "image/png" | ||||
|         }, | ||||
|         { | ||||
|             "src": "/img/favicon/android-chrome-192x192.png", | ||||
|             "sizes": "192x192", | ||||
|             "type": "image/png" | ||||
|         }, | ||||
|         { | ||||
|             "src": "/img/favicon/android-chrome-194x194.png", | ||||
|             "sizes": "194x194", | ||||
|             "type": "image/png" | ||||
|         }, | ||||
|         { | ||||
|             "src": "/img/favicon/android-chrome-196x196.png", | ||||
|             "sizes": "194x194", | ||||
|             "type": "image/png" | ||||
|         }, | ||||
|         { | ||||
|             "src": "/img/favicon/android-chrome-512x512.png", | ||||
|             "sizes": "512x512", | ||||
|             "type": "image/png" | ||||
|         } | ||||
|     ], | ||||
|     "theme_color": "#ffffff", | ||||
|     "theme_color": "#ff6c88", | ||||
|     "background_color": "#ffffff", | ||||
|     "display": "standalone" | ||||
| } | ||||
|  |  | |||
| After Width: | Height: | Size: 148 KiB | 
| After Width: | Height: | Size: 7.7 KiB | 
| After Width: | Height: | Size: 41 KiB | 
|  | @ -841,6 +841,7 @@ const tagPhotos = [ | |||
| 	['fake-cum', 3, 'Alexia Anders in "Thanksgiving Creampies" for Cum 4K'], | ||||
| 	['fake-cum', 0, 'Jynx Maze for Cumshot Surprise (Porn Pros)'], | ||||
| 	['fake-cum', 1, 'Ricki White for Fucked Up Facials'], | ||||
| 	['fake-cum', 4, 'Vina Sky in "Creaming Her Pipes" for Anal 4K'], | ||||
| 	['femdom', 1, 'Little Caprice in "Femdom" for Little Caprice Dreams'], | ||||
| 	['fingering', 2, 'Kylie Page and Hadley Viscara in "Busty Blonde Bombshells" for LesbianX'], | ||||
| 	['fingering', 0, 'Ashly Anderson in "Rough Love" for Hookup Hotshot'], | ||||
|  |  | |||
|  | @ -760,8 +760,6 @@ async function scrapeActors(argNames) { | |||
| 	const actorNames = await getActorNames(argNames); | ||||
| 	const baseActors = toBaseActors(actorNames); | ||||
| 
 | ||||
| 	console.log(baseActors); | ||||
| 
 | ||||
| 	logger.info(`Scraping profiles for ${actorNames.length} actors`); | ||||
| 
 | ||||
| 	const sources = argv.profileSources || config.profiles || Object.keys(scrapers.actors); | ||||
|  | @ -898,13 +896,17 @@ async function getOrCreateActors(baseActors, batchId) { | |||
| 	return existingActors; | ||||
| } | ||||
| 
 | ||||
| async function associateActors(releases, batchId) { | ||||
| async function associatePeople(releases, batchId, type = 'actor') { | ||||
| 	try { | ||||
| 		const baseActorsByReleaseId = releases.reduce((acc, release) => { | ||||
| 			if (release.actors) { | ||||
| 			if (type === 'actors' && release.actors) { | ||||
| 				acc[release.id] = toBaseActors(release.actors, release); | ||||
| 			} | ||||
| 
 | ||||
| 			if (type === 'directors' && release.director) { | ||||
| 				acc[release.id] = toBaseActors([release.director], release); | ||||
| 			} | ||||
| 
 | ||||
| 			return acc; | ||||
| 		}, {}); | ||||
| 
 | ||||
|  | @ -922,6 +924,11 @@ async function associateActors(releases, batchId) { | |||
| 		const uniqueBaseActors = Object.values(baseActorsBySlug); | ||||
| 		const actors = await getOrCreateActors(uniqueBaseActors, batchId); | ||||
| 
 | ||||
| 		const personKey = ({ | ||||
| 			actors: 'actor_id', | ||||
| 			directors: 'director_id', | ||||
| 		})[type]; | ||||
| 
 | ||||
| 		const actorIdsByEntityIdEntryIdAndSlug = actors.reduce((acc, actor) => ({ | ||||
| 			...acc, | ||||
| 			[actor.entity_id]: { | ||||
|  | @ -929,7 +936,7 @@ async function associateActors(releases, batchId) { | |||
| 				[actor.entry_id]: { | ||||
| 					...acc[actor.entity_id]?.[actor.entry_id], | ||||
| 					[actor.slug]: { | ||||
| 						actor_id: actor.alias_for || actor.id, | ||||
| 						[personKey]: actor.alias_for || actor.id, | ||||
| 						alias_id: actor.alias_for ? actor.id : null, | ||||
| 					}, | ||||
| 				}, | ||||
|  | @ -944,15 +951,15 @@ async function associateActors(releases, batchId) { | |||
| 				}))) | ||||
| 			.flat(); | ||||
| 
 | ||||
| 		const validReleaseActorAssociations = releaseActorAssociations.filter(association => association.release_id && association.actor_id); | ||||
| 		const validReleaseActorAssociations = releaseActorAssociations.filter(association => association.release_id && association[personKey]); | ||||
| 
 | ||||
| 		if (releaseActorAssociations.length > validReleaseActorAssociations.length) { | ||||
| 			const invalidReleaseActorAssociations = releaseActorAssociations.filter(association => !association.release_id || !association.actor_id); | ||||
| 			const invalidReleaseActorAssociations = releaseActorAssociations.filter(association => !association.release_id || !association[personKey]); | ||||
| 
 | ||||
| 			logger.error(invalidReleaseActorAssociations); | ||||
| 		} | ||||
| 
 | ||||
| 		await bulkInsert('releases_actors', validReleaseActorAssociations, false); | ||||
| 		await bulkInsert(`releases_${type}`, validReleaseActorAssociations, false); | ||||
| 
 | ||||
| 		logger.verbose(`Associated ${releaseActorAssociations.length} actors to ${releases.length} scenes`); | ||||
| 
 | ||||
|  | @ -964,6 +971,14 @@ async function associateActors(releases, batchId) { | |||
| 	} | ||||
| } | ||||
| 
 | ||||
| async function associateActors(releases, batchId) { | ||||
| 	return associatePeople(releases, batchId, 'actors'); | ||||
| } | ||||
| 
 | ||||
| async function associateDirectors(releases, batchId) { | ||||
| 	return associatePeople(releases, batchId, 'directors'); | ||||
| } | ||||
| 
 | ||||
| async function fetchActor(actorId) { | ||||
| 	const actor = await knex('actors') | ||||
| 		.select(knex.raw(` | ||||
|  | @ -1081,6 +1096,7 @@ async function flushActors() { | |||
| 
 | ||||
| module.exports = { | ||||
| 	associateActors, | ||||
| 	associateDirectors, | ||||
| 	deleteActors, | ||||
| 	fetchActor, | ||||
| 	flushActors, | ||||
|  |  | |||
|  | @ -9,7 +9,7 @@ const slugify = require('./utils/slugify'); | |||
| const bulkInsert = require('./utils/bulk-insert'); | ||||
| const resolvePlace = require('./utils/resolve-place'); | ||||
| const { formatDate } = require('./utils/qu'); | ||||
| const { associateActors, scrapeActors, toBaseActors } = require('./actors'); | ||||
| const { associateActors, associateDirectors, scrapeActors, toBaseActors } = require('./actors'); | ||||
| const { associateReleaseTags } = require('./tags'); | ||||
| const { curateEntity } = require('./entities'); | ||||
| const { associateReleaseMedia } = require('./media'); | ||||
|  | @ -229,6 +229,7 @@ async function updateReleasesSearch(releaseIds) { | |||
|                 COALESCE(releases.shoot_id, '') || ' ' || | ||||
|                 COALESCE(TO_CHAR(releases.date, 'YYYY YY MM FMMM FMmonth mon DD FMDD'), '') || ' ' || | ||||
|                 STRING_AGG(COALESCE(actors.name, ''), ' ') || ' ' || | ||||
|                 STRING_AGG(COALESCE(directors.name, ''), ' ') || ' ' || | ||||
|                 STRING_AGG(COALESCE(tags.name, ''), ' ') || ' ' || | ||||
|                 STRING_AGG(COALESCE(tags_aliases.name, ''), ' ') | ||||
|             ) as document | ||||
|  | @ -236,8 +237,10 @@ async function updateReleasesSearch(releaseIds) { | |||
|         LEFT JOIN entities ON releases.entity_id = entities.id | ||||
|         LEFT JOIN entities AS parents ON parents.id = entities.parent_id | ||||
|         LEFT JOIN releases_actors AS local_actors ON local_actors.release_id = releases.id | ||||
|         LEFT JOIN releases_directors AS local_directors ON local_directors.release_id = releases.id | ||||
|         LEFT JOIN releases_tags AS local_tags ON local_tags.release_id = releases.id | ||||
|         LEFT JOIN actors ON local_actors.actor_id = actors.id | ||||
|         LEFT JOIN actors AS directors ON local_directors.director_id = directors.id | ||||
|         LEFT JOIN tags ON local_tags.tag_id = tags.id AND tags.priority >= 6 | ||||
|         LEFT JOIN tags as tags_aliases ON local_tags.tag_id = tags_aliases.alias_for AND tags_aliases.secondary = true | ||||
|         ${releaseIds ? 'WHERE releases.id = ANY(?)' : ''} | ||||
|  | @ -319,6 +322,7 @@ async function storeScenes(releases) { | |||
| 
 | ||||
| 	const [actors] = await Promise.all([ | ||||
| 		associateActors(releasesWithId, batchId), | ||||
| 		associateDirectors(releasesWithId, batchId), | ||||
| 		associateReleaseTags(releasesWithId), | ||||
| 		storeChapters(releasesWithId), | ||||
| 	]); | ||||
|  |  | |||