2020-01-08 20:04:43 +00:00
|
|
|
#!/usr/bin/env node
|
|
|
|
|
2020-01-08 16:37:52 +00:00
|
|
|
'use strict'
|
|
|
|
|
|
|
|
// Koa application to provide HTTP interface.
|
|
|
|
|
|
|
|
const cli = require('./cli')
|
|
|
|
const config = cli()
|
|
|
|
|
|
|
|
if (config.debug) {
|
|
|
|
process.env.DEBUG = 'oasis,oasis:*'
|
|
|
|
}
|
|
|
|
|
|
|
|
// HACK: We must get the CLI config and then delete environment variables.
|
|
|
|
// This hides arguments from other upstream modules who might parse them.
|
|
|
|
//
|
|
|
|
// Unfortunately some modules think that our CLI options are meant for them,
|
|
|
|
// and since there's no way to disable that behavior (!) we have to hide them
|
|
|
|
// manually by setting the args property to an empty array.
|
|
|
|
process.argv = []
|
|
|
|
|
|
|
|
const http = require('./http')
|
|
|
|
|
|
|
|
const debug = require('debug')('oasis')
|
|
|
|
const fs = require('fs').promises
|
|
|
|
const koaBody = require('koa-body')
|
2020-01-08 21:56:52 +00:00
|
|
|
const { nav, ul, li, a } = require('hyperaxe')
|
2020-01-08 16:37:52 +00:00
|
|
|
const open = require('open')
|
|
|
|
const path = require('path')
|
2020-01-08 21:56:52 +00:00
|
|
|
const pull = require('pull-stream')
|
2020-01-08 16:37:52 +00:00
|
|
|
const requireStyle = require('require-style')
|
|
|
|
const router = require('koa-router')()
|
2020-01-08 21:56:52 +00:00
|
|
|
const ssbMentions = require('ssb-mentions')
|
2020-01-08 16:37:52 +00:00
|
|
|
const ssbRef = require('ssb-ref')
|
2020-01-08 21:56:52 +00:00
|
|
|
const { themeNames } = require('@fraction/base16-css')
|
2020-01-08 16:37:52 +00:00
|
|
|
|
2020-01-09 17:04:46 +00:00
|
|
|
const ssb = require('./ssb')
|
|
|
|
|
|
|
|
// Create "cooler"-style interface from SSB connection.
|
|
|
|
// This handle is passed to the models for their convenience.
|
2020-01-09 17:26:17 +00:00
|
|
|
const cooler = ssb({ offline: config.offline })
|
2020-01-09 17:04:46 +00:00
|
|
|
|
2020-01-08 20:38:47 +00:00
|
|
|
const {
|
2020-01-08 21:56:52 +00:00
|
|
|
about,
|
2020-01-08 20:38:47 +00:00
|
|
|
blob,
|
2020-01-08 21:56:52 +00:00
|
|
|
friend,
|
2020-01-08 20:38:47 +00:00
|
|
|
meta,
|
2020-01-08 21:56:52 +00:00
|
|
|
post,
|
|
|
|
vote
|
2020-01-09 17:04:46 +00:00
|
|
|
} = require('./models')(cooler)
|
2020-01-08 21:56:52 +00:00
|
|
|
|
|
|
|
const {
|
|
|
|
authorView,
|
|
|
|
commentView,
|
|
|
|
listView,
|
|
|
|
markdownView,
|
|
|
|
metaView,
|
|
|
|
publicView,
|
|
|
|
replyView,
|
|
|
|
searchView
|
|
|
|
} = require('./views')
|
|
|
|
|
|
|
|
let sharp
|
|
|
|
|
|
|
|
try {
|
|
|
|
sharp = require('sharp')
|
|
|
|
} catch (e) {
|
|
|
|
// Optional dependency
|
|
|
|
}
|
2020-01-08 16:37:52 +00:00
|
|
|
|
|
|
|
const defaultTheme = 'atelier-sulphurPool-light'.toLowerCase()
|
|
|
|
|
|
|
|
// TODO: refactor
|
|
|
|
const start = async () => {
|
|
|
|
const filePath = path.join(__dirname, '..', 'README.md')
|
|
|
|
config.readme = await fs.readFile(filePath, 'utf8')
|
|
|
|
}
|
|
|
|
start()
|
|
|
|
|
|
|
|
router
|
|
|
|
.param('imageSize', (imageSize, ctx, next) => {
|
|
|
|
const size = Number(imageSize)
|
|
|
|
const isInteger = size % 1 === 0
|
|
|
|
const overMinSize = size > 2
|
|
|
|
const underMaxSize = size <= 256
|
|
|
|
ctx.assert(isInteger && overMinSize && underMaxSize, 'Invalid image size')
|
|
|
|
return next()
|
|
|
|
})
|
|
|
|
.param('blobId', (blobId, ctx, next) => {
|
|
|
|
ctx.assert(ssbRef.isBlob(blobId), 400, 'Invalid blob link')
|
|
|
|
return next()
|
|
|
|
})
|
|
|
|
.param('message', (message, ctx, next) => {
|
|
|
|
ctx.assert(ssbRef.isMsg(message), 400, 'Invalid message link')
|
|
|
|
return next()
|
|
|
|
})
|
|
|
|
.param('feed', (message, ctx, next) => {
|
|
|
|
ctx.assert(ssbRef.isFeedId(message), 400, 'Invalid feed link')
|
|
|
|
return next()
|
|
|
|
})
|
|
|
|
.get('/', async (ctx) => {
|
|
|
|
ctx.redirect('/public/popular/day')
|
|
|
|
})
|
|
|
|
.get('/public/popular/:period', async (ctx) => {
|
|
|
|
const { period } = ctx.params
|
2020-01-08 21:56:52 +00:00
|
|
|
const publicPopular = async ({ period }) => {
|
|
|
|
const messages = await post.popular({ period })
|
|
|
|
|
|
|
|
const option = (somePeriod) =>
|
|
|
|
li(
|
|
|
|
period === somePeriod
|
|
|
|
? a({ class: 'current', href: `./${somePeriod}` }, somePeriod)
|
|
|
|
: a({ href: `./${somePeriod}` }, somePeriod)
|
|
|
|
)
|
|
|
|
|
|
|
|
const prefix = nav(
|
|
|
|
ul(
|
|
|
|
option('day'),
|
|
|
|
option('week'),
|
|
|
|
option('month'),
|
|
|
|
option('year')
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
return publicView({
|
|
|
|
messages,
|
|
|
|
prefix
|
|
|
|
})
|
|
|
|
}
|
2020-01-08 20:38:47 +00:00
|
|
|
ctx.body = await publicPopular({ period })
|
2020-01-08 16:37:52 +00:00
|
|
|
})
|
|
|
|
.get('/public/latest', async (ctx) => {
|
2020-01-08 21:56:52 +00:00
|
|
|
const publicLatest = async () => {
|
|
|
|
const messages = await post.latest()
|
|
|
|
return publicView({ messages })
|
|
|
|
}
|
2020-01-08 20:38:47 +00:00
|
|
|
ctx.body = await publicLatest()
|
2020-01-08 16:37:52 +00:00
|
|
|
})
|
|
|
|
.get('/author/:feed', async (ctx) => {
|
|
|
|
const { feed } = ctx.params
|
2020-01-08 21:56:52 +00:00
|
|
|
const author = async (feedId) => {
|
|
|
|
const description = await about.description(feedId)
|
|
|
|
const name = await about.name(feedId)
|
|
|
|
const image = await about.image(feedId)
|
|
|
|
const messages = await post.fromFeed(feedId)
|
|
|
|
const relationship = await friend.getRelationship(feedId)
|
|
|
|
|
|
|
|
const avatarUrl = `/image/256/${encodeURIComponent(image)}`
|
|
|
|
|
|
|
|
return authorView({
|
|
|
|
feedId,
|
|
|
|
messages,
|
|
|
|
name,
|
|
|
|
description,
|
|
|
|
avatarUrl,
|
|
|
|
relationship
|
|
|
|
})
|
|
|
|
}
|
2020-01-08 16:37:52 +00:00
|
|
|
ctx.body = await author(feed)
|
|
|
|
})
|
|
|
|
.get('/search/', async (ctx) => {
|
|
|
|
const { query } = ctx.query
|
2020-01-08 21:56:52 +00:00
|
|
|
const search = async ({ query }) => {
|
|
|
|
if (typeof query === 'string') {
|
|
|
|
// https://github.com/ssbc/ssb-search/issues/7
|
|
|
|
query = query.toLowerCase()
|
|
|
|
}
|
|
|
|
|
|
|
|
const messages = await post.search({ query })
|
|
|
|
|
|
|
|
return searchView({ messages, query })
|
|
|
|
}
|
2020-01-08 20:38:47 +00:00
|
|
|
ctx.body = await search({ query })
|
2020-01-08 16:37:52 +00:00
|
|
|
})
|
|
|
|
.get('/inbox', async (ctx) => {
|
2020-01-08 21:56:52 +00:00
|
|
|
const inbox = async () => {
|
|
|
|
const messages = await post.inbox()
|
|
|
|
|
|
|
|
return listView({ messages })
|
|
|
|
}
|
2020-01-08 20:38:47 +00:00
|
|
|
ctx.body = await inbox()
|
2020-01-08 16:37:52 +00:00
|
|
|
})
|
|
|
|
.get('/hashtag/:channel', async (ctx) => {
|
|
|
|
const { channel } = ctx.params
|
2020-01-08 21:56:52 +00:00
|
|
|
const hashtag = async (channel) => {
|
|
|
|
const messages = await post.fromHashtag(channel)
|
|
|
|
|
|
|
|
return listView({ messages })
|
|
|
|
}
|
2020-01-08 16:37:52 +00:00
|
|
|
ctx.body = await hashtag(channel)
|
|
|
|
})
|
|
|
|
.get('/theme.css', (ctx) => {
|
|
|
|
const theme = ctx.cookies.get('theme') || defaultTheme
|
|
|
|
|
|
|
|
const packageName = '@fraction/base16-css'
|
|
|
|
const filePath = `${packageName}/src/base16-${theme}.css`
|
|
|
|
ctx.type = 'text/css'
|
|
|
|
ctx.body = requireStyle(filePath)
|
|
|
|
})
|
|
|
|
.get('/profile/', async (ctx) => {
|
2020-01-08 21:56:52 +00:00
|
|
|
const profile = async () => {
|
|
|
|
const myFeedId = await meta.myFeedId()
|
|
|
|
|
|
|
|
const description = await about.description(myFeedId)
|
|
|
|
const name = await about.name(myFeedId)
|
|
|
|
const image = await about.image(myFeedId)
|
|
|
|
|
|
|
|
const messages = await post.fromFeed(myFeedId)
|
|
|
|
|
|
|
|
const avatarUrl = `/image/256/${encodeURIComponent(image)}`
|
|
|
|
|
|
|
|
return authorView({
|
|
|
|
feedId: myFeedId,
|
|
|
|
messages,
|
|
|
|
name,
|
|
|
|
description,
|
|
|
|
avatarUrl,
|
|
|
|
relationship: null
|
|
|
|
})
|
|
|
|
}
|
2020-01-08 16:37:52 +00:00
|
|
|
ctx.body = await profile()
|
|
|
|
})
|
|
|
|
.get('/json/:message', async (ctx) => {
|
|
|
|
const { message } = ctx.params
|
|
|
|
ctx.type = 'application/json'
|
2020-01-08 21:56:52 +00:00
|
|
|
const json = async (message) => {
|
|
|
|
const json = await meta.get(message)
|
|
|
|
return JSON.stringify(json, null, 2)
|
|
|
|
}
|
2020-01-08 16:37:52 +00:00
|
|
|
ctx.body = await json(message)
|
|
|
|
})
|
|
|
|
.get('/blob/:blobId', async (ctx) => {
|
|
|
|
const { blobId } = ctx.params
|
2020-01-08 21:56:52 +00:00
|
|
|
const getBlob = async ({ blobId }) => {
|
|
|
|
const bufferSource = await blob.get({ blobId })
|
|
|
|
|
|
|
|
debug('got buffer source')
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
pull(
|
|
|
|
bufferSource,
|
|
|
|
pull.collect(async (err, bufferArray) => {
|
|
|
|
if (err) {
|
|
|
|
await blob.want({ blobId })
|
|
|
|
resolve(Buffer.alloc(0))
|
|
|
|
} else {
|
|
|
|
const buffer = Buffer.concat(bufferArray)
|
|
|
|
resolve(buffer)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
ctx.body = await getBlob({ blobId })
|
2020-01-08 16:37:52 +00:00
|
|
|
|
|
|
|
if (ctx.body.length === 0) {
|
|
|
|
ctx.response.status = 404
|
|
|
|
} else {
|
|
|
|
ctx.set('Cache-Control', 'public,max-age=31536000,immutable')
|
|
|
|
}
|
|
|
|
|
|
|
|
// This prevents an auto-download when visiting the URL.
|
|
|
|
ctx.attachment(blobId, { type: 'inline' })
|
|
|
|
})
|
|
|
|
.get('/image/:imageSize/:blobId', async (ctx) => {
|
|
|
|
const { blobId, imageSize } = ctx.params
|
|
|
|
ctx.type = 'image/png'
|
2020-01-08 21:56:52 +00:00
|
|
|
const fakePixel =
|
|
|
|
Buffer.from(
|
|
|
|
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=',
|
|
|
|
'base64'
|
|
|
|
)
|
|
|
|
const fakeImage = (imageSize) =>
|
|
|
|
sharp
|
|
|
|
? sharp({
|
|
|
|
create: {
|
|
|
|
width: imageSize,
|
|
|
|
height: imageSize,
|
|
|
|
channels: 4,
|
|
|
|
background: {
|
|
|
|
r: 0, g: 0, b: 0, alpha: 0.5
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
: new Promise((resolve) => resolve(fakePixel))
|
|
|
|
const image = async ({ blobId, imageSize }) => {
|
|
|
|
const bufferSource = await blob.get({ blobId })
|
|
|
|
const fakeId = '&0000000000000000000000000000000000000000000=.sha256'
|
|
|
|
|
|
|
|
debug('got buffer source')
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
if (blobId === fakeId) {
|
|
|
|
debug('fake image')
|
|
|
|
fakeImage(imageSize).then(result => resolve(result))
|
|
|
|
} else {
|
|
|
|
debug('not fake image')
|
|
|
|
pull(
|
|
|
|
bufferSource,
|
|
|
|
pull.collect(async (err, bufferArray) => {
|
|
|
|
if (err) {
|
|
|
|
await blob.want({ blobId })
|
|
|
|
const result = fakeImage(imageSize)
|
|
|
|
debug({ result })
|
|
|
|
resolve(result)
|
|
|
|
} else {
|
|
|
|
const buffer = Buffer.concat(bufferArray)
|
|
|
|
|
|
|
|
if (sharp) {
|
|
|
|
sharp(buffer)
|
|
|
|
.resize(imageSize, imageSize)
|
|
|
|
.png()
|
|
|
|
.toBuffer()
|
|
|
|
.then((data) => {
|
|
|
|
resolve(data)
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
resolve(buffer)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2020-01-08 16:37:52 +00:00
|
|
|
ctx.body = await image({ blobId, imageSize: Number(imageSize) })
|
|
|
|
})
|
|
|
|
.get('/meta/', async (ctx) => {
|
|
|
|
const theme = ctx.cookies.get('theme') || defaultTheme
|
2020-01-08 21:56:52 +00:00
|
|
|
const getMeta = async ({ theme }) => {
|
|
|
|
const status = await meta.status()
|
|
|
|
const peers = await meta.peers()
|
|
|
|
|
|
|
|
return metaView({ status, peers, theme, themeNames })
|
|
|
|
}
|
|
|
|
ctx.body = await getMeta({ theme })
|
2020-01-08 16:37:52 +00:00
|
|
|
})
|
|
|
|
.get('/likes/:feed', async (ctx) => {
|
|
|
|
const { feed } = ctx.params
|
2020-01-08 21:56:52 +00:00
|
|
|
|
|
|
|
const likes = async ({ feed }) => {
|
|
|
|
const messages = await post.likes({ feed })
|
|
|
|
return listView({ messages })
|
|
|
|
}
|
2020-01-08 20:38:47 +00:00
|
|
|
ctx.body = await likes({ feed })
|
2020-01-08 16:37:52 +00:00
|
|
|
})
|
|
|
|
.get('/meta/readme/', async (ctx) => {
|
2020-01-08 21:56:52 +00:00
|
|
|
const status = async (text) => {
|
|
|
|
return markdownView({ text })
|
|
|
|
}
|
2020-01-08 20:38:47 +00:00
|
|
|
ctx.body = await status(config.readme)
|
2020-01-08 16:37:52 +00:00
|
|
|
})
|
|
|
|
.get('/mentions/', async (ctx) => {
|
2020-01-08 21:56:52 +00:00
|
|
|
const mentions = async () => {
|
|
|
|
const messages = await post.mentionsMe()
|
|
|
|
|
|
|
|
return listView({ messages })
|
|
|
|
}
|
2020-01-08 16:37:52 +00:00
|
|
|
ctx.body = await mentions()
|
|
|
|
})
|
|
|
|
.get('/thread/:message', async (ctx) => {
|
|
|
|
const { message } = ctx.params
|
2020-01-08 21:56:52 +00:00
|
|
|
const thread = async (message) => {
|
|
|
|
const messages = await post.fromThread(message)
|
|
|
|
debug('got %i messages', messages.length)
|
|
|
|
|
|
|
|
return listView({ messages })
|
|
|
|
}
|
|
|
|
|
2020-01-08 16:37:52 +00:00
|
|
|
ctx.body = await thread(message)
|
|
|
|
})
|
|
|
|
.get('/reply/:message', async (ctx) => {
|
|
|
|
const { message } = ctx.params
|
2020-01-08 21:56:52 +00:00
|
|
|
const reply = async (parentId) => {
|
|
|
|
const rootMessage = await post.get(parentId)
|
|
|
|
const myFeedId = await meta.myFeedId()
|
|
|
|
|
|
|
|
debug('%O', rootMessage)
|
|
|
|
const messages = [rootMessage]
|
|
|
|
|
|
|
|
return replyView({ messages, myFeedId })
|
|
|
|
}
|
2020-01-08 16:37:52 +00:00
|
|
|
ctx.body = await reply(message)
|
|
|
|
})
|
|
|
|
.get('/comment/:message', async (ctx) => {
|
|
|
|
const { message } = ctx.params
|
2020-01-08 21:56:52 +00:00
|
|
|
const comment = async (parentId) => {
|
|
|
|
const parentMessage = await post.get(parentId)
|
|
|
|
const myFeedId = await meta.myFeedId()
|
|
|
|
|
|
|
|
const hasRoot = typeof parentMessage.value.content.root === 'string' && ssbRef.isMsg(parentMessage.value.content.root)
|
|
|
|
const hasFork = typeof parentMessage.value.content.fork === 'string' && ssbRef.isMsg(parentMessage.value.content.fork)
|
|
|
|
|
|
|
|
const rootMessage = hasRoot
|
|
|
|
? hasFork
|
|
|
|
? parentMessage
|
|
|
|
: await post.get(parentMessage.value.content.root)
|
|
|
|
: parentMessage
|
|
|
|
|
|
|
|
const messages = await post.threadReplies(rootMessage.key)
|
|
|
|
|
|
|
|
messages.push(rootMessage)
|
|
|
|
|
|
|
|
return commentView({ messages, myFeedId, parentMessage })
|
|
|
|
}
|
2020-01-08 16:37:52 +00:00
|
|
|
ctx.body = await comment(message)
|
|
|
|
})
|
|
|
|
.post('/reply/:message', koaBody(), async (ctx) => {
|
|
|
|
const { message } = ctx.params
|
|
|
|
const text = String(ctx.request.body.text)
|
2020-01-08 21:56:52 +00:00
|
|
|
const publishReply = async ({ message, text }) => {
|
|
|
|
// TODO: rename `message` to `parent` or `ancestor` or similar
|
|
|
|
const mentions = ssbMentions(text).filter((mention) =>
|
|
|
|
mention != null
|
|
|
|
) || undefined
|
|
|
|
|
|
|
|
const parent = await post.get(message)
|
|
|
|
return post.reply({
|
|
|
|
parent,
|
|
|
|
message: { text, mentions }
|
|
|
|
})
|
|
|
|
}
|
2020-01-08 16:37:52 +00:00
|
|
|
ctx.body = await publishReply({ message, text })
|
|
|
|
ctx.redirect(`/thread/${encodeURIComponent(message)}`)
|
|
|
|
})
|
|
|
|
.post('/comment/:message', koaBody(), async (ctx) => {
|
|
|
|
const { message } = ctx.params
|
|
|
|
const text = String(ctx.request.body.text)
|
2020-01-08 21:56:52 +00:00
|
|
|
const publishComment = async ({ message, text }) => {
|
|
|
|
// TODO: rename `message` to `parent` or `ancestor` or similar
|
|
|
|
const mentions = ssbMentions(text).filter((mention) =>
|
|
|
|
mention != null
|
|
|
|
) || undefined
|
|
|
|
const parent = await meta.get(message)
|
|
|
|
|
|
|
|
return post.comment({
|
|
|
|
parent,
|
|
|
|
message: { text, mentions }
|
|
|
|
})
|
|
|
|
}
|
2020-01-08 16:37:52 +00:00
|
|
|
ctx.body = await publishComment({ message, text })
|
|
|
|
ctx.redirect(`/thread/${encodeURIComponent(message)}`)
|
|
|
|
})
|
|
|
|
.post('/publish/', koaBody(), async (ctx) => {
|
|
|
|
const text = String(ctx.request.body.text)
|
2020-01-08 21:56:52 +00:00
|
|
|
const publish = async ({ text }) => {
|
|
|
|
const mentions = ssbMentions(text).filter((mention) =>
|
|
|
|
mention != null
|
|
|
|
) || undefined
|
|
|
|
|
|
|
|
return post.root({
|
|
|
|
text,
|
|
|
|
mentions
|
|
|
|
})
|
|
|
|
}
|
2020-01-08 16:37:52 +00:00
|
|
|
ctx.body = await publish({ text })
|
|
|
|
ctx.redirect('/')
|
|
|
|
})
|
2020-01-11 23:34:43 +00:00
|
|
|
.post('/follow/:feed', koaBody(), async (ctx) => {
|
|
|
|
const { feed } = ctx.params
|
|
|
|
const referer = new URL(ctx.request.header.referer)
|
|
|
|
ctx.body = await friend.follow(feed)
|
|
|
|
ctx.redirect(referer)
|
|
|
|
})
|
|
|
|
.post('/unfollow/:feed', koaBody(), async (ctx) => {
|
|
|
|
const { feed } = ctx.params
|
|
|
|
const referer = new URL(ctx.request.header.referer)
|
|
|
|
ctx.body = await friend.unfollow(feed)
|
|
|
|
ctx.redirect(referer)
|
|
|
|
})
|
2020-01-08 16:37:52 +00:00
|
|
|
.post('/like/:message', koaBody(), async (ctx) => {
|
|
|
|
const { message } = ctx.params
|
|
|
|
// TODO: convert all so `message` is full message and `messageKey` is key
|
|
|
|
const messageKey = message
|
|
|
|
|
|
|
|
const voteValue = Number(ctx.request.body.voteValue)
|
|
|
|
|
|
|
|
const encoded = {
|
|
|
|
message: encodeURIComponent(message)
|
|
|
|
}
|
|
|
|
|
|
|
|
const referer = new URL(ctx.request.header.referer)
|
|
|
|
referer.hash = `centered-footer-${encoded.message}`
|
|
|
|
|
2020-01-08 21:56:52 +00:00
|
|
|
const like = async ({ messageKey, voteValue }) => {
|
|
|
|
const value = Number(voteValue)
|
|
|
|
const message = await post.get(messageKey)
|
|
|
|
|
|
|
|
const isPrivate = message.value.meta.private === true
|
|
|
|
const messageRecipients = isPrivate ? message.value.content.recps : []
|
|
|
|
|
|
|
|
const normalized = messageRecipients.map((recipient) => {
|
|
|
|
if (typeof recipient === 'string') {
|
|
|
|
return recipient
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof recipient.link === 'string') {
|
|
|
|
return recipient.link
|
|
|
|
}
|
|
|
|
|
|
|
|
return null
|
|
|
|
})
|
|
|
|
|
|
|
|
const recipients = normalized.length > 0 ? normalized : undefined
|
|
|
|
|
|
|
|
return vote.publish({ messageKey, value, recps: recipients })
|
|
|
|
}
|
2020-01-08 16:37:52 +00:00
|
|
|
ctx.body = await like({ messageKey, voteValue })
|
|
|
|
ctx.redirect(referer)
|
|
|
|
})
|
|
|
|
.post('/theme.css', koaBody(), async (ctx) => {
|
|
|
|
const theme = String(ctx.request.body.theme)
|
|
|
|
ctx.cookies.set('theme', theme)
|
|
|
|
const referer = new URL(ctx.request.header.referer)
|
|
|
|
ctx.redirect(referer)
|
|
|
|
})
|
|
|
|
|
|
|
|
const { host } = config
|
|
|
|
const { port } = config
|
2020-01-08 21:56:52 +00:00
|
|
|
const routes = router.routes()
|
2020-01-08 16:37:52 +00:00
|
|
|
|
2020-01-08 21:56:52 +00:00
|
|
|
http({ host, port, routes })
|
2020-01-08 16:37:52 +00:00
|
|
|
|
|
|
|
const uri = `http://${host}:${port}/`
|
|
|
|
|
2020-01-09 17:26:17 +00:00
|
|
|
const isDebugEnabled = debug.enabled
|
2020-01-08 16:37:52 +00:00
|
|
|
debug.enabled = true
|
|
|
|
debug(`Listening on ${uri}`)
|
2020-01-09 17:26:17 +00:00
|
|
|
debug.enabled = isDebugEnabled
|
2020-01-08 16:37:52 +00:00
|
|
|
|
|
|
|
if (config.open === true) {
|
|
|
|
open(uri)
|
|
|
|
}
|