diff options
| author | tamaina <tamaina@hotmail.co.jp> | 2018-04-11 20:27:09 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-04-11 20:27:09 +0900 |
| commit | d43fe853c3605696e2e57e240845d0fc9c284f61 (patch) | |
| tree | 838914e262c0fca5737588a7bba64e2b9f3d8e5f /src/api | |
| parent | Update README.md (diff) | |
| parent | wip #1443 (diff) | |
| download | misskey-d43fe853c3605696e2e57e240845d0fc9c284f61.tar.gz misskey-d43fe853c3605696e2e57e240845d0fc9c284f61.tar.bz2 misskey-d43fe853c3605696e2e57e240845d0fc9c284f61.zip | |
Merge pull request #1 from syuilo/master
追従
Diffstat (limited to 'src/api')
167 files changed, 0 insertions, 11229 deletions
diff --git a/src/api/api-handler.ts b/src/api/api-handler.ts deleted file mode 100644 index fb603a0e2a..0000000000 --- a/src/api/api-handler.ts +++ /dev/null @@ -1,56 +0,0 @@ -import * as express from 'express'; - -import { Endpoint } from './endpoints'; -import authenticate from './authenticate'; -import { IAuthContext } from './authenticate'; -import _reply from './reply'; -import limitter from './limitter'; - -export default async (endpoint: Endpoint, req: express.Request, res: express.Response) => { - const reply = _reply.bind(null, res); - let ctx: IAuthContext; - - // Authentication - try { - ctx = await authenticate(req); - } catch (e) { - return reply(403, 'AUTHENTICATION_FAILED'); - } - - if (endpoint.secure && !ctx.isSecure) { - return reply(403, 'ACCESS_DENIED'); - } - - if (endpoint.withCredential && ctx.user == null) { - return reply(401, 'PLZ_SIGNIN'); - } - - if (ctx.app && endpoint.kind) { - if (!ctx.app.permission.some(p => p === endpoint.kind)) { - return reply(403, 'ACCESS_DENIED'); - } - } - - if (endpoint.withCredential && endpoint.limit) { - try { - await limitter(endpoint, ctx); // Rate limit - } catch (e) { - // drop request if limit exceeded - return reply(429); - } - } - - let exec = require(`${__dirname}/endpoints/${endpoint.name}`); - - if (endpoint.withFile) { - exec = exec.bind(null, req.file); - } - - // API invoking - try { - const res = await exec(req.body, ctx.user, ctx.app, ctx.isSecure); - reply(res); - } catch (e) { - reply(400, e); - } -}; diff --git a/src/api/authenticate.ts b/src/api/authenticate.ts deleted file mode 100644 index b289959ac1..0000000000 --- a/src/api/authenticate.ts +++ /dev/null @@ -1,69 +0,0 @@ -import * as express from 'express'; -import App from './models/app'; -import { default as User, IUser } from './models/user'; -import AccessToken from './models/access-token'; -import isNativeToken from './common/is-native-token'; - -export interface IAuthContext { - /** - * App which requested - */ - app: any; - - /** - * Authenticated user - */ - user: IUser; - - /** - * Whether requested with a User-Native Token - */ - isSecure: boolean; -} - -export default (req: express.Request) => new Promise<IAuthContext>(async (resolve, reject) => { - const token = req.body['i'] as string; - - if (token == null) { - return resolve({ - app: null, - user: null, - isSecure: false - }); - } - - if (isNativeToken(token)) { - const user: IUser = await User - .findOne({ token: token }); - - if (user === null) { - return reject('user not found'); - } - - return resolve({ - app: null, - user: user, - isSecure: true - }); - } else { - const accessToken = await AccessToken.findOne({ - hash: token.toLowerCase() - }); - - if (accessToken === null) { - return reject('invalid signature'); - } - - const app = await App - .findOne({ _id: accessToken.app_id }); - - const user = await User - .findOne({ _id: accessToken.user_id }); - - return resolve({ - app: app, - user: user, - isSecure: false - }); - } -}); diff --git a/src/api/bot/core.ts b/src/api/bot/core.ts deleted file mode 100644 index ddae6405f5..0000000000 --- a/src/api/bot/core.ts +++ /dev/null @@ -1,503 +0,0 @@ -import * as EventEmitter from 'events'; -import * as bcrypt from 'bcryptjs'; - -import User, { IUser, init as initUser } from '../models/user'; - -import getPostSummary from '../../common/get-post-summary'; -import getUserSummary from '../../common/get-user-summary'; -import getNotificationSummary from '../../common/get-notification-summary'; - -import Othello, { ai as othelloAi } from '../../common/othello'; - -const hmm = [ - '?', - 'ふぅ~む...?', - 'ちょっと何言ってるかわからないです', - '「ヘルプ」と言うと利用可能な操作が確認できますよ' -]; - -/** - * Botの頭脳 - */ -export default class BotCore extends EventEmitter { - public user: IUser = null; - - private context: Context = null; - - constructor(user?: IUser) { - super(); - - this.user = user; - } - - public clearContext() { - this.setContext(null); - } - - public setContext(context: Context) { - this.context = context; - this.emit('updated'); - - if (context) { - context.on('updated', () => { - this.emit('updated'); - }); - } - } - - public export() { - return { - user: this.user, - context: this.context ? this.context.export() : null - }; - } - - protected _import(data) { - this.user = data.user ? initUser(data.user) : null; - this.setContext(data.context ? Context.import(this, data.context) : null); - } - - public static import(data) { - const bot = new BotCore(); - bot._import(data); - return bot; - } - - public async q(query: string): Promise<string> { - if (this.context != null) { - return await this.context.q(query); - } - - if (/^@[a-zA-Z0-9-]+$/.test(query)) { - return await this.showUserCommand(query); - } - - switch (query) { - case 'ping': - return 'PONG'; - - case 'help': - case 'ヘルプ': - return '利用可能なコマンド一覧です:\n' + - 'help: これです\n' + - 'me: アカウント情報を見ます\n' + - 'login, signin: サインインします\n' + - 'logout, signout: サインアウトします\n' + - 'post: 投稿します\n' + - 'tl: タイムラインを見ます\n' + - 'no: 通知を見ます\n' + - '@<ユーザー名>: ユーザーを表示します\n' + - '\n' + - 'タイムラインや通知を見た後、「次」というとさらに遡ることができます。'; - - case 'me': - return this.user ? `${this.user.name}としてサインインしています。\n\n${getUserSummary(this.user)}` : 'サインインしていません'; - - case 'login': - case 'signin': - case 'ログイン': - case 'サインイン': - if (this.user != null) return '既にサインインしていますよ!'; - this.setContext(new SigninContext(this)); - return await this.context.greet(); - - case 'logout': - case 'signout': - case 'ログアウト': - case 'サインアウト': - if (this.user == null) return '今はサインインしてないですよ!'; - this.signout(); - return 'ご利用ありがとうございました <3'; - - case 'post': - case '投稿': - if (this.user == null) return 'まずサインインしてください。'; - this.setContext(new PostContext(this)); - return await this.context.greet(); - - case 'tl': - case 'タイムライン': - if (this.user == null) return 'まずサインインしてください。'; - this.setContext(new TlContext(this)); - return await this.context.greet(); - - case 'no': - case 'notifications': - case '通知': - if (this.user == null) return 'まずサインインしてください。'; - this.setContext(new NotificationsContext(this)); - return await this.context.greet(); - - case 'guessing-game': - case '数当てゲーム': - this.setContext(new GuessingGameContext(this)); - return await this.context.greet(); - - case 'othello': - case 'オセロ': - this.setContext(new OthelloContext(this)); - return await this.context.greet(); - - default: - return hmm[Math.floor(Math.random() * hmm.length)]; - } - } - - public signin(user: IUser) { - this.user = user; - this.emit('signin', user); - this.emit('updated'); - } - - public signout() { - const user = this.user; - this.user = null; - this.emit('signout', user); - this.emit('updated'); - } - - public async refreshUser() { - this.user = await User.findOne({ - _id: this.user._id - }, { - fields: { - data: false - } - }); - - this.emit('updated'); - } - - public async showUserCommand(q: string): Promise<string> { - try { - const user = await require('../endpoints/users/show')({ - username: q.substr(1) - }, this.user); - - const text = getUserSummary(user); - - return text; - } catch (e) { - return `問題が発生したようです...: ${e}`; - } - } -} - -abstract class Context extends EventEmitter { - protected bot: BotCore; - - public abstract async greet(): Promise<string>; - public abstract async q(query: string): Promise<string>; - public abstract export(): any; - - constructor(bot: BotCore) { - super(); - this.bot = bot; - } - - public static import(bot: BotCore, data: any) { - if (data.type == 'guessing-game') return GuessingGameContext.import(bot, data.content); - if (data.type == 'othello') return OthelloContext.import(bot, data.content); - if (data.type == 'post') return PostContext.import(bot, data.content); - if (data.type == 'tl') return TlContext.import(bot, data.content); - if (data.type == 'notifications') return NotificationsContext.import(bot, data.content); - if (data.type == 'signin') return SigninContext.import(bot, data.content); - return null; - } -} - -class SigninContext extends Context { - private temporaryUser: IUser = null; - - public async greet(): Promise<string> { - return 'まずユーザー名を教えてください:'; - } - - public async q(query: string): Promise<string> { - if (this.temporaryUser == null) { - // Fetch user - const user: IUser = await User.findOne({ - username_lower: query.toLowerCase() - }, { - fields: { - data: false - } - }); - - if (user === null) { - return `${query}というユーザーは存在しませんでした... もう一度教えてください:`; - } else { - this.temporaryUser = user; - this.emit('updated'); - return `パスワードを教えてください:`; - } - } else { - // Compare password - const same = await bcrypt.compare(query, this.temporaryUser.password); - - if (same) { - this.bot.signin(this.temporaryUser); - this.bot.clearContext(); - return `${this.temporaryUser.name}さん、おかえりなさい!`; - } else { - return `パスワードが違います... もう一度教えてください:`; - } - } - } - - public export() { - return { - type: 'signin', - content: { - temporaryUser: this.temporaryUser - } - }; - } - - public static import(bot: BotCore, data: any) { - const context = new SigninContext(bot); - context.temporaryUser = data.temporaryUser; - return context; - } -} - -class PostContext extends Context { - public async greet(): Promise<string> { - return '内容:'; - } - - public async q(query: string): Promise<string> { - await require('../endpoints/posts/create')({ - text: query - }, this.bot.user); - this.bot.clearContext(); - return '投稿しましたよ!'; - } - - public export() { - return { - type: 'post' - }; - } - - public static import(bot: BotCore, data: any) { - const context = new PostContext(bot); - return context; - } -} - -class TlContext extends Context { - private next: string = null; - - public async greet(): Promise<string> { - return await this.getTl(); - } - - public async q(query: string): Promise<string> { - if (query == '次') { - return await this.getTl(); - } else { - this.bot.clearContext(); - return await this.bot.q(query); - } - } - - private async getTl() { - const tl = await require('../endpoints/posts/timeline')({ - limit: 5, - max_id: this.next ? this.next : undefined - }, this.bot.user); - - if (tl.length > 0) { - this.next = tl[tl.length - 1].id; - this.emit('updated'); - - const text = tl - .map(post => `${post.user.name}\n「${getPostSummary(post)}」`) - .join('\n-----\n'); - - return text; - } else { - return 'タイムラインに表示するものがありません...'; - } - } - - public export() { - return { - type: 'tl', - content: { - next: this.next, - } - }; - } - - public static import(bot: BotCore, data: any) { - const context = new TlContext(bot); - context.next = data.next; - return context; - } -} - -class NotificationsContext extends Context { - private next: string = null; - - public async greet(): Promise<string> { - return await this.getNotifications(); - } - - public async q(query: string): Promise<string> { - if (query == '次') { - return await this.getNotifications(); - } else { - this.bot.clearContext(); - return await this.bot.q(query); - } - } - - private async getNotifications() { - const notifications = await require('../endpoints/i/notifications')({ - limit: 5, - max_id: this.next ? this.next : undefined - }, this.bot.user); - - if (notifications.length > 0) { - this.next = notifications[notifications.length - 1].id; - this.emit('updated'); - - const text = notifications - .map(notification => getNotificationSummary(notification)) - .join('\n-----\n'); - - return text; - } else { - return '通知はありません'; - } - } - - public export() { - return { - type: 'notifications', - content: { - next: this.next, - } - }; - } - - public static import(bot: BotCore, data: any) { - const context = new NotificationsContext(bot); - context.next = data.next; - return context; - } -} - -class GuessingGameContext extends Context { - private secret: number; - private history: number[] = []; - - public async greet(): Promise<string> { - this.secret = Math.floor(Math.random() * 100); - this.emit('updated'); - return '0~100の秘密の数を当ててみてください:'; - } - - public async q(query: string): Promise<string> { - if (query == 'やめる') { - this.bot.clearContext(); - return 'やめました。'; - } - - const guess = parseInt(query, 10); - - if (isNaN(guess)) { - return '整数で推測してください。「やめる」と言うとゲームをやめます。'; - } - - const firsttime = this.history.indexOf(guess) === -1; - - this.history.push(guess); - this.emit('updated'); - - if (this.secret < guess) { - return firsttime ? `${guess}よりも小さいですね` : `もう一度言いますが${guess}より小さいですよ`; - } else if (this.secret > guess) { - return firsttime ? `${guess}よりも大きいですね` : `もう一度言いますが${guess}より大きいですよ`; - } else { - this.bot.clearContext(); - return `正解です🎉 (${this.history.length}回目で当てました)`; - } - } - - public export() { - return { - type: 'guessing-game', - content: { - secret: this.secret, - history: this.history - } - }; - } - - public static import(bot: BotCore, data: any) { - const context = new GuessingGameContext(bot); - context.secret = data.secret; - context.history = data.history; - return context; - } -} - -class OthelloContext extends Context { - private othello: Othello = null; - - constructor(bot: BotCore) { - super(bot); - - this.othello = new Othello(); - } - - public async greet(): Promise<string> { - return this.othello.toPatternString('black'); - } - - public async q(query: string): Promise<string> { - if (query == 'やめる') { - this.bot.clearContext(); - return 'オセロをやめました。'; - } - - const n = parseInt(query, 10); - - if (isNaN(n)) { - return '番号で指定してください。「やめる」と言うとゲームをやめます。'; - } - - this.othello.setByNumber('black', n); - const s = this.othello.toString() + '\n\n...(AI)...\n\n'; - othelloAi('white', this.othello); - if (this.othello.getPattern('black').length === 0) { - this.bot.clearContext(); - const blackCount = this.othello.board.map(row => row.filter(s => s == 'black').length).reduce((a, b) => a + b); - const whiteCount = this.othello.board.map(row => row.filter(s => s == 'white').length).reduce((a, b) => a + b); - const winner = blackCount == whiteCount ? '引き分け' : blackCount > whiteCount ? '黒の勝ち' : '白の勝ち'; - return this.othello.toString() + `\n\n~終了~\n\n黒${blackCount}、白${whiteCount}で${winner}です。`; - } else { - this.emit('updated'); - return s + this.othello.toPatternString('black'); - } - } - - public export() { - return { - type: 'othello', - content: { - board: this.othello.board - } - }; - } - - public static import(bot: BotCore, data: any) { - const context = new OthelloContext(bot); - context.othello = new Othello(); - context.othello.board = data.board; - return context; - } -} diff --git a/src/api/bot/interfaces/line.ts b/src/api/bot/interfaces/line.ts deleted file mode 100644 index 43c25f8032..0000000000 --- a/src/api/bot/interfaces/line.ts +++ /dev/null @@ -1,236 +0,0 @@ -import * as EventEmitter from 'events'; -import * as express from 'express'; -import * as request from 'request'; -import * as crypto from 'crypto'; -import User from '../../models/user'; -import config from '../../../conf'; -import BotCore from '../core'; -import _redis from '../../../db/redis'; -import prominence = require('prominence'); -import getPostSummary from '../../../common/get-post-summary'; - -const redis = prominence(_redis); - -// SEE: https://developers.line.me/media/messaging-api/messages/sticker_list.pdf -const stickers = [ - '297', - '298', - '299', - '300', - '301', - '302', - '303', - '304', - '305', - '306', - '307' -]; - -class LineBot extends BotCore { - private replyToken: string; - - private reply(messages: any[]) { - request.post({ - url: 'https://api.line.me/v2/bot/message/reply', - headers: { - 'Authorization': `Bearer ${config.line_bot.channel_access_token}` - }, - json: { - replyToken: this.replyToken, - messages: messages - } - }, (err, res, body) => { - if (err) { - console.error(err); - return; - } - }); - } - - public async react(ev: any): Promise<void> { - this.replyToken = ev.replyToken; - - switch (ev.type) { - // メッセージ - case 'message': - switch (ev.message.type) { - // テキスト - case 'text': - const res = await this.q(ev.message.text); - if (res == null) return; - // 返信 - this.reply([{ - type: 'text', - text: res - }]); - break; - - // スタンプ - case 'sticker': - // スタンプで返信 - this.reply([{ - type: 'sticker', - packageId: '4', - stickerId: stickers[Math.floor(Math.random() * stickers.length)] - }]); - break; - } - break; - - // postback - case 'postback': - const data = ev.postback.data; - const cmd = data.split('|')[0]; - const arg = data.split('|')[1]; - switch (cmd) { - case 'showtl': - this.showUserTimelinePostback(arg); - break; - } - break; - } - } - - public static import(data) { - const bot = new LineBot(); - bot._import(data); - return bot; - } - - public async showUserCommand(q: string) { - const user = await require('../../endpoints/users/show')({ - username: q.substr(1) - }, this.user); - - const actions = []; - - actions.push({ - type: 'postback', - label: 'タイムラインを見る', - data: `showtl|${user.id}` - }); - - if (user.twitter) { - actions.push({ - type: 'uri', - label: 'Twitterアカウントを見る', - uri: `https://twitter.com/${user.twitter.screen_name}` - }); - } - - actions.push({ - type: 'uri', - label: 'Webで見る', - uri: `${config.url}/${user.username}` - }); - - this.reply([{ - type: 'template', - altText: await super.showUserCommand(q), - template: { - type: 'buttons', - thumbnailImageUrl: `${user.avatar_url}?thumbnail&size=1024`, - title: `${user.name} (@${user.username})`, - text: user.description || '(no description)', - actions: actions - } - }]); - - return null; - } - - public async showUserTimelinePostback(userId: string) { - const tl = await require('../../endpoints/users/posts')({ - user_id: userId, - limit: 5 - }, this.user); - - const text = `${tl[0].user.name}さんのタイムラインはこちらです:\n\n` + tl - .map(post => getPostSummary(post)) - .join('\n-----\n'); - - this.reply([{ - type: 'text', - text: text - }]); - } -} - -module.exports = async (app: express.Application) => { - if (config.line_bot == null) return; - - const handler = new EventEmitter(); - - handler.on('event', async (ev) => { - - const sourceId = ev.source.userId; - const sessionId = `line-bot-sessions:${sourceId}`; - - const session = await redis.get(sessionId); - let bot: LineBot; - - if (session == null) { - const user = await User.findOne({ - line: { - user_id: sourceId - } - }); - - bot = new LineBot(user); - - bot.on('signin', user => { - User.update(user._id, { - $set: { - line: { - user_id: sourceId - } - } - }); - }); - - bot.on('signout', user => { - User.update(user._id, { - $set: { - line: { - user_id: null - } - } - }); - }); - - redis.set(sessionId, JSON.stringify(bot.export())); - } else { - bot = LineBot.import(JSON.parse(session)); - } - - bot.on('updated', () => { - redis.set(sessionId, JSON.stringify(bot.export())); - }); - - if (session != null) bot.refreshUser(); - - bot.react(ev); - }); - - app.post('/hooks/line', (req, res, next) => { - // req.headers['x-line-signature'] は常に string ですが、型定義の都合上 - // string | string[] になっているので string を明示しています - const sig1 = req.headers['x-line-signature'] as string; - - const hash = crypto.createHmac('SHA256', config.line_bot.channel_secret) - .update((req as any).rawBody); - - const sig2 = hash.digest('base64'); - - // シグネチャ比較 - if (sig1 === sig2) { - req.body.events.forEach(ev => { - handler.emit('event', ev); - }); - - res.sendStatus(200); - } else { - res.sendStatus(400); - } - }); -}; diff --git a/src/api/common/add-file-to-drive.ts b/src/api/common/add-file-to-drive.ts deleted file mode 100644 index 109e886106..0000000000 --- a/src/api/common/add-file-to-drive.ts +++ /dev/null @@ -1,271 +0,0 @@ -import { Buffer } from 'buffer'; -import * as fs from 'fs'; -import * as tmp from 'tmp'; -import * as stream from 'stream'; - -import * as mongodb from 'mongodb'; -import * as crypto from 'crypto'; -import * as _gm from 'gm'; -import * as debug from 'debug'; -import fileType = require('file-type'); -import prominence = require('prominence'); - -import DriveFile, { getGridFSBucket } from '../models/drive-file'; -import DriveFolder from '../models/drive-folder'; -import serialize from '../serializers/drive-file'; -import event, { publishDriveStream } from '../event'; -import config from '../../conf'; - -const gm = _gm.subClass({ - imageMagick: true -}); - -const log = debug('misskey:register-drive-file'); - -const tmpFile = (): Promise<string> => new Promise((resolve, reject) => { - tmp.file((e, path) => { - if (e) return reject(e); - resolve(path); - }); -}); - -const addToGridFS = (name: string, readable: stream.Readable, type: string, metadata: any): Promise<any> => - getGridFSBucket() - .then(bucket => new Promise((resolve, reject) => { - const writeStream = bucket.openUploadStream(name, { contentType: type, metadata }); - writeStream.once('finish', (doc) => { resolve(doc); }); - writeStream.on('error', reject); - readable.pipe(writeStream); - })); - -const addFile = async ( - user: any, - path: string, - name: string = null, - comment: string = null, - folderId: mongodb.ObjectID = null, - force: boolean = false -) => { - log(`registering ${name} (user: ${user.username}, path: ${path})`); - - // Calculate hash, get content type and get file size - const [hash, [mime, ext], size] = await Promise.all([ - // hash - ((): Promise<string> => new Promise((res, rej) => { - const readable = fs.createReadStream(path); - const hash = crypto.createHash('md5'); - const chunks = []; - readable - .on('error', rej) - .pipe(hash) - .on('error', rej) - .on('data', (chunk) => chunks.push(chunk)) - .on('end', () => { - const buffer = Buffer.concat(chunks); - res(buffer.toString('hex')); - }); - }))(), - // mime - ((): Promise<[string, string | null]> => new Promise((res, rej) => { - const readable = fs.createReadStream(path); - readable - .on('error', rej) - .once('data', (buffer: Buffer) => { - readable.destroy(); - const type = fileType(buffer); - if (type) { - return res([type.mime, type.ext]); - } else { - // 種類が同定できなかったら application/octet-stream にする - return res(['application/octet-stream', null]); - } - }); - }))(), - // size - ((): Promise<number> => new Promise((res, rej) => { - fs.stat(path, (err, stats) => { - if (err) return rej(err); - res(stats.size); - }); - }))() - ]); - - log(`hash: ${hash}, mime: ${mime}, ext: ${ext}, size: ${size}`); - - // detect name - const detectedName: string = name || (ext ? `untitled.${ext}` : 'untitled'); - - if (!force) { - // Check if there is a file with the same hash - const much = await DriveFile.findOne({ - md5: hash, - 'metadata.user_id': user._id - }); - - if (much !== null) { - log('file with same hash is found'); - return much; - } else { - log('file with same hash is not found'); - } - } - - const [wh, folder] = await Promise.all([ - // Width and height (when image) - (async () => { - // 画像かどうか - if (!/^image\/.*$/.test(mime)) { - return null; - } - - const imageType = mime.split('/')[1]; - - // 画像でもPNGかJPEGかGIFでないならスキップ - if (imageType != 'png' && imageType != 'jpeg' && imageType != 'gif') { - return null; - } - - // Calculate width and height - const g = gm(fs.createReadStream(path), name); - const size = await prominence(g).size(); - - log('image width and height is calculated'); - - return [size.width, size.height]; - })(), - // folder - (async () => { - if (!folderId) { - return null; - } - const driveFolder = await DriveFolder.findOne({ - _id: folderId, - user_id: user._id - }); - if (!driveFolder) { - throw 'folder-not-found'; - } - return driveFolder; - })(), - // usage checker - (async () => { - // Calculate drive usage - const usage = await DriveFile - .aggregate([{ - $match: { 'metadata.user_id': user._id } - }, { - $project: { - length: true - } - }, { - $group: { - _id: null, - usage: { $sum: '$length' } - } - }]) - .then((aggregates: any[]) => { - if (aggregates.length > 0) { - return aggregates[0].usage; - } - return 0; - }); - - log(`drive usage is ${usage}`); - - // If usage limit exceeded - if (usage + size > user.drive_capacity) { - throw 'no-free-space'; - } - })() - ]); - - const readable = fs.createReadStream(path); - - const properties = {}; - - if (wh) { - properties['width'] = wh[0]; - properties['height'] = wh[1]; - } - - return addToGridFS(detectedName, readable, mime, { - user_id: user._id, - folder_id: folder !== null ? folder._id : null, - comment: comment, - properties: properties - }); -}; - -/** - * Add file to drive - * - * @param user User who wish to add file - * @param file File path or readableStream - * @param comment Comment - * @param type File type - * @param folderId Folder ID - * @param force If set to true, forcibly upload the file even if there is a file with the same hash. - * @return Object that represents added file - */ -export default (user: any, file: string | stream.Readable, ...args) => new Promise<any>((resolve, reject) => { - // Get file path - new Promise((res: (v: [string, boolean]) => void, rej) => { - if (typeof file === 'string') { - res([file, false]); - return; - } - if (typeof file === 'object' && typeof file.read === 'function') { - tmpFile() - .then(path => { - const readable: stream.Readable = file; - const writable = fs.createWriteStream(path); - readable - .on('error', rej) - .on('end', () => { - res([path, true]); - }) - .pipe(writable) - .on('error', rej); - }) - .catch(rej); - } - rej(new Error('un-compatible file.')); - }) - .then(([path, remove]): Promise<any> => new Promise((res, rej) => { - addFile(user, path, ...args) - .then(file => { - res(file); - if (remove) { - fs.unlink(path, (e) => { - if (e) log(e.stack); - }); - } - }) - .catch(rej); - })) - .then(file => { - log(`drive file has been created ${file._id}`); - resolve(file); - - serialize(file).then(serializedFile => { - // Publish drive_file_created event - event(user._id, 'drive_file_created', serializedFile); - publishDriveStream(user._id, 'file_created', serializedFile); - - // Register to search database - if (config.elasticsearch.enable) { - const es = require('../../db/elasticsearch'); - es.index({ - index: 'misskey', - type: 'drive_file', - id: file._id.toString(), - body: { - name: file.name, - user_id: user._id.toString() - } - }); - } - }); - }) - .catch(reject); -}); diff --git a/src/api/common/generate-native-user-token.ts b/src/api/common/generate-native-user-token.ts deleted file mode 100644 index 2082b89a5a..0000000000 --- a/src/api/common/generate-native-user-token.ts +++ /dev/null @@ -1,3 +0,0 @@ -import rndstr from 'rndstr'; - -export default () => `!${rndstr('a-zA-Z0-9', 32)}`; diff --git a/src/api/common/get-friends.ts b/src/api/common/get-friends.ts deleted file mode 100644 index db6313816d..0000000000 --- a/src/api/common/get-friends.ts +++ /dev/null @@ -1,26 +0,0 @@ -import * as mongodb from 'mongodb'; -import Following from '../models/following'; - -export default async (me: mongodb.ObjectID, includeMe: boolean = true) => { - // Fetch relation to other users who the I follows - // SELECT followee - const myfollowing = await Following - .find({ - follower_id: me, - // 削除されたドキュメントは除く - deleted_at: { $exists: false } - }, { - fields: { - followee_id: true - } - }); - - // ID list of other users who the I follows - const myfollowingIds = myfollowing.map(follow => follow.followee_id); - - if (includeMe) { - myfollowingIds.push(me); - } - - return myfollowingIds; -}; diff --git a/src/api/common/is-native-token.ts b/src/api/common/is-native-token.ts deleted file mode 100644 index 0769a4812e..0000000000 --- a/src/api/common/is-native-token.ts +++ /dev/null @@ -1 +0,0 @@ -export default (token: string) => token[0] == '!'; diff --git a/src/api/common/notify.ts b/src/api/common/notify.ts deleted file mode 100644 index 4b3e6a5d54..0000000000 --- a/src/api/common/notify.ts +++ /dev/null @@ -1,38 +0,0 @@ -import * as mongo from 'mongodb'; -import Notification from '../models/notification'; -import event from '../event'; -import serialize from '../serializers/notification'; - -export default ( - notifiee: mongo.ObjectID, - notifier: mongo.ObjectID, - type: string, - content?: any -) => new Promise<any>(async (resolve, reject) => { - if (notifiee.equals(notifier)) { - return resolve(); - } - - // Create notification - const notification = await Notification.insert(Object.assign({ - created_at: new Date(), - notifiee_id: notifiee, - notifier_id: notifier, - type: type, - is_read: false - }, content)); - - resolve(notification); - - // Publish notification event - event(notifiee, 'notification', - await serialize(notification)); - - // 3秒経っても(今回作成した)通知が既読にならなかったら「未読の通知がありますよ」イベントを発行する - setTimeout(async () => { - const fresh = await Notification.findOne({ _id: notification._id }, { is_read: true }); - if (!fresh.is_read) { - event(notifiee, 'unread_notification', await serialize(notification)); - } - }, 3000); -}); diff --git a/src/api/common/push-sw.ts b/src/api/common/push-sw.ts deleted file mode 100644 index 2993c760ee..0000000000 --- a/src/api/common/push-sw.ts +++ /dev/null @@ -1,52 +0,0 @@ -const push = require('web-push'); -import * as mongo from 'mongodb'; -import Subscription from '../models/sw-subscription'; -import config from '../../conf'; - -if (config.sw) { - // アプリケーションの連絡先と、サーバーサイドの鍵ペアの情報を登録 - push.setVapidDetails( - config.maintainer.url, - config.sw.public_key, - config.sw.private_key); -} - -export default async function(userId: mongo.ObjectID | string, type, body?) { - if (!config.sw) return; - - if (typeof userId === 'string') { - userId = new mongo.ObjectID(userId); - } - - // Fetch - const subscriptions = await Subscription.find({ - user_id: userId - }); - - subscriptions.forEach(subscription => { - const pushSubscription = { - endpoint: subscription.endpoint, - keys: { - auth: subscription.auth, - p256dh: subscription.publickey - } - }; - - push.sendNotification(pushSubscription, JSON.stringify({ - type, body - })).catch(err => { - //console.log(err.statusCode); - //console.log(err.headers); - //console.log(err.body); - - if (err.statusCode == 410) { - Subscription.remove({ - user_id: userId, - endpoint: subscription.endpoint, - auth: subscription.auth, - publickey: subscription.publickey - }); - } - }); - }); -} diff --git a/src/api/common/read-messaging-message.ts b/src/api/common/read-messaging-message.ts deleted file mode 100644 index 8e5e5b2b68..0000000000 --- a/src/api/common/read-messaging-message.ts +++ /dev/null @@ -1,66 +0,0 @@ -import * as mongo from 'mongodb'; -import Message from '../models/messaging-message'; -import { IMessagingMessage as IMessage } from '../models/messaging-message'; -import publishUserStream from '../event'; -import { publishMessagingStream } from '../event'; -import { publishMessagingIndexStream } from '../event'; - -/** - * Mark as read message(s) - */ -export default ( - user: string | mongo.ObjectID, - otherparty: string | mongo.ObjectID, - message: string | string[] | IMessage | IMessage[] | mongo.ObjectID | mongo.ObjectID[] -) => new Promise<any>(async (resolve, reject) => { - - const userId = mongo.ObjectID.prototype.isPrototypeOf(user) - ? user - : new mongo.ObjectID(user); - - const otherpartyId = mongo.ObjectID.prototype.isPrototypeOf(otherparty) - ? otherparty - : new mongo.ObjectID(otherparty); - - const ids: mongo.ObjectID[] = Array.isArray(message) - ? mongo.ObjectID.prototype.isPrototypeOf(message[0]) - ? (message as mongo.ObjectID[]) - : typeof message[0] === 'string' - ? (message as string[]).map(m => new mongo.ObjectID(m)) - : (message as IMessage[]).map(m => m._id) - : mongo.ObjectID.prototype.isPrototypeOf(message) - ? [(message as mongo.ObjectID)] - : typeof message === 'string' - ? [new mongo.ObjectID(message)] - : [(message as IMessage)._id]; - - // Update documents - await Message.update({ - _id: { $in: ids }, - user_id: otherpartyId, - recipient_id: userId, - is_read: false - }, { - $set: { - is_read: true - } - }, { - multi: true - }); - - // Publish event - publishMessagingStream(otherpartyId, userId, 'read', ids.map(id => id.toString())); - publishMessagingIndexStream(userId, 'read', ids.map(id => id.toString())); - - // Calc count of my unread messages - const count = await Message - .count({ - recipient_id: userId, - is_read: false - }); - - if (count == 0) { - // 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行 - publishUserStream(userId, 'read_all_messaging_messages'); - } -}); diff --git a/src/api/common/read-notification.ts b/src/api/common/read-notification.ts deleted file mode 100644 index 3009cc5d08..0000000000 --- a/src/api/common/read-notification.ts +++ /dev/null @@ -1,52 +0,0 @@ -import * as mongo from 'mongodb'; -import { default as Notification, INotification } from '../models/notification'; -import publishUserStream from '../event'; - -/** - * Mark as read notification(s) - */ -export default ( - user: string | mongo.ObjectID, - message: string | string[] | INotification | INotification[] | mongo.ObjectID | mongo.ObjectID[] -) => new Promise<any>(async (resolve, reject) => { - - const userId = mongo.ObjectID.prototype.isPrototypeOf(user) - ? user - : new mongo.ObjectID(user); - - const ids: mongo.ObjectID[] = Array.isArray(message) - ? mongo.ObjectID.prototype.isPrototypeOf(message[0]) - ? (message as mongo.ObjectID[]) - : typeof message[0] === 'string' - ? (message as string[]).map(m => new mongo.ObjectID(m)) - : (message as INotification[]).map(m => m._id) - : mongo.ObjectID.prototype.isPrototypeOf(message) - ? [(message as mongo.ObjectID)] - : typeof message === 'string' - ? [new mongo.ObjectID(message)] - : [(message as INotification)._id]; - - // Update documents - await Notification.update({ - _id: { $in: ids }, - is_read: false - }, { - $set: { - is_read: true - } - }, { - multi: true - }); - - // Calc count of my unread notifications - const count = await Notification - .count({ - notifiee_id: userId, - is_read: false - }); - - if (count == 0) { - // 全ての(いままで未読だった)通知を(これで)読みましたよというイベントを発行 - publishUserStream(userId, 'read_all_notifications'); - } -}); diff --git a/src/api/common/signin.ts b/src/api/common/signin.ts deleted file mode 100644 index 693e62f39f..0000000000 --- a/src/api/common/signin.ts +++ /dev/null @@ -1,19 +0,0 @@ -import config from '../../conf'; - -export default function(res, user, redirect: boolean) { - const expires = 1000 * 60 * 60 * 24 * 365; // One Year - res.cookie('i', user.token, { - path: '/', - domain: `.${config.host}`, - secure: config.url.substr(0, 5) === 'https', - httpOnly: false, - expires: new Date(Date.now() + expires), - maxAge: expires - }); - - if (redirect) { - res.redirect(config.url); - } else { - res.sendStatus(204); - } -} diff --git a/src/api/common/text/core/syntax-highlighter.ts b/src/api/common/text/core/syntax-highlighter.ts deleted file mode 100644 index c0396b1fc6..0000000000 --- a/src/api/common/text/core/syntax-highlighter.ts +++ /dev/null @@ -1,334 +0,0 @@ -function escape(text) { - return text - .replace(/>/g, '>') - .replace(/</g, '<'); -} - -// 文字数が多い順にソートします -// そうしないと、「function」という文字列が与えられたときに「func」が先にマッチしてしまう可能性があるためです -const _keywords = [ - 'true', - 'false', - 'null', - 'nil', - 'undefined', - 'void', - 'var', - 'const', - 'let', - 'mut', - 'dim', - 'if', - 'then', - 'else', - 'switch', - 'match', - 'case', - 'default', - 'for', - 'each', - 'in', - 'while', - 'loop', - 'continue', - 'break', - 'do', - 'goto', - 'next', - 'end', - 'sub', - 'throw', - 'try', - 'catch', - 'finally', - 'enum', - 'delegate', - 'function', - 'func', - 'fun', - 'fn', - 'return', - 'yield', - 'async', - 'await', - 'require', - 'include', - 'import', - 'imports', - 'export', - 'exports', - 'from', - 'as', - 'using', - 'use', - 'internal', - 'module', - 'namespace', - 'where', - 'select', - 'struct', - 'union', - 'new', - 'delete', - 'this', - 'super', - 'base', - 'class', - 'interface', - 'abstract', - 'static', - 'public', - 'private', - 'protected', - 'virtual', - 'partial', - 'override', - 'extends', - 'implements', - 'constructor' -]; - -const keywords = _keywords - .concat(_keywords.map(k => k[0].toUpperCase() + k.substr(1))) - .concat(_keywords.map(k => k.toUpperCase())) - .sort((a, b) => b.length - a.length); - -const symbols = [ - '=', - '+', - '-', - '*', - '/', - '%', - '~', - '^', - '&', - '|', - '>', - '<', - '!', - '?' -]; - -const elements = [ - // comment - code => { - if (code.substr(0, 2) != '//') return null; - const match = code.match(/^\/\/(.+?)(\n|$)/); - if (!match) return null; - const comment = match[0]; - return { - html: `<span class="comment">${escape(comment)}</span>`, - next: comment.length - }; - }, - - // block comment - code => { - const match = code.match(/^\/\*([\s\S]+?)\*\//); - if (!match) return null; - return { - html: `<span class="comment">${escape(match[0])}</span>`, - next: match[0].length - }; - }, - - // string - code => { - if (!/^['"`]/.test(code)) return null; - const begin = code[0]; - let str = begin; - let thisIsNotAString = false; - for (let i = 1; i < code.length; i++) { - const char = code[i]; - if (char == '\\') { - str += char; - str += code[i + 1] || ''; - i++; - continue; - } else if (char == begin) { - str += char; - break; - } else if (char == '\n' || i == (code.length - 1)) { - thisIsNotAString = true; - break; - } else { - str += char; - } - } - if (thisIsNotAString) { - return null; - } else { - return { - html: `<span class="string">${escape(str)}</span>`, - next: str.length - }; - } - }, - - // regexp - code => { - if (code[0] != '/') return null; - let regexp = ''; - let thisIsNotARegexp = false; - for (let i = 1; i < code.length; i++) { - const char = code[i]; - if (char == '\\') { - regexp += char; - regexp += code[i + 1] || ''; - i++; - continue; - } else if (char == '/') { - break; - } else if (char == '\n' || i == (code.length - 1)) { - thisIsNotARegexp = true; - break; - } else { - regexp += char; - } - } - - if (thisIsNotARegexp) return null; - if (regexp == '') return null; - if (regexp[0] == ' ' && regexp[regexp.length - 1] == ' ') return null; - - return { - html: `<span class="regexp">/${escape(regexp)}/</span>`, - next: regexp.length + 2 - }; - }, - - // label - code => { - if (code[0] != '@') return null; - const match = code.match(/^@([a-zA-Z_-]+?)\n/); - if (!match) return null; - const label = match[0]; - return { - html: `<span class="label">${label}</span>`, - next: label.length - }; - }, - - // number - (code, i, source) => { - const prev = source[i - 1]; - if (prev && /[a-zA-Z]/.test(prev)) return null; - if (!/^[\-\+]?[0-9\.]+/.test(code)) return null; - const match = code.match(/^[\-\+]?[0-9\.]+/)[0]; - if (match) { - return { - html: `<span class="number">${match}</span>`, - next: match.length - }; - } else { - return null; - } - }, - - // nan - (code, i, source) => { - const prev = source[i - 1]; - if (prev && /[a-zA-Z]/.test(prev)) return null; - if (code.substr(0, 3) == 'NaN') { - return { - html: `<span class="nan">NaN</span>`, - next: 3 - }; - } else { - return null; - } - }, - - // method - code => { - const match = code.match(/^([a-zA-Z_-]+?)\(/); - if (!match) return null; - - if (match[1] == '-') return null; - - return { - html: `<span class="method">${match[1]}</span>`, - next: match[1].length - }; - }, - - // property - (code, i, source) => { - const prev = source[i - 1]; - if (prev != '.') return null; - - const match = code.match(/^[a-zA-Z0-9_-]+/); - if (!match) return null; - - return { - html: `<span class="property">${match[0]}</span>`, - next: match[0].length - }; - }, - - // keyword - (code, i, source) => { - const prev = source[i - 1]; - if (prev && /[a-zA-Z]/.test(prev)) return null; - - const match = keywords.filter(k => code.substr(0, k.length) == k)[0]; - if (match) { - if (/^[a-zA-Z]/.test(code.substr(match.length))) return null; - return { - html: `<span class="keyword ${match}">${match}</span>`, - next: match.length - }; - } else { - return null; - } - }, - - // symbol - code => { - const match = symbols.filter(s => code[0] == s)[0]; - if (match) { - return { - html: `<span class="symbol">${match}</span>`, - next: 1 - }; - } else { - return null; - } - } -]; - -// specify lang is todo -export default (source: string, lang?: string) => { - let code = source; - let html = ''; - - let i = 0; - - function push(token) { - html += token.html; - code = code.substr(token.next); - i += token.next; - } - - while (code != '') { - const parsed = elements.some(el => { - const e = el(code, i, source); - if (e) { - push(e); - return true; - } else { - return false; - } - }); - - if (!parsed) { - push({ - html: escape(code[0]), - next: 1 - }); - } - } - - return html; -}; diff --git a/src/api/common/text/elements/bold.ts b/src/api/common/text/elements/bold.ts deleted file mode 100644 index ce25764457..0000000000 --- a/src/api/common/text/elements/bold.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Bold - */ - -module.exports = text => { - const match = text.match(/^\*\*(.+?)\*\*/); - if (!match) return null; - const bold = match[0]; - return { - type: 'bold', - content: bold, - bold: bold.substr(2, bold.length - 4) - }; -}; diff --git a/src/api/common/text/elements/code.ts b/src/api/common/text/elements/code.ts deleted file mode 100644 index 4821e95fe2..0000000000 --- a/src/api/common/text/elements/code.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Code (block) - */ - -import genHtml from '../core/syntax-highlighter'; - -module.exports = text => { - const match = text.match(/^```([\s\S]+?)```/); - if (!match) return null; - const code = match[0]; - return { - type: 'code', - content: code, - code: code.substr(3, code.length - 6).trim(), - html: genHtml(code.substr(3, code.length - 6).trim()) - }; -}; diff --git a/src/api/common/text/elements/emoji.ts b/src/api/common/text/elements/emoji.ts deleted file mode 100644 index e24231a223..0000000000 --- a/src/api/common/text/elements/emoji.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Emoji - */ - -module.exports = text => { - const match = text.match(/^:[a-zA-Z0-9+-_]+:/); - if (!match) return null; - const emoji = match[0]; - return { - type: 'emoji', - content: emoji, - emoji: emoji.substr(1, emoji.length - 2) - }; -}; diff --git a/src/api/common/text/elements/hashtag.ts b/src/api/common/text/elements/hashtag.ts deleted file mode 100644 index ee57b140b8..0000000000 --- a/src/api/common/text/elements/hashtag.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Hashtag - */ - -module.exports = (text, i) => { - if (!(/^\s#[^\s]+/.test(text) || (i == 0 && /^#[^\s]+/.test(text)))) return null; - const isHead = text[0] == '#'; - const hashtag = text.match(/^\s?#[^\s]+/)[0]; - const res: any[] = !isHead ? [{ - type: 'text', - content: text[0] - }] : []; - res.push({ - type: 'hashtag', - content: isHead ? hashtag : hashtag.substr(1), - hashtag: isHead ? hashtag.substr(1) : hashtag.substr(2) - }); - return res; -}; diff --git a/src/api/common/text/elements/inline-code.ts b/src/api/common/text/elements/inline-code.ts deleted file mode 100644 index 9f9ef51a2b..0000000000 --- a/src/api/common/text/elements/inline-code.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Code (inline) - */ - -import genHtml from '../core/syntax-highlighter'; - -module.exports = text => { - const match = text.match(/^`(.+?)`/); - if (!match) return null; - const code = match[0]; - return { - type: 'inline-code', - content: code, - code: code.substr(1, code.length - 2).trim(), - html: genHtml(code.substr(1, code.length - 2).trim()) - }; -}; diff --git a/src/api/common/text/elements/link.ts b/src/api/common/text/elements/link.ts deleted file mode 100644 index 35563ddc3d..0000000000 --- a/src/api/common/text/elements/link.ts +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Link - */ - -module.exports = text => { - const match = text.match(/^\??\[([^\[\]]+?)\]\((https?:\/\/[\w\/:%#@\$&\?!\(\)\[\]~\.=\+\-]+?)\)/); - if (!match) return null; - const silent = text[0] == '?'; - const link = match[0]; - const title = match[1]; - const url = match[2]; - return { - type: 'link', - content: link, - title: title, - url: url, - silent: silent - }; -}; diff --git a/src/api/common/text/elements/mention.ts b/src/api/common/text/elements/mention.ts deleted file mode 100644 index e0fac4dd76..0000000000 --- a/src/api/common/text/elements/mention.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Mention - */ - -module.exports = text => { - const match = text.match(/^@[a-zA-Z0-9\-]+/); - if (!match) return null; - const mention = match[0]; - return { - type: 'mention', - content: mention, - username: mention.substr(1) - }; -}; diff --git a/src/api/common/text/elements/url.ts b/src/api/common/text/elements/url.ts deleted file mode 100644 index 1003aff9c3..0000000000 --- a/src/api/common/text/elements/url.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * URL - */ - -module.exports = text => { - const match = text.match(/^https?:\/\/[\w\/:%#@\$&\?!\(\)\[\]~\.=\+\-]+/); - if (!match) return null; - const url = match[0]; - return { - type: 'url', - content: url, - url: url - }; -}; diff --git a/src/api/common/text/index.ts b/src/api/common/text/index.ts deleted file mode 100644 index 47127e8646..0000000000 --- a/src/api/common/text/index.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Misskey Text Analyzer - */ - -const elements = [ - require('./elements/bold'), - require('./elements/url'), - require('./elements/link'), - require('./elements/mention'), - require('./elements/hashtag'), - require('./elements/code'), - require('./elements/inline-code'), - require('./elements/emoji') -]; - -export default (source: string) => { - - if (source == '') { - return null; - } - - const tokens = []; - - function push(token) { - if (token != null) { - tokens.push(token); - source = source.substr(token.content.length); - } - } - - let i = 0; - - // パース - while (source != '') { - const parsed = elements.some(el => { - let tokens = el(source, i); - if (tokens) { - if (!Array.isArray(tokens)) { - tokens = [tokens]; - } - tokens.forEach(push); - return true; - } else { - return false; - } - }); - - if (!parsed) { - push({ - type: 'text', - content: source[0] - }); - } - - i++; - } - - // テキストを纏める - tokens[0] = [tokens[0]]; - return tokens.reduce((a, b) => { - if (a[a.length - 1].type == 'text' && b.type == 'text') { - const tail = a.pop(); - return a.concat({ - type: 'text', - content: tail.content + b.content - }); - } else { - return a.concat(b); - } - }); -}; diff --git a/src/api/common/watch-post.ts b/src/api/common/watch-post.ts deleted file mode 100644 index 1a50f0edaa..0000000000 --- a/src/api/common/watch-post.ts +++ /dev/null @@ -1,26 +0,0 @@ -import * as mongodb from 'mongodb'; -import Watching from '../models/post-watching'; - -export default async (me: mongodb.ObjectID, post: object) => { - // 自分の投稿はwatchできない - if (me.equals((post as any).user_id)) { - return; - } - - // if watching now - const exist = await Watching.findOne({ - post_id: (post as any)._id, - user_id: me, - deleted_at: { $exists: false } - }); - - if (exist !== null) { - return; - } - - await Watching.insert({ - created_at: new Date(), - post_id: (post as any)._id, - user_id: me - }); -}; diff --git a/src/api/endpoints.ts b/src/api/endpoints.ts deleted file mode 100644 index 1138df193b..0000000000 --- a/src/api/endpoints.ts +++ /dev/null @@ -1,533 +0,0 @@ -const ms = require('ms'); - -/** - * エンドポイントを表します。 - */ -export type Endpoint = { - - /** - * エンドポイント名 - */ - name: string; - - /** - * このエンドポイントにリクエストするのにユーザー情報が必須か否か - * 省略した場合は false として解釈されます。 - */ - withCredential?: boolean; - - /** - * エンドポイントのリミテーションに関するやつ - * 省略した場合はリミテーションは無いものとして解釈されます。 - * また、withCredential が false の場合はリミテーションを行うことはできません。 - */ - limit?: { - - /** - * 複数のエンドポイントでリミットを共有したい場合に指定するキー - */ - key?: string; - - /** - * リミットを適用する期間(ms) - * このプロパティを設定する場合、max プロパティも設定する必要があります。 - */ - duration?: number; - - /** - * durationで指定した期間内にいくつまでリクエストできるのか - * このプロパティを設定する場合、duration プロパティも設定する必要があります。 - */ - max?: number; - - /** - * 最低でもどれくらいの間隔を開けてリクエストしなければならないか(ms) - */ - minInterval?: number; - }; - - /** - * ファイルの添付を必要とするか否か - * 省略した場合は false として解釈されます。 - */ - withFile?: boolean; - - /** - * サードパーティアプリからはリクエストすることができないか否か - * 省略した場合は false として解釈されます。 - */ - secure?: boolean; - - /** - * エンドポイントの種類 - * パーミッションの実現に利用されます。 - */ - kind?: string; -}; - -const endpoints: Endpoint[] = [ - { - name: 'meta' - }, - { - name: 'stats' - }, - { - name: 'username/available' - }, - { - name: 'my/apps', - withCredential: true - }, - { - name: 'app/create', - withCredential: true, - limit: { - duration: ms('1day'), - max: 3 - } - }, - { - name: 'app/show' - }, - { - name: 'app/name_id/available' - }, - { - name: 'auth/session/generate' - }, - { - name: 'auth/session/show' - }, - { - name: 'auth/session/userkey' - }, - { - name: 'auth/accept', - withCredential: true, - secure: true - }, - { - name: 'auth/deny', - withCredential: true, - secure: true - }, - { - name: 'aggregation/posts', - }, - { - name: 'aggregation/users', - }, - { - name: 'aggregation/users/activity', - }, - { - name: 'aggregation/users/post', - }, - { - name: 'aggregation/users/followers' - }, - { - name: 'aggregation/users/following' - }, - { - name: 'aggregation/users/reaction' - }, - { - name: 'aggregation/posts/repost' - }, - { - name: 'aggregation/posts/reply' - }, - { - name: 'aggregation/posts/reaction' - }, - { - name: 'aggregation/posts/reactions' - }, - - { - name: 'sw/register', - withCredential: true - }, - - { - name: 'i', - withCredential: true - }, - { - name: 'i/2fa/register', - withCredential: true, - secure: true - }, - { - name: 'i/2fa/unregister', - withCredential: true, - secure: true - }, - { - name: 'i/2fa/done', - withCredential: true, - secure: true - }, - { - name: 'i/update', - withCredential: true, - limit: { - duration: ms('1day'), - max: 50 - }, - kind: 'account-write' - }, - { - name: 'i/update_home', - withCredential: true, - kind: 'account-write' - }, - { - name: 'i/change_password', - withCredential: true, - secure: true - }, - { - name: 'i/regenerate_token', - withCredential: true, - secure: true - }, - { - name: 'i/pin', - kind: 'account-write' - }, - { - name: 'i/appdata/get', - withCredential: true - }, - { - name: 'i/appdata/set', - withCredential: true - }, - { - name: 'i/signin_history', - withCredential: true, - kind: 'account-read' - }, - { - name: 'i/authorized_apps', - withCredential: true, - secure: true - }, - - { - name: 'i/notifications', - withCredential: true, - kind: 'notification-read' - }, - { - name: 'notifications/get_unread_count', - withCredential: true, - kind: 'notification-read' - }, - { - name: 'notifications/delete', - withCredential: true, - kind: 'notification-write' - }, - { - name: 'notifications/delete_all', - withCredential: true, - kind: 'notification-write' - }, - { - name: 'notifications/mark_as_read_all', - withCredential: true, - kind: 'notification-write' - }, - - { - name: 'drive', - withCredential: true, - kind: 'drive-read' - }, - { - name: 'drive/stream', - withCredential: true, - kind: 'drive-read' - }, - { - name: 'drive/files', - withCredential: true, - kind: 'drive-read' - }, - { - name: 'drive/files/create', - withCredential: true, - limit: { - duration: ms('1hour'), - max: 100 - }, - withFile: true, - kind: 'drive-write' - }, - { - name: 'drive/files/upload_from_url', - withCredential: true, - limit: { - duration: ms('1hour'), - max: 10 - }, - kind: 'drive-write' - }, - { - name: 'drive/files/show', - withCredential: true, - kind: 'drive-read' - }, - { - name: 'drive/files/find', - withCredential: true, - kind: 'drive-read' - }, - { - name: 'drive/files/delete', - withCredential: true, - kind: 'drive-write' - }, - { - name: 'drive/files/update', - withCredential: true, - kind: 'drive-write' - }, - { - name: 'drive/folders', - withCredential: true, - kind: 'drive-read' - }, - { - name: 'drive/folders/create', - withCredential: true, - limit: { - duration: ms('1hour'), - max: 50 - }, - kind: 'drive-write' - }, - { - name: 'drive/folders/show', - withCredential: true, - kind: 'drive-read' - }, - { - name: 'drive/folders/find', - withCredential: true, - kind: 'drive-read' - }, - { - name: 'drive/folders/update', - withCredential: true, - kind: 'drive-write' - }, - - { - name: 'users' - }, - { - name: 'users/show' - }, - { - name: 'users/search' - }, - { - name: 'users/search_by_username' - }, - { - name: 'users/posts' - }, - { - name: 'users/following' - }, - { - name: 'users/followers' - }, - { - name: 'users/recommendation', - withCredential: true, - kind: 'account-read' - }, - { - name: 'users/get_frequently_replied_users' - }, - - { - name: 'following/create', - withCredential: true, - limit: { - duration: ms('1hour'), - max: 100 - }, - kind: 'following-write' - }, - { - name: 'following/delete', - withCredential: true, - limit: { - duration: ms('1hour'), - max: 100 - }, - kind: 'following-write' - }, - - { - name: 'posts' - }, - { - name: 'posts/show' - }, - { - name: 'posts/replies' - }, - { - name: 'posts/context' - }, - { - name: 'posts/create', - withCredential: true, - limit: { - duration: ms('1hour'), - max: 120, - minInterval: ms('1second') - }, - kind: 'post-write' - }, - { - name: 'posts/reposts' - }, - { - name: 'posts/search' - }, - { - name: 'posts/timeline', - withCredential: true, - limit: { - duration: ms('10minutes'), - max: 100 - } - }, - { - name: 'posts/mentions', - withCredential: true, - limit: { - duration: ms('10minutes'), - max: 100 - } - }, - { - name: 'posts/trend', - withCredential: true - }, - { - name: 'posts/categorize', - withCredential: true - }, - { - name: 'posts/reactions', - withCredential: true - }, - { - name: 'posts/reactions/create', - withCredential: true, - limit: { - duration: ms('1hour'), - max: 100 - }, - kind: 'reaction-write' - }, - { - name: 'posts/reactions/delete', - withCredential: true, - limit: { - duration: ms('1hour'), - max: 100 - }, - kind: 'reaction-write' - }, - { - name: 'posts/favorites/create', - withCredential: true, - limit: { - duration: ms('1hour'), - max: 100 - }, - kind: 'favorite-write' - }, - { - name: 'posts/favorites/delete', - withCredential: true, - limit: { - duration: ms('1hour'), - max: 100 - }, - kind: 'favorite-write' - }, - { - name: 'posts/polls/vote', - withCredential: true, - limit: { - duration: ms('1hour'), - max: 100 - }, - kind: 'vote-write' - }, - { - name: 'posts/polls/recommendation', - withCredential: true - }, - - { - name: 'messaging/history', - withCredential: true, - kind: 'messaging-read' - }, - { - name: 'messaging/unread', - withCredential: true, - kind: 'messaging-read' - }, - { - name: 'messaging/messages', - withCredential: true, - kind: 'messaging-read' - }, - { - name: 'messaging/messages/create', - withCredential: true, - kind: 'messaging-write' - }, - { - name: 'channels/create', - withCredential: true, - limit: { - duration: ms('1hour'), - max: 3, - minInterval: ms('10seconds') - } - }, - { - name: 'channels/show' - }, - { - name: 'channels/posts' - }, - { - name: 'channels/watch', - withCredential: true - }, - { - name: 'channels/unwatch', - withCredential: true - }, - { - name: 'channels' - }, -]; - -export default endpoints; diff --git a/src/api/endpoints/aggregation/posts.ts b/src/api/endpoints/aggregation/posts.ts deleted file mode 100644 index 9d8bccbdb2..0000000000 --- a/src/api/endpoints/aggregation/posts.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Post from '../../models/post'; - -/** - * Aggregate posts - * - * @param {any} params - * @return {Promise<any>} - */ -module.exports = params => new Promise(async (res, rej) => { - // Get 'limit' parameter - const [limit = 365, limitErr] = $(params.limit).optional.number().range(1, 365).$; - if (limitErr) return rej('invalid limit param'); - - const datas = await Post - .aggregate([ - { $project: { - repost_id: '$repost_id', - reply_id: '$reply_id', - created_at: { $add: ['$created_at', 9 * 60 * 60 * 1000] } // Convert into JST - }}, - { $project: { - date: { - year: { $year: '$created_at' }, - month: { $month: '$created_at' }, - day: { $dayOfMonth: '$created_at' } - }, - type: { - $cond: { - if: { $ne: ['$repost_id', null] }, - then: 'repost', - else: { - $cond: { - if: { $ne: ['$reply_id', null] }, - then: 'reply', - else: 'post' - } - } - } - }} - }, - { $group: { _id: { - date: '$date', - type: '$type' - }, count: { $sum: 1 } } }, - { $group: { - _id: '$_id.date', - data: { $addToSet: { - type: '$_id.type', - count: '$count' - }} - } } - ]); - - datas.forEach(data => { - data.date = data._id; - delete data._id; - - data.posts = (data.data.filter(x => x.type == 'post')[0] || { count: 0 }).count; - data.reposts = (data.data.filter(x => x.type == 'repost')[0] || { count: 0 }).count; - data.replies = (data.data.filter(x => x.type == 'reply')[0] || { count: 0 }).count; - - delete data.data; - }); - - const graph = []; - - for (let i = 0; i < limit; i++) { - const day = new Date(new Date().setDate(new Date().getDate() - i)); - - const data = datas.filter(d => - d.date.year == day.getFullYear() && d.date.month == day.getMonth() + 1 && d.date.day == day.getDate() - )[0]; - - if (data) { - graph.push(data); - } else { - graph.push({ - posts: 0, - reposts: 0, - replies: 0 - }); - } - } - - res(graph); -}); diff --git a/src/api/endpoints/aggregation/posts/reaction.ts b/src/api/endpoints/aggregation/posts/reaction.ts deleted file mode 100644 index eb99b9d088..0000000000 --- a/src/api/endpoints/aggregation/posts/reaction.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Post from '../../../models/post'; -import Reaction from '../../../models/post-reaction'; - -/** - * Aggregate reaction of a post - * - * @param {any} params - * @return {Promise<any>} - */ -module.exports = (params) => new Promise(async (res, rej) => { - // Get 'post_id' parameter - const [postId, postIdErr] = $(params.post_id).id().$; - if (postIdErr) return rej('invalid post_id param'); - - // Lookup post - const post = await Post.findOne({ - _id: postId - }); - - if (post === null) { - return rej('post not found'); - } - - const datas = await Reaction - .aggregate([ - { $match: { post_id: post._id } }, - { $project: { - created_at: { $add: ['$created_at', 9 * 60 * 60 * 1000] } // Convert into JST - }}, - { $project: { - date: { - year: { $year: '$created_at' }, - month: { $month: '$created_at' }, - day: { $dayOfMonth: '$created_at' } - } - }}, - { $group: { - _id: '$date', - count: { $sum: 1 } - }} - ]); - - datas.forEach(data => { - data.date = data._id; - delete data._id; - }); - - const graph = []; - - for (let i = 0; i < 30; i++) { - const day = new Date(new Date().setDate(new Date().getDate() - i)); - - const data = datas.filter(d => - d.date.year == day.getFullYear() && d.date.month == day.getMonth() + 1 && d.date.day == day.getDate() - )[0]; - - if (data) { - graph.push(data); - } else { - graph.push({ - date: { - year: day.getFullYear(), - month: day.getMonth() + 1, // In JavaScript, month is zero-based. - day: day.getDate() - }, - count: 0 - }); - } - } - - res(graph); -}); diff --git a/src/api/endpoints/aggregation/posts/reactions.ts b/src/api/endpoints/aggregation/posts/reactions.ts deleted file mode 100644 index 2cd4588ae1..0000000000 --- a/src/api/endpoints/aggregation/posts/reactions.ts +++ /dev/null @@ -1,69 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Post from '../../../models/post'; -import Reaction from '../../../models/post-reaction'; - -/** - * Aggregate reactions of a post - * - * @param {any} params - * @return {Promise<any>} - */ -module.exports = (params) => new Promise(async (res, rej) => { - // Get 'post_id' parameter - const [postId, postIdErr] = $(params.post_id).id().$; - if (postIdErr) return rej('invalid post_id param'); - - // Lookup post - const post = await Post.findOne({ - _id: postId - }); - - if (post === null) { - return rej('post not found'); - } - - const startTime = new Date(new Date().setMonth(new Date().getMonth() - 1)); - - const reactions = await Reaction - .find({ - post_id: post._id, - $or: [ - { deleted_at: { $exists: false } }, - { deleted_at: { $gt: startTime } } - ] - }, { - _id: false, - post_id: false - }, { - sort: { created_at: -1 } - }); - - const graph = []; - - for (let i = 0; i < 30; i++) { - let day = new Date(new Date().setDate(new Date().getDate() - i)); - day = new Date(day.setMilliseconds(999)); - day = new Date(day.setSeconds(59)); - day = new Date(day.setMinutes(59)); - day = new Date(day.setHours(23)); - // day = day.getTime(); - - const count = reactions.filter(r => - r.created_at < day && (r.deleted_at == null || r.deleted_at > day) - ).length; - - graph.push({ - date: { - year: day.getFullYear(), - month: day.getMonth() + 1, // In JavaScript, month is zero-based. - day: day.getDate() - }, - count: count - }); - } - - res(graph); -}); diff --git a/src/api/endpoints/aggregation/posts/reply.ts b/src/api/endpoints/aggregation/posts/reply.ts deleted file mode 100644 index b114c34e1e..0000000000 --- a/src/api/endpoints/aggregation/posts/reply.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Post from '../../../models/post'; - -/** - * Aggregate reply of a post - * - * @param {any} params - * @return {Promise<any>} - */ -module.exports = (params) => new Promise(async (res, rej) => { - // Get 'post_id' parameter - const [postId, postIdErr] = $(params.post_id).id().$; - if (postIdErr) return rej('invalid post_id param'); - - // Lookup post - const post = await Post.findOne({ - _id: postId - }); - - if (post === null) { - return rej('post not found'); - } - - const datas = await Post - .aggregate([ - { $match: { reply: post._id } }, - { $project: { - created_at: { $add: ['$created_at', 9 * 60 * 60 * 1000] } // Convert into JST - }}, - { $project: { - date: { - year: { $year: '$created_at' }, - month: { $month: '$created_at' }, - day: { $dayOfMonth: '$created_at' } - } - }}, - { $group: { - _id: '$date', - count: { $sum: 1 } - }} - ]); - - datas.forEach(data => { - data.date = data._id; - delete data._id; - }); - - const graph = []; - - for (let i = 0; i < 30; i++) { - const day = new Date(new Date().setDate(new Date().getDate() - i)); - - const data = datas.filter(d => - d.date.year == day.getFullYear() && d.date.month == day.getMonth() + 1 && d.date.day == day.getDate() - )[0]; - - if (data) { - graph.push(data); - } else { - graph.push({ - date: { - year: day.getFullYear(), - month: day.getMonth() + 1, // In JavaScript, month is zero-based. - day: day.getDate() - }, - count: 0 - }); - } - } - - res(graph); -}); diff --git a/src/api/endpoints/aggregation/posts/repost.ts b/src/api/endpoints/aggregation/posts/repost.ts deleted file mode 100644 index 217159caa7..0000000000 --- a/src/api/endpoints/aggregation/posts/repost.ts +++ /dev/null @@ -1,75 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Post from '../../../models/post'; - -/** - * Aggregate repost of a post - * - * @param {any} params - * @return {Promise<any>} - */ -module.exports = (params) => new Promise(async (res, rej) => { - // Get 'post_id' parameter - const [postId, postIdErr] = $(params.post_id).id().$; - if (postIdErr) return rej('invalid post_id param'); - - // Lookup post - const post = await Post.findOne({ - _id: postId - }); - - if (post === null) { - return rej('post not found'); - } - - const datas = await Post - .aggregate([ - { $match: { repost_id: post._id } }, - { $project: { - created_at: { $add: ['$created_at', 9 * 60 * 60 * 1000] } // Convert into JST - }}, - { $project: { - date: { - year: { $year: '$created_at' }, - month: { $month: '$created_at' }, - day: { $dayOfMonth: '$created_at' } - } - }}, - { $group: { - _id: '$date', - count: { $sum: 1 } - }} - ]); - - datas.forEach(data => { - data.date = data._id; - delete data._id; - }); - - const graph = []; - - for (let i = 0; i < 30; i++) { - const day = new Date(new Date().setDate(new Date().getDate() - i)); - - const data = datas.filter(d => - d.date.year == day.getFullYear() && d.date.month == day.getMonth() + 1 && d.date.day == day.getDate() - )[0]; - - if (data) { - graph.push(data); - } else { - graph.push({ - date: { - year: day.getFullYear(), - month: day.getMonth() + 1, // In JavaScript, month is zero-based. - day: day.getDate() - }, - count: 0 - }); - } - } - - res(graph); -}); diff --git a/src/api/endpoints/aggregation/users.ts b/src/api/endpoints/aggregation/users.ts deleted file mode 100644 index 9eb2d035ec..0000000000 --- a/src/api/endpoints/aggregation/users.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import User from '../../models/user'; - -/** - * Aggregate users - * - * @param {any} params - * @return {Promise<any>} - */ -module.exports = params => new Promise(async (res, rej) => { - // Get 'limit' parameter - const [limit = 365, limitErr] = $(params.limit).optional.number().range(1, 365).$; - if (limitErr) return rej('invalid limit param'); - - const users = await User - .find({}, { - _id: false, - created_at: true, - deleted_at: true - }, { - sort: { created_at: -1 } - }); - - const graph = []; - - for (let i = 0; i < limit; i++) { - let dayStart = new Date(new Date().setDate(new Date().getDate() - i)); - dayStart = new Date(dayStart.setMilliseconds(0)); - dayStart = new Date(dayStart.setSeconds(0)); - dayStart = new Date(dayStart.setMinutes(0)); - dayStart = new Date(dayStart.setHours(0)); - - let dayEnd = new Date(new Date().setDate(new Date().getDate() - i)); - dayEnd = new Date(dayEnd.setMilliseconds(999)); - dayEnd = new Date(dayEnd.setSeconds(59)); - dayEnd = new Date(dayEnd.setMinutes(59)); - dayEnd = new Date(dayEnd.setHours(23)); - // day = day.getTime(); - - const total = users.filter(u => - u.created_at < dayEnd && (u.deleted_at == null || u.deleted_at > dayEnd) - ).length; - - const created = users.filter(u => - u.created_at < dayEnd && u.created_at > dayStart - ).length; - - graph.push({ - total: total, - created: created - }); - } - - res(graph); -}); diff --git a/src/api/endpoints/aggregation/users/activity.ts b/src/api/endpoints/aggregation/users/activity.ts deleted file mode 100644 index 102a71d7cb..0000000000 --- a/src/api/endpoints/aggregation/users/activity.ts +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import User from '../../../models/user'; -import Post from '../../../models/post'; - -// TODO: likeやfollowも集計 - -/** - * Aggregate activity of a user - * - * @param {any} params - * @return {Promise<any>} - */ -module.exports = (params) => new Promise(async (res, rej) => { - // Get 'limit' parameter - const [limit = 365, limitErr] = $(params.limit).optional.number().range(1, 365).$; - if (limitErr) return rej('invalid limit param'); - - // Get 'user_id' parameter - const [userId, userIdErr] = $(params.user_id).id().$; - if (userIdErr) return rej('invalid user_id param'); - - // Lookup user - const user = await User.findOne({ - _id: userId - }, { - fields: { - _id: true - } - }); - - if (user === null) { - return rej('user not found'); - } - - const datas = await Post - .aggregate([ - { $match: { user_id: user._id } }, - { $project: { - repost_id: '$repost_id', - reply_id: '$reply_id', - created_at: { $add: ['$created_at', 9 * 60 * 60 * 1000] } // Convert into JST - }}, - { $project: { - date: { - year: { $year: '$created_at' }, - month: { $month: '$created_at' }, - day: { $dayOfMonth: '$created_at' } - }, - type: { - $cond: { - if: { $ne: ['$repost_id', null] }, - then: 'repost', - else: { - $cond: { - if: { $ne: ['$reply_id', null] }, - then: 'reply', - else: 'post' - } - } - } - }} - }, - { $group: { _id: { - date: '$date', - type: '$type' - }, count: { $sum: 1 } } }, - { $group: { - _id: '$_id.date', - data: { $addToSet: { - type: '$_id.type', - count: '$count' - }} - } } - ]); - - datas.forEach(data => { - data.date = data._id; - delete data._id; - - data.posts = (data.data.filter(x => x.type == 'post')[0] || { count: 0 }).count; - data.reposts = (data.data.filter(x => x.type == 'repost')[0] || { count: 0 }).count; - data.replies = (data.data.filter(x => x.type == 'reply')[0] || { count: 0 }).count; - - delete data.data; - }); - - const graph = []; - - for (let i = 0; i < limit; i++) { - const day = new Date(new Date().setDate(new Date().getDate() - i)); - - const data = datas.filter(d => - d.date.year == day.getFullYear() && d.date.month == day.getMonth() + 1 && d.date.day == day.getDate() - )[0]; - - if (data) { - graph.push(data); - } else { - graph.push({ - date: { - year: day.getFullYear(), - month: day.getMonth() + 1, // In JavaScript, month is zero-based. - day: day.getDate() - }, - posts: 0, - reposts: 0, - replies: 0 - }); - } - } - - res(graph); -}); diff --git a/src/api/endpoints/aggregation/users/followers.ts b/src/api/endpoints/aggregation/users/followers.ts deleted file mode 100644 index 3022b2b002..0000000000 --- a/src/api/endpoints/aggregation/users/followers.ts +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import User from '../../../models/user'; -import Following from '../../../models/following'; - -/** - * Aggregate followers of a user - * - * @param {any} params - * @return {Promise<any>} - */ -module.exports = (params) => new Promise(async (res, rej) => { - // Get 'user_id' parameter - const [userId, userIdErr] = $(params.user_id).id().$; - if (userIdErr) return rej('invalid user_id param'); - - // Lookup user - const user = await User.findOne({ - _id: userId - }, { - fields: { - _id: true - } - }); - - if (user === null) { - return rej('user not found'); - } - - const startTime = new Date(new Date().setMonth(new Date().getMonth() - 1)); - - const following = await Following - .find({ - followee_id: user._id, - $or: [ - { deleted_at: { $exists: false } }, - { deleted_at: { $gt: startTime } } - ] - }, { - _id: false, - follower_id: false, - followee_id: false - }, { - sort: { created_at: -1 } - }); - - const graph = []; - - for (let i = 0; i < 30; i++) { - let day = new Date(new Date().setDate(new Date().getDate() - i)); - day = new Date(day.setMilliseconds(999)); - day = new Date(day.setSeconds(59)); - day = new Date(day.setMinutes(59)); - day = new Date(day.setHours(23)); - // day = day.getTime(); - - const count = following.filter(f => - f.created_at < day && (f.deleted_at == null || f.deleted_at > day) - ).length; - - graph.push({ - date: { - year: day.getFullYear(), - month: day.getMonth() + 1, // In JavaScript, month is zero-based. - day: day.getDate() - }, - count: count - }); - } - - res(graph); -}); diff --git a/src/api/endpoints/aggregation/users/following.ts b/src/api/endpoints/aggregation/users/following.ts deleted file mode 100644 index 92da7e6921..0000000000 --- a/src/api/endpoints/aggregation/users/following.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import User from '../../../models/user'; -import Following from '../../../models/following'; - -/** - * Aggregate following of a user - * - * @param {any} params - * @return {Promise<any>} - */ -module.exports = (params) => new Promise(async (res, rej) => { - // Get 'user_id' parameter - const [userId, userIdErr] = $(params.user_id).id().$; - if (userIdErr) return rej('invalid user_id param'); - - // Lookup user - const user = await User.findOne({ - _id: userId - }, { - fields: { - _id: true - } - }); - - if (user === null) { - return rej('user not found'); - } - - const startTime = new Date(new Date().setMonth(new Date().getMonth() - 1)); - - const following = await Following - .find({ - follower_id: user._id, - $or: [ - { deleted_at: { $exists: false } }, - { deleted_at: { $gt: startTime } } - ] - }, { - _id: false, - follower_id: false, - followee_id: false - }, { - sort: { created_at: -1 } - }); - - const graph = []; - - for (let i = 0; i < 30; i++) { - let day = new Date(new Date().setDate(new Date().getDate() - i)); - day = new Date(day.setMilliseconds(999)); - day = new Date(day.setSeconds(59)); - day = new Date(day.setMinutes(59)); - day = new Date(day.setHours(23)); - - const count = following.filter(f => - f.created_at < day && (f.deleted_at == null || f.deleted_at > day) - ).length; - - graph.push({ - date: { - year: day.getFullYear(), - month: day.getMonth() + 1, // In JavaScript, month is zero-based. - day: day.getDate() - }, - count: count - }); - } - - res(graph); -}); diff --git a/src/api/endpoints/aggregation/users/post.ts b/src/api/endpoints/aggregation/users/post.ts deleted file mode 100644 index c6a75eee39..0000000000 --- a/src/api/endpoints/aggregation/users/post.ts +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import User from '../../../models/user'; -import Post from '../../../models/post'; - -/** - * Aggregate post of a user - * - * @param {any} params - * @return {Promise<any>} - */ -module.exports = (params) => new Promise(async (res, rej) => { - // Get 'user_id' parameter - const [userId, userIdErr] = $(params.user_id).id().$; - if (userIdErr) return rej('invalid user_id param'); - - // Lookup user - const user = await User.findOne({ - _id: userId - }, { - fields: { - _id: true - } - }); - - if (user === null) { - return rej('user not found'); - } - - const datas = await Post - .aggregate([ - { $match: { user_id: user._id } }, - { $project: { - repost_id: '$repost_id', - reply_id: '$reply_id', - created_at: { $add: ['$created_at', 9 * 60 * 60 * 1000] } // Convert into JST - }}, - { $project: { - date: { - year: { $year: '$created_at' }, - month: { $month: '$created_at' }, - day: { $dayOfMonth: '$created_at' } - }, - type: { - $cond: { - if: { $ne: ['$repost_id', null] }, - then: 'repost', - else: { - $cond: { - if: { $ne: ['$reply_id', null] }, - then: 'reply', - else: 'post' - } - } - } - }} - }, - { $group: { _id: { - date: '$date', - type: '$type' - }, count: { $sum: 1 } } }, - { $group: { - _id: '$_id.date', - data: { $addToSet: { - type: '$_id.type', - count: '$count' - }} - } } - ]); - - datas.forEach(data => { - data.date = data._id; - delete data._id; - - data.posts = (data.data.filter(x => x.type == 'post')[0] || { count: 0 }).count; - data.reposts = (data.data.filter(x => x.type == 'repost')[0] || { count: 0 }).count; - data.replies = (data.data.filter(x => x.type == 'reply')[0] || { count: 0 }).count; - - delete data.data; - }); - - const graph = []; - - for (let i = 0; i < 30; i++) { - const day = new Date(new Date().setDate(new Date().getDate() - i)); - - const data = datas.filter(d => - d.date.year == day.getFullYear() && d.date.month == day.getMonth() + 1 && d.date.day == day.getDate() - )[0]; - - if (data) { - graph.push(data); - } else { - graph.push({ - date: { - year: day.getFullYear(), - month: day.getMonth() + 1, // In JavaScript, month is zero-based. - day: day.getDate() - }, - posts: 0, - reposts: 0, - replies: 0 - }); - } - } - - res(graph); -}); diff --git a/src/api/endpoints/aggregation/users/reaction.ts b/src/api/endpoints/aggregation/users/reaction.ts deleted file mode 100644 index 0a082ed1b7..0000000000 --- a/src/api/endpoints/aggregation/users/reaction.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import User from '../../../models/user'; -import Reaction from '../../../models/post-reaction'; - -/** - * Aggregate reaction of a user - * - * @param {any} params - * @return {Promise<any>} - */ -module.exports = (params) => new Promise(async (res, rej) => { - // Get 'user_id' parameter - const [userId, userIdErr] = $(params.user_id).id().$; - if (userIdErr) return rej('invalid user_id param'); - - // Lookup user - const user = await User.findOne({ - _id: userId - }, { - fields: { - _id: true - } - }); - - if (user === null) { - return rej('user not found'); - } - - const datas = await Reaction - .aggregate([ - { $match: { user_id: user._id } }, - { $project: { - created_at: { $add: ['$created_at', 9 * 60 * 60 * 1000] } // Convert into JST - }}, - { $project: { - date: { - year: { $year: '$created_at' }, - month: { $month: '$created_at' }, - day: { $dayOfMonth: '$created_at' } - } - }}, - { $group: { - _id: '$date', - count: { $sum: 1 } - }} - ]); - - datas.forEach(data => { - data.date = data._id; - delete data._id; - }); - - const graph = []; - - for (let i = 0; i < 30; i++) { - const day = new Date(new Date().setDate(new Date().getDate() - i)); - - const data = datas.filter(d => - d.date.year == day.getFullYear() && d.date.month == day.getMonth() + 1 && d.date.day == day.getDate() - )[0]; - - if (data) { - graph.push(data); - } else { - graph.push({ - date: { - year: day.getFullYear(), - month: day.getMonth() + 1, // In JavaScript, month is zero-based. - day: day.getDate() - }, - count: 0 - }); - } - } - - res(graph); -}); diff --git a/src/api/endpoints/app/create.ts b/src/api/endpoints/app/create.ts deleted file mode 100644 index ca684de02d..0000000000 --- a/src/api/endpoints/app/create.ts +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Module dependencies - */ -import rndstr from 'rndstr'; -import $ from 'cafy'; -import App from '../../models/app'; -import { isValidNameId } from '../../models/app'; -import serialize from '../../serializers/app'; - -/** - * @swagger - * /app/create: - * post: - * summary: Create an application - * parameters: - * - $ref: "#/parameters/AccessToken" - * - - * name: name_id - * description: Application unique name - * in: formData - * required: true - * type: string - * - - * name: name - * description: Application name - * in: formData - * required: true - * type: string - * - - * name: description - * description: Application description - * in: formData - * required: true - * type: string - * - - * name: permission - * description: Permissions that application has - * in: formData - * required: true - * type: array - * items: - * type: string - * collectionFormat: csv - * - - * name: callback_url - * description: URL called back after authentication - * in: formData - * required: false - * type: string - * - * responses: - * 200: - * description: Created application's information - * schema: - * $ref: "#/definitions/Application" - * - * default: - * description: Failed - * schema: - * $ref: "#/definitions/Error" - */ - -/** - * Create an app - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = async (params, user) => new Promise(async (res, rej) => { - // Get 'name_id' parameter - const [nameId, nameIdErr] = $(params.name_id).string().pipe(isValidNameId).$; - if (nameIdErr) return rej('invalid name_id param'); - - // Get 'name' parameter - const [name, nameErr] = $(params.name).string().$; - if (nameErr) return rej('invalid name param'); - - // Get 'description' parameter - const [description, descriptionErr] = $(params.description).string().$; - if (descriptionErr) return rej('invalid description param'); - - // Get 'permission' parameter - const [permission, permissionErr] = $(params.permission).array('string').unique().$; - if (permissionErr) return rej('invalid permission param'); - - // Get 'callback_url' parameter - // TODO: Check it is valid url - const [callbackUrl = null, callbackUrlErr] = $(params.callback_url).optional.nullable.string().$; - if (callbackUrlErr) return rej('invalid callback_url param'); - - // Generate secret - const secret = rndstr('a-zA-Z0-9', 32); - - // Create account - const app = await App.insert({ - created_at: new Date(), - user_id: user._id, - name: name, - name_id: nameId, - name_id_lower: nameId.toLowerCase(), - description: description, - permission: permission, - callback_url: callbackUrl, - secret: secret - }); - - // Response - res(await serialize(app)); -}); diff --git a/src/api/endpoints/app/name_id/available.ts b/src/api/endpoints/app/name_id/available.ts deleted file mode 100644 index 3d2c710322..0000000000 --- a/src/api/endpoints/app/name_id/available.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import App from '../../../models/app'; -import { isValidNameId } from '../../../models/app'; - -/** - * @swagger - * /app/name_id/available: - * post: - * summary: Check available name_id on creation an application - * parameters: - * - - * name: name_id - * description: Application unique name - * in: formData - * required: true - * type: string - * - * responses: - * 200: - * description: Success - * schema: - * type: object - * properties: - * available: - * description: Whether name_id is available - * type: boolean - * - * default: - * description: Failed - * schema: - * $ref: "#/definitions/Error" - */ - -/** - * Check available name_id of app - * - * @param {any} params - * @return {Promise<any>} - */ -module.exports = async (params) => new Promise(async (res, rej) => { - // Get 'name_id' parameter - const [nameId, nameIdErr] = $(params.name_id).string().pipe(isValidNameId).$; - if (nameIdErr) return rej('invalid name_id param'); - - // Get exist - const exist = await App - .count({ - name_id_lower: nameId.toLowerCase() - }, { - limit: 1 - }); - - // Reply - res({ - available: exist === 0 - }); -}); diff --git a/src/api/endpoints/app/show.ts b/src/api/endpoints/app/show.ts deleted file mode 100644 index 054aab8596..0000000000 --- a/src/api/endpoints/app/show.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import App from '../../models/app'; -import serialize from '../../serializers/app'; - -/** - * @swagger - * /app/show: - * post: - * summary: Show an application's information - * description: Require app_id or name_id - * parameters: - * - - * name: app_id - * description: Application ID - * in: formData - * type: string - * - - * name: name_id - * description: Application unique name - * in: formData - * type: string - * - * responses: - * 200: - * description: Success - * schema: - * $ref: "#/definitions/Application" - * - * default: - * description: Failed - * schema: - * $ref: "#/definitions/Error" - */ - -/** - * Show an app - * - * @param {any} params - * @param {any} user - * @param {any} _ - * @param {any} isSecure - * @return {Promise<any>} - */ -module.exports = (params, user, _, isSecure) => new Promise(async (res, rej) => { - // Get 'app_id' parameter - const [appId, appIdErr] = $(params.app_id).optional.id().$; - if (appIdErr) return rej('invalid app_id param'); - - // Get 'name_id' parameter - const [nameId, nameIdErr] = $(params.name_id).optional.string().$; - if (nameIdErr) return rej('invalid name_id param'); - - if (appId === undefined && nameId === undefined) { - return rej('app_id or name_id is required'); - } - - // Lookup app - const app = appId !== undefined - ? await App.findOne({ _id: appId }) - : await App.findOne({ name_id_lower: nameId.toLowerCase() }); - - if (app === null) { - return rej('app not found'); - } - - // Send response - res(await serialize(app, user, { - includeSecret: isSecure && app.user_id.equals(user._id) - })); -}); diff --git a/src/api/endpoints/auth/accept.ts b/src/api/endpoints/auth/accept.ts deleted file mode 100644 index 4ee20a6d25..0000000000 --- a/src/api/endpoints/auth/accept.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Module dependencies - */ -import rndstr from 'rndstr'; -const crypto = require('crypto'); -import $ from 'cafy'; -import App from '../../models/app'; -import AuthSess from '../../models/auth-session'; -import AccessToken from '../../models/access-token'; - -/** - * @swagger - * /auth/accept: - * post: - * summary: Accept a session - * parameters: - * - $ref: "#/parameters/NativeToken" - * - - * name: token - * description: Session Token - * in: formData - * required: true - * type: string - * responses: - * 204: - * description: OK - * - * default: - * description: Failed - * schema: - * $ref: "#/definitions/Error" - */ - -/** - * Accept - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'token' parameter - const [token, tokenErr] = $(params.token).string().$; - if (tokenErr) return rej('invalid token param'); - - // Fetch token - const session = await AuthSess - .findOne({ token: token }); - - if (session === null) { - return rej('session not found'); - } - - // Generate access token - const accessToken = rndstr('a-zA-Z0-9', 32); - - // Fetch exist access token - const exist = await AccessToken.findOne({ - app_id: session.app_id, - user_id: user._id, - }); - - if (exist === null) { - // Lookup app - const app = await App.findOne({ - _id: session.app_id - }); - - // Generate Hash - const sha256 = crypto.createHash('sha256'); - sha256.update(accessToken + app.secret); - const hash = sha256.digest('hex'); - - // Insert access token doc - await AccessToken.insert({ - created_at: new Date(), - app_id: session.app_id, - user_id: user._id, - token: accessToken, - hash: hash - }); - } - - // Update session - await AuthSess.update(session._id, { - $set: { - user_id: user._id - } - }); - - // Response - res(); -}); diff --git a/src/api/endpoints/auth/session/generate.ts b/src/api/endpoints/auth/session/generate.ts deleted file mode 100644 index 510382247e..0000000000 --- a/src/api/endpoints/auth/session/generate.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Module dependencies - */ -import * as uuid from 'uuid'; -import $ from 'cafy'; -import App from '../../../models/app'; -import AuthSess from '../../../models/auth-session'; -import config from '../../../../conf'; - -/** - * @swagger - * /auth/session/generate: - * post: - * summary: Generate a session - * parameters: - * - - * name: app_secret - * description: App Secret - * in: formData - * required: true - * type: string - * - * responses: - * 200: - * description: OK - * schema: - * type: object - * properties: - * token: - * type: string - * description: Session Token - * url: - * type: string - * description: Authentication form's URL - * default: - * description: Failed - * schema: - * $ref: "#/definitions/Error" - */ - -/** - * Generate a session - * - * @param {any} params - * @return {Promise<any>} - */ -module.exports = (params) => new Promise(async (res, rej) => { - // Get 'app_secret' parameter - const [appSecret, appSecretErr] = $(params.app_secret).string().$; - if (appSecretErr) return rej('invalid app_secret param'); - - // Lookup app - const app = await App.findOne({ - secret: appSecret - }); - - if (app == null) { - return rej('app not found'); - } - - // Generate token - const token = uuid.v4(); - - // Create session token document - const doc = await AuthSess.insert({ - created_at: new Date(), - app_id: app._id, - token: token - }); - - // Response - res({ - token: doc.token, - url: `${config.auth_url}/${doc.token}` - }); -}); diff --git a/src/api/endpoints/auth/session/show.ts b/src/api/endpoints/auth/session/show.ts deleted file mode 100644 index ede8a67634..0000000000 --- a/src/api/endpoints/auth/session/show.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import AuthSess from '../../../models/auth-session'; -import serialize from '../../../serializers/auth-session'; - -/** - * @swagger - * /auth/session/show: - * post: - * summary: Show a session information - * parameters: - * - - * name: token - * description: Session Token - * in: formData - * required: true - * type: string - * - * responses: - * 200: - * description: OK - * schema: - * type: object - * properties: - * created_at: - * type: string - * format: date-time - * description: Date and time of the session creation - * app_id: - * type: string - * description: Application ID - * token: - * type: string - * description: Session Token - * user_id: - * type: string - * description: ID of user who create the session - * app: - * $ref: "#/definitions/Application" - * default: - * description: Failed - * schema: - * $ref: "#/definitions/Error" - */ - -/** - * Show a session - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'token' parameter - const [token, tokenErr] = $(params.token).string().$; - if (tokenErr) return rej('invalid token param'); - - // Lookup session - const session = await AuthSess.findOne({ - token: token - }); - - if (session == null) { - return rej('session not found'); - } - - // Response - res(await serialize(session, user)); -}); diff --git a/src/api/endpoints/auth/session/userkey.ts b/src/api/endpoints/auth/session/userkey.ts deleted file mode 100644 index afd3250b04..0000000000 --- a/src/api/endpoints/auth/session/userkey.ts +++ /dev/null @@ -1,109 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import App from '../../../models/app'; -import AuthSess from '../../../models/auth-session'; -import AccessToken from '../../../models/access-token'; -import serialize from '../../../serializers/user'; - -/** - * @swagger - * /auth/session/userkey: - * post: - * summary: Get an access token(userkey) - * parameters: - * - - * name: app_secret - * description: App Secret - * in: formData - * required: true - * type: string - * - - * name: token - * description: Session Token - * in: formData - * required: true - * type: string - * - * responses: - * 200: - * description: OK - * schema: - * type: object - * properties: - * userkey: - * type: string - * description: Access Token - * user: - * $ref: "#/definitions/User" - * default: - * description: Failed - * schema: - * $ref: "#/definitions/Error" - */ - -/** - * Generate a session - * - * @param {any} params - * @return {Promise<any>} - */ -module.exports = (params) => new Promise(async (res, rej) => { - // Get 'app_secret' parameter - const [appSecret, appSecretErr] = $(params.app_secret).string().$; - if (appSecretErr) return rej('invalid app_secret param'); - - // Lookup app - const app = await App.findOne({ - secret: appSecret - }); - - if (app == null) { - return rej('app not found'); - } - - // Get 'token' parameter - const [token, tokenErr] = $(params.token).string().$; - if (tokenErr) return rej('invalid token param'); - - // Fetch token - const session = await AuthSess - .findOne({ - token: token, - app_id: app._id - }); - - if (session === null) { - return rej('session not found'); - } - - if (session.user_id == null) { - return rej('this session is not allowed yet'); - } - - // Lookup access token - const accessToken = await AccessToken.findOne({ - app_id: app._id, - user_id: session.user_id - }); - - // Delete session - - /* https://github.com/Automattic/monk/issues/178 - AuthSess.deleteOne({ - _id: session._id - }); - */ - AuthSess.remove({ - _id: session._id - }); - - // Response - res({ - access_token: accessToken.token, - user: await serialize(session.user_id, null, { - detail: true - }) - }); -}); diff --git a/src/api/endpoints/channels.ts b/src/api/endpoints/channels.ts deleted file mode 100644 index e10c943896..0000000000 --- a/src/api/endpoints/channels.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Channel from '../models/channel'; -import serialize from '../serializers/channel'; - -/** - * Get all channels - * - * @param {any} params - * @param {any} me - * @return {Promise<any>} - */ -module.exports = (params, me) => new Promise(async (res, rej) => { - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); - - // Get 'since_id' parameter - const [sinceId, sinceIdErr] = $(params.since_id).optional.id().$; - if (sinceIdErr) return rej('invalid since_id param'); - - // Get 'max_id' parameter - const [maxId, maxIdErr] = $(params.max_id).optional.id().$; - if (maxIdErr) return rej('invalid max_id param'); - - // Check if both of since_id and max_id is specified - if (sinceId && maxId) { - return rej('cannot set since_id and max_id'); - } - - // Construct query - const sort = { - _id: -1 - }; - const query = {} as any; - if (sinceId) { - sort._id = 1; - query._id = { - $gt: sinceId - }; - } else if (maxId) { - query._id = { - $lt: maxId - }; - } - - // Issue query - const channels = await Channel - .find(query, { - limit: limit, - sort: sort - }); - - // Serialize - res(await Promise.all(channels.map(async channel => - await serialize(channel, me)))); -}); diff --git a/src/api/endpoints/channels/create.ts b/src/api/endpoints/channels/create.ts deleted file mode 100644 index a8d7c29dc1..0000000000 --- a/src/api/endpoints/channels/create.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Channel from '../../models/channel'; -import Watching from '../../models/channel-watching'; -import serialize from '../../serializers/channel'; - -/** - * Create a channel - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = async (params, user) => new Promise(async (res, rej) => { - // Get 'title' parameter - const [title, titleErr] = $(params.title).string().range(1, 100).$; - if (titleErr) return rej('invalid title param'); - - // Create a channel - const channel = await Channel.insert({ - created_at: new Date(), - user_id: user._id, - title: title, - index: 0, - watching_count: 1 - }); - - // Response - res(await serialize(channel)); - - // Create Watching - await Watching.insert({ - created_at: new Date(), - user_id: user._id, - channel_id: channel._id - }); -}); diff --git a/src/api/endpoints/channels/posts.ts b/src/api/endpoints/channels/posts.ts deleted file mode 100644 index 5c071a124f..0000000000 --- a/src/api/endpoints/channels/posts.ts +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import { default as Channel, IChannel } from '../../models/channel'; -import Post from '../../models/post'; -import serialize from '../../serializers/post'; - -/** - * Show a posts of a channel - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'limit' parameter - const [limit = 1000, limitErr] = $(params.limit).optional.number().range(1, 1000).$; - if (limitErr) return rej('invalid limit param'); - - // Get 'since_id' parameter - const [sinceId, sinceIdErr] = $(params.since_id).optional.id().$; - if (sinceIdErr) return rej('invalid since_id param'); - - // Get 'max_id' parameter - const [maxId, maxIdErr] = $(params.max_id).optional.id().$; - if (maxIdErr) return rej('invalid max_id param'); - - // Check if both of since_id and max_id is specified - if (sinceId && maxId) { - return rej('cannot set since_id and max_id'); - } - - // Get 'channel_id' parameter - const [channelId, channelIdErr] = $(params.channel_id).id().$; - if (channelIdErr) return rej('invalid channel_id param'); - - // Fetch channel - const channel: IChannel = await Channel.findOne({ - _id: channelId - }); - - if (channel === null) { - return rej('channel not found'); - } - - //#region Construct query - const sort = { - _id: -1 - }; - - const query = { - channel_id: channel._id - } as any; - - if (sinceId) { - sort._id = 1; - query._id = { - $gt: sinceId - }; - } else if (maxId) { - query._id = { - $lt: maxId - }; - } - //#endregion Construct query - - // Issue query - const posts = await Post - .find(query, { - limit: limit, - sort: sort - }); - - // Serialize - res(await Promise.all(posts.map(async (post) => - await serialize(post, user) - ))); -}); diff --git a/src/api/endpoints/channels/show.ts b/src/api/endpoints/channels/show.ts deleted file mode 100644 index 8861e54594..0000000000 --- a/src/api/endpoints/channels/show.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import { default as Channel, IChannel } from '../../models/channel'; -import serialize from '../../serializers/channel'; - -/** - * Show a channel - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'channel_id' parameter - const [channelId, channelIdErr] = $(params.channel_id).id().$; - if (channelIdErr) return rej('invalid channel_id param'); - - // Fetch channel - const channel: IChannel = await Channel.findOne({ - _id: channelId - }); - - if (channel === null) { - return rej('channel not found'); - } - - // Serialize - res(await serialize(channel, user)); -}); diff --git a/src/api/endpoints/channels/unwatch.ts b/src/api/endpoints/channels/unwatch.ts deleted file mode 100644 index 19d3be118a..0000000000 --- a/src/api/endpoints/channels/unwatch.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Channel from '../../models/channel'; -import Watching from '../../models/channel-watching'; - -/** - * Unwatch a channel - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'channel_id' parameter - const [channelId, channelIdErr] = $(params.channel_id).id().$; - if (channelIdErr) return rej('invalid channel_id param'); - - //#region Fetch channel - const channel = await Channel.findOne({ - _id: channelId - }); - - if (channel === null) { - return rej('channel not found'); - } - //#endregion - - //#region Check whether not watching - const exist = await Watching.findOne({ - user_id: user._id, - channel_id: channel._id, - deleted_at: { $exists: false } - }); - - if (exist === null) { - return rej('already not watching'); - } - //#endregion - - // Delete watching - await Watching.update({ - _id: exist._id - }, { - $set: { - deleted_at: new Date() - } - }); - - // Send response - res(); - - // Decrement watching count - Channel.update(channel._id, { - $inc: { - watching_count: -1 - } - }); -}); diff --git a/src/api/endpoints/channels/watch.ts b/src/api/endpoints/channels/watch.ts deleted file mode 100644 index 030e0dd411..0000000000 --- a/src/api/endpoints/channels/watch.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Channel from '../../models/channel'; -import Watching from '../../models/channel-watching'; - -/** - * Watch a channel - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'channel_id' parameter - const [channelId, channelIdErr] = $(params.channel_id).id().$; - if (channelIdErr) return rej('invalid channel_id param'); - - //#region Fetch channel - const channel = await Channel.findOne({ - _id: channelId - }); - - if (channel === null) { - return rej('channel not found'); - } - //#endregion - - //#region Check whether already watching - const exist = await Watching.findOne({ - user_id: user._id, - channel_id: channel._id, - deleted_at: { $exists: false } - }); - - if (exist !== null) { - return rej('already watching'); - } - //#endregion - - // Create Watching - await Watching.insert({ - created_at: new Date(), - user_id: user._id, - channel_id: channel._id - }); - - // Send response - res(); - - // Increment watching count - Channel.update(channel._id, { - $inc: { - watching_count: 1 - } - }); -}); diff --git a/src/api/endpoints/drive.ts b/src/api/endpoints/drive.ts deleted file mode 100644 index d92473633a..0000000000 --- a/src/api/endpoints/drive.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Module dependencies - */ -import DriveFile from '../models/drive-file'; - -/** - * Get drive information - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Calculate drive usage - const usage = ((await DriveFile - .aggregate([ - { $match: { 'metadata.user_id': user._id } }, - { - $project: { - length: true - } - }, - { - $group: { - _id: null, - usage: { $sum: '$length' } - } - } - ]))[0] || { - usage: 0 - }).usage; - - res({ - capacity: user.drive_capacity, - usage: usage - }); -}); diff --git a/src/api/endpoints/drive/files.ts b/src/api/endpoints/drive/files.ts deleted file mode 100644 index b2e094775c..0000000000 --- a/src/api/endpoints/drive/files.ts +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import DriveFile from '../../models/drive-file'; -import serialize from '../../serializers/drive-file'; - -/** - * Get drive files - * - * @param {any} params - * @param {any} user - * @param {any} app - * @return {Promise<any>} - */ -module.exports = async (params, user, app) => { - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) throw 'invalid limit param'; - - // Get 'since_id' parameter - const [sinceId, sinceIdErr] = $(params.since_id).optional.id().$; - if (sinceIdErr) throw 'invalid since_id param'; - - // Get 'max_id' parameter - const [maxId, maxIdErr] = $(params.max_id).optional.id().$; - if (maxIdErr) throw 'invalid max_id param'; - - // Check if both of since_id and max_id is specified - if (sinceId && maxId) { - throw 'cannot set since_id and max_id'; - } - - // Get 'folder_id' parameter - const [folderId = null, folderIdErr] = $(params.folder_id).optional.nullable.id().$; - if (folderIdErr) throw 'invalid folder_id param'; - - // Get 'type' parameter - const [type, typeErr] = $(params.type).optional.string().match(/^[a-zA-Z\/\-\*]+$/).$; - if (typeErr) throw 'invalid type param'; - - // Construct query - const sort = { - _id: -1 - }; - const query = { - 'metadata.user_id': user._id, - 'metadata.folder_id': folderId - } as any; - if (sinceId) { - sort._id = 1; - query._id = { - $gt: sinceId - }; - } else if (maxId) { - query._id = { - $lt: maxId - }; - } - if (type) { - query.contentType = new RegExp(`^${type.replace(/\*/g, '.+?')}$`); - } - - // Issue query - const files = await DriveFile - .find(query, { - limit: limit, - sort: sort - }); - - // Serialize - const _files = await Promise.all(files.map(file => serialize(file))); - return _files; -}; diff --git a/src/api/endpoints/drive/files/create.ts b/src/api/endpoints/drive/files/create.ts deleted file mode 100644 index 7546eca309..0000000000 --- a/src/api/endpoints/drive/files/create.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import { validateFileName } from '../../../models/drive-file'; -import serialize from '../../../serializers/drive-file'; -import create from '../../../common/add-file-to-drive'; - -/** - * Create a file - * - * @param {any} file - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = async (file, params, user): Promise<any> => { - if (file == null) { - throw 'file is required'; - } - - // Get 'name' parameter - let name = file.originalname; - if (name !== undefined && name !== null) { - name = name.trim(); - if (name.length === 0) { - name = null; - } else if (name === 'blob') { - name = null; - } else if (!validateFileName(name)) { - throw 'invalid name'; - } - } else { - name = null; - } - - // Get 'folder_id' parameter - const [folderId = null, folderIdErr] = $(params.folder_id).optional.nullable.id().$; - if (folderIdErr) throw 'invalid folder_id param'; - - // Create file - const driveFile = await create(user, file.path, name, null, folderId); - - // Serialize - return serialize(driveFile); -}; diff --git a/src/api/endpoints/drive/files/find.ts b/src/api/endpoints/drive/files/find.ts deleted file mode 100644 index a1cdf1643e..0000000000 --- a/src/api/endpoints/drive/files/find.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import DriveFile from '../../../models/drive-file'; -import serialize from '../../../serializers/drive-file'; - -/** - * Find a file(s) - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'name' parameter - const [name, nameErr] = $(params.name).string().$; - if (nameErr) return rej('invalid name param'); - - // Get 'folder_id' parameter - const [folderId = null, folderIdErr] = $(params.folder_id).optional.nullable.id().$; - if (folderIdErr) return rej('invalid folder_id param'); - - // Issue query - const files = await DriveFile - .find({ - filename: name, - 'metadata.user_id': user._id, - 'metadata.folder_id': folderId - }); - - // Serialize - res(await Promise.all(files.map(async file => - await serialize(file)))); -}); diff --git a/src/api/endpoints/drive/files/show.ts b/src/api/endpoints/drive/files/show.ts deleted file mode 100644 index 3c7cf774f9..0000000000 --- a/src/api/endpoints/drive/files/show.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import DriveFile from '../../../models/drive-file'; -import serialize from '../../../serializers/drive-file'; - -/** - * Show a file - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = async (params, user) => { - // Get 'file_id' parameter - const [fileId, fileIdErr] = $(params.file_id).id().$; - if (fileIdErr) throw 'invalid file_id param'; - - // Fetch file - const file = await DriveFile - .findOne({ - _id: fileId, - 'metadata.user_id': user._id - }); - - if (file === null) { - throw 'file-not-found'; - } - - // Serialize - const _file = await serialize(file, { - detail: true - }); - - return _file; -}; diff --git a/src/api/endpoints/drive/files/update.ts b/src/api/endpoints/drive/files/update.ts deleted file mode 100644 index f39a420d6e..0000000000 --- a/src/api/endpoints/drive/files/update.ts +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import DriveFolder from '../../../models/drive-folder'; -import DriveFile from '../../../models/drive-file'; -import { validateFileName } from '../../../models/drive-file'; -import serialize from '../../../serializers/drive-file'; -import { publishDriveStream } from '../../../event'; - -/** - * Update a file - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'file_id' parameter - const [fileId, fileIdErr] = $(params.file_id).id().$; - if (fileIdErr) return rej('invalid file_id param'); - - // Fetch file - const file = await DriveFile - .findOne({ - _id: fileId, - 'metadata.user_id': user._id - }); - - if (file === null) { - return rej('file-not-found'); - } - - // Get 'name' parameter - const [name, nameErr] = $(params.name).optional.string().pipe(validateFileName).$; - if (nameErr) return rej('invalid name param'); - if (name) file.filename = name; - - // Get 'folder_id' parameter - const [folderId, folderIdErr] = $(params.folder_id).optional.nullable.id().$; - if (folderIdErr) return rej('invalid folder_id param'); - - if (folderId !== undefined) { - if (folderId === null) { - file.metadata.folder_id = null; - } else { - // Fetch folder - const folder = await DriveFolder - .findOne({ - _id: folderId, - user_id: user._id - }); - - if (folder === null) { - return rej('folder-not-found'); - } - - file.metadata.folder_id = folder._id; - } - } - - await DriveFile.update(file._id, { - $set: { - filename: file.filename, - 'metadata.folder_id': file.metadata.folder_id - } - }); - - // Serialize - const fileObj = await serialize(file); - - // Response - res(fileObj); - - // Publish file_updated event - publishDriveStream(user._id, 'file_updated', fileObj); -}); diff --git a/src/api/endpoints/drive/files/upload_from_url.ts b/src/api/endpoints/drive/files/upload_from_url.ts deleted file mode 100644 index 519e0bdf65..0000000000 --- a/src/api/endpoints/drive/files/upload_from_url.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Module dependencies - */ -import * as URL from 'url'; -import $ from 'cafy'; -import { validateFileName } from '../../../models/drive-file'; -import serialize from '../../../serializers/drive-file'; -import create from '../../../common/add-file-to-drive'; -import * as debug from 'debug'; -import * as tmp from 'tmp'; -import * as fs from 'fs'; -import * as request from 'request'; - -const log = debug('misskey:endpoint:upload_from_url'); - -/** - * Create a file from a URL - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = async (params, user): Promise<any> => { - // Get 'url' parameter - // TODO: Validate this url - const [url, urlErr] = $(params.url).string().$; - if (urlErr) throw 'invalid url param'; - - let name = URL.parse(url).pathname.split('/').pop(); - if (!validateFileName(name)) { - name = null; - } - - // Get 'folder_id' parameter - const [folderId = null, folderIdErr] = $(params.folder_id).optional.nullable.id().$; - if (folderIdErr) throw 'invalid folder_id param'; - - // Create temp file - const path = await new Promise((res: (string) => void, rej) => { - tmp.file((e, path) => { - if (e) return rej(e); - res(path); - }); - }); - - // write content at URL to temp file - await new Promise((res, rej) => { - const writable = fs.createWriteStream(path); - request(url) - .on('error', rej) - .on('end', () => { - writable.close(); - res(path); - }) - .pipe(writable) - .on('error', rej); - }); - - const driveFile = await create(user, path, name, null, folderId); - - // clean-up - fs.unlink(path, (e) => { - if (e) log(e.stack); - }); - - return serialize(driveFile); -}; diff --git a/src/api/endpoints/drive/folders.ts b/src/api/endpoints/drive/folders.ts deleted file mode 100644 index d49ef0af03..0000000000 --- a/src/api/endpoints/drive/folders.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import DriveFolder from '../../models/drive-folder'; -import serialize from '../../serializers/drive-folder'; - -/** - * Get drive folders - * - * @param {any} params - * @param {any} user - * @param {any} app - * @return {Promise<any>} - */ -module.exports = (params, user, app) => new Promise(async (res, rej) => { - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); - - // Get 'since_id' parameter - const [sinceId, sinceIdErr] = $(params.since_id).optional.id().$; - if (sinceIdErr) return rej('invalid since_id param'); - - // Get 'max_id' parameter - const [maxId, maxIdErr] = $(params.max_id).optional.id().$; - if (maxIdErr) return rej('invalid max_id param'); - - // Check if both of since_id and max_id is specified - if (sinceId && maxId) { - return rej('cannot set since_id and max_id'); - } - - // Get 'folder_id' parameter - const [folderId = null, folderIdErr] = $(params.folder_id).optional.nullable.id().$; - if (folderIdErr) return rej('invalid folder_id param'); - - // Construct query - const sort = { - _id: -1 - }; - const query = { - user_id: user._id, - parent_id: folderId - } as any; - if (sinceId) { - sort._id = 1; - query._id = { - $gt: sinceId - }; - } else if (maxId) { - query._id = { - $lt: maxId - }; - } - - // Issue query - const folders = await DriveFolder - .find(query, { - limit: limit, - sort: sort - }); - - // Serialize - res(await Promise.all(folders.map(async folder => - await serialize(folder)))); -}); diff --git a/src/api/endpoints/drive/folders/create.ts b/src/api/endpoints/drive/folders/create.ts deleted file mode 100644 index be847b2153..0000000000 --- a/src/api/endpoints/drive/folders/create.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import DriveFolder from '../../../models/drive-folder'; -import { isValidFolderName } from '../../../models/drive-folder'; -import serialize from '../../../serializers/drive-folder'; -import { publishDriveStream } from '../../../event'; - -/** - * Create drive folder - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'name' parameter - const [name = '無題のフォルダー', nameErr] = $(params.name).optional.string().pipe(isValidFolderName).$; - if (nameErr) return rej('invalid name param'); - - // Get 'parent_id' parameter - const [parentId = null, parentIdErr] = $(params.parent_id).optional.nullable.id().$; - if (parentIdErr) return rej('invalid parent_id param'); - - // If the parent folder is specified - let parent = null; - if (parentId) { - // Fetch parent folder - parent = await DriveFolder - .findOne({ - _id: parentId, - user_id: user._id - }); - - if (parent === null) { - return rej('parent-not-found'); - } - } - - // Create folder - const folder = await DriveFolder.insert({ - created_at: new Date(), - name: name, - parent_id: parent !== null ? parent._id : null, - user_id: user._id - }); - - // Serialize - const folderObj = await serialize(folder); - - // Response - res(folderObj); - - // Publish folder_created event - publishDriveStream(user._id, 'folder_created', folderObj); -}); diff --git a/src/api/endpoints/drive/folders/find.ts b/src/api/endpoints/drive/folders/find.ts deleted file mode 100644 index a5eb8e015d..0000000000 --- a/src/api/endpoints/drive/folders/find.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import DriveFolder from '../../../models/drive-folder'; -import serialize from '../../../serializers/drive-folder'; - -/** - * Find a folder(s) - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'name' parameter - const [name, nameErr] = $(params.name).string().$; - if (nameErr) return rej('invalid name param'); - - // Get 'parent_id' parameter - const [parentId = null, parentIdErr] = $(params.parent_id).optional.nullable.id().$; - if (parentIdErr) return rej('invalid parent_id param'); - - // Issue query - const folders = await DriveFolder - .find({ - name: name, - user_id: user._id, - parent_id: parentId - }); - - // Serialize - res(await Promise.all(folders.map(folder => serialize(folder)))); -}); diff --git a/src/api/endpoints/drive/folders/show.ts b/src/api/endpoints/drive/folders/show.ts deleted file mode 100644 index 9b1c04ca3c..0000000000 --- a/src/api/endpoints/drive/folders/show.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import DriveFolder from '../../../models/drive-folder'; -import serialize from '../../../serializers/drive-folder'; - -/** - * Show a folder - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'folder_id' parameter - const [folderId, folderIdErr] = $(params.folder_id).id().$; - if (folderIdErr) return rej('invalid folder_id param'); - - // Get folder - const folder = await DriveFolder - .findOne({ - _id: folderId, - user_id: user._id - }); - - if (folder === null) { - return rej('folder-not-found'); - } - - // Serialize - res(await serialize(folder, { - detail: true - })); -}); diff --git a/src/api/endpoints/drive/folders/update.ts b/src/api/endpoints/drive/folders/update.ts deleted file mode 100644 index ff673402ab..0000000000 --- a/src/api/endpoints/drive/folders/update.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import DriveFolder from '../../../models/drive-folder'; -import { isValidFolderName } from '../../../models/drive-folder'; -import serialize from '../../../serializers/drive-folder'; -import { publishDriveStream } from '../../../event'; - -/** - * Update a folder - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'folder_id' parameter - const [folderId, folderIdErr] = $(params.folder_id).id().$; - if (folderIdErr) return rej('invalid folder_id param'); - - // Fetch folder - const folder = await DriveFolder - .findOne({ - _id: folderId, - user_id: user._id - }); - - if (folder === null) { - return rej('folder-not-found'); - } - - // Get 'name' parameter - const [name, nameErr] = $(params.name).optional.string().pipe(isValidFolderName).$; - if (nameErr) return rej('invalid name param'); - if (name) folder.name = name; - - // Get 'parent_id' parameter - const [parentId, parentIdErr] = $(params.parent_id).optional.nullable.id().$; - if (parentIdErr) return rej('invalid parent_id param'); - if (parentId !== undefined) { - if (parentId === null) { - folder.parent_id = null; - } else { - // Get parent folder - const parent = await DriveFolder - .findOne({ - _id: parentId, - user_id: user._id - }); - - if (parent === null) { - return rej('parent-folder-not-found'); - } - - // Check if the circular reference will occur - async function checkCircle(folderId) { - // Fetch folder - const folder2 = await DriveFolder.findOne({ - _id: folderId - }, { - _id: true, - parent_id: true - }); - - if (folder2._id.equals(folder._id)) { - return true; - } else if (folder2.parent_id) { - return await checkCircle(folder2.parent_id); - } else { - return false; - } - } - - if (parent.parent_id !== null) { - if (await checkCircle(parent.parent_id)) { - return rej('detected-circular-definition'); - } - } - - folder.parent_id = parent._id; - } - } - - // Update - DriveFolder.update(folder._id, { - $set: { - name: folder.name, - parent_id: folder.parent_id - } - }); - - // Serialize - const folderObj = await serialize(folder); - - // Response - res(folderObj); - - // Publish folder_updated event - publishDriveStream(user._id, 'folder_updated', folderObj); -}); diff --git a/src/api/endpoints/drive/stream.ts b/src/api/endpoints/drive/stream.ts deleted file mode 100644 index 7ee255e5d1..0000000000 --- a/src/api/endpoints/drive/stream.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import DriveFile from '../../models/drive-file'; -import serialize from '../../serializers/drive-file'; - -/** - * Get drive stream - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); - - // Get 'since_id' parameter - const [sinceId, sinceIdErr] = $(params.since_id).optional.id().$; - if (sinceIdErr) return rej('invalid since_id param'); - - // Get 'max_id' parameter - const [maxId, maxIdErr] = $(params.max_id).optional.id().$; - if (maxIdErr) return rej('invalid max_id param'); - - // Check if both of since_id and max_id is specified - if (sinceId && maxId) { - return rej('cannot set since_id and max_id'); - } - - // Get 'type' parameter - const [type, typeErr] = $(params.type).optional.string().match(/^[a-zA-Z\/\-\*]+$/).$; - if (typeErr) return rej('invalid type param'); - - // Construct query - const sort = { - _id: -1 - }; - const query = { - 'metadata.user_id': user._id - } as any; - if (sinceId) { - sort._id = 1; - query._id = { - $gt: sinceId - }; - } else if (maxId) { - query._id = { - $lt: maxId - }; - } - if (type) { - query.contentType = new RegExp(`^${type.replace(/\*/g, '.+?')}$`); - } - - // Issue query - const files = await DriveFile - .find(query, { - limit: limit, - sort: sort - }); - - // Serialize - res(await Promise.all(files.map(async file => - await serialize(file)))); -}); diff --git a/src/api/endpoints/following/create.ts b/src/api/endpoints/following/create.ts deleted file mode 100644 index b4a2217b16..0000000000 --- a/src/api/endpoints/following/create.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import User from '../../models/user'; -import Following from '../../models/following'; -import notify from '../../common/notify'; -import event from '../../event'; -import serializeUser from '../../serializers/user'; - -/** - * Follow a user - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - const follower = user; - - // Get 'user_id' parameter - const [userId, userIdErr] = $(params.user_id).id().$; - if (userIdErr) return rej('invalid user_id param'); - - // 自分自身 - if (user._id.equals(userId)) { - return rej('followee is yourself'); - } - - // Get followee - const followee = await User.findOne({ - _id: userId - }, { - fields: { - data: false, - profile: false - } - }); - - if (followee === null) { - return rej('user not found'); - } - - // Check if already following - const exist = await Following.findOne({ - follower_id: follower._id, - followee_id: followee._id, - deleted_at: { $exists: false } - }); - - if (exist !== null) { - return rej('already following'); - } - - // Create following - await Following.insert({ - created_at: new Date(), - follower_id: follower._id, - followee_id: followee._id - }); - - // Send response - res(); - - // Increment following count - User.update(follower._id, { - $inc: { - following_count: 1 - } - }); - - // Increment followers count - User.update({ _id: followee._id }, { - $inc: { - followers_count: 1 - } - }); - - // Publish follow event - event(follower._id, 'follow', await serializeUser(followee, follower)); - event(followee._id, 'followed', await serializeUser(follower, followee)); - - // Notify - notify(followee._id, follower._id, 'follow'); -}); diff --git a/src/api/endpoints/following/delete.ts b/src/api/endpoints/following/delete.ts deleted file mode 100644 index aa1639ef6c..0000000000 --- a/src/api/endpoints/following/delete.ts +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import User from '../../models/user'; -import Following from '../../models/following'; -import event from '../../event'; -import serializeUser from '../../serializers/user'; - -/** - * Unfollow a user - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - const follower = user; - - // Get 'user_id' parameter - const [userId, userIdErr] = $(params.user_id).id().$; - if (userIdErr) return rej('invalid user_id param'); - - // Check if the followee is yourself - if (user._id.equals(userId)) { - return rej('followee is yourself'); - } - - // Get followee - const followee = await User.findOne({ - _id: userId - }, { - fields: { - data: false, - profile: false - } - }); - - if (followee === null) { - return rej('user not found'); - } - - // Check not following - const exist = await Following.findOne({ - follower_id: follower._id, - followee_id: followee._id, - deleted_at: { $exists: false } - }); - - if (exist === null) { - return rej('already not following'); - } - - // Delete following - await Following.update({ - _id: exist._id - }, { - $set: { - deleted_at: new Date() - } - }); - - // Send response - res(); - - // Decrement following count - User.update({ _id: follower._id }, { - $inc: { - following_count: -1 - } - }); - - // Decrement followers count - User.update({ _id: followee._id }, { - $inc: { - followers_count: -1 - } - }); - - // Publish follow event - event(follower._id, 'unfollow', await serializeUser(followee, follower)); -}); diff --git a/src/api/endpoints/i.ts b/src/api/endpoints/i.ts deleted file mode 100644 index ae75f11d54..0000000000 --- a/src/api/endpoints/i.ts +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Module dependencies - */ -import User from '../models/user'; -import serialize from '../serializers/user'; - -/** - * Show myself - * - * @param {any} params - * @param {any} user - * @param {any} app - * @param {Boolean} isSecure - * @return {Promise<any>} - */ -module.exports = (params, user, _, isSecure) => new Promise(async (res, rej) => { - // Serialize - res(await serialize(user, user, { - detail: true, - includeSecrets: isSecure - })); - - // Update lastUsedAt - User.update({ _id: user._id }, { - $set: { - last_used_at: new Date() - } - }); -}); diff --git a/src/api/endpoints/i/2fa/done.ts b/src/api/endpoints/i/2fa/done.ts deleted file mode 100644 index 0b36033bb6..0000000000 --- a/src/api/endpoints/i/2fa/done.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import * as speakeasy from 'speakeasy'; -import User from '../../../models/user'; - -module.exports = async (params, user) => new Promise(async (res, rej) => { - // Get 'token' parameter - const [token, tokenErr] = $(params.token).string().$; - if (tokenErr) return rej('invalid token param'); - - const _token = token.replace(/\s/g, ''); - - if (user.two_factor_temp_secret == null) { - return rej('二段階認証の設定が開始されていません'); - } - - const verified = (speakeasy as any).totp.verify({ - secret: user.two_factor_temp_secret, - encoding: 'base32', - token: _token - }); - - if (!verified) { - return rej('not verified'); - } - - await User.update(user._id, { - $set: { - two_factor_secret: user.two_factor_temp_secret, - two_factor_enabled: true - } - }); - - res(); -}); diff --git a/src/api/endpoints/i/2fa/register.ts b/src/api/endpoints/i/2fa/register.ts deleted file mode 100644 index c2b5037a29..0000000000 --- a/src/api/endpoints/i/2fa/register.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import * as bcrypt from 'bcryptjs'; -import * as speakeasy from 'speakeasy'; -import * as QRCode from 'qrcode'; -import User from '../../../models/user'; -import config from '../../../../conf'; - -module.exports = async (params, user) => new Promise(async (res, rej) => { - // Get 'password' parameter - const [password, passwordErr] = $(params.password).string().$; - if (passwordErr) return rej('invalid password param'); - - // Compare password - const same = await bcrypt.compare(password, user.password); - - if (!same) { - return rej('incorrect password'); - } - - // Generate user's secret key - const secret = speakeasy.generateSecret({ - length: 32 - }); - - await User.update(user._id, { - $set: { - two_factor_temp_secret: secret.base32 - } - }); - - // Get the data URL of the authenticator URL - QRCode.toDataURL(speakeasy.otpauthURL({ - secret: secret.base32, - encoding: 'base32', - label: user.username, - issuer: config.host - }), (err, data_url) => { - res({ - qr: data_url, - secret: secret.base32, - label: user.username, - issuer: config.host - }); - }); -}); diff --git a/src/api/endpoints/i/2fa/unregister.ts b/src/api/endpoints/i/2fa/unregister.ts deleted file mode 100644 index 6bee6a26f2..0000000000 --- a/src/api/endpoints/i/2fa/unregister.ts +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import * as bcrypt from 'bcryptjs'; -import User from '../../../models/user'; - -module.exports = async (params, user) => new Promise(async (res, rej) => { - // Get 'password' parameter - const [password, passwordErr] = $(params.password).string().$; - if (passwordErr) return rej('invalid password param'); - - // Compare password - const same = await bcrypt.compare(password, user.password); - - if (!same) { - return rej('incorrect password'); - } - - await User.update(user._id, { - $set: { - two_factor_secret: null, - two_factor_enabled: false - } - }); - - res(); -}); diff --git a/src/api/endpoints/i/appdata/get.ts b/src/api/endpoints/i/appdata/get.ts deleted file mode 100644 index 571208d46c..0000000000 --- a/src/api/endpoints/i/appdata/get.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Appdata from '../../../models/appdata'; - -/** - * Get app data - * - * @param {any} params - * @param {any} user - * @param {any} app - * @param {Boolean} isSecure - * @return {Promise<any>} - */ -module.exports = (params, user, app) => new Promise(async (res, rej) => { - if (app == null) return rej('このAPIはサードパーティAppからのみ利用できます'); - - // Get 'key' parameter - const [key = null, keyError] = $(params.key).optional.nullable.string().match(/[a-z_]+/).$; - if (keyError) return rej('invalid key param'); - - const select = {}; - if (key !== null) { - select[`data.${key}`] = true; - } - const appdata = await Appdata.findOne({ - app_id: app._id, - user_id: user._id - }, { - fields: select - }); - - if (appdata) { - res(appdata.data); - } else { - res(); - } -}); diff --git a/src/api/endpoints/i/appdata/set.ts b/src/api/endpoints/i/appdata/set.ts deleted file mode 100644 index 2804a14cb3..0000000000 --- a/src/api/endpoints/i/appdata/set.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Appdata from '../../../models/appdata'; - -/** - * Set app data - * - * @param {any} params - * @param {any} user - * @param {any} app - * @param {Boolean} isSecure - * @return {Promise<any>} - */ -module.exports = (params, user, app) => new Promise(async (res, rej) => { - if (app == null) return rej('このAPIはサードパーティAppからのみ利用できます'); - - // Get 'data' parameter - const [data, dataError] = $(params.data).optional.object() - .pipe(obj => { - const hasInvalidData = Object.entries(obj).some(([k, v]) => - $(k).string().match(/^[a-z_]+$/).nok() && $(v).string().nok()); - return !hasInvalidData; - }).$; - if (dataError) return rej('invalid data param'); - - // Get 'key' parameter - const [key, keyError] = $(params.key).optional.string().match(/[a-z_]+/).$; - if (keyError) return rej('invalid key param'); - - // Get 'value' parameter - const [value, valueError] = $(params.value).optional.string().$; - if (valueError) return rej('invalid value param'); - - const set = {}; - if (data) { - Object.entries(data).forEach(([k, v]) => { - set[`data.${k}`] = v; - }); - } else { - set[`data.${key}`] = value; - } - - await Appdata.update({ - app_id: app._id, - user_id: user._id - }, Object.assign({ - app_id: app._id, - user_id: user._id - }, { - $set: set - }), { - upsert: true - }); - - res(204); -}); diff --git a/src/api/endpoints/i/authorized_apps.ts b/src/api/endpoints/i/authorized_apps.ts deleted file mode 100644 index 807ca5b1e7..0000000000 --- a/src/api/endpoints/i/authorized_apps.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import AccessToken from '../../models/access-token'; -import serialize from '../../serializers/app'; - -/** - * Get authorized apps of my account - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); - - // Get 'offset' parameter - const [offset = 0, offsetErr] = $(params.offset).optional.number().min(0).$; - if (offsetErr) return rej('invalid offset param'); - - // Get 'sort' parameter - const [sort = 'desc', sortError] = $(params.sort).optional.string().or('desc asc').$; - if (sortError) return rej('invalid sort param'); - - // Get tokens - const tokens = await AccessToken - .find({ - user_id: user._id - }, { - limit: limit, - skip: offset, - sort: { - _id: sort == 'asc' ? 1 : -1 - } - }); - - // Serialize - res(await Promise.all(tokens.map(async token => - await serialize(token.app_id)))); -}); diff --git a/src/api/endpoints/i/change_password.ts b/src/api/endpoints/i/change_password.ts deleted file mode 100644 index 16f1a2e4ec..0000000000 --- a/src/api/endpoints/i/change_password.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import * as bcrypt from 'bcryptjs'; -import User from '../../models/user'; - -/** - * Change password - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = async (params, user) => new Promise(async (res, rej) => { - // Get 'current_password' parameter - const [currentPassword, currentPasswordErr] = $(params.current_password).string().$; - if (currentPasswordErr) return rej('invalid current_password param'); - - // Get 'new_password' parameter - const [newPassword, newPasswordErr] = $(params.new_password).string().$; - if (newPasswordErr) return rej('invalid new_password param'); - - // Compare password - const same = await bcrypt.compare(currentPassword, user.password); - - if (!same) { - return rej('incorrect password'); - } - - // Generate hash of password - const salt = await bcrypt.genSalt(8); - const hash = await bcrypt.hash(newPassword, salt); - - await User.update(user._id, { - $set: { - password: hash - } - }); - - res(); -}); diff --git a/src/api/endpoints/i/favorites.ts b/src/api/endpoints/i/favorites.ts deleted file mode 100644 index a66eaa7546..0000000000 --- a/src/api/endpoints/i/favorites.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Favorite from '../../models/favorite'; -import serialize from '../../serializers/post'; - -/** - * Get followers of a user - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); - - // Get 'offset' parameter - const [offset = 0, offsetErr] = $(params.offset).optional.number().min(0).$; - if (offsetErr) return rej('invalid offset param'); - - // Get 'sort' parameter - const [sort = 'desc', sortError] = $(params.sort).optional.string().or('desc asc').$; - if (sortError) return rej('invalid sort param'); - - // Get favorites - const favorites = await Favorite - .find({ - user_id: user._id - }, { - limit: limit, - skip: offset, - sort: { - _id: sort == 'asc' ? 1 : -1 - } - }); - - // Serialize - res(await Promise.all(favorites.map(async favorite => - await serialize(favorite.post) - ))); -}); diff --git a/src/api/endpoints/i/notifications.ts b/src/api/endpoints/i/notifications.ts deleted file mode 100644 index 607e0768a4..0000000000 --- a/src/api/endpoints/i/notifications.ts +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Notification from '../../models/notification'; -import serialize from '../../serializers/notification'; -import getFriends from '../../common/get-friends'; -import read from '../../common/read-notification'; - -/** - * Get notifications - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'following' parameter - const [following = false, followingError] = - $(params.following).optional.boolean().$; - if (followingError) return rej('invalid following param'); - - // Get 'mark_as_read' parameter - const [markAsRead = true, markAsReadErr] = $(params.mark_as_read).optional.boolean().$; - if (markAsReadErr) return rej('invalid mark_as_read param'); - - // Get 'type' parameter - const [type, typeErr] = $(params.type).optional.array('string').unique().$; - if (typeErr) return rej('invalid type param'); - - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); - - // Get 'since_id' parameter - const [sinceId, sinceIdErr] = $(params.since_id).optional.id().$; - if (sinceIdErr) return rej('invalid since_id param'); - - // Get 'max_id' parameter - const [maxId, maxIdErr] = $(params.max_id).optional.id().$; - if (maxIdErr) return rej('invalid max_id param'); - - // Check if both of since_id and max_id is specified - if (sinceId && maxId) { - return rej('cannot set since_id and max_id'); - } - - const query = { - notifiee_id: user._id - } as any; - - const sort = { - _id: -1 - }; - - if (following) { - // ID list of the user $self and other users who the user follows - const followingIds = await getFriends(user._id); - - query.notifier_id = { - $in: followingIds - }; - } - - if (type) { - query.type = { - $in: type - }; - } - - if (sinceId) { - sort._id = 1; - query._id = { - $gt: sinceId - }; - } else if (maxId) { - query._id = { - $lt: maxId - }; - } - - // Issue query - const notifications = await Notification - .find(query, { - limit: limit, - sort: sort - }); - - // Serialize - res(await Promise.all(notifications.map(async notification => - await serialize(notification)))); - - // Mark as read all - if (notifications.length > 0 && markAsRead) { - read(user._id, notifications); - } -}); diff --git a/src/api/endpoints/i/pin.ts b/src/api/endpoints/i/pin.ts deleted file mode 100644 index a94950d22b..0000000000 --- a/src/api/endpoints/i/pin.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import User from '../../models/user'; -import Post from '../../models/post'; -import serialize from '../../serializers/user'; - -/** - * Pin post - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = async (params, user) => new Promise(async (res, rej) => { - // Get 'post_id' parameter - const [postId, postIdErr] = $(params.post_id).id().$; - if (postIdErr) return rej('invalid post_id param'); - - // Fetch pinee - const post = await Post.findOne({ - _id: postId, - user_id: user._id - }); - - if (post === null) { - return rej('post not found'); - } - - await User.update(user._id, { - $set: { - pinned_post_id: post._id - } - }); - - // Serialize - const iObj = await serialize(user, user, { - detail: true - }); - - // Send response - res(iObj); -}); diff --git a/src/api/endpoints/i/regenerate_token.ts b/src/api/endpoints/i/regenerate_token.ts deleted file mode 100644 index 653468330f..0000000000 --- a/src/api/endpoints/i/regenerate_token.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import * as bcrypt from 'bcryptjs'; -import User from '../../models/user'; -import event from '../../event'; -import generateUserToken from '../../common/generate-native-user-token'; - -/** - * Regenerate native token - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = async (params, user) => new Promise(async (res, rej) => { - // Get 'password' parameter - const [password, passwordErr] = $(params.password).string().$; - if (passwordErr) return rej('invalid password param'); - - // Compare password - const same = await bcrypt.compare(password, user.password); - - if (!same) { - return rej('incorrect password'); - } - - // Generate secret - const secret = generateUserToken(); - - await User.update(user._id, { - $set: { - token: secret - } - }); - - res(); - - // Publish event - event(user._id, 'my_token_regenerated'); -}); diff --git a/src/api/endpoints/i/signin_history.ts b/src/api/endpoints/i/signin_history.ts deleted file mode 100644 index 1a6e50c7c8..0000000000 --- a/src/api/endpoints/i/signin_history.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Signin from '../../models/signin'; -import serialize from '../../serializers/signin'; - -/** - * Get signin history of my account - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); - - // Get 'since_id' parameter - const [sinceId, sinceIdErr] = $(params.since_id).optional.id().$; - if (sinceIdErr) return rej('invalid since_id param'); - - // Get 'max_id' parameter - const [maxId, maxIdErr] = $(params.max_id).optional.id().$; - if (maxIdErr) return rej('invalid max_id param'); - - // Check if both of since_id and max_id is specified - if (sinceId && maxId) { - return rej('cannot set since_id and max_id'); - } - - const query = { - user_id: user._id - } as any; - - const sort = { - _id: -1 - }; - - if (sinceId) { - sort._id = 1; - query._id = { - $gt: sinceId - }; - } else if (maxId) { - query._id = { - $lt: maxId - }; - } - - // Issue query - const history = await Signin - .find(query, { - limit: limit, - sort: sort - }); - - // Serialize - res(await Promise.all(history.map(async record => - await serialize(record)))); -}); diff --git a/src/api/endpoints/i/update.ts b/src/api/endpoints/i/update.ts deleted file mode 100644 index c484c51a96..0000000000 --- a/src/api/endpoints/i/update.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import User from '../../models/user'; -import { isValidName, isValidDescription, isValidLocation, isValidBirthday } from '../../models/user'; -import serialize from '../../serializers/user'; -import event from '../../event'; -import config from '../../../conf'; - -/** - * Update myself - * - * @param {any} params - * @param {any} user - * @param {any} _ - * @param {boolean} isSecure - * @return {Promise<any>} - */ -module.exports = async (params, user, _, isSecure) => new Promise(async (res, rej) => { - // Get 'name' parameter - const [name, nameErr] = $(params.name).optional.string().pipe(isValidName).$; - if (nameErr) return rej('invalid name param'); - if (name) user.name = name; - - // Get 'description' parameter - const [description, descriptionErr] = $(params.description).optional.nullable.string().pipe(isValidDescription).$; - if (descriptionErr) return rej('invalid description param'); - if (description !== undefined) user.description = description; - - // Get 'location' parameter - const [location, locationErr] = $(params.location).optional.nullable.string().pipe(isValidLocation).$; - if (locationErr) return rej('invalid location param'); - if (location !== undefined) user.profile.location = location; - - // Get 'birthday' parameter - const [birthday, birthdayErr] = $(params.birthday).optional.nullable.string().pipe(isValidBirthday).$; - if (birthdayErr) return rej('invalid birthday param'); - if (birthday !== undefined) user.profile.birthday = birthday; - - // Get 'avatar_id' parameter - const [avatarId, avatarIdErr] = $(params.avatar_id).optional.id().$; - if (avatarIdErr) return rej('invalid avatar_id param'); - if (avatarId) user.avatar_id = avatarId; - - // Get 'banner_id' parameter - const [bannerId, bannerIdErr] = $(params.banner_id).optional.id().$; - if (bannerIdErr) return rej('invalid banner_id param'); - if (bannerId) user.banner_id = bannerId; - - // Get 'show_donation' parameter - const [showDonation, showDonationErr] = $(params.show_donation).optional.boolean().$; - if (showDonationErr) return rej('invalid show_donation param'); - if (showDonation) user.client_settings.show_donation = showDonation; - - await User.update(user._id, { - $set: { - name: user.name, - description: user.description, - avatar_id: user.avatar_id, - banner_id: user.banner_id, - profile: user.profile, - 'client_settings.show_donation': user.client_settings.show_donation - } - }); - - // Serialize - const iObj = await serialize(user, user, { - detail: true, - includeSecrets: isSecure - }); - - // Send response - res(iObj); - - // Publish i updated event - event(user._id, 'i_updated', iObj); - - // Update search index - if (config.elasticsearch.enable) { - const es = require('../../../db/elasticsearch'); - - es.index({ - index: 'misskey', - type: 'user', - id: user._id.toString(), - body: { - name: user.name, - bio: user.bio - } - }); - } -}); diff --git a/src/api/endpoints/i/update_home.ts b/src/api/endpoints/i/update_home.ts deleted file mode 100644 index 429e88529a..0000000000 --- a/src/api/endpoints/i/update_home.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import User from '../../models/user'; - -/** - * Update myself - * - * @param {any} params - * @param {any} user - * @param {any} _ - * @param {boolean} isSecure - * @return {Promise<any>} - */ -module.exports = async (params, user, _, isSecure) => new Promise(async (res, rej) => { - // Get 'home' parameter - const [home, homeErr] = $(params.home).optional.array().each( - $().strict.object() - .have('name', $().string()) - .have('id', $().string()) - .have('place', $().string()) - .have('data', $().object())).$; - if (homeErr) return rej('invalid home param'); - - // Get 'id' parameter - const [id, idErr] = $(params.id).optional.string().$; - if (idErr) return rej('invalid id param'); - - // Get 'data' parameter - const [data, dataErr] = $(params.data).optional.object().$; - if (dataErr) return rej('invalid data param'); - - if (home) { - await User.update(user._id, { - $set: { - 'client_settings.home': home - } - }); - - res(); - } else { - if (id == null && data == null) return rej('you need to set id and data params if home param unset'); - - const _home = user.client_settings.home; - const widget = _home.find(w => w.id == id); - - if (widget == null) return rej('widget not found'); - - widget.data = data; - - await User.update(user._id, { - $set: { - 'client_settings.home': _home - } - }); - - res(); - } -}); diff --git a/src/api/endpoints/messaging/history.ts b/src/api/endpoints/messaging/history.ts deleted file mode 100644 index 5f7c9276dd..0000000000 --- a/src/api/endpoints/messaging/history.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import History from '../../models/messaging-history'; -import serialize from '../../serializers/messaging-message'; - -/** - * Show messaging history - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); - - // Get history - const history = await History - .find({ - user_id: user._id - }, { - limit: limit, - sort: { - updated_at: -1 - } - }); - - // Serialize - res(await Promise.all(history.map(async h => - await serialize(h.message, user)))); -}); diff --git a/src/api/endpoints/messaging/messages.ts b/src/api/endpoints/messaging/messages.ts deleted file mode 100644 index 7b270924eb..0000000000 --- a/src/api/endpoints/messaging/messages.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Message from '../../models/messaging-message'; -import User from '../../models/user'; -import serialize from '../../serializers/messaging-message'; -import read from '../../common/read-messaging-message'; - -/** - * Get messages - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'user_id' parameter - const [recipientId, recipientIdErr] = $(params.user_id).id().$; - if (recipientIdErr) return rej('invalid user_id param'); - - // Fetch recipient - const recipient = await User.findOne({ - _id: recipientId - }, { - fields: { - _id: true - } - }); - - if (recipient === null) { - return rej('user not found'); - } - - // Get 'mark_as_read' parameter - const [markAsRead = true, markAsReadErr] = $(params.mark_as_read).optional.boolean().$; - if (markAsReadErr) return rej('invalid mark_as_read param'); - - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); - - // Get 'since_id' parameter - const [sinceId, sinceIdErr] = $(params.since_id).optional.id().$; - if (sinceIdErr) return rej('invalid since_id param'); - - // Get 'max_id' parameter - const [maxId, maxIdErr] = $(params.max_id).optional.id().$; - if (maxIdErr) return rej('invalid max_id param'); - - // Check if both of since_id and max_id is specified - if (sinceId && maxId) { - return rej('cannot set since_id and max_id'); - } - - const query = { - $or: [{ - user_id: user._id, - recipient_id: recipient._id - }, { - user_id: recipient._id, - recipient_id: user._id - }] - } as any; - - const sort = { - _id: -1 - }; - - if (sinceId) { - sort._id = 1; - query._id = { - $gt: sinceId - }; - } else if (maxId) { - query._id = { - $lt: maxId - }; - } - - // Issue query - const messages = await Message - .find(query, { - limit: limit, - sort: sort - }); - - // Serialize - res(await Promise.all(messages.map(async message => - await serialize(message, user, { - populateRecipient: false - })))); - - if (messages.length === 0) { - return; - } - - // Mark as read all - if (markAsRead) { - read(user._id, recipient._id, messages); - } -}); diff --git a/src/api/endpoints/messaging/messages/create.ts b/src/api/endpoints/messaging/messages/create.ts deleted file mode 100644 index 3c7689f967..0000000000 --- a/src/api/endpoints/messaging/messages/create.ts +++ /dev/null @@ -1,144 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Message from '../../../models/messaging-message'; -import { isValidText } from '../../../models/messaging-message'; -import History from '../../../models/messaging-history'; -import User from '../../../models/user'; -import DriveFile from '../../../models/drive-file'; -import serialize from '../../../serializers/messaging-message'; -import publishUserStream from '../../../event'; -import { publishMessagingStream, publishMessagingIndexStream, pushSw } from '../../../event'; -import config from '../../../../conf'; - -/** - * Create a message - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'user_id' parameter - const [recipientId, recipientIdErr] = $(params.user_id).id().$; - if (recipientIdErr) return rej('invalid user_id param'); - - // Myself - if (recipientId.equals(user._id)) { - return rej('cannot send message to myself'); - } - - // Fetch recipient - const recipient = await User.findOne({ - _id: recipientId - }, { - fields: { - _id: true - } - }); - - if (recipient === null) { - return rej('user not found'); - } - - // Get 'text' parameter - const [text, textErr] = $(params.text).optional.string().pipe(isValidText).$; - if (textErr) return rej('invalid text'); - - // Get 'file_id' parameter - const [fileId, fileIdErr] = $(params.file_id).optional.id().$; - if (fileIdErr) return rej('invalid file_id param'); - - let file = null; - if (fileId !== undefined) { - file = await DriveFile.findOne({ - _id: fileId, - 'metadata.user_id': user._id - }); - - if (file === null) { - return rej('file not found'); - } - } - - // テキストが無いかつ添付ファイルも無かったらエラー - if (text === undefined && file === null) { - return rej('text or file is required'); - } - - // メッセージを作成 - const message = await Message.insert({ - created_at: new Date(), - file_id: file ? file._id : undefined, - recipient_id: recipient._id, - text: text ? text : undefined, - user_id: user._id, - is_read: false - }); - - // Serialize - const messageObj = await serialize(message); - - // Reponse - res(messageObj); - - // 自分のストリーム - publishMessagingStream(message.user_id, message.recipient_id, 'message', messageObj); - publishMessagingIndexStream(message.user_id, 'message', messageObj); - publishUserStream(message.user_id, 'messaging_message', messageObj); - - // 相手のストリーム - publishMessagingStream(message.recipient_id, message.user_id, 'message', messageObj); - publishMessagingIndexStream(message.recipient_id, 'message', messageObj); - publishUserStream(message.recipient_id, 'messaging_message', messageObj); - - // 3秒経っても(今回作成した)メッセージが既読にならなかったら「未読のメッセージがありますよ」イベントを発行する - setTimeout(async () => { - const freshMessage = await Message.findOne({ _id: message._id }, { is_read: true }); - if (!freshMessage.is_read) { - publishUserStream(message.recipient_id, 'unread_messaging_message', messageObj); - pushSw(message.recipient_id, 'unread_messaging_message', messageObj); - } - }, 3000); - - // Register to search database - if (message.text && config.elasticsearch.enable) { - const es = require('../../../db/elasticsearch'); - - es.index({ - index: 'misskey', - type: 'messaging_message', - id: message._id.toString(), - body: { - text: message.text - } - }); - } - - // 履歴作成(自分) - History.update({ - user_id: user._id, - partner: recipient._id - }, { - updated_at: new Date(), - user_id: user._id, - partner: recipient._id, - message: message._id - }, { - upsert: true - }); - - // 履歴作成(相手) - History.update({ - user_id: recipient._id, - partner: user._id - }, { - updated_at: new Date(), - user_id: recipient._id, - partner: user._id, - message: message._id - }, { - upsert: true - }); -}); diff --git a/src/api/endpoints/messaging/unread.ts b/src/api/endpoints/messaging/unread.ts deleted file mode 100644 index 40bc83fe1c..0000000000 --- a/src/api/endpoints/messaging/unread.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Module dependencies - */ -import Message from '../../models/messaging-message'; - -/** - * Get count of unread messages - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - const count = await Message - .count({ - recipient_id: user._id, - is_read: false - }); - - res({ - count: count - }); -}); diff --git a/src/api/endpoints/meta.ts b/src/api/endpoints/meta.ts deleted file mode 100644 index 1370ead3c5..0000000000 --- a/src/api/endpoints/meta.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Module dependencies - */ -import * as os from 'os'; -import version from '../../version'; -import config from '../../conf'; -import Meta from '../models/meta'; - -/** - * @swagger - * /meta: - * post: - * summary: Show the misskey's information - * responses: - * 200: - * description: Success - * schema: - * type: object - * properties: - * maintainer: - * description: maintainer's name - * type: string - * commit: - * description: latest commit's hash - * type: string - * secure: - * description: whether the server supports secure protocols - * type: boolean - * - * default: - * description: Failed - * schema: - * $ref: "#/definitions/Error" - */ - -/** - * Show core info - * - * @param {any} params - * @return {Promise<any>} - */ -module.exports = (params) => new Promise(async (res, rej) => { - const meta = (await Meta.findOne()) || {}; - - res({ - maintainer: config.maintainer, - version: version, - secure: config.https != null, - machine: os.hostname(), - os: os.platform(), - node: process.version, - cpu: { - model: os.cpus()[0].model, - cores: os.cpus().length - }, - top_image: meta.top_image, - broadcasts: meta.broadcasts - }); -}); diff --git a/src/api/endpoints/my/apps.ts b/src/api/endpoints/my/apps.ts deleted file mode 100644 index eb9c758768..0000000000 --- a/src/api/endpoints/my/apps.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import App from '../../models/app'; -import serialize from '../../serializers/app'; - -/** - * Get my apps - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); - - // Get 'offset' parameter - const [offset = 0, offsetErr] = $(params.offset).optional.number().min(0).$; - if (offsetErr) return rej('invalid offset param'); - - const query = { - user_id: user._id - }; - - // Execute query - const apps = await App - .find(query, { - limit: limit, - skip: offset, - sort: { - _id: -1 - } - }); - - // Reply - res(await Promise.all(apps.map(async app => - await serialize(app)))); -}); diff --git a/src/api/endpoints/notifications/get_unread_count.ts b/src/api/endpoints/notifications/get_unread_count.ts deleted file mode 100644 index 9514e78713..0000000000 --- a/src/api/endpoints/notifications/get_unread_count.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Module dependencies - */ -import Notification from '../../models/notification'; - -/** - * Get count of unread notifications - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - const count = await Notification - .count({ - notifiee_id: user._id, - is_read: false - }); - - res({ - count: count - }); -}); diff --git a/src/api/endpoints/notifications/mark_as_read_all.ts b/src/api/endpoints/notifications/mark_as_read_all.ts deleted file mode 100644 index 3550e344c4..0000000000 --- a/src/api/endpoints/notifications/mark_as_read_all.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Module dependencies - */ -import Notification from '../../models/notification'; -import event from '../../event'; - -/** - * Mark as read all notifications - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Update documents - await Notification.update({ - notifiee_id: user._id, - is_read: false - }, { - $set: { - is_read: true - } - }, { - multi: true - }); - - // Response - res(); - - // 全ての通知を読みましたよというイベントを発行 - event(user._id, 'read_all_notifications'); -}); diff --git a/src/api/endpoints/posts.ts b/src/api/endpoints/posts.ts deleted file mode 100644 index f6efcc108d..0000000000 --- a/src/api/endpoints/posts.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Post from '../models/post'; -import serialize from '../serializers/post'; - -/** - * Lists all posts - * - * @param {any} params - * @return {Promise<any>} - */ -module.exports = (params) => new Promise(async (res, rej) => { - // Get 'reply' parameter - const [reply, replyErr] = $(params.reply).optional.boolean().$; - if (replyErr) return rej('invalid reply param'); - - // Get 'repost' parameter - const [repost, repostErr] = $(params.repost).optional.boolean().$; - if (repostErr) return rej('invalid repost param'); - - // Get 'media' parameter - const [media, mediaErr] = $(params.media).optional.boolean().$; - if (mediaErr) return rej('invalid media param'); - - // Get 'poll' parameter - const [poll, pollErr] = $(params.poll).optional.boolean().$; - if (pollErr) return rej('invalid poll param'); - - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); - - // Get 'since_id' parameter - const [sinceId, sinceIdErr] = $(params.since_id).optional.id().$; - if (sinceIdErr) return rej('invalid since_id param'); - - // Get 'max_id' parameter - const [maxId, maxIdErr] = $(params.max_id).optional.id().$; - if (maxIdErr) return rej('invalid max_id param'); - - // Check if both of since_id and max_id is specified - if (sinceId && maxId) { - return rej('cannot set since_id and max_id'); - } - - // Construct query - const sort = { - _id: -1 - }; - const query = {} as any; - if (sinceId) { - sort._id = 1; - query._id = { - $gt: sinceId - }; - } else if (maxId) { - query._id = { - $lt: maxId - }; - } - - if (reply != undefined) { - query.reply_id = reply ? { $exists: true, $ne: null } : null; - } - - if (repost != undefined) { - query.repost_id = repost ? { $exists: true, $ne: null } : null; - } - - if (media != undefined) { - query.media_ids = media ? { $exists: true, $ne: null } : null; - } - - if (poll != undefined) { - query.poll = poll ? { $exists: true, $ne: null } : null; - } - - // Issue query - const posts = await Post - .find(query, { - limit: limit, - sort: sort - }); - - // Serialize - res(await Promise.all(posts.map(async post => await serialize(post)))); -}); diff --git a/src/api/endpoints/posts/categorize.ts b/src/api/endpoints/posts/categorize.ts deleted file mode 100644 index 3530ba6bc4..0000000000 --- a/src/api/endpoints/posts/categorize.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Post from '../../models/post'; - -/** - * Categorize a post - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - if (!user.is_pro) { - return rej('This endpoint is available only from a Pro account'); - } - - // Get 'post_id' parameter - const [postId, postIdErr] = $(params.post_id).id().$; - if (postIdErr) return rej('invalid post_id param'); - - // Get categorizee - const post = await Post.findOne({ - _id: postId - }); - - if (post === null) { - return rej('post not found'); - } - - if (post.is_category_verified) { - return rej('This post already has the verified category'); - } - - // Get 'category' parameter - const [category, categoryErr] = $(params.category).string().or([ - 'music', 'game', 'anime', 'it', 'gadgets', 'photography' - ]).$; - if (categoryErr) return rej('invalid category param'); - - // Set category - Post.update({ _id: post._id }, { - $set: { - category: category, - is_category_verified: true - } - }); - - // Send response - res(); -}); diff --git a/src/api/endpoints/posts/context.ts b/src/api/endpoints/posts/context.ts deleted file mode 100644 index bad59a6bee..0000000000 --- a/src/api/endpoints/posts/context.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Post from '../../models/post'; -import serialize from '../../serializers/post'; - -/** - * Show a context of a post - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'post_id' parameter - const [postId, postIdErr] = $(params.post_id).id().$; - if (postIdErr) return rej('invalid post_id param'); - - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); - - // Get 'offset' parameter - const [offset = 0, offsetErr] = $(params.offset).optional.number().min(0).$; - if (offsetErr) return rej('invalid offset param'); - - // Lookup post - const post = await Post.findOne({ - _id: postId - }); - - if (post === null) { - return rej('post not found'); - } - - const context = []; - let i = 0; - - async function get(id) { - i++; - const p = await Post.findOne({ _id: id }); - - if (i > offset) { - context.push(p); - } - - if (context.length == limit) { - return; - } - - if (p.reply_id) { - await get(p.reply_id); - } - } - - if (post.reply_id) { - await get(post.reply_id); - } - - // Serialize - res(await Promise.all(context.map(async post => - await serialize(post, user)))); -}); diff --git a/src/api/endpoints/posts/create.ts b/src/api/endpoints/posts/create.ts deleted file mode 100644 index ae4959dae4..0000000000 --- a/src/api/endpoints/posts/create.ts +++ /dev/null @@ -1,484 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import deepEqual = require('deep-equal'); -import parse from '../../common/text'; -import { default as Post, IPost, isValidText } from '../../models/post'; -import { default as User, IUser } from '../../models/user'; -import { default as Channel, IChannel } from '../../models/channel'; -import Following from '../../models/following'; -import DriveFile from '../../models/drive-file'; -import Watching from '../../models/post-watching'; -import ChannelWatching from '../../models/channel-watching'; -import serialize from '../../serializers/post'; -import notify from '../../common/notify'; -import watch from '../../common/watch-post'; -import event, { pushSw, publishChannelStream } from '../../event'; -import config from '../../../conf'; - -/** - * Create a post - * - * @param {any} params - * @param {any} user - * @param {any} app - * @return {Promise<any>} - */ -module.exports = (params, user: IUser, app) => new Promise(async (res, rej) => { - // Get 'text' parameter - const [text, textErr] = $(params.text).optional.string().pipe(isValidText).$; - if (textErr) return rej('invalid text'); - - // Get 'media_ids' parameter - const [mediaIds, mediaIdsErr] = $(params.media_ids).optional.array('id').unique().range(1, 4).$; - if (mediaIdsErr) return rej('invalid media_ids'); - - let files = []; - if (mediaIds !== undefined) { - // Fetch files - // forEach だと途中でエラーなどがあっても return できないので - // 敢えて for を使っています。 - for (const mediaId of mediaIds) { - // Fetch file - // SELECT _id - const entity = await DriveFile.findOne({ - _id: mediaId, - 'metadata.user_id': user._id - }); - - if (entity === null) { - return rej('file not found'); - } else { - files.push(entity); - } - } - } else { - files = null; - } - - // Get 'repost_id' parameter - const [repostId, repostIdErr] = $(params.repost_id).optional.id().$; - if (repostIdErr) return rej('invalid repost_id'); - - let repost: IPost = null; - let isQuote = false; - if (repostId !== undefined) { - // Fetch repost to post - repost = await Post.findOne({ - _id: repostId - }); - - if (repost == null) { - return rej('repostee is not found'); - } else if (repost.repost_id && !repost.text && !repost.media_ids) { - return rej('cannot repost to repost'); - } - - // Fetch recently post - const latestPost = await Post.findOne({ - user_id: user._id - }, { - sort: { - _id: -1 - } - }); - - isQuote = text != null || files != null; - - // 直近と同じRepost対象かつ引用じゃなかったらエラー - if (latestPost && - latestPost.repost_id && - latestPost.repost_id.equals(repost._id) && - !isQuote) { - return rej('cannot repost same post that already reposted in your latest post'); - } - - // 直近がRepost対象かつ引用じゃなかったらエラー - if (latestPost && - latestPost._id.equals(repost._id) && - !isQuote) { - return rej('cannot repost your latest post'); - } - } - - // Get 'reply_id' parameter - const [replyId, replyIdErr] = $(params.reply_id).optional.id().$; - if (replyIdErr) return rej('invalid reply_id'); - - let reply: IPost = null; - if (replyId !== undefined) { - // Fetch reply - reply = await Post.findOne({ - _id: replyId - }); - - if (reply === null) { - return rej('in reply to post is not found'); - } - - // 返信対象が引用でないRepostだったらエラー - if (reply.repost_id && !reply.text && !reply.media_ids) { - return rej('cannot reply to repost'); - } - } - - // Get 'channel_id' parameter - const [channelId, channelIdErr] = $(params.channel_id).optional.id().$; - if (channelIdErr) return rej('invalid channel_id'); - - let channel: IChannel = null; - if (channelId !== undefined) { - // Fetch channel - channel = await Channel.findOne({ - _id: channelId - }); - - if (channel === null) { - return rej('channel not found'); - } - - // 返信対象の投稿がこのチャンネルじゃなかったらダメ - if (reply && !channelId.equals(reply.channel_id)) { - return rej('チャンネル内部からチャンネル外部の投稿に返信することはできません'); - } - - // Repost対象の投稿がこのチャンネルじゃなかったらダメ - if (repost && !channelId.equals(repost.channel_id)) { - return rej('チャンネル内部からチャンネル外部の投稿をRepostすることはできません'); - } - - // 引用ではないRepostはダメ - if (repost && !isQuote) { - return rej('チャンネル内部では引用ではないRepostをすることはできません'); - } - } else { - // 返信対象の投稿がチャンネルへの投稿だったらダメ - if (reply && reply.channel_id != null) { - return rej('チャンネル外部からチャンネル内部の投稿に返信することはできません'); - } - - // Repost対象の投稿がチャンネルへの投稿だったらダメ - if (repost && repost.channel_id != null) { - return rej('チャンネル外部からチャンネル内部の投稿をRepostすることはできません'); - } - } - - // Get 'poll' parameter - const [poll, pollErr] = $(params.poll).optional.strict.object() - .have('choices', $().array('string') - .unique() - .range(2, 10) - .each(c => c.length > 0 && c.length < 50)) - .$; - if (pollErr) return rej('invalid poll'); - - if (poll) { - (poll as any).choices = (poll as any).choices.map((choice, i) => ({ - id: i, // IDを付与 - text: choice.trim(), - votes: 0 - })); - } - - // テキストが無いかつ添付ファイルが無いかつRepostも無いかつ投票も無かったらエラー - if (text === undefined && files === null && repost === null && poll === undefined) { - return rej('text, media_ids, repost_id or poll is required'); - } - - // 直近の投稿と重複してたらエラー - // TODO: 直近の投稿が一日前くらいなら重複とは見なさない - if (user.latest_post) { - if (deepEqual({ - text: user.latest_post.text, - reply: user.latest_post.reply_id ? user.latest_post.reply_id.toString() : null, - repost: user.latest_post.repost_id ? user.latest_post.repost_id.toString() : null, - media_ids: (user.latest_post.media_ids || []).map(id => id.toString()) - }, { - text: text, - reply: reply ? reply._id.toString() : null, - repost: repost ? repost._id.toString() : null, - media_ids: (files || []).map(file => file._id.toString()) - })) { - return rej('duplicate'); - } - } - - // 投稿を作成 - const post = await Post.insert({ - created_at: new Date(), - channel_id: channel ? channel._id : undefined, - index: channel ? channel.index + 1 : undefined, - media_ids: files ? files.map(file => file._id) : undefined, - reply_id: reply ? reply._id : undefined, - repost_id: repost ? repost._id : undefined, - poll: poll, - text: text, - user_id: user._id, - app_id: app ? app._id : null - }); - - // Serialize - const postObj = await serialize(post); - - // Reponse - res(postObj); - - //#region Post processes - - User.update({ _id: user._id }, { - $set: { - latest_post: post - } - }); - - const mentions = []; - - function addMention(mentionee, reason) { - // Reject if already added - if (mentions.some(x => x.equals(mentionee))) return; - - // Add mention - mentions.push(mentionee); - - // Publish event - if (!user._id.equals(mentionee)) { - event(mentionee, reason, postObj); - pushSw(mentionee, reason, postObj); - } - } - - // タイムラインへの投稿 - if (!channel) { - // Publish event to myself's stream - event(user._id, 'post', postObj); - - // Fetch all followers - const followers = await Following - .find({ - followee_id: user._id, - // 削除されたドキュメントは除く - deleted_at: { $exists: false } - }, { - follower_id: true, - _id: false - }); - - // Publish event to followers stream - followers.forEach(following => - event(following.follower_id, 'post', postObj)); - } - - // チャンネルへの投稿 - if (channel) { - // Increment channel index(posts count) - Channel.update({ _id: channel._id }, { - $inc: { - index: 1 - } - }); - - // Publish event to channel - publishChannelStream(channel._id, 'post', postObj); - - // Get channel watchers - const watches = await ChannelWatching.find({ - channel_id: channel._id, - // 削除されたドキュメントは除く - deleted_at: { $exists: false } - }); - - // チャンネルの視聴者(のタイムライン)に配信 - watches.forEach(w => { - event(w.user_id, 'post', postObj); - }); - } - - // Increment my posts count - User.update({ _id: user._id }, { - $inc: { - posts_count: 1 - } - }); - - // If has in reply to post - if (reply) { - // Increment replies count - Post.update({ _id: reply._id }, { - $inc: { - replies_count: 1 - } - }); - - // 自分自身へのリプライでない限りは通知を作成 - notify(reply.user_id, user._id, 'reply', { - post_id: post._id - }); - - // Fetch watchers - Watching - .find({ - post_id: reply._id, - user_id: { $ne: user._id }, - // 削除されたドキュメントは除く - deleted_at: { $exists: false } - }, { - fields: { - user_id: true - } - }) - .then(watchers => { - watchers.forEach(watcher => { - notify(watcher.user_id, user._id, 'reply', { - post_id: post._id - }); - }); - }); - - // この投稿をWatchする - // TODO: ユーザーが「返信したときに自動でWatchする」設定を - // オフにしていた場合はしない - watch(user._id, reply); - - // Add mention - addMention(reply.user_id, 'reply'); - } - - // If it is repost - if (repost) { - // Notify - const type = text ? 'quote' : 'repost'; - notify(repost.user_id, user._id, type, { - post_id: post._id - }); - - // Fetch watchers - Watching - .find({ - post_id: repost._id, - user_id: { $ne: user._id }, - // 削除されたドキュメントは除く - deleted_at: { $exists: false } - }, { - fields: { - user_id: true - } - }) - .then(watchers => { - watchers.forEach(watcher => { - notify(watcher.user_id, user._id, type, { - post_id: post._id - }); - }); - }); - - // この投稿をWatchする - // TODO: ユーザーが「Repostしたときに自動でWatchする」設定を - // オフにしていた場合はしない - watch(user._id, repost); - - // If it is quote repost - if (text) { - // Add mention - addMention(repost.user_id, 'quote'); - } else { - // Publish event - if (!user._id.equals(repost.user_id)) { - event(repost.user_id, 'repost', postObj); - } - } - - // 今までで同じ投稿をRepostしているか - const existRepost = await Post.findOne({ - user_id: user._id, - repost_id: repost._id, - _id: { - $ne: post._id - } - }); - - if (!existRepost) { - // Update repostee status - Post.update({ _id: repost._id }, { - $inc: { - repost_count: 1 - } - }); - } - } - - // If has text content - if (text) { - // Analyze - const tokens = parse(text); - /* - // Extract a hashtags - const hashtags = tokens - .filter(t => t.type == 'hashtag') - .map(t => t.hashtag) - // Drop dupulicates - .filter((v, i, s) => s.indexOf(v) == i); - - // ハッシュタグをデータベースに登録 - registerHashtags(user, hashtags); - */ - // Extract an '@' mentions - const atMentions = tokens - .filter(t => t.type == 'mention') - .map(m => m.username) - // Drop dupulicates - .filter((v, i, s) => s.indexOf(v) == i); - - // Resolve all mentions - await Promise.all(atMentions.map(async (mention) => { - // Fetch mentioned user - // SELECT _id - const mentionee = await User - .findOne({ - username_lower: mention.toLowerCase() - }, { _id: true }); - - // When mentioned user not found - if (mentionee == null) return; - - // 既に言及されたユーザーに対する返信や引用repostの場合も無視 - if (reply && reply.user_id.equals(mentionee._id)) return; - if (repost && repost.user_id.equals(mentionee._id)) return; - - // Add mention - addMention(mentionee._id, 'mention'); - - // Create notification - notify(mentionee._id, user._id, 'mention', { - post_id: post._id - }); - - return; - })); - } - - // Register to search database - if (text && config.elasticsearch.enable) { - const es = require('../../../db/elasticsearch'); - - es.index({ - index: 'misskey', - type: 'post', - id: post._id.toString(), - body: { - text: post.text - } - }); - } - - // Append mentions data - if (mentions.length > 0) { - Post.update({ _id: post._id }, { - $set: { - mentions: mentions - } - }); - } - - //#endregion -}); diff --git a/src/api/endpoints/posts/favorites/create.ts b/src/api/endpoints/posts/favorites/create.ts deleted file mode 100644 index f9dee271b5..0000000000 --- a/src/api/endpoints/posts/favorites/create.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Favorite from '../../../models/favorite'; -import Post from '../../../models/post'; - -/** - * Favorite a post - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'post_id' parameter - const [postId, postIdErr] = $(params.post_id).id().$; - if (postIdErr) return rej('invalid post_id param'); - - // Get favoritee - const post = await Post.findOne({ - _id: postId - }); - - if (post === null) { - return rej('post not found'); - } - - // if already favorited - const exist = await Favorite.findOne({ - post_id: post._id, - user_id: user._id - }); - - if (exist !== null) { - return rej('already favorited'); - } - - // Create favorite - await Favorite.insert({ - created_at: new Date(), - post_id: post._id, - user_id: user._id - }); - - // Send response - res(); -}); diff --git a/src/api/endpoints/posts/favorites/delete.ts b/src/api/endpoints/posts/favorites/delete.ts deleted file mode 100644 index c4fe7d3234..0000000000 --- a/src/api/endpoints/posts/favorites/delete.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Favorite from '../../../models/favorite'; -import Post from '../../../models/post'; - -/** - * Unfavorite a post - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'post_id' parameter - const [postId, postIdErr] = $(params.post_id).id().$; - if (postIdErr) return rej('invalid post_id param'); - - // Get favoritee - const post = await Post.findOne({ - _id: postId - }); - - if (post === null) { - return rej('post not found'); - } - - // if already favorited - const exist = await Favorite.findOne({ - post_id: post._id, - user_id: user._id - }); - - if (exist === null) { - return rej('already not favorited'); - } - - // Delete favorite - await Favorite.deleteOne({ - _id: exist._id - }); - - // Send response - res(); -}); diff --git a/src/api/endpoints/posts/mentions.ts b/src/api/endpoints/posts/mentions.ts deleted file mode 100644 index 0ebe8be503..0000000000 --- a/src/api/endpoints/posts/mentions.ts +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Post from '../../models/post'; -import getFriends from '../../common/get-friends'; -import serialize from '../../serializers/post'; - -/** - * Get mentions of myself - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'following' parameter - const [following = false, followingError] = - $(params.following).optional.boolean().$; - if (followingError) return rej('invalid following param'); - - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); - - // Get 'since_id' parameter - const [sinceId, sinceIdErr] = $(params.since_id).optional.id().$; - if (sinceIdErr) return rej('invalid since_id param'); - - // Get 'max_id' parameter - const [maxId, maxIdErr] = $(params.max_id).optional.id().$; - if (maxIdErr) return rej('invalid max_id param'); - - // Check if both of since_id and max_id is specified - if (sinceId && maxId) { - return rej('cannot set since_id and max_id'); - } - - // Construct query - const query = { - mentions: user._id - } as any; - - const sort = { - _id: -1 - }; - - if (following) { - const followingIds = await getFriends(user._id); - - query.user_id = { - $in: followingIds - }; - } - - if (sinceId) { - sort._id = 1; - query._id = { - $gt: sinceId - }; - } else if (maxId) { - query._id = { - $lt: maxId - }; - } - - // Issue query - const mentions = await Post - .find(query, { - limit: limit, - sort: sort - }); - - // Serialize - res(await Promise.all(mentions.map(async mention => - await serialize(mention, user) - ))); -}); diff --git a/src/api/endpoints/posts/polls/recommendation.ts b/src/api/endpoints/posts/polls/recommendation.ts deleted file mode 100644 index 9c92d6cac4..0000000000 --- a/src/api/endpoints/posts/polls/recommendation.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Vote from '../../../models/poll-vote'; -import Post from '../../../models/post'; -import serialize from '../../../serializers/post'; - -/** - * Get recommended polls - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); - - // Get 'offset' parameter - const [offset = 0, offsetErr] = $(params.offset).optional.number().min(0).$; - if (offsetErr) return rej('invalid offset param'); - - // Get votes - const votes = await Vote.find({ - user_id: user._id - }, { - fields: { - _id: false, - post_id: true - } - }); - - const nin = votes && votes.length != 0 ? votes.map(v => v.post_id) : []; - - const posts = await Post - .find({ - _id: { - $nin: nin - }, - user_id: { - $ne: user._id - }, - poll: { - $exists: true, - $ne: null - } - }, { - limit: limit, - skip: offset, - sort: { - _id: -1 - } - }); - - // Serialize - res(await Promise.all(posts.map(async post => - await serialize(post, user, { detail: true })))); -}); diff --git a/src/api/endpoints/posts/polls/vote.ts b/src/api/endpoints/posts/polls/vote.ts deleted file mode 100644 index 5a4fd1c268..0000000000 --- a/src/api/endpoints/posts/polls/vote.ts +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Vote from '../../../models/poll-vote'; -import Post from '../../../models/post'; -import Watching from '../../../models/post-watching'; -import notify from '../../../common/notify'; -import watch from '../../../common/watch-post'; -import { publishPostStream } from '../../../event'; - -/** - * Vote poll of a post - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'post_id' parameter - const [postId, postIdErr] = $(params.post_id).id().$; - if (postIdErr) return rej('invalid post_id param'); - - // Get votee - const post = await Post.findOne({ - _id: postId - }); - - if (post === null) { - return rej('post not found'); - } - - if (post.poll == null) { - return rej('poll not found'); - } - - // Get 'choice' parameter - const [choice, choiceError] = - $(params.choice).number() - .pipe(c => post.poll.choices.some(x => x.id == c)) - .$; - if (choiceError) return rej('invalid choice param'); - - // if already voted - const exist = await Vote.findOne({ - post_id: post._id, - user_id: user._id - }); - - if (exist !== null) { - return rej('already voted'); - } - - // Create vote - await Vote.insert({ - created_at: new Date(), - post_id: post._id, - user_id: user._id, - choice: choice - }); - - // Send response - res(); - - const inc = {}; - inc[`poll.choices.${findWithAttr(post.poll.choices, 'id', choice)}.votes`] = 1; - - // Increment votes count - await Post.update({ _id: post._id }, { - $inc: inc - }); - - publishPostStream(post._id, 'poll_voted'); - - // Notify - notify(post.user_id, user._id, 'poll_vote', { - post_id: post._id, - choice: choice - }); - - // Fetch watchers - Watching - .find({ - post_id: post._id, - user_id: { $ne: user._id }, - // 削除されたドキュメントは除く - deleted_at: { $exists: false } - }, { - fields: { - user_id: true - } - }) - .then(watchers => { - watchers.forEach(watcher => { - notify(watcher.user_id, user._id, 'poll_vote', { - post_id: post._id, - choice: choice - }); - }); - }); - - // この投稿をWatchする - // TODO: ユーザーが「投票したときに自動でWatchする」設定を - // オフにしていた場合はしない - watch(user._id, post); -}); - -function findWithAttr(array, attr, value) { - for (let i = 0; i < array.length; i += 1) { - if (array[i][attr] === value) { - return i; - } - } - return -1; -} diff --git a/src/api/endpoints/posts/reactions.ts b/src/api/endpoints/posts/reactions.ts deleted file mode 100644 index eab5d9b258..0000000000 --- a/src/api/endpoints/posts/reactions.ts +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Post from '../../models/post'; -import Reaction from '../../models/post-reaction'; -import serialize from '../../serializers/post-reaction'; - -/** - * Show reactions of a post - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'post_id' parameter - const [postId, postIdErr] = $(params.post_id).id().$; - if (postIdErr) return rej('invalid post_id param'); - - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); - - // Get 'offset' parameter - const [offset = 0, offsetErr] = $(params.offset).optional.number().min(0).$; - if (offsetErr) return rej('invalid offset param'); - - // Get 'sort' parameter - const [sort = 'desc', sortError] = $(params.sort).optional.string().or('desc asc').$; - if (sortError) return rej('invalid sort param'); - - // Lookup post - const post = await Post.findOne({ - _id: postId - }); - - if (post === null) { - return rej('post not found'); - } - - // Issue query - const reactions = await Reaction - .find({ - post_id: post._id, - deleted_at: { $exists: false } - }, { - limit: limit, - skip: offset, - sort: { - _id: sort == 'asc' ? 1 : -1 - } - }); - - // Serialize - res(await Promise.all(reactions.map(async reaction => - await serialize(reaction, user)))); -}); diff --git a/src/api/endpoints/posts/reactions/create.ts b/src/api/endpoints/posts/reactions/create.ts deleted file mode 100644 index d537463dfe..0000000000 --- a/src/api/endpoints/posts/reactions/create.ts +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Reaction from '../../../models/post-reaction'; -import Post from '../../../models/post'; -import Watching from '../../../models/post-watching'; -import notify from '../../../common/notify'; -import watch from '../../../common/watch-post'; -import { publishPostStream, pushSw } from '../../../event'; -import serializePost from '../../../serializers/post'; -import serializeUser from '../../../serializers/user'; - -/** - * React to a post - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'post_id' parameter - const [postId, postIdErr] = $(params.post_id).id().$; - if (postIdErr) return rej('invalid post_id param'); - - // Get 'reaction' parameter - const [reaction, reactionErr] = $(params.reaction).string().or([ - 'like', - 'love', - 'laugh', - 'hmm', - 'surprise', - 'congrats', - 'angry', - 'confused', - 'pudding' - ]).$; - if (reactionErr) return rej('invalid reaction param'); - - // Fetch reactee - const post = await Post.findOne({ - _id: postId - }); - - if (post === null) { - return rej('post not found'); - } - - // Myself - if (post.user_id.equals(user._id)) { - return rej('cannot react to my post'); - } - - // if already reacted - const exist = await Reaction.findOne({ - post_id: post._id, - user_id: user._id, - deleted_at: { $exists: false } - }); - - if (exist !== null) { - return rej('already reacted'); - } - - // Create reaction - await Reaction.insert({ - created_at: new Date(), - post_id: post._id, - user_id: user._id, - reaction: reaction - }); - - // Send response - res(); - - const inc = {}; - inc[`reaction_counts.${reaction}`] = 1; - - // Increment reactions count - await Post.update({ _id: post._id }, { - $inc: inc - }); - - publishPostStream(post._id, 'reacted'); - - // Notify - notify(post.user_id, user._id, 'reaction', { - post_id: post._id, - reaction: reaction - }); - - pushSw(post.user_id, 'reaction', { - user: await serializeUser(user, post.user_id), - post: await serializePost(post, post.user_id), - reaction: reaction - }); - - // Fetch watchers - Watching - .find({ - post_id: post._id, - user_id: { $ne: user._id }, - // 削除されたドキュメントは除く - deleted_at: { $exists: false } - }, { - fields: { - user_id: true - } - }) - .then(watchers => { - watchers.forEach(watcher => { - notify(watcher.user_id, user._id, 'reaction', { - post_id: post._id, - reaction: reaction - }); - }); - }); - - // この投稿をWatchする - // TODO: ユーザーが「リアクションしたときに自動でWatchする」設定を - // オフにしていた場合はしない - watch(user._id, post); -}); diff --git a/src/api/endpoints/posts/reactions/delete.ts b/src/api/endpoints/posts/reactions/delete.ts deleted file mode 100644 index 922c57ab18..0000000000 --- a/src/api/endpoints/posts/reactions/delete.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Reaction from '../../../models/post-reaction'; -import Post from '../../../models/post'; -// import event from '../../../event'; - -/** - * Unreact to a post - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'post_id' parameter - const [postId, postIdErr] = $(params.post_id).id().$; - if (postIdErr) return rej('invalid post_id param'); - - // Fetch unreactee - const post = await Post.findOne({ - _id: postId - }); - - if (post === null) { - return rej('post not found'); - } - - // if already unreacted - const exist = await Reaction.findOne({ - post_id: post._id, - user_id: user._id, - deleted_at: { $exists: false } - }); - - if (exist === null) { - return rej('never reacted'); - } - - // Delete reaction - await Reaction.update({ - _id: exist._id - }, { - $set: { - deleted_at: new Date() - } - }); - - // Send response - res(); - - const dec = {}; - dec[`reaction_counts.${exist.reaction}`] = -1; - - // Decrement reactions count - Post.update({ _id: post._id }, { - $inc: dec - }); -}); diff --git a/src/api/endpoints/posts/replies.ts b/src/api/endpoints/posts/replies.ts deleted file mode 100644 index 3fd6a46769..0000000000 --- a/src/api/endpoints/posts/replies.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Post from '../../models/post'; -import serialize from '../../serializers/post'; - -/** - * Show a replies of a post - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'post_id' parameter - const [postId, postIdErr] = $(params.post_id).id().$; - if (postIdErr) return rej('invalid post_id param'); - - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); - - // Get 'offset' parameter - const [offset = 0, offsetErr] = $(params.offset).optional.number().min(0).$; - if (offsetErr) return rej('invalid offset param'); - - // Get 'sort' parameter - const [sort = 'desc', sortError] = $(params.sort).optional.string().or('desc asc').$; - if (sortError) return rej('invalid sort param'); - - // Lookup post - const post = await Post.findOne({ - _id: postId - }); - - if (post === null) { - return rej('post not found'); - } - - // Issue query - const replies = await Post - .find({ reply_id: post._id }, { - limit: limit, - skip: offset, - sort: { - _id: sort == 'asc' ? 1 : -1 - } - }); - - // Serialize - res(await Promise.all(replies.map(async post => - await serialize(post, user)))); -}); diff --git a/src/api/endpoints/posts/reposts.ts b/src/api/endpoints/posts/reposts.ts deleted file mode 100644 index b701ff7574..0000000000 --- a/src/api/endpoints/posts/reposts.ts +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Post from '../../models/post'; -import serialize from '../../serializers/post'; - -/** - * Show a reposts of a post - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'post_id' parameter - const [postId, postIdErr] = $(params.post_id).id().$; - if (postIdErr) return rej('invalid post_id param'); - - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); - - // Get 'since_id' parameter - const [sinceId, sinceIdErr] = $(params.since_id).optional.id().$; - if (sinceIdErr) return rej('invalid since_id param'); - - // Get 'max_id' parameter - const [maxId, maxIdErr] = $(params.max_id).optional.id().$; - if (maxIdErr) return rej('invalid max_id param'); - - // Check if both of since_id and max_id is specified - if (sinceId && maxId) { - return rej('cannot set since_id and max_id'); - } - - // Lookup post - const post = await Post.findOne({ - _id: postId - }); - - if (post === null) { - return rej('post not found'); - } - - // Construct query - const sort = { - _id: -1 - }; - const query = { - repost_id: post._id - } as any; - if (sinceId) { - sort._id = 1; - query._id = { - $gt: sinceId - }; - } else if (maxId) { - query._id = { - $lt: maxId - }; - } - - // Issue query - const reposts = await Post - .find(query, { - limit: limit, - sort: sort - }); - - // Serialize - res(await Promise.all(reposts.map(async post => - await serialize(post, user)))); -}); diff --git a/src/api/endpoints/posts/search.ts b/src/api/endpoints/posts/search.ts deleted file mode 100644 index b434f64342..0000000000 --- a/src/api/endpoints/posts/search.ts +++ /dev/null @@ -1,119 +0,0 @@ -/** - * Module dependencies - */ -import * as mongo from 'mongodb'; -import $ from 'cafy'; -const escapeRegexp = require('escape-regexp'); -import Post from '../../models/post'; -import serialize from '../../serializers/post'; -import config from '../../../conf'; - -/** - * Search a post - * - * @param {any} params - * @param {any} me - * @return {Promise<any>} - */ -module.exports = (params, me) => new Promise(async (res, rej) => { - // Get 'query' parameter - const [query, queryError] = $(params.query).string().pipe(x => x != '').$; - if (queryError) return rej('invalid query param'); - - // Get 'offset' parameter - const [offset = 0, offsetErr] = $(params.offset).optional.number().min(0).$; - if (offsetErr) return rej('invalid offset param'); - - // Get 'max' parameter - const [max = 10, maxErr] = $(params.max).optional.number().range(1, 30).$; - if (maxErr) return rej('invalid max param'); - - // If Elasticsearch is available, search by $ - // If not, search by MongoDB - (config.elasticsearch.enable ? byElasticsearch : byNative) - (res, rej, me, query, offset, max); -}); - -// Search by MongoDB -async function byNative(res, rej, me, query, offset, max) { - const escapedQuery = escapeRegexp(query); - - // Search posts - const posts = await Post - .find({ - text: new RegExp(escapedQuery) - }, { - sort: { - _id: -1 - }, - limit: max, - skip: offset - }); - - // Serialize - res(await Promise.all(posts.map(async post => - await serialize(post, me)))); -} - -// Search by Elasticsearch -async function byElasticsearch(res, rej, me, query, offset, max) { - const es = require('../../db/elasticsearch'); - - es.search({ - index: 'misskey', - type: 'post', - body: { - size: max, - from: offset, - query: { - simple_query_string: { - fields: ['text'], - query: query, - default_operator: 'and' - } - }, - sort: [ - { _doc: 'desc' } - ], - highlight: { - pre_tags: ['<mark>'], - post_tags: ['</mark>'], - encoder: 'html', - fields: { - text: {} - } - } - } - }, async (error, response) => { - if (error) { - console.error(error); - return res(500); - } - - if (response.hits.total === 0) { - return res([]); - } - - const hits = response.hits.hits.map(hit => new mongo.ObjectID(hit._id)); - - // Fetch found posts - const posts = await Post - .find({ - _id: { - $in: hits - } - }, { - sort: { - _id: -1 - } - }); - - posts.map(post => { - post._highlight = response.hits.hits.filter(hit => post._id.equals(hit._id))[0].highlight.text[0]; - }); - - // Serialize - res(await Promise.all(posts.map(async post => - await serialize(post, me)))); - }); -} diff --git a/src/api/endpoints/posts/show.ts b/src/api/endpoints/posts/show.ts deleted file mode 100644 index 5bfe4f6605..0000000000 --- a/src/api/endpoints/posts/show.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Post from '../../models/post'; -import serialize from '../../serializers/post'; - -/** - * Show a post - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'post_id' parameter - const [postId, postIdErr] = $(params.post_id).id().$; - if (postIdErr) return rej('invalid post_id param'); - - // Get post - const post = await Post.findOne({ - _id: postId - }); - - if (post === null) { - return rej('post not found'); - } - - // Serialize - res(await serialize(post, user, { - detail: true - })); -}); diff --git a/src/api/endpoints/posts/timeline.ts b/src/api/endpoints/posts/timeline.ts deleted file mode 100644 index 0d08b95463..0000000000 --- a/src/api/endpoints/posts/timeline.ts +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import rap from '@prezzemolo/rap'; -import Post from '../../models/post'; -import ChannelWatching from '../../models/channel-watching'; -import getFriends from '../../common/get-friends'; -import serialize from '../../serializers/post'; - -/** - * Get timeline of myself - * - * @param {any} params - * @param {any} user - * @param {any} app - * @return {Promise<any>} - */ -module.exports = async (params, user, app) => { - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) throw 'invalid limit param'; - - // Get 'since_id' parameter - const [sinceId, sinceIdErr] = $(params.since_id).optional.id().$; - if (sinceIdErr) throw 'invalid since_id param'; - - // Get 'max_id' parameter - const [maxId, maxIdErr] = $(params.max_id).optional.id().$; - if (maxIdErr) throw 'invalid max_id param'; - - // Get 'since_date' parameter - const [sinceDate, sinceDateErr] = $(params.since_date).optional.number().$; - if (sinceDateErr) throw 'invalid since_date param'; - - // Get 'max_date' parameter - const [maxDate, maxDateErr] = $(params.max_date).optional.number().$; - if (maxDateErr) throw 'invalid max_date param'; - - // Check if only one of since_id, max_id, since_date, max_date specified - if ([sinceId, maxId, sinceDate, maxDate].filter(x => x != null).length > 1) { - throw 'only one of since_id, max_id, since_date, max_date can be specified'; - } - - const { followingIds, watchingChannelIds } = await rap({ - // ID list of the user itself and other users who the user follows - followingIds: getFriends(user._id), - // Watchしているチャンネルを取得 - watchingChannelIds: ChannelWatching.find({ - user_id: user._id, - // 削除されたドキュメントは除く - deleted_at: { $exists: false } - }).then(watches => watches.map(w => w.channel_id)) - }); - - //#region Construct query - const sort = { - _id: -1 - }; - - const query = { - $or: [{ - // フォローしている人のタイムラインへの投稿 - user_id: { - $in: followingIds - }, - // 「タイムラインへの」投稿に限定するためにチャンネルが指定されていないもののみに限る - $or: [{ - channel_id: { - $exists: false - } - }, { - channel_id: null - }] - }, { - // Watchしているチャンネルへの投稿 - channel_id: { - $in: watchingChannelIds - } - }] - } as any; - - if (sinceId) { - sort._id = 1; - query._id = { - $gt: sinceId - }; - } else if (maxId) { - query._id = { - $lt: maxId - }; - } else if (sinceDate) { - sort._id = 1; - query.created_at = { - $gt: new Date(sinceDate) - }; - } else if (maxDate) { - query.created_at = { - $lt: new Date(maxDate) - }; - } - //#endregion - - // Issue query - const timeline = await Post - .find(query, { - limit: limit, - sort: sort - }); - - // Serialize - return await Promise.all(timeline.map(post => serialize(post, user))); -}; diff --git a/src/api/endpoints/posts/trend.ts b/src/api/endpoints/posts/trend.ts deleted file mode 100644 index 64a195dff1..0000000000 --- a/src/api/endpoints/posts/trend.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * Module dependencies - */ -const ms = require('ms'); -import $ from 'cafy'; -import Post from '../../models/post'; -import serialize from '../../serializers/post'; - -/** - * Get trend posts - * - * @param {any} params - * @param {any} user - * @return {Promise<any>} - */ -module.exports = (params, user) => new Promise(async (res, rej) => { - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); - - // Get 'offset' parameter - const [offset = 0, offsetErr] = $(params.offset).optional.number().min(0).$; - if (offsetErr) return rej('invalid offset param'); - - // Get 'reply' parameter - const [reply, replyErr] = $(params.reply).optional.boolean().$; - if (replyErr) return rej('invalid reply param'); - - // Get 'repost' parameter - const [repost, repostErr] = $(params.repost).optional.boolean().$; - if (repostErr) return rej('invalid repost param'); - - // Get 'media' parameter - const [media, mediaErr] = $(params.media).optional.boolean().$; - if (mediaErr) return rej('invalid media param'); - - // Get 'poll' parameter - const [poll, pollErr] = $(params.poll).optional.boolean().$; - if (pollErr) return rej('invalid poll param'); - - const query = { - created_at: { - $gte: new Date(Date.now() - ms('1days')) - }, - repost_count: { - $gt: 0 - } - } as any; - - if (reply != undefined) { - query.reply_id = reply ? { $exists: true, $ne: null } : null; - } - - if (repost != undefined) { - query.repost_id = repost ? { $exists: true, $ne: null } : null; - } - - if (media != undefined) { - query.media_ids = media ? { $exists: true, $ne: null } : null; - } - - if (poll != undefined) { - query.poll = poll ? { $exists: true, $ne: null } : null; - } - - // Issue query - const posts = await Post - .find(query, { - limit: limit, - skip: offset, - sort: { - repost_count: -1, - _id: -1 - } - }); - - // Serialize - res(await Promise.all(posts.map(async post => - await serialize(post, user, { detail: true })))); -}); diff --git a/src/api/endpoints/stats.ts b/src/api/endpoints/stats.ts deleted file mode 100644 index a6084cd17a..0000000000 --- a/src/api/endpoints/stats.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Module dependencies - */ -import Post from '../models/post'; -import User from '../models/user'; - -/** - * @swagger - * /stats: - * post: - * summary: Show the misskey's statistics - * responses: - * 200: - * description: Success - * schema: - * type: object - * properties: - * posts_count: - * description: count of all posts of misskey - * type: number - * users_count: - * description: count of all users of misskey - * type: number - * - * default: - * description: Failed - * schema: - * $ref: "#/definitions/Error" - */ - -/** - * Show the misskey's statistics - * - * @param {any} params - * @return {Promise<any>} - */ -module.exports = params => new Promise(async (res, rej) => { - const postsCount = await Post - .count(); - - const usersCount = await User - .count(); - - res({ - posts_count: postsCount, - users_count: usersCount - }); -}); diff --git a/src/api/endpoints/sw/register.ts b/src/api/endpoints/sw/register.ts deleted file mode 100644 index 99406138db..0000000000 --- a/src/api/endpoints/sw/register.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Subscription from '../../models/sw-subscription'; - -/** - * subscribe service worker - * - * @param {any} params - * @param {any} user - * @param {any} _ - * @param {boolean} isSecure - * @return {Promise<any>} - */ -module.exports = async (params, user, _, isSecure) => new Promise(async (res, rej) => { - // Get 'endpoint' parameter - const [endpoint, endpointErr] = $(params.endpoint).string().$; - if (endpointErr) return rej('invalid endpoint param'); - - // Get 'auth' parameter - const [auth, authErr] = $(params.auth).string().$; - if (authErr) return rej('invalid auth param'); - - // Get 'publickey' parameter - const [publickey, publickeyErr] = $(params.publickey).string().$; - if (publickeyErr) return rej('invalid publickey param'); - - // if already subscribed - const exist = await Subscription.findOne({ - user_id: user._id, - endpoint: endpoint, - auth: auth, - publickey: publickey, - deleted_at: { $exists: false } - }); - - if (exist !== null) { - return res(); - } - - await Subscription.insert({ - user_id: user._id, - endpoint: endpoint, - auth: auth, - publickey: publickey - }); - - res(); -}); diff --git a/src/api/endpoints/username/available.ts b/src/api/endpoints/username/available.ts deleted file mode 100644 index 3be7bcba32..0000000000 --- a/src/api/endpoints/username/available.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import User from '../../models/user'; -import { validateUsername } from '../../models/user'; - -/** - * Check available username - * - * @param {any} params - * @return {Promise<any>} - */ -module.exports = async (params) => new Promise(async (res, rej) => { - // Get 'username' parameter - const [username, usernameError] = $(params.username).string().pipe(validateUsername).$; - if (usernameError) return rej('invalid username param'); - - // Get exist - const exist = await User - .count({ - username_lower: username.toLowerCase() - }, { - limit: 1 - }); - - // Reply - res({ - available: exist === 0 - }); -}); diff --git a/src/api/endpoints/users.ts b/src/api/endpoints/users.ts deleted file mode 100644 index 134f262fb1..0000000000 --- a/src/api/endpoints/users.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import User from '../models/user'; -import serialize from '../serializers/user'; - -/** - * Lists all users - * - * @param {any} params - * @param {any} me - * @return {Promise<any>} - */ -module.exports = (params, me) => new Promise(async (res, rej) => { - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); - - // Get 'since_id' parameter - const [sinceId, sinceIdErr] = $(params.since_id).optional.id().$; - if (sinceIdErr) return rej('invalid since_id param'); - - // Get 'max_id' parameter - const [maxId, maxIdErr] = $(params.max_id).optional.id().$; - if (maxIdErr) return rej('invalid max_id param'); - - // Check if both of since_id and max_id is specified - if (sinceId && maxId) { - return rej('cannot set since_id and max_id'); - } - - // Construct query - const sort = { - _id: -1 - }; - const query = {} as any; - if (sinceId) { - sort._id = 1; - query._id = { - $gt: sinceId - }; - } else if (maxId) { - query._id = { - $lt: maxId - }; - } - - // Issue query - const users = await User - .find(query, { - limit: limit, - sort: sort - }); - - // Serialize - res(await Promise.all(users.map(async user => - await serialize(user, me)))); -}); diff --git a/src/api/endpoints/users/followers.ts b/src/api/endpoints/users/followers.ts deleted file mode 100644 index 4905323ba5..0000000000 --- a/src/api/endpoints/users/followers.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import User from '../../models/user'; -import Following from '../../models/following'; -import serialize from '../../serializers/user'; -import getFriends from '../../common/get-friends'; - -/** - * Get followers of a user - * - * @param {any} params - * @param {any} me - * @return {Promise<any>} - */ -module.exports = (params, me) => new Promise(async (res, rej) => { - // Get 'user_id' parameter - const [userId, userIdErr] = $(params.user_id).id().$; - if (userIdErr) return rej('invalid user_id param'); - - // Get 'iknow' parameter - const [iknow = false, iknowErr] = $(params.iknow).optional.boolean().$; - if (iknowErr) return rej('invalid iknow param'); - - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); - - // Get 'cursor' parameter - const [cursor = null, cursorErr] = $(params.cursor).optional.id().$; - if (cursorErr) return rej('invalid cursor param'); - - // Lookup user - const user = await User.findOne({ - _id: userId - }, { - fields: { - _id: true - } - }); - - if (user === null) { - return rej('user not found'); - } - - // Construct query - const query = { - followee_id: user._id, - deleted_at: { $exists: false } - } as any; - - // ログインしていてかつ iknow フラグがあるとき - if (me && iknow) { - // Get my friends - const myFriends = await getFriends(me._id); - - query.follower_id = { - $in: myFriends - }; - } - - // カーソルが指定されている場合 - if (cursor) { - query._id = { - $lt: cursor - }; - } - - // Get followers - const following = await Following - .find(query, { - limit: limit + 1, - sort: { _id: -1 } - }); - - // 「次のページ」があるかどうか - const inStock = following.length === limit + 1; - if (inStock) { - following.pop(); - } - - // Serialize - const users = await Promise.all(following.map(async f => - await serialize(f.follower_id, me, { detail: true }))); - - // Response - res({ - users: users, - next: inStock ? following[following.length - 1]._id : null, - }); -}); diff --git a/src/api/endpoints/users/following.ts b/src/api/endpoints/users/following.ts deleted file mode 100644 index dc2ff49bbe..0000000000 --- a/src/api/endpoints/users/following.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import User from '../../models/user'; -import Following from '../../models/following'; -import serialize from '../../serializers/user'; -import getFriends from '../../common/get-friends'; - -/** - * Get following users of a user - * - * @param {any} params - * @param {any} me - * @return {Promise<any>} - */ -module.exports = (params, me) => new Promise(async (res, rej) => { - // Get 'user_id' parameter - const [userId, userIdErr] = $(params.user_id).id().$; - if (userIdErr) return rej('invalid user_id param'); - - // Get 'iknow' parameter - const [iknow = false, iknowErr] = $(params.iknow).optional.boolean().$; - if (iknowErr) return rej('invalid iknow param'); - - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); - - // Get 'cursor' parameter - const [cursor = null, cursorErr] = $(params.cursor).optional.id().$; - if (cursorErr) return rej('invalid cursor param'); - - // Lookup user - const user = await User.findOne({ - _id: userId - }, { - fields: { - _id: true - } - }); - - if (user === null) { - return rej('user not found'); - } - - // Construct query - const query = { - follower_id: user._id, - deleted_at: { $exists: false } - } as any; - - // ログインしていてかつ iknow フラグがあるとき - if (me && iknow) { - // Get my friends - const myFriends = await getFriends(me._id); - - query.followee_id = { - $in: myFriends - }; - } - - // カーソルが指定されている場合 - if (cursor) { - query._id = { - $lt: cursor - }; - } - - // Get followers - const following = await Following - .find(query, { - limit: limit + 1, - sort: { _id: -1 } - }); - - // 「次のページ」があるかどうか - const inStock = following.length === limit + 1; - if (inStock) { - following.pop(); - } - - // Serialize - const users = await Promise.all(following.map(async f => - await serialize(f.followee_id, me, { detail: true }))); - - // Response - res({ - users: users, - next: inStock ? following[following.length - 1]._id : null, - }); -}); diff --git a/src/api/endpoints/users/get_frequently_replied_users.ts b/src/api/endpoints/users/get_frequently_replied_users.ts deleted file mode 100644 index a8add623d4..0000000000 --- a/src/api/endpoints/users/get_frequently_replied_users.ts +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Post from '../../models/post'; -import User from '../../models/user'; -import serialize from '../../serializers/user'; - -module.exports = (params, me) => new Promise(async (res, rej) => { - // Get 'user_id' parameter - const [userId, userIdErr] = $(params.user_id).id().$; - if (userIdErr) return rej('invalid user_id param'); - - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); - - // Lookup user - const user = await User.findOne({ - _id: userId - }, { - fields: { - _id: true - } - }); - - if (user === null) { - return rej('user not found'); - } - - // Fetch recent posts - const recentPosts = await Post.find({ - user_id: user._id, - reply_id: { - $exists: true, - $ne: null - } - }, { - sort: { - _id: -1 - }, - limit: 1000, - fields: { - _id: false, - reply_id: true - } - }); - - // 投稿が少なかったら中断 - if (recentPosts.length === 0) { - return res([]); - } - - const replyTargetPosts = await Post.find({ - _id: { - $in: recentPosts.map(p => p.reply_id) - }, - user_id: { - $ne: user._id - } - }, { - fields: { - _id: false, - user_id: true - } - }); - - const repliedUsers = {}; - - // Extract replies from recent posts - replyTargetPosts.forEach(post => { - const userId = post.user_id.toString(); - if (repliedUsers[userId]) { - repliedUsers[userId]++; - } else { - repliedUsers[userId] = 1; - } - }); - - // Calc peak - let peak = 0; - Object.keys(repliedUsers).forEach(user => { - if (repliedUsers[user] > peak) peak = repliedUsers[user]; - }); - - // Sort replies by frequency - const repliedUsersSorted = Object.keys(repliedUsers).sort((a, b) => repliedUsers[b] - repliedUsers[a]); - - // Extract top replied users - const topRepliedUsers = repliedUsersSorted.slice(0, limit); - - // Make replies object (includes weights) - const repliesObj = await Promise.all(topRepliedUsers.map(async (user) => ({ - user: await serialize(user, me, { detail: true }), - weight: repliedUsers[user] / peak - }))); - - // Response - res(repliesObj); -}); diff --git a/src/api/endpoints/users/posts.ts b/src/api/endpoints/users/posts.ts deleted file mode 100644 index fe821cf17a..0000000000 --- a/src/api/endpoints/users/posts.ts +++ /dev/null @@ -1,129 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import Post from '../../models/post'; -import User from '../../models/user'; -import serialize from '../../serializers/post'; - -/** - * Get posts of a user - * - * @param {any} params - * @param {any} me - * @return {Promise<any>} - */ -module.exports = (params, me) => new Promise(async (res, rej) => { - // Get 'user_id' parameter - const [userId, userIdErr] = $(params.user_id).optional.id().$; - if (userIdErr) return rej('invalid user_id param'); - - // Get 'username' parameter - const [username, usernameErr] = $(params.username).optional.string().$; - if (usernameErr) return rej('invalid username param'); - - if (userId === undefined && username === undefined) { - return rej('user_id or username is required'); - } - - // Get 'include_replies' parameter - const [includeReplies = true, includeRepliesErr] = $(params.include_replies).optional.boolean().$; - if (includeRepliesErr) return rej('invalid include_replies param'); - - // Get 'with_media' parameter - const [withMedia = false, withMediaErr] = $(params.with_media).optional.boolean().$; - if (withMediaErr) return rej('invalid with_media param'); - - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); - - // Get 'since_id' parameter - const [sinceId, sinceIdErr] = $(params.since_id).optional.id().$; - if (sinceIdErr) return rej('invalid since_id param'); - - // Get 'max_id' parameter - const [maxId, maxIdErr] = $(params.max_id).optional.id().$; - if (maxIdErr) return rej('invalid max_id param'); - - // Get 'since_date' parameter - const [sinceDate, sinceDateErr] = $(params.since_date).optional.number().$; - if (sinceDateErr) throw 'invalid since_date param'; - - // Get 'max_date' parameter - const [maxDate, maxDateErr] = $(params.max_date).optional.number().$; - if (maxDateErr) throw 'invalid max_date param'; - - // Check if only one of since_id, max_id, since_date, max_date specified - if ([sinceId, maxId, sinceDate, maxDate].filter(x => x != null).length > 1) { - throw 'only one of since_id, max_id, since_date, max_date can be specified'; - } - - const q = userId !== undefined - ? { _id: userId } - : { username_lower: username.toLowerCase() } ; - - // Lookup user - const user = await User.findOne(q, { - fields: { - _id: true - } - }); - - if (user === null) { - return rej('user not found'); - } - - //#region Construct query - const sort = { - _id: -1 - }; - - const query = { - user_id: user._id - } as any; - - if (sinceId) { - sort._id = 1; - query._id = { - $gt: sinceId - }; - } else if (maxId) { - query._id = { - $lt: maxId - }; - } else if (sinceDate) { - sort._id = 1; - query.created_at = { - $gt: new Date(sinceDate) - }; - } else if (maxDate) { - query.created_at = { - $lt: new Date(maxDate) - }; - } - - if (!includeReplies) { - query.reply_id = null; - } - - if (withMedia) { - query.media_ids = { - $exists: true, - $ne: null - }; - } - //#endregion - - // Issue query - const posts = await Post - .find(query, { - limit: limit, - sort: sort - }); - - // Serialize - res(await Promise.all(posts.map(async (post) => - await serialize(post, me) - ))); -}); diff --git a/src/api/endpoints/users/recommendation.ts b/src/api/endpoints/users/recommendation.ts deleted file mode 100644 index 731d68a7b1..0000000000 --- a/src/api/endpoints/users/recommendation.ts +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Module dependencies - */ -const ms = require('ms'); -import $ from 'cafy'; -import User from '../../models/user'; -import serialize from '../../serializers/user'; -import getFriends from '../../common/get-friends'; - -/** - * Get recommended users - * - * @param {any} params - * @param {any} me - * @return {Promise<any>} - */ -module.exports = (params, me) => new Promise(async (res, rej) => { - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); - - // Get 'offset' parameter - const [offset = 0, offsetErr] = $(params.offset).optional.number().min(0).$; - if (offsetErr) return rej('invalid offset param'); - - // ID list of the user itself and other users who the user follows - const followingIds = await getFriends(me._id); - - const users = await User - .find({ - _id: { - $nin: followingIds - }, - last_used_at: { - $gte: new Date(Date.now() - ms('7days')) - } - }, { - limit: limit, - skip: offset, - sort: { - followers_count: -1 - } - }); - - // Serialize - res(await Promise.all(users.map(async user => - await serialize(user, me, { detail: true })))); -}); diff --git a/src/api/endpoints/users/search.ts b/src/api/endpoints/users/search.ts deleted file mode 100644 index 73a5db47e2..0000000000 --- a/src/api/endpoints/users/search.ts +++ /dev/null @@ -1,99 +0,0 @@ -/** - * Module dependencies - */ -import * as mongo from 'mongodb'; -import $ from 'cafy'; -import User from '../../models/user'; -import serialize from '../../serializers/user'; -import config from '../../../conf'; -const escapeRegexp = require('escape-regexp'); - -/** - * Search a user - * - * @param {any} params - * @param {any} me - * @return {Promise<any>} - */ -module.exports = (params, me) => new Promise(async (res, rej) => { - // Get 'query' parameter - const [query, queryError] = $(params.query).string().pipe(x => x != '').$; - if (queryError) return rej('invalid query param'); - - // Get 'offset' parameter - const [offset = 0, offsetErr] = $(params.offset).optional.number().min(0).$; - if (offsetErr) return rej('invalid offset param'); - - // Get 'max' parameter - const [max = 10, maxErr] = $(params.max).optional.number().range(1, 30).$; - if (maxErr) return rej('invalid max param'); - - // If Elasticsearch is available, search by $ - // If not, search by MongoDB - (config.elasticsearch.enable ? byElasticsearch : byNative) - (res, rej, me, query, offset, max); -}); - -// Search by MongoDB -async function byNative(res, rej, me, query, offset, max) { - const escapedQuery = escapeRegexp(query); - - // Search users - const users = await User - .find({ - $or: [{ - username_lower: new RegExp(escapedQuery.replace('@', '').toLowerCase()) - }, { - name: new RegExp(escapedQuery) - }] - }, { - limit: max - }); - - // Serialize - res(await Promise.all(users.map(async user => - await serialize(user, me, { detail: true })))); -} - -// Search by Elasticsearch -async function byElasticsearch(res, rej, me, query, offset, max) { - const es = require('../../db/elasticsearch'); - - es.search({ - index: 'misskey', - type: 'user', - body: { - size: max, - from: offset, - query: { - simple_query_string: { - fields: ['username', 'name', 'bio'], - query: query, - default_operator: 'and' - } - } - } - }, async (error, response) => { - if (error) { - console.error(error); - return res(500); - } - - if (response.hits.total === 0) { - return res([]); - } - - const hits = response.hits.hits.map(hit => new mongo.ObjectID(hit._id)); - - const users = await User - .find({ - _id: { - $in: hits - } - }); - - // Serialize - res(await Promise.all(users.map(async user => - await serialize(user, me, { detail: true })))); - }); -} diff --git a/src/api/endpoints/users/search_by_username.ts b/src/api/endpoints/users/search_by_username.ts deleted file mode 100644 index 7f2f42f0a6..0000000000 --- a/src/api/endpoints/users/search_by_username.ts +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import User from '../../models/user'; -import serialize from '../../serializers/user'; - -/** - * Search a user by username - * - * @param {any} params - * @param {any} me - * @return {Promise<any>} - */ -module.exports = (params, me) => new Promise(async (res, rej) => { - // Get 'query' parameter - const [query, queryError] = $(params.query).string().$; - if (queryError) return rej('invalid query param'); - - // Get 'offset' parameter - const [offset = 0, offsetErr] = $(params.offset).optional.number().min(0).$; - if (offsetErr) return rej('invalid offset param'); - - // Get 'limit' parameter - const [limit = 10, limitErr] = $(params.limit).optional.number().range(1, 100).$; - if (limitErr) return rej('invalid limit param'); - - const users = await User - .find({ - username_lower: new RegExp(query.toLowerCase()) - }, { - limit: limit, - skip: offset - }); - - // Serialize - res(await Promise.all(users.map(async user => - await serialize(user, me, { detail: true })))); -}); diff --git a/src/api/endpoints/users/show.ts b/src/api/endpoints/users/show.ts deleted file mode 100644 index 8e74b0fe3f..0000000000 --- a/src/api/endpoints/users/show.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Module dependencies - */ -import $ from 'cafy'; -import User from '../../models/user'; -import serialize from '../../serializers/user'; - -/** - * Show a user - * - * @param {any} params - * @param {any} me - * @return {Promise<any>} - */ -module.exports = (params, me) => new Promise(async (res, rej) => { - // Get 'user_id' parameter - const [userId, userIdErr] = $(params.user_id).optional.id().$; - if (userIdErr) return rej('invalid user_id param'); - - // Get 'username' parameter - const [username, usernameErr] = $(params.username).optional.string().$; - if (usernameErr) return rej('invalid username param'); - - if (userId === undefined && username === undefined) { - return rej('user_id or username is required'); - } - - const q = userId !== undefined - ? { _id: userId } - : { username_lower: username.toLowerCase() }; - - // Lookup user - const user = await User.findOne(q, { - fields: { - data: false - } - }); - - if (user === null) { - return rej('user not found'); - } - - // Send response - res(await serialize(user, me, { - detail: true - })); -}); diff --git a/src/api/event.ts b/src/api/event.ts deleted file mode 100644 index 4a2e4e453d..0000000000 --- a/src/api/event.ts +++ /dev/null @@ -1,68 +0,0 @@ -import * as mongo from 'mongodb'; -import * as redis from 'redis'; -import swPush from './common/push-sw'; -import config from '../conf'; - -type ID = string | mongo.ObjectID; - -class MisskeyEvent { - private redisClient: redis.RedisClient; - - constructor() { - // Connect to Redis - this.redisClient = redis.createClient( - config.redis.port, config.redis.host); - } - - public publishUserStream(userId: ID, type: string, value?: any): void { - this.publish(`user-stream:${userId}`, type, typeof value === 'undefined' ? null : value); - } - - public publishSw(userId: ID, type: string, value?: any): void { - swPush(userId, type, value); - } - - public publishDriveStream(userId: ID, type: string, value?: any): void { - this.publish(`drive-stream:${userId}`, type, typeof value === 'undefined' ? null : value); - } - - public publishPostStream(postId: ID, type: string, value?: any): void { - this.publish(`post-stream:${postId}`, type, typeof value === 'undefined' ? null : value); - } - - public publishMessagingStream(userId: ID, otherpartyId: ID, type: string, value?: any): void { - this.publish(`messaging-stream:${userId}-${otherpartyId}`, type, typeof value === 'undefined' ? null : value); - } - - public publishMessagingIndexStream(userId: ID, type: string, value?: any): void { - this.publish(`messaging-index-stream:${userId}`, type, typeof value === 'undefined' ? null : value); - } - - public publishChannelStream(channelId: ID, type: string, value?: any): void { - this.publish(`channel-stream:${channelId}`, type, typeof value === 'undefined' ? null : value); - } - - private publish(channel: string, type: string, value?: any): void { - const message = value == null ? - { type: type } : - { type: type, body: value }; - - this.redisClient.publish(`misskey:${channel}`, JSON.stringify(message)); - } -} - -const ev = new MisskeyEvent(); - -export default ev.publishUserStream.bind(ev); - -export const pushSw = ev.publishSw.bind(ev); - -export const publishDriveStream = ev.publishDriveStream.bind(ev); - -export const publishPostStream = ev.publishPostStream.bind(ev); - -export const publishMessagingStream = ev.publishMessagingStream.bind(ev); - -export const publishMessagingIndexStream = ev.publishMessagingIndexStream.bind(ev); - -export const publishChannelStream = ev.publishChannelStream.bind(ev); diff --git a/src/api/limitter.ts b/src/api/limitter.ts deleted file mode 100644 index 10c50c3403..0000000000 --- a/src/api/limitter.ts +++ /dev/null @@ -1,82 +0,0 @@ -import * as Limiter from 'ratelimiter'; -import * as debug from 'debug'; -import limiterDB from '../db/redis'; -import { Endpoint } from './endpoints'; -import { IAuthContext } from './authenticate'; - -const log = debug('misskey:limitter'); - -export default (endpoint: Endpoint, ctx: IAuthContext) => new Promise((ok, reject) => { - const limitation = endpoint.limit; - - const key = limitation.hasOwnProperty('key') - ? limitation.key - : endpoint.name; - - const hasShortTermLimit = - limitation.hasOwnProperty('minInterval'); - - const hasLongTermLimit = - limitation.hasOwnProperty('duration') && - limitation.hasOwnProperty('max'); - - if (hasShortTermLimit) { - min(); - } else if (hasLongTermLimit) { - max(); - } else { - ok(); - } - - // Short-term limit - function min() { - const minIntervalLimiter = new Limiter({ - id: `${ctx.user._id}:${key}:min`, - duration: limitation.minInterval, - max: 1, - db: limiterDB - }); - - minIntervalLimiter.get((err, info) => { - if (err) { - return reject('ERR'); - } - - log(`@${ctx.user.username} ${endpoint.name} min remaining: ${info.remaining}`); - - if (info.remaining === 0) { - reject('BRIEF_REQUEST_INTERVAL'); - } else { - if (hasLongTermLimit) { - max(); - } else { - ok(); - } - } - }); - } - - // Long term limit - function max() { - const limiter = new Limiter({ - id: `${ctx.user._id}:${key}`, - duration: limitation.duration, - max: limitation.max, - db: limiterDB - }); - - limiter.get((err, info) => { - if (err) { - return reject('ERR'); - } - - log(`@${ctx.user.username} ${endpoint.name} max remaining: ${info.remaining}`); - - if (info.remaining === 0) { - reject('RATE_LIMIT_EXCEEDED'); - } else { - ok(); - } - }); - } -}); diff --git a/src/api/models/access-token.ts b/src/api/models/access-token.ts deleted file mode 100644 index 9985be5013..0000000000 --- a/src/api/models/access-token.ts +++ /dev/null @@ -1,8 +0,0 @@ -import db from '../../db/mongodb'; - -const collection = db.get('access_tokens'); - -(collection as any).createIndex('token'); // fuck type definition -(collection as any).createIndex('hash'); // fuck type definition - -export default collection as any; // fuck type definition diff --git a/src/api/models/app.ts b/src/api/models/app.ts deleted file mode 100644 index 68f2f448b0..0000000000 --- a/src/api/models/app.ts +++ /dev/null @@ -1,13 +0,0 @@ -import db from '../../db/mongodb'; - -const collection = db.get('apps'); - -(collection as any).createIndex('name_id'); // fuck type definition -(collection as any).createIndex('name_id_lower'); // fuck type definition -(collection as any).createIndex('secret'); // fuck type definition - -export default collection as any; // fuck type definition - -export function isValidNameId(nameId: string): boolean { - return typeof nameId == 'string' && /^[a-zA-Z0-9\-]{3,30}$/.test(nameId); -} diff --git a/src/api/models/appdata.ts b/src/api/models/appdata.ts deleted file mode 100644 index 3e68354fa4..0000000000 --- a/src/api/models/appdata.ts +++ /dev/null @@ -1,3 +0,0 @@ -import db from '../../db/mongodb'; - -export default db.get('appdata') as any; // fuck type definition diff --git a/src/api/models/auth-session.ts b/src/api/models/auth-session.ts deleted file mode 100644 index b264a133e9..0000000000 --- a/src/api/models/auth-session.ts +++ /dev/null @@ -1,3 +0,0 @@ -import db from '../../db/mongodb'; - -export default db.get('auth_sessions') as any; // fuck type definition diff --git a/src/api/models/channel-watching.ts b/src/api/models/channel-watching.ts deleted file mode 100644 index 6184ae408d..0000000000 --- a/src/api/models/channel-watching.ts +++ /dev/null @@ -1,3 +0,0 @@ -import db from '../../db/mongodb'; - -export default db.get('channel_watching') as any; // fuck type definition diff --git a/src/api/models/channel.ts b/src/api/models/channel.ts deleted file mode 100644 index c80e84dbc8..0000000000 --- a/src/api/models/channel.ts +++ /dev/null @@ -1,14 +0,0 @@ -import * as mongo from 'mongodb'; -import db from '../../db/mongodb'; - -const collection = db.get('channels'); - -export default collection as any; // fuck type definition - -export type IChannel = { - _id: mongo.ObjectID; - created_at: Date; - title: string; - user_id: mongo.ObjectID; - index: number; -}; diff --git a/src/api/models/drive-file.ts b/src/api/models/drive-file.ts deleted file mode 100644 index 802ee5a5fe..0000000000 --- a/src/api/models/drive-file.ts +++ /dev/null @@ -1,26 +0,0 @@ -import * as mongodb from 'mongodb'; -import monkDb, { nativeDbConn } from '../../db/mongodb'; - -const collection = monkDb.get('drive_files.files'); - -export default collection as any; // fuck type definition - -const getGridFSBucket = async (): Promise<mongodb.GridFSBucket> => { - const db = await nativeDbConn(); - const bucket = new mongodb.GridFSBucket(db, { - bucketName: 'drive_files' - }); - return bucket; -}; - -export { getGridFSBucket }; - -export function validateFileName(name: string): boolean { - return ( - (name.trim().length > 0) && - (name.length <= 200) && - (name.indexOf('\\') === -1) && - (name.indexOf('/') === -1) && - (name.indexOf('..') === -1) - ); -} diff --git a/src/api/models/drive-folder.ts b/src/api/models/drive-folder.ts deleted file mode 100644 index f81ffe855d..0000000000 --- a/src/api/models/drive-folder.ts +++ /dev/null @@ -1,10 +0,0 @@ -import db from '../../db/mongodb'; - -export default db.get('drive_folders') as any; // fuck type definition - -export function isValidFolderName(name: string): boolean { - return ( - (name.trim().length > 0) && - (name.length <= 200) - ); -} diff --git a/src/api/models/drive-tag.ts b/src/api/models/drive-tag.ts deleted file mode 100644 index 991c935e81..0000000000 --- a/src/api/models/drive-tag.ts +++ /dev/null @@ -1,3 +0,0 @@ -import db from '../../db/mongodb'; - -export default db.get('drive_tags') as any; // fuck type definition diff --git a/src/api/models/favorite.ts b/src/api/models/favorite.ts deleted file mode 100644 index e01d9e343c..0000000000 --- a/src/api/models/favorite.ts +++ /dev/null @@ -1,3 +0,0 @@ -import db from '../../db/mongodb'; - -export default db.get('favorites') as any; // fuck type definition diff --git a/src/api/models/following.ts b/src/api/models/following.ts deleted file mode 100644 index cb3db9b539..0000000000 --- a/src/api/models/following.ts +++ /dev/null @@ -1,3 +0,0 @@ -import db from '../../db/mongodb'; - -export default db.get('following') as any; // fuck type definition diff --git a/src/api/models/messaging-history.ts b/src/api/models/messaging-history.ts deleted file mode 100644 index c06987e451..0000000000 --- a/src/api/models/messaging-history.ts +++ /dev/null @@ -1,3 +0,0 @@ -import db from '../../db/mongodb'; - -export default db.get('messaging_histories') as any; // fuck type definition diff --git a/src/api/models/messaging-message.ts b/src/api/models/messaging-message.ts deleted file mode 100644 index 18afa57e44..0000000000 --- a/src/api/models/messaging-message.ts +++ /dev/null @@ -1,12 +0,0 @@ -import * as mongo from 'mongodb'; -import db from '../../db/mongodb'; - -export default db.get('messaging_messages') as any; // fuck type definition - -export interface IMessagingMessage { - _id: mongo.ObjectID; -} - -export function isValidText(text: string): boolean { - return text.length <= 1000 && text.trim() != ''; -} diff --git a/src/api/models/meta.ts b/src/api/models/meta.ts deleted file mode 100644 index c7dba8fcba..0000000000 --- a/src/api/models/meta.ts +++ /dev/null @@ -1,7 +0,0 @@ -import db from '../../db/mongodb'; - -export default db.get('meta') as any; // fuck type definition - -export type IMeta = { - top_image: string; -}; diff --git a/src/api/models/notification.ts b/src/api/models/notification.ts deleted file mode 100644 index e3dc6c70a3..0000000000 --- a/src/api/models/notification.ts +++ /dev/null @@ -1,47 +0,0 @@ -import * as mongo from 'mongodb'; -import db from '../../db/mongodb'; -import { IUser } from './user'; - -export default db.get('notifications') as any; // fuck type definition - -export interface INotification { - _id: mongo.ObjectID; - created_at: Date; - - /** - * 通知の受信者 - */ - notifiee?: IUser; - - /** - * 通知の受信者 - */ - notifiee_id: mongo.ObjectID; - - /** - * イニシエータ(initiator)、Origin。通知を行う原因となったユーザー - */ - notifier?: IUser; - - /** - * イニシエータ(initiator)、Origin。通知を行う原因となったユーザー - */ - notifier_id: mongo.ObjectID; - - /** - * 通知の種類。 - * follow - フォローされた - * mention - 投稿で自分が言及された - * reply - (自分または自分がWatchしている)投稿が返信された - * repost - (自分または自分がWatchしている)投稿がRepostされた - * quote - (自分または自分がWatchしている)投稿が引用Repostされた - * reaction - (自分または自分がWatchしている)投稿にリアクションされた - * poll_vote - (自分または自分がWatchしている)投稿の投票に投票された - */ - type: 'follow' | 'mention' | 'reply' | 'repost' | 'quote' | 'reaction' | 'poll_vote'; - - /** - * 通知が読まれたかどうか - */ - is_read: Boolean; -} diff --git a/src/api/models/poll-vote.ts b/src/api/models/poll-vote.ts deleted file mode 100644 index af77a2643e..0000000000 --- a/src/api/models/poll-vote.ts +++ /dev/null @@ -1,3 +0,0 @@ -import db from '../../db/mongodb'; - -export default db.get('poll_votes') as any; // fuck type definition diff --git a/src/api/models/post-reaction.ts b/src/api/models/post-reaction.ts deleted file mode 100644 index 282ae5bd21..0000000000 --- a/src/api/models/post-reaction.ts +++ /dev/null @@ -1,3 +0,0 @@ -import db from '../../db/mongodb'; - -export default db.get('post_reactions') as any; // fuck type definition diff --git a/src/api/models/post-watching.ts b/src/api/models/post-watching.ts deleted file mode 100644 index 41d37e2703..0000000000 --- a/src/api/models/post-watching.ts +++ /dev/null @@ -1,3 +0,0 @@ -import db from '../../db/mongodb'; - -export default db.get('post_watching') as any; // fuck type definition diff --git a/src/api/models/post.ts b/src/api/models/post.ts deleted file mode 100644 index 7584ce182d..0000000000 --- a/src/api/models/post.ts +++ /dev/null @@ -1,22 +0,0 @@ -import * as mongo from 'mongodb'; - -import db from '../../db/mongodb'; - -export default db.get('posts') as any; // fuck type definition - -export function isValidText(text: string): boolean { - return text.length <= 1000 && text.trim() != ''; -} - -export type IPost = { - _id: mongo.ObjectID; - channel_id: mongo.ObjectID; - created_at: Date; - media_ids: mongo.ObjectID[]; - reply_id: mongo.ObjectID; - repost_id: mongo.ObjectID; - poll: {}; // todo - text: string; - user_id: mongo.ObjectID; - app_id: mongo.ObjectID; -}; diff --git a/src/api/models/signin.ts b/src/api/models/signin.ts deleted file mode 100644 index 385a348f2e..0000000000 --- a/src/api/models/signin.ts +++ /dev/null @@ -1,3 +0,0 @@ -import db from '../../db/mongodb'; - -export default db.get('signin') as any; // fuck type definition diff --git a/src/api/models/sw-subscription.ts b/src/api/models/sw-subscription.ts deleted file mode 100644 index ecca04cb91..0000000000 --- a/src/api/models/sw-subscription.ts +++ /dev/null @@ -1,3 +0,0 @@ -import db from '../../db/mongodb'; - -export default db.get('sw_subscriptions') as any; // fuck type definition diff --git a/src/api/models/user.ts b/src/api/models/user.ts deleted file mode 100644 index 018979158f..0000000000 --- a/src/api/models/user.ts +++ /dev/null @@ -1,85 +0,0 @@ -import * as mongo from 'mongodb'; - -import db from '../../db/mongodb'; -import { IPost } from './post'; - -const collection = db.get('users'); - -(collection as any).createIndex('username'); // fuck type definition -(collection as any).createIndex('token'); // fuck type definition - -export default collection as any; // fuck type definition - -export function validateUsername(username: string): boolean { - return typeof username == 'string' && /^[a-zA-Z0-9\-]{3,20}$/.test(username); -} - -export function validatePassword(password: string): boolean { - return typeof password == 'string' && password != ''; -} - -export function isValidName(name: string): boolean { - return typeof name == 'string' && name.length < 30 && name.trim() != ''; -} - -export function isValidDescription(description: string): boolean { - return typeof description == 'string' && description.length < 500 && description.trim() != ''; -} - -export function isValidLocation(location: string): boolean { - return typeof location == 'string' && location.length < 50 && location.trim() != ''; -} - -export function isValidBirthday(birthday: string): boolean { - return typeof birthday == 'string' && /^([0-9]{4})\-([0-9]{2})-([0-9]{2})$/.test(birthday); -} - -export type IUser = { - _id: mongo.ObjectID; - created_at: Date; - email: string; - followers_count: number; - following_count: number; - links: string[]; - name: string; - password: string; - posts_count: number; - drive_capacity: number; - username: string; - username_lower: string; - token: string; - avatar_id: mongo.ObjectID; - banner_id: mongo.ObjectID; - data: any; - twitter: { - access_token: string; - access_token_secret: string; - user_id: string; - screen_name: string; - }; - line: { - user_id: string; - }; - description: string; - profile: { - location: string; - birthday: string; // 'YYYY-MM-DD' - tags: string[]; - }; - last_used_at: Date; - latest_post: IPost; - pinned_post_id: mongo.ObjectID; - is_pro: boolean; - is_suspended: boolean; - keywords: string[]; - two_factor_secret: string; - two_factor_enabled: boolean; -}; - -export function init(user): IUser { - user._id = new mongo.ObjectID(user._id); - user.avatar_id = new mongo.ObjectID(user.avatar_id); - user.banner_id = new mongo.ObjectID(user.banner_id); - user.pinned_post_id = new mongo.ObjectID(user.pinned_post_id); - return user; -} diff --git a/src/api/private/signin.ts b/src/api/private/signin.ts deleted file mode 100644 index 7376921e28..0000000000 --- a/src/api/private/signin.ts +++ /dev/null @@ -1,87 +0,0 @@ -import * as express from 'express'; -import * as bcrypt from 'bcryptjs'; -import * as speakeasy from 'speakeasy'; -import { default as User, IUser } from '../models/user'; -import Signin from '../models/signin'; -import serialize from '../serializers/signin'; -import event from '../event'; -import signin from '../common/signin'; - -export default async (req: express.Request, res: express.Response) => { - res.header('Access-Control-Allow-Credentials', 'true'); - - const username = req.body['username']; - const password = req.body['password']; - const token = req.body['token']; - - if (typeof username != 'string') { - res.sendStatus(400); - return; - } - - if (typeof password != 'string') { - res.sendStatus(400); - return; - } - - if (token != null && typeof token != 'string') { - res.sendStatus(400); - return; - } - - // Fetch user - const user: IUser = await User.findOne({ - username_lower: username.toLowerCase() - }, { - fields: { - data: false, - profile: false - } - }); - - if (user === null) { - res.status(404).send({ - error: 'user not found' - }); - return; - } - - // Compare password - const same = await bcrypt.compare(password, user.password); - - if (same) { - if (user.two_factor_enabled) { - const verified = (speakeasy as any).totp.verify({ - secret: user.two_factor_secret, - encoding: 'base32', - token: token - }); - - if (verified) { - signin(res, user, false); - } else { - res.status(400).send({ - error: 'invalid token' - }); - } - } else { - signin(res, user, false); - } - } else { - res.status(400).send({ - error: 'incorrect password' - }); - } - - // Append signin history - const record = await Signin.insert({ - created_at: new Date(), - user_id: user._id, - ip: req.ip, - headers: req.headers, - success: same - }); - - // Publish signin event - event(user._id, 'signin', await serialize(record)); -}; diff --git a/src/api/private/signup.ts b/src/api/private/signup.ts deleted file mode 100644 index 466c6a489f..0000000000 --- a/src/api/private/signup.ts +++ /dev/null @@ -1,159 +0,0 @@ -import * as uuid from 'uuid'; -import * as express from 'express'; -import * as bcrypt from 'bcryptjs'; -import recaptcha = require('recaptcha-promise'); -import { default as User, IUser } from '../models/user'; -import { validateUsername, validatePassword } from '../models/user'; -import serialize from '../serializers/user'; -import generateUserToken from '../common/generate-native-user-token'; -import config from '../../conf'; - -recaptcha.init({ - secret_key: config.recaptcha.secret_key -}); - -const home = { - left: [ - 'profile', - 'calendar', - 'activity', - 'rss-reader', - 'trends', - 'photo-stream', - 'version' - ], - right: [ - 'broadcast', - 'notifications', - 'user-recommendation', - 'recommended-polls', - 'server', - 'donation', - 'nav', - 'tips' - ] -}; - -export default async (req: express.Request, res: express.Response) => { - // Verify recaptcha - // ただしテスト時はこの機構は障害となるため無効にする - if (process.env.NODE_ENV !== 'test') { - const success = await recaptcha(req.body['g-recaptcha-response']); - - if (!success) { - res.status(400).send('recaptcha-failed'); - return; - } - } - - const username = req.body['username']; - const password = req.body['password']; - const name = '名無し'; - - // Validate username - if (!validateUsername(username)) { - res.sendStatus(400); - return; - } - - // Validate password - if (!validatePassword(password)) { - res.sendStatus(400); - return; - } - - // Fetch exist user that same username - const usernameExist = await User - .count({ - username_lower: username.toLowerCase() - }, { - limit: 1 - }); - - // Check username already used - if (usernameExist !== 0) { - res.sendStatus(400); - return; - } - - // Generate hash of password - const salt = await bcrypt.genSalt(8); - const hash = await bcrypt.hash(password, salt); - - // Generate secret - const secret = generateUserToken(); - - //#region Construct home data - const homeData = []; - - home.left.forEach(widget => { - homeData.push({ - name: widget, - id: uuid(), - place: 'left', - data: {} - }); - }); - - home.right.forEach(widget => { - homeData.push({ - name: widget, - id: uuid(), - place: 'right', - data: {} - }); - }); - //#endregion - - // Create account - const account: IUser = await User.insert({ - token: secret, - avatar_id: null, - banner_id: null, - created_at: new Date(), - description: null, - email: null, - followers_count: 0, - following_count: 0, - links: null, - name: name, - password: hash, - posts_count: 0, - likes_count: 0, - liked_count: 0, - drive_capacity: 1073741824, // 1GB - username: username, - username_lower: username.toLowerCase(), - profile: { - bio: null, - birthday: null, - blood: null, - gender: null, - handedness: null, - height: null, - location: null, - weight: null - }, - settings: {}, - client_settings: { - home: homeData, - show_donation: false - } - }); - - // Response - res.send(await serialize(account)); - - // Create search index - if (config.elasticsearch.enable) { - const es = require('../../db/elasticsearch'); - es.index({ - index: 'misskey', - type: 'user', - id: account._id.toString(), - body: { - username: username - } - }); - } -}; diff --git a/src/api/reply.ts b/src/api/reply.ts deleted file mode 100644 index e47fc85b9b..0000000000 --- a/src/api/reply.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as express from 'express'; - -export default (res: express.Response, x?: any, y?: any) => { - if (x === undefined) { - res.sendStatus(204); - } else if (typeof x === 'number') { - res.status(x).send({ - error: x === 500 ? 'INTERNAL_ERROR' : y - }); - } else { - res.send(x); - } -}; diff --git a/src/api/serializers/app.ts b/src/api/serializers/app.ts deleted file mode 100644 index 9d1c46dca4..0000000000 --- a/src/api/serializers/app.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Module dependencies - */ -import * as mongo from 'mongodb'; -import deepcopy = require('deepcopy'); -import App from '../models/app'; -import AccessToken from '../models/access-token'; -import config from '../../conf'; - -/** - * Serialize an app - * - * @param {any} app - * @param {any} me? - * @param {any} options? - * @return {Promise<any>} - */ -export default ( - app: any, - me?: any, - options?: { - includeSecret?: boolean, - includeProfileImageIds?: boolean - } -) => new Promise<any>(async (resolve, reject) => { - const opts = options || { - includeSecret: false, - includeProfileImageIds: false - }; - - let _app: any; - - // Populate the app if 'app' is ID - if (mongo.ObjectID.prototype.isPrototypeOf(app)) { - _app = await App.findOne({ - _id: app - }); - } else if (typeof app === 'string') { - _app = await App.findOne({ - _id: new mongo.ObjectID(app) - }); - } else { - _app = deepcopy(app); - } - - // Me - if (me && !mongo.ObjectID.prototype.isPrototypeOf(me)) { - if (typeof me === 'string') { - me = new mongo.ObjectID(me); - } else { - me = me._id; - } - } - - // Rename _id to id - _app.id = _app._id; - delete _app._id; - - delete _app.name_id_lower; - - // Visible by only owner - if (!opts.includeSecret) { - delete _app.secret; - } - - _app.icon_url = _app.icon != null - ? `${config.drive_url}/${_app.icon}` - : `${config.drive_url}/app-default.jpg`; - - if (me) { - // 既に連携しているか - const exist = await AccessToken.count({ - app_id: _app.id, - user_id: me, - }, { - limit: 1 - }); - - _app.is_authorized = exist === 1; - } - - resolve(_app); -}); diff --git a/src/api/serializers/auth-session.ts b/src/api/serializers/auth-session.ts deleted file mode 100644 index a9acf1243a..0000000000 --- a/src/api/serializers/auth-session.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Module dependencies - */ -import * as mongo from 'mongodb'; -import deepcopy = require('deepcopy'); -import serializeApp from './app'; - -/** - * Serialize an auth session - * - * @param {any} session - * @param {any} me? - * @return {Promise<any>} - */ -export default ( - session: any, - me?: any -) => new Promise<any>(async (resolve, reject) => { - let _session: any; - - // TODO: Populate session if it ID - - _session = deepcopy(session); - - // Me - if (me && !mongo.ObjectID.prototype.isPrototypeOf(me)) { - if (typeof me === 'string') { - me = new mongo.ObjectID(me); - } else { - me = me._id; - } - } - - delete _session._id; - - // Populate app - _session.app = await serializeApp(_session.app_id, me); - - resolve(_session); -}); diff --git a/src/api/serializers/channel.ts b/src/api/serializers/channel.ts deleted file mode 100644 index 3cba39aa16..0000000000 --- a/src/api/serializers/channel.ts +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Module dependencies - */ -import * as mongo from 'mongodb'; -import deepcopy = require('deepcopy'); -import { IUser } from '../models/user'; -import { default as Channel, IChannel } from '../models/channel'; -import Watching from '../models/channel-watching'; - -/** - * Serialize a channel - * - * @param channel target - * @param me? serializee - * @return response - */ -export default ( - channel: string | mongo.ObjectID | IChannel, - me?: string | mongo.ObjectID | IUser -) => new Promise<any>(async (resolve, reject) => { - - let _channel: any; - - // Populate the channel if 'channel' is ID - if (mongo.ObjectID.prototype.isPrototypeOf(channel)) { - _channel = await Channel.findOne({ - _id: channel - }); - } else if (typeof channel === 'string') { - _channel = await Channel.findOne({ - _id: new mongo.ObjectID(channel) - }); - } else { - _channel = deepcopy(channel); - } - - // Rename _id to id - _channel.id = _channel._id; - delete _channel._id; - - // Remove needless properties - delete _channel.user_id; - - // Me - const meId: mongo.ObjectID = me - ? mongo.ObjectID.prototype.isPrototypeOf(me) - ? me as mongo.ObjectID - : typeof me === 'string' - ? new mongo.ObjectID(me) - : (me as IUser)._id - : null; - - if (me) { - //#region Watchしているかどうか - const watch = await Watching.findOne({ - user_id: meId, - channel_id: _channel.id, - deleted_at: { $exists: false } - }); - - _channel.is_watching = watch !== null; - //#endregion - } - - resolve(_channel); -}); diff --git a/src/api/serializers/drive-file.ts b/src/api/serializers/drive-file.ts deleted file mode 100644 index 92a9492d86..0000000000 --- a/src/api/serializers/drive-file.ts +++ /dev/null @@ -1,76 +0,0 @@ -/** - * Module dependencies - */ -import * as mongo from 'mongodb'; -import DriveFile from '../models/drive-file'; -import serializeDriveFolder from './drive-folder'; -import serializeDriveTag from './drive-tag'; -import deepcopy = require('deepcopy'); -import config from '../../conf'; - -/** - * Serialize a drive file - * - * @param {any} file - * @param {any} options? - * @return {Promise<any>} - */ -export default ( - file: any, - options?: { - detail: boolean - } -) => new Promise<any>(async (resolve, reject) => { - const opts = Object.assign({ - detail: false - }, options); - - let _file: any; - - // Populate the file if 'file' is ID - if (mongo.ObjectID.prototype.isPrototypeOf(file)) { - _file = await DriveFile.findOne({ - _id: file - }); - } else if (typeof file === 'string') { - _file = await DriveFile.findOne({ - _id: new mongo.ObjectID(file) - }); - } else { - _file = deepcopy(file); - } - - if (!_file) return reject('invalid file arg.'); - - // rendered target - let _target: any = {}; - - _target.id = _file._id; - _target.created_at = _file.uploadDate; - _target.name = _file.filename; - _target.type = _file.contentType; - _target.datasize = _file.length; - _target.md5 = _file.md5; - - _target = Object.assign(_target, _file.metadata); - - _target.url = `${config.drive_url}/${_target.id}/${encodeURIComponent(_target.name)}`; - - if (opts.detail) { - if (_target.folder_id) { - // Populate folder - _target.folder = await serializeDriveFolder(_target.folder_id, { - detail: true - }); - } - - if (_target.tags) { - // Populate tags - _target.tags = await _target.tags.map(async (tag: any) => - await serializeDriveTag(tag) - ); - } - } - - resolve(_target); -}); diff --git a/src/api/serializers/drive-folder.ts b/src/api/serializers/drive-folder.ts deleted file mode 100644 index 6ebf454a28..0000000000 --- a/src/api/serializers/drive-folder.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Module dependencies - */ -import * as mongo from 'mongodb'; -import DriveFolder from '../models/drive-folder'; -import DriveFile from '../models/drive-file'; -import deepcopy = require('deepcopy'); - -/** - * Serialize a drive folder - * - * @param {any} folder - * @param {any} options? - * @return {Promise<any>} - */ -const self = ( - folder: any, - options?: { - detail: boolean - } -) => new Promise<any>(async (resolve, reject) => { - const opts = Object.assign({ - detail: false - }, options); - - let _folder: any; - - // Populate the folder if 'folder' is ID - if (mongo.ObjectID.prototype.isPrototypeOf(folder)) { - _folder = await DriveFolder.findOne({ _id: folder }); - } else if (typeof folder === 'string') { - _folder = await DriveFolder.findOne({ _id: new mongo.ObjectID(folder) }); - } else { - _folder = deepcopy(folder); - } - - // Rename _id to id - _folder.id = _folder._id; - delete _folder._id; - - if (opts.detail) { - const childFoldersCount = await DriveFolder.count({ - parent_id: _folder.id - }); - - const childFilesCount = await DriveFile.count({ - 'metadata.folder_id': _folder.id - }); - - _folder.folders_count = childFoldersCount; - _folder.files_count = childFilesCount; - } - - if (opts.detail && _folder.parent_id) { - // Populate parent folder - _folder.parent = await self(_folder.parent_id, { - detail: true - }); - } - - resolve(_folder); -}); - -export default self; diff --git a/src/api/serializers/drive-tag.ts b/src/api/serializers/drive-tag.ts deleted file mode 100644 index 2f152381bd..0000000000 --- a/src/api/serializers/drive-tag.ts +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Module dependencies - */ -import * as mongo from 'mongodb'; -import DriveTag from '../models/drive-tag'; -import deepcopy = require('deepcopy'); - -/** - * Serialize a drive tag - * - * @param {any} tag - * @return {Promise<any>} - */ -const self = ( - tag: any -) => new Promise<any>(async (resolve, reject) => { - let _tag: any; - - // Populate the tag if 'tag' is ID - if (mongo.ObjectID.prototype.isPrototypeOf(tag)) { - _tag = await DriveTag.findOne({ _id: tag }); - } else if (typeof tag === 'string') { - _tag = await DriveTag.findOne({ _id: new mongo.ObjectID(tag) }); - } else { - _tag = deepcopy(tag); - } - - // Rename _id to id - _tag.id = _tag._id; - delete _tag._id; - - resolve(_tag); -}); - -export default self; diff --git a/src/api/serializers/messaging-message.ts b/src/api/serializers/messaging-message.ts deleted file mode 100644 index 4ab95e42a3..0000000000 --- a/src/api/serializers/messaging-message.ts +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Module dependencies - */ -import * as mongo from 'mongodb'; -import deepcopy = require('deepcopy'); -import Message from '../models/messaging-message'; -import serializeUser from './user'; -import serializeDriveFile from './drive-file'; -import parse from '../common/text'; - -/** - * Serialize a message - * - * @param {any} message - * @param {any} me? - * @param {any} options? - * @return {Promise<any>} - */ -export default ( - message: any, - me?: any, - options?: { - populateRecipient: boolean - } -) => new Promise<any>(async (resolve, reject) => { - const opts = options || { - populateRecipient: true - }; - - let _message: any; - - // Populate the message if 'message' is ID - if (mongo.ObjectID.prototype.isPrototypeOf(message)) { - _message = await Message.findOne({ - _id: message - }); - } else if (typeof message === 'string') { - _message = await Message.findOne({ - _id: new mongo.ObjectID(message) - }); - } else { - _message = deepcopy(message); - } - - // Rename _id to id - _message.id = _message._id; - delete _message._id; - - // Parse text - if (_message.text) { - _message.ast = parse(_message.text); - } - - // Populate user - _message.user = await serializeUser(_message.user_id, me); - - if (_message.file) { - // Populate file - _message.file = await serializeDriveFile(_message.file_id); - } - - if (opts.populateRecipient) { - // Populate recipient - _message.recipient = await serializeUser(_message.recipient_id, me); - } - - resolve(_message); -}); diff --git a/src/api/serializers/notification.ts b/src/api/serializers/notification.ts deleted file mode 100644 index ac919dc8b0..0000000000 --- a/src/api/serializers/notification.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Module dependencies - */ -import * as mongo from 'mongodb'; -import Notification from '../models/notification'; -import serializeUser from './user'; -import serializePost from './post'; -import deepcopy = require('deepcopy'); - -/** - * Serialize a notification - * - * @param {any} notification - * @return {Promise<any>} - */ -export default (notification: any) => new Promise<any>(async (resolve, reject) => { - let _notification: any; - - // Populate the notification if 'notification' is ID - if (mongo.ObjectID.prototype.isPrototypeOf(notification)) { - _notification = await Notification.findOne({ - _id: notification - }); - } else if (typeof notification === 'string') { - _notification = await Notification.findOne({ - _id: new mongo.ObjectID(notification) - }); - } else { - _notification = deepcopy(notification); - } - - // Rename _id to id - _notification.id = _notification._id; - delete _notification._id; - - // Rename notifier_id to user_id - _notification.user_id = _notification.notifier_id; - delete _notification.notifier_id; - - const me = _notification.notifiee_id; - delete _notification.notifiee_id; - - // Populate notifier - _notification.user = await serializeUser(_notification.user_id, me); - - switch (_notification.type) { - case 'follow': - // nope - break; - case 'mention': - case 'reply': - case 'repost': - case 'quote': - case 'reaction': - case 'poll_vote': - // Populate post - _notification.post = await serializePost(_notification.post_id, me); - break; - default: - console.error(`Unknown type: ${_notification.type}`); - break; - } - - resolve(_notification); -}); diff --git a/src/api/serializers/post-reaction.ts b/src/api/serializers/post-reaction.ts deleted file mode 100644 index b8807a741c..0000000000 --- a/src/api/serializers/post-reaction.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Module dependencies - */ -import * as mongo from 'mongodb'; -import deepcopy = require('deepcopy'); -import Reaction from '../models/post-reaction'; -import serializeUser from './user'; - -/** - * Serialize a reaction - * - * @param {any} reaction - * @param {any} me? - * @return {Promise<any>} - */ -export default ( - reaction: any, - me?: any -) => new Promise<any>(async (resolve, reject) => { - let _reaction: any; - - // Populate the reaction if 'reaction' is ID - if (mongo.ObjectID.prototype.isPrototypeOf(reaction)) { - _reaction = await Reaction.findOne({ - _id: reaction - }); - } else if (typeof reaction === 'string') { - _reaction = await Reaction.findOne({ - _id: new mongo.ObjectID(reaction) - }); - } else { - _reaction = deepcopy(reaction); - } - - // Rename _id to id - _reaction.id = _reaction._id; - delete _reaction._id; - - // Populate user - _reaction.user = await serializeUser(_reaction.user_id, me); - - resolve(_reaction); -}); diff --git a/src/api/serializers/post.ts b/src/api/serializers/post.ts deleted file mode 100644 index 03fd120772..0000000000 --- a/src/api/serializers/post.ts +++ /dev/null @@ -1,192 +0,0 @@ -/** - * Module dependencies - */ -import * as mongo from 'mongodb'; -import deepcopy = require('deepcopy'); -import { default as Post, IPost } from '../models/post'; -import Reaction from '../models/post-reaction'; -import { IUser } from '../models/user'; -import Vote from '../models/poll-vote'; -import serializeApp from './app'; -import serializeChannel from './channel'; -import serializeUser from './user'; -import serializeDriveFile from './drive-file'; -import parse from '../common/text'; -import rap from '@prezzemolo/rap'; - -/** - * Serialize a post - * - * @param post target - * @param me? serializee - * @param options? serialize options - * @return response - */ -const self = async ( - post: string | mongo.ObjectID | IPost, - me?: string | mongo.ObjectID | IUser, - options?: { - detail: boolean - } -) => { - const opts = options || { - detail: true, - }; - - // Me - const meId: mongo.ObjectID = me - ? mongo.ObjectID.prototype.isPrototypeOf(me) - ? me as mongo.ObjectID - : typeof me === 'string' - ? new mongo.ObjectID(me) - : (me as IUser)._id - : null; - - let _post: any; - - // Populate the post if 'post' is ID - if (mongo.ObjectID.prototype.isPrototypeOf(post)) { - _post = await Post.findOne({ - _id: post - }); - } else if (typeof post === 'string') { - _post = await Post.findOne({ - _id: new mongo.ObjectID(post) - }); - } else { - _post = deepcopy(post); - } - - if (!_post) throw 'invalid post arg.'; - - const id = _post._id; - - // Rename _id to id - _post.id = _post._id; - delete _post._id; - - delete _post.mentions; - - // Parse text - if (_post.text) { - _post.ast = parse(_post.text); - } - - // Populate user - _post.user = serializeUser(_post.user_id, meId); - - // Populate app - if (_post.app_id) { - _post.app = serializeApp(_post.app_id); - } - - // Populate channel - if (_post.channel_id) { - _post.channel = serializeChannel(_post.channel_id); - } - - // Populate media - if (_post.media_ids) { - _post.media = Promise.all(_post.media_ids.map(fileId => - serializeDriveFile(fileId) - )); - } - - // When requested a detailed post data - if (opts.detail) { - // Get previous post info - _post.prev = (async () => { - const prev = await Post.findOne({ - user_id: _post.user_id, - _id: { - $lt: id - } - }, { - fields: { - _id: true - }, - sort: { - _id: -1 - } - }); - return prev ? prev._id : null; - })(); - - // Get next post info - _post.next = (async () => { - const next = await Post.findOne({ - user_id: _post.user_id, - _id: { - $gt: id - } - }, { - fields: { - _id: true - }, - sort: { - _id: 1 - } - }); - return next ? next._id : null; - })(); - - if (_post.reply_id) { - // Populate reply to post - _post.reply = self(_post.reply_id, meId, { - detail: false - }); - } - - if (_post.repost_id) { - // Populate repost - _post.repost = self(_post.repost_id, meId, { - detail: _post.text == null - }); - } - - // Poll - if (meId && _post.poll) { - _post.poll = (async (poll) => { - const vote = await Vote - .findOne({ - user_id: meId, - post_id: id - }); - - if (vote != null) { - const myChoice = poll.choices - .filter(c => c.id == vote.choice)[0]; - - myChoice.is_voted = true; - } - - return poll; - })(_post.poll); - } - - // Fetch my reaction - if (meId) { - _post.my_reaction = (async () => { - const reaction = await Reaction - .findOne({ - user_id: meId, - post_id: id, - deleted_at: { $exists: false } - }); - - if (reaction) { - return reaction.reaction; - } - - return null; - })(); - } - } - - // resolve promises in _post object - _post = await rap(_post); - - return _post; -}; - -export default self; diff --git a/src/api/serializers/signin.ts b/src/api/serializers/signin.ts deleted file mode 100644 index 4068067678..0000000000 --- a/src/api/serializers/signin.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Module dependencies - */ -import deepcopy = require('deepcopy'); - -/** - * Serialize a signin record - * - * @param {any} record - * @return {Promise<any>} - */ -export default ( - record: any -) => new Promise<any>(async (resolve, reject) => { - - const _record = deepcopy(record); - - // Rename _id to id - _record.id = _record._id; - delete _record._id; - - resolve(_record); -}); diff --git a/src/api/serializers/user.ts b/src/api/serializers/user.ts deleted file mode 100644 index fe924911c1..0000000000 --- a/src/api/serializers/user.ts +++ /dev/null @@ -1,179 +0,0 @@ -/** - * Module dependencies - */ -import * as mongo from 'mongodb'; -import deepcopy = require('deepcopy'); -import { default as User, IUser } from '../models/user'; -import serializePost from './post'; -import Following from '../models/following'; -import getFriends from '../common/get-friends'; -import config from '../../conf'; -import rap from '@prezzemolo/rap'; - -/** - * Serialize a user - * - * @param user target - * @param me? serializee - * @param options? serialize options - * @return response - */ -export default ( - user: string | mongo.ObjectID | IUser, - me?: string | mongo.ObjectID | IUser, - options?: { - detail?: boolean, - includeSecrets?: boolean - } -) => new Promise<any>(async (resolve, reject) => { - - const opts = Object.assign({ - detail: false, - includeSecrets: false - }, options); - - let _user: any; - - const fields = opts.detail ? { - settings: false - } : { - settings: false, - client_settings: false, - profile: false, - keywords: false, - domains: false - }; - - // Populate the user if 'user' is ID - if (mongo.ObjectID.prototype.isPrototypeOf(user)) { - _user = await User.findOne({ - _id: user - }, { fields }); - } else if (typeof user === 'string') { - _user = await User.findOne({ - _id: new mongo.ObjectID(user) - }, { fields }); - } else { - _user = deepcopy(user); - } - - if (!_user) return reject('invalid user arg.'); - - // Me - const meId: mongo.ObjectID = me - ? mongo.ObjectID.prototype.isPrototypeOf(me) - ? me as mongo.ObjectID - : typeof me === 'string' - ? new mongo.ObjectID(me) - : (me as IUser)._id - : null; - - // Rename _id to id - _user.id = _user._id; - delete _user._id; - - // Remove needless properties - delete _user.latest_post; - - // Remove private properties - delete _user.password; - delete _user.token; - delete _user.two_factor_temp_secret; - delete _user.two_factor_secret; - delete _user.username_lower; - if (_user.twitter) { - delete _user.twitter.access_token; - delete _user.twitter.access_token_secret; - } - delete _user.line; - - // Visible via only the official client - if (!opts.includeSecrets) { - delete _user.email; - delete _user.client_settings; - } - - if (!opts.detail) { - delete _user.two_factor_enabled; - } - - _user.avatar_url = _user.avatar_id != null - ? `${config.drive_url}/${_user.avatar_id}` - : `${config.drive_url}/default-avatar.jpg`; - - _user.banner_url = _user.banner_id != null - ? `${config.drive_url}/${_user.banner_id}` - : null; - - if (!meId || !meId.equals(_user.id) || !opts.detail) { - delete _user.avatar_id; - delete _user.banner_id; - - delete _user.drive_capacity; - } - - if (meId && !meId.equals(_user.id)) { - // If the user is following - _user.is_following = (async () => { - const follow = await Following.findOne({ - follower_id: meId, - followee_id: _user.id, - deleted_at: { $exists: false } - }); - return follow !== null; - })(); - - // If the user is followed - _user.is_followed = (async () => { - const follow2 = await Following.findOne({ - follower_id: _user.id, - followee_id: meId, - deleted_at: { $exists: false } - }); - return follow2 !== null; - })(); - } - - if (opts.detail) { - if (_user.pinned_post_id) { - // Populate pinned post - _user.pinned_post = serializePost(_user.pinned_post_id, meId, { - detail: true - }); - } - - if (meId && !meId.equals(_user.id)) { - const myFollowingIds = await getFriends(meId); - - // Get following you know count - _user.following_you_know_count = Following.count({ - followee_id: { $in: myFollowingIds }, - follower_id: _user.id, - deleted_at: { $exists: false } - }); - - // Get followers you know count - _user.followers_you_know_count = Following.count({ - followee_id: _user.id, - follower_id: { $in: myFollowingIds }, - deleted_at: { $exists: false } - }); - } - } - - // resolve promises in _user object - _user = await rap(_user); - - resolve(_user); -}); -/* -function img(url) { - return { - thumbnail: { - large: `${url}`, - medium: '', - small: '' - } - }; -} -*/ diff --git a/src/api/server.ts b/src/api/server.ts deleted file mode 100644 index 026357b465..0000000000 --- a/src/api/server.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * API Server - */ - -import * as express from 'express'; -import * as bodyParser from 'body-parser'; -import * as cors from 'cors'; -import * as multer from 'multer'; - -// import authenticate from './authenticate'; -import endpoints from './endpoints'; - -/** - * Init app - */ -const app = express(); - -app.disable('x-powered-by'); -app.set('etag', false); -app.use(bodyParser.urlencoded({ extended: true })); -app.use(bodyParser.json({ - type: ['application/json', 'text/plain'], - verify: (req, res, buf, encoding) => { - if (buf && buf.length) { - (req as any).rawBody = buf.toString(encoding || 'utf8'); - } - } -})); -app.use(cors({ - origin: true -})); - -app.get('/', (req, res) => { - res.send('YEE HAW'); -}); - -/** - * Register endpoint handlers - */ -endpoints.forEach(endpoint => - endpoint.withFile ? - app.post(`/${endpoint.name}`, - endpoint.withFile ? multer({ storage: multer.diskStorage({}) }).single('file') : null, - require('./api-handler').default.bind(null, endpoint)) : - app.post(`/${endpoint.name}`, - require('./api-handler').default.bind(null, endpoint)) -); - -app.post('/signup', require('./private/signup').default); -app.post('/signin', require('./private/signin').default); - -app.use((req, res, next) => { - // req.headers['cookie'] は常に string ですが、型定義の都合上 - // string | string[] になっているので string を明示しています - res.locals.user = ((req.headers['cookie'] as string || '').match(/i=(!\w+)/) || [null, null])[1]; - next(); -}); - -require('./service/github')(app); -require('./service/twitter')(app); - -require('./bot/interfaces/line')(app); - -module.exports = app; diff --git a/src/api/service/github.ts b/src/api/service/github.ts deleted file mode 100644 index 1c78267c0f..0000000000 --- a/src/api/service/github.ts +++ /dev/null @@ -1,138 +0,0 @@ -import * as EventEmitter from 'events'; -import * as express from 'express'; -import * as request from 'request'; -const crypto = require('crypto'); -import User from '../models/user'; -import config from '../../conf'; - -module.exports = async (app: express.Application) => { - if (config.github_bot == null) return; - - const bot = await User.findOne({ - username_lower: config.github_bot.username.toLowerCase() - }); - - if (bot == null) { - console.warn(`GitHub hook bot specified, but not found: @${config.github_bot.username}`); - return; - } - - const post = text => require('../endpoints/posts/create')({ text }, bot); - - const handler = new EventEmitter(); - - app.post('/hooks/github', (req, res, next) => { - // req.headers['x-hub-signature'] および - // req.headers['x-github-event'] は常に string ですが、型定義の都合上 - // string | string[] になっているので string を明示しています - if ((new Buffer(req.headers['x-hub-signature'] as string)).equals(new Buffer(`sha1=${crypto.createHmac('sha1', config.github_bot.hook_secret).update(JSON.stringify(req.body)).digest('hex')}`))) { - handler.emit(req.headers['x-github-event'] as string, req.body); - res.sendStatus(200); - } else { - res.sendStatus(400); - } - }); - - handler.on('status', event => { - const state = event.state; - switch (state) { - case 'error': - case 'failure': - const commit = event.commit; - const parent = commit.parents[0]; - - // Fetch parent status - request({ - url: `${parent.url}/statuses`, - headers: { - 'User-Agent': 'misskey' - } - }, (err, res, body) => { - if (err) { - console.error(err); - return; - } - const parentStatuses = JSON.parse(body); - const parentState = parentStatuses[0].state; - const stillFailed = parentState == 'failure' || parentState == 'error'; - if (stillFailed) { - post(`**⚠️BUILD STILL FAILED⚠️**: ?[${commit.commit.message}](${commit.html_url})`); - } else { - post(`**🚨BUILD FAILED🚨**: →→→?[${commit.commit.message}](${commit.html_url})←←←`); - } - }); - break; - } - }); - - handler.on('push', event => { - const ref = event.ref; - switch (ref) { - case 'refs/heads/master': - const pusher = event.pusher; - const compare = event.compare; - const commits = event.commits; - post([ - `Pushed by **${pusher.name}** with ?[${commits.length} commit${commits.length > 1 ? 's' : ''}](${compare}):`, - commits.reverse().map(commit => `・[?[${commit.id.substr(0, 7)}](${commit.url})] ${commit.message.split('\n')[0]}`).join('\n'), - ].join('\n')); - break; - case 'refs/heads/release': - const commit = event.commits[0]; - post(`RELEASED: ${commit.message}`); - break; - } - }); - - handler.on('issues', event => { - const issue = event.issue; - const action = event.action; - let title: string; - switch (action) { - case 'opened': title = 'Issue opened'; break; - case 'closed': title = 'Issue closed'; break; - case 'reopened': title = 'Issue reopened'; break; - default: return; - } - post(`${title}: <${issue.number}>「${issue.title}」\n${issue.html_url}`); - }); - - handler.on('issue_comment', event => { - const issue = event.issue; - const comment = event.comment; - const action = event.action; - let text: string; - switch (action) { - case 'created': text = `Commented to「${issue.title}」:${comment.user.login}「${comment.body}」\n${comment.html_url}`; break; - default: return; - } - post(text); - }); - - handler.on('watch', event => { - const sender = event.sender; - post(`⭐️ Starred by **${sender.login}** ⭐️`); - }); - - handler.on('fork', event => { - const repo = event.forkee; - post(`🍴 Forked:\n${repo.html_url} 🍴`); - }); - - handler.on('pull_request', event => { - const pr = event.pull_request; - const action = event.action; - let text: string; - switch (action) { - case 'opened': text = `New Pull Request:「${pr.title}」\n${pr.html_url}`; break; - case 'reopened': text = `Pull Request Reopened:「${pr.title}」\n${pr.html_url}`; break; - case 'closed': - text = pr.merged - ? `Pull Request Merged!:「${pr.title}」\n${pr.html_url}` - : `Pull Request Closed:「${pr.title}」\n${pr.html_url}`; - break; - default: return; - } - post(text); - }); -}; diff --git a/src/api/service/twitter.ts b/src/api/service/twitter.ts deleted file mode 100644 index f164cdc458..0000000000 --- a/src/api/service/twitter.ts +++ /dev/null @@ -1,131 +0,0 @@ -import * as express from 'express'; -import * as cookie from 'cookie'; -import * as uuid from 'uuid'; -// import * as Twitter from 'twitter'; -// const Twitter = require('twitter'); -import autwh from 'autwh'; -import redis from '../../db/redis'; -import User from '../models/user'; -import serialize from '../serializers/user'; -import event from '../event'; -import config from '../../conf'; -import signin from '../common/signin'; - -module.exports = (app: express.Application) => { - app.get('/disconnect/twitter', async (req, res): Promise<any> => { - if (res.locals.user == null) return res.send('plz signin'); - const user = await User.findOneAndUpdate({ - token: res.locals.user - }, { - $set: { - twitter: null - } - }); - - res.send(`Twitterの連携を解除しました :v:`); - - // Publish i updated event - event(user._id, 'i_updated', await serialize(user, user, { - detail: true, - includeSecrets: true - })); - }); - - if (config.twitter == null) { - app.get('/connect/twitter', (req, res) => { - res.send('現在Twitterへ接続できません (このインスタンスではTwitterはサポートされていません)'); - }); - - app.get('/signin/twitter', (req, res) => { - res.send('現在Twitterへ接続できません (このインスタンスではTwitterはサポートされていません)'); - }); - - return; - } - - const twAuth = autwh({ - consumerKey: config.twitter.consumer_key, - consumerSecret: config.twitter.consumer_secret, - callbackUrl: `${config.api_url}/tw/cb` - }); - - app.get('/connect/twitter', async (req, res): Promise<any> => { - if (res.locals.user == null) return res.send('plz signin'); - const ctx = await twAuth.begin(); - redis.set(res.locals.user, JSON.stringify(ctx)); - res.redirect(ctx.url); - }); - - app.get('/signin/twitter', async (req, res): Promise<any> => { - const ctx = await twAuth.begin(); - - const sessid = uuid(); - - redis.set(sessid, JSON.stringify(ctx)); - - const expires = 1000 * 60 * 60; // 1h - res.cookie('signin_with_twitter_session_id', sessid, { - path: '/', - domain: `.${config.host}`, - secure: config.url.substr(0, 5) === 'https', - httpOnly: true, - expires: new Date(Date.now() + expires), - maxAge: expires - }); - - res.redirect(ctx.url); - }); - - app.get('/tw/cb', (req, res): any => { - if (res.locals.user == null) { - // req.headers['cookie'] は常に string ですが、型定義の都合上 - // string | string[] になっているので string を明示しています - const cookies = cookie.parse((req.headers['cookie'] as string || '')); - - const sessid = cookies['signin_with_twitter_session_id']; - - if (sessid == undefined) { - res.status(400).send('invalid session'); - } - - redis.get(sessid, async (_, ctx) => { - const result = await twAuth.done(JSON.parse(ctx), req.query.oauth_verifier); - - const user = await User.findOne({ - 'twitter.user_id': result.userId - }); - - if (user == null) { - res.status(404).send(`@${result.screenName}と連携しているMisskeyアカウントはありませんでした...`); - } - - signin(res, user, true); - }); - } else { - redis.get(res.locals.user, async (_, ctx) => { - const result = await twAuth.done(JSON.parse(ctx), req.query.oauth_verifier); - - const user = await User.findOneAndUpdate({ - token: res.locals.user - }, { - $set: { - twitter: { - access_token: result.accessToken, - access_token_secret: result.accessTokenSecret, - user_id: result.userId, - screen_name: result.screenName - } - } - }); - - res.send(`Twitter: @${result.screenName} を、Misskey: @${user.username} に接続しました!`); - - // Publish i updated event - event(user._id, 'i_updated', await serialize(user, user, { - detail: true, - includeSecrets: true - })); - }); - } - }); -}; diff --git a/src/api/stream/channel.ts b/src/api/stream/channel.ts deleted file mode 100644 index d67d77cbf4..0000000000 --- a/src/api/stream/channel.ts +++ /dev/null @@ -1,12 +0,0 @@ -import * as websocket from 'websocket'; -import * as redis from 'redis'; - -export default function(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient): void { - const channel = request.resourceURL.query.channel; - - // Subscribe channel stream - subscriber.subscribe(`misskey:channel-stream:${channel}`); - subscriber.on('message', (_, data) => { - connection.send(data); - }); -} diff --git a/src/api/stream/drive.ts b/src/api/stream/drive.ts deleted file mode 100644 index c97ab80dcc..0000000000 --- a/src/api/stream/drive.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as websocket from 'websocket'; -import * as redis from 'redis'; - -export default function(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient, user: any): void { - // Subscribe drive stream - subscriber.subscribe(`misskey:drive-stream:${user._id}`); - subscriber.on('message', (_, data) => { - connection.send(data); - }); -} diff --git a/src/api/stream/home.ts b/src/api/stream/home.ts deleted file mode 100644 index 7c8f3bfec8..0000000000 --- a/src/api/stream/home.ts +++ /dev/null @@ -1,62 +0,0 @@ -import * as websocket from 'websocket'; -import * as redis from 'redis'; -import * as debug from 'debug'; - -import User from '../models/user'; -import serializePost from '../serializers/post'; -import readNotification from '../common/read-notification'; - -const log = debug('misskey'); - -export default function homeStream(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient, user: any): void { - // Subscribe Home stream channel - subscriber.subscribe(`misskey:user-stream:${user._id}`); - - subscriber.on('message', async (channel, data) => { - switch (channel.split(':')[1]) { - case 'user-stream': - connection.send(data); - break; - case 'post-stream': - const postId = channel.split(':')[2]; - log(`RECEIVED: ${postId} ${data} by @${user.username}`); - const post = await serializePost(postId, user, { - detail: true - }); - connection.send(JSON.stringify({ - type: 'post-updated', - body: { - post: post - } - })); - break; - } - }); - - connection.on('message', data => { - const msg = JSON.parse(data.utf8Data); - - switch (msg.type) { - case 'alive': - // Update lastUsedAt - User.update({ _id: user._id }, { - $set: { - last_used_at: new Date() - } - }); - break; - - case 'read_notification': - if (!msg.id) return; - readNotification(user._id, msg.id); - break; - - case 'capture': - if (!msg.id) return; - const postId = msg.id; - log(`CAPTURE: ${postId} by @${user.username}`); - subscriber.subscribe(`misskey:post-stream:${postId}`); - break; - } - }); -} diff --git a/src/api/stream/messaging-index.ts b/src/api/stream/messaging-index.ts deleted file mode 100644 index c1b2fbc806..0000000000 --- a/src/api/stream/messaging-index.ts +++ /dev/null @@ -1,10 +0,0 @@ -import * as websocket from 'websocket'; -import * as redis from 'redis'; - -export default function(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient, user: any): void { - // Subscribe messaging index stream - subscriber.subscribe(`misskey:messaging-index-stream:${user._id}`); - subscriber.on('message', (_, data) => { - connection.send(data); - }); -} diff --git a/src/api/stream/messaging.ts b/src/api/stream/messaging.ts deleted file mode 100644 index 3f505cfafa..0000000000 --- a/src/api/stream/messaging.ts +++ /dev/null @@ -1,24 +0,0 @@ -import * as websocket from 'websocket'; -import * as redis from 'redis'; -import read from '../common/read-messaging-message'; - -export default function messagingStream(request: websocket.request, connection: websocket.connection, subscriber: redis.RedisClient, user: any): void { - const otherparty = request.resourceURL.query.otherparty; - - // Subscribe messaging stream - subscriber.subscribe(`misskey:messaging-stream:${user._id}-${otherparty}`); - subscriber.on('message', (_, data) => { - connection.send(data); - }); - - connection.on('message', async (data) => { - const msg = JSON.parse(data.utf8Data); - - switch (msg.type) { - case 'read': - if (!msg.id) return; - read(user._id, otherparty, msg.id); - break; - } - }); -} diff --git a/src/api/stream/requests.ts b/src/api/stream/requests.ts deleted file mode 100644 index 2c36e58b6e..0000000000 --- a/src/api/stream/requests.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as websocket from 'websocket'; -import Xev from 'xev'; - -const ev = new Xev(); - -export default function homeStream(request: websocket.request, connection: websocket.connection): void { - const onRequest = request => { - connection.send(JSON.stringify({ - type: 'request', - body: request - })); - }; - - ev.addListener('request', onRequest); - - connection.on('close', () => { - ev.removeListener('request', onRequest); - }); -} diff --git a/src/api/stream/server.ts b/src/api/stream/server.ts deleted file mode 100644 index 0db6643d40..0000000000 --- a/src/api/stream/server.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as websocket from 'websocket'; -import Xev from 'xev'; - -const ev = new Xev(); - -export default function homeStream(request: websocket.request, connection: websocket.connection): void { - const onStats = stats => { - connection.send(JSON.stringify({ - type: 'stats', - body: stats - })); - }; - - ev.addListener('stats', onStats); - - connection.on('close', () => { - ev.removeListener('stats', onStats); - }); -} diff --git a/src/api/streaming.ts b/src/api/streaming.ts deleted file mode 100644 index c06d64c245..0000000000 --- a/src/api/streaming.ts +++ /dev/null @@ -1,109 +0,0 @@ -import * as http from 'http'; -import * as websocket from 'websocket'; -import * as redis from 'redis'; -import config from '../conf'; -import { default as User, IUser } from './models/user'; -import AccessToken from './models/access-token'; -import isNativeToken from './common/is-native-token'; - -import homeStream from './stream/home'; -import driveStream from './stream/drive'; -import messagingStream from './stream/messaging'; -import messagingIndexStream from './stream/messaging-index'; -import serverStream from './stream/server'; -import requestsStream from './stream/requests'; -import channelStream from './stream/channel'; - -module.exports = (server: http.Server) => { - /** - * Init websocket server - */ - const ws = new websocket.server({ - httpServer: server - }); - - ws.on('request', async (request) => { - const connection = request.accept(); - - if (request.resourceURL.pathname === '/server') { - serverStream(request, connection); - return; - } - - if (request.resourceURL.pathname === '/requests') { - requestsStream(request, connection); - return; - } - - // Connect to Redis - const subscriber = redis.createClient( - config.redis.port, config.redis.host); - - connection.on('close', () => { - subscriber.unsubscribe(); - subscriber.quit(); - }); - - if (request.resourceURL.pathname === '/channel') { - channelStream(request, connection, subscriber); - return; - } - - const user = await authenticate(request.resourceURL.query.i); - - if (user == null) { - connection.send('authentication-failed'); - connection.close(); - return; - } - - const channel = - request.resourceURL.pathname === '/' ? homeStream : - request.resourceURL.pathname === '/drive' ? driveStream : - request.resourceURL.pathname === '/messaging' ? messagingStream : - request.resourceURL.pathname === '/messaging-index' ? messagingIndexStream : - null; - - if (channel !== null) { - channel(request, connection, subscriber, user); - } else { - connection.close(); - } - }); -}; - -/** - * 接続してきたユーザーを取得します - * @param token 送信されてきたトークン - */ -function authenticate(token: string): Promise<IUser> { - if (token == null) { - return Promise.resolve(null); - } - - return new Promise(async (resolve, reject) => { - if (isNativeToken(token)) { - // Fetch user - const user: IUser = await User - .findOne({ - token: token - }); - - resolve(user); - } else { - const accessToken = await AccessToken.findOne({ - hash: token - }); - - if (accessToken == null) { - return reject('invalid signature'); - } - - // Fetch user - const user: IUser = await User - .findOne({ _id: accessToken.user_id }); - - resolve(user); - } - }); -} |