Skip to content
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