api copied to hudozka-api module based on express
This commit is contained in:
1 parent
70007d3199
commit
fc0724a900
21 files changed
+1093
No files matched your search
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"name": "hudozka-api",
|
||||||
|
"private": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "src/server.ts",
|
||||||
|
"scripts": {
|
||||||
|
"start": "ts-node src/server.ts",
|
||||||
|
"sitemap": "node scripts/sitemap.js",
|
||||||
|
"feed": "node scripts/feed.js"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/tmshv/hudozka"
|
||||||
|
},
|
||||||
|
"author": "Roman Timashev",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^0.19.0",
|
||||||
|
"dotenv": "^8.2.0",
|
||||||
|
"express": "^4.17.1",
|
||||||
|
"feed": "^4.0.0",
|
||||||
|
"lodash": "^4.17.15",
|
||||||
|
"moment": "^2.24.0",
|
||||||
|
"mongodb": "^3.5.3",
|
||||||
|
"sitemap": "^1.5.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/classnames": "^2.2.9",
|
||||||
|
"@types/koa": "^2.11.1",
|
||||||
|
"@types/koa-route": "^3.2.4",
|
||||||
|
"@types/mongodb": "^3.3.16",
|
||||||
|
"@types/node": "^13.7.1",
|
||||||
|
"ts-node": "^8.6.2",
|
||||||
|
"typescript": "^3.7.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { Response, Request } from 'express'
|
||||||
|
import Album from '../core/Album'
|
||||||
|
|
||||||
|
export async function getItem(req: Request, res: Response) {
|
||||||
|
const id = req.params.slug
|
||||||
|
const album = await Album.findById(id)
|
||||||
|
|
||||||
|
if (album) {
|
||||||
|
res.json(album)
|
||||||
|
} else {
|
||||||
|
res.status(404)
|
||||||
|
res.json({
|
||||||
|
error: `Resource ${id} not found`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAll(req: Request, res: Response) {
|
||||||
|
const data = await Album.find({}, { sort: { date: -1 } })
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
res.json({
|
||||||
|
items: data,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
res.status(404)
|
||||||
|
res.json({
|
||||||
|
error: `Not found`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUrls (req: Request, res: Response) {
|
||||||
|
const data = await Album.find({})
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
const items = data.map(x => `/album/${x.id}`)
|
||||||
|
res.json({
|
||||||
|
items
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
res.status(404)
|
||||||
|
res.json({
|
||||||
|
error: `Not found`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
import Article from '../core/Article'
|
||||||
|
import { encodeArticle, findArticles } from '../server/articles'
|
||||||
|
import { Request, Response } from 'express'
|
||||||
|
|
||||||
|
const int = (value: string | string[]) => parseInt(Array.isArray(value)
|
||||||
|
? value[0]
|
||||||
|
: value
|
||||||
|
)
|
||||||
|
|
||||||
|
export async function getItem(req: Request, res: Response) {
|
||||||
|
const id = req.params.slug
|
||||||
|
const article = await Article.findById(id)
|
||||||
|
|
||||||
|
if (article) {
|
||||||
|
res.json(encodeArticle(article))
|
||||||
|
} else {
|
||||||
|
res.status(404)
|
||||||
|
res.json({
|
||||||
|
error: `Resource ${id} not found`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAll(req: Request, res: Response) {
|
||||||
|
const { query } = req
|
||||||
|
const page = int(query.page)
|
||||||
|
const pageSize = int(query.pageSize)
|
||||||
|
|
||||||
|
const data = await findArticles(page, pageSize)
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
res.json(data)
|
||||||
|
} else {
|
||||||
|
res.status(404)
|
||||||
|
res.json({
|
||||||
|
error: `Articles not found`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUrls(req: Request, res: Response) {
|
||||||
|
const data = await Article.find({})
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
const items = data.map(x => `/article/${x.id}`)
|
||||||
|
res.json({
|
||||||
|
items
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
res.status(404)
|
||||||
|
res.json({
|
||||||
|
error: `Not found`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { Request, Response } from 'express'
|
||||||
|
import Document from '../core/Document'
|
||||||
|
import { encodeFile } from '../api/files'
|
||||||
|
|
||||||
|
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))
|
||||||
|
} 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`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUrls(req: Request, res: Response) {
|
||||||
|
const data = await Document.find({})
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
const items = data.map(x => `/document/${x.id}`)
|
||||||
|
res.json({
|
||||||
|
items
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
res.status(404)
|
||||||
|
res.json({
|
||||||
|
error: `Not found`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import Image from '../core/Image'
|
||||||
|
import { encodeImage } from '../api/image'
|
||||||
|
import { Request, Response } from 'express'
|
||||||
|
|
||||||
|
export async function getItem(req: Request, res: Response) {
|
||||||
|
const file = req.params.slug
|
||||||
|
const image = await Image.findByFile(file)
|
||||||
|
|
||||||
|
if (image) {
|
||||||
|
res.json(encodeImage(image))
|
||||||
|
} else {
|
||||||
|
res.status(404)
|
||||||
|
res.json({
|
||||||
|
error: `Resource ${file} not found`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { Request, Response } from 'express'
|
||||||
|
import Page from '../core/Page'
|
||||||
|
import { getPathWithNoTrailingSlash } from '../lib/url'
|
||||||
|
import { encodePage } from '../api/page'
|
||||||
|
|
||||||
|
export async function getItem(req: Request, res: Response) {
|
||||||
|
const id = req.params.slug
|
||||||
|
const page = getPathWithNoTrailingSlash(id)
|
||||||
|
const resource = await Page.findByUrl(page)
|
||||||
|
|
||||||
|
if (resource) {
|
||||||
|
res.json(encodePage(resource))
|
||||||
|
} else {
|
||||||
|
res.status(404)
|
||||||
|
res.json({
|
||||||
|
error: `Resource ${page} not found`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAll(req: Request, res: Response) {
|
||||||
|
const items = await Page.find({})
|
||||||
|
|
||||||
|
if (items) {
|
||||||
|
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({})
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
const items = data.map(x => x.url)
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
items,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
res.status(404)
|
||||||
|
res.json({
|
||||||
|
error: `Not found`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import Teacher from '../core/Teacher'
|
||||||
|
import { encodePerson } from '../api/persons'
|
||||||
|
import { Request, Response } from 'express'
|
||||||
|
import { prioritySort } from '../lib/sort'
|
||||||
|
|
||||||
|
export async function getItem(req: Request, res: Response) {
|
||||||
|
const id = req.params.slug
|
||||||
|
const data = await Teacher.findById(id)
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
res.json(encodePerson(data))
|
||||||
|
} else {
|
||||||
|
res.status(404)
|
||||||
|
res.json({
|
||||||
|
error: `Resource ${id} not found`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getAll(req: Request, res: Response) {
|
||||||
|
const order = [
|
||||||
|
'mg-timasheva',
|
||||||
|
'va-sarzhin',
|
||||||
|
'od-gogoleva',
|
||||||
|
'my-valkova',
|
||||||
|
'nv-andreeva',
|
||||||
|
'vv-voronova',
|
||||||
|
]
|
||||||
|
const teachersSorted = prioritySort.bind(null, [...order], t => t.id)
|
||||||
|
|
||||||
|
const teachers = await Teacher.find({ hidden: false })
|
||||||
|
const data = teachersSorted(teachers).map(encodePerson)
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
res.json({
|
||||||
|
items: data,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
res.status(404)
|
||||||
|
res.json({
|
||||||
|
error: `Not found`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getUrls(req: Request, res: Response) {
|
||||||
|
const data = await Teacher.find({})
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
const items = data.map(x => `/teacher/${x.id}`)
|
||||||
|
res.json({
|
||||||
|
items
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
res.status(404)
|
||||||
|
res.json({
|
||||||
|
error: `Not found`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { findSchedule } from '../server/schedule'
|
||||||
|
import { Request, Response } from 'express'
|
||||||
|
import { collection } from '../db'
|
||||||
|
|
||||||
|
export async function getItem(req: Request, res: Response) {
|
||||||
|
const period = req.query.period
|
||||||
|
const semester = req.query.semester
|
||||||
|
const data = await findSchedule(collection, period, semester)
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
res.json(data)
|
||||||
|
} else {
|
||||||
|
res.status(404)
|
||||||
|
res.json({
|
||||||
|
error: `Schedule ${period}:${semester} not found`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import Data from './Data'
|
||||||
|
import Image from './Image'
|
||||||
|
import ImageArtifactType from './ImageArtifactType'
|
||||||
|
import { find, findOne } from '../lib/store'
|
||||||
|
|
||||||
|
const previewTypes = [
|
||||||
|
ImageArtifactType.SMALL,
|
||||||
|
ImageArtifactType.MEDIUM,
|
||||||
|
ImageArtifactType.BIG,
|
||||||
|
ImageArtifactType.ORIGIN,
|
||||||
|
]
|
||||||
|
const store = () => Data.getStore('Album')
|
||||||
|
|
||||||
|
export default class Album {
|
||||||
|
public id: string
|
||||||
|
|
||||||
|
static async find(query, options = {}): Promise<Album[]> {
|
||||||
|
const items = await find(store(), query, options as any)
|
||||||
|
|
||||||
|
return Promise.all(items.map(processAlbum))
|
||||||
|
}
|
||||||
|
|
||||||
|
static async findById(id) {
|
||||||
|
const item = await findOne(store(), { id })
|
||||||
|
|
||||||
|
return !item
|
||||||
|
? null
|
||||||
|
: processAlbum(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const previewFromImage = (imgs = []) => imgs.length
|
||||||
|
? imgs[0]
|
||||||
|
: null
|
||||||
|
|
||||||
|
const processAlbum = async album => {
|
||||||
|
let preview = {}
|
||||||
|
// {
|
||||||
|
// data: [
|
||||||
|
// {
|
||||||
|
// url: null,
|
||||||
|
// width: 0,
|
||||||
|
// height: 0,
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// }
|
||||||
|
|
||||||
|
const previewImageId = album.preview ? album.preview : previewFromImage(album.images)
|
||||||
|
if (previewImageId) {
|
||||||
|
const image = await Image.findById(previewImageId)
|
||||||
|
if (image) {
|
||||||
|
// const imageArtifacts = image.findArtifact(previewTypes)
|
||||||
|
// if (imageArtifacts) {
|
||||||
|
// preview = imageArtifacts
|
||||||
|
// }
|
||||||
|
preview = image.getPicture(ImageArtifactType.SMALL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...album,
|
||||||
|
preview,
|
||||||
|
url: `/album/${album.id}`,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,134 @@
|
|||||||
|
import Data from './Data'
|
||||||
|
import Image from './Image'
|
||||||
|
import Config from './Config'
|
||||||
|
import { find, findOne, total } from '../lib/store'
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
return Promise.all(items.map(processArticle))
|
||||||
|
}
|
||||||
|
|
||||||
|
static async findById(id) {
|
||||||
|
const item = await findOne(store(), { id })
|
||||||
|
|
||||||
|
return !item
|
||||||
|
? null
|
||||||
|
: processArticle(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async total(query = {}) {
|
||||||
|
return total(store(), query)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async findPinned() {
|
||||||
|
const now = new Date()
|
||||||
|
const query = { until: { $gte: now } }
|
||||||
|
|
||||||
|
return Article.find(query, {
|
||||||
|
sort: { date: -1 },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public id: any
|
||||||
|
public images: any
|
||||||
|
public post: any
|
||||||
|
public folder: any
|
||||||
|
public date: any
|
||||||
|
public title: any
|
||||||
|
public until: any
|
||||||
|
public tags: any
|
||||||
|
public content: any
|
||||||
|
public hash: any
|
||||||
|
public type: any
|
||||||
|
public origin: any
|
||||||
|
public file: any
|
||||||
|
public version: any
|
||||||
|
public preview: any
|
||||||
|
public url: any
|
||||||
|
|
||||||
|
constructor(data) {
|
||||||
|
this.id = data.id
|
||||||
|
this.images = data.images
|
||||||
|
this.post = data.post
|
||||||
|
this.folder = data.folder
|
||||||
|
this.date = data.date
|
||||||
|
this.title = data.title
|
||||||
|
this.until = data.until
|
||||||
|
this.tags = (data.tags || []).map(make(Tag))
|
||||||
|
this.content = data.content
|
||||||
|
this.hash = data.hash
|
||||||
|
this.type = data.type
|
||||||
|
this.origin = data.origin
|
||||||
|
this.file = data.file
|
||||||
|
this.version = data.version
|
||||||
|
this.preview = data.preview
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Tag {
|
||||||
|
public name: string
|
||||||
|
|
||||||
|
constructor(tag) {
|
||||||
|
this.name = tag;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const previewFromImage = (imgs = []) => imgs.length
|
||||||
|
? imgs[0]
|
||||||
|
: null
|
||||||
|
|
||||||
|
async function resolveDefaultPreview() {
|
||||||
|
const config = await Config.findConfig()
|
||||||
|
if (!config) return null
|
||||||
|
|
||||||
|
return Image.findByFile(config.articleCardDefaultPreview)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processArticle(article) {
|
||||||
|
let preview = null
|
||||||
|
const previewId = article.preview
|
||||||
|
? article.preview
|
||||||
|
: previewFromImage(article.images)
|
||||||
|
|
||||||
|
if (previewId) {
|
||||||
|
preview = await Image.findById(previewId)
|
||||||
|
} else {
|
||||||
|
preview = await resolveDefaultPreview()
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Article({
|
||||||
|
...article,
|
||||||
|
preview,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import Data from './Data'
|
||||||
|
import { findOne } from '../lib/store'
|
||||||
|
|
||||||
|
const store = () => Data.getStore('Settings')
|
||||||
|
|
||||||
|
export default class Config {
|
||||||
|
static async findConfig() {
|
||||||
|
const config = await Config.findById('settings')
|
||||||
|
return config || new Config({} as any)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async findById(id: string) {
|
||||||
|
const data = await findOne(store(), { id })
|
||||||
|
if (!data) return null
|
||||||
|
|
||||||
|
return new Config(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
public hash: any
|
||||||
|
public file: any
|
||||||
|
public server: any
|
||||||
|
public articlesPerPage: any
|
||||||
|
public collectiveOrder: any
|
||||||
|
public articleCardDefaultPreview: any
|
||||||
|
public collectiveImage: any
|
||||||
|
public redirect: any
|
||||||
|
|
||||||
|
constructor({ hash, file, ...data }) {
|
||||||
|
this.hash = hash
|
||||||
|
this.file = file
|
||||||
|
this.server = data.server
|
||||||
|
this.articlesPerPage = data.articlesPerPage || 5
|
||||||
|
this.collectiveOrder = data.collectiveOrder || []
|
||||||
|
this.articleCardDefaultPreview = data.articleCardDefaultPreview
|
||||||
|
this.collectiveImage = data.collectiveImage
|
||||||
|
|
||||||
|
const redirect = data.redirect || {}
|
||||||
|
this.redirect = new Map(Object
|
||||||
|
.entries(redirect)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { Collection } from 'mongodb'
|
||||||
|
import * as db from '../db'
|
||||||
|
|
||||||
|
const store = new Map<string, Collection>()
|
||||||
|
|
||||||
|
export default class Data {
|
||||||
|
static setStore(key: string, collection: Collection) {
|
||||||
|
store.set(key, collection)
|
||||||
|
}
|
||||||
|
|
||||||
|
static getStore<T>(key: string): Collection<T> {
|
||||||
|
return store.get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
static getCollection(name: string): Collection {
|
||||||
|
return db.collection(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import Data from './Data'
|
||||||
|
import Image from './Image'
|
||||||
|
import { find, findOne } from '../lib/store'
|
||||||
|
|
||||||
|
const store = () => Data.getStore('Document')
|
||||||
|
|
||||||
|
export default class Document {
|
||||||
|
static async find(query, options = {}): Promise<Document[]> {
|
||||||
|
const items = await find(store(), query, options as any)
|
||||||
|
|
||||||
|
return Promise.all(items.map(processDocument))
|
||||||
|
}
|
||||||
|
|
||||||
|
static async findById(id) {
|
||||||
|
const data = await findOne(store(), { id })
|
||||||
|
if (!data) return null
|
||||||
|
|
||||||
|
return processDocument(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
public id: any
|
||||||
|
public hash: any
|
||||||
|
public file: any
|
||||||
|
public fileInfo: any
|
||||||
|
public type: any
|
||||||
|
public title: any
|
||||||
|
public category: any
|
||||||
|
public url: any
|
||||||
|
public preview: any
|
||||||
|
public viewUrl: any
|
||||||
|
|
||||||
|
constructor(data) {
|
||||||
|
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)
|
||||||
|
this.type = data.type
|
||||||
|
this.title = data.title
|
||||||
|
this.category = data.category
|
||||||
|
this.url = data.url
|
||||||
|
|
||||||
|
this.preview = data.preview
|
||||||
|
this.viewUrl = `/document/${this.id}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DocumentFile {
|
||||||
|
public name: any
|
||||||
|
public size: any
|
||||||
|
|
||||||
|
constructor(data) {
|
||||||
|
this.name = data.name
|
||||||
|
this.size = data.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const processDocument = async data => {
|
||||||
|
const imageId = data.preview
|
||||||
|
const preview = await Image.findById(imageId)
|
||||||
|
|
||||||
|
return new Document({
|
||||||
|
...data,
|
||||||
|
preview,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import Data from './Data'
|
||||||
|
import ImageArtifactType from './ImageArtifactType'
|
||||||
|
import { findOne } from '../lib/store'
|
||||||
|
import { mapOf } from '../server/lib'
|
||||||
|
|
||||||
|
const store = () => Data.getStore('Image')
|
||||||
|
|
||||||
|
export default class Image {
|
||||||
|
static async findById(id) {
|
||||||
|
const data = await findOne(store(), { _id: id })
|
||||||
|
if (!data) return null
|
||||||
|
|
||||||
|
const artifacts = data.data
|
||||||
|
return new Image({ ...data, artifacts })
|
||||||
|
}
|
||||||
|
|
||||||
|
static async findByFile(file: string) {
|
||||||
|
const data = await findOne(store(), { file })
|
||||||
|
if (!data) return null
|
||||||
|
|
||||||
|
const artifacts = data.data
|
||||||
|
return new Image({ ...data, artifacts })
|
||||||
|
}
|
||||||
|
public hash: any
|
||||||
|
public file: any
|
||||||
|
public artifacts: any
|
||||||
|
|
||||||
|
constructor({ hash, file, artifacts }) {
|
||||||
|
this.hash = hash
|
||||||
|
this.file = file
|
||||||
|
|
||||||
|
this.artifacts = mapOf(ImageArtifact, artifacts)
|
||||||
|
}
|
||||||
|
|
||||||
|
getArtifact(size) {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ImageArtifact {
|
||||||
|
public url: any
|
||||||
|
public size: any
|
||||||
|
public width: any
|
||||||
|
public height: any
|
||||||
|
|
||||||
|
constructor({ url, size, width, height }) {
|
||||||
|
this.url = url
|
||||||
|
this.size = size
|
||||||
|
this.width = width
|
||||||
|
this.height = height
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
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
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import Data from './Data'
|
||||||
|
import { find as storeFind, findOne, total } from '../lib/store'
|
||||||
|
import Image from './Image'
|
||||||
|
import Config from './Config'
|
||||||
|
|
||||||
|
const store = () => Data.getStore('Page')
|
||||||
|
|
||||||
|
export default class Page {
|
||||||
|
static async find(query, options = {}): Promise<Page[]> {
|
||||||
|
const items = await storeFind(store(), query, options as any)
|
||||||
|
|
||||||
|
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) {
|
||||||
|
const item = await findOne(store(), { url })
|
||||||
|
|
||||||
|
return !item
|
||||||
|
? null
|
||||||
|
: processPage(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async total(query = {}) {
|
||||||
|
return total(store(), query)
|
||||||
|
}
|
||||||
|
|
||||||
|
public id: any
|
||||||
|
public hash: any
|
||||||
|
public file: any
|
||||||
|
public url: string
|
||||||
|
public content: any
|
||||||
|
public title: any
|
||||||
|
public data: any
|
||||||
|
public images: any
|
||||||
|
public documents: any
|
||||||
|
public preview: any
|
||||||
|
public tokens: any[]
|
||||||
|
|
||||||
|
constructor(data) {
|
||||||
|
this.id = data.id
|
||||||
|
this.hash = data.hash
|
||||||
|
this.file = data.file
|
||||||
|
this.url = data.url
|
||||||
|
this.content = data.content
|
||||||
|
this.title = data.title
|
||||||
|
this.data = data.data
|
||||||
|
this.images = data.images
|
||||||
|
this.documents = data.documents
|
||||||
|
this.preview = data.preview
|
||||||
|
this.tokens = data.tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
plain() {
|
||||||
|
return {
|
||||||
|
id: this.id,
|
||||||
|
images: this.images,
|
||||||
|
documents: this.documents,
|
||||||
|
data: this.data,
|
||||||
|
title: this.title,
|
||||||
|
content: this.content,
|
||||||
|
hash: this.hash,
|
||||||
|
file: this.file,
|
||||||
|
preview: this.preview,
|
||||||
|
url: this.url,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const previewFromImage = (imgs = []) => imgs.length
|
||||||
|
? imgs[0]
|
||||||
|
: null
|
||||||
|
|
||||||
|
async function resolveDefaultPreview() {
|
||||||
|
const config = await Config.findConfig()
|
||||||
|
if (!config) return null
|
||||||
|
|
||||||
|
return Image.findByFile(config.articleCardDefaultPreview)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function processPage(article) {
|
||||||
|
let preview = null
|
||||||
|
const previewId = article.preview
|
||||||
|
? article.preview
|
||||||
|
: previewFromImage(article.images)
|
||||||
|
|
||||||
|
if (previewId) {
|
||||||
|
preview = await Image.findById(previewId)
|
||||||
|
} else {
|
||||||
|
preview = await resolveDefaultPreview()
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Page({
|
||||||
|
...article,
|
||||||
|
preview,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
import Data from './Data'
|
||||||
|
import Image from './Image'
|
||||||
|
import { find, findOne } from '../lib/store'
|
||||||
|
|
||||||
|
const store = () => Data.getStore('Teacher')
|
||||||
|
|
||||||
|
export default class Teacher {
|
||||||
|
static async find(query, options = {}): Promise<Teacher[]> {
|
||||||
|
const items = await find(store(), query, options as any)
|
||||||
|
|
||||||
|
return Promise.all(items.map(processProfile))
|
||||||
|
}
|
||||||
|
|
||||||
|
static async findById(id) {
|
||||||
|
const data = await findOne(store(), { id })
|
||||||
|
if (!data) return null
|
||||||
|
|
||||||
|
return processProfile(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
public id: any
|
||||||
|
public post: any
|
||||||
|
public file: any
|
||||||
|
public edu: any
|
||||||
|
public picture: any
|
||||||
|
public preview: any
|
||||||
|
public diploma: any
|
||||||
|
public position: any
|
||||||
|
public name: any
|
||||||
|
public status: any
|
||||||
|
public hash: any
|
||||||
|
public url: any
|
||||||
|
|
||||||
|
constructor(data) {
|
||||||
|
this.id = data.id
|
||||||
|
this.post = data.post
|
||||||
|
this.file = data.file
|
||||||
|
this.edu = data.edu
|
||||||
|
this.picture = data.picture
|
||||||
|
this.preview = data.preview
|
||||||
|
this.diploma = data.diploma
|
||||||
|
this.position = data.position
|
||||||
|
this.name = data.name
|
||||||
|
this.status = data.status
|
||||||
|
this.hash = data.hash
|
||||||
|
|
||||||
|
this.url = `/teacher/${this.id}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* "First Middle Last" -> "F. M. Last"
|
||||||
|
*
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
shortName() {
|
||||||
|
const f = i => i.replace(/^(.).+/, '$1')
|
||||||
|
const [first, middle, last] = this.splitName()
|
||||||
|
|
||||||
|
return `${f(first)}. ${f(middle)}. ${last}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* "First Middle Last" -> ["First", "Middle", "Last"]
|
||||||
|
*
|
||||||
|
* @returns {Array|{index: number, input: string}}
|
||||||
|
*/
|
||||||
|
splitName() {
|
||||||
|
const name = this.name
|
||||||
|
const m = /(.+)\s(.+)\s(.+)/.exec(name)
|
||||||
|
return m.slice(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const processProfile = async profile => {
|
||||||
|
const imageId = profile.picture
|
||||||
|
const previewId = profile.preview
|
||||||
|
const picture = await Image.findById(imageId)
|
||||||
|
const preview = await Image.findById(previewId)
|
||||||
|
|
||||||
|
return new Teacher({
|
||||||
|
...profile,
|
||||||
|
picture,
|
||||||
|
preview,
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import {c} from '../core/db';
|
||||||
|
|
||||||
|
const PAGES = 'pages';
|
||||||
|
|
||||||
|
export async function getPageUrls() {
|
||||||
|
return await c(PAGES)
|
||||||
|
.find({}, {url: 1})
|
||||||
|
.toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find a person by it's id
|
||||||
|
*
|
||||||
|
* @param {string} url
|
||||||
|
* @returns {Promise|*}
|
||||||
|
*/
|
||||||
|
export async function getPage(url) {
|
||||||
|
return await c(PAGES).findOne({url: url});
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import Data from './core/Data'
|
||||||
|
import { MongoClient, ObjectId, Db } from 'mongodb'
|
||||||
|
|
||||||
|
var db: Db
|
||||||
|
|
||||||
|
export async function connect(dbName: string, mongoUri: string) {
|
||||||
|
if (db) {
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
const connection = await MongoClient.connect(mongoUri, { useUnifiedTopology: true })
|
||||||
|
db = init(connection.db(dbName))
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
export function collection(name) {
|
||||||
|
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
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
function init(db: Db) {
|
||||||
|
console.log('INIT DB')
|
||||||
|
|
||||||
|
Data.setStore('Page', db.collection('pages'))
|
||||||
|
Data.setStore('Article', db.collection('articles'))
|
||||||
|
Data.setStore('Album', db.collection('albums'))
|
||||||
|
Data.setStore('Image', db.collection('images'))
|
||||||
|
Data.setStore('Teacher', db.collection('collective'))
|
||||||
|
Data.setStore('Document', db.collection('documents'))
|
||||||
|
Data.setStore('Settings', db.collection('settings'))
|
||||||
|
|
||||||
|
return db
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import dotenv from 'dotenv'
|
||||||
|
import express from 'express'
|
||||||
|
import { connect } from './db'
|
||||||
|
import * as person from './controllers/person'
|
||||||
|
import * as article from './controllers/article'
|
||||||
|
import * as album from './controllers/album'
|
||||||
|
import * as schedule from './controllers/schedule'
|
||||||
|
import * as file from './controllers/file'
|
||||||
|
import * as page from './controllers/page'
|
||||||
|
import * as image from './controllers/image'
|
||||||
|
|
||||||
|
dotenv.config()
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const port = parseInt(process.env.PORT, 10) || 3000
|
||||||
|
const mongoUri = process.env.MONGO_URI
|
||||||
|
|
||||||
|
try {
|
||||||
|
await connect('hudozka', mongoUri)
|
||||||
|
|
||||||
|
console.log('connected')
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e.message)
|
||||||
|
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = express()
|
||||||
|
app.get('/persons', person.getAll)
|
||||||
|
app.get('/persons/urls', person.getUrls)
|
||||||
|
app.get('/persons/:slug', person.getItem)
|
||||||
|
|
||||||
|
app.get('/articles', article.getAll)
|
||||||
|
app.get('/articles/urls', article.getUrls)
|
||||||
|
app.get('/articles/:slug', article.getItem)
|
||||||
|
|
||||||
|
app.get('/albums', album.getAll)
|
||||||
|
app.get('/albums/urls', album.getUrls)
|
||||||
|
app.get('/albums/:slug', album.getItem)
|
||||||
|
|
||||||
|
app.get('/files', file.getAll)
|
||||||
|
app.get('/files/urls', file.getUrls)
|
||||||
|
app.get('/files/:slug', file.getItem)
|
||||||
|
|
||||||
|
app.get('/pages', page.getAll)
|
||||||
|
app.get('/pages/urls', page.getUrls)
|
||||||
|
app.get('/pages/:slug', page.getItem)
|
||||||
|
|
||||||
|
app.get('/images/:slug', image.getItem)
|
||||||
|
|
||||||
|
app.get('/schedule', schedule.getItem)
|
||||||
|
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`> Ready on http://localhost:${port}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "esnext",
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": false,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
],
|
||||||
|
"include": [
|
||||||
|
"next-env.d.ts",
|
||||||
|
"**/*.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in new issue
Block a user