diff --git a/modules/hudozka-api/package-lock.json b/modules/hudozka-api/package-lock.json index e7300aca..ab08508c 100644 --- a/modules/hudozka-api/package-lock.json +++ b/modules/hudozka-api/package-lock.json @@ -4,6 +4,16 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@types/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, "@types/bson": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/bson/-/bson-4.0.1.tgz", @@ -19,6 +29,42 @@ "integrity": "sha512-MNl+rT5UmZeilaPxAVs6YaPC2m6aA8rofviZbhbxpPpl61uKodfdQVsBtgJGTqGizEf02oW3tsVe7FYB8kK14A==", "dev": true }, + "@types/connect": { + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", + "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/express": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.2.tgz", + "integrity": "sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.2", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.2.tgz", + "integrity": "sha512-El9yMpctM6tORDAiBwZVLMcxoTMcqqRO9dVyYcn7ycLWbvR8klrDn8CAOwRfZujZtWD7yS/mshTdz43jMOejbg==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/range-parser": "*" + } + }, + "@types/mime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", + "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==", + "dev": true + }, "@types/mongodb": { "version": "3.3.16", "resolved": "https://registry.npmjs.org/@types/mongodb/-/mongodb-3.3.16.tgz", @@ -35,6 +81,22 @@ "integrity": "sha512-Zq8gcQGmn4txQEJeiXo/KiLpon8TzAl0kmKH4zdWctPj05nWwp1ClMdAVEloqrQKfaC48PNLdgN/aVaLqUrluA==", "dev": true }, + "@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", + "dev": true + }, + "@types/serve-static": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz", + "integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==", + "dev": true, + "requires": { + "@types/express-serve-static-core": "*", + "@types/mime": "*" + } + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", diff --git a/modules/hudozka-api/package.json b/modules/hudozka-api/package.json index de0f1b08..44dd1b00 100644 --- a/modules/hudozka-api/package.json +++ b/modules/hudozka-api/package.json @@ -27,6 +27,7 @@ }, "devDependencies": { "@types/classnames": "^2.2.9", + "@types/express": "^4.17.2", "@types/mongodb": "^3.3.16", "@types/node": "^13.7.1", "ts-node": "^8.6.2", diff --git a/modules/hudozka-api/src/controllers/breadcrumb.ts b/modules/hudozka-api/src/controllers/breadcrumb.ts new file mode 100644 index 00000000..cda40fb5 --- /dev/null +++ b/modules/hudozka-api/src/controllers/breadcrumb.ts @@ -0,0 +1,23 @@ +import { getPathWaterfall } from '../lib/url' +import { Breadcrumb } from '../types' +import Page from '../core/Page' + +export async function getItems(path: string): Promise { + const bc = getPathWaterfall(path) + const parts = await Promise.all(bc.map(async href => { + const resource = await Page.findByUrl(href) + + if (!resource) { + return null + } + + const name = resource.title + + return { + name, + href, + } + })) + + return parts.filter(Boolean) +} diff --git a/modules/hudozka-api/src/controllers/page.ts b/modules/hudozka-api/src/controllers/page.ts index 242b7040..58f99752 100644 --- a/modules/hudozka-api/src/controllers/page.ts +++ b/modules/hudozka-api/src/controllers/page.ts @@ -1,15 +1,16 @@ import { Request, Response } from 'express' import Page from '../core/Page' -import { getPathWithNoTrailingSlash } from '../lib/url' +import { dropTrailingSlash, getPathWaterfall } from '../lib/url' import { encodePage } from '../factory/page' +import * as breadcrumb from './breadcrumb' export async function getItem(req: Request, res: Response) { - const id = req.query.page - const page = getPathWithNoTrailingSlash(id) + const page = dropTrailingSlash(req.query.page) + const breadcrumbs = await breadcrumb.getItems(page) const resource = await Page.findByUrl(page) - + if (resource) { - res.json(encodePage(resource)) + res.json(encodePage(resource, breadcrumbs)) } else { res.status(404) res.json({ diff --git a/modules/hudozka-api/src/factory/page.ts b/modules/hudozka-api/src/factory/page.ts index ef6bb38d..7d0fee31 100644 --- a/modules/hudozka-api/src/factory/page.ts +++ b/modules/hudozka-api/src/factory/page.ts @@ -1,6 +1,8 @@ import { encodeImage } from './image' +import Page from '../core/Page' +import { Breadcrumb } from '../types' -export function encodePage(page) { +export function encodePage(page: Page, breadcrumb: Breadcrumb) { return { id: page.id, title: page.title, @@ -8,5 +10,6 @@ export function encodePage(page) { data: page.data, preview: encodeImage(page.preview), tokens: page.tokens, + breadcrumb, } } diff --git a/modules/hudozka-api/src/lib/url.js b/modules/hudozka-api/src/lib/url.js deleted file mode 100644 index d68c640d..00000000 --- a/modules/hudozka-api/src/lib/url.js +++ /dev/null @@ -1,40 +0,0 @@ -function getPathWithNoTrailingSlash(path='') { - path = path.charAt(path.length - 1) === '/' - ? path.substr(0, path.length - 1) - : path - return path.length === 0 - ? '/' - : path -} - -function isEqualPath(a, b, ignoreTrailingSlash = false) { - return ignoreTrailingSlash - ? getPathWithNoTrailingSlash(a) === getPathWithNoTrailingSlash(b) - : a === b -} - -function isMatchPathPattern(pattern, path, ignoreTrailingSlash = false) { - const test = ignoreTrailingSlash - ? getPathWithNoTrailingSlash(path) - : path - return pattern.test(test) -} - -/** - * - * /article/* -> ^\/article\/[/\w\d-_]+\/?$ - * - * @param url - * @return {RegExp} - */ -function urlToPattern(url) { - const u = url.replace('/*', '[/\\w\\d-_]+') - const pattern = `^${u}/?$` - - return new RegExp(pattern) -} - -exports.getPathWithNoTrailingSlash = getPathWithNoTrailingSlash -exports.isMatchPathPattern = isMatchPathPattern -exports.urlToPattern = urlToPattern -exports.isEqualPath = isEqualPath diff --git a/modules/hudozka-api/src/lib/url.ts b/modules/hudozka-api/src/lib/url.ts new file mode 100644 index 00000000..f6c2bd5f --- /dev/null +++ b/modules/hudozka-api/src/lib/url.ts @@ -0,0 +1,28 @@ +export function dropTrailingSlash(path: string) { + if (path === '/' || path === '') { + return '/' + } + + const lastChar = path.charAt(path.length - 1) + if (lastChar === '/') { + return path.substr(0, path.length - 1) + } + + return path +} + +export function getPathWaterfall(path: string): string[] { + const parts = path + .split('/') + .filter(Boolean) + .reduce((acc, part, index, parts) => { + const i = index + 1 + const item = `/${parts.slice(0, i).join('/')}` + return [ + ...acc, + item + ] + }, []) + + return ['/', ...parts] +} diff --git a/modules/hudozka-api/src/types.ts b/modules/hudozka-api/src/types.ts new file mode 100644 index 00000000..238157c9 --- /dev/null +++ b/modules/hudozka-api/src/types.ts @@ -0,0 +1,6 @@ +export type BreadcrumbPart = { + name: string + href: string +} + +export type Breadcrumb = BreadcrumbPart[]