diff --git a/server/api/index.js b/server/api/index.js index e806bc5..563edda 100644 --- a/server/api/index.js +++ b/server/api/index.js @@ -221,11 +221,6 @@ const API = { return {error: null, user: user} } } - }, - External: { - serviceCallback: async function () { - - } } } diff --git a/server/api/news.js b/server/api/news.js new file mode 100644 index 0000000..f191ab0 --- /dev/null +++ b/server/api/news.js @@ -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 diff --git a/server/api/oauth2/controller/code/code.js b/server/api/oauth2/controller/code/code.js index cc2a00a..0da45d5 100644 --- a/server/api/oauth2/controller/code/code.js +++ b/server/api/oauth2/controller/code/code.js @@ -9,7 +9,7 @@ module.exports = async (req, res, client, scope, user, redirectUri, createAllowF } 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') } else if (req.body['decision'] === '0') { throw new error.AccessDenied('User denied access to the resource') diff --git a/server/api/oauth2/controller/code/implicit.js b/server/api/oauth2/controller/code/implicit.js index 4a2442b..f00a497 100644 --- a/server/api/oauth2/controller/code/implicit.js +++ b/server/api/oauth2/controller/code/implicit.js @@ -9,7 +9,7 @@ module.exports = async (req, res, client, scope, user, redirectUri, createAllowF } 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') } else if (req.body['decision'] === '0') { throw new error.AccessDenied('User denied access to the resource') diff --git a/server/api/oauth2/controller/decision.js b/server/api/oauth2/controller/decision.js index c239dd3..de0cb9a 100644 --- a/server/api/oauth2/controller/decision.js +++ b/server/api/oauth2/controller/decision.js @@ -1,5 +1,3 @@ module.exports = function (req, res, client, scope, user) { - res.locals.client = client - res.locals.scope = scope - res.render('authorization') + res.render('authorization', { client: client, scope: scope }) } diff --git a/server/api/oauth2/index.js b/server/api/oauth2/index.js index e5be603..3f012e5 100644 --- a/server/api/oauth2/index.js +++ b/server/api/oauth2/index.js @@ -1,8 +1,6 @@ import middleware from './middleware' import controller from './controller' import decision from './controller/decision' -import response from './response' -import error from './error' import model from './model' class OAuth2Provider { @@ -10,8 +8,6 @@ class OAuth2Provider { this.bearer = middleware this.controller = controller this.decision = decision - this.response = response - this.error = error.OAuth2Error this.model = model } diff --git a/server/routes/api.js b/server/routes/api.js index bee73bf..7770d57 100644 --- a/server/routes/api.js +++ b/server/routes/api.js @@ -5,6 +5,7 @@ import config from '../../scripts/load-config' import wrap from '../../scripts/asyncRoute' import API from '../api' import APIExtern from '../api/external' +import News from '../api/news' let router = express.Router() @@ -206,4 +207,46 @@ router.get('/external/discord/callback', wrap(async (req, res) => { 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 diff --git a/server/routes/index.js b/server/routes/index.js index 60f2132..512f4e2 100644 --- a/server/routes/index.js +++ b/server/routes/index.js @@ -7,6 +7,7 @@ import config from '../../scripts/load-config' import wrap from '../../scripts/asyncRoute' import http from '../../scripts/http' import API from '../api' +import News from '../api/news' import apiRouter from './api' import oauthRouter from './oauth2' @@ -358,6 +359,31 @@ router.get('/docs/:name', (req, res) => { 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 diff --git a/server/routes/oauth2.js b/server/routes/oauth2.js index a05f9a0..af0f6be 100644 --- a/server/routes/oauth2.js +++ b/server/routes/oauth2.js @@ -34,6 +34,7 @@ router.post('/introspect', oauth.controller.introspection) router.get('/user', oauth.bearer, wrap(async (req, res) => { let accessToken = req.oauth2.accessToken let user = await uapi.User.get(accessToken.user_id) + if (!user) { return res.status(404).jsonp({ error: 'No such user' @@ -42,14 +43,17 @@ router.get('/user', oauth.bearer, wrap(async (req, res) => { let udata = { id: user.id, - name: user.display_name, + username: user.username, + display_name: user.display_name, avatar_file: user.avatar_file } + // Include Email if (accessToken.scope.indexOf('email') != -1) { udata.email = user.email } + // Include privilege number if (accessToken.scope.indexOf('privilege') != -1) { udata.privilege = user.nw_privilege } diff --git a/src/script/main.js b/src/script/main.js index 29dde45..7453877 100644 --- a/src/script/main.js +++ b/src/script/main.js @@ -2,9 +2,9 @@ window.$ = require('jquery') $(document).ready(function () { if (window.location.hash) { - let hash = window.location.hash - if ($(hash).length) { - $(window).scrollTop($(hash).offset().top - $('.navigator').innerHeight() * 2) + var locha = window.location.hash + if ($(locha).length) { + $(window).scrollTop($(locha).offset().top - $('.navigator').innerHeight() * 2) } } @@ -30,7 +30,7 @@ $(document).ready(function () { if (!$(this.hash).length) return e.preventDefault() - let dest = 0 + var dest = 0 if ($(this.hash).offset().top > $(document).height() - $(window).height()) { dest = $(document).height() - $(window).height() } else { @@ -55,8 +55,8 @@ $(document).ready(function () { if ($('#repeatcheck').length) { function pwcheck (e) { - let pw = $('#password').val() - let pwa = $('#password_repeat').val() + var pw = $('#password').val() + var pwa = $('#password_repeat').val() if (pwa !== pw) { $('#password_repeat').addClass('invalid') $('#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 += '