Compare commits

...

43 Commits

Author SHA1 Message Date
f6f8cdbcf2 11.0.0-beta.13 2019-04-14 02:36:21 +09:00
f46f53b8a3 Refactor 2019-04-14 02:36:00 +09:00
a2fcae4383 Fix bug 2019-04-14 02:33:50 +09:00
483f043768 11.0.0-beta.12 2019-04-14 02:24:27 +09:00
f8e0f4f21f Fix bug 2019-04-14 02:21:57 +09:00
9c3613e96d Improve error handling 2019-04-14 02:19:59 +09:00
d9cacdc86d Update meid.ts 2019-04-14 01:47:46 +09:00
aa3d2deeaa Add meid 2019-04-14 01:40:29 +09:00
e64912545a Update id generation methods 2019-04-14 01:08:26 +09:00
b247be80cc 11.0.0-beta.11 2019-04-13 20:02:47 +09:00
343f2f1f33 Fix bug 2019-04-13 20:02:31 +09:00
2d590df900 Clean up 2019-04-13 20:01:32 +09:00
ba56b2b9fd Update CHANGELOG.md 2019-04-13 19:40:03 +09:00
bf472b0c5e refactor 2019-04-13 19:36:57 +09:00
f7961f34c5 Update CHANGELOG.md 2019-04-13 19:23:32 +09:00
e369031a28 Redis必須に 2019-04-13 19:19:32 +09:00
186d7bbfd9 Fix bug 2019-04-13 19:08:41 +09:00
60a11f8da5 11.0.0-beta.10 2019-04-13 18:58:48 +09:00
1181fcdceb Fix bug 2019-04-13 18:58:29 +09:00
8cb9852058 リプライ先をエラー時に無視すると本来は投票なのに投票じゃないと扱われおかしくなるのでエラーにするように 2019-04-13 18:45:07 +09:00
53d46d1cbe Fix error 2019-04-13 18:23:32 +09:00
275e1c8de9 Refactor 2019-04-13 18:17:27 +09:00
d46eca4c87 Refactor 2019-04-13 18:14:32 +09:00
2927fb1597 11.0.0-beta.9 2019-04-13 17:27:17 +09:00
8c72e011d2 Fix bug 2019-04-13 17:26:38 +09:00
69662e24c3 Fix bug 2019-04-13 17:24:56 +09:00
96099ffe98 11.0.0-beta.8 2019-04-13 16:55:13 +09:00
ae16b45c11 Fix bug 2019-04-13 16:54:21 +09:00
cfee87d3ef 11.0.0-beta.7 2019-04-13 15:18:27 +09:00
5fcf5bc635 Fix bug 2019-04-13 15:18:12 +09:00
14bcb813cc Update migrate.ts 2019-04-13 15:09:16 +09:00
6af19794b6 11.0.0-beta.6 2019-04-13 15:03:56 +09:00
8316186695 Clean packed responses 2019-04-13 15:02:15 +09:00
85d3023cd5 Clean packed responses 2019-04-13 14:55:59 +09:00
78414dee29 Add note 2019-04-13 14:45:51 +09:00
084135141f typo 2019-04-13 14:37:45 +09:00
467a21f028 Update CONTRIBUTING.md 2019-04-13 14:36:35 +09:00
6e284c44d6 Update CONTRIBUTING.md 2019-04-13 14:34:34 +09:00
daf9a449e8 Update CONTRIBUTING.md 2019-04-13 14:31:05 +09:00
960268fd33 typo 2019-04-13 14:17:50 +09:00
8c331da315 Fix bug 2019-04-13 14:11:15 +09:00
b0d626d862 Update CONTRIBUTING.md 2019-04-13 14:04:29 +09:00
a51fbd7316 Suppress errors 2019-04-13 04:00:02 +09:00
44 changed files with 256 additions and 259 deletions

View File

@ -65,10 +65,10 @@ db:
# ┌─────────────────────┐ # ┌─────────────────────┐
#───┘ Redis configuration └───────────────────────────────────── #───┘ Redis configuration └─────────────────────────────────────
#redis: redis:
# host: localhost host: localhost
# port: 6379 port: 6379
# pass: example-pass #pass: example-pass
# ┌─────────────────────────────┐ # ┌─────────────────────────────┐
#───┘ Elasticsearch configuration └───────────────────────────── #───┘ Elasticsearch configuration └─────────────────────────────
@ -127,28 +127,12 @@ drive:
# change it according to your preferences. # change it according to your preferences.
# Available methods: # Available methods:
# aid1 ... Use AID for ID generation (with random 1 char) # aid ... Short, Millisecond accuracy
# aid2 ... Use AID for ID generation (with random 2 chars) # meid ... Similar to ObjectID, Millisecond accuracy
# aid3 ... Use AID for ID generation (with random 3 chars) # ulid ... Millisecond accuracy
# aid4 ... Use AID for ID generation (with random 4 chars) # objectid ... This is left for backward compatibility
# ulid ... Use ulid for ID generation
# objectid ... This is left for backward compatibility.
# AID(n) is the original ID generation method. id: 'aid'
# The trailing n represents the number of random characters that
# will be suffixed.
# The larger n is the safer. If n is small, the possibility of
# collision at the same time increases, but there are also
# advantages such as shortening of the URL.
# ULID: Universally Unique Lexicographically Sortable Identifier.
# for more details: https://github.com/ulid/spec
# * Normally, AID should be sufficient.
# ObjectID is the method used in previous versions of Misskey.
# * Choose this if you are migrating from a previous Misskey.
id: 'aid2'
# ┌─────────────────────┐ # ┌─────────────────────┐
#───┘ Other configuration └───────────────────────────────────── #───┘ Other configuration └─────────────────────────────────────

View File

@ -7,7 +7,8 @@ If you encounter any problems with updating, please try the following:
11.0.0 11.0.0
---------- ----------
* データベースがMongoDBからPostgreSQLに変更されました * **データベースがMongoDBからPostgreSQLに変更されました**
* **Redisが必須に**
* アカウントを完全に削除できるように * アカウントを完全に削除できるように
* ミュート/ブロック時にそのユーザーの投稿のウォッチをすべて解除するように * ミュート/ブロック時にそのユーザーの投稿のウォッチをすべて解除するように
* フォロー申請数が実際より1すくなくなる問題を修正 * フォロー申請数が実際より1すくなくなる問題を修正
@ -19,6 +20,15 @@ If you encounter any problems with updating, please try the following:
### APIの破壊的変更 ### APIの破壊的変更
* v10時点で deprecated だったパラメータなどを削除 * v10時点で deprecated だったパラメータなどを削除
* ユーザーリストの title が name に * ユーザーリストの title が name に
* リバーシの対局の`settings`プロパティがなくなり、その中にあったプロパティがすべて上の階層に
* 例えば`game.settings.map``game.map`になる
### 既知の問題
* アプリが作成できない
* 依存ライブラリの問題と思わるため、対応が難しい
### Migration
coming soon...
10.100.0 10.100.0
---------- ----------

View File

@ -130,6 +130,40 @@ const users = userIds.length > 0 ? await Users.find({
}) : []; }) : [];
``` ```
### 配列のインデックス in SQL
SQLでは配列のインデックスは**1始まり**。
`[a, b, c]`の `a`にアクセスしたいなら`[0]`ではなく`[1]`と書く
### `undefined`にご用心 ### `undefined`にご用心
MongoDBの時とは違い、findOneでレコードを取得する時に対象レコードが存在しない場合 **`undefined`** が返ってくるので注意。 MongoDBの時とは違い、findOneでレコードを取得する時に対象レコードが存在しない場合 **`undefined`** が返ってくるので注意。
MongoDBは`null`で返してきてたので、その感覚で`if (x === null)`とか書くとバグる。代わりに`if (x == null)`と書いてください MongoDBは`null`で返してきてたので、その感覚で`if (x === null)`とか書くとバグる。代わりに`if (x == null)`と書いてください
### 簡素な`undefined`チェック
データベースからレコードを取得するときに、プログラムの流れ的に(ほぼ)絶対`undefined`にはならない場合でも、`undefined`チェックしないとTypeScriptに怒られます。
でもいちいち複数行を費やして、発生するはずのない`undefined`をチェックするのも面倒なので、`ensure`というユーティリティ関数を用意しています。
例えば、
``` ts
const user = await Users.findOne(userId);
// この時点で user の型は User | undefined
if (user == null) {
throw 'missing user';
}
// この時点で user の型は User
```
という処理を`ensure`を使うと
``` ts
const user = await Users.findOne(userId).then(ensure);
// この時点で user の型は User
```
という風に書けます。
もちろん`ensure`内部でエラーを握りつぶすようなことはしておらず、万が一`undefined`だった場合はPromiseがRejectされ後続の処理は実行されません。
``` ts
const user = await Users.findOne(userId).then(ensure);
// 万が一 Users.findOne の結果が undefined だったら、ensure でエラーが発生するので
// この行に到達することは無い
// なので、.then(ensure) は
// if (user == null) {
// throw 'missing user';
// }
// の糖衣構文のような扱いです
```

View File

@ -24,18 +24,13 @@ Please install and setup these softwares:
#### Dependencies :package: #### Dependencies :package:
* **[Node.js](https://nodejs.org/en/)** >= 11.7.0 * **[Node.js](https://nodejs.org/en/)** >= 11.7.0
* **[PostgreSQL](https://www.postgresql.org/)** >= 10 * **[PostgreSQL](https://www.postgresql.org/)** >= 10
* **[Redis](https://redis.io/)**
##### Optional ##### Optional
* [Redis](https://redis.io/)
* Redis is optional, but we strongly recommended to install it
* [Elasticsearch](https://www.elastic.co/) - required to enable the search feature * [Elasticsearch](https://www.elastic.co/) - required to enable the search feature
* [FFmpeg](https://www.ffmpeg.org/) * [FFmpeg](https://www.ffmpeg.org/)
*3.* Setup PostgreSQL *3.* Install Misskey
----------------------------------------------------------------
:)
*4.* Install Misskey
---------------------------------------------------------------- ----------------------------------------------------------------
1. `su - misskey` Connect to misskey user. 1. `su - misskey` Connect to misskey user.
2. `git clone -b master git://github.com/syuilo/misskey.git` Clone the misskey repo from master branch. 2. `git clone -b master git://github.com/syuilo/misskey.git` Clone the misskey repo from master branch.
@ -43,12 +38,12 @@ Please install and setup these softwares:
4. `git checkout $(git tag -l | grep -Ev -- '-(rc|alpha)\.[0-9]+$' | sort -V | tail -n 1)` Checkout to the [latest release](https://github.com/syuilo/misskey/releases/latest) 4. `git checkout $(git tag -l | grep -Ev -- '-(rc|alpha)\.[0-9]+$' | sort -V | tail -n 1)` Checkout to the [latest release](https://github.com/syuilo/misskey/releases/latest)
5. `npm install` Install misskey dependencies. 5. `npm install` Install misskey dependencies.
*5.* Configure Misskey *4.* Configure Misskey
---------------------------------------------------------------- ----------------------------------------------------------------
1. `cp .config/example.yml .config/default.yml` Copy the `.config/example.yml` and rename it to `default.yml`. 1. `cp .config/example.yml .config/default.yml` Copy the `.config/example.yml` and rename it to `default.yml`.
2. Edit `default.yml` 2. Edit `default.yml`
*6.* Build Misskey *5.* Build Misskey
---------------------------------------------------------------- ----------------------------------------------------------------
Build misskey with the following: Build misskey with the following:
@ -64,13 +59,13 @@ If you're still encountering errors about some modules, use node-gyp:
3. `node-gyp build` 3. `node-gyp build`
4. `NODE_ENV=production npm run build` 4. `NODE_ENV=production npm run build`
*7.* Init DB *6.* Init DB
---------------------------------------------------------------- ----------------------------------------------------------------
``` shell ``` shell
npm run init npm run init
``` ```
*8.* That is it. *7.* That is it.
---------------------------------------------------------------- ----------------------------------------------------------------
Well done! Now, you have an environment that run to Misskey. Well done! Now, you have an environment that run to Misskey.

View File

@ -24,18 +24,13 @@ Installez les paquets suivants :
#### Dépendences :package: #### Dépendences :package:
* **[Node.js](https://nodejs.org/en/)** >= 11.7.0 * **[Node.js](https://nodejs.org/en/)** >= 11.7.0
* **[PostgreSQL](https://www.postgresql.org/)** >= 10 * **[PostgreSQL](https://www.postgresql.org/)** >= 10
* **[Redis](https://redis.io/)**
##### Optionnels ##### Optionnels
* [Redis](https://redis.io/)
* Redis est optionnel mais nous vous recommandons vivement de l'installer
* [Elasticsearch](https://www.elastic.co/) - requis pour pouvoir activer la fonctionnalité de recherche * [Elasticsearch](https://www.elastic.co/) - requis pour pouvoir activer la fonctionnalité de recherche
* [FFmpeg](https://www.ffmpeg.org/) * [FFmpeg](https://www.ffmpeg.org/)
*3.* Paramètrage de PostgreSQL *3.* Installation de Misskey
----------------------------------------------------------------
:)
*4.* Installation de Misskey
---------------------------------------------------------------- ----------------------------------------------------------------
1. `su - misskey` Basculez vers l'utilisateur misskey. 1. `su - misskey` Basculez vers l'utilisateur misskey.
2. `git clone -b master git://github.com/syuilo/misskey.git` Clonez la branche master du dépôt misskey. 2. `git clone -b master git://github.com/syuilo/misskey.git` Clonez la branche master du dépôt misskey.
@ -43,12 +38,12 @@ Installez les paquets suivants :
4. `git checkout $(git tag -l | grep -Ev -- '-(rc|alpha)\.[0-9]+$' | sort -V | tail -n 1)` Checkout sur le tag de la [version la plus récente](https://github.com/syuilo/misskey/releases/latest) 4. `git checkout $(git tag -l | grep -Ev -- '-(rc|alpha)\.[0-9]+$' | sort -V | tail -n 1)` Checkout sur le tag de la [version la plus récente](https://github.com/syuilo/misskey/releases/latest)
5. `npm install` Installez les dépendances de misskey. 5. `npm install` Installez les dépendances de misskey.
*5.* Création du fichier de configuration *4.* Création du fichier de configuration
---------------------------------------------------------------- ----------------------------------------------------------------
1. `cp .config/example.yml .config/default.yml` Copiez le fichier `.config/example.yml` et renommez-le`default.yml`. 1. `cp .config/example.yml .config/default.yml` Copiez le fichier `.config/example.yml` et renommez-le`default.yml`.
2. Editez le fichier `default.yml` 2. Editez le fichier `default.yml`
*6.* Construction de Misskey *5.* Construction de Misskey
---------------------------------------------------------------- ----------------------------------------------------------------
Construisez Misskey comme ceci : Construisez Misskey comme ceci :
@ -64,7 +59,7 @@ Si vous rencontrez des erreurs concernant certains modules, utilisez node-gyp:
3. `node-gyp build` 3. `node-gyp build`
4. `NODE_ENV=production npm run build` 4. `NODE_ENV=production npm run build`
*7.* C'est tout. *6.* C'est tout.
---------------------------------------------------------------- ----------------------------------------------------------------
Excellent ! Maintenant, vous avez un environnement prêt pour lancer Misskey Excellent ! Maintenant, vous avez un environnement prêt pour lancer Misskey

View File

@ -24,25 +24,14 @@ adduser --disabled-password --disabled-login misskey
#### 依存関係 :package: #### 依存関係 :package:
* **[Node.js](https://nodejs.org/en/)** (11.7.0以上) * **[Node.js](https://nodejs.org/en/)** (11.7.0以上)
* **[PostgreSQL](https://www.postgresql.org/)** (10以上) * **[PostgreSQL](https://www.postgresql.org/)** (10以上)
* **[Redis](https://redis.io/)**
##### オプション ##### オプション
* [Redis](https://redis.io/)
* Redisはオプションですが、インストールすることを強く推奨します。
* インストールしなくていいのは、あなたのインスタンスが自分専用のときだけとお考えください。
* 具体的には、Redisをインストールしないと、次の事が出来なくなります:
* Misskeyプロセスを複数起動しての負荷分散
* レートリミット
* ジョブキュー
* Twitter連携
* [Elasticsearch](https://www.elastic.co/) * [Elasticsearch](https://www.elastic.co/)
* 検索機能を有効にするためにはインストールが必要です。 * 検索機能を有効にするためにはインストールが必要です。
* [FFmpeg](https://www.ffmpeg.org/) * [FFmpeg](https://www.ffmpeg.org/)
*3.* PostgreSQLの設定 *3.* Misskeyのインストール
----------------------------------------------------------------
:)
*4.* Misskeyのインストール
---------------------------------------------------------------- ----------------------------------------------------------------
1. `su - misskey` misskeyユーザーを使用 1. `su - misskey` misskeyユーザーを使用
2. `git clone -b master git://github.com/syuilo/misskey.git` masterブランチからMisskeyレポジトリをクローン 2. `git clone -b master git://github.com/syuilo/misskey.git` masterブランチからMisskeyレポジトリをクローン
@ -50,12 +39,12 @@ adduser --disabled-password --disabled-login misskey
4. `git checkout $(git tag -l | grep -Ev -- '-(rc|alpha)\.[0-9]+$' | sort -V | tail -n 1)` [最新のリリース](https://github.com/syuilo/misskey/releases/latest)を確認 4. `git checkout $(git tag -l | grep -Ev -- '-(rc|alpha)\.[0-9]+$' | sort -V | tail -n 1)` [最新のリリース](https://github.com/syuilo/misskey/releases/latest)を確認
5. `npm install` Misskeyの依存パッケージをインストール 5. `npm install` Misskeyの依存パッケージをインストール
*5.* 設定ファイルを作成する *4.* 設定ファイルを作成する
---------------------------------------------------------------- ----------------------------------------------------------------
1. `cp .config/example.yml .config/default.yml` `.config/example.yml`をコピーし名前を`default.yml`にする。 1. `cp .config/example.yml .config/default.yml` `.config/example.yml`をコピーし名前を`default.yml`にする。
2. `default.yml` を編集する。 2. `default.yml` を編集する。
*6.* Misskeyのビルド *5.* Misskeyのビルド
---------------------------------------------------------------- ----------------------------------------------------------------
次のコマンドでMisskeyをビルドしてください: 次のコマンドでMisskeyをビルドしてください:
@ -70,13 +59,13 @@ Debianをお使いであれば、`build-essential`パッケージをインスト
3. `node-gyp build` 3. `node-gyp build`
4. `NODE_ENV=production npm run build` 4. `NODE_ENV=production npm run build`
*7.* データベースを初期化 *6.* データベースを初期化
---------------------------------------------------------------- ----------------------------------------------------------------
``` shell ``` shell
npm run init npm run init
``` ```
*8.* 以上です! *7.* 以上です!
---------------------------------------------------------------- ----------------------------------------------------------------
お疲れ様でした。これでMisskeyを動かす準備は整いました。 お疲れ様でした。これでMisskeyを動かす準備は整いました。

View File

@ -1,7 +1,7 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <i@syuilo.com>", "author": "syuilo <i@syuilo.com>",
"version": "11.0.0-beta.5", "version": "11.0.0-beta.13",
"codename": "daybreak", "codename": "daybreak",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@ -1,7 +1,7 @@
import * as redis from 'redis'; import * as redis from 'redis';
import config from '../config'; import config from '../config';
export default config.redis ? redis.createClient( export default redis.createClient(
config.redis.port, config.redis.port,
config.redis.host, config.redis.host,
{ {
@ -9,4 +9,4 @@ export default config.redis ? redis.createClient(
prefix: config.redis.prefix, prefix: config.redis.prefix,
db: config.redis.db || 0 db: config.redis.db || 0
} }
) : null; );

View File

@ -24,9 +24,14 @@ import { UserPublickey } from './models/entities/user-publickey';
import { UserKeypair } from './models/entities/user-keypair'; import { UserKeypair } from './models/entities/user-keypair';
import { extractPublic } from './crypto_key'; import { extractPublic } from './crypto_key';
import { Emoji } from './models/entities/emoji'; import { Emoji } from './models/entities/emoji';
import { toPuny } from './misc/convert-host'; import { toPuny as _toPuny } from './misc/convert-host';
import { UserProfile } from './models/entities/user-profile'; import { UserProfile } from './models/entities/user-profile';
function toPuny(x: string | null): string | null {
if (x == null) return null;
return _toPuny(x);
}
const u = (config as any).mongodb.user ? encodeURIComponent((config as any).mongodb.user) : null; const u = (config as any).mongodb.user ? encodeURIComponent((config as any).mongodb.user) : null;
const p = (config as any).mongodb.pass ? encodeURIComponent((config as any).mongodb.pass) : null; const p = (config as any).mongodb.pass ? encodeURIComponent((config as any).mongodb.pass) : null;

View File

@ -1,26 +0,0 @@
// AID
// 長さ8の[2000年1月1日からの経過ミリ秒をbase36でエンコードしたもの] + 長さnの[ランダムな文字列]
const CHARS = '0123456789abcdefghijklmnopqrstuvwxyz';
const TIME2000 = 946684800000;
function getTime(time: number) {
time = time - TIME2000;
if (time < 0) time = 0;
return time.toString(36);
}
function getRandom(length: number) {
let str = '';
for (let i = 0; i < length; i++) {
str += CHARS[Math.floor(Math.random() * CHARS.length)];
}
return str;
}
export function genAid(date: Date, rand: number): string {
return getTime(date.getTime()).padStart(8, CHARS[0]) + getRandom(rand);
}

View File

@ -1,26 +0,0 @@
// AID(Cheep)
// 長さ6の[2000年1月1日からの経過秒をbase36でエンコードしたもの] + 長さ3の[ランダムな文字列]
const CHARS = '0123456789abcdefghijklmnopqrstuvwxyz';
const TIME2000 = 946684800000;
function getTime(time: number) {
time = time - TIME2000;
if (time < 0) time = 0;
time = Math.floor(time / 1000);
return time.toString(36);
}
function getRandom() {
let str = '';
for (let i = 0; i < 3; i++) {
str += CHARS[Math.floor(Math.random() * CHARS.length)];
}
return str;
}
export function genAidc(date: Date): string {
return getTime(date.getTime()).padStart(6, CHARS[0]) + getRandom();
}

View File

@ -19,3 +19,8 @@ export function extractDbHost(uri: string) {
export function toPuny(host: string) { export function toPuny(host: string) {
return toASCII(host.toLowerCase()); return toASCII(host.toLowerCase());
} }
export function toPunyNullable(host: string | null | undefined): string | null {
if (host == null) return null;
return toASCII(host.toLowerCase());
}

View File

@ -1,7 +1,7 @@
import { ulid } from 'ulid'; import { ulid } from 'ulid';
import { genAid } from './aid'; import { genAid } from './id/aid';
import { genAidc } from './aidc'; import { genMeid } from './id/meid';
import { genObjectId } from './object-id'; import { genObjectId } from './id/object-id';
import config from '../config'; import config from '../config';
const metohd = config.id.toLowerCase(); const metohd = config.id.toLowerCase();
@ -10,11 +10,8 @@ export function genId(date?: Date): string {
if (!date || (date > new Date())) date = new Date(); if (!date || (date > new Date())) date = new Date();
switch (metohd) { switch (metohd) {
case 'aidc': return genAidc(date); case 'aid': return genAid(date);
case 'aid1': return genAid(date, 1); case 'meid': return genMeid(date);
case 'aid2': return genAid(date, 2);
case 'aid3': return genAid(date, 3);
case 'aid4': return genAid(date, 4);
case 'ulid': return ulid(date.getTime()); case 'ulid': return ulid(date.getTime());
case 'objectid': return genObjectId(date); case 'objectid': return genObjectId(date);
default: throw 'unknown id generation method'; default: throw 'unknown id generation method';

23
src/misc/id/aid.ts Normal file
View File

@ -0,0 +1,23 @@
// AID
// 長さ8の[2000年1月1日からの経過ミリ秒をbase36でエンコードしたもの] + 長さ2の[ノイズ文字列]
import * as cluster from 'cluster';
const TIME2000 = 946684800000;
let counter = process.pid + (cluster.isMaster ? 0 : cluster.worker.id);
function getTime(time: number) {
time = time - TIME2000;
if (time < 0) time = 0;
return time.toString(36).padStart(8, '0');
}
function getNoise() {
return counter.toString(36).padStart(2, '0').slice(-2);
}
export function genAid(date: Date): string {
counter++;
return getTime(date.getTime()) + getNoise();
}

26
src/misc/id/meid.ts Normal file
View File

@ -0,0 +1,26 @@
const CHARS = '0123456789abcdef';
function getTime(time: number) {
if (time < 0) time = 0;
if (time === 0) {
return CHARS[0];
}
time += 0x800000000000;
return time.toString(16).padStart(12, CHARS[0]);
}
function getRandom() {
let str = '';
for (let i = 0; i < 12; i++) {
str += CHARS[Math.floor(Math.random() * CHARS.length)];
}
return str;
}
export function genMeid(date: Date): string {
return 'f' + getTime(date.getTime()) + getRandom();
}

View File

@ -8,7 +8,7 @@ function getTime(time: number) {
time = Math.floor(time / 1000); time = Math.floor(time / 1000);
return time.toString(16); return time.toString(16).padStart(8, CHARS[0]);
} }
function getRandom() { function getRandom() {

View File

@ -20,7 +20,7 @@ export async function getFallbackReaction(): Promise<string> {
return meta.useStarForReactionFallback ? 'star' : 'like'; return meta.useStarForReactionFallback ? 'star' : 'like';
} }
export async function toDbReaction(reaction: string, enableEmoji = true): Promise<string> { export async function toDbReaction(reaction?: string | null, enableEmoji = true): Promise<string> {
if (reaction == null) return await getFallbackReaction(); if (reaction == null) return await getFallbackReaction();
// 既存の文字列リアクションはそのまま // 既存の文字列リアクションはそのまま

View File

@ -19,11 +19,15 @@ export class AuthSession {
}) })
public token: string; public token: string;
@Column(id()) @Column({
...id(),
nullable: true
})
public userId: User['id']; public userId: User['id'];
@ManyToOne(type => User, { @ManyToOne(type => User, {
onDelete: 'CASCADE' onDelete: 'CASCADE',
nullable: true
}) })
@JoinColumn() @JoinColumn()
public user: User | null; public user: User | null;

View File

@ -148,7 +148,7 @@ export class NoteRepository extends Repository<Note> {
return reaction.reaction; return reaction.reaction;
} }
return null; return undefined;
} }
let text = note.text; let text = note.text;
@ -162,15 +162,15 @@ export class NoteRepository extends Repository<Note> {
const packed = await rap({ const packed = await rap({
id: note.id, id: note.id,
createdAt: note.createdAt, createdAt: note.createdAt,
app: note.appId ? Apps.pack(note.appId) : null, app: note.appId ? Apps.pack(note.appId) : undefined,
userId: note.userId, userId: note.userId,
user: Users.pack(note.user || note.userId, meId), user: Users.pack(note.user || note.userId, meId),
text: text, text: text,
cw: note.cw, cw: note.cw,
visibility: note.visibility, visibility: note.visibility,
localOnly: note.localOnly, localOnly: note.localOnly || undefined,
visibleUserIds: note.visibleUserIds, visibleUserIds: note.visibility === 'specified' ? note.visibleUserIds : undefined,
viaMobile: note.viaMobile, viaMobile: note.viaMobile || undefined,
renoteCount: note.renoteCount, renoteCount: note.renoteCount,
repliesCount: note.repliesCount, repliesCount: note.repliesCount,
reactions: note.reactions, reactions: note.reactions,
@ -188,13 +188,13 @@ export class NoteRepository extends Repository<Note> {
...(opts.detail ? { ...(opts.detail ? {
reply: note.replyId ? this.pack(note.replyId, meId, { reply: note.replyId ? this.pack(note.replyId, meId, {
detail: false detail: false
}) : null, }) : undefined,
renote: note.renoteId ? this.pack(note.renoteId, meId, { renote: note.renoteId ? this.pack(note.renoteId, meId, {
detail: true detail: true
}) : null, }) : undefined,
poll: note.hasPoll ? populatePoll() : null, poll: note.hasPoll ? populatePoll() : undefined,
...(meId ? { ...(meId ? {
myReaction: populateMyReaction() myReaction: populateMyReaction()

View File

@ -1,6 +1,7 @@
import { EntityRepository, Repository } from 'typeorm'; import { EntityRepository, Repository } from 'typeorm';
import { UserList } from '../entities/user-list'; import { UserList } from '../entities/user-list';
import { ensure } from '../../prelude/ensure'; import { ensure } from '../../prelude/ensure';
import { UserListJoinings } from '..';
@EntityRepository(UserList) @EntityRepository(UserList)
export class UserListRepository extends Repository<UserList> { export class UserListRepository extends Repository<UserList> {
@ -9,9 +10,14 @@ export class UserListRepository extends Repository<UserList> {
) { ) {
const userList = typeof src === 'object' ? src : await this.findOne(src).then(ensure); const userList = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
const users = await UserListJoinings.find({
userListId: userList.id
});
return { return {
id: userList.id, id: userList.id,
name: userList.name name: userList.name,
userIds: users.map(x => x.userId)
}; };
} }
} }

View File

@ -89,13 +89,11 @@ export class UserRepository extends Repository<User> {
username: user.username, username: user.username,
host: user.host, host: user.host,
avatarUrl: user.avatarUrl, avatarUrl: user.avatarUrl,
bannerUrl: user.bannerUrl,
avatarColor: user.avatarColor, avatarColor: user.avatarColor,
bannerColor: user.bannerColor, isAdmin: user.isAdmin || undefined,
isAdmin: user.isAdmin, isBot: user.isBot || undefined,
isBot: user.isBot, isCat: user.isCat || undefined,
isCat: user.isCat, isVerified: user.isVerified || undefined,
isVerified: user.isVerified,
// カスタム絵文字添付 // カスタム絵文字添付
emojis: user.emojis.length > 0 ? Emojis.find({ emojis: user.emojis.length > 0 ? Emojis.find({
@ -121,6 +119,8 @@ export class UserRepository extends Repository<User> {
url: profile!.url, url: profile!.url,
createdAt: user.createdAt, createdAt: user.createdAt,
updatedAt: user.updatedAt, updatedAt: user.updatedAt,
bannerUrl: user.bannerUrl,
bannerColor: user.bannerColor,
description: profile!.description, description: profile!.description,
location: profile!.location, location: profile!.location,
birthday: profile!.birthday, birthday: profile!.birthday,

View File

@ -1,3 +1,6 @@
/**
* 値が null または undefined の場合はエラーを発生させ、そうでない場合は値をそのまま返します
*/
export function ensure<T>(x: T): NonNullable<T> { export function ensure<T>(x: T): NonNullable<T> {
if (x == null) { if (x == null) {
throw 'ぬるぽ'; throw 'ぬるぽ';

View File

@ -23,6 +23,14 @@ function initializeQueue(name: string) {
}); });
} }
function renderError(e: Error): any {
return {
stack: e.stack,
message: e.message,
name: e.name
};
}
export const deliverQueue = initializeQueue('deliver'); export const deliverQueue = initializeQueue('deliver');
export const inboxQueue = initializeQueue('inbox'); export const inboxQueue = initializeQueue('inbox');
export const dbQueue = initializeQueue('db'); export const dbQueue = initializeQueue('db');
@ -34,16 +42,16 @@ deliverQueue
.on('waiting', (jobId) => deliverLogger.debug(`waiting id=${jobId}`)) .on('waiting', (jobId) => deliverLogger.debug(`waiting id=${jobId}`))
.on('active', (job) => deliverLogger.debug(`active id=${job.id} to=${job.data.to}`)) .on('active', (job) => deliverLogger.debug(`active id=${job.id} to=${job.data.to}`))
.on('completed', (job, result) => deliverLogger.debug(`completed(${result}) id=${job.id} to=${job.data.to}`)) .on('completed', (job, result) => deliverLogger.debug(`completed(${result}) id=${job.id} to=${job.data.to}`))
.on('failed', (job, err) => deliverLogger.warn(`failed(${err}) id=${job.id} to=${job.data.to}`)) .on('failed', (job, err) => deliverLogger.warn(`failed(${err}) id=${job.id} to=${job.data.to}`, renderError(err)))
.on('error', (error) => deliverLogger.error(`error ${error}`)) .on('error', (error) => deliverLogger.error(`error ${error}`, renderError(error)))
.on('stalled', (job) => deliverLogger.warn(`stalled id=${job.id} to=${job.data.to}`)); .on('stalled', (job) => deliverLogger.warn(`stalled id=${job.id} to=${job.data.to}`));
inboxQueue inboxQueue
.on('waiting', (jobId) => inboxLogger.debug(`waiting id=${jobId}`)) .on('waiting', (jobId) => inboxLogger.debug(`waiting id=${jobId}`))
.on('active', (job) => inboxLogger.debug(`active id=${job.id}`)) .on('active', (job) => inboxLogger.debug(`active id=${job.id}`))
.on('completed', (job, result) => inboxLogger.debug(`completed(${result}) id=${job.id}`)) .on('completed', (job, result) => inboxLogger.debug(`completed(${result}) id=${job.id}`))
.on('failed', (job, err) => inboxLogger.warn(`failed(${err}) id=${job.id} activity=${job.data.activity ? job.data.activity.id : 'none'}`)) .on('failed', (job, err) => inboxLogger.warn(`failed(${err}) id=${job.id} activity=${job.data.activity ? job.data.activity.id : 'none'}`, renderError(err)))
.on('error', (error) => inboxLogger.error(`error ${error}`)) .on('error', (error) => inboxLogger.error(`error ${error}`, renderError(error)))
.on('stalled', (job) => inboxLogger.warn(`stalled id=${job.id} activity=${job.data.activity ? job.data.activity.id : 'none'}`)); .on('stalled', (job) => inboxLogger.warn(`stalled id=${job.id} activity=${job.data.activity ? job.data.activity.id : 'none'}`));
export function deliver(user: ILocalUser, content: any, to: any) { export function deliver(user: ILocalUser, content: any, to: any) {

View File

@ -12,7 +12,7 @@ import { Instances, Users, UserPublickeys } from '../../models';
import { instanceChart } from '../../services/chart'; import { instanceChart } from '../../services/chart';
import { UserPublickey } from '../../models/entities/user-publickey'; import { UserPublickey } from '../../models/entities/user-publickey';
import fetchMeta from '../../misc/fetch-meta'; import fetchMeta from '../../misc/fetch-meta';
import { toPuny } from '../../misc/convert-host'; import { toPuny, toPunyNullable } from '../../misc/convert-host';
import { validActor } from '../../remote/activitypub/type'; import { validActor } from '../../remote/activitypub/type';
import { ensure } from '../../prelude/ensure'; import { ensure } from '../../prelude/ensure';
@ -36,7 +36,7 @@ export default async (job: Bull.Job): Promise<void> => {
if (keyIdLower.startsWith('acct:')) { if (keyIdLower.startsWith('acct:')) {
const acct = parseAcct(keyIdLower.slice('acct:'.length)); const acct = parseAcct(keyIdLower.slice('acct:'.length));
const host = acct.host ? toPuny(acct.host) : null; const host = toPunyNullable(acct.host);
const username = toPuny(acct.username); const username = toPuny(acct.username);
if (host === null) { if (host === null) {

View File

@ -120,13 +120,15 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
: []; : [];
// リプライ // リプライ
const reply: Note | undefined | null = note.inReplyTo const reply: Note | null = note.inReplyTo
? await resolveNote(note.inReplyTo, resolver).catch(e => { ? await resolveNote(note.inReplyTo, resolver).then(x => {
// 4xxの場合はリプライしてないことにする if (x == null) {
if (e.statusCode >= 400 && e.statusCode < 500) { logger.warn(`Specified inReplyTo, but nout found`);
logger.warn(`Ignored inReplyTo ${note.inReplyTo} - ${e.statusCode} `); throw 'inReplyTo not found';
return null; } else {
return x;
} }
}).catch(e => {
logger.warn(`Error in inReplyTo ${note.inReplyTo} - ${e.statusCode || e}`); logger.warn(`Error in inReplyTo ${note.inReplyTo} - ${e.statusCode || e}`);
throw e; throw e;
}) })
@ -150,11 +152,11 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
const cw = note.summary === '' ? null : note.summary; const cw = note.summary === '' ? null : note.summary;
// テキストのパース // テキストのパース
const text = note._misskey_content || fromHtml(note.content); const text = note._misskey_content || (note.content ? fromHtml(note.content) : null);
// vote // vote
if (reply && reply.hasPoll) { if (reply && reply.hasPoll) {
const poll = await Polls.findOne({ noteId: reply.id }).then(ensure); const poll = await Polls.findOne(reply.id).then(ensure);
const tryCreateVote = async (name: string, index: number): Promise<null> => { const tryCreateVote = async (name: string, index: number): Promise<null> => {
if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) { if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) {

View File

@ -12,7 +12,7 @@ export interface IObject {
attachment?: any[]; attachment?: any[];
inReplyTo?: any; inReplyTo?: any;
replies?: ICollection; replies?: ICollection;
content: string; content?: string;
name?: string; name?: string;
startTime?: Date; startTime?: Date;
endTime?: Date; endTime?: Date;
@ -44,16 +44,16 @@ export interface IOrderedCollection extends IObject {
export interface INote extends IObject { export interface INote extends IObject {
type: 'Note' | 'Question'; type: 'Note' | 'Question';
_misskey_content: string; _misskey_content?: string;
_misskey_quote: string; _misskey_quote?: string;
_misskey_question: string; _misskey_question?: string;
} }
export interface IQuestion extends IObject { export interface IQuestion extends IObject {
type: 'Note' | 'Question'; type: 'Note' | 'Question';
_misskey_content: string; _misskey_content?: string;
_misskey_quote: string; _misskey_quote?: string;
_misskey_question: string; _misskey_question?: string;
oneOf?: IQuestionChoice[]; oneOf?: IQuestionChoice[];
anyOf?: IQuestionChoice[]; anyOf?: IQuestionChoice[];
endTime?: Date; endTime?: Date;
@ -129,7 +129,7 @@ export interface IRemove extends IActivity {
export interface ILike extends IActivity { export interface ILike extends IActivity {
type: 'Like'; type: 'Like';
_misskey_reaction: string; _misskey_reaction?: string;
} }
export interface IAnnounce extends IActivity { export interface IAnnounce extends IActivity {

View File

@ -1,7 +1,7 @@
import $ from 'cafy'; import $ from 'cafy';
import define from '../../../define'; import define from '../../../define';
import { Emojis } from '../../../../../models'; import { Emojis } from '../../../../../models';
import { toPuny } from '../../../../../misc/convert-host'; import { toPunyNullable } from '../../../../../misc/convert-host';
export const meta = { export const meta = {
desc: { desc: {
@ -23,7 +23,7 @@ export const meta = {
export default define(meta, async (ps) => { export default define(meta, async (ps) => {
const emojis = await Emojis.find({ const emojis = await Emojis.find({
host: ps.host ? toPuny(ps.host) : null host: toPunyNullable(ps.host)
}); });
return emojis.map(e => ({ return emojis.map(e => ({

View File

@ -34,7 +34,7 @@ export default define(meta, async (ps) => {
if (ps.domain) { if (ps.domain) {
const whiteDomains = ps.domain.split(' ').filter(x => !x.startsWith('-')); const whiteDomains = ps.domain.split(' ').filter(x => !x.startsWith('-'));
const blackDomains = ps.domain.split(' ').filter(x => x.startsWith('-')); const blackDomains = ps.domain.split(' ').filter(x => x.startsWith('-')).map(x => x.substr(1));
if (whiteDomains.length > 0) { if (whiteDomains.length > 0) {
query.andWhere(new Brackets(qb => { query.andWhere(new Brackets(qb => {
@ -53,11 +53,17 @@ export default define(meta, async (ps) => {
if (blackDomains.length > 0) { if (blackDomains.length > 0) {
query.andWhere(new Brackets(qb => { query.andWhere(new Brackets(qb => {
for (const blackDomain of blackDomains) { for (const blackDomain of blackDomains) {
const subDomains = blackDomain.split('.');
let i = 0; let i = 0;
for (const subDomain of blackDomain.split('.')) { for (const subDomain of subDomains) {
const p = `blackSubDomain_${subDomain}_${i}`; const p = `blackSubDomain_${subDomain}_${i}`;
// SQL is 1 based, so we need '+ 1' if (i === subDomains.length - 1) {
qb.andWhere(`log.domain[${i + 1}] != :${p}`, { [p]: subDomain }); // SQL is 1 based, so we need '+ 1'
qb.andWhere(`log.domain[${i + 1}] != :${p}`, { [p]: subDomain });
} else {
// SQL is 1 based, so we need '+ 1'
qb.andWhere(`log.domain[${i + 1}] = :${p}`, { [p]: subDomain });
}
i++; i++;
} }
} }

View File

@ -38,7 +38,7 @@ export default define(meta, async (ps, user) => {
const app = await Apps.save({ const app = await Apps.save({
id: genId(), id: genId(),
createdAt: new Date(), createdAt: new Date(),
userId: user && user.id, userId: user ? user.id : null,
name: ps.name, name: ps.name,
description: ps.description, description: ps.description,
permission: ps.permission, permission: ps.permission,

View File

@ -71,6 +71,4 @@ export default define(meta, async (ps, user) => {
await AuthSessions.update(session.id, { await AuthSessions.update(session.id, {
userId: user.id userId: user.id
}); });
return;
}); });

View File

@ -76,12 +76,12 @@ export default define(meta, async (ps, user) => {
user: meta.smtpUser, user: meta.smtpUser,
pass: meta.smtpPass pass: meta.smtpPass
} : undefined } : undefined
}); } as any);
const link = `${config.url}/verify-email/${code}`; const link = `${config.url}/verify-email/${code}`;
transporter.sendMail({ transporter.sendMail({
from: meta.email, from: meta.email!,
to: ps.email, to: ps.email,
subject: meta.name || 'Misskey', subject: meta.name || 'Misskey',
text: `To verify email, please click this link: ${link}` text: `To verify email, please click this link: ${link}`

View File

@ -124,7 +124,7 @@ export default define(meta, async (ps, user) => {
// Increment votes count // Increment votes count
const index = ps.choice + 1; // In SQL, array index is 1 based const index = ps.choice + 1; // In SQL, array index is 1 based
await Polls.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE noteId = '${poll.noteId}'`); await Polls.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE "noteId" = '${poll.noteId}'`);
publishNoteStream(note.id, 'pollVoted', { publishNoteStream(note.id, 'pollVoted', {
choice: ps.choice, choice: ps.choice,

View File

@ -4,7 +4,7 @@ import define from '../../define';
import { ApiError } from '../../error'; import { ApiError } from '../../error';
import { Users, Followings } from '../../../../models'; import { Users, Followings } from '../../../../models';
import { makePaginationQuery } from '../../common/make-pagination-query'; import { makePaginationQuery } from '../../common/make-pagination-query';
import { toPuny } from '../../../../misc/convert-host'; import { toPunyNullable } from '../../../../misc/convert-host';
export const meta = { export const meta = {
desc: { desc: {
@ -66,7 +66,7 @@ export const meta = {
export default define(meta, async (ps, me) => { export default define(meta, async (ps, me) => {
const user = await Users.findOne(ps.userId != null const user = await Users.findOne(ps.userId != null
? { id: ps.userId } ? { id: ps.userId }
: { usernameLower: ps.username!.toLowerCase(), host: toPuny(ps.host!) }); : { usernameLower: ps.username!.toLowerCase(), host: toPunyNullable(ps.host) });
if (user == null) { if (user == null) {
throw new ApiError(meta.errors.noSuchUser); throw new ApiError(meta.errors.noSuchUser);

View File

@ -4,7 +4,7 @@ import define from '../../define';
import { ApiError } from '../../error'; import { ApiError } from '../../error';
import { Users, Followings } from '../../../../models'; import { Users, Followings } from '../../../../models';
import { makePaginationQuery } from '../../common/make-pagination-query'; import { makePaginationQuery } from '../../common/make-pagination-query';
import { toPuny } from '../../../../misc/convert-host'; import { toPunyNullable } from '../../../../misc/convert-host';
export const meta = { export const meta = {
desc: { desc: {
@ -66,7 +66,7 @@ export const meta = {
export default define(meta, async (ps, me) => { export default define(meta, async (ps, me) => {
const user = await Users.findOne(ps.userId != null const user = await Users.findOne(ps.userId != null
? { id: ps.userId } ? { id: ps.userId }
: { usernameLower: ps.username!.toLowerCase(), host: toPuny(ps.host!) }); : { usernameLower: ps.username!.toLowerCase(), host: toPunyNullable(ps.host) });
if (user == null) { if (user == null) {
throw new ApiError(meta.errors.noSuchUser); throw new ApiError(meta.errors.noSuchUser);

View File

@ -8,12 +8,6 @@ import Logger from '../../services/logger';
const logger = new Logger('limiter'); const logger = new Logger('limiter');
export default (endpoint: IEndpoint, user: User) => new Promise((ok, reject) => { export default (endpoint: IEndpoint, user: User) => new Promise((ok, reject) => {
// Redisがインストールされてない場合は常に許可
if (limiterDB == null) {
ok();
return;
}
const limitation = endpoint.meta.limit!; const limitation = endpoint.meta.limit!;
const key = limitation.hasOwnProperty('key') const key = limitation.hasOwnProperty('key')

View File

@ -10,7 +10,7 @@ import { genId } from '../../../misc/gen-id';
import { usersChart } from '../../../services/chart'; import { usersChart } from '../../../services/chart';
import { User } from '../../../models/entities/user'; import { User } from '../../../models/entities/user';
import { UserKeypair } from '../../../models/entities/user-keypair'; import { UserKeypair } from '../../../models/entities/user-keypair';
import { toPuny } from '../../../misc/convert-host'; import { toPunyNullable } from '../../../misc/convert-host';
import { UserProfile } from '../../../models/entities/user-profile'; import { UserProfile } from '../../../models/entities/user-profile';
import { getConnection } from 'typeorm'; import { getConnection } from 'typeorm';
@ -36,7 +36,7 @@ export default async (ctx: Koa.BaseContext) => {
const username = body['username']; const username = body['username'];
const password = body['password']; const password = body['password'];
const host = process.env.NODE_ENV === 'test' ? (body['host'] || null) : null; const host: string | null = process.env.NODE_ENV === 'test' ? (body['host'] || null) : null;
const invitationCode = body['invitationCode']; const invitationCode = body['invitationCode'];
if (instance && instance.disableRegistration) { if (instance && instance.disableRegistration) {
@ -96,7 +96,7 @@ export default async (ctx: Koa.BaseContext) => {
cipher: undefined, cipher: undefined,
passphrase: undefined passphrase: undefined
} }
}, (e, publicKey, privateKey) => } as any, (e, publicKey, privateKey) =>
e ? j(e) : s([publicKey, privateKey]) e ? j(e) : s([publicKey, privateKey])
)); ));
@ -109,7 +109,7 @@ export default async (ctx: Koa.BaseContext) => {
createdAt: new Date(), createdAt: new Date(),
username: username, username: username,
usernameLower: username.toLowerCase(), usernameLower: username.toLowerCase(),
host: toPuny(host), host: toPunyNullable(host),
token: secret, token: secret,
isAdmin: config.autoAdmin && usersCount === 0, isAdmin: config.autoAdmin && usersCount === 0,
})); }));

View File

@ -83,8 +83,6 @@ async function getOAuth2() {
} }
router.get('/connect/discord', async ctx => { router.get('/connect/discord', async ctx => {
if (redis == null) return;
if (!compareOrigin(ctx)) { if (!compareOrigin(ctx)) {
ctx.throw(400, 'invalid origin'); ctx.throw(400, 'invalid origin');
return; return;
@ -110,8 +108,6 @@ router.get('/connect/discord', async ctx => {
}); });
router.get('/signin/discord', async ctx => { router.get('/signin/discord', async ctx => {
if (redis == null) return;
const sessid = uuid(); const sessid = uuid();
const params = { const params = {
@ -138,8 +134,6 @@ router.get('/signin/discord', async ctx => {
}); });
router.get('/dc/cb', async ctx => { router.get('/dc/cb', async ctx => {
if (redis == null) return;
const userToken = getUserToken(ctx); const userToken = getUserToken(ctx);
const oauth2 = await getOAuth2(); const oauth2 = await getOAuth2();
@ -160,7 +154,7 @@ router.get('/dc/cb', async ctx => {
} }
const { redirect_uri, state } = await new Promise<any>((res, rej) => { const { redirect_uri, state } = await new Promise<any>((res, rej) => {
redis!.get(sessid, async (_, state) => { redis.get(sessid, async (_, state) => {
res(JSON.parse(state)); res(JSON.parse(state));
}); });
}); });
@ -241,7 +235,7 @@ router.get('/dc/cb', async ctx => {
} }
const { redirect_uri, state } = await new Promise<any>((res, rej) => { const { redirect_uri, state } = await new Promise<any>((res, rej) => {
redis!.get(userToken, async (_, state) => { redis.get(userToken, async (_, state) => {
res(JSON.parse(state)); res(JSON.parse(state));
}); });
}); });

View File

@ -80,8 +80,6 @@ async function getOath2() {
} }
router.get('/connect/github', async ctx => { router.get('/connect/github', async ctx => {
if (redis == null) return;
if (!compareOrigin(ctx)) { if (!compareOrigin(ctx)) {
ctx.throw(400, 'invalid origin'); ctx.throw(400, 'invalid origin');
return; return;
@ -106,8 +104,6 @@ router.get('/connect/github', async ctx => {
}); });
router.get('/signin/github', async ctx => { router.get('/signin/github', async ctx => {
if (redis == null) return;
const sessid = uuid(); const sessid = uuid();
const params = { const params = {
@ -133,8 +129,6 @@ router.get('/signin/github', async ctx => {
}); });
router.get('/gh/cb', async ctx => { router.get('/gh/cb', async ctx => {
if (redis == null) return;
const userToken = getUserToken(ctx); const userToken = getUserToken(ctx);
const oauth2 = await getOath2(); const oauth2 = await getOath2();
@ -155,7 +149,7 @@ router.get('/gh/cb', async ctx => {
} }
const { redirect_uri, state } = await new Promise<any>((res, rej) => { const { redirect_uri, state } = await new Promise<any>((res, rej) => {
redis!.get(sessid, async (_, state) => { redis.get(sessid, async (_, state) => {
res(JSON.parse(state)); res(JSON.parse(state));
}); });
}); });
@ -222,7 +216,7 @@ router.get('/gh/cb', async ctx => {
} }
const { redirect_uri, state } = await new Promise<any>((res, rej) => { const { redirect_uri, state } = await new Promise<any>((res, rej) => {
redis!.get(userToken, async (_, state) => { redis.get(userToken, async (_, state) => {
res(JSON.parse(state)); res(JSON.parse(state));
}); });
}); });

View File

@ -79,8 +79,6 @@ async function getTwAuth() {
} }
router.get('/connect/twitter', async ctx => { router.get('/connect/twitter', async ctx => {
if (redis == null) return;
if (!compareOrigin(ctx)) { if (!compareOrigin(ctx)) {
ctx.throw(400, 'invalid origin'); ctx.throw(400, 'invalid origin');
return; return;
@ -99,8 +97,6 @@ router.get('/connect/twitter', async ctx => {
}); });
router.get('/signin/twitter', async ctx => { router.get('/signin/twitter', async ctx => {
if (redis == null) return;
const twAuth = await getTwAuth(); const twAuth = await getTwAuth();
const twCtx = await twAuth!.begin(); const twCtx = await twAuth!.begin();
@ -122,8 +118,6 @@ router.get('/signin/twitter', async ctx => {
}); });
router.get('/tw/cb', async ctx => { router.get('/tw/cb', async ctx => {
if (redis == null) return;
const userToken = getUserToken(ctx); const userToken = getUserToken(ctx);
const twAuth = await getTwAuth(); const twAuth = await getTwAuth();
@ -137,7 +131,7 @@ router.get('/tw/cb', async ctx => {
} }
const get = new Promise<any>((res, rej) => { const get = new Promise<any>((res, rej) => {
redis!.get(sessid, async (_, twCtx) => { redis.get(sessid, async (_, twCtx) => {
res(twCtx); res(twCtx);
}); });
}); });
@ -170,7 +164,7 @@ router.get('/tw/cb', async ctx => {
} }
const get = new Promise<any>((res, rej) => { const get = new Promise<any>((res, rej) => {
redis!.get(userToken, async (_, twCtx) => { redis.get(userToken, async (_, twCtx) => {
res(twCtx); res(twCtx);
}); });
}); });

View File

@ -1,7 +1,6 @@
import * as http from 'http'; import * as http from 'http';
import * as websocket from 'websocket'; import * as websocket from 'websocket';
import * as redis from 'redis'; import * as redis from 'redis';
import Xev from 'xev';
import MainStreamConnection from './stream'; import MainStreamConnection from './stream';
import { ParsedUrlQuery } from 'querystring'; import { ParsedUrlQuery } from 'querystring';
@ -23,28 +22,24 @@ module.exports = (server: http.Server) => {
let ev: EventEmitter; let ev: EventEmitter;
if (config.redis) { // Connect to Redis
// Connect to Redis const subscriber = redis.createClient(
const subscriber = redis.createClient( config.redis.port, config.redis.host);
config.redis.port, config.redis.host);
subscriber.subscribe('misskey'); subscriber.subscribe('misskey');
ev = new EventEmitter(); ev = new EventEmitter();
subscriber.on('message', async (_, data) => { subscriber.on('message', async (_, data) => {
const obj = JSON.parse(data); const obj = JSON.parse(data);
ev.emit(obj.channel, obj.message); ev.emit(obj.channel, obj.message);
}); });
connection.once('close', () => { connection.once('close', () => {
subscriber.unsubscribe(); subscriber.unsubscribe();
subscriber.quit(); subscriber.quit();
}); });
} else {
ev = new Xev();
}
const main = new MainStreamConnection(connection, ev, user, app); const main = new MainStreamConnection(connection, ev, user, app);

View File

@ -8,7 +8,7 @@ import { genId } from '../../../misc/gen-id';
import { createNotification } from '../../create-notification'; import { createNotification } from '../../create-notification';
export default async function(user: User, note: Note, choice: number) { export default async function(user: User, note: Note, choice: number) {
const poll = await Polls.findOne({ noteId: note.id }); const poll = await Polls.findOne(note.id);
if (poll == null) throw 'poll not found'; if (poll == null) throw 'poll not found';
@ -40,7 +40,7 @@ export default async function(user: User, note: Note, choice: number) {
// Increment votes count // Increment votes count
const index = choice + 1; // In SQL, array index is 1 based const index = choice + 1; // In SQL, array index is 1 based
await Polls.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE noteId = '${poll.noteId}'`); await Polls.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE "noteId" = '${poll.noteId}'`);
publishNoteStream(note.id, 'pollVoted', { publishNoteStream(note.id, 'pollVoted', {
choice: choice, choice: choice,

View File

@ -16,7 +16,7 @@ import { NoteReaction } from '../../../models/entities/note-reaction';
import { createNotification } from '../../create-notification'; import { createNotification } from '../../create-notification';
import { isDuplicateKeyValueError } from '../../../misc/is-duplicate-key-value-error'; import { isDuplicateKeyValueError } from '../../../misc/is-duplicate-key-value-error';
export default async (user: User, note: Note, reaction: string) => { export default async (user: User, note: Note, reaction?: string) => {
// Myself // Myself
if (note.userId === user.id) { if (note.userId === user.id) {
throw new IdentifiableError('2d8e7297-1873-4c00-8404-792c68d7bef0', 'cannot react to my note'); throw new IdentifiableError('2d8e7297-1873-4c00-8404-792c68d7bef0', 'cannot react to my note');

View File

@ -1,33 +1,19 @@
import redis from '../db/redis'; import redis from '../db/redis';
import Xev from 'xev';
import { User } from '../models/entities/user'; import { User } from '../models/entities/user';
import { Note } from '../models/entities/note'; import { Note } from '../models/entities/note';
import { UserList } from '../models/entities/user-list'; import { UserList } from '../models/entities/user-list';
import { ReversiGame } from '../models/entities/games/reversi/game'; import { ReversiGame } from '../models/entities/games/reversi/game';
class Publisher { class Publisher {
private ev: Xev | null = null;
constructor() {
// Redisがインストールされてないときはプロセス間通信を使う
if (redis == null) {
this.ev = new Xev();
}
}
private publish = (channel: string, type: string | null, value?: any): void => { private publish = (channel: string, type: string | null, value?: any): void => {
const message = type == null ? value : value == null ? const message = type == null ? value : value == null ?
{ type: type, body: null } : { type: type, body: null } :
{ type: type, body: value }; { type: type, body: value };
if (this.ev) { redis.publish('misskey', JSON.stringify({
this.ev.emit(channel, message); channel: channel,
} else { message: message
redis!.publish('misskey', JSON.stringify({ }));
channel: channel,
message: message
}));
}
} }
public publishMainStream = (userId: User['id'], type: string, value?: any): void => { public publishMainStream = (userId: User['id'], type: string, value?: any): void => {

View File

@ -63,7 +63,9 @@ export async function updateHashtag(user: User, tag: string, isUserAttached = fa
} }
} }
q.execute(); if (Object.keys(set).length > 0) {
q.execute();
}
} else { } else {
if (isUserAttached) { if (isUserAttached) {
Hashtags.save({ Hashtags.save({