news
This commit is contained in:
parent
1a389ec1dd
commit
d32e47d3ff
@ -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
107
server/api/news.js
Normal 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
|
@ -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')
|
||||||
|
@ -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')
|
||||||
|
@ -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')
|
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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
24
views/article.pug
Normal 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
|
||||||
|
|
@ -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
34
views/news.pug
Normal 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
|
||||||
|
|
Reference in New Issue
Block a user