forked from DebaucheryLibrarian/traxxx
185 lines
4.5 KiB
JavaScript
185 lines
4.5 KiB
JavaScript
'use strict';
|
|
|
|
const knex = require('./knex');
|
|
const slugify = require('./utils/slugify');
|
|
const bulkInsert = require('./utils/bulk-insert');
|
|
|
|
function curateTagMedia(media) {
|
|
if (!media) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
id: media.id,
|
|
path: media.path,
|
|
thumbnail: media.thumbnail,
|
|
lazy: media.lazy,
|
|
comment: media.comment,
|
|
credit: media.credit,
|
|
};
|
|
}
|
|
|
|
function curateTag(tag) {
|
|
if (!tag) {
|
|
return null;
|
|
}
|
|
|
|
const curatedTag = {
|
|
id: tag.id,
|
|
name: tag.name,
|
|
slug: tag.slug,
|
|
description: tag.description,
|
|
priority: tag.priority,
|
|
group: curateTag(tag.group),
|
|
aliasFor: curateTag(tag.alias),
|
|
aliases: (tag.aliases || []).map(aliasTag => curateTag(aliasTag)),
|
|
};
|
|
|
|
if (tag.poster) {
|
|
curatedTag.poster = curateTagMedia(tag.poster);
|
|
}
|
|
|
|
if (tag.photos) {
|
|
curatedTag.photos = tag.photos.map(photo => curateTagMedia(photo));
|
|
}
|
|
|
|
return curatedTag;
|
|
}
|
|
|
|
function withRelations(queryBuilder, withMedia) {
|
|
queryBuilder
|
|
.select(knex.raw(`
|
|
tags.*,
|
|
COALESCE(json_agg(DISTINCT aliases) FILTER (WHERE aliases.id IS NOT NULL), '[]') as aliases,
|
|
row_to_json(tags_groups) as group,
|
|
row_to_json(roots) as alias
|
|
`))
|
|
.leftJoin('tags_groups', 'tags_groups.id', 'tags.group_id')
|
|
.leftJoin('tags as roots', 'roots.id', 'tags.alias_for')
|
|
.leftJoin('tags as aliases', 'aliases.alias_for', 'tags.id')
|
|
.groupBy('tags.id', 'tags_groups.id', 'roots.id');
|
|
|
|
if (withMedia) {
|
|
queryBuilder
|
|
.select(knex.raw(`
|
|
row_to_json(posters) as poster,
|
|
COALESCE(json_agg(DISTINCT photos) FILTER (WHERE photos.id IS NOT NULL), '[]') as photos
|
|
`))
|
|
.leftJoin('tags_posters', 'tags_posters.tag_id', 'tags.id')
|
|
.leftJoin('tags_photos', 'tags_photos.tag_id', 'tags.id')
|
|
.leftJoin('media as posters', 'posters.id', 'tags_posters.media_id')
|
|
.leftJoin('media as photos', 'photos.id', 'tags_photos.media_id')
|
|
.groupBy('posters.id');
|
|
}
|
|
}
|
|
|
|
async function matchReleaseTags(releases) {
|
|
const rawTags = releases
|
|
.map(release => release.tags).flat()
|
|
.filter(Boolean);
|
|
|
|
const casedTags = [...new Set(
|
|
rawTags
|
|
.concat(rawTags.map(tag => tag.toLowerCase()))
|
|
.concat(rawTags.map(tag => tag.toUpperCase())),
|
|
)];
|
|
|
|
const tagEntries = await knex('tags')
|
|
.select('tags.id', 'tags.name', 'tags.alias_for')
|
|
.whereIn('tags.name', casedTags);
|
|
|
|
const tagIdsBySlug = tagEntries
|
|
.reduce((acc, tag) => ({
|
|
...acc,
|
|
[slugify(tag.name)]: tag.alias_for || tag.id,
|
|
}), {});
|
|
|
|
return tagIdsBySlug;
|
|
}
|
|
|
|
async function getEntityTags(releases) {
|
|
const entityIds = releases.map(release => release.entity?.id).filter(Boolean);
|
|
const entityTags = await knex('entities_tags').whereIn('entity_id', entityIds);
|
|
|
|
const entityTagIdsByEntityId = entityTags.reduce((acc, entityTag) => {
|
|
if (!acc[entityTag.entity_id]) {
|
|
acc[entityTag.entity_id] = [];
|
|
}
|
|
|
|
acc[entityTag.entity_id].push(entityTag.tag_id);
|
|
|
|
return acc;
|
|
}, {});
|
|
|
|
return entityTagIdsByEntityId;
|
|
}
|
|
|
|
function buildReleaseTagAssociations(releases, tagIdsBySlug, entityTagIdsByEntityId, type) {
|
|
const tagAssociations = releases
|
|
.map((release) => {
|
|
const entityTagIds = entityTagIdsByEntityId[release.entity?.id] || [];
|
|
const releaseTags = release.tags || [];
|
|
|
|
const releaseTagIds = releaseTags.every(tag => typeof tag === 'number')
|
|
? releaseTags // obsolete scraper returned pre-matched tags
|
|
: releaseTags.map(tag => tagIdsBySlug[slugify(tag)]);
|
|
|
|
const tags = [...new Set(
|
|
// filter duplicates and empties
|
|
releaseTagIds
|
|
.concat(entityTagIds)
|
|
.filter(Boolean),
|
|
)]
|
|
.map(tagId => ({
|
|
[`${type}_id`]: release.id,
|
|
tag_id: tagId,
|
|
}));
|
|
|
|
return tags;
|
|
})
|
|
.flat();
|
|
|
|
return tagAssociations;
|
|
}
|
|
|
|
async function associateReleaseTags(releases, type = 'release') {
|
|
const tagIdsBySlug = await matchReleaseTags(releases);
|
|
const EntityTagIdsByEntityId = await getEntityTags(releases);
|
|
|
|
const tagAssociations = buildReleaseTagAssociations(releases, tagIdsBySlug, EntityTagIdsByEntityId, type);
|
|
|
|
await bulkInsert(`${type}s_tags`, tagAssociations, false);
|
|
}
|
|
|
|
async function fetchTag(tagId) {
|
|
const tag = await knex('tags')
|
|
.modify(queryBuilder => withRelations(queryBuilder, true))
|
|
.where((builder) => {
|
|
if (Number(tagId)) {
|
|
builder.where('tags.id', tagId);
|
|
return;
|
|
}
|
|
|
|
builder
|
|
.where('tags.name', tagId)
|
|
.orWhere('tags.slug', tagId);
|
|
})
|
|
.first();
|
|
|
|
return curateTag(tag);
|
|
}
|
|
|
|
async function fetchTags(limit = 100) {
|
|
const tags = await knex('tags')
|
|
.modify(queryBuilder => withRelations(queryBuilder, false))
|
|
.limit(limit);
|
|
|
|
return tags.map(tag => curateTag(tag));
|
|
}
|
|
|
|
module.exports = {
|
|
associateReleaseTags,
|
|
fetchTag,
|
|
fetchTags,
|
|
};
|