diff --git a/assets/components/movies/movies.vue b/assets/components/movies/movies.vue index 678079a8..f9e7b9d4 100644 --- a/assets/components/movies/movies.vue +++ b/assets/components/movies/movies.vue @@ -43,7 +43,7 @@ export default { .tiles { display: grid; - grid-template-columns: repeat(auto-fill, minmax(30rem, 1fr)); + grid-template-columns: repeat(auto-fill, minmax(25rem, 1fr)); grid-gap: 1rem; } diff --git a/assets/components/movies/tile.vue b/assets/components/movies/tile.vue index 45881d4e..d18533a3 100644 --- a/assets/components/movies/tile.vue +++ b/assets/components/movies/tile.vue @@ -12,16 +12,96 @@
+
+ + + + {{ movie.entity.name }} + + + {{ movie.entity.name }} + + {{ formatDate(movie.date, 'MMMM D, YYYY', movie.datePrecision) }} + + {{ `(${formatDate(movie.dateAdded, 'MMMM D, YYYY')})` }} +
+

{{ movie.title }}

+ + + +
- -
{{ movie.entity.name }}
@@ -41,7 +121,7 @@ export default { display: flex; flex-direction: column; background: var(--background); - box-shadow: 0 0 3px var(--darken); + box-shadow: 0 0 3px var(--darken-weak); font-size: 0; } @@ -55,6 +135,9 @@ export default { } .details { + display: flex; + justify-content: space-between; + align-items: center; color: var(--text-light); background: var(--profile); padding: .5rem 1rem; @@ -62,17 +145,101 @@ export default { font-weight: bold; } -.cover { - width: 12rem; +.entity-link, +.date { + display: flex; + align-items: center; + color: inherit; + font-size: inherit; + text-decoration: none; - img { - width: 100%; + .favicon { + width: 1rem; + height: 1rem; + margin: 0 .5rem 0 0; + } + + .icon { + fill: var(--highlight-weak); + margin: 0 .25rem 0 0; + } + + &:hover .icon { + fill: var(--text-light); } } +.cover { + height: 16rem; + box-shadow: 0 0 3px var(--darken-weak); + + img { + height: 100%; + max-width: 12rem; + object-fit: cover; + object-position: center ; + } +} + +.info { + flex-grow: 1; + overflow: hidden; +} + .title { + box-sizing: border-box; padding: 1rem; margin: 0; font-size: 1rem; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +.actors { + padding: 0 1rem; + margin: 0 0 1rem 0; + line-height: 1.5; +} + +.actor:not(:last-child)::after { + content: ','; + margin: 0 .25rem 0 0; + font-size: 1rem; +} + +.actor-link { + font-size: 1rem; + color: var(--link); + text-decoration: none; + + &:hover { + color: var(--primary); + } +} + +.tags { + padding: .2rem 1rem 0 1rem; + height: 1.75rem; + line-height: 2; + overflow: hidden; +} + +.tag { + margin: 0 0 .5rem 0; +} + +.tag-link { + background: var(--background); + font-size: .75rem; + padding: .25rem .5rem; + color: var(--shadow); + font-weight: bold; + text-decoration: none; + box-shadow: 0 0 3px var(--shadow-weak); + + &:hover { + color: var(--primary); + } } diff --git a/assets/components/tags/tag.vue b/assets/components/tags/tag.vue index 95b9829c..4f4434ad 100644 --- a/assets/components/tags/tag.vue +++ b/assets/components/tags/tag.vue @@ -123,7 +123,7 @@ export default { } .title { - padding: .75rem 1rem; + padding: .5rem 1rem; margin: 0; flex-shrink: 0; text-transform: capitalize; diff --git a/assets/js/curate.js b/assets/js/curate.js index d7deedef..0999a3ac 100644 --- a/assets/js/curate.js +++ b/assets/js/curate.js @@ -64,7 +64,7 @@ function curateRelease(release) { ...release, actors: [], poster: release.poster && release.poster.media, - tags: release.tags ? release.tags.map(({ tag }) => tag) : [], + tags: release.tags ? release.tags.map(tag => tag.tag || tag) : [], }; if (release.scenes) curatedRelease.scenes = release.scenes.map(({ scene }) => curateRelease(scene)); @@ -73,7 +73,7 @@ function curateRelease(release) { if (release.covers) curatedRelease.covers = release.covers.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.map(({ actor }) => curateActor(actor, curatedRelease)); + if (release.actors) curatedRelease.actors = release.actors.map(actor => curateActor(actor.actor || actor, curatedRelease)); if (release.movieTags && release.movieTags.length > 0) curatedRelease.tags = release.movieTags.map(({ tag }) => tag); if (release.movieActors && release.movieActors.length > 0) curatedRelease.actors = release.movieActors.map(({ actor }) => curateActor(actor, curatedRelease)); diff --git a/assets/js/releases/actions.js b/assets/js/releases/actions.js index 01403b1f..a24b2a7d 100644 --- a/assets/js/releases/actions.js +++ b/assets/js/releases/actions.js @@ -65,10 +65,27 @@ function initReleasesActions(store, _router) { slug date datePrecision + actors { + id + name + slug + } + tags { + id + name + slug + } entity { id name slug + type + parent { + id + name + slug + type + } } covers: moviesCoversByReleaseId { media { diff --git a/migrations/20190325001339_releases.js b/migrations/20190325001339_releases.js index de54d1a6..0d9271f0 100644 --- a/migrations/20190325001339_releases.js +++ b/migrations/20190325001339_releases.js @@ -958,6 +958,36 @@ exports.up = knex => Promise.resolve() END; $$ LANGUAGE SQL STABLE; + CREATE FUNCTION movies_actors(movie movies) RETURNS SETOF actors AS $$ + SELECT actors.* + FROM movies_scenes + LEFT JOIN + releases ON releases.id = movies_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 movies_scenes.movie_id = movie.id + AND actors.id IS NOT NULL + GROUP BY actors.id + ORDER BY actors.name, actors.gender + $$ LANGUAGE SQL STABLE; + + CREATE FUNCTION movies_tags(movie movies) RETURNS SETOF tags AS $$ + SELECT tags.* + FROM movies_scenes + LEFT JOIN + releases ON releases.id = movies_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 movies_scenes.movie_id = movie.id + AND tags.id IS NOT NULL + GROUP BY tags.id + ORDER BY tags.priority DESC + $$ LANGUAGE SQL STABLE; + CREATE FUNCTION releases_is_new(release releases) RETURNS boolean AS $$ SELECT EXISTS(SELECT true WHERE (SELECT id FROM batches ORDER BY created_at DESC LIMIT 1) = release.created_batch_id); $$ LANGUAGE sql STABLE; @@ -1038,6 +1068,9 @@ exports.down = (knex) => { // eslint-disable-line arrow-body-style DROP FUNCTION IF EXISTS actors_channels; DROP FUNCTION IF EXISTS actors_scenes; + DROP FUNCTION IF EXISTS movies_actors; + DROP FUNCTION IF EXISTS movies_tags; + DROP TEXT SEARCH CONFIGURATION IF EXISTS traxxx; DROP TEXT SEARCH DICTIONARY IF EXISTS traxxx_dict; `); diff --git a/public/img/tags/dap/8.jpeg b/public/img/tags/dap/8.jpeg new file mode 100644 index 00000000..7df0800e Binary files /dev/null and b/public/img/tags/dap/8.jpeg differ diff --git a/public/img/tags/dap/lazy/8.jpeg b/public/img/tags/dap/lazy/8.jpeg new file mode 100644 index 00000000..32b1028b Binary files /dev/null and b/public/img/tags/dap/lazy/8.jpeg differ diff --git a/public/img/tags/dap/thumbs/0.jpeg b/public/img/tags/dap/thumbs/0.jpeg index 287c6738..20afbf30 100644 Binary files a/public/img/tags/dap/thumbs/0.jpeg and b/public/img/tags/dap/thumbs/0.jpeg differ diff --git a/public/img/tags/dap/thumbs/1.jpeg b/public/img/tags/dap/thumbs/1.jpeg index 5a1629f7..31c7c7d3 100644 Binary files a/public/img/tags/dap/thumbs/1.jpeg and b/public/img/tags/dap/thumbs/1.jpeg differ diff --git a/public/img/tags/dap/thumbs/2.jpeg b/public/img/tags/dap/thumbs/2.jpeg index c73b2382..91718cfa 100644 Binary files a/public/img/tags/dap/thumbs/2.jpeg and b/public/img/tags/dap/thumbs/2.jpeg differ diff --git a/public/img/tags/dap/thumbs/3.jpeg b/public/img/tags/dap/thumbs/3.jpeg index 60494667..f99425b5 100644 Binary files a/public/img/tags/dap/thumbs/3.jpeg and b/public/img/tags/dap/thumbs/3.jpeg differ diff --git a/public/img/tags/dap/thumbs/4.jpeg b/public/img/tags/dap/thumbs/4.jpeg index 2b7ccae5..e352334a 100644 Binary files a/public/img/tags/dap/thumbs/4.jpeg and b/public/img/tags/dap/thumbs/4.jpeg differ diff --git a/public/img/tags/dap/thumbs/5.jpeg b/public/img/tags/dap/thumbs/5.jpeg index 5318dbdc..6afe7d9a 100644 Binary files a/public/img/tags/dap/thumbs/5.jpeg and b/public/img/tags/dap/thumbs/5.jpeg differ diff --git a/public/img/tags/dap/thumbs/6.jpeg b/public/img/tags/dap/thumbs/6.jpeg index ea6382ef..b03a62af 100644 Binary files a/public/img/tags/dap/thumbs/6.jpeg and b/public/img/tags/dap/thumbs/6.jpeg differ diff --git a/public/img/tags/dap/thumbs/7.jpeg b/public/img/tags/dap/thumbs/7.jpeg index 05421d91..e71fe80d 100644 Binary files a/public/img/tags/dap/thumbs/7.jpeg and b/public/img/tags/dap/thumbs/7.jpeg differ diff --git a/public/img/tags/dap/thumbs/8.jpeg b/public/img/tags/dap/thumbs/8.jpeg new file mode 100644 index 00000000..149da6ab Binary files /dev/null and b/public/img/tags/dap/thumbs/8.jpeg differ diff --git a/public/img/tags/dap/thumbs/poster.jpeg b/public/img/tags/dap/thumbs/poster.jpeg index ce429ede..20a48b7e 100644 Binary files a/public/img/tags/dap/thumbs/poster.jpeg and b/public/img/tags/dap/thumbs/poster.jpeg differ diff --git a/seeds/04_media.js b/seeds/04_media.js index 947baf95..bc8c5e2e 100644 --- a/seeds/04_media.js +++ b/seeds/04_media.js @@ -686,6 +686,7 @@ const tagPhotos = [ ['dap', 2, 'Lana Rhoades in "Lana Rhoades Unleashed" for HardX'], ['dap', 6, 'Sheena Shaw in "Ass Worship 14" for Jules Jordan'], ['dap', 5, 'Riley Reid in "The Gangbang of Riley Reid" for Jules Jordan'], + ['dap', 8, 'Lady Gang in SZ2478 LegalPorno'], ['dap', 'poster', 'Haley Reed in "Young Hot Ass" for Evil Angel'], ['dap', 0, 'Nicole Black doing double anal during a gangbang in GIO971 for LegalPorno'], ['dap', 1, 'Ria Sunn in SZ1801 for LegalPorno'], diff --git a/src/app.js b/src/app.js index 38c615ac..3413ced6 100644 --- a/src/app.js +++ b/src/app.js @@ -49,12 +49,14 @@ async function init() { } if (argv.save) { - if (deepScenes.length + deepMovieScenes.length > 0) { - await storeScenes(deepScenes.concat(deepMovieScenes)); + if (deepScenes.length > 0 || deepMovieScenes.length > 0) { + await storeScenes(deepScenes || []); } if (deepMovies.length > 0) { - await storeMovies(deepMovies); + const storedMovieScenes = await storeScenes(deepMovieScenes); + + await storeMovies(deepMovies, storedMovieScenes); } } diff --git a/src/deep.js b/src/deep.js index 49a6cda2..1916b99c 100644 --- a/src/deep.js +++ b/src/deep.js @@ -165,7 +165,9 @@ async function fetchScenes(baseReleasesOrUrls) { } async function fetchMovies(baseReleasesOrUrls) { - return fetchReleases(baseReleasesOrUrls, 'movie'); + const movies = await fetchReleases(baseReleasesOrUrls, 'movie'); + + return movies; } module.exports = { diff --git a/src/store-releases.js b/src/store-releases.js index a9c1671b..08bc4576 100644 --- a/src/store-releases.js +++ b/src/store-releases.js @@ -255,7 +255,38 @@ async function storeScenes(releases) { return releasesWithId; } -async function storeMovies(movies) { +async function associateMovieScenes(movies, movieScenes) { + const movieScenesByEntityIdAndEntryId = movieScenes.reduce((acc, scene) => ({ + ...acc, + [scene.entity.id]: { + ...acc[scene.entity.id], + [scene.entryId]: scene, + }, + }), {}); + + const associations = movies.map((movie) => { + if (!movie.scenes) { + return null; + } + + return movie.scenes.map((scene) => { + const movieScene = movieScenesByEntityIdAndEntryId[movie.entity.id]?.[scene.entryId]; + + if (movieScene) { + return { + movie_id: movie.id, + scene_id: movieScene.id, + }; + } + + return null; + }); + }).flat().filter(Boolean); + + await knex.batchInsert('movies_scenes', associations); +} + +async function storeMovies(movies, movieScenes) { const { uniqueReleases } = await filterDuplicateReleases(movies); const [batchId] = await knex('batches').insert({ comment: null }).returning('id'); @@ -264,6 +295,7 @@ async function storeMovies(movies) { const moviesWithId = attachReleaseIds(movies, storedMovies); + await associateMovieScenes(moviesWithId, movieScenes); await associateReleaseMedia(moviesWithId, 'movies'); return storedMovies; diff --git a/src/updates.js b/src/updates.js index 9ec3d642..8e3dcea1 100644 --- a/src/updates.js +++ b/src/updates.js @@ -177,7 +177,7 @@ async function scrapeMovies(scraper, entity) { } async function scrapeChannelReleases(scraper, channelEntity, preData) { - const [latestReleases, upcomingReleases] = await Promise.all([ + const [latestReleases, upcomingReleases, movies] = await Promise.all([ argv.latest ? scrapeLatestReleases(scraper, channelEntity, preData) : [], @@ -189,6 +189,8 @@ async function scrapeChannelReleases(scraper, channelEntity, preData) { : [], ]); + console.log(movies); + logger.info(`Fetching ${latestReleases.length} latest and ${upcomingReleases.length} upcoming updates for '${channelEntity.name}' (${channelEntity.parent?.name})`); return [...latestReleases, ...upcomingReleases];