Changed scene year filter to range input.
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue';
|
import { computed, ref } from 'vue';
|
||||||
|
|
||||||
|
import Range from '#/components/form/range.vue';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
filters: {
|
filters: {
|
||||||
@@ -13,36 +15,86 @@ const props = defineProps({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['update']);
|
const emit = defineEmits(['update']);
|
||||||
const selected = ref(props.filters.years);
|
|
||||||
|
|
||||||
function updateYears() {
|
// values while dragging, before the change is committed and the scenes reload
|
||||||
emit('update', 'years', selected.value);
|
const dragging = ref(null);
|
||||||
|
|
||||||
|
const slider = computed(() => {
|
||||||
|
const aggYears = (props.years || []).map((year) => year.year);
|
||||||
|
const currentYear = new Date().getFullYear();
|
||||||
|
const min = aggYears.length > 0 ? Math.min(...aggYears) : currentYear - 1;
|
||||||
|
const max = Math.max(aggYears.length > 0 ? Math.max(...aggYears) : currentYear, min + 1); // keep at least one step
|
||||||
|
const range = props.filters.yearRange;
|
||||||
|
const list = props.filters.years || [];
|
||||||
|
|
||||||
|
// a year list from an older link is shown as the span it covers
|
||||||
|
const value = list.length > 0
|
||||||
|
? [Math.min(...list), Math.max(...list)]
|
||||||
|
: [range?.min ?? min, range?.max ?? max];
|
||||||
|
|
||||||
|
return {
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
value,
|
||||||
|
positions: [flipYear(value[1], min, max), flipYear(value[0], min, max)],
|
||||||
|
display: dragging.value || value,
|
||||||
|
isFiltered: !!range || list.length > 0,
|
||||||
|
mountKey: `${min}-${max}-${value.join('-')}`, // remount when the bounds or filter change, the slider only reads its value on mount
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// the slider runs from newest on the left to oldest on the right, by mirroring the years onto the ascending slider positions
|
||||||
|
// the same function converts both ways: years to positions and positions to years
|
||||||
|
function flipYear(yearOrPosition, min, max) {
|
||||||
|
return min + max - yearOrPosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearYears() {
|
function clearYears() {
|
||||||
selected.value = [];
|
emit('update', 'years', [], false);
|
||||||
emit('update', 'years', selected.value);
|
emit('update', 'yearRange', null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateRange([positionA, positionB], reload) {
|
||||||
|
const min = flipYear(positionB, slider.value.min, slider.value.max);
|
||||||
|
const max = flipYear(positionA, slider.value.min, slider.value.max);
|
||||||
|
|
||||||
|
if (!reload) {
|
||||||
|
dragging.value = [min, max];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dragging.value = null;
|
||||||
|
|
||||||
|
// ends at the slider limits are left open, so 'and later' keeps including new years, the full range means no filter
|
||||||
|
const range = {
|
||||||
|
min: min === slider.value.min ? null : min,
|
||||||
|
max: max === slider.value.max ? null : max,
|
||||||
|
};
|
||||||
|
|
||||||
|
emit('update', 'years', [], false); // replaces a year list from an older link
|
||||||
|
emit('update', 'yearRange', range.min === null && range.max === null ? null : range, true);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="years-container">
|
<div class="years-filter">
|
||||||
<select
|
<div class="years-range">
|
||||||
v-model="selected"
|
<Range
|
||||||
class="years nobar"
|
:key="slider.mountKey"
|
||||||
multiple
|
:min="slider.min"
|
||||||
@change="updateYears"
|
:max="slider.max"
|
||||||
|
:value="slider.positions"
|
||||||
|
:allow-enable="false"
|
||||||
|
@input="(positions) => updateRange(positions, false)"
|
||||||
|
@change="(positions) => updateRange(positions, true)"
|
||||||
>
|
>
|
||||||
<option
|
<template #start><span class="range-label">{{ slider.display[1] }}</span></template>
|
||||||
v-for="year in years"
|
<template #end><span class="range-label">{{ slider.display[0] }}</span></template>
|
||||||
:key="`year-${year.year}`"
|
</Range>
|
||||||
class="year"
|
</div>
|
||||||
:value="year.year"
|
|
||||||
>{{ year.year }}</option>
|
|
||||||
</select>
|
|
||||||
|
|
||||||
<Icon
|
<Icon
|
||||||
v-show="selected.length > 0"
|
v-show="slider.isFiltered"
|
||||||
icon="cross2"
|
icon="cross2"
|
||||||
class="clear"
|
class="clear"
|
||||||
@click="clearYears"
|
@click="clearYears"
|
||||||
@@ -51,54 +103,22 @@ function clearYears() {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.years-container {
|
.years-filter {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
border-bottom: solid 1px var(--glass-weak-30);
|
border-bottom: solid 1px var(--glass-weak-30);
|
||||||
}
|
}
|
||||||
|
|
||||||
.years {
|
.years-range {
|
||||||
height: 2.5rem;
|
padding: .25rem 0;
|
||||||
padding: .5rem;
|
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
border: none;
|
|
||||||
overflow-x: auto;
|
|
||||||
overflow-y: hidden;
|
|
||||||
background: none;
|
|
||||||
color: var(--text);
|
|
||||||
|
|
||||||
&:focus .year:checked {
|
|
||||||
background: var(--primary) linear-gradient(0deg, var(--primary) 0%, var(--primary) 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.year {
|
|
||||||
display: inline-block;
|
|
||||||
padding: .25rem;
|
|
||||||
color: var(--text);
|
|
||||||
font-size: .8rem;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--primary);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:checked {
|
|
||||||
color: var(--primary);
|
|
||||||
background: none;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.clear {
|
.clear {
|
||||||
height: auto;
|
height: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 .5rem;
|
padding: 0 .5rem 0 0;
|
||||||
fill: var(--glass);
|
fill: var(--glass);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@@ -106,4 +126,13 @@ function clearYears() {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.range-label {
|
||||||
|
display: inline-block;
|
||||||
|
min-width: 2.5rem;
|
||||||
|
color: var(--glass-strong-10);
|
||||||
|
font-size: .8rem;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { get } from '#/src/api.js';
|
|||||||
import events from '#/src/events.js';
|
import events from '#/src/events.js';
|
||||||
import navigate from '#/src/navigate.js';
|
import navigate from '#/src/navigate.js';
|
||||||
import { getActorIdentifier, parseActorIdentifier } from '#/src/query.js';
|
import { getActorIdentifier, parseActorIdentifier } from '#/src/query.js';
|
||||||
|
import { formatYears, parseYears } from '#/utils/parse-range.js';
|
||||||
|
|
||||||
const pageContext = inject('pageContext');
|
const pageContext = inject('pageContext');
|
||||||
const { pageProps, routeParams, urlParsed } = pageContext;
|
const { pageProps, routeParams, urlParsed } = pageContext;
|
||||||
@@ -49,7 +50,7 @@ const queryEntity = networks[urlParsed.search.e] || channels[urlParsed.search.e]
|
|||||||
|
|
||||||
const filters = ref({
|
const filters = ref({
|
||||||
search: urlParsed.search.q,
|
search: urlParsed.search.q,
|
||||||
years: urlParsed.search.years?.split(',').map((year) => Number(year)).filter(Boolean) || [],
|
...parseYears(urlParsed.search.years),
|
||||||
tags: urlParsed.search.tags?.split(',').filter(Boolean) || [],
|
tags: urlParsed.search.tags?.split(',').filter(Boolean) || [],
|
||||||
entity: queryEntity,
|
entity: queryEntity,
|
||||||
actors: queryActors,
|
actors: queryActors,
|
||||||
@@ -103,7 +104,7 @@ async function search(options = {}) {
|
|||||||
|
|
||||||
navigate(getPath(scope.value, false), {
|
navigate(getPath(scope.value, false), {
|
||||||
...query,
|
...query,
|
||||||
years: filters.value.years.join(',') || undefined,
|
years: formatYears(filters.value),
|
||||||
actors: filters.value.actors.map((filterActor) => getActorIdentifier(filterActor)).join(',') || undefined, // don't include page actor ID in query, already a parameter
|
actors: filters.value.actors.map((filterActor) => getActorIdentifier(filterActor)).join(',') || undefined, // don't include page actor ID in query, already a parameter
|
||||||
tags: filters.value.tags.join(',') || undefined,
|
tags: filters.value.tags.join(',') || undefined,
|
||||||
e: filters.value.entity?.type === 'network' ? `_${filters.value.entity.slug}` : (filters.value.entity?.slug || undefined),
|
e: filters.value.entity?.type === 'network' ? `_${filters.value.entity.slug}` : (filters.value.entity?.slug || undefined),
|
||||||
@@ -111,7 +112,7 @@ async function search(options = {}) {
|
|||||||
|
|
||||||
const res = await get('/movies', {
|
const res = await get('/movies', {
|
||||||
...query,
|
...query,
|
||||||
years: filters.value.years.filter(Boolean).join(','), // if we're on an actor page, that actor ID needs to be included
|
years: formatYears(filters.value),
|
||||||
actors: [pageActor, ...filters.value.actors].filter(Boolean).map((filterActor) => getActorIdentifier(filterActor)).join(','), // if we're on an actor page, that actor ID needs to be included
|
actors: [pageActor, ...filters.value.actors].filter(Boolean).map((filterActor) => getActorIdentifier(filterActor)).join(','), // if we're on an actor page, that actor ID needs to be included
|
||||||
tags: [pageTag?.slug, ...filters.value.tags].filter(Boolean).join(','),
|
tags: [pageTag?.slug, ...filters.value.tags].filter(Boolean).join(','),
|
||||||
e: entitySlug,
|
e: entitySlug,
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import { get } from '#/src/api.js';
|
|||||||
import entityPrefixes from '#/src/entities-prefixes.js';
|
import entityPrefixes from '#/src/entities-prefixes.js';
|
||||||
import navigate from '#/src/navigate.js';
|
import navigate from '#/src/navigate.js';
|
||||||
import { getActorIdentifier, parseActorIdentifier } from '#/src/query.js';
|
import { getActorIdentifier, parseActorIdentifier } from '#/src/query.js';
|
||||||
import { formatRange, parseRange } from '#/utils/parse-range.js';
|
import { formatRange, formatYears, parseRange, parseYears } from '#/utils/parse-range.js';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
showFilters: {
|
showFilters: {
|
||||||
@@ -86,7 +86,7 @@ const queryEntity = networks[urlParsed.search.e] || channels[urlParsed.search.e]
|
|||||||
|
|
||||||
const filters = ref({
|
const filters = ref({
|
||||||
search: urlParsed.search.q,
|
search: urlParsed.search.q,
|
||||||
years: urlParsed.search.years?.split(',').filter(Boolean).map(Number) || [],
|
...parseYears(urlParsed.search.years),
|
||||||
tags: urlParsed.search.tags?.split(',').filter(Boolean) || [],
|
tags: urlParsed.search.tags?.split(',').filter(Boolean) || [],
|
||||||
onlyActorTags: Object.hasOwn(urlParsed.search, 'at'),
|
onlyActorTags: Object.hasOwn(urlParsed.search, 'at'),
|
||||||
entity: queryEntity,
|
entity: queryEntity,
|
||||||
@@ -153,7 +153,7 @@ async function search(options = {}) {
|
|||||||
|
|
||||||
navigate(getPath(scope.value, false), {
|
navigate(getPath(scope.value, false), {
|
||||||
...query,
|
...query,
|
||||||
years: filters.value.years.join(',') || undefined,
|
years: formatYears(filters.value),
|
||||||
actors: filters.value.actors.map((filterActor) => getActorIdentifier(filterActor)).join(',') || undefined, // don't include page actor ID in query, already a parameter
|
actors: filters.value.actors.map((filterActor) => getActorIdentifier(filterActor)).join(',') || undefined, // don't include page actor ID in query, already a parameter
|
||||||
tags: filters.value.tags.join(',') || undefined,
|
tags: filters.value.tags.join(',') || undefined,
|
||||||
at: (filters.value.tags.length > 0 && filters.value.onlyActorTags) || undefined,
|
at: (filters.value.tags.length > 0 && filters.value.onlyActorTags) || undefined,
|
||||||
@@ -163,7 +163,7 @@ async function search(options = {}) {
|
|||||||
|
|
||||||
const res = await get('/scenes', {
|
const res = await get('/scenes', {
|
||||||
...query,
|
...query,
|
||||||
years: filters.value.years.filter(Boolean).join(','), // if we're on an actor page, that actor ID needs to be included
|
years: formatYears(filters.value),
|
||||||
actors: [pageActor, ...filters.value.actors].filter(Boolean).map((filterActor) => getActorIdentifier(filterActor)).join(','), // if we're on an actor page, that actor ID needs to be included
|
actors: [pageActor, ...filters.value.actors].filter(Boolean).map((filterActor) => getActorIdentifier(filterActor)).join(','), // if we're on an actor page, that actor ID needs to be included
|
||||||
tags: [pageTag?.slug, ...filters.value.tags].filter(Boolean).join(','),
|
tags: [pageTag?.slug, ...filters.value.tags].filter(Boolean).join(','),
|
||||||
at: !!filters.value.onlyActorTags,
|
at: !!filters.value.onlyActorTags,
|
||||||
|
|||||||
+25
-2
@@ -289,6 +289,14 @@ async function queryManticoreSql(filters, options) {
|
|||||||
builder.whereIn('effective_year', filters.years);
|
builder.whereIn('effective_year', filters.years);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof filters.yearRange?.min === 'number') {
|
||||||
|
builder.where('effective_year', '>=', filters.yearRange.min);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof filters.yearRange?.max === 'number') {
|
||||||
|
builder.where('effective_year', '<=', filters.yearRange.max);
|
||||||
|
}
|
||||||
|
|
||||||
filters.tagIds?.forEach((tagId) => {
|
filters.tagIds?.forEach((tagId) => {
|
||||||
builder.where('any(tag_ids)', tagId);
|
builder.where('any(tag_ids)', tagId);
|
||||||
});
|
});
|
||||||
@@ -428,9 +436,24 @@ export async function fetchMovies(filters, rawOptions, reqUser, context) {
|
|||||||
console.log(options);
|
console.log(options);
|
||||||
console.log(filters);
|
console.log(filters);
|
||||||
|
|
||||||
const result = await queryManticoreSql(filters, options);
|
// an active year filter would narrow the year range to the filtered years, get the full range from a lightweight parallel query
|
||||||
|
const hasYearFilter = options.aggregateYears && (filters.years?.length > 0 || !!filters.yearRange);
|
||||||
|
|
||||||
const aggYears = options.aggregateYears && result.aggregations.years.map((bucket) => ({ year: bucket.key, count: bucket.doc_count }));
|
const yearsOptions = {
|
||||||
|
...options,
|
||||||
|
limit: 1,
|
||||||
|
page: 1,
|
||||||
|
aggregateActors: false,
|
||||||
|
aggregateTags: false,
|
||||||
|
aggregateChannels: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const [result, yearsResult] = await Promise.all([
|
||||||
|
queryManticoreSql(filters, options),
|
||||||
|
hasYearFilter ? queryManticoreSql({ ...filters, years: [], yearRange: null }, yearsOptions) : null,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const aggYears = options.aggregateYears && (yearsResult || result).aggregations.years.map((bucket) => ({ year: bucket.key, count: bucket.doc_count }));
|
||||||
|
|
||||||
const actorCounts = options.aggregateActors && countAggregations(result.aggregations?.actorIds);
|
const actorCounts = options.aggregateActors && countAggregations(result.aggregations?.actorIds);
|
||||||
const tagCounts = options.aggregateTags && countAggregations(result.aggregations?.tagIds);
|
const tagCounts = options.aggregateTags && countAggregations(result.aggregations?.tagIds);
|
||||||
|
|||||||
+17
-5
@@ -558,6 +558,14 @@ async function queryManticoreSql(filters, options, _reqUser) {
|
|||||||
builder.whereIn('effective_year', filters.years);
|
builder.whereIn('effective_year', filters.years);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof filters.yearRange?.min === 'number') {
|
||||||
|
builder.where('effective_year', '>=', filters.yearRange.min);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof filters.yearRange?.max === 'number') {
|
||||||
|
builder.where('effective_year', '<=', filters.yearRange.max);
|
||||||
|
}
|
||||||
|
|
||||||
filters.tagIds?.forEach((tagId) => {
|
filters.tagIds?.forEach((tagId) => {
|
||||||
if (filters.onlyActorTags) {
|
if (filters.onlyActorTags) {
|
||||||
builder.where((whereBuilder) => {
|
builder.where((whereBuilder) => {
|
||||||
@@ -776,18 +784,22 @@ export async function fetchScenes(filters, rawOptions, reqUser, context) {
|
|||||||
console.log('options', options);
|
console.log('options', options);
|
||||||
}
|
}
|
||||||
|
|
||||||
// an active count filter would narrow its own range to the filtered value, get the unfiltered ranges from a lightweight parallel query (~10ms vs 40-500ms for the main query with its actor and tag facets)
|
// an active count or year filter would narrow its own aggregation to the filtered values, get the unfiltered ranges from a lightweight parallel query (~10ms vs 40-500ms for the main query with its actor and tag facets)
|
||||||
|
// the range filters are stripped together, so with several active, each of their aggregations ignores the others
|
||||||
const activeCountKeys = options.aggregateActorCounts
|
const activeCountKeys = options.aggregateActorCounts
|
||||||
? Object.keys(actorCountFilters).filter((key) => filters[actorCountFilters[key]])
|
? Object.keys(actorCountFilters).filter((key) => filters[actorCountFilters[key]])
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const countsFilters = Object.fromEntries(Object.entries(filters).filter(([key]) => !Object.values(actorCountFilters).includes(key)));
|
const hasYearFilter = options.aggregateYears && (filters.years?.length > 0 || !!filters.yearRange);
|
||||||
|
const rangeFilterKeys = [...Object.values(actorCountFilters), 'years', 'yearRange'];
|
||||||
|
|
||||||
|
const countsFilters = Object.fromEntries(Object.entries(filters).filter(([key]) => !rangeFilterKeys.includes(key)));
|
||||||
|
|
||||||
const countsOptions = {
|
const countsOptions = {
|
||||||
...options,
|
...options,
|
||||||
limit: 1,
|
limit: 1,
|
||||||
page: 1,
|
page: 1,
|
||||||
aggregateYears: false,
|
aggregateYears: hasYearFilter,
|
||||||
aggregateActors: false,
|
aggregateActors: false,
|
||||||
aggregateTags: false,
|
aggregateTags: false,
|
||||||
aggregateChannels: false,
|
aggregateChannels: false,
|
||||||
@@ -797,12 +809,12 @@ export async function fetchScenes(filters, rawOptions, reqUser, context) {
|
|||||||
|
|
||||||
const [result, countsResult] = await Promise.all([
|
const [result, countsResult] = await Promise.all([
|
||||||
queryManticoreSql(filters, options, reqUser),
|
queryManticoreSql(filters, options, reqUser),
|
||||||
activeCountKeys.length > 0 ? queryManticoreSql(countsFilters, countsOptions, reqUser) : null,
|
activeCountKeys.length > 0 || hasYearFilter ? queryManticoreSql(countsFilters, countsOptions, reqUser) : null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
console.timeEnd('manticore sql');
|
console.timeEnd('manticore sql');
|
||||||
|
|
||||||
const aggYears = options.aggregateYears && result.aggregations.years.map((bucket) => ({ year: bucket.key, count: bucket.doc_count }));
|
const aggYears = options.aggregateYears && (hasYearFilter ? countsResult : result).aggregations.years.map((bucket) => ({ year: bucket.key, count: bucket.doc_count }));
|
||||||
const entityIds = options.aggregateChannels && [...(result.aggregations.channelIds || []), ...(result.aggregations.studioIds || [])];
|
const entityIds = options.aggregateChannels && [...(result.aggregations.channelIds || []), ...(result.aggregations.studioIds || [])];
|
||||||
|
|
||||||
// keyed by URL parameter, e.g. { group: [{ value: 2, count: 1234 }, ...], females: [...] }, sorted by value so the first and last bucket give the range
|
// keyed by URL parameter, e.g. { group: [{ value: 2, count: 1234 }, ...], females: [...] }, sorted by value so the first and last bucket give the range
|
||||||
|
|||||||
+2
-1
@@ -1,3 +1,4 @@
|
|||||||
|
import { parseYears } from '../../utils/parse-range.js';
|
||||||
import promiseProps from '../../utils/promise-props.js';
|
import promiseProps from '../../utils/promise-props.js';
|
||||||
import slugify from '../../utils/slugify.js';
|
import slugify from '../../utils/slugify.js';
|
||||||
import { getIdsBySlug } from '../cache.js';
|
import { getIdsBySlug } from '../cache.js';
|
||||||
@@ -10,7 +11,7 @@ export async function curateMoviesQuery(query) {
|
|||||||
return {
|
return {
|
||||||
scope: query.scope || 'latest',
|
scope: query.scope || 'latest',
|
||||||
query: query.q,
|
query: query.q,
|
||||||
years: query.years?.split(',')?.map((year) => Number(year)).filter(Boolean) || [],
|
...parseYears(query.years),
|
||||||
actorIds: [query.actorId, ...(query.actors?.split(',') || []).map((identifier) => parseActorIdentifier(identifier)?.id)].filter(Boolean),
|
actorIds: [query.actorId, ...(query.actors?.split(',') || []).map((identifier) => parseActorIdentifier(identifier)?.id)].filter(Boolean),
|
||||||
tagIds: await getIdsBySlug([query.tagSlug, ...(query.tags?.split(',') || [])], 'tags'),
|
tagIds: await getIdsBySlug([query.tagSlug, ...(query.tags?.split(',') || [])], 'tags'),
|
||||||
entityId: query.e ? await getIdsBySlug([query.e], 'entities').then(([id]) => id) : query.entityId,
|
entityId: query.e ? await getIdsBySlug([query.e], 'entities').then(([id]) => id) : query.entityId,
|
||||||
|
|||||||
+4
-3
@@ -1,6 +1,6 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
|
|
||||||
import { parseRange } from '../../utils/parse-range.js';
|
import { parseRange, parseYears } from '../../utils/parse-range.js';
|
||||||
|
|
||||||
import promiseProps from '../../utils/promise-props.js';
|
import promiseProps from '../../utils/promise-props.js';
|
||||||
import slugify from '../../utils/slugify.js';
|
import slugify from '../../utils/slugify.js';
|
||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
import { fetchStashByUsernameAndSlug } from '../stashes.js';
|
import { fetchStashByUsernameAndSlug } from '../stashes.js';
|
||||||
|
|
||||||
export async function curateScenesQuery(query) {
|
export async function curateScenesQuery(query) {
|
||||||
const splitYears = query.years?.split(',') || [];
|
const { years, yearRange } = parseYears(query.years);
|
||||||
const splitTags = query.tags?.split(',') || [];
|
const splitTags = query.tags?.split(',') || [];
|
||||||
const splitActors = query.actors?.split(',') || [];
|
const splitActors = query.actors?.split(',') || [];
|
||||||
const splitEntities = query.e?.split(',') || [];
|
const splitEntities = query.e?.split(',') || [];
|
||||||
@@ -39,7 +39,8 @@ export async function curateScenesQuery(query) {
|
|||||||
return {
|
return {
|
||||||
scope: query.scope || 'latest',
|
scope: query.scope || 'latest',
|
||||||
query: query.q,
|
query: query.q,
|
||||||
years: splitYears.map((year) => Number(year)).filter(Boolean) || [],
|
years,
|
||||||
|
yearRange,
|
||||||
actorIds: [query.actorId, ...splitActors.filter((actor) => actor.charAt(0) !== '!').map((identifier) => parseActorIdentifier(identifier)?.id)].filter(Boolean),
|
actorIds: [query.actorId, ...splitActors.filter((actor) => actor.charAt(0) !== '!').map((identifier) => parseActorIdentifier(identifier)?.id)].filter(Boolean),
|
||||||
notActorIds: splitActors.filter((actor) => actor.charAt(0) === '!').map((identifier) => parseActorIdentifier(identifier.slice(1))?.id).filter(Boolean),
|
notActorIds: splitActors.filter((actor) => actor.charAt(0) === '!').map((identifier) => parseActorIdentifier(identifier.slice(1))?.id).filter(Boolean),
|
||||||
actorCount: parseRange(query.group),
|
actorCount: parseRange(query.group),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { syncManticoreActors } from '../src/sync.js';
|
|||||||
async function init() {
|
async function init() {
|
||||||
if (argv.update) {
|
if (argv.update) {
|
||||||
await utilsApi.sql('drop table if exists actors');
|
await utilsApi.sql('drop table if exists actors');
|
||||||
|
// bigint rather than timestamp, which is unsigned 32-bit and wraps dates before 1970 into the 2100s
|
||||||
await utilsApi.sql(`create table actors(
|
await utilsApi.sql(`create table actors(
|
||||||
id int,
|
id int,
|
||||||
name text,
|
name text,
|
||||||
@@ -16,7 +17,7 @@ async function init() {
|
|||||||
entity_id int,
|
entity_id int,
|
||||||
alias_for int,
|
alias_for int,
|
||||||
gender string,
|
gender string,
|
||||||
date_of_birth timestamp,
|
date_of_birth bigint,
|
||||||
country string,
|
country string,
|
||||||
has_avatar bool,
|
has_avatar bool,
|
||||||
mass int,
|
mass int,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { syncManticoreMovies } from '../src/sync.js';
|
|||||||
async function init() {
|
async function init() {
|
||||||
if (argv.update) {
|
if (argv.update) {
|
||||||
await utilsApi.sql('drop table if exists movies');
|
await utilsApi.sql('drop table if exists movies');
|
||||||
|
// bigint rather than timestamp, which is unsigned 32-bit and wraps dates before 1970 into the 2100s
|
||||||
await utilsApi.sql(`create table movies (
|
await utilsApi.sql(`create table movies (
|
||||||
id int,
|
id int,
|
||||||
title text,
|
title text,
|
||||||
@@ -23,10 +24,10 @@ async function init() {
|
|||||||
tag_ids multi,
|
tag_ids multi,
|
||||||
tags text,
|
tags text,
|
||||||
meta text,
|
meta text,
|
||||||
date timestamp,
|
date bigint,
|
||||||
has_cover bool,
|
has_cover bool,
|
||||||
created_at timestamp,
|
created_at bigint,
|
||||||
effective_date timestamp,
|
effective_date bigint,
|
||||||
stashed int,
|
stashed int,
|
||||||
stashed_scenes int,
|
stashed_scenes int,
|
||||||
stashed_total int,
|
stashed_total int,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { syncManticoreScenes } from '../src/sync.js';
|
|||||||
async function init() {
|
async function init() {
|
||||||
if (argv.update) {
|
if (argv.update) {
|
||||||
await utilsApi.sql('drop table if exists scenes');
|
await utilsApi.sql('drop table if exists scenes');
|
||||||
|
// bigint rather than timestamp, which is unsigned 32-bit and wraps dates before 1970 into the 2100s
|
||||||
await utilsApi.sql(`create table scenes (
|
await utilsApi.sql(`create table scenes (
|
||||||
id int,
|
id int,
|
||||||
title text,
|
title text,
|
||||||
@@ -38,11 +39,11 @@ async function init() {
|
|||||||
serie_ids multi,
|
serie_ids multi,
|
||||||
series text,
|
series text,
|
||||||
meta text,
|
meta text,
|
||||||
date timestamp,
|
date bigint,
|
||||||
fingerprints text,
|
fingerprints text,
|
||||||
is_showcased bool,
|
is_showcased bool,
|
||||||
created_at timestamp,
|
created_at bigint,
|
||||||
effective_date timestamp,
|
effective_date bigint,
|
||||||
stashed int,
|
stashed int,
|
||||||
dupe_index int
|
dupe_index int
|
||||||
)`);
|
)`);
|
||||||
|
|||||||
@@ -38,3 +38,19 @@ export function formatRange(range) {
|
|||||||
|
|
||||||
return `${range.min ?? ''}-${range.max ?? ''}`;
|
return `${range.min ?? ''}-${range.max ?? ''}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// years are a range like 2015-2020 or 2020-, or a list like 2015,2019 from older links
|
||||||
|
export function parseYears(yearsString) {
|
||||||
|
if (yearsString?.includes('-')) {
|
||||||
|
return { years: [], yearRange: parseRange(yearsString) };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
years: yearsString?.split(',').map((year) => Number(year)).filter(Boolean) || [],
|
||||||
|
yearRange: null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatYears({ years, yearRange }) {
|
||||||
|
return formatRange(yearRange) || years?.filter(Boolean).join(',') || undefined;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user