rm hudozka-api
This commit is contained in:
1 parent
bc4e11d538
commit
1348eab71f
26 files changed
-2197
No files matched your search
@@ -1,4 +0,0 @@
|
||||
.env
|
||||
*.tar
|
||||
node_modules
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
node_modules
|
||||
.env
|
||||
*.tar
|
||||
@@ -1,27 +0,0 @@
|
||||
# BUILD
|
||||
FROM node:12-alpine as build
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
COPY . .
|
||||
|
||||
# Building app
|
||||
RUN npm run build
|
||||
|
||||
# RUNTIME
|
||||
FROM node:12-alpine
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm install --production
|
||||
|
||||
COPY --from=build /app/build ./build
|
||||
|
||||
EXPOSE 80
|
||||
ENV PORT 80
|
||||
ENV NODE_ENV production
|
||||
|
||||
# Running the app
|
||||
CMD [ "npm", "start" ]
|
||||
Generated
-1216
File diff suppressed because it is too large.
Load diff
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"name": "hudozka-api",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "src/server.ts",
|
||||
"scripts": {
|
||||
"start": "node build/server.js",
|
||||
"dev": "ts-node src/server.ts",
|
||||
"build": "tsc",
|
||||
"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": {
|
||||
"dotenv": "^8.2.0",
|
||||
"express": "^4.22.1",
|
||||
"feed": "^4.0.0",
|
||||
"lodash": "^4.18.1",
|
||||
"mongodb": "^3.7.4",
|
||||
"sitemap": "^1.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/classnames": "^2.2.9",
|
||||
"@types/express": "^4.17.2",
|
||||
"@types/mongodb": "^3.3.16",
|
||||
"@types/node": "^13.7.1",
|
||||
"ts-node": "^8.6.2",
|
||||
"typescript": "^3.7.5"
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { getPathWaterfall } from '../lib/url'
|
||||
import { Breadcrumb } from '../types'
|
||||
import Page from '../core/Page'
|
||||
|
||||
export async function getItems(path: string): Promise<Breadcrumb> {
|
||||
const bc = getPathWaterfall(path)
|
||||
const parts = await Promise.all(bc.map(async href => {
|
||||
const resource = await Page.findByUrl(href)
|
||||
|
||||
if (!resource) {
|
||||
return null
|
||||
}
|
||||
|
||||
const name = resource.title
|
||||
|
||||
return {
|
||||
name,
|
||||
href,
|
||||
}
|
||||
}))
|
||||
|
||||
return parts.filter(Boolean)
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
import { Request, Response } from 'express'
|
||||
import Document from '../core/Document'
|
||||
import { encodeFile, encodeFileUrl } from '../factory/file'
|
||||
|
||||
export async function getItem(req: Request, res: Response) {
|
||||
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 ${slug} not found`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUrls(req: Request, res: Response) {
|
||||
const data = await Document.find({})
|
||||
|
||||
if (data) {
|
||||
const items = data.map(encodeFileUrl)
|
||||
res.json({
|
||||
items
|
||||
})
|
||||
} else {
|
||||
res.status(404)
|
||||
res.json({
|
||||
error: `Not found`
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
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 {
|
||||
res.status(404)
|
||||
res.json({
|
||||
error: `Resource ${file} not found`
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
import { pick } from 'lodash'
|
||||
import { Request, Response } from 'express'
|
||||
import Page from '../core/Page'
|
||||
import { dropTrailingSlash, readQueryArray, readQueryInt, readQueryString } from '../lib/url'
|
||||
import { encodePage } from '../factory/page'
|
||||
import * as breadcrumb from './breadcrumb'
|
||||
import { isArrayOk } from '../lib/array'
|
||||
|
||||
function getSort(sortBy: string) {
|
||||
if (!sortBy) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (sortBy.charAt(0) === '-') {
|
||||
const key = sortBy.substr(1, sortBy.length)
|
||||
|
||||
return {
|
||||
[key]: -1,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
[sortBy]: 1,
|
||||
}
|
||||
}
|
||||
|
||||
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 pageUrl = dropTrailingSlash(req.query.page)
|
||||
const resource = await Page.findByUrl(pageUrl)
|
||||
|
||||
if (resource) {
|
||||
const page = await resolveItem(resource)
|
||||
res.json(encodePage(page))
|
||||
} else {
|
||||
res.status(404)
|
||||
res.json({
|
||||
error: `Resource ${pageUrl} 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 getByTags(req: Request, res: Response) {
|
||||
const skip = readQueryInt(req, 'skip')
|
||||
const limit = readQueryInt(req, 'limit')
|
||||
const sortBy = readQueryString(req, 'sortBy')
|
||||
const sort = getSort(sortBy)
|
||||
const fields = readQueryArray(req, 'field')
|
||||
|
||||
const tags = readQueryArray(req, 'tag')
|
||||
if (tags.length === 0) {
|
||||
res.status(400)
|
||||
return res.json({
|
||||
error: `No tags in request`
|
||||
})
|
||||
}
|
||||
|
||||
const options = { skip, limit, sort }
|
||||
const models = await Page.find({
|
||||
tags: { $in: tags },
|
||||
}, options)
|
||||
|
||||
if (models) {
|
||||
const pages = await Promise.all(models.map(resolveItem))
|
||||
const encodedItems = pages.map(encodePage)
|
||||
const items = isArrayOk(fields)
|
||||
? encodedItems.map(x => pick(x, fields))
|
||||
: encodedItems
|
||||
|
||||
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`
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import Page from '../core/Page'
|
||||
import { Request, Response } from 'express'
|
||||
import { unique } from '../lib/array'
|
||||
|
||||
export async function getAll(req: Request, res: Response) {
|
||||
const data = await Page.find({})
|
||||
const tags = data
|
||||
.flatMap(x => x.tags)
|
||||
.filter(x => x.isValid())
|
||||
const items = unique(tags, t => t.slug)
|
||||
|
||||
if (data) {
|
||||
res.json({
|
||||
items,
|
||||
})
|
||||
} else {
|
||||
res.status(404)
|
||||
res.json({
|
||||
error: `Not found`
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
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 findBySlug(slug: string) {
|
||||
const data = await findOne(store(), { id: slug })
|
||||
if (!data) return null
|
||||
|
||||
return processDocument(data)
|
||||
}
|
||||
|
||||
public id: any
|
||||
public hash: any
|
||||
public file: any
|
||||
public fileInfo: FileInfo
|
||||
public type: any
|
||||
public title: any
|
||||
public category: any
|
||||
public url: any
|
||||
public preview: Image
|
||||
public viewUrl: any
|
||||
|
||||
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 FileInfo(data.fileInfo)
|
||||
: new FileInfo(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 FileInfo {
|
||||
public name: string
|
||||
public size: number
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
import Data from './Data'
|
||||
import { findOne } from '../lib/store'
|
||||
import { ObjectId } from 'mongodb'
|
||||
|
||||
/**
|
||||
*
|
||||
* {a: {}, b: {}, ...} -> Map (a: Class, b: Class)
|
||||
*
|
||||
* @param Class
|
||||
* @param object
|
||||
*/
|
||||
function mapOf(Class, object) {
|
||||
return Object
|
||||
.entries(object)
|
||||
.reduce((map, i) => (
|
||||
map.set(i[0], new Class(i[1]))
|
||||
), new Map())
|
||||
}
|
||||
|
||||
const store = () => Data.getStore('Image')
|
||||
|
||||
export default class Image {
|
||||
static async findById(id: ObjectId) {
|
||||
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: string
|
||||
public file: string
|
||||
public artifacts: Map<string, ImageArtifact>
|
||||
|
||||
constructor({ hash, file, artifacts }) {
|
||||
this.hash = hash
|
||||
this.file = file
|
||||
|
||||
this.artifacts = mapOf(ImageArtifact, artifacts)
|
||||
}
|
||||
|
||||
getArtifact(size: string) {
|
||||
return this.artifacts.get(size)
|
||||
}
|
||||
}
|
||||
|
||||
class ImageArtifact {
|
||||
public url: string
|
||||
public size: string
|
||||
public width: number
|
||||
public height: number
|
||||
|
||||
constructor({ url, size, width, height }) {
|
||||
this.url = url
|
||||
this.size = size
|
||||
this.width = width
|
||||
this.height = height
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
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)
|
||||
|
||||
return Promise.all(items.map(processPage))
|
||||
}
|
||||
|
||||
static async findByUrl(url: string) {
|
||||
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 date: Date
|
||||
public tags: Tag[]
|
||||
public data: any
|
||||
public images: ObjectId[]
|
||||
public documents: any
|
||||
public preview?: Image
|
||||
public tokens: any[]
|
||||
public description: string
|
||||
public featured: boolean
|
||||
|
||||
private breadcrumb: Breadcrumb
|
||||
|
||||
constructor(data: { [name: string]: any }) {
|
||||
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.date = data.date
|
||||
this.images = data.images
|
||||
this.documents = data.documents
|
||||
this.preview = data.preview
|
||||
this.tokens = data.tokens
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
function firstItem<T>(items: T[] = []): T {
|
||||
return items.length
|
||||
? items[0]
|
||||
: null
|
||||
}
|
||||
|
||||
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({
|
||||
...item,
|
||||
preview,
|
||||
})
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
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(name: string, slug: string) {
|
||||
this.name = name
|
||||
this.slug = slug
|
||||
}
|
||||
|
||||
public isValid() {
|
||||
return !!this.slug
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
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: string) {
|
||||
return db.collection(name)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
function init(db: Db) {
|
||||
console.log('INIT DB')
|
||||
|
||||
Data.setStore('Page', db.collection('pages'))
|
||||
Data.setStore('Image', db.collection('images'))
|
||||
Data.setStore('Document', db.collection('documents'))
|
||||
Data.setStore('Settings', db.collection('settings'))
|
||||
|
||||
return db
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import Document from '../core/Document'
|
||||
import { encodeImage, ImageDto } from './image'
|
||||
|
||||
export type FileDto = {
|
||||
url: string
|
||||
slug: string
|
||||
name: string
|
||||
cover?: ImageDto
|
||||
filepath: string
|
||||
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,
|
||||
filepath: file.file,
|
||||
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}`
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import Image from '../core/Image'
|
||||
|
||||
const ORIGINAL = 'original'
|
||||
|
||||
export type ImageDto = {
|
||||
width: number
|
||||
height: number
|
||||
src: string
|
||||
filepath: string
|
||||
}
|
||||
|
||||
export function encodeImage(image: Image): ImageDto {
|
||||
const a = image.getArtifact(ORIGINAL)
|
||||
if (!image) {
|
||||
return null
|
||||
}
|
||||
|
||||
return {
|
||||
src: a.url,
|
||||
width: a.width,
|
||||
height: a.height,
|
||||
filepath: image.file,
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
import { encodeImage, ImageDto } from './image'
|
||||
import Page from '../core/Page'
|
||||
import { Breadcrumb } from '../types'
|
||||
import { Tag } from '../core/Tag'
|
||||
|
||||
type Token = {
|
||||
token: string
|
||||
data: any
|
||||
}
|
||||
|
||||
type PageDto = {
|
||||
title: string
|
||||
date?: string
|
||||
url: string
|
||||
data: string
|
||||
description: string
|
||||
cover?: ImageDto
|
||||
tokens: Token[]
|
||||
tags: Tag[]
|
||||
featured: boolean
|
||||
breadcrumb: Breadcrumb
|
||||
}
|
||||
|
||||
export function encodePage(page: Page): PageDto {
|
||||
const cover = page.preview ? encodeImage(page.preview) : null
|
||||
const date = page.date ? page.date.toString() : null
|
||||
const breadcrumb = page.getBreadcrumb()
|
||||
|
||||
return {
|
||||
title: page.title,
|
||||
url: page.url,
|
||||
data: page.data,
|
||||
description: page.description,
|
||||
tokens: page.tokens,
|
||||
tags: page.tags,
|
||||
featured: page.featured,
|
||||
cover,
|
||||
date,
|
||||
breadcrumb,
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
export function unique<T>(items: T[], key: (item: T) => string): T[] {
|
||||
const map = new Map(items.map(x => [key(x), x]))
|
||||
|
||||
return [...map.values()]
|
||||
}
|
||||
|
||||
export function ensureArray<T>(value: T | T[]): T[] {
|
||||
if (!value) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value
|
||||
}
|
||||
|
||||
return [value]
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Check if array has one item at least
|
||||
*
|
||||
* @param items
|
||||
*/
|
||||
export function isArrayOk<T>(items: T[]): boolean {
|
||||
if (!Array.isArray(items)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return items.length > 0
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
export async function find(collection, query, { sort, skip, limit }) {
|
||||
const cursor = collection.find(query)
|
||||
|
||||
if (sort) cursor.sort(sort)
|
||||
if (skip) cursor.skip(skip)
|
||||
if (limit) cursor.limit(limit)
|
||||
|
||||
return cursor.toArray()
|
||||
}
|
||||
|
||||
export async function findOne(collection, query) {
|
||||
return collection.findOne(query)
|
||||
}
|
||||
|
||||
export async function total(collection, query) {
|
||||
return collection
|
||||
.find(query)
|
||||
.count()
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
const map = new Map<string, string>([
|
||||
['', '_'],
|
||||
|
||||
['А', 'A'],
|
||||
['а', 'a'],
|
||||
['Б', 'B'],
|
||||
['б', 'b'],
|
||||
['В', 'V'],
|
||||
['в', 'v'],
|
||||
['Г', 'G'],
|
||||
['г', 'g'],
|
||||
['Д', 'D'],
|
||||
['д', 'd'],
|
||||
['Е', 'E'],
|
||||
['е', 'e'],
|
||||
['Ё', 'E'],
|
||||
['ё', 'e'],
|
||||
['Ж', 'Zh'],
|
||||
['ж', 'zh'],
|
||||
['З', 'Z'],
|
||||
['з', 'z'],
|
||||
['И', 'I'],
|
||||
['и', 'i'],
|
||||
['Й', 'Y'],
|
||||
['й', 'y'],
|
||||
['К', 'K'],
|
||||
['к', 'k'],
|
||||
['Л', 'L'],
|
||||
['л', 'l'],
|
||||
['М', 'M'],
|
||||
['м', 'm'],
|
||||
['Н', 'N'],
|
||||
['н', 'n'],
|
||||
['О', 'O'],
|
||||
['о', 'o'],
|
||||
['П', 'P'],
|
||||
['п', 'p'],
|
||||
['Р', 'R'],
|
||||
['р', 'r'],
|
||||
['С', 'S'],
|
||||
['с', 's'],
|
||||
['Т', 'T'],
|
||||
['т', 't'],
|
||||
['У', 'U'],
|
||||
['у', 'u'],
|
||||
['Ф', 'F'],
|
||||
['ф', 'f'],
|
||||
['Х', 'Kh'],
|
||||
['х', 'kh'],
|
||||
['Ц', 'Ts'],
|
||||
['ц', 'ts'],
|
||||
['Ч', 'Ch'],
|
||||
['ч', 'ch'],
|
||||
['Ш', 'Sh'],
|
||||
['ш', 'sh'],
|
||||
['Щ', 'Sch'],
|
||||
['щ', 'sch'],
|
||||
['ь', ''],
|
||||
['Ы', 'Y'],
|
||||
['ы', 'y'],
|
||||
['ъ', ''],
|
||||
['Э', 'E'],
|
||||
['э', 'e'],
|
||||
['Ю', 'Yu'],
|
||||
['ю', 'yu'],
|
||||
['Я', 'Ya'],
|
||||
['я', 'ya'],
|
||||
])
|
||||
|
||||
export function translit(sample: string): string {
|
||||
return [...sample]
|
||||
.map(char => {
|
||||
if (map.has(char)) {
|
||||
return map.get(char)
|
||||
}
|
||||
|
||||
return ''
|
||||
})
|
||||
.join('')
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import { Request } from 'express'
|
||||
import { ensureArray } from './array'
|
||||
|
||||
export function dropTrailingSlash(path: string) {
|
||||
if (path === '/' || path === '') {
|
||||
return '/'
|
||||
}
|
||||
|
||||
const lastChar = path.charAt(path.length - 1)
|
||||
if (lastChar === '/') {
|
||||
return path.substr(0, path.length - 1)
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
export function getPathWaterfall(path: string): string[] {
|
||||
const parts = path
|
||||
.split('/')
|
||||
.filter(Boolean)
|
||||
.reduce((acc, part, index, parts) => {
|
||||
const i = index + 1
|
||||
const item = `/${parts.slice(0, i).join('/')}`
|
||||
return [
|
||||
...acc,
|
||||
item
|
||||
]
|
||||
}, [])
|
||||
|
||||
return ['/', ...parts]
|
||||
}
|
||||
|
||||
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 readQueryString(req: Request, key: string) {
|
||||
return `${req.query[key]}`
|
||||
}
|
||||
|
||||
export function readQueryArray(req: Request, key: string) {
|
||||
return ensureArray(req.query[key])
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import { config } from 'dotenv'
|
||||
import express from 'express'
|
||||
import { connect } from './db'
|
||||
import * as file from './controllers/file'
|
||||
import * as page from './controllers/page'
|
||||
import * as image from './controllers/image'
|
||||
import * as tag from './controllers/tag'
|
||||
|
||||
async function main() {
|
||||
const port = process.env.PORT || '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('/files/urls', file.getUrls)
|
||||
app.get('/files/:slug', file.getItem)
|
||||
|
||||
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)
|
||||
|
||||
app.get('/tags', tag.getAll)
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`> Ready on http://localhost:${port}`)
|
||||
})
|
||||
}
|
||||
|
||||
config()
|
||||
main()
|
||||
@@ -1,6 +0,0 @@
|
||||
export type BreadcrumbPart = {
|
||||
name: string
|
||||
href: string
|
||||
}
|
||||
|
||||
export type Breadcrumb = BreadcrumbPart[]
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./build",
|
||||
"target": "esnext",
|
||||
"lib": [
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
],
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
]
|
||||
}
|
||||
Reference in new issue
Block a user