2022-08-18 18:38:42 +03:00
|
|
|
/**
|
|
|
|
* @typedef {Object} UrlService
|
2022-08-22 12:36:24 +03:00
|
|
|
* @prop {(resourceId: string, options) => Object} getResource
|
2022-08-19 23:39:18 +03:00
|
|
|
* @prop {(resourceId: string, options) => string} getUrlByResourceId
|
2023-05-02 23:43:47 +03:00
|
|
|
*
|
2022-08-18 18:38:42 +03:00
|
|
|
*/
|
|
|
|
|
|
|
|
/**
|
2022-08-24 17:11:25 +03:00
|
|
|
* Translate a url into, (id+type), or a resource, and vice versa
|
2022-08-18 18:38:42 +03:00
|
|
|
*/
|
|
|
|
class UrlTranslator {
|
|
|
|
/**
|
2023-05-02 23:43:47 +03:00
|
|
|
*
|
|
|
|
* @param {Object} deps
|
2022-08-18 18:38:42 +03:00
|
|
|
* @param {UrlService} deps.urlService
|
2022-08-22 18:16:18 +03:00
|
|
|
* @param {Object} deps.urlUtils
|
2022-08-19 23:39:18 +03:00
|
|
|
* @param {Object} deps.models
|
|
|
|
* @param {Object} deps.models.Post
|
|
|
|
* @param {Object} deps.models.Tag
|
|
|
|
* @param {Object} deps.models.User
|
2022-08-18 18:38:42 +03:00
|
|
|
*/
|
2022-08-22 18:16:18 +03:00
|
|
|
constructor({urlService, urlUtils, models}) {
|
2022-08-18 18:38:42 +03:00
|
|
|
this.urlService = urlService;
|
2022-08-22 18:16:18 +03:00
|
|
|
this.urlUtils = urlUtils;
|
2022-08-19 23:39:18 +03:00
|
|
|
this.models = models;
|
2022-08-18 18:38:42 +03:00
|
|
|
}
|
|
|
|
|
2022-08-22 18:16:18 +03:00
|
|
|
/**
|
|
|
|
* Convert root relative URLs to subdirectory relative URLs
|
|
|
|
*/
|
|
|
|
stripSubdirectoryFromPath(path) {
|
|
|
|
// Bit weird, but only way to do it with the urlUtils atm
|
|
|
|
|
|
|
|
// First convert path to an absolute path
|
|
|
|
const absolute = this.urlUtils.relativeToAbsolute(path);
|
|
|
|
|
|
|
|
// Then convert it to a relative path, but without subdirectory
|
|
|
|
return this.urlUtils.absoluteToRelative(absolute, {withoutSubdirectory: true});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert root relative URLs to subdirectory relative URLs
|
|
|
|
*/
|
|
|
|
relativeToAbsolute(path) {
|
|
|
|
return this.urlUtils.relativeToAbsolute(path);
|
|
|
|
}
|
|
|
|
|
2022-08-25 15:51:38 +03:00
|
|
|
/**
|
|
|
|
* Gives an ordinary URL a name, e.g. / is 'homepage'
|
|
|
|
*/
|
|
|
|
getUrlTitle(url) {
|
|
|
|
return url === '/' ? 'homepage' : url;
|
|
|
|
}
|
|
|
|
|
2022-09-14 22:50:54 +03:00
|
|
|
/**
|
|
|
|
* Get the resource type and ID from a URLHistory item that was added by the frontend attribution script
|
2023-05-02 23:43:47 +03:00
|
|
|
* @param {import('./UrlHistory').UrlHistoryItem} item
|
2022-09-14 22:50:54 +03:00
|
|
|
* @returns {Promise<{type: string, id: string | null, url: string}|null>} Returns null if the item is invalid
|
|
|
|
*/
|
|
|
|
async getResourceDetails(item) {
|
|
|
|
if (item.type) {
|
|
|
|
const resource = await this.getResourceById(item.id, item.type);
|
|
|
|
if (resource) {
|
|
|
|
return {
|
|
|
|
type: item.type,
|
|
|
|
id: item.id,
|
|
|
|
url: this.getUrlByResourceId(item.id, {absolute: false})
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Invalid id: ignore
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!item.path) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
const path = this.stripSubdirectoryFromPath(item.path);
|
|
|
|
return {
|
|
|
|
type: 'url',
|
|
|
|
id: null,
|
|
|
|
...this.getTypeAndIdFromPath(path),
|
|
|
|
url: path
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the resource type and ID from a path that was visited on the site
|
|
|
|
* @param {string} path (excluding subdirectory)
|
|
|
|
*/
|
|
|
|
getTypeAndIdFromPath(path) {
|
|
|
|
const resource = this.urlService.getResource(path);
|
2022-08-18 18:38:42 +03:00
|
|
|
if (!resource) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (resource.config.type === 'posts') {
|
|
|
|
return {
|
|
|
|
type: 'post',
|
|
|
|
id: resource.data.id
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (resource.config.type === 'pages') {
|
|
|
|
return {
|
|
|
|
type: 'page',
|
|
|
|
id: resource.data.id
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (resource.config.type === 'tags') {
|
|
|
|
return {
|
|
|
|
type: 'tag',
|
|
|
|
id: resource.data.id
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (resource.config.type === 'authors') {
|
|
|
|
return {
|
|
|
|
type: 'author',
|
|
|
|
id: resource.data.id
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
2022-08-19 23:39:18 +03:00
|
|
|
|
2022-08-24 17:11:25 +03:00
|
|
|
getUrlByResourceId(id, options = {absolute: true}) {
|
|
|
|
return this.urlService.getUrlByResourceId(id, options);
|
|
|
|
}
|
2022-08-19 23:39:18 +03:00
|
|
|
|
2022-08-24 17:11:25 +03:00
|
|
|
async getResourceById(id, type) {
|
2022-08-19 23:39:18 +03:00
|
|
|
switch (type) {
|
|
|
|
case 'post':
|
|
|
|
case 'page': {
|
|
|
|
const post = await this.models.Post.findOne({id}, {require: false});
|
|
|
|
if (!post) {
|
|
|
|
return null;
|
|
|
|
}
|
2023-05-02 23:43:47 +03:00
|
|
|
|
2022-08-24 17:11:25 +03:00
|
|
|
return post;
|
2022-08-19 23:39:18 +03:00
|
|
|
}
|
|
|
|
case 'author': {
|
|
|
|
const user = await this.models.User.findOne({id}, {require: false});
|
|
|
|
if (!user) {
|
|
|
|
return null;
|
|
|
|
}
|
2023-05-02 23:43:47 +03:00
|
|
|
|
2022-08-24 17:11:25 +03:00
|
|
|
return user;
|
2022-08-19 23:39:18 +03:00
|
|
|
}
|
|
|
|
case 'tag': {
|
|
|
|
const tag = await this.models.Tag.findOne({id}, {require: false});
|
|
|
|
if (!tag) {
|
|
|
|
return null;
|
|
|
|
}
|
2023-05-02 23:43:47 +03:00
|
|
|
|
2022-08-24 17:11:25 +03:00
|
|
|
return tag;
|
2022-08-19 23:39:18 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
2022-08-18 18:38:42 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = UrlTranslator;
|