license-server/src/controllers/articles-controller.js

466 lines
12 KiB
JavaScript

const slug = require('slug')
const uuid = require('uuid')
const humps = require('humps')
const _ = require('lodash')
const comments = require('./comments-controller')
const {ValidationError} = require('lib/errors')
const {getSelect} = require('lib/utils')
const {articleFields, userFields, relationsMaps} = require('lib/relations-map')
const joinJs = require('join-js').default
module.exports = {
async bySlug (slug, ctx, next) {
if (!slug) {
ctx.throw(404)
}
const article = await ctx.app.db('articles')
.first()
.where({slug})
if (!article) {
ctx.throw(404)
}
const tagsRelations = await ctx.app.db('articles_tags')
.select()
.where({article: article.id})
let tagList = []
if (tagsRelations && tagsRelations.length > 0) {
tagList = await ctx.app.db('tags')
.select()
.whereIn('id', tagsRelations.map(r => r.tag))
tagList = tagList.map(t => t.name)
}
article.tagList = tagList
article.favorited = false
const author = await ctx.app.db('users')
.first('username', 'bio', 'image', 'id')
.where({id: article.author})
article.author = author
article.author.following = false
const {user} = ctx.state
if (user && user.username !== article.author.username) {
const res = await ctx.app.db('followers')
.where({user: article.author.id, follower: user.id})
.select()
if (res.length > 0) {
article.author.following = true
}
}
let favorites = []
if (user) {
favorites = await ctx.app.db('favorites')
.where({user: user.id, article: article.id})
.select()
if (favorites.length > 0) {
article.favorited = true
}
}
ctx.params.article = article
ctx.params.favorites = favorites
ctx.params.author = author
ctx.params.tagList = tagList
ctx.params.tagsRelations = tagsRelations
await next()
delete ctx.params.author.id
},
async get (ctx) {
const {user} = ctx.state
const {offset, limit, tag, author, favorited} = ctx.query
let articlesQuery = ctx.app.db('articles')
.select(
...getSelect('articles', 'article', articleFields),
...getSelect('users', 'author', userFields),
...getSelect('articles_tags', 'tag', ['id']),
...getSelect('tags', 'tag', ['id', 'name']),
'favorites.id as article_favorited',
'followers.id as author_following'
)
.limit(limit)
.offset(offset)
.orderBy('articles.created_at', 'desc')
let countQuery = ctx.app.db('articles').count()
if (author && author.length > 0) {
const subQuery = ctx.app.db('users')
.select('id')
.whereIn('username', author)
articlesQuery = articlesQuery.andWhere('articles.author', 'in', subQuery)
countQuery = countQuery.andWhere('articles.author', 'in', subQuery)
}
if (favorited && favorited.length > 0) {
const subQuery = ctx.app.db('favorites')
.select('article')
.whereIn(
'user',
ctx.app.db('users').select('id').whereIn('username', favorited)
)
articlesQuery = articlesQuery.andWhere('articles.id', 'in', subQuery)
countQuery = countQuery.andWhere('articles.id', 'in', subQuery)
}
if (tag && tag.length > 0) {
const subQuery = ctx.app.db('articles_tags')
.select('article')
.whereIn(
'tag',
ctx.app.db('tags').select('id').whereIn('name', tag)
)
articlesQuery = articlesQuery.andWhere('articles.id', 'in', subQuery)
countQuery = countQuery.andWhere('articles.id', 'in', subQuery)
}
articlesQuery = articlesQuery
.leftJoin('users', 'articles.author', 'users.id')
.leftJoin('articles_tags', 'articles.id', 'articles_tags.article')
.leftJoin('tags', 'articles_tags.tag', 'tags.id')
.leftJoin('favorites', function () {
this.on('articles.id', '=', 'favorites.article')
.onIn('favorites.user', [user && user.id])
})
.leftJoin('followers', function () {
this.on('articles.author', '=', 'followers.user')
.onIn('followers.follower', [user && user.id])
})
let [articles, [countRes]] = await Promise.all([articlesQuery, countQuery])
articles = joinJs
.map(articles, relationsMaps, 'articleMap', 'article_')
.map(a => {
a.favorited = Boolean(a.favorited)
a.tagList = a.tagList.map(t => t.name)
a.author.following = Boolean(a.author.following)
delete a.author.id
return a
})
let articlesCount = countRes.count || countRes['count(*)']
articlesCount = Number(articlesCount)
ctx.body = {articles, articlesCount}
},
async getOne (ctx) {
ctx.body = {article: ctx.params.article}
},
async post (ctx) {
const {body} = ctx.request
let {article} = body
let tags
const opts = {abortEarly: false}
article.id = uuid()
article.author = ctx.state.user.id
article = await ctx.app.schemas.article.validate(article, opts)
article.slug = slug(_.get(article, 'title', ''), {lower: true})
if (article.tagList && article.tagList.length > 0) {
tags = await Promise.all(
article.tagList
.map(t => ({id: uuid(), name: t}))
.map(t => ctx.app.schemas.tag.validate(t, opts))
)
}
try {
await ctx.app.db('articles')
.insert(humps.decamelizeKeys(_.omit(article, ['tagList'])))
} catch (err) {
if (Number(err.errno) === 19 || Number(err.code) === 23505) {
article.slug = article.slug + '-' + uuid().substr(-6)
await ctx.app.db('articles')
.insert(humps.decamelizeKeys(_.omit(article, ['tagList'])))
} else {
throw err
}
}
if (tags && tags.length) {
for (var i = 0; i < tags.length; i++) {
try {
await ctx.app.db('tags').insert(humps.decamelizeKeys(tags[i]))
} catch (err) {
if (Number(err.errno) !== 19 && Number(err.code) !== 23505) {
throw err
}
}
}
tags = await ctx.app.db('tags')
.select()
.whereIn('name', tags.map(t => t.name))
const relations = tags.map(t => ({
id: uuid(),
tag: t.id,
article: article.id
}))
await ctx.app.db('articles_tags').insert(relations)
}
article.favorited = false
article.author = _.pick(ctx.state.user, ['username', 'bio', 'image'])
article.author.following = false
ctx.body = {article}
},
async put (ctx) {
const {article} = ctx.params
if (article.author.id !== ctx.state.user.id) {
ctx.throw(403, new ValidationError(['not owned by user'], '', 'article'))
}
const {body} = ctx.request
let {article: fields = {}} = body
const opts = {abortEarly: false}
let newArticle = Object.assign({}, article, fields)
newArticle.author = newArticle.author.id
newArticle = await ctx.app.schemas.article.validate(
humps.camelizeKeys(newArticle),
opts
)
if (fields.title) {
newArticle.slug = slug(_.get(newArticle, 'title', ''), {lower: true})
}
newArticle.updatedAt = new Date().toISOString()
try {
await ctx.app.db('articles')
.update(humps.decamelizeKeys(
_.pick(
newArticle,
['title', 'slug', 'body', 'description', 'updatedAt']
)
))
.where({id: article.id})
} catch (err) {
if (Number(err.errno) === 19 || Number(err.code) === 23505) {
newArticle.slug = newArticle.slug + '-' + uuid().substr(-6)
await ctx.app.db('articles')
.update(humps.decamelizeKeys(
_.pick(
newArticle,
['title', 'slug', 'body', 'description', 'updatedAt']
)
))
.where({id: article.id})
} else {
throw err
}
}
if (fields.tagList && fields.tagList.length === 0) {
await ctx.app.db('articles_tags')
.del()
.where({article: article.id})
}
if (fields.tagList && fields.tagList.length > 0) {
if (_.difference(article.tagList).length || _.difference(fields.tagList).length) {
await ctx.app.db('articles_tags')
.del()
.where({article: article.id})
let tags = await Promise.all(
newArticle.tagList
.map(t => ({id: uuid(), name: t}))
.map(t => ctx.app.schemas.tag.validate(t, opts))
)
for (var i = 0; i < tags.length; i++) {
try {
await ctx.app.db('tags').insert(humps.decamelizeKeys(tags[i]))
} catch (err) {
if (Number(err.errno) !== 19 && Number(err.code) !== 23505) {
throw err
}
}
}
tags = await ctx.app.db('tags')
.select()
.whereIn('name', tags.map(t => t.name))
const relations = tags.map(t => ({
id: uuid(),
tag: t.id,
article: article.id
}))
await ctx.app.db('articles_tags').insert(relations)
}
}
newArticle.author = ctx.params.author
newArticle.favorited = article.favorited
ctx.body = {article: newArticle}
},
async del (ctx) {
const {article} = ctx.params
if (article.author.id !== ctx.state.user.id) {
ctx.throw(403, new ValidationError(['not owned by user'], '', 'article'))
}
await Promise.all([
ctx.app.db('favorites')
.del()
.where({user: ctx.state.user.id, article: article.id}),
ctx.app.db('articles_tags')
.del()
.where({article: article.id}),
ctx.app.db('articles')
.del()
.where({id: article.id})
])
ctx.body = {}
},
feed: {
async get (ctx) {
const {user} = ctx.state
const {offset, limit} = ctx.query
const followedQuery = ctx.app.db('followers')
.pluck('user')
.where({follower: user.id})
let [articles, [countRes]] = await Promise.all([
ctx.app.db('articles')
.select(
...getSelect('articles', 'article', articleFields),
...getSelect('users', 'author', userFields),
...getSelect('articles_tags', 'tag', ['id']),
...getSelect('tags', 'tag', ['id', 'name']),
'favorites.id as article_favorited'
)
.whereIn('articles.author', followedQuery)
.limit(limit)
.offset(offset)
.orderBy('articles.created_at', 'desc')
.leftJoin('users', 'articles.author', 'users.id')
.leftJoin('articles_tags', 'articles.id', 'articles_tags.article')
.leftJoin('tags', 'articles_tags.tag', 'tags.id')
.leftJoin('favorites', function () {
this.on('articles.id', '=', 'favorites.article')
.onIn('favorites.user', [user && user.id])
}),
ctx.app.db('articles').count().whereIn('author', followedQuery)
])
articles = joinJs
.map(articles, relationsMaps, 'articleMap', 'article_')
.map(a => {
a.favorited = Boolean(a.favorited)
a.tagList = a.tagList.map(t => t.name)
a.author.following = true
delete a.author.id
return a
})
let articlesCount = countRes.count || countRes['count(*)']
articlesCount = Number(articlesCount)
ctx.body = {articles, articlesCount}
}
},
favorite: {
async post (ctx) {
const {article} = ctx.params
if (article.favorited) {
ctx.body = {article: ctx.params.article}
return
}
await Promise.all([
ctx.app.db('favorites').insert({
id: uuid(),
user: ctx.state.user.id,
article: article.id
}),
ctx.app.db('articles')
.increment('favorites_count', 1)
.where({id: article.id})
])
article.favorited = true
article.favorites_count = Number(article.favorites_count) + 1
ctx.body = {article: ctx.params.article}
},
async del (ctx) {
const {article} = ctx.params
if (!article.favorited) {
ctx.body = {article: ctx.params.article}
return
}
await Promise.all([
ctx.app.db('favorites')
.del()
.where({user: ctx.state.user.id, article: article.id}),
ctx.app.db('articles')
.decrement('favorites_count', 1)
.where({id: article.id})
])
article.favorited = false
article.favorites_count = Number(article.favorites_count) - 1
ctx.body = {article: ctx.params.article}
}
},
comments
}