/** * Web Client Server */ import * as os from 'os'; import ms = require('ms'); import * as Koa from 'koa'; import * as Router from 'koa-router'; import * as send from 'koa-send'; import * as favicon from 'koa-favicon'; import * as views from 'koa-views'; import { ObjectID } from 'mongodb'; import docs from './docs'; import packFeed from './feed'; import User from '../../models/user'; import parseAcct from '../../misc/acct/parse'; import config from '../../config'; import Note, { pack as packNote } from '../../models/note'; import getNoteSummary from '../../misc/get-note-summary'; import fetchMeta from '../../misc/fetch-meta'; import Emoji from '../../models/emoji'; const pkg = require('../../../package.json'); const client = `${__dirname}/../../client/`; // Init app const app = new Koa(); // Init renderer app.use(views(__dirname + '/views', { extension: 'pug', options: { config } })); // Serve favicon app.use(favicon(`${client}/assets/favicon.ico`)); // Common request handler app.use(async (ctx, next) => { // IFrameの中に入れられないようにする ctx.set('X-Frame-Options', 'DENY'); await next(); }); // Init router const router = new Router(); //#region static assets router.get('/assets/*', async ctx => { await send(ctx, ctx.path, { root: client, maxage: ms('7 days'), immutable: true }); }); // Apple touch icon router.get('/apple-touch-icon.png', async ctx => { await send(ctx, '/assets/apple-touch-icon.png', { root: client }); }); // ServiceWorker router.get(/^\/sw\.(.+?)\.js$/, async ctx => { await send(ctx, `/assets/sw.${ctx.params[0]}.js`, { root: client }); }); // Manifest router.get('/manifest.json', async ctx => { await send(ctx, '/assets/manifest.json', { root: client }); }); //#endregion // Docs router.use('/docs', docs.routes()); // URL preview endpoint router.get('/url', require('./url-preview')); const getFeed = async (acct: string) => { const { username, host } = parseAcct(acct); const user = await User.findOne({ usernameLower: username.toLowerCase(), host }); return user && await packFeed(user); }; // Atom router.get('/@:user.atom', async ctx => { const feed = await getFeed(ctx.params.user); if (feed) { ctx.set('Content-Type', 'application/atom+xml; charset=utf-8'); ctx.body = feed.atom1(); } else { ctx.status = 404; } }); // RSS router.get('/@:user.rss', async ctx => { const feed = await getFeed(ctx.params.user); if (feed) { ctx.set('Content-Type', 'application/rss+xml; charset=utf-8'); ctx.body = feed.rss2(); } else { ctx.status = 404; } }); // JSON router.get('/@:user.json', async ctx => { const feed = await getFeed(ctx.params.user); if (feed) { ctx.set('Content-Type', 'application/json; charset=utf-8'); ctx.body = feed.json1(); } else { ctx.status = 404; } }); //#region for crawlers // User router.get('/@:user', async (ctx, next) => { const { username, host } = parseAcct(ctx.params.user); const user = await User.findOne({ usernameLower: username.toLowerCase(), host }); if (user != null) { await ctx.render('user', { user }); ctx.set('Cache-Control', 'public, max-age=180'); } else { // リモートユーザーなので await next(); } }); router.get('/users/:user', async ctx => { if (!ObjectID.isValid(ctx.params.user)) { ctx.status = 404; return; } const userId = new ObjectID(ctx.params.user); const user = await User.findOne({ _id: userId, host: null }); if (user === null) { ctx.status = 404; return; } ctx.redirect(`/@${user.username}${ user.host == null ? '' : '@' + user.host}`); }); // Note router.get('/notes/:note', async ctx => { if (ObjectID.isValid(ctx.params.note)) { const note = await Note.findOne({ _id: ctx.params.note }); if (note) { const _note = await packNote(note); await ctx.render('note', { note: _note, summary: getNoteSummary(_note) }); if (['public', 'home'].includes(note.visibility)) { ctx.set('Cache-Control', 'public, max-age=180'); } else { ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); } return; } } ctx.status = 404; }); //#endregion router.get('/info', async ctx => { const meta = await fetchMeta(); const emojis = await Emoji.find({ host: null }, { fields: { _id: false } }); await ctx.render('info', { version: pkg.version, machine: os.hostname(), os: os.platform(), node: process.version, cpu: { model: os.cpus()[0].model, cores: os.cpus().length }, emojis: emojis, meta: meta }); }); // Render base html for all requests router.get('*', async ctx => { const meta = await fetchMeta(); await ctx.render('base', { img: meta.bannerUrl }); ctx.set('Cache-Control', 'public, max-age=86400'); }); // Register router app.use(router.routes()); module.exports = app;