import knex from './knex.js'; import redis from './redis.js'; import initLogger from './logger.js'; import { curateEntity } from './entities.js'; const logger = initLogger(); function curateTag(tag, context) { return { id: tag.id, name: tag.name, slug: tag.slug, description: tag.description, priority: tag.priority, poster: tag.poster && { id: tag.poster.id, path: tag.poster.path, thumbnail: tag.poster.thumbnail, lazy: tag.poster.lazy, isS3: tag.poster.is_s3, comment: tag.poster.comment, entity: tag.poster.entity && curateEntity({ ...tag.poster.entity, parent: tag.poster.entity_parent, }), }, ...context?.append?.[tag.id], }; } export async function fetchTags(options = {}) { const [tags, posters] = await Promise.all([ knex('tags') .select('tags.*', 'tags_posters.media_id as poster_id') .modify((builder) => { if (!options.includeAliases) { builder.whereNull('alias_for'); } }), knex('tags_posters') .select('tags_posters.tag_id', 'media.*', knex.raw('row_to_json(entities) as entity'), knex.raw('row_to_json(parents) as entity_parent')) .leftJoin('media', 'media.id', 'tags_posters.media_id') .leftJoin('entities', 'entities.id', 'media.entity_id') .leftJoin('entities as parents', 'entities.id', 'entities.parent_id'), ]); const postersByTagId = Object.fromEntries(posters.map((poster) => [poster.tag_id, poster])); return tags.map((tagEntry) => curateTag({ ...tagEntry, poster: postersByTagId[tagEntry.id], })); } export async function fetchTagsById(tagIds, options = {}) { const [tags, posters] = await Promise.all([ knex('tags') .whereIn('tags.id', tagIds.filter((tagId) => typeof tagId === 'number')) .orWhereIn('tags.slug', tagIds.filter((tagId) => typeof tagId === 'string')) .modify((builder) => { if (options.order) { builder.orderBy(...options.order); } }), knex('tags_posters') .select('tags_posters.tag_id', 'media.*', knex.raw('row_to_json(entities) as entity'), knex.raw('row_to_json(parents) as entity_parent')) .leftJoin('tags', 'tags.id', 'tags_posters.tag_id') .leftJoin('media', 'media.id', 'tags_posters.media_id') .leftJoin('entities', 'entities.id', 'media.entity_id') .leftJoin('entities as parents', 'parents.id', 'entities.parent_id') .whereIn('tags.id', tagIds.filter((tagId) => typeof tagId === 'number')) .orWhereIn('tags.slug', tagIds.filter((tagId) => typeof tagId === 'string')), ]); const postersByTagId = Object.fromEntries(posters.map((poster) => [poster.tag_id, poster])); if (options.order) { return tags.map((tagEntry) => curateTag({ ...tagEntry, poster: postersByTagId[tagEntry.id], }, { append: options.append })); } const curatedTags = tagIds.map((tagId) => { const tag = tags.find((tagEntry) => (typeof tagId === 'string' ? tagEntry.slug === tagId : tagEntry.id === tagId)); if (!tag) { console.warn(`Can't match tag ${tagId}`); return null; } return curateTag({ ...tag, poster: postersByTagId[tag.id], }, { append: options.append }); }).filter(Boolean); return curatedTags; } export async function cacheTagIds() { const tags = await knex('tags') .select('id', 'slug') .whereNull('alias_for') .whereNotNull('slug'); await redis.del('traxxx:tags:id_by_slug'); await redis.hSet('traxxx:tags:id_by_slug', tags.map((tag) => [tag.slug, tag.id])); logger.info('Cached tag IDs by slug'); }