220 files changed
+11864
-15736
No files matched your search
@@ -11,7 +11,7 @@
|
||||
],
|
||||
"plugins": [
|
||||
"transform-react-jsx",
|
||||
"transform-object-rest-spread",
|
||||
"@babel/plugin-proposal-object-rest-spread",
|
||||
"transform-class-properties"
|
||||
]
|
||||
}
|
||||
+3
-1
@@ -8,12 +8,14 @@ private*.json
|
||||
/node_modules
|
||||
/bower_components
|
||||
/modules/hudozka-api-v1/lib
|
||||
/public
|
||||
/build
|
||||
/out
|
||||
dist
|
||||
.next
|
||||
**/env
|
||||
**/venv
|
||||
**/__pycache__
|
||||
**/*.pyc
|
||||
/coverage
|
||||
**/.vscode
|
||||
*.log
|
||||
+42
-21
@@ -1,27 +1,48 @@
|
||||
# Build
|
||||
FROM node:10 as build
|
||||
WORKDIR /app
|
||||
# # Build
|
||||
# FROM node:10 as build
|
||||
# WORKDIR /app
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm i
|
||||
COPY webpack.config.js gulpfile.js .babelrc ./
|
||||
COPY robots.txt ./
|
||||
COPY src ./src
|
||||
RUN npm run gulp
|
||||
RUN npm run build-app
|
||||
RUN npm run out
|
||||
# COPY package.json package-lock.json ./
|
||||
# RUN npm i
|
||||
# COPY webpack.config.js gulpfile.js .babelrc ./
|
||||
# COPY robots.txt ./
|
||||
# COPY src ./src
|
||||
# RUN npm run gulp
|
||||
# RUN npm run build-app
|
||||
# RUN npm run out
|
||||
|
||||
|
||||
# # Run
|
||||
# FROM node:8-alpine
|
||||
# WORKDIR /app
|
||||
|
||||
# COPY package.json package-lock.json ./
|
||||
# RUN npm i --production
|
||||
|
||||
# COPY --from=build /app/out ./src
|
||||
# COPY --from=build /app/public ./public
|
||||
|
||||
# EXPOSE 3000
|
||||
|
||||
# CMD npm start
|
||||
|
||||
|
||||
# FROM node:12-alpine as build
|
||||
# WORKDIR /app
|
||||
|
||||
# COPY package.json package-lock.json ./
|
||||
# RUN npm ci
|
||||
|
||||
# COPY . .
|
||||
# RUN npm run build
|
||||
# RUN npm run start & npm run export
|
||||
|
||||
|
||||
# Run
|
||||
FROM node:8-alpine
|
||||
WORKDIR /app
|
||||
FROM nginx:stable-alpine
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm i --production
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
COPY --from=build /app/out ./src
|
||||
COPY --from=build /app/public ./public
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD npm start
|
||||
WORKDIR /usr/share/nginx/html
|
||||
# COPY --from=build /app/dist /usr/share/nginx/html
|
||||
COPY ./dist /usr/share/nginx/html
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"uid": "hudozka",
|
||||
"append": true,
|
||||
"watch": false,
|
||||
"script": "index.js",
|
||||
"sourceDir": "/home/hoster/www/art.shburg.org/out",
|
||||
"wordingDir": "/home/hoster/www/art.shburg.org"
|
||||
}
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
const gulp = require('gulp')
|
||||
const postcss = require('gulp-postcss')
|
||||
const sass = require('gulp-sass')
|
||||
const imageMin = require('gulp-imagemin')
|
||||
const concat = require('gulp-concat')
|
||||
const autoprefixer = require('autoprefixer')
|
||||
const cssnano = require('cssnano')
|
||||
|
||||
gulp.task('style', () => {
|
||||
const processors = [
|
||||
autoprefixer({
|
||||
browsers: ['last 1 version']
|
||||
}),
|
||||
cssnano({
|
||||
zindex: false
|
||||
})
|
||||
]
|
||||
|
||||
return gulp.src('./src/style/style.scss')
|
||||
.pipe(sass())
|
||||
.pipe(postcss(processors))
|
||||
.pipe(concat('style.css'))
|
||||
.pipe(gulp.dest('./public'))
|
||||
})
|
||||
|
||||
gulp.task('copy fonts', () => {
|
||||
return gulp.src('./src/assets/fonts/*/*')
|
||||
.pipe(gulp.dest('./public/fonts'))
|
||||
})
|
||||
|
||||
gulp.task('copy graphics', () => {
|
||||
return gulp.src('./src/assets/graphics/*')
|
||||
.pipe(gulp.dest('./public/graphics'))
|
||||
})
|
||||
|
||||
gulp.task('copy 3rdparty', () => {
|
||||
return gulp.src('./src/3rdparty/*')
|
||||
.pipe(gulp.dest('./public'))
|
||||
})
|
||||
|
||||
gulp.task('copy robots.txt', () => {
|
||||
return gulp.src('./robots.txt')
|
||||
.pipe(gulp.dest('./public'))
|
||||
})
|
||||
|
||||
gulp.task('copy views', () => {
|
||||
return gulp.src('./src/views/**')
|
||||
.pipe(gulp.dest('./out/views'))
|
||||
})
|
||||
|
||||
gulp.task('imagemin', () => {
|
||||
return gulp.src('./src/assets/graphics/**/*')
|
||||
.pipe(imageMin())
|
||||
.pipe(gulp.dest('./public/graphics'))
|
||||
})
|
||||
|
||||
gulp.task('copy', gulp.series('copy views', 'copy fonts', 'copy graphics', 'copy robots.txt', 'copy 3rdparty'))
|
||||
gulp.task('default', gulp.series('style', 'copy', 'imagemin'))
|
||||
-16
@@ -1,16 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
dir_path="$1"
|
||||
build_file="$2"
|
||||
app="hudozka"
|
||||
script="forever.json"
|
||||
|
||||
cd ${dir_path}
|
||||
|
||||
gunzip -c ${build_file} | tar xopf -
|
||||
|
||||
#rm build.tar
|
||||
|
||||
npm install --production --loglevel error
|
||||
|
||||
echo "Deploying finish. Please restart hudozka.service manually"
|
||||
@@ -1,27 +0,0 @@
|
||||
FROM alpine:3.5
|
||||
|
||||
RUN apk add --no-cache python3 && \
|
||||
apk add --no-cache py3-lxml && \
|
||||
python3 -m ensurepip && \
|
||||
rm -r /usr/lib/python*/ensurepip && \
|
||||
pip3 install --upgrade pip setuptools && \
|
||||
rm -r /root/.cache
|
||||
|
||||
LABEL maintainer "Roman Timashev <roman@tmshv.ru>"
|
||||
|
||||
ENV INSTALL_PATH /app
|
||||
RUN mkdir -p $INSTALL_PATH
|
||||
WORKDIR /$INSTALL_PATH
|
||||
|
||||
COPY requirements.txt .
|
||||
RUN pip3 install -r requirements.txt
|
||||
|
||||
COPY app ./app
|
||||
|
||||
ENV DB_URI "mongodb://localhost:27017"
|
||||
ENV DB_USER ""
|
||||
ENV DB_PASSWORD ""
|
||||
ENV DB_NAME "hudozka"
|
||||
|
||||
EXPOSE 5000
|
||||
CMD python3 app/main.py
|
||||
@@ -1,10 +0,0 @@
|
||||
# Feed
|
||||
|
||||
Link to feed: [art.shlisselburg.org/feed](https://art.shlisselburg.org/feed)
|
||||
|
||||
## How to deploy with Docker
|
||||
|
||||
```bash
|
||||
docker build -t hudozka/feed .
|
||||
docker run -d --name hudozka-feed -p 18050:5000 -e DB_USER="<DB_USER>" -e DB_PASSWORD="<DB_PASSWORD>" -e DB_URI="<DB_URI>" hudozka/feed
|
||||
```
|
||||
@@ -1,13 +0,0 @@
|
||||
from pymongo import MongoClient
|
||||
from settings import db_uri, db_name, db_user, db_password
|
||||
|
||||
db = MongoClient(db_uri)[db_name]
|
||||
if db_user and db_password:
|
||||
auth_status = db.authenticate(db_user, db_password)
|
||||
if not auth_status:
|
||||
print('Failed to authenticate user')
|
||||
exit(1)
|
||||
|
||||
|
||||
def collection(name):
|
||||
return db[name]
|
||||
@@ -1,39 +0,0 @@
|
||||
import html
|
||||
|
||||
from feedgen.feed import FeedGenerator
|
||||
|
||||
from db import collection
|
||||
|
||||
|
||||
def get_items(collection_name, query={}):
|
||||
return list(
|
||||
collection(collection_name).find(query)
|
||||
)
|
||||
|
||||
|
||||
def get_feed():
|
||||
events = get_items('events')
|
||||
news = get_items('timeline', {'type': 'post'})
|
||||
|
||||
articles = sorted(events + news, key=lambda item: item['date'], reverse=True)
|
||||
|
||||
fg = FeedGenerator()
|
||||
fg.id('art.shlisselburg.org')
|
||||
fg.title('Шлиссельбургская ДХШ')
|
||||
fg.link(href='https://art.shlisselburg.org', rel='alternate')
|
||||
fg.logo('https://static.shlisselburg.org/art/graphics/favicon-144.png')
|
||||
fg.link(href='https://art.shlisselburg.org/feed', rel='self')
|
||||
fg.language('ru')
|
||||
|
||||
for article in articles:
|
||||
content = html.unescape(article['post'])
|
||||
article_id = article['id']
|
||||
url = 'https://art.shlisselburg.org/article/{id}'.format(id=article_id)
|
||||
|
||||
fe = fg.add_entry()
|
||||
fe.id(article_id)
|
||||
fe.title(article['title'])
|
||||
fe.content(content, None, 'CDATA')
|
||||
fe.link({'href': url})
|
||||
|
||||
return fg.atom_str(pretty=True)
|
||||
@@ -1,5 +0,0 @@
|
||||
from server import app
|
||||
from settings import host
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host=host)
|
||||
@@ -1,14 +0,0 @@
|
||||
from flask import Flask
|
||||
|
||||
from feed import get_feed
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def root():
|
||||
feed = get_feed()
|
||||
if not feed:
|
||||
return '', 404
|
||||
|
||||
return feed
|
||||
@@ -1,7 +0,0 @@
|
||||
import os
|
||||
|
||||
db_uri = os.getenv('DB_URI', 'mongodb://localhost:27017')
|
||||
db_name = os.getenv('DB_NAME', 'hudozka')
|
||||
db_user = os.getenv('DB_USER', None)
|
||||
db_password = os.getenv('DB_PASSWORD', None)
|
||||
host = os.getenv('APP_HOST', '0.0.0.0')
|
||||
@@ -1,5 +0,0 @@
|
||||
pymongo==3.4.0
|
||||
Flask==0.12
|
||||
redis==2.10.5
|
||||
PyYAML==3.12
|
||||
feedgen==0.5.1
|
||||
+628
@@ -0,0 +1,628 @@
|
||||
const withCss = require('@zeit/next-css')
|
||||
const withSass = require('@zeit/next-sass')
|
||||
const { range } = require('lodash')
|
||||
|
||||
function createPath(page, map, query, items) {
|
||||
return items.reduce((acc, path) => {
|
||||
return {
|
||||
...acc,
|
||||
[path]: {
|
||||
page,
|
||||
query: query(map(path)),
|
||||
}
|
||||
}
|
||||
}, {})
|
||||
}
|
||||
|
||||
module.exports = withCss(withSass({
|
||||
exportTrailingSlash: true,
|
||||
serverRuntimeConfig: {
|
||||
dbUri: '',
|
||||
// Will only be available on the server side
|
||||
// mySecret: 'secret',
|
||||
// secondSecret: process.env.SECOND_SECRET // Pass through env variables
|
||||
},
|
||||
exportPathMap: async (defaultPathMap, { dev }) => {
|
||||
if (dev) {
|
||||
return null
|
||||
}
|
||||
|
||||
const articlesPageSize = 15
|
||||
const articlesLastPage = 8
|
||||
|
||||
return {
|
||||
// ...defaultPathMap,
|
||||
'/': { page: '/', query: { pageSize: articlesPageSize } },
|
||||
'/documents': { page: '/documents' },
|
||||
'/gallery': { page: '/gallery' },
|
||||
'/collective': { page: '/collective' },
|
||||
'/schedule': { page: '/schedule' },
|
||||
|
||||
...createPath(
|
||||
'/articles/[page]',
|
||||
x => x.replace('/articles/', ''),
|
||||
page => ({ page, pageSize: articlesPageSize }),
|
||||
range(1, articlesLastPage + 1).map(x => `/articles/${x}`),
|
||||
),
|
||||
...createPath(
|
||||
'/next',
|
||||
x => x,
|
||||
() => ({}),
|
||||
[
|
||||
'/info/vacancy',
|
||||
'/info/new',
|
||||
'/info/support',
|
||||
'/info/education',
|
||||
'/info/standards',
|
||||
'/info/main',
|
||||
'/info/paid-services',
|
||||
'/info',
|
||||
'/info/tools',
|
||||
'/info/grants',
|
||||
'/info/structure',
|
||||
'/info/finances',
|
||||
'/courses',
|
||||
'/courses/batik',
|
||||
'/courses/ceramics',
|
||||
'/courses/composition',
|
||||
'/courses/costume-composition',
|
||||
'/courses/cg',
|
||||
'/courses/scale-modeling',
|
||||
'/courses/drawing',
|
||||
'/courses/easel-composition',
|
||||
'/courses/felting',
|
||||
'/info/history',
|
||||
'/courses/art-history',
|
||||
'/info/documents',
|
||||
'/info/online-education',
|
||||
'/info/education/listochki',
|
||||
'/info/education/matreshki',
|
||||
'/info/education/perishki',
|
||||
'/info/support/objects',
|
||||
'/info/support/tools',
|
||||
'/info/support/sport',
|
||||
'/info/support/network',
|
||||
'/info/education/painting',
|
||||
'/info/education/art-for-beginners',
|
||||
'/join',
|
||||
'/info/audit',
|
||||
'/summer-school/2017',
|
||||
'/info/health-saving',
|
||||
'/summer-school/2017',
|
||||
'/info/education/painting',
|
||||
'/info/education/art-for-beginners',
|
||||
'/info/education/listochki',
|
||||
'/info/education/matreshki',
|
||||
'/info/education/perishki',
|
||||
'/info/vacancy',
|
||||
'/info/online-education',
|
||||
'/info/documents',
|
||||
'/info/new',
|
||||
'/info/support',
|
||||
'/info/support/objects',
|
||||
'/info/support/tools',
|
||||
'/info/education',
|
||||
'/info/standards',
|
||||
'/info/main',
|
||||
'/info/paid-services',
|
||||
'/join',
|
||||
'/courses',
|
||||
'/info/support/network',
|
||||
'/info',
|
||||
'/info/tools',
|
||||
'/info/support/sport',
|
||||
'/info/grants',
|
||||
'/info/structure',
|
||||
'/info/audit',
|
||||
'/info/finances',
|
||||
'/info/health-saving',
|
||||
'/info/history',
|
||||
'/courses/batik',
|
||||
'/courses/felting',
|
||||
'/courses/art-history',
|
||||
'/courses/ceramics',
|
||||
'/courses/composition',
|
||||
'/courses/costume-composition',
|
||||
'/courses/cg',
|
||||
'/courses/scale-modeling',
|
||||
'/courses/drawing',
|
||||
'/courses/easel-composition',
|
||||
'/courses/listochki',
|
||||
'/courses/matreshki',
|
||||
'/courses/perishki',
|
||||
'/neighbors',
|
||||
'/neighbors/2017',
|
||||
'/neighbors/2017/continuing-the-chronicle',
|
||||
'/neighbors/2017/favorite-view-of-native-place',
|
||||
'/neighbors/2016',
|
||||
'/neighbors/2016/continuing-the-chronicle',
|
||||
'/neighbors/2016/quite-provincial-life',
|
||||
'/neighbors/2016/illustrations-rimsky-korsakov-works',
|
||||
'/neighbors/2017/quite-provincial-life',
|
||||
'/neighbors/2016/favorite-view-of-native-place',
|
||||
'/neighbors/2015',
|
||||
'/neighbors/2015/industrial-landscape',
|
||||
'/neighbors/2015/favorite-view-of-native-place',
|
||||
'/neighbors/2015/continuing-the-chronicle',
|
||||
'/neighbors/2015/these-places-remember-the-war',
|
||||
'/info/corruption',
|
||||
'/domik',
|
||||
'/courses/ladushki',
|
||||
'/info/security',
|
||||
'/neighbors/2018/continuing-the-chronicle',
|
||||
'/neighbors/2018',
|
||||
'/neighbors/2018/favorite-view-of-native-place',
|
||||
'/neighbors/2018/industrial-landscape',
|
||||
'/info/navigator',
|
||||
]
|
||||
),
|
||||
...createPath(
|
||||
'/teacher/[person]',
|
||||
x => x.replace('/teacher/', ''),
|
||||
person => ({ person }),
|
||||
[
|
||||
'/teacher/as-timasheva',
|
||||
'/teacher/va-sarzhin',
|
||||
'/teacher/vv-voronova',
|
||||
'/teacher/ea-burovtseva',
|
||||
'/teacher/ey-tarasova',
|
||||
'/teacher/iv-khramov',
|
||||
'/teacher/in-vturina',
|
||||
'/teacher/ma-khramova',
|
||||
'/teacher/mg-timasheva',
|
||||
'/teacher/my-valkova',
|
||||
'/teacher/nv-andreeva',
|
||||
'/teacher/od-gogoleva',
|
||||
'/teacher/rk-timashev',
|
||||
'/teacher/sa-latipova',
|
||||
],
|
||||
),
|
||||
...createPath(
|
||||
'/article/[article]',
|
||||
x => x.replace('/article/', ''),
|
||||
person => ({ person }),
|
||||
[
|
||||
'/article/2009-ntv',
|
||||
'/article/2010-tv100',
|
||||
'/article/oblastnoi-plener-udalsia-na-slavu',
|
||||
'/article/pervyi-kinopokaz',
|
||||
'/article/30',
|
||||
'/article/kinopokaz',
|
||||
'/article/elka-dlia-malyshei-2015',
|
||||
'/article/vystavka-kukly-korennykh-narodov-leningradskoi-oblasti',
|
||||
'/article/vystavka-kukly-srednei-azii-i-kavkaza',
|
||||
'/article/detskii-sadik',
|
||||
'/article/think-try-create',
|
||||
'/article/konkurs-volshebnaia-kist-na-nekrasova',
|
||||
'/article/master-klass-po-izgotovleniiu-vatnykh-kukol-na-nekrasova',
|
||||
'/article/2015-pridumyvai-probui-tvori',
|
||||
'/article/vystavka-vesna-2015',
|
||||
'/article/exhibition-70',
|
||||
'/article/vesna-2015',
|
||||
'/article/vystavka-letnikh-rabot',
|
||||
'/article/kinopokaz-chukovsky',
|
||||
'/article/kino-domik',
|
||||
'/article/vystavka-v-biblioteke-nekrasova',
|
||||
'/article/konkurs-volshebnaia-kist',
|
||||
'/article/russian-history-competition',
|
||||
'/article/master-klass-pogoleshechnaia-kukla',
|
||||
'/article/2016-pridumyvai-probui-tvori',
|
||||
'/article/prazdnik-v-muzykalnoi-shkole-shlisselburga',
|
||||
'/article/nabor-detei-s-7-mi-do-9-ti-let',
|
||||
'/article/serebro-za-vizitku',
|
||||
'/article/talant-vospityvaetsia-v-trude',
|
||||
'/article/2016-10-kaniculi',
|
||||
'/article/2016-tikhvin',
|
||||
'/article/2016-mi-sosedi',
|
||||
'/article/master-klass-po-kalligrafii',
|
||||
'/article/2016-master-class-calligraphy',
|
||||
'/article/2016-11-kaniculi',
|
||||
'/article/vtoroi-master-klass-po-kalligrafii',
|
||||
'/article/2016-master-class-vatnoe-papie-mashe',
|
||||
'/article/2016-ny-master-class',
|
||||
'/article/2016-master-class-calligraphy-2',
|
||||
'/article/v-nominatsii-sovremennaia-kukla-pobedili-iolochnye-ukrasheniia-shlisselburgskoi-masteritsy',
|
||||
'/article/anons-master-klassa-kukly-voennykh-let',
|
||||
'/article/master-klass-kukly-voennykh-let',
|
||||
'/article/deiateli-kultury-gordost-leningradskoi-oblasti',
|
||||
'/article/gostevoi-dom-shlisselburg',
|
||||
'/article/uchastie-v-konkurse-zvuk-i-forma',
|
||||
'/article/vizit-gubernatora-leningradskoi-oblasti',
|
||||
'/article/anons-konkursa-smotrimvidim',
|
||||
'/article/anons-letnei-shkoly',
|
||||
'/article/master-class-papier-mache-2017',
|
||||
'/article/cats-master-class-2017',
|
||||
'/article/vyborg-announcement-2017',
|
||||
'/article/vyborg-info-2017',
|
||||
'/article/smotrim-vidim-final-announcement-2017',
|
||||
'/article/summer-school-info-2017',
|
||||
'/article/smotrim-vidim-final-2017',
|
||||
'/article/vyborg-2017',
|
||||
'/article/2017-info-new',
|
||||
'/article/preparing-2017',
|
||||
'/article/sarzhin-star',
|
||||
'/article/toropets',
|
||||
'/article/sarzhin-october',
|
||||
'/article/2017-10-kaniculi',
|
||||
'/article/2017-10-workshop',
|
||||
'/article/shlisselshved',
|
||||
'/article/shlisselburg-315',
|
||||
'/article/scholarship-2017',
|
||||
'/article/sarzhin-70',
|
||||
'/article/2017-10-workshop-ceramics',
|
||||
'/article/2017-katavasia',
|
||||
'/article/2017-golden-hands',
|
||||
'/article/2016-liditse',
|
||||
'/article/2017-the-authors-style',
|
||||
'/article/2017-dpi-methods',
|
||||
'/article/2017-oreshek-workshop',
|
||||
'/article/2017-neighbors-vybord-rewarding',
|
||||
'/article/2017-stars',
|
||||
'/article/2017-petrovskaya-aquatoria-trip',
|
||||
'/article/2017-dnt',
|
||||
'/article/silver-pencil-2018',
|
||||
'/article/magic-brush-2018',
|
||||
'/article/blokada-workshop-2018',
|
||||
'/article/best-teacher-2018',
|
||||
'/article/save-nature-competition-2018',
|
||||
'/article/win-day-2018',
|
||||
'/article/site-iii-2018',
|
||||
'/article/israel-2018',
|
||||
'/article/blokada-workshop-2-2018',
|
||||
'/article/plain-air-2018',
|
||||
'/article/graduation-2018',
|
||||
'/article/blokada-workshop-3-2018',
|
||||
'/article/2012-timestamp',
|
||||
'/article/plain-air-june-2018',
|
||||
'/article/berezka-2018',
|
||||
'/article/plain-air-pro-2018',
|
||||
'/article/2018-neighbors-kronshtadt',
|
||||
'/article/vyborg-competition-2018',
|
||||
'/article/norway-2018',
|
||||
'/article/family-album-2018',
|
||||
'/article/2018-neighbors-ceremony',
|
||||
'/article/2018-100',
|
||||
'/article/2018-buduschee-moey-strany',
|
||||
'/article/2019-nv-andreeva',
|
||||
'/article/2019-blokada-workshop',
|
||||
'/article/2018-culture-star',
|
||||
'/article/2018-guests-from-germany',
|
||||
'/article/2018-grants',
|
||||
'/article/2018-inclusive-education',
|
||||
'/article/2018-neighbors-announcement',
|
||||
'/article/2019-fortuna',
|
||||
'/article/2019-orchestra',
|
||||
'/article/2019-magic-brush',
|
||||
'/article/2019-silver-pencil',
|
||||
'/article/2019-spring-subbotnik',
|
||||
'/article/2019-win500',
|
||||
'/article/2019-korushka',
|
||||
'/article/2019-pleinair',
|
||||
],
|
||||
),
|
||||
...createPath(
|
||||
'/album/[album]',
|
||||
x => x.replace('/album/', ''),
|
||||
album => ({ album }),
|
||||
[
|
||||
'/album/2012-tekhnika-mokrogo-valianiia',
|
||||
'/album/2013-valianie-zverushki',
|
||||
'/album/2013-shlisselburg',
|
||||
'/album/2013-natiurmort',
|
||||
'/album/2015-kukly-valianie',
|
||||
'/album/2014-kollazh',
|
||||
'/album/2014-shlisselburg',
|
||||
'/album/2014-elochnye-domiki',
|
||||
'/album/2014-elochnye-igrushki',
|
||||
'/album/2014-zhivopis-narody-severa',
|
||||
'/album/2015-maketirovanie-doma-peterburga',
|
||||
'/album/2015-zhivopis',
|
||||
'/album/2015-maketirovanie',
|
||||
'/album/2015-podvodnyi-mir-stankovaia-kompozitsiia',
|
||||
'/album/2015-risunok',
|
||||
'/album/2015-skazki',
|
||||
'/album/2015-stankovaia-kompozitsiia',
|
||||
'/album/2015-tsvety-stankovaia-kompozitsiia',
|
||||
'/album/2015-graviura',
|
||||
'/album/2015-guash',
|
||||
'/album/2015-shlisselburg',
|
||||
'/album/2015-dpk-portret-v-tekhnike-applikatsiia',
|
||||
'/album/2015-nominatsiia-navstrechu-pobede',
|
||||
'/album/2015-domiki',
|
||||
'/album/2015-skulptury-na-temu-70-let-pobedy',
|
||||
'/album/2015-history-competition',
|
||||
'/album/2015-rozhdestvenskaia-skazka',
|
||||
'/album/2016-konkurs-zimnie-zabavy',
|
||||
'/album/2016-konkurs-na-kryliakh-babochek',
|
||||
'/album/2016-konkurs-narodnye-motivy',
|
||||
'/album/around-the-world',
|
||||
'/album/comod',
|
||||
'/album/leto-domovenok',
|
||||
'/album/2016-tikhvin-mi-sosedi',
|
||||
'/album/2017-konkurs-budushchee-moei-strany',
|
||||
'/album/2017-konkurs-puteshestvie-k-dinozavram',
|
||||
'/album/2017-konkurs-eto-mamin-den',
|
||||
'/album/2017-konkurs-forma-i-zvuk',
|
||||
'/album/pleinair-competition-2017',
|
||||
'/album/ceramics-2017',
|
||||
'/album/vyborg-makets-2017',
|
||||
'/album/ceramics-march-2018',
|
||||
'/album/ceramics-june-2018',
|
||||
'/album/batik-june-2018',
|
||||
'/album/village-pictures-june-2018',
|
||||
'/album/fortress-june-2018',
|
||||
'/album/baloons-june-2018',
|
||||
'/album/drawing-jule-2018',
|
||||
'/album/drawing-june-2018',
|
||||
'/album/painting-june-2018',
|
||||
],
|
||||
),
|
||||
...createPath(
|
||||
'/document/[document]',
|
||||
x => x.replace('/document/', ''),
|
||||
document => ({ document }),
|
||||
[
|
||||
'/document/documents-2017-2018-kalendarnyi-grafik-2017-2018-pdf',
|
||||
'/document/documents-arkhiv-obrazovatelnaia-litsenziia-2013-pdf',
|
||||
'/document/documents-arkhiv-plan-vnutrishkolnogo-kontrolia-2012-pdf',
|
||||
'/document/documents-arkhiv-polozhenie-o-vnutrishkolnom-kontrole-2012-pdf',
|
||||
'/document/documents-arkhiv-polozhenie-o-pedagogicheskom-sovete-pdf',
|
||||
'/document/documents-arkhiv-polozhenie-o-prieme-perevode-otchislenii-pdf',
|
||||
'/document/documents-arkhiv-polozhenie-o-propusknom-rezhime-2015-pdf',
|
||||
'/document/documents-arkhiv-pravila-i-polozhenie-o-prieme-v-shkolu-pdf',
|
||||
'/document/documents-arkhiv-ustav-2013-pdf',
|
||||
'/document/documents-dokumenty-dlia-postupleniia-dogovor-o-pozhertvovanii-pdf',
|
||||
'/document/documents-dokumenty-dlia-postupleniia-dogovor-o-sotrudnichestve-pdf',
|
||||
'/document/documents-dokumenty-dlia-postupleniia-zaiavlenie-roditelei-pdf',
|
||||
'/document/documents-instruktsii-instruktsiia-po-organizatsii-okhrany-zhizni-i-zdorovia-detei-pdf',
|
||||
'/document/documents-instruktsii-instruktsiia-po-okhrane-zhizni-i-zdorovia-detei-pdf',
|
||||
'/document/documents-instruktsii-plan-vnutrishkolnogo-kontrolia-2016-pdf',
|
||||
'/document/documents-instruktsii-pravila-vnutrennego-trudovogo-rasporiadka-pdf',
|
||||
'/document/documents-materialno-tekhnicheskoe-obespechenie-i-osnashchennost-obrazovatelnogo-protsessa-vnutrishkolnyi-metodicheskii-bibliotechnyi-fond-pdf',
|
||||
'/document/documents-materialno-tekhnicheskoe-obespechenie-i-osnashchennost-obrazovatelnogo-protsessa-dogovor-na-meditsinskoe-obsluzhivanie-pdf',
|
||||
'/document/documents-materialno-tekhnicheskoe-obespechenie-i-osnashchennost-obrazovatelnogo-protsessa-dogovor-na-okazanie-uslug-po-organizatsii-pitaniia-pdf',
|
||||
'/document/documents-materialno-tekhnicheskoe-obespechenie-i-osnashchennost-obrazovatelnogo-protsessa-dogovor-na-okazanie-uslug-sviazi-pdf',
|
||||
'/document/documents-materialno-tekhnicheskoe-obespechenie-i-osnashchennost-obrazovatelnogo-protsessa-materialno-tekhnicheskoe-obespecheninie-obrazovatelnogo-protsessa-pdf',
|
||||
'/document/documents-obrazovanie-dopolnitelnaia-obshcherazvivaiushchaia-programma-khudozhestvennoi-napravlennosti-izobrazitelnoe-iskusstvo-dlia-nachinaiushchikh-pdf',
|
||||
'/document/documents-obrazovanie-dopolnitelnaia-obshcherazvivaiushchaia-programma-khudozhestvennoi-napravlennosti-dlia-obuchaiushchikhsia-podgotovitelnykh-klassov-v-dkhsh-listochki-pdf',
|
||||
'/document/documents-obrazovanie-dopolnitelnaia-obshcherazvivaiushchaia-programma-khudozhestvennoi-napravlennosti-dlia-obuchaiushchikhsia-podgotovitelnykh-klassov-v-dkhsh-matreshki-pdf',
|
||||
'/document/documents-obrazovanie-dopolnitelnaia-obshcherazvivaiushchaia-programma-khudozhestvennoi-napravlennosti-dlia-obuchaiushchikhsia-podgotovitelnykh-klassov-v-dkhsh-peryshki-pdf',
|
||||
'/document/documents-obrazovanie-dopolnitelnaia-predprofessionalnaia-obshcheobrazovatelnaia-programma-v-oblasti-izobrazitelnogo-iskusstva-zhivopis-pdf',
|
||||
'/document/documents-obrazovanie-dpop-zhivopis-pdf',
|
||||
'/document/documents-obrazovanie-kalendarnyi-uchebnyi-grafik-pdf',
|
||||
'/document/documents-obrazovanie-opisanie-obrazovatelnoi-programmy-izobrazitelnoe-iskusstvo-dlia-nachinaiushchikh-pdf',
|
||||
'/document/documents-obrazovanie-opisanie-obshcheobrazovatelnoi-programmy-v-oblasti-izobrazitelnogo-iskusstva-zhivopis-pdf',
|
||||
'/document/documents-obrazovanie-polozhenie-o-komplektovanii-i-napolniaemosti-grupp-pdf',
|
||||
'/document/documents-obrazovanie-polozhenie-o-poriadke-obucheniia-po-individualnomu-uchebnomu-planu-pdf',
|
||||
'/document/documents-obrazovanie-polozhenie-o-iazyke-obrazovaniia-v-mbudo-shlisselburgskaia-dkhsh-pdf',
|
||||
'/document/documents-obrazovanie-programma-obucheniia-2017-pdf',
|
||||
'/document/documents-obrazovanie-uchebnyi-plan-po-izobrazitelnomu-iskusstvu-pdf',
|
||||
'/document/documents-obrazovanie-uchebnyi-plan-po-dopolnitelnoi-obshcherazvivaiushchei-podgotovitelnoi-programme-po-izobrazitelnomu-iskusstvu-pdf',
|
||||
'/document/documents-obrazovanie-uchebnyi-plan-po-dopolnitelnoi-predprofessionalnoi-obshcheobrazovatelnoi-programme-v-oblasti-izobrazitelnogo-iskusstva-zhivopis-pdf',
|
||||
'/document/documents-osnovnye-dokumenty-egriul-pdf',
|
||||
'/document/documents-osnovnye-dokumenty-inn-pdf',
|
||||
'/document/documents-osnovnye-dokumenty-kopiia-resheniia-o-naznachenii-rukovoditelia-pdf',
|
||||
'/document/documents-osnovnye-dokumenty-obrazovatelnaia-litsenziia-2015-pdf',
|
||||
'/document/documents-osnovnye-dokumenty-polozhenie-o-vnutrishkolnom-kontrole-2017-pdf',
|
||||
'/document/documents-osnovnye-dokumenty-polozhenie-o-dobrovolnom-pozhertvovanii-pdf',
|
||||
'/document/documents-osnovnye-dokumenty-polozhenie-o-kodekse-professionalnoi-etiki-pedagogicheskikh-rabotnikov-pdf',
|
||||
'/document/documents-osnovnye-dokumenty-polozhenie-o-pedagogicheskom-sovete-pdf',
|
||||
'/document/documents-osnovnye-dokumenty-polozhenie-o-poriadke-vydachi-dokumenta-ob-obuchenii-pdf',
|
||||
'/document/documents-osnovnye-dokumenty-polozhenie-o-pravilakh-vnutrennego-rasporiadka-obuchaiushchikhsia-pdf',
|
||||
'/document/documents-osnovnye-dokumenty-polozhenie-o-prieme-v-shkolu-pdf',
|
||||
'/document/documents-osnovnye-dokumenty-polozhenie-o-propusknom-rezhime-pdf',
|
||||
'/document/documents-osnovnye-dokumenty-polozhenie-o-sovete-shkoly-2017-pdf',
|
||||
'/document/documents-osnovnye-dokumenty-polozhenie-o-formakh-periodichnosti-i-poriadke-tekushchego-kontrolia-uspevaemosti-promezhutochnoi-attestatsii-obuchaiaiushchikhsia-pdf',
|
||||
'/document/documents-osnovnye-dokumenty-polozhenie-ob-appeliatsionnoi-komissii-pdf',
|
||||
'/document/documents-osnovnye-dokumenty-polozhenie-ob-itogovoi-attestatsii-pdf',
|
||||
'/document/documents-osnovnye-dokumenty-polozhenie-ob-obshchem-sobranii-rabotnikov-pdf',
|
||||
'/document/documents-osnovnye-dokumenty-polozhenie-ob-usloviiakh-provedeniia-individualnogo-otbora-dlia-postupaiushchikh-s-ogranichennymi-vozmozhnostiami-zdorovia-pdf',
|
||||
'/document/documents-osnovnye-dokumenty-polozhenie-po-promezhutochnoi-attestatsii-pdf',
|
||||
'/document/documents-osnovnye-dokumenty-postanovlenie-o-sozdanii-munitsipalnogo-obrazovatelnogo-uchrezhdeniia-kultury-detskaia-khudozhestvennaia-shkola-pdf',
|
||||
'/document/documents-osnovnye-dokumenty-programma-razvitiia-na-2017-2020-gg-pdf',
|
||||
'/document/documents-osnovnye-dokumenty-rezhim-zaniatii-obuchaiushchikhsia-pdf',
|
||||
'/document/documents-osnovnye-dokumenty-reshenie-o-sozdanii-shkoly-pdf',
|
||||
'/document/documents-osnovnye-dokumenty-svidetelstvo-na-pomeshchenie-pdf',
|
||||
'/document/documents-osnovnye-dokumenty-ustav-2015-pdf',
|
||||
'/document/documents-plany-fkhd-munitsipalnoe-zadanie-na-2017-god-i-na-planovyi-period-2018-i-2019-godov-pdf',
|
||||
'/document/documents-plany-fkhd-otchet-o-vypolnenii-munitsipalnogo-zadaniia-2016-god-pdf',
|
||||
'/document/documents-plany-fkhd-plan-finansovoi-khoziaistvennoi-deiatelnosti-na-2016-god-pdf',
|
||||
'/document/documents-plany-fkhd-plan-finansovoi-khoziaistvennoi-deiatelnosti-na-2017-god-pdf',
|
||||
'/document/documents-platnye-obrazovatelnye-uslugi-dogovor-ob-okazanii-platnykh-obrazovatelnykh-uslug-pdf',
|
||||
'/document/documents-platnye-obrazovatelnye-uslugi-zaiavlenie-na-platnye-obrazovatelnye-uslugi-pdf',
|
||||
'/document/documents-platnye-obrazovatelnye-uslugi-polozhenie-o-poriadke-predostavleniia-platnykh-obrazovatelnykh-uslug-pdf',
|
||||
'/document/documents-platnye-obrazovatelnye-uslugi-prikaz-ob-ustanovlenii-razmera-platnykh-obrazovatelnykh-uslug-pdf',
|
||||
'/document/documents-prochee-2016-zaiavka-na-uchastie-v-44-mvkhpd-v-liditse-pdf',
|
||||
'/document/documents-prochee-2016-rezultat-samoobsledovaniia-pdf',
|
||||
'/document/documents-prochee-dostizheniia-obuchaiushchikhsia-v-2015-2016-godu-pdf',
|
||||
'/document/documents-prochee-litsenziia-spbimi-pdf',
|
||||
'/document/documents-prochee-otsenka-kachestva-uslug-uchrezhdeniia-kultury-pdf',
|
||||
'/document/documents-prochee-polozhenie-smotrim-vidim-pdf',
|
||||
'/document/documents-prochee-rasporiazhenie-o-prisvoenii-stipendii-2017-2018-pdf',
|
||||
'/document/documents-prochee-rasporiazhenie-ot-30-05-2017-pdf',
|
||||
'/document/documents-prochee-spisok-materialov-dlia-zaniatiia-v-khudozhestvennoi-shkole-pdf',
|
||||
'/document/documents-samoobsledovanie-deiatelnosti-otchet-o-rezultatakh-samoobsledovaniia-01-04-2016-pdf',
|
||||
'/document/documents-samoobsledovanie-deiatelnosti-otchet-o-rezultatakh-samoobsledovaniia-01-04-2017-pdf',
|
||||
'/document/documents-tekushchii-kontrol-akt-proverki-komiteta-obshchego-i-professionalnogo-obrazovaniia-leningradskoi-oblasti-2017-pdf',
|
||||
'/document/documents-uchebnye-programmy-izobrazitelnoe-iskusstvo-dlia-nachinaiushchikh-pdf',
|
||||
'/document/documents-uchebnye-programmy-programma-po-predmetu-skulptura-pdf',
|
||||
'/document/documents-uchebnye-programmy-progrmma-po-predmetu-plener-pdf',
|
||||
'/document/documents-uchebnye-programmy-uchebnaia-programma-zhivopis-pdf',
|
||||
'/document/documents-uchebnye-programmy-uchebnaia-programma-istoriia-iskusstva-pdf',
|
||||
'/document/documents-uchebnye-programmy-uchebnaia-programma-prikladnaia-kompozitsiia-pdf',
|
||||
'/document/documents-uchebnye-programmy-uchebnaia-programma-risunok-pdf',
|
||||
'/document/documents-uchebnye-programmy-uchebnaia-programma-stankovaia-kompozitsiia-pdf',
|
||||
'/document/documents-finansovo-khoziaistvennaia-deiatelnost-balans-gosudarstvennogo-(munitsipalnogo)-uchrezhdeniia-pdf',
|
||||
'/document/documents-finansovo-khoziaistvennaia-deiatelnost-otchet-o-finansovykh-rezultatakh-deiatelnosti-uchrezhdeniia-pdf',
|
||||
'/document/documents-finansovo-khoziaistvennaia-deiatelnost-otchet-ob-ispolnenii-uchrezhdeniem-plana-ego-finansovo-khoziaistvennoi-deiatelnosti-forma-737-kfo-4-pdf',
|
||||
'/document/documents-finansovo-khoziaistvennaia-deiatelnost-otchet-ob-ispolnenii-uchrezhdeniem-plana-ego-finansovo-khoziaistvennoi-deiatelnosti-forma-737-kfo-5-pdf',
|
||||
'/document/documents-finansovo-khoziaistvennaia-deiatelnost-otchet-ob-ispolnenii-uchrezhdeniem-plana-ego-finansovo-khoziaistvennoi-deiatelnosti-pdf',
|
||||
'/document/pages-finansovo-khoziaistvennaia-deiatelnost-balans-pdf',
|
||||
'/document/370f8cc297f40d170470e8768ddd227c',
|
||||
'/document/792bf2823b7840318b02f7d2758af788',
|
||||
'/document/f574413240ff8ff35091f023976688ee',
|
||||
'/document/758260df3b7fa615e2fc8a3d2ed9e34c',
|
||||
'/document/00c90a531da459ab9f10a3d52bac233f',
|
||||
'/document/2eae32c837bbbd84f403dd7628354b1d',
|
||||
'/document/fa42795ab1879d009961b5bf69e8c597',
|
||||
'/document/8f74abfd61354daf8d73e4000b525fdd',
|
||||
'/document/fe14659e16c98005f4c5168ee3cccc9a',
|
||||
'/document/98564c3f7c9ce1e5c5f1aa1fe302eaf6',
|
||||
'/document/6d7359437624b3dd1484d82be66f1ea0',
|
||||
'/document/4e4ed5431035f97da26eb6b17d88658e',
|
||||
'/document/dfef1cc07b4317d2d380efc4259dc363',
|
||||
'/document/8c19548aa39c40a8a2abc4bca20714ca',
|
||||
'/document/caef7508b14605982d483a5f05756714',
|
||||
'/document/9f6683f63172a794fd3c6031f781d348',
|
||||
'/document/acaa47304de042433e26e5eabbbfdeec',
|
||||
'/document/e094b1f74e8d381b77b510d9f040a616',
|
||||
'/document/b19092ace94edcf2e7ee0274207b8aa9',
|
||||
'/document/47883f1e226c48c42e1cb7a9e811bec8',
|
||||
'/document/ee54f828803e91c7f385b621143f0daf',
|
||||
'/document/9149c0d182401643c6307ac9ffb1dc65',
|
||||
'/document/7f229ba5f7882a8a2684289204a8308e',
|
||||
'/document/37277ec7ddc90a316effef4a3a6ec1c3',
|
||||
'/document/d50a7953d5a65f4155f7035740594a47',
|
||||
'/document/ae3d2bc577bfd45f1cbc420f8691de2e',
|
||||
'/document/b54ad2943689658cccd20c215724bc7d',
|
||||
'/document/918b27593c43576dfd85b817005f9925',
|
||||
'/document/77144e7b8eb675d0189c63be1818d964',
|
||||
'/document/20f2fafe77196edb54857fe0b98438a6',
|
||||
'/document/e0cdee9ba8a56919f92af7f0363e713f',
|
||||
'/document/e3e2da7bdd4cdb4fb05a7d72a1c5aa24',
|
||||
'/document/5883367e713100cfdfa074fbab675907',
|
||||
'/document/0415c9a50eb0b56651bbcf3d7388a373',
|
||||
'/document/aa69b76d675e1e30661365deb2e5deef',
|
||||
'/document/4e35368e42c1840e89c3a3714d3f323b',
|
||||
'/document/5bd563e2b1b8e0cb20ee2438a5d4888a',
|
||||
'/document/b19069cc9b3c5be5a92487f9d3385064',
|
||||
'/document/53f8111c3882521453d889aa84dedea3',
|
||||
'/document/58e4a7445b19ae0dbe1ee6313cbcee50',
|
||||
'/document/76a8fc2870f1254a52e80712115ea448',
|
||||
'/document/dab46182c1c119210b3a8ba4a3079d15',
|
||||
'/document/52101c7e4bb3bbdc315f93dff8b826ca',
|
||||
'/document/5a677e08537853a1f4d11a7ae492f8b6',
|
||||
'/document/2dc1c2e7cfa996c13fb90045c5e4b5fb',
|
||||
'/document/952e1ed8ec2879eb4d2e300c1b50d890',
|
||||
'/document/0d20b54c2023563ec0cdff648ed8070d',
|
||||
'/document/82e9e339678a9385ab44897837a0e7f6',
|
||||
'/document/ae64123551407d5c325f75d83f58a338',
|
||||
'/document/bd1ace5ed47ee0958254f6a9732482cc',
|
||||
'/document/534566e01344b0d1dfec8e3cb0dfc8a9',
|
||||
'/document/739bd6b500c6bebe742df3d70a0c1fdc',
|
||||
'/document/00a6c04a383bea80a619d4a86bb1b46f',
|
||||
'/document/5c9708d3d3c9d6141ed7865a8a3d9566',
|
||||
'/document/6fec7aa79b5edef99dc5498d8ddc9b28',
|
||||
'/document/2f4ab4732107b2ce84e2dd9327d2788f',
|
||||
'/document/d91de578e0a3b5d0878857bebf859124',
|
||||
'/document/a056376a142ac924485ff6f222ab116d',
|
||||
'/document/ded3933486acbc63ee0897d768a0af5a',
|
||||
'/document/f3a21a6b0dc669034542c575dc4a9cb0',
|
||||
'/document/f08cd96300031e83a55699f257046eb3',
|
||||
'/document/77b23264aa63ebc2f60811fe46595140',
|
||||
'/document/58269cb439dd9860b277a772a5651597',
|
||||
'/document/e87b1ce25b43a4f4613bf35aa1ce17ef',
|
||||
'/document/2a17275330cfc5933447fa5631a1a137',
|
||||
'/document/e0726255b3b1275c468432e6825acc8c',
|
||||
'/document/0d8ba20dc95b0b8cecde26f83821ac60',
|
||||
'/document/cc02b4688caa24f55da4a36d54d89712',
|
||||
'/document/3df73e48c1bc1acf4725e158a7abbea2',
|
||||
'/document/c27d39f66acd8c1e50c66ddc5d0a2d39',
|
||||
'/document/efd17fc5298d12a4b4a73238e01bc4c1',
|
||||
'/document/19e094faea42621de8ea362493c6149a',
|
||||
'/document/869d3c46dc48c31870ff169e1d53ba0f',
|
||||
'/document/be8201b8f5b0d995faa851c8dee9bb30',
|
||||
'/document/9e79ef49c9aa132f1b6be66ade0e55ff',
|
||||
'/document/198f53b9de3c8123b0977d9350c26ace',
|
||||
'/document/f16c8bc8830d6d5c939fcb3dc3e9dfc8',
|
||||
'/document/60933dd039b87c2ce5b3f637ec791f71',
|
||||
'/document/1c05245b4e5889bc0f3cc6e7ee3a022b',
|
||||
'/document/fb589fa77a5e78a11e5aa90f27fdb10a',
|
||||
'/document/631962a3257d1196f281f568d780d98f',
|
||||
'/document/e8877efd3f9234997677b0595b0aaa22',
|
||||
'/document/4fe9c744e0b1b9de8dec11db5d6ab29f',
|
||||
'/document/85280b9f3f951c06284ab7fe77a595b6',
|
||||
'/document/76607ea30bd2a7d86710781b6b9fddd4',
|
||||
'/document/ec40bc1ed9ce38d563c2bece509b5021',
|
||||
'/document/074b2b248efc55ae7fe9d157891c59e9',
|
||||
'/document/1d038d40939be8999fe585f71579f09d',
|
||||
'/document/8f1764396b0a6c1a0a32235ad4cd69eb',
|
||||
'/document/4d6524f6c56d7cbeaee699c8b5e37c1b',
|
||||
'/document/c2ea674b1a3069369b1829e568891b05',
|
||||
'/document/6c1fb8a2428b7b95ca103363f1273c57',
|
||||
'/document/e7dae8347ac64981377932e4a93f1237',
|
||||
'/document/10098b1edb8a242253cdc0636106b880',
|
||||
'/document/deba55994dd9dc1bd304c360656e3843',
|
||||
'/document/976dec5025c210b879192929a40b6b27',
|
||||
'/document/1d48ee849ddea9406a423e4b1e1a7405',
|
||||
'/document/cc845874fd3b183b0cd17e7138075028',
|
||||
'/document/81c9a9d965a12ceaf3564128bffd37ad',
|
||||
'/document/e1720a6b5792a81788f10865ef6ecc79',
|
||||
'/document/0f0b7ef81ba0d0a2af96a67322110751',
|
||||
'/document/ba581ef075ea68be80f750ac467e5baf',
|
||||
'/document/3c9062a9dd236b9d533160c0058c8648',
|
||||
'/document/27958a0d3dd1e22e7b2f7c68ca753fb3',
|
||||
'/document/3663f97b6151720d4749574dc9d1e8a4',
|
||||
'/document/2d21b714a1ba64c24289e2e22d080202',
|
||||
'/document/7ebc6f2cae58d6505143a20da047dd80',
|
||||
'/document/1d67ff95477334666d8ae6451f1a32e2',
|
||||
'/document/c2524a36ba4f9885dcd2abcb1fc00afd',
|
||||
'/document/c49ef7b1944a96a8b2552aa7fdb60d1b',
|
||||
'/document/d2b40e5804363a0f326f4d14ad9e0b73',
|
||||
'/document/3149e573fa3a5b680662a4db84078044',
|
||||
'/document/c7ccacb1e86467f1250084280e16a58f',
|
||||
'/document/9b996ba28a688d2b2834b202e14ff317',
|
||||
'/document/8ddbabaf875dfc1652ed327d9760b6cf',
|
||||
'/document/4da000c1bbca5231c9768aaeccdffe1a',
|
||||
'/document/c44153837b872ba80f5d94e7d3641114',
|
||||
'/document/d99da3c6e7962195f6ae5ce31759d076',
|
||||
'/document/6025a889bd12cec858d218f68686b916',
|
||||
'/document/2b2672d859944f1c1b3a63b26ad693ab',
|
||||
'/document/1a3234642a9bf0493c75511eb3bf98c5',
|
||||
'/document/554c91d0c84c693f00b8666534f198d1',
|
||||
'/document/a9bf213af314b8258d70e50ab0a5a3f9',
|
||||
'/document/7bc0d8238189fd0497d6faf51c1ec6ce',
|
||||
'/document/27b858d66f1dd403504139389f1c985f',
|
||||
'/document/2f9873564859bf38c24c2735d3dfc537',
|
||||
'/document/40256172e2be27799361b3980c39737b',
|
||||
'/document/c08980a17eaffa33816aa0b9f52d35da',
|
||||
'/document/26f7783a944186eed230ac234b999da6',
|
||||
'/document/8b1abc982beae70ac2885856aeaef116',
|
||||
'/document/d063d09eb6f2d1d765ab904cb211ff61',
|
||||
'/document/36988b164efadc1348c81916c30076b2',
|
||||
'/document/765798adfe9b6c75f72c20c716705b82',
|
||||
'/document/9a0d71c2d8aa33273b9c631af2b47eb0',
|
||||
'/document/1b8dc4f624b2db9c37097df6011948ca',
|
||||
'/document/8d4ccb007257e11d7ca7c957d2fa715c',
|
||||
'/document/7c1fcbed5fa818d1256a33c628eb6695',
|
||||
'/document/94d1cb5860ec81e00e9fdf24af847a6b',
|
||||
'/document/808a5a90dda72cc3411ca9c1942f0889',
|
||||
'/document/b5ed5defd7154073dd44ed6f8cc4c51d',
|
||||
'/document/65e8b74faa3d2839f08255f8e1835d8e',
|
||||
'/document/f3af7921743fc47718ca39716fdc32d5',
|
||||
'/document/3311e9b7d4e57893b94807c3acbde279',
|
||||
'/document/4ec0139e3466527f71a102e26697b344',
|
||||
'/document/491c8b0ea8032e4fe40e735711258277',
|
||||
'/document/50d171c70356995b921c52568f1cf64e',
|
||||
'/document/033e121e5bc2b487353f512c0258ed1f',
|
||||
'/document/18831d6d910b64c1a7aaa409d694607c',
|
||||
'/document/bee4ca5c3a191bd7c43bb7098259bfcb',
|
||||
'/document/67e0ecd5b41e3b8b18b6d8e809b34f2d',
|
||||
'/document/ffdd72718d2f9d6f824e0304fb4ed0e8',
|
||||
'/document/49a03e53293d4e5be6e16b20fe78f107',
|
||||
'/document/5282789e6ee9ec37b2322abc4011aa80',
|
||||
'/document/e07f6b95ece80ed82977b096156f4ba8',
|
||||
'/document/7fdf9aa60d503f2f01f89a8b19b0c770',
|
||||
'/document/0053224c128c26593816041cfd923fcf',
|
||||
'/document/c150b448136a61c9f30bbc5372a246d1',
|
||||
'/document/40f2efd755b816586ea6fc4459159edf',
|
||||
'/document/d8afbafcf061f1f28cea58f2c2271c68',
|
||||
'/document/966fda6686c99a28bb1792730ea2057f',
|
||||
'/document/7c4f6647a6e5e4b5175eb5ecae971320',
|
||||
'/document/38b8b5be9a2ed9def3a4c80f98a80482',
|
||||
'/document/e33c22584756e7bb9ee0cd84f7899f4b',
|
||||
]
|
||||
),
|
||||
}
|
||||
}
|
||||
}))
|
||||
+12
-44
@@ -1,48 +1,16 @@
|
||||
server {
|
||||
listen 443 ssl;
|
||||
charset utf-8;
|
||||
|
||||
server_name art.shlisselburg.org;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/art.shlisselburg.org/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/art.shlisselburg.org/privkey.pem;
|
||||
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_ciphers AES256+EECDH:AES256+EDH:!aNULL;
|
||||
|
||||
location / {
|
||||
proxy_pass http://hudozka;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
}
|
||||
|
||||
error_log /var/log/nginx/art.shburg.error.log;
|
||||
access_log /var/log/nginx/art.shburg.access.log;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name art.shlisselburg.org art.shburg.org;
|
||||
|
||||
location ^~ /.well-known/acme-challenge {
|
||||
root /home/hoster/www/art.shburg.org/public;
|
||||
}
|
||||
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
charset utf-8;
|
||||
location / {
|
||||
return 301 https://art.shlisselburg.org$request_uri;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
try_files $uri $uri.html $uri/index.html =404;
|
||||
}
|
||||
}
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
upstream hudozka {
|
||||
server localhost:1800;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
# redirect server error pages to the static page /50x.html
|
||||
#
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"version": 2,
|
||||
"name": "hudozka",
|
||||
"alias": [
|
||||
"hudozka.now.sh"
|
||||
],
|
||||
"builds": [
|
||||
{
|
||||
"src": "dist/**",
|
||||
"use": "@now/static"
|
||||
}
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"src": "/(.*)",
|
||||
"dest": "dist/$1"
|
||||
}
|
||||
]
|
||||
}
|
||||
Generated
+8795
-9170
File diff suppressed because it is too large.
Load diff
+77
-108
@@ -1,109 +1,78 @@
|
||||
{
|
||||
"name": "Hudozka",
|
||||
"private": true,
|
||||
"version": "4.0.0",
|
||||
"description": "",
|
||||
"main": "src/bin/www.js",
|
||||
"scripts": {
|
||||
"start": "node src/index.js",
|
||||
"build-edit": "webpack -w --env.NODE_ENV=production --env.TARGET=edit",
|
||||
"build-app": "webpack --env.NODE_ENV=production --env.TARGET=app",
|
||||
"out": "babel src/ -d out/",
|
||||
"gulp": "gulp",
|
||||
"test": "mocha --compilers js:babel-register",
|
||||
"coverage": "istanbul cover mocha -- --compilers js:babel-register"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tmshv/hudozka"
|
||||
},
|
||||
"author": "Roman Timashev",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@mikeljames/draft-js-drag-n-drop-upload-plugin": "^2.0.1",
|
||||
"async-file": "^2.0.2",
|
||||
"classnames": "^2.2.5",
|
||||
"co": "^4.4.0",
|
||||
"draft-js": "^0.10.5",
|
||||
"draft-js-alignment-plugin": "^2.0.3",
|
||||
"draft-js-drag-n-drop-plugin": "^2.0.3",
|
||||
"draft-js-focus-plugin": "^2.0.4",
|
||||
"draft-js-image-plugin": "^2.0.5",
|
||||
"draft-js-plugins-editor": "^2.0.8",
|
||||
"draft-js-resizeable-plugin": "^2.0.6",
|
||||
"ejson": "^2.1.2",
|
||||
"handlebars": "^4.0.5",
|
||||
"immutable": "^3.8.2",
|
||||
"instagram-node": "^0.5.1",
|
||||
"koa": "^2.5.1",
|
||||
"koa-bodyparser": "^3.0.0",
|
||||
"koa-compose": "^3.1.0",
|
||||
"koa-conditional-get": "^1.0.2",
|
||||
"koa-convert": "^1.2.0",
|
||||
"koa-cookie": "^1.0.0",
|
||||
"koa-etag": "^2.0.0",
|
||||
"koa-helmet": "^1.0.0",
|
||||
"koa-logger": "^2.0.0",
|
||||
"koa-mount": "^2.0.0",
|
||||
"koa-route": "^3.1.0",
|
||||
"koa-session": "^4.0.0",
|
||||
"koa-static": "^3.0.0",
|
||||
"markdown": "^0.5.0",
|
||||
"moment": "^2.22.2",
|
||||
"mongodb": "^2.1.14",
|
||||
"mz": "^2.4.0",
|
||||
"react": "^16.3.2",
|
||||
"react-dom": "^16.3.2",
|
||||
"react-redux": "^4.4.8",
|
||||
"react-router": "^4.2.0",
|
||||
"react-router-redux": "^4.0.8",
|
||||
"redux": "^3.7.2",
|
||||
"redux-thunk": "^2.2.0",
|
||||
"request-promise": "^2.0.1",
|
||||
"sitemap": "^1.5.0",
|
||||
"thunkify": "^2.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.0.0-beta.47",
|
||||
"@babel/core": "^7.0.0-beta.47",
|
||||
"@babel/plugin-proposal-class-properties": "^7.0.0-beta.47",
|
||||
"@babel/polyfill": "^7.0.0-beta.47",
|
||||
"@babel/preset-env": "^7.0.0-beta.47",
|
||||
"@babel/register": "^7.0.0-beta.47",
|
||||
"autoprefixer": "^6.3.6",
|
||||
"babel-loader": "^8.0.0-beta.3",
|
||||
"babel-plugin-transform-class-properties": "^6.24.1",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.26.0",
|
||||
"babel-plugin-transform-react-jsx": "^6.23.0",
|
||||
"babel-plugin-transform-regenerator": "^6.26.0",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"babel-register": "^6.26.0",
|
||||
"css-loader": "^0.28.9",
|
||||
"cssnano": "^3.4.0",
|
||||
"eslint": "^3.9.1",
|
||||
"eslint-config-standard": "^6.2.1",
|
||||
"eslint-plugin-promise": "^3.3.1",
|
||||
"eslint-plugin-react": "^7.4.0",
|
||||
"eslint-plugin-standard": "^2.0.1",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-concat": "^2.4.1",
|
||||
"gulp-if": "^2.0.0",
|
||||
"gulp-imagemin": "~2.4.0",
|
||||
"gulp-postcss": "^6.0.1",
|
||||
"gulp-rename": "^1.2.2",
|
||||
"gulp-sass": "^4.0.1",
|
||||
"gulp-uglify": "~1.5.0",
|
||||
"html-minify-loader": "~1.1.0",
|
||||
"html-webpack-plugin": "^2.30.1",
|
||||
"istanbul": "~0.4.2",
|
||||
"less": "^3.0.4",
|
||||
"less-loader": "^4.1.0",
|
||||
"mocha": "~2.4.5",
|
||||
"raw-loader": "~0.5.1",
|
||||
"should": "^8.2.2",
|
||||
"style-loader": "^0.20.2",
|
||||
"vinyl-named": "~1.1.0",
|
||||
"webpack": "^3.11.0",
|
||||
"webpack-merge": "^4.1.1"
|
||||
}
|
||||
}
|
||||
"name": "hudozka",
|
||||
"private": true,
|
||||
"version": "5.0.0",
|
||||
"description": "",
|
||||
"main": "src/bin/www.js",
|
||||
"scripts": {
|
||||
"start": "NODE_ENV=production node src/server.js",
|
||||
"dev": "node src/server.js",
|
||||
"build": "next build && next export -o dist && npm run sitemap && npm run feed && npm run robots-txt",
|
||||
"robots-txt": "node scripts/robots-txt.js",
|
||||
"sitemap": "node scripts/sitemap.js",
|
||||
"feed": "node scripts/feed.js",
|
||||
"test": "mocha --compilers js:babel-register"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/tmshv/hudozka"
|
||||
},
|
||||
"author": "Roman Timashev",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^0.19.0",
|
||||
"classnames": "^2.2.5",
|
||||
"co": "^4.4.0",
|
||||
"ejson": "^2.1.2",
|
||||
"handlebars": "^4.1.2",
|
||||
"immutable": "^3.8.2",
|
||||
"instagram-node": "^0.5.1",
|
||||
"koa": "^2.7.0",
|
||||
"koa-route": "^3.1.0",
|
||||
"lodash": "^4.17.15",
|
||||
"markdown": "^0.5.0",
|
||||
"moment": "^2.22.2",
|
||||
"mongodb": "^2.1.14",
|
||||
"mz": "^2.4.0",
|
||||
"next": "^9.0.2",
|
||||
"react": "^16.8.6",
|
||||
"react-dom": "^16.8.6",
|
||||
"react-redux": "^4.4.8",
|
||||
"react-router": "^4.2.0",
|
||||
"react-router-redux": "^4.0.8",
|
||||
"redux": "^3.7.2",
|
||||
"redux-thunk": "^2.2.0",
|
||||
"request-promise": "^2.0.1",
|
||||
"sitemap": "^1.5.0",
|
||||
"thunkify": "^2.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.4.4",
|
||||
"@babel/core": "^7.4.5",
|
||||
"@babel/node": "^7.4.5",
|
||||
"@babel/plugin-proposal-class-properties": "^7.4.4",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.4.4",
|
||||
"@babel/polyfill": "^7.0.0-beta.47",
|
||||
"@babel/preset-env": "^7.4.5",
|
||||
"@babel/register": "^7.0.0-beta.47",
|
||||
"@zeit/next-css": "^1.0.1",
|
||||
"@zeit/next-sass": "^1.0.1",
|
||||
"autoprefixer": "^6.3.6",
|
||||
"babel-plugin-transform-class-properties": "^6.24.1",
|
||||
"babel-plugin-transform-react-jsx": "^6.23.0",
|
||||
"babel-plugin-transform-regenerator": "^6.26.0",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
"babel-register": "^6.26.0",
|
||||
"cssnano": "^4.1.10",
|
||||
"eslint": "^3.9.1",
|
||||
"eslint-config-standard": "^6.2.1",
|
||||
"eslint-plugin-promise": "^3.3.1",
|
||||
"eslint-plugin-react": "^7.4.0",
|
||||
"eslint-plugin-standard": "^2.0.1",
|
||||
"feed": "^4.0.0",
|
||||
"mocha": "^6.1.4",
|
||||
"node-sass": "^4.12.0",
|
||||
"should": "^8.2.2",
|
||||
"webpack": "^4.32.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import React from 'react'
|
||||
import Document, { Html, Head, Main, NextScript } from 'next/document'
|
||||
import { YMetrika } from '../src/components/YMetrika'
|
||||
import { GAnalytics } from '../src/components/GAnalytics'
|
||||
|
||||
export default class MyDocument extends Document {
|
||||
// static async getInitialProps(ctx) {
|
||||
// const initialProps = await Document.getInitialProps(ctx);
|
||||
// return { ...initialProps };
|
||||
// }
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Html>
|
||||
<Head>
|
||||
<style>{`body { margin: 0 } /* custom! */`}</style>
|
||||
|
||||
<meta charSet="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, maximum-scale=1.0" />
|
||||
<meta name="referrer" content="unsafe-url" />
|
||||
|
||||
<meta name="google-site-verification" content="8BxOTw-Q02DS2KAdcGcnIMwiPZ58DVB18XpOfRibCcg" />
|
||||
<meta name="google-site-verification" content="A-TEemUw4hHpULUDCd6xhxfHnyRbgKtss90JGKXnSpw" />
|
||||
<meta name='yandex-verification' content='643c7dac144b9dac' />
|
||||
<meta name="msvalidate.01" content="E878301FE3E3F12D233A6B1156889601" />
|
||||
|
||||
<link rel="icon" type="image/png" href="/static/graphics/favicon-32.png" />
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/static/graphics/favicon-57.png" />
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/static/graphics/favicon-72.png" />
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="/static/graphics/favicon-114.png" />
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/static/graphics/favicon-144.png" />
|
||||
|
||||
<link
|
||||
rel="alternate"
|
||||
type="application/rss+xml"
|
||||
title="Шлиссельбургская Детская Художественная Школа"
|
||||
href="/feed.xml"
|
||||
/>
|
||||
|
||||
{/* Fotorama + jQuery*/}
|
||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js" />
|
||||
<link href="//cdnjs.cloudflare.com/ajax/libs/fotorama/4.6.4/fotorama.css" rel="stylesheet" />
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/fotorama/4.6.4/fotorama.js" />
|
||||
|
||||
{/* Opa */}
|
||||
<script src="/static/opa.js" />
|
||||
<link rel="stylesheet" href="/static/opa.css" />
|
||||
|
||||
{/* Likely */}
|
||||
<script src='/static/likely.js' />
|
||||
<link href='/static/likely.css' rel='stylesheet' />
|
||||
|
||||
{/* Fonts */}
|
||||
<link href="https://fonts.googleapis.com/css?family=PT+Sans|PT+Serif" rel="stylesheet" as="font" />
|
||||
|
||||
{/* Instagram Embed */}
|
||||
<script async defer src="//platform.instagram.com/en_US/embeds.js" />
|
||||
|
||||
<YMetrika account={24027460} />
|
||||
<GAnalytics account={'UA-60209057-1'} />
|
||||
</Head>
|
||||
|
||||
<body className="custom_class">
|
||||
<Main />
|
||||
<NextScript />
|
||||
|
||||
<script src="/static/app.js" />
|
||||
</body>
|
||||
</Html>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from 'react'
|
||||
import '../src/style/style.scss'
|
||||
|
||||
const Page = props => (
|
||||
<div className="body-wrapper">
|
||||
<main className="body-wrapper__content">
|
||||
<section className="error-404">
|
||||
<div>
|
||||
<h1>Страница не найдена.</h1>
|
||||
<img src="/static/graphics/skull.jpg" />
|
||||
</div>
|
||||
|
||||
<div><a href="/">На главную</a></div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer className="footer--centered">
|
||||
<div className="copyright">
|
||||
<span>© 2012—2019 Шлиссельбургская детская художественная школа</span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,44 @@
|
||||
import React from 'react'
|
||||
import Head from 'next/head'
|
||||
import { App } from '../../src/components/App'
|
||||
import Article from '../../src/components/Article'
|
||||
import Html from '../../src/components/Html'
|
||||
import menuModel from '../../src/models/menu'
|
||||
import { buildMenu } from '../../src/lib/menu'
|
||||
import { createApiUrl, requestGet, wrapInitialProps } from '../../src/next-lib'
|
||||
|
||||
const Page = (props) => (
|
||||
<App
|
||||
menu={buildMenu(props.pageUrl, menuModel)}
|
||||
showAuthor={true}
|
||||
menuPadding={true}
|
||||
>
|
||||
<Head>
|
||||
<title>{props.album.title}</title>
|
||||
</Head>
|
||||
<div className="content content_thin">
|
||||
<Article
|
||||
title={props.album.title}
|
||||
date={props.album.date}
|
||||
shareable={true}
|
||||
>
|
||||
<Html
|
||||
html={props.album.post}
|
||||
/>
|
||||
</Article>
|
||||
</div>
|
||||
</App>
|
||||
)
|
||||
|
||||
Page.getInitialProps = wrapInitialProps(async (ctx) => {
|
||||
const pageUrl = '/gallery'
|
||||
const id = ctx.query.album
|
||||
const album = await requestGet(createApiUrl(ctx.req, `/api/albums/${id}`), {})
|
||||
|
||||
return {
|
||||
album,
|
||||
pageUrl,
|
||||
}
|
||||
})
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,19 @@
|
||||
import { connect } from '../../../src/core/db'
|
||||
import Album from '../../../src/core/Album'
|
||||
import { withMiddleware } from '../../../src/middlewares/withMiddleware'
|
||||
|
||||
export default withMiddleware(async (req, res) => {
|
||||
await connect()
|
||||
|
||||
const id = req.query.album
|
||||
const album = await Album.findById(id)
|
||||
|
||||
if (album) {
|
||||
res.json(album)
|
||||
} else {
|
||||
res.status(404)
|
||||
res.json({
|
||||
error: `Album ${id} not found`
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,19 @@
|
||||
import { connect } from '../../../src/core/db'
|
||||
import Album from '../../../src/core/Album'
|
||||
import { withMiddleware } from '../../../src/middlewares/withMiddleware'
|
||||
|
||||
export default withMiddleware(async (req, res) => {
|
||||
await connect()
|
||||
|
||||
const data = await Album.find({}, { sort: { date: -1 } })
|
||||
if (data) {
|
||||
res.json({
|
||||
items: data,
|
||||
})
|
||||
} else {
|
||||
res.status(404)
|
||||
res.json({
|
||||
error: `Article not found`
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,20 @@
|
||||
import { connect } from '../../../src/core/db'
|
||||
import Album from '../../../src/core/Album'
|
||||
|
||||
export default async (req, res) => {
|
||||
await connect()
|
||||
|
||||
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,22 @@
|
||||
import { connect } from '../../../src/core/db'
|
||||
import Article from '../../../src/core/Article'
|
||||
import { encodeArticle } from '../../../src/api/articles'
|
||||
|
||||
export default async (req, res) => {
|
||||
await connect()
|
||||
|
||||
const {
|
||||
query: { article: id }
|
||||
} = req;
|
||||
|
||||
const article = await Article.findById(id)
|
||||
|
||||
if (article) {
|
||||
res.json(encodeArticle(article))
|
||||
} else {
|
||||
res.status(404)
|
||||
res.json({
|
||||
error: `Article ${id} not found`
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { connect } from '../../../src/core/db'
|
||||
import { findArticles } from '../../../src/api/articles'
|
||||
import { withMiddleware } from '../../../src/middlewares/withMiddleware'
|
||||
|
||||
export default withMiddleware(async (req, res) => {
|
||||
await connect()
|
||||
|
||||
const { query } = req
|
||||
const page = parseInt(query.page)
|
||||
const pageSize = parseInt(query.pageSize)
|
||||
|
||||
const data = await findArticles(page, pageSize)
|
||||
|
||||
if (data) {
|
||||
res.json(data)
|
||||
} else {
|
||||
res.status(404)
|
||||
res.json({
|
||||
error: `Article ${id} not found`
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,21 @@
|
||||
import { connect } from '../../../src/core/db'
|
||||
import Article from '../../../src/core/Article'
|
||||
import { withMiddleware } from '../../../src/middlewares/withMiddleware'
|
||||
|
||||
export default withMiddleware(async (req, res) => {
|
||||
await connect()
|
||||
|
||||
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,20 @@
|
||||
import { connect } from '../../../src/core/db'
|
||||
import Document from '../../../src/core/Document'
|
||||
import { encodeFile } from '../../../src/api/files'
|
||||
import { withMiddleware } from '../../../src/middlewares/withMiddleware'
|
||||
|
||||
export default withMiddleware(async (req, res) => {
|
||||
await connect()
|
||||
|
||||
const id = req.query.document
|
||||
const document = await Document.findById(id)
|
||||
|
||||
if (document) {
|
||||
res.json(encodeFile(document))
|
||||
} else {
|
||||
res.status(404)
|
||||
res.json({
|
||||
error: `Document ${id} not found`
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,24 @@
|
||||
import { connect } from '../../../src/core/db'
|
||||
import Document from '../../../src/core/Document'
|
||||
import { encodeFile } from '../../../src/api/files'
|
||||
import { withMiddleware } from '../../../src/middlewares/withMiddleware'
|
||||
|
||||
export default withMiddleware(async (req, res) => {
|
||||
await connect()
|
||||
|
||||
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: `Documents not found`
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,21 @@
|
||||
import { connect } from '../../../src/core/db'
|
||||
import Document from '../../../src/core/Document'
|
||||
import { withMiddleware } from '../../../src/middlewares/withMiddleware'
|
||||
|
||||
export default withMiddleware(async (req, res) => {
|
||||
await connect()
|
||||
|
||||
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,20 @@
|
||||
import { connect } from '../../src/core/db'
|
||||
import Image from '../../src/core/Image'
|
||||
import { encodeImage } from '../../src/api/image'
|
||||
import { withMiddleware } from '../../src/middlewares/withMiddleware'
|
||||
|
||||
export default withMiddleware(async (req, res) => {
|
||||
await connect()
|
||||
|
||||
const file = req.query.file
|
||||
const image = await Image.findByFile(file)
|
||||
|
||||
if (image) {
|
||||
res.json(encodeImage(image))
|
||||
} else {
|
||||
res.status(404)
|
||||
res.json({
|
||||
error: `Image ${file} not found`
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,21 @@
|
||||
import { connect } from '../../src/core/db'
|
||||
import Page from '../../src/core/Page'
|
||||
import { getPathWithNoTrailingSlash } from '../../src/lib/url'
|
||||
import { encodePage } from '../../src/api/page'
|
||||
import { withMiddleware } from '../../src/middlewares/withMiddleware'
|
||||
|
||||
export default withMiddleware(async (req, res) => {
|
||||
await connect()
|
||||
|
||||
const page = getPathWithNoTrailingSlash(req.query.page)
|
||||
const resource = await Page.findByUrl(page)
|
||||
|
||||
if (resource) {
|
||||
res.json(encodePage(resource))
|
||||
} else {
|
||||
res.status(404)
|
||||
res.json({
|
||||
error: `Image ${page} not found`
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,20 @@
|
||||
import { connect } from '../../../src/core/db'
|
||||
import Page from '../../../src/core/Page'
|
||||
import { withMiddleware } from '../../../src/middlewares/withMiddleware'
|
||||
|
||||
export default withMiddleware(async (req, res) => {
|
||||
await connect()
|
||||
|
||||
const items = await Page.find({})
|
||||
|
||||
if (items) {
|
||||
res.json({
|
||||
items,
|
||||
})
|
||||
} else {
|
||||
res.status(404)
|
||||
res.json({
|
||||
error: `Article ${id} not found`
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,21 @@
|
||||
import { connect } from '../../../src/core/db'
|
||||
import Page from '../../../src/core/Page'
|
||||
import { withMiddleware } from '../../../src/middlewares/withMiddleware'
|
||||
|
||||
export default withMiddleware(async (req, res) => {
|
||||
await connect()
|
||||
|
||||
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,20 @@
|
||||
import { connect } from '../../../src/core/db'
|
||||
import Teacher from '../../../src/core/Teacher'
|
||||
import { encodePerson } from '../../../src/api/persons'
|
||||
import { withMiddleware } from '../../../src/middlewares/withMiddleware'
|
||||
|
||||
export default withMiddleware(async (req, res) => {
|
||||
await connect()
|
||||
|
||||
const id = req.query.person
|
||||
const data = await Teacher.findById(id)
|
||||
|
||||
if (data) {
|
||||
res.json(encodePerson(data))
|
||||
} else {
|
||||
res.status(404)
|
||||
res.json({
|
||||
error: `Article ${id} not found`
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,33 @@
|
||||
import { connect } from '../../../src/core/db'
|
||||
import Teacher from '../../../src/core/Teacher'
|
||||
import { prioritySort } from '../../../src/lib/sort'
|
||||
import { encodePerson } from '../../../src/api/persons'
|
||||
import { withMiddleware } from '../../../src/middlewares/withMiddleware'
|
||||
|
||||
export default withMiddleware(async (req, res) => {
|
||||
await connect()
|
||||
|
||||
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: `Article ${id} not found`
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,21 @@
|
||||
import { connect } from '../../../src/core/db'
|
||||
import Teacher from '../../../src/core/Teacher'
|
||||
import { withMiddleware } from '../../../src/middlewares/withMiddleware'
|
||||
|
||||
export default withMiddleware(async (req, res) => {
|
||||
await connect()
|
||||
|
||||
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,20 @@
|
||||
import { connect } from '../../src/core/db'
|
||||
import { findSchedule } from '../../src/core/schedule'
|
||||
import { withMiddleware } from '../../src/middlewares/withMiddleware'
|
||||
|
||||
export default withMiddleware(async (req, res) => {
|
||||
await connect()
|
||||
|
||||
const period = req.query.period
|
||||
const semester = req.query.semester
|
||||
const data = await findSchedule(period, semester)
|
||||
|
||||
if (data) {
|
||||
res.json(data)
|
||||
} else {
|
||||
res.status(404)
|
||||
res.json({
|
||||
error: `Schedule ${period}:${semester} not found`
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,56 @@
|
||||
import React from 'react'
|
||||
import Head from 'next/head'
|
||||
import { get } from 'lodash'
|
||||
import { App } from '../../src/components/App'
|
||||
import Article from '../../src/components/Article'
|
||||
import Html from '../../src/components/Html'
|
||||
import menuModel from '../../src/models/menu'
|
||||
import { buildMenu } from '../../src/lib/menu'
|
||||
import { Meta } from '../../src/components/Meta'
|
||||
import { meta } from '../../src/lib/meta'
|
||||
import { createApiUrl, requestGet, wrapInitialProps } from '../../src/next-lib'
|
||||
|
||||
const Page = (props) => (
|
||||
<App
|
||||
menu={buildMenu(props.pageUrl, menuModel)}
|
||||
showAuthor={true}
|
||||
menuPadding={true}
|
||||
>
|
||||
<Head>
|
||||
<title>{props.article.title}</title>
|
||||
<Meta meta={props.meta} />
|
||||
</Head>
|
||||
<div className={'content content_thin'}>
|
||||
<Article
|
||||
title={props.article.title}
|
||||
date={props.article.date}
|
||||
tags={props.article.tags}
|
||||
shareable={true}
|
||||
>
|
||||
<Html
|
||||
html={props.article.post}
|
||||
/>
|
||||
</Article>
|
||||
</div>
|
||||
</App>
|
||||
)
|
||||
|
||||
Page.getInitialProps = wrapInitialProps(async (ctx) => {
|
||||
const pageUrl = '/'
|
||||
const id = ctx.query.article
|
||||
const article = await requestGet(createApiUrl(ctx.req, `/api/articles/${id}`), {})
|
||||
const image = get(article, 'preview.artifacts.fb', {})
|
||||
|
||||
return {
|
||||
article,
|
||||
pageUrl,
|
||||
meta: meta({
|
||||
title: article.title,
|
||||
image: image.src,
|
||||
imageWidth: image.width,
|
||||
imageHeight: image.height,
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,53 @@
|
||||
import React from 'react'
|
||||
import Head from 'next/head'
|
||||
import { App } from '../../src/components/App'
|
||||
import { ArticleCardList } from '../../src/components/ArticleCardList'
|
||||
import menuModel from '../../src/models/menu'
|
||||
import { buildMenu } from '../../src/lib/menu'
|
||||
import { meta } from '../../src/lib/meta'
|
||||
import { Meta } from '../../src/components/Meta'
|
||||
import { createApiUrl, requestGet, wrapInitialProps } from '../../src/next-lib'
|
||||
|
||||
const Page = (props) => (
|
||||
<App
|
||||
menu={buildMenu(props.pageUrl, menuModel)}
|
||||
showAuthor={true}
|
||||
menuPadding={true}
|
||||
>
|
||||
<Head>
|
||||
<title>{props.title}</title>
|
||||
<Meta meta={props.meta} />
|
||||
</Head>
|
||||
<div className="content content_wide">
|
||||
<ArticleCardList
|
||||
articles={props.articles}
|
||||
prevPage={props.prevPage}
|
||||
nextPage={props.nextPage}
|
||||
/>
|
||||
</div>
|
||||
</App>
|
||||
)
|
||||
|
||||
Page.getInitialProps = wrapInitialProps(async (ctx) => {
|
||||
const pageUrl = '/'
|
||||
const page = parseInt(ctx.query.page)
|
||||
const pageSize = parseInt(ctx.query.pageSize) || 15
|
||||
const res = await requestGet(createApiUrl(ctx.req, `/api/articles?page=${page}&pageSize=${pageSize}`), {})
|
||||
const articles = res.items || []
|
||||
const nextPage = res.nextPage
|
||||
const prevPage = res.prevPage
|
||||
const title = 'События'
|
||||
|
||||
return {
|
||||
articles,
|
||||
nextPage,
|
||||
prevPage,
|
||||
pageUrl,
|
||||
title,
|
||||
meta: meta({
|
||||
title,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,74 @@
|
||||
import React from 'react'
|
||||
import Head from 'next/head'
|
||||
import { get } from 'lodash'
|
||||
import { App } from '../src/components/App'
|
||||
import { Meta } from '../src/components/Meta'
|
||||
import { CollectiveImage } from '../src/components/CollectiveImage'
|
||||
import PersonCard from '../src/components/PersonCard'
|
||||
import { CardList } from '../src/components/CardList'
|
||||
import menuModel from '../src/models/menu'
|
||||
import { buildMenu } from '../src/lib/menu'
|
||||
import { meta } from '../src/lib/meta'
|
||||
import { createApiUrl, requestGet, wrapInitialProps } from '../src/next-lib'
|
||||
|
||||
const Page = (props) => (
|
||||
<App
|
||||
menu={buildMenu(props.pageUrl, menuModel)}
|
||||
showAuthor={true}
|
||||
menuPadding={true}
|
||||
>
|
||||
<Head>
|
||||
<title>{props.title}</title>
|
||||
<Meta meta={props.meta} />
|
||||
</Head>
|
||||
|
||||
<div className="content content_semi-wide">
|
||||
{!props.image ? null : (
|
||||
<CollectiveImage
|
||||
data={props.image}
|
||||
style={{
|
||||
marginBottom: 'var(--double-margin)',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<CardList
|
||||
items={props.persons}
|
||||
renderItem={(person, index) => (
|
||||
<PersonCard
|
||||
key={index}
|
||||
profile={person}
|
||||
picture={person.picture}
|
||||
url={person.url}
|
||||
name={person.name}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</App>
|
||||
)
|
||||
|
||||
Page.getInitialProps = wrapInitialProps(async (ctx) => {
|
||||
const pageUrl = ctx.req.url
|
||||
|
||||
const res = await requestGet(createApiUrl(ctx.req, '/api/persons'), {})
|
||||
const persons = res.items || []
|
||||
const title = 'Преподаватели Шлиссельбургской ДХШ'
|
||||
const imageFile = 'Images/HudozkaCollective2017.jpg'
|
||||
const resImage = await requestGet(createApiUrl(ctx.req, `/api/image?file=${imageFile}`), null)
|
||||
const image = get(resImage, 'data.artifacts.large', null)
|
||||
|
||||
return {
|
||||
persons,
|
||||
image,
|
||||
pageUrl,
|
||||
title,
|
||||
meta: meta({
|
||||
title,
|
||||
url: pageUrl,
|
||||
description: 'Преподаватели Шлиссельбургской ДХШ',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,43 @@
|
||||
import React from 'react'
|
||||
import Head from 'next/head'
|
||||
import { App } from '../../src/components/App'
|
||||
import Document from '../../src/components/Document'
|
||||
import Share from '../../src/components/Share'
|
||||
import menuModel from '../../src/models/menu'
|
||||
import { buildMenu } from '../../src/lib/menu'
|
||||
import { meta } from '../../src/lib/meta'
|
||||
import { Meta } from '../../src/components/Meta'
|
||||
import { createApiUrl, requestGet, wrapInitialProps } from '../../src/next-lib'
|
||||
|
||||
const Page = (props) => (
|
||||
<App
|
||||
menu={buildMenu(props.pageUrl, menuModel)}
|
||||
showAuthor={true}
|
||||
menuPadding={true}
|
||||
>
|
||||
<Head>
|
||||
<title>{props.file.title}</title>
|
||||
<Meta meta={props.meta} />
|
||||
</Head>
|
||||
<div className="content content_wide">
|
||||
<Document {...props.file} />
|
||||
<Share />
|
||||
</div>
|
||||
</App>
|
||||
)
|
||||
|
||||
Page.getInitialProps = wrapInitialProps(async (ctx) => {
|
||||
const pageUrl = '/documents'
|
||||
const id = ctx.query.document
|
||||
const file = await requestGet(createApiUrl(ctx.req, `/api/files/${id}`), {})
|
||||
|
||||
return {
|
||||
file,
|
||||
pageUrl,
|
||||
meta: meta({
|
||||
title: file.meta,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,128 @@
|
||||
import React from 'react'
|
||||
import Head from 'next/head'
|
||||
import { App } from '../src/components/App'
|
||||
import { Meta } from '../src/components/Meta'
|
||||
import menuModel from '../src/models/menu'
|
||||
import { buildMenu } from '../src/lib/menu'
|
||||
import { meta } from '../src/lib/meta'
|
||||
|
||||
import DocumentList from '../src/components/DocumentList'
|
||||
import { unique } from '../src/utils/common'
|
||||
import { createApiUrl, requestGet, wrapInitialProps } from '../src/next-lib'
|
||||
|
||||
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)
|
||||
}], [])
|
||||
}
|
||||
|
||||
const Page = (props) => (
|
||||
<App
|
||||
menu={buildMenu(props.pageUrl, menuModel)}
|
||||
showAuthor={true}
|
||||
menuPadding={true}
|
||||
>
|
||||
<Head>
|
||||
<title>{props.title}</title>
|
||||
<Meta meta={props.meta} />
|
||||
</Head>
|
||||
|
||||
<div className="content content_thin">
|
||||
{props.collections.map(({ name, items }, index) => (
|
||||
<DocumentList
|
||||
key={index}
|
||||
name={name}
|
||||
documents={items}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</App>
|
||||
)
|
||||
|
||||
Page.getInitialProps = wrapInitialProps(async (ctx) => {
|
||||
const pageUrl = '/documents'
|
||||
const res = await requestGet(createApiUrl(ctx.req, '/api/files'), {})
|
||||
const files = res.items || []
|
||||
const title = 'Документы'
|
||||
const collections = getSorted(files)
|
||||
|
||||
return {
|
||||
collections,
|
||||
pageUrl,
|
||||
title,
|
||||
meta: meta({
|
||||
title,
|
||||
description: 'Документы Шлиссельбургской ДХШ',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,102 @@
|
||||
import React from 'react'
|
||||
import Head from 'next/head'
|
||||
import { App } from '../src/components/App'
|
||||
// import Album from '../src/components/Album'
|
||||
import Article from '../src/components/Article'
|
||||
import { Meta } from '../src/components/Meta'
|
||||
import menuModel from '../src/models/menu'
|
||||
import { buildMenu } from '../src/lib/menu'
|
||||
import { splitBy } from '../src/lib/array'
|
||||
import { meta } from '../src/lib/meta'
|
||||
import { createApiUrl, requestGet, wrapInitialProps } from '../src/next-lib'
|
||||
|
||||
const albumsByYear = splitBy(album => new Date(album.date).getFullYear())
|
||||
|
||||
function getMeta() {
|
||||
return {
|
||||
description: 'Галерея работ учащихся Шлиссельбургской Детской Художественной Школы'
|
||||
}
|
||||
}
|
||||
|
||||
const m = 1
|
||||
const AlbumImage = ({ data, alt }) => (
|
||||
<picture>
|
||||
<img
|
||||
className="opa"
|
||||
alt={alt}
|
||||
src={data.src}
|
||||
width={data.width * m}
|
||||
height={data.height * m}
|
||||
srcSet={(data.set || []).map(({ url, density }) => `${url} ${density}x`)}
|
||||
/>
|
||||
</picture>
|
||||
)
|
||||
|
||||
const GItem = ({ album }) => (
|
||||
<div className="gallery-item">
|
||||
<a className="invisible" href={album.url}>
|
||||
<AlbumImage data={album.preview} alt={album.title} />
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
|
||||
const ACollection = ({ title, albums }) => (
|
||||
<div className="album-collection">
|
||||
<div className="album-collection__title">{title}</div>
|
||||
<div className="album-collection__body">{
|
||||
albums.map((album, index) => (
|
||||
<GItem key={index} album={album} />
|
||||
))
|
||||
}</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
|
||||
const Page = (props) => (
|
||||
<App
|
||||
menu={buildMenu(props.pageUrl, menuModel)}
|
||||
showAuthor={true}
|
||||
menuPadding={true}
|
||||
>
|
||||
<Head>
|
||||
<title>{props.title}</title>
|
||||
<Meta meta={props.meta} />
|
||||
</Head>
|
||||
|
||||
<div className="content content_semi-wide">
|
||||
<Article
|
||||
// title={meta.title}
|
||||
title={'Галерея'}
|
||||
>
|
||||
{props.collections.map(([year, albums], index) => (
|
||||
<ACollection
|
||||
key={index}
|
||||
title={year}
|
||||
albums={albums}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</Article>
|
||||
</div>
|
||||
</App>
|
||||
)
|
||||
|
||||
Page.getInitialProps = wrapInitialProps(async (ctx) => {
|
||||
const pageUrl = ctx.req.url
|
||||
const res = await requestGet(createApiUrl(ctx.req, `/api/albums`), {})
|
||||
const items = res.items || []
|
||||
const albumCollections = albumsByYear(items)
|
||||
const title = 'Галерея'
|
||||
|
||||
return {
|
||||
collections: [...albumCollections.entries()],
|
||||
pageUrl,
|
||||
title,
|
||||
meta: meta({
|
||||
title,
|
||||
description: 'Галерея работ учащихся Шлиссельбургской Детской Художественной Школы'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,58 @@
|
||||
import React from 'react'
|
||||
import Head from 'next/head'
|
||||
import { App } from '../src/components/App'
|
||||
import { Meta } from '../src/components/Meta'
|
||||
import { ArticleCardList } from '../src/components/ArticleCardList'
|
||||
import { HudozkaTitle } from '../src/components/HudozkaTitle'
|
||||
import menuModel from '../src/models/menu'
|
||||
import { meta } from '../src/lib/meta'
|
||||
import { buildMenu } from '../src/lib/menu'
|
||||
import { createApiUrl, requestGet, wrapInitialProps } from '../src/next-lib'
|
||||
|
||||
const Page = (props) => (
|
||||
<App
|
||||
menu={buildMenu(props.pageUrl, menuModel)}
|
||||
showAuthor={true}
|
||||
menuPadding={true}
|
||||
>
|
||||
<Head>
|
||||
<title>{props.title}</title>
|
||||
<Meta meta={props.meta} />
|
||||
</Head>
|
||||
|
||||
<div className="content content_wide">
|
||||
<HudozkaTitle />
|
||||
|
||||
<ArticleCardList
|
||||
articles={props.articles}
|
||||
prevPage={props.prevPage}
|
||||
nextPage={props.nextPage}
|
||||
/>
|
||||
</div>
|
||||
</App>
|
||||
)
|
||||
|
||||
Page.getInitialProps = wrapInitialProps(async (ctx) => {
|
||||
const pageUrl = '/'
|
||||
const page = 1
|
||||
const pageSize = ctx.query.pageSize || 15
|
||||
const res = await requestGet(createApiUrl(ctx.req, `/api/articles?page=${page}&pageSize=${pageSize}`), {})
|
||||
const articles = res.items || []
|
||||
const nextPage = res.nextPage
|
||||
const prevPage = res.prevPage
|
||||
const title = 'Шлиссельбургская ДХШ'
|
||||
|
||||
return {
|
||||
articles,
|
||||
nextPage,
|
||||
prevPage,
|
||||
pageUrl,
|
||||
title,
|
||||
meta: meta({
|
||||
title,
|
||||
url: pageUrl,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,51 @@
|
||||
import React from 'react'
|
||||
import Head from 'next/head'
|
||||
import { App } from '../src/components/App'
|
||||
import Page from '../src/components/Page'
|
||||
import menuModel from '../src/models/menu'
|
||||
import { buildMenu } from '../src/lib/menu'
|
||||
import { Meta } from '../src/components/Meta'
|
||||
import { meta } from '../src/lib/meta'
|
||||
import { createApiUrl, requestGet, wrapInitialProps } from '../src/next-lib'
|
||||
|
||||
const Index = (props) => (
|
||||
<App
|
||||
menu={buildMenu(props.pageUrl, menuModel)}
|
||||
showAuthor={true}
|
||||
menuPadding={true}
|
||||
>
|
||||
<Head>
|
||||
<title>{props.title}</title>
|
||||
<Meta meta={props.meta} />
|
||||
</Head>
|
||||
|
||||
<div className={'content content_thin'}>
|
||||
<Page
|
||||
shareable={true}
|
||||
>
|
||||
{props.content}
|
||||
</Page>
|
||||
</div>
|
||||
</App>
|
||||
)
|
||||
|
||||
Index.getInitialProps = wrapInitialProps(async (ctx) => {
|
||||
const pageUrl = ctx.req.url
|
||||
// // const pageUrl = '/neighbors/2018'
|
||||
const page = await requestGet(createApiUrl(ctx.req, `/api/page?page=${pageUrl}`), {})
|
||||
const image = page.preview ? page.preview.artifacts.fb : {}
|
||||
|
||||
return {
|
||||
content: page.data,
|
||||
pageUrl,
|
||||
title: page.title,
|
||||
meta: meta({
|
||||
title: page.title,
|
||||
image: image.src,
|
||||
imageWidth: image.width,
|
||||
imageHeight: image.height,
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
export default Index
|
||||
@@ -0,0 +1,43 @@
|
||||
import React from 'react'
|
||||
import Head from 'next/head'
|
||||
import { App } from '../src/components/App'
|
||||
import Schedule from '../src/components/Schedule'
|
||||
import Share from '../src/components/Share'
|
||||
import menuModel from '../src/models/menu'
|
||||
import { buildMenu } from '../src/lib/menu'
|
||||
import { createApiUrl, requestGet, wrapInitialProps } from '../src/next-lib'
|
||||
|
||||
const Page = (props) => (
|
||||
<App
|
||||
menu={buildMenu(props.pageUrl, menuModel)}
|
||||
showAuthor={true}
|
||||
menuPadding={true}
|
||||
>
|
||||
<Head>
|
||||
<title>{props.title}</title>
|
||||
</Head>
|
||||
|
||||
<div className="content content_wide">
|
||||
{/*<ScheduleList schedules={schedules}/>*/}
|
||||
|
||||
<Schedule {...props.schedule} />
|
||||
|
||||
<Share />
|
||||
</div>
|
||||
</App>
|
||||
)
|
||||
|
||||
Page.getInitialProps = wrapInitialProps(async (ctx) => {
|
||||
const pageUrl = ctx.req.url
|
||||
const period = '2016-2017'
|
||||
const semester = 'spring'
|
||||
const schedule = await requestGet(createApiUrl(ctx.req, `/api/schedule?period=${period}&semester=${semester}`), {})
|
||||
|
||||
return {
|
||||
schedule,
|
||||
pageUrl,
|
||||
title: 'Расписание',
|
||||
}
|
||||
})
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,51 @@
|
||||
import React from 'react'
|
||||
import axios from 'axios'
|
||||
import Head from 'next/head'
|
||||
import { App } from '../../src/components/App'
|
||||
import Page from '../../src/components/Page'
|
||||
import { Meta } from '../../src/components/Meta'
|
||||
import menuModel from '../../src/models/menu'
|
||||
import { buildMenu } from '../../src/lib/menu'
|
||||
import { meta } from '../../src/lib/meta'
|
||||
import { createApiUrl, requestGet, wrapInitialProps } from '../../src/next-lib'
|
||||
|
||||
const Index = (props) => (
|
||||
<App
|
||||
menu={buildMenu(props.pageUrl, menuModel)}
|
||||
showAuthor={true}
|
||||
menuPadding={true}
|
||||
>
|
||||
<Head>
|
||||
<title>{props.title}</title>
|
||||
<Meta meta={props.meta} />
|
||||
</Head>
|
||||
<div className="content content_thin">
|
||||
<Page shareable={true}>
|
||||
{props.person.post}
|
||||
</Page>
|
||||
</div>
|
||||
</App>
|
||||
)
|
||||
|
||||
Index.getInitialProps = wrapInitialProps(async (ctx) => {
|
||||
const pageUrl = '/collective'
|
||||
const id = ctx.query.person
|
||||
const person = await requestGet(createApiUrl(ctx.req, `/api/persons/${id}`), {})
|
||||
const name = person.name || []
|
||||
const title = name.join(' ')
|
||||
const image = person.preview ? person.preview.artifacts.fb : {}
|
||||
|
||||
return {
|
||||
pageUrl,
|
||||
person,
|
||||
title,
|
||||
meta: meta({
|
||||
title,
|
||||
image: image.src,
|
||||
imageWidth: image.width,
|
||||
imageHeight: image.height,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export default Index
|
||||
File renamed without changes.
@@ -0,0 +1,43 @@
|
||||
const { Feed } = require('feed')
|
||||
const axios = require('axios')
|
||||
const fs = require('fs').promises
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: 'http://localhost:3000',
|
||||
})
|
||||
|
||||
async function getArticles(pageSize) {
|
||||
const page = 1
|
||||
const res = await api.get(`/api/articles?page=${page}&pageSize=${pageSize}`)
|
||||
|
||||
return res.data.items
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const articles = await getArticles(30)
|
||||
|
||||
const url = 'https://art.shlisselburg.org'
|
||||
const feed = new Feed({
|
||||
title: 'Шлиссельбургская ДХШ',
|
||||
id: url,
|
||||
link: url,
|
||||
feedLinks: {
|
||||
atom: 'https://art.shlisselburg.org/feed.xml',
|
||||
},
|
||||
});
|
||||
|
||||
articles.forEach(article => {
|
||||
feed.addItem({
|
||||
title: article.title,
|
||||
date: new Date(article.date),
|
||||
id: article.id,
|
||||
link: article.url,
|
||||
content: article.post,
|
||||
})
|
||||
})
|
||||
|
||||
const content = feed.atom1()
|
||||
|
||||
await fs.writeFile('dist/feed.xml', content, { encoding: 'utf-8' })
|
||||
})()
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
const fs = require('fs').promises;
|
||||
|
||||
(async () => {
|
||||
await fs.copyFile('public/robots.txt', 'dist/robots.txt')
|
||||
})()
|
||||
@@ -0,0 +1,80 @@
|
||||
const sitemap = require('sitemap')
|
||||
const axios = require('axios')
|
||||
const fs = require('fs').promises
|
||||
const homeUrl = 'https://art.shlisselburg.org'
|
||||
const sitemapCacheTime = 600000
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: 'http://localhost:3000',
|
||||
})
|
||||
|
||||
async function getMenuUrls(frequency = 'daily') {
|
||||
const menu = [
|
||||
'/',
|
||||
'/schedule',
|
||||
'/documents',
|
||||
'/collective',
|
||||
'/gallery',
|
||||
]
|
||||
return menu
|
||||
// .filter(i => 'url' in i)
|
||||
.map(url => ({
|
||||
url,
|
||||
changefreq: frequency
|
||||
}))
|
||||
}
|
||||
|
||||
async function getPageUrls(frequency = 'daily') {
|
||||
const res = await api.get('/api/pages/urls')
|
||||
|
||||
return res.data.items.map(url => ({
|
||||
changefreq: frequency,
|
||||
url,
|
||||
}))
|
||||
}
|
||||
|
||||
async function getAlbumsUrls() {
|
||||
const res = await api.get('/api/albums/urls')
|
||||
return res.data.items.map(url => ({
|
||||
changefreq: 'monthly',
|
||||
url,
|
||||
}))
|
||||
}
|
||||
|
||||
async function getTeacherUrls() {
|
||||
const res = await api.get('/api/persons/urls')
|
||||
|
||||
return res.data.items.map(url => ({
|
||||
changefreq: 'monthly',
|
||||
url,
|
||||
}))
|
||||
}
|
||||
|
||||
async function getArticleUrls() {
|
||||
const res = await api.get('/api/articles/urls')
|
||||
|
||||
return res.data.items.map(url => ({
|
||||
changefreq: 'monthly',
|
||||
url,
|
||||
}))
|
||||
}
|
||||
|
||||
(async () => {
|
||||
let urls = await Promise.all([
|
||||
getMenuUrls(),
|
||||
getPageUrls(),
|
||||
getAlbumsUrls(),
|
||||
getTeacherUrls(),
|
||||
getArticleUrls(),
|
||||
])
|
||||
urls = urls.reduce((urls, i) => urls.concat(i))
|
||||
|
||||
let map = sitemap.createSitemap({
|
||||
hostname: homeUrl,
|
||||
cacheTime: sitemapCacheTime,
|
||||
urls
|
||||
})
|
||||
const content = map.toString()
|
||||
|
||||
await fs.writeFile('dist/sitemap.xml', content, { encoding: 'utf-8' })
|
||||
})()
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"presets": ["es2015", "react", "stage-0"]
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
.DS_Store
|
||||
.idea
|
||||
*.sublime*
|
||||
npm-debug.log
|
||||
|
||||
.private*
|
||||
|
||||
/node_modules
|
||||
/build
|
||||
/out
|
||||
/coverage
|
||||
@@ -1,41 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
echo "Deploying Instagram Microservice"
|
||||
|
||||
#NODE_ENV=production gulp deploy
|
||||
|
||||
server="art.shburg.org"
|
||||
tarfile="build.tar.gz"
|
||||
tarpath=$TMPDIR${tarfile}
|
||||
destpath="~/www/art.shburg.org/services/instagram"
|
||||
private=".private"
|
||||
|
||||
echo "Building..."
|
||||
NODE_ENV=production npm run webpack
|
||||
npm run build
|
||||
|
||||
echo "Packing..."
|
||||
env GZIP=-9 tar -czf ${tarpath} \
|
||||
--disable-copyfile \
|
||||
--exclude=node_modules \
|
||||
--exclude=.git \
|
||||
--exclude=.idea \
|
||||
--exclude=.DS_Store \
|
||||
--exclude=.private* \
|
||||
--exclude=src \
|
||||
--exclude=deploy.sh \
|
||||
--exclude=install.sh \
|
||||
.
|
||||
|
||||
echo "Build file: $tarpath"
|
||||
|
||||
scp ${tarpath} hoster@${server}:${destpath}
|
||||
echo "Build file successfully uploaded on ${server}"
|
||||
|
||||
scp ${private} hoster@${server}:${destpath}
|
||||
|
||||
echo "Installing..."
|
||||
ssh hoster@${server} "bash -s" -- < ./install.sh ${destpath} ${tarfile}
|
||||
|
||||
rm ${tarpath}
|
||||
echo "Done"
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"uid": "hudozka-instagram",
|
||||
"append": true,
|
||||
"watch": false,
|
||||
"script": "index.js",
|
||||
"sourceDir": "/home/hoster/www/art.shburg.org/services/instagram/out",
|
||||
"wordingDir": "/home/hoster/www/art.shburg.org/services/instagram"
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
dir_path="$1"
|
||||
build_file="$2"
|
||||
app="hudozka-instagram"
|
||||
script="forever.json"
|
||||
|
||||
cd ${dir_path}
|
||||
gunzip -c ${build_file} | tar xopf -
|
||||
npm install --production --loglevel error
|
||||
|
||||
if forever restart ${app}; then
|
||||
echo Instagram Microservice restarted
|
||||
else
|
||||
forever start ${script}
|
||||
echo Instagram Microservice started
|
||||
fi
|
||||
@@ -1,39 +0,0 @@
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name art.shburg.org;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/art.shlisselburg.org/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/art.shlisselburg.org/privkey.pem;
|
||||
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
ssl_prefer_server_ciphers on;
|
||||
ssl_ciphers AES256+EECDH:AES256+EDH:!aNULL;
|
||||
|
||||
location /service/instagram {
|
||||
proxy_pass http://hudozka_instagram/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $connection_upgrade;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $remote_addr;
|
||||
}
|
||||
|
||||
error_log /var/log/nginx/art.shburg.instagram.error.log;
|
||||
access_log /var/log/nginx/art.shburg.instagram.access.log;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name art.shburg.org;
|
||||
return 301 https://art.shburg.org$request_uri;
|
||||
}
|
||||
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
|
||||
upstream hudozka_instagram {
|
||||
server localhost:18000;
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"name": "hudozka-instagram",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"devwebpack": "NODE_ENV=development webpack",
|
||||
"webpack": "webpack",
|
||||
"start": "babel-node src/index.js",
|
||||
"build": "babel src -d out"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-polyfill": "^6.7.2",
|
||||
"co": "^4.6.0",
|
||||
"ejson": "^2.1.2",
|
||||
"instagram-node": "^0.5.8",
|
||||
"jsonwebtoken": "^5.7.0",
|
||||
"kerberos": "0.0.19",
|
||||
"koa": "^2.0.0-alpha.3",
|
||||
"koa-bodyparser": "^3.0.0",
|
||||
"koa-compose": "^3.1.0",
|
||||
"koa-convert": "^1.2.0",
|
||||
"koa-logger": "^2.0.0",
|
||||
"koa-mount": "^2.0.0",
|
||||
"koa-route": "^3.0.0",
|
||||
"koa-session-mongo": "^1.1.0",
|
||||
"koa-session-store": "^1.1.1",
|
||||
"koa-static": "^3.0.0",
|
||||
"mongodb": "^2.1.10",
|
||||
"mz": "^2.3.1",
|
||||
"react": "^0.14.7",
|
||||
"react-dom": "^0.14.7",
|
||||
"react-router": "^2.0.1",
|
||||
"request-promise": "^2.0.1",
|
||||
"thunkify": "^2.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.7.2",
|
||||
"babel-preset-es2015": "^6.6.0",
|
||||
"babel-preset-stage-0": "^6.5.0",
|
||||
"babel-cli": "^6.6.5",
|
||||
"babel-loader": "^6.2.4",
|
||||
"babel-preset-react": "^6.5.0",
|
||||
"html-webpack-plugin": "^2.10.0",
|
||||
"webpack": "^1.12.14"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
export default class EndPoint{
|
||||
constructor(request) {
|
||||
this.request = request;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import EndPoint from './EndPoint';
|
||||
|
||||
export default class Feed extends EndPoint {
|
||||
add(post) {
|
||||
return this.request.request('PUT', '/timeline', post);
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import {stringify} from 'ejson';
|
||||
import {EventEmitter} from 'events';
|
||||
import request from 'request-promise';
|
||||
import Feed from './Feed';
|
||||
|
||||
let json = i => stringify(i);
|
||||
|
||||
export default class Service extends EventEmitter{
|
||||
constructor(host, service, secret) {
|
||||
super();
|
||||
this.version = 'v1';
|
||||
this.host = host;
|
||||
this.service = service;
|
||||
this.secret = secret;
|
||||
this.feed = new Feed(this);
|
||||
}
|
||||
|
||||
request(method, path, data) {
|
||||
let buildPath = path => `${this.host}/api/${this.version}${path}`;
|
||||
let options = {
|
||||
method: method,
|
||||
uri: buildPath(path),
|
||||
headers: {
|
||||
'Content-Type': 'application/ejson',
|
||||
'X-Service-Name': this.service,
|
||||
'X-Service-Token': this.secret
|
||||
},
|
||||
body: json(data)
|
||||
};
|
||||
|
||||
return request(options);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import React from 'react';
|
||||
import {render} from 'react-dom';
|
||||
import {Router, Route, browserHistory} from 'react-router';
|
||||
import App from './components/App';
|
||||
|
||||
render(
|
||||
(
|
||||
<Router history={browserHistory}>
|
||||
<Route path="/" component={App}>
|
||||
|
||||
</Route>
|
||||
</Router>
|
||||
),
|
||||
document.querySelector('.app-container')
|
||||
);
|
||||
|
||||
// <Route path="login" component={Login}/>
|
||||
// <Route path="logout" component={Logout}/>
|
||||
// <Route path="about" component={About}/>
|
||||
// <Route path="dashboard" component={Dashboard} onEnter={requireAuth}/>
|
||||
|
||||
// render(
|
||||
// <App/>,
|
||||
// document.querySelector('.app-container')
|
||||
// );
|
||||
@@ -1,18 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
export default class App extends React.Component{
|
||||
handleClick(event){
|
||||
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<div>
|
||||
<h1>Панель управления инстаграмом художки</h1>
|
||||
<p>Чтобы управлять, вы должны быть <a href="https://instagram.com/hudozka">hudozka</a></p>
|
||||
|
||||
<a href="auth" onClick={this.handleClick.bind(this)}>Войти</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
import path from 'path';
|
||||
import {readFileSync} from 'fs';
|
||||
|
||||
const env = process.env['NODE_ENV'] || 'production';
|
||||
const isProduction = env === 'production';
|
||||
|
||||
let privatePath = path.join(
|
||||
__dirname,
|
||||
process.env['PRIVATE'] || '../.private'
|
||||
);
|
||||
let privateData = JSON.parse(
|
||||
readFileSync(privatePath, 'utf-8')
|
||||
);
|
||||
|
||||
export const staticPath = path.join(
|
||||
__dirname,
|
||||
'../build'
|
||||
);
|
||||
|
||||
export const port = isProduction ? 18000 : 3000;
|
||||
export const pathPrefix = isProduction ? '/service/instagram' : '';
|
||||
export const baseHref = isProduction ? '/service/instagram/' : '/';
|
||||
// export const pathPrefix = '';
|
||||
export const indexPath = path.join(
|
||||
__dirname,
|
||||
// './templates/index.html'
|
||||
'../build/main.html'
|
||||
);
|
||||
// export const indexPath = '../build/main.html';
|
||||
|
||||
export const clientId = privateData['clientId'];
|
||||
export const clientSecret = privateData['clientSecret'];
|
||||
export const redirectUri = privateData['redirectUri'];
|
||||
|
||||
export const serviceHost = isProduction ? 'http://localhost:1800' : 'http://localhost:8080';
|
||||
export const serviceName = 'instagram';
|
||||
export const serviceSecret = privateData['serviceSecret'];
|
||||
|
||||
export const jwtSecret = privateData['jwtSecret'];
|
||||
|
||||
export const sessionSecret = privateData['sessionSecret'];
|
||||
export const sessionExpirationTime = 60 * 60 * 24 * 2; // two days in seconds
|
||||
|
||||
export const authScope = [
|
||||
'basic',
|
||||
// 'public_content',
|
||||
// 'follower_list',
|
||||
'comments',
|
||||
// 'relationships',
|
||||
'likes'
|
||||
];
|
||||
|
||||
export const postType = 'instagram-2';
|
||||
export const fetchItemsPerUpdateLoop = 1;
|
||||
export const updateSubscriptionDelay = 1000 * 60 * 60; // one hour in milliseconds
|
||||
export const updateSubscriptionCallbackUrl = isProduction ? `https://art.shburg.org${pathPrefix}/callback/hudozka` : 'http://dev.tmshv.ru/callback/hudozka';
|
||||
|
||||
export const database = {
|
||||
host: 'localhost',
|
||||
port: 27017,
|
||||
db: 'hudozka',
|
||||
username: privateData['databaseUser'],
|
||||
password: privateData['databasePassword']
|
||||
};
|
||||
|
||||
const databaseAuth = database.username && database.password ? `${database.username}:${database.password}@` : '';
|
||||
export const databaseUri = `mongodb://${databaseAuth}${database.host}:${database.port}/${database.db}`;
|
||||
@@ -1,48 +0,0 @@
|
||||
import {collection} from './db';
|
||||
import {jwtSecret} from '../config';
|
||||
import {sign} from 'jsonwebtoken';
|
||||
|
||||
/**
|
||||
* {
|
||||
* username: String,
|
||||
* bio: String,
|
||||
* website: String,
|
||||
* profile_picture: String,
|
||||
* full_name: String,
|
||||
* id: String
|
||||
* }
|
||||
*
|
||||
* @param accessToken
|
||||
* @param user
|
||||
*/
|
||||
export async function authorizeUser(accessToken, user) {
|
||||
const provider = 'instagram';
|
||||
const users = collection('users');
|
||||
|
||||
let profile = await users.findOne({
|
||||
provider: provider,
|
||||
username: user.username
|
||||
});
|
||||
|
||||
if(profile){
|
||||
Object.keys(user).forEach(i => {
|
||||
profile[i] = user[i];
|
||||
});
|
||||
profile.accessToken = accessToken;
|
||||
profile.jwtToken = await createToken(profile);
|
||||
|
||||
await users.update({_id: profile['_id']}, profile);
|
||||
return profile;
|
||||
}else{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function createToken(user){
|
||||
return new Promise((resolve, reject) => {
|
||||
sign(user, jwtSecret, {}, token => {
|
||||
if (!token) return reject();
|
||||
resolve(token);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import {MongoClient} from 'mongodb';
|
||||
import co from 'co';
|
||||
|
||||
export var db;
|
||||
|
||||
export function connect(uri){
|
||||
return co(function*() {
|
||||
db = yield MongoClient.connect(uri);
|
||||
return db;
|
||||
})
|
||||
}
|
||||
|
||||
export function collection(name){
|
||||
return db.collection(name);
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
import {collection} from './db';
|
||||
import {api} from '../instagram';
|
||||
import {postFromInstagramMedia} from '../models/post';
|
||||
import Service from '../api/Service';
|
||||
import {serviceHost, serviceName, serviceSecret, fetchItemsPerUpdateLoop as num} from '../config';
|
||||
|
||||
const service = new Service(serviceHost, serviceName, serviceSecret);
|
||||
|
||||
var instagramClient;
|
||||
|
||||
export default async function update(id, data) {
|
||||
const updateTimestamp = data.time;
|
||||
let items = await fetch(updateTimestamp);
|
||||
return await Promise.all(
|
||||
items.map(post => push(post))
|
||||
);
|
||||
}
|
||||
|
||||
export async function fetch(updateTimestamp) {
|
||||
const instagram = await client();
|
||||
try {
|
||||
let data = await instagram.user.mediaRecent(null, 1, updateTimestamp);
|
||||
return data.medias.map(postFromInstagramMedia);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export async function push(data) {
|
||||
try {
|
||||
return await service.feed.add(data);
|
||||
} catch (e) {
|
||||
console.log('PUSHING ERROR');
|
||||
console.log(e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function clearSubscriptions(callbackUrl) {
|
||||
const instagram = await client();
|
||||
return await instagram.subscriptions.removeAll();
|
||||
}
|
||||
|
||||
export async function updateSubscription(callbackUrl) {
|
||||
const instagram = await client();
|
||||
|
||||
let data = await instagram.subscriptions.list();
|
||||
let activeSubscriptions = data.result;
|
||||
|
||||
console.log(`Active subscriptions ${activeSubscriptions}`);
|
||||
|
||||
let objectSubscriptions = activeSubscriptions.filter(i => i.object === 'user' && !i.object_id && i.callback_url === callbackUrl)
|
||||
|
||||
if (!objectSubscriptions.length) {
|
||||
// {
|
||||
// result: {
|
||||
// object: 'tag',
|
||||
// object_id: 'hello_lol',
|
||||
// aspect: 'media',
|
||||
// callback_url: 'http://dev.tmshv.ru/callback/hudozka',
|
||||
// type: 'subscription',
|
||||
// id: '22502218'
|
||||
// },
|
||||
// remaining: 4988,
|
||||
// limit: 5000
|
||||
// }
|
||||
|
||||
// {
|
||||
// result: {
|
||||
// object: 'user',
|
||||
// object_id: null,
|
||||
// aspect: 'media',
|
||||
// callback_url: 'http://dev.tmshv.ru/callback/hudozka',
|
||||
// type: 'subscription',
|
||||
// id: '22501989'
|
||||
// },
|
||||
// remaining: 4995,
|
||||
// limit: 5000
|
||||
// }
|
||||
await instagram.subscriptions.subscribeUser(callbackUrl);
|
||||
}
|
||||
}
|
||||
|
||||
async function client() {
|
||||
if (instagramClient) return instagramClient;
|
||||
|
||||
const provider = 'instagram';
|
||||
const users = collection('users');
|
||||
|
||||
let profile = await users.findOne({
|
||||
provider: provider
|
||||
});
|
||||
|
||||
if (profile) {
|
||||
instagramClient = api(profile.accessToken);
|
||||
return instagramClient;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import 'babel-polyfill';
|
||||
|
||||
import {databaseUri, port, updateSubscriptionDelay, updateSubscriptionCallbackUrl} from './config'
|
||||
import {connect} from './core/db';
|
||||
import server from './server';
|
||||
// import update from './core/update';
|
||||
import {updateSubscription, clearSubscriptions} from './core/update';
|
||||
|
||||
let repeat = time => fn => setInterval(fn, time);
|
||||
|
||||
function main(){
|
||||
async function loop(){
|
||||
try{
|
||||
await connect(databaseUri);
|
||||
}catch(error){
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
try{
|
||||
let app = server();
|
||||
app.listen(port);
|
||||
console.log(`Start listening ${port}`);
|
||||
}catch(error){
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
await clearSubscriptions();
|
||||
await updateSubscription(updateSubscriptionCallbackUrl);
|
||||
repeat(updateSubscriptionDelay)(() => {
|
||||
updateSubscription(updateSubscriptionCallbackUrl);
|
||||
});
|
||||
}
|
||||
|
||||
loop();
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
// .then(() => {
|
||||
// return update('hudozka', {
|
||||
// "changed_aspect": "media",
|
||||
// "object": "tag",
|
||||
// "object_id": "love",
|
||||
// // "time": 1458675620,
|
||||
// "time": 1458594000,
|
||||
// "subscription_id": 22487204,
|
||||
// "data": {}
|
||||
// })
|
||||
// .catch(e => {
|
||||
// console.log(e);
|
||||
// })
|
||||
// .then(result => {
|
||||
// console.log('update done');
|
||||
// console.log(result);
|
||||
// });
|
||||
// })
|
||||
@@ -1,25 +0,0 @@
|
||||
import request from 'request-promise';
|
||||
|
||||
export default class EndPoint{
|
||||
constructor(client, token) {
|
||||
this.client = client;
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
method(name){
|
||||
return this.client[name];
|
||||
}
|
||||
|
||||
request(method, path, data=null) {
|
||||
let buildPath = path => `https://api.instagram.com/v1${path}`;
|
||||
let options = {
|
||||
method: method,
|
||||
uri: buildPath(path),
|
||||
headers: {}
|
||||
};
|
||||
|
||||
if(data) options.body = data;
|
||||
return request(options)
|
||||
.then(i => JSON.parse(i));
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import Subscription from './Subscription';
|
||||
import User from './User';
|
||||
import Media from './Media';
|
||||
|
||||
export default class Instagram {
|
||||
constructor(clientId, clientSecret, accessToken, createApi) {
|
||||
this.clientId = clientId;
|
||||
this.clientSecret = clientSecret;
|
||||
this.accessToken = accessToken;
|
||||
|
||||
const authenticatedApi = createApi(clientId, clientSecret, accessToken);
|
||||
const api = createApi(clientId, clientSecret);
|
||||
|
||||
//noinspection JSUnresolvedVariable
|
||||
this.user = new User(authenticatedApi);
|
||||
this.subscriptions = new Subscription(api);
|
||||
this.media = new Media(api, accessToken);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import EndPoint from './EndPoint';
|
||||
|
||||
export default class Media extends EndPoint {
|
||||
shortname(name){
|
||||
return this.request('GET', `/media/shortcode/${name}?access_token=${this.token}`);
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
import EndPoint from './EndPoint';
|
||||
|
||||
export default class Subscription extends EndPoint {
|
||||
list() {
|
||||
const m = this.method('subscriptions');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
m((error, result, remaining, limit) => {
|
||||
if(error) return reject(error);
|
||||
resolve({
|
||||
result: result,
|
||||
remaining: remaining,
|
||||
limit: limit
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
subscribeTag(tag, callbackUrl) {
|
||||
const m = this.method('add_tag_subscription');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
m(tag, callbackUrl, (error, result, remaining, limit) => {
|
||||
if(error) return reject(error);
|
||||
resolve({
|
||||
result: result,
|
||||
remaining: remaining,
|
||||
limit: limit
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
subscribeUser(callbackUrl) {
|
||||
const m = this.method('add_user_subscription');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
m(callbackUrl, (error, result, remaining, limit) => {
|
||||
if(error) return reject(error);
|
||||
resolve({
|
||||
result: result,
|
||||
remaining: remaining,
|
||||
limit: limit
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
removeAll(){
|
||||
const m = this.method('del_subscription');
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
m({all: true}, (error, result, remaining, limit) => {
|
||||
if (error) return reject(error);
|
||||
resolve({
|
||||
result: result,
|
||||
remaining: remaining,
|
||||
limit: limit
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
remove(id){
|
||||
// ig.del_subscription({ id: 1 }, function(err, subscriptions, remaining, limit){});
|
||||
}
|
||||
|
||||
// unsubscribe: function *(id) {
|
||||
// var ig = createClient();
|
||||
// var sub = thunkify(ig["del_subscription"]);
|
||||
// try {
|
||||
// var s = yield sub({id: id});
|
||||
// return s[0];
|
||||
// } catch (error) {
|
||||
// return null;
|
||||
// }
|
||||
// },
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import EndPoint from './EndPoint';
|
||||
|
||||
export default class User extends EndPoint {
|
||||
mediaRecent(id = null, count = null, minTime = null, maxTime = null) {
|
||||
function callback(resolve, reject) {
|
||||
return function (error, medias, pagination, remaining, limit) {
|
||||
if (error) return reject(error);
|
||||
resolve({
|
||||
medias: medias,
|
||||
pagination: pagination,
|
||||
remaining: remaining,
|
||||
limit: limit
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const options = {};
|
||||
if (count) options.count = count;
|
||||
if (minTime) options.min_timestamp = minTime;
|
||||
if (maxTime) options.max_timestamp = maxTime;
|
||||
|
||||
if (id) {
|
||||
let m = this.method('user_media_recent');
|
||||
return new Promise((resolve, reject) => {
|
||||
m(id, options, callback(resolve, reject));
|
||||
});
|
||||
} else {
|
||||
let m = this.method('user_self_media_recent');
|
||||
return new Promise((resolve, reject) => {
|
||||
m(options, callback(resolve, reject));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
import {instagram} from 'instagram-node';
|
||||
|
||||
import {clientId, clientSecret} from '../config';
|
||||
import Instagram from './Instagram';
|
||||
|
||||
export function api(accessToken){
|
||||
return new Instagram(clientId, clientSecret, accessToken, createClient);
|
||||
}
|
||||
|
||||
export function createAuthorizationUrl(redirectUri, scope, state) {
|
||||
let params = {
|
||||
scope: scope || ['basic']
|
||||
};
|
||||
if (state) params.state = state;
|
||||
|
||||
let client = createClient(clientId, clientSecret);
|
||||
return client['get_authorization_url'](redirectUri, params);
|
||||
}
|
||||
|
||||
export function authorize(code, redirectUri) {
|
||||
let api = createClient(clientId, clientSecret);
|
||||
let authorize = api['authorize_user'];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
authorize(code, redirectUri, (error, user) => {
|
||||
if(error){
|
||||
reject(error);
|
||||
}else{
|
||||
resolve(user);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createClient(clientId, clientSecret, accessToken = null) {
|
||||
const client = instagram();
|
||||
client.use({
|
||||
client_id: clientId,
|
||||
client_secret: clientSecret
|
||||
});
|
||||
if (accessToken) client.use({access_token: accessToken});
|
||||
return client;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
import {api} from '../instagram';
|
||||
|
||||
export default function () {
|
||||
return async(ctx, next) => {
|
||||
var code = ctx.session.accessToken;
|
||||
if (code) {
|
||||
ctx.instagram = api(code);
|
||||
}
|
||||
|
||||
await next();
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
import {postType} from '../config';
|
||||
|
||||
let date = i => new Date(parseInt(i) * 1000);
|
||||
|
||||
/**
|
||||
* @param i
|
||||
* {
|
||||
* "attribution": null,
|
||||
* "tags": ["люстра", "кукольныйдомик", "shlb_hudozka", "шлиссельбург", "стекло", "выставочноепространство", "художка"],
|
||||
* "type": "image",
|
||||
* "location": null,
|
||||
* "comments": {"count": 0, "data": []},
|
||||
* "filter": "Normal",
|
||||
* "created_time": "1458652687",
|
||||
* "link": "https://www.instagram.com/p/BDQbcm4Jiid/",
|
||||
* "likes": {
|
||||
* "count": 138,
|
||||
* "data": [{
|
||||
* "username": "marinada29",
|
||||
* "profile_picture": "https://scontent.cdninstagram.com/t51.2885-19/10958645_950152745008546_618790272_a.jpg",
|
||||
* "id": "326555798",
|
||||
* "full_name": "Marina Davidovich 89024808351"
|
||||
* }]
|
||||
* },
|
||||
* "images": {
|
||||
* "low_resolution": {
|
||||
* "url": "https://scontent.cdninstagram.com/t51.2885-15/s320x320/e35/12479404_949349891830020_632926748_n.jpg?ig_cache_key=MTIxMTU4OTAxMjkwODAyNjAxMw%3D%3D.2.l",
|
||||
* "width": 320,
|
||||
* "height": 320
|
||||
* },
|
||||
* "thumbnail": {
|
||||
* "url": "https://scontent.cdninstagram.com/t51.2885-15/s150x150/e35/c0.134.1080.1080/12816835_959212777507222_1085128603_n.jpg?ig_cache_key=MTIxMTU4OTAxMjkwODAyNjAxMw%3D%3D.2.c",
|
||||
* "width": 150,
|
||||
* "height": 150
|
||||
* },
|
||||
* "standard_resolution": {
|
||||
* "url": "https://scontent.cdninstagram.com/t51.2885-15/s640x640/sh0.08/e35/12479404_949349891830020_632926748_n.jpg?ig_cache_key=MTIxMTU4OTAxMjkwODAyNjAxMw%3D%3D.2.l",
|
||||
* "width": 640,
|
||||
* "height": 640
|
||||
* }
|
||||
* },
|
||||
* "users_in_photo": [],
|
||||
* "caption": {
|
||||
* "created_time": "1458652687",
|
||||
* "text": "Люстра. В выставочном пространстве \"Кукольный домик\" оформляется одна из ниш. #shlb_hudozka #кукольныйдомик #выставочноепространство #стекло #люстра #художка #шлиссельбург",
|
||||
* "from": {
|
||||
* "username": "hudozka",
|
||||
* "profile_picture": "https://scontent.cdninstagram.com/t51.2885-19/11357496_464997000344800_2124591831_a.jpg",
|
||||
* "id": "1668537441",
|
||||
* "full_name": "Шлиссельбургская ДХШ"
|
||||
* },
|
||||
* "id": "1211589015567214752"
|
||||
* },
|
||||
* "user_has_liked": false,
|
||||
* "id": "1211589012908026013_1668537441",
|
||||
* "user": {
|
||||
* "username": "hudozka",
|
||||
* "profile_picture": "https://scontent.cdninstagram.com/t51.2885-19/11357496_464997000344800_2124591831_a.jpg",
|
||||
* "id": "1668537441",
|
||||
* "full_name": "Шлиссельбургская ДХШ"
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
export const postFromInstagramMedia = i => ({
|
||||
data: i,
|
||||
type: postType,
|
||||
date: date(i['created_time']),
|
||||
id: i.id
|
||||
});
|
||||
@@ -1,64 +0,0 @@
|
||||
import {get, url} from './';
|
||||
import {authorize, createAuthorizationUrl} from '../instagram';
|
||||
import {redirectUri, authScope} from '../config';
|
||||
import {authorizeUser} from '../core/auth';
|
||||
|
||||
export function auth() {
|
||||
return get('/auth', async(ctx) => {
|
||||
const state = url('/');
|
||||
const i = createAuthorizationUrl(redirectUri, authScope, state);
|
||||
ctx.redirect(i, state);
|
||||
});
|
||||
}
|
||||
|
||||
export function authCallback() {
|
||||
const root = url('/');
|
||||
|
||||
return get('/auth/callback', async(ctx) => {
|
||||
let state = ctx.query.state;
|
||||
let code = ctx.query.code;
|
||||
|
||||
if(!ctx.session.accessToken) {
|
||||
try{
|
||||
var client = await authorize(code, redirectUri);
|
||||
}catch (e){
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
if (client){
|
||||
var user = await authorizeUser(client.access_token, client.user);
|
||||
var accessToken = client.access_token;
|
||||
}
|
||||
|
||||
if(user) ctx.cookies.set('jwt', user.jwtToken);
|
||||
ctx.status = user ? 200 : 401;
|
||||
ctx.session.user = user;
|
||||
ctx.session.accessToken = accessToken;
|
||||
}
|
||||
|
||||
ctx.redirect(state, root);
|
||||
});
|
||||
}
|
||||
|
||||
export function authOut(redirectUrl = '/') {
|
||||
const redirectTo = url(redirectUrl);
|
||||
const root = url('/');
|
||||
|
||||
return get('/auth/out', async(ctx) => {
|
||||
ctx.session = null;
|
||||
ctx.redirect(redirectTo, root);
|
||||
});
|
||||
}
|
||||
|
||||
export function ensureAuth(failRedirect = '/') {
|
||||
const redirectTo = url(failRedirect);
|
||||
const root = url('/');
|
||||
|
||||
return async (ctx, next) => {
|
||||
if(!ctx.session.user) {
|
||||
ctx.redirect(redirectTo, root)
|
||||
}else{
|
||||
await next();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
import {get, post} from './';
|
||||
import update from '../core/update';
|
||||
|
||||
export function callbackGet() {
|
||||
return get('/callback/:id', async (ctx, id) => {
|
||||
// console.log('challenge', id);
|
||||
|
||||
if(ctx.query['hub.mode'] == 'subscribe'){
|
||||
ctx.body = ctx.query['hub.challenge'];
|
||||
}else{
|
||||
ctx.status = 400;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function callbackPost() {
|
||||
return post('/callback/:id', async (ctx, id) => {
|
||||
let body = ctx.request.body;
|
||||
// console.log(id, body);
|
||||
update(id, body);
|
||||
|
||||
ctx.status = 200;
|
||||
ctx.body = '';
|
||||
|
||||
//body example
|
||||
// {
|
||||
// "changed_aspect": "media",
|
||||
// "object": "tag",
|
||||
// "object_id": "love",
|
||||
// "time": 1458675620,
|
||||
// "subscription_id": 22487204,
|
||||
// "data": {}
|
||||
// }
|
||||
|
||||
// let sign = ctx.header['x-hub-signature'];
|
||||
//var hmac = crypto.createHmac("sha1", instagram.client_secret);
|
||||
//hmac.update(body);
|
||||
//var shasum = hmac.digest("hex");
|
||||
|
||||
//console.log("X-Hub-Signature", sign);
|
||||
//console.log("Hmac", shasum);
|
||||
//console.log();
|
||||
|
||||
//if(shasum.digest("hex") == sign) {
|
||||
// instagram.update(body);
|
||||
//}
|
||||
});
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
import fs from 'mz/fs';
|
||||
import compose from 'koa-compose';
|
||||
import route from 'koa-route';
|
||||
import {indexPath, pathPrefix} from '../config';
|
||||
|
||||
import main from './main';
|
||||
import {auth, authCallback, authOut} from './auth';
|
||||
import {list, addTag, addUser} from './subscription';
|
||||
import {ensureAuth} from './auth';
|
||||
import {callbackGet, callbackPost} from './callback';
|
||||
import {shortname} from './media';
|
||||
|
||||
export function statelessRouters() {
|
||||
return compose([
|
||||
callbackGet(),
|
||||
callbackPost()
|
||||
]);
|
||||
}
|
||||
|
||||
export function routers() {
|
||||
return compose([
|
||||
// main(),
|
||||
auth(),
|
||||
authCallback(),
|
||||
authOut(),
|
||||
user(),
|
||||
list(),
|
||||
addTag(),
|
||||
addUser(),
|
||||
shortname()
|
||||
]);
|
||||
}
|
||||
|
||||
export const url = (path, root=pathPrefix) => `${root}${path}`;
|
||||
// export const url = path => path;
|
||||
export const get = (path, fn) => route['get'](url(path, ''), fn);
|
||||
export const post = (path, fn) => route['post'](url(path, ''), fn);
|
||||
|
||||
export function index(fn){
|
||||
return async (ctx) => {
|
||||
try{
|
||||
ctx.type = 'text/html';
|
||||
ctx.body = await fs.createReadStream(indexPath);
|
||||
}catch(e){
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function user(){
|
||||
return compose([
|
||||
ensureAuth(),
|
||||
get('/user', async (ctx) => {
|
||||
const user = ctx.session.user.id;
|
||||
const instagram = ctx.instagram;
|
||||
|
||||
let data = await instagram.user.mediaRecent(user);
|
||||
|
||||
console.log(data);
|
||||
ctx.body = data;
|
||||
})
|
||||
]);
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import {index, get} from './';
|
||||
|
||||
export default function () {
|
||||
return get('/', async (ctx) => {
|
||||
await index()(ctx);
|
||||
});
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import compose from 'koa-compose';
|
||||
import {get} from './';
|
||||
import {ensureAuth} from './auth';
|
||||
import {push} from '../core/update';
|
||||
import {postFromInstagramMedia} from '../models/post';
|
||||
|
||||
export function shortname() {
|
||||
return compose([
|
||||
ensureAuth(),
|
||||
get('/media/shortname/:name', async(ctx, name) => {
|
||||
const instagram = ctx.instagram;
|
||||
let result = await instagram.media.shortname(name);
|
||||
|
||||
if(result.meta.code === 200) {
|
||||
let post = postFromInstagramMedia(result.data);
|
||||
console.log('SHORTNAME');
|
||||
|
||||
ctx.set('Content-Type', 'application/json');
|
||||
ctx.body = await push(post);
|
||||
}
|
||||
})
|
||||
]);
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import compose from 'koa-compose';
|
||||
import {get} from './';
|
||||
import {ensureAuth} from './auth';
|
||||
|
||||
export function list() {
|
||||
return compose([
|
||||
ensureAuth(),
|
||||
get('/subscriptions', async(ctx) => {
|
||||
const instagram = ctx.instagram;
|
||||
|
||||
ctx.set('Content-Type', 'application/json');
|
||||
ctx.body = await instagram.subscriptions.list();
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
export function addTag() {
|
||||
return compose([
|
||||
ensureAuth(),
|
||||
get('/subscriptions/add-tag/:tag', async(ctx, tag) => {
|
||||
let callbackUrl = ctx.query.url;
|
||||
let result = await ctx.instagram.subscriptions.subscribeTag(tag, callbackUrl);
|
||||
if (result) {
|
||||
ctx.body = result;
|
||||
} else {
|
||||
ctx.status = 500;
|
||||
}
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
export function addUser() {
|
||||
return compose([
|
||||
ensureAuth(),
|
||||
get('/subscriptions/add-user', async (ctx) => {
|
||||
let callbackUrl = ctx.query.url;
|
||||
let result = await ctx.instagram.subscriptions.subscribeUser(callbackUrl);
|
||||
if (result) {
|
||||
ctx.body = result;
|
||||
} else {
|
||||
ctx.status = 500;
|
||||
}
|
||||
})
|
||||
])
|
||||
}
|
||||
|
||||
// export function remove() {
|
||||
// return get('/subscriptions/remove/:id', async (ctx, id) => {
|
||||
// let result = yield instagram.api.unsubscribe(id);
|
||||
// if(result) {
|
||||
// ctx.body = result;
|
||||
// }else{
|
||||
// ctx.status = 500;
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
@@ -1,53 +0,0 @@
|
||||
import Koa from 'koa';
|
||||
import mount from 'koa-mount';
|
||||
import serve from 'koa-static';
|
||||
import logger from 'koa-logger';
|
||||
import convert from 'koa-convert';
|
||||
import sessionStore from 'koa-session-store';
|
||||
import store from 'koa-session-mongo';
|
||||
import bodyParser from 'koa-bodyparser';
|
||||
|
||||
import {db} from './core/db';
|
||||
import instagram from './middlewares/instagram';
|
||||
import {routers, statelessRouters} from './routers';
|
||||
import {pathPrefix, staticPath, sessionSecret, sessionExpirationTime} from './config';
|
||||
|
||||
export default function(){
|
||||
const app = new Koa();
|
||||
app.proxy = true;
|
||||
app.keys = [sessionSecret];
|
||||
|
||||
app.use(logger());
|
||||
app.use(serve(staticPath));
|
||||
app.use(bodyParser());
|
||||
app.use(statelessRouters());
|
||||
app.use(session('sid', sessionExpirationTime));
|
||||
app.use(instagram());
|
||||
app.use(routers());
|
||||
app.use(error());
|
||||
|
||||
return app;
|
||||
|
||||
// const root = new Koa();
|
||||
// root.use(mount(pathPrefix, app));
|
||||
|
||||
// return root;
|
||||
}
|
||||
|
||||
function session(name, exp){
|
||||
return convert(sessionStore({
|
||||
name: name,
|
||||
expirationTime: exp,
|
||||
store: store.create({
|
||||
db: db,
|
||||
collection: 'sessions'
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
function error(){
|
||||
return ctx => {
|
||||
ctx.status = 404;
|
||||
ctx.body = `Document ${ctx.path} not found`;
|
||||
};
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Instagram Control</title>
|
||||
<base href="<%= htmlWebpackPlugin.options.baseHref %>">
|
||||
</head>
|
||||
<body>
|
||||
<div class="app-container"></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,54 +0,0 @@
|
||||
import webpack from'webpack';
|
||||
import HTML from 'html-webpack-plugin';
|
||||
|
||||
const env = process.env['NODE_ENV'] || 'production';
|
||||
const isProduction = env === 'production';
|
||||
|
||||
// import autoprefixer from 'autoprefixer';
|
||||
|
||||
import {baseHref} from './src/config';
|
||||
|
||||
module.exports = {
|
||||
entry: './src/app.js',
|
||||
watch: !isProduction,
|
||||
//resolve: {
|
||||
// modulesDirectories: [
|
||||
// './node_modules'
|
||||
// ]
|
||||
//},
|
||||
output: {
|
||||
path: './build',
|
||||
filename: 'app.js'
|
||||
},
|
||||
// devServer: {
|
||||
// historyApiFallback: true,
|
||||
// hot: true,
|
||||
// inline: true,
|
||||
// progress: true,
|
||||
// host: 'localhost',
|
||||
// port: 3000
|
||||
// },
|
||||
plugins: [
|
||||
// new webpack.HotModuleReplacementPlugin(),
|
||||
new HTML({
|
||||
filename: 'index.html',
|
||||
template: 'src/templates/index.ejs',
|
||||
inject: 'body',
|
||||
baseHref: baseHref
|
||||
})
|
||||
],
|
||||
module: {
|
||||
loaders: [
|
||||
{
|
||||
test: /\.js$/,
|
||||
// exclude: /node_modules/,
|
||||
//exclude: /(node_modules|bower_components)/,
|
||||
loader: 'babel',
|
||||
query: {
|
||||
// presets: ['react'],
|
||||
cacheDirectory: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
import Article from '../core/Article'
|
||||
import ImageArtifactType from '../core/ImageArtifactType'
|
||||
import { sortBy } from '../utils/sort'
|
||||
import { timestamp } from '../lib/date'
|
||||
import { encodeImage } from './image'
|
||||
|
||||
export const sortArticleByDate = sortBy(
|
||||
x => timestamp(new Date(x.date))
|
||||
)
|
||||
|
||||
export async function findArticlesNin(nin, skip, limit, sort) {
|
||||
const query = {
|
||||
_id: { $nin: nin }
|
||||
}
|
||||
|
||||
return Article.find(query, {
|
||||
sort,
|
||||
skip,
|
||||
limit,
|
||||
})
|
||||
}
|
||||
|
||||
export async function findArticles(page, pageSize) {
|
||||
const limit = pageSize
|
||||
const skip = (page - 1) * pageSize
|
||||
|
||||
const pinnedArticles = page === 1
|
||||
? await Article.findPinned()
|
||||
: []
|
||||
|
||||
const total = await Article.total()
|
||||
const totalPages = Math.ceil(total / pageSize)
|
||||
|
||||
const id = i => i._id
|
||||
const pinnedIds = pinnedArticles.map(id)
|
||||
const articles = await findArticlesNin(pinnedIds, skip, limit, { date: -1 })
|
||||
|
||||
const prevPage = page > 1
|
||||
? page - 1
|
||||
: null
|
||||
|
||||
const nextPage = page < totalPages
|
||||
? page + 1
|
||||
: null
|
||||
|
||||
const items = [
|
||||
...pinnedArticles.sort(sortArticleByDate),
|
||||
...articles
|
||||
]
|
||||
.map(x => x.plain())
|
||||
.map(x => ({
|
||||
...x,
|
||||
preview: !x.preview ? null : (
|
||||
x.preview.getPicture(ImageArtifactType.MEDIUM)
|
||||
)
|
||||
}))
|
||||
|
||||
return {
|
||||
items,
|
||||
prevPage,
|
||||
nextPage,
|
||||
totalPages,
|
||||
}
|
||||
}
|
||||
|
||||
export function encodeArticle(article) {
|
||||
return {
|
||||
...article,
|
||||
preview: encodeImage(article.preview),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import ImageArtifactType from '../core/ImageArtifactType'
|
||||
|
||||
const sizes = [
|
||||
ImageArtifactType.MEDIUM,
|
||||
ImageArtifactType.BIG,
|
||||
ImageArtifactType.ORIGIN,
|
||||
]
|
||||
|
||||
export function encodeFilePreview(file, sizes) {
|
||||
const preview = file.preview
|
||||
if (!preview) return null
|
||||
|
||||
const image = preview.findArtifact(sizes)
|
||||
if (!image) return null
|
||||
|
||||
return image.url
|
||||
}
|
||||
|
||||
export function encodeFile(file) {
|
||||
const imageUrl = encodeFilePreview(file, sizes)
|
||||
const category = (file.category || '')
|
||||
.replace('Documents/', '')
|
||||
.replace('Pages/', '')
|
||||
.replace('Albums/', '')
|
||||
.replace('Articles /', '')
|
||||
|
||||
return {
|
||||
imageUrl,
|
||||
category,
|
||||
title: file.title,
|
||||
fileName: file.fileInfo.name,
|
||||
fileSize: file.fileInfo.size,
|
||||
fileUrl: file.url,
|
||||
url: file.viewUrl,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import ImageArtifactType from '../core/ImageArtifactType'
|
||||
|
||||
export function encodeImage(image) {
|
||||
const artifacts = [
|
||||
ImageArtifactType.SMALL,
|
||||
ImageArtifactType.MEDIUM,
|
||||
ImageArtifactType.BIG,
|
||||
ImageArtifactType.LARGE,
|
||||
ImageArtifactType.ORIGIN,
|
||||
ImageArtifactType.ORIGINAL,
|
||||
ImageArtifactType.FACEBOOK = 'fb'
|
||||
].reduce((acc, size) => {
|
||||
return {
|
||||
...acc,
|
||||
[size]: image.getPicture(size),
|
||||
}
|
||||
}, {})
|
||||
|
||||
return {
|
||||
artifacts,
|
||||
hash: image.hash,
|
||||
file: image.file,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import { encodeImage } from './image'
|
||||
|
||||
export function encodePage(page) {
|
||||
return {
|
||||
id: page.id,
|
||||
title: page.title,
|
||||
url: page.url,
|
||||
data: page.data,
|
||||
preview: encodeImage(page.preview),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import ImageArtifactType from '../core/ImageArtifactType'
|
||||
import { encodeImage } from './image'
|
||||
const profilePicture = profile => profile.picture.getPicture(ImageArtifactType.BIG)
|
||||
|
||||
export function encodePerson(person) {
|
||||
return {
|
||||
...person,
|
||||
shortName: person.shortName(),
|
||||
name: person.splitName(),
|
||||
picture: profilePicture(person),
|
||||
preview: encodeImage(person.picture),
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,11 @@
|
||||
const React = require('react')
|
||||
const Menu = require('./Menu')
|
||||
const Footer = require('./Footer')
|
||||
const Comments = require('./Comments')
|
||||
import React from 'react'
|
||||
import Menu from './Menu'
|
||||
import Footer from './Footer'
|
||||
import Comments from './Comments'
|
||||
|
||||
const App = ({children, menu, showAuthor, menuPadding}) => (
|
||||
import '../style/style.scss'
|
||||
|
||||
export const App = ({children, menu, showAuthor, menuPadding}) => (
|
||||
<div className="body-wrapper theme-default">
|
||||
<header>
|
||||
<div className="navigation">
|
||||
@@ -28,5 +30,3 @@ const App = ({children, menu, showAuthor, menuPadding}) => (
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
module.exports = App
|
||||
+44
-44
@@ -1,60 +1,60 @@
|
||||
const React = require('react')
|
||||
const dateFormat = require('../lib/date').dateFormat
|
||||
import React from 'react'
|
||||
import { dateFormat } from '../lib/date'
|
||||
|
||||
const Tag = ({children}) => (
|
||||
<li className="ArticleTags-Item">
|
||||
{children}
|
||||
</li>
|
||||
const Tag = ({ children }) => (
|
||||
<li className="ArticleTags-Item">
|
||||
{children}
|
||||
</li>
|
||||
)
|
||||
|
||||
const TagList = ({tags}) => (
|
||||
<ul className="ArticleTags">
|
||||
{tags.map((x, i) => (
|
||||
<Tag key={i}># {x.name}</Tag>
|
||||
))}
|
||||
</ul>
|
||||
const TagList = ({ tags }) => (
|
||||
<ul className="ArticleTags">
|
||||
{tags.map((x, i) => (
|
||||
<Tag key={i}># {x.name}</Tag>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
|
||||
const Head = ({date, tags = [], children}) => (
|
||||
<header className="Article-Head">
|
||||
<h1>{children}</h1>
|
||||
const Head = ({ date, tags = [], children }) => (
|
||||
<header className="Article-Head">
|
||||
<h1>{children}</h1>
|
||||
|
||||
{!date ? null : (
|
||||
<p className="date">{dateFormat(date)}</p>
|
||||
)}
|
||||
{!date ? null : (
|
||||
<p className="date">{dateFormat(date)}</p>
|
||||
)}
|
||||
|
||||
{!(tags.length) ? null : (
|
||||
<TagList tags={tags}/>
|
||||
)}
|
||||
</header>
|
||||
{!(tags.length) ? null : (
|
||||
<TagList tags={tags} />
|
||||
)}
|
||||
</header>
|
||||
)
|
||||
|
||||
const Share = () => (
|
||||
<div className="likely">
|
||||
<div className="vkontakte">Поделиться</div>
|
||||
<div className="facebook">Поделиться</div>
|
||||
<div className="telegram">Отправить</div>
|
||||
<div className="twitter">Твитнуть</div>
|
||||
</div>
|
||||
<div className="likely">
|
||||
<div className="vkontakte">Поделиться</div>
|
||||
<div className="facebook">Поделиться</div>
|
||||
<div className="telegram">Отправить</div>
|
||||
<div className="twitter">Твитнуть</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
const Article = ({children, date, tags, title, shareable}) => (
|
||||
<article className="Article">
|
||||
<Head
|
||||
date={date}
|
||||
tags={tags}
|
||||
>
|
||||
{title}
|
||||
</Head>
|
||||
const Article = ({ children, date, tags, title, shareable }) => (
|
||||
<article className="Article">
|
||||
<Head
|
||||
date={date}
|
||||
tags={tags}
|
||||
>
|
||||
{title}
|
||||
</Head>
|
||||
|
||||
<div className="Article-Body">
|
||||
{children}
|
||||
</div>
|
||||
<div className="Article-Body">
|
||||
{children}
|
||||
</div>
|
||||
|
||||
{!shareable ? null : (
|
||||
<Share/>
|
||||
)}
|
||||
</article>
|
||||
{!shareable ? null : (
|
||||
<Share />
|
||||
)}
|
||||
</article>
|
||||
)
|
||||
|
||||
module.exports = Article
|
||||
export default Article
|
||||
@@ -1,35 +0,0 @@
|
||||
const React = require('react')
|
||||
const dateFormat = require('../lib/date').dateFormat
|
||||
const Image = require('./Image')
|
||||
|
||||
const Date = ({children}) => (
|
||||
<p className="date">{dateFormat(children)}</p>
|
||||
)
|
||||
|
||||
const Link = ({href, children}) => (
|
||||
<a className="invisible" href={href}>
|
||||
{children}
|
||||
</a>
|
||||
)
|
||||
|
||||
const ArticleCard = ({article}) => (
|
||||
<article className="ArticleCard">
|
||||
<Link href={article.url}>
|
||||
<div className="ArticleCard-image">
|
||||
{!article.preview ? null : (
|
||||
<Image data={article.preview} alt={article.title}/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="ArticleCard-body">
|
||||
{article.title}
|
||||
|
||||
{!article.date ? null : (
|
||||
<Date>{article.date}</Date>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
</article>
|
||||
)
|
||||
|
||||
module.exports = ArticleCard
|
||||
@@ -0,0 +1,122 @@
|
||||
import React from 'react'
|
||||
import { Image } from './Image'
|
||||
import { dateFormat } from '../lib/date'
|
||||
|
||||
const Date = ({ children }) => (
|
||||
<p className="date">{dateFormat(children)}</p>
|
||||
)
|
||||
|
||||
export const ArticleCard = ({ article }) => (
|
||||
<article className="ArticleCard">
|
||||
<style jsx>{`
|
||||
.ArticleCard {
|
||||
--article-card-width: 30%;
|
||||
--article-card-image-height: 15em;
|
||||
--article-card-margin: var(--double-margin) var(--single-margin);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
margin: var(--article-card-margin);
|
||||
width: var(--article-card-width);
|
||||
min-width: 10em;
|
||||
max-width: 30em;
|
||||
|
||||
border-radius: 10px;
|
||||
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ArticleCard > a {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ArticleCard-image {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: var(--article-card-image-height);
|
||||
}
|
||||
|
||||
.ArticleCard-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
|
||||
transition: all 150ms ease-out;
|
||||
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.ArticleCard-body {
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
|
||||
height: 5em;
|
||||
|
||||
padding: .75em 1em;
|
||||
}
|
||||
|
||||
.ArticleCard .date {
|
||||
color: #ccc;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.ArticleCard:hover img {
|
||||
width: 105%;
|
||||
height: 105%;
|
||||
|
||||
top: -2.5%;
|
||||
left: -2.5%;
|
||||
}
|
||||
|
||||
// Tablet
|
||||
|
||||
@media screen and (min-width: var(--tablet-min-width)) and (max-width: var(--tablet-max-width)) {
|
||||
.ArticleCard {
|
||||
--article-card-image-height: 12em;
|
||||
}
|
||||
|
||||
@media screen and (orientation: portrait) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Mobile
|
||||
|
||||
@media (max-width: var(--mobile-width)) {
|
||||
.ArticleCard {
|
||||
--article-card-width: 100%;
|
||||
--article-card-margin: var(--single-margin) 0;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
|
||||
<a className="invisible" href={article.url}>
|
||||
<div className="ArticleCard-image">
|
||||
{!article.preview ? null : (
|
||||
<Image
|
||||
data={article.preview}
|
||||
alt={article.title}
|
||||
opa={false}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="ArticleCard-body">
|
||||
{article.title}
|
||||
|
||||
{!article.date ? null : (
|
||||
<Date>{article.date}</Date>
|
||||
)}
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
)
|
||||
@@ -1,26 +0,0 @@
|
||||
const React = require('react')
|
||||
const Paginator = require('./Paginator')
|
||||
const ArticleCard = require('./ArticleCard')
|
||||
|
||||
const ArticleCardList = ({articles, nextPage, prevPage}) => (
|
||||
<div className="ArticleCardList">
|
||||
{!prevPage ? null : (
|
||||
<Paginator url={`/articles/${prevPage}`} type="top"/>
|
||||
)}
|
||||
|
||||
<div className="ArticleCardList-body">
|
||||
{articles.map((article, i) => (
|
||||
<ArticleCard
|
||||
key={i}
|
||||
article={article}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{!nextPage ? null : (
|
||||
<Paginator url={`/articles/${nextPage}`} type="bottom"/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
||||
module.exports = ArticleCardList
|
||||
@@ -0,0 +1,24 @@
|
||||
import React from 'react'
|
||||
import Paginator from './Paginator'
|
||||
import { ArticleCard } from './ArticleCard'
|
||||
|
||||
export const ArticleCardList = ({ articles, nextPage, prevPage }) => (
|
||||
<div className="ArticleCardList">
|
||||
{!prevPage ? null : (
|
||||
<Paginator url={`/articles/${prevPage}`} type="top" />
|
||||
)}
|
||||
|
||||
<div className="ArticleCardList-body">
|
||||
{articles.map((article, i) => (
|
||||
<ArticleCard
|
||||
key={i}
|
||||
article={article}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{!nextPage ? null : (
|
||||
<Paginator url={`/articles/${nextPage}`} type="bottom" />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from 'react'
|
||||
|
||||
export const CardList = props => (
|
||||
<div className="CardList">
|
||||
<style jsx>{`
|
||||
.CardList {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
|
||||
justify-content: space-between;
|
||||
}
|
||||
`}</style>
|
||||
|
||||
{props.items.map(
|
||||
(item, index) => props.renderItem(item, index)
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
@@ -1,8 +0,0 @@
|
||||
const React = require('react')
|
||||
const Image = require('./Image')
|
||||
|
||||
module.exports = ({data}) => (
|
||||
<div className="CollectiveImage">
|
||||
<Image data={data}/>
|
||||
</div>
|
||||
)
|
||||
Loaded 100 of 220 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user