Initial commit 🍀
							
								
								
									
										26
									
								
								.ci-files/config.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,26 @@
 | 
				
			||||||
 | 
					maintainer: '@syuilo'
 | 
				
			||||||
 | 
					url: 'https://misskey.xyz'
 | 
				
			||||||
 | 
					secondary_url: 'https://himasaku.net'
 | 
				
			||||||
 | 
					port: 80
 | 
				
			||||||
 | 
					https:
 | 
				
			||||||
 | 
					  enable: false
 | 
				
			||||||
 | 
					  key: null
 | 
				
			||||||
 | 
					  cert: null
 | 
				
			||||||
 | 
					  ca: null
 | 
				
			||||||
 | 
					mongodb:
 | 
				
			||||||
 | 
					  host: localhost
 | 
				
			||||||
 | 
					  port: 27017
 | 
				
			||||||
 | 
					  db: misskey
 | 
				
			||||||
 | 
					  user: syuilo
 | 
				
			||||||
 | 
					  pass: ''
 | 
				
			||||||
 | 
					redis:
 | 
				
			||||||
 | 
					  host: localhost
 | 
				
			||||||
 | 
					  port: 6379
 | 
				
			||||||
 | 
					  pass: ''
 | 
				
			||||||
 | 
					elasticsearch:
 | 
				
			||||||
 | 
					  host: localhost
 | 
				
			||||||
 | 
					  port: 9200
 | 
				
			||||||
 | 
					  pass: ''
 | 
				
			||||||
 | 
					recaptcha:
 | 
				
			||||||
 | 
					  siteKey: hima
 | 
				
			||||||
 | 
					  secretKey: saku
 | 
				
			||||||
							
								
								
									
										3
									
								
								.gitattributes
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					*.svg -diff -text
 | 
				
			||||||
 | 
					*.psd -diff -text
 | 
				
			||||||
 | 
					*.ai -diff -text
 | 
				
			||||||
							
								
								
									
										5
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,5 @@
 | 
				
			||||||
 | 
					/.config
 | 
				
			||||||
 | 
					/.vscode
 | 
				
			||||||
 | 
					/node_modules
 | 
				
			||||||
 | 
					/built
 | 
				
			||||||
 | 
					npm-debug.log
 | 
				
			||||||
							
								
								
									
										8
									
								
								.travis.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,8 @@
 | 
				
			||||||
 | 
					language: node_js
 | 
				
			||||||
 | 
					node_js:
 | 
				
			||||||
 | 
					  - "7.3.0"
 | 
				
			||||||
 | 
					before_script:
 | 
				
			||||||
 | 
					  - "mkdir -p ./.config && cp ./.ci-files/config.yml ./.config"
 | 
				
			||||||
 | 
					cache:
 | 
				
			||||||
 | 
					  directories:
 | 
				
			||||||
 | 
					    - node_modules
 | 
				
			||||||
							
								
								
									
										21
									
								
								LICENSE
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,21 @@
 | 
				
			||||||
 | 
					The MIT License (MIT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Copyright (c) 2014-2016 syuilo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Permission is hereby granted, free of charge, to any person obtaining a copy
 | 
				
			||||||
 | 
					of this software and associated documentation files (the "Software"), to deal
 | 
				
			||||||
 | 
					in the Software without restriction, including without limitation the rights
 | 
				
			||||||
 | 
					to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 | 
				
			||||||
 | 
					copies of the Software, and to permit persons to whom the Software is
 | 
				
			||||||
 | 
					furnished to do so, subject to the following conditions:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The above copyright notice and this permission notice shall be included in all
 | 
				
			||||||
 | 
					copies or substantial portions of the Software.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 | 
				
			||||||
 | 
					IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 | 
				
			||||||
 | 
					FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 | 
				
			||||||
 | 
					AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 | 
				
			||||||
 | 
					LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 | 
				
			||||||
 | 
					OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 | 
				
			||||||
 | 
					SOFTWARE.
 | 
				
			||||||
							
								
								
									
										44
									
								
								README.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,44 @@
 | 
				
			||||||
 | 
					# Misskey
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[![][travis-badge]][travis-link]
 | 
				
			||||||
 | 
					[![][dependencies-badge]][dependencies-link]
 | 
				
			||||||
 | 
					[![][mit-badge]][mit]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					A miniblog-based SNS.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Dependencies
 | 
				
			||||||
 | 
					* Node.js
 | 
				
			||||||
 | 
					* MongoDB
 | 
				
			||||||
 | 
					* Redis
 | 
				
			||||||
 | 
					* GraphicsMagick
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Optional dependencies
 | 
				
			||||||
 | 
					* Elasticsearch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Get started
 | 
				
			||||||
 | 
					Misskey requires two domains called the primary domain and the secondary domain.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* The primary domain is used to provide main service of Misskey.
 | 
				
			||||||
 | 
					* The secondary domain is used to avoid vulnerabilities such as XSS.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Ensure that the secondary domain is not a subdomain of the primary domain.**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Build
 | 
				
			||||||
 | 
					1. `git clone git://github.com/syuilo/misskey.git`
 | 
				
			||||||
 | 
					2. `cd misskey`
 | 
				
			||||||
 | 
					3. `npm install`
 | 
				
			||||||
 | 
					4. `npm run config`
 | 
				
			||||||
 | 
					5. `npm run build`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Launch
 | 
				
			||||||
 | 
					`npm start`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## License
 | 
				
			||||||
 | 
					[MIT](LICENSE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[mit]:                http://opensource.org/licenses/MIT
 | 
				
			||||||
 | 
					[mit-badge]:          https://img.shields.io/badge/license-MIT-444444.svg?style=flat-square
 | 
				
			||||||
 | 
					[travis-link]:        https://travis-ci.org/syuilo/misskey
 | 
				
			||||||
 | 
					[travis-badge]:       http://img.shields.io/travis/syuilo/misskey.svg?style=flat-square
 | 
				
			||||||
 | 
					[dependencies-link]:  https://gemnasium.com/syuilo/misskey
 | 
				
			||||||
 | 
					[dependencies-badge]: https://img.shields.io/gemnasium/syuilo/misskey.svg?style=flat-square
 | 
				
			||||||
							
								
								
									
										6
									
								
								elasticsearch/README.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					How to create indexes
 | 
				
			||||||
 | 
					=====================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					``` shell
 | 
				
			||||||
 | 
					curl -XPOST localhost:9200/misskey -d @path/to/mappings.json
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
							
								
								
									
										65
									
								
								elasticsearch/mappings.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,65 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						"settings": {
 | 
				
			||||||
 | 
							"analysis": {
 | 
				
			||||||
 | 
								"analyzer": {
 | 
				
			||||||
 | 
									"bigram": {
 | 
				
			||||||
 | 
										"tokenizer": "bigram_tokenizer"
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								"tokenizer": {
 | 
				
			||||||
 | 
									"bigram_tokenizer": {
 | 
				
			||||||
 | 
										"type": "nGram",
 | 
				
			||||||
 | 
										"min_gram": 2,
 | 
				
			||||||
 | 
										"max_gram": 2,
 | 
				
			||||||
 | 
										"token_chars": [
 | 
				
			||||||
 | 
											"letter",
 | 
				
			||||||
 | 
											"digit"
 | 
				
			||||||
 | 
										]
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"mappings": {
 | 
				
			||||||
 | 
							"user": {
 | 
				
			||||||
 | 
								"properties": {
 | 
				
			||||||
 | 
									"username": {
 | 
				
			||||||
 | 
										"type": "string",
 | 
				
			||||||
 | 
										"index": "analyzed",
 | 
				
			||||||
 | 
										"analyzer": "bigram"
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									"name": {
 | 
				
			||||||
 | 
										"type": "string",
 | 
				
			||||||
 | 
										"index": "analyzed",
 | 
				
			||||||
 | 
										"analyzer": "bigram"
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									"bio": {
 | 
				
			||||||
 | 
										"type": "string",
 | 
				
			||||||
 | 
										"index": "analyzed",
 | 
				
			||||||
 | 
										"analyzer": "kuromoji"
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"post": {
 | 
				
			||||||
 | 
								"properties": {
 | 
				
			||||||
 | 
									"text": {
 | 
				
			||||||
 | 
										"type": "string",
 | 
				
			||||||
 | 
										"index": "analyzed",
 | 
				
			||||||
 | 
										"analyzer": "kuromoji"
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"drive_file": {
 | 
				
			||||||
 | 
								"properties": {
 | 
				
			||||||
 | 
									"name": {
 | 
				
			||||||
 | 
										"type": "string",
 | 
				
			||||||
 | 
										"index": "analyzed",
 | 
				
			||||||
 | 
										"analyzer": "kuromoji"
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									"user": {
 | 
				
			||||||
 | 
										"type": "string",
 | 
				
			||||||
 | 
										"index": "not_analyzed"
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								gulpfile.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					eval(require('typescript').transpile(require('fs').readFileSync('./gulpfile.ts').toString()));
 | 
				
			||||||
							
								
								
									
										568
									
								
								gulpfile.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,568 @@
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Gulp tasks
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import * as gulp from 'gulp';
 | 
				
			||||||
 | 
					import * as gutil from 'gulp-util';
 | 
				
			||||||
 | 
					import * as babel from 'gulp-babel';
 | 
				
			||||||
 | 
					import * as ts from 'gulp-typescript';
 | 
				
			||||||
 | 
					import * as tslint from 'gulp-tslint';
 | 
				
			||||||
 | 
					import * as glob from 'glob';
 | 
				
			||||||
 | 
					import * as browserify from 'browserify';
 | 
				
			||||||
 | 
					import * as source from 'vinyl-source-stream';
 | 
				
			||||||
 | 
					import * as buffer from 'vinyl-buffer';
 | 
				
			||||||
 | 
					import * as es from 'event-stream';
 | 
				
			||||||
 | 
					const stylus = require('gulp-stylus');
 | 
				
			||||||
 | 
					const cssnano = require('gulp-cssnano');
 | 
				
			||||||
 | 
					import * as uglify from 'gulp-uglify';
 | 
				
			||||||
 | 
					const ls = require('browserify-livescript');
 | 
				
			||||||
 | 
					const aliasify = require('aliasify');
 | 
				
			||||||
 | 
					const riotify = require('riotify');
 | 
				
			||||||
 | 
					const transformify = require('syuilo-transformify');
 | 
				
			||||||
 | 
					const pug = require('gulp-pug');
 | 
				
			||||||
 | 
					const git = require('git-last-commit');
 | 
				
			||||||
 | 
					import * as rimraf from 'rimraf';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const env = process.env.NODE_ENV;
 | 
				
			||||||
 | 
					const isProduction = env === 'production';
 | 
				
			||||||
 | 
					const isDebug = !isProduction;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { IConfig } from './src/config';
 | 
				
			||||||
 | 
					const config = eval(require('typescript').transpile(require('fs').readFileSync('./src/config.ts').toString()))
 | 
				
			||||||
 | 
						('.config/config.yml') as IConfig;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const project = ts.createProject('tsconfig.json');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gulp.task('build', [
 | 
				
			||||||
 | 
						'build:js',
 | 
				
			||||||
 | 
						'build:ts',
 | 
				
			||||||
 | 
						'build:copy',
 | 
				
			||||||
 | 
						'build:client'
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gulp.task('rebuild', [
 | 
				
			||||||
 | 
						'clean',
 | 
				
			||||||
 | 
						'build'
 | 
				
			||||||
 | 
					]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gulp.task('build:js', () =>
 | 
				
			||||||
 | 
						gulp.src(['./src/**/*.js', '!./src/web/**/*.js'])
 | 
				
			||||||
 | 
							.pipe(babel({
 | 
				
			||||||
 | 
								presets: ['es2015', 'stage-3']
 | 
				
			||||||
 | 
							}))
 | 
				
			||||||
 | 
							.pipe(gulp.dest('./built/'))
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gulp.task('build:ts', () =>
 | 
				
			||||||
 | 
						project
 | 
				
			||||||
 | 
							.src()
 | 
				
			||||||
 | 
							.pipe(project())
 | 
				
			||||||
 | 
							.pipe(babel({
 | 
				
			||||||
 | 
								presets: ['es2015', 'stage-3']
 | 
				
			||||||
 | 
							}))
 | 
				
			||||||
 | 
							.pipe(gulp.dest('./built/'))
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gulp.task('build:copy', () => {
 | 
				
			||||||
 | 
						gulp.src([
 | 
				
			||||||
 | 
							'./src/**/resources/**/*',
 | 
				
			||||||
 | 
							'!./src/web/app/**/resources/**/*'
 | 
				
			||||||
 | 
						]).pipe(gulp.dest('./built/'));
 | 
				
			||||||
 | 
						gulp.src([
 | 
				
			||||||
 | 
							'./src/web/about/**/*'
 | 
				
			||||||
 | 
						]).pipe(gulp.dest('./built/web/about/'));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gulp.task('test', ['lint', 'build']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gulp.task('lint', () =>
 | 
				
			||||||
 | 
						gulp.src('./src/**/*.ts')
 | 
				
			||||||
 | 
							.pipe(tslint({
 | 
				
			||||||
 | 
								formatter: 'verbose'
 | 
				
			||||||
 | 
							}))
 | 
				
			||||||
 | 
							.pipe(tslint.report())
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gulp.task('clean', cb =>
 | 
				
			||||||
 | 
						rimraf('./built', cb)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gulp.task('cleanall', ['clean'], cb =>
 | 
				
			||||||
 | 
						rimraf('./node_modules', cb)
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gulp.task('default', ['build']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const aliasifyConfig = {
 | 
				
			||||||
 | 
						aliases: {
 | 
				
			||||||
 | 
							'fetch': './node_modules/whatwg-fetch/fetch.js',
 | 
				
			||||||
 | 
							'page': './node_modules/page/page.js',
 | 
				
			||||||
 | 
							'NProgress': './node_modules/nprogress/nprogress.js',
 | 
				
			||||||
 | 
							'velocity': './node_modules/velocity-animate/velocity.js',
 | 
				
			||||||
 | 
							'chart.js': './node_modules/chart.js/src/chart.js',
 | 
				
			||||||
 | 
							'textarea-caret-position': './node_modules/textarea-caret/index.js',
 | 
				
			||||||
 | 
							'misskey-text': './src/common/text/index.js',
 | 
				
			||||||
 | 
							'strength.js': './node_modules/syuilo-password-strength/strength.js',
 | 
				
			||||||
 | 
							'cropper': './node_modules/cropperjs/dist/cropper.js',
 | 
				
			||||||
 | 
							'Sortable': './node_modules/sortablejs/Sortable.js',
 | 
				
			||||||
 | 
							'fuck-adblock': './node_modules/fuckadblock/fuckadblock.js',
 | 
				
			||||||
 | 
							'reconnecting-websocket': './node_modules/reconnecting-websocket/dist/index.js'
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						appliesTo: {
 | 
				
			||||||
 | 
							'includeExtensions': ['.js', '.ls']
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gulp.task('build:client', [
 | 
				
			||||||
 | 
						'build:ts', 'build:js',
 | 
				
			||||||
 | 
						'build:client:scripts',
 | 
				
			||||||
 | 
						'build:client:styles',
 | 
				
			||||||
 | 
						'build:client:pug',
 | 
				
			||||||
 | 
						'copy:client'
 | 
				
			||||||
 | 
					], () => {
 | 
				
			||||||
 | 
						gutil.log('ビルドが終了しました。');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (isDebug) {
 | 
				
			||||||
 | 
							gutil.log('■ 注意! 開発モードでのビルドです。');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gulp.task('build:client:scripts', done => {
 | 
				
			||||||
 | 
						gutil.log('スクリプトを構築します...');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get commit info
 | 
				
			||||||
 | 
						git.getLastCommit((err, commit) => {
 | 
				
			||||||
 | 
							glob('./src/web/app/*/script.js', (err, files) => {
 | 
				
			||||||
 | 
								const tasks = files.map(entry => {
 | 
				
			||||||
 | 
									let bundle =
 | 
				
			||||||
 | 
										browserify({
 | 
				
			||||||
 | 
											entries: [entry]
 | 
				
			||||||
 | 
										})
 | 
				
			||||||
 | 
										.transform(ls)
 | 
				
			||||||
 | 
										.transform(aliasify, aliasifyConfig)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										.transform(transformify((source, file) => {
 | 
				
			||||||
 | 
											if (file.substr(-4) !== '.tag') return source;
 | 
				
			||||||
 | 
											console.log(file);
 | 
				
			||||||
 | 
											return source;
 | 
				
			||||||
 | 
										}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// tagの{}の''を不要にする (その代わりスタイルの記法は使えなくなるけど)
 | 
				
			||||||
 | 
										.transform(transformify((source, file) => {
 | 
				
			||||||
 | 
											if (file.substr(-4) !== '.tag') return source;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											const tag = new Tag(source);
 | 
				
			||||||
 | 
											const html = tag.sections.filter(s => s.name == 'html')[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											html.lines = html.lines.map(line => {
 | 
				
			||||||
 | 
												if (line.replace(/\t/g, '')[0] === '|') {
 | 
				
			||||||
 | 
													return line;
 | 
				
			||||||
 | 
												} else {
 | 
				
			||||||
 | 
													return line.replace(/([+=])\s?\{(.+?)\}/g, '$1"{$2}"');
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											const styles = tag.sections.filter(s => s.name == 'style');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											if (styles.length == 0) {
 | 
				
			||||||
 | 
												return tag.compile();
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											styles.forEach(style => {
 | 
				
			||||||
 | 
												let head = style.lines.shift();
 | 
				
			||||||
 | 
												head = head.replace(/([+=])\s?\{(.+?)\}/g, '$1"{$2}"');
 | 
				
			||||||
 | 
												style.lines.unshift(head);
 | 
				
			||||||
 | 
											});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											return tag.compile();
 | 
				
			||||||
 | 
										}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// tagの@hogeをref='hoge'にする
 | 
				
			||||||
 | 
										.transform(transformify((source, file) => {
 | 
				
			||||||
 | 
											if (file.substr(-4) !== '.tag') return source;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											const tag = new Tag(source);
 | 
				
			||||||
 | 
											const html = tag.sections.filter(s => s.name == 'html')[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											html.lines = html.lines.map(line => {
 | 
				
			||||||
 | 
												if (line.indexOf('@') === -1) {
 | 
				
			||||||
 | 
													return line;
 | 
				
			||||||
 | 
												} else if (line.replace(/\t/g, '')[0] === '|') {
 | 
				
			||||||
 | 
													return line;
 | 
				
			||||||
 | 
												} else {
 | 
				
			||||||
 | 
													while (line.match(/[^\s']@[a-z-]+/) !== null) {
 | 
				
			||||||
 | 
														const match = line.match(/@[a-z-]+/);
 | 
				
			||||||
 | 
														let name = match[0];
 | 
				
			||||||
 | 
														if (line[line.indexOf(name) + name.length] === '(') {
 | 
				
			||||||
 | 
															line = line.replace(name + '(', '(ref=\'' + camelCase(name.substr(1)) + '\',');
 | 
				
			||||||
 | 
														} else {
 | 
				
			||||||
 | 
															line = line.replace(name, '(ref=\'' + camelCase(name.substr(1)) + '\')');
 | 
				
			||||||
 | 
														}
 | 
				
			||||||
 | 
													}
 | 
				
			||||||
 | 
													return line;
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											return tag.compile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											function camelCase(str): string {
 | 
				
			||||||
 | 
												return str.replace(/-([^\s])/g, (match, group1) => {
 | 
				
			||||||
 | 
													return group1.toUpperCase();
 | 
				
			||||||
 | 
												});
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// tagのchain-caseをcamelCaseにする
 | 
				
			||||||
 | 
										.transform(transformify((source, file) => {
 | 
				
			||||||
 | 
											if (file.substr(-4) !== '.tag') return source;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											const tag = new Tag(source);
 | 
				
			||||||
 | 
											const html = tag.sections.filter(s => s.name == 'html')[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											html.lines = html.lines.map(line => {
 | 
				
			||||||
 | 
												(line.match(/\{.+?\}/g) || []).forEach(x => {
 | 
				
			||||||
 | 
													line = line.replace(x, camelCase(x));
 | 
				
			||||||
 | 
												});
 | 
				
			||||||
 | 
												return line;
 | 
				
			||||||
 | 
											});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											return tag.compile();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											function camelCase(str): string {
 | 
				
			||||||
 | 
												str = str.replace(/([a-z\-]+):/g, (match, group1) => {
 | 
				
			||||||
 | 
													return group1.replace(/\-/g, '###') + ':';
 | 
				
			||||||
 | 
												});
 | 
				
			||||||
 | 
												str = str.replace(/'(.+?)'/g, (match, group1) => {
 | 
				
			||||||
 | 
													return "'" + group1.replace(/\-/g, '###') + "'";
 | 
				
			||||||
 | 
												});
 | 
				
			||||||
 | 
												str = str.replace(/-([^\s0-9])/g, (match, group1) => {
 | 
				
			||||||
 | 
													return group1.toUpperCase();
 | 
				
			||||||
 | 
												});
 | 
				
			||||||
 | 
												str = str.replace(/###/g, '-');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												return str;
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// tagのstyleの属性
 | 
				
			||||||
 | 
										.transform(transformify((source, file) => {
 | 
				
			||||||
 | 
											if (file.substr(-4) !== '.tag') return source;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											const tag = new Tag(source);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											const styles = tag.sections.filter(s => s.name == 'style');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											if (styles.length == 0) {
 | 
				
			||||||
 | 
												return tag.compile();
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											styles.forEach(style => {
 | 
				
			||||||
 | 
												let head = style.lines.shift();
 | 
				
			||||||
 | 
												if (style.attr) {
 | 
				
			||||||
 | 
													style.attr = style.attr + ', type=\'stylus\', scoped';
 | 
				
			||||||
 | 
												} else {
 | 
				
			||||||
 | 
													style.attr = 'type=\'stylus\', scoped';
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
												style.lines.unshift(head);
 | 
				
			||||||
 | 
											});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											return tag.compile();
 | 
				
			||||||
 | 
										}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// tagのstyleの定数
 | 
				
			||||||
 | 
										.transform(transformify((source, file) => {
 | 
				
			||||||
 | 
											if (file.substr(-4) !== '.tag') return source;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											const tag = new Tag(source);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											const styles = tag.sections.filter(s => s.name == 'style');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											if (styles.length == 0) {
 | 
				
			||||||
 | 
												return tag.compile();
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											styles.forEach(style => {
 | 
				
			||||||
 | 
												const head = style.lines.shift();
 | 
				
			||||||
 | 
												style.lines.unshift('$theme-color = ' + config.themeColor);
 | 
				
			||||||
 | 
												style.lines.unshift('$theme-color-foreground = #fff');
 | 
				
			||||||
 | 
												style.lines.unshift(head);
 | 
				
			||||||
 | 
											});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											return tag.compile();
 | 
				
			||||||
 | 
										}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// tagのstyleを暗黙的に:scopeにする
 | 
				
			||||||
 | 
										.transform(transformify((source, file) => {
 | 
				
			||||||
 | 
											if (file.substr(-4) !== '.tag') return source;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											const tag = new Tag(source);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											const styles = tag.sections.filter(s => s.name == 'style');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											if (styles.length == 0) {
 | 
				
			||||||
 | 
												return tag.compile();
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											styles.forEach((style, i) => {
 | 
				
			||||||
 | 
												if (i != 0) {
 | 
				
			||||||
 | 
													return;
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
												const head = style.lines.shift();
 | 
				
			||||||
 | 
												style.lines = style.lines.map(line => {
 | 
				
			||||||
 | 
													return '\t' + line;
 | 
				
			||||||
 | 
												});
 | 
				
			||||||
 | 
												style.lines.unshift(':scope');
 | 
				
			||||||
 | 
												style.lines.unshift(head);
 | 
				
			||||||
 | 
											});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											return tag.compile();
 | 
				
			||||||
 | 
										}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// tagのtheme styleのパース
 | 
				
			||||||
 | 
										.transform(transformify((source, file) => {
 | 
				
			||||||
 | 
											if (file.substr(-4) !== '.tag') return source;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											const tag = new Tag(source);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											const styles = tag.sections.filter(s => s.name == 'style');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											if (styles.length == 0) {
 | 
				
			||||||
 | 
												return tag.compile();
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											styles.forEach((style, i) => {
 | 
				
			||||||
 | 
												if (i == 0) {
 | 
				
			||||||
 | 
													return;
 | 
				
			||||||
 | 
												} else if (style.attr.substr(0, 6) != 'theme=') {
 | 
				
			||||||
 | 
													return;
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
												const head = style.lines.shift();
 | 
				
			||||||
 | 
												style.lines = style.lines.map(line => {
 | 
				
			||||||
 | 
													return '\t' + line;
 | 
				
			||||||
 | 
												});
 | 
				
			||||||
 | 
												style.lines.unshift(':scope');
 | 
				
			||||||
 | 
												style.lines = style.lines.map(line => {
 | 
				
			||||||
 | 
													return '\t' + line;
 | 
				
			||||||
 | 
												});
 | 
				
			||||||
 | 
												style.lines.unshift('html[data-' + style.attr.match(/theme='(.+?)'/)[0] + ']');
 | 
				
			||||||
 | 
												style.lines.unshift(head);
 | 
				
			||||||
 | 
											});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											return tag.compile();
 | 
				
			||||||
 | 
										}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// tagのstyleおよびscriptのインデントを不要にする
 | 
				
			||||||
 | 
										.transform(transformify((source, file) => {
 | 
				
			||||||
 | 
											if (file.substr(-4) !== '.tag') return source;
 | 
				
			||||||
 | 
											const tag = new Tag(source);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											tag.sections = tag.sections.map(section => {
 | 
				
			||||||
 | 
												if (section.name != 'html') {
 | 
				
			||||||
 | 
													section.indent++;
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
												return section;
 | 
				
			||||||
 | 
											});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											return tag.compile();
 | 
				
			||||||
 | 
										}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// スペースでインデントされてないとエラーが出る
 | 
				
			||||||
 | 
										.transform(transformify((source, file) => {
 | 
				
			||||||
 | 
											if (file.substr(-4) !== '.tag') return source;
 | 
				
			||||||
 | 
											return source.replace(/\t/g, '  ');
 | 
				
			||||||
 | 
										}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										.transform(transformify((source, file) => {
 | 
				
			||||||
 | 
											return source
 | 
				
			||||||
 | 
												.replace(/VERSION/g, `'${commit ? commit.hash : 'null'}'`)
 | 
				
			||||||
 | 
												.replace(/CONFIG\.theme-color/g, `'${config.themeColor}'`)
 | 
				
			||||||
 | 
												.replace(/CONFIG\.themeColor/g, `'${config.themeColor}'`)
 | 
				
			||||||
 | 
												.replace(/CONFIG\.api\.url/g, `'${config.scheme}://api.${config.host}'`)
 | 
				
			||||||
 | 
												.replace(/CONFIG\.urls\.about/g, `'${config.scheme}://about.${config.host}'`)
 | 
				
			||||||
 | 
												.replace(/CONFIG\.urls\.dev/g, `'${config.scheme}://dev.${config.host}'`)
 | 
				
			||||||
 | 
												.replace(/CONFIG\.url/g, `'${config.url}'`)
 | 
				
			||||||
 | 
												.replace(/CONFIG\.host/g, `'${config.host}'`)
 | 
				
			||||||
 | 
												.replace(/CONFIG\.recaptcha\.siteKey/g, `'${config.recaptcha.siteKey}'`)
 | 
				
			||||||
 | 
												;
 | 
				
			||||||
 | 
										}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										.transform(riotify, {
 | 
				
			||||||
 | 
											template: 'pug',
 | 
				
			||||||
 | 
											type: 'livescript',
 | 
				
			||||||
 | 
											expr: false,
 | 
				
			||||||
 | 
											compact: true,
 | 
				
			||||||
 | 
											parserOptions: {
 | 
				
			||||||
 | 
												style: {
 | 
				
			||||||
 | 
													compress: true,
 | 
				
			||||||
 | 
													rawDefine: config
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										})
 | 
				
			||||||
 | 
										// Riotが謎の空白を挿入する
 | 
				
			||||||
 | 
										.transform(transformify((source, file) => {
 | 
				
			||||||
 | 
											if (file.substr(-4) !== '.tag') return source;
 | 
				
			||||||
 | 
											return source.replace(/\s<mk\-ellipsis>/g, '<mk-ellipsis>');
 | 
				
			||||||
 | 
										}))
 | 
				
			||||||
 | 
										/*
 | 
				
			||||||
 | 
										// LiveScruptがHTMLクラスのショートカットを変な風に生成するのでそれを修正
 | 
				
			||||||
 | 
										.transform(transformify((source, file) => {
 | 
				
			||||||
 | 
											if (file.substr(-4) !== '.tag') return source;
 | 
				
			||||||
 | 
											return source.replace(/class="\{\(\{(.+?)\}\)\}"/g, 'class="{$1}"');
 | 
				
			||||||
 | 
										}))*/
 | 
				
			||||||
 | 
										.bundle()
 | 
				
			||||||
 | 
										.pipe(source(entry.replace('./src/web/app/', './').replace('.ls', '.js')));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (isProduction) {
 | 
				
			||||||
 | 
										bundle = bundle
 | 
				
			||||||
 | 
											.pipe(buffer())
 | 
				
			||||||
 | 
											// ↓ https://github.com/mishoo/UglifyJS2/issues/448
 | 
				
			||||||
 | 
											.pipe(babel({
 | 
				
			||||||
 | 
												presets: ['es2015']
 | 
				
			||||||
 | 
											}))
 | 
				
			||||||
 | 
											.pipe(uglify({
 | 
				
			||||||
 | 
												compress: true
 | 
				
			||||||
 | 
											}));
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									return bundle
 | 
				
			||||||
 | 
										.pipe(gulp.dest('./built/web/resources/'));
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								es.merge(tasks).on('end', done);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gulp.task('build:client:styles', () => {
 | 
				
			||||||
 | 
						gutil.log('フロントサイドスタイルを構築します...');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return gulp.src('./src/web/app/**/*.styl')
 | 
				
			||||||
 | 
							.pipe(stylus({
 | 
				
			||||||
 | 
								'include css': true,
 | 
				
			||||||
 | 
								compress: true,
 | 
				
			||||||
 | 
								rawDefine: config
 | 
				
			||||||
 | 
							}))
 | 
				
			||||||
 | 
							.pipe(isProduction
 | 
				
			||||||
 | 
								? cssnano({
 | 
				
			||||||
 | 
									safe: true // 高度な圧縮は無効にする (一部デザインが不適切になる場合があるため)
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								: gutil.noop())
 | 
				
			||||||
 | 
							.pipe(gulp.dest('./built/web/resources/'));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gulp.task('copy:client', [
 | 
				
			||||||
 | 
						'build:client:scripts',
 | 
				
			||||||
 | 
						'build:client:styles'
 | 
				
			||||||
 | 
					], () => {
 | 
				
			||||||
 | 
						gutil.log('必要なリソースをコピーします...');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return es.merge(
 | 
				
			||||||
 | 
							gulp.src('./resources/**/*').pipe(gulp.dest('./built/web/resources/')),
 | 
				
			||||||
 | 
							gulp.src('./src/web/app/desktop/resources/**/*').pipe(gulp.dest('./built/web/resources/desktop/')),
 | 
				
			||||||
 | 
							gulp.src('./src/web/app/mobile/resources/**/*').pipe(gulp.dest('./built/web/resources/mobile/')),
 | 
				
			||||||
 | 
							gulp.src('./src/web/app/dev/resources/**/*').pipe(gulp.dest('./built/web/resources/dev/')),
 | 
				
			||||||
 | 
							gulp.src('./src/web/app/auth/resources/**/*').pipe(gulp.dest('./built/web/resources/auth/'))
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gulp.task('build:client:pug', [
 | 
				
			||||||
 | 
						'copy:client',
 | 
				
			||||||
 | 
						'build:client:scripts',
 | 
				
			||||||
 | 
						'build:client:styles'
 | 
				
			||||||
 | 
					], () => {
 | 
				
			||||||
 | 
						gutil.log('Pugをコンパイルします...');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return gulp.src([
 | 
				
			||||||
 | 
							'./src/web/app/*/view.pug'
 | 
				
			||||||
 | 
						])
 | 
				
			||||||
 | 
							.pipe(pug({
 | 
				
			||||||
 | 
								locals: {
 | 
				
			||||||
 | 
									themeColor: config.themeColor
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}))
 | 
				
			||||||
 | 
							.pipe(gulp.dest('./built/web/app/'));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Tag {
 | 
				
			||||||
 | 
						sections: {
 | 
				
			||||||
 | 
							name: string;
 | 
				
			||||||
 | 
							attr?: string;
 | 
				
			||||||
 | 
							indent: number;
 | 
				
			||||||
 | 
							lines: string[];
 | 
				
			||||||
 | 
						}[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						constructor(source) {
 | 
				
			||||||
 | 
							this.sections = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							source = source
 | 
				
			||||||
 | 
								.replace(/\r\n/g, '\n')
 | 
				
			||||||
 | 
								.replace(/\n(\t+?)\n/g, '\n')
 | 
				
			||||||
 | 
								.replace(/\n+/g, '\n');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const html = {
 | 
				
			||||||
 | 
								name: 'html',
 | 
				
			||||||
 | 
								indent: 0,
 | 
				
			||||||
 | 
								lines: []
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							let flag = false;
 | 
				
			||||||
 | 
							source.split('\n').forEach((line, i) => {
 | 
				
			||||||
 | 
								const indent = line.lastIndexOf('\t') + 1;
 | 
				
			||||||
 | 
								if (i != 0 && indent == 0) {
 | 
				
			||||||
 | 
									flag = true;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if (!flag) {
 | 
				
			||||||
 | 
									source = source.replace(/^.*?\n/, '');
 | 
				
			||||||
 | 
									html.lines.push(i == 0 ? line : line.substr(1));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.sections.push(html);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							while (source != '') {
 | 
				
			||||||
 | 
								const line = source.substr(0, source.indexOf('\n'));
 | 
				
			||||||
 | 
								const root = line.match(/^\t*([a-z]+)(\.|\()?/)[1];
 | 
				
			||||||
 | 
								const beginIndent = line.lastIndexOf('\t') + 1;
 | 
				
			||||||
 | 
								flag = false;
 | 
				
			||||||
 | 
								const section = {
 | 
				
			||||||
 | 
									name: root,
 | 
				
			||||||
 | 
									attr: (line.match(/\((.+?)\)/) || [null, null])[1],
 | 
				
			||||||
 | 
									indent: beginIndent,
 | 
				
			||||||
 | 
									lines: []
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
								source.split('\n').forEach((line, i) => {
 | 
				
			||||||
 | 
									const currentIndent = line.lastIndexOf('\t') + 1;
 | 
				
			||||||
 | 
									if (i != 0 && (currentIndent == beginIndent || currentIndent == 0)) {
 | 
				
			||||||
 | 
										flag = true;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if (!flag) {
 | 
				
			||||||
 | 
										if (i == 0 && line[line.length - 1] == '.') {
 | 
				
			||||||
 | 
											line = line.substr(0, line.length - 1);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										if (i == 0 && line.indexOf('(') != -1) {
 | 
				
			||||||
 | 
											line = line.substr(0, line.indexOf('('));
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										source = source.replace(/^.*?\n/, '');
 | 
				
			||||||
 | 
										section.lines.push(i == 0 ? line.substr(beginIndent) : line.substr(beginIndent + 1));
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
								this.sections.push(section);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						compile(): string {
 | 
				
			||||||
 | 
							let dist = '';
 | 
				
			||||||
 | 
							this.sections.forEach((section, j) => {
 | 
				
			||||||
 | 
								dist += section.lines.map((line, i) => {
 | 
				
			||||||
 | 
									if (i == 0) {
 | 
				
			||||||
 | 
										const attr = section.attr != null ? '(' + section.attr + ')' : '';
 | 
				
			||||||
 | 
										const tail = j != 0 ? '.' : '';
 | 
				
			||||||
 | 
										return '\t'.repeat(section.indent) + line + attr + tail;
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										return '\t'.repeat(section.indent + 1) + line;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}).join('\n') + '\n';
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
							return dist;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										182
									
								
								init.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,182 @@
 | 
				
			||||||
 | 
					const fs = require('fs');
 | 
				
			||||||
 | 
					const yaml = require('js-yaml');
 | 
				
			||||||
 | 
					const inquirer = require('inquirer');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const configDirPath = `${__dirname}/.config`;
 | 
				
			||||||
 | 
					const configPath = `${configDirPath}/config.yml`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const form = [
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							type: 'input',
 | 
				
			||||||
 | 
							name: 'maintainer',
 | 
				
			||||||
 | 
							message: 'Maintainer name(and email address):'
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							type: 'input',
 | 
				
			||||||
 | 
							name: 'url',
 | 
				
			||||||
 | 
							message: 'PRIMARY URL:'
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							type: 'input',
 | 
				
			||||||
 | 
							name: 'secondary_url',
 | 
				
			||||||
 | 
							message: 'SECONDARY URL:'
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							type: 'input',
 | 
				
			||||||
 | 
							name: 'port',
 | 
				
			||||||
 | 
							message: 'Listen port:'
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							type: 'confirm',
 | 
				
			||||||
 | 
							name: 'https',
 | 
				
			||||||
 | 
							message: 'Use TLS?',
 | 
				
			||||||
 | 
							default: false
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							type: 'input',
 | 
				
			||||||
 | 
							name: 'https_key',
 | 
				
			||||||
 | 
							message: 'Path of tls key:',
 | 
				
			||||||
 | 
							when: ctx => ctx.https
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							type: 'input',
 | 
				
			||||||
 | 
							name: 'https_cert',
 | 
				
			||||||
 | 
							message: 'Path of tls cert:',
 | 
				
			||||||
 | 
							when: ctx => ctx.https
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							type: 'input',
 | 
				
			||||||
 | 
							name: 'https_ca',
 | 
				
			||||||
 | 
							message: 'Path of tls ca:',
 | 
				
			||||||
 | 
							when: ctx => ctx.https
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							type: 'input',
 | 
				
			||||||
 | 
							name: 'mongo_host',
 | 
				
			||||||
 | 
							message: 'MongoDB\'s host:',
 | 
				
			||||||
 | 
							default: 'localhost'
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							type: 'input',
 | 
				
			||||||
 | 
							name: 'mongo_port',
 | 
				
			||||||
 | 
							message: 'MongoDB\'s port:',
 | 
				
			||||||
 | 
							default: '27017'
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							type: 'input',
 | 
				
			||||||
 | 
							name: 'mongo_db',
 | 
				
			||||||
 | 
							message: 'MongoDB\'s db:',
 | 
				
			||||||
 | 
							default: 'misskey'
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							type: 'input',
 | 
				
			||||||
 | 
							name: 'mongo_user',
 | 
				
			||||||
 | 
							message: 'MongoDB\'s user:'
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							type: 'password',
 | 
				
			||||||
 | 
							name: 'mongo_pass',
 | 
				
			||||||
 | 
							message: 'MongoDB\'s password:'
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							type: 'input',
 | 
				
			||||||
 | 
							name: 'redis_host',
 | 
				
			||||||
 | 
							message: 'Redis\'s host:',
 | 
				
			||||||
 | 
							default: 'localhost'
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							type: 'input',
 | 
				
			||||||
 | 
							name: 'redis_port',
 | 
				
			||||||
 | 
							message: 'Redis\'s port:',
 | 
				
			||||||
 | 
							default: '6379'
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							type: 'password',
 | 
				
			||||||
 | 
							name: 'redis_pass',
 | 
				
			||||||
 | 
							message: 'Redis\'s password:'
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							type: 'confirm',
 | 
				
			||||||
 | 
							name: 'elasticsearch',
 | 
				
			||||||
 | 
							message: 'Use Elasticsearch?',
 | 
				
			||||||
 | 
							default: false
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							type: 'input',
 | 
				
			||||||
 | 
							name: 'es_host',
 | 
				
			||||||
 | 
							message: 'Elasticsearch\'s host:',
 | 
				
			||||||
 | 
							default: 'localhost',
 | 
				
			||||||
 | 
							when: ctx => ctx.elasticsearch
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							type: 'input',
 | 
				
			||||||
 | 
							name: 'es_port',
 | 
				
			||||||
 | 
							message: 'Elasticsearch\'s port:',
 | 
				
			||||||
 | 
							default: '9200',
 | 
				
			||||||
 | 
							when: ctx => ctx.elasticsearch
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							type: 'password',
 | 
				
			||||||
 | 
							name: 'es_pass',
 | 
				
			||||||
 | 
							message: 'Elasticsearch\'s password:',
 | 
				
			||||||
 | 
							when: ctx => ctx.elasticsearch
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							type: 'input',
 | 
				
			||||||
 | 
							name: 'recaptcha_site',
 | 
				
			||||||
 | 
							message: 'reCAPTCHA\'s site key:'
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							type: 'input',
 | 
				
			||||||
 | 
							name: 'recaptcha_secret',
 | 
				
			||||||
 | 
							message: 'reCAPTCHA\'s secret key:'
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					inquirer.prompt(form).then(as => {
 | 
				
			||||||
 | 
						// Mapping answers
 | 
				
			||||||
 | 
						const conf = {
 | 
				
			||||||
 | 
							maintainer: as['maintainer'],
 | 
				
			||||||
 | 
							url: as['url'],
 | 
				
			||||||
 | 
							secondary_url: as['secondary_url'],
 | 
				
			||||||
 | 
							port: parseInt(as['port'], 10),
 | 
				
			||||||
 | 
							https: {
 | 
				
			||||||
 | 
								enable: as['https'],
 | 
				
			||||||
 | 
								key: as['https_key'] || null,
 | 
				
			||||||
 | 
								cert: as['https_cert'] || null,
 | 
				
			||||||
 | 
								ca: as['https_ca'] || null
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							mongodb: {
 | 
				
			||||||
 | 
								host: as['mongo_host'],
 | 
				
			||||||
 | 
								port: parseInt(as['mongo_port'], 10),
 | 
				
			||||||
 | 
								db: as['mongo_db'],
 | 
				
			||||||
 | 
								user: as['mongo_user'],
 | 
				
			||||||
 | 
								pass: as['mongo_pass']
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							redis: {
 | 
				
			||||||
 | 
								host: as['redis_host'],
 | 
				
			||||||
 | 
								port: parseInt(as['redis_port'], 10),
 | 
				
			||||||
 | 
								pass: as['redis_pass']
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							elasticsearch: {
 | 
				
			||||||
 | 
								enable: as['elasticsearch'],
 | 
				
			||||||
 | 
								host: as['es_host'] || null,
 | 
				
			||||||
 | 
								port: parseInt(as['es_port'], 10) || null,
 | 
				
			||||||
 | 
								pass: as['es_pass'] || null
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							recaptcha: {
 | 
				
			||||||
 | 
								siteKey: as['recaptcha_site'],
 | 
				
			||||||
 | 
								secretKey: as['recaptcha_secret']
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						console.log('Thanks. Writing the configuration to a file...');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						try {
 | 
				
			||||||
 | 
							fs.mkdirSync(configDirPath);
 | 
				
			||||||
 | 
							fs.writeFileSync(configPath, yaml.dump(conf));
 | 
				
			||||||
 | 
							console.log('Well done.');
 | 
				
			||||||
 | 
						} catch (e) {
 | 
				
			||||||
 | 
							console.error(e);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										14
									
								
								jsconfig.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,14 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Please visit https://go.microsoft.com/fwlink/?LinkId=759670 for more information about jsconfig.json
 | 
				
			||||||
 | 
						"compilerOptions": {
 | 
				
			||||||
 | 
							"target": "es6",
 | 
				
			||||||
 | 
							"module": "commonjs",
 | 
				
			||||||
 | 
							"allowSyntheticDefaultImports": true
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"exclude": [
 | 
				
			||||||
 | 
							"node_modules",
 | 
				
			||||||
 | 
							"jspm_packages",
 | 
				
			||||||
 | 
							"tmp",
 | 
				
			||||||
 | 
							"temp"
 | 
				
			||||||
 | 
						]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										135
									
								
								package.json
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,135 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "private": true,
 | 
				
			||||||
 | 
					  "name": "misskey",
 | 
				
			||||||
 | 
					  "version": "0.0.0",
 | 
				
			||||||
 | 
					  "description": "A miniblog-based SNS",
 | 
				
			||||||
 | 
					  "author": "syuilo <i@syuilo.com>",
 | 
				
			||||||
 | 
					  "license": "MIT",
 | 
				
			||||||
 | 
					  "repository": "https://github.com/syuilo/misskey.git",
 | 
				
			||||||
 | 
					  "bugs": "https://github.com/syuilo/misskey/issues",
 | 
				
			||||||
 | 
					  "main": "./built/index.js",
 | 
				
			||||||
 | 
					  "scripts": {
 | 
				
			||||||
 | 
					    "config": "node ./init.js",
 | 
				
			||||||
 | 
					    "start": "node ./built/index.js",
 | 
				
			||||||
 | 
					    "build": "gulp build",
 | 
				
			||||||
 | 
					    "rebuild": "gulp rebuild",
 | 
				
			||||||
 | 
					    "clean": "gulp clean",
 | 
				
			||||||
 | 
					    "cleanall": "gulp cleanall",
 | 
				
			||||||
 | 
					    "lint": "gulp lint",
 | 
				
			||||||
 | 
					    "test": "gulp test"
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  "dependencies": {
 | 
				
			||||||
 | 
					    "@types/bcrypt": "0.0.30",
 | 
				
			||||||
 | 
					    "@types/body-parser": "0.0.33",
 | 
				
			||||||
 | 
					    "@types/browserify": "12.0.30",
 | 
				
			||||||
 | 
					    "@types/chalk": "0.4.31",
 | 
				
			||||||
 | 
					    "@types/compression": "0.0.33",
 | 
				
			||||||
 | 
					    "@types/cors": "0.0.33",
 | 
				
			||||||
 | 
					    "@types/elasticsearch": "5.0.0",
 | 
				
			||||||
 | 
					    "@types/event-stream": "3.3.30",
 | 
				
			||||||
 | 
					    "@types/express": "4.0.34",
 | 
				
			||||||
 | 
					    "@types/glob": "5.0.30",
 | 
				
			||||||
 | 
					    "@types/gm": "1.17.29",
 | 
				
			||||||
 | 
					    "@types/gulp": "3.8.32",
 | 
				
			||||||
 | 
					    "@types/gulp-babel": "6.1.29",
 | 
				
			||||||
 | 
					    "@types/gulp-tslint": "3.6.30",
 | 
				
			||||||
 | 
					    "@types/gulp-typescript": "0.0.32",
 | 
				
			||||||
 | 
					    "@types/gulp-uglify": "0.0.29",
 | 
				
			||||||
 | 
					    "@types/gulp-util": "3.0.30",
 | 
				
			||||||
 | 
					    "@types/inquirer": "0.0.31",
 | 
				
			||||||
 | 
					    "@types/js-yaml": "3.5.28",
 | 
				
			||||||
 | 
					    "@types/mongodb": "2.1.34",
 | 
				
			||||||
 | 
					    "@types/ms": "0.7.29",
 | 
				
			||||||
 | 
					    "@types/multer": "0.0.32",
 | 
				
			||||||
 | 
					    "@types/ratelimiter": "2.1.28",
 | 
				
			||||||
 | 
					    "@types/redis": "0.12.32",
 | 
				
			||||||
 | 
					    "@types/request": "0.0.33",
 | 
				
			||||||
 | 
					    "@types/rimraf": "0.0.28",
 | 
				
			||||||
 | 
					    "@types/serve-favicon": "2.2.28",
 | 
				
			||||||
 | 
					    "@types/shelljs": "0.3.32",
 | 
				
			||||||
 | 
					    "@types/uuid": "2.0.29",
 | 
				
			||||||
 | 
					    "@types/vinyl-buffer": "0.0.28",
 | 
				
			||||||
 | 
					    "@types/vinyl-source-stream": "0.0.28",
 | 
				
			||||||
 | 
					    "@types/websocket": "0.0.32",
 | 
				
			||||||
 | 
					    "accesses": "1.2.0",
 | 
				
			||||||
 | 
					    "aliasify": "2.1.0",
 | 
				
			||||||
 | 
					    "argv": "0.0.2",
 | 
				
			||||||
 | 
					    "babel-core": "6.20.0",
 | 
				
			||||||
 | 
					    "babel-polyfill": "6.20.0",
 | 
				
			||||||
 | 
					    "babel-preset-es2015": "6.18.0",
 | 
				
			||||||
 | 
					    "babel-preset-stage-3": "6.17.0",
 | 
				
			||||||
 | 
					    "bcrypt": "1.0.1",
 | 
				
			||||||
 | 
					    "body-parser": "1.15.2",
 | 
				
			||||||
 | 
					    "browserify": "13.1.1",
 | 
				
			||||||
 | 
					    "browserify-livescript": "0.2.3",
 | 
				
			||||||
 | 
					    "chalk": "1.1.3",
 | 
				
			||||||
 | 
					    "chart.js": "2.4.0",
 | 
				
			||||||
 | 
					    "compression": "1.6.2",
 | 
				
			||||||
 | 
					    "cors": "2.8.1",
 | 
				
			||||||
 | 
					    "cropperjs": "1.0.0-alpha",
 | 
				
			||||||
 | 
					    "deepcopy": "0.6.3",
 | 
				
			||||||
 | 
					    "del": "2.2.2",
 | 
				
			||||||
 | 
					    "elasticsearch": "12.1.2",
 | 
				
			||||||
 | 
					    "escape-regexp": "0.0.1",
 | 
				
			||||||
 | 
					    "event-stream": "3.3.4",
 | 
				
			||||||
 | 
					    "express": "4.14.0",
 | 
				
			||||||
 | 
					    "file-type": "4.0.0",
 | 
				
			||||||
 | 
					    "fuckadblock": "3.2.1",
 | 
				
			||||||
 | 
					    "git-last-commit": "0.2.0",
 | 
				
			||||||
 | 
					    "glob": "7.1.1",
 | 
				
			||||||
 | 
					    "gm": "1.23.0",
 | 
				
			||||||
 | 
					    "gulp": "3.9.1",
 | 
				
			||||||
 | 
					    "gulp-babel": "6.1.2",
 | 
				
			||||||
 | 
					    "gulp-cssnano": "2.1.2",
 | 
				
			||||||
 | 
					    "gulp-livescript": "3.0.1",
 | 
				
			||||||
 | 
					    "gulp-pug": "3.2.0",
 | 
				
			||||||
 | 
					    "gulp-replace": "0.5.4",
 | 
				
			||||||
 | 
					    "gulp-stylus": "2.6.0",
 | 
				
			||||||
 | 
					    "gulp-tslint": "7.0.1",
 | 
				
			||||||
 | 
					    "gulp-typescript": "3.1.3",
 | 
				
			||||||
 | 
					    "gulp-uglify": "2.0.0",
 | 
				
			||||||
 | 
					    "gulp-util": "3.0.7",
 | 
				
			||||||
 | 
					    "inquirer": "2.0.0",
 | 
				
			||||||
 | 
					    "js-yaml": "3.7.0",
 | 
				
			||||||
 | 
					    "livescript": "1.5.0",
 | 
				
			||||||
 | 
					    "log-cool": "1.1.0",
 | 
				
			||||||
 | 
					    "mime-types": "2.1.13",
 | 
				
			||||||
 | 
					    "mongodb": "2.2.16",
 | 
				
			||||||
 | 
					    "ms": "0.7.2",
 | 
				
			||||||
 | 
					    "multer": "1.2.0",
 | 
				
			||||||
 | 
					    "nprogress": "0.2.0",
 | 
				
			||||||
 | 
					    "page": "1.7.1",
 | 
				
			||||||
 | 
					    "prominence": "0.2.0",
 | 
				
			||||||
 | 
					    "pug": "2.0.0-beta6",
 | 
				
			||||||
 | 
					    "ratelimiter": "2.1.3",
 | 
				
			||||||
 | 
					    "recaptcha-promise": "0.1.2",
 | 
				
			||||||
 | 
					    "reconnecting-websocket": "3.0.3",
 | 
				
			||||||
 | 
					    "redis": "2.6.3",
 | 
				
			||||||
 | 
					    "request": "2.79.0",
 | 
				
			||||||
 | 
					    "rimraf": "2.5.4",
 | 
				
			||||||
 | 
					    "riot": "3.0.5",
 | 
				
			||||||
 | 
					    "riot-compiler": "3.1.1",
 | 
				
			||||||
 | 
					    "riotify": "2.0.0",
 | 
				
			||||||
 | 
					    "rndstr": "1.0.0",
 | 
				
			||||||
 | 
					    "serve-favicon": "2.3.2",
 | 
				
			||||||
 | 
					    "shelljs": "0.7.5",
 | 
				
			||||||
 | 
					    "sortablejs": "1.5.0-rc1",
 | 
				
			||||||
 | 
					    "subdomain": "1.2.0",
 | 
				
			||||||
 | 
					    "summaly": "1.2.7",
 | 
				
			||||||
 | 
					    "syuilo-password-strength": "0.0.1",
 | 
				
			||||||
 | 
					    "syuilo-transformify": "0.1.2",
 | 
				
			||||||
 | 
					    "tcp-port-used": "0.1.2",
 | 
				
			||||||
 | 
					    "textarea-caret": "3.0.2",
 | 
				
			||||||
 | 
					    "tslint": "4.0.2",
 | 
				
			||||||
 | 
					    "typescript": "2.1.4",
 | 
				
			||||||
 | 
					    "uuid": "3.0.1",
 | 
				
			||||||
 | 
					    "velocity-animate": "1.4.0",
 | 
				
			||||||
 | 
					    "vhost": "3.0.2",
 | 
				
			||||||
 | 
					    "vinyl-buffer": "1.0.0",
 | 
				
			||||||
 | 
					    "vinyl-source-stream": "1.1.0",
 | 
				
			||||||
 | 
					    "websocket": "1.0.23",
 | 
				
			||||||
 | 
					    "whatwg-fetch": "2.0.1",
 | 
				
			||||||
 | 
					    "xml2json": "0.10.0",
 | 
				
			||||||
 | 
					    "yargs": "6.5.0"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								resources/apple-touch-icon.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.2 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								resources/favicon.ico
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 352 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								resources/favicon/128.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 1.4 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								resources/favicon/16.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 323 B  | 
							
								
								
									
										
											BIN
										
									
								
								resources/favicon/256.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.7 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								resources/favicon/32.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 532 B  | 
							
								
								
									
										
											BIN
										
									
								
								resources/favicon/64.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 930 B  | 
							
								
								
									
										1794
									
								
								resources/icon.ai
									
										
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								resources/icon.png
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 2.7 KiB  | 
							
								
								
									
										21
									
								
								resources/icon.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,21 @@
 | 
				
			||||||
 | 
					<?xml version="1.0" encoding="utf-8"?>
 | 
				
			||||||
 | 
					<!-- Generator: Adobe Illustrator 16.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
 | 
				
			||||||
 | 
					<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 | 
				
			||||||
 | 
					<svg version="1.1" id="Layer" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
 | 
				
			||||||
 | 
						 width="256px" height="256px" viewBox="0 0 256 256" enable-background="new 0 0 256 256" xml:space="preserve">
 | 
				
			||||||
 | 
					<path fill="#EC6B43" d="M128,32c-44.183,0-80,35.817-80,80c0,31.234,16,56,44.002,71.462C111.037,193.973,112,224,128,224
 | 
				
			||||||
 | 
						s16.964-30.025,36-40.538C192,168,208,143.233,208,112C208,67.817,172.183,32,128,32z M128,132c-11.046,0-20-8.954-20-20
 | 
				
			||||||
 | 
						s8.954-20,20-20s20,8.954,20,20S139.046,132,128,132z"/>
 | 
				
			||||||
 | 
					<g>
 | 
				
			||||||
 | 
					</g>
 | 
				
			||||||
 | 
					<g>
 | 
				
			||||||
 | 
					</g>
 | 
				
			||||||
 | 
					<g>
 | 
				
			||||||
 | 
					</g>
 | 
				
			||||||
 | 
					<g>
 | 
				
			||||||
 | 
					</g>
 | 
				
			||||||
 | 
					<g>
 | 
				
			||||||
 | 
					</g>
 | 
				
			||||||
 | 
					<g>
 | 
				
			||||||
 | 
					</g>
 | 
				
			||||||
 | 
					</svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 843 B  | 
							
								
								
									
										7
									
								
								resources/logo.svg
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					<?xml version="1.0" encoding="utf-8"?>
 | 
				
			||||||
 | 
					<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 | 
				
			||||||
 | 
					<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
 | 
				
			||||||
 | 
						 width="1024px" height="1024px" viewBox="0 0 1024 1024" enable-background="new 0 0 1024 1024" xml:space="preserve">
 | 
				
			||||||
 | 
					<polyline fill="none" stroke="#000" stroke-width="34" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
 | 
				
			||||||
 | 
						896.5,608.5 800.5,416.5 704.5,608.5 608.5,416.5 512.5,608.5 416.5,416.5 320.5,608.5 224.5,416.5 128.5,608.5 "/>
 | 
				
			||||||
 | 
					</svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 628 B  | 
							
								
								
									
										55
									
								
								src/api/api-handler.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,55 @@
 | 
				
			||||||
 | 
					import * as express from 'express';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { IEndpoint } from './endpoints';
 | 
				
			||||||
 | 
					import authenticate from './authenticate';
 | 
				
			||||||
 | 
					import { IAuthContext } from './authenticate';
 | 
				
			||||||
 | 
					import _reply from './reply';
 | 
				
			||||||
 | 
					import limitter from './limitter';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default async (endpoint: IEndpoint, req: express.Request, res: express.Response) => {
 | 
				
			||||||
 | 
						const reply = _reply.bind(null, res);
 | 
				
			||||||
 | 
						let ctx: IAuthContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Authetication
 | 
				
			||||||
 | 
						try {
 | 
				
			||||||
 | 
							ctx = await authenticate(req);
 | 
				
			||||||
 | 
						} catch (e) {
 | 
				
			||||||
 | 
							return reply(403, 'AUTHENTICATION_FAILED');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (endpoint.secure && !ctx.isSecure) {
 | 
				
			||||||
 | 
							return reply(403, 'ACCESS_DENIED');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (endpoint.shouldBeSignin && ctx.user == null) {
 | 
				
			||||||
 | 
							return reply(401, 'PLZ_SIGNIN');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (ctx.app && endpoint.kind) {
 | 
				
			||||||
 | 
							if (!ctx.app.permission.some((p: any) => p === endpoint.kind)) {
 | 
				
			||||||
 | 
								return reply(403, 'ACCESS_DENIED');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (endpoint.shouldBeSignin) {
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								await limitter(endpoint, ctx); // Rate limit
 | 
				
			||||||
 | 
							} catch (e) {
 | 
				
			||||||
 | 
								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);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										61
									
								
								src/api/authenticate.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,61 @@
 | 
				
			||||||
 | 
					import * as express from 'express';
 | 
				
			||||||
 | 
					import App from './models/app';
 | 
				
			||||||
 | 
					import User from './models/user';
 | 
				
			||||||
 | 
					import Userkey from './models/userkey';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IAuthContext {
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * App which requested
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						app: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Authenticated user
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						user: any;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Weather if the request is via the (Misskey Web Client or user direct) or not
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						isSecure: boolean;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default (req: express.Request) =>
 | 
				
			||||||
 | 
						new Promise<IAuthContext>(async (resolve, reject) => {
 | 
				
			||||||
 | 
						const token = req.body['i'];
 | 
				
			||||||
 | 
						if (token) {
 | 
				
			||||||
 | 
							const user = await User
 | 
				
			||||||
 | 
								.findOne({ token: token });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (user === null) {
 | 
				
			||||||
 | 
								return reject('user not found');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return resolve({
 | 
				
			||||||
 | 
								app: null,
 | 
				
			||||||
 | 
								user: user,
 | 
				
			||||||
 | 
								isSecure: true
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const userkey = req.headers['userkey'] || req.body['_userkey'];
 | 
				
			||||||
 | 
						if (userkey) {
 | 
				
			||||||
 | 
							const userkeyDoc = await Userkey.findOne({
 | 
				
			||||||
 | 
								key: userkey
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (userkeyDoc === null) {
 | 
				
			||||||
 | 
								return reject('invalid userkey');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const app = await App
 | 
				
			||||||
 | 
								.findOne({ _id: userkeyDoc.app_id });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const user = await User
 | 
				
			||||||
 | 
								.findOne({ _id: userkeyDoc.user_id });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return resolve({ app: app, user: user, isSecure: false });
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return resolve({ app: null, user: null, isSecure: false });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										149
									
								
								src/api/common/add-file-to-drive.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,149 @@
 | 
				
			||||||
 | 
					import * as mongodb from 'mongodb';
 | 
				
			||||||
 | 
					import * as crypto from 'crypto';
 | 
				
			||||||
 | 
					import * as gm from 'gm';
 | 
				
			||||||
 | 
					const fileType = require('file-type');
 | 
				
			||||||
 | 
					const prominence = require('prominence');
 | 
				
			||||||
 | 
					import DriveFile from '../models/drive-file';
 | 
				
			||||||
 | 
					import DriveFolder from '../models/drive-folder';
 | 
				
			||||||
 | 
					import serialize from '../serializers/drive-file';
 | 
				
			||||||
 | 
					import event from '../event';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Add file to drive
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param user User who wish to add file
 | 
				
			||||||
 | 
					 * @param fileName File name
 | 
				
			||||||
 | 
					 * @param data Contents
 | 
				
			||||||
 | 
					 * @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,
 | 
				
			||||||
 | 
						data: Buffer,
 | 
				
			||||||
 | 
						name: string = null,
 | 
				
			||||||
 | 
						comment: string = null,
 | 
				
			||||||
 | 
						folderId: mongodb.ObjectID = null,
 | 
				
			||||||
 | 
						force: boolean = false
 | 
				
			||||||
 | 
					) => new Promise<any>(async (resolve, reject) => {
 | 
				
			||||||
 | 
						// File size
 | 
				
			||||||
 | 
						const size = data.byteLength;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// File type
 | 
				
			||||||
 | 
						let mime = 'application/octet-stream';
 | 
				
			||||||
 | 
						const type = fileType(data);
 | 
				
			||||||
 | 
						if (type !== null) {
 | 
				
			||||||
 | 
							mime = type.mime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (name === null) {
 | 
				
			||||||
 | 
								name = `untitled.${type.ext}`;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							if (name === null) {
 | 
				
			||||||
 | 
								name = 'untitled';
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Generate hash
 | 
				
			||||||
 | 
						const hash = crypto
 | 
				
			||||||
 | 
							.createHash('sha256')
 | 
				
			||||||
 | 
							.update(data)
 | 
				
			||||||
 | 
							.digest('hex') as string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!force) {
 | 
				
			||||||
 | 
							// Check if there is a file with the same hash and same data size (to be safe)
 | 
				
			||||||
 | 
							const much = await DriveFile.findOne({
 | 
				
			||||||
 | 
								user_id: user._id,
 | 
				
			||||||
 | 
								hash: hash,
 | 
				
			||||||
 | 
								datasize: size
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (much !== null) {
 | 
				
			||||||
 | 
								resolve(much);
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Fetch all files to calculate drive usage
 | 
				
			||||||
 | 
						const files = await DriveFile
 | 
				
			||||||
 | 
							.find({ user_id: user._id }, {
 | 
				
			||||||
 | 
								datasize: true,
 | 
				
			||||||
 | 
								_id: false
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Calculate drive usage (in byte)
 | 
				
			||||||
 | 
						const usage = files.map(file => file.datasize).reduce((x, y) => x + y, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If usage limit exceeded
 | 
				
			||||||
 | 
						if (usage + size > user.drive_capacity) {
 | 
				
			||||||
 | 
							return reject('no-free-space');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If the folder is specified
 | 
				
			||||||
 | 
						let folder: any = null;
 | 
				
			||||||
 | 
						if (folderId !== null) {
 | 
				
			||||||
 | 
							folder = await DriveFolder
 | 
				
			||||||
 | 
								.findOne({
 | 
				
			||||||
 | 
									_id: folderId,
 | 
				
			||||||
 | 
									user_id: user._id
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (folder === null) {
 | 
				
			||||||
 | 
								return reject('folder-not-found');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let properties: any = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If the file is an image
 | 
				
			||||||
 | 
						if (/^image\/.*$/.test(mime)) {
 | 
				
			||||||
 | 
							// Calculate width and height to save in property
 | 
				
			||||||
 | 
							const g = gm(data, name);
 | 
				
			||||||
 | 
							const size = await prominence(g).size();
 | 
				
			||||||
 | 
							properties = {
 | 
				
			||||||
 | 
								width: size.width,
 | 
				
			||||||
 | 
								height: size.height
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Create DriveFile document
 | 
				
			||||||
 | 
						const res = await DriveFile.insert({
 | 
				
			||||||
 | 
							created_at: new Date(),
 | 
				
			||||||
 | 
							user_id: user._id,
 | 
				
			||||||
 | 
							folder_id: folder !== null ? folder._id : null,
 | 
				
			||||||
 | 
							data: data,
 | 
				
			||||||
 | 
							datasize: size,
 | 
				
			||||||
 | 
							type: mime,
 | 
				
			||||||
 | 
							name: name,
 | 
				
			||||||
 | 
							comment: comment,
 | 
				
			||||||
 | 
							hash: hash,
 | 
				
			||||||
 | 
							properties: properties
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const file = res.ops[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						resolve(file);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						const fileObj = await serialize(file);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Publish drive_file_created event
 | 
				
			||||||
 | 
						event(user._id, 'drive_file_created', fileObj);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 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()
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										25
									
								
								src/api/common/get-friends.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,25 @@
 | 
				
			||||||
 | 
					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 }
 | 
				
			||||||
 | 
							}, {
 | 
				
			||||||
 | 
								followee_id: true
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// ID list of other users who the I follows
 | 
				
			||||||
 | 
						const myfollowingIds = myfollowing.map(follow => follow.followee_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (includeMe) {
 | 
				
			||||||
 | 
							myfollowingIds.push(me);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return myfollowingIds;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										32
									
								
								src/api/common/notify.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,32 @@
 | 
				
			||||||
 | 
					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 res = await Notification.insert(Object.assign({
 | 
				
			||||||
 | 
							created_at: new Date(),
 | 
				
			||||||
 | 
							notifiee_id: notifiee,
 | 
				
			||||||
 | 
							notifier_id: notifier,
 | 
				
			||||||
 | 
							type: type,
 | 
				
			||||||
 | 
							is_read: false
 | 
				
			||||||
 | 
						}, content));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const notification = res.ops[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						resolve(notification);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Publish notification event
 | 
				
			||||||
 | 
						event(notifiee, 'notification',
 | 
				
			||||||
 | 
							await serialize(notification));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										101
									
								
								src/api/endpoints.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,101 @@
 | 
				
			||||||
 | 
					const second = 1000;
 | 
				
			||||||
 | 
					const minute = 60 * second;
 | 
				
			||||||
 | 
					const hour = 60 * minute;
 | 
				
			||||||
 | 
					const day = 24 * hour;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IEndpoint {
 | 
				
			||||||
 | 
						name: string;
 | 
				
			||||||
 | 
						shouldBeSignin: boolean;
 | 
				
			||||||
 | 
						limitKey?: string;
 | 
				
			||||||
 | 
						limitDuration?: number;
 | 
				
			||||||
 | 
						limitMax?: number;
 | 
				
			||||||
 | 
						minInterval?: number;
 | 
				
			||||||
 | 
						withFile?: boolean;
 | 
				
			||||||
 | 
						secure?: boolean;
 | 
				
			||||||
 | 
						kind?: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default [
 | 
				
			||||||
 | 
						{ name: 'meta',   shouldBeSignin: false },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{ name: 'username/available', shouldBeSignin: false },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{ name: 'my/apps', shouldBeSignin: true },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{ name: 'app/create',            shouldBeSignin: true, limitDuration: day, limitMax: 3 },
 | 
				
			||||||
 | 
						{ name: 'app/show',              shouldBeSignin: false },
 | 
				
			||||||
 | 
						{ name: 'app/name_id/available', shouldBeSignin: false },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{ name: 'auth/session/generate', shouldBeSignin: false },
 | 
				
			||||||
 | 
						{ name: 'auth/session/show',     shouldBeSignin: false },
 | 
				
			||||||
 | 
						{ name: 'auth/session/userkey',  shouldBeSignin: false },
 | 
				
			||||||
 | 
						{ name: 'auth/accept',           shouldBeSignin: true, secure: true },
 | 
				
			||||||
 | 
						{ name: 'auth/deny',             shouldBeSignin: true, secure: true },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{ name: 'aggregation/users/post',      shouldBeSignin: false },
 | 
				
			||||||
 | 
						{ name: 'aggregation/users/like',      shouldBeSignin: false },
 | 
				
			||||||
 | 
						{ name: 'aggregation/users/followers', shouldBeSignin: false },
 | 
				
			||||||
 | 
						{ name: 'aggregation/users/following', shouldBeSignin: false },
 | 
				
			||||||
 | 
						{ name: 'aggregation/posts/like',      shouldBeSignin: false },
 | 
				
			||||||
 | 
						{ name: 'aggregation/posts/likes',     shouldBeSignin: false },
 | 
				
			||||||
 | 
						{ name: 'aggregation/posts/repost',    shouldBeSignin: false },
 | 
				
			||||||
 | 
						{ name: 'aggregation/posts/reply',     shouldBeSignin: false },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{ name: 'i',                shouldBeSignin: true },
 | 
				
			||||||
 | 
						{ name: 'i/update',         shouldBeSignin: true, limitDuration: day, limitMax: 50, kind: 'account-write' },
 | 
				
			||||||
 | 
						{ name: 'i/appdata/get',    shouldBeSignin: true },
 | 
				
			||||||
 | 
						{ name: 'i/appdata/set',    shouldBeSignin: true },
 | 
				
			||||||
 | 
						{ name: 'i/signin_history', shouldBeSignin: true, kind: 'account-read' },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{ name: 'i/notifications',                shouldBeSignin: true, kind: 'notification-read' },
 | 
				
			||||||
 | 
						{ name: 'notifications/delete',           shouldBeSignin: true, kind: 'notification-write' },
 | 
				
			||||||
 | 
						{ name: 'notifications/delete_all',       shouldBeSignin: true, kind: 'notification-write' },
 | 
				
			||||||
 | 
						{ name: 'notifications/mark_as_read',     shouldBeSignin: true, kind: 'notification-write' },
 | 
				
			||||||
 | 
						{ name: 'notifications/mark_as_read_all', shouldBeSignin: true, kind: 'notification-write' },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{ name: 'drive',                shouldBeSignin: true, kind: 'drive-read' },
 | 
				
			||||||
 | 
						{ name: 'drive/stream',         shouldBeSignin: true, kind: 'drive-read' },
 | 
				
			||||||
 | 
						{ name: 'drive/files',          shouldBeSignin: true, kind: 'drive-read' },
 | 
				
			||||||
 | 
						{ name: 'drive/files/create',   shouldBeSignin: true, limitDuration: hour, limitMax: 100, withFile: true, kind: 'drive-write' },
 | 
				
			||||||
 | 
						{ name: 'drive/files/show',     shouldBeSignin: true, kind: 'drive-read' },
 | 
				
			||||||
 | 
						{ name: 'drive/files/find',     shouldBeSignin: true, kind: 'drive-read' },
 | 
				
			||||||
 | 
						{ name: 'drive/files/delete',   shouldBeSignin: true, kind: 'drive-write' },
 | 
				
			||||||
 | 
						{ name: 'drive/files/update',   shouldBeSignin: true, kind: 'drive-write' },
 | 
				
			||||||
 | 
						{ name: 'drive/folders',        shouldBeSignin: true, kind: 'drive-read' },
 | 
				
			||||||
 | 
						{ name: 'drive/folders/create', shouldBeSignin: true, limitDuration: hour, limitMax: 50, kind: 'drive-write' },
 | 
				
			||||||
 | 
						{ name: 'drive/folders/show',   shouldBeSignin: true, kind: 'drive-read' },
 | 
				
			||||||
 | 
						{ name: 'drive/folders/find',   shouldBeSignin: true, kind: 'drive-read' },
 | 
				
			||||||
 | 
						{ name: 'drive/folders/update', shouldBeSignin: true, kind: 'drive-write' },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{ name: 'users',                    shouldBeSignin: false },
 | 
				
			||||||
 | 
						{ name: 'users/show',               shouldBeSignin: false },
 | 
				
			||||||
 | 
						{ name: 'users/search',             shouldBeSignin: false },
 | 
				
			||||||
 | 
						{ name: 'users/search_by_username', shouldBeSignin: false },
 | 
				
			||||||
 | 
						{ name: 'users/posts',              shouldBeSignin: false },
 | 
				
			||||||
 | 
						{ name: 'users/following',          shouldBeSignin: false },
 | 
				
			||||||
 | 
						{ name: 'users/followers',          shouldBeSignin: false },
 | 
				
			||||||
 | 
						{ name: 'users/recommendation',     shouldBeSignin: true, kind: 'account-read' },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{ name: 'following/create', shouldBeSignin: true, limitDuration: hour, limitMax: 100, kind: 'following-write' },
 | 
				
			||||||
 | 
						{ name: 'following/delete', shouldBeSignin: true, limitDuration: hour, limitMax: 100, kind: 'following-write' },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{ name: 'posts/show',             shouldBeSignin: false },
 | 
				
			||||||
 | 
						{ name: 'posts/replies',          shouldBeSignin: false },
 | 
				
			||||||
 | 
						{ name: 'posts/context',          shouldBeSignin: false },
 | 
				
			||||||
 | 
						{ name: 'posts/create',           shouldBeSignin: true, limitDuration: hour, limitMax: 120, minInterval: 1 * second, kind: 'post-write' },
 | 
				
			||||||
 | 
						{ name: 'posts/reposts',          shouldBeSignin: false },
 | 
				
			||||||
 | 
						{ name: 'posts/search',           shouldBeSignin: false },
 | 
				
			||||||
 | 
						{ name: 'posts/timeline',         shouldBeSignin: true, limitDuration: 10 * minute, limitMax: 100 },
 | 
				
			||||||
 | 
						{ name: 'posts/mentions',         shouldBeSignin: true, limitDuration: 10 * minute, limitMax: 100 },
 | 
				
			||||||
 | 
						{ name: 'posts/likes',            shouldBeSignin: true },
 | 
				
			||||||
 | 
						{ name: 'posts/likes/create',     shouldBeSignin: true, limitDuration: hour, limitMax: 100, kind: 'like-write' },
 | 
				
			||||||
 | 
						{ name: 'posts/likes/delete',     shouldBeSignin: true, limitDuration: hour, limitMax: 100, kind: 'like-write' },
 | 
				
			||||||
 | 
						{ name: 'posts/favorites/create', shouldBeSignin: true, limitDuration: hour, limitMax: 100, kind: 'favorite-write' },
 | 
				
			||||||
 | 
						{ name: 'posts/favorites/delete', shouldBeSignin: true, limitDuration: hour, limitMax: 100, kind: 'favorite-write' },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{ name: 'messaging/history',         shouldBeSignin: true, kind: 'messaging-read' },
 | 
				
			||||||
 | 
						{ name: 'messaging/unread',          shouldBeSignin: true, kind: 'messaging-read' },
 | 
				
			||||||
 | 
						{ name: 'messaging/messages',        shouldBeSignin: true, kind: 'messaging-read' },
 | 
				
			||||||
 | 
						{ name: 'messaging/messages/create', shouldBeSignin: true, kind: 'messaging-write' }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					] as IEndpoint[];
 | 
				
			||||||
							
								
								
									
										83
									
								
								src/api/endpoints/aggregation/posts/like.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,83 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import Post from '../../../models/post';
 | 
				
			||||||
 | 
					import Like from '../../../models/like';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Aggregate like of a post
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'post_id' parameter
 | 
				
			||||||
 | 
						const postId = params.post_id;
 | 
				
			||||||
 | 
						if (postId === undefined || postId === null) {
 | 
				
			||||||
 | 
							return rej('post_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Lookup post
 | 
				
			||||||
 | 
						const post = await Post.findOne({
 | 
				
			||||||
 | 
							_id: new mongo.ObjectID(postId)
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (post === null) {
 | 
				
			||||||
 | 
							return rej('post not found');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const datas = await Like
 | 
				
			||||||
 | 
							.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 }
 | 
				
			||||||
 | 
								}}
 | 
				
			||||||
 | 
							])
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						datas.forEach(data => {
 | 
				
			||||||
 | 
							data.date = data._id;
 | 
				
			||||||
 | 
							delete data._id;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const graph = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (let i = 0; i < 30; i++) {
 | 
				
			||||||
 | 
							let 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);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										76
									
								
								src/api/endpoints/aggregation/posts/likes.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,76 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import Post from '../../../models/post';
 | 
				
			||||||
 | 
					import Like from '../../../models/like';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Aggregate likes of a post
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'post_id' parameter
 | 
				
			||||||
 | 
						const postId = params.post_id;
 | 
				
			||||||
 | 
						if (postId === undefined || postId === null) {
 | 
				
			||||||
 | 
							return rej('post_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Lookup post
 | 
				
			||||||
 | 
						const post = await Post.findOne({
 | 
				
			||||||
 | 
							_id: new mongo.ObjectID(postId)
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (post === null) {
 | 
				
			||||||
 | 
							return rej('post not found');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const startTime = new Date(new Date().setMonth(new Date().getMonth() - 1));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const likes = await Like
 | 
				
			||||||
 | 
							.find({
 | 
				
			||||||
 | 
								post_id: post._id,
 | 
				
			||||||
 | 
								$or: [
 | 
				
			||||||
 | 
									{ deleted_at: { $exists: false } },
 | 
				
			||||||
 | 
									{ deleted_at: { $gt: startTime } }
 | 
				
			||||||
 | 
								]
 | 
				
			||||||
 | 
							}, {
 | 
				
			||||||
 | 
								_id: false,
 | 
				
			||||||
 | 
								post_id: false
 | 
				
			||||||
 | 
							}, {
 | 
				
			||||||
 | 
								sort: { created_at: -1 }
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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 = likes.filter(l =>
 | 
				
			||||||
 | 
								l.created_at < day && (l.deleted_at == null || l.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);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										82
									
								
								src/api/endpoints/aggregation/posts/reply.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,82 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import Post from '../../../models/post';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Aggregate reply of a post
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'post_id' parameter
 | 
				
			||||||
 | 
						const postId = params.post_id;
 | 
				
			||||||
 | 
						if (postId === undefined || postId === null) {
 | 
				
			||||||
 | 
							return rej('post_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Lookup post
 | 
				
			||||||
 | 
						const post = await Post.findOne({
 | 
				
			||||||
 | 
							_id: new mongo.ObjectID(postId)
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (post === null) {
 | 
				
			||||||
 | 
							return rej('post not found');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const datas = await Post
 | 
				
			||||||
 | 
							.aggregate([
 | 
				
			||||||
 | 
								{ $match: { reply_to: 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 }
 | 
				
			||||||
 | 
								}}
 | 
				
			||||||
 | 
							])
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						datas.forEach(data => {
 | 
				
			||||||
 | 
							data.date = data._id;
 | 
				
			||||||
 | 
							delete data._id;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const graph = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (let i = 0; i < 30; i++) {
 | 
				
			||||||
 | 
							let 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);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										82
									
								
								src/api/endpoints/aggregation/posts/repost.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,82 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import Post from '../../../models/post';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Aggregate repost of a post
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'post_id' parameter
 | 
				
			||||||
 | 
						const postId = params.post_id;
 | 
				
			||||||
 | 
						if (postId === undefined || postId === null) {
 | 
				
			||||||
 | 
							return rej('post_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Lookup post
 | 
				
			||||||
 | 
						const post = await Post.findOne({
 | 
				
			||||||
 | 
							_id: new mongo.ObjectID(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 }
 | 
				
			||||||
 | 
								}}
 | 
				
			||||||
 | 
							])
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						datas.forEach(data => {
 | 
				
			||||||
 | 
							data.date = data._id;
 | 
				
			||||||
 | 
							delete data._id;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const graph = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (let i = 0; i < 30; i++) {
 | 
				
			||||||
 | 
							let 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);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										77
									
								
								src/api/endpoints/aggregation/users/followers.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,77 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import User from '../../../models/user';
 | 
				
			||||||
 | 
					import Following from '../../../models/following';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Aggregate followers of a user
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'user_id' parameter
 | 
				
			||||||
 | 
						const userId = params.user_id;
 | 
				
			||||||
 | 
						if (userId === undefined || userId === null) {
 | 
				
			||||||
 | 
							return rej('user_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Lookup user
 | 
				
			||||||
 | 
						const user = await User.findOne({
 | 
				
			||||||
 | 
							_id: new mongo.ObjectID(userId)
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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 }
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										76
									
								
								src/api/endpoints/aggregation/users/following.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,76 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import User from '../../../models/user';
 | 
				
			||||||
 | 
					import Following from '../../../models/following';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Aggregate following of a user
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'user_id' parameter
 | 
				
			||||||
 | 
						const userId = params.user_id;
 | 
				
			||||||
 | 
						if (userId === undefined || userId === null) {
 | 
				
			||||||
 | 
							return rej('user_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Lookup user
 | 
				
			||||||
 | 
						const user = await User.findOne({
 | 
				
			||||||
 | 
							_id: new mongo.ObjectID(userId)
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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 }
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										83
									
								
								src/api/endpoints/aggregation/users/like.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,83 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import User from '../../../models/user';
 | 
				
			||||||
 | 
					import Like from '../../../models/like';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Aggregate like of a user
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'user_id' parameter
 | 
				
			||||||
 | 
						const userId = params.user_id;
 | 
				
			||||||
 | 
						if (userId === undefined || userId === null) {
 | 
				
			||||||
 | 
							return rej('user_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Lookup user
 | 
				
			||||||
 | 
						const user = await User.findOne({
 | 
				
			||||||
 | 
							_id: new mongo.ObjectID(userId)
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (user === null) {
 | 
				
			||||||
 | 
							return rej('user not found');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const datas = await Like
 | 
				
			||||||
 | 
							.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 }
 | 
				
			||||||
 | 
								}}
 | 
				
			||||||
 | 
							])
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						datas.forEach(data => {
 | 
				
			||||||
 | 
							data.date = data._id;
 | 
				
			||||||
 | 
							delete data._id;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const graph = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (let i = 0; i < 30; i++) {
 | 
				
			||||||
 | 
							let 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);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										113
									
								
								src/api/endpoints/aggregation/users/post.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,113 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import User from '../../../models/user';
 | 
				
			||||||
 | 
					import Post from '../../../models/post';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Aggregate post of a user
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'user_id' parameter
 | 
				
			||||||
 | 
						const userId = params.user_id;
 | 
				
			||||||
 | 
						if (userId === undefined || userId === null) {
 | 
				
			||||||
 | 
							return rej('user_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Lookup user
 | 
				
			||||||
 | 
						const user = await User.findOne({
 | 
				
			||||||
 | 
							_id: new mongo.ObjectID(userId)
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (user === null) {
 | 
				
			||||||
 | 
							return rej('user not found');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const datas = await Post
 | 
				
			||||||
 | 
							.aggregate([
 | 
				
			||||||
 | 
								{ $match: { user_id: user._id } },
 | 
				
			||||||
 | 
								{ $project: {
 | 
				
			||||||
 | 
									repost_id: '$repost_id',
 | 
				
			||||||
 | 
									reply_to_id: '$reply_to_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_to_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'
 | 
				
			||||||
 | 
									}}
 | 
				
			||||||
 | 
								} }
 | 
				
			||||||
 | 
							])
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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++) {
 | 
				
			||||||
 | 
							let 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);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										75
									
								
								src/api/endpoints/app/create.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,75 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import rndstr from 'rndstr';
 | 
				
			||||||
 | 
					import App from '../../models/app';
 | 
				
			||||||
 | 
					import serialize from '../../serializers/app';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Create an app
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = async (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'name_id' parameter
 | 
				
			||||||
 | 
						const nameId = params.name_id;
 | 
				
			||||||
 | 
						if (nameId == null || nameId == '') {
 | 
				
			||||||
 | 
							return rej('name_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Validate name_id
 | 
				
			||||||
 | 
						if (!/^[a-zA-Z0-9\-]{3,30}$/.test(nameId)) {
 | 
				
			||||||
 | 
							return rej('invalid name_id');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'name' parameter
 | 
				
			||||||
 | 
						const name = params.name;
 | 
				
			||||||
 | 
						if (name == null || name == '') {
 | 
				
			||||||
 | 
							return rej('name is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'description' parameter
 | 
				
			||||||
 | 
						const description = params.description;
 | 
				
			||||||
 | 
						if (description == null || description == '') {
 | 
				
			||||||
 | 
							return rej('description is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'permission' parameter
 | 
				
			||||||
 | 
						const permission = params.permission;
 | 
				
			||||||
 | 
						if (permission == null || permission == '') {
 | 
				
			||||||
 | 
							return rej('permission is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'callback_url' parameter
 | 
				
			||||||
 | 
						let callback = params.callback_url;
 | 
				
			||||||
 | 
						if (callback === '') {
 | 
				
			||||||
 | 
							callback = null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Generate secret
 | 
				
			||||||
 | 
						const secret = rndstr('a-zA-Z0-9', 32);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Create account
 | 
				
			||||||
 | 
						const inserted = 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.split(','),
 | 
				
			||||||
 | 
							callback_url: callback,
 | 
				
			||||||
 | 
							secret: secret
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const app = inserted.ops[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Response
 | 
				
			||||||
 | 
						res(await serialize(app));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										40
									
								
								src/api/endpoints/app/name_id/available.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,40 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import App from '../../../models/app';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Check available name_id of app
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = async (params) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'name_id' parameter
 | 
				
			||||||
 | 
						const nameId = params.name_id;
 | 
				
			||||||
 | 
						if (nameId == null || nameId == '') {
 | 
				
			||||||
 | 
							return rej('name_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Validate name_id
 | 
				
			||||||
 | 
						if (!/^[a-zA-Z0-9\-]{3,30}$/.test(nameId)) {
 | 
				
			||||||
 | 
							return rej('invalid name_id');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get exist
 | 
				
			||||||
 | 
						const exist = await App
 | 
				
			||||||
 | 
							.count({
 | 
				
			||||||
 | 
								name_id_lower: nameId.toLowerCase()
 | 
				
			||||||
 | 
							}, {
 | 
				
			||||||
 | 
								limit: 1
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Reply
 | 
				
			||||||
 | 
						res({
 | 
				
			||||||
 | 
							available: exist === 0
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										51
									
								
								src/api/endpoints/app/show.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,51 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import App from '../../models/app';
 | 
				
			||||||
 | 
					import serialize from '../../serializers/app';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Show an app
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @param {Object} _
 | 
				
			||||||
 | 
					 * @param {Object} isSecure
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user, _, isSecure) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'app_id' parameter
 | 
				
			||||||
 | 
						let appId = params.app_id;
 | 
				
			||||||
 | 
						if (appId == null || appId == '') {
 | 
				
			||||||
 | 
							appId = null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'name_id' parameter
 | 
				
			||||||
 | 
						let nameId = params.name_id;
 | 
				
			||||||
 | 
						if (nameId == null || nameId == '') {
 | 
				
			||||||
 | 
							nameId = null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (appId === null && nameId === null) {
 | 
				
			||||||
 | 
							return rej('app_id or name_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Lookup app
 | 
				
			||||||
 | 
						const app = appId !== null
 | 
				
			||||||
 | 
							? await App.findOne({ _id: new mongo.ObjectID(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)
 | 
				
			||||||
 | 
						}));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										64
									
								
								src/api/endpoints/auth/accept.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,64 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import rndstr from 'rndstr';
 | 
				
			||||||
 | 
					import AuthSess from '../../models/auth-session';
 | 
				
			||||||
 | 
					import Userkey from '../../models/userkey';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Accept
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'token' parameter
 | 
				
			||||||
 | 
						const token = params.token;
 | 
				
			||||||
 | 
						if (token == null) {
 | 
				
			||||||
 | 
							return rej('token is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Fetch token
 | 
				
			||||||
 | 
						const session = await AuthSess
 | 
				
			||||||
 | 
							.findOne({ token: token });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (session === null) {
 | 
				
			||||||
 | 
							return rej('session not found');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Generate userkey
 | 
				
			||||||
 | 
						const key = rndstr('a-zA-Z0-9', 32);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Fetch exist userkey
 | 
				
			||||||
 | 
						const exist = await Userkey.findOne({
 | 
				
			||||||
 | 
							app_id: session.app_id,
 | 
				
			||||||
 | 
							user_id: user._id,
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (exist === null) {
 | 
				
			||||||
 | 
							// Insert userkey doc
 | 
				
			||||||
 | 
							await Userkey.insert({
 | 
				
			||||||
 | 
								created_at: new Date(),
 | 
				
			||||||
 | 
								app_id: session.app_id,
 | 
				
			||||||
 | 
								user_id: user._id,
 | 
				
			||||||
 | 
								key: key
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Update session
 | 
				
			||||||
 | 
						await AuthSess.updateOne({
 | 
				
			||||||
 | 
							_id: session._id
 | 
				
			||||||
 | 
						}, {
 | 
				
			||||||
 | 
							$set: {
 | 
				
			||||||
 | 
								user_id: user._id
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Response
 | 
				
			||||||
 | 
						res();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										51
									
								
								src/api/endpoints/auth/session/generate.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,51 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as uuid from 'uuid';
 | 
				
			||||||
 | 
					import App from '../../../models/app';
 | 
				
			||||||
 | 
					import AuthSess from '../../../models/auth-session';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Generate a session
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'app_secret' parameter
 | 
				
			||||||
 | 
						const appSecret = params.app_secret;
 | 
				
			||||||
 | 
						if (appSecret == null) {
 | 
				
			||||||
 | 
							return rej('app_secret is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 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 inserted = await AuthSess.insert({
 | 
				
			||||||
 | 
							created_at: new Date(),
 | 
				
			||||||
 | 
							app_id: app._id,
 | 
				
			||||||
 | 
							token: token
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const doc = inserted.ops[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Response
 | 
				
			||||||
 | 
						res({
 | 
				
			||||||
 | 
							token: doc.token,
 | 
				
			||||||
 | 
							url: `${config.auth_url}/${doc.token}`
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										36
									
								
								src/api/endpoints/auth/session/show.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,36 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import AuthSess from '../../../models/auth-session';
 | 
				
			||||||
 | 
					import serialize from '../../../serializers/auth-session';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Show a session
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'token' parameter
 | 
				
			||||||
 | 
						const token = params.token;
 | 
				
			||||||
 | 
						if (token == null) {
 | 
				
			||||||
 | 
							return rej('token is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Lookup session
 | 
				
			||||||
 | 
						const session = await AuthSess.findOne({
 | 
				
			||||||
 | 
							token: token
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (session == null) {
 | 
				
			||||||
 | 
							return rej('session not found');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Response
 | 
				
			||||||
 | 
						res(await serialize(session, user));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										74
									
								
								src/api/endpoints/auth/session/userkey.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,74 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import App from '../../../models/app';
 | 
				
			||||||
 | 
					import AuthSess from '../../../models/auth-session';
 | 
				
			||||||
 | 
					import Userkey from '../../../models/userkey';
 | 
				
			||||||
 | 
					import serialize from '../../../serializers/user';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Generate a session
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'app_secret' parameter
 | 
				
			||||||
 | 
						const appSecret = params.app_secret;
 | 
				
			||||||
 | 
						if (appSecret == null) {
 | 
				
			||||||
 | 
							return rej('app_secret is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Lookup app
 | 
				
			||||||
 | 
						const app = await App.findOne({
 | 
				
			||||||
 | 
							secret: appSecret
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (app == null) {
 | 
				
			||||||
 | 
							return rej('app not found');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'token' parameter
 | 
				
			||||||
 | 
						const token = params.token;
 | 
				
			||||||
 | 
						if (token == null) {
 | 
				
			||||||
 | 
							return rej('token is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 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 userkey
 | 
				
			||||||
 | 
						const userkey = await Userkey.findOne({
 | 
				
			||||||
 | 
							app_id: app._id,
 | 
				
			||||||
 | 
							user_id: session.user_id
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Delete session
 | 
				
			||||||
 | 
						AuthSess.deleteOne({
 | 
				
			||||||
 | 
							_id: session._id
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Response
 | 
				
			||||||
 | 
						res({
 | 
				
			||||||
 | 
							userkey: userkey.key,
 | 
				
			||||||
 | 
							user: await serialize(session.user_id, null, {
 | 
				
			||||||
 | 
								detail: true
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										33
									
								
								src/api/endpoints/drive.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,33 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import DriveFile from './models/drive-file';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get drive information
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Fetch all files to calculate drive usage
 | 
				
			||||||
 | 
						const files = await DriveFile
 | 
				
			||||||
 | 
							.find({ user_id: user._id }, {
 | 
				
			||||||
 | 
								datasize: true,
 | 
				
			||||||
 | 
								_id: false
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Calculate drive usage (in byte)
 | 
				
			||||||
 | 
						const usage = files.map(file => file.datasize).reduce((x, y) => x + y, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res({
 | 
				
			||||||
 | 
							capacity: user.drive_capacity,
 | 
				
			||||||
 | 
							usage: usage
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										82
									
								
								src/api/endpoints/drive/files.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,82 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import DriveFile from '../../models/drive-file';
 | 
				
			||||||
 | 
					import serialize from '../../serializers/drive-file';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get drive files
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @param {Object} app
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user, app) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'limit' parameter
 | 
				
			||||||
 | 
						let limit = params.limit;
 | 
				
			||||||
 | 
						if (limit !== undefined && limit !== null) {
 | 
				
			||||||
 | 
							limit = parseInt(limit, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// From 1 to 100
 | 
				
			||||||
 | 
							if (!(1 <= limit && limit <= 100)) {
 | 
				
			||||||
 | 
								return rej('invalid limit range');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							limit = 10;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const since = params.since_id || null;
 | 
				
			||||||
 | 
						const max = params.max_id || null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check if both of since_id and max_id is specified
 | 
				
			||||||
 | 
						if (since !== null && max !== null) {
 | 
				
			||||||
 | 
							return rej('cannot set since_id and max_id');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'folder_id' parameter
 | 
				
			||||||
 | 
						let folder = params.folder_id;
 | 
				
			||||||
 | 
						if (folder === undefined || folder === null || folder === 'null') {
 | 
				
			||||||
 | 
							folder = null;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							folder = new mongo.ObjectID(folder);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Construct query
 | 
				
			||||||
 | 
						const sort = {
 | 
				
			||||||
 | 
							_id: -1
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						const query = {
 | 
				
			||||||
 | 
							user_id: user._id,
 | 
				
			||||||
 | 
							folder_id: folder
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						if (since !== null) {
 | 
				
			||||||
 | 
							sort._id = 1;
 | 
				
			||||||
 | 
							query._id = {
 | 
				
			||||||
 | 
								$gt: new mongo.ObjectID(since)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						} else if (max !== null) {
 | 
				
			||||||
 | 
							query._id = {
 | 
				
			||||||
 | 
								$lt: new mongo.ObjectID(max)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Issue query
 | 
				
			||||||
 | 
						const files = await DriveFile
 | 
				
			||||||
 | 
							.find(query, {
 | 
				
			||||||
 | 
								data: false
 | 
				
			||||||
 | 
							}, {
 | 
				
			||||||
 | 
								limit: limit,
 | 
				
			||||||
 | 
								sort: sort
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						res(await Promise.all(files.map(async file =>
 | 
				
			||||||
 | 
							await serialize(file))));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										59
									
								
								src/api/endpoints/drive/files/create.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,59 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as fs from 'fs';
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import File from '../../../models/drive-file';
 | 
				
			||||||
 | 
					import { validateFileName } from '../../../models/drive-file';
 | 
				
			||||||
 | 
					import User from '../../../models/user';
 | 
				
			||||||
 | 
					import serialize from '../../../serializers/drive-file';
 | 
				
			||||||
 | 
					import create from '../../../common/add-file-to-drive';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Create a file
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} file
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (file, params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const buffer = fs.readFileSync(file.path);
 | 
				
			||||||
 | 
						fs.unlink(file.path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 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)) {
 | 
				
			||||||
 | 
								return rej('invalid name');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							name = null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'folder_id' parameter
 | 
				
			||||||
 | 
						let folder = params.folder_id;
 | 
				
			||||||
 | 
						if (folder === undefined || folder === null || folder === 'null') {
 | 
				
			||||||
 | 
							folder = null;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							folder = new mongo.ObjectID(folder);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Create file
 | 
				
			||||||
 | 
						const driveFile = await create(user, buffer, name, null, folder);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						const fileObj = await serialize(driveFile);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Response
 | 
				
			||||||
 | 
						res(fileObj);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										48
									
								
								src/api/endpoints/drive/files/find.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,48 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import DriveFile from '../../../models/drive-file';
 | 
				
			||||||
 | 
					import serialize from '../../../serializers/drive-file';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Find a file(s)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'name' parameter
 | 
				
			||||||
 | 
						const name = params.name;
 | 
				
			||||||
 | 
						if (name === undefined || name === null) {
 | 
				
			||||||
 | 
							return rej('name is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'folder_id' parameter
 | 
				
			||||||
 | 
						let folder = params.folder_id;
 | 
				
			||||||
 | 
						if (folder === undefined || folder === null || folder === 'null') {
 | 
				
			||||||
 | 
							folder = null;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							folder = new mongo.ObjectID(folder);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Issue query
 | 
				
			||||||
 | 
						const files = await DriveFile
 | 
				
			||||||
 | 
							.find({
 | 
				
			||||||
 | 
								name: name,
 | 
				
			||||||
 | 
								user_id: user._id,
 | 
				
			||||||
 | 
								folder_id: folder
 | 
				
			||||||
 | 
							}, {
 | 
				
			||||||
 | 
								data: false
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						res(await Promise.all(files.map(async file =>
 | 
				
			||||||
 | 
							await serialize(file))));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										40
									
								
								src/api/endpoints/drive/files/show.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,40 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import DriveFile from '../../../models/drive-file';
 | 
				
			||||||
 | 
					import serialize from '../../../serializers/drive-file';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Show a file
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'file_id' parameter
 | 
				
			||||||
 | 
						const fileId = params.file_id;
 | 
				
			||||||
 | 
						if (fileId === undefined || fileId === null) {
 | 
				
			||||||
 | 
							return rej('file_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const file = await DriveFile
 | 
				
			||||||
 | 
							.findOne({
 | 
				
			||||||
 | 
								_id: new mongo.ObjectID(fileId),
 | 
				
			||||||
 | 
								user_id: user._id
 | 
				
			||||||
 | 
							}, {
 | 
				
			||||||
 | 
								data: false
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (file === null) {
 | 
				
			||||||
 | 
							return rej('file-not-found');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						res(await serialize(file));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										89
									
								
								src/api/endpoints/drive/files/update.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,89 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					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 event from '../../../event';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Update a file
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'file_id' parameter
 | 
				
			||||||
 | 
						const fileId = params.file_id;
 | 
				
			||||||
 | 
						if (fileId === undefined || fileId === null) {
 | 
				
			||||||
 | 
							return rej('file_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const file = await DriveFile
 | 
				
			||||||
 | 
							.findOne({
 | 
				
			||||||
 | 
								_id: new mongo.ObjectID(fileId),
 | 
				
			||||||
 | 
								user_id: user._id
 | 
				
			||||||
 | 
							}, {
 | 
				
			||||||
 | 
								data: false
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (file === null) {
 | 
				
			||||||
 | 
							return rej('file-not-found');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'name' parameter
 | 
				
			||||||
 | 
						let name = params.name;
 | 
				
			||||||
 | 
						if (name) {
 | 
				
			||||||
 | 
							name = name.trim();
 | 
				
			||||||
 | 
							if (validateFileName(name)) {
 | 
				
			||||||
 | 
								file.name = name;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								return rej('invalid file name');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'folder_id' parameter
 | 
				
			||||||
 | 
						let folderId = params.folder_id;
 | 
				
			||||||
 | 
						if (folderId !== undefined && folderId !== 'null') {
 | 
				
			||||||
 | 
							folderId = new mongo.ObjectID(folderId);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let folder = null;
 | 
				
			||||||
 | 
						if (folderId !== undefined && folderId !== null) {
 | 
				
			||||||
 | 
							if (folderId === 'null') {
 | 
				
			||||||
 | 
								file.folder_id = null;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								folder = await DriveFolder
 | 
				
			||||||
 | 
									.findOne({
 | 
				
			||||||
 | 
										_id: folderId,
 | 
				
			||||||
 | 
										user_id: user._id
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (folder === null) {
 | 
				
			||||||
 | 
									return reject('folder-not-found');
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								file.folder_id = folder._id;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						DriveFile.updateOne({ _id: file._id }, {
 | 
				
			||||||
 | 
							$set: file
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						const fileObj = await serialize(file);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Response
 | 
				
			||||||
 | 
						res(fileObj);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Publish drive_file_updated event
 | 
				
			||||||
 | 
						event(user._id, 'drive_file_updated', fileObj);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										82
									
								
								src/api/endpoints/drive/folders.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,82 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import DriveFolder from '../../models/drive-folder';
 | 
				
			||||||
 | 
					import serialize from '../../serializers/drive-folder';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get drive folders
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @param {Object} app
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user, app) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'limit' parameter
 | 
				
			||||||
 | 
						let limit = params.limit;
 | 
				
			||||||
 | 
						if (limit !== undefined && limit !== null) {
 | 
				
			||||||
 | 
							limit = parseInt(limit, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// From 1 to 100
 | 
				
			||||||
 | 
							if (!(1 <= limit && limit <= 100)) {
 | 
				
			||||||
 | 
								return rej('invalid limit range');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							limit = 10;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const since = params.since_id || null;
 | 
				
			||||||
 | 
						const max = params.max_id || null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check if both of since_id and max_id is specified
 | 
				
			||||||
 | 
						if (since !== null && max !== null) {
 | 
				
			||||||
 | 
							return rej('cannot set since_id and max_id');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'folder_id' parameter
 | 
				
			||||||
 | 
						let folder = params.folder_id;
 | 
				
			||||||
 | 
						if (folder === undefined || folder === null || folder === 'null') {
 | 
				
			||||||
 | 
							folder = null;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							folder = new mongo.ObjectID(folder);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Construct query
 | 
				
			||||||
 | 
						const sort = {
 | 
				
			||||||
 | 
							created_at: -1
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						const query = {
 | 
				
			||||||
 | 
							user_id: user._id,
 | 
				
			||||||
 | 
							parent_id: folder
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						if (since !== null) {
 | 
				
			||||||
 | 
							sort.created_at = 1;
 | 
				
			||||||
 | 
							query._id = {
 | 
				
			||||||
 | 
								$gt: new mongo.ObjectID(since)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						} else if (max !== null) {
 | 
				
			||||||
 | 
							query._id = {
 | 
				
			||||||
 | 
								$lt: new mongo.ObjectID(max)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Issue query
 | 
				
			||||||
 | 
						const folders = await DriveFolder
 | 
				
			||||||
 | 
							.find(query, {
 | 
				
			||||||
 | 
								data: false
 | 
				
			||||||
 | 
							}, {
 | 
				
			||||||
 | 
								limit: limit,
 | 
				
			||||||
 | 
								sort: sort
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						res(await Promise.all(folders.map(async folder =>
 | 
				
			||||||
 | 
							await serialize(folder))));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										79
									
								
								src/api/endpoints/drive/folders/create.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,79 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import DriveFolder from '../../../models/drive-folder';
 | 
				
			||||||
 | 
					import { isValidFolderName } from '../../../models/drive-folder';
 | 
				
			||||||
 | 
					import serialize from '../../../serializers/drive-folder';
 | 
				
			||||||
 | 
					import event from '../../../event';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Create drive folder
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'name' parameter
 | 
				
			||||||
 | 
						let name = params.name;
 | 
				
			||||||
 | 
						if (name !== undefined && name !== null) {
 | 
				
			||||||
 | 
							name = name.trim();
 | 
				
			||||||
 | 
							if (name.length === 0) {
 | 
				
			||||||
 | 
								name = null;
 | 
				
			||||||
 | 
							} else if (!isValidFolderName(name)) {
 | 
				
			||||||
 | 
								return rej('invalid name');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							name = null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (name == null) {
 | 
				
			||||||
 | 
							name = '無題のフォルダー';
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'folder_id' parameter
 | 
				
			||||||
 | 
						let parentId = params.folder_id;
 | 
				
			||||||
 | 
						if (parentId === undefined || parentId === null) {
 | 
				
			||||||
 | 
							parentId = null;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							parentId = new mongo.ObjectID(parentId);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If the parent folder is specified
 | 
				
			||||||
 | 
						let parent = null;
 | 
				
			||||||
 | 
						if (parentId !== null) {
 | 
				
			||||||
 | 
							parent = await DriveFolder
 | 
				
			||||||
 | 
								.findOne({
 | 
				
			||||||
 | 
									_id: parentId,
 | 
				
			||||||
 | 
									user_id: user._id
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (parent === null) {
 | 
				
			||||||
 | 
								return reject('parent-not-found');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Create folder
 | 
				
			||||||
 | 
						const inserted = await DriveFolder.insert({
 | 
				
			||||||
 | 
							created_at: new Date(),
 | 
				
			||||||
 | 
							name: name,
 | 
				
			||||||
 | 
							parent_id: parent !== null ? parent._id : null,
 | 
				
			||||||
 | 
							user_id: user._id
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const folder = inserted.ops[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						const folderObj = await serialize(folder);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Response
 | 
				
			||||||
 | 
						res(folderObj);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Publish drive_folder_created event
 | 
				
			||||||
 | 
						event(user._id, 'drive_folder_created', folderObj);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										46
									
								
								src/api/endpoints/drive/folders/find.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,46 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import DriveFolder from '../../../models/drive-folder';
 | 
				
			||||||
 | 
					import serialize from '../../../serializers/drive-folder';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Find a folder(s)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'name' parameter
 | 
				
			||||||
 | 
						const name = params.name;
 | 
				
			||||||
 | 
						if (name === undefined || name === null) {
 | 
				
			||||||
 | 
							return rej('name is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'parent_id' parameter
 | 
				
			||||||
 | 
						let parentId = params.parent_id;
 | 
				
			||||||
 | 
						if (parentId === undefined || parentId === null || parentId === 'null') {
 | 
				
			||||||
 | 
							parentId = null;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							parentId = new mongo.ObjectID(parentId);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Issue query
 | 
				
			||||||
 | 
						const folders = await DriveFolder
 | 
				
			||||||
 | 
							.find({
 | 
				
			||||||
 | 
								name: name,
 | 
				
			||||||
 | 
								user_id: user._id,
 | 
				
			||||||
 | 
								parent_id: parentId
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						res(await Promise.all(folders.map(async folder =>
 | 
				
			||||||
 | 
							await serialize(folder))));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										41
									
								
								src/api/endpoints/drive/folders/show.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,41 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import DriveFolder from '../../../models/drive-folder';
 | 
				
			||||||
 | 
					import serialize from '../../../serializers/drive-folder';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Show a folder
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'folder_id' parameter
 | 
				
			||||||
 | 
						const folderId = params.folder_id;
 | 
				
			||||||
 | 
						if (folderId === undefined || folderId === null) {
 | 
				
			||||||
 | 
							return rej('folder_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get folder
 | 
				
			||||||
 | 
						const folder = await DriveFolder
 | 
				
			||||||
 | 
							.findOne({
 | 
				
			||||||
 | 
								_id: new mongo.ObjectID(folderId),
 | 
				
			||||||
 | 
								user_id: user._id
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (folder === null) {
 | 
				
			||||||
 | 
							return rej('folder-not-found');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						res(await serialize(folder, {
 | 
				
			||||||
 | 
							includeParent: true
 | 
				
			||||||
 | 
						}));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										114
									
								
								src/api/endpoints/drive/folders/update.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,114 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import DriveFolder from '../../../models/drive-folder';
 | 
				
			||||||
 | 
					import { isValidFolderName } from '../../../models/drive-folder';
 | 
				
			||||||
 | 
					import serialize from '../../../serializers/drive-file';
 | 
				
			||||||
 | 
					import event from '../../../event';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Update a folder
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'folder_id' parameter
 | 
				
			||||||
 | 
						const folderId = params.folder_id;
 | 
				
			||||||
 | 
						if (folderId === undefined || folderId === null) {
 | 
				
			||||||
 | 
							return rej('folder_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Fetch folder
 | 
				
			||||||
 | 
						const folder = await DriveFolder
 | 
				
			||||||
 | 
							.findOne({
 | 
				
			||||||
 | 
								_id: new mongo.ObjectID(folderId),
 | 
				
			||||||
 | 
								user_id: user._id
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (folder === null) {
 | 
				
			||||||
 | 
							return rej('folder-not-found');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'name' parameter
 | 
				
			||||||
 | 
						let name = params.name;
 | 
				
			||||||
 | 
						if (name) {
 | 
				
			||||||
 | 
							name = name.trim();
 | 
				
			||||||
 | 
							if (isValidFolderName(name)) {
 | 
				
			||||||
 | 
								folder.name = name;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								return rej('invalid folder name');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'parent_id' parameter
 | 
				
			||||||
 | 
						let parentId = params.parent_id;
 | 
				
			||||||
 | 
						if (parentId !== undefined && parentId !== 'null') {
 | 
				
			||||||
 | 
							parentId = new mongo.ObjectID(parentId);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let parent = null;
 | 
				
			||||||
 | 
						if (parentId !== undefined && parentId !== null) {
 | 
				
			||||||
 | 
							if (parentId === 'null') {
 | 
				
			||||||
 | 
								folder.parent_id = null;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								// Get parent folder
 | 
				
			||||||
 | 
								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 be occured
 | 
				
			||||||
 | 
								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.updateOne({ _id: folder._id }, {
 | 
				
			||||||
 | 
							$set: folder
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						const folderObj = await serialize(folder);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Response
 | 
				
			||||||
 | 
						res(folderObj);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Publish drive_folder_updated event
 | 
				
			||||||
 | 
						event(user._id, 'drive_folder_updated', folderObj);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										85
									
								
								src/api/endpoints/drive/stream.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,85 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import DriveFile from '../../models/drive-file';
 | 
				
			||||||
 | 
					import serialize from '../../serializers/drive-file';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get drive stream
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'limit' parameter
 | 
				
			||||||
 | 
						let limit = params.limit;
 | 
				
			||||||
 | 
						if (limit !== undefined && limit !== null) {
 | 
				
			||||||
 | 
							limit = parseInt(limit, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// From 1 to 100
 | 
				
			||||||
 | 
							if (!(1 <= limit && limit <= 100)) {
 | 
				
			||||||
 | 
								return rej('invalid limit range');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							limit = 10;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const since = params.since_id || null;
 | 
				
			||||||
 | 
						const max = params.max_id || null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check if both of since_id and max_id is specified
 | 
				
			||||||
 | 
						if (since !== null && max !== null) {
 | 
				
			||||||
 | 
							return rej('cannot set since_id and max_id');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'type' parameter
 | 
				
			||||||
 | 
						let type = params.type;
 | 
				
			||||||
 | 
						if (type === undefined || type === null) {
 | 
				
			||||||
 | 
							type = null;
 | 
				
			||||||
 | 
						} else if (!/^[a-zA-Z\/\-\*]+$/.test(type)) {
 | 
				
			||||||
 | 
							return rej('invalid type format');
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							type = new RegExp(`^${type.replace(/\*/g, '.+?')}$`);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Construct query
 | 
				
			||||||
 | 
						const sort = {
 | 
				
			||||||
 | 
							created_at: -1
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						const query = {
 | 
				
			||||||
 | 
							user_id: user._id
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						if (since !== null) {
 | 
				
			||||||
 | 
							sort.created_at = 1;
 | 
				
			||||||
 | 
							query._id = {
 | 
				
			||||||
 | 
								$gt: new mongo.ObjectID(since)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						} else if (max !== null) {
 | 
				
			||||||
 | 
							query._id = {
 | 
				
			||||||
 | 
								$lt: new mongo.ObjectID(max)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (type !== null) {
 | 
				
			||||||
 | 
							query.type = type;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Issue query
 | 
				
			||||||
 | 
						const files = await DriveFile
 | 
				
			||||||
 | 
							.find(query, {
 | 
				
			||||||
 | 
								data: false
 | 
				
			||||||
 | 
							}, {
 | 
				
			||||||
 | 
								limit: limit,
 | 
				
			||||||
 | 
								sort: sort
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						res(await Promise.all(files.map(async file =>
 | 
				
			||||||
 | 
							await serialize(file))));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										86
									
								
								src/api/endpoints/following/create.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,86 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					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 {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const follower = user;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'user_id' parameter
 | 
				
			||||||
 | 
						let userId = params.user_id;
 | 
				
			||||||
 | 
						if (userId === undefined || userId === null) {
 | 
				
			||||||
 | 
							return rej('user_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 自分自身
 | 
				
			||||||
 | 
						if (user._id.equals(userId)) {
 | 
				
			||||||
 | 
							return rej('followee is yourself');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get followee
 | 
				
			||||||
 | 
						const followee = await User.findOne({
 | 
				
			||||||
 | 
							_id: new mongo.ObjectID(userId)
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (followee === null) {
 | 
				
			||||||
 | 
							return rej('user not found');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check arleady 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.updateOne({ _id: follower._id }, {
 | 
				
			||||||
 | 
							$inc: {
 | 
				
			||||||
 | 
								following_count: 1
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Increment followers count
 | 
				
			||||||
 | 
						User.updateOne({ _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');
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										83
									
								
								src/api/endpoints/following/delete.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,83 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import User from '../../models/user';
 | 
				
			||||||
 | 
					import Following from '../../models/following';
 | 
				
			||||||
 | 
					import event from '../../event';
 | 
				
			||||||
 | 
					import serializeUser from '../../serializers/user';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Unfollow a user
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const follower = user;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'user_id' parameter
 | 
				
			||||||
 | 
						let userId = params.user_id;
 | 
				
			||||||
 | 
						if (userId === undefined || userId === null) {
 | 
				
			||||||
 | 
							return rej('user_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check if the followee is yourself
 | 
				
			||||||
 | 
						if (user._id.equals(userId)) {
 | 
				
			||||||
 | 
							return rej('followee is yourself');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get followee
 | 
				
			||||||
 | 
						const followee = await User.findOne({
 | 
				
			||||||
 | 
							_id: new mongo.ObjectID(userId)
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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.updateOne({
 | 
				
			||||||
 | 
							_id: exist._id
 | 
				
			||||||
 | 
						}, {
 | 
				
			||||||
 | 
							$set: {
 | 
				
			||||||
 | 
								deleted_at: new Date()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Send response
 | 
				
			||||||
 | 
						res();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Decrement following count
 | 
				
			||||||
 | 
						User.updateOne({ _id: follower._id }, {
 | 
				
			||||||
 | 
							$inc: {
 | 
				
			||||||
 | 
								following_count: -1
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Decrement followers count
 | 
				
			||||||
 | 
						User.updateOne({ _id: followee._id }, {
 | 
				
			||||||
 | 
							$inc: {
 | 
				
			||||||
 | 
								followers_count: -1
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Publish follow event
 | 
				
			||||||
 | 
						event(follower._id, 'unfollow', await serializeUser(followee, follower));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										25
									
								
								src/api/endpoints/i.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,25 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import serialize from '../serializers/user';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Show myself
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @param {Object} app
 | 
				
			||||||
 | 
					 * @param {Boolean} isSecure
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user, _, isSecure) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						res(await serialize(user, user, {
 | 
				
			||||||
 | 
							detail: true,
 | 
				
			||||||
 | 
							includeSecrets: isSecure
 | 
				
			||||||
 | 
						}));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										53
									
								
								src/api/endpoints/i/appdata/get.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,53 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import Appdata from '../../../models/appdata';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get app data
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @param {Object} app
 | 
				
			||||||
 | 
					 * @param {Boolean} isSecure
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user, app, isSecure) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'key' parameter
 | 
				
			||||||
 | 
						let key = params.key;
 | 
				
			||||||
 | 
						if (key === undefined) {
 | 
				
			||||||
 | 
							key = null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (isSecure) {
 | 
				
			||||||
 | 
							if (!user.data) {
 | 
				
			||||||
 | 
								return res();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (key !== null) {
 | 
				
			||||||
 | 
								const data = {};
 | 
				
			||||||
 | 
								data[key] = user.data[key];
 | 
				
			||||||
 | 
								res(data);
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								res(user.data);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							const select = {};
 | 
				
			||||||
 | 
							if (key !== null) {
 | 
				
			||||||
 | 
								select['data.' + key] = true;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							const appdata = await Appdata.findOne({
 | 
				
			||||||
 | 
								app_id: app._id,
 | 
				
			||||||
 | 
								user_id: user._id
 | 
				
			||||||
 | 
							}, select);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (appdata) {
 | 
				
			||||||
 | 
								res(appdata.data);
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								res();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										55
									
								
								src/api/endpoints/i/appdata/set.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,55 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import Appdata from '../../../models/appdata';
 | 
				
			||||||
 | 
					import User from '../../../models/user';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Set app data
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @param {Object} app
 | 
				
			||||||
 | 
					 * @param {Boolean} isSecure
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user, app, isSecure) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const data = params.data;
 | 
				
			||||||
 | 
						if (data == null) {
 | 
				
			||||||
 | 
							return rej('data is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (isSecure) {
 | 
				
			||||||
 | 
							const set = {
 | 
				
			||||||
 | 
								$set: {
 | 
				
			||||||
 | 
									data: Object.assign(user.data || {}, JSON.parse(data))
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							await User.updateOne({ _id: user._id }, set);
 | 
				
			||||||
 | 
							res(204);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							const appdata = await Appdata.findOne({
 | 
				
			||||||
 | 
								app_id: app._id,
 | 
				
			||||||
 | 
								user_id: user._id
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
							const set = {
 | 
				
			||||||
 | 
								$set: {
 | 
				
			||||||
 | 
									data: Object.assign((appdata || {}).data || {}, JSON.parse(data))
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							await Appdata.updateOne({
 | 
				
			||||||
 | 
								app_id: app._id,
 | 
				
			||||||
 | 
								user_id: user._id
 | 
				
			||||||
 | 
							}, Object.assign({
 | 
				
			||||||
 | 
								app_id: app._id,
 | 
				
			||||||
 | 
								user_id: user._id
 | 
				
			||||||
 | 
							}, set), {
 | 
				
			||||||
 | 
								upsert: true
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
							res(204);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										60
									
								
								src/api/endpoints/i/favorites.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,60 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import Favorite from '../../models/favorite';
 | 
				
			||||||
 | 
					import serialize from '../../serializers/post';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get followers of a user
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'limit' parameter
 | 
				
			||||||
 | 
						let limit = params.limit;
 | 
				
			||||||
 | 
						if (limit !== undefined && limit !== null) {
 | 
				
			||||||
 | 
							limit = parseInt(limit, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// From 1 to 100
 | 
				
			||||||
 | 
							if (!(1 <= limit && limit <= 100)) {
 | 
				
			||||||
 | 
								return rej('invalid limit range');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							limit = 10;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'offset' parameter
 | 
				
			||||||
 | 
						let offset = params.offset;
 | 
				
			||||||
 | 
						if (offset !== undefined && offset !== null) {
 | 
				
			||||||
 | 
							offset = parseInt(offset, 10);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							offset = 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'sort' parameter
 | 
				
			||||||
 | 
						let sort = params.sort || 'desc';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get favorites
 | 
				
			||||||
 | 
						const favorites = await Favorites
 | 
				
			||||||
 | 
							.find({
 | 
				
			||||||
 | 
								user_id: user._id
 | 
				
			||||||
 | 
							}, {}, {
 | 
				
			||||||
 | 
								limit: limit,
 | 
				
			||||||
 | 
								skip: offset,
 | 
				
			||||||
 | 
								sort: {
 | 
				
			||||||
 | 
									_id: sort == 'asc' ? 1 : -1
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						res(await Promise.all(favorites.map(async favorite =>
 | 
				
			||||||
 | 
							await serialize(favorite.post)
 | 
				
			||||||
 | 
						)));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										120
									
								
								src/api/endpoints/i/notifications.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,120 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import Notification from '../../models/notification';
 | 
				
			||||||
 | 
					import serialize from '../../serializers/notification';
 | 
				
			||||||
 | 
					import getFriends from '../../common/get-friends';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get notifications
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'following' parameter
 | 
				
			||||||
 | 
						const following = params.following === 'true';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'mark_as_read' parameter
 | 
				
			||||||
 | 
						let markAsRead = params.mark_as_read;
 | 
				
			||||||
 | 
						if (markAsRead == null) {
 | 
				
			||||||
 | 
							markAsRead = true;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							markAsRead = markAsRead === 'true';
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'type' parameter
 | 
				
			||||||
 | 
						let type = params.type;
 | 
				
			||||||
 | 
						if (type !== undefined && type !== null) {
 | 
				
			||||||
 | 
							type = type.split(',').map(x => x.trim());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'limit' parameter
 | 
				
			||||||
 | 
						let limit = params.limit;
 | 
				
			||||||
 | 
						if (limit !== undefined && limit !== null) {
 | 
				
			||||||
 | 
							limit = parseInt(limit, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// From 1 to 100
 | 
				
			||||||
 | 
							if (!(1 <= limit && limit <= 100)) {
 | 
				
			||||||
 | 
								return rej('invalid limit range');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							limit = 10;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const since = params.since_id || null;
 | 
				
			||||||
 | 
						const max = params.max_id || null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check if both of since_id and max_id is specified
 | 
				
			||||||
 | 
						if (since !== null && max !== null) {
 | 
				
			||||||
 | 
							return rej('cannot set since_id and max_id');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const query = {
 | 
				
			||||||
 | 
							notifiee_id: user._id
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const sort = {
 | 
				
			||||||
 | 
							_id: -1
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (following) {
 | 
				
			||||||
 | 
							// ID list of the user itself 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 (since !== null) {
 | 
				
			||||||
 | 
							sort._id = 1;
 | 
				
			||||||
 | 
							query._id = {
 | 
				
			||||||
 | 
								$gt: new mongo.ObjectID(since)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						} else if (max !== null) {
 | 
				
			||||||
 | 
							query._id = {
 | 
				
			||||||
 | 
								$lt: new mongo.ObjectID(max)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Issue query
 | 
				
			||||||
 | 
						const notifications = await Notification
 | 
				
			||||||
 | 
							.find(query, {}, {
 | 
				
			||||||
 | 
								limit: limit,
 | 
				
			||||||
 | 
								sort: sort
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						res(await Promise.all(notifications.map(async notification =>
 | 
				
			||||||
 | 
							await serialize(notification))));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Mark as read all
 | 
				
			||||||
 | 
						if (notifications.length > 0 && markAsRead) {
 | 
				
			||||||
 | 
							const ids = notifications
 | 
				
			||||||
 | 
								.filter(x => x.is_read == false)
 | 
				
			||||||
 | 
								.map(x => x._id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Update documents
 | 
				
			||||||
 | 
							await Notification.update({
 | 
				
			||||||
 | 
								_id: { $in: ids }
 | 
				
			||||||
 | 
							}, {
 | 
				
			||||||
 | 
								$set: { is_read: true }
 | 
				
			||||||
 | 
							}, {
 | 
				
			||||||
 | 
								multi: true
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										71
									
								
								src/api/endpoints/i/signin_history.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,71 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import Signin from '../../models/signin';
 | 
				
			||||||
 | 
					import serialize from '../../serializers/signin';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get signin history of my account
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'limit' parameter
 | 
				
			||||||
 | 
						let limit = params.limit;
 | 
				
			||||||
 | 
						if (limit !== undefined && limit !== null) {
 | 
				
			||||||
 | 
							limit = parseInt(limit, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// From 1 to 100
 | 
				
			||||||
 | 
							if (!(1 <= limit && limit <= 100)) {
 | 
				
			||||||
 | 
								return rej('invalid limit range');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							limit = 10;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const since = params.since_id || null;
 | 
				
			||||||
 | 
						const max = params.max_id || null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check if both of since_id and max_id is specified
 | 
				
			||||||
 | 
						if (since !== null && max !== null) {
 | 
				
			||||||
 | 
							return rej('cannot set since_id and max_id');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const query = {
 | 
				
			||||||
 | 
							user_id: user._id
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const sort = {
 | 
				
			||||||
 | 
							_id: -1
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (since !== null) {
 | 
				
			||||||
 | 
							sort._id = 1;
 | 
				
			||||||
 | 
							query._id = {
 | 
				
			||||||
 | 
								$gt: new mongo.ObjectID(since)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						} else if (max !== null) {
 | 
				
			||||||
 | 
							query._id = {
 | 
				
			||||||
 | 
								$lt: new mongo.ObjectID(max)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Issue query
 | 
				
			||||||
 | 
						const history = await Signin
 | 
				
			||||||
 | 
							.find(query, {}, {
 | 
				
			||||||
 | 
								limit: limit,
 | 
				
			||||||
 | 
								sort: sort
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						res(await Promise.all(history.map(async record =>
 | 
				
			||||||
 | 
							await serialize(record))));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										95
									
								
								src/api/endpoints/i/update.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,95 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import User from '../../models/user';
 | 
				
			||||||
 | 
					import serialize from '../../serializers/user';
 | 
				
			||||||
 | 
					import event from '../../event';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Update myself
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @param {Object} _
 | 
				
			||||||
 | 
					 * @param {boolean} isSecure
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = async (params, user, _, isSecure) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'name' parameter
 | 
				
			||||||
 | 
						const name = params.name;
 | 
				
			||||||
 | 
						if (name !== undefined && name !== null) {
 | 
				
			||||||
 | 
							if (name.length > 50) {
 | 
				
			||||||
 | 
								return rej('too long name');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							user.name = name;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'location' parameter
 | 
				
			||||||
 | 
						const location = params.location;
 | 
				
			||||||
 | 
						if (location !== undefined && location !== null) {
 | 
				
			||||||
 | 
							if (location.length > 50) {
 | 
				
			||||||
 | 
								return rej('too long location');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							user.location = location;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'bio' parameter
 | 
				
			||||||
 | 
						const bio = params.bio;
 | 
				
			||||||
 | 
						if (bio !== undefined && bio !== null) {
 | 
				
			||||||
 | 
							if (bio.length > 500) {
 | 
				
			||||||
 | 
								return rej('too long bio');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							user.bio = bio;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'avatar_id' parameter
 | 
				
			||||||
 | 
						const avatar = params.avatar_id;
 | 
				
			||||||
 | 
						if (avatar !== undefined && avatar !== null) {
 | 
				
			||||||
 | 
							user.avatar_id = new mongo.ObjectID(avatar);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'banner_id' parameter
 | 
				
			||||||
 | 
						const banner = params.banner_id;
 | 
				
			||||||
 | 
						if (banner !== undefined && banner !== null) {
 | 
				
			||||||
 | 
							user.banner_id = new mongo.ObjectID(banner);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						await User.updateOne({ _id: user._id }, {
 | 
				
			||||||
 | 
							$set: user
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 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
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										48
									
								
								src/api/endpoints/messaging/history.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,48 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import History from '../../models/messaging-history';
 | 
				
			||||||
 | 
					import serialize from '../../serializers/messaging-message';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Show messaging history
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'limit' parameter
 | 
				
			||||||
 | 
						let limit = params.limit;
 | 
				
			||||||
 | 
						if (limit !== undefined && limit !== null) {
 | 
				
			||||||
 | 
							limit = parseInt(limit, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// From 1 to 100
 | 
				
			||||||
 | 
							if (!(1 <= limit && limit <= 100)) {
 | 
				
			||||||
 | 
								return rej('invalid limit range');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							limit = 10;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get history
 | 
				
			||||||
 | 
						const history = await History
 | 
				
			||||||
 | 
							.find({
 | 
				
			||||||
 | 
								user_id: user._id
 | 
				
			||||||
 | 
							}, {}, {
 | 
				
			||||||
 | 
								limit: limit,
 | 
				
			||||||
 | 
								sort: {
 | 
				
			||||||
 | 
									updated_at: -1
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						res(await Promise.all(history.map(async h =>
 | 
				
			||||||
 | 
							await serialize(h.message, user))));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										139
									
								
								src/api/endpoints/messaging/messages.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,139 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import Message from '../../models/messaging-message';
 | 
				
			||||||
 | 
					import User from '../../models/user';
 | 
				
			||||||
 | 
					import serialize from '../../serializers/messaging-message';
 | 
				
			||||||
 | 
					import publishUserStream from '../../event';
 | 
				
			||||||
 | 
					import { publishMessagingStream } from '../../event';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get messages
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'user_id' parameter
 | 
				
			||||||
 | 
						let recipient = params.user_id;
 | 
				
			||||||
 | 
						if (recipient !== undefined && recipient !== null) {
 | 
				
			||||||
 | 
							recipient = await User.findOne({
 | 
				
			||||||
 | 
								_id: new mongo.ObjectID(recipient)
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (recipient === null) {
 | 
				
			||||||
 | 
								return rej('user not found');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return rej('user_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'mark_as_read' parameter
 | 
				
			||||||
 | 
						let markAsRead = params.mark_as_read;
 | 
				
			||||||
 | 
						if (markAsRead == null) {
 | 
				
			||||||
 | 
							markAsRead = true;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							markAsRead = markAsRead === 'true';
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'limit' parameter
 | 
				
			||||||
 | 
						let limit = params.limit;
 | 
				
			||||||
 | 
						if (limit !== undefined && limit !== null) {
 | 
				
			||||||
 | 
							limit = parseInt(limit, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// From 1 to 100
 | 
				
			||||||
 | 
							if (!(1 <= limit && limit <= 100)) {
 | 
				
			||||||
 | 
								return rej('invalid limit range');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							limit = 10;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const since = params.since_id || null;
 | 
				
			||||||
 | 
						const max = params.max_id || null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check if both of since_id and max_id is specified
 | 
				
			||||||
 | 
						if (since !== null && max !== null) {
 | 
				
			||||||
 | 
							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
 | 
				
			||||||
 | 
							}]
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const sort = {
 | 
				
			||||||
 | 
							created_at: -1
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (since !== null) {
 | 
				
			||||||
 | 
							sort.created_at = 1;
 | 
				
			||||||
 | 
							query._id = {
 | 
				
			||||||
 | 
								$gt: new mongo.ObjectID(since)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						} else if (max !== null) {
 | 
				
			||||||
 | 
							query._id = {
 | 
				
			||||||
 | 
								$lt: new mongo.ObjectID(max)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Issue query
 | 
				
			||||||
 | 
						const messages = await Message
 | 
				
			||||||
 | 
							.find(query, {}, {
 | 
				
			||||||
 | 
								limit: limit,
 | 
				
			||||||
 | 
								sort: sort
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 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) {
 | 
				
			||||||
 | 
							const ids = messages
 | 
				
			||||||
 | 
								.filter(m => m.is_read == false)
 | 
				
			||||||
 | 
								.filter(m => m.recipient_id.equals(user._id))
 | 
				
			||||||
 | 
								.map(m => m._id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Update documents
 | 
				
			||||||
 | 
							await Message.update({
 | 
				
			||||||
 | 
								_id: { $in: ids }
 | 
				
			||||||
 | 
							}, {
 | 
				
			||||||
 | 
								$set: { is_read: true }
 | 
				
			||||||
 | 
							}, {
 | 
				
			||||||
 | 
								multi: true
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Publish event
 | 
				
			||||||
 | 
							publishMessagingStream(recipient._id, user._id, 'read', ids.map(id => id.toString()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const count = await Message
 | 
				
			||||||
 | 
								.count({
 | 
				
			||||||
 | 
									recipient_id: user._id,
 | 
				
			||||||
 | 
									is_read: false
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (count == 0) {
 | 
				
			||||||
 | 
								// 全ての(いままで未読だった)メッセージを(これで)読みましたよというイベントを発行
 | 
				
			||||||
 | 
								publishUserStream(user._id, 'read_all_messaging_messages');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										152
									
								
								src/api/endpoints/messaging/messages/create.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,152 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import Message 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 } from '../../../event';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 最大文字数
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const maxTextLength = 500;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Create a message
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'user_id' parameter
 | 
				
			||||||
 | 
						let recipient = params.user_id;
 | 
				
			||||||
 | 
						if (recipient !== undefined && recipient !== null) {
 | 
				
			||||||
 | 
							recipient = await User.findOne({
 | 
				
			||||||
 | 
								_id: new mongo.ObjectID(recipient)
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (recipient === null) {
 | 
				
			||||||
 | 
								return rej('user not found');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return rej('user_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'text' parameter
 | 
				
			||||||
 | 
						let text = params.text;
 | 
				
			||||||
 | 
						if (text !== undefined && text !== null) {
 | 
				
			||||||
 | 
							text = text.trim();
 | 
				
			||||||
 | 
							if (text.length === 0) {
 | 
				
			||||||
 | 
								text = null;
 | 
				
			||||||
 | 
							} else if (text.length > maxTextLength) {
 | 
				
			||||||
 | 
								return rej('too long text');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							text = null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'file_id' parameter
 | 
				
			||||||
 | 
						let file = params.file_id;
 | 
				
			||||||
 | 
						if (file !== undefined && file !== null) {
 | 
				
			||||||
 | 
							file = await DriveFile.findOne({
 | 
				
			||||||
 | 
								_id: new mongo.ObjectID(file),
 | 
				
			||||||
 | 
								user_id: user._id
 | 
				
			||||||
 | 
							}, {
 | 
				
			||||||
 | 
								data: false
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (file === null) {
 | 
				
			||||||
 | 
								return rej('file not found');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							file = null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// テキストが無いかつ添付ファイルも無かったらエラー
 | 
				
			||||||
 | 
						if (text === null && file === null) {
 | 
				
			||||||
 | 
							return rej('text or file is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// メッセージを作成
 | 
				
			||||||
 | 
						const inserted = 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
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const message = inserted.ops[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						const messageObj = await serialize(message);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Reponse
 | 
				
			||||||
 | 
						res(messageObj);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 自分のストリーム
 | 
				
			||||||
 | 
						publishMessagingStream(message.user_id, message.recipient_id, 'message', messageObj);
 | 
				
			||||||
 | 
						publishUserStream(message.user_id, 'messaging_message', messageObj);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 相手のストリーム
 | 
				
			||||||
 | 
						publishMessagingStream(message.recipient_id, message.user_id, 'message', messageObj);
 | 
				
			||||||
 | 
						publishUserStream(message.recipient_id, 'messaging_message', messageObj);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 5秒経っても(今回作成した)メッセージが既読にならなかったら「未読のメッセージがありますよ」イベントを発行する
 | 
				
			||||||
 | 
						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);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}, 5000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 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.updateOne({
 | 
				
			||||||
 | 
							user_id: user._id,
 | 
				
			||||||
 | 
							partner: recipient._id
 | 
				
			||||||
 | 
						}, {
 | 
				
			||||||
 | 
							updated_at: new Date(),
 | 
				
			||||||
 | 
							user_id: user._id,
 | 
				
			||||||
 | 
							partner: recipient._id,
 | 
				
			||||||
 | 
							message: message._id
 | 
				
			||||||
 | 
						}, {
 | 
				
			||||||
 | 
							upsert: true
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 履歴作成(相手)
 | 
				
			||||||
 | 
						History.updateOne({
 | 
				
			||||||
 | 
							user_id: recipient._id,
 | 
				
			||||||
 | 
							partner: user._id
 | 
				
			||||||
 | 
						}, {
 | 
				
			||||||
 | 
							updated_at: new Date(),
 | 
				
			||||||
 | 
							user_id: recipient._id,
 | 
				
			||||||
 | 
							partner: user._id,
 | 
				
			||||||
 | 
							message: message._id
 | 
				
			||||||
 | 
						}, {
 | 
				
			||||||
 | 
							upsert: true
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										27
									
								
								src/api/endpoints/messaging/unread.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,27 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import Message from '../../models/messaging-message';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get count of unread messages
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const count = await Message
 | 
				
			||||||
 | 
							.count({
 | 
				
			||||||
 | 
								recipient_id: user._id,
 | 
				
			||||||
 | 
								is_read: false
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res({
 | 
				
			||||||
 | 
							count: count
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										24
									
								
								src/api/endpoints/meta.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,24 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import Git from 'nodegit';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Show core info
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const repository = await Git.Repository.open(__dirname + '/../../');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						res({
 | 
				
			||||||
 | 
							maintainer: config.maintainer,
 | 
				
			||||||
 | 
							commit: (await repository.getHeadCommit()).sha(),
 | 
				
			||||||
 | 
							secure: config.https.enable
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										59
									
								
								src/api/endpoints/my/apps.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,59 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import App from '../../models/app';
 | 
				
			||||||
 | 
					import serialize from '../../serializers/app';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get my apps
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'limit' parameter
 | 
				
			||||||
 | 
						let limit = params.limit;
 | 
				
			||||||
 | 
						if (limit !== undefined && limit !== null) {
 | 
				
			||||||
 | 
							limit = parseInt(limit, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// From 1 to 100
 | 
				
			||||||
 | 
							if (!(1 <= limit && limit <= 100)) {
 | 
				
			||||||
 | 
								return rej('invalid limit range');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							limit = 10;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'offset' parameter
 | 
				
			||||||
 | 
						let offset = params.offset;
 | 
				
			||||||
 | 
						if (offset !== undefined && offset !== null) {
 | 
				
			||||||
 | 
							offset = parseInt(offset, 10);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							offset = 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const query = {
 | 
				
			||||||
 | 
							user_id: user._id
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Execute query
 | 
				
			||||||
 | 
						const apps = await App
 | 
				
			||||||
 | 
							.find(query, {}, {
 | 
				
			||||||
 | 
								limit: limit,
 | 
				
			||||||
 | 
								skip: offset,
 | 
				
			||||||
 | 
								sort: {
 | 
				
			||||||
 | 
									created_at: -1
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Reply
 | 
				
			||||||
 | 
						res(await Promise.all(apps.map(async app =>
 | 
				
			||||||
 | 
							await serialize(app))));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										54
									
								
								src/api/endpoints/notifications/mark_as_read.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,54 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import Notification from '../../../models/notification';
 | 
				
			||||||
 | 
					import serialize from '../../../serializers/notification';
 | 
				
			||||||
 | 
					import event from '../../../event';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Mark as read a notification
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						const notificationId = params.notification;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (notificationId === undefined || notificationId === null) {
 | 
				
			||||||
 | 
							return rej('notification is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get notifcation
 | 
				
			||||||
 | 
						const notification = await Notification
 | 
				
			||||||
 | 
							.findOne({
 | 
				
			||||||
 | 
								_id: new mongo.ObjectID(notificationId),
 | 
				
			||||||
 | 
								i: user._id
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (notification === null) {
 | 
				
			||||||
 | 
							return rej('notification-not-found');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Update
 | 
				
			||||||
 | 
						notification.is_read = true;
 | 
				
			||||||
 | 
						Notification.updateOne({ _id: notification._id }, {
 | 
				
			||||||
 | 
							$set: {
 | 
				
			||||||
 | 
								is_read: true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Response
 | 
				
			||||||
 | 
						res();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						const notificationObj = await serialize(notification);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Publish read_notification event
 | 
				
			||||||
 | 
						event(user._id, 'read_notification', notificationObj);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										65
									
								
								src/api/endpoints/posts.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,65 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import Post from '../models/post';
 | 
				
			||||||
 | 
					import serialize from '../serializers/post';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Lists all posts
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'limit' parameter
 | 
				
			||||||
 | 
						let limit = params.limit;
 | 
				
			||||||
 | 
						if (limit !== undefined && limit !== null) {
 | 
				
			||||||
 | 
							limit = parseInt(limit, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// From 1 to 100
 | 
				
			||||||
 | 
							if (!(1 <= limit && limit <= 100)) {
 | 
				
			||||||
 | 
								return rej('invalid limit range');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							limit = 10;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const since = params.since_id || null;
 | 
				
			||||||
 | 
						const max = params.max_id || null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check if both of since_id and max_id is specified
 | 
				
			||||||
 | 
						if (since !== null && max !== null) {
 | 
				
			||||||
 | 
							return rej('cannot set since_id and max_id');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Construct query
 | 
				
			||||||
 | 
						const sort = {
 | 
				
			||||||
 | 
							created_at: -1
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						const query = {};
 | 
				
			||||||
 | 
						if (since !== null) {
 | 
				
			||||||
 | 
							sort.created_at = 1;
 | 
				
			||||||
 | 
							query._id = {
 | 
				
			||||||
 | 
								$gt: new mongo.ObjectID(since)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						} else if (max !== null) {
 | 
				
			||||||
 | 
							query._id = {
 | 
				
			||||||
 | 
								$lt: new mongo.ObjectID(max)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Issue query
 | 
				
			||||||
 | 
						const posts = await Post
 | 
				
			||||||
 | 
							.find(query, {}, {
 | 
				
			||||||
 | 
								limit: limit,
 | 
				
			||||||
 | 
								sort: sort
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						res(await Promise.all(posts.map(async post => await serialize(post))));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										83
									
								
								src/api/endpoints/posts/context.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,83 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import Post from '../../models/post';
 | 
				
			||||||
 | 
					import serialize from '../../serializers/post';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Show a context of a post
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'post_id' parameter
 | 
				
			||||||
 | 
						const postId = params.post_id;
 | 
				
			||||||
 | 
						if (postId === undefined || postId === null) {
 | 
				
			||||||
 | 
							return rej('post_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'limit' parameter
 | 
				
			||||||
 | 
						let limit = params.limit;
 | 
				
			||||||
 | 
						if (limit !== undefined && limit !== null) {
 | 
				
			||||||
 | 
							limit = parseInt(limit, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// From 1 to 100
 | 
				
			||||||
 | 
							if (!(1 <= limit && limit <= 100)) {
 | 
				
			||||||
 | 
								return rej('invalid limit range');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							limit = 10;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'offset' parameter
 | 
				
			||||||
 | 
						let offset = params.offset;
 | 
				
			||||||
 | 
						if (offset !== undefined && offset !== null) {
 | 
				
			||||||
 | 
							offset = parseInt(offset, 10);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							offset = 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Lookup post
 | 
				
			||||||
 | 
						const post = await Post.findOne({
 | 
				
			||||||
 | 
							_id: new mongo.ObjectID(postId)
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (post === null) {
 | 
				
			||||||
 | 
							return rej('post not found', '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_to_id) {
 | 
				
			||||||
 | 
								await get(p.reply_to_id);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (post.reply_to_id) {
 | 
				
			||||||
 | 
							await get(post.reply_to_id);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						res(await Promise.all(context.map(async post =>
 | 
				
			||||||
 | 
							await serialize(post, user))));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										345
									
								
								src/api/endpoints/posts/create.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,345 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import parse from '../../../common/text';
 | 
				
			||||||
 | 
					import Post from '../../models/post';
 | 
				
			||||||
 | 
					import User from '../../models/user';
 | 
				
			||||||
 | 
					import Following from '../../models/following';
 | 
				
			||||||
 | 
					import DriveFile from '../../models/drive-file';
 | 
				
			||||||
 | 
					import serialize from '../../serializers/post';
 | 
				
			||||||
 | 
					import createFile from '../../common/add-file-to-drive';
 | 
				
			||||||
 | 
					import notify from '../../common/notify';
 | 
				
			||||||
 | 
					import event from '../../event';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 最大文字数
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const maxTextLength = 300;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 添付できるファイルの数
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const maxMediaCount = 4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Create a post
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @param {Object} app
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user, app) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'text' parameter
 | 
				
			||||||
 | 
						let text = params.text;
 | 
				
			||||||
 | 
						if (text !== undefined && text !== null) {
 | 
				
			||||||
 | 
							text = text.trim();
 | 
				
			||||||
 | 
							if (text.length == 0) {
 | 
				
			||||||
 | 
								text = null;
 | 
				
			||||||
 | 
							} else if (text.length > maxTextLength) {
 | 
				
			||||||
 | 
								return rej('too long text');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							text = null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'media_ids' parameter
 | 
				
			||||||
 | 
						let media = params.media_ids;
 | 
				
			||||||
 | 
						let files = [];
 | 
				
			||||||
 | 
						if (media !== undefined && media !== null) {
 | 
				
			||||||
 | 
							media = media.split(',');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (media.length > maxMediaCount) {
 | 
				
			||||||
 | 
								return rej('too many media');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Drop duplicates
 | 
				
			||||||
 | 
							media = media.filter((x, i, s) => s.indexOf(x) == i);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Fetch files
 | 
				
			||||||
 | 
							// forEach だと途中でエラーなどがあっても return できないので
 | 
				
			||||||
 | 
							// 敢えて for を使っています。
 | 
				
			||||||
 | 
							for (let i = 0; i < media.length; i++) {
 | 
				
			||||||
 | 
								const image = media[i];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Fetch file
 | 
				
			||||||
 | 
								// SELECT _id
 | 
				
			||||||
 | 
								const entity = await DriveFile.findOne({
 | 
				
			||||||
 | 
									_id: new mongo.ObjectID(image),
 | 
				
			||||||
 | 
									user_id: user._id
 | 
				
			||||||
 | 
								}, {
 | 
				
			||||||
 | 
									_id: true
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (entity === null) {
 | 
				
			||||||
 | 
									return rej('file not found');
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									files.push(entity);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							files = null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'repost_id' parameter
 | 
				
			||||||
 | 
						let repost = params.repost_id;
 | 
				
			||||||
 | 
						if (repost !== undefined && repost !== null) {
 | 
				
			||||||
 | 
							// Fetch repost to post
 | 
				
			||||||
 | 
							repost = await Post.findOne({
 | 
				
			||||||
 | 
								_id: new mongo.ObjectID(repost)
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							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
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 直近と同じRepost対象かつ引用じゃなかったらエラー
 | 
				
			||||||
 | 
							if (latestPost &&
 | 
				
			||||||
 | 
									latestPost.repost_id &&
 | 
				
			||||||
 | 
									latestPost.repost_id.equals(repost._id) &&
 | 
				
			||||||
 | 
									text === null && files === null) {
 | 
				
			||||||
 | 
								return rej('二重Repostです(NEED TRANSLATE)');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 直近がRepost対象かつ引用じゃなかったらエラー
 | 
				
			||||||
 | 
							if (latestPost &&
 | 
				
			||||||
 | 
									latestPost._id.equals(repost._id) &&
 | 
				
			||||||
 | 
									text === null && files === null) {
 | 
				
			||||||
 | 
								return rej('二重Repostです(NEED TRANSLATE)');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							repost = null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'reply_to_id' parameter
 | 
				
			||||||
 | 
						let replyTo = params.reply_to_id;
 | 
				
			||||||
 | 
						if (replyTo !== undefined && replyTo !== null) {
 | 
				
			||||||
 | 
							replyTo = await Post.findOne({
 | 
				
			||||||
 | 
								_id: new mongo.ObjectID(replyTo)
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (replyTo === null) {
 | 
				
			||||||
 | 
								return rej('reply to post is not found');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 返信対象が引用でないRepostだったらエラー
 | 
				
			||||||
 | 
							if (replyTo.repost_id && !replyTo.text && !replyTo.media_ids) {
 | 
				
			||||||
 | 
								return rej('cannot reply to repost');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							replyTo = null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// テキストが無いかつ添付ファイルが無いかつRepostも無かったらエラー
 | 
				
			||||||
 | 
						if (text === null && files === null && repost === null) {
 | 
				
			||||||
 | 
							return rej('text, media_ids or repost_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 投稿を作成
 | 
				
			||||||
 | 
						const inserted = await Post.insert({
 | 
				
			||||||
 | 
							created_at: new Date(),
 | 
				
			||||||
 | 
							media_ids: media ? files.map(file => file._id) : undefined,
 | 
				
			||||||
 | 
							reply_to_id: replyTo ? replyTo._id : undefined,
 | 
				
			||||||
 | 
							repost_id: repost ? repost._id : undefined,
 | 
				
			||||||
 | 
							text: text,
 | 
				
			||||||
 | 
							user_id: user._id,
 | 
				
			||||||
 | 
							app_id: app ? app._id : null
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const post = inserted.ops[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						const postObj = await serialize(post);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Reponse
 | 
				
			||||||
 | 
						res(postObj);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						//--------------------------------
 | 
				
			||||||
 | 
						// Post processes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let mentions = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						function addMention(mentionee, type) {
 | 
				
			||||||
 | 
							// 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, type, postObj);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 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
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Publish event to followers stream
 | 
				
			||||||
 | 
						followers.forEach(following =>
 | 
				
			||||||
 | 
							event(following.follower_id, 'post', postObj));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Increment my posts count
 | 
				
			||||||
 | 
						User.updateOne({ _id: user._id }, {
 | 
				
			||||||
 | 
							$inc: {
 | 
				
			||||||
 | 
								posts_count: 1
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If has in reply to post
 | 
				
			||||||
 | 
						if (replyTo) {
 | 
				
			||||||
 | 
							// Increment replies count
 | 
				
			||||||
 | 
							Post.updateOne({ _id: replyTo._id }, {
 | 
				
			||||||
 | 
								$inc: {
 | 
				
			||||||
 | 
									replies_count: 1
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 自分自身へのリプライでない限りは通知を作成
 | 
				
			||||||
 | 
							notify(replyTo.user_id, user._id, 'reply', {
 | 
				
			||||||
 | 
								post_id: post._id
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Add mention
 | 
				
			||||||
 | 
							addMention(replyTo.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
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 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.updateOne({ _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 (replyTo && replyTo.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.updateOne({ _id: post._id }, {
 | 
				
			||||||
 | 
								$set: {
 | 
				
			||||||
 | 
									mentions: mentions
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										56
									
								
								src/api/endpoints/posts/favorites/create.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,56 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import Favorite from '../../models/favorite';
 | 
				
			||||||
 | 
					import Post from '../../models/post';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Favorite a post
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'post_id' parameter
 | 
				
			||||||
 | 
						let postId = params.post_id;
 | 
				
			||||||
 | 
						if (postId === undefined || postId === null) {
 | 
				
			||||||
 | 
							return rej('post_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get favoritee
 | 
				
			||||||
 | 
						const post = await Post.findOne({
 | 
				
			||||||
 | 
							_id: new mongo.ObjectID(postId)
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (post === null) {
 | 
				
			||||||
 | 
							return rej('post not found');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check arleady favorited
 | 
				
			||||||
 | 
						const exist = await Favorite.findOne({
 | 
				
			||||||
 | 
							post_id: post._id,
 | 
				
			||||||
 | 
							user_id: user._id
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (exist !== null) {
 | 
				
			||||||
 | 
							return rej('already favorited');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Create favorite
 | 
				
			||||||
 | 
						const inserted = await Favorite.insert({
 | 
				
			||||||
 | 
							created_at: new Date(),
 | 
				
			||||||
 | 
							post_id: post._id,
 | 
				
			||||||
 | 
							user_id: user._id
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const favorite = inserted.ops[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Send response
 | 
				
			||||||
 | 
						res();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										52
									
								
								src/api/endpoints/posts/favorites/delete.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,52 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import Favorite from '../../models/favorite';
 | 
				
			||||||
 | 
					import Post from '../../models/post';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Unfavorite a post
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'post_id' parameter
 | 
				
			||||||
 | 
						let postId = params.post_id;
 | 
				
			||||||
 | 
						if (postId === undefined || postId === null) {
 | 
				
			||||||
 | 
							return rej('post_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get favoritee
 | 
				
			||||||
 | 
						const post = await Post.findOne({
 | 
				
			||||||
 | 
							_id: new mongo.ObjectID(postId)
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (post === null) {
 | 
				
			||||||
 | 
							return rej('post not found');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check arleady 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();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										77
									
								
								src/api/endpoints/posts/likes.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,77 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import Post from '../../models/post';
 | 
				
			||||||
 | 
					import Like from '../../models/like';
 | 
				
			||||||
 | 
					import serialize from '../../serializers/user';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Show a likes of a post
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'post_id' parameter
 | 
				
			||||||
 | 
						const postId = params.post_id;
 | 
				
			||||||
 | 
						if (postId === undefined || postId === null) {
 | 
				
			||||||
 | 
							return rej('post_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'limit' parameter
 | 
				
			||||||
 | 
						let limit = params.limit;
 | 
				
			||||||
 | 
						if (limit !== undefined && limit !== null) {
 | 
				
			||||||
 | 
							limit = parseInt(limit, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// From 1 to 100
 | 
				
			||||||
 | 
							if (!(1 <= limit && limit <= 100)) {
 | 
				
			||||||
 | 
								return rej('invalid limit range');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							limit = 10;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'offset' parameter
 | 
				
			||||||
 | 
						let offset = params.offset;
 | 
				
			||||||
 | 
						if (offset !== undefined && offset !== null) {
 | 
				
			||||||
 | 
							offset = parseInt(offset, 10);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							offset = 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'sort' parameter
 | 
				
			||||||
 | 
						let sort = params.sort || 'desc';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Lookup post
 | 
				
			||||||
 | 
						const post = await Post.findOne({
 | 
				
			||||||
 | 
							_id: new mongo.ObjectID(postId)
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (post === null) {
 | 
				
			||||||
 | 
							return rej('post not found');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Issue query
 | 
				
			||||||
 | 
						const likes = await Like
 | 
				
			||||||
 | 
							.find({
 | 
				
			||||||
 | 
								post_id: post._id,
 | 
				
			||||||
 | 
								deleted_at: { $exists: false }
 | 
				
			||||||
 | 
							}, {}, {
 | 
				
			||||||
 | 
								limit: limit,
 | 
				
			||||||
 | 
								skip: offset,
 | 
				
			||||||
 | 
								sort: {
 | 
				
			||||||
 | 
									_id: sort == 'asc' ? 1 : -1
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						res(await Promise.all(likes.map(async like =>
 | 
				
			||||||
 | 
							await serialize(like.user_id, user))));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										93
									
								
								src/api/endpoints/posts/likes/create.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,93 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import Like from '../../../models/like';
 | 
				
			||||||
 | 
					import Post from '../../../models/post';
 | 
				
			||||||
 | 
					import User from '../../../models/user';
 | 
				
			||||||
 | 
					import notify from '../../../common/notify';
 | 
				
			||||||
 | 
					import event from '../../../event';
 | 
				
			||||||
 | 
					import serializeUser from '../../../serializers/user';
 | 
				
			||||||
 | 
					import serializePost from '../../../serializers/post';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Like a post
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'post_id' parameter
 | 
				
			||||||
 | 
						let postId = params.post_id;
 | 
				
			||||||
 | 
						if (postId === undefined || postId === null) {
 | 
				
			||||||
 | 
							return rej('post_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get likee
 | 
				
			||||||
 | 
						const post = await Post.findOne({
 | 
				
			||||||
 | 
							_id: new mongo.ObjectID(postId)
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (post === null) {
 | 
				
			||||||
 | 
							return rej('post not found');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Myself
 | 
				
			||||||
 | 
						if (post.user_id.equals(user._id)) {
 | 
				
			||||||
 | 
							return rej('-need-translate-');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check arleady liked
 | 
				
			||||||
 | 
						const exist = await Like.findOne({
 | 
				
			||||||
 | 
							post_id: post._id,
 | 
				
			||||||
 | 
							user_id: user._id,
 | 
				
			||||||
 | 
							deleted_at: { $exists: false }
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (exist !== null) {
 | 
				
			||||||
 | 
							return rej('already liked');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Create like
 | 
				
			||||||
 | 
						const inserted = await Like.insert({
 | 
				
			||||||
 | 
							created_at: new Date(),
 | 
				
			||||||
 | 
							post_id: post._id,
 | 
				
			||||||
 | 
							user_id: user._id
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const like = inserted.ops[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Send response
 | 
				
			||||||
 | 
						res();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Increment likes count
 | 
				
			||||||
 | 
						Post.updateOne({ _id: post._id }, {
 | 
				
			||||||
 | 
							$inc: {
 | 
				
			||||||
 | 
								likes_count: 1
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Increment user likes count
 | 
				
			||||||
 | 
						User.updateOne({ _id: user._id }, {
 | 
				
			||||||
 | 
							$inc: {
 | 
				
			||||||
 | 
								likes_count: 1
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Increment user liked count
 | 
				
			||||||
 | 
						User.updateOne({ _id: post.user_id }, {
 | 
				
			||||||
 | 
							$inc: {
 | 
				
			||||||
 | 
								liked_count: 1
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Notify
 | 
				
			||||||
 | 
						notify(post.user_id, user._id, 'like', {
 | 
				
			||||||
 | 
							post_id: post._id
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										80
									
								
								src/api/endpoints/posts/likes/delete.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,80 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import Like from '../../../models/like';
 | 
				
			||||||
 | 
					import Post from '../../../models/post';
 | 
				
			||||||
 | 
					import User from '../../../models/user';
 | 
				
			||||||
 | 
					// import event from '../../../event';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Unlike a post
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'post_id' parameter
 | 
				
			||||||
 | 
						let postId = params.post_id;
 | 
				
			||||||
 | 
						if (postId === undefined || postId === null) {
 | 
				
			||||||
 | 
							return rej('post_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get likee
 | 
				
			||||||
 | 
						const post = await Post.findOne({
 | 
				
			||||||
 | 
							_id: new mongo.ObjectID(postId)
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (post === null) {
 | 
				
			||||||
 | 
							return rej('post not found');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check arleady liked
 | 
				
			||||||
 | 
						const exist = await Like.findOne({
 | 
				
			||||||
 | 
							post_id: post._id,
 | 
				
			||||||
 | 
							user_id: user._id,
 | 
				
			||||||
 | 
							deleted_at: { $exists: false }
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (exist === null) {
 | 
				
			||||||
 | 
							return rej('already not liked');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Delete like
 | 
				
			||||||
 | 
						await Like.updateOne({
 | 
				
			||||||
 | 
							_id: exist._id
 | 
				
			||||||
 | 
						}, {
 | 
				
			||||||
 | 
							$set: {
 | 
				
			||||||
 | 
								deleted_at: new Date()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Send response
 | 
				
			||||||
 | 
						res();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Decrement likes count
 | 
				
			||||||
 | 
						Post.updateOne({ _id: post._id }, {
 | 
				
			||||||
 | 
							$inc: {
 | 
				
			||||||
 | 
								likes_count: -1
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Decrement user likes count
 | 
				
			||||||
 | 
						User.updateOne({ _id: user._id }, {
 | 
				
			||||||
 | 
							$inc: {
 | 
				
			||||||
 | 
								likes_count: -1
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Decrement user liked count
 | 
				
			||||||
 | 
						User.updateOne({ _id: post.user_id }, {
 | 
				
			||||||
 | 
							$inc: {
 | 
				
			||||||
 | 
								liked_count: -1
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										85
									
								
								src/api/endpoints/posts/mentions.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,85 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import Post from '../../models/post';
 | 
				
			||||||
 | 
					import getFriends from '../../common/get-friends';
 | 
				
			||||||
 | 
					import serialize from '../../serializers/post';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get mentions of myself
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'following' parameter
 | 
				
			||||||
 | 
						const following = params.following === 'true';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'limit' parameter
 | 
				
			||||||
 | 
						let limit = params.limit;
 | 
				
			||||||
 | 
						if (limit !== undefined && limit !== null) {
 | 
				
			||||||
 | 
							limit = parseInt(limit, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// From 1 to 100
 | 
				
			||||||
 | 
							if (!(1 <= limit && limit <= 100)) {
 | 
				
			||||||
 | 
								return rej('invalid limit range');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							limit = 10;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const since = params.since_id || null;
 | 
				
			||||||
 | 
						const max = params.max_id || null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check if both of since_id and max_id is specified
 | 
				
			||||||
 | 
						if (since !== null && max !== null) {
 | 
				
			||||||
 | 
							return rej('cannot set since_id and max_id');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Construct query
 | 
				
			||||||
 | 
						const query = {
 | 
				
			||||||
 | 
							mentions: user._id
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const sort = {
 | 
				
			||||||
 | 
							_id: -1
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (following) {
 | 
				
			||||||
 | 
							const followingIds = await getFriends(user._id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							query.user_id = {
 | 
				
			||||||
 | 
								$in: followingIds
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (since) {
 | 
				
			||||||
 | 
							sort._id = 1;
 | 
				
			||||||
 | 
							query._id = {
 | 
				
			||||||
 | 
								$gt: new mongo.ObjectID(since)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						} else if (max) {
 | 
				
			||||||
 | 
							query._id = {
 | 
				
			||||||
 | 
								$lt: new mongo.ObjectID(max)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Issue query
 | 
				
			||||||
 | 
						const mentions = await Post
 | 
				
			||||||
 | 
							.find(query, {}, {
 | 
				
			||||||
 | 
								limit: limit,
 | 
				
			||||||
 | 
								sort: sort
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						res(await Promise.all(mentions.map(async mention =>
 | 
				
			||||||
 | 
							await serialize(mention, user)
 | 
				
			||||||
 | 
						)));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										73
									
								
								src/api/endpoints/posts/replies.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,73 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import Post from '../../models/post';
 | 
				
			||||||
 | 
					import serialize from '../../serializers/post';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Show a replies of a post
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'post_id' parameter
 | 
				
			||||||
 | 
						const postId = params.post_id;
 | 
				
			||||||
 | 
						if (postId === undefined || postId === null) {
 | 
				
			||||||
 | 
							return rej('post_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'limit' parameter
 | 
				
			||||||
 | 
						let limit = params.limit;
 | 
				
			||||||
 | 
						if (limit !== undefined && limit !== null) {
 | 
				
			||||||
 | 
							limit = parseInt(limit, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// From 1 to 100
 | 
				
			||||||
 | 
							if (!(1 <= limit && limit <= 100)) {
 | 
				
			||||||
 | 
								return rej('invalid limit range');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							limit = 10;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'offset' parameter
 | 
				
			||||||
 | 
						let offset = params.offset;
 | 
				
			||||||
 | 
						if (offset !== undefined && offset !== null) {
 | 
				
			||||||
 | 
							offset = parseInt(offset, 10);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							offset = 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'sort' parameter
 | 
				
			||||||
 | 
						let sort = params.sort || 'desc';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Lookup post
 | 
				
			||||||
 | 
						const post = await Post.findOne({
 | 
				
			||||||
 | 
							_id: new mongo.ObjectID(postId)
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (post === null) {
 | 
				
			||||||
 | 
							return rej('post not found', 'POST_NOT_FOUND');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Issue query
 | 
				
			||||||
 | 
						const replies = await Post
 | 
				
			||||||
 | 
							.find({ reply_to_id: post._id }, {}, {
 | 
				
			||||||
 | 
								limit: limit,
 | 
				
			||||||
 | 
								skip: offset,
 | 
				
			||||||
 | 
								sort: {
 | 
				
			||||||
 | 
									_id: sort == 'asc' ? 1 : -1
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						res(await Promise.all(replies.map(async post =>
 | 
				
			||||||
 | 
							await serialize(post, user))));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										85
									
								
								src/api/endpoints/posts/reposts.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,85 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import Post from '../../models/post';
 | 
				
			||||||
 | 
					import serialize from '../../serializers/post';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Show a reposts of a post
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'post_id' parameter
 | 
				
			||||||
 | 
						const postId = params.post_id;
 | 
				
			||||||
 | 
						if (postId === undefined || postId === null) {
 | 
				
			||||||
 | 
							return rej('post_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'limit' parameter
 | 
				
			||||||
 | 
						let limit = params.limit;
 | 
				
			||||||
 | 
						if (limit !== undefined && limit !== null) {
 | 
				
			||||||
 | 
							limit = parseInt(limit, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// From 1 to 100
 | 
				
			||||||
 | 
							if (!(1 <= limit && limit <= 100)) {
 | 
				
			||||||
 | 
								return rej('invalid limit range');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							limit = 10;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const since = params.since_id || null;
 | 
				
			||||||
 | 
						const max = params.max_id || null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check if both of since_id and max_id is specified
 | 
				
			||||||
 | 
						if (since !== null && max !== null) {
 | 
				
			||||||
 | 
							return rej('cannot set since_id and max_id');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Lookup post
 | 
				
			||||||
 | 
						const post = await Post.findOne({
 | 
				
			||||||
 | 
							_id: new mongo.ObjectID(postId)
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (post === null) {
 | 
				
			||||||
 | 
							return rej('post not found', 'POST_NOT_FOUND');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Construct query
 | 
				
			||||||
 | 
						const sort = {
 | 
				
			||||||
 | 
							created_at: -1
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						const query = {
 | 
				
			||||||
 | 
							repost_id: post._id
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						if (since !== null) {
 | 
				
			||||||
 | 
							sort.created_at = 1;
 | 
				
			||||||
 | 
							query._id = {
 | 
				
			||||||
 | 
								$gt: new mongo.ObjectID(since)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						} else if (max !== null) {
 | 
				
			||||||
 | 
							query._id = {
 | 
				
			||||||
 | 
								$lt: new mongo.ObjectID(max)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Issue query
 | 
				
			||||||
 | 
						const reposts = await Post
 | 
				
			||||||
 | 
							.find(query, {}, {
 | 
				
			||||||
 | 
								limit: limit,
 | 
				
			||||||
 | 
								sort: sort
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						res(await Promise.all(reposts.map(async post =>
 | 
				
			||||||
 | 
							await serialize(post, user))));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										138
									
								
								src/api/endpoints/posts/search.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,138 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import Post from '../../models/post';
 | 
				
			||||||
 | 
					import serialize from '../../serializers/post';
 | 
				
			||||||
 | 
					const escapeRegexp = require('escape-regexp');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Search a post
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} me
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, me) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'query' parameter
 | 
				
			||||||
 | 
						let query = params.query;
 | 
				
			||||||
 | 
						if (query === undefined || query === null || query.trim() === '') {
 | 
				
			||||||
 | 
							return rej('query is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'offset' parameter
 | 
				
			||||||
 | 
						let offset = params.offset;
 | 
				
			||||||
 | 
						if (offset !== undefined && offset !== null) {
 | 
				
			||||||
 | 
							offset = parseInt(offset, 10);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							offset = 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'max' parameter
 | 
				
			||||||
 | 
						let max = params.max;
 | 
				
			||||||
 | 
						if (max !== undefined && max !== null) {
 | 
				
			||||||
 | 
							max = parseInt(max, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// From 1 to 30
 | 
				
			||||||
 | 
							if (!(1 <= max && max <= 30)) {
 | 
				
			||||||
 | 
								return rej('invalid max range');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							max = 10;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If Elasticsearch is available, search by it
 | 
				
			||||||
 | 
						// 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
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 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));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Fetxh found posts
 | 
				
			||||||
 | 
							const posts = await Post
 | 
				
			||||||
 | 
								.find({
 | 
				
			||||||
 | 
									_id: {
 | 
				
			||||||
 | 
										$in: hits
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}, {}, {
 | 
				
			||||||
 | 
									sort: {
 | 
				
			||||||
 | 
										_id: -1
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							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))));
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										40
									
								
								src/api/endpoints/posts/show.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,40 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import Post from '../../models/post';
 | 
				
			||||||
 | 
					import serialize from '../../serializers/post';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Show a post
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'post_id' parameter
 | 
				
			||||||
 | 
						const postId = params.post_id;
 | 
				
			||||||
 | 
						if (postId === undefined || postId === null) {
 | 
				
			||||||
 | 
							return rej('post_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get post
 | 
				
			||||||
 | 
						const post = await Post.findOne({
 | 
				
			||||||
 | 
							_id: new mongo.ObjectID(postId)
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (post === null) {
 | 
				
			||||||
 | 
							return rej('post not found');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						res(await serialize(post, user, {
 | 
				
			||||||
 | 
							serializeReplyTo: true,
 | 
				
			||||||
 | 
							includeIsLiked: true
 | 
				
			||||||
 | 
						}));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										78
									
								
								src/api/endpoints/posts/timeline.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,78 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import Post from '../../models/post';
 | 
				
			||||||
 | 
					import getFriends from '../../common/get-friends';
 | 
				
			||||||
 | 
					import serialize from '../../serializers/post';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get timeline of myself
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} user
 | 
				
			||||||
 | 
					 * @param {Object} app
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, user, app) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'limit' parameter
 | 
				
			||||||
 | 
						let limit = params.limit;
 | 
				
			||||||
 | 
						if (limit !== undefined && limit !== null) {
 | 
				
			||||||
 | 
							limit = parseInt(limit, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// From 1 to 100
 | 
				
			||||||
 | 
							if (!(1 <= limit && limit <= 100)) {
 | 
				
			||||||
 | 
								return rej('invalid limit range');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							limit = 10;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const since = params.since_id || null;
 | 
				
			||||||
 | 
						const max = params.max_id || null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check if both of since_id and max_id is specified
 | 
				
			||||||
 | 
						if (since !== null && max !== null) {
 | 
				
			||||||
 | 
							return rej('cannot set since_id and max_id');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// ID list of the user itself and other users who the user follows
 | 
				
			||||||
 | 
						const followingIds = await getFriends(user._id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Construct query
 | 
				
			||||||
 | 
						const sort = {
 | 
				
			||||||
 | 
							_id: -1
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						const query = {
 | 
				
			||||||
 | 
							user_id: {
 | 
				
			||||||
 | 
								$in: followingIds
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						if (since !== null) {
 | 
				
			||||||
 | 
							sort._id = 1;
 | 
				
			||||||
 | 
							query._id = {
 | 
				
			||||||
 | 
								$gt: new mongo.ObjectID(since)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						} else if (max !== null) {
 | 
				
			||||||
 | 
							query._id = {
 | 
				
			||||||
 | 
								$lt: new mongo.ObjectID(max)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Issue query
 | 
				
			||||||
 | 
						const timeline = await Post
 | 
				
			||||||
 | 
							.find(query, {}, {
 | 
				
			||||||
 | 
								limit: limit,
 | 
				
			||||||
 | 
								sort: sort
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						res(await Promise.all(timeline.map(async post =>
 | 
				
			||||||
 | 
							await serialize(post, user)
 | 
				
			||||||
 | 
						)));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										41
									
								
								src/api/endpoints/username/available.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,41 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import User from '../../models/user';
 | 
				
			||||||
 | 
					import { validateUsername } from '../../models/user';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Check available username
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = async (params) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'username' parameter
 | 
				
			||||||
 | 
						const username = params.username;
 | 
				
			||||||
 | 
						if (username == null || username == '') {
 | 
				
			||||||
 | 
							return rej('username-is-required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Validate username
 | 
				
			||||||
 | 
						if (!validateUsername(username)) {
 | 
				
			||||||
 | 
							return rej('invalid-username');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get exist
 | 
				
			||||||
 | 
						const exist = await User
 | 
				
			||||||
 | 
							.count({
 | 
				
			||||||
 | 
								username_lower: username.toLowerCase()
 | 
				
			||||||
 | 
							}, {
 | 
				
			||||||
 | 
								limit: 1
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Reply
 | 
				
			||||||
 | 
						res({
 | 
				
			||||||
 | 
							available: exist === 0
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										67
									
								
								src/api/endpoints/users.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,67 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import User from '../models/user';
 | 
				
			||||||
 | 
					import serialize from '../serializers/user';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Lists all users
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} me
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, me) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'limit' parameter
 | 
				
			||||||
 | 
						let limit = params.limit;
 | 
				
			||||||
 | 
						if (limit !== undefined && limit !== null) {
 | 
				
			||||||
 | 
							limit = parseInt(limit, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// From 1 to 100
 | 
				
			||||||
 | 
							if (!(1 <= limit && limit <= 100)) {
 | 
				
			||||||
 | 
								return rej('invalid limit range');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							limit = 10;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const since = params.since_id || null;
 | 
				
			||||||
 | 
						const max = params.max_id || null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check if both of since_id and max_id is specified
 | 
				
			||||||
 | 
						if (since !== null && max !== null) {
 | 
				
			||||||
 | 
							return rej('cannot set since_id and max_id');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Construct query
 | 
				
			||||||
 | 
						const sort = {
 | 
				
			||||||
 | 
							created_at: -1
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						const query = {};
 | 
				
			||||||
 | 
						if (since !== null) {
 | 
				
			||||||
 | 
							sort.created_at = 1;
 | 
				
			||||||
 | 
							query._id = {
 | 
				
			||||||
 | 
								$gt: new mongo.ObjectID(since)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						} else if (max !== null) {
 | 
				
			||||||
 | 
							query._id = {
 | 
				
			||||||
 | 
								$lt: new mongo.ObjectID(max)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Issue query
 | 
				
			||||||
 | 
						const users = await User
 | 
				
			||||||
 | 
							.find(query, {}, {
 | 
				
			||||||
 | 
								limit: limit,
 | 
				
			||||||
 | 
								sort: sort
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						res(await Promise.all(users.map(async user =>
 | 
				
			||||||
 | 
							await serialize(user, me))));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										102
									
								
								src/api/endpoints/users/followers.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,102 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					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 {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} me
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, me) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'user_id' parameter
 | 
				
			||||||
 | 
						const userId = params.user_id;
 | 
				
			||||||
 | 
						if (userId === undefined || userId === null) {
 | 
				
			||||||
 | 
							return rej('user_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'iknow' parameter
 | 
				
			||||||
 | 
						const iknow = params.iknow === 'true';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'limit' parameter
 | 
				
			||||||
 | 
						let limit = params.limit;
 | 
				
			||||||
 | 
						if (limit !== undefined && limit !== null) {
 | 
				
			||||||
 | 
							limit = parseInt(limit, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// From 1 to 100
 | 
				
			||||||
 | 
							if (!(1 <= limit && limit <= 100)) {
 | 
				
			||||||
 | 
								return rej('invalid limit range');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							limit = 10;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'cursor' parameter
 | 
				
			||||||
 | 
						const cursor = params.cursor || null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Lookup user
 | 
				
			||||||
 | 
						const user = await User.findOne({
 | 
				
			||||||
 | 
							_id: new mongo.ObjectID(userId)
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (user === null) {
 | 
				
			||||||
 | 
							return rej('user not found');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Construct query
 | 
				
			||||||
 | 
						const query = {
 | 
				
			||||||
 | 
							followee_id: user._id,
 | 
				
			||||||
 | 
							deleted_at: { $exists: false }
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// ログインしていてかつ iknow フラグがあるとき
 | 
				
			||||||
 | 
						if (me && iknow) {
 | 
				
			||||||
 | 
							// Get my friends
 | 
				
			||||||
 | 
							const myFriends = await getFriends(me._id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							query.follower_id = {
 | 
				
			||||||
 | 
								$in: myFriends
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// カーソルが指定されている場合
 | 
				
			||||||
 | 
						if (cursor) {
 | 
				
			||||||
 | 
							query._id = {
 | 
				
			||||||
 | 
								$lt: new mongo.ObjectID(cursor)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get followers
 | 
				
			||||||
 | 
						const following = await Following
 | 
				
			||||||
 | 
							.find(query, {}, {
 | 
				
			||||||
 | 
								limit: limit + 1,
 | 
				
			||||||
 | 
								sort: { _id: -1 }
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 「次のページ」があるかどうか
 | 
				
			||||||
 | 
						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,
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										102
									
								
								src/api/endpoints/users/following.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,102 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					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 {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} me
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, me) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'user_id' parameter
 | 
				
			||||||
 | 
						const userId = params.user_id;
 | 
				
			||||||
 | 
						if (userId === undefined || userId === null) {
 | 
				
			||||||
 | 
							return rej('user_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'iknow' parameter
 | 
				
			||||||
 | 
						const iknow = params.iknow === 'true';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'limit' parameter
 | 
				
			||||||
 | 
						let limit = params.limit;
 | 
				
			||||||
 | 
						if (limit !== undefined && limit !== null) {
 | 
				
			||||||
 | 
							limit = parseInt(limit, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// From 1 to 100
 | 
				
			||||||
 | 
							if (!(1 <= limit && limit <= 100)) {
 | 
				
			||||||
 | 
								return rej('invalid limit range');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							limit = 10;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'cursor' parameter
 | 
				
			||||||
 | 
						const cursor = params.cursor || null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Lookup user
 | 
				
			||||||
 | 
						const user = await User.findOne({
 | 
				
			||||||
 | 
							_id: new mongo.ObjectID(userId)
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (user === null) {
 | 
				
			||||||
 | 
							return rej('user not found');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Construct query
 | 
				
			||||||
 | 
						const query = {
 | 
				
			||||||
 | 
							follower_id: user._id,
 | 
				
			||||||
 | 
							deleted_at: { $exists: false }
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// ログインしていてかつ iknow フラグがあるとき
 | 
				
			||||||
 | 
						if (me && iknow) {
 | 
				
			||||||
 | 
							// Get my friends
 | 
				
			||||||
 | 
							const myFriends = await getFriends(me._id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							query.followee_id = {
 | 
				
			||||||
 | 
								$in: myFriends
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// カーソルが指定されている場合
 | 
				
			||||||
 | 
						if (cursor) {
 | 
				
			||||||
 | 
							query._id = {
 | 
				
			||||||
 | 
								$lt: new mongo.ObjectID(cursor)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get followers
 | 
				
			||||||
 | 
						const following = await Following
 | 
				
			||||||
 | 
							.find(query, {}, {
 | 
				
			||||||
 | 
								limit: limit + 1,
 | 
				
			||||||
 | 
								sort: { _id: -1 }
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 「次のページ」があるかどうか
 | 
				
			||||||
 | 
						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,
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										114
									
								
								src/api/endpoints/users/posts.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,114 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import Post from '../../models/post';
 | 
				
			||||||
 | 
					import User from '../../models/user';
 | 
				
			||||||
 | 
					import serialize from '../../serializers/post';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get posts of a user
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} me
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, me) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'user_id' parameter
 | 
				
			||||||
 | 
						const userId = params.user_id;
 | 
				
			||||||
 | 
						if (userId === undefined || userId === null) {
 | 
				
			||||||
 | 
							return rej('user_id is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'with_replies' parameter
 | 
				
			||||||
 | 
						let withReplies = params.with_replies;
 | 
				
			||||||
 | 
						if (withReplies !== undefined && withReplies !== null && withReplies === 'true') {
 | 
				
			||||||
 | 
							withReplies = true;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							withReplies = false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'with_media' parameter
 | 
				
			||||||
 | 
						let withMedia = params.with_media;
 | 
				
			||||||
 | 
						if (withMedia !== undefined && withMedia !== null && withMedia === 'true') {
 | 
				
			||||||
 | 
							withMedia = true;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							withMedia = false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'limit' parameter
 | 
				
			||||||
 | 
						let limit = params.limit;
 | 
				
			||||||
 | 
						if (limit !== undefined && limit !== null) {
 | 
				
			||||||
 | 
							limit = parseInt(limit, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// From 1 to 100
 | 
				
			||||||
 | 
							if (!(1 <= limit && limit <= 100)) {
 | 
				
			||||||
 | 
								return rej('invalid limit range');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							limit = 10;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const since = params.since_id || null;
 | 
				
			||||||
 | 
						const max = params.max_id || null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check if both of since_id and max_id is specified
 | 
				
			||||||
 | 
						if (since !== null && max !== null) {
 | 
				
			||||||
 | 
							return rej('cannot set since_id and max_id');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Lookup user
 | 
				
			||||||
 | 
						const user = await User.findOne({
 | 
				
			||||||
 | 
							_id: new mongo.ObjectID(userId)
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (user === null) {
 | 
				
			||||||
 | 
							return rej('user not found');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Construct query
 | 
				
			||||||
 | 
						const sort = {
 | 
				
			||||||
 | 
							_id: -1
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						const query = {
 | 
				
			||||||
 | 
							user_id: user._id
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
						if (since !== null) {
 | 
				
			||||||
 | 
							sort._id = 1;
 | 
				
			||||||
 | 
							query._id = {
 | 
				
			||||||
 | 
								$gt: new mongo.ObjectID(since)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						} else if (max !== null) {
 | 
				
			||||||
 | 
							query._id = {
 | 
				
			||||||
 | 
								$lt: new mongo.ObjectID(max)
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!withReplies) {
 | 
				
			||||||
 | 
							query.reply_to_id = null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (withMedia) {
 | 
				
			||||||
 | 
							query.media_ids = {
 | 
				
			||||||
 | 
								$exists: true,
 | 
				
			||||||
 | 
								$ne: null
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Issue query
 | 
				
			||||||
 | 
						const posts = await Post
 | 
				
			||||||
 | 
							.find(query, {}, {
 | 
				
			||||||
 | 
								limit: limit,
 | 
				
			||||||
 | 
								sort: sort
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						res(await Promise.all(posts.map(async (post) =>
 | 
				
			||||||
 | 
							await serialize(post, me)
 | 
				
			||||||
 | 
						)));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										61
									
								
								src/api/endpoints/users/recommendation.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,61 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import User from '../../models/user';
 | 
				
			||||||
 | 
					import serialize from '../../serializers/user';
 | 
				
			||||||
 | 
					import getFriends from '../../common/get-friends';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Get recommended users
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} me
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, me) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'limit' parameter
 | 
				
			||||||
 | 
						let limit = params.limit;
 | 
				
			||||||
 | 
						if (limit !== undefined && limit !== null) {
 | 
				
			||||||
 | 
							limit = parseInt(limit, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// From 1 to 100
 | 
				
			||||||
 | 
							if (!(1 <= limit && limit <= 100)) {
 | 
				
			||||||
 | 
								return rej('invalid limit range');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							limit = 10;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'offset' parameter
 | 
				
			||||||
 | 
						let offset = params.offset;
 | 
				
			||||||
 | 
						if (offset !== undefined && offset !== null) {
 | 
				
			||||||
 | 
							offset = parseInt(offset, 10);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							offset = 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 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
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}, {}, {
 | 
				
			||||||
 | 
								limit: limit,
 | 
				
			||||||
 | 
								skip: offset,
 | 
				
			||||||
 | 
								sort: {
 | 
				
			||||||
 | 
									followers_count: -1
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						res(await Promise.all(users.map(async user =>
 | 
				
			||||||
 | 
							await serialize(user, me, { detail: true }))));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										116
									
								
								src/api/endpoints/users/search.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,116 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import User from '../../models/user';
 | 
				
			||||||
 | 
					import serialize from '../../serializers/user';
 | 
				
			||||||
 | 
					const escapeRegexp = require('escape-regexp');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Search a user
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} me
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, me) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'query' parameter
 | 
				
			||||||
 | 
						let query = params.query;
 | 
				
			||||||
 | 
						if (query === undefined || query === null || query.trim() === '') {
 | 
				
			||||||
 | 
							return rej('query is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'offset' parameter
 | 
				
			||||||
 | 
						let offset = params.offset;
 | 
				
			||||||
 | 
						if (offset !== undefined && offset !== null) {
 | 
				
			||||||
 | 
							offset = parseInt(offset, 10);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							offset = 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'max' parameter
 | 
				
			||||||
 | 
						let max = params.max;
 | 
				
			||||||
 | 
						if (max !== undefined && max !== null) {
 | 
				
			||||||
 | 
							max = parseInt(max, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// From 1 to 30
 | 
				
			||||||
 | 
							if (!(1 <= max && max <= 30)) {
 | 
				
			||||||
 | 
								return rej('invalid max range');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							max = 10;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// If Elasticsearch is available, search by it
 | 
				
			||||||
 | 
						// 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.toLowerCase())
 | 
				
			||||||
 | 
								}, {
 | 
				
			||||||
 | 
									name: new RegExp(escapedQuery)
 | 
				
			||||||
 | 
								}]
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 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
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
								.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Serialize
 | 
				
			||||||
 | 
							res(await Promise.all(users.map(async user =>
 | 
				
			||||||
 | 
								await serialize(user, me, { detail: true }))));
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										65
									
								
								src/api/endpoints/users/search_by_username.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,65 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import User from '../../models/user';
 | 
				
			||||||
 | 
					import serialize from '../../serializers/user';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Search a user by username
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} me
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, me) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'query' parameter
 | 
				
			||||||
 | 
						let query = params.query;
 | 
				
			||||||
 | 
						if (query === undefined || query === null || query.trim() === '') {
 | 
				
			||||||
 | 
							return rej('query is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						query = query.trim();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!/^[a-zA-Z0-9-]+$/.test(query)) {
 | 
				
			||||||
 | 
							return rej('invalid query');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'limit' parameter
 | 
				
			||||||
 | 
						let limit = params.limit;
 | 
				
			||||||
 | 
						if (limit !== undefined && limit !== null) {
 | 
				
			||||||
 | 
							limit = parseInt(limit, 10);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// From 1 to 100
 | 
				
			||||||
 | 
							if (!(1 <= limit && limit <= 100)) {
 | 
				
			||||||
 | 
								return rej('invalid limit range');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							limit = 10;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'offset' parameter
 | 
				
			||||||
 | 
						let offset = params.offset;
 | 
				
			||||||
 | 
						if (offset !== undefined && offset !== null) {
 | 
				
			||||||
 | 
							offset = parseInt(offset, 10);
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							offset = 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const users = await User
 | 
				
			||||||
 | 
							.find({
 | 
				
			||||||
 | 
								username_lower: new RegExp(query.toLowerCase())
 | 
				
			||||||
 | 
							}, {
 | 
				
			||||||
 | 
								limit: limit,
 | 
				
			||||||
 | 
								skip: offset
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.toArray();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Serialize
 | 
				
			||||||
 | 
						res(await Promise.all(users.map(async user =>
 | 
				
			||||||
 | 
							await serialize(user, me, { detail: true }))));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										49
									
								
								src/api/endpoints/users/show.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,49 @@
 | 
				
			||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Module dependencies
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import User from '../../models/user';
 | 
				
			||||||
 | 
					import serialize from '../../serializers/user';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Show a user
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Object} params
 | 
				
			||||||
 | 
					 * @param {Object} me
 | 
				
			||||||
 | 
					 * @return {Promise<object>}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports = (params, me) =>
 | 
				
			||||||
 | 
						new Promise(async (res, rej) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// Get 'user_id' parameter
 | 
				
			||||||
 | 
						let userId = params.user_id;
 | 
				
			||||||
 | 
						if (userId === undefined || userId === null || userId === '') {
 | 
				
			||||||
 | 
							userId = null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Get 'username' parameter
 | 
				
			||||||
 | 
						let username = params.username;
 | 
				
			||||||
 | 
						if (username === undefined || username === null || username === '') {
 | 
				
			||||||
 | 
							username = null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (userId === null && username === null) {
 | 
				
			||||||
 | 
							return rej('user_id or username is required');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Lookup user
 | 
				
			||||||
 | 
						const user = userId !== null
 | 
				
			||||||
 | 
							? await User.findOne({ _id: new mongo.ObjectID(userId) })
 | 
				
			||||||
 | 
							: await User.findOne({ username_lower: username.toLowerCase() });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (user === null) {
 | 
				
			||||||
 | 
							return rej('user not found');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Send response
 | 
				
			||||||
 | 
						res(await serialize(user, me, {
 | 
				
			||||||
 | 
							detail: true
 | 
				
			||||||
 | 
						}));
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										36
									
								
								src/api/event.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,36 @@
 | 
				
			||||||
 | 
					import * as mongo from 'mongodb';
 | 
				
			||||||
 | 
					import * as redis from 'redis';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private publish(channel: string, type: string, value?: Object): void {
 | 
				
			||||||
 | 
							const message = value == null ?
 | 
				
			||||||
 | 
								{ type: type } :
 | 
				
			||||||
 | 
								{ type: type, body: value };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.redisClient.publish(`misskey:${channel}`, JSON.stringify(message));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public publishUserStream(userId: ID, type: string, value?: Object): void {
 | 
				
			||||||
 | 
							this.publish(`user-stream:${userId}`, type, typeof value === 'undefined' ? null : value);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public publishMessagingStream(userId: ID, otherpartyId: ID, type: string, value?: Object): void {
 | 
				
			||||||
 | 
							this.publish(`messaging-stream:${userId}-${otherpartyId}`, type, typeof value === 'undefined' ? null : value);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ev = new MisskeyEvent();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default ev.publishUserStream.bind(ev);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const publishMessagingStream = ev.publishMessagingStream.bind(ev);
 | 
				
			||||||
							
								
								
									
										69
									
								
								src/api/limitter.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,69 @@
 | 
				
			||||||
 | 
					import * as Limiter from 'ratelimiter';
 | 
				
			||||||
 | 
					import limiterDB from '../db/redis';
 | 
				
			||||||
 | 
					import { IEndpoint } from './endpoints';
 | 
				
			||||||
 | 
					import { IAuthContext } from './authenticate';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default (endpoint: IEndpoint, ctx: IAuthContext) => new Promise((ok, reject) => {
 | 
				
			||||||
 | 
						const limitKey = endpoint.hasOwnProperty('limitKey')
 | 
				
			||||||
 | 
							? endpoint.limitKey
 | 
				
			||||||
 | 
							: endpoint.name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const hasMinInterval =
 | 
				
			||||||
 | 
							endpoint.hasOwnProperty('minInterval');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const hasRateLimit =
 | 
				
			||||||
 | 
							endpoint.hasOwnProperty('limitDuration') &&
 | 
				
			||||||
 | 
							endpoint.hasOwnProperty('limitMax');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (hasMinInterval) {
 | 
				
			||||||
 | 
							min();
 | 
				
			||||||
 | 
						} else if (hasRateLimit) {
 | 
				
			||||||
 | 
							max();
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							ok();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Short-term limit
 | 
				
			||||||
 | 
						function min(): void {
 | 
				
			||||||
 | 
							const minIntervalLimiter = new Limiter({
 | 
				
			||||||
 | 
								id: `${ctx.user._id}:${limitKey}:min`,
 | 
				
			||||||
 | 
								duration: endpoint.minInterval,
 | 
				
			||||||
 | 
								max: 1,
 | 
				
			||||||
 | 
								db: limiterDB
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							minIntervalLimiter.get((limitErr, limit) => {
 | 
				
			||||||
 | 
								if (limitErr) {
 | 
				
			||||||
 | 
									reject('ERR');
 | 
				
			||||||
 | 
								} else if (limit.remaining === 0) {
 | 
				
			||||||
 | 
									reject('BRIEF_REQUEST_INTERVAL');
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									if (hasRateLimit) {
 | 
				
			||||||
 | 
										max();
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										ok();
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Long term limit
 | 
				
			||||||
 | 
						function max(): void {
 | 
				
			||||||
 | 
							const limiter = new Limiter({
 | 
				
			||||||
 | 
								id: `${ctx.user._id}:${limitKey}`,
 | 
				
			||||||
 | 
								duration: endpoint.limitDuration,
 | 
				
			||||||
 | 
								max: endpoint.limitMax,
 | 
				
			||||||
 | 
								db: limiterDB
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							limiter.get((limitErr, limit) => {
 | 
				
			||||||
 | 
								if (limitErr) {
 | 
				
			||||||
 | 
									reject('ERR');
 | 
				
			||||||
 | 
								} else if (limit.remaining === 0) {
 | 
				
			||||||
 | 
									reject('RATE_LIMIT_EXCEEDED');
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									ok();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										7
									
								
								src/api/models/app.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1,7 @@
 | 
				
			||||||
 | 
					const collection = global.db.collection('apps');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					collection.createIndex('name_id');
 | 
				
			||||||
 | 
					collection.createIndex('name_id_lower');
 | 
				
			||||||
 | 
					collection.createIndex('secret');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default collection;
 | 
				
			||||||
							
								
								
									
										1
									
								
								src/api/models/appdata.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					export default global.db.collection('appdata');
 | 
				
			||||||