From fc0724a900eb1ce9cfea3bba1fa2409bb6b74c12 Mon Sep 17 00:00:00 2001 From: Roman Timashev Date: Sat, 15 Feb 2020 01:38:07 +0300 Subject: [PATCH] api copied to hudozka-api module based on express --- modules/hudozka-api/package.json | 37 +++++ modules/hudozka-api/src/controllers/album.ts | 47 ++++++ .../hudozka-api/src/controllers/article.ts | 55 +++++++ modules/hudozka-api/src/controllers/file.ts | 51 +++++++ modules/hudozka-api/src/controllers/image.ts | 17 +++ modules/hudozka-api/src/controllers/page.ts | 51 +++++++ modules/hudozka-api/src/controllers/person.ts | 60 ++++++++ .../hudozka-api/src/controllers/schedule.ts | 18 +++ modules/hudozka-api/src/core/Album.ts | 65 +++++++++ modules/hudozka-api/src/core/Article.ts | 134 ++++++++++++++++++ modules/hudozka-api/src/core/Config.ts | 42 ++++++ modules/hudozka-api/src/core/Data.ts | 18 +++ modules/hudozka-api/src/core/Document.ts | 68 +++++++++ modules/hudozka-api/src/core/Image.ts | 81 +++++++++++ .../hudozka-api/src/core/ImageArtifactType.js | 19 +++ modules/hudozka-api/src/core/Page.ts | 104 ++++++++++++++ modules/hudozka-api/src/core/Teacher.ts | 87 ++++++++++++ modules/hudozka-api/src/core/pages.js | 19 +++ modules/hudozka-api/src/db.ts | 38 +++++ modules/hudozka-api/src/server.ts | 58 ++++++++ modules/hudozka-api/tsconfig.json | 24 ++++ 21 files changed, 1093 insertions(+) create mode 100644 modules/hudozka-api/package.json create mode 100644 modules/hudozka-api/src/controllers/album.ts create mode 100644 modules/hudozka-api/src/controllers/article.ts create mode 100644 modules/hudozka-api/src/controllers/file.ts create mode 100644 modules/hudozka-api/src/controllers/image.ts create mode 100644 modules/hudozka-api/src/controllers/page.ts create mode 100644 modules/hudozka-api/src/controllers/person.ts create mode 100644 modules/hudozka-api/src/controllers/schedule.ts create mode 100644 modules/hudozka-api/src/core/Album.ts create mode 100644 modules/hudozka-api/src/core/Article.ts create mode 100644 modules/hudozka-api/src/core/Config.ts create mode 100644 modules/hudozka-api/src/core/Data.ts create mode 100644 modules/hudozka-api/src/core/Document.ts create mode 100644 modules/hudozka-api/src/core/Image.ts create mode 100644 modules/hudozka-api/src/core/ImageArtifactType.js create mode 100644 modules/hudozka-api/src/core/Page.ts create mode 100644 modules/hudozka-api/src/core/Teacher.ts create mode 100644 modules/hudozka-api/src/core/pages.js create mode 100644 modules/hudozka-api/src/db.ts create mode 100644 modules/hudozka-api/src/server.ts create mode 100644 modules/hudozka-api/tsconfig.json diff --git a/modules/hudozka-api/package.json b/modules/hudozka-api/package.json new file mode 100644 index 00000000..036a9d82 --- /dev/null +++ b/modules/hudozka-api/package.json @@ -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" + } +} diff --git a/modules/hudozka-api/src/controllers/album.ts b/modules/hudozka-api/src/controllers/album.ts new file mode 100644 index 00000000..4b1734e0 --- /dev/null +++ b/modules/hudozka-api/src/controllers/album.ts @@ -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` + }) + } +} diff --git a/modules/hudozka-api/src/controllers/article.ts b/modules/hudozka-api/src/controllers/article.ts new file mode 100644 index 00000000..3529591b --- /dev/null +++ b/modules/hudozka-api/src/controllers/article.ts @@ -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` + }) + } +} diff --git a/modules/hudozka-api/src/controllers/file.ts b/modules/hudozka-api/src/controllers/file.ts new file mode 100644 index 00000000..5122eb4a --- /dev/null +++ b/modules/hudozka-api/src/controllers/file.ts @@ -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` + }) + } +} diff --git a/modules/hudozka-api/src/controllers/image.ts b/modules/hudozka-api/src/controllers/image.ts new file mode 100644 index 00000000..bd4bc5ca --- /dev/null +++ b/modules/hudozka-api/src/controllers/image.ts @@ -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` + }) + } +} diff --git a/modules/hudozka-api/src/controllers/page.ts b/modules/hudozka-api/src/controllers/page.ts new file mode 100644 index 00000000..ed857ea7 --- /dev/null +++ b/modules/hudozka-api/src/controllers/page.ts @@ -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` + }) + } +} diff --git a/modules/hudozka-api/src/controllers/person.ts b/modules/hudozka-api/src/controllers/person.ts new file mode 100644 index 00000000..e195e94f --- /dev/null +++ b/modules/hudozka-api/src/controllers/person.ts @@ -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` + }) + } +} diff --git a/modules/hudozka-api/src/controllers/schedule.ts b/modules/hudozka-api/src/controllers/schedule.ts new file mode 100644 index 00000000..706bb983 --- /dev/null +++ b/modules/hudozka-api/src/controllers/schedule.ts @@ -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` + }) + } +} diff --git a/modules/hudozka-api/src/core/Album.ts b/modules/hudozka-api/src/core/Album.ts new file mode 100644 index 00000000..b34b6497 --- /dev/null +++ b/modules/hudozka-api/src/core/Album.ts @@ -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 { + 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}`, + } +} diff --git a/modules/hudozka-api/src/core/Article.ts b/modules/hudozka-api/src/core/Article.ts new file mode 100644 index 00000000..c279f325 --- /dev/null +++ b/modules/hudozka-api/src/core/Article.ts @@ -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') + +function make(C) { + return x => new C(x) +} + +export default class Article { + static async find(query, options = {}): Promise { + 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, + }) +} diff --git a/modules/hudozka-api/src/core/Config.ts b/modules/hudozka-api/src/core/Config.ts new file mode 100644 index 00000000..db3188ff --- /dev/null +++ b/modules/hudozka-api/src/core/Config.ts @@ -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) + ) + } +} diff --git a/modules/hudozka-api/src/core/Data.ts b/modules/hudozka-api/src/core/Data.ts new file mode 100644 index 00000000..8b0846d0 --- /dev/null +++ b/modules/hudozka-api/src/core/Data.ts @@ -0,0 +1,18 @@ +import { Collection } from 'mongodb' +import * as db from '../db' + +const store = new Map() + +export default class Data { + static setStore(key: string, collection: Collection) { + store.set(key, collection) + } + + static getStore(key: string): Collection { + return store.get(key) + } + + static getCollection(name: string): Collection { + return db.collection(name) + } +} diff --git a/modules/hudozka-api/src/core/Document.ts b/modules/hudozka-api/src/core/Document.ts new file mode 100644 index 00000000..7dd182b5 --- /dev/null +++ b/modules/hudozka-api/src/core/Document.ts @@ -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 { + 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, + }) +} diff --git a/modules/hudozka-api/src/core/Image.ts b/modules/hudozka-api/src/core/Image.ts new file mode 100644 index 00000000..3e1608f2 --- /dev/null +++ b/modules/hudozka-api/src/core/Image.ts @@ -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 + } +} diff --git a/modules/hudozka-api/src/core/ImageArtifactType.js b/modules/hudozka-api/src/core/ImageArtifactType.js new file mode 100644 index 00000000..360cf5d8 --- /dev/null +++ b/modules/hudozka-api/src/core/ImageArtifactType.js @@ -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 diff --git a/modules/hudozka-api/src/core/Page.ts b/modules/hudozka-api/src/core/Page.ts new file mode 100644 index 00000000..da26be7b --- /dev/null +++ b/modules/hudozka-api/src/core/Page.ts @@ -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 { + 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, + }) +} diff --git a/modules/hudozka-api/src/core/Teacher.ts b/modules/hudozka-api/src/core/Teacher.ts new file mode 100644 index 00000000..582ec73f --- /dev/null +++ b/modules/hudozka-api/src/core/Teacher.ts @@ -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 { + 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, + }) +} diff --git a/modules/hudozka-api/src/core/pages.js b/modules/hudozka-api/src/core/pages.js new file mode 100644 index 00000000..b140f10f --- /dev/null +++ b/modules/hudozka-api/src/core/pages.js @@ -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}); +} diff --git a/modules/hudozka-api/src/db.ts b/modules/hudozka-api/src/db.ts new file mode 100644 index 00000000..048e2813 --- /dev/null +++ b/modules/hudozka-api/src/db.ts @@ -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 +} diff --git a/modules/hudozka-api/src/server.ts b/modules/hudozka-api/src/server.ts new file mode 100644 index 00000000..e462d078 --- /dev/null +++ b/modules/hudozka-api/src/server.ts @@ -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() diff --git a/modules/hudozka-api/tsconfig.json b/modules/hudozka-api/tsconfig.json new file mode 100644 index 00000000..c2b089f6 --- /dev/null +++ b/modules/hudozka-api/tsconfig.json @@ -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" + ] +} \ No newline at end of file