api Page updated: refactor to make it looks like article
This commit is contained in:
1 parent
68b7f4225d
commit
348c3baba8
11 files changed
+160
-63
No files matched your search
@@ -2,11 +2,7 @@ import Article from '../core/Article'
|
||||
import { findArticles } from '../server/articles'
|
||||
import { Request, Response } from 'express'
|
||||
import { encodeArticle } from '../factory/article'
|
||||
|
||||
const int = (value: string | string[]) => parseInt(Array.isArray(value)
|
||||
? value[0]
|
||||
: value
|
||||
)
|
||||
import { readQueryInt } from '../lib/url'
|
||||
|
||||
export async function getItem(req: Request, res: Response) {
|
||||
const id = req.params.slug
|
||||
@@ -23,9 +19,8 @@ export async function getItem(req: Request, res: Response) {
|
||||
}
|
||||
|
||||
export async function getAll(req: Request, res: Response) {
|
||||
const { query } = req
|
||||
const page = int(query.page)
|
||||
const pageSize = int(query.pageSize)
|
||||
const page = readQueryInt(req, 'page')
|
||||
const pageSize = readQueryInt(req, 'pageSize')
|
||||
|
||||
const data = await findArticles(page, pageSize)
|
||||
|
||||
|
||||
@@ -1,20 +1,27 @@
|
||||
import { Request, Response } from 'express'
|
||||
import Page from '../core/Page'
|
||||
import { dropTrailingSlash, getPathWaterfall } from '../lib/url'
|
||||
import { dropTrailingSlash, getPathWaterfall, readQueryArray, readQueryInt } from '../lib/url'
|
||||
import { encodePage } from '../factory/page'
|
||||
import * as breadcrumb from './breadcrumb'
|
||||
|
||||
async function resolveItem(page: Page) {
|
||||
const breadcrumbs = await breadcrumb.getItems(page.getUrl())
|
||||
page.setBreadcrumb(breadcrumbs)
|
||||
|
||||
return page
|
||||
}
|
||||
|
||||
export async function getItem(req: Request, res: Response) {
|
||||
const page = dropTrailingSlash(req.query.page)
|
||||
const breadcrumbs = await breadcrumb.getItems(page)
|
||||
const resource = await Page.findByUrl(page)
|
||||
const pageUrl = dropTrailingSlash(req.query.page)
|
||||
const resource = await Page.findByUrl(pageUrl)
|
||||
|
||||
if (resource) {
|
||||
res.json(encodePage(resource, breadcrumbs))
|
||||
const page = await resolveItem(resource)
|
||||
res.json(encodePage(page))
|
||||
} else {
|
||||
res.status(404)
|
||||
res.json({
|
||||
error: `Resource ${page} not found`
|
||||
error: `Resource ${pageUrl} not found`
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -34,6 +41,38 @@ export async function getAll(req: Request, res: Response) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getByTags(req: Request, res: Response) {
|
||||
const skip = readQueryInt(req, 'skip')
|
||||
const limit = readQueryInt(req, 'limit')
|
||||
|
||||
const tags = readQueryArray(req, 'tag')
|
||||
if (tags.length === 0) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `No tags in request`
|
||||
})
|
||||
}
|
||||
|
||||
const options = { skip, limit }
|
||||
const models = await Page.find({
|
||||
tags: { $in: ['event', 'album'] },
|
||||
}, options)
|
||||
|
||||
if (models) {
|
||||
const pages = await Promise.all(models.map(resolveItem))
|
||||
const items = pages.map(encodePage)
|
||||
|
||||
res.json({
|
||||
items,
|
||||
})
|
||||
} else {
|
||||
res.status(404)
|
||||
res.json({
|
||||
error: `Not found`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUrls(req: Request, res: Response) {
|
||||
const data = await Page.find({})
|
||||
|
||||
|
||||
@@ -5,10 +5,6 @@ import { Tag } from './Tag'
|
||||
|
||||
const store = () => Data.getStore<Article>('Article')
|
||||
|
||||
function make(C) {
|
||||
return x => new C(x)
|
||||
}
|
||||
|
||||
export default class Article {
|
||||
static async find(query, options = {}): Promise<Article[]> {
|
||||
const items = await find(store(), query, options as any)
|
||||
@@ -63,7 +59,7 @@ export default class Article {
|
||||
this.date = data.date
|
||||
this.title = data.title
|
||||
this.until = data.until
|
||||
this.tags = (data.tags || []).map(make(Tag))
|
||||
this.tags = (data.tags || []).map((x: string) => Tag.fromCyrillic(x))
|
||||
this.content = data.content
|
||||
this.hash = data.hash
|
||||
this.type = data.type
|
||||
@@ -76,27 +72,27 @@ export default class Article {
|
||||
this.url = `/article/${this.id}`
|
||||
}
|
||||
|
||||
plain() {
|
||||
return {
|
||||
id: this.id,
|
||||
images: this.images,
|
||||
post: this.post,
|
||||
folder: this.folder,
|
||||
date: this.date,
|
||||
title: this.title,
|
||||
until: this.until,
|
||||
tags: this.tags,
|
||||
content: this.content,
|
||||
hash: this.hash,
|
||||
type: this.type,
|
||||
origin: this.origin,
|
||||
file: this.file,
|
||||
version: this.version,
|
||||
preview: this.preview,
|
||||
url: this.url,
|
||||
featured: this.featured,
|
||||
}
|
||||
}
|
||||
// plain() {
|
||||
// return {
|
||||
// id: this.id,
|
||||
// images: this.images,
|
||||
// post: this.post,
|
||||
// folder: this.folder,
|
||||
// date: this.date,
|
||||
// title: this.title,
|
||||
// until: this.until,
|
||||
// tags: this.tags,
|
||||
// content: this.content,
|
||||
// hash: this.hash,
|
||||
// type: this.type,
|
||||
// origin: this.origin,
|
||||
// file: this.file,
|
||||
// version: this.version,
|
||||
// preview: this.preview,
|
||||
// url: this.url,
|
||||
// featured: this.featured,
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
const previewFromImage = (imgs = []) => imgs.length
|
||||
|
||||
@@ -2,11 +2,12 @@ import Data from './Data'
|
||||
import ImageArtifactType from './ImageArtifactType'
|
||||
import { findOne } from '../lib/store'
|
||||
import { mapOf } from '../server/lib'
|
||||
import { ObjectId } from 'mongodb'
|
||||
|
||||
const store = () => Data.getStore('Image')
|
||||
|
||||
export default class Image {
|
||||
static async findById(id) {
|
||||
static async findById(id: ObjectId) {
|
||||
const data = await findOne(store(), { _id: id })
|
||||
if (!data) return null
|
||||
|
||||
|
||||
@@ -2,9 +2,17 @@ import Data from './Data'
|
||||
import { find as storeFind, findOne, total } from '../lib/store'
|
||||
import Image from './Image'
|
||||
import { Tag } from './Tag'
|
||||
import { ObjectId } from 'mongodb'
|
||||
import { Breadcrumb } from '../types'
|
||||
|
||||
const store = () => Data.getStore('Page')
|
||||
|
||||
type PageDto = {
|
||||
[name: string]: any
|
||||
preview?: ObjectId
|
||||
images: ObjectId[]
|
||||
}
|
||||
|
||||
export default class Page {
|
||||
static async find(query, options = {}): Promise<Page[]> {
|
||||
const items = await storeFind(store(), query, options as any)
|
||||
@@ -20,7 +28,7 @@ export default class Page {
|
||||
: processPage(item)
|
||||
}
|
||||
|
||||
static async findByUrl(url) {
|
||||
static async findByUrl(url: string) {
|
||||
const item = await findOne(store(), { url })
|
||||
|
||||
return !item
|
||||
@@ -41,13 +49,16 @@ export default class Page {
|
||||
public date: Date
|
||||
public tags: Tag[]
|
||||
public data: any
|
||||
public images: any
|
||||
public images: ObjectId[]
|
||||
public documents: any
|
||||
public preview: any
|
||||
public preview?: Image
|
||||
public tokens: any[]
|
||||
public description: string
|
||||
public featured: boolean
|
||||
|
||||
constructor(data) {
|
||||
private breadcrumb: Breadcrumb
|
||||
|
||||
constructor(data: { [name: string]: any }) {
|
||||
this.id = data.id
|
||||
this.hash = data.hash
|
||||
this.file = data.file
|
||||
@@ -60,27 +71,43 @@ export default class Page {
|
||||
this.documents = data.documents
|
||||
this.preview = data.preview
|
||||
this.tokens = data.tokens
|
||||
this.tags = (data.tags || []).map(x => new Tag(x))
|
||||
this.tags = (data.tags || []).map((x: string) => new Tag(x, x))
|
||||
this.featured = data.featured
|
||||
this.description = data.description
|
||||
}
|
||||
|
||||
public setBreadcrumb(value: Breadcrumb) {
|
||||
this.breadcrumb = value
|
||||
return this
|
||||
}
|
||||
|
||||
public getBreadcrumb() {
|
||||
return this.breadcrumb
|
||||
}
|
||||
|
||||
public getUrl() {
|
||||
return this.url
|
||||
}
|
||||
}
|
||||
|
||||
const previewFromImage = (imgs = []) => imgs.length
|
||||
? imgs[0]
|
||||
: null
|
||||
function firstItem<T>(items: T[] = []): T {
|
||||
return items.length
|
||||
? items[0]
|
||||
: null
|
||||
}
|
||||
|
||||
async function processPage(article) {
|
||||
let preview = null
|
||||
const previewId = article.preview
|
||||
? article.preview
|
||||
: previewFromImage(article.images)
|
||||
async function processPage(item: PageDto): Promise<Page> {
|
||||
let preview: Image = null
|
||||
const previewId = item.preview
|
||||
? item.preview
|
||||
: firstItem(item.images)
|
||||
|
||||
if (previewId) {
|
||||
preview = await Image.findById(previewId)
|
||||
}
|
||||
|
||||
return new Page({
|
||||
...article,
|
||||
...item,
|
||||
preview,
|
||||
})
|
||||
}
|
||||
@@ -1,15 +1,20 @@
|
||||
import { translit } from '../lib/translit'
|
||||
|
||||
export class Tag {
|
||||
public static fromCyrillic(tag: string) {
|
||||
const slug = tag
|
||||
? translit(tag).toLowerCase()
|
||||
: tag
|
||||
|
||||
return new Tag(tag, slug)
|
||||
}
|
||||
|
||||
public name: string
|
||||
public slug: string
|
||||
|
||||
constructor(tag: string) {
|
||||
this.name = tag
|
||||
|
||||
if (tag) {
|
||||
this.slug = translit(tag).toLowerCase()
|
||||
}
|
||||
constructor(name: string, slug: string) {
|
||||
this.name = name
|
||||
this.slug = slug
|
||||
}
|
||||
|
||||
public isValid() {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import ImageArtifactType from '../core/ImageArtifactType'
|
||||
import Image from '../core/Image'
|
||||
|
||||
export type ImageArtifactDto = {
|
||||
height: number
|
||||
@@ -23,7 +24,7 @@ export type ImageDto = {
|
||||
file: string
|
||||
}
|
||||
|
||||
export function encodeImage(image): ImageDto {
|
||||
export function encodeImage(image: Image): ImageDto {
|
||||
const artifacts = [
|
||||
ImageArtifactType.SMALL,
|
||||
ImageArtifactType.MEDIUM,
|
||||
|
||||
@@ -18,12 +18,14 @@ type PageDto = {
|
||||
preview?: ImageDto
|
||||
tokens: Token[]
|
||||
tags: Tag[]
|
||||
featured: boolean
|
||||
breadcrumb: Breadcrumb
|
||||
}
|
||||
|
||||
export function encodePage(page: Page, breadcrumb: Breadcrumb): PageDto {
|
||||
export function encodePage(page: Page): PageDto {
|
||||
const preview = page.preview ? encodeImage(page.preview) : null
|
||||
const date = page.date ? page.date.toString() : null
|
||||
const breadcrumb = page.getBreadcrumb()
|
||||
|
||||
return {
|
||||
id: page.id,
|
||||
@@ -33,6 +35,7 @@ export function encodePage(page: Page, breadcrumb: Breadcrumb): PageDto {
|
||||
description: page.description,
|
||||
tokens: page.tokens,
|
||||
tags: page.tags,
|
||||
featured: page.featured,
|
||||
preview,
|
||||
date,
|
||||
breadcrumb,
|
||||
|
||||
@@ -3,3 +3,16 @@ export function unique<T>(items: T[], key: (item: T) => string): T[]{
|
||||
|
||||
return [...map.values()]
|
||||
}
|
||||
|
||||
export function ensureArray<T>(value: T | T[]): T[] {
|
||||
if (!value) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value
|
||||
}
|
||||
|
||||
return [value]
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import { Request } from 'express'
|
||||
import { ensureArray } from './array'
|
||||
|
||||
export function dropTrailingSlash(path: string) {
|
||||
if (path === '/' || path === '') {
|
||||
return '/'
|
||||
@@ -26,3 +29,16 @@ export function getPathWaterfall(path: string): string[] {
|
||||
|
||||
return ['/', ...parts]
|
||||
}
|
||||
|
||||
export function readQueryInt(req: Request, key: string) {
|
||||
const int = (value: string | string[]) => parseInt(Array.isArray(value)
|
||||
? value[0]
|
||||
: value
|
||||
)
|
||||
|
||||
return int(req.query[key]) || 0
|
||||
}
|
||||
|
||||
export function readQueryArray(req: Request, key: string) {
|
||||
return ensureArray(req.query[key])
|
||||
}
|
||||
@@ -33,6 +33,7 @@ async function main() {
|
||||
|
||||
app.get('/pages', page.getAll)
|
||||
app.get('/pages/urls', page.getUrls)
|
||||
app.get('/pages/tags', page.getByTags)
|
||||
app.get('/page', page.getItem)
|
||||
|
||||
app.get('/image', image.getItem)
|
||||
|
||||
Reference in new issue
Block a user