diff --git a/.config/docker_example.env b/.config/docker_example.env new file mode 100644 index 000000000..411d93659 --- /dev/null +++ b/.config/docker_example.env @@ -0,0 +1,5 @@ +# db settings +POSTGRES_PASSWORD="example-misskey-pass" +POSTGRES_USER="example-misskey-user" +POSTGRES_DB="misskey" + diff --git a/.config/example.yml b/.config/example.yml index 70c096baa..10239f1a7 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -1,8 +1,16 @@ +#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# Misskey configuration +#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +# ┌─────┐ +#───┘ URL └───────────────────────────────────────────────────── + # Final accessible URL seen by a user. url: https://example.tld/ +# ┌───────────────────────┐ +#───┘ Port and TLS settings └─────────────────────────────────── -### Port and TLS settings ###################################### # # Misskey supports two deployment options for public. # @@ -30,28 +38,51 @@ url: https://example.tld/ # You need to set Certificate in 'https' section. # To use option 1, uncomment below line. -# port: 3000 # A port that your Misskey server should listen. +#port: 3000 # A port that your Misskey server should listen. # To use option 2, uncomment below lines. -# port: 443 -# -# https: -# # path for certification -# key: /etc/letsencrypt/live/example.tld/privkey.pem -# cert: /etc/letsencrypt/live/example.tld/fullchain.pem +#port: 443 -################################################################ +#https: +# # path for certification +# key: /etc/letsencrypt/live/example.tld/privkey.pem +# cert: /etc/letsencrypt/live/example.tld/fullchain.pem +# ┌──────────────────────────┐ +#───┘ PostgreSQL configuration └──────────────────────────────── -mongodb: +db: host: localhost - port: 27017 + port: 5432 + + # Database name db: misskey + + # Auth user: example-misskey-user pass: example-misskey-pass +# ┌─────────────────────┐ +#───┘ Redis configuration └───────────────────────────────────── + +#redis: +# host: localhost +# port: 6379 +# pass: example-pass + +# ┌─────────────────────────────┐ +#───┘ Elasticsearch configuration └───────────────────────────── + +#elasticsearch: +# host: localhost +# port: 9200 +# pass: null + +# ┌────────────────────────────────────┐ +#───┘ File storage (Drive) configuration └────────────────────── + drive: - storage: 'db' + storage: 'fs' # OR @@ -88,26 +119,44 @@ drive: # accessKey: XXX # secretKey: YYY +# ┌───────────────┐ +#───┘ ID generation └─────────────────────────────────────────── + +# You can select the ID generation method. +# You don't usually need to change this setting, but you can +# change it according to your preferences. + +# Available methods: +# aid1 ... Use AID for ID generation (with random 1 char) +# aid2 ... Use AID for ID generation (with random 2 chars) +# aid3 ... Use AID for ID generation (with random 3 chars) +# aid4 ... Use AID for ID generation (with random 4 chars) +# ulid ... Use ulid for ID generation +# objectid ... This is left for backward compatibility. + +# AID(n) is the original ID generation method. +# 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 └───────────────────────────────────── + # If enabled: # The first account created is automatically marked as Admin. autoAdmin: true -# -# Below settings are optional -# - -# Redis -#redis: -# host: localhost -# port: 6379 -# pass: example-pass - -# Elasticsearch -#elasticsearch: -# host: localhost -# port: 9200 -# pass: null - # Whether disable HSTS #disableHsts: true diff --git a/.config/mongo_initdb_example.js b/.config/mongo_initdb_example.js deleted file mode 100644 index b7e7321f3..000000000 --- a/.config/mongo_initdb_example.js +++ /dev/null @@ -1,13 +0,0 @@ -var user = { - user: 'example-misskey-user', - pwd: 'example-misskey-pass', - roles: [ - { - role: 'readWrite', - db: 'misskey' - } - ] -}; - -db.createUser(user); - diff --git a/.dockerignore b/.dockerignore old mode 100755 new mode 100644 index a25d4e571..324c4bce5 --- a/.dockerignore +++ b/.dockerignore @@ -5,8 +5,8 @@ .vscode Dockerfile build/ +db/ docker-compose.yml -node_modules/ -mongo/ -redis/ elasticsearch/ +node_modules/ +redis/ diff --git a/.gitignore b/.gitignore index 98ee82cd7..650d4f612 100644 --- a/.gitignore +++ b/.gitignore @@ -8,14 +8,15 @@ built /data /.cache-loader +/db +/elasticsearch npm-debug.log *.pem run.bat api-docs.json *.log /redis -/mongo -/elasticsearch *.code-workspace yarn.lock .DS_Store +/files diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c1ad1f804..a45ed5cb5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -75,3 +75,61 @@ src ... Source code test ... Test code ``` + +## Notes +### placeholder +SQLをクエリビルダで組み立てる際、使用するプレースホルダは重複してはならない +例えば +``` ts +query.andWhere(new Brackets(qb => { + for (const type of ps.fileType) { + qb.orWhere(`:type = ANY(note.attachedFileTypes)`, { type: type }); + } +})); +``` +と書くと、ループ中で`type`というプレースホルダが複数回使われてしまいおかしくなる +だから次のようにする必要がある +```ts +query.andWhere(new Brackets(qb => { + for (const type of ps.fileType) { + const i = ps.fileType.indexOf(type); + qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, { [`type${i}`]: type }); + } +})); +``` + +### `null` in SQL +SQLを発行する際、パラメータが`null`になる可能性のある場合はSQL文を出し分けなければならない +例えば +``` ts +query.where('file.folderId = :folderId', { folderId: ps.folderId }); +``` +という処理で、`ps.folderId`が`null`だと結果的に`file.folderId = null`のようなクエリが発行されてしまい、これは正しいSQLではないので期待した結果が得られない +だから次のようにする必要がある +``` ts +if (ps.folderId) { + query.where('file.folderId = :folderId', { folderId: ps.folderId }); +} else { + query.where('file.folderId IS NULL'); +} +``` + +### `[]` in SQL +SQLを発行する際、`IN`のパラメータが`[]`(空の配列)になる可能性のある場合はSQL文を出し分けなければならない +例えば +``` ts +const users = await Users.find({ + id: In(userIds) +}); +``` +という処理で、`userIds`が`[]`だと結果的に`user.id IN ()`のようなクエリが発行されてしまい、これは正しいSQLではないので期待した結果が得られない +だから次のようにする必要がある +``` ts +const users = userIds.length > 0 ? await Users.find({ + id: In(userIds) +}) : []; +``` + +### `undefined`にご用心 +MongoDBの時とは違い、findOneでレコードを取得する時に対象レコードが存在しない場合 **`undefined`** が返ってくるので注意。 +MongoDBは`null`で返してきてたので、その感覚で`if (x === null)`とか書くとバグる。代わりに`if (x == null)`と書いてください diff --git a/Dockerfile b/Dockerfile index ad04fb33d..ec7d8a6a2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,8 +23,9 @@ RUN apk add --no-cache \ zlib-dev RUN npm i -g yarn -COPY . ./ +COPY package.json ./ RUN yarn install +COPY . ./ RUN yarn build FROM base AS runner diff --git a/binding.gyp b/binding.gyp deleted file mode 100644 index 0349526d5..000000000 --- a/binding.gyp +++ /dev/null @@ -1,9 +0,0 @@ -{ - 'targets': [ - { - 'target_name': 'crypto_key', - 'sources': ['src/crypto_key.cc'], - 'include_dirs': [' { - const promise = new Promise(async (res, rej) => { - const file = await DriveFile.findOne(prev ? { - _id: { $gt: prev._id } - } : {}, { - sort: { - _id: 1 - } - }); - - prev = file; - - const user = await User.findOne({ _id: file.metadata.userId }); - - DriveFile.update({ - _id: file._id - }, { - $set: { - 'metadata._user': { - host: user.host - } - } - }).then(() => { - res([i, file]); - }).catch(rej); - }); - - promise.then(([i, file]) => { - console.log(chalk`{gray ${i}} {green done: {bold ${file._id}} ${file.filename}}`); - }); - - return promise; - }); - } - - return await sequential(promiseGens); -} - -main().then(() => { - console.log('ALL DONE'); -}).catch(console.error); diff --git a/cli/migration/2.4.0.js b/cli/migration/2.4.0.js deleted file mode 100644 index aa37849aa..000000000 --- a/cli/migration/2.4.0.js +++ /dev/null @@ -1,71 +0,0 @@ -// for Node.js interpret - -const chalk = require('chalk'); -const sequential = require('promise-sequential'); - -const { default: User } = require('../../built/models/user'); -const { default: DriveFile } = require('../../built/models/drive-file'); - -async function main() { - const promiseGens = []; - - const count = await User.count({}); - - let prev; - - for (let i = 0; i < count; i++) { - promiseGens.push(() => { - const promise = new Promise(async (res, rej) => { - const user = await User.findOne(prev ? { - _id: { $gt: prev._id } - } : {}, { - sort: { - _id: 1 - } - }); - - prev = user; - - const set = {}; - - if (user.avatarId != null) { - const file = await DriveFile.findOne({ _id: user.avatarId }); - - if (file && file.metadata.properties.avgColor) { - set.avatarColor = file.metadata.properties.avgColor; - } - } - - if (user.bannerId != null) { - const file = await DriveFile.findOne({ _id: user.bannerId }); - - if (file && file.metadata.properties.avgColor) { - set.bannerColor = file.metadata.properties.avgColor; - } - } - - if (Object.keys(set).length === 0) return res([i, user]); - - User.update({ - _id: user._id - }, { - $set: set - }).then(() => { - res([i, user]); - }).catch(rej); - }); - - promise.then(([i, user]) => { - console.log(chalk`{gray ${i}} {green done: {bold ${user._id}} @${user.username}}`); - }); - - return promise; - }); - } - - return await sequential(promiseGens); -} - -main().then(() => { - console.log('ALL DONE'); -}).catch(console.error); diff --git a/cli/migration/5.0.0.js b/cli/migration/5.0.0.js deleted file mode 100644 index bef103fe4..000000000 --- a/cli/migration/5.0.0.js +++ /dev/null @@ -1,9 +0,0 @@ -const { default: DriveFile } = require('../../built/models/drive-file'); - -DriveFile.update({}, { - $rename: { - 'metadata.isMetaOnly': 'metadata.withoutChunks' - } -}, { - multi: true -}); diff --git a/cli/migration/7.0.0.js b/cli/migration/7.0.0.js deleted file mode 100644 index fa5e363db..000000000 --- a/cli/migration/7.0.0.js +++ /dev/null @@ -1,134 +0,0 @@ -const { default: Stats } = require('../../built/models/stats'); -const { default: User } = require('../../built/models/user'); -const { default: Note } = require('../../built/models/note'); -const { default: DriveFile } = require('../../built/models/drive-file'); - -const now = new Date(); -const y = now.getFullYear(); -const m = now.getMonth(); -const d = now.getDate(); -const today = new Date(y, m, d); - -async function main() { - const localUsersCount = await User.count({ - host: null - }); - - const remoteUsersCount = await User.count({ - host: { $ne: null } - }); - - const localNotesCount = await Note.count({ - '_user.host': null - }); - - const remoteNotesCount = await Note.count({ - '_user.host': { $ne: null } - }); - - const localDriveFilesCount = await DriveFile.count({ - 'metadata._user.host': null - }); - - const remoteDriveFilesCount = await DriveFile.count({ - 'metadata._user.host': { $ne: null } - }); - - const localDriveFilesSize = await DriveFile - .aggregate([{ - $match: { - 'metadata._user.host': null, - 'metadata.deletedAt': { $exists: false } - } - }, { - $project: { - length: true - } - }, { - $group: { - _id: null, - usage: { $sum: '$length' } - } - }]) - .then(aggregates => { - if (aggregates.length > 0) { - return aggregates[0].usage; - } - return 0; - }); - - const remoteDriveFilesSize = await DriveFile - .aggregate([{ - $match: { - 'metadata._user.host': { $ne: null }, - 'metadata.deletedAt': { $exists: false } - } - }, { - $project: { - length: true - } - }, { - $group: { - _id: null, - usage: { $sum: '$length' } - } - }]) - .then(aggregates => { - if (aggregates.length > 0) { - return aggregates[0].usage; - } - return 0; - }); - - await Stats.insert({ - date: today, - users: { - local: { - total: localUsersCount, - diff: 0 - }, - remote: { - total: remoteUsersCount, - diff: 0 - } - }, - notes: { - local: { - total: localNotesCount, - diff: 0, - diffs: { - normal: 0, - reply: 0, - renote: 0 - } - }, - remote: { - total: remoteNotesCount, - diff: 0, - diffs: { - normal: 0, - reply: 0, - renote: 0 - } - } - }, - drive: { - local: { - totalCount: localDriveFilesCount, - totalSize: localDriveFilesSize, - diffCount: 0, - diffSize: 0 - }, - remote: { - totalCount: remoteDriveFilesCount, - totalSize: remoteDriveFilesSize, - diffCount: 0, - diffSize: 0 - } - } - }); - - console.log('done'); -} - -main(); diff --git a/cli/migration/8.0.0.js b/cli/migration/8.0.0.js deleted file mode 100644 index fd6cb2452..000000000 --- a/cli/migration/8.0.0.js +++ /dev/null @@ -1,144 +0,0 @@ -const { default: Stats } = require('../../built/models/stats'); -const { default: User } = require('../../built/models/user'); -const { default: Note } = require('../../built/models/note'); -const { default: DriveFile } = require('../../built/models/drive-file'); - -const now = new Date(); -const y = now.getFullYear(); -const m = now.getMonth(); -const d = now.getDate(); -const h = now.getHours(); -const date = new Date(y, m, d, h); - -async function main() { - await Stats.update({}, { - $set: { - span: 'day' - } - }, { - multi: true - }); - - const localUsersCount = await User.count({ - host: null - }); - - const remoteUsersCount = await User.count({ - host: { $ne: null } - }); - - const localNotesCount = await Note.count({ - '_user.host': null - }); - - const remoteNotesCount = await Note.count({ - '_user.host': { $ne: null } - }); - - const localDriveFilesCount = await DriveFile.count({ - 'metadata._user.host': null - }); - - const remoteDriveFilesCount = await DriveFile.count({ - 'metadata._user.host': { $ne: null } - }); - - const localDriveFilesSize = await DriveFile - .aggregate([{ - $match: { - 'metadata._user.host': null, - 'metadata.deletedAt': { $exists: false } - } - }, { - $project: { - length: true - } - }, { - $group: { - _id: null, - usage: { $sum: '$length' } - } - }]) - .then(aggregates => { - if (aggregates.length > 0) { - return aggregates[0].usage; - } - return 0; - }); - - const remoteDriveFilesSize = await DriveFile - .aggregate([{ - $match: { - 'metadata._user.host': { $ne: null }, - 'metadata.deletedAt': { $exists: false } - } - }, { - $project: { - length: true - } - }, { - $group: { - _id: null, - usage: { $sum: '$length' } - } - }]) - .then(aggregates => { - if (aggregates.length > 0) { - return aggregates[0].usage; - } - return 0; - }); - - await Stats.insert({ - date: date, - span: 'hour', - users: { - local: { - total: localUsersCount, - diff: 0 - }, - remote: { - total: remoteUsersCount, - diff: 0 - } - }, - notes: { - local: { - total: localNotesCount, - diff: 0, - diffs: { - normal: 0, - reply: 0, - renote: 0 - } - }, - remote: { - total: remoteNotesCount, - diff: 0, - diffs: { - normal: 0, - reply: 0, - renote: 0 - } - } - }, - drive: { - local: { - totalCount: localDriveFilesCount, - totalSize: localDriveFilesSize, - diffCount: 0, - diffSize: 0 - }, - remote: { - totalCount: remoteDriveFilesCount, - totalSize: remoteDriveFilesSize, - diffCount: 0, - diffSize: 0 - } - } - }); - - console.log('done'); -} - -main(); diff --git a/docker-compose.yml b/docker-compose.yml index 7ff8f6a26..184738aa8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ services: build: . restart: always links: - - mongo + - db # - redis # - es ports: @@ -19,21 +19,18 @@ services: # image: redis:4.0-alpine # networks: # - internal_network -### Uncomment to enable Redis persistance -## volumes: -## - ./redis:/data +# volumes: +# - ./redis:/data - mongo: + db: restart: always - image: mongo:4.1 + image: postgres:11.2-alpine networks: - internal_network - environment: - MONGO_INITDB_DATABASE: "misskey" + env_file: + - .config/docker.env volumes: - - ./.config/mongo_initdb.js:/docker-entrypoint-initdb.d/mongo_initdb.js:ro -### Uncomment to enable MongoDB persistance -# - ./mongo:/data + - ./db:/var/lib/postgresql/data # es: # restart: always @@ -42,9 +39,8 @@ services: # - "ES_JAVA_OPTS=-Xms512m -Xmx512m" # networks: # - internal_network -#### Uncomment to enable ES persistence -## volumes: -## - ./elasticsearch:/usr/share/elasticsearch/data +# volumes: +# - ./elasticsearch:/usr/share/elasticsearch/data networks: internal_network: diff --git a/docs/backup.fr.md b/docs/backup.fr.md deleted file mode 100644 index 19e99068c..000000000 --- a/docs/backup.fr.md +++ /dev/null @@ -1,22 +0,0 @@ -Comment faire une sauvegarde de votre Misskey ? -========================== - -Assurez-vous d'avoir installé **mongodb-tools**. - ---- - -Dans votre terminal : -``` shell -$ mongodump --archive=db-backup -u -p -``` - -Pour plus de détails, merci de consulter [la documentation de mongodump](https://docs.mongodb.com/manual/reference/program/mongodump/). - -Restauration -------- - -``` shell -$ mongorestore --archive=db-backup -``` - -Pour plus de détails, merci de consulter [la documentation de mongorestore](https://docs.mongodb.com/manual/reference/program/mongorestore/). diff --git a/docs/backup.md b/docs/backup.md deleted file mode 100644 index a69af0255..000000000 --- a/docs/backup.md +++ /dev/null @@ -1,22 +0,0 @@ -How to backup your Misskey -========================== - -Make sure **mongodb-tools** installed. - ---- - -In your shell: -``` shell -$ mongodump --archive=db-backup -u -p -``` - -For details, please see [mongodump docs](https://docs.mongodb.com/manual/reference/program/mongodump/). - -Restore -------- - -``` shell -$ mongorestore --archive=db-backup -``` - -For details, please see [mongorestore docs](https://docs.mongodb.com/manual/reference/program/mongorestore/). diff --git a/docs/docker.en.md b/docs/docker.en.md index f0fcdb66d..ee69b6d7a 100644 --- a/docs/docker.en.md +++ b/docs/docker.en.md @@ -15,9 +15,37 @@ This guide describes how to install and setup Misskey with Docker. *2.* Configure Misskey ---------------------------------------------------------------- -1. `cp .config/example.yml .config/default.yml` Copy the `.config/example.yml` and rename it to `default.yml`. -2. `cp .config/mongo_initdb_example.js .config/mongo_initdb.js` Copy the `.config/mongo_initdb_example.js` and rename it to `mongo_initdb.js`. -3. Edit `default.yml` and `mongo_initdb.js`. + +Create configuration files with following: + +```bash +cd .config +cp example.yml default.yml +cp docker_example.env docker.env +``` + +### `default.yml` + +Edit this file the same as non-Docker environment. +However hostname of Postgresql, Redis and Elasticsearch are not `localhost`, they are set in `docker-compose.yml`. +The following is default hostname: + +| Service | Hostname | +|---------------|----------| +| Postgresql | `db` | +| Redis | `redis` | +| Elasticsearch | `es` | + +### `docker.env` + +Configure Postgresql in this file. +The minimum required settings are: + +| name | Description | +|---------------------|---------------| +| `POSTGRES_PASSWORD` | Password | +| `POSTGRES_USER` | Username | +| `POSTGRES_DB` | Database name | *3.* Configure Docker ---------------------------------------------------------------- diff --git a/docs/docker.ja.md b/docs/docker.ja.md index 0baf28572..060d4e7bd 100644 --- a/docs/docker.ja.md +++ b/docs/docker.ja.md @@ -13,11 +13,39 @@ Dockerを使ったMisskey構築方法 2. `cd misskey` misskeyディレクトリに移動 3. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` [最新のリリース](https://github.com/syuilo/misskey/releases/latest)を確認 -*2.* 設定ファイルを作成する +*2.* 設定ファイルの作成と編集 ---------------------------------------------------------------- -1. `cp .config/example.yml .config/default.yml` `.config/example.yml`をコピーし名前を`default.yml`にする -2. `cp .config/mongo_initdb_example.js .config/mongo_initdb.js` `.config/mongo_initdb_example.js`をコピーし名前を`mongo_initdb.js`にする -3. `default.yml`と`mongo_initdb.js`を編集する + +下記コマンドで設定ファイルを作成してください。 + +```bash +cd .config +cp example.yml default.yml +cp docker_example.env docker.env +``` + +### `default.yml`の編集 + +非Docker環境と同じ様に編集してください。 +ただし、Postgresql、RedisとElasticsearchのホストは`localhost`ではなく、`docker-compose.yml`で設定されたサービス名になっています。 +標準設定では次の通りです。 + +| サービス | ホスト名 | +|---------------|---------| +| Postgresql |`db` | +| Redis |`redis` | +| Elasticsearch |`es` | + +### `docker.env`の編集 + +このファイルはPostgresqlの設定を記述します。 +最低限記述する必要がある設定は次の通りです。 + +| 設定 | 内容 | +|---------------------|--------------| +| `POSTGRES_PASSWORD` | パスワード | +| `POSTGRES_USER` | ユーザー名 | +| `POSTGRES_DB` | データベース名 | *3.* Dockerの設定 ---------------------------------------------------------------- diff --git a/docs/setup.en.md b/docs/setup.en.md index 112508144..28de1f32f 100644 --- a/docs/setup.en.md +++ b/docs/setup.en.md @@ -22,8 +22,8 @@ adduser --disabled-password --disabled-login misskey Please install and setup these softwares: #### Dependencies :package: -* **[Node.js](https://nodejs.org/en/)** >= 10.0.0 -* **[MongoDB](https://www.mongodb.com/)** >= 3.6 +* **[Node.js](https://nodejs.org/en/)** >= 11.7.0 +* **[PostgreSQL](https://www.postgresql.org/)** >= 10 ##### Optional * [Redis](https://redis.io/) @@ -31,13 +31,9 @@ Please install and setup these softwares: * [Elasticsearch](https://www.elastic.co/) - required to enable the search feature * [FFmpeg](https://www.ffmpeg.org/) -*3.* Setup MongoDB +*3.* Setup PostgreSQL ---------------------------------------------------------------- -As root: -1. `mongo` Go to the mongo shell -2. `use misskey` Use the misskey database -3. `db.createUser( { user: "misskey", pwd: "", roles: [ { role: "readWrite", db: "misskey" } ] } )` Create the misskey user. -4. `exit` You're done! +:) *4.* Install Misskey ---------------------------------------------------------------- @@ -68,7 +64,13 @@ If you're still encountering errors about some modules, use node-gyp: 3. `node-gyp build` 4. `NODE_ENV=production npm run build` -*7.* That is it. +*7.* Init DB +---------------------------------------------------------------- +``` shell +npm run init +``` + +*8.* That is it. ---------------------------------------------------------------- Well done! Now, you have an environment that run to Misskey. diff --git a/docs/setup.fr.md b/docs/setup.fr.md index 959ec3392..217a4c6a5 100644 --- a/docs/setup.fr.md +++ b/docs/setup.fr.md @@ -22,8 +22,8 @@ adduser --disabled-password --disabled-login misskey Installez les paquets suivants : #### Dépendences :package: -* **[Node.js](https://nodejs.org/en/)** >= 10.0.0 -* **[MongoDB](https://www.mongodb.com/)** >= 3.6 +* **[Node.js](https://nodejs.org/en/)** >= 11.7.0 +* **[PostgreSQL](https://www.postgresql.org/)** >= 10 ##### Optionnels * [Redis](https://redis.io/) @@ -31,13 +31,9 @@ Installez les paquets suivants : * [Elasticsearch](https://www.elastic.co/) - requis pour pouvoir activer la fonctionnalité de recherche * [FFmpeg](https://www.ffmpeg.org/) -*3.* Paramètrage de MongoDB +*3.* Paramètrage de PostgreSQL ---------------------------------------------------------------- -En root : -1. `mongo` Ouvrez le shell mongo -2. `use misskey` Utilisez la base de données misskey -3. `db.createUser( { user: "misskey", pwd: "", roles: [ { role: "readWrite", db: "misskey" } ] } )` Créez l'utilisateur misskey. -4. `exit` Vous avez terminé ! +:) *4.* Installation de Misskey ---------------------------------------------------------------- diff --git a/docs/setup.ja.md b/docs/setup.ja.md index 8a21e104d..1543541ee 100644 --- a/docs/setup.ja.md +++ b/docs/setup.ja.md @@ -22,8 +22,8 @@ adduser --disabled-password --disabled-login misskey これらのソフトウェアをインストール・設定してください: #### 依存関係 :package: -* **[Node.js](https://nodejs.org/en/)** (10.0.0以上) -* **[MongoDB](https://www.mongodb.com/)** (3.6以上) +* **[Node.js](https://nodejs.org/en/)** (11.7.0以上) +* **[PostgreSQL](https://www.postgresql.org/)** (10以上) ##### オプション * [Redis](https://redis.io/) @@ -38,13 +38,9 @@ adduser --disabled-password --disabled-login misskey * 検索機能を有効にするためにはインストールが必要です。 * [FFmpeg](https://www.ffmpeg.org/) -*3.* MongoDBの設定 +*3.* PostgreSQLの設定 ---------------------------------------------------------------- -ルートで: -1. `mongo` mongoシェルを起動 -2. `use misskey` misskeyデータベースを使用 -3. `db.createUser( { user: "misskey", pwd: "", roles: [ { role: "readWrite", db: "misskey" } ] } )` misskeyユーザーを作成 -4. `exit` mongoシェルを終了 +:) *4.* Misskeyのインストール ---------------------------------------------------------------- @@ -74,7 +70,13 @@ Debianをお使いであれば、`build-essential`パッケージをインスト 3. `node-gyp build` 4. `NODE_ENV=production npm run build` -*7.* 以上です! +*7.* データベースを初期化 +---------------------------------------------------------------- +``` shell +npm run init +``` + +*8.* 以上です! ---------------------------------------------------------------- お疲れ様でした。これでMisskeyを動かす準備は整いました。 diff --git a/gulpfile.ts b/gulpfile.ts index b2956c240..bf0b87ef2 100644 --- a/gulpfile.ts +++ b/gulpfile.ts @@ -49,7 +49,6 @@ gulp.task('build:copy:views', () => gulp.task('build:copy', gulp.parallel('build:copy:views', () => gulp.src([ - './build/Release/crypto_key.node', './src/const.json', './src/server/web/views/**/*', './src/**/assets/**/*', diff --git a/index.js b/index.js index 5b7d1347a..bc7e8b2f3 100644 --- a/index.js +++ b/index.js @@ -1 +1 @@ -require('./built'); +require('./built').default(); diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index d4457b659..43d8cb309 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1238,11 +1238,6 @@ admin/views/instance.vue: save: "保存" saved: "保存しました" user-recommendation-config: "おすすめユーザー" - enable-external-user-recommendation: "外部ユーザーレコメンデーションを有効にする" - external-user-recommendation-engine: "エンジン" - external-user-recommendation-engine-desc: "例: https://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-misskey-api.cgi?{{host}}+{{user}}+{{limit}}+{{offset}}" - external-user-recommendation-timeout: "タイムアウト" - external-user-recommendation-timeout-desc: "ミリ秒単位 (例: 300000)" email-config: "メールサーバーの設定" email-config-info: "メールアドレス確認やパスワードリセットの際に使われます。" enable-email: "メール配信を有効にする" diff --git a/package.json b/package.json index 39e6aa7de..a7c12862f 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "misskey", "author": "syuilo ", - "version": "10.99.0", - "codename": "nighthike", + "version": "11.0.0", + "codename": "daybreak", "repository": { "type": "git", "url": "https://github.com/syuilo/misskey.git" @@ -11,6 +11,7 @@ "private": true, "scripts": { "start": "node ./index.js", + "init": "node ./built/init.js", "debug": "DEBUG=misskey:* node ./index.js", "build": "webpack && gulp build", "webpack": "webpack", @@ -62,10 +63,9 @@ "@types/koa-send": "4.1.1", "@types/koa-views": "2.0.3", "@types/koa__cors": "2.2.3", + "@types/lolex": "3.1.1", "@types/minio": "7.0.1", - "@types/mkdirp": "0.5.2", - "@types/mocha": "5.2.5", - "@types/mongodb": "3.1.20", + "@types/mocha": "5.2.6", "@types/node": "11.10.4", "@types/nodemailer": "4.6.6", "@types/nprogress": "0.0.29", @@ -107,6 +107,7 @@ "chai": "4.2.0", "chai-http": "4.2.1", "chalk": "2.4.2", + "cli-highlight": "2.1.0", "commander": "2.20.0", "content-disposition": "0.5.3", "crc-32": "1.2.0", @@ -114,12 +115,10 @@ "cssnano": "4.1.10", "dateformat": "3.0.3", "deep-equal": "1.0.1", - "deepcopy": "0.6.3", "diskusage": "1.0.0", "double-ended-queue": "2.1.0-0", "elasticsearch": "15.4.1", "emojilib": "2.4.0", - "escape-regexp": "0.0.1", "eslint": "5.15.1", "eslint-plugin-vue": "5.2.2", "eventemitter3": "3.1.0", @@ -163,23 +162,22 @@ "koa-views": "6.2.0", "langmap": "0.0.16", "loader-utils": "1.2.3", + "lolex": "3.1.0", "lookup-dns-cache": "2.1.0", "minio": "7.0.5", - "mkdirp": "0.5.1", - "mocha": "5.2.0", + "mocha": "6.0.2", "moji": "0.5.1", "moment": "2.24.0", - "mongodb": "3.2.2", - "monk": "6.0.6", "ms": "2.1.1", - "nan": "2.12.1", "nested-property": "0.0.7", + "node-fetch": "2.3.0", "nodemailer": "5.1.1", "nprogress": "0.2.0", "object-assign-deep": "0.4.0", "os-utils": "0.0.14", "parse5": "5.1.0", "parsimmon": "1.12.0", + "pg": "7.9.0", "portscanner": "2.2.0", "postcss-loader": "3.0.0", "prismjs": "1.16.0", @@ -195,10 +193,12 @@ "recaptcha-promise": "0.1.3", "reconnecting-websocket": "4.1.10", "redis": "2.8.0", + "reflect-metadata": "0.1.13", "rename": "1.0.4", "request": "2.88.0", "request-promise-native": "1.0.7", "request-stats": "3.0.0", + "require-all": "3.0.0", "rimraf": "2.6.3", "rndstr": "1.0.0", "s-age": "1.1.2", @@ -219,12 +219,14 @@ "tinycolor2": "1.4.1", "tmp": "0.0.33", "ts-loader": "5.3.3", - "ts-node": "8.0.3", + "ts-node": "7.0.1", "tslint": "5.13.1", "tslint-sonarts": "1.9.0", + "typeorm": "0.2.16-rc.1", "typescript": "3.3.3333", "typescript-eslint-parser": "22.0.0", "uglify-es": "3.3.9", + "ulid": "2.3.0", "url-loader": "1.1.2", "uuid": "3.3.2", "v-animate-css": "0.0.3", diff --git a/src/@types/deepcopy.d.ts b/src/@types/deepcopy.d.ts deleted file mode 100644 index f276b7e67..000000000 --- a/src/@types/deepcopy.d.ts +++ /dev/null @@ -1,19 +0,0 @@ -declare module 'deepcopy' { - type DeepcopyCustomizerValueType = 'Object'; - - type DeepcopyCustomizer = ( - value: T, - valueType: DeepcopyCustomizerValueType) => T; - - interface IDeepcopyOptions { - customizer: DeepcopyCustomizer; - } - - function deepcopy( - value: T, - options?: IDeepcopyOptions | DeepcopyCustomizer): T; - - namespace deepcopy {} // Hack - - export = deepcopy; -} diff --git a/src/@types/escape-regexp.d.ts b/src/@types/escape-regexp.d.ts deleted file mode 100644 index d68e6048a..000000000 --- a/src/@types/escape-regexp.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -declare module 'escape-regexp' { - function escapeRegExp(str: string): string; - - namespace escapeRegExp {} // Hack - - export = escapeRegExp; -} diff --git a/src/argv.ts b/src/argv.ts index b5540441c..562852d17 100644 --- a/src/argv.ts +++ b/src/argv.ts @@ -15,5 +15,8 @@ program .parse(process.argv); if (process.env.MK_ONLY_QUEUE) program.onlyQueue = true; +if (process.env.NODE_ENV === 'test') program.disableClustering = true; +if (process.env.NODE_ENV === 'test') program.quiet = true; +if (process.env.NODE_ENV === 'test') program.noDaemons = true; export { program }; diff --git a/src/boot/index.ts b/src/boot/index.ts new file mode 100644 index 000000000..2c86d8ed8 --- /dev/null +++ b/src/boot/index.ts @@ -0,0 +1,77 @@ +import * as cluster from 'cluster'; +import chalk from 'chalk'; +import Xev from 'xev'; + +import Logger from '../services/logger'; +import { program } from '../argv'; + +// for typeorm +import 'reflect-metadata'; +import { masterMain } from './master'; +import { workerMain } from './worker'; + +const logger = new Logger('core', 'cyan'); +const clusterLogger = logger.createSubLogger('cluster', 'orange', false); +const ev = new Xev(); + +/** + * Init process + */ +export default async function() { + process.title = `Misskey (${cluster.isMaster ? 'master' : 'worker'})`; + + if (cluster.isMaster || program.disableClustering) { + await masterMain(); + + if (cluster.isMaster) { + ev.mount(); + } + } + + if (cluster.isWorker || program.disableClustering) { + await workerMain(); + } + + // ユニットテスト時にMisskeyが子プロセスで起動された時のため + // それ以外のときは process.send は使えないので弾く + if (process.send) { + process.send('ok'); + } +} + +//#region Events + +// Listen new workers +cluster.on('fork', worker => { + clusterLogger.debug(`Process forked: [${worker.id}]`); +}); + +// Listen online workers +cluster.on('online', worker => { + clusterLogger.debug(`Process is now online: [${worker.id}]`); +}); + +// Listen for dying workers +cluster.on('exit', worker => { + // Replace the dead worker, + // we're not sentimental + clusterLogger.error(chalk.red(`[${worker.id}] died :(`)); + cluster.fork(); +}); + +// Display detail of unhandled promise rejection +if (!program.quiet) { + process.on('unhandledRejection', console.dir); +} + +// Display detail of uncaught exception +process.on('uncaughtException', err => { + logger.error(err); +}); + +// Dying away... +process.on('exit', code => { + logger.info(`The process is going to exit with code ${code}`); +}); + +//#endregion diff --git a/src/boot/master.ts b/src/boot/master.ts new file mode 100644 index 000000000..2d4080fdb --- /dev/null +++ b/src/boot/master.ts @@ -0,0 +1,176 @@ +import * as os from 'os'; +import * as cluster from 'cluster'; +import chalk from 'chalk'; +import * as portscanner from 'portscanner'; +import * as isRoot from 'is-root'; + +import Logger from '../services/logger'; +import loadConfig from '../config/load'; +import { Config } from '../config/types'; +import { lessThan } from '../prelude/array'; +import * as pkg from '../../package.json'; +import { program } from '../argv'; +import { showMachineInfo } from '../misc/show-machine-info'; +import { initDb } from '../db/postgre'; + +const logger = new Logger('core', 'cyan'); +const bootLogger = logger.createSubLogger('boot', 'magenta', false); + +function greet() { + if (!program.quiet) { + //#region Misskey logo + const v = `v${pkg.version}`; + console.log(' _____ _ _ '); + console.log(' | |_|___ ___| |_ ___ _ _ '); + console.log(' | | | | |_ -|_ -| \'_| -_| | |'); + console.log(' |_|_|_|_|___|___|_,_|___|_ |'); + console.log(' ' + chalk.gray(v) + (' |___|\n'.substr(v.length))); + //#endregion + + console.log(' Misskey is maintained by @syuilo, @AyaMorisawa, @mei23, and @acid-chicken.'); + console.log(chalk.keyword('orange')(' If you like Misskey, please donate to support development. https://www.patreon.com/syuilo')); + + console.log(''); + console.log(chalk`< ${os.hostname()} {gray (PID: ${process.pid.toString()})} >`); + } + + bootLogger.info('Welcome to Misskey!'); + bootLogger.info(`Misskey v${pkg.version}`, null, true); +} + +/** + * Init master process + */ +export async function masterMain() { + greet(); + + let config: Config; + + try { + // initialize app + config = await init(); + + if (config.port == null) { + bootLogger.error('The port is not configured. Please configure port.', null, true); + process.exit(1); + } + + if (process.platform === 'linux' && isWellKnownPort(config.port) && !isRoot()) { + bootLogger.error('You need root privileges to listen on well-known port on Linux', null, true); + process.exit(1); + } + + if (!await isPortAvailable(config.port)) { + bootLogger.error(`Port ${config.port} is already in use`, null, true); + process.exit(1); + } + } catch (e) { + bootLogger.error('Fatal error occurred during initialization', null, true); + process.exit(1); + } + + bootLogger.succ('Misskey initialized'); + + if (!program.disableClustering) { + await spawnWorkers(config.clusterLimit); + } + + if (!program.noDaemons) { + require('../daemons/server-stats').default(); + require('../daemons/notes-stats').default(); + require('../daemons/queue-stats').default(); + } + + bootLogger.succ(`Now listening on port ${config.port} on ${config.url}`, null, true); +} + +const runningNodejsVersion = process.version.slice(1).split('.').map(x => parseInt(x, 10)); +const requiredNodejsVersion = [11, 7, 0]; +const satisfyNodejsVersion = !lessThan(runningNodejsVersion, requiredNodejsVersion); + +function isWellKnownPort(port: number): boolean { + return port < 1024; +} + +async function isPortAvailable(port: number): Promise { + return await portscanner.checkPortStatus(port, '127.0.0.1') === 'closed'; +} + +function showEnvironment(): void { + const env = process.env.NODE_ENV; + const logger = bootLogger.createSubLogger('env'); + logger.info(typeof env == 'undefined' ? 'NODE_ENV is not set' : `NODE_ENV: ${env}`); + + if (env !== 'production') { + logger.warn('The environment is not in production mode.'); + logger.warn('DO NOT USE FOR PRODUCTION PURPOSE!', null, true); + } + + logger.info(`You ${isRoot() ? '' : 'do not '}have root privileges`); +} + +/** + * Init app + */ +async function init(): Promise { + showEnvironment(); + + const nodejsLogger = bootLogger.createSubLogger('nodejs'); + + nodejsLogger.info(`Version ${runningNodejsVersion.join('.')}`); + + if (!satisfyNodejsVersion) { + nodejsLogger.error(`Node.js version is less than ${requiredNodejsVersion.join('.')}. Please upgrade it.`, null, true); + process.exit(1); + } + + await showMachineInfo(bootLogger); + + const configLogger = bootLogger.createSubLogger('config'); + let config; + + try { + config = loadConfig(); + } catch (exception) { + if (typeof exception === 'string') { + configLogger.error(exception); + process.exit(1); + } + if (exception.code === 'ENOENT') { + configLogger.error('Configuration file not found', null, true); + process.exit(1); + } + throw exception; + } + + configLogger.succ('Loaded'); + + // Try to connect to DB + try { + bootLogger.info('Connecting database...'); + await initDb(); + } catch (e) { + bootLogger.error('Cannot connect to database', null, true); + bootLogger.error(e); + process.exit(1); + } + + return config; +} + +async function spawnWorkers(limit: number = Infinity) { + const workers = Math.min(limit, os.cpus().length); + bootLogger.info(`Starting ${workers} worker${workers === 1 ? '' : 's'}...`); + await Promise.all([...Array(workers)].map(spawnWorker)); + bootLogger.succ('All workers started'); +} + +function spawnWorker(): Promise { + return new Promise(res => { + const worker = cluster.fork(); + worker.on('message', message => { + if (message !== 'ready') return; + res(); + }); + }); +} diff --git a/src/boot/worker.ts b/src/boot/worker.ts new file mode 100644 index 000000000..ca3716972 --- /dev/null +++ b/src/boot/worker.ts @@ -0,0 +1,20 @@ +import * as cluster from 'cluster'; +import { initDb } from '../db/postgre'; + +/** + * Init worker process + */ +export async function workerMain() { + await initDb(); + + // start server + await require('../server').default(); + + // start job queue + require('../queue').default(); + + if (cluster.isWorker) { + // Send a 'ready' message to parent process + process.send('ready'); + } +} diff --git a/src/client/app/admin/views/drive.vue b/src/client/app/admin/views/drive.vue index 7812aadaa..491050b1f 100644 --- a/src/client/app/admin/views/drive.vue +++ b/src/client/app/admin/views/drive.vue @@ -48,7 +48,7 @@
{{ file.type }} - {{ file.datasize | bytes }} + {{ file.size | bytes }}
diff --git a/src/client/app/admin/views/hashtags.vue b/src/client/app/admin/views/hashtags.vue index b3190c29c..e1cc4b494 100644 --- a/src/client/app/admin/views/hashtags.vue +++ b/src/client/app/admin/views/hashtags.vue @@ -3,7 +3,7 @@
- + {{ $t('save') }}
@@ -18,18 +18,18 @@ export default Vue.extend({ i18n: i18n('admin/views/hashtags.vue'), data() { return { - hidedTags: '', + hiddenTags: '', }; }, created() { this.$root.getMeta().then(meta => { - this.hidedTags = meta.hidedTags.join('\n'); + this.hiddenTags = meta.hiddenTags.join('\n'); }); }, methods: { save() { this.$root.api('admin/update-meta', { - hidedTags: this.hidedTags.split('\n') + hiddenTags: this.hiddenTags.split('\n') }).then(() => { //this.$root.os.apis.dialog({ text: `Saved` }); }).catch(e => { diff --git a/src/client/app/admin/views/instance.vue b/src/client/app/admin/views/instance.vue index 2d2a07784..bc2a5fba8 100644 --- a/src/client/app/admin/views/instance.vue +++ b/src/client/app/admin/views/instance.vue @@ -77,12 +77,6 @@
summaly Proxy
URL -
-
{{ $t('user-recommendation-config') }}
- {{ $t('enable-external-user-recommendation') }} - {{ $t('external-user-recommendation-engine') }} - {{ $t('external-user-recommendation-timeout') }} -
{{ $t('save') }}
@@ -184,9 +178,6 @@ export default Vue.extend({ discordClientSecret: null, proxyAccount: null, inviteCode: null, - enableExternalUserRecommendation: false, - externalUserRecommendationEngine: null, - externalUserRecommendationTimeout: null, summalyProxy: null, enableEmail: false, email: null, @@ -205,8 +196,8 @@ export default Vue.extend({ created() { this.$root.getMeta().then(meta => { - this.maintainerName = meta.maintainer.name; - this.maintainerEmail = meta.maintainer.email; + this.maintainerName = meta.maintainerName; + this.maintainerEmail = meta.maintainerEmail; this.disableRegistration = meta.disableRegistration; this.disableLocalTimeline = meta.disableLocalTimeline; this.disableGlobalTimeline = meta.disableGlobalTimeline; @@ -236,9 +227,6 @@ export default Vue.extend({ this.enableDiscordIntegration = meta.enableDiscordIntegration; this.discordClientId = meta.discordClientId; this.discordClientSecret = meta.discordClientSecret; - this.enableExternalUserRecommendation = meta.enableExternalUserRecommendation; - this.externalUserRecommendationEngine = meta.externalUserRecommendationEngine; - this.externalUserRecommendationTimeout = meta.externalUserRecommendationTimeout; this.summalyProxy = meta.summalyProxy; this.enableEmail = meta.enableEmail; this.email = meta.email; @@ -299,9 +287,6 @@ export default Vue.extend({ enableDiscordIntegration: this.enableDiscordIntegration, discordClientId: this.discordClientId, discordClientSecret: this.discordClientSecret, - enableExternalUserRecommendation: this.enableExternalUserRecommendation, - externalUserRecommendationEngine: this.externalUserRecommendationEngine, - externalUserRecommendationTimeout: parseInt(this.externalUserRecommendationTimeout, 10), summalyProxy: this.summalyProxy, enableEmail: this.enableEmail, email: this.email, diff --git a/src/client/app/admin/views/logs.vue b/src/client/app/admin/views/logs.vue index 4a2d957ed..5c2cfdb39 100644 --- a/src/client/app/admin/views/logs.vue +++ b/src/client/app/admin/views/logs.vue @@ -19,7 +19,7 @@
- +
[{{ log.domain.join('.') }}] {{ log.message }} diff --git a/src/client/app/admin/views/users.vue b/src/client/app/admin/views/users.vue index ff485cec8..0f46b564a 100644 --- a/src/client/app/admin/views/users.vue +++ b/src/client/app/admin/views/users.vue @@ -165,7 +165,7 @@ export default Vue.extend({ /** 処理対象ユーザーの情報を更新する */ async refreshUser() { - this.$root.api('admin/show-user', { userId: this.user._id }).then(info => { + this.$root.api('admin/show-user', { userId: this.user.id }).then(info => { this.user = info; }); }, @@ -173,7 +173,7 @@ export default Vue.extend({ async resetPassword() { if (!await this.getConfirmed(this.$t('reset-password-confirm'))) return; - this.$root.api('admin/reset-password', { userId: this.user._id }).then(res => { + this.$root.api('admin/reset-password', { userId: this.user.id }).then(res => { this.$root.dialog({ type: 'success', text: this.$t('password-updated', { password: res.password }) @@ -187,7 +187,7 @@ export default Vue.extend({ this.verifying = true; const process = async () => { - await this.$root.api('admin/verify-user', { userId: this.user._id }); + await this.$root.api('admin/verify-user', { userId: this.user.id }); this.$root.dialog({ type: 'success', text: this.$t('verified') @@ -212,7 +212,7 @@ export default Vue.extend({ this.unverifying = true; const process = async () => { - await this.$root.api('admin/unverify-user', { userId: this.user._id }); + await this.$root.api('admin/unverify-user', { userId: this.user.id }); this.$root.dialog({ type: 'success', text: this.$t('unverified') @@ -233,7 +233,7 @@ export default Vue.extend({ async silenceUser() { const process = async () => { - await this.$root.api('admin/silence-user', { userId: this.user._id }); + await this.$root.api('admin/silence-user', { userId: this.user.id }); this.$root.dialog({ type: 'success', splash: true @@ -252,7 +252,7 @@ export default Vue.extend({ async unsilenceUser() { const process = async () => { - await this.$root.api('admin/unsilence-user', { userId: this.user._id }); + await this.$root.api('admin/unsilence-user', { userId: this.user.id }); this.$root.dialog({ type: 'success', splash: true @@ -275,7 +275,7 @@ export default Vue.extend({ this.suspending = true; const process = async () => { - await this.$root.api('admin/suspend-user', { userId: this.user._id }); + await this.$root.api('admin/suspend-user', { userId: this.user.id }); this.$root.dialog({ type: 'success', text: this.$t('suspended') @@ -300,7 +300,7 @@ export default Vue.extend({ this.unsuspending = true; const process = async () => { - await this.$root.api('admin/unsuspend-user', { userId: this.user._id }); + await this.$root.api('admin/unsuspend-user', { userId: this.user.id }); this.$root.dialog({ type: 'success', text: this.$t('unsuspended') @@ -320,7 +320,7 @@ export default Vue.extend({ }, async updateRemoteUser() { - this.$root.api('admin/update-remote-user', { userId: this.user._id }).then(res => { + this.$root.api('admin/update-remote-user', { userId: this.user.id }).then(res => { this.$root.dialog({ type: 'success', text: this.$t('remote-user-updated') diff --git a/src/client/app/auth/views/form.vue b/src/client/app/auth/views/form.vue index 105af375b..d5d6b25f0 100644 --- a/src/client/app/auth/views/form.vue +++ b/src/client/app/auth/views/form.vue @@ -14,15 +14,15 @@

{{ $t('permission-ask') }}

diff --git a/src/client/app/common/define-widget.ts b/src/client/app/common/define-widget.ts index 1efdbb188..632ddf2ed 100644 --- a/src/client/app/common/define-widget.ts +++ b/src/client/app/common/define-widget.ts @@ -45,15 +45,9 @@ export default function (data: { this.$watch('props', () => { this.mergeProps(); }); - - this.bakeProps(); }, methods: { - bakeProps() { - this.bakedOldProps = JSON.stringify(this.props); - }, - mergeProps() { if (data.props) { const defaultProps = data.props(); @@ -65,17 +59,10 @@ export default function (data: { }, save() { - if (this.bakedOldProps == JSON.stringify(this.props)) return; - - this.bakeProps(); - if (this.platform == 'deck') { this.$store.commit('device/updateDeckColumn', this.column); } else { - this.$root.api('i/update_widget', { - id: this.id, - data: this.props - }); + this.$store.commit('device/updateWidget', this.widget); } } } diff --git a/src/client/app/common/scripts/note-mixin.ts b/src/client/app/common/scripts/note-mixin.ts index 5707d1bb4..67bbe8c0a 100644 --- a/src/client/app/common/scripts/note-mixin.ts +++ b/src/client/app/common/scripts/note-mixin.ts @@ -70,8 +70,8 @@ export default (opts: Opts = {}) => ({ }, reactionsCount(): number { - return this.appearNote.reactionCounts - ? sum(Object.values(this.appearNote.reactionCounts)) + return this.appearNote.reactions + ? sum(Object.values(this.appearNote.reactions)) : 0; }, diff --git a/src/client/app/common/scripts/note-subscriber.ts b/src/client/app/common/scripts/note-subscriber.ts index c2b4dd6df..02d810ded 100644 --- a/src/client/app/common/scripts/note-subscriber.ts +++ b/src/client/app/common/scripts/note-subscriber.ts @@ -87,16 +87,16 @@ export default prop => ({ case 'reacted': { const reaction = body.reaction; - if (this.$_ns_target.reactionCounts == null) { - Vue.set(this.$_ns_target, 'reactionCounts', {}); + if (this.$_ns_target.reactions == null) { + Vue.set(this.$_ns_target, 'reactions', {}); } - if (this.$_ns_target.reactionCounts[reaction] == null) { - Vue.set(this.$_ns_target.reactionCounts, reaction, 0); + if (this.$_ns_target.reactions[reaction] == null) { + Vue.set(this.$_ns_target.reactions, reaction, 0); } // Increment the count - this.$_ns_target.reactionCounts[reaction]++; + this.$_ns_target.reactions[reaction]++; if (body.userId == this.$store.state.i.id) { Vue.set(this.$_ns_target, 'myReaction', reaction); @@ -107,16 +107,16 @@ export default prop => ({ case 'unreacted': { const reaction = body.reaction; - if (this.$_ns_target.reactionCounts == null) { + if (this.$_ns_target.reactions == null) { return; } - if (this.$_ns_target.reactionCounts[reaction] == null) { + if (this.$_ns_target.reactions[reaction] == null) { return; } // Decrement the count - if (this.$_ns_target.reactionCounts[reaction] > 0) this.$_ns_target.reactionCounts[reaction]--; + if (this.$_ns_target.reactions[reaction] > 0) this.$_ns_target.reactions[reaction]--; if (body.userId == this.$store.state.i.id) { Vue.set(this.$_ns_target, 'myReaction', null); @@ -125,9 +125,11 @@ export default prop => ({ } case 'pollVoted': { - if (body.userId == this.$store.state.i.id) return; const choice = body.choice; - this.$_ns_target.poll.choices.find(c => c.id === choice).votes++; + this.$_ns_target.poll.choices[choice].votes++; + if (body.userId == this.$store.state.i.id) { + Vue.set(this.$_ns_target.poll.choices[choice], 'isVoted', true); + } break; } diff --git a/src/client/app/common/views/components/avatar.vue b/src/client/app/common/views/components/avatar.vue index dce594e70..c074fb600 100644 --- a/src/client/app/common/views/components/avatar.vue +++ b/src/client/app/common/views/components/avatar.vue @@ -55,11 +55,12 @@ export default Vue.extend({ }, icon(): any { return { - backgroundColor: this.lightmode - ? `rgb(${this.user.avatarColor.slice(0, 3).join(',')})` - : this.user.avatarColor && this.user.avatarColor.length == 3 - ? `rgb(${this.user.avatarColor.join(',')})` - : null, + backgroundColor: this.user.avatarColor ? this.lightmode + ? this.user.avatarColor + : this.user.avatarColor.startsWith('rgb(') + ? this.user.avatarColor + : null + : null, backgroundImage: this.lightmode ? null : `url(${this.url})`, borderRadius: this.$store.state.settings.circleIcons ? '100%' : null }; @@ -67,7 +68,7 @@ export default Vue.extend({ }, mounted() { if (this.user.avatarColor) { - this.$el.style.color = `rgb(${this.user.avatarColor.slice(0, 3).join(',')})`; + this.$el.style.color = this.user.avatarColor; } }, methods: { diff --git a/src/client/app/common/views/components/games/reversi/reversi.game.vue b/src/client/app/common/views/components/games/reversi/reversi.game.vue index c6fc36db3..bd0401f78 100644 --- a/src/client/app/common/views/components/games/reversi/reversi.game.vue +++ b/src/client/app/common/views/components/games/reversi/reversi.game.vue @@ -24,11 +24,11 @@
- {{ String.fromCharCode(64 + i) }} + {{ String.fromCharCode(64 + i) }}
-
{{ i }}
+
{{ i }}
-
{{ i }}
+
{{ i }}
- {{ String.fromCharCode(64 + i) }} + {{ String.fromCharCode(64 + i) }}
@@ -71,9 +71,9 @@
-

{{ $t('is-llotheo') }}

-

{{ $t('looped-map') }}

-

{{ $t('can-put-everywhere') }}

+

{{ $t('is-llotheo') }}

+

{{ $t('looped-map') }}

+

{{ $t('can-put-everywhere') }}

@@ -160,8 +160,8 @@ export default Vue.extend({ cellsStyle(): any { return { - 'grid-template-rows': `repeat(${this.game.settings.map.length}, 1fr)`, - 'grid-template-columns': `repeat(${this.game.settings.map[0].length}, 1fr)` + 'grid-template-rows': `repeat(${this.game.map.length}, 1fr)`, + 'grid-template-columns': `repeat(${this.game.map[0].length}, 1fr)` }; } }, @@ -169,10 +169,10 @@ export default Vue.extend({ watch: { logPos(v) { if (!this.game.isEnded) return; - this.o = new Reversi(this.game.settings.map, { - isLlotheo: this.game.settings.isLlotheo, - canPutEverywhere: this.game.settings.canPutEverywhere, - loopedBoard: this.game.settings.loopedBoard + this.o = new Reversi(this.game.map, { + isLlotheo: this.game.isLlotheo, + canPutEverywhere: this.game.canPutEverywhere, + loopedBoard: this.game.loopedBoard }); for (const log of this.logs.slice(0, v)) { this.o.put(log.color, log.pos); @@ -184,10 +184,10 @@ export default Vue.extend({ created() { this.game = this.initGame; - this.o = new Reversi(this.game.settings.map, { - isLlotheo: this.game.settings.isLlotheo, - canPutEverywhere: this.game.settings.canPutEverywhere, - loopedBoard: this.game.settings.loopedBoard + this.o = new Reversi(this.game.map, { + isLlotheo: this.game.isLlotheo, + canPutEverywhere: this.game.canPutEverywhere, + loopedBoard: this.game.loopedBoard }); for (const log of this.game.logs) { @@ -286,10 +286,10 @@ export default Vue.extend({ onRescue(game) { this.game = game; - this.o = new Reversi(this.game.settings.map, { - isLlotheo: this.game.settings.isLlotheo, - canPutEverywhere: this.game.settings.canPutEverywhere, - loopedBoard: this.game.settings.loopedBoard + this.o = new Reversi(this.game.map, { + isLlotheo: this.game.isLlotheo, + canPutEverywhere: this.game.canPutEverywhere, + loopedBoard: this.game.loopedBoard }); for (const log of this.game.logs) { diff --git a/src/client/app/common/views/components/games/reversi/reversi.room.vue b/src/client/app/common/views/components/games/reversi/reversi.room.vue index d5d148790..9ee1a78b8 100644 --- a/src/client/app/common/views/components/games/reversi/reversi.room.vue +++ b/src/client/app/common/views/components/games/reversi/reversi.room.vue @@ -17,9 +17,9 @@
-
-
-
+
+
@@ -35,9 +35,9 @@
- {{ $t('random') }} - {{ this.$t('black-is').split('{}')[0] }}{{ this.$t('black-is').split('{}')[1] }} - {{ this.$t('black-is').split('{}')[0] }}{{ this.$t('black-is').split('{}')[1] }} + {{ $t('random') }} + {{ this.$t('black-is').split('{}')[0] }}{{ this.$t('black-is').split('{}')[1] }} + {{ this.$t('black-is').split('{}')[0] }}{{ this.$t('black-is').split('{}')[1] }}
@@ -47,9 +47,9 @@
- {{ $t('is-llotheo') }} - {{ $t('looped-map') }} - {{ $t('can-put-everywhere') }} + {{ $t('is-llotheo') }} + {{ $t('looped-map') }} + {{ $t('can-put-everywhere') }}
@@ -159,8 +159,8 @@ export default Vue.extend({ this.connection.on('initForm', this.onInitForm); this.connection.on('message', this.onMessage); - if (this.game.user1Id != this.$store.state.i.id && this.game.settings.form1) this.form = this.game.settings.form1; - if (this.game.user2Id != this.$store.state.i.id && this.game.settings.form2) this.form = this.game.settings.form2; + if (this.game.user1Id != this.$store.state.i.id && this.game.form1) this.form = this.game.form1; + if (this.game.user2Id != this.$store.state.i.id && this.game.form2) this.form = this.game.form2; }, beforeDestroy() { @@ -189,18 +189,19 @@ export default Vue.extend({ this.$forceUpdate(); }, - updateSettings() { + updateSettings(key: string) { this.connection.send('updateSettings', { - settings: this.game.settings + key: key, + value: this.game[key] }); }, - onUpdateSettings(settings) { - this.game.settings = settings; - if (this.game.settings.map == null) { + onUpdateSettings({ key, value }) { + this.game[key] = value; + if (this.game.map == null) { this.mapName = null; } else { - const found = Object.values(maps).find(x => x.data.join('') == this.game.settings.map.join('')); + const found = Object.values(maps).find(x => x.data.join('') == this.game.map.join('')); this.mapName = found ? found.name : '-Custom-'; } }, @@ -224,27 +225,27 @@ export default Vue.extend({ onMapChange() { if (this.mapName == null) { - this.game.settings.map = null; + this.game.map = null; } else { - this.game.settings.map = Object.values(maps).find(x => x.name == this.mapName).data; + this.game.map = Object.values(maps).find(x => x.name == this.mapName).data; } this.$forceUpdate(); this.updateSettings(); }, onPixelClick(pos, pixel) { - const x = pos % this.game.settings.map[0].length; - const y = Math.floor(pos / this.game.settings.map[0].length); + const x = pos % this.game.map[0].length; + const y = Math.floor(pos / this.game.map[0].length); const newPixel = pixel == ' ' ? '-' : pixel == '-' ? 'b' : pixel == 'b' ? 'w' : ' '; - const line = this.game.settings.map[y].split(''); + const line = this.game.map[y].split(''); line[x] = newPixel; - this.$set(this.game.settings.map, y, line.join('')); + this.$set(this.game.map, y, line.join('')); this.$forceUpdate(); - this.updateSettings(); + this.updateSettings('map'); } } }); diff --git a/src/client/app/common/views/components/games/reversi/reversi.vue b/src/client/app/common/views/components/games/reversi/reversi.vue index b6803cd7f..d33471a04 100644 --- a/src/client/app/common/views/components/games/reversi/reversi.vue +++ b/src/client/app/common/views/components/games/reversi/reversi.vue @@ -106,7 +106,7 @@ export default Vue.extend({ async nav(game, actualNav = true) { if (this.selfNav) { // 受け取ったゲーム情報が省略されたものなら完全な情報を取得する - if (game != null && (game.settings == null || game.settings.map == null)) { + if (game != null && game.map == null) { game = await this.$root.api('games/reversi/games/show', { gameId: game.id }); diff --git a/src/client/app/common/views/components/instance.vue b/src/client/app/common/views/components/instance.vue index 7b8d4f8e0..497e4976f 100644 --- a/src/client/app/common/views/components/instance.vue +++ b/src/client/app/common/views/components/instance.vue @@ -2,7 +2,7 @@
-

{{ meta.name }}

+

{{ meta.name || 'Misskey' }}

{{ $t('start') }}
diff --git a/src/client/app/common/views/components/mention.vue b/src/client/app/common/views/components/mention.vue index 11dddbd52..e1f67282b 100644 --- a/src/client/app/common/views/components/mention.vue +++ b/src/client/app/common/views/components/mention.vue @@ -33,7 +33,7 @@ export default Vue.extend({ }, computed: { canonical(): string { - return `@${this.username}@${toUnicode(this.host)}`; + return this.host === localHost ? `@${this.username}` : `@${this.username}@${toUnicode(this.host)}`; }, isMe(): boolean { return this.$store.getters.isSignedIn && this.canonical.toLowerCase() === `@${this.$store.state.i.username}@${toUnicode(localHost)}`.toLowerCase(); diff --git a/src/client/app/common/views/components/poll.vue b/src/client/app/common/views/components/poll.vue index ba14ba3a4..dc3aaa34f 100644 --- a/src/client/app/common/views/components/poll.vue +++ b/src/client/app/common/views/components/poll.vue @@ -1,7 +1,7 @@