forked from DebaucheryLibrarian/traxxx
Added tags and entities to REST API..
This commit is contained in:
parent
3d86e52b25
commit
e6c52002f0
104
src/entities.js
104
src/entities.js
|
@ -4,7 +4,6 @@ const config = require('config');
|
|||
|
||||
const argv = require('./argv');
|
||||
const knex = require('./knex');
|
||||
const whereOr = require('./utils/where-or');
|
||||
|
||||
function curateEntity(entity, includeParameters = false) {
|
||||
if (!entity) {
|
||||
|
@ -29,6 +28,15 @@ function curateEntity(entity, includeParameters = false) {
|
|||
}, includeParameters));
|
||||
}
|
||||
|
||||
if (entity.tags) {
|
||||
curatedEntity.tags = entity.tags.map(tag => ({
|
||||
id: tag.id,
|
||||
name: tag.name,
|
||||
slug: tag.slug,
|
||||
priority: tag.priority,
|
||||
}));
|
||||
}
|
||||
|
||||
return curatedEntity;
|
||||
}
|
||||
|
||||
|
@ -102,33 +110,89 @@ async function fetchIncludedEntities() {
|
|||
return curatedNetworks;
|
||||
}
|
||||
|
||||
async function fetchChannels(queryObject) {
|
||||
const sites = await knex('sites')
|
||||
.where(builder => whereOr(queryObject, 'sites', builder))
|
||||
.select(
|
||||
'sites.*',
|
||||
'networks.name as network_name', 'networks.slug as network_slug', 'networks.url as network_url', 'networks.description as network_description', 'networks.parameters as network_parameters',
|
||||
)
|
||||
.leftJoin('networks', 'sites.network_id', 'networks.id')
|
||||
.limit(100);
|
||||
async function fetchEntity(entityId, type) {
|
||||
const entity = await knex('entities')
|
||||
.select(knex.raw(`
|
||||
entities.*,
|
||||
COALESCE(json_agg(children) FILTER (WHERE children.id IS NOT NULL), '[]') as children,
|
||||
COALESCE(json_agg(tags) FILTER (WHERE tags.id IS NOT NULL), '[]') as tags,
|
||||
row_to_json(parents) as parent
|
||||
`))
|
||||
.modify((queryBuilder) => {
|
||||
if (Number(entityId)) {
|
||||
queryBuilder.where('entities.id', entityId);
|
||||
return;
|
||||
}
|
||||
|
||||
return curateEntities(sites);
|
||||
if (type) {
|
||||
queryBuilder
|
||||
.where('entities.slug', entityId)
|
||||
.where('entities.type', type);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error('Invalid ID or unspecified entity type');
|
||||
})
|
||||
.leftJoin('entities as parents', 'parents.id', 'entities.parent_id')
|
||||
.leftJoin('entities as children', 'children.parent_id', 'entities.id')
|
||||
.leftJoin('entities_tags', 'entities_tags.entity_id', 'entities.id')
|
||||
.leftJoin('tags', 'tags.id', 'entities_tags.tag_id')
|
||||
.groupBy('entities.id', 'parents.id')
|
||||
.first();
|
||||
|
||||
return curateEntity(entity);
|
||||
}
|
||||
|
||||
async function fetchChannelsFromReleases() {
|
||||
const sites = await knex('releases')
|
||||
.select('site_id', '')
|
||||
.leftJoin('sites', 'sites.id', 'releases.site_id')
|
||||
.groupBy('sites.id')
|
||||
.limit(100);
|
||||
async function fetchEntities(type, limit) {
|
||||
const entities = await knex('entities')
|
||||
.select(knex.raw(`
|
||||
entities.*,
|
||||
COALESCE(json_agg(tags) FILTER (WHERE tags.id IS NOT NULL), '[]') as tags,
|
||||
row_to_json(parents) as parent
|
||||
`))
|
||||
.modify((queryBuilder) => {
|
||||
if (type) {
|
||||
queryBuilder.where('entities.type', type);
|
||||
}
|
||||
})
|
||||
.leftJoin('entities as parents', 'parents.id', 'entities.parent_id')
|
||||
.leftJoin('entities_tags', 'entities_tags.entity_id', 'entities.id')
|
||||
.leftJoin('tags', 'tags.id', 'entities_tags.tag_id')
|
||||
.groupBy('entities.id', 'parents.id')
|
||||
.limit(limit || 100);
|
||||
|
||||
return curateEntities(sites);
|
||||
return curateEntities(entities);
|
||||
}
|
||||
|
||||
async function searchEntities(query, type, limit) {
|
||||
const entities = await knex
|
||||
.select(knex.raw(`
|
||||
entities.*,
|
||||
COALESCE(json_agg(tags) FILTER (WHERE tags.id IS NOT NULL), '[]') as tags,
|
||||
row_to_json(parents) as parent
|
||||
`))
|
||||
.from(knex.raw('search_entities(?) as entities', [query]))
|
||||
.modify((queryBuilder) => {
|
||||
if (type) {
|
||||
queryBuilder.where('entities.type', type);
|
||||
}
|
||||
})
|
||||
.leftJoin('entities as parents', 'parents.id', 'entities.parent_id')
|
||||
.leftJoin('entities_tags', 'entities_tags.entity_id', 'entities.id')
|
||||
.leftJoin('tags', 'tags.id', 'entities_tags.tag_id')
|
||||
.groupBy('entities.id', 'parents.id')
|
||||
.limit(limit || 100);
|
||||
|
||||
return curateEntities(entities);
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
curateEntity,
|
||||
curateEntities,
|
||||
fetchIncludedEntities,
|
||||
fetchChannels,
|
||||
fetchChannelsFromReleases,
|
||||
fetchEntity,
|
||||
fetchEntities,
|
||||
searchEntities,
|
||||
};
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
const knex = require('./knex');
|
||||
const whereOr = require('./utils/where-or');
|
||||
const { fetchSites } = require('./sites');
|
||||
|
||||
async function curateNetwork(network, includeParameters = false, includeSites = true, includeStudios = false) {
|
||||
const curatedNetwork = {
|
||||
id: network.id,
|
||||
name: network.name,
|
||||
url: network.url,
|
||||
description: network.description,
|
||||
slug: network.slug,
|
||||
parameters: includeParameters ? network.parameters : null,
|
||||
};
|
||||
|
||||
if (includeSites) {
|
||||
curatedNetwork.sites = await fetchSites({ network_id: network.id });
|
||||
}
|
||||
|
||||
if (includeStudios) {
|
||||
const studios = await knex('studios').where({ network_id: network.id });
|
||||
|
||||
curatedNetwork.studios = studios.map(studio => ({
|
||||
id: studio.id,
|
||||
name: studio.name,
|
||||
url: studio.url,
|
||||
description: studio.description,
|
||||
slug: studio.slug,
|
||||
}));
|
||||
}
|
||||
|
||||
return curatedNetwork;
|
||||
}
|
||||
|
||||
function curateNetworks(releases) {
|
||||
return Promise.all(releases.map(async release => curateNetwork(release)));
|
||||
}
|
||||
|
||||
async function findNetworkByUrl(url) {
|
||||
const { hostname } = new URL(url);
|
||||
const domain = hostname.replace(/^www./, '');
|
||||
|
||||
const network = await knex('networks')
|
||||
.where('networks.url', 'like', `%${domain}`)
|
||||
.orWhere('networks.url', url)
|
||||
.first();
|
||||
|
||||
if (network) {
|
||||
return curateNetwork(network, true);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function fetchNetworks(queryObject) {
|
||||
const releases = await knex('networks')
|
||||
.where(builder => whereOr(queryObject, 'networks', builder))
|
||||
.limit(100);
|
||||
|
||||
return curateNetworks(releases);
|
||||
}
|
||||
|
||||
async function fetchNetworksFromReleases() {
|
||||
const releases = await knex('releases')
|
||||
.select('site_id', '')
|
||||
.leftJoin('sites', 'sites.id', 'releases.site_id')
|
||||
.leftJoin('networks', 'networks.id', 'sites.network_id')
|
||||
.groupBy('networks.id')
|
||||
.limit(100);
|
||||
|
||||
return curateNetworks(releases);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
curateNetwork,
|
||||
curateNetworks,
|
||||
fetchNetworks,
|
||||
fetchNetworksFromReleases,
|
||||
findNetworkByUrl,
|
||||
};
|
97
src/tags.js
97
src/tags.js
|
@ -4,6 +4,75 @@ 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()
|
||||
|
@ -82,6 +151,34 @@ async function associateReleaseTags(releases, type = 'release') {
|
|||
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,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
'use strict';
|
||||
|
||||
const { fetchEntity, fetchEntities, searchEntities } = require('../entities');
|
||||
|
||||
async function fetchEntityApi(req, res, type) {
|
||||
const entity = await fetchEntity(req.params.entityId, type || req.query.type);
|
||||
|
||||
if (entity) {
|
||||
res.send({ entity });
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(404).send({ entity: null });
|
||||
}
|
||||
|
||||
async function fetchEntitiesApi(req, res, type) {
|
||||
const query = req.query.query || req.query.q;
|
||||
|
||||
const entities = query
|
||||
? await searchEntities(query, type || req.query.type, req.query.limit)
|
||||
: await fetchEntities(type || req.query.type, req.query.limit);
|
||||
|
||||
res.send({ entities });
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fetchEntity: fetchEntityApi,
|
||||
fetchEntities: fetchEntitiesApi,
|
||||
};
|
|
@ -1,26 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
const { fetchNetworks, fetchNetworksFromReleases } = require('../networks');
|
||||
|
||||
async function fetchNetworksApi(req, res) {
|
||||
const networkId = typeof req.params.networkId === 'number' ? req.params.networkId : undefined; // null will literally include NULL results
|
||||
const networkSlug = typeof req.params.networkId === 'string' ? req.params.networkId : undefined;
|
||||
|
||||
const networks = await fetchNetworks({
|
||||
id: networkId,
|
||||
slug: networkSlug,
|
||||
});
|
||||
|
||||
res.send(networks);
|
||||
}
|
||||
|
||||
async function fetchNetworksFromReleasesApi(req, res) {
|
||||
const networks = await fetchNetworksFromReleases();
|
||||
|
||||
res.send(networks);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fetchNetworks: fetchNetworksApi,
|
||||
fetchNetworksFromReleases: fetchNetworksFromReleasesApi,
|
||||
};
|
|
@ -26,6 +26,16 @@ const {
|
|||
fetchActors,
|
||||
} = require('./actors');
|
||||
|
||||
const {
|
||||
fetchEntity,
|
||||
fetchEntities,
|
||||
} = require('./entities');
|
||||
|
||||
const {
|
||||
fetchTag,
|
||||
fetchTags,
|
||||
} = require('./tags');
|
||||
|
||||
async function initServer() {
|
||||
const app = express();
|
||||
const router = Router();
|
||||
|
@ -78,6 +88,21 @@ async function initServer() {
|
|||
router.get('/api/actors', fetchActors);
|
||||
router.get('/api/actors/:actorId', fetchActor);
|
||||
|
||||
router.get('/api/entities', async (req, res) => fetchEntities(req, res, null));
|
||||
router.get('/api/entities/:entityId', async (req, res) => fetchEntity(req, res, null));
|
||||
|
||||
router.get('/api/channels', async (req, res) => fetchEntities(req, res, 'channel'));
|
||||
router.get('/api/channels/:entityId', async (req, res) => fetchEntity(req, res, 'channel'));
|
||||
|
||||
router.get('/api/networks', async (req, res) => fetchEntities(req, res, 'network'));
|
||||
router.get('/api/networks/:entityId', async (req, res) => fetchEntity(req, res, 'network'));
|
||||
|
||||
router.get('/api/studios', async (req, res) => fetchEntities(req, res, 'studio'));
|
||||
router.get('/api/studios/:entityId', async (req, res) => fetchEntity(req, res, 'studio'));
|
||||
|
||||
router.get('/api/tags', fetchTags);
|
||||
router.get('/api/tags/:tagId', fetchTag);
|
||||
|
||||
router.get('*', (req, res) => {
|
||||
res.render(path.join(__dirname, '../../assets/index.ejs'), {
|
||||
env: JSON.stringify({
|
||||
|
|
|
@ -1,40 +1,26 @@
|
|||
'use strict';
|
||||
|
||||
const { fetchTags } = require('../tags');
|
||||
const { fetchTag, fetchTags } = require('../tags');
|
||||
|
||||
async function fetchTagsApi(req, res) {
|
||||
const tagId = typeof req.params.tagId === 'number' ? req.params.tagId : undefined; // null will literally include NULL results
|
||||
const tagSlug = typeof req.params.tagId === 'string' ? req.params.tagId : undefined;
|
||||
async function fetchTagApi(req, res) {
|
||||
const tag = await fetchTag(req.params.tagId);
|
||||
|
||||
if (tagId || tagSlug) {
|
||||
const tags = await fetchTags({
|
||||
id: tagId,
|
||||
slug: tagSlug,
|
||||
}, null, req.query.limit);
|
||||
|
||||
if (tags.length > 0) {
|
||||
res.send(tags[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(404).send();
|
||||
if (tag) {
|
||||
res.send({ tag });
|
||||
return;
|
||||
}
|
||||
|
||||
const query = {};
|
||||
const groupsQuery = {};
|
||||
|
||||
if (req.query.priority) query.priority = req.query.priority.split(',');
|
||||
if (req.query.slug) query.slug = req.query.slug.split(',');
|
||||
if (req.query.group) {
|
||||
groupsQuery.slug = req.query.group.split(',');
|
||||
}
|
||||
|
||||
const tags = await fetchTags(query, groupsQuery, req.query.limit);
|
||||
|
||||
res.send(tags);
|
||||
res.status(404).send({ tag: null });
|
||||
}
|
||||
|
||||
async function fetchTagsApi(req, res) {
|
||||
const tags = await fetchTags(req.query.limit);
|
||||
|
||||
res.send({ tags });
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
fetchTag: fetchTagApi,
|
||||
fetchTags: fetchTagsApi,
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue