Documents serverside

This commit is contained in:
tmshv committed 2017-05-26 13:44:53 +03:00
1 parent 244f1efd03
commit 864d895105
17 files changed
+528 -119

No files matched your search

+1 -37
View File
@@ -54,7 +54,7 @@ import API from './services/api'
import Theme from './services/theme'
import {sortByPattern} from '../utils/sort'
import {documentsSortPattern, collectiveSortPattern} from './config'
import {documentsSortPattern} from './config'
let app = angular.module('hudozka', [
'ngRoute',
@@ -92,42 +92,6 @@ app.config(($locationProvider, $routeProvider) => {
.list()
.then(i => i.data)
}
},
{
name: '/awards',
template: '<page-awards items="$resolve.awards"></page-awards>',
title: 'Награды',
resolve: {
awards: api => api.document
.awards()
.then(i => i.data),
}
},
{
name: '/documents',
template: '<page-documents documents="$resolve.documents"></page-documents>',
title: 'Документы',
resolve: {
documents: api => api.document
.documents()
.then(i => i.data)
.then(i => {
return sortByPattern(i, documentsSortPattern, i => i.category)
})
}
},
{
name: '/documents/:id',
template: '<page-document item="$resolve.document"></page-document>',
resolve: {
document: ($route, api) => {
let id = $route.current.params.id
return api.document
.id(id)
.then(i => i.data)
}
}
}
]
.concat(pages)
+23
View File
@@ -0,0 +1,23 @@
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
+20
View File
@@ -0,0 +1,20 @@
const React = require('react')
const Document = require('./DocumentListItem')
const DocumentList = ({documents, name}) => (
<div className="documents-container">
<div className="documents-container__head">
<h2>{name}</h2>
</div>
<div className="documents-container__body">
{documents.map((d, i) => (
<div>
<Document key={i} {...d}/>
</div>
))}
</div>
</div>
)
module.exports = DocumentList
+23
View File
@@ -0,0 +1,23 @@
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-row">
<a href={url} className="invisible">
<div className="document-row__image">
<img src={imageUrl} alt={title}/>
</div>
</a>
<div className="document-row__file">
<a href={url}>{titleCase(title)}</a>
</div>
<div className="document-row__file-info">
<a href={fileUrl} target="_blank">{ext(fileName)} ({size(fileSize)})</a>
</div>
</div>
)
module.exports = Document
+54
View File
@@ -0,0 +1,54 @@
const Data = require('./Data')
const Image = require('./Image')
const {find, findOne} = require('../lib/store')
const store = () => Data.getStore(Document)
class Document {
static async find(query, options = {}) {
const items = await find(store(), query, options)
return Promise.all(items.map(processDocument))
}
static async findById(id) {
const data = await findOne(store(), {id})
if (!data) return null
return processDocument(data)
}
constructor(data) {
this.id = data.id
this.hash = data.hash
this.file = 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 {
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,
})
}
module
.exports = Document
+2
View File
@@ -1,6 +1,7 @@
const Data = require('./Data')
const Album = require('./Album')
const Teacher = require('./Teacher')
const Document = require('./Document')
const Image = require('./Image')
const {MongoClient, ObjectId} = require('mongodb')
@@ -30,6 +31,7 @@ function init(db) {
Data.setStore(Album, db.collection('albums'))
Data.setStore(Image, db.collection('images'))
Data.setStore(Teacher, db.collection('collective'))
Data.setStore(Document, db.collection('documents'))
return db
}
+12
View File
@@ -0,0 +1,12 @@
function splitBy(key) {
return items => {
return items.reduce((acc, item) => {
const name = key(item)
const items = acc.get(name) || []
return acc.set(key(item), [...items, item])
}, new Map())
}
}
exports.splitBy = splitBy
+27
View File
@@ -0,0 +1,27 @@
function ext(filname) {
let getExt = name => /\.(\w+)$/.exec(name)[1]
let defaultExt = ext => ext.toUpperCase()
const exts = ['png', 'pdf', 'jpg', 'jpeg', 'gif', 'doc']
const extd = ['ПНГ', 'ПДФ', 'ЖПГ', 'ЖПГ', 'ГИФ', 'ДОК']
let ext = getExt(filname)
const i = exts.indexOf(ext)
return i > -1
? extd[i]
: defaultExt(ext)
}
function size(bytes, precision = 1) {
if (isNaN(parseFloat(bytes)) || !isFinite(bytes)) return ''
const units = ['байт', 'КБ', 'МБ', 'ГБ', 'ТБ', 'ПБ']
const number = Math.floor(Math.log(bytes) / Math.log(1024))
const size = (bytes / Math.pow(1024, Math.floor(number))).toFixed(precision)
return `${size} ${units[number]}`
}
exports.ext = ext
exports.size = size
+12
View File
@@ -0,0 +1,12 @@
function titleCase(str) {
return str.charAt(0).toUpperCase() + str.slice(1)
}
function trim(string) {
return string
.replace(/^\s+/, '')
.replace(/\s+$/, '')
}
exports.titleCase = titleCase
exports.trim = trim
+6 -3
View File
@@ -56,9 +56,12 @@ export default [
]
},
{
'text': 'Документы',
'url': '/documents',
'color': 'yellow'
text: 'Документы',
url: '/documents',
color: 'yellow',
items: [
{url: '/document/*', text: ''},
]
},
{
url: '/info/',
+144
View File
@@ -0,0 +1,144 @@
const React = require('react')
const Share = require('../components/Share')
const DocumentComponent = require('../components/Document')
const Document = require('../core/Document')
const ImageArtifactType = require('../core/ImageArtifactType')
const {render} = require('../lib/render')
const {prioritySort} = require('../lib/sort')
const getPathWithNoTrailingSlash = require('../lib/url').getPathWithNoTrailingSlash
const {get} = require('koa-route')
const {splitBy} = require('../lib/array')
const {unique} = require('../utils/common')
const sizes = [
ImageArtifactType.MEDIUM,
ImageArtifactType.BIG,
ImageArtifactType.ORIGIN,
]
function getMeta(document) {
return {
title: document.title,
description: document.title,
}
}
async function bakeDocument(document) {
//const preview = await document.preview.unpack()
const preview = document.preview
if (!preview) return null
const image = preview.findArtifact(sizes)
if (!image) return null
return {
category: document.category,
title: document.title,
fileName: document.file.name,
fileSize: document.file.size,
fileUrl: document.url,
imageUrl: image.url,
url: document.viewUrl,
}
}
function getDocument() {
return get('/document/:id', async (ctx, id) => {
const path = getPathWithNoTrailingSlash(ctx.path)
const document = await Document.findById(id)
const data = document
? await bakeDocument(document)
: null
if (data) {
const Component = (
<div className="content content_wide">
<DocumentComponent {...data}/>
<Share/>
</div>
)
ctx.type = 'text/html'
ctx.body = await render(path, Component, getMeta(document))
} else {
ctx.status = 404
}
})
}
function getSorted(documents) {
const compose = (...fns) => value => fns
.map(fn => fn)
.reverse()
.reduce((acc, fn) => fn(acc), value)
const pass = fn => value => {
fn(value)
return value
}
const subtract = (a, b) => a - b
const uniqueCategories = unique(i => i.category)
const priority = {
'Основные документы': [
'https://static.shlisselburg.org/art/uploads/ustav-2015.pdf',
'https://static.shlisselburg.org/art/uploads/obrazovatelnaia-litsenziia-2015.pdf',
'https://static.shlisselburg.org/art/uploads/svidetelstvo-na-pomeshchenie.pdf',
'https://static.shlisselburg.org/art/uploads/egriul.pdf',
'https://static.shlisselburg.org/art/uploads/inn.pdf',
'https://static.shlisselburg.org/art/uploads/reshenie-o-sozdanii-shkoly.pdf',
'https://static.shlisselburg.org/art/uploads/kopiia-resheniia-o-naznachenii-rukovoditelia.pdf',
'https://static.shlisselburg.org/art/uploads/postanovlenie-o-sozdanii-munitsipalnogo-obrazovatelnogo-uchrezhdeniia-kultury-detskaia-khudozhestvennaia-shkola.pdf',
],
'Учебные программы': [
'https://static.shlisselburg.org/art/uploads/uchebnaia-programma-zhivopis.pdf',
]
}
const CATEGORY_PRIORITY = [
'Основные документы',
'Планы ФХД',
'Самообследование деятельности',
'Инструкции',
'Образование',
'Учебные программы',
'Платные образовательные услуги',
'Дополнительные общеразвивающие программы',
'Документы для поступления',
]
const sortBy = fn => (a, b) => [a, b]
.map(fn)
.reduce(subtract)
const getPriorityFn = (m, list) => value => list.includes(value)
? list.indexOf(value)
: m
const safeSelect = (d, store, key) => key in store
? store[key]
: d
const categoryPriority = getPriorityFn(10000, CATEGORY_PRIORITY)
const documentPriority = ({category, fileUrl}) => compose(
getPriorityFn.bind(null, 10000),
safeSelect.bind(null, [], priority)
)(category)(fileUrl)
const documentsOf = category => documents.filter(i => i.category === category)
const sorted = documents => documents.sort(sortBy(documentPriority))
const collectDocumentsByCategory = compose(sorted, documentsOf)
return uniqueCategories(documents)
.sort(sortBy(categoryPriority))
.reduce((acc, name) => [...acc, {
name,
items: collectDocumentsByCategory(name)
}], [])
}
exports.getDocument = getDocument
+137 -64
View File
@@ -1,82 +1,155 @@
import compose from 'koa-compose'
import route from 'koa-route'
import {index, json, accepts} from './'
import {c} from '../core/db'
const React = require('react')
const Article = require('../components/Article')
const DocumentList = require('../components/DocumentList')
const Document = require('../core/Document')
const ImageArtifactType = require('../core/ImageArtifactType')
const {render} = require('../lib/render')
const {prioritySort} = require('../lib/sort')
const getPathWithNoTrailingSlash = require('../lib/url').getPathWithNoTrailingSlash
const {get} = require('koa-route')
const {splitBy} = require('../lib/array')
const {unique} = require('../utils/common')
async function exists(ctx, id) {
let i = await c('documents').findOne({id: id})
return Boolean(i)
const sizes = [
ImageArtifactType.MEDIUM,
ImageArtifactType.BIG,
ImageArtifactType.ORIGIN,
]
function getDocumentsMeta() {
return {
title: 'Документы',
description: 'Документы Шлиссельбургской ДХШ',
}
}
export default function (store) {
return compose([
docs(store),
documents(),
document(),
type()
])
function getMeta(teacher) {
return {
title: teacher.name,
description: teacher.name,
}
}
function docs(store) {
return route.get('/docs', async ctx => {
const docs = await store.document.find({})
ctx.body = docs
})
async function bakeDocument(document) {
//const preview = await document.preview.unpack()
const preview = document.preview
if (!preview) return null
const image = preview.findArtifact(sizes)
if (!image) return null
return {
category: document.category,
title: document.title,
fileName: document.file.name,
fileSize: document.file.size,
fileUrl: document.url,
imageUrl: image.url,
url: document.viewUrl,
}
}
function documents() {
return route.get('/documents', json(
async(ctx) => {
let query = {}
ctx.body = await c('documents')
.find(query)
.toArray()
}
))
}
function getDocuments() {
return get('/documents', async (ctx) => {
const path = getPathWithNoTrailingSlash(ctx.path)
let documents = await Document.find({})
function document() {
return route.get('/documents/:id', accepts({
'text/html': index(exists),
'text/plain': index(exists),
'application/json': async(ctx, id) => {
let i = await c('documents').findOne({id: id})
if (!i) ctx.status = 404
else {
i = await populateDocPreview(i)
ctx.body = i
}
}
}))
}
if (documents) {
//documents = teachersSorted(documents)
documents = await Promise.all(documents.map(bakeDocument))
documents = documents.filter(Boolean)
function type() {
return route.get('/documents/type/:type', async(ctx, type) => {
let types = {
'document': ['documents', {type: 'document'}, populateDocPreview],
'award': ['awards', {type: 'award'}, populateDocPreview]
}
//const collections = splitBy(d => d.category)(documents)
const collections = getSorted(documents)
if (type in types) {
let [collection, query, mapFn] = types[type]
const Component = (
<div className="content content_thin">
{collections.map(({name, items}, index) => (
<DocumentList key={index} name={name} documents={items}/>
))}
</div>
)
let docs = await c(collection)
.find(query)
.toArray()
if (mapFn) {
docs = await Promise.all(
docs.map(mapFn)
) }
ctx.body = docs
ctx.type = 'text/html'
ctx.body = await render(path, Component, getDocumentsMeta())
} else {
ctx.status = 404
}
})
}
async function populateDocPreview(doc) {
doc.preview = await c('images').findOne({_id: doc.preview})
return doc
function getSorted(documents) {
const compose = (...fns) => value => fns
.map(fn => fn)
.reverse()
.reduce((acc, fn) => fn(acc), value)
const pass = fn => value => {
fn(value)
return value
}
const subtract = (a, b) => a - b
const uniqueCategories = unique(i => i.category)
const priority = {
'Основные документы': [
'https://static.shlisselburg.org/art/uploads/ustav-2015.pdf',
'https://static.shlisselburg.org/art/uploads/obrazovatelnaia-litsenziia-2015.pdf',
'https://static.shlisselburg.org/art/uploads/svidetelstvo-na-pomeshchenie.pdf',
'https://static.shlisselburg.org/art/uploads/egriul.pdf',
'https://static.shlisselburg.org/art/uploads/inn.pdf',
'https://static.shlisselburg.org/art/uploads/reshenie-o-sozdanii-shkoly.pdf',
'https://static.shlisselburg.org/art/uploads/kopiia-resheniia-o-naznachenii-rukovoditelia.pdf',
'https://static.shlisselburg.org/art/uploads/postanovlenie-o-sozdanii-munitsipalnogo-obrazovatelnogo-uchrezhdeniia-kultury-detskaia-khudozhestvennaia-shkola.pdf',
],
'Учебные программы': [
'https://static.shlisselburg.org/art/uploads/uchebnaia-programma-zhivopis.pdf',
]
}
const CATEGORY_PRIORITY = [
'Основные документы',
'Планы ФХД',
'Самообследование деятельности',
'Инструкции',
'Образование',
'Учебные программы',
'Платные образовательные услуги',
'Дополнительные общеразвивающие программы',
'Документы для поступления',
]
const sortBy = fn => (a, b) => [a, b]
.map(fn)
.reduce(subtract)
const getPriorityFn = (m, list) => value => list.includes(value)
? list.indexOf(value)
: m
const safeSelect = (d, store, key) => key in store
? store[key]
: d
const categoryPriority = getPriorityFn(10000, CATEGORY_PRIORITY)
const documentPriority = ({category, fileUrl}) => compose(
getPriorityFn.bind(null, 10000),
safeSelect.bind(null, [], priority)
)(category)(fileUrl)
const documentsOf = category => documents.filter(i => i.category === category)
const sorted = documents => documents.sort(sortBy(documentPriority))
const collectDocumentsByCategory = compose(sorted, documentsOf)
return uniqueCategories(documents)
.sort(sortBy(categoryPriority))
.reduce((acc, name) => [...acc, {
name,
items: collectDocumentsByCategory(name)
}], [])
}
exports.getDocuments = getDocuments
-2
View File
@@ -7,14 +7,12 @@ import {index as indexFile} from '../config'
import sitemap from './sitemap'
import schedule from './schedule'
import documents from './documents'
import pages from './pages'
export function routes(store) {
return compose([
sitemap(),
schedule(),
documents(store),
pages(),
])
}
+4
View File
@@ -25,6 +25,8 @@ const article = require('./routes/articles')
const albums = require('./routes/albums')
const gallery = require('./routes/gallery')
const teachers = require('./routes/teachers')
const documents = require('./routes/documents')
const document = require('./routes/document')
const dirPublic = path.join(__dirname, '../public')
const dirTemplates = path.join(__dirname, 'templates')
@@ -70,6 +72,8 @@ export default function (store) {
app.use(albums.getAlbum())
app.use(teachers.getCollective(collectiveOrder))
app.use(teachers.getTeacher())
app.use(documents.getDocuments())
app.use(document.getDocument())
app.use(routes(store))
return Server(app.callback())
+50
View File
@@ -0,0 +1,50 @@
@import "../variables";
.document-row {
display: flex;
flex-direction: row;
padding: 0;
margin-bottom: $single-margin;
}
.document-row__image{
min-width: 4em;
width: 4em;
height: 4em;
display: flex;
justify-content: center;
align-items: flex-start;
//align-items: center;
img {
max-width: 100%;
max-height: 100%;
box-shadow: $white-shadow;
}
}
.document-row__image--medium{
}
.document-row__image--big{
max-width: 20em;
}
.document-row__file {
margin-left: $single-margin;
flex-grow: 10;
}
.document-row__file-info {
width: 25%;
flex-grow: 1;
font-size: $smaller-font-size;
text-align: right;
}
@media (max-width: $mobile-width) {
.document-row {
margin: 0 $single-margin/2 $single-margin;
}
}
+12 -13
View File
@@ -2,19 +2,22 @@
.document {
display: flex;
flex-direction: row;
flex-direction: column;
align-items: center;
padding: 0;
margin-bottom: $single-margin;
margin-bottom: $double-margin;
}
.document__image{
min-width: 4em;
width: 4em;
height: 4em;
//min-width: 10em;
//width: 20em;
//height: 20em;
display: flex;
justify-content: center;
align-items: flex-start;
margin-bottom: $single-margin;
//display: flex;
//justify-content: center;
//align-items: flex-start;
//align-items: center;
img {
@@ -32,15 +35,11 @@
}
.document__file {
margin-left: $single-margin;
flex-grow: 10;
}
.document__file-info {
width: 25%;
flex-grow: 1;
font-size: $smaller-font-size;
text-align: right;
}
@media (max-width: $mobile-width) {
+1
View File
@@ -1,6 +1,7 @@
@import "components/album-collection";
@import "components/gallery-item";
@import "components/document";
@import "components/document-row";
@import "components/documents-container";
@import "components/teacher-proifile";
@import "components/main-menu";