Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/custom note url base #1704

Open
wants to merge 11 commits into
base: develop
Choose a base branch
from
157 changes: 56 additions & 101 deletions lib/note/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,54 +8,13 @@ const { newCheckViewPermission, errorForbidden, responseCodiMD, errorNotFound, e
const { updateHistory, historyDelete } = require('../history')
const { actionPublish, actionSlide, actionInfo, actionDownload, actionPDF, actionGist, actionRevision, actionPandoc } = require('./noteActions')
const realtime = require('../realtime/realtime')
const service = require('./service')

async function getNoteById (noteId, { includeUser } = { includeUser: false }) {
const id = await Note.parseNoteIdAsync(noteId)

const includes = []

if (includeUser) {
includes.push({
model: User,
as: 'owner'
}, {
model: User,
as: 'lastchangeuser'
})
}

const note = await Note.findOne({
where: {
id: id
},
include: includes
})
return note
}

async function createNote (userId, noteAlias) {
if (!config.allowAnonymous && !userId) {
throw new Error('can not create note')
}

const note = await Note.create({
ownerId: userId,
alias: noteAlias
})

if (userId) {
updateHistory(userId, note)
}

return note
}

// controller
async function showNote (req, res) {
exports.showNote = async (req, res) => {
const noteId = req.params.noteId
const userId = req.user ? req.user.id : null

let note = await getNoteById(noteId)
let note = await service.getNote(noteId)

if (!note) {
// if allow free url enable, auto create note
Expand All @@ -64,7 +23,7 @@ async function showNote (req, res) {
} else if (!config.allowAnonymous && !userId) {
return errorForbidden(req, res)
}
note = await createNote(userId, noteId)
note = await service.createNote(userId, noteId)
}

if (!newCheckViewPermission(note, req.isAuthenticated(), userId)) {
Expand All @@ -79,33 +38,23 @@ async function showNote (req, res) {
return responseCodiMD(res, note)
}

function canViewNote (note, isLogin, userId) {
if (note.permission === 'private') {
return note.ownerId === userId
}
if (note.permission === 'limited' || note.permission === 'protected') {
return isLogin
}
return true
}

async function showPublishNote (req, res) {
exports.showPublishNote = async (req, res) => {
const shortid = req.params.shortid

const note = await getNoteById(shortid, {
const note = await service.getNote(shortid, {
includeUser: true
})

if (!note) {
return errorNotFound(req, res)
}

if (!canViewNote(note, req.isAuthenticated(), req.user ? req.user.id : null)) {
if (!service.canViewNote(note, req.isAuthenticated(), req.user ? req.user.id : null)) {
return errorForbidden(req, res)
}

if ((note.alias && shortid !== note.alias) || (!note.alias && shortid !== note.shortid)) {
return res.redirect(config.serverURL + '/s/' + (note.alias || note.shortid))
return res.redirect(config.serviceerURL + '/s/' + (note.alias || note.shortid))
}

await note.increment('viewcount')
Expand Down Expand Up @@ -143,16 +92,16 @@ async function showPublishNote (req, res) {
res.render('pretty.ejs', data)
}

async function noteActions (req, res) {
exports.noteActions = async (req, res) => {
const noteId = req.params.noteId

const note = await getNoteById(noteId)
const note = await service.getNote(noteId)

if (!note) {
return errorNotFound(req, res)
}

if (!canViewNote(note, req.isAuthenticated(), req.user ? req.user.id : null)) {
if (!service.canViewNote(note, req.isAuthenticated(), req.user ? req.user.id : null)) {
return errorForbidden(req, res)
}

Expand Down Expand Up @@ -187,41 +136,13 @@ async function noteActions (req, res) {
actionPandoc(req, res, note)
break
default:
return res.redirect(config.serverURL + '/' + noteId)
}
}

async function getMyNoteList (userId, callback) {
const myNotes = await Note.findAll({
where: {
ownerId: userId
}
})
if (!myNotes) {
return callback(null, null)
}
try {
const myNoteList = myNotes.map(note => ({
id: Note.encodeNoteId(note.id),
text: note.title,
tags: Note.parseNoteInfo(note.content).tags,
createdAt: note.createdAt,
lastchangeAt: note.lastchangeAt,
shortId: note.shortid
}))
if (config.debug) {
logger.info('Parse myNoteList success: ' + userId)
}
return callback(null, myNoteList)
} catch (err) {
logger.error('Parse myNoteList failed')
return callback(err, null)
return res.redirect(config.serviceerURL + '/' + noteId)
}
}

function listMyNotes (req, res) {
exports.listMyNotes = (req, res) => {
if (req.isAuthenticated()) {
getMyNoteList(req.user.id, (err, myNoteList) => {
service.getMyNoteList(req.user.id, (err, myNoteList) => {
if (err) return errorInternalError(req, res)
if (!myNoteList) return errorNotFound(req, res)
res.send({
Expand All @@ -233,7 +154,7 @@ function listMyNotes (req, res) {
}
}

const deleteNote = async (req, res) => {
exports.deleteNote = async (req, res) => {
if (req.isAuthenticated()) {
const noteId = await Note.parseNoteIdAsync(req.params.noteId)
try {
Expand Down Expand Up @@ -267,7 +188,7 @@ const deleteNote = async (req, res) => {
}
}

const updateNote = async (req, res) => {
exports.updateNote = async (req, res) => {
if (req.isAuthenticated() || config.allowAnonymousEdits) {
const noteId = await Note.parseNoteIdAsync(req.params.noteId)
try {
Expand Down Expand Up @@ -332,9 +253,43 @@ const updateNote = async (req, res) => {
}
}

exports.showNote = showNote
exports.showPublishNote = showPublishNote
exports.noteActions = noteActions
exports.listMyNotes = listMyNotes
exports.deleteNote = deleteNote
exports.updateNote = updateNote
exports.updateNoteAlias = async (req, res) => {
const originAliasOrNoteId = req.params.originAliasOrNoteId
const alias = req.body.alias || ''
const userId = req.user ? req.user.id : null
const note = await service.getNote(originAliasOrNoteId)
.catch((err) => {
logger.error('get note failed:' + err.message)
return false
})

if (!note) {
logger.error('update note alias failed: note not found.')
return res.status(404).json({ status: 'error', message: 'Not found' })
}

if (note.ownerId !== userId) {
return res.status(403).json({ status: 'error', message: 'Forbidden' })
}

const result = await service.updateAlias(originAliasOrNoteId, alias)
.catch((err) => {
logger.error('update note alias failed:' + err.message)
return false
})

if (!result) {
return res.status(500).json({ status: 'error', message: 'Internal Error' })
}

const { isSuccess, errorMessage } = result

if (!isSuccess) {
res.status(400).json({ status: 'error', message: errorMessage })
return
}

res.send({
status: 'ok'
})
}
150 changes: 150 additions & 0 deletions lib/note/service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@

'use strict'
const { Note, User } = require('../models')
const config = require('../config')
const logger = require('../logger')
const realtime = require('../realtime/realtime')
const { updateHistory } = require('../history')

const EXIST_WEB_ROUTES = ['', 'new', 'me', 'history', '403', '404', '500', 'config']
const FORBIDDEN_ALIASES = [...EXIST_WEB_ROUTES, ...config.forbiddenNoteIDs]

exports.getNote = async (originAliasOrNoteId, { includeUser } = { includeUser: false }) => {
const id = await Note.parseNoteIdAsync(originAliasOrNoteId)

const includes = []

if (includeUser) {
includes.push({
model: User,
as: 'owner'
}, {
model: User,
as: 'lastchangeuser'
})
}

const note = await Note.findOne({
where: {
id: id
},
include: includes
})
return note
}

exports.createNote = async (userId, noteAlias) => {
if (!config.allowAnonymous && !userId) {
throw new Error('can not create note')
}

const note = await Note.create({
ownerId: userId,
alias: noteAlias
})

if (userId) {
updateHistory(userId, note)
}

return note
}

exports.canViewNote = (note, isLogin, userId) => {
if (note.permission === 'private') {
return note.ownerId === userId
}
if (note.permission === 'limited' || note.permission === 'protected') {
return isLogin
}
return true
}

exports.getMyNoteList = async (userId, callback) => {
const myNotes = await Note.findAll({
where: {
ownerId: userId
}
})
if (!myNotes) {
return callback(null, null)
}
try {
const myNoteList = myNotes.map(note => ({
id: Note.encodeNoteId(note.id),
text: note.title,
tags: Note.parseNoteInfo(note.content).tags,
createdAt: note.createdAt,
lastchangeAt: note.lastchangeAt,
shortId: note.shortid
}))
if (config.debug) {
logger.info('Parse myNoteList success: ' + userId)
}
return callback(null, myNoteList)
} catch (err) {
logger.error('Parse myNoteList failed')
return callback(err, null)
}
}

const sanitizeAlias = (alias) => {
return alias.replace(/( |\/)/g, '')
}

const checkAliasValid = async (originAliasOrNoteId, alias) => {
const sanitizedAlias = sanitizeAlias(alias)
if (FORBIDDEN_ALIASES.includes(sanitizedAlias)) {
return {
isValid: false,
errorMessage: 'The url is exist.'
}
}

if (!/^[0-9a-z-_]+$/.test(alias)) {
return {
isValid: false,
errorMessage: 'The url must be lowercase letters, decimal digits, hyphen or underscore.'
}
}

const conflictNote = await exports.getNote(alias)
const note = await exports.getNote(originAliasOrNoteId)

if (conflictNote && conflictNote.id !== note.id) {
return {
isValid: false,
errorMessage: 'The url is exist.'
}
}

return {
isValid: true
}
}

exports.updateAlias = async (originAliasOrNoteId, alias) => {
const sanitizedAlias = sanitizeAlias(alias)
const note = await exports.getNote(originAliasOrNoteId)
const { isValid, errorMessage } = await checkAliasValid(originAliasOrNoteId, alias)
if (!isValid) {
return {
isSuccess: false,
errorMessage
}
}

const updatedNote = await note.update({
alias: sanitizedAlias,
lastchangeAt: Date.now()
})

realtime.io.to(updatedNote.id)
.emit('alias updated', {
alias: updatedNote.alias
})

return {
isSuccess: true
}
}
3 changes: 2 additions & 1 deletion lib/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ function responseCodiMD (res, note) {
'X-Robots-Tag': 'noindex, nofollow' // prevent crawling
})
res.render('codimd.ejs', {
title: title
title: title,
alias: note.alias
})
}

Expand Down
Loading