This commit is contained in:
Evert Prants 2017-08-24 13:52:12 +03:00
parent 1a389ec1dd
commit d32e47d3ff
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
14 changed files with 329 additions and 23 deletions

View File

@ -221,11 +221,6 @@ const API = {
return {error: null, user: user} return {error: null, user: user}
} }
} }
},
External: {
serviceCallback: async function () {
}
} }
} }

107
server/api/news.js Normal file
View File

@ -0,0 +1,107 @@
import API from './index'
import Models from './models'
import config from '../../scripts/load-config'
import database from '../../scripts/load-database'
const perPage = 8
function slugify (title) {
return title.toLowerCase().replace(/\W/g, '-').substring(0, 16)
}
//** ppp - Posts Per Page; dcount - Post Count; page - number of current page
function Pagination (ppp, dcount, page) {
if (!ppp) ppp = 5
if (!dcount) return null
let pageCount = Math.ceil(dcount / ppp)
if (page > pageCount) page = pageCount
let offset = (page - 1) * ppp
return {
page: page,
perPage: ppp,
pages: pageCount,
offset: offset,
total: dcount
}
}
async function cleanArticle (entry, shortenContent = false) {
let poster = await API.User.get(entry.user_id)
let article = {
id: entry.id,
slug: slugify(entry.title),
title: entry.title,
content: entry.content,
tags: entry.tags.split(','),
created_at: entry.created_at,
updated_at: entry.updated_at
}
if (poster) {
article.author = {
id: poster.id,
display_name: poster.display_name
}
}
if (shortenContent) {
article.content = article.content.replace(/(<([^>]+)>)/ig, '').substring(0, 128) + '...'
}
return article
}
const News = {
preview: async () => {
// Fetch 3 latest stories
let news = await Models.News.query().orderBy('created_at', 'desc').limit(3)
if (!news.length) return []
let articles = []
for (let i in news) {
let entry = news[i]
articles.push(await cleanArticle(entry, true))
}
return articles
},
listNews: async (page) => {
let count = await Models.News.query().count('id as ids')
if (page < 1) page = 1
if (!count.length || !count[0]['ids'] || isNaN(page)) {
return {error: 'No articles found'}
}
count = count[0].ids
let paginated = Pagination(perPage, parseInt(count), page)
let news = await Models.News.query().orderBy('created_at', 'desc').offset(paginated.offset).limit(perPage)
let articles = []
for (let i in news) {
let entry = news[i]
articles.push(await cleanArticle(entry))
}
return {
page: paginated,
articles: articles
}
},
article: async (id) => {
let article = await Models.News.query().where('id', id)
if (!article.length) return {}
article = article[0]
let poster = await API.User.get(article.user_id)
return await cleanArticle(article)
}
}
module.exports = News

View File

@ -9,7 +9,7 @@ module.exports = async (req, res, client, scope, user, redirectUri, createAllowF
} }
if (createAllowFuture) { if (createAllowFuture) {
if (!req.body || (typeof req.body['decision']) === 'undefined') { if (!req.body || (typeof req.body['decision']) === undefined) {
throw new error.InvalidRequest('No decision parameter passed') throw new error.InvalidRequest('No decision parameter passed')
} else if (req.body['decision'] === '0') { } else if (req.body['decision'] === '0') {
throw new error.AccessDenied('User denied access to the resource') throw new error.AccessDenied('User denied access to the resource')

View File

@ -9,7 +9,7 @@ module.exports = async (req, res, client, scope, user, redirectUri, createAllowF
} }
if (createAllowFuture) { if (createAllowFuture) {
if (!req.body || (typeof req.body['decision']) === 'undefined') { if (!req.body || (typeof req.body['decision']) === undefined) {
throw new error.InvalidRequest('No decision parameter passed') throw new error.InvalidRequest('No decision parameter passed')
} else if (req.body['decision'] === '0') { } else if (req.body['decision'] === '0') {
throw new error.AccessDenied('User denied access to the resource') throw new error.AccessDenied('User denied access to the resource')

View File

@ -1,5 +1,3 @@
module.exports = function (req, res, client, scope, user) { module.exports = function (req, res, client, scope, user) {
res.locals.client = client res.render('authorization', { client: client, scope: scope })
res.locals.scope = scope
res.render('authorization')
} }

View File

@ -1,8 +1,6 @@
import middleware from './middleware' import middleware from './middleware'
import controller from './controller' import controller from './controller'
import decision from './controller/decision' import decision from './controller/decision'
import response from './response'
import error from './error'
import model from './model' import model from './model'
class OAuth2Provider { class OAuth2Provider {
@ -10,8 +8,6 @@ class OAuth2Provider {
this.bearer = middleware this.bearer = middleware
this.controller = controller this.controller = controller
this.decision = decision this.decision = decision
this.response = response
this.error = error.OAuth2Error
this.model = model this.model = model
} }

View File

@ -5,6 +5,7 @@ import config from '../../scripts/load-config'
import wrap from '../../scripts/asyncRoute' import wrap from '../../scripts/asyncRoute'
import API from '../api' import API from '../api'
import APIExtern from '../api/external' import APIExtern from '../api/external'
import News from '../api/news'
let router = express.Router() let router = express.Router()
@ -206,4 +207,46 @@ router.get('/external/discord/callback', wrap(async (req, res) => {
res.redirect(uri) res.redirect(uri)
})) }))
/* ========
* NEWS
* ========
*/
// Get page of articles
router.get('/news/all/:page', wrap(async (req, res) => {
if (!req.params.page || isNaN(parseInt(req.params.page))) {
return res.status(400).jsonp({error: 'Invalid page number.'})
}
let page = parseInt(req.params.page)
let articles = await News.listNews(page)
res.jsonp(articles)
}))
// Redirect to page one
router.get('/news/all/', (req, res) => {
res.redirect('/api/news/all/1')
})
// Fetch article
router.get('/news/:id', wrap(async (req, res) => {
if (!req.params.id || isNaN(parseInt(req.params.id))) {
return res.status(400).jsonp({error: 'Invalid ID number.'})
}
let id = parseInt(req.params.id)
let article = await News.article(id)
res.jsonp(article)
}))
// Preview endpoint
router.get('/news', wrap(async (req, res) => {
let articles = await News.preview()
res.jsonp(articles)
}))
module.exports = router module.exports = router

View File

@ -7,6 +7,7 @@ import config from '../../scripts/load-config'
import wrap from '../../scripts/asyncRoute' import wrap from '../../scripts/asyncRoute'
import http from '../../scripts/http' import http from '../../scripts/http'
import API from '../api' import API from '../api'
import News from '../api/news'
import apiRouter from './api' import apiRouter from './api'
import oauthRouter from './oauth2' import oauthRouter from './oauth2'
@ -358,6 +359,31 @@ router.get('/docs/:name', (req, res) => {
res.render('document', {doc: doc}) res.render('document', {doc: doc})
}) })
router.get('/news/:id?-*', wrap(async (req, res) => {
let id = parseInt(req.params.id)
if (isNaN(id)) {
return res.status(404).render('article', {article: null})
}
let article = await News.article(id)
if (!article.id) {
return res.status(404).render('article', {article: null})
}
res.render('article', {article: article})
}))
router.get('/news/', wrap(async (req, res) => {
let page = parseInt(req.query.page)
if (isNaN(page)) {
page = 1
}
let news = await News.listNews(page)
res.render('news', {news: news})
}))
/* /*
========= =========
OTHER OTHER

View File

@ -34,6 +34,7 @@ router.post('/introspect', oauth.controller.introspection)
router.get('/user', oauth.bearer, wrap(async (req, res) => { router.get('/user', oauth.bearer, wrap(async (req, res) => {
let accessToken = req.oauth2.accessToken let accessToken = req.oauth2.accessToken
let user = await uapi.User.get(accessToken.user_id) let user = await uapi.User.get(accessToken.user_id)
if (!user) { if (!user) {
return res.status(404).jsonp({ return res.status(404).jsonp({
error: 'No such user' error: 'No such user'
@ -42,14 +43,17 @@ router.get('/user', oauth.bearer, wrap(async (req, res) => {
let udata = { let udata = {
id: user.id, id: user.id,
name: user.display_name, username: user.username,
display_name: user.display_name,
avatar_file: user.avatar_file avatar_file: user.avatar_file
} }
// Include Email
if (accessToken.scope.indexOf('email') != -1) { if (accessToken.scope.indexOf('email') != -1) {
udata.email = user.email udata.email = user.email
} }
// Include privilege number
if (accessToken.scope.indexOf('privilege') != -1) { if (accessToken.scope.indexOf('privilege') != -1) {
udata.privilege = user.nw_privilege udata.privilege = user.nw_privilege
} }

View File

@ -2,9 +2,9 @@ window.$ = require('jquery')
$(document).ready(function () { $(document).ready(function () {
if (window.location.hash) { if (window.location.hash) {
let hash = window.location.hash var locha = window.location.hash
if ($(hash).length) { if ($(locha).length) {
$(window).scrollTop($(hash).offset().top - $('.navigator').innerHeight() * 2) $(window).scrollTop($(locha).offset().top - $('.navigator').innerHeight() * 2)
} }
} }
@ -30,7 +30,7 @@ $(document).ready(function () {
if (!$(this.hash).length) return if (!$(this.hash).length) return
e.preventDefault() e.preventDefault()
let dest = 0 var dest = 0
if ($(this.hash).offset().top > $(document).height() - $(window).height()) { if ($(this.hash).offset().top > $(document).height() - $(window).height()) {
dest = $(document).height() - $(window).height() dest = $(document).height() - $(window).height()
} else { } else {
@ -55,8 +55,8 @@ $(document).ready(function () {
if ($('#repeatcheck').length) { if ($('#repeatcheck').length) {
function pwcheck (e) { function pwcheck (e) {
let pw = $('#password').val() var pw = $('#password').val()
let pwa = $('#password_repeat').val() var pwa = $('#password_repeat').val()
if (pwa !== pw) { if (pwa !== pw) {
$('#password_repeat').addClass('invalid') $('#password_repeat').addClass('invalid')
$('#repeatcheck').show() $('#repeatcheck').show()
@ -76,6 +76,30 @@ $(document).ready(function () {
}) })
} }
if ($('.newsfeed').length) {
$.ajax({
type: 'get',
url: '/api/news',
dataType: 'json',
success: function (data) {
if (!data.length) {
return $('.newsfeed').html('There is nothing to show at this moment.')
}
var html = ''
for (var i in data) {
var article = data[i]
html += '<div class="prvarticle">'
html += '<a class="title" href="/news/' + article.id + '-' + article.slug + '">' + article.title + '</a>'
html += '<span class="timestamp">Published at ' + new Date(article.created_at) + '</span>'
html += '<div class="prvcontent">' + article.content + '</div>'
html += '<a href="/news/' + article.id + '-' + article.slug + '">Read More</a>'
html += '</div>'
}
$('.newsfeed').html(html)
}
})
}
window.checkLoginState = function () { window.checkLoginState = function () {
FB.getLoginStatus(function(response) { FB.getLoginStatus(function(response) {
$.ajax({ $.ajax({
@ -83,7 +107,7 @@ $(document).ready(function () {
url: '/api/external/facebook/callback', url: '/api/external/facebook/callback',
dataType: 'json', dataType: 'json',
data: response, data: response,
success: (data) => { success: function (data) {
if (data.error) { if (data.error) {
$('.message').addClass('error') $('.message').addClass('error')
$('.message span').text(data.error) $('.message span').text(data.error)

View File

@ -216,7 +216,7 @@ input:not([type="submit"])
box-shadow: inset 2px 2px 5px #ddd box-shadow: inset 2px 2px 5px #ddd
transition: border 0.1s linear transition: border 0.1s linear
input[type="submit"] .button, input[type="submit"]
display: block display: block
padding: 5px 10px padding: 5px 10px
background-color: #fbfbfb background-color: #fbfbfb
@ -226,6 +226,9 @@ input[type="submit"]
margin: 10px 0 margin: 10px 0
cursor: pointer cursor: pointer
.button
display: inline-block
.boxcont .boxcont
.box .box
.left, .right .left, .right
@ -249,6 +252,14 @@ input[type="submit"]
h1, h2, h3 h1, h2, h3
margin-top: 0 margin-top: 0
.pgn
display: inline-block
margin-left: 10px
.pagenum
display: inline-block
padding: 10px
.dlbtn .dlbtn
display: block display: block
img img
@ -371,6 +382,45 @@ input.invalid
color: red color: red
padding: 10px padding: 10px
.article
.title
font-size: 200%
font-weight: bold
.author
font-size: 80%
color: #565656
.timestamp
display: inline-block
.content
margin-top: 20px
margin-bottom: 20px
.newsfeed
.load
font-size: 120%
span
vertical-align: super
margin-left: 10px
.prvarticle
margin-bottom: 10px
.title
font-size: 200%
display: block
font-weight: bold
.timestamp
font-size: 80%
color: #565656
.prvcontent
margin: 10px 0
.return
margin-top: 40px
.content
hr
border-color: #ffffff
margin: 20px 0
@media all and (max-width: 800px) @media all and (max-width: 800px)
.navigator .navigator
padding: 0 10px padding: 0 10px

24
views/article.pug Normal file
View File

@ -0,0 +1,24 @@
extends layout.pug
block title
if article
|Icy Network - News - #{article.title}
else
|Icy Network - News - 404
block body
.document
.content
if !article
span.error No such article
else
.article
.title= article.title
.author Published by
span #{article.author.display_name}
|at
.timestamp #{new Date(article.created_at)}
.content!= article.content
.return
a(href="/news") Back to news directory

View File

@ -22,3 +22,8 @@ block body
section#news section#news
.content .content
h1 Icy Network News h1 Icy Network News
.newsfeed
span.load
i.fa.fa-spin.fa-spinner.fa-2x
span Loading feed
a.older(href="/news") View older articles

34
views/news.pug Normal file
View File

@ -0,0 +1,34 @@
extends layout.pug
block title
|Icy Network - News
block body
.document
.content
h1 Icy Network News
if news.error
span.error There are no articles to show.
else
if news.page
span.pagenum
|Page #{news.page.page} of #{news.page.pages}
.pgn
if news.page.page > 1
a.button(href="/news/?page=" + news.page.page - 1) Previous
- var n = 0
while n < news.page.pages
a.button(href="/news/?page=" + (n + 1))= n + 1
- n++
if news.page.pages > news.page.page
a.button(href="/news/?page=" + news.page.page + 1) Next
each val in news.articles
.article
a.title(href="/news/" + val.id + "-" + val.slug)= val.title
.author Published by
span #{val.author.display_name}
|at
.timestamp #{new Date(val.created_at)}
.content!= val.content
hr