Merge pull request #43 from tmshv/fix/api-image-simplify

API image simplify
This commit is contained in:
tmshv authored and GitHub committed 2020-03-11 13:44:16 +03:00
commit 93ee4ed9ca
22 files changed
+176 -255

No files matched your search

+7 -26
View File
@@ -1,35 +1,16 @@
import { Request, Response } from 'express'
import Document from '../core/Document'
import { encodeFile } from '../factory/file'
import { encodeFile, encodeFileUrl } from '../factory/file'
export async function getItem(req: Request, res: Response) {
const id = req.params.slug
const document = await Document.findById(id)
if (document) {
res.json(encodeFile(document))
const slug = req.params.slug
const item = await Document.findBySlug(slug)
if (item) {
res.json(encodeFile(item))
} else {
res.status(404)
res.json({
error: `Resource ${id} not found`
})
}
}
export async function getAll(req: Request, res: Response) {
const documents = await Document.find({ hidden: false })
if (documents) {
const items = documents
.map(encodeFile)
.filter(x => x.imageUrl) // skip documents without preview image; needs to be refactored
res.json({
items,
})
} else {
res.status(404)
res.json({
error: `Not found`
error: `Resource ${slug} not found`
})
}
}
@@ -38,7 +19,7 @@ export async function getUrls(req: Request, res: Response) {
const data = await Document.find({})
if (data) {
const items = data.map(x => `/document/${x.id}`)
const items = data.map(encodeFileUrl)
res.json({
items
})
+23 -1
View File
@@ -1,11 +1,33 @@
import Image from '../core/Image'
import { encodeImage } from '../factory/image'
import { Request, Response } from 'express'
import { oid } from '../db'
export async function getItem(req: Request, res: Response) {
const slug = req.params.slug
const id = oid(slug)
if (!id) {
res.status(400)
return res.json({
error: `Failed to parse ${slug} as ObjectId`
})
}
const image = await Image.findById(id)
if (image) {
res.json(encodeImage(image))
} else {
res.status(404)
res.json({
error: `Resource ${slug} not found`
})
}
}
export async function getItemByFile(req: Request, res: Response) {
const file = req.query.file
const image = await Image.findByFile(file)
if (image) {
res.json(encodeImage(image))
} else {
+10 -10
View File
@@ -11,8 +11,8 @@ export default class Document {
return Promise.all(items.map(processDocument))
}
static async findById(id) {
const data = await findOne(store(), { id })
static async findBySlug(slug: string) {
const data = await findOne(store(), { id: slug })
if (!data) return null
return processDocument(data)
@@ -21,22 +21,22 @@ export default class Document {
public id: any
public hash: any
public file: any
public fileInfo: any
public fileInfo: FileInfo
public type: any
public title: any
public category: any
public url: any
public preview: any
public preview: Image
public viewUrl: any
constructor(data) {
constructor(data: { preview: Image, [name: string]: any }) {
this.id = data.id
this.hash = data.hash
this.file = data.file
// In the future: use fileInfo only
this.fileInfo = data.fileInfo
? new DocumentFile(data.fileInfo)
: new DocumentFile(data.file)
? new FileInfo(data.fileInfo)
: new FileInfo(data.file)
this.type = data.type
this.title = data.title
this.category = data.category
@@ -47,9 +47,9 @@ export default class Document {
}
}
class DocumentFile {
public name: any
public size: any
class FileInfo {
public name: string
public size: number
constructor(data) {
this.name = data.name
+1 -40
View File
@@ -1,5 +1,4 @@
import Data from './Data'
import ImageArtifactType from './ImageArtifactType'
import { findOne } from '../lib/store'
import { ObjectId } from 'mongodb'
@@ -47,47 +46,9 @@ export default class Image {
this.artifacts = mapOf(ImageArtifact, artifacts)
}
getArtifact(size) {
getArtifact(size: string) {
return this.artifacts.get(size)
}
findArtifact(sizes) {
for (let size of sizes) {
const a = this.getArtifact(size)
if (a) return a
}
return null
}
getPicture(size) {
const retinaSize = ImageArtifactType.retina(size)
const a = this.getArtifact(size)
const a2 = this.getArtifact(retinaSize)
const srcset = !a2
? []
: [{
url: a2.url,
density: ImageArtifactType.RETINA_DENSITY,
}]
return {
src: a.url,
width: a.width,
height: a.height,
set: srcset,
}
}
getSrc(): string | null {
const a = this.getArtifact(ImageArtifactType.ORIGINAL)
if (!a) {
return null
}
return a.url
}
}
class ImageArtifact {
@@ -1,19 +0,0 @@
class ImageArtifactType {
constructor() {
throw new Error('Cannot create instance of ImageArtifactType')
}
}
ImageArtifactType.SMALL = 'small'
ImageArtifactType.MEDIUM = 'medium'
ImageArtifactType.BIG = 'big'
ImageArtifactType.LARGE = 'large'
ImageArtifactType.ORIGIN = 'original'
ImageArtifactType.ORIGINAL = 'original'
ImageArtifactType.FACEBOOK = 'fb'
ImageArtifactType.RETINA_DENSITY = 2
ImageArtifactType.retina = type => `${type}@${ImageArtifactType.RETINA_DENSITY}`
module.exports = ImageArtifactType
-8
View File
@@ -20,14 +20,6 @@ export default class Page {
return Promise.all(items.map(processPage))
}
static async findById(id) {
const item = await findOne(store(), { id })
return !item
? null
: processPage(item)
}
static async findByUrl(url: string) {
const item = await findOne(store(), { url })
+16 -5
View File
@@ -12,14 +12,25 @@ export async function connect(dbName: string, mongoUri: string) {
return db
}
export function collection(name) {
export function collection(name: string) {
return db.collection(name)
}
export function id(i) {
if (typeof i === 'string') return new ObjectId(i)
else if (i instanceof ObjectId) return i
else if (i instanceof Object) return '_id' in i ? new ObjectId(i._id) : null
export type ObjectIdLike = ObjectId | string
export function oid(i: ObjectIdLike | { _id: ObjectIdLike }): ObjectId | null {
if (i instanceof ObjectId) {
return i
} else if (typeof i === 'string') {
try {
return new ObjectId(i)
} catch (error) {
console.error(error)
return null
}
} else if (i instanceof Object) {
return '_id' in i ? oid(i._id) : null
}
return null
}
+33 -32
View File
@@ -1,35 +1,36 @@
import ImageArtifactType from '../core/ImageArtifactType'
import Document from '../core/Document'
import { encodeImage, ImageDto } from './image'
const sizes = [
ImageArtifactType.MEDIUM,
ImageArtifactType.BIG,
ImageArtifactType.ORIGIN,
]
export function encodeFilePreview(file, sizes) {
const preview = file.preview
if (!preview) return null
const image = preview.findArtifact(sizes)
if (!image) return null
return image.url
}
export function encodeFile(file) {
const imageUrl = encodeFilePreview(file, sizes)
const category = (file.category || '')
.replace('Documents/', '')
.replace('Pages/', '')
.replace('Articles /', '')
return {
imageUrl,
category,
title: file.title,
fileName: file.fileInfo.name,
fileSize: file.fileInfo.size,
fileUrl: file.url,
url: file.viewUrl,
export type FileDto = {
url: string
slug: string
name: string
cover?: ImageDto
file: {
name: string
size: number
type: string
src: string
}
}
export function encodeFile(file: Document): FileDto {
const cover = file.preview ? encodeImage(file.preview) : null
return {
url: encodeFileUrl(file),
slug: file.id,
cover,
name: file.title,
file: {
type: 'application/pdf',
name: file.fileInfo.name,
size: file.fileInfo.size,
src: file.url,
}
}
}
export function encodeFileUrl(file: Document): string {
return `/document/${file.id}`
}
+11 -37
View File
@@ -1,48 +1,22 @@
import ImageArtifactType from '../core/ImageArtifactType'
import Image from '../core/Image'
export type ImageArtifactDto = {
height: number
width: number
src: string
set: Array<{
density: number
url: string
}>
}
const ORIGINAL = 'original'
export type ImageDto = {
artifacts: {
original: ImageArtifactDto,
large: ImageArtifactDto,
big: ImageArtifactDto,
medium: ImageArtifactDto,
small: ImageArtifactDto,
fb: ImageArtifactDto,
},
hash: string
file: string
width: number
height: number
src: string
}
export function encodeImage(image: Image): ImageDto {
const artifacts = [
ImageArtifactType.SMALL,
ImageArtifactType.MEDIUM,
ImageArtifactType.BIG,
ImageArtifactType.LARGE,
ImageArtifactType.ORIGIN,
ImageArtifactType.ORIGINAL,
ImageArtifactType.FACEBOOK = 'fb'
].reduce((acc, size) => {
return {
...acc,
[size]: image.getPicture(size),
}
}, {})
const a = image.getArtifact(ORIGINAL)
if (!image) {
return null
}
return {
artifacts: artifacts as any,
hash: image.hash,
file: image.file,
src: a.url,
width: a.width,
height: a.height,
}
}
+3 -9
View File
@@ -9,29 +9,24 @@ type Token = {
}
type PageDto = {
id: string
title: string
date?: string
url: string
data: string
description: string
preview?: ImageDto
cover?: ImageDto
tokens: Token[]
tags: Tag[]
featured: boolean
breadcrumb: Breadcrumb
coverSrc?: string
}
export function encodePage(page: Page): PageDto {
const preview = page.preview ? encodeImage(page.preview) : null
const cover = page.preview ? encodeImage(page.preview) : null
const date = page.date ? page.date.toString() : null
const breadcrumb = page.getBreadcrumb()
const coverSrc = page.preview ? page.preview.getSrc() : null
return {
id: page.id,
title: page.title,
url: page.url,
data: page.data,
@@ -39,9 +34,8 @@ export function encodePage(page: Page): PageDto {
tokens: page.tokens,
tags: page.tags,
featured: page.featured,
preview,
cover,
date,
breadcrumb,
coverSrc,
}
}
-1
View File
@@ -21,7 +21,6 @@ async function main() {
}
const app = express()
app.get('/files', file.getAll)
app.get('/files/urls', file.getUrls)
app.get('/files/:slug', file.getItem)
+8
View File
@@ -49,6 +49,8 @@ image_sizes = [
('large', 2500, 1000),
('large@2', 5000, 2000),
# ('large', 2500, 1000),
# ('large@2', 5000, 2000),
('big', 1500, 667),
('big@2', 3000, 1334),
@@ -58,12 +60,18 @@ image_sizes = [
('small', 250, 175),
('small@2', 500, 350),
# ('small', 250, 175),
# ('small@2', 500, 350),
('og', 968, 504),
('og@2', 1936, 1008),
# ('og', 968, 504),
# ('og@2', 1936, 1008),
('fb', 1200, 630),
('fb@2', 2400, 1260),
# ('fb', 1200, 630),
# ('fb@2', 2400, 1260),
]
date_formats = [
+1 -1
View File
@@ -86,7 +86,7 @@ async def create_post(provider: Provider, folder: str, md: str, sizes):
text = data['caption'] if data['caption'] else data['file']
url = f'/document/{document.id}'
image_url = document.preview.get_size('small')['url']
image_url = document.preview.get_src()
return {
'url': url,
+1 -1
View File
@@ -62,7 +62,7 @@ export const getStaticProps = async (ctx: any) => {
const breadcrumbSize = page?.breadcrumb?.length ?? 0
const breadcrumb = breadcrumbSize < 2 ? null : page.breadcrumb
const meta = (new MetaBuilder())
.setImage(page.preview)
.setImage(page.cover)
.setTitle(page.title)
.setDescription(description)
.build()
+2 -4
View File
@@ -109,15 +109,13 @@ export const getStaticProps = async (ctx: NextPageContext) => {
const data: CardData[] = pages
.filter(Boolean)
.map(x => {
const image = x.preview.artifacts.large
return {
title: x.title,
caption: x.description,
href: x.url,
imageAlt: title,
imageSrc: image.src,
imageSrcSet: imageSrcSet(image.src, [ImageSize.medium, ImageSize.small])
imageSrc: x.cover.src,
imageSrcSet: imageSrcSet(x.cover.src, [ImageSize.medium, ImageSize.small])
}
})
+14 -12
View File
@@ -1,16 +1,15 @@
import React from 'react'
import Head from 'next/head'
import { App } from 'src/components/App'
import Document from 'src/components/Document'
import { File } from 'src/components/File'
import { Share } from 'src/components/Share'
import { meta } from 'src/lib/meta'
import { MetaBuilder } from 'src/lib/meta'
import { Meta } from 'src/components/Meta'
import { createApiUrl, requestGet, IResponseItems } from 'src/next-lib'
import { NextPage } from 'next'
import { IMeta } from 'src/types'
import { IMeta, FileDefinition } from 'src/types'
type Props = {
data: any //(file)
data: FileDefinition
meta: IMeta
}
@@ -26,25 +25,28 @@ const Page: NextPage<Props> = props => {
showAuthor={true}
>
<Head>
<title>{props.data.title}</title>
<title>{props.data.name}</title>
<Meta meta={props.meta} />
</Head>
<Document {...props.data} />
<File {...props.data} />
<Share />
</App>
)
}
export const getStaticProps = async (ctx: any) => {
const id = ctx.params.slug
const data = await requestGet(createApiUrl(`/api/files/${id}`), {})
const slug = ctx.params.slug
const data = await requestGet<FileDefinition>(createApiUrl(`/api/files/${slug}`), null)
const meta = (new MetaBuilder())
.setImage(data.cover)
.setTitle(data.name)
.setDescription(data.name)
.build()
return {
props: {
data,
meta: meta({
// title: file.meta,
})
meta,
}
}
}
+1 -1
View File
@@ -20,7 +20,7 @@ const Index: NextPage<IProps> = props => {
title: item.title,
featured: item.featured,
date: new Date(item.date),
coverSrc: item.coverSrc,
coverSrc: item.cover?.src,
}))
return (
-23
View File
@@ -1,23 +0,0 @@
const React = require('react')
const {ext, size} = require('../lib/file')
const {titleCase} = require('../lib/string')
const Document = ({title, url, imageUrl, fileUrl, fileName, fileSize}) => (
<div className="document">
<div className="document__image">
<a href={fileUrl} target="_blank" className="invisible">
<img src={imageUrl} alt={title}/>
</a>
</div>
<div className="document__file">
<span>{titleCase(title)}</span>
</div>
<div className="document__file-info">
<a href={fileUrl} target="_blank">{ext(fileName)} ({size(fileSize)})</a>
</div>
</div>
)
module.exports = Document
+25
View File
@@ -0,0 +1,25 @@
import { FileDefinition } from 'src/types'
import { ext, size } from 'src/lib/file'
import { titleCase } from 'src/lib/string'
export const File: React.SFC<FileDefinition> = props => (
<div className="document">
<h1>{titleCase(props.name)}</h1>
{!props.cover?.src ? null : (
<div className="document__image">
<a href={props.file.src} target="_blank" className="invisible">
<img src={props.cover.src} alt={props.name} />
</a>
</div>
)}
<div className="document__file">
<span>{titleCase(props.name)}</span>
</div>
<div className="document__file-info">
<a href={props.file.src} target="_blank">{ext(props.file.name)} ({size(props.file.size)})</a>
</div>
</div>
)
+3 -4
View File
@@ -66,11 +66,10 @@ export class MetaBuilder {
return null
}
const artifact = this.image.artifacts.fb
return {
image: artifact.src,
imageWidth: artifact.width,
imageHeight: artifact.height,
image: this.image.src,
imageWidth: this.image.width,
imageHeight: this.image.height,
}
}
}
+1 -1
View File
@@ -27,7 +27,7 @@ export async function getPagesCardsByTags(tags: string[]): Promise<PageCardDto[]
'title',
'featured',
'date',
'coverSrc'
'cover'
]
const items = await getPagesByTags<PageCardDto>(tags, { fields, skip, limit, sortBy: '-date' })
+16 -20
View File
@@ -21,7 +21,20 @@ export type PageCardDto = {
title: string
featured: boolean
date: string
coverSrc: string
cover?: ImageDefinition
}
export type FileDefinition = {
url: string
slug: string
name: string
cover?: ImageDefinition
file: {
name: string
size: string
type: string
src: string
}
}
export interface IPage {
@@ -30,7 +43,7 @@ export interface IPage {
url: string
data: string
date: string
preview: ImageDefinition
cover: ImageDefinition
description: string
tokens: any[]
tags: ITag[]
@@ -85,25 +98,8 @@ export enum ImageSize {
thumbnail = 200,
}
export type ImageArtifact = {
export type ImageDefinition = {
width: number
height: number
src: string
set: Array<{
density: number
url: string
}>
}
export type ImageDefinition = {
file: string
hash: string
artifacts: {
original: ImageArtifact
large?: ImageArtifact
big?: ImageArtifact
medium?: ImageArtifact
small?: ImageArtifact
fb?: ImageArtifact
}
}