forked from kaverti/website
extra routes and stuff
This commit is contained in:
parent
197c5e1473
commit
9725247bbd
|
@ -5,7 +5,8 @@
|
|||
"database": "kaverti",
|
||||
"host": "127.0.0.1",
|
||||
"dialect": "mysql",
|
||||
"maintenance": "true"
|
||||
"maintenance": "true",
|
||||
"passkey": "true"
|
||||
},
|
||||
"test": {
|
||||
"username": "root",
|
||||
|
@ -13,7 +14,8 @@
|
|||
"database": "kaverti",
|
||||
"host": "127.0.0.1",
|
||||
"dialect": "mysql",
|
||||
"maintenance": "true"
|
||||
"maintenance": "true",
|
||||
"passkey": "true"
|
||||
},
|
||||
"production": {
|
||||
"username": "root",
|
||||
|
@ -21,6 +23,7 @@
|
|||
"database": "kaverti",
|
||||
"host": "127.0.0.1",
|
||||
"dialect": "mysql",
|
||||
"maintenance": "false"
|
||||
"maintenance": "false",
|
||||
"passkey": "true"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,9 @@ font-size: 24px !important;
|
|||
<p style='margin-top: 0;' v-if='$store.state.token'>
|
||||
<strong>Providing the state token is still valid, this will create an admin account</strong>
|
||||
</p>
|
||||
<p style='margin-top: 0;' v-if='$store.state.passkey'>
|
||||
<strong>Providing a PassKey, this will create a normal user account</strong>
|
||||
</p>
|
||||
<p style='margin-top: 0;' v-else>
|
||||
Sign up to {{name}} today!
|
||||
<br/>It's fast and easy.
|
||||
|
@ -352,7 +355,8 @@ font-size: 24px !important;
|
|||
this.signup.email = ''
|
||||
|
||||
this.$store.commit('setToken', null)
|
||||
},
|
||||
this.$store.commit('setPassKey', null)
|
||||
},
|
||||
clearSignupErrors () {
|
||||
this.signup.errors.username = ''
|
||||
this.signup.errors.hash = ''
|
||||
|
@ -386,6 +390,9 @@ font-size: 24px !important;
|
|||
postParams.admin = true
|
||||
postParams.token = this.$store.state.token
|
||||
}
|
||||
if(this.$store.state.passkey) {
|
||||
postParams.passkey = this.$store.state.passkey
|
||||
}
|
||||
|
||||
if(this.signup.password !== this.signup.confirmPassword) {
|
||||
this.signup.errors.confirmPassword = 'Passwords must match'
|
||||
|
|
|
@ -261,6 +261,11 @@
|
|||
this.$store.commit('setAccountTabs', 0)
|
||||
this.$store.commit('setAccountModalState', true)
|
||||
}
|
||||
if(this.$route.query.passkey) {
|
||||
this.$store.commit('setPassKey', this.$route.query.passkey)
|
||||
this.$store.commit('setAccountTabs', 0)
|
||||
this.$store.commit('setAccountModalState', true)
|
||||
}
|
||||
|
||||
logger('index')
|
||||
},
|
||||
|
|
|
@ -55,7 +55,6 @@ const Index = () => import('./components/routes/Index')
|
|||
const HomeUnauthenticated = () => import('./components/routes/HomeUnauthenticated')
|
||||
|
||||
const P = () => import('./components/routes/P')
|
||||
const Start = () => import('./components/routes/Start')
|
||||
const Thread = () => import('./components/routes/Thread')
|
||||
const ThreadNew = () => import('./components/routes/ThreadNew')
|
||||
const Games = () => import('./components/routes/Games')
|
||||
|
@ -133,8 +132,9 @@ const router = new VueRouter({
|
|||
{ path: '/', component: HomeUnauthenticated },
|
||||
{ path: '/category/:category', component: Index },
|
||||
{ path: '/p/:id', component: P },
|
||||
{ path: '/start', component: Start },
|
||||
{ path: '/thread/:slug/:id', component: Thread },
|
||||
{ path: '/start', component: Reserved },
|
||||
{ path: '/experiments', component: Reserved },
|
||||
{ path: '/thread/:slug/:id', component: Thread },
|
||||
{ path: '/thread/:slug/:id/:post_number', name: 'thread-post', component: Thread },
|
||||
{ path: '/thread/new', component: ThreadNew },
|
||||
{ path: '/marketplace', component: Reserved },
|
||||
|
|
|
@ -22,8 +22,9 @@ export default new Vuex.Store({
|
|||
admin: false,
|
||||
|
||||
token: null,
|
||||
passkey: null,
|
||||
|
||||
show404Page: false,
|
||||
show404Page: false,
|
||||
|
||||
ajaxErrors: [],
|
||||
ajaxErrorsModal: false,
|
||||
|
@ -75,6 +76,9 @@ export default new Vuex.Store({
|
|||
setToken (state, token) {
|
||||
state.token = token
|
||||
},
|
||||
setPassKey (state, passkey) {
|
||||
state.passkey = passkey
|
||||
},
|
||||
set404Page (state, value) {
|
||||
state.show404Page = value
|
||||
},
|
||||
|
|
|
@ -48,7 +48,11 @@ let Errors = {
|
|||
401
|
||||
],
|
||||
invalidToken: [
|
||||
'Your session token is invalid, If this issue persists, please contact Kaverti support.',
|
||||
'Your QACT (Quick Admin Creation Token) is invalid.',
|
||||
401
|
||||
],
|
||||
invalidPassKey: [
|
||||
'Your PassKey is invalid.',
|
||||
401
|
||||
],
|
||||
noSettings: [
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
module.exports = {
|
||||
up(queryInterface, Sequelize) {
|
||||
return Promise.all([
|
||||
queryInterface.renameColumn(
|
||||
'settings',
|
||||
'siteName',
|
||||
'siteName',
|
||||
),
|
||||
queryInterface.renameColumn(
|
||||
'settings',
|
||||
'forumDescription',
|
||||
'siteDesc',
|
||||
),
|
||||
queryInterface.addColumn(
|
||||
'settings',
|
||||
'createdAt',
|
||||
),
|
||||
queryInterface.addColumn(
|
||||
'settings',
|
||||
'updatedAt',
|
||||
),
|
||||
]);
|
||||
},
|
||||
};
|
|
@ -1,22 +0,0 @@
|
|||
module.exports = {
|
||||
up(queryInterface, Sequelize) {
|
||||
return Promise.all([
|
||||
queryInterface.addColumn(
|
||||
'settings',
|
||||
'created',
|
||||
{
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false
|
||||
},
|
||||
),
|
||||
queryInterface.addColumn(
|
||||
'settings',
|
||||
'updated',
|
||||
{
|
||||
type: Sequelize.DATE,
|
||||
allowNull: false
|
||||
},
|
||||
),
|
||||
]);
|
||||
},
|
||||
};
|
|
@ -1,16 +0,0 @@
|
|||
module.exports = {
|
||||
up(queryInterface, Sequelize) {
|
||||
return Promise.all([
|
||||
queryInterface.renameColumn(
|
||||
'settings',
|
||||
'created',
|
||||
'createdAt',
|
||||
),
|
||||
queryInterface.renameColumn(
|
||||
'settings',
|
||||
'updated',
|
||||
'updatedAt',
|
||||
),
|
||||
]);
|
||||
},
|
||||
};
|
|
@ -0,0 +1,30 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
up: (queryInterface, Sequelize) => {
|
||||
return queryInterface.createTable('passkey', {
|
||||
code: {
|
||||
type: Sequelize.STRING(191),
|
||||
primaryKey: true,
|
||||
required: true
|
||||
},
|
||||
createdAt: Sequelize.DATE,
|
||||
uses: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
primaryKey: true,
|
||||
required: true
|
||||
},
|
||||
maxUses: {
|
||||
type: Sequelize.BOOLEAN,
|
||||
primaryKey: true,
|
||||
required: true
|
||||
}
|
||||
}, {
|
||||
charset: 'utf8mb4'
|
||||
})
|
||||
},
|
||||
|
||||
down: (queryInterface, Sequelize) => {
|
||||
return queryInterface.dropTable('passkey');
|
||||
}
|
||||
};
|
120
models/blog.js
120
models/blog.js
|
@ -1,120 +0,0 @@
|
|||
let urlSlug = require('url-slug')
|
||||
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
let Thread = sequelize.define('BlogPost', {
|
||||
name: {
|
||||
type: DataTypes.TEXT,
|
||||
set (val) {
|
||||
this.setDataValue('name', val)
|
||||
if(val) {
|
||||
this.setDataValue(
|
||||
'slug',
|
||||
//if you don't covert to lowercase it doesn't
|
||||
//correctly slugify diacritics, e.g. thrËad
|
||||
//becomes 'thr-ead' not 'thread'
|
||||
urlSlug(val.toString().toLowerCase() || '') || '_'
|
||||
)
|
||||
}
|
||||
},
|
||||
allowNull: false,
|
||||
validate: {
|
||||
notEmpty: {
|
||||
msg: 'Did you forget something? (Fill this in!)'
|
||||
},
|
||||
len: {
|
||||
args: [4, 256],
|
||||
msg: 'The title must be between 4 and 256 characters'
|
||||
},
|
||||
isString (val) {
|
||||
if(typeof val !== 'string') {
|
||||
throw new sequelize.ValidationError('The title must be a string')
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
slug: DataTypes.TEXT,
|
||||
}, {
|
||||
instanceMethods: {
|
||||
getMeta (limit) {
|
||||
let meta = {}
|
||||
|
||||
let blogposts = this.Blogposts
|
||||
let firstPost = posts[0]
|
||||
let lastPost = posts.slice(-1)[0]
|
||||
|
||||
//next url
|
||||
if(!lastPost || lastPost.postNumber+1 === this.postsCount) {
|
||||
meta.nextURL = null
|
||||
} else {
|
||||
meta.nextURL =
|
||||
`/api/v1/blog/post/${this.id}?limit=${limit}&from=${lastPost.postNumber + 1}`
|
||||
}
|
||||
|
||||
//previous url
|
||||
if(!firstPost || firstPost.postNumber === 0) {
|
||||
meta.previousURL = null
|
||||
} else if(firstPost.postNumber - limit < 0) {
|
||||
meta.previousURL =
|
||||
`/api/v1/blog/post/${this.id}?limit=${firstPost.postNumber}&from=0`
|
||||
} else {
|
||||
meta.previousURL =
|
||||
`/api/v1/blog/post/${this.id}?limit=${limit}&from=${firstPost.postNumber - limit}`
|
||||
}
|
||||
|
||||
//remaining posts
|
||||
if(lastPost === undefined) {
|
||||
meta.nextPostsCount = 0
|
||||
meta.previousPostsCount = 0
|
||||
meta.postsRemaining = 0
|
||||
} else {
|
||||
let postsRemaining =
|
||||
this.postsCount - lastPost.postNumber - 1
|
||||
|
||||
meta.postsRemaining = postsRemaining
|
||||
|
||||
if(postsRemaining < limit) {
|
||||
meta.nextPostsCount = postsRemaining
|
||||
} else {
|
||||
meta.nextPostsCount = limit
|
||||
}
|
||||
|
||||
if(firstPost.postNumber === 0) {
|
||||
meta.previousPostsCount = 0
|
||||
} else if(firstPost.postNumber - limit < 0) {
|
||||
meta.previousPostsCount = firstPost.postNumber
|
||||
} else {
|
||||
meta.previousPostsCount = limit
|
||||
}
|
||||
}
|
||||
|
||||
return meta
|
||||
}
|
||||
},
|
||||
classMethods: {
|
||||
associate (models) {
|
||||
Thread.belongsTo(models.User)
|
||||
Thread.hasMany(models.Post, { foreignKeyConstraint: true, onDelete: 'CASCADE' })
|
||||
},
|
||||
includeOptions (from, limit) {
|
||||
let models = sequelize.models
|
||||
|
||||
return [
|
||||
{ model: models.User, attributes: ['username', 'createdAt', 'color', 'picture', 'updatedAt', 'id'] },
|
||||
{
|
||||
model: models.Post,
|
||||
where: { postNumber: { $gte: from } },
|
||||
order: [['id', 'ASC']],
|
||||
limit,
|
||||
include: [
|
||||
{ model: models.Thread, attributes: ['slug'] },
|
||||
{ model: models.User, as: 'Likes', attributes: ['username', 'createdAt', 'id', 'color', 'picture'] },
|
||||
{ model: models.User, attributes: ['username', 'createdAt', 'id', 'color', 'picture', 'admin'] },
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return Thread
|
||||
}
|
|
@ -24,6 +24,7 @@
|
|||
"dompurify": "^2.0.12",
|
||||
"ejs": "^2.5.7",
|
||||
"express": "^4.16.4",
|
||||
"express-rate-limit": "^5.1.3",
|
||||
"express-recaptcha": "^5.0.2",
|
||||
"express-session": "^1.15.6",
|
||||
"helmet": "^3.23.3",
|
||||
|
|
137
routes/blog.js
137
routes/blog.js
|
@ -1,137 +0,0 @@
|
|||
let express = require('express')
|
||||
let router = express.Router()
|
||||
|
||||
const Errors = require('../lib/errors.js')
|
||||
let { User, BlogPost, Sequelize } = require('../models')
|
||||
let pagination = require('../lib/pagination.js')
|
||||
|
||||
router.get('/:blogpost_id', async (req, res, next) => {
|
||||
try {
|
||||
let { from, limit } = pagination.getPaginationProps(req.query)
|
||||
let blogpost = await BlogPost.findById(req.params.blogpost_id, {
|
||||
include: BlogPost.includeOptions(from, limit)
|
||||
})
|
||||
if(!blogpost) throw Errors.invalidParameter('id', 'blogpost does not exist')
|
||||
|
||||
let meta = blogpost.getMeta(limit)
|
||||
|
||||
res.json(Object.assign( blogpost.toJSON(), { meta } ))
|
||||
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
|
||||
//Only logged in routes
|
||||
router.all('*', (req, res, next) => {
|
||||
if(req.session.loggedIn) {
|
||||
next()
|
||||
} else {
|
||||
res.status(401)
|
||||
res.json({
|
||||
errors: [Errors.requestNotAuthorized]
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
router.post('/', async (req, res, next) => {
|
||||
let validationErrors = []
|
||||
try {
|
||||
if(!req.session.admin) {
|
||||
res.status(401)
|
||||
res.json({
|
||||
errors: [Errors.requestNotAuthorized]
|
||||
})}
|
||||
|
||||
let user = await User.findOne({ where: {
|
||||
username: req.session.username
|
||||
}})
|
||||
|
||||
|
||||
let blogpost = await BlogPost.create({
|
||||
name: req.body.name
|
||||
})
|
||||
|
||||
await blogpost.setCategory("Blog")
|
||||
await blogpost.setUser(user)
|
||||
|
||||
res.json(await blogpost.reload({
|
||||
include: [
|
||||
{ model: User, attributes: ['username', 'createdAt', 'updatedAt', 'id'] },
|
||||
Category
|
||||
]
|
||||
}))
|
||||
|
||||
req.app.get('io').to('index').emit('new blogpost', {
|
||||
name: "Blog",
|
||||
value: category.value
|
||||
})
|
||||
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
|
||||
//Only admin routes
|
||||
router.all('*', (req, res, next) => {
|
||||
if(req.session.admin) {
|
||||
next()
|
||||
} else {
|
||||
res.status(401)
|
||||
res.json({
|
||||
errors: [Errors.requestNotAuthorized]
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
router.delete('/:blogpost_id', async (req, res, next) => {
|
||||
try {
|
||||
let blogpost = await BlogPost.findById(req.params.blogpost_id)
|
||||
|
||||
if(!blogpost) {
|
||||
throw Errors.sequelizeValidation(Sequelize, {
|
||||
error: 'invalid blogpost id',
|
||||
value: req.params.blogpost_id
|
||||
})
|
||||
} else {
|
||||
//Find all posts with reports and get reports
|
||||
//Then delete those reports
|
||||
//Temporary fix because cascade is not working
|
||||
let posts = await Post.findAll({
|
||||
where: {
|
||||
BlogPostId: blogpost.id
|
||||
},
|
||||
})
|
||||
let reports = posts
|
||||
.map(post => post.Reports)
|
||||
.reduce((a, b) => a.concat(b), [])
|
||||
|
||||
let destroyPromises = reports.map(report => report.destroy())
|
||||
|
||||
await Promise.all(destroyPromises)
|
||||
await Post.destroy({ where: { BlogPostId: blogpost.id } })
|
||||
await blogpost.destroy()
|
||||
|
||||
res.json({ success: true })
|
||||
}
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
|
||||
router.put('/:blogpost_id', async (req, res, next) => {
|
||||
try {
|
||||
let blogpost = await BlogPost.findById(req.params.blogpost_id)
|
||||
|
||||
if(!blogpost) {
|
||||
res.status(400)
|
||||
res.json({ errors:
|
||||
[Errors.invalidParameter('blogpostId', 'blogpost does not exist')]
|
||||
})
|
||||
} else {
|
||||
if(req.body.locked) {
|
||||
await blogpost.update({ locked: true })
|
||||
} else {
|
||||
await blogpost.update({ locked: false })
|
||||
}
|
||||
|
||||
res.json({ success: true })
|
||||
}
|
||||
} catch (e) { next(e) }
|
||||
})
|
||||
|
||||
module.exports = router
|
|
@ -8,7 +8,7 @@ var reCAPTCHASecret = "6LdlbrwZAAAAAKvtcVQhVl_QaNOqmQ4PgyW3SKHy";
|
|||
const Errors = require('../lib/errors.js')
|
||||
var format = require('date-format');
|
||||
let {
|
||||
User, Post, ProfilePicture, AdminToken, Thread, Category, Sequelize, Ip, Ban, sequelize
|
||||
User, Post, ProfilePicture, AdminToken, PassKey, Thread, Category, Sequelize, Ip, Ban, sequelize
|
||||
} = require('../models')
|
||||
let pagination = require('../lib/pagination.js')
|
||||
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
let crypto = require('crypto')
|
||||
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
let PassKey = sequelize.define('PassKey', {
|
||||
passkey: {
|
||||
type: DataTypes.STRING,
|
||||
defaultValue () {
|
||||
return crypto.randomBytes(64).toString('hex')
|
||||
},
|
||||
validate: {
|
||||
isString (val) {
|
||||
if(typeof val !== 'string') {
|
||||
throw new sequelize.ValidationError('passkey must be a string')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
instanceMethods: {
|
||||
isValid () {
|
||||
let ms = Date.now() - this.createdAt
|
||||
let dayMs = 1000*60*60*24
|
||||
|
||||
//Has less than 1 day passed
|
||||
//since generating token?
|
||||
return ms / dayMs < 1
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return PassKey
|
||||
}
|
|
@ -16,6 +16,7 @@ let helmet = require('helmet')
|
|||
var Recaptcha = require('express-recaptcha').RecaptchaV3;
|
||||
var recaptcha = new Recaptcha('6LdlbrwZAAAAAKvtcVQhVl_QaNOqmQ4PgyW3SKHy', '6LdlbrwZAAAAAMAWPVDrL8eNPxrws6AMDtLf1bgd');
|
||||
|
||||
|
||||
let path = require('path')
|
||||
|
||||
|
||||
|
@ -34,7 +35,6 @@ app.use(compression())
|
|||
app.use(bodyParser.json({ limit: '5mb' }))
|
||||
app.use(bodyParser.urlencoded({ extended: true }))
|
||||
app.use(session)
|
||||
|
||||
var maintoptions = {
|
||||
current: config.maintenance, // current state, default **false**
|
||||
httpEndpoint: true, // expose http endpoint for hot-switch, default **false**,
|
||||
|
@ -53,9 +53,9 @@ if(process.env.NODE_ENV !== 'test' && process.env.NODE_ENV !== 'production') {
|
|||
|
||||
app.use('/api/v1/user/', require('./routes/user'))
|
||||
app.use('/api/v1/admin/admin_token', require('./routes/admin_token'))
|
||||
app.use('/api/v1/admin/passkey', require('./routes/user_passkey'))
|
||||
app.use('/api/v1/forums/category', require('./routes/category'))
|
||||
app.use('/api/v1/forums/thread', require('./routes/thread'))
|
||||
app.use('/api/v1/blog/', require('./routes/blog'))
|
||||
app.use('/api/v1/users/notification', require('./routes/notification'))
|
||||
app.use('/api/v1/forums/post', require('./routes/post'))
|
||||
app.use('/api/v1/kaverti/state', require('./routes/settings'))
|
||||
|
|
|
@ -1094,6 +1094,11 @@ expand-template@^2.0.3:
|
|||
resolved "https://npm.open-registry.dev/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
|
||||
integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
|
||||
|
||||
express-rate-limit@^5.1.3:
|
||||
version "5.1.3"
|
||||
resolved "https://registry.yarnpkg.com/express-rate-limit/-/express-rate-limit-5.1.3.tgz#656bacce3f093034976346958a0f0199902c9174"
|
||||
integrity sha512-TINcxve5510pXj4n9/1AMupkj3iWxl3JuZaWhCdYDlZeoCPqweGZrxbrlqTCFb1CT5wli7s8e2SH/Qz2c9GorA==
|
||||
|
||||
express-recaptcha@^5.0.2:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/express-recaptcha/-/express-recaptcha-5.0.2.tgz#17de212c165489462ab85e1e07c4ea81661d5e5f"
|
||||
|
|
Loading…
Reference in New Issue