From f0a29721c9fb10f97faf386bc9d6b1b2fad97895 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 7 Apr 2019 21:50:36 +0900 Subject: Use PostgreSQL instead of MongoDB (#4572) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * wip * Update note.ts * Update timeline.ts * Update core.ts * wip * Update generate-visibility-query.ts * wip * wip * wip * wip * wip * Update global-timeline.ts * wip * wip * wip * Update vote.ts * wip * wip * Update create.ts * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * Update files.ts * wip * wip * Update CONTRIBUTING.md * wip * wip * wip * wip * wip * wip * wip * wip * Update read-notification.ts * wip * wip * wip * wip * wip * wip * wip * Update cancel.ts * wip * wip * wip * Update show.ts * wip * wip * Update gen-id.ts * Update create.ts * Update id.ts * wip * wip * wip * wip * wip * wip * wip * Docker: Update files about Docker (#4599) * Docker: Use cache if files used by `yarn install` was not updated This patch reduces the number of times to installing node_modules. For example, `yarn install` step will be skipped when only ".config/default.yml" is updated. * Docker: Migrate MongoDB to Postgresql Misskey uses Postgresql as a database instead of Mongodb since version 11. * Docker: Uncomment about data persistence This patch will save a lot of databases. * wip * wip * wip * Update activitypub.ts * wip * wip * wip * Update logs.ts * wip * Update drive-file.ts * Update register.ts * wip * wip * Update mentions.ts * wip * wip * wip * Update recommendation.ts * wip * Update index.ts * wip * Update recommendation.ts * Doc: Update docker.ja.md and docker.en.md (#1) (#4608) Update how to set up misskey. * wip * :v: * wip * Update note.ts * Update postgre.ts * wip * wip * wip * wip * Update add-file.ts * wip * wip * wip * Clean up * Update logs.ts * wip * :pizza: * wip * Ad notes * wip * Update api-visibility.ts * Update note.ts * Update add-file.ts * tests * tests * Update postgre.ts * Update utils.ts * wip * wip * Refactor * wip * Refactor * wip * wip * Update show-users.ts * Update update-instance.ts * wip * Update feed.ts * Update outbox.ts * Update outbox.ts * Update user.ts * wip * Update list.ts * Update update-hashtag.ts * wip * Update update-hashtag.ts * Refactor * Update update.ts * wip * wip * :v: * clean up * docs * Update push.ts * wip * Update api.ts * wip * :v: * Update make-pagination-query.ts * :v: * Delete hashtags.ts * Update instances.ts * Update instances.ts * Update create.ts * Update search.ts * Update reversi-game.ts * Update signup.ts * Update user.ts * id * Update example.yml * :art: * objectid * fix * reversi * reversi * Fix bug of chart engine * Add test of chart engine * Improve test * Better testing * Improve chart engine * Refactor * Add test of chart engine * Refactor * Add chart test * Fix bug * コミットし忘れ * Refactoring * :v: * Add tests * Add test * Extarct note tests * Refactor * 存在しないユーザーにメンションできなくなっていた問題を修正 * Fix bug * Update update-meta.ts * Fix bug * Update mention.vue * Fix bug * Update meta.ts * Update CONTRIBUTING.md * Fix bug * Fix bug * Fix bug * Clean up * Clean up * Update notification.ts * Clean up * Add mute tests * Add test * Refactor * Add test * Fix test * Refactor * Refactor * Add tests * Update utils.ts * Update utils.ts * Fix test * Update package.json * Update update.ts * Update manifest.ts * Fix bug * Fix bug * Add test * :art: * Update endpoint permissions * Updaye permisison * Update person.ts #4299 * データベースと同期しないように * Fix bug * Fix bug * Update reversi-game.ts * Use a feature of Node v11.7.0 to extract a public key (#4644) * wip * wip * :v: * Refactoring #1540 * test * test * test * test * test * test * test * Fix bug * Fix test * :sushi: * wip * #4471 * Add test for #4335 * Refactor * Fix test * Add tests * :clock4: * Fix bug * Add test * Add test * rename * Fix bug --- src/models/abuse-user-report.ts | 52 --- src/models/access-token.ts | 16 - src/models/app.ts | 102 ----- src/models/auth-session.ts | 49 --- src/models/blocking.ts | 56 --- src/models/drive-file-thumbnail.ts | 29 -- src/models/drive-file-webpublic.ts | 29 -- src/models/drive-file.ts | 232 ------------ src/models/drive-folder.ts | 75 ---- src/models/emoji.ts | 21 -- src/models/entities/abuse-user-report.ts | 41 ++ src/models/entities/access-token.ts | 45 +++ src/models/entities/app.ts | 60 +++ src/models/entities/auth-session.ts | 39 ++ src/models/entities/blocking.ts | 42 +++ src/models/entities/drive-file.ts | 154 ++++++++ src/models/entities/drive-folder.ts | 49 +++ src/models/entities/emoji.ts | 46 +++ src/models/entities/follow-request.ts | 85 +++++ src/models/entities/following.ts | 80 ++++ src/models/entities/games/reversi/game.ts | 133 +++++++ src/models/entities/games/reversi/matching.ts | 35 ++ src/models/entities/hashtag.ts | 87 +++++ src/models/entities/instance.ts | 132 +++++++ src/models/entities/log.ts | 46 +++ src/models/entities/messaging-message.ts | 64 ++++ src/models/entities/meta.ts | 264 +++++++++++++ src/models/entities/muting.ts | 42 +++ src/models/entities/note-favorite.ts | 35 ++ src/models/entities/note-reaction.ts | 42 +++ src/models/entities/note-unread.ts | 43 +++ src/models/entities/note-watching.ts | 52 +++ src/models/entities/note.ts | 236 ++++++++++++ src/models/entities/notification.ts | 94 +++++ src/models/entities/poll-vote.ts | 40 ++ src/models/entities/poll.ts | 67 ++++ src/models/entities/registration-tickets.ts | 17 + src/models/entities/signin.ts | 35 ++ src/models/entities/sw-subscription.ts | 37 ++ src/models/entities/user-keypair.ts | 24 ++ src/models/entities/user-list-joining.ts | 41 ++ src/models/entities/user-list.ts | 33 ++ src/models/entities/user-note-pinings.ts | 35 ++ src/models/entities/user-publickey.ts | 30 ++ src/models/entities/user-service-linking.ts | 108 ++++++ src/models/entities/user.ts | 297 +++++++++++++++ src/models/favorite.ts | 65 ---- src/models/follow-request.ts | 66 ---- src/models/following.ts | 27 -- src/models/games/reversi/game.ts | 111 ------ src/models/games/reversi/matching.ts | 45 --- src/models/hashtag.ts | 63 ---- src/models/id.ts | 4 + src/models/index.ts | 74 ++++ src/models/instance.ts | 90 ----- src/models/log.ts | 19 - src/models/messaging-message.ts | 75 ---- src/models/meta.ts | 257 ------------- src/models/mute.ts | 56 --- src/models/note-reaction.ts | 51 --- src/models/note-unread.ts | 19 - src/models/note-watching.ts | 15 - src/models/note.ts | 418 --------------------- src/models/notification.ts | 120 ------ src/models/poll-vote.ts | 17 - src/models/registration-tickets.ts | 12 - src/models/repositories/abuse-user-report.ts | 32 ++ src/models/repositories/app.ts | 36 ++ src/models/repositories/auth-session.ts | 19 + src/models/repositories/blocking.ts | 28 ++ src/models/repositories/drive-file.ts | 113 ++++++ src/models/repositories/drive-folder.ts | 49 +++ src/models/repositories/follow-request.ts | 19 + src/models/repositories/following.ts | 44 +++ src/models/repositories/games/reversi/game.ts | 49 +++ src/models/repositories/games/reversi/matching.ts | 27 ++ src/models/repositories/messaging-message.ts | 37 ++ src/models/repositories/muting.ts | 28 ++ src/models/repositories/note-favorite.ts | 25 ++ src/models/repositories/note-reaction.ts | 18 + src/models/repositories/note.ts | 210 +++++++++++ src/models/repositories/notification.ts | 47 +++ src/models/repositories/signin.ts | 11 + src/models/repositories/user-list.ts | 16 + src/models/repositories/user.ts | 198 ++++++++++ src/models/signin.ts | 34 -- src/models/sw-subscription.ts | 13 - src/models/user-list.ts | 41 -- src/models/user.ts | 438 ---------------------- 89 files changed, 3794 insertions(+), 2713 deletions(-) delete mode 100644 src/models/abuse-user-report.ts delete mode 100644 src/models/access-token.ts delete mode 100644 src/models/app.ts delete mode 100644 src/models/auth-session.ts delete mode 100644 src/models/blocking.ts delete mode 100644 src/models/drive-file-thumbnail.ts delete mode 100644 src/models/drive-file-webpublic.ts delete mode 100644 src/models/drive-file.ts delete mode 100644 src/models/drive-folder.ts delete mode 100644 src/models/emoji.ts create mode 100644 src/models/entities/abuse-user-report.ts create mode 100644 src/models/entities/access-token.ts create mode 100644 src/models/entities/app.ts create mode 100644 src/models/entities/auth-session.ts create mode 100644 src/models/entities/blocking.ts create mode 100644 src/models/entities/drive-file.ts create mode 100644 src/models/entities/drive-folder.ts create mode 100644 src/models/entities/emoji.ts create mode 100644 src/models/entities/follow-request.ts create mode 100644 src/models/entities/following.ts create mode 100644 src/models/entities/games/reversi/game.ts create mode 100644 src/models/entities/games/reversi/matching.ts create mode 100644 src/models/entities/hashtag.ts create mode 100644 src/models/entities/instance.ts create mode 100644 src/models/entities/log.ts create mode 100644 src/models/entities/messaging-message.ts create mode 100644 src/models/entities/meta.ts create mode 100644 src/models/entities/muting.ts create mode 100644 src/models/entities/note-favorite.ts create mode 100644 src/models/entities/note-reaction.ts create mode 100644 src/models/entities/note-unread.ts create mode 100644 src/models/entities/note-watching.ts create mode 100644 src/models/entities/note.ts create mode 100644 src/models/entities/notification.ts create mode 100644 src/models/entities/poll-vote.ts create mode 100644 src/models/entities/poll.ts create mode 100644 src/models/entities/registration-tickets.ts create mode 100644 src/models/entities/signin.ts create mode 100644 src/models/entities/sw-subscription.ts create mode 100644 src/models/entities/user-keypair.ts create mode 100644 src/models/entities/user-list-joining.ts create mode 100644 src/models/entities/user-list.ts create mode 100644 src/models/entities/user-note-pinings.ts create mode 100644 src/models/entities/user-publickey.ts create mode 100644 src/models/entities/user-service-linking.ts create mode 100644 src/models/entities/user.ts delete mode 100644 src/models/favorite.ts delete mode 100644 src/models/follow-request.ts delete mode 100644 src/models/following.ts delete mode 100644 src/models/games/reversi/game.ts delete mode 100644 src/models/games/reversi/matching.ts delete mode 100644 src/models/hashtag.ts create mode 100644 src/models/id.ts create mode 100644 src/models/index.ts delete mode 100644 src/models/instance.ts delete mode 100644 src/models/log.ts delete mode 100644 src/models/messaging-message.ts delete mode 100644 src/models/meta.ts delete mode 100644 src/models/mute.ts delete mode 100644 src/models/note-reaction.ts delete mode 100644 src/models/note-unread.ts delete mode 100644 src/models/note-watching.ts delete mode 100644 src/models/note.ts delete mode 100644 src/models/notification.ts delete mode 100644 src/models/poll-vote.ts delete mode 100644 src/models/registration-tickets.ts create mode 100644 src/models/repositories/abuse-user-report.ts create mode 100644 src/models/repositories/app.ts create mode 100644 src/models/repositories/auth-session.ts create mode 100644 src/models/repositories/blocking.ts create mode 100644 src/models/repositories/drive-file.ts create mode 100644 src/models/repositories/drive-folder.ts create mode 100644 src/models/repositories/follow-request.ts create mode 100644 src/models/repositories/following.ts create mode 100644 src/models/repositories/games/reversi/game.ts create mode 100644 src/models/repositories/games/reversi/matching.ts create mode 100644 src/models/repositories/messaging-message.ts create mode 100644 src/models/repositories/muting.ts create mode 100644 src/models/repositories/note-favorite.ts create mode 100644 src/models/repositories/note-reaction.ts create mode 100644 src/models/repositories/note.ts create mode 100644 src/models/repositories/notification.ts create mode 100644 src/models/repositories/signin.ts create mode 100644 src/models/repositories/user-list.ts create mode 100644 src/models/repositories/user.ts delete mode 100644 src/models/signin.ts delete mode 100644 src/models/sw-subscription.ts delete mode 100644 src/models/user-list.ts delete mode 100644 src/models/user.ts (limited to 'src/models') diff --git a/src/models/abuse-user-report.ts b/src/models/abuse-user-report.ts deleted file mode 100644 index f3900d348d..0000000000 --- a/src/models/abuse-user-report.ts +++ /dev/null @@ -1,52 +0,0 @@ -import * as mongo from 'mongodb'; -import * as deepcopy from 'deepcopy'; -import db from '../db/mongodb'; -import isObjectId from '../misc/is-objectid'; -import { pack as packUser } from './user'; - -const AbuseUserReport = db.get('abuseUserReports'); -AbuseUserReport.createIndex('userId'); -AbuseUserReport.createIndex('reporterId'); -AbuseUserReport.createIndex(['userId', 'reporterId'], { unique: true }); -export default AbuseUserReport; - -export interface IAbuseUserReport { - _id: mongo.ObjectID; - createdAt: Date; - userId: mongo.ObjectID; - reporterId: mongo.ObjectID; - comment: string; -} - -export const packMany = ( - reports: (string | mongo.ObjectID | IAbuseUserReport)[] -) => { - return Promise.all(reports.map(x => pack(x))); -}; - -export const pack = ( - report: any -) => new Promise(async (resolve, reject) => { - let _report: any; - - if (isObjectId(report)) { - _report = await AbuseUserReport.findOne({ - _id: report - }); - } else if (typeof report === 'string') { - _report = await AbuseUserReport.findOne({ - _id: new mongo.ObjectID(report) - }); - } else { - _report = deepcopy(report); - } - - // Rename _id to id - _report.id = _report._id; - delete _report._id; - - _report.reporter = await packUser(_report.reporterId, null, { detail: true }); - _report.user = await packUser(_report.userId, null, { detail: true }); - - resolve(_report); -}); diff --git a/src/models/access-token.ts b/src/models/access-token.ts deleted file mode 100644 index 66c5c91c0b..0000000000 --- a/src/models/access-token.ts +++ /dev/null @@ -1,16 +0,0 @@ -import * as mongo from 'mongodb'; -import db from '../db/mongodb'; - -const AccessToken = db.get('accessTokens'); -AccessToken.createIndex('token'); -AccessToken.createIndex('hash'); -export default AccessToken; - -export type IAccessToken = { - _id: mongo.ObjectID; - createdAt: Date; - appId: mongo.ObjectID; - userId: mongo.ObjectID; - token: string; - hash: string; -}; diff --git a/src/models/app.ts b/src/models/app.ts deleted file mode 100644 index 45d50bccda..0000000000 --- a/src/models/app.ts +++ /dev/null @@ -1,102 +0,0 @@ -import * as mongo from 'mongodb'; -import * as deepcopy from 'deepcopy'; -import AccessToken from './access-token'; -import db from '../db/mongodb'; -import isObjectId from '../misc/is-objectid'; -import config from '../config'; -import { dbLogger } from '../db/logger'; - -const App = db.get('apps'); -App.createIndex('secret'); -export default App; - -export type IApp = { - _id: mongo.ObjectID; - createdAt: Date; - userId: mongo.ObjectID | null; - secret: string; - name: string; - description: string; - permission: string[]; - callbackUrl: string; -}; - -/** - * Pack an app for API response - */ -export const pack = ( - app: any, - me?: any, - options?: { - detail?: boolean, - includeSecret?: boolean, - includeProfileImageIds?: boolean - } -) => new Promise(async (resolve, reject) => { - const opts = Object.assign({ - detail: false, - includeSecret: false, - includeProfileImageIds: false - }, options); - - let _app: any; - - const fields = opts.detail ? {} : { - name: true - }; - - // Populate the app if 'app' is ID - if (isObjectId(app)) { - _app = await App.findOne({ - _id: app - }); - } else if (typeof app === 'string') { - _app = await App.findOne({ - _id: new mongo.ObjectID(app) - }, { fields }); - } else { - _app = deepcopy(app); - } - - // Me - if (me && !isObjectId(me)) { - if (typeof me === 'string') { - me = new mongo.ObjectID(me); - } else { - me = me._id; - } - } - - // (データベースの欠損などで)アプリがデータベース上に見つからなかったとき - if (_app == null) { - dbLogger.warn(`[DAMAGED DB] (missing) pkg: app :: ${app}`); - return null; - } - - // Rename _id to id - _app.id = _app._id; - delete _app._id; - - // Visible by only owner - if (!opts.includeSecret) { - delete _app.secret; - } - - _app.iconUrl = _app.icon != null - ? `${config.driveUrl}/${_app.icon}` - : `${config.driveUrl}/app-default.jpg`; - - if (me) { - // 既に連携しているか - const exist = await AccessToken.count({ - appId: _app.id, - userId: me, - }, { - limit: 1 - }); - - _app.isAuthorized = exist === 1; - } - - resolve(_app); -}); diff --git a/src/models/auth-session.ts b/src/models/auth-session.ts deleted file mode 100644 index 428c707470..0000000000 --- a/src/models/auth-session.ts +++ /dev/null @@ -1,49 +0,0 @@ -import * as mongo from 'mongodb'; -import * as deepcopy from 'deepcopy'; -import db from '../db/mongodb'; -import isObjectId from '../misc/is-objectid'; -import { pack as packApp } from './app'; - -const AuthSession = db.get('authSessions'); -export default AuthSession; - -export interface IAuthSession { - _id: mongo.ObjectID; - createdAt: Date; - appId: mongo.ObjectID; - userId: mongo.ObjectID; - token: string; -} - -/** - * Pack an auth session for API response - * - * @param {any} session - * @param {any} me? - * @return {Promise} - */ -export const pack = ( - session: any, - me?: any -) => new Promise(async (resolve, reject) => { - let _session: any; - - // TODO: Populate session if it ID - _session = deepcopy(session); - - // Me - if (me && !isObjectId(me)) { - if (typeof me === 'string') { - me = new mongo.ObjectID(me); - } else { - me = me._id; - } - } - - delete _session._id; - - // Populate app - _session.app = await packApp(_session.appId, me); - - resolve(_session); -}); diff --git a/src/models/blocking.ts b/src/models/blocking.ts deleted file mode 100644 index 4bdaa741e9..0000000000 --- a/src/models/blocking.ts +++ /dev/null @@ -1,56 +0,0 @@ -import * as mongo from 'mongodb'; -import db from '../db/mongodb'; -import isObjectId from '../misc/is-objectid'; -import * as deepcopy from 'deepcopy'; -import { pack as packUser, IUser } from './user'; - -const Blocking = db.get('blocking'); -Blocking.createIndex('blockerId'); -Blocking.createIndex('blockeeId'); -Blocking.createIndex(['blockerId', 'blockeeId'], { unique: true }); -export default Blocking; - -export type IBlocking = { - _id: mongo.ObjectID; - createdAt: Date; - blockeeId: mongo.ObjectID; - blockerId: mongo.ObjectID; -}; - -export const packMany = ( - blockings: (string | mongo.ObjectID | IBlocking)[], - me?: string | mongo.ObjectID | IUser -) => { - return Promise.all(blockings.map(x => pack(x, me))); -}; - -export const pack = ( - blocking: any, - me?: any -) => new Promise(async (resolve, reject) => { - let _blocking: any; - - // Populate the blocking if 'blocking' is ID - if (isObjectId(blocking)) { - _blocking = await Blocking.findOne({ - _id: blocking - }); - } else if (typeof blocking === 'string') { - _blocking = await Blocking.findOne({ - _id: new mongo.ObjectID(blocking) - }); - } else { - _blocking = deepcopy(blocking); - } - - // Rename _id to id - _blocking.id = _blocking._id; - delete _blocking._id; - - // Populate blockee - _blocking.blockee = await packUser(_blocking.blockeeId, me, { - detail: true - }); - - resolve(_blocking); -}); diff --git a/src/models/drive-file-thumbnail.ts b/src/models/drive-file-thumbnail.ts deleted file mode 100644 index bdb3d010e6..0000000000 --- a/src/models/drive-file-thumbnail.ts +++ /dev/null @@ -1,29 +0,0 @@ -import * as mongo from 'mongodb'; -import monkDb, { nativeDbConn } from '../db/mongodb'; - -const DriveFileThumbnail = monkDb.get('driveFileThumbnails.files'); -DriveFileThumbnail.createIndex('metadata.originalId', { sparse: true, unique: true }); -export default DriveFileThumbnail; - -export const DriveFileThumbnailChunk = monkDb.get('driveFileThumbnails.chunks'); - -export const getDriveFileThumbnailBucket = async (): Promise => { - const db = await nativeDbConn(); - const bucket = new mongo.GridFSBucket(db, { - bucketName: 'driveFileThumbnails' - }); - return bucket; -}; - -export type IMetadata = { - originalId: mongo.ObjectID; -}; - -export type IDriveFileThumbnail = { - _id: mongo.ObjectID; - uploadDate: Date; - md5: string; - filename: string; - contentType: string; - metadata: IMetadata; -}; diff --git a/src/models/drive-file-webpublic.ts b/src/models/drive-file-webpublic.ts deleted file mode 100644 index d087c355d3..0000000000 --- a/src/models/drive-file-webpublic.ts +++ /dev/null @@ -1,29 +0,0 @@ -import * as mongo from 'mongodb'; -import monkDb, { nativeDbConn } from '../db/mongodb'; - -const DriveFileWebpublic = monkDb.get('driveFileWebpublics.files'); -DriveFileWebpublic.createIndex('metadata.originalId', { sparse: true, unique: true }); -export default DriveFileWebpublic; - -export const DriveFileWebpublicChunk = monkDb.get('driveFileWebpublics.chunks'); - -export const getDriveFileWebpublicBucket = async (): Promise => { - const db = await nativeDbConn(); - const bucket = new mongo.GridFSBucket(db, { - bucketName: 'driveFileWebpublics' - }); - return bucket; -}; - -export type IMetadata = { - originalId: mongo.ObjectID; -}; - -export type IDriveFileWebpublic = { - _id: mongo.ObjectID; - uploadDate: Date; - md5: string; - filename: string; - contentType: string; - metadata: IMetadata; -}; diff --git a/src/models/drive-file.ts b/src/models/drive-file.ts deleted file mode 100644 index c31e9a709f..0000000000 --- a/src/models/drive-file.ts +++ /dev/null @@ -1,232 +0,0 @@ -import * as mongo from 'mongodb'; -import * as deepcopy from 'deepcopy'; -import { pack as packFolder } from './drive-folder'; -import { pack as packUser } from './user'; -import monkDb, { nativeDbConn } from '../db/mongodb'; -import isObjectId from '../misc/is-objectid'; -import getDriveFileUrl, { getOriginalUrl } from '../misc/get-drive-file-url'; -import { dbLogger } from '../db/logger'; - -const DriveFile = monkDb.get('driveFiles.files'); -DriveFile.createIndex('md5'); -DriveFile.createIndex('metadata.uri'); -DriveFile.createIndex('metadata.userId'); -DriveFile.createIndex('metadata.folderId'); -DriveFile.createIndex('metadata._user.host'); -export default DriveFile; - -export const DriveFileChunk = monkDb.get('driveFiles.chunks'); - -export const getDriveFileBucket = async (): Promise => { - const db = await nativeDbConn(); - const bucket = new mongo.GridFSBucket(db, { - bucketName: 'driveFiles' - }); - return bucket; -}; - -export type IMetadata = { - properties: any; - userId: mongo.ObjectID; - _user: any; - folderId: mongo.ObjectID; - comment: string; - - /** - * リモートインスタンスから取得した場合の元URL - */ - uri?: string; - - /** - * URL for web(生成されている場合) or original - * * オブジェクトストレージを利用している or リモートサーバーへの直リンクである 場合のみ - */ - url?: string; - - /** - * URL for thumbnail (thumbnailがなければなし) - * * オブジェクトストレージを利用している or リモートサーバーへの直リンクである 場合のみ - */ - thumbnailUrl?: string; - - /** - * URL for original (web用が生成されてない場合はurlがoriginalを指す) - * * オブジェクトストレージを利用している or リモートサーバーへの直リンクである 場合のみ - */ - webpublicUrl?: string; - - accessKey?: string; - - src?: string; - deletedAt?: Date; - - /** - * このファイルの中身データがMongoDB内に保存されていないか否か - * オブジェクトストレージを利用している or リモートサーバーへの直リンクである - * な場合は true になります - */ - withoutChunks?: boolean; - - storage?: string; - - /*** - * ObjectStorage の格納先の情報 - */ - storageProps?: IStorageProps; - isSensitive?: boolean; - - /** - * このファイルが添付された投稿のID一覧 - */ - attachedNoteIds?: mongo.ObjectID[]; - - /** - * 外部の(信頼されていない)URLへの直リンクか否か - */ - isRemote?: boolean; -}; - -export type IStorageProps = { - /** - * ObjectStorage key for original - */ - key: string; - - /*** - * ObjectStorage key for thumbnail (thumbnailがなければなし) - */ - thumbnailKey?: string; - - /*** - * ObjectStorage key for webpublic (webpublicがなければなし) - */ - webpublicKey?: string; - - id?: string; -}; - -export type IDriveFile = { - _id: mongo.ObjectID; - uploadDate: Date; - md5: string; - filename: string; - contentType: string; - metadata: IMetadata; - - /** - * ファイルサイズ - */ - length: number; -}; - -export function validateFileName(name: string): boolean { - return ( - (name.trim().length > 0) && - (name.length <= 200) && - (name.indexOf('\\') === -1) && - (name.indexOf('/') === -1) && - (name.indexOf('..') === -1) - ); -} - -export const packMany = ( - files: any[], - options?: { - detail?: boolean - self?: boolean, - withUser?: boolean, - } -) => { - return Promise.all(files.map(f => pack(f, options))); -}; - -/** - * Pack a drive file for API response - */ -export const pack = ( - file: any, - options?: { - detail?: boolean, - self?: boolean, - withUser?: boolean, - } -) => new Promise(async (resolve, reject) => { - const opts = Object.assign({ - detail: false, - self: false - }, options); - - let _file: any; - - // Populate the file if 'file' is ID - if (isObjectId(file)) { - _file = await DriveFile.findOne({ - _id: file - }); - } else if (typeof file === 'string') { - _file = await DriveFile.findOne({ - _id: new mongo.ObjectID(file) - }); - } else { - _file = deepcopy(file); - } - - // (データベースの欠損などで)ファイルがデータベース上に見つからなかったとき - if (_file == null) { - dbLogger.warn(`[DAMAGED DB] (missing) pkg: driveFile :: ${file}`); - return resolve(null); - } - - // rendered target - let _target: any = {}; - - _target.id = _file._id; - _target.createdAt = _file.uploadDate; - _target.name = _file.filename; - _target.type = _file.contentType; - _target.datasize = _file.length; - _target.md5 = _file.md5; - - _target = Object.assign(_target, _file.metadata); - - _target.url = getDriveFileUrl(_file); - _target.thumbnailUrl = getDriveFileUrl(_file, true); - _target.isRemote = _file.metadata.isRemote; - - if (_target.properties == null) _target.properties = {}; - - if (opts.detail) { - if (_target.folderId) { - // Populate folder - _target.folder = await packFolder(_target.folderId, { - detail: true - }); - } - - /* - if (_target.tags) { - // Populate tags - _target.tags = await _target.tags.map(async (tag: any) => - await serializeDriveTag(tag) - ); - } - */ - } - - if (opts.withUser) { - // Populate user - _target.user = await packUser(_file.metadata.userId); - } - - delete _target.withoutChunks; - delete _target.storage; - delete _target.storageProps; - delete _target.isRemote; - delete _target._user; - - if (opts.self) { - _target.url = getOriginalUrl(_file); - } - - resolve(_target); -}); diff --git a/src/models/drive-folder.ts b/src/models/drive-folder.ts deleted file mode 100644 index b0f6e4273e..0000000000 --- a/src/models/drive-folder.ts +++ /dev/null @@ -1,75 +0,0 @@ -import * as mongo from 'mongodb'; -import * as deepcopy from 'deepcopy'; -import db from '../db/mongodb'; -import isObjectId from '../misc/is-objectid'; -import DriveFile from './drive-file'; - -const DriveFolder = db.get('driveFolders'); -DriveFolder.createIndex('userId'); -export default DriveFolder; - -export type IDriveFolder = { - _id: mongo.ObjectID; - createdAt: Date; - name: string; - userId: mongo.ObjectID; - parentId: mongo.ObjectID; -}; - -export function isValidFolderName(name: string): boolean { - return ( - (name.trim().length > 0) && - (name.length <= 200) - ); -} - -/** - * Pack a drive folder for API response - */ -export const pack = ( - folder: any, - options?: { - detail: boolean - } -) => new Promise(async (resolve, reject) => { - const opts = Object.assign({ - detail: false - }, options); - - let _folder: any; - - // Populate the folder if 'folder' is ID - if (isObjectId(folder)) { - _folder = await DriveFolder.findOne({ _id: folder }); - } else if (typeof folder === 'string') { - _folder = await DriveFolder.findOne({ _id: new mongo.ObjectID(folder) }); - } else { - _folder = deepcopy(folder); - } - - // Rename _id to id - _folder.id = _folder._id; - delete _folder._id; - - if (opts.detail) { - const childFoldersCount = await DriveFolder.count({ - parentId: _folder.id - }); - - const childFilesCount = await DriveFile.count({ - 'metadata.folderId': _folder.id - }); - - _folder.foldersCount = childFoldersCount; - _folder.filesCount = childFilesCount; - } - - if (opts.detail && _folder.parentId) { - // Populate parent folder - _folder.parent = await pack(_folder.parentId, { - detail: true - }); - } - - resolve(_folder); -}); diff --git a/src/models/emoji.ts b/src/models/emoji.ts deleted file mode 100644 index cbf939222e..0000000000 --- a/src/models/emoji.ts +++ /dev/null @@ -1,21 +0,0 @@ -import * as mongo from 'mongodb'; -import db from '../db/mongodb'; - -const Emoji = db.get('emoji'); -Emoji.createIndex('name'); -Emoji.createIndex('host'); -Emoji.createIndex(['name', 'host'], { unique: true }); - -export default Emoji; - -export type IEmoji = { - _id: mongo.ObjectID; - name: string; - host: string; - url: string; - aliases?: string[]; - updatedAt?: Date; - /** AP object id */ - uri?: string; - type?: string; -}; diff --git a/src/models/entities/abuse-user-report.ts b/src/models/entities/abuse-user-report.ts new file mode 100644 index 0000000000..43ab56023a --- /dev/null +++ b/src/models/entities/abuse-user-report.ts @@ -0,0 +1,41 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; +import { User } from './user'; +import { id } from '../id'; + +@Entity() +@Index(['userId', 'reporterId'], { unique: true }) +export class AbuseUserReport { + @PrimaryColumn(id()) + public id: string; + + @Index() + @Column('timestamp with time zone', { + comment: 'The created date of the AbuseUserReport.' + }) + public createdAt: Date; + + @Index() + @Column(id()) + public userId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user: User | null; + + @Index() + @Column(id()) + public reporterId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public reporter: User | null; + + @Column('varchar', { + length: 512, + }) + public comment: string; +} diff --git a/src/models/entities/access-token.ts b/src/models/entities/access-token.ts new file mode 100644 index 0000000000..d08930cf5a --- /dev/null +++ b/src/models/entities/access-token.ts @@ -0,0 +1,45 @@ +import { Entity, PrimaryColumn, Index, Column, ManyToOne, JoinColumn, RelationId } from 'typeorm'; +import { User } from './user'; +import { App } from './app'; +import { id } from '../id'; + +@Entity() +export class AccessToken { + @PrimaryColumn(id()) + public id: string; + + @Column('timestamp with time zone', { + comment: 'The created date of the AccessToken.' + }) + public createdAt: Date; + + @Index() + @Column('varchar', { + length: 128 + }) + public token: string; + + @Index() + @Column('varchar', { + length: 128 + }) + public hash: string; + + @RelationId((self: AccessToken) => self.user) + public userId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user: User | null; + + @Column(id()) + public appId: App['id']; + + @ManyToOne(type => App, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public app: App | null; +} diff --git a/src/models/entities/app.ts b/src/models/entities/app.ts new file mode 100644 index 0000000000..d0c89000fc --- /dev/null +++ b/src/models/entities/app.ts @@ -0,0 +1,60 @@ +import { Entity, PrimaryColumn, Column, Index, ManyToOne } from 'typeorm'; +import { User } from './user'; +import { id } from '../id'; + +@Entity() +export class App { + @PrimaryColumn(id()) + public id: string; + + @Index() + @Column('timestamp with time zone', { + comment: 'The created date of the App.' + }) + public createdAt: Date; + + @Index() + @Column({ + ...id(), + nullable: true, + comment: 'The owner ID.' + }) + public userId: User['id'] | null; + + @ManyToOne(type => User, { + onDelete: 'SET NULL', + nullable: true, + }) + public user: User | null; + + @Index() + @Column('varchar', { + length: 64, + comment: 'The secret key of the App.' + }) + public secret: string; + + @Column('varchar', { + length: 128, + comment: 'The name of the App.' + }) + public name: string; + + @Column('varchar', { + length: 512, + comment: 'The description of the App.' + }) + public description: string; + + @Column('varchar', { + length: 64, array: true, + comment: 'The permission of the App.' + }) + public permission: string[]; + + @Column('varchar', { + length: 256, nullable: true, + comment: 'The callbackUrl of the App.' + }) + public callbackUrl: string | null; +} diff --git a/src/models/entities/auth-session.ts b/src/models/entities/auth-session.ts new file mode 100644 index 0000000000..83f8365630 --- /dev/null +++ b/src/models/entities/auth-session.ts @@ -0,0 +1,39 @@ +import { Entity, PrimaryColumn, Index, Column, ManyToOne, JoinColumn } from 'typeorm'; +import { User } from './user'; +import { App } from './app'; +import { id } from '../id'; + +@Entity() +export class AuthSession { + @PrimaryColumn(id()) + public id: string; + + @Column('timestamp with time zone', { + comment: 'The created date of the AuthSession.' + }) + public createdAt: Date; + + @Index() + @Column('varchar', { + length: 128 + }) + public token: string; + + @Column(id()) + public userId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user: User | null; + + @Column(id()) + public appId: App['id']; + + @ManyToOne(type => App, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public app: App | null; +} diff --git a/src/models/entities/blocking.ts b/src/models/entities/blocking.ts new file mode 100644 index 0000000000..48487cb086 --- /dev/null +++ b/src/models/entities/blocking.ts @@ -0,0 +1,42 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; +import { User } from './user'; +import { id } from '../id'; + +@Entity() +@Index(['blockerId', 'blockeeId'], { unique: true }) +export class Blocking { + @PrimaryColumn(id()) + public id: string; + + @Index() + @Column('timestamp with time zone', { + comment: 'The created date of the Blocking.' + }) + public createdAt: Date; + + @Index() + @Column({ + ...id(), + comment: 'The blockee user ID.' + }) + public blockeeId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public blockee: User | null; + + @Index() + @Column({ + ...id(), + comment: 'The blocker user ID.' + }) + public blockerId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public blocker: User | null; +} diff --git a/src/models/entities/drive-file.ts b/src/models/entities/drive-file.ts new file mode 100644 index 0000000000..a8f8c69e56 --- /dev/null +++ b/src/models/entities/drive-file.ts @@ -0,0 +1,154 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; +import { User } from './user'; +import { DriveFolder } from './drive-folder'; +import { id } from '../id'; + +@Entity() +export class DriveFile { + @PrimaryColumn(id()) + public id: string; + + @Index() + @Column('timestamp with time zone', { + comment: 'The created date of the DriveFile.' + }) + public createdAt: Date; + + @Index() + @Column({ + ...id(), + nullable: true, + comment: 'The owner ID.' + }) + public userId: User['id'] | null; + + @ManyToOne(type => User, { + onDelete: 'SET NULL' + }) + @JoinColumn() + public user: User | null; + + @Index() + @Column('varchar', { + length: 128, nullable: true, + comment: 'The host of owner. It will be null if the user in local.' + }) + public userHost: string | null; + + @Index() + @Column('varchar', { + length: 32, + comment: 'The MD5 hash of the DriveFile.' + }) + public md5: string; + + @Column('varchar', { + length: 256, + comment: 'The file name of the DriveFile.' + }) + public name: string; + + @Index() + @Column('varchar', { + length: 128, + comment: 'The content type (MIME) of the DriveFile.' + }) + public type: string; + + @Column('integer', { + comment: 'The file size (bytes) of the DriveFile.' + }) + public size: number; + + @Column('varchar', { + length: 512, nullable: true, + comment: 'The comment of the DriveFile.' + }) + public comment: string | null; + + @Column('jsonb', { + default: {}, + comment: 'The any properties of the DriveFile. For example, it includes image width/height.' + }) + public properties: Record; + + @Column('boolean') + public storedInternal: boolean; + + @Column('varchar', { + length: 512, + comment: 'The URL of the DriveFile.' + }) + public url: string; + + @Column('varchar', { + length: 512, nullable: true, + comment: 'The URL of the thumbnail of the DriveFile.' + }) + public thumbnailUrl: string | null; + + @Column('varchar', { + length: 512, nullable: true, + comment: 'The URL of the webpublic of the DriveFile.' + }) + public webpublicUrl: string | null; + + @Index({ unique: true }) + @Column('varchar', { + length: 256, + }) + public accessKey: string; + + @Index({ unique: true }) + @Column('varchar', { + length: 256, nullable: true, + }) + public thumbnailAccessKey: string | null; + + @Index({ unique: true }) + @Column('varchar', { + length: 256, nullable: true, + }) + public webpublicAccessKey: string | null; + + @Index() + @Column('varchar', { + length: 512, nullable: true, + comment: 'The URI of the DriveFile. it will be null when the DriveFile is local.' + }) + public uri: string | null; + + @Column('varchar', { + length: 512, nullable: true, + }) + public src: string | null; + + @Index() + @Column({ + ...id(), + nullable: true, + comment: 'The parent folder ID. If null, it means the DriveFile is located in root.' + }) + public folderId: DriveFolder['id'] | null; + + @ManyToOne(type => DriveFolder, { + onDelete: 'SET NULL' + }) + @JoinColumn() + public folder: DriveFolder | null; + + @Column('boolean', { + default: false, + comment: 'Whether the DriveFile is NSFW.' + }) + public isSensitive: boolean; + + /** + * 外部の(信頼されていない)URLへの直リンクか否か + */ + @Column('boolean', { + default: false, + comment: 'Whether the DriveFile is direct link to remote server.' + }) + public isRemote: boolean; +} diff --git a/src/models/entities/drive-folder.ts b/src/models/entities/drive-folder.ts new file mode 100644 index 0000000000..a80d075855 --- /dev/null +++ b/src/models/entities/drive-folder.ts @@ -0,0 +1,49 @@ +import { JoinColumn, ManyToOne, Entity, PrimaryColumn, Index, Column } from 'typeorm'; +import { User } from './user'; +import { id } from '../id'; + +@Entity() +export class DriveFolder { + @PrimaryColumn(id()) + public id: string; + + @Index() + @Column('timestamp with time zone', { + comment: 'The created date of the DriveFolder.' + }) + public createdAt: Date; + + @Column('varchar', { + length: 128, + comment: 'The name of the DriveFolder.' + }) + public name: string; + + @Index() + @Column({ + ...id(), + nullable: true, + comment: 'The owner ID.' + }) + public userId: User['id'] | null; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user: User | null; + + @Index() + @Column({ + ...id(), + nullable: true, + comment: 'The parent folder ID. If null, it means the DriveFolder is located in root.' + }) + public parentId: DriveFolder['id'] | null; + + @ManyToOne(type => DriveFolder, { + onDelete: 'SET NULL' + }) + @JoinColumn() + public parent: DriveFolder | null; +} diff --git a/src/models/entities/emoji.ts b/src/models/entities/emoji.ts new file mode 100644 index 0000000000..da04da897e --- /dev/null +++ b/src/models/entities/emoji.ts @@ -0,0 +1,46 @@ +import { PrimaryColumn, Entity, Index, Column } from 'typeorm'; +import { id } from '../id'; + +@Entity() +@Index(['name', 'host'], { unique: true }) +export class Emoji { + @PrimaryColumn(id()) + public id: string; + + @Column('timestamp with time zone', { + nullable: true + }) + public updatedAt: Date | null; + + @Index() + @Column('varchar', { + length: 128 + }) + public name: string; + + @Index() + @Column('varchar', { + length: 128, nullable: true + }) + public host: string | null; + + @Column('varchar', { + length: 256, + }) + public url: string; + + @Column('varchar', { + length: 256, nullable: true + }) + public uri: string | null; + + @Column('varchar', { + length: 64, nullable: true + }) + public type: string | null; + + @Column('varchar', { + array: true, length: 128, default: '{}' + }) + public aliases: string[]; +} diff --git a/src/models/entities/follow-request.ts b/src/models/entities/follow-request.ts new file mode 100644 index 0000000000..80a71fe482 --- /dev/null +++ b/src/models/entities/follow-request.ts @@ -0,0 +1,85 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; +import { User } from './user'; +import { id } from '../id'; + +@Entity() +@Index(['followerId', 'followeeId'], { unique: true }) +export class FollowRequest { + @PrimaryColumn(id()) + public id: string; + + @Column('timestamp with time zone', { + comment: 'The created date of the FollowRequest.' + }) + public createdAt: Date; + + @Index() + @Column({ + ...id(), + comment: 'The followee user ID.' + }) + public followeeId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public followee: User | null; + + @Index() + @Column({ + ...id(), + comment: 'The follower user ID.' + }) + public followerId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public follower: User | null; + + @Column('varchar', { + length: 128, nullable: true, + comment: 'id of Follow Activity.' + }) + public requestId: string | null; + + //#region Denormalized fields + @Column('varchar', { + length: 128, nullable: true, + comment: '[Denormalized]' + }) + public followerHost: string | null; + + @Column('varchar', { + length: 256, nullable: true, + comment: '[Denormalized]' + }) + public followerInbox: string | null; + + @Column('varchar', { + length: 256, nullable: true, + comment: '[Denormalized]' + }) + public followerSharedInbox: string | null; + + @Column('varchar', { + length: 128, nullable: true, + comment: '[Denormalized]' + }) + public followeeHost: string | null; + + @Column('varchar', { + length: 256, nullable: true, + comment: '[Denormalized]' + }) + public followeeInbox: string | null; + + @Column('varchar', { + length: 256, nullable: true, + comment: '[Denormalized]' + }) + public followeeSharedInbox: string | null; + //#endregion +} diff --git a/src/models/entities/following.ts b/src/models/entities/following.ts new file mode 100644 index 0000000000..963873d112 --- /dev/null +++ b/src/models/entities/following.ts @@ -0,0 +1,80 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; +import { User } from './user'; +import { id } from '../id'; + +@Entity() +@Index(['followerId', 'followeeId'], { unique: true }) +export class Following { + @PrimaryColumn(id()) + public id: string; + + @Index() + @Column('timestamp with time zone', { + comment: 'The created date of the Following.' + }) + public createdAt: Date; + + @Index() + @Column({ + ...id(), + comment: 'The followee user ID.' + }) + public followeeId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public followee: User | null; + + @Index() + @Column({ + ...id(), + comment: 'The follower user ID.' + }) + public followerId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public follower: User | null; + + //#region Denormalized fields + @Column('varchar', { + length: 128, nullable: true, + comment: '[Denormalized]' + }) + public followerHost: string | null; + + @Column('varchar', { + length: 256, nullable: true, + comment: '[Denormalized]' + }) + public followerInbox: string | null; + + @Column('varchar', { + length: 256, nullable: true, + comment: '[Denormalized]' + }) + public followerSharedInbox: string | null; + + @Column('varchar', { + length: 128, nullable: true, + comment: '[Denormalized]' + }) + public followeeHost: string | null; + + @Column('varchar', { + length: 256, nullable: true, + comment: '[Denormalized]' + }) + public followeeInbox: string | null; + + @Column('varchar', { + length: 256, nullable: true, + comment: '[Denormalized]' + }) + public followeeSharedInbox: string | null; + //#endregion +} diff --git a/src/models/entities/games/reversi/game.ts b/src/models/entities/games/reversi/game.ts new file mode 100644 index 0000000000..9deacaf5c6 --- /dev/null +++ b/src/models/entities/games/reversi/game.ts @@ -0,0 +1,133 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; +import { User } from '../../user'; +import { id } from '../../../id'; + +@Entity() +export class ReversiGame { + @PrimaryColumn(id()) + public id: string; + + @Index() + @Column('timestamp with time zone', { + comment: 'The created date of the ReversiGame.' + }) + public createdAt: Date; + + @Column('timestamp with time zone', { + nullable: true, + comment: 'The started date of the ReversiGame.' + }) + public startedAt: Date | null; + + @Column(id()) + public user1Id: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user1: User | null; + + @Column(id()) + public user2Id: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user2: User | null; + + @Column('boolean', { + default: false, + }) + public user1Accepted: boolean; + + @Column('boolean', { + default: false, + }) + public user2Accepted: boolean; + + /** + * どちらのプレイヤーが先行(黒)か + * 1 ... user1 + * 2 ... user2 + */ + @Column('integer', { + nullable: true, + }) + public black: number | null; + + @Column('boolean', { + default: false, + }) + public isStarted: boolean; + + @Column('boolean', { + default: false, + }) + public isEnded: boolean; + + @Column({ + ...id(), + nullable: true + }) + public winnerId: User['id'] | null; + + @Column({ + ...id(), + nullable: true + }) + public surrendered: User['id'] | null; + + @Column('jsonb', { + default: [], + }) + public logs: { + at: Date; + color: boolean; + pos: number; + }[]; + + @Column('varchar', { + array: true, length: 64, + }) + public map: string[]; + + @Column('varchar', { + length: 32 + }) + public bw: string; + + @Column('boolean', { + default: false, + }) + public isLlotheo: boolean; + + @Column('boolean', { + default: false, + }) + public canPutEverywhere: boolean; + + @Column('boolean', { + default: false, + }) + public loopedBoard: boolean; + + @Column('jsonb', { + nullable: true, default: null, + }) + public form1: any | null; + + @Column('jsonb', { + nullable: true, default: null, + }) + public form2: any | null; + + /** + * ログのposを文字列としてすべて連結したもののCRC32値 + */ + @Column('varchar', { + length: 32, nullable: true + }) + public crc32: string | null; +} diff --git a/src/models/entities/games/reversi/matching.ts b/src/models/entities/games/reversi/matching.ts new file mode 100644 index 0000000000..477a29316e --- /dev/null +++ b/src/models/entities/games/reversi/matching.ts @@ -0,0 +1,35 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; +import { User } from '../../user'; +import { id } from '../../../id'; + +@Entity() +export class ReversiMatching { + @PrimaryColumn(id()) + public id: string; + + @Index() + @Column('timestamp with time zone', { + comment: 'The created date of the ReversiMatching.' + }) + public createdAt: Date; + + @Index() + @Column(id()) + public parentId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public parent: User | null; + + @Index() + @Column(id()) + public childId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public child: User | null; +} diff --git a/src/models/entities/hashtag.ts b/src/models/entities/hashtag.ts new file mode 100644 index 0000000000..842cdaa562 --- /dev/null +++ b/src/models/entities/hashtag.ts @@ -0,0 +1,87 @@ +import { Entity, PrimaryColumn, Index, Column } from 'typeorm'; +import { User } from './user'; +import { id } from '../id'; + +@Entity() +export class Hashtag { + @PrimaryColumn(id()) + public id: string; + + @Index({ unique: true }) + @Column('varchar', { + length: 128 + }) + public name: string; + + @Column({ + ...id(), + array: true, + }) + public mentionedUserIds: User['id'][]; + + @Index() + @Column('integer', { + default: 0 + }) + public mentionedUsersCount: number; + + @Column({ + ...id(), + array: true, + }) + public mentionedLocalUserIds: User['id'][]; + + @Index() + @Column('integer', { + default: 0 + }) + public mentionedLocalUsersCount: number; + + @Column({ + ...id(), + array: true, + }) + public mentionedRemoteUserIds: User['id'][]; + + @Index() + @Column('integer', { + default: 0 + }) + public mentionedRemoteUsersCount: number; + + @Column({ + ...id(), + array: true, + }) + public attachedUserIds: User['id'][]; + + @Index() + @Column('integer', { + default: 0 + }) + public attachedUsersCount: number; + + @Column({ + ...id(), + array: true, + }) + public attachedLocalUserIds: User['id'][]; + + @Index() + @Column('integer', { + default: 0 + }) + public attachedLocalUsersCount: number; + + @Column({ + ...id(), + array: true, + }) + public attachedRemoteUserIds: User['id'][]; + + @Index() + @Column('integer', { + default: 0 + }) + public attachedRemoteUsersCount: number; +} diff --git a/src/models/entities/instance.ts b/src/models/entities/instance.ts new file mode 100644 index 0000000000..977054263c --- /dev/null +++ b/src/models/entities/instance.ts @@ -0,0 +1,132 @@ +import { Entity, PrimaryColumn, Index, Column } from 'typeorm'; +import { id } from '../id'; + +@Entity() +export class Instance { + @PrimaryColumn(id()) + public id: string; + + /** + * このインスタンスを捕捉した日時 + */ + @Index() + @Column('timestamp with time zone', { + comment: 'The caught date of the Instance.' + }) + public caughtAt: Date; + + /** + * ホスト + */ + @Index({ unique: true }) + @Column('varchar', { + length: 128, + comment: 'The host of the Instance.' + }) + public host: string; + + /** + * インスタンスのシステム (MastodonとかMisskeyとかPleromaとか) + */ + @Column('varchar', { + length: 64, nullable: true, + comment: 'The system of the Instance.' + }) + public system: string | null; + + /** + * インスタンスのユーザー数 + */ + @Column('integer', { + default: 0, + comment: 'The count of the users of the Instance.' + }) + public usersCount: number; + + /** + * インスタンスの投稿数 + */ + @Column('integer', { + default: 0, + comment: 'The count of the notes of the Instance.' + }) + public notesCount: number; + + /** + * このインスタンスのユーザーからフォローされている、自インスタンスのユーザーの数 + */ + @Column('integer', { + default: 0, + }) + public followingCount: number; + + /** + * このインスタンスのユーザーをフォローしている、自インスタンスのユーザーの数 + */ + @Column('integer', { + default: 0, + }) + public followersCount: number; + + /** + * ドライブ使用量 + */ + @Column('integer', { + default: 0, + }) + public driveUsage: number; + + /** + * ドライブのファイル数 + */ + @Column('integer', { + default: 0, + }) + public driveFiles: number; + + /** + * 直近のリクエスト送信日時 + */ + @Column('timestamp with time zone', { + nullable: true, + }) + public latestRequestSentAt: Date | null; + + /** + * 直近のリクエスト送信時のHTTPステータスコード + */ + @Column('integer', { + nullable: true, + }) + public latestStatus: number | null; + + /** + * 直近のリクエスト受信日時 + */ + @Column('timestamp with time zone', { + nullable: true, + }) + public latestRequestReceivedAt: Date | null; + + /** + * このインスタンスと最後にやり取りした日時 + */ + @Column('timestamp with time zone') + public lastCommunicatedAt: Date; + + /** + * このインスタンスと不通かどうか + */ + @Column('boolean', { + default: false + }) + public isNotResponding: boolean; + + /** + * このインスタンスが閉鎖済みとしてマークされているか + */ + @Column('boolean', { + default: false + }) + public isMarkedAsClosed: boolean; +} diff --git a/src/models/entities/log.ts b/src/models/entities/log.ts new file mode 100644 index 0000000000..99e1e8947e --- /dev/null +++ b/src/models/entities/log.ts @@ -0,0 +1,46 @@ +import { Entity, PrimaryColumn, Index, Column } from 'typeorm'; +import { id } from '../id'; + +@Entity() +export class Log { + @PrimaryColumn(id()) + public id: string; + + @Index() + @Column('timestamp with time zone', { + comment: 'The created date of the Log.' + }) + public createdAt: Date; + + @Index() + @Column('varchar', { + length: 64, array: true, default: '{}' + }) + public domain: string[]; + + @Index() + @Column('enum', { + enum: ['error', 'warning', 'info', 'success', 'debug'] + }) + public level: string; + + @Column('varchar', { + length: 8 + }) + public worker: string; + + @Column('varchar', { + length: 128 + }) + public machine: string; + + @Column('varchar', { + length: 1024 + }) + public message: string; + + @Column('jsonb', { + default: {} + }) + public data: Record; +} diff --git a/src/models/entities/messaging-message.ts b/src/models/entities/messaging-message.ts new file mode 100644 index 0000000000..d3c3eab3a2 --- /dev/null +++ b/src/models/entities/messaging-message.ts @@ -0,0 +1,64 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; +import { User } from './user'; +import { DriveFile } from './drive-file'; +import { id } from '../id'; + +@Entity() +export class MessagingMessage { + @PrimaryColumn(id()) + public id: string; + + @Index() + @Column('timestamp with time zone', { + comment: 'The created date of the MessagingMessage.' + }) + public createdAt: Date; + + @Index() + @Column({ + ...id(), + comment: 'The sender user ID.' + }) + public userId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user: User | null; + + @Index() + @Column({ + ...id(), + comment: 'The recipient user ID.' + }) + public recipientId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public recipient: User | null; + + @Column('varchar', { + length: 4096, nullable: true + }) + public text: string | null; + + @Column('boolean', { + default: false, + }) + public isRead: boolean; + + @Column({ + ...id(), + nullable: true, + }) + public fileId: DriveFile['id'] | null; + + @ManyToOne(type => DriveFile, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public file: DriveFile | null; +} diff --git a/src/models/entities/meta.ts b/src/models/entities/meta.ts new file mode 100644 index 0000000000..c34f5b6904 --- /dev/null +++ b/src/models/entities/meta.ts @@ -0,0 +1,264 @@ +import { Entity, Column, PrimaryColumn } from 'typeorm'; +import { id } from '../id'; + +@Entity() +export class Meta { + @PrimaryColumn(id()) + public id: string; + + @Column('varchar', { + length: 128, nullable: true + }) + public name: string | null; + + @Column('varchar', { + length: 1024, nullable: true + }) + public description: string | null; + + /** + * メンテナの名前 + */ + @Column('varchar', { + length: 128, nullable: true + }) + public maintainerName: string | null; + + /** + * メンテナの連絡先 + */ + @Column('varchar', { + length: 128, nullable: true + }) + public maintainerEmail: string | null; + + @Column('jsonb', { + default: [], + }) + public announcements: Record[]; + + @Column('boolean', { + default: false, + }) + public disableRegistration: boolean; + + @Column('boolean', { + default: false, + }) + public disableLocalTimeline: boolean; + + @Column('boolean', { + default: false, + }) + public disableGlobalTimeline: boolean; + + @Column('boolean', { + default: true, + }) + public enableEmojiReaction: boolean; + + @Column('boolean', { + default: false, + }) + public useStarForReactionFallback: boolean; + + @Column('varchar', { + length: 64, array: true, default: '{}' + }) + public langs: string[]; + + @Column('varchar', { + length: 256, array: true, default: '{}' + }) + public hiddenTags: string[]; + + @Column('varchar', { + length: 256, array: true, default: '{}' + }) + public blockedHosts: string[]; + + @Column('varchar', { + length: 256, + nullable: true, + default: '/assets/ai.png' + }) + public mascotImageUrl: string | null; + + @Column('varchar', { + length: 256, + nullable: true + }) + public bannerUrl: string | null; + + @Column('varchar', { + length: 256, + nullable: true, + default: 'https://ai.misskey.xyz/aiart/yubitun.png' + }) + public errorImageUrl: string | null; + + @Column('varchar', { + length: 256, + nullable: true + }) + public iconUrl: string | null; + + @Column('boolean', { + default: true, + }) + public cacheRemoteFiles: boolean; + + @Column('varchar', { + length: 128, + nullable: true + }) + public proxyAccount: string | null; + + @Column('boolean', { + default: false, + }) + public enableRecaptcha: boolean; + + @Column('varchar', { + length: 64, + nullable: true + }) + public recaptchaSiteKey: string | null; + + @Column('varchar', { + length: 64, + nullable: true + }) + public recaptchaSecretKey: string | null; + + @Column('integer', { + default: 1024, + comment: 'Drive capacity of a local user (MB)' + }) + public localDriveCapacityMb: number; + + @Column('integer', { + default: 32, + comment: 'Drive capacity of a remote user (MB)' + }) + public remoteDriveCapacityMb: number; + + @Column('integer', { + default: 500, + comment: 'Max allowed note text length in characters' + }) + public maxNoteTextLength: number; + + @Column('varchar', { + length: 128, + nullable: true + }) + public summalyProxy: string | null; + + @Column('boolean', { + default: false, + }) + public enableEmail: boolean; + + @Column('varchar', { + length: 128, + nullable: true + }) + public email: string | null; + + @Column('boolean', { + default: false, + }) + public smtpSecure: boolean; + + @Column('varchar', { + length: 128, + nullable: true + }) + public smtpHost: string | null; + + @Column('integer', { + nullable: true + }) + public smtpPort: number | null; + + @Column('varchar', { + length: 128, + nullable: true + }) + public smtpUser: string | null; + + @Column('varchar', { + length: 128, + nullable: true + }) + public smtpPass: string | null; + + @Column('boolean', { + default: false, + }) + public enableServiceWorker: boolean; + + @Column('varchar', { + length: 128, + nullable: true + }) + public swPublicKey: string | null; + + @Column('varchar', { + length: 128, + nullable: true + }) + public swPrivateKey: string | null; + + @Column('boolean', { + default: false, + }) + public enableTwitterIntegration: boolean; + + @Column('varchar', { + length: 128, + nullable: true + }) + public twitterConsumerKey: string | null; + + @Column('varchar', { + length: 128, + nullable: true + }) + public twitterConsumerSecret: string | null; + + @Column('boolean', { + default: false, + }) + public enableGithubIntegration: boolean; + + @Column('varchar', { + length: 128, + nullable: true + }) + public githubClientId: string | null; + + @Column('varchar', { + length: 128, + nullable: true + }) + public githubClientSecret: string | null; + + @Column('boolean', { + default: false, + }) + public enableDiscordIntegration: boolean; + + @Column('varchar', { + length: 128, + nullable: true + }) + public discordClientId: string | null; + + @Column('varchar', { + length: 128, + nullable: true + }) + public discordClientSecret: string | null; +} diff --git a/src/models/entities/muting.ts b/src/models/entities/muting.ts new file mode 100644 index 0000000000..0084213bcc --- /dev/null +++ b/src/models/entities/muting.ts @@ -0,0 +1,42 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; +import { User } from './user'; +import { id } from '../id'; + +@Entity() +@Index(['muterId', 'muteeId'], { unique: true }) +export class Muting { + @PrimaryColumn(id()) + public id: string; + + @Index() + @Column('timestamp with time zone', { + comment: 'The created date of the Muting.' + }) + public createdAt: Date; + + @Index() + @Column({ + ...id(), + comment: 'The mutee user ID.' + }) + public muteeId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public mutee: User | null; + + @Index() + @Column({ + ...id(), + comment: 'The muter user ID.' + }) + public muterId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public muter: User | null; +} diff --git a/src/models/entities/note-favorite.ts b/src/models/entities/note-favorite.ts new file mode 100644 index 0000000000..0713c3ae56 --- /dev/null +++ b/src/models/entities/note-favorite.ts @@ -0,0 +1,35 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; +import { Note } from './note'; +import { User } from './user'; +import { id } from '../id'; + +@Entity() +@Index(['userId', 'noteId'], { unique: true }) +export class NoteFavorite { + @PrimaryColumn(id()) + public id: string; + + @Column('timestamp with time zone', { + comment: 'The created date of the NoteFavorite.' + }) + public createdAt: Date; + + @Index() + @Column(id()) + public userId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user: User | null; + + @Column(id()) + public noteId: Note['id']; + + @ManyToOne(type => Note, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public note: Note | null; +} diff --git a/src/models/entities/note-reaction.ts b/src/models/entities/note-reaction.ts new file mode 100644 index 0000000000..1ce5d841fb --- /dev/null +++ b/src/models/entities/note-reaction.ts @@ -0,0 +1,42 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; +import { User } from './user'; +import { Note } from './note'; +import { id } from '../id'; + +@Entity() +@Index(['userId', 'noteId'], { unique: true }) +export class NoteReaction { + @PrimaryColumn(id()) + public id: string; + + @Index() + @Column('timestamp with time zone', { + comment: 'The created date of the NoteReaction.' + }) + public createdAt: Date; + + @Index() + @Column(id()) + public userId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user: User | null; + + @Index() + @Column(id()) + public noteId: Note['id']; + + @ManyToOne(type => Note, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public note: Note | null; + + @Column('varchar', { + length: 32 + }) + public reaction: string; +} diff --git a/src/models/entities/note-unread.ts b/src/models/entities/note-unread.ts new file mode 100644 index 0000000000..2d18728256 --- /dev/null +++ b/src/models/entities/note-unread.ts @@ -0,0 +1,43 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; +import { User } from './user'; +import { Note } from './note'; +import { id } from '../id'; + +@Entity() +@Index(['userId', 'noteId'], { unique: true }) +export class NoteUnread { + @PrimaryColumn(id()) + public id: string; + + @Index() + @Column(id()) + public userId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user: User | null; + + @Index() + @Column(id()) + public noteId: Note['id']; + + @ManyToOne(type => Note, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public note: Note | null; + + @Column({ + ...id(), + comment: '[Denormalized]' + }) + public noteUserId: User['id']; + + /** + * ダイレクト投稿か + */ + @Column('boolean') + public isSpecified: boolean; +} diff --git a/src/models/entities/note-watching.ts b/src/models/entities/note-watching.ts new file mode 100644 index 0000000000..741a1c0c8b --- /dev/null +++ b/src/models/entities/note-watching.ts @@ -0,0 +1,52 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; +import { User } from './user'; +import { Note } from './note'; +import { id } from '../id'; + +@Entity() +@Index(['userId', 'noteId'], { unique: true }) +export class NoteWatching { + @PrimaryColumn(id()) + public id: string; + + @Index() + @Column('timestamp with time zone', { + comment: 'The created date of the NoteWatching.' + }) + public createdAt: Date; + + @Index() + @Column({ + ...id(), + comment: 'The watcher ID.' + }) + public userId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user: User | null; + + @Index() + @Column({ + ...id(), + comment: 'The target Note ID.' + }) + public noteId: Note['id']; + + @ManyToOne(type => Note, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public note: Note | null; + + //#region Denormalized fields + @Index() + @Column({ + ...id(), + comment: '[Denormalized]' + }) + public noteUserId: Note['userId']; + //#endregion +} diff --git a/src/models/entities/note.ts b/src/models/entities/note.ts new file mode 100644 index 0000000000..0bcb9b4a44 --- /dev/null +++ b/src/models/entities/note.ts @@ -0,0 +1,236 @@ +import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm'; +import { User } from './user'; +import { App } from './app'; +import { DriveFile } from './drive-file'; +import { id } from '../id'; + +@Entity() +export class Note { + @PrimaryColumn(id()) + public id: string; + + @Index() + @Column('timestamp with time zone', { + comment: 'The created date of the Note.' + }) + public createdAt: Date; + + @Index() + @Column('timestamp with time zone', { + nullable: true, + comment: 'The updated date of the Note.' + }) + public updatedAt: Date | null; + + @Index() + @Column({ + ...id(), + nullable: true, + comment: 'The ID of reply target.' + }) + public replyId: Note['id'] | null; + + @ManyToOne(type => Note, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public reply: Note | null; + + @Index() + @Column({ + ...id(), + nullable: true, + comment: 'The ID of renote target.' + }) + public renoteId: Note['id'] | null; + + @ManyToOne(type => Note, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public renote: Note | null; + + @Column({ + type: 'text', nullable: true + }) + public text: string | null; + + @Column('varchar', { + length: 256, nullable: true + }) + public name: string | null; + + @Column('varchar', { + length: 512, nullable: true + }) + public cw: string | null; + + @Column({ + ...id(), + nullable: true + }) + public appId: App['id'] | null; + + @ManyToOne(type => App, { + onDelete: 'SET NULL' + }) + @JoinColumn() + public app: App | null; + + @Index() + @Column({ + ...id(), + comment: 'The ID of author.' + }) + public userId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user: User | null; + + @Column('boolean', { + default: false + }) + public viaMobile: boolean; + + @Column('boolean', { + default: false + }) + public localOnly: boolean; + + @Column('integer', { + default: 0 + }) + public renoteCount: number; + + @Column('integer', { + default: 0 + }) + public repliesCount: number; + + @Column('jsonb', { + default: {} + }) + public reactions: Record; + + /** + * public ... 公開 + * home ... ホームタイムライン(ユーザーページのタイムライン含む)のみに流す + * followers ... フォロワーのみ + * specified ... visibleUserIds で指定したユーザーのみ + */ + @Column('enum', { enum: ['public', 'home', 'followers', 'specified'] }) + public visibility: 'public' | 'home' | 'followers' | 'specified'; + + @Index({ unique: true }) + @Column('varchar', { + length: 256, nullable: true, + comment: 'The URI of a note. it will be null when the note is local.' + }) + public uri: string | null; + + @Column('integer', { + default: 0 + }) + public score: number; + + @Column({ + ...id(), + array: true, default: '{}' + }) + public fileIds: DriveFile['id'][]; + + @Column('varchar', { + length: 256, array: true, default: '{}' + }) + public attachedFileTypes: string[]; + + @Index() + @Column({ + ...id(), + array: true, default: '{}' + }) + public visibleUserIds: User['id'][]; + + @Index() + @Column({ + ...id(), + array: true, default: '{}' + }) + public mentions: User['id'][]; + + @Column('text', { + default: '[]' + }) + public mentionedRemoteUsers: string; + + @Column('varchar', { + length: 128, array: true, default: '{}' + }) + public emojis: string[]; + + @Index() + @Column('varchar', { + length: 128, array: true, default: '{}' + }) + public tags: string[]; + + @Column('boolean', { + default: false + }) + public hasPoll: boolean; + + @Column('jsonb', { + nullable: true, default: {} + }) + public geo: any | null; + + //#region Denormalized fields + @Index() + @Column('varchar', { + length: 128, nullable: true, + comment: '[Denormalized]' + }) + public userHost: string | null; + + @Column('varchar', { + length: 128, nullable: true, + comment: '[Denormalized]' + }) + public userInbox: string | null; + + @Column({ + ...id(), + nullable: true, + comment: '[Denormalized]' + }) + public replyUserId: User['id'] | null; + + @Column('varchar', { + length: 128, nullable: true, + comment: '[Denormalized]' + }) + public replyUserHost: string | null; + + @Column({ + ...id(), + nullable: true, + comment: '[Denormalized]' + }) + public renoteUserId: User['id'] | null; + + @Column('varchar', { + length: 128, nullable: true, + comment: '[Denormalized]' + }) + public renoteUserHost: string | null; + //#endregion +} + +export type IMentionedRemoteUsers = { + uri: string; + username: string; + host: string; +}[]; diff --git a/src/models/entities/notification.ts b/src/models/entities/notification.ts new file mode 100644 index 0000000000..627a57bece --- /dev/null +++ b/src/models/entities/notification.ts @@ -0,0 +1,94 @@ +import { Entity, Index, JoinColumn, ManyToOne, Column, PrimaryColumn } from 'typeorm'; +import { User } from './user'; +import { id } from '../id'; +import { Note } from './note'; + +@Entity() +export class Notification { + @PrimaryColumn(id()) + public id: string; + + @Index() + @Column('timestamp with time zone', { + comment: 'The created date of the Notification.' + }) + public createdAt: Date; + + /** + * 通知の受信者 + */ + @Index() + @Column({ + ...id(), + comment: 'The ID of recipient user of the Notification.' + }) + public notifieeId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public notifiee: User | null; + + /** + * 通知の送信者(initiator) + */ + @Column({ + ...id(), + comment: 'The ID of sender user of the Notification.' + }) + public notifierId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public notifier: User | null; + + /** + * 通知の種類。 + * follow - フォローされた + * mention - 投稿で自分が言及された + * reply - (自分または自分がWatchしている)投稿が返信された + * renote - (自分または自分がWatchしている)投稿がRenoteされた + * quote - (自分または自分がWatchしている)投稿が引用Renoteされた + * reaction - (自分または自分がWatchしている)投稿にリアクションされた + * pollVote - (自分または自分がWatchしている)投稿の投票に投票された + */ + @Column('varchar', { + length: 32, + comment: 'The type of the Notification.' + }) + public type: string; + + /** + * 通知が読まれたかどうか + */ + @Column('boolean', { + default: false, + comment: 'Whether the Notification is read.' + }) + public isRead: boolean; + + @Column({ + ...id(), + nullable: true + }) + public noteId: Note['id'] | null; + + @ManyToOne(type => Note, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public note: Note | null; + + @Column('varchar', { + length: 128, nullable: true + }) + public reaction: string; + + @Column('integer', { + nullable: true + }) + public choice: number; +} diff --git a/src/models/entities/poll-vote.ts b/src/models/entities/poll-vote.ts new file mode 100644 index 0000000000..709376f909 --- /dev/null +++ b/src/models/entities/poll-vote.ts @@ -0,0 +1,40 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; +import { User } from './user'; +import { Note } from './note'; +import { id } from '../id'; + +@Entity() +@Index(['userId', 'noteId', 'choice'], { unique: true }) +export class PollVote { + @PrimaryColumn(id()) + public id: string; + + @Index() + @Column('timestamp with time zone', { + comment: 'The created date of the PollVote.' + }) + public createdAt: Date; + + @Index() + @Column(id()) + public userId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user: User | null; + + @Index() + @Column(id()) + public noteId: Note['id']; + + @ManyToOne(type => Note, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public note: Note | null; + + @Column('integer') + public choice: number; +} diff --git a/src/models/entities/poll.ts b/src/models/entities/poll.ts new file mode 100644 index 0000000000..204f102f51 --- /dev/null +++ b/src/models/entities/poll.ts @@ -0,0 +1,67 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, OneToOne } from 'typeorm'; +import { id } from '../id'; +import { Note } from './note'; +import { User } from './user'; + +@Entity() +export class Poll { + @PrimaryColumn(id()) + public id: string; + + @Index({ unique: true }) + @Column(id()) + public noteId: Note['id']; + + @OneToOne(type => Note, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public note: Note | null; + + @Column('timestamp with time zone', { + nullable: true + }) + public expiresAt: Date | null; + + @Column('boolean') + public multiple: boolean; + + @Column('varchar', { + length: 128, array: true, default: '{}' + }) + public choices: string[]; + + @Column('integer', { + array: true, + }) + public votes: number[]; + + //#region Denormalized fields + @Column('enum', { + enum: ['public', 'home', 'followers', 'specified'], + comment: '[Denormalized]' + }) + public noteVisibility: 'public' | 'home' | 'followers' | 'specified'; + + @Index() + @Column({ + ...id(), + comment: '[Denormalized]' + }) + public userId: User['id']; + + @Index() + @Column('varchar', { + length: 128, nullable: true, + comment: '[Denormalized]' + }) + public userHost: string | null; + //#endregion +} + +export type IPoll = { + choices: string[]; + votes?: number[]; + multiple: boolean; + expiresAt: Date; +}; diff --git a/src/models/entities/registration-tickets.ts b/src/models/entities/registration-tickets.ts new file mode 100644 index 0000000000..d962f78a78 --- /dev/null +++ b/src/models/entities/registration-tickets.ts @@ -0,0 +1,17 @@ +import { PrimaryColumn, Entity, Index, Column } from 'typeorm'; +import { id } from '../id'; + +@Entity() +export class RegistrationTicket { + @PrimaryColumn(id()) + public id: string; + + @Column('timestamp with time zone') + public createdAt: Date; + + @Index({ unique: true }) + @Column('varchar', { + length: 64, + }) + public code: string; +} diff --git a/src/models/entities/signin.ts b/src/models/entities/signin.ts new file mode 100644 index 0000000000..7e047084b1 --- /dev/null +++ b/src/models/entities/signin.ts @@ -0,0 +1,35 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; +import { User } from './user'; +import { id } from '../id'; + +@Entity() +export class Signin { + @PrimaryColumn(id()) + public id: string; + + @Column('timestamp with time zone', { + comment: 'The created date of the Signin.' + }) + public createdAt: Date; + + @Index() + @Column(id()) + public userId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user: User | null; + + @Column('varchar', { + length: 128, + }) + public ip: string; + + @Column('jsonb') + public headers: Record; + + @Column('boolean') + public success: boolean; +} diff --git a/src/models/entities/sw-subscription.ts b/src/models/entities/sw-subscription.ts new file mode 100644 index 0000000000..f0f2a69f1b --- /dev/null +++ b/src/models/entities/sw-subscription.ts @@ -0,0 +1,37 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; +import { User } from './user'; +import { id } from '../id'; + +@Entity() +export class SwSubscription { + @PrimaryColumn(id()) + public id: string; + + @Column('timestamp with time zone') + public createdAt: Date; + + @Index() + @Column(id()) + public userId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user: User | null; + + @Column('varchar', { + length: 256, + }) + public endpoint: string; + + @Column('varchar', { + length: 256, + }) + public auth: string; + + @Column('varchar', { + length: 128, + }) + public publickey: string; +} diff --git a/src/models/entities/user-keypair.ts b/src/models/entities/user-keypair.ts new file mode 100644 index 0000000000..06b98d2536 --- /dev/null +++ b/src/models/entities/user-keypair.ts @@ -0,0 +1,24 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, OneToOne } from 'typeorm'; +import { User } from './user'; +import { id } from '../id'; + +@Entity() +export class UserKeypair { + @PrimaryColumn(id()) + public id: string; + + @Index({ unique: true }) + @Column(id()) + public userId: User['id']; + + @OneToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user: User | null; + + @Column('varchar', { + length: 4096, + }) + public keyPem: string; +} diff --git a/src/models/entities/user-list-joining.ts b/src/models/entities/user-list-joining.ts new file mode 100644 index 0000000000..8af4efb6a7 --- /dev/null +++ b/src/models/entities/user-list-joining.ts @@ -0,0 +1,41 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; +import { User } from './user'; +import { UserList } from './user-list'; +import { id } from '../id'; + +@Entity() +export class UserListJoining { + @PrimaryColumn(id()) + public id: string; + + @Column('timestamp with time zone', { + comment: 'The created date of the UserListJoining.' + }) + public createdAt: Date; + + @Index() + @Column({ + ...id(), + comment: 'The user ID.' + }) + public userId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user: User | null; + + @Index() + @Column({ + ...id(), + comment: 'The list ID.' + }) + public userListId: UserList['id']; + + @ManyToOne(type => UserList, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public userList: UserList | null; +} diff --git a/src/models/entities/user-list.ts b/src/models/entities/user-list.ts new file mode 100644 index 0000000000..35a83ef8c3 --- /dev/null +++ b/src/models/entities/user-list.ts @@ -0,0 +1,33 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; +import { User } from './user'; +import { id } from '../id'; + +@Entity() +export class UserList { + @PrimaryColumn(id()) + public id: string; + + @Column('timestamp with time zone', { + comment: 'The created date of the UserList.' + }) + public createdAt: Date; + + @Index() + @Column({ + ...id(), + comment: 'The owner ID.' + }) + public userId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user: User | null; + + @Column('varchar', { + length: 128, + comment: 'The name of the UserList.' + }) + public name: string; +} diff --git a/src/models/entities/user-note-pinings.ts b/src/models/entities/user-note-pinings.ts new file mode 100644 index 0000000000..04a6f8f645 --- /dev/null +++ b/src/models/entities/user-note-pinings.ts @@ -0,0 +1,35 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; +import { Note } from './note'; +import { User } from './user'; +import { id } from '../id'; + +@Entity() +@Index(['userId', 'noteId'], { unique: true }) +export class UserNotePining { + @PrimaryColumn(id()) + public id: string; + + @Column('timestamp with time zone', { + comment: 'The created date of the UserNotePinings.' + }) + public createdAt: Date; + + @Index() + @Column(id()) + public userId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user: User | null; + + @Column(id()) + public noteId: Note['id']; + + @ManyToOne(type => Note, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public note: Note | null; +} diff --git a/src/models/entities/user-publickey.ts b/src/models/entities/user-publickey.ts new file mode 100644 index 0000000000..6c019f3313 --- /dev/null +++ b/src/models/entities/user-publickey.ts @@ -0,0 +1,30 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, OneToOne } from 'typeorm'; +import { User } from './user'; +import { id } from '../id'; + +@Entity() +export class UserPublickey { + @PrimaryColumn(id()) + public id: string; + + @Index({ unique: true }) + @Column(id()) + public userId: User['id']; + + @OneToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user: User | null; + + @Index({ unique: true }) + @Column('varchar', { + length: 256, + }) + public keyId: string; + + @Column('varchar', { + length: 4096, + }) + public keyPem: string; +} diff --git a/src/models/entities/user-service-linking.ts b/src/models/entities/user-service-linking.ts new file mode 100644 index 0000000000..3d99554e1e --- /dev/null +++ b/src/models/entities/user-service-linking.ts @@ -0,0 +1,108 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, OneToOne } from 'typeorm'; +import { User } from './user'; +import { id } from '../id'; + +@Entity() +export class UserServiceLinking { + @PrimaryColumn(id()) + public id: string; + + @Index({ unique: true }) + @Column(id()) + public userId: User['id']; + + @OneToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user: User | null; + + @Column('boolean', { + default: false, + }) + public twitter: boolean; + + @Column('varchar', { + length: 64, nullable: true, default: null, + }) + public twitterAccessToken: string | null; + + @Column('varchar', { + length: 64, nullable: true, default: null, + }) + public twitterAccessTokenSecret: string | null; + + @Column('varchar', { + length: 64, nullable: true, default: null, + }) + public twitterUserId: string | null; + + @Column('varchar', { + length: 64, nullable: true, default: null, + }) + public twitterScreenName: string | null; + + @Column('boolean', { + default: false, + }) + public github: boolean; + + @Column('varchar', { + length: 64, nullable: true, default: null, + }) + public githubAccessToken: string | null; + + @Column('integer', { + nullable: true, default: null, + }) + public githubId: number | null; + + @Column('varchar', { + length: 64, nullable: true, default: null, + }) + public githubLogin: string | null; + + @Column('boolean', { + default: false, + }) + public discord: boolean; + + @Column('varchar', { + length: 64, nullable: true, default: null, + }) + public discordAccessToken: string | null; + + @Column('varchar', { + length: 64, nullable: true, default: null, + }) + public discordRefreshToken: string | null; + + @Column('integer', { + nullable: true, default: null, + }) + public discordExpiresDate: number | null; + + @Column('varchar', { + length: 64, nullable: true, default: null, + }) + public discordId: string | null; + + @Column('varchar', { + length: 64, nullable: true, default: null, + }) + public discordUsername: string | null; + + @Column('varchar', { + length: 64, nullable: true, default: null, + }) + public discordDiscriminator: string | null; + + //#region Denormalized fields + @Index() + @Column('varchar', { + length: 128, nullable: true, + comment: '[Denormalized]' + }) + public userHost: string | null; + //#endregion +} diff --git a/src/models/entities/user.ts b/src/models/entities/user.ts new file mode 100644 index 0000000000..1ef98cadc2 --- /dev/null +++ b/src/models/entities/user.ts @@ -0,0 +1,297 @@ +import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm'; +import { DriveFile } from './drive-file'; +import { id } from '../id'; + +@Entity() +@Index(['usernameLower', 'host'], { unique: true }) +export class User { + @PrimaryColumn(id()) + public id: string; + + @Index() + @Column('timestamp with time zone', { + comment: 'The created date of the User.' + }) + public createdAt: Date; + + @Index() + @Column('timestamp with time zone', { + nullable: true, + comment: 'The updated date of the User.' + }) + public updatedAt: Date | null; + + @Column('timestamp with time zone', { + nullable: true + }) + public lastFetchedAt: Date | null; + + @Column('varchar', { + length: 128, + comment: 'The username of the User.' + }) + public username: string; + + @Index() + @Column('varchar', { + length: 128, select: false, + comment: 'The username (lowercased) of the User.' + }) + public usernameLower: string; + + @Column('varchar', { + length: 128, nullable: true, + comment: 'The name of the User.' + }) + public name: string | null; + + @Column('varchar', { + length: 128, nullable: true, + comment: 'The location of the User.' + }) + public location: string | null; + + @Column('char', { + length: 10, nullable: true, + comment: 'The birthday (YYYY-MM-DD) of the User.' + }) + public birthday: string | null; + + @Column('integer', { + default: 0, + comment: 'The count of followers.' + }) + public followersCount: number; + + @Column('integer', { + default: 0, + comment: 'The count of following.' + }) + public followingCount: number; + + @Column('integer', { + default: 0, + comment: 'The count of notes.' + }) + public notesCount: number; + + @Column({ + ...id(), + nullable: true, + comment: 'The ID of avatar DriveFile.' + }) + public avatarId: DriveFile['id'] | null; + + @OneToOne(type => DriveFile, { + onDelete: 'SET NULL' + }) + @JoinColumn() + public avatar: DriveFile | null; + + @Column({ + ...id(), + nullable: true, + comment: 'The ID of banner DriveFile.' + }) + public bannerId: DriveFile['id'] | null; + + @OneToOne(type => DriveFile, { + onDelete: 'SET NULL' + }) + @JoinColumn() + public banner: DriveFile | null; + + @Column('varchar', { + length: 1024, nullable: true, + comment: 'The description (bio) of the User.' + }) + public description: string | null; + + @Index() + @Column('varchar', { + length: 128, array: true, default: '{}' + }) + public tags: string[]; + + @Column('varchar', { + length: 128, nullable: true, + comment: 'The email address of the User.' + }) + public email: string | null; + + @Column('varchar', { + length: 128, nullable: true, + }) + public emailVerifyCode: string | null; + + @Column('boolean', { + default: false, + }) + public emailVerified: boolean; + + @Column('varchar', { + length: 128, nullable: true, + }) + public twoFactorTempSecret: string | null; + + @Column('varchar', { + length: 128, nullable: true, + }) + public twoFactorSecret: string | null; + + @Column('varchar', { + length: 256, nullable: true, + }) + public avatarUrl: string | null; + + @Column('varchar', { + length: 256, nullable: true, + }) + public bannerUrl: string | null; + + @Column('varchar', { + length: 32, nullable: true, + }) + public avatarColor: string | null; + + @Column('varchar', { + length: 32, nullable: true, + }) + public bannerColor: string | null; + + @Column('boolean', { + default: false, + comment: 'Whether the User is suspended.' + }) + public isSuspended: boolean; + + @Column('boolean', { + default: false, + comment: 'Whether the User is silenced.' + }) + public isSilenced: boolean; + + @Column('boolean', { + default: false, + comment: 'Whether the User is locked.' + }) + public isLocked: boolean; + + @Column('boolean', { + default: false, + comment: 'Whether the User is a bot.' + }) + public isBot: boolean; + + @Column('boolean', { + default: false, + comment: 'Whether the User is a cat.' + }) + public isCat: boolean; + + @Column('boolean', { + default: false, + comment: 'Whether the User is the admin.' + }) + public isAdmin: boolean; + + @Column('boolean', { + default: false, + comment: 'Whether the User is a moderator.' + }) + public isModerator: boolean; + + @Column('boolean', { + default: false, + }) + public isVerified: boolean; + + @Column('boolean', { + default: false, + }) + public twoFactorEnabled: boolean; + + @Column('varchar', { + length: 128, array: true, default: '{}' + }) + public emojis: string[]; + + @Index() + @Column('varchar', { + length: 128, nullable: true, + comment: 'The host of the User. It will be null if the origin of the user is local.' + }) + public host: string | null; + + @Column('varchar', { + length: 256, nullable: true, + comment: 'The inbox of the User. It will be null if the origin of the user is local.' + }) + public inbox: string | null; + + @Column('varchar', { + length: 256, nullable: true, + comment: 'The sharedInbox of the User. It will be null if the origin of the user is local.' + }) + public sharedInbox: string | null; + + @Column('varchar', { + length: 256, nullable: true, + comment: 'The featured of the User. It will be null if the origin of the user is local.' + }) + public featured: string | null; + + @Index() + @Column('varchar', { + length: 256, nullable: true, + comment: 'The URI of the User. It will be null if the origin of the user is local.' + }) + public uri: string | null; + + @Column('varchar', { + length: 128, nullable: true, + comment: 'The password hash of the User. It will be null if the origin of the user is local.' + }) + public password: string | null; + + @Index({ unique: true }) + @Column('varchar', { + length: 32, nullable: true, unique: true, + comment: 'The native access token of the User. It will be null if the origin of the user is local.' + }) + public token: string | null; + + @Column('jsonb', { + default: {}, + comment: 'The client-specific data of the User.' + }) + public clientData: Record; + + @Column('boolean', { + default: false, + }) + public autoWatch: boolean; + + @Column('boolean', { + default: false, + }) + public autoAcceptFollowed: boolean; + + @Column('boolean', { + default: false, + }) + public alwaysMarkNsfw: boolean; + + @Column('boolean', { + default: false, + }) + public carefulBot: boolean; +} + +export interface ILocalUser extends User { + host: null; +} + +export interface IRemoteUser extends User { + host: string; +} diff --git a/src/models/favorite.ts b/src/models/favorite.ts deleted file mode 100644 index 2008edbfaf..0000000000 --- a/src/models/favorite.ts +++ /dev/null @@ -1,65 +0,0 @@ -import * as mongo from 'mongodb'; -import * as deepcopy from 'deepcopy'; -import db from '../db/mongodb'; -import isObjectId from '../misc/is-objectid'; -import { pack as packNote } from './note'; -import { dbLogger } from '../db/logger'; - -const Favorite = db.get('favorites'); -Favorite.createIndex('userId'); -Favorite.createIndex(['userId', 'noteId'], { unique: true }); -export default Favorite; - -export type IFavorite = { - _id: mongo.ObjectID; - createdAt: Date; - userId: mongo.ObjectID; - noteId: mongo.ObjectID; -}; - -export const packMany = ( - favorites: any[], - me: any -) => { - return Promise.all(favorites.map(f => pack(f, me))); -}; - -/** - * Pack a favorite for API response - */ -export const pack = ( - favorite: any, - me: any -) => new Promise(async (resolve, reject) => { - let _favorite: any; - - // Populate the favorite if 'favorite' is ID - if (isObjectId(favorite)) { - _favorite = await Favorite.findOne({ - _id: favorite - }); - } else if (typeof favorite === 'string') { - _favorite = await Favorite.findOne({ - _id: new mongo.ObjectID(favorite) - }); - } else { - _favorite = deepcopy(favorite); - } - - // Rename _id to id - _favorite.id = _favorite._id; - delete _favorite._id; - - // Populate note - _favorite.note = await packNote(_favorite.noteId, me, { - detail: true - }); - - // (データベースの不具合などで)投稿が見つからなかったら - if (_favorite.note == null) { - dbLogger.warn(`[DAMAGED DB] (missing) pkg: favorite -> note :: ${_favorite.id} (note ${_favorite.noteId})`); - return resolve(null); - } - - resolve(_favorite); -}); diff --git a/src/models/follow-request.ts b/src/models/follow-request.ts deleted file mode 100644 index 4f75c63a32..0000000000 --- a/src/models/follow-request.ts +++ /dev/null @@ -1,66 +0,0 @@ -import * as mongo from 'mongodb'; -import * as deepcopy from 'deepcopy'; -import db from '../db/mongodb'; -import isObjectId from '../misc/is-objectid'; -import { pack as packUser } from './user'; - -const FollowRequest = db.get('followRequests'); -FollowRequest.createIndex('followerId'); -FollowRequest.createIndex('followeeId'); -FollowRequest.createIndex(['followerId', 'followeeId'], { unique: true }); -export default FollowRequest; - -export type IFollowRequest = { - _id: mongo.ObjectID; - createdAt: Date; - followeeId: mongo.ObjectID; - followerId: mongo.ObjectID; - requestId?: string; // id of Follow Activity - - // 非正規化 - _followee: { - host: string; - inbox?: string; - sharedInbox?: string; - }, - _follower: { - host: string; - inbox?: string; - sharedInbox?: string; - } -}; - -/** - * Pack a request for API response - */ -export const pack = ( - request: any, - me?: any -) => new Promise(async (resolve, reject) => { - let _request: any; - - // Populate the request if 'request' is ID - if (isObjectId(request)) { - _request = await FollowRequest.findOne({ - _id: request - }); - } else if (typeof request === 'string') { - _request = await FollowRequest.findOne({ - _id: new mongo.ObjectID(request) - }); - } else { - _request = deepcopy(request); - } - - // Rename _id to id - _request.id = _request._id; - delete _request._id; - - // Populate follower - _request.follower = await packUser(_request.followerId, me); - - // Populate followee - _request.followee = await packUser(_request.followeeId, me); - - resolve(_request); -}); diff --git a/src/models/following.ts b/src/models/following.ts deleted file mode 100644 index 12cc27211b..0000000000 --- a/src/models/following.ts +++ /dev/null @@ -1,27 +0,0 @@ -import * as mongo from 'mongodb'; -import db from '../db/mongodb'; - -const Following = db.get('following'); -Following.createIndex('followerId'); -Following.createIndex('followeeId'); -Following.createIndex(['followerId', 'followeeId'], { unique: true }); -export default Following; - -export type IFollowing = { - _id: mongo.ObjectID; - createdAt: Date; - followeeId: mongo.ObjectID; - followerId: mongo.ObjectID; - - // 非正規化 - _followee: { - host: string; - inbox?: string; - sharedInbox?: string; - }, - _follower: { - host: string; - inbox?: string; - sharedInbox?: string; - } -}; diff --git a/src/models/games/reversi/game.ts b/src/models/games/reversi/game.ts deleted file mode 100644 index 57c493cff5..0000000000 --- a/src/models/games/reversi/game.ts +++ /dev/null @@ -1,111 +0,0 @@ -import * as mongo from 'mongodb'; -import * as deepcopy from 'deepcopy'; -import db from '../../../db/mongodb'; -import isObjectId from '../../../misc/is-objectid'; -import { IUser, pack as packUser } from '../../user'; - -const ReversiGame = db.get('reversiGames'); -export default ReversiGame; - -export interface IReversiGame { - _id: mongo.ObjectID; - createdAt: Date; - startedAt: Date; - user1Id: mongo.ObjectID; - user2Id: mongo.ObjectID; - user1Accepted: boolean; - user2Accepted: boolean; - - /** - * どちらのプレイヤーが先行(黒)か - * 1 ... user1 - * 2 ... user2 - */ - black: number; - - isStarted: boolean; - isEnded: boolean; - winnerId: mongo.ObjectID; - surrendered: mongo.ObjectID; - logs: { - at: Date; - color: boolean; - pos: number; - }[]; - settings: { - map: string[]; - bw: string | number; - isLlotheo: boolean; - canPutEverywhere: boolean; - loopedBoard: boolean; - }; - form1: any; - form2: any; - - // ログのposを文字列としてすべて連結したもののCRC32値 - crc32: string; -} - -/** - * Pack an reversi game for API response - */ -export const pack = ( - game: any, - me?: string | mongo.ObjectID | IUser, - options?: { - detail?: boolean - } -) => new Promise(async (resolve, reject) => { - const opts = Object.assign({ - detail: true - }, options); - - let _game: any; - - // Populate the game if 'game' is ID - if (isObjectId(game)) { - _game = await ReversiGame.findOne({ - _id: game - }); - } else if (typeof game === 'string') { - _game = await ReversiGame.findOne({ - _id: new mongo.ObjectID(game) - }); - } else { - _game = deepcopy(game); - } - - // Me - const meId: mongo.ObjectID = me - ? isObjectId(me) - ? me as mongo.ObjectID - : typeof me === 'string' - ? new mongo.ObjectID(me) - : (me as IUser)._id - : null; - - // Rename _id to id - _game.id = _game._id; - delete _game._id; - - if (opts.detail === false) { - delete _game.logs; - delete _game.settings.map; - } else { - // 互換性のため - if (_game.settings.map.hasOwnProperty('size')) { - _game.settings.map = _game.settings.map.data.match(new RegExp(`.{1,${_game.settings.map.size}}`, 'g')); - } - } - - // Populate user - _game.user1 = await packUser(_game.user1Id, meId); - _game.user2 = await packUser(_game.user2Id, meId); - if (_game.winnerId) { - _game.winner = await packUser(_game.winnerId, meId); - } else { - _game.winner = null; - } - - resolve(_game); -}); diff --git a/src/models/games/reversi/matching.ts b/src/models/games/reversi/matching.ts deleted file mode 100644 index ba2ac1bc05..0000000000 --- a/src/models/games/reversi/matching.ts +++ /dev/null @@ -1,45 +0,0 @@ -import * as mongo from 'mongodb'; -import * as deepcopy from 'deepcopy'; -import db from '../../../db/mongodb'; -import isObjectId from '../../../misc/is-objectid'; -import { IUser, pack as packUser } from '../../user'; - -const Matching = db.get('reversiMatchings'); -export default Matching; - -export interface IMatching { - _id: mongo.ObjectID; - createdAt: Date; - parentId: mongo.ObjectID; - childId: mongo.ObjectID; -} - -/** - * Pack an reversi matching for API response - */ -export const pack = ( - matching: any, - me?: string | mongo.ObjectID | IUser -) => new Promise(async (resolve, reject) => { - - // Me - const meId: mongo.ObjectID = me - ? isObjectId(me) - ? me as mongo.ObjectID - : typeof me === 'string' - ? new mongo.ObjectID(me) - : (me as IUser)._id - : null; - - const _matching = deepcopy(matching); - - // Rename _id to id - _matching.id = _matching._id; - delete _matching._id; - - // Populate user - _matching.parent = await packUser(_matching.parentId, meId); - _matching.child = await packUser(_matching.childId, meId); - - resolve(_matching); -}); diff --git a/src/models/hashtag.ts b/src/models/hashtag.ts deleted file mode 100644 index c1de42086e..0000000000 --- a/src/models/hashtag.ts +++ /dev/null @@ -1,63 +0,0 @@ -import * as mongo from 'mongodb'; -import db from '../db/mongodb'; - -const Hashtag = db.get('hashtags'); -Hashtag.createIndex('tag', { unique: true }); -Hashtag.createIndex('mentionedUsersCount'); -Hashtag.createIndex('mentionedLocalUsersCount'); -Hashtag.createIndex('mentionedRemoteUsersCount'); -Hashtag.createIndex('attachedUsersCount'); -Hashtag.createIndex('attachedLocalUsersCount'); -Hashtag.createIndex('attachedRemoteUsersCount'); -export default Hashtag; - -// 後方互換性のため -Hashtag.findOne({ attachedUserIds: { $exists: false }}).then(h => { - if (h != null) { - Hashtag.update({}, { - $rename: { - mentionedUserIdsCount: 'mentionedUsersCount' - }, - $set: { - mentionedLocalUserIds: [], - mentionedLocalUsersCount: 0, - attachedUserIds: [], - attachedUsersCount: 0, - attachedLocalUserIds: [], - attachedLocalUsersCount: 0, - } - }, { - multi: true - }); - } -}); -Hashtag.findOne({ attachedRemoteUserIds: { $exists: false }}).then(h => { - if (h != null) { - Hashtag.update({}, { - $set: { - mentionedRemoteUserIds: [], - mentionedRemoteUsersCount: 0, - attachedRemoteUserIds: [], - attachedRemoteUsersCount: 0, - } - }, { - multi: true - }); - } -}); - -export interface IHashtags { - tag: string; - mentionedUserIds: mongo.ObjectID[]; - mentionedUsersCount: number; - mentionedLocalUserIds: mongo.ObjectID[]; - mentionedLocalUsersCount: number; - mentionedRemoteUserIds: mongo.ObjectID[]; - mentionedRemoteUsersCount: number; - attachedUserIds: mongo.ObjectID[]; - attachedUsersCount: number; - attachedLocalUserIds: mongo.ObjectID[]; - attachedLocalUsersCount: number; - attachedRemoteUserIds: mongo.ObjectID[]; - attachedRemoteUsersCount: number; -} diff --git a/src/models/id.ts b/src/models/id.ts new file mode 100644 index 0000000000..be2cccfe3b --- /dev/null +++ b/src/models/id.ts @@ -0,0 +1,4 @@ +export const id = () => ({ + type: 'varchar' as 'varchar', + length: 32 +}); diff --git a/src/models/index.ts b/src/models/index.ts new file mode 100644 index 0000000000..f88bb8d636 --- /dev/null +++ b/src/models/index.ts @@ -0,0 +1,74 @@ +import { getRepository, getCustomRepository } from 'typeorm'; +import { Instance } from './entities/instance'; +import { Emoji } from './entities/emoji'; +import { Poll } from './entities/poll'; +import { PollVote } from './entities/poll-vote'; +import { Meta } from './entities/meta'; +import { SwSubscription } from './entities/sw-subscription'; +import { NoteWatching } from './entities/note-watching'; +import { UserListJoining } from './entities/user-list-joining'; +import { Hashtag } from './entities/hashtag'; +import { NoteUnread } from './entities/note-unread'; +import { RegistrationTicket } from './entities/registration-tickets'; +import { UserRepository } from './repositories/user'; +import { NoteRepository } from './repositories/note'; +import { DriveFileRepository } from './repositories/drive-file'; +import { DriveFolderRepository } from './repositories/drive-folder'; +import { Log } from './entities/log'; +import { AccessToken } from './entities/access-token'; +import { UserNotePining } from './entities/user-note-pinings'; +import { SigninRepository } from './repositories/signin'; +import { MessagingMessageRepository } from './repositories/messaging-message'; +import { ReversiGameRepository } from './repositories/games/reversi/game'; +import { UserListRepository } from './repositories/user-list'; +import { FollowRequestRepository } from './repositories/follow-request'; +import { MutingRepository } from './repositories/muting'; +import { BlockingRepository } from './repositories/blocking'; +import { NoteReactionRepository } from './repositories/note-reaction'; +import { UserServiceLinking } from './entities/user-service-linking'; +import { NotificationRepository } from './repositories/notification'; +import { NoteFavoriteRepository } from './repositories/note-favorite'; +import { ReversiMatchingRepository } from './repositories/games/reversi/matching'; +import { UserPublickey } from './entities/user-publickey'; +import { UserKeypair } from './entities/user-keypair'; +import { AppRepository } from './repositories/app'; +import { FollowingRepository } from './repositories/following'; +import { AbuseUserReportRepository } from './repositories/abuse-user-report'; +import { AuthSessionRepository } from './repositories/auth-session'; + +export const Apps = getCustomRepository(AppRepository); +export const Notes = getCustomRepository(NoteRepository); +export const NoteFavorites = getCustomRepository(NoteFavoriteRepository); +export const NoteWatchings = getRepository(NoteWatching); +export const NoteReactions = getCustomRepository(NoteReactionRepository); +export const NoteUnreads = getRepository(NoteUnread); +export const Polls = getRepository(Poll); +export const PollVotes = getRepository(PollVote); +export const Users = getCustomRepository(UserRepository); +export const UserKeypairs = getRepository(UserKeypair); +export const UserPublickeys = getRepository(UserPublickey); +export const UserLists = getCustomRepository(UserListRepository); +export const UserListJoinings = getRepository(UserListJoining); +export const UserNotePinings = getRepository(UserNotePining); +export const UserServiceLinkings = getRepository(UserServiceLinking); +export const Followings = getCustomRepository(FollowingRepository); +export const FollowRequests = getCustomRepository(FollowRequestRepository); +export const Instances = getRepository(Instance); +export const Emojis = getRepository(Emoji); +export const DriveFiles = getCustomRepository(DriveFileRepository); +export const DriveFolders = getCustomRepository(DriveFolderRepository); +export const Notifications = getCustomRepository(NotificationRepository); +export const Metas = getRepository(Meta); +export const Mutings = getCustomRepository(MutingRepository); +export const Blockings = getCustomRepository(BlockingRepository); +export const SwSubscriptions = getRepository(SwSubscription); +export const Hashtags = getRepository(Hashtag); +export const AbuseUserReports = getCustomRepository(AbuseUserReportRepository); +export const RegistrationTickets = getRepository(RegistrationTicket); +export const AuthSessions = getCustomRepository(AuthSessionRepository); +export const AccessTokens = getRepository(AccessToken); +export const Signins = getCustomRepository(SigninRepository); +export const MessagingMessages = getCustomRepository(MessagingMessageRepository); +export const ReversiGames = getCustomRepository(ReversiGameRepository); +export const ReversiMatchings = getCustomRepository(ReversiMatchingRepository); +export const Logs = getRepository(Log); diff --git a/src/models/instance.ts b/src/models/instance.ts deleted file mode 100644 index cdce570a4b..0000000000 --- a/src/models/instance.ts +++ /dev/null @@ -1,90 +0,0 @@ -import * as mongo from 'mongodb'; -import db from '../db/mongodb'; - -const Instance = db.get('instances'); -Instance.createIndex('host', { unique: true }); -export default Instance; - -export interface IInstance { - _id: mongo.ObjectID; - - /** - * ホスト - */ - host: string; - - /** - * このインスタンスを捕捉した日時 - */ - caughtAt: Date; - - /** - * このインスタンスのシステム (MastodonとかMisskeyとかPleromaとか) - */ - system: string; - - /** - * このインスタンスのユーザー数 - */ - usersCount: number; - - /** - * このインスタンスから受け取った投稿数 - */ - notesCount: number; - - /** - * このインスタンスのユーザーからフォローされている、自インスタンスのユーザーの数 - */ - followingCount: number; - - /** - * このインスタンスのユーザーをフォローしている、自インスタンスのユーザーの数 - */ - followersCount: number; - - /** - * ドライブ使用量 - */ - driveUsage: number; - - /** - * ドライブのファイル数 - */ - driveFiles: number; - - /** - * 直近のリクエスト送信日時 - */ - latestRequestSentAt?: Date; - - /** - * 直近のリクエスト送信時のHTTPステータスコード - */ - latestStatus?: number; - - /** - * 直近のリクエスト受信日時 - */ - latestRequestReceivedAt?: Date; - - /** - * このインスタンスと不通かどうか - */ - isNotResponding: boolean; - - /** - * このインスタンスと最後にやり取りした日時 - */ - lastCommunicatedAt: Date; - - /** - * このインスタンスをブロックしているか - */ - isBlocked: boolean; - - /** - * このインスタンスが閉鎖済みとしてマークされているか - */ - isMarkedAsClosed: boolean; -} diff --git a/src/models/log.ts b/src/models/log.ts deleted file mode 100644 index 6f79e83c78..0000000000 --- a/src/models/log.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as mongo from 'mongodb'; -import db from '../db/mongodb'; - -const Log = db.get('logs'); -Log.createIndex('createdAt', { expireAfterSeconds: 3600 * 24 * 3 }); -Log.createIndex('level'); -Log.createIndex('domain'); -export default Log; - -export interface ILog { - _id: mongo.ObjectID; - createdAt: Date; - machine: string; - worker: string; - domain: string[]; - level: string; - message: string; - data: any; -} diff --git a/src/models/messaging-message.ts b/src/models/messaging-message.ts deleted file mode 100644 index 67abb4d111..0000000000 --- a/src/models/messaging-message.ts +++ /dev/null @@ -1,75 +0,0 @@ -import * as mongo from 'mongodb'; -import * as deepcopy from 'deepcopy'; -import { pack as packUser } from './user'; -import { pack as packFile } from './drive-file'; -import db from '../db/mongodb'; -import isObjectId from '../misc/is-objectid'; -import { length } from 'stringz'; - -const MessagingMessage = db.get('messagingMessages'); -MessagingMessage.createIndex('userId'); -MessagingMessage.createIndex('recipientId'); -export default MessagingMessage; - -export interface IMessagingMessage { - _id: mongo.ObjectID; - createdAt: Date; - text: string; - userId: mongo.ObjectID; - recipientId: mongo.ObjectID; - isRead: boolean; - fileId: mongo.ObjectID; -} - -export function isValidText(text: string): boolean { - return length(text.trim()) <= 1000 && text.trim() != ''; -} - -/** - * Pack a messaging message for API response - */ -export const pack = ( - message: any, - me?: any, - options?: { - populateRecipient: boolean - } -) => new Promise(async (resolve, reject) => { - const opts = options || { - populateRecipient: true - }; - - let _message: any; - - // Populate the message if 'message' is ID - if (isObjectId(message)) { - _message = await MessagingMessage.findOne({ - _id: message - }); - } else if (typeof message === 'string') { - _message = await MessagingMessage.findOne({ - _id: new mongo.ObjectID(message) - }); - } else { - _message = deepcopy(message); - } - - // Rename _id to id - _message.id = _message._id; - delete _message._id; - - // Populate user - _message.user = await packUser(_message.userId, me); - - if (_message.fileId) { - // Populate file - _message.file = await packFile(_message.fileId); - } - - if (opts.populateRecipient) { - // Populate recipient - _message.recipient = await packUser(_message.recipientId, me); - } - - resolve(_message); -}); diff --git a/src/models/meta.ts b/src/models/meta.ts deleted file mode 100644 index 5ca0f01236..0000000000 --- a/src/models/meta.ts +++ /dev/null @@ -1,257 +0,0 @@ -import db from '../db/mongodb'; -import config from '../config'; -import User from './user'; -import { transform } from '../misc/cafy-id'; - -const Meta = db.get('meta'); -export default Meta; - -// 後方互換性のため。 -// 過去のMisskeyではインスタンス名や紹介を設定ファイルに記述していたのでそれを移行 -if ((config as any).name) { - Meta.findOne({}).then(m => { - if (m != null && m.name == null) { - Meta.update({}, { - $set: { - name: (config as any).name - } - }); - } - }); -} -if ((config as any).description) { - Meta.findOne({}).then(m => { - if (m != null && m.description == null) { - Meta.update({}, { - $set: { - description: (config as any).description - } - }); - } - }); -} -if ((config as any).localDriveCapacityMb) { - Meta.findOne({}).then(m => { - if (m != null && m.localDriveCapacityMb == null) { - Meta.update({}, { - $set: { - localDriveCapacityMb: (config as any).localDriveCapacityMb - } - }); - } - }); -} -if ((config as any).remoteDriveCapacityMb) { - Meta.findOne({}).then(m => { - if (m != null && m.remoteDriveCapacityMb == null) { - Meta.update({}, { - $set: { - remoteDriveCapacityMb: (config as any).remoteDriveCapacityMb - } - }); - } - }); -} -if ((config as any).preventCacheRemoteFiles) { - Meta.findOne({}).then(m => { - if (m != null && m.cacheRemoteFiles == null) { - Meta.update({}, { - $set: { - cacheRemoteFiles: !(config as any).preventCacheRemoteFiles - } - }); - } - }); -} -if ((config as any).recaptcha) { - Meta.findOne({}).then(m => { - if (m != null && m.enableRecaptcha == null) { - Meta.update({}, { - $set: { - enableRecaptcha: (config as any).recaptcha != null, - recaptchaSiteKey: (config as any).recaptcha.site_key, - recaptchaSecretKey: (config as any).recaptcha.secret_key, - } - }); - } - }); -} -if ((config as any).ghost) { - Meta.findOne({}).then(async m => { - if (m != null && m.proxyAccount == null) { - const account = await User.findOne({ _id: transform((config as any).ghost) }); - Meta.update({}, { - $set: { - proxyAccount: account.username - } - }); - } - }); -} -if ((config as any).maintainer) { - Meta.findOne({}).then(m => { - if (m != null && m.maintainer == null) { - Meta.update({}, { - $set: { - maintainer: (config as any).maintainer - } - }); - } - }); -} -if ((config as any).twitter) { - Meta.findOne({}).then(m => { - if (m != null && m.enableTwitterIntegration == null) { - Meta.update({}, { - $set: { - enableTwitterIntegration: true, - twitterConsumerKey: (config as any).twitter.consumer_key, - twitterConsumerSecret: (config as any).twitter.consumer_secret - } - }); - } - }); -} -if ((config as any).github) { - Meta.findOne({}).then(m => { - if (m != null && m.enableGithubIntegration == null) { - Meta.update({}, { - $set: { - enableGithubIntegration: true, - githubClientId: (config as any).github.client_id, - githubClientSecret: (config as any).github.client_secret - } - }); - } - }); -} -if ((config as any).user_recommendation) { - Meta.findOne({}).then(m => { - if (m != null && m.enableExternalUserRecommendation == null) { - Meta.update({}, { - $set: { - enableExternalUserRecommendation: true, - externalUserRecommendationEngine: (config as any).user_recommendation.engine, - externalUserRecommendationTimeout: (config as any).user_recommendation.timeout - } - }); - } - }); -} -if ((config as any).sw) { - Meta.findOne({}).then(m => { - if (m != null && m.enableServiceWorker == null) { - Meta.update({}, { - $set: { - enableServiceWorker: true, - swPublicKey: (config as any).sw.public_key, - swPrivateKey: (config as any).sw.private_key - } - }); - } - }); -} -Meta.findOne({}).then(m => { - if (m != null && (m as any).broadcasts != null) { - Meta.update({}, { - $rename: { - broadcasts: 'announcements' - } - }); - } -}); - -export type IMeta = { - name?: string; - description?: string; - - /** - * メンテナ情報 - */ - maintainer: { - /** - * メンテナの名前 - */ - name: string; - - /** - * メンテナの連絡先 - */ - email?: string; - }; - - langs?: string[]; - - announcements?: any[]; - - stats?: { - notesCount: number; - originalNotesCount: number; - usersCount: number; - originalUsersCount: number; - }; - - disableRegistration?: boolean; - disableLocalTimeline?: boolean; - disableGlobalTimeline?: boolean; - enableEmojiReaction?: boolean; - useStarForReactionFallback?: boolean; - hidedTags?: string[]; - mascotImageUrl?: string; - bannerUrl?: string; - errorImageUrl?: string; - iconUrl?: string; - - cacheRemoteFiles?: boolean; - - proxyAccount?: string; - - enableRecaptcha?: boolean; - recaptchaSiteKey?: string; - recaptchaSecretKey?: string; - - /** - * Drive capacity of a local user (MB) - */ - localDriveCapacityMb?: number; - - /** - * Drive capacity of a remote user (MB) - */ - remoteDriveCapacityMb?: number; - - /** - * Max allowed note text length in characters - */ - maxNoteTextLength?: number; - - summalyProxy?: string; - - enableTwitterIntegration?: boolean; - twitterConsumerKey?: string; - twitterConsumerSecret?: string; - - enableGithubIntegration?: boolean; - githubClientId?: string; - githubClientSecret?: string; - - enableDiscordIntegration?: boolean; - discordClientId?: string; - discordClientSecret?: string; - - enableExternalUserRecommendation?: boolean; - externalUserRecommendationEngine?: string; - externalUserRecommendationTimeout?: number; - - enableEmail?: boolean; - email?: string; - smtpSecure?: boolean; - smtpHost?: string; - smtpPort?: number; - smtpUser?: string; - smtpPass?: string; - - enableServiceWorker?: boolean; - swPublicKey?: string; - swPrivateKey?: string; -}; diff --git a/src/models/mute.ts b/src/models/mute.ts deleted file mode 100644 index 52775e13ca..0000000000 --- a/src/models/mute.ts +++ /dev/null @@ -1,56 +0,0 @@ -import * as mongo from 'mongodb'; -import db from '../db/mongodb'; -import isObjectId from '../misc/is-objectid'; -import * as deepcopy from 'deepcopy'; -import { pack as packUser, IUser } from './user'; - -const Mute = db.get('mute'); -Mute.createIndex('muterId'); -Mute.createIndex('muteeId'); -Mute.createIndex(['muterId', 'muteeId'], { unique: true }); -export default Mute; - -export interface IMute { - _id: mongo.ObjectID; - createdAt: Date; - muterId: mongo.ObjectID; - muteeId: mongo.ObjectID; -} - -export const packMany = ( - mutes: (string | mongo.ObjectID | IMute)[], - me?: string | mongo.ObjectID | IUser -) => { - return Promise.all(mutes.map(x => pack(x, me))); -}; - -export const pack = ( - mute: any, - me?: any -) => new Promise(async (resolve, reject) => { - let _mute: any; - - // Populate the mute if 'mute' is ID - if (isObjectId(mute)) { - _mute = await Mute.findOne({ - _id: mute - }); - } else if (typeof mute === 'string') { - _mute = await Mute.findOne({ - _id: new mongo.ObjectID(mute) - }); - } else { - _mute = deepcopy(mute); - } - - // Rename _id to id - _mute.id = _mute._id; - delete _mute._id; - - // Populate mutee - _mute.mutee = await packUser(_mute.muteeId, me, { - detail: true - }); - - resolve(_mute); -}); diff --git a/src/models/note-reaction.ts b/src/models/note-reaction.ts deleted file mode 100644 index 89b7529350..0000000000 --- a/src/models/note-reaction.ts +++ /dev/null @@ -1,51 +0,0 @@ -import * as mongo from 'mongodb'; -import * as deepcopy from 'deepcopy'; -import db from '../db/mongodb'; -import isObjectId from '../misc/is-objectid'; -import { pack as packUser } from './user'; - -const NoteReaction = db.get('noteReactions'); -NoteReaction.createIndex('noteId'); -NoteReaction.createIndex('userId'); -NoteReaction.createIndex(['userId', 'noteId'], { unique: true }); -export default NoteReaction; - -export interface INoteReaction { - _id: mongo.ObjectID; - createdAt: Date; - noteId: mongo.ObjectID; - userId: mongo.ObjectID; - reaction: string; -} - -/** - * Pack a reaction for API response - */ -export const pack = ( - reaction: any, - me?: any -) => new Promise(async (resolve, reject) => { - let _reaction: any; - - // Populate the reaction if 'reaction' is ID - if (isObjectId(reaction)) { - _reaction = await NoteReaction.findOne({ - _id: reaction - }); - } else if (typeof reaction === 'string') { - _reaction = await NoteReaction.findOne({ - _id: new mongo.ObjectID(reaction) - }); - } else { - _reaction = deepcopy(reaction); - } - - // Rename _id to id - _reaction.id = _reaction._id; - delete _reaction._id; - - // Populate user - _reaction.user = await packUser(_reaction.userId, me); - - resolve(_reaction); -}); diff --git a/src/models/note-unread.ts b/src/models/note-unread.ts deleted file mode 100644 index dd08640d85..0000000000 --- a/src/models/note-unread.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as mongo from 'mongodb'; -import db from '../db/mongodb'; - -const NoteUnread = db.get('noteUnreads'); -NoteUnread.createIndex('userId'); -NoteUnread.createIndex('noteId'); -NoteUnread.createIndex(['userId', 'noteId'], { unique: true }); -export default NoteUnread; - -export interface INoteUnread { - _id: mongo.ObjectID; - noteId: mongo.ObjectID; - userId: mongo.ObjectID; - isSpecified: boolean; - - _note: { - userId: mongo.ObjectID; - }; -} diff --git a/src/models/note-watching.ts b/src/models/note-watching.ts deleted file mode 100644 index 83aaf8ad06..0000000000 --- a/src/models/note-watching.ts +++ /dev/null @@ -1,15 +0,0 @@ -import * as mongo from 'mongodb'; -import db from '../db/mongodb'; - -const NoteWatching = db.get('noteWatching'); -NoteWatching.createIndex('userId'); -NoteWatching.createIndex('noteId'); -NoteWatching.createIndex(['userId', 'noteId'], { unique: true }); -export default NoteWatching; - -export interface INoteWatching { - _id: mongo.ObjectID; - createdAt: Date; - userId: mongo.ObjectID; - noteId: mongo.ObjectID; -} diff --git a/src/models/note.ts b/src/models/note.ts deleted file mode 100644 index 8c71c1940c..0000000000 --- a/src/models/note.ts +++ /dev/null @@ -1,418 +0,0 @@ -import * as mongo from 'mongodb'; -import * as deepcopy from 'deepcopy'; -import rap from '@prezzemolo/rap'; -import db from '../db/mongodb'; -import isObjectId from '../misc/is-objectid'; -import { length } from 'stringz'; -import { IUser, pack as packUser } from './user'; -import { pack as packApp } from './app'; -import PollVote from './poll-vote'; -import NoteReaction from './note-reaction'; -import { packMany as packFileMany, IDriveFile } from './drive-file'; -import Following from './following'; -import Emoji from './emoji'; -import { dbLogger } from '../db/logger'; -import { unique, concat } from '../prelude/array'; - -const Note = db.get('notes'); -Note.createIndex('uri', { sparse: true, unique: true }); -Note.createIndex('userId'); -Note.createIndex('mentions'); -Note.createIndex('visibleUserIds'); -Note.createIndex('replyId'); -Note.createIndex('renoteId'); -Note.createIndex('tagsLower'); -Note.createIndex('_user.host'); -Note.createIndex('_files._id'); -Note.createIndex('_files.contentType'); -Note.createIndex({ createdAt: -1 }); -Note.createIndex({ score: -1 }, { sparse: true }); -export default Note; - -export function isValidCw(text: string): boolean { - return length(text.trim()) <= 100; -} - -export type INote = { - _id: mongo.ObjectID; - createdAt: Date; - deletedAt: Date; - updatedAt?: Date; - fileIds: mongo.ObjectID[]; - replyId: mongo.ObjectID; - renoteId: mongo.ObjectID; - poll: IPoll; - name?: string; - text: string; - tags: string[]; - tagsLower: string[]; - emojis: string[]; - cw: string; - userId: mongo.ObjectID; - appId: mongo.ObjectID; - viaMobile: boolean; - localOnly: boolean; - renoteCount: number; - repliesCount: number; - reactionCounts: Record; - mentions: mongo.ObjectID[]; - mentionedRemoteUsers: { - uri: string; - username: string; - host: string; - }[]; - - /** - * public ... 公開 - * home ... ホームタイムライン(ユーザーページのタイムライン含む)のみに流す - * followers ... フォロワーのみ - * specified ... visibleUserIds で指定したユーザーのみ - */ - visibility: 'public' | 'home' | 'followers' | 'specified'; - - visibleUserIds: mongo.ObjectID[]; - - geo: { - coordinates: number[]; - altitude: number; - accuracy: number; - altitudeAccuracy: number; - heading: number; - speed: number; - }; - - uri: string; - - /** - * 人気の投稿度合いを表すスコア - */ - score: number; - - // 非正規化 - _reply?: { - userId: mongo.ObjectID; - }; - _renote?: { - userId: mongo.ObjectID; - }; - _user: { - host: string; - inbox?: string; - }; - _files?: IDriveFile[]; -}; - -export type IPoll = { - choices: IChoice[]; - multiple?: boolean; - expiresAt?: Date; -}; - -export type IChoice = { - id: number; - text: string; - votes: number; -}; - -export const hideNote = async (packedNote: any, meId: mongo.ObjectID) => { - let hide = false; - - // visibility が private かつ投稿者のIDが自分のIDではなかったら非表示(後方互換性のため) - if (packedNote.visibility == 'private' && (meId == null || !meId.equals(packedNote.userId))) { - hide = true; - } - - // visibility が specified かつ自分が指定されていなかったら非表示 - if (packedNote.visibility == 'specified') { - if (meId == null) { - hide = true; - } else if (meId.equals(packedNote.userId)) { - hide = false; - } else { - // 指定されているかどうか - const specified = packedNote.visibleUserIds.some((id: any) => meId.equals(id)); - - if (specified) { - hide = false; - } else { - hide = true; - } - } - } - - // visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示 - if (packedNote.visibility == 'followers') { - if (meId == null) { - hide = true; - } else if (meId.equals(packedNote.userId)) { - hide = false; - } else if (packedNote.reply && meId.equals(packedNote.reply.userId)) { - // 自分の投稿に対するリプライ - hide = false; - } else if (packedNote.mentions && packedNote.mentions.some((id: any) => meId.equals(id))) { - // 自分へのメンション - hide = false; - } else { - // フォロワーかどうか - const following = await Following.findOne({ - followeeId: packedNote.userId, - followerId: meId - }); - - if (following == null) { - hide = true; - } else { - hide = false; - } - } - } - - if (hide) { - packedNote.fileIds = []; - packedNote.files = []; - packedNote.text = null; - packedNote.poll = null; - packedNote.cw = null; - packedNote.tags = []; - packedNote.geo = null; - packedNote.isHidden = true; - } -}; - -export const packMany = ( - notes: (string | mongo.ObjectID | INote)[], - me?: string | mongo.ObjectID | IUser, - options?: { - detail?: boolean; - skipHide?: boolean; - } -) => { - return Promise.all(notes.map(n => pack(n, me, options))); -}; - -/** - * Pack a note for API response - * - * @param note target - * @param me? serializee - * @param options? serialize options - * @return response - */ -export const pack = async ( - note: string | mongo.ObjectID | INote, - me?: string | mongo.ObjectID | IUser, - options?: { - detail?: boolean; - skipHide?: boolean; - } -) => { - const opts = Object.assign({ - detail: true, - skipHide: false - }, options); - - // Me - const meId: mongo.ObjectID = me - ? isObjectId(me) - ? me as mongo.ObjectID - : typeof me === 'string' - ? new mongo.ObjectID(me) - : (me as IUser)._id - : null; - - let _note: any; - - // Populate the note if 'note' is ID - if (isObjectId(note)) { - _note = await Note.findOne({ - _id: note - }); - } else if (typeof note === 'string') { - _note = await Note.findOne({ - _id: new mongo.ObjectID(note) - }); - } else { - _note = deepcopy(note); - } - - // (データベースの欠損などで)投稿がデータベース上に見つからなかったとき - if (_note == null) { - dbLogger.warn(`[DAMAGED DB] (missing) pkg: note :: ${note}`); - return null; - } - - const id = _note._id; - - // Some counts - _note.renoteCount = _note.renoteCount || 0; - _note.repliesCount = _note.repliesCount || 0; - _note.reactionCounts = _note.reactionCounts || {}; - - // _note._userを消す前か、_note.userを解決した後でないとホストがわからない - if (_note._user) { - const host = _note._user.host; - // 互換性のため。(古いMisskeyではNoteにemojisが無い) - if (_note.emojis == null) { - _note.emojis = Emoji.find({ - host: host - }, { - fields: { _id: false } - }); - } else { - _note.emojis = unique(concat([_note.emojis, Object.keys(_note.reactionCounts).map(x => x.replace(/:/g, ''))])); - - _note.emojis = Emoji.find({ - name: { $in: _note.emojis }, - host: host - }, { - fields: { _id: false } - }); - } - } - - // Rename _id to id - _note.id = _note._id; - delete _note._id; - - delete _note.prev; - delete _note.next; - delete _note.tagsLower; - delete _note.score; - delete _note._user; - delete _note._reply; - delete _note._renote; - delete _note._files; - delete _note._replyIds; - delete _note.mentionedRemoteUsers; - - if (_note.geo) delete _note.geo.type; - - // Populate user - _note.user = packUser(_note.userId, meId); - - // Populate app - if (_note.appId) { - _note.app = packApp(_note.appId); - } - - // Populate files - _note.files = packFileMany(_note.fileIds || []); - - // 後方互換性のため - _note.mediaIds = _note.fileIds; - _note.media = _note.files; - - // When requested a detailed note data - if (opts.detail) { - if (_note.replyId) { - // Populate reply to note - _note.reply = pack(_note.replyId, meId, { - detail: false - }); - } - - if (_note.renoteId) { - // Populate renote - _note.renote = pack(_note.renoteId, meId, { - detail: _note.text == null - }); - } - - // Poll - if (meId && _note.poll) { - _note.poll = (async poll => { - if (poll.multiple) { - const votes = await PollVote.find({ - userId: meId, - noteId: id - }); - - const myChoices = (poll.choices as IChoice[]).filter(x => votes.some(y => x.id == y.choice)); - for (const myChoice of myChoices) { - (myChoice as any).isVoted = true; - } - - return poll; - } else { - poll.multiple = false; - } - - const vote = await PollVote - .findOne({ - userId: meId, - noteId: id - }); - - if (vote) { - const myChoice = (poll.choices as IChoice[]) - .filter(x => x.id == vote.choice)[0] as any; - - myChoice.isVoted = true; - } - - return poll; - })(_note.poll); - } - - if (meId) { - // Fetch my reaction - _note.myReaction = (async () => { - const reaction = await NoteReaction - .findOne({ - userId: meId, - noteId: id, - deletedAt: { $exists: false } - }); - - if (reaction) { - return reaction.reaction; - } - - return null; - })(); - } - } - - // resolve promises in _note object - _note = await rap(_note); - - //#region (データベースの欠損などで)参照しているデータがデータベース上に見つからなかったとき - if (_note.user == null) { - dbLogger.warn(`[DAMAGED DB] (missing) pkg: note -> user :: ${_note.id} (user ${_note.userId})`); - return null; - } - - if (opts.detail) { - if (_note.replyId != null && _note.reply == null) { - dbLogger.warn(`[DAMAGED DB] (missing) pkg: note -> reply :: ${_note.id} (reply ${_note.replyId})`); - return null; - } - - if (_note.renoteId != null && _note.renote == null) { - dbLogger.warn(`[DAMAGED DB] (missing) pkg: note -> renote :: ${_note.id} (renote ${_note.renoteId})`); - return null; - } - } - //#endregion - - if (_note.name) { - _note.text = `【${_note.name}】\n${_note.text}`; - } - - if (_note.user.isCat && _note.text) { - _note.text = (_note.text - // ja-JP - .replace(/な/g, 'にゃ').replace(/ナ/g, 'ニャ').replace(/ナ/g, 'ニャ') - // ko-KR - .replace(/[나-낳]/g, (match: string) => String.fromCharCode( - match.codePointAt(0) + '냐'.charCodeAt(0) - '나'.charCodeAt(0) - )) - ); - } - - if (!opts.skipHide) { - await hideNote(_note, meId); - } - - return _note; -}; diff --git a/src/models/notification.ts b/src/models/notification.ts deleted file mode 100644 index 75456af57b..0000000000 --- a/src/models/notification.ts +++ /dev/null @@ -1,120 +0,0 @@ -import * as mongo from 'mongodb'; -import * as deepcopy from 'deepcopy'; -import db from '../db/mongodb'; -import isObjectId from '../misc/is-objectid'; -import { IUser, pack as packUser } from './user'; -import { pack as packNote } from './note'; -import { dbLogger } from '../db/logger'; - -const Notification = db.get('notifications'); -Notification.createIndex('notifieeId'); -export default Notification; - -export interface INotification { - _id: mongo.ObjectID; - createdAt: Date; - - /** - * 通知の受信者 - */ - notifiee?: IUser; - - /** - * 通知の受信者 - */ - notifieeId: mongo.ObjectID; - - /** - * イニシエータ(initiator)、Origin。通知を行う原因となったユーザー - */ - notifier?: IUser; - - /** - * イニシエータ(initiator)、Origin。通知を行う原因となったユーザー - */ - notifierId: mongo.ObjectID; - - /** - * 通知の種類。 - * follow - フォローされた - * mention - 投稿で自分が言及された - * reply - (自分または自分がWatchしている)投稿が返信された - * renote - (自分または自分がWatchしている)投稿がRenoteされた - * quote - (自分または自分がWatchしている)投稿が引用Renoteされた - * reaction - (自分または自分がWatchしている)投稿にリアクションされた - * poll_vote - (自分または自分がWatchしている)投稿の投票に投票された - */ - type: 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'poll_vote'; - - /** - * 通知が読まれたかどうか - */ - isRead: boolean; -} - -export const packMany = ( - notifications: any[] -) => { - return Promise.all(notifications.map(n => pack(n))); -}; - -/** - * Pack a notification for API response - */ -export const pack = (notification: any) => new Promise(async (resolve, reject) => { - let _notification: any; - - // Populate the notification if 'notification' is ID - if (isObjectId(notification)) { - _notification = await Notification.findOne({ - _id: notification - }); - } else if (typeof notification === 'string') { - _notification = await Notification.findOne({ - _id: new mongo.ObjectID(notification) - }); - } else { - _notification = deepcopy(notification); - } - - // Rename _id to id - _notification.id = _notification._id; - delete _notification._id; - - // Rename notifierId to userId - _notification.userId = _notification.notifierId; - delete _notification.notifierId; - - const me = _notification.notifieeId; - delete _notification.notifieeId; - - // Populate notifier - _notification.user = await packUser(_notification.userId, me); - - switch (_notification.type) { - case 'follow': - case 'receiveFollowRequest': - // nope - break; - case 'mention': - case 'reply': - case 'renote': - case 'quote': - case 'reaction': - case 'poll_vote': - // Populate note - _notification.note = await packNote(_notification.noteId, me); - - // (データベースの不具合などで)投稿が見つからなかったら - if (_notification.note == null) { - dbLogger.warn(`[DAMAGED DB] (missing) pkg: notification -> note :: ${_notification.id} (note ${_notification.noteId})`); - return resolve(null); - } - break; - default: - dbLogger.error(`Unknown type: ${_notification.type}`); - break; - } - - resolve(_notification); -}); diff --git a/src/models/poll-vote.ts b/src/models/poll-vote.ts deleted file mode 100644 index e6178cbc26..0000000000 --- a/src/models/poll-vote.ts +++ /dev/null @@ -1,17 +0,0 @@ -import * as mongo from 'mongodb'; -import db from '../db/mongodb'; - -const PollVote = db.get('pollVotes'); -PollVote.dropIndex(['userId', 'noteId'], { unique: true }).catch(() => {}); -PollVote.createIndex('userId'); -PollVote.createIndex('noteId'); -PollVote.createIndex(['userId', 'noteId', 'choice'], { unique: true }); -export default PollVote; - -export interface IPollVote { - _id: mongo.ObjectID; - createdAt: Date; - userId: mongo.ObjectID; - noteId: mongo.ObjectID; - choice: number; -} diff --git a/src/models/registration-tickets.ts b/src/models/registration-tickets.ts deleted file mode 100644 index 846acefedf..0000000000 --- a/src/models/registration-tickets.ts +++ /dev/null @@ -1,12 +0,0 @@ -import * as mongo from 'mongodb'; -import db from '../db/mongodb'; - -const RegistrationTicket = db.get('registrationTickets'); -RegistrationTicket.createIndex('code', { unique: true }); -export default RegistrationTicket; - -export interface IRegistrationTicket { - _id: mongo.ObjectID; - createdAt: Date; - code: string; -} diff --git a/src/models/repositories/abuse-user-report.ts b/src/models/repositories/abuse-user-report.ts new file mode 100644 index 0000000000..c72a582c04 --- /dev/null +++ b/src/models/repositories/abuse-user-report.ts @@ -0,0 +1,32 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { Users } from '..'; +import rap from '@prezzemolo/rap'; +import { AbuseUserReport } from '../entities/abuse-user-report'; + +@EntityRepository(AbuseUserReport) +export class AbuseUserReportRepository extends Repository { + public packMany( + reports: any[], + ) { + return Promise.all(reports.map(x => this.pack(x))); + } + + public async pack( + src: AbuseUserReport['id'] | AbuseUserReport, + ) { + const report = typeof src === 'object' ? src : await this.findOne(src); + + return await rap({ + id: report.id, + createdAt: report.createdAt, + reporterId: report.reporterId, + userId: report.userId, + reporter: Users.pack(report.reporter || report.reporterId, null, { + detail: true + }), + user: Users.pack(report.user || report.userId, null, { + detail: true + }), + }); + } +} diff --git a/src/models/repositories/app.ts b/src/models/repositories/app.ts new file mode 100644 index 0000000000..2e3323baf8 --- /dev/null +++ b/src/models/repositories/app.ts @@ -0,0 +1,36 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { App } from '../entities/app'; +import { AccessTokens } from '..'; + +@EntityRepository(App) +export class AppRepository extends Repository { + public async pack( + src: App['id'] | App, + me?: any, + options?: { + detail?: boolean, + includeSecret?: boolean, + includeProfileImageIds?: boolean + } + ) { + const opts = Object.assign({ + detail: false, + includeSecret: false, + includeProfileImageIds: false + }, options); + + const app = typeof src === 'object' ? src : await this.findOne(src); + + return { + id: app.id, + name: app.name, + ...(opts.includeSecret ? { secret: app.secret } : {}), + ...(me ? { + isAuthorized: await AccessTokens.count({ + appId: app.id, + userId: me, + }).then(count => count > 0) + } : {}) + }; + } +} diff --git a/src/models/repositories/auth-session.ts b/src/models/repositories/auth-session.ts new file mode 100644 index 0000000000..76e3ddf9ab --- /dev/null +++ b/src/models/repositories/auth-session.ts @@ -0,0 +1,19 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { Apps } from '..'; +import rap from '@prezzemolo/rap'; +import { AuthSession } from '../entities/auth-session'; + +@EntityRepository(AuthSession) +export class AuthSessionRepository extends Repository { + public async pack( + src: AuthSession['id'] | AuthSession, + me?: any + ) { + const session = typeof src === 'object' ? src : await this.findOne(src); + + return await rap({ + id: session.id, + app: Apps.pack(session.appId, me) + }); + } +} diff --git a/src/models/repositories/blocking.ts b/src/models/repositories/blocking.ts new file mode 100644 index 0000000000..81f3866131 --- /dev/null +++ b/src/models/repositories/blocking.ts @@ -0,0 +1,28 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { Users } from '..'; +import rap from '@prezzemolo/rap'; +import { Blocking } from '../entities/blocking'; + +@EntityRepository(Blocking) +export class BlockingRepository extends Repository { + public packMany( + blockings: any[], + me: any + ) { + return Promise.all(blockings.map(x => this.pack(x, me))); + } + + public async pack( + src: Blocking['id'] | Blocking, + me?: any + ) { + const blocking = typeof src === 'object' ? src : await this.findOne(src); + + return await rap({ + id: blocking.id, + blockee: Users.pack(blocking.blockeeId, me, { + detail: true + }) + }); + } +} diff --git a/src/models/repositories/drive-file.ts b/src/models/repositories/drive-file.ts new file mode 100644 index 0000000000..fe0ca72bfb --- /dev/null +++ b/src/models/repositories/drive-file.ts @@ -0,0 +1,113 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { DriveFile } from '../entities/drive-file'; +import { Users, DriveFolders } from '..'; +import rap from '@prezzemolo/rap'; +import { User } from '../entities/user'; + +@EntityRepository(DriveFile) +export class DriveFileRepository extends Repository { + public validateFileName(name: string): boolean { + return ( + (name.trim().length > 0) && + (name.length <= 200) && + (name.indexOf('\\') === -1) && + (name.indexOf('/') === -1) && + (name.indexOf('..') === -1) + ); + } + + public getPublicUrl(file: DriveFile, thumbnail = false): string { + if (thumbnail) { + return file.thumbnailUrl || file.webpublicUrl || file.url; + } else { + return file.webpublicUrl || file.thumbnailUrl || file.url; + } + } + + public async clacDriveUsageOf(user: User['id'] | User): Promise { + const id = typeof user === 'object' ? user.id : user; + + const { sum } = await this + .createQueryBuilder('file') + .where('file.userId = :id', { id: id }) + .select('SUM(file.size)', 'sum') + .getRawOne(); + + return parseInt(sum, 10) || 0; + } + + public async clacDriveUsageOfHost(host: string): Promise { + const { sum } = await this + .createQueryBuilder('file') + .where('file.userHost = :host', { host: host }) + .select('SUM(file.size)', 'sum') + .getRawOne(); + + return parseInt(sum, 10) || 0; + } + + public async clacDriveUsageOfLocal(): Promise { + const { sum } = await this + .createQueryBuilder('file') + .where('file.userHost IS NULL') + .select('SUM(file.size)', 'sum') + .getRawOne(); + + return parseInt(sum, 10) || 0; + } + + public async clacDriveUsageOfRemote(): Promise { + const { sum } = await this + .createQueryBuilder('file') + .where('file.userHost IS NOT NULL') + .select('SUM(file.size)', 'sum') + .getRawOne(); + + return parseInt(sum, 10) || 0; + } + + public packMany( + files: any[], + options?: { + detail?: boolean + self?: boolean, + withUser?: boolean, + } + ) { + return Promise.all(files.map(f => this.pack(f, options))); + } + + public async pack( + src: DriveFile['id'] | DriveFile, + options?: { + detail?: boolean, + self?: boolean, + withUser?: boolean, + } + ) { + const opts = Object.assign({ + detail: false, + self: false + }, options); + + const file = typeof src === 'object' ? src : await this.findOne(src); + + return await rap({ + id: file.id, + createdAt: file.createdAt, + name: file.name, + type: file.type, + md5: file.md5, + size: file.size, + isSensitive: file.isSensitive, + properties: file.properties, + url: opts.self ? file.url : this.getPublicUrl(file, false), + thumbnailUrl: this.getPublicUrl(file, true), + folderId: file.folderId, + folder: opts.detail && file.folderId ? DriveFolders.pack(file.folderId, { + detail: true + }) : null, + user: opts.withUser ? Users.pack(file.userId) : null + }); + } +} diff --git a/src/models/repositories/drive-folder.ts b/src/models/repositories/drive-folder.ts new file mode 100644 index 0000000000..faf0f353aa --- /dev/null +++ b/src/models/repositories/drive-folder.ts @@ -0,0 +1,49 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { DriveFolders, DriveFiles } from '..'; +import rap from '@prezzemolo/rap'; +import { DriveFolder } from '../entities/drive-folder'; + +@EntityRepository(DriveFolder) +export class DriveFolderRepository extends Repository { + public validateFolderName(name: string): boolean { + return ( + (name.trim().length > 0) && + (name.length <= 200) + ); + } + + public async pack( + src: DriveFolder['id'] | DriveFolder, + options?: { + detail: boolean + } + ): Promise> { + const opts = Object.assign({ + detail: false + }, options); + + const folder = typeof src === 'object' ? src : await this.findOne(src); + + return await rap({ + id: folder.id, + createdAt: folder.createdAt, + name: folder.name, + parentId: folder.parentId, + + ...(opts.detail ? { + foldersCount: DriveFolders.count({ + parentId: folder.id + }), + filesCount: DriveFiles.count({ + folderId: folder.id + }), + + ...(folder.parentId ? { + parent: this.pack(folder.parentId, { + detail: true + }) + } : {}) + } : {}) + }); + } +} diff --git a/src/models/repositories/follow-request.ts b/src/models/repositories/follow-request.ts new file mode 100644 index 0000000000..bead093b21 --- /dev/null +++ b/src/models/repositories/follow-request.ts @@ -0,0 +1,19 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { FollowRequest } from '../entities/follow-request'; +import { Users } from '..'; + +@EntityRepository(FollowRequest) +export class FollowRequestRepository extends Repository { + public async pack( + src: FollowRequest['id'] | FollowRequest, + me?: any + ) { + const request = typeof src === 'object' ? src : await this.findOne(src); + + return { + id: request.id, + follower: await Users.pack(request.followerId, me), + followee: await Users.pack(request.followeeId, me), + }; + } +} diff --git a/src/models/repositories/following.ts b/src/models/repositories/following.ts new file mode 100644 index 0000000000..02253d272d --- /dev/null +++ b/src/models/repositories/following.ts @@ -0,0 +1,44 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { Users } from '..'; +import rap from '@prezzemolo/rap'; +import { Following } from '../entities/following'; + +@EntityRepository(Following) +export class FollowingRepository extends Repository { + public packMany( + followings: any[], + me?: any, + opts?: { + populateFollowee?: boolean; + populateFollower?: boolean; + } + ) { + return Promise.all(followings.map(x => this.pack(x, me, opts))); + } + + public async pack( + src: Following['id'] | Following, + me?: any, + opts?: { + populateFollowee?: boolean; + populateFollower?: boolean; + } + ) { + const following = typeof src === 'object' ? src : await this.findOne(src); + + if (opts == null) opts = {}; + + return await rap({ + id: following.id, + createdAt: following.createdAt, + followeeId: following.followeeId, + followerId: following.followerId, + followee: opts.populateFollowee ? Users.pack(following.followee || following.followeeId, me, { + detail: true + }) : null, + follower: opts.populateFollower ? Users.pack(following.follower || following.followerId, me, { + detail: true + }) : null, + }); + } +} diff --git a/src/models/repositories/games/reversi/game.ts b/src/models/repositories/games/reversi/game.ts new file mode 100644 index 0000000000..f0cb6ff905 --- /dev/null +++ b/src/models/repositories/games/reversi/game.ts @@ -0,0 +1,49 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { Users } from '../../..'; +import { ReversiGame } from '../../../entities/games/reversi/game'; + +@EntityRepository(ReversiGame) +export class ReversiGameRepository extends Repository { + public async pack( + src: ReversiGame['id'] | ReversiGame, + me?: any, + options?: { + detail?: boolean + } + ) { + const opts = Object.assign({ + detail: true + }, options); + + const game = typeof src === 'object' ? src : await this.findOne(src); + const meId = me ? typeof me === 'string' ? me : me.id : null; + + return { + id: game.id, + createdAt: game.createdAt, + startedAt: game.startedAt, + isStarted: game.isStarted, + isEnded: game.isEnded, + form1: game.form1, + form2: game.form2, + user1Accepted: game.user1Accepted, + user2Accepted: game.user2Accepted, + user1Id: game.user1Id, + user2Id: game.user2Id, + user1: await Users.pack(game.user1Id, meId), + user2: await Users.pack(game.user2Id, meId), + winnerId: game.winnerId, + winner: game.winnerId ? await Users.pack(game.winnerId, meId) : null, + surrendered: game.surrendered, + black: game.black, + bw: game.bw, + isLlotheo: game.isLlotheo, + canPutEverywhere: game.canPutEverywhere, + loopedBoard: game.loopedBoard, + ...(opts.detail ? { + logs: game.logs, + map: game.map, + } : {}) + }; + } +} diff --git a/src/models/repositories/games/reversi/matching.ts b/src/models/repositories/games/reversi/matching.ts new file mode 100644 index 0000000000..3612ac5c47 --- /dev/null +++ b/src/models/repositories/games/reversi/matching.ts @@ -0,0 +1,27 @@ +import { EntityRepository, Repository } from 'typeorm'; +import rap from '@prezzemolo/rap'; +import { ReversiMatching } from '../../../entities/games/reversi/matching'; +import { Users } from '../../..'; + +@EntityRepository(ReversiMatching) +export class ReversiMatchingRepository extends Repository { + public async pack( + src: ReversiMatching['id'] | ReversiMatching, + me: any + ) { + const matching = typeof src === 'object' ? src : await this.findOne(src); + + return await rap({ + id: matching.id, + createdAt: matching.createdAt, + parentId: matching.parentId, + parent: Users.pack(matching.parentId, me, { + detail: true + }), + childId: matching.childId, + child: Users.pack(matching.childId, me, { + detail: true + }) + }); + } +} diff --git a/src/models/repositories/messaging-message.ts b/src/models/repositories/messaging-message.ts new file mode 100644 index 0000000000..b87b30388a --- /dev/null +++ b/src/models/repositories/messaging-message.ts @@ -0,0 +1,37 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { MessagingMessage } from '../entities/messaging-message'; +import { Users, DriveFiles } from '..'; + +@EntityRepository(MessagingMessage) +export class MessagingMessageRepository extends Repository { + public isValidText(text: string): boolean { + return text.trim().length <= 1000 && text.trim() != ''; + } + + public async pack( + src: MessagingMessage['id'] | MessagingMessage, + me?: any, + options?: { + populateRecipient: boolean + } + ) { + const opts = options || { + populateRecipient: true + }; + + const message = typeof src === 'object' ? src : await this.findOne(src); + + return { + id: message.id, + createdAt: message.createdAt, + text: message.text, + userId: message.userId, + user: await Users.pack(message.user || message.userId, me), + recipientId: message.recipientId, + recipient: opts.populateRecipient ? await Users.pack(message.recipient || message.recipientId, me) : null, + fileId: message.fileId, + file: message.fileId ? await DriveFiles.pack(message.fileId) : null, + isRead: message.isRead + }; + } +} diff --git a/src/models/repositories/muting.ts b/src/models/repositories/muting.ts new file mode 100644 index 0000000000..cd98cb4fec --- /dev/null +++ b/src/models/repositories/muting.ts @@ -0,0 +1,28 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { Users } from '..'; +import rap from '@prezzemolo/rap'; +import { Muting } from '../entities/muting'; + +@EntityRepository(Muting) +export class MutingRepository extends Repository { + public packMany( + mutings: any[], + me: any + ) { + return Promise.all(mutings.map(x => this.pack(x, me))); + } + + public async pack( + src: Muting['id'] | Muting, + me?: any + ) { + const muting = typeof src === 'object' ? src : await this.findOne(src); + + return await rap({ + id: muting.id, + mutee: Users.pack(muting.muteeId, me, { + detail: true + }) + }); + } +} diff --git a/src/models/repositories/note-favorite.ts b/src/models/repositories/note-favorite.ts new file mode 100644 index 0000000000..4526461e69 --- /dev/null +++ b/src/models/repositories/note-favorite.ts @@ -0,0 +1,25 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { NoteFavorite } from '../entities/note-favorite'; +import { Notes } from '..'; + +@EntityRepository(NoteFavorite) +export class NoteFavoriteRepository extends Repository { + public packMany( + favorites: any[], + me: any + ) { + return Promise.all(favorites.map(x => this.pack(x, me))); + } + + public async pack( + src: NoteFavorite['id'] | NoteFavorite, + me?: any + ) { + const favorite = typeof src === 'object' ? src : await this.findOne(src); + + return { + id: favorite.id, + note: await Notes.pack(favorite.note || favorite.noteId, me), + }; + } +} diff --git a/src/models/repositories/note-reaction.ts b/src/models/repositories/note-reaction.ts new file mode 100644 index 0000000000..7189da8e20 --- /dev/null +++ b/src/models/repositories/note-reaction.ts @@ -0,0 +1,18 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { NoteReaction } from '../entities/note-reaction'; +import { Users } from '..'; + +@EntityRepository(NoteReaction) +export class NoteReactionRepository extends Repository { + public async pack( + src: NoteReaction['id'] | NoteReaction, + me?: any + ) { + const reaction = typeof src === 'object' ? src : await this.findOne(src); + + return { + id: reaction.id, + user: await Users.pack(reaction.userId, me), + }; + } +} diff --git a/src/models/repositories/note.ts b/src/models/repositories/note.ts new file mode 100644 index 0000000000..4df0135115 --- /dev/null +++ b/src/models/repositories/note.ts @@ -0,0 +1,210 @@ +import { EntityRepository, Repository, In } from 'typeorm'; +import { Note } from '../entities/note'; +import { User } from '../entities/user'; +import { unique, concat } from '../../prelude/array'; +import { nyaize } from '../../misc/nyaize'; +import { Emojis, Users, Apps, PollVotes, DriveFiles, NoteReactions, Followings, Polls } from '..'; +import rap from '@prezzemolo/rap'; + +@EntityRepository(Note) +export class NoteRepository extends Repository { + public validateCw(x: string) { + return x.trim().length <= 100; + } + + private async hideNote(packedNote: any, meId: User['id']) { + let hide = false; + + // visibility が specified かつ自分が指定されていなかったら非表示 + if (packedNote.visibility == 'specified') { + if (meId == null) { + hide = true; + } else if (meId === packedNote.userId) { + hide = false; + } else { + // 指定されているかどうか + const specified = packedNote.visibleUserIds.some((id: any) => meId === id); + + if (specified) { + hide = false; + } else { + hide = true; + } + } + } + + // visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示 + if (packedNote.visibility == 'followers') { + if (meId == null) { + hide = true; + } else if (meId === packedNote.userId) { + hide = false; + } else if (packedNote.reply && (meId === packedNote.reply.userId)) { + // 自分の投稿に対するリプライ + hide = false; + } else if (packedNote.mentions && packedNote.mentions.some((id: any) => meId === id)) { + // 自分へのメンション + hide = false; + } else { + // フォロワーかどうか + const following = await Followings.findOne({ + followeeId: packedNote.userId, + followerId: meId + }); + + if (following == null) { + hide = true; + } else { + hide = false; + } + } + } + + if (hide) { + packedNote.visibleUserIds = null; + packedNote.fileIds = []; + packedNote.files = []; + packedNote.text = null; + packedNote.poll = null; + packedNote.cw = null; + packedNote.tags = []; + packedNote.geo = null; + packedNote.isHidden = true; + } + } + + public packMany( + notes: (Note['id'] | Note)[], + me?: User['id'] | User, + options?: { + detail?: boolean; + skipHide?: boolean; + } + ) { + return Promise.all(notes.map(n => this.pack(n, me, options))); + } + + public async pack( + src: Note['id'] | Note, + me?: User['id'] | User, + options?: { + detail?: boolean; + skipHide?: boolean; + } + ): Promise> { + const opts = Object.assign({ + detail: true, + skipHide: false + }, options); + + const meId = me ? typeof me === 'string' ? me : me.id : null; + const note = typeof src === 'object' ? src : await this.findOne(src); + const host = note.userHost; + + async function populatePoll() { + const poll = await Polls.findOne({ noteId: note.id }); + const choices = poll.choices.map(c => ({ + text: c, + votes: poll.votes[poll.choices.indexOf(c)], + isVoted: false + })); + + if (poll.multiple) { + const votes = await PollVotes.find({ + userId: meId, + noteId: note.id + }); + + const myChoices = votes.map(v => v.choice); + for (const myChoice of myChoices) { + choices[myChoice].isVoted = true; + } + } else { + const vote = await PollVotes.findOne({ + userId: meId, + noteId: note.id + }); + + if (vote) { + choices[vote.choice].isVoted = true; + } + } + + return { + multiple: poll.multiple, + expiresAt: poll.expiresAt, + choices + }; + } + + async function populateMyReaction() { + const reaction = await NoteReactions.findOne({ + userId: meId, + noteId: note.id, + }); + + if (reaction) { + return reaction.reaction; + } + + return null; + } + + let text = note.text; + + if (note.name) { + text = `【${note.name}】\n${note.text}`; + } + + const reactionEmojis = unique(concat([note.emojis, Object.keys(note.reactions)])); + + const packed = await rap({ + id: note.id, + createdAt: note.createdAt, + app: note.appId ? Apps.pack(note.appId) : null, + userId: note.userId, + user: Users.pack(note.user || note.userId, meId), + text: text, + cw: note.cw, + visibility: note.visibility, + visibleUserIds: note.visibleUserIds, + viaMobile: note.viaMobile, + reactions: note.reactions, + emojis: reactionEmojis.length > 0 ? Emojis.find({ + name: In(reactionEmojis), + host: host + }) : [], + tags: note.tags, + fileIds: note.fileIds, + files: DriveFiles.packMany(note.fileIds), + replyId: note.replyId, + renoteId: note.renoteId, + + ...(opts.detail ? { + reply: note.replyId ? this.pack(note.replyId, meId, { + detail: false + }) : null, + + renote: note.renoteId ? this.pack(note.renoteId, meId, { + detail: false + }) : null, + + poll: note.hasPoll ? populatePoll() : null, + + ...(meId ? { + myReaction: populateMyReaction() + } : {}) + } : {}) + }); + + if (packed.user.isCat && packed.text) { + packed.text = nyaize(packed.text); + } + + if (!opts.skipHide) { + await this.hideNote(packed, meId); + } + + return packed; + } +} diff --git a/src/models/repositories/notification.ts b/src/models/repositories/notification.ts new file mode 100644 index 0000000000..9bc569cd3f --- /dev/null +++ b/src/models/repositories/notification.ts @@ -0,0 +1,47 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { Users, Notes } from '..'; +import rap from '@prezzemolo/rap'; +import { Notification } from '../entities/notification'; + +@EntityRepository(Notification) +export class NotificationRepository extends Repository { + public packMany( + notifications: any[], + ) { + return Promise.all(notifications.map(x => this.pack(x))); + } + + public async pack( + src: Notification['id'] | Notification, + ) { + const notification = typeof src === 'object' ? src : await this.findOne(src); + + return await rap({ + id: notification.id, + createdAt: notification.createdAt, + type: notification.type, + userId: notification.notifierId, + user: Users.pack(notification.notifier || notification.notifierId), + ...(notification.type === 'mention' ? { + note: Notes.pack(notification.note || notification.noteId), + } : {}), + ...(notification.type === 'reply' ? { + note: Notes.pack(notification.note || notification.noteId), + } : {}), + ...(notification.type === 'renote' ? { + note: Notes.pack(notification.note || notification.noteId), + } : {}), + ...(notification.type === 'quote' ? { + note: Notes.pack(notification.note || notification.noteId), + } : {}), + ...(notification.type === 'reaction' ? { + note: Notes.pack(notification.note || notification.noteId), + reaction: notification.reaction + } : {}), + ...(notification.type === 'pollVote' ? { + note: Notes.pack(notification.note || notification.noteId), + choice: notification.choice + } : {}) + }); + } +} diff --git a/src/models/repositories/signin.ts b/src/models/repositories/signin.ts new file mode 100644 index 0000000000..f5b90c0e9e --- /dev/null +++ b/src/models/repositories/signin.ts @@ -0,0 +1,11 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { Signin } from '../entities/signin'; + +@EntityRepository(Signin) +export class SigninRepository extends Repository { + public async pack( + src: any, + ) { + return src; + } +} diff --git a/src/models/repositories/user-list.ts b/src/models/repositories/user-list.ts new file mode 100644 index 0000000000..921c18ca7a --- /dev/null +++ b/src/models/repositories/user-list.ts @@ -0,0 +1,16 @@ +import { EntityRepository, Repository } from 'typeorm'; +import { UserList } from '../entities/user-list'; + +@EntityRepository(UserList) +export class UserListRepository extends Repository { + public async pack( + src: any, + ) { + const userList = typeof src === 'object' ? src : await this.findOne(src); + + return { + id: userList.id, + name: userList.name + }; + } +} diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts new file mode 100644 index 0000000000..7c4cc545cf --- /dev/null +++ b/src/models/repositories/user.ts @@ -0,0 +1,198 @@ +import { EntityRepository, Repository, In } from 'typeorm'; +import { User, ILocalUser, IRemoteUser } from '../entities/user'; +import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings } from '..'; +import rap from '@prezzemolo/rap'; + +@EntityRepository(User) +export class UserRepository extends Repository { + public async getRelation(me: User['id'], target: User['id']) { + const [following1, following2, followReq1, followReq2, toBlocking, fromBlocked, mute] = await Promise.all([ + Followings.findOne({ + followerId: me, + followeeId: target + }), + Followings.findOne({ + followerId: target, + followeeId: me + }), + FollowRequests.findOne({ + followerId: me, + followeeId: target + }), + FollowRequests.findOne({ + followerId: target, + followeeId: me + }), + Blockings.findOne({ + blockerId: me, + blockeeId: target + }), + Blockings.findOne({ + blockerId: target, + blockeeId: me + }), + Mutings.findOne({ + muterId: me, + muteeId: target + }) + ]); + + return { + id: target, + isFollowing: following1 != null, + hasPendingFollowRequestFromYou: followReq1 != null, + hasPendingFollowRequestToYou: followReq2 != null, + isFollowed: following2 != null, + isBlocking: toBlocking != null, + isBlocked: fromBlocked != null, + isMuted: mute != null + }; + } + + public packMany( + users: (User['id'] | User)[], + me?: User['id'] | User, + options?: { + detail?: boolean, + includeSecrets?: boolean, + includeHasUnreadNotes?: boolean + } + ) { + return Promise.all(users.map(u => this.pack(u, me, options))); + } + + public async pack( + src: User['id'] | User, + me?: User['id'] | User, + options?: { + detail?: boolean, + includeSecrets?: boolean, + includeHasUnreadNotes?: boolean + } + ): Promise> { + const opts = Object.assign({ + detail: false, + includeSecrets: false + }, options); + + const user = typeof src === 'object' ? src : await this.findOne(src); + const meId = me ? typeof me === 'string' ? me : me.id : null; + + const relation = meId && (meId !== user.id) && opts.detail ? await this.getRelation(meId, user.id) : null; + const pins = opts.detail ? await UserNotePinings.find({ userId: user.id }) : []; + + return await rap({ + id: user.id, + name: user.name, + username: user.username, + host: user.host, + avatarUrl: user.avatarUrl, + bannerUrl: user.bannerUrl, + avatarColor: user.avatarColor, + bannerColor: user.bannerColor, + isAdmin: user.isAdmin, + + // カスタム絵文字添付 + emojis: user.emojis.length > 0 ? Emojis.find({ + where: { + name: In(user.emojis), + host: user.host + }, + select: ['name', 'host', 'url', 'aliases'] + }) : [], + + ...(opts.includeHasUnreadNotes ? { + hasUnreadSpecifiedNotes: NoteUnreads.count({ + where: { userId: user.id, isSpecified: true }, + take: 1 + }).then(count => count > 0), + hasUnreadMentions: NoteUnreads.count({ + where: { userId: user.id }, + take: 1 + }).then(count => count > 0), + } : {}), + + ...(opts.detail ? { + description: user.description, + location: user.location, + birthday: user.birthday, + followersCount: user.followersCount, + followingCount: user.followingCount, + notesCount: user.notesCount, + pinnedNoteIds: pins.map(pin => pin.noteId), + pinnedNotes: Notes.packMany(pins.map(pin => pin.noteId), meId, { + detail: true + }), + } : {}), + + ...(opts.detail && meId === user.id ? { + avatarId: user.avatarId, + bannerId: user.bannerId, + autoWatch: user.autoWatch, + alwaysMarkNsfw: user.alwaysMarkNsfw, + carefulBot: user.carefulBot, + hasUnreadMessagingMessage: MessagingMessages.count({ + where: { + recipientId: user.id, + isRead: false + }, + take: 1 + }).then(count => count > 0), + hasUnreadNotification: Notifications.count({ + where: { + userId: user.id, + isRead: false + }, + take: 1 + }).then(count => count > 0), + pendingReceivedFollowRequestsCount: FollowRequests.count({ + followeeId: user.id + }), + } : {}), + + ...(relation ? { + isFollowing: relation.isFollowing, + isFollowed: relation.isFollowed, + hasPendingFollowRequestFromYou: relation.hasPendingFollowRequestFromYou, + hasPendingFollowRequestToYou: relation.hasPendingFollowRequestToYou, + isBlocking: relation.isBlocking, + isBlocked: relation.isBlocked, + isMuted: relation.isMuted, + } : {}) + }); + } + + public isLocalUser(user: User): user is ILocalUser { + return user.host === null; + } + + public isRemoteUser(user: User): user is IRemoteUser { + return !this.isLocalUser(user); + } + + //#region Validators + public validateUsername(username: string, remote = false): boolean { + return typeof username == 'string' && (remote ? /^\w([\w-]*\w)?$/ : /^\w{1,20}$/).test(username); + } + + public validatePassword(password: string): boolean { + return typeof password == 'string' && password != ''; + } + + public isValidName(name?: string): boolean { + return name === null || (typeof name == 'string' && name.length < 50 && name.trim() != ''); + } + + public isValidDescription(description: string): boolean { + return typeof description == 'string' && description.length < 500 && description.trim() != ''; + } + + public isValidLocation(location: string): boolean { + return typeof location == 'string' && location.length < 50 && location.trim() != ''; + } + + public isValidBirthday(birthday: string): boolean { + return typeof birthday == 'string' && /^([0-9]{4})\-([0-9]{2})-([0-9]{2})$/.test(birthday); + } + //#endregion +} diff --git a/src/models/signin.ts b/src/models/signin.ts deleted file mode 100644 index d8b05c0e30..0000000000 --- a/src/models/signin.ts +++ /dev/null @@ -1,34 +0,0 @@ -import * as mongo from 'mongodb'; -import * as deepcopy from 'deepcopy'; -import db from '../db/mongodb'; - -const Signin = db.get('signin'); -export default Signin; - -export interface ISignin { - _id: mongo.ObjectID; - createdAt: Date; - userId: mongo.ObjectID; - ip: string; - headers: any; - success: boolean; -} - -/** - * Pack a signin record for API response - * - * @param {any} record - * @return {Promise} - */ -export const pack = ( - record: any -) => new Promise(async (resolve, reject) => { - - const _record = deepcopy(record); - - // Rename _id to id - _record.id = _record._id; - delete _record._id; - - resolve(_record); -}); diff --git a/src/models/sw-subscription.ts b/src/models/sw-subscription.ts deleted file mode 100644 index 743d0d2dd9..0000000000 --- a/src/models/sw-subscription.ts +++ /dev/null @@ -1,13 +0,0 @@ -import * as mongo from 'mongodb'; -import db from '../db/mongodb'; - -const SwSubscription = db.get('swSubscriptions'); -export default SwSubscription; - -export interface ISwSubscription { - _id: mongo.ObjectID; - userId: mongo.ObjectID; - endpoint: string; - auth: string; - publickey: string; -} diff --git a/src/models/user-list.ts b/src/models/user-list.ts deleted file mode 100644 index e7dd74bdd1..0000000000 --- a/src/models/user-list.ts +++ /dev/null @@ -1,41 +0,0 @@ -import * as mongo from 'mongodb'; -import * as deepcopy from 'deepcopy'; -import db from '../db/mongodb'; -import isObjectId from '../misc/is-objectid'; - -const UserList = db.get('userList'); -export default UserList; - -export interface IUserList { - _id: mongo.ObjectID; - createdAt: Date; - title: string; - userId: mongo.ObjectID; - userIds: mongo.ObjectID[]; -} - -export const pack = ( - userList: string | mongo.ObjectID | IUserList -) => new Promise(async (resolve, reject) => { - let _userList: any; - - if (isObjectId(userList)) { - _userList = await UserList.findOne({ - _id: userList - }); - } else if (typeof userList === 'string') { - _userList = await UserList.findOne({ - _id: new mongo.ObjectID(userList) - }); - } else { - _userList = deepcopy(userList); - } - - if (!_userList) throw `invalid userList arg ${userList}`; - - // Rename _id to id - _userList.id = _userList._id; - delete _userList._id; - - resolve(_userList); -}); diff --git a/src/models/user.ts b/src/models/user.ts deleted file mode 100644 index 0c3f7b5508..0000000000 --- a/src/models/user.ts +++ /dev/null @@ -1,438 +0,0 @@ -import * as mongo from 'mongodb'; -import * as deepcopy from 'deepcopy'; -import rap from '@prezzemolo/rap'; -import db from '../db/mongodb'; -import isObjectId from '../misc/is-objectid'; -import { packMany as packNoteMany } from './note'; -import Following from './following'; -import Blocking from './blocking'; -import Mute from './mute'; -import { getFriendIds } from '../server/api/common/get-friends'; -import config from '../config'; -import FollowRequest from './follow-request'; -import fetchMeta from '../misc/fetch-meta'; -import Emoji from './emoji'; -import { dbLogger } from '../db/logger'; - -const User = db.get('users'); - -User.createIndex('createdAt'); -User.createIndex('updatedAt'); -User.createIndex('followersCount'); -User.createIndex('tags'); -User.createIndex('isSuspended'); -User.createIndex('username'); -User.createIndex('usernameLower'); -User.createIndex('host'); -User.createIndex(['username', 'host'], { unique: true }); -User.createIndex(['usernameLower', 'host'], { unique: true }); -User.createIndex('token', { sparse: true, unique: true }); -User.createIndex('uri', { sparse: true, unique: true }); - -export default User; - -type IUserBase = { - _id: mongo.ObjectID; - createdAt: Date; - updatedAt?: Date; - deletedAt?: Date; - followersCount: number; - followingCount: number; - name?: string; - notesCount: number; - username: string; - usernameLower: string; - avatarId: mongo.ObjectID; - bannerId: mongo.ObjectID; - avatarUrl?: string; - bannerUrl?: string; - avatarColor?: any; - bannerColor?: any; - wallpaperId: mongo.ObjectID; - wallpaperUrl?: string; - data: any; - description: string; - lang?: string; - pinnedNoteIds: mongo.ObjectID[]; - emojis?: string[]; - tags?: string[]; - - isDeleted: boolean; - - /** - * 凍結されているか否か - */ - isSuspended: boolean; - - /** - * サイレンスされているか否か - */ - isSilenced: boolean; - - /** - * 鍵アカウントか否か - */ - isLocked: boolean; - - /** - * Botか否か - */ - isBot: boolean; - - /** - * Botからのフォローを承認制にするか - */ - carefulBot: boolean; - - /** - * フォローしているユーザーからのフォローリクエストを自動承認するか - */ - autoAcceptFollowed: boolean; - - /** - * このアカウントに届いているフォローリクエストの数 - */ - pendingReceivedFollowRequestsCount: number; - - host: string; -}; - -export interface ILocalUser extends IUserBase { - host: null; - keypair: string; - email: string; - emailVerified?: boolean; - emailVerifyCode?: string; - password: string; - token: string; - twitter: { - accessToken: string; - accessTokenSecret: string; - userId: string; - screenName: string; - }; - github: { - accessToken: string; - id: string; - login: string; - }; - discord: { - accessToken: string; - refreshToken: string; - expiresDate: number; - id: string; - username: string; - discriminator: string; - }; - profile: { - location: string; - birthday: string; // 'YYYY-MM-DD' - tags: string[]; - }; - fields?: { - name: string; - value: string; - }[]; - isCat: boolean; - isAdmin?: boolean; - isModerator?: boolean; - isVerified?: boolean; - twoFactorSecret: string; - twoFactorEnabled: boolean; - twoFactorTempSecret?: string; - clientSettings: any; - settings: { - autoWatch: boolean; - alwaysMarkNsfw?: boolean; - }; - hasUnreadNotification: boolean; - hasUnreadMessagingMessage: boolean; -} - -export interface IRemoteUser extends IUserBase { - inbox: string; - sharedInbox?: string; - featured?: string; - endpoints: string[]; - uri: string; - url?: string; - publicKey: { - id: string; - publicKeyPem: string; - }; - lastFetchedAt: Date; - isAdmin: false; - isModerator: false; -} - -export type IUser = ILocalUser | IRemoteUser; - -export const isLocalUser = (user: any): user is ILocalUser => - user.host === null; - -export const isRemoteUser = (user: any): user is IRemoteUser => - !isLocalUser(user); - -//#region Validators -export function validateUsername(username: string, remote = false): boolean { - return typeof username == 'string' && (remote ? /^\w([\w-]*\w)?$/ : /^\w{1,20}$/).test(username); -} - -export function validatePassword(password: string): boolean { - return typeof password == 'string' && password != ''; -} - -export function isValidName(name?: string): boolean { - return name === null || (typeof name == 'string' && name.length < 50 && name.trim() != ''); -} - -export function isValidDescription(description: string): boolean { - return typeof description == 'string' && description.length < 500 && description.trim() != ''; -} - -export function isValidLocation(location: string): boolean { - return typeof location == 'string' && location.length < 50 && location.trim() != ''; -} - -export function isValidBirthday(birthday: string): boolean { - return typeof birthday == 'string' && /^([0-9]{4})\-([0-9]{2})-([0-9]{2})$/.test(birthday); -} -//#endregion - -export async function getRelation(me: mongo.ObjectId, target: mongo.ObjectId) { - const [following1, following2, followReq1, followReq2, toBlocking, fromBlocked, mute] = await Promise.all([ - Following.findOne({ - followerId: me, - followeeId: target - }), - Following.findOne({ - followerId: target, - followeeId: me - }), - FollowRequest.findOne({ - followerId: me, - followeeId: target - }), - FollowRequest.findOne({ - followerId: target, - followeeId: me - }), - Blocking.findOne({ - blockerId: me, - blockeeId: target - }), - Blocking.findOne({ - blockerId: target, - blockeeId: me - }), - Mute.findOne({ - muterId: me, - muteeId: target - }) - ]); - - return { - id: target, - isFollowing: following1 !== null, - hasPendingFollowRequestFromYou: followReq1 !== null, - hasPendingFollowRequestToYou: followReq2 !== null, - isFollowed: following2 !== null, - isBlocking: toBlocking !== null, - isBlocked: fromBlocked !== null, - isMuted: mute !== null - }; -} - -/** - * Pack a user for API response - * - * @param user target - * @param me? serializee - * @param options? serialize options - * @return Packed user - */ -export const pack = ( - user: string | mongo.ObjectID | IUser, - me?: string | mongo.ObjectID | IUser, - options?: { - detail?: boolean, - includeSecrets?: boolean, - includeHasUnreadNotes?: boolean - } -) => new Promise(async (resolve, reject) => { - const opts = Object.assign({ - detail: false, - includeSecrets: false - }, options); - - let _user: any; - - const fields = opts.detail ? {} : { - name: true, - username: true, - host: true, - avatarColor: true, - avatarUrl: true, - emojis: true, - isCat: true, - isBot: true, - isAdmin: true, - isVerified: true - }; - - // Populate the user if 'user' is ID - if (isObjectId(user)) { - _user = await User.findOne({ - _id: user - }, { fields }); - } else if (typeof user === 'string') { - _user = await User.findOne({ - _id: new mongo.ObjectID(user) - }, { fields }); - } else { - _user = deepcopy(user); - } - - // (データベースの欠損などで)ユーザーがデータベース上に見つからなかったとき - if (_user == null) { - dbLogger.warn(`user not found on database: ${user}`); - return resolve(null); - } - - // Me - const meId: mongo.ObjectID = me - ? isObjectId(me) - ? me as mongo.ObjectID - : typeof me === 'string' - ? new mongo.ObjectID(me) - : (me as IUser)._id - : null; - - // Rename _id to id - _user.id = _user._id; - delete _user._id; - - delete _user.usernameLower; - delete _user.emailVerifyCode; - - if (_user.host == null) { - // Remove private properties - delete _user.keypair; - delete _user.password; - delete _user.token; - delete _user.twoFactorTempSecret; - delete _user.two_factor_temp_secret; // 後方互換性のため - delete _user.twoFactorSecret; - if (_user.twitter) { - delete _user.twitter.accessToken; - delete _user.twitter.accessTokenSecret; - } - if (_user.github) { - delete _user.github.accessToken; - } - if (_user.discord) { - delete _user.discord.accessToken; - delete _user.discord.refreshToken; - delete _user.discord.expiresDate; - } - - // Visible via only the official client - if (!opts.includeSecrets) { - delete _user.email; - delete _user.emailVerified; - delete _user.settings; - delete _user.clientSettings; - } - - if (!opts.detail) { - delete _user.twoFactorEnabled; - } - } else { - delete _user.publicKey; - } - - if (_user.avatarUrl == null) { - _user.avatarUrl = `${config.driveUrl}/default-avatar.jpg`; - } - - if (!meId || !meId.equals(_user.id) || !opts.detail) { - delete _user.avatarId; - delete _user.bannerId; - delete _user.hasUnreadMessagingMessage; - delete _user.hasUnreadNotification; - } - - if (meId && !meId.equals(_user.id) && opts.detail) { - const relation = await getRelation(meId, _user.id); - - _user.isFollowing = relation.isFollowing; - _user.isFollowed = relation.isFollowed; - _user.hasPendingFollowRequestFromYou = relation.hasPendingFollowRequestFromYou; - _user.hasPendingFollowRequestToYou = relation.hasPendingFollowRequestToYou; - _user.isBlocking = relation.isBlocking; - _user.isBlocked = relation.isBlocked; - _user.isMuted = relation.isMuted; - } - - if (opts.detail) { - if (_user.pinnedNoteIds) { - // Populate pinned notes - _user.pinnedNotes = packNoteMany(_user.pinnedNoteIds, meId, { - detail: true - }); - } - - if (meId && !meId.equals(_user.id)) { - const myFollowingIds = await getFriendIds(meId); - - // Get following you know count - _user.followingYouKnowCount = Following.count({ - followeeId: { $in: myFollowingIds }, - followerId: _user.id - }); - - // Get followers you know count - _user.followersYouKnowCount = Following.count({ - followeeId: _user.id, - followerId: { $in: myFollowingIds } - }); - } - } - - if (!opts.includeHasUnreadNotes) { - delete _user.hasUnreadSpecifiedNotes; - delete _user.hasUnreadMentions; - } - - // カスタム絵文字添付 - if (_user.emojis) { - _user.emojis = Emoji.find({ - name: { $in: _user.emojis }, - host: _user.host - }, { - fields: { _id: false } - }); - } - - // resolve promises in _user object - _user = await rap(_user); - - resolve(_user); -}); - -/* -function img(url) { - return { - thumbnail: { - large: `${url}`, - medium: '', - small: '' - } - }; -} -*/ - -export async function fetchProxyAccount(): Promise { - const meta = await fetchMeta(); - return await User.findOne({ username: meta.proxyAccount, host: null }) as ILocalUser; -} -- cgit v1.2.3-freya From dc82203e9b0f88e5da386d17e13e7e2b46db49cf Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 8 Apr 2019 00:24:01 +0900 Subject: Fix bug --- src/models/repositories/user.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/models') diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts index 7c4cc545cf..acdf6c8f18 100644 --- a/src/models/repositories/user.ts +++ b/src/models/repositories/user.ts @@ -140,7 +140,7 @@ export class UserRepository extends Repository { }).then(count => count > 0), hasUnreadNotification: Notifications.count({ where: { - userId: user.id, + notifieeId: user.id, isRead: false }, take: 1 -- cgit v1.2.3-freya From 142d59be85cd06b11d9df1375bcd0a84c0425e22 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 8 Apr 2019 03:35:02 +0900 Subject: Fix bug --- src/models/entities/user-keypair.ts | 7 ++++++- src/remote/activitypub/renderer/key.ts | 3 +-- src/remote/activitypub/request.ts | 2 +- src/server/api/private/signup.ts | 35 ++++++++++++++++++++-------------- 4 files changed, 29 insertions(+), 18 deletions(-) (limited to 'src/models') diff --git a/src/models/entities/user-keypair.ts b/src/models/entities/user-keypair.ts index 06b98d2536..be264641f7 100644 --- a/src/models/entities/user-keypair.ts +++ b/src/models/entities/user-keypair.ts @@ -20,5 +20,10 @@ export class UserKeypair { @Column('varchar', { length: 4096, }) - public keyPem: string; + public publicKey: string; + + @Column('varchar', { + length: 4096, + }) + public privateKey: string; } diff --git a/src/remote/activitypub/renderer/key.ts b/src/remote/activitypub/renderer/key.ts index fb5975a6c4..334e5e00cd 100644 --- a/src/remote/activitypub/renderer/key.ts +++ b/src/remote/activitypub/renderer/key.ts @@ -1,4 +1,3 @@ -import { createPublicKey } from 'crypto'; import config from '../../../config'; import { ILocalUser } from '../../../models/entities/user'; import { UserKeypair } from '../../../models/entities/user-keypair'; @@ -7,5 +6,5 @@ export default (user: ILocalUser, key: UserKeypair) => ({ id: `${config.url}/users/${user.id}/publickey`, type: 'Key', owner: `${config.url}/users/${user.id}`, - publicKeyPem: createPublicKey(key.keyPem) + publicKeyPem: key.publicKey }); diff --git a/src/remote/activitypub/request.ts b/src/remote/activitypub/request.ts index a089ed371c..c50d05e2a6 100644 --- a/src/remote/activitypub/request.ts +++ b/src/remote/activitypub/request.ts @@ -67,7 +67,7 @@ export default async (user: ILocalUser, url: string, object: any) => { sign(req, { authorizationHeaderName: 'Signature', - key: keypair.keyPem, + key: keypair.privateKey, keyId: `${config.url}/users/${user.id}/publickey`, headers: ['date', 'host', 'digest'] }); diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts index 1d304b8e11..8ab702bd8a 100644 --- a/src/server/api/private/signup.ts +++ b/src/server/api/private/signup.ts @@ -10,6 +10,7 @@ import { genId } from '../../../misc/gen-id'; import { usersChart } from '../../../services/chart'; import { UserServiceLinking } from '../../../models/entities/user-service-linking'; import { User } from '../../../models/entities/user'; +import { UserKeypair } from '../../../models/entities/user-keypair'; export default async (ctx: Koa.BaseContext) => { const body = ctx.request.body as any; @@ -80,6 +81,23 @@ export default async (ctx: Koa.BaseContext) => { return; } + const keyPair = await new Promise((s, j) => + generateKeyPair('rsa', { + modulusLength: 4096, + publicKeyEncoding: { + type: 'pkcs1', + format: 'pem' + }, + privateKeyEncoding: { + type: 'pkcs1', + format: 'pem', + cipher: undefined, + passphrase: undefined + } + }, (e, publicKey, privateKey) => + e ? j(e) : s([publicKey, privateKey]) + )); + const account = await Users.save({ id: genId(), createdAt: new Date(), @@ -95,21 +113,10 @@ export default async (ctx: Koa.BaseContext) => { await UserKeypairs.save({ id: genId(), - keyPem: await new Promise((s, j) => generateKeyPair('rsa', { - modulusLength: 4096, - publicKeyEncoding: { - type: 'pkcs1', - format: 'pem' - }, - privateKeyEncoding: { - type: 'pkcs1', - format: 'pem', - cipher: undefined, - passphrase: undefined - } - }, (e, _, x) => e ? j(e) : s(x))), + publicKey: keyPair[0], + privateKey: keyPair[1], userId: account.id - }); + } as UserKeypair); await UserServiceLinkings.save({ id: genId(), -- cgit v1.2.3-freya From 802153ff9b16919a1c6fcd0295d9ca03dfa3ce4d Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 8 Apr 2019 16:49:05 +0900 Subject: Fix bug --- src/models/repositories/note.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/models') diff --git a/src/models/repositories/note.ts b/src/models/repositories/note.ts index 4df0135115..ac1502479e 100644 --- a/src/models/repositories/note.ts +++ b/src/models/repositories/note.ts @@ -169,6 +169,8 @@ export class NoteRepository extends Repository { visibility: note.visibility, visibleUserIds: note.visibleUserIds, viaMobile: note.viaMobile, + renoteCount: note.renoteCount, + repliesCount: note.repliesCount, reactions: note.reactions, emojis: reactionEmojis.length > 0 ? Emojis.find({ name: In(reactionEmojis), -- cgit v1.2.3-freya From 0b99293d4c7dd7e8d320efa9267e20e208df605e Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 8 Apr 2019 17:04:37 +0900 Subject: Fix bug --- src/models/repositories/note.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/models') diff --git a/src/models/repositories/note.ts b/src/models/repositories/note.ts index ac1502479e..56a08d090a 100644 --- a/src/models/repositories/note.ts +++ b/src/models/repositories/note.ts @@ -188,7 +188,7 @@ export class NoteRepository extends Repository { }) : null, renote: note.renoteId ? this.pack(note.renoteId, meId, { - detail: false + detail: true }) : null, poll: note.hasPoll ? populatePoll() : null, -- cgit v1.2.3-freya From 735687be21b08a11bf167663b9336a56eeb05313 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 8 Apr 2019 20:29:52 +0900 Subject: update token generation --- src/models/entities/user.ts | 4 ++-- src/server/api/common/generate-native-user-token.ts | 2 +- src/server/api/common/is-native-token.ts | 2 +- src/server/api/endpoints/auth/accept.ts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src/models') diff --git a/src/models/entities/user.ts b/src/models/entities/user.ts index 1ef98cadc2..0a2878c0c9 100644 --- a/src/models/entities/user.ts +++ b/src/models/entities/user.ts @@ -255,8 +255,8 @@ export class User { public password: string | null; @Index({ unique: true }) - @Column('varchar', { - length: 32, nullable: true, unique: true, + @Column('char', { + length: 16, nullable: true, unique: true, comment: 'The native access token of the User. It will be null if the origin of the user is local.' }) public token: string | null; diff --git a/src/server/api/common/generate-native-user-token.ts b/src/server/api/common/generate-native-user-token.ts index 92f8a3a0e8..9d44885630 100644 --- a/src/server/api/common/generate-native-user-token.ts +++ b/src/server/api/common/generate-native-user-token.ts @@ -1,3 +1,3 @@ import rndstr from 'rndstr'; -export default () => `!${rndstr('a-zA-Z0-9', 31)}`; +export default () => `0${rndstr('a-zA-Z0-9', 15)}`; diff --git a/src/server/api/common/is-native-token.ts b/src/server/api/common/is-native-token.ts index 6afbc99ab5..22af84aad2 100644 --- a/src/server/api/common/is-native-token.ts +++ b/src/server/api/common/is-native-token.ts @@ -1 +1 @@ -export default (token: string) => token.startsWith('!'); +export default (token: string) => token.startsWith('0'); diff --git a/src/server/api/endpoints/auth/accept.ts b/src/server/api/endpoints/auth/accept.ts index 21a78011dc..0baa6b111c 100644 --- a/src/server/api/endpoints/auth/accept.ts +++ b/src/server/api/endpoints/auth/accept.ts @@ -38,7 +38,7 @@ export default define(meta, async (ps, user) => { } // Generate access token - const accessToken = rndstr('a-zA-Z0-9', 32); + const accessToken = '1' + rndstr('a-zA-Z0-9', 15); // Fetch exist access token const exist = await AccessTokens.findOne({ -- cgit v1.2.3-freya From ee7ef89dfbd9d5e3f1e5a0f3b1a7d50bf92b5f26 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 9 Apr 2019 01:31:48 +0900 Subject: Fix bug --- src/models/entities/drive-file.ts | 4 ++-- src/services/drive/delete-file.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/models') diff --git a/src/models/entities/drive-file.ts b/src/models/entities/drive-file.ts index a8f8c69e56..21db4bd713 100644 --- a/src/models/entities/drive-file.ts +++ b/src/models/entities/drive-file.ts @@ -95,9 +95,9 @@ export class DriveFile { @Index({ unique: true }) @Column('varchar', { - length: 256, + length: 256, nullable: true, }) - public accessKey: string; + public accessKey: string | null; @Index({ unique: true }) @Column('varchar', { diff --git a/src/services/drive/delete-file.ts b/src/services/drive/delete-file.ts index adf57416fe..9c23b39bfc 100644 --- a/src/services/drive/delete-file.ts +++ b/src/services/drive/delete-file.ts @@ -16,7 +16,7 @@ export default async function(file: DriveFile, isExpired = false) { if (file.webpublicUrl) { InternalStorage.del(file.webpublicAccessKey); } - } else { + } else if (!file.isRemote) { const minio = new Minio.Client(config.drive.config); await minio.removeObject(config.drive.bucket, file.accessKey); -- cgit v1.2.3-freya From c07eaef2d150555f2865de21d259c336d2c25443 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 9 Apr 2019 12:25:19 +0900 Subject: Update note.ts --- src/models/entities/note.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/models') diff --git a/src/models/entities/note.ts b/src/models/entities/note.ts index 0bcb9b4a44..4c481964b6 100644 --- a/src/models/entities/note.ts +++ b/src/models/entities/note.ts @@ -183,7 +183,7 @@ export class Note { public hasPoll: boolean; @Column('jsonb', { - nullable: true, default: {} + nullable: true, default: null }) public geo: any | null; -- cgit v1.2.3-freya From e38335077e14a6c8d87bd3eed413d6fcf7f87ca8 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 9 Apr 2019 12:36:01 +0900 Subject: Update user.ts --- src/models/repositories/user.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'src/models') diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts index acdf6c8f18..589b2e8ce3 100644 --- a/src/models/repositories/user.ts +++ b/src/models/repositories/user.ts @@ -86,6 +86,7 @@ export class UserRepository extends Repository { name: user.name, username: user.username, host: user.host, + uri: user.uri, avatarUrl: user.avatarUrl, bannerUrl: user.bannerUrl, avatarColor: user.avatarColor, -- cgit v1.2.3-freya From c651921c79fad4f74b1cf8d53785971104f97dcb Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 9 Apr 2019 18:36:43 +0900 Subject: Fix bug --- src/models/repositories/note.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'src/models') diff --git a/src/models/repositories/note.ts b/src/models/repositories/note.ts index 56a08d090a..80514c3752 100644 --- a/src/models/repositories/note.ts +++ b/src/models/repositories/note.ts @@ -167,6 +167,7 @@ export class NoteRepository extends Repository { text: text, cw: note.cw, visibility: note.visibility, + localOnly: note.localOnly, visibleUserIds: note.visibleUserIds, viaMobile: note.viaMobile, renoteCount: note.renoteCount, -- cgit v1.2.3-freya From 1016f94bbbfc04633302e97010a1234053bd3a59 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 9 Apr 2019 20:47:31 +0900 Subject: Fix bug --- src/models/repositories/user.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/models') diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts index 589b2e8ce3..f1a51c1c5f 100644 --- a/src/models/repositories/user.ts +++ b/src/models/repositories/user.ts @@ -92,6 +92,8 @@ export class UserRepository extends Repository { avatarColor: user.avatarColor, bannerColor: user.bannerColor, isAdmin: user.isAdmin, + isBot: user.isBot, + isCat: user.isCat, // カスタム絵文字添付 emojis: user.emojis.length > 0 ? Emojis.find({ -- cgit v1.2.3-freya From ea06665c51c86f8cdde4ed1f7114e2963d59c767 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 9 Apr 2019 23:07:08 +0900 Subject: isRemote --> isLink --- src/models/entities/drive-file.ts | 2 +- src/remote/activitypub/models/image.ts | 2 +- src/services/drive/add-file.ts | 2 +- src/services/drive/delete-file.ts | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src/models') diff --git a/src/models/entities/drive-file.ts b/src/models/entities/drive-file.ts index 21db4bd713..130af39ede 100644 --- a/src/models/entities/drive-file.ts +++ b/src/models/entities/drive-file.ts @@ -150,5 +150,5 @@ export class DriveFile { default: false, comment: 'Whether the DriveFile is direct link to remote server.' }) - public isRemote: boolean; + public isLink: boolean; } diff --git a/src/remote/activitypub/models/image.ts b/src/remote/activitypub/models/image.ts index 87095acd88..c9991dba3b 100644 --- a/src/remote/activitypub/models/image.ts +++ b/src/remote/activitypub/models/image.ts @@ -40,7 +40,7 @@ export async function createImage(actor: IRemoteUser, value: any): Promise Date: Tue, 9 Apr 2019 23:59:32 +0900 Subject: ドメインは常にPunycodeで保存するように MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/config/load.ts | 7 ++++--- src/migrate.ts | 15 ++++++++------- src/misc/acct/render.ts | 2 +- src/misc/convert-host.ts | 15 +++++---------- src/models/repositories/user.ts | 2 +- src/queue/processors/db/import-following.ts | 4 ++-- src/queue/processors/db/import-user-lists.ts | 4 ++-- src/remote/activitypub/models/person.ts | 3 ++- src/server/api/private/signup.ts | 3 ++- src/server/api/stream/channels/hybrid-timeline.ts | 2 +- src/services/register-or-fetch-instance-doc.ts | 3 +++ 11 files changed, 31 insertions(+), 29 deletions(-) (limited to 'src/models') diff --git a/src/config/load.ts b/src/config/load.ts index 50ae47d9e2..4d174d0e27 100644 --- a/src/config/load.ts +++ b/src/config/load.ts @@ -7,6 +7,7 @@ import { URL } from 'url'; import * as yaml from 'js-yaml'; import { Source, Mixin } from './types'; import * as pkg from '../../package.json'; +import { toPuny } from '../misc/convert-host'; /** * Path of configuration directory @@ -27,12 +28,12 @@ export default function load() { const url = validateUrl(config.url); - config.url = normalizeUrl(config.url); + config.url = toPuny(normalizeUrl(config.url)); config.port = config.port || parseInt(process.env.PORT, 10); - mixin.host = url.host; - mixin.hostname = url.hostname; + mixin.host = toPuny(url.host); + mixin.hostname = toPuny(url.hostname); mixin.scheme = url.protocol.replace(/:$/, ''); mixin.wsScheme = mixin.scheme.replace('http', 'ws'); mixin.wsUrl = `${mixin.wsScheme}://${mixin.host}`; diff --git a/src/migrate.ts b/src/migrate.ts index 46df8429db..b9d6eb3396 100644 --- a/src/migrate.ts +++ b/src/migrate.ts @@ -25,6 +25,7 @@ import { UserPublickey } from './models/entities/user-publickey'; import { UserKeypair } from './models/entities/user-keypair'; import { extractPublic } from './crypto_key'; import { Emoji } from './models/entities/emoji'; +import { toPuny } from './misc/convert-host'; 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; @@ -87,7 +88,7 @@ async function main() { createdAt: user.createdAt || new Date(), username: user.username, usernameLower: user.username.toLowerCase(), - host: user.host, + host: toPuny(user.host), token: generateUserToken(), password: user.password, isAdmin: user.isAdmin, @@ -133,10 +134,10 @@ async function main() { followeeId: following.followeeId.toHexString(), // 非正規化 - followerHost: following._follower ? following._follower.host : null, + followerHost: following._follower ? toPuny(following._follower.host) : null, followerInbox: following._follower ? following._follower.inbox : null, followerSharedInbox: following._follower ? following._follower.sharedInbox : null, - followeeHost: following._followee ? following._followee.host : null, + followeeHost: following._followee ? toPuny(following._followee.host) : null, followeeInbox: following._followee ? following._followee.inbox : null, followeeSharedInbox: following._followee ? following._followee.sharedInbo : null }); @@ -159,7 +160,7 @@ async function main() { await DriveFiles.save({ id: file._id.toHexString(), userId: user._id.toHexString(), - userHost: user.host, + userHost: toPuny(user.host), createdAt: file.uploadDate || new Date(), md5: file.md5, name: file.filename, @@ -191,7 +192,7 @@ async function main() { await DriveFiles.save({ id: file._id.toHexString(), userId: user._id.toHexString(), - userHost: user.host, + userHost: toPuny(user.host), createdAt: file.uploadDate || new Date(), md5: file.md5, name: file.filename, @@ -210,7 +211,7 @@ async function main() { await DriveFiles.save({ id: file._id.toHexString(), userId: user._id.toHexString(), - userHost: user.host, + userHost: toPuny(user.host), createdAt: file.uploadDate || new Date(), md5: file.md5, name: file.filename, @@ -313,7 +314,7 @@ async function main() { aliases: emoji.aliases, url: emoji.url, uri: emoji.uri, - host: emoji.host, + host: toPuny(emoji.host), name: emoji.name }); } diff --git a/src/misc/acct/render.ts b/src/misc/acct/render.ts index 67e063fcb3..094eceffe9 100644 --- a/src/misc/acct/render.ts +++ b/src/misc/acct/render.ts @@ -1,5 +1,5 @@ import Acct from './type'; export default (user: Acct) => { - return user.host === null ? user.username : `${user.username}@${user.host}`; + return user.host == null ? user.username : `${user.username}@${user.host}`; }; diff --git a/src/misc/convert-host.ts b/src/misc/convert-host.ts index 8f2f1c7aba..f7feebd55c 100644 --- a/src/misc/convert-host.ts +++ b/src/misc/convert-host.ts @@ -1,27 +1,22 @@ import config from '../config'; -import { toUnicode, toASCII } from 'punycode'; +import { toASCII } from 'punycode'; import { URL } from 'url'; export function getFullApAccount(username: string, host: string) { - return host ? `${username}@${toApHost(host)}` : `${username}@${toApHost(config.host)}`; + return host ? `${username}@${toPuny(host)}` : `${username}@${toPuny(config.host)}`; } export function isSelfHost(host: string) { if (host == null) return true; - return toApHost(config.host) === toApHost(host); + return toPuny(config.host) === toPuny(host); } export function extractDbHost(uri: string) { const url = new URL(uri); - return toDbHost(url.hostname); + return toPuny(url.hostname); } -export function toDbHost(host: string) { - if (host == null) return null; - return toUnicode(host.toLowerCase()); -} - -export function toApHost(host: string) { +export function toPuny(host: string) { if (host == null) return null; return toASCII(host.toLowerCase()); } diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts index f1a51c1c5f..3939e3142a 100644 --- a/src/models/repositories/user.ts +++ b/src/models/repositories/user.ts @@ -166,7 +166,7 @@ export class UserRepository extends Repository { } public isLocalUser(user: User): user is ILocalUser { - return user.host === null; + return user.host == null; } public isRemoteUser(user: User): user is IRemoteUser { diff --git a/src/queue/processors/db/import-following.ts b/src/queue/processors/db/import-following.ts index 07a1639494..aae24b22d6 100644 --- a/src/queue/processors/db/import-following.ts +++ b/src/queue/processors/db/import-following.ts @@ -5,7 +5,7 @@ import follow from '../../../services/following/create'; import parseAcct from '../../../misc/acct/parse'; import { resolveUser } from '../../../remote/resolve-user'; import { downloadTextFile } from '../../../misc/download-text-file'; -import { isSelfHost, toDbHost } from '../../../misc/convert-host'; +import { isSelfHost, toPuny } from '../../../misc/convert-host'; import { Users, DriveFiles } from '../../../models'; const logger = queueLogger.createSubLogger('import-following'); @@ -35,7 +35,7 @@ export async function importFollowing(job: Bull.Job, done: any): Promise { host: null, usernameLower: username.toLowerCase() }) : await Users.findOne({ - host: toDbHost(host), + host: toPuny(host), usernameLower: username.toLowerCase() }); diff --git a/src/queue/processors/db/import-user-lists.ts b/src/queue/processors/db/import-user-lists.ts index ff896c1bef..c7273ea6b4 100644 --- a/src/queue/processors/db/import-user-lists.ts +++ b/src/queue/processors/db/import-user-lists.ts @@ -5,7 +5,7 @@ import parseAcct from '../../../misc/acct/parse'; import { resolveUser } from '../../../remote/resolve-user'; import { pushUserToUserList } from '../../../services/user-list/push'; import { downloadTextFile } from '../../../misc/download-text-file'; -import { isSelfHost, toDbHost } from '../../../misc/convert-host'; +import { isSelfHost, toPuny } from '../../../misc/convert-host'; import { DriveFiles, Users, UserLists, UserListJoinings } from '../../../models'; import { genId } from '../../../misc/gen-id'; @@ -47,7 +47,7 @@ export async function importUserLists(job: Bull.Job, done: any): Promise { host: null, usernameLower: username.toLowerCase() }) : await Users.findOne({ - host: toDbHost(host), + host: toPuny(host), usernameLower: username.toLowerCase() }); diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts index fbc329e860..2362455e02 100644 --- a/src/remote/activitypub/models/person.ts +++ b/src/remote/activitypub/models/person.ts @@ -24,6 +24,7 @@ import { UserServiceLinking } from '../../../models/entities/user-service-linkin import { instanceChart, usersChart } from '../../../services/chart'; import { UserPublickey } from '../../../models/entities/user-publickey'; import { isDuplicateKeyValueError } from '../../../misc/is-duplicate-key-value-error'; +import { toPuny } from '../../../misc/convert-host'; const logger = apLogger; /** @@ -124,7 +125,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise { const body = ctx.request.body as any; @@ -103,7 +104,7 @@ export default async (ctx: Koa.BaseContext) => { createdAt: new Date(), username: username, usernameLower: username.toLowerCase(), - host: host, + host: toPuny(host), token: secret, password: hash, isAdmin: config.autoAdmin && usersCount === 0, diff --git a/src/server/api/stream/channels/hybrid-timeline.ts b/src/server/api/stream/channels/hybrid-timeline.ts index 3f09dd8398..30643aeda8 100644 --- a/src/server/api/stream/channels/hybrid-timeline.ts +++ b/src/server/api/stream/channels/hybrid-timeline.ts @@ -24,7 +24,7 @@ export default class extends Channel { if (!( this.user.id === note.userId || this.following.includes(note.userId) || - note.user.host === null + note.user.host == null )) return; if (['followers', 'specified'].includes(note.visibility)) { diff --git a/src/services/register-or-fetch-instance-doc.ts b/src/services/register-or-fetch-instance-doc.ts index c96c8a1e32..459f538e96 100644 --- a/src/services/register-or-fetch-instance-doc.ts +++ b/src/services/register-or-fetch-instance-doc.ts @@ -2,10 +2,13 @@ import { Instance } from '../models/entities/instance'; import { Instances } from '../models'; import { federationChart } from './chart'; import { genId } from '../misc/gen-id'; +import { toPuny } from '../misc/convert-host'; export async function registerOrFetchInstanceDoc(host: string): Promise { if (host == null) return null; + host = toPuny(host); + const index = await Instances.findOne({ host }); if (index == null) { -- cgit v1.2.3-freya From 236d72685dfec013135fc1450b890e33ec377de1 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 10 Apr 2019 00:59:41 +0900 Subject: More puny --- src/models/repositories/drive-file.ts | 3 ++- src/queue/processors/inbox.ts | 26 +++++++++++++--------- src/remote/activitypub/models/note.ts | 7 +++--- src/remote/activitypub/models/person.ts | 7 +++--- src/remote/activitypub/request.ts | 4 ++-- src/remote/resolve-user.ts | 19 ++++++---------- src/server/api/common/get-host-lower.ts | 6 ----- src/server/api/endpoints/admin/emoji/list.ts | 3 ++- .../endpoints/admin/federation/update-instance.ts | 5 +++-- .../api/endpoints/federation/show-instance.ts | 3 ++- src/server/api/endpoints/users/followers.ts | 3 ++- src/server/api/endpoints/users/following.ts | 3 ++- src/server/api/index.ts | 5 +---- src/services/chart/charts/classes/instance.ts | 13 ++++++----- 14 files changed, 52 insertions(+), 55 deletions(-) delete mode 100644 src/server/api/common/get-host-lower.ts (limited to 'src/models') diff --git a/src/models/repositories/drive-file.ts b/src/models/repositories/drive-file.ts index fe0ca72bfb..817677fa3b 100644 --- a/src/models/repositories/drive-file.ts +++ b/src/models/repositories/drive-file.ts @@ -3,6 +3,7 @@ import { DriveFile } from '../entities/drive-file'; import { Users, DriveFolders } from '..'; import rap from '@prezzemolo/rap'; import { User } from '../entities/user'; +import { toPuny } from '../../misc/convert-host'; @EntityRepository(DriveFile) export class DriveFileRepository extends Repository { @@ -39,7 +40,7 @@ export class DriveFileRepository extends Repository { public async clacDriveUsageOfHost(host: string): Promise { const { sum } = await this .createQueryBuilder('file') - .where('file.userHost = :host', { host: host }) + .where('file.userHost = :host', { host: toPuny(host) }) .select('SUM(file.size)', 'sum') .getRawOne(); diff --git a/src/queue/processors/inbox.ts b/src/queue/processors/inbox.ts index 16badabcf7..481bcbb1c4 100644 --- a/src/queue/processors/inbox.ts +++ b/src/queue/processors/inbox.ts @@ -4,7 +4,6 @@ import parseAcct from '../../misc/acct/parse'; import { IRemoteUser } from '../../models/entities/user'; import perform from '../../remote/activitypub/perform'; import { resolvePerson, updatePerson } from '../../remote/activitypub/models/person'; -import { toUnicode } from 'punycode'; import { URL } from 'url'; import { publishApLogStream } from '../../services/stream'; import Logger from '../../services/logger'; @@ -13,6 +12,7 @@ import { Instances, Users, UserPublickeys } from '../../models'; import { instanceChart } from '../../services/chart'; import { UserPublickey } from '../../models/entities/user-publickey'; import fetchMeta from '../../misc/fetch-meta'; +import { toPuny } from '../../misc/convert-host'; const logger = new Logger('inbox'); @@ -33,7 +33,10 @@ export default async (job: Bull.Job): Promise => { let key: UserPublickey; if (keyIdLower.startsWith('acct:')) { - const { username, host } = parseAcct(keyIdLower.slice('acct:'.length)); + const acct = parseAcct(keyIdLower.slice('acct:'.length)); + const host = toPuny(acct.host); + const username = toPuny(acct.username); + if (host === null) { logger.warn(`request was made by local user: @${username}`); return; @@ -50,19 +53,22 @@ export default async (job: Bull.Job): Promise => { // ブロックしてたら中断 // TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく const meta = await fetchMeta(); - if (meta.blockedHosts.includes(host.toLowerCase())) { + if (meta.blockedHosts.includes(host)) { logger.info(`Blocked request: ${host}`); return; } - user = await Users.findOne({ usernameLower: username, host: host.toLowerCase() }) as IRemoteUser; + user = await Users.findOne({ + usernameLower: username.toLowerCase(), + host: host + }) as IRemoteUser; key = await UserPublickeys.findOne({ userId: user.id }); } else { // アクティビティ内のホストの検証 - const host = toUnicode(new URL(signature.keyId).hostname.toLowerCase()); + const host = toPuny(new URL(signature.keyId).hostname); try { ValidateActivity(activity, host); } catch (e) { @@ -73,7 +79,7 @@ export default async (job: Bull.Job): Promise => { // ブロックしてたら中断 // TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく const meta = await fetchMeta(); - if (meta.blockedHosts.includes(host.toLowerCase())) { + if (meta.blockedHosts.includes(host)) { logger.info(`Blocked request: ${host}`); return; } @@ -145,7 +151,7 @@ export default async (job: Bull.Job): Promise => { function ValidateActivity(activity: any, host: string) { // id (if exists) if (typeof activity.id === 'string') { - const uriHost = toUnicode(new URL(activity.id).hostname.toLowerCase()); + const uriHost = toPuny(new URL(activity.id).hostname); if (host !== uriHost) { const diag = activity.signature ? '. Has LD-Signature. Forwarded?' : ''; throw new Error(`activity.id(${activity.id}) has different host(${host})${diag}`); @@ -154,7 +160,7 @@ function ValidateActivity(activity: any, host: string) { // actor (if exists) if (typeof activity.actor === 'string') { - const uriHost = toUnicode(new URL(activity.actor).hostname.toLowerCase()); + const uriHost = toPuny(new URL(activity.actor).hostname); if (host !== uriHost) throw new Error('activity.actor has different host'); } @@ -162,13 +168,13 @@ function ValidateActivity(activity: any, host: string) { if (activity.type === 'Create' && activity.object) { // object.id (if exists) if (typeof activity.object.id === 'string') { - const uriHost = toUnicode(new URL(activity.object.id).hostname.toLowerCase()); + const uriHost = toPuny(new URL(activity.object.id).hostname); if (host !== uriHost) throw new Error('activity.object.id has different host'); } // object.attributedTo (if exists) if (typeof activity.object.attributedTo === 'string') { - const uriHost = toUnicode(new URL(activity.object.attributedTo).hostname.toLowerCase()); + const uriHost = toPuny(new URL(activity.object.attributedTo).hostname); if (host !== uriHost) throw new Error('activity.object.attributedTo has different host'); } } diff --git a/src/remote/activitypub/models/note.ts b/src/remote/activitypub/models/note.ts index cd587c51cf..07991d4eb2 100644 --- a/src/remote/activitypub/models/note.ts +++ b/src/remote/activitypub/models/note.ts @@ -8,14 +8,13 @@ import { resolveImage } from './image'; import { IRemoteUser, User } from '../../../models/entities/user'; import { fromHtml } from '../../../mfm/fromHtml'; import { ITag, extractHashtags } from './tag'; -import { toUnicode } from 'punycode'; import { unique, concat, difference } from '../../../prelude/array'; import { extractPollFromQuestion } from './question'; import vote from '../../../services/note/polls/vote'; import { apLogger } from '../logger'; import { DriveFile } from '../../../models/entities/drive-file'; import { deliverQuestionUpdate } from '../../../services/note/polls/update'; -import { extractDbHost } from '../../../misc/convert-host'; +import { extractDbHost, toPuny } from '../../../misc/convert-host'; import { Notes, Emojis, Polls } from '../../../models'; import { Note } from '../../../models/entities/note'; import { IObject, INote } from '../type'; @@ -246,8 +245,8 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver): return await createNote(uri, resolver); } -export async function extractEmojis(tags: ITag[], host_: string) { - const host = toUnicode(host_.toLowerCase()); +export async function extractEmojis(tags: ITag[], host: string) { + host = toPuny(host); if (!tags) return []; diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts index 2362455e02..715bae40e8 100644 --- a/src/remote/activitypub/models/person.ts +++ b/src/remote/activitypub/models/person.ts @@ -1,5 +1,4 @@ import * as promiseLimit from 'promise-limit'; -import { toUnicode } from 'punycode'; import config from '../../../config'; import Resolver from '../resolver'; @@ -33,7 +32,7 @@ const logger = apLogger; * @param uri Fetch target URI */ function validatePerson(x: any, uri: string) { - const expectHost = toUnicode(new URL(uri).hostname.toLowerCase()); + const expectHost = toPuny(new URL(uri).hostname); if (x == null) { return new Error('invalid person: object is null'); @@ -63,7 +62,7 @@ function validatePerson(x: any, uri: string) { return new Error('invalid person: id is not a string'); } - const idHost = toUnicode(new URL(x.id).hostname.toLowerCase()); + const idHost = toPuny(new URL(x.id).hostname); if (idHost !== expectHost) { return new Error('invalid person: id has different host'); } @@ -72,7 +71,7 @@ function validatePerson(x: any, uri: string) { return new Error('invalid person: publicKey.id is not a string'); } - const publicKeyIdHost = toUnicode(new URL(x.publicKey.id).hostname.toLowerCase()); + const publicKeyIdHost = toPuny(new URL(x.publicKey.id).hostname); if (publicKeyIdHost !== expectHost) { return new Error('invalid person: publicKey.id has different host'); } diff --git a/src/remote/activitypub/request.ts b/src/remote/activitypub/request.ts index c50d05e2a6..8aca5e8102 100644 --- a/src/remote/activitypub/request.ts +++ b/src/remote/activitypub/request.ts @@ -4,7 +4,6 @@ import { URL } from 'url'; import * as crypto from 'crypto'; import { lookup, IRunOptions } from 'lookup-dns-cache'; import * as promiseAny from 'promise-any'; -import { toUnicode } from 'punycode'; import config from '../../config'; import { ILocalUser } from '../../models/entities/user'; @@ -12,6 +11,7 @@ import { publishApLogStream } from '../../services/stream'; import { apLogger } from './logger'; import { UserKeypairs } from '../../models'; import fetchMeta from '../../misc/fetch-meta'; +import { toPuny } from '../../misc/convert-host'; export const logger = apLogger.createSubLogger('deliver'); @@ -25,7 +25,7 @@ export default async (user: ILocalUser, url: string, object: any) => { // ブロックしてたら中断 // TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく const meta = await fetchMeta(); - if (meta.blockedHosts.includes(toUnicode(host))) return; + if (meta.blockedHosts.includes(toPuny(host))) return; const data = JSON.stringify(object); diff --git a/src/remote/resolve-user.ts b/src/remote/resolve-user.ts index a10d3c2d84..e6a11bc0da 100644 --- a/src/remote/resolve-user.ts +++ b/src/remote/resolve-user.ts @@ -1,4 +1,3 @@ -import { toUnicode, toASCII } from 'punycode'; import webFinger from './webfinger'; import config from '../config'; import { createPerson, updatePerson } from './activitypub/models/person'; @@ -7,31 +6,27 @@ import { remoteLogger } from './logger'; import chalk from 'chalk'; import { User, IRemoteUser } from '../models/entities/user'; import { Users } from '../models'; +import { toPuny } from '../misc/convert-host'; const logger = remoteLogger.createSubLogger('resolve-user'); -export async function resolveUser(username: string, _host: string, option?: any, resync = false): Promise { +export async function resolveUser(username: string, host: string, option?: any, resync = false): Promise { const usernameLower = username.toLowerCase(); + host = toPuny(host); - if (_host == null) { + if (host == null) { logger.info(`return local user: ${usernameLower}`); return await Users.findOne({ usernameLower, host: null }); } - const configHostAscii = toASCII(config.host).toLowerCase(); - const configHost = toUnicode(configHostAscii); - - const hostAscii = toASCII(_host).toLowerCase(); - const host = toUnicode(hostAscii); - - if (configHost == host) { + if (config.host == host) { logger.info(`return local user: ${usernameLower}`); return await Users.findOne({ usernameLower, host: null }); } const user = await Users.findOne({ usernameLower, host }, option); - const acctLower = `${usernameLower}@${hostAscii}`; + const acctLower = `${usernameLower}@${host}`; if (user == null) { const self = await resolveSelf(acctLower); @@ -51,7 +46,7 @@ export async function resolveUser(username: string, _host: string, option?: any, // validate uri const uri = new URL(self.href); - if (uri.hostname !== hostAscii) { + if (uri.hostname !== host) { throw new Error(`Invalied uri`); } diff --git a/src/server/api/common/get-host-lower.ts b/src/server/api/common/get-host-lower.ts deleted file mode 100644 index 26ddf6c6d0..0000000000 --- a/src/server/api/common/get-host-lower.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { toUnicode } from 'punycode'; - -export default (host: string) => { - if (host == null) return null; - return toUnicode(host).toLowerCase(); -}; diff --git a/src/server/api/endpoints/admin/emoji/list.ts b/src/server/api/endpoints/admin/emoji/list.ts index 07174723b9..26385d4e23 100644 --- a/src/server/api/endpoints/admin/emoji/list.ts +++ b/src/server/api/endpoints/admin/emoji/list.ts @@ -1,6 +1,7 @@ import $ from 'cafy'; import define from '../../../define'; import { Emojis } from '../../../../../models'; +import { toPuny } from '../../../../../misc/convert-host'; export const meta = { desc: { @@ -22,7 +23,7 @@ export const meta = { export default define(meta, async (ps) => { const emojis = await Emojis.find({ - host: ps.host + host: toPuny(ps.host) }); return emojis.map(e => ({ diff --git a/src/server/api/endpoints/admin/federation/update-instance.ts b/src/server/api/endpoints/admin/federation/update-instance.ts index d1abe95a5b..90ab7a3ec5 100644 --- a/src/server/api/endpoints/admin/federation/update-instance.ts +++ b/src/server/api/endpoints/admin/federation/update-instance.ts @@ -1,6 +1,7 @@ import $ from 'cafy'; import define from '../../../define'; import { Instances } from '../../../../../models'; +import { toPuny } from '../../../../../misc/convert-host'; export const meta = { tags: ['admin'], @@ -20,13 +21,13 @@ export const meta = { }; export default define(meta, async (ps, me) => { - const instance = await Instances.findOne({ host: ps.host }); + const instance = await Instances.findOne({ host: toPuny(ps.host) }); if (instance == null) { throw new Error('instance not found'); } - Instances.update({ host: ps.host }, { + Instances.update({ host: toPuny(ps.host) }, { isMarkedAsClosed: ps.isClosed }); }); diff --git a/src/server/api/endpoints/federation/show-instance.ts b/src/server/api/endpoints/federation/show-instance.ts index 875afa05b2..9afcf28a7c 100644 --- a/src/server/api/endpoints/federation/show-instance.ts +++ b/src/server/api/endpoints/federation/show-instance.ts @@ -1,6 +1,7 @@ import $ from 'cafy'; import define from '../../define'; import { Instances } from '../../../../models'; +import { toPuny } from '../../../../misc/convert-host'; export const meta = { tags: ['federation'], @@ -16,7 +17,7 @@ export const meta = { export default define(meta, async (ps, me) => { const instance = await Instances - .findOne({ host: ps.host }); + .findOne({ host: toPuny(ps.host) }); return instance; }); diff --git a/src/server/api/endpoints/users/followers.ts b/src/server/api/endpoints/users/followers.ts index 51b007ddaa..64d63e2d03 100644 --- a/src/server/api/endpoints/users/followers.ts +++ b/src/server/api/endpoints/users/followers.ts @@ -4,6 +4,7 @@ import define from '../../define'; import { ApiError } from '../../error'; import { Users, Followings } from '../../../../models'; import { makePaginationQuery } from '../../common/make-pagination-query'; +import { toPuny } from '../../../../misc/convert-host'; export const meta = { desc: { @@ -65,7 +66,7 @@ export const meta = { export default define(meta, async (ps, me) => { const user = await Users.findOne(ps.userId != null ? { id: ps.userId } - : { usernameLower: ps.username.toLowerCase(), host: ps.host }); + : { usernameLower: ps.username.toLowerCase(), host: toPuny(ps.host) }); if (user == null) { throw new ApiError(meta.errors.noSuchUser); diff --git a/src/server/api/endpoints/users/following.ts b/src/server/api/endpoints/users/following.ts index 46550f0f77..0e28001680 100644 --- a/src/server/api/endpoints/users/following.ts +++ b/src/server/api/endpoints/users/following.ts @@ -4,6 +4,7 @@ import define from '../../define'; import { ApiError } from '../../error'; import { Users, Followings } from '../../../../models'; import { makePaginationQuery } from '../../common/make-pagination-query'; +import { toPuny } from '../../../../misc/convert-host'; export const meta = { desc: { @@ -65,7 +66,7 @@ export const meta = { export default define(meta, async (ps, me) => { const user = await Users.findOne(ps.userId != null ? { id: ps.userId } - : { usernameLower: ps.username.toLowerCase(), host: ps.host }); + : { usernameLower: ps.username.toLowerCase(), host: toPuny(ps.host) }); if (user == null) { throw new ApiError(meta.errors.noSuchUser); diff --git a/src/server/api/index.ts b/src/server/api/index.ts index 7858efd927..8c2b97775f 100644 --- a/src/server/api/index.ts +++ b/src/server/api/index.ts @@ -15,7 +15,6 @@ import signin from './private/signin'; import discord from './service/discord'; import github from './service/github'; import twitter from './service/twitter'; -import { toASCII } from 'punycode'; import { Instances } from '../../models'; // Init app @@ -71,9 +70,7 @@ router.get('/v1/instance/peers', async ctx => { select: ['host'] }); - const punyCodes = instances.map(instance => toASCII(instance.host)); - - ctx.body = punyCodes; + ctx.body = instances.map(instance => instance.host); }); // Return 404 for unknown API diff --git a/src/services/chart/charts/classes/instance.ts b/src/services/chart/charts/classes/instance.ts index 55db534573..f3d341f383 100644 --- a/src/services/chart/charts/classes/instance.ts +++ b/src/services/chart/charts/classes/instance.ts @@ -5,6 +5,7 @@ import { DriveFiles, Followings, Users, Notes } from '../../../../models'; import { DriveFile } from '../../../../models/entities/drive-file'; import { name, schema } from '../schemas/instance'; import { Note } from '../../../../models/entities/note'; +import { toPuny } from '../../../../misc/convert-host'; type InstanceLog = SchemaType; @@ -79,7 +80,7 @@ export default class InstanceChart extends Chart { requests: { received: 1 } - }, host); + }, toPuny(host)); } @autobind @@ -94,7 +95,7 @@ export default class InstanceChart extends Chart { await this.inc({ requests: update - }, host); + }, toPuny(host)); } @autobind @@ -104,7 +105,7 @@ export default class InstanceChart extends Chart { total: 1, inc: 1 } - }, host); + }, toPuny(host)); } @autobind @@ -126,7 +127,7 @@ export default class InstanceChart extends Chart { dec: isAdditional ? 0 : 1, diffs: diffs } - }, host); + }, toPuny(host)); } @autobind @@ -137,7 +138,7 @@ export default class InstanceChart extends Chart { inc: isAdditional ? 1 : 0, dec: isAdditional ? 0 : 1, } - }, host); + }, toPuny(host)); } @autobind @@ -148,7 +149,7 @@ export default class InstanceChart extends Chart { inc: isAdditional ? 1 : 0, dec: isAdditional ? 0 : 1, } - }, host); + }, toPuny(host)); } @autobind -- cgit v1.2.3-freya From 626cfb61ac3940bee7a3acf1b1c5c4cae4ae410c Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 10 Apr 2019 15:04:27 +0900 Subject: テーブル分割 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/db/postgre.ts | 4 +- src/migrate.ts | 20 ++- src/models/entities/user-keypair.ts | 5 +- src/models/entities/user-profile.ts | 196 +++++++++++++++++++++ src/models/entities/user-publickey.ts | 5 +- src/models/entities/user-service-linking.ts | 108 ------------ src/models/entities/user.ts | 81 --------- src/models/index.ts | 4 +- src/models/repositories/user.ts | 15 +- src/remote/activitypub/models/person.ts | 23 ++- src/remote/activitypub/renderer/person.ts | 26 +-- src/server/api/endpoints/admin/reset-password.ts | 6 +- src/server/api/endpoints/i/2fa/done.ts | 16 +- src/server/api/endpoints/i/2fa/register.ts | 8 +- src/server/api/endpoints/i/2fa/unregister.ts | 10 +- src/server/api/endpoints/i/change-password.ts | 8 +- src/server/api/endpoints/i/delete-account.ts | 6 +- src/server/api/endpoints/i/regenerate-token.ts | 6 +- .../api/endpoints/i/update-client-setting.ts | 4 +- src/server/api/endpoints/i/update-email.ts | 10 +- src/server/api/endpoints/i/update.ts | 28 +-- src/server/api/endpoints/notes/polls/vote.ts | 6 +- src/server/api/private/signin.ts | 10 +- src/server/api/private/signup.ts | 18 +- src/server/api/service/discord.ts | 14 +- src/server/api/service/github.ts | 8 +- src/server/api/service/twitter.ts | 8 +- src/server/index.ts | 8 +- src/server/web/feed.ts | 6 +- src/services/drive/add-file.ts | 6 +- src/services/following/create.ts | 8 +- src/services/note/create.ts | 8 +- src/services/note/polls/vote.ts | 6 +- src/services/note/reaction/create.ts | 6 +- 34 files changed, 368 insertions(+), 333 deletions(-) create mode 100644 src/models/entities/user-profile.ts delete mode 100644 src/models/entities/user-service-linking.ts (limited to 'src/models') diff --git a/src/db/postgre.ts b/src/db/postgre.ts index bc5ee4ce8c..e5726e9c87 100644 --- a/src/db/postgre.ts +++ b/src/db/postgre.ts @@ -36,10 +36,10 @@ import { Emoji } from '../models/entities/emoji'; import { ReversiGame } from '../models/entities/games/reversi/game'; import { ReversiMatching } from '../models/entities/games/reversi/matching'; import { UserNotePining } from '../models/entities/user-note-pinings'; -import { UserServiceLinking } from '../models/entities/user-service-linking'; import { Poll } from '../models/entities/poll'; import { UserKeypair } from '../models/entities/user-keypair'; import { UserPublickey } from '../models/entities/user-publickey'; +import { UserProfile } from '../models/entities/user-profile'; const sqlLogger = dbLogger.createSubLogger('sql', 'white', false); @@ -101,12 +101,12 @@ export function initDb(justBorrow = false, sync = false, log = false) { AuthSession, AccessToken, User, + UserProfile, UserKeypair, UserPublickey, UserList, UserListJoining, UserNotePining, - UserServiceLinking, Following, FollowRequest, Muting, diff --git a/src/migrate.ts b/src/migrate.ts index b9d6eb3396..f833ec180b 100644 --- a/src/migrate.ts +++ b/src/migrate.ts @@ -26,6 +26,7 @@ import { UserKeypair } from './models/entities/user-keypair'; import { extractPublic } from './crypto_key'; import { Emoji } from './models/entities/emoji'; import { toPuny } from './misc/convert-host'; +import { UserProfile } from './models/entities/user-profile'; 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; @@ -70,6 +71,7 @@ const getDriveFileBucket = async (): Promise => { async function main() { await initDb(); const Users = getRepository(User); + const UserProfiles = getRepository(UserProfile); const DriveFiles = getRepository(DriveFile); const DriveFolders = getRepository(DriveFolder); const Notes = getRepository(Note); @@ -90,17 +92,11 @@ async function main() { usernameLower: user.username.toLowerCase(), host: toPuny(user.host), token: generateUserToken(), - password: user.password, isAdmin: user.isAdmin, - autoAcceptFollowed: true, - autoWatch: false, name: user.name, - location: user.profile ? user.profile.location : null, - birthday: user.profile ? user.profile.birthday : null, followersCount: user.followersCount, followingCount: user.followingCount, notesCount: user.notesCount, - description: user.description, isBot: user.isBot, isCat: user.isCat, isVerified: user.isVerified, @@ -108,9 +104,18 @@ async function main() { sharedInbox: user.sharedInbox, uri: user.uri, }); + await UserProfiles.save({ + userId: user._id.toHexString(), + description: user.description, + userHost: toPuny(user.host), + autoAcceptFollowed: true, + autoWatch: false, + password: user.password, + location: user.profile ? user.profile.location : null, + birthday: user.profile ? user.profile.birthday : null, + }); if (user.publicKey) { await UserPublickeys.save({ - id: genId(), userId: user._id.toHexString(), keyId: user.publicKey.id, keyPem: user.publicKey.publicKeyPem @@ -118,7 +123,6 @@ async function main() { } if (user.keypair) { await UserKeypairs.save({ - id: genId(), userId: user._id.toHexString(), publicKey: extractPublic(user.keypair), privateKey: user.keypair, diff --git a/src/models/entities/user-keypair.ts b/src/models/entities/user-keypair.ts index be264641f7..9181abf8cb 100644 --- a/src/models/entities/user-keypair.ts +++ b/src/models/entities/user-keypair.ts @@ -4,11 +4,8 @@ import { id } from '../id'; @Entity() export class UserKeypair { - @PrimaryColumn(id()) - public id: string; - @Index({ unique: true }) - @Column(id()) + @PrimaryColumn(id()) public userId: User['id']; @OneToOne(type => User, { diff --git a/src/models/entities/user-profile.ts b/src/models/entities/user-profile.ts new file mode 100644 index 0000000000..24b92231f5 --- /dev/null +++ b/src/models/entities/user-profile.ts @@ -0,0 +1,196 @@ +import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm'; +import { id } from '../id'; +import { User } from './user'; + +@Entity() +export class UserProfile { + @Index({ unique: true }) + @PrimaryColumn(id()) + public userId: User['id']; + + @OneToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user: User | null; + + @Column('varchar', { + length: 128, nullable: true, + comment: 'The location of the User.' + }) + public location: string | null; + + @Column('char', { + length: 10, nullable: true, + comment: 'The birthday (YYYY-MM-DD) of the User.' + }) + public birthday: string | null; + + @Column('varchar', { + length: 1024, nullable: true, + comment: 'The description (bio) of the User.' + }) + public description: string | null; + + @Column('jsonb', { + default: [], + }) + public fields: { + name: string; + value: string; + }[]; + + @Column('varchar', { + length: 128, nullable: true, + comment: 'The email address of the User.' + }) + public email: string | null; + + @Column('varchar', { + length: 128, nullable: true, + }) + public emailVerifyCode: string | null; + + @Column('boolean', { + default: false, + }) + public emailVerified: boolean; + + @Column('varchar', { + length: 128, nullable: true, + }) + public twoFactorTempSecret: string | null; + + @Column('varchar', { + length: 128, nullable: true, + }) + public twoFactorSecret: string | null; + + @Column('boolean', { + default: false, + }) + public twoFactorEnabled: boolean; + + @Column('varchar', { + length: 128, nullable: true, + comment: 'The password hash of the User. It will be null if the origin of the user is local.' + }) + public password: string | null; + + @Column('jsonb', { + default: {}, + comment: 'The client-specific data of the User.' + }) + public clientData: Record; + + @Column('boolean', { + default: false, + }) + public autoWatch: boolean; + + @Column('boolean', { + default: false, + }) + public autoAcceptFollowed: boolean; + + @Column('boolean', { + default: false, + }) + public alwaysMarkNsfw: boolean; + + @Column('boolean', { + default: false, + }) + public carefulBot: boolean; + + //#region Linking + @Column('boolean', { + default: false, + }) + public twitter: boolean; + + @Column('varchar', { + length: 64, nullable: true, default: null, + }) + public twitterAccessToken: string | null; + + @Column('varchar', { + length: 64, nullable: true, default: null, + }) + public twitterAccessTokenSecret: string | null; + + @Column('varchar', { + length: 64, nullable: true, default: null, + }) + public twitterUserId: string | null; + + @Column('varchar', { + length: 64, nullable: true, default: null, + }) + public twitterScreenName: string | null; + + @Column('boolean', { + default: false, + }) + public github: boolean; + + @Column('varchar', { + length: 64, nullable: true, default: null, + }) + public githubAccessToken: string | null; + + @Column('integer', { + nullable: true, default: null, + }) + public githubId: number | null; + + @Column('varchar', { + length: 64, nullable: true, default: null, + }) + public githubLogin: string | null; + + @Column('boolean', { + default: false, + }) + public discord: boolean; + + @Column('varchar', { + length: 64, nullable: true, default: null, + }) + public discordAccessToken: string | null; + + @Column('varchar', { + length: 64, nullable: true, default: null, + }) + public discordRefreshToken: string | null; + + @Column('integer', { + nullable: true, default: null, + }) + public discordExpiresDate: number | null; + + @Column('varchar', { + length: 64, nullable: true, default: null, + }) + public discordId: string | null; + + @Column('varchar', { + length: 64, nullable: true, default: null, + }) + public discordUsername: string | null; + + @Column('varchar', { + length: 64, nullable: true, default: null, + }) + public discordDiscriminator: string | null; + //#endregion + + //#region Denormalized fields + @Index() + @Column('varchar', { + length: 128, nullable: true, + comment: '[Denormalized]' + }) + public userHost: string | null; + //#endregion +} diff --git a/src/models/entities/user-publickey.ts b/src/models/entities/user-publickey.ts index 6c019f3313..81c42404fa 100644 --- a/src/models/entities/user-publickey.ts +++ b/src/models/entities/user-publickey.ts @@ -4,11 +4,8 @@ import { id } from '../id'; @Entity() export class UserPublickey { - @PrimaryColumn(id()) - public id: string; - @Index({ unique: true }) - @Column(id()) + @PrimaryColumn(id()) public userId: User['id']; @OneToOne(type => User, { diff --git a/src/models/entities/user-service-linking.ts b/src/models/entities/user-service-linking.ts deleted file mode 100644 index 3d99554e1e..0000000000 --- a/src/models/entities/user-service-linking.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, OneToOne } from 'typeorm'; -import { User } from './user'; -import { id } from '../id'; - -@Entity() -export class UserServiceLinking { - @PrimaryColumn(id()) - public id: string; - - @Index({ unique: true }) - @Column(id()) - public userId: User['id']; - - @OneToOne(type => User, { - onDelete: 'CASCADE' - }) - @JoinColumn() - public user: User | null; - - @Column('boolean', { - default: false, - }) - public twitter: boolean; - - @Column('varchar', { - length: 64, nullable: true, default: null, - }) - public twitterAccessToken: string | null; - - @Column('varchar', { - length: 64, nullable: true, default: null, - }) - public twitterAccessTokenSecret: string | null; - - @Column('varchar', { - length: 64, nullable: true, default: null, - }) - public twitterUserId: string | null; - - @Column('varchar', { - length: 64, nullable: true, default: null, - }) - public twitterScreenName: string | null; - - @Column('boolean', { - default: false, - }) - public github: boolean; - - @Column('varchar', { - length: 64, nullable: true, default: null, - }) - public githubAccessToken: string | null; - - @Column('integer', { - nullable: true, default: null, - }) - public githubId: number | null; - - @Column('varchar', { - length: 64, nullable: true, default: null, - }) - public githubLogin: string | null; - - @Column('boolean', { - default: false, - }) - public discord: boolean; - - @Column('varchar', { - length: 64, nullable: true, default: null, - }) - public discordAccessToken: string | null; - - @Column('varchar', { - length: 64, nullable: true, default: null, - }) - public discordRefreshToken: string | null; - - @Column('integer', { - nullable: true, default: null, - }) - public discordExpiresDate: number | null; - - @Column('varchar', { - length: 64, nullable: true, default: null, - }) - public discordId: string | null; - - @Column('varchar', { - length: 64, nullable: true, default: null, - }) - public discordUsername: string | null; - - @Column('varchar', { - length: 64, nullable: true, default: null, - }) - public discordDiscriminator: string | null; - - //#region Denormalized fields - @Index() - @Column('varchar', { - length: 128, nullable: true, - comment: '[Denormalized]' - }) - public userHost: string | null; - //#endregion -} diff --git a/src/models/entities/user.ts b/src/models/entities/user.ts index 0a2878c0c9..40d27a42bb 100644 --- a/src/models/entities/user.ts +++ b/src/models/entities/user.ts @@ -45,18 +45,6 @@ export class User { }) public name: string | null; - @Column('varchar', { - length: 128, nullable: true, - comment: 'The location of the User.' - }) - public location: string | null; - - @Column('char', { - length: 10, nullable: true, - comment: 'The birthday (YYYY-MM-DD) of the User.' - }) - public birthday: string | null; - @Column('integer', { default: 0, comment: 'The count of followers.' @@ -101,44 +89,12 @@ export class User { @JoinColumn() public banner: DriveFile | null; - @Column('varchar', { - length: 1024, nullable: true, - comment: 'The description (bio) of the User.' - }) - public description: string | null; - @Index() @Column('varchar', { length: 128, array: true, default: '{}' }) public tags: string[]; - @Column('varchar', { - length: 128, nullable: true, - comment: 'The email address of the User.' - }) - public email: string | null; - - @Column('varchar', { - length: 128, nullable: true, - }) - public emailVerifyCode: string | null; - - @Column('boolean', { - default: false, - }) - public emailVerified: boolean; - - @Column('varchar', { - length: 128, nullable: true, - }) - public twoFactorTempSecret: string | null; - - @Column('varchar', { - length: 128, nullable: true, - }) - public twoFactorSecret: string | null; - @Column('varchar', { length: 256, nullable: true, }) @@ -206,11 +162,6 @@ export class User { }) public isVerified: boolean; - @Column('boolean', { - default: false, - }) - public twoFactorEnabled: boolean; - @Column('varchar', { length: 128, array: true, default: '{}' }) @@ -248,44 +199,12 @@ export class User { }) public uri: string | null; - @Column('varchar', { - length: 128, nullable: true, - comment: 'The password hash of the User. It will be null if the origin of the user is local.' - }) - public password: string | null; - @Index({ unique: true }) @Column('char', { length: 16, nullable: true, unique: true, comment: 'The native access token of the User. It will be null if the origin of the user is local.' }) public token: string | null; - - @Column('jsonb', { - default: {}, - comment: 'The client-specific data of the User.' - }) - public clientData: Record; - - @Column('boolean', { - default: false, - }) - public autoWatch: boolean; - - @Column('boolean', { - default: false, - }) - public autoAcceptFollowed: boolean; - - @Column('boolean', { - default: false, - }) - public alwaysMarkNsfw: boolean; - - @Column('boolean', { - default: false, - }) - public carefulBot: boolean; } export interface ILocalUser extends User { diff --git a/src/models/index.ts b/src/models/index.ts index f88bb8d636..d66e4e710a 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -25,7 +25,6 @@ import { FollowRequestRepository } from './repositories/follow-request'; import { MutingRepository } from './repositories/muting'; import { BlockingRepository } from './repositories/blocking'; import { NoteReactionRepository } from './repositories/note-reaction'; -import { UserServiceLinking } from './entities/user-service-linking'; import { NotificationRepository } from './repositories/notification'; import { NoteFavoriteRepository } from './repositories/note-favorite'; import { ReversiMatchingRepository } from './repositories/games/reversi/matching'; @@ -35,6 +34,7 @@ import { AppRepository } from './repositories/app'; import { FollowingRepository } from './repositories/following'; import { AbuseUserReportRepository } from './repositories/abuse-user-report'; import { AuthSessionRepository } from './repositories/auth-session'; +import { UserProfile } from './entities/user-profile'; export const Apps = getCustomRepository(AppRepository); export const Notes = getCustomRepository(NoteRepository); @@ -45,12 +45,12 @@ export const NoteUnreads = getRepository(NoteUnread); export const Polls = getRepository(Poll); export const PollVotes = getRepository(PollVote); export const Users = getCustomRepository(UserRepository); +export const UserProfiles = getRepository(UserProfile); export const UserKeypairs = getRepository(UserKeypair); export const UserPublickeys = getRepository(UserPublickey); export const UserLists = getCustomRepository(UserListRepository); export const UserListJoinings = getRepository(UserListJoining); export const UserNotePinings = getRepository(UserNotePining); -export const UserServiceLinkings = getRepository(UserServiceLinking); export const Followings = getCustomRepository(FollowingRepository); export const FollowRequests = getCustomRepository(FollowRequestRepository); export const Instances = getRepository(Instance); diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts index 3939e3142a..9e11b2aa88 100644 --- a/src/models/repositories/user.ts +++ b/src/models/repositories/user.ts @@ -1,6 +1,6 @@ import { EntityRepository, Repository, In } from 'typeorm'; import { User, ILocalUser, IRemoteUser } from '../entities/user'; -import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings } from '..'; +import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles } from '..'; import rap from '@prezzemolo/rap'; @EntityRepository(User) @@ -80,6 +80,7 @@ export class UserRepository extends Repository { const relation = meId && (meId !== user.id) && opts.detail ? await this.getRelation(meId, user.id) : null; const pins = opts.detail ? await UserNotePinings.find({ userId: user.id }) : []; + const profile = opts.detail ? await UserProfiles.findOne({ userId: user.id }) : null; return await rap({ id: user.id, @@ -116,9 +117,9 @@ export class UserRepository extends Repository { } : {}), ...(opts.detail ? { - description: user.description, - location: user.location, - birthday: user.birthday, + description: profile.description, + location: profile.location, + birthday: profile.birthday, followersCount: user.followersCount, followingCount: user.followingCount, notesCount: user.notesCount, @@ -131,9 +132,9 @@ export class UserRepository extends Repository { ...(opts.detail && meId === user.id ? { avatarId: user.avatarId, bannerId: user.bannerId, - autoWatch: user.autoWatch, - alwaysMarkNsfw: user.alwaysMarkNsfw, - carefulBot: user.carefulBot, + autoWatch: profile.autoWatch, + alwaysMarkNsfw: profile.alwaysMarkNsfw, + carefulBot: profile.carefulBot, hasUnreadMessagingMessage: MessagingMessages.count({ where: { recipientId: user.id, diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts index 6129dad3d3..a6f7482bba 100644 --- a/src/remote/activitypub/models/person.ts +++ b/src/remote/activitypub/models/person.ts @@ -14,16 +14,16 @@ import { IIdentifier } from './identifier'; import { apLogger } from '../logger'; import { Note } from '../../../models/entities/note'; import { updateHashtag } from '../../../services/update-hashtag'; -import { Users, UserNotePinings, Instances, DriveFiles, Followings, UserServiceLinkings, UserPublickeys } from '../../../models'; +import { Users, UserNotePinings, Instances, DriveFiles, Followings, UserProfiles, UserPublickeys } from '../../../models'; import { User, IRemoteUser } from '../../../models/entities/user'; import { Emoji } from '../../../models/entities/emoji'; import { UserNotePining } from '../../../models/entities/user-note-pinings'; import { genId } from '../../../misc/gen-id'; -import { UserServiceLinking } from '../../../models/entities/user-service-linking'; import { instanceChart, usersChart } from '../../../services/chart'; import { UserPublickey } from '../../../models/entities/user-publickey'; import { isDuplicateKeyValueError } from '../../../misc/is-duplicate-key-value-error'; import { toPuny } from '../../../misc/convert-host'; +import { UserProfile } from '../../../models/entities/user-profile'; const logger = apLogger; /** @@ -126,7 +126,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise tag.toLowerCase()); @@ -141,7 +141,6 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise); + await UserPublickeys.save({ - id: genId(), userId: user.id, keyId: person.publicKey.id, keyPem: person.publicKey.publicKeyPem } as UserPublickey); - await UserServiceLinkings.save({ - id: genId(), - userId: user.id - } as UserServiceLinking); - // Register host registerOrFetchInstanceDoc(host).then(i => { Instances.increment({ id: i.id }, 'usersCount', 1); @@ -347,7 +344,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje keyPem: person.publicKey.publicKeyPem }); - await UserServiceLinkings.update({ userId: exist.id }, { + await UserProfiles.update({ userId: exist.id }, { twitterUserId: services.twitter.userId, twitterScreenName: services.twitter.screenName, githubId: services.github.id, diff --git a/src/remote/activitypub/renderer/person.ts b/src/remote/activitypub/renderer/person.ts index 4c6b518eb6..e561e47c68 100644 --- a/src/remote/activitypub/renderer/person.ts +++ b/src/remote/activitypub/renderer/person.ts @@ -8,15 +8,15 @@ import { getEmojis } from './note'; import renderEmoji from './emoji'; import { IIdentifier } from '../models/identifier'; import renderHashtag from './hashtag'; -import { DriveFiles, UserServiceLinkings, UserKeypairs } from '../../../models'; +import { DriveFiles, UserProfiles, UserKeypairs } from '../../../models'; export async function renderPerson(user: ILocalUser) { const id = `${config.url}/users/${user.id}`; - const [avatar, banner, links] = await Promise.all([ + const [avatar, banner, profile] = await Promise.all([ DriveFiles.findOne(user.avatarId), DriveFiles.findOne(user.bannerId), - UserServiceLinkings.findOne({ userId: user.id }) + UserProfiles.findOne({ userId: user.id }) ]); const attachment: { @@ -27,41 +27,41 @@ export async function renderPerson(user: ILocalUser) { identifier?: IIdentifier }[] = []; - if (links.twitter) { + if (profile.twitter) { attachment.push({ type: 'PropertyValue', name: 'Twitter', - value: `@${links.twitterScreenName}`, + value: `@${profile.twitterScreenName}`, identifier: { type: 'PropertyValue', name: 'misskey:authentication:twitter', - value: `${links.twitterUserId}@${links.twitterScreenName}` + value: `${profile.twitterUserId}@${profile.twitterScreenName}` } }); } - if (links.github) { + if (profile.github) { attachment.push({ type: 'PropertyValue', name: 'GitHub', - value: `@${links.githubLogin}`, + value: `@${profile.githubLogin}`, identifier: { type: 'PropertyValue', name: 'misskey:authentication:github', - value: `${links.githubId}@${links.githubLogin}` + value: `${profile.githubId}@${profile.githubLogin}` } }); } - if (links.discord) { + if (profile.discord) { attachment.push({ type: 'PropertyValue', name: 'Discord', - value: `${links.discordUsername}#${links.discordDiscriminator}`, + value: `${profile.discordUsername}#${profile.discordDiscriminator}`, identifier: { type: 'PropertyValue', name: 'misskey:authentication:discord', - value: `${links.discordId}@${links.discordUsername}#${links.discordDiscriminator}` + value: `${profile.discordId}@${profile.discordUsername}#${profile.discordDiscriminator}` } }); } @@ -93,7 +93,7 @@ export async function renderPerson(user: ILocalUser) { url: `${config.url}/@${user.username}`, preferredUsername: user.username, name: user.name, - summary: toHtml(parse(user.description)), + summary: toHtml(parse(profile.description)), icon: user.avatarId && renderImage(avatar), image: user.bannerId && renderImage(banner), tag, diff --git a/src/server/api/endpoints/admin/reset-password.ts b/src/server/api/endpoints/admin/reset-password.ts index 07b8b6d938..42df668606 100644 --- a/src/server/api/endpoints/admin/reset-password.ts +++ b/src/server/api/endpoints/admin/reset-password.ts @@ -3,7 +3,7 @@ import { ID } from '../../../../misc/cafy-id'; import define from '../../define'; import * as bcrypt from 'bcryptjs'; import rndstr from 'rndstr'; -import { Users } from '../../../../models'; +import { Users, UserProfiles } from '../../../../models'; export const meta = { desc: { @@ -42,7 +42,9 @@ export default define(meta, async (ps) => { // Generate hash of password const hash = bcrypt.hashSync(passwd); - await Users.update(user.id, { + await UserProfiles.update({ + userId: user.id + }, { password: hash }); diff --git a/src/server/api/endpoints/i/2fa/done.ts b/src/server/api/endpoints/i/2fa/done.ts index 8ccb09b8b7..edc7cefd26 100644 --- a/src/server/api/endpoints/i/2fa/done.ts +++ b/src/server/api/endpoints/i/2fa/done.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import * as speakeasy from 'speakeasy'; import define from '../../../define'; -import { Users } from '../../../../../models'; +import { UserProfiles } from '../../../../../models'; export const meta = { requireCredential: true, @@ -16,24 +16,26 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const _token = ps.token.replace(/\s/g, ''); + const token = ps.token.replace(/\s/g, ''); - if (user.twoFactorTempSecret == null) { + const profile = await UserProfiles.findOne({ userId: user.id }); + + if (profile.twoFactorTempSecret == null) { throw new Error('二段階認証の設定が開始されていません'); } const verified = (speakeasy as any).totp.verify({ - secret: user.twoFactorTempSecret, + secret: profile.twoFactorTempSecret, encoding: 'base32', - token: _token + token: token }); if (!verified) { throw new Error('not verified'); } - await Users.update(user.id, { - twoFactorSecret: user.twoFactorTempSecret, + await UserProfiles.update({ userId: user.id }, { + twoFactorSecret: profile.twoFactorTempSecret, twoFactorEnabled: true }); }); diff --git a/src/server/api/endpoints/i/2fa/register.ts b/src/server/api/endpoints/i/2fa/register.ts index 5efe77900a..db9a2fe944 100644 --- a/src/server/api/endpoints/i/2fa/register.ts +++ b/src/server/api/endpoints/i/2fa/register.ts @@ -4,7 +4,7 @@ import * as speakeasy from 'speakeasy'; import * as QRCode from 'qrcode'; import config from '../../../../../config'; import define from '../../../define'; -import { Users } from '../../../../../models'; +import { UserProfiles } from '../../../../../models'; export const meta = { requireCredential: true, @@ -19,8 +19,10 @@ export const meta = { }; export default define(meta, async (ps, user) => { + const profile = await UserProfiles.findOne({ userId: user.id }); + // Compare password - const same = await bcrypt.compare(ps.password, user.password); + const same = await bcrypt.compare(ps.password, profile.password); if (!same) { throw new Error('incorrect password'); @@ -31,7 +33,7 @@ export default define(meta, async (ps, user) => { length: 32 }); - await Users.update(user.id, { + await UserProfiles.update({ userId: user.id }, { twoFactorTempSecret: secret.base32 }); diff --git a/src/server/api/endpoints/i/2fa/unregister.ts b/src/server/api/endpoints/i/2fa/unregister.ts index fb3ecd4043..fa25b74391 100644 --- a/src/server/api/endpoints/i/2fa/unregister.ts +++ b/src/server/api/endpoints/i/2fa/unregister.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; import define from '../../../define'; -import { Users } from '../../../../../models'; +import { UserProfiles } from '../../../../../models'; export const meta = { requireCredential: true, @@ -16,17 +16,17 @@ export const meta = { }; export default define(meta, async (ps, user) => { + const profile = await UserProfiles.findOne({ userId: user.id }); + // Compare password - const same = await bcrypt.compare(ps.password, user.password); + const same = await bcrypt.compare(ps.password, profile.password); if (!same) { throw new Error('incorrect password'); } - await Users.update(user.id, { + await UserProfiles.update({ userId: user.id }, { twoFactorSecret: null, twoFactorEnabled: false }); - - return; }); diff --git a/src/server/api/endpoints/i/change-password.ts b/src/server/api/endpoints/i/change-password.ts index f8f977200f..d0e0695e18 100644 --- a/src/server/api/endpoints/i/change-password.ts +++ b/src/server/api/endpoints/i/change-password.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; import define from '../../define'; -import { Users } from '../../../../models'; +import { UserProfiles } from '../../../../models'; export const meta = { requireCredential: true, @@ -20,8 +20,10 @@ export const meta = { }; export default define(meta, async (ps, user) => { + const profile = await UserProfiles.findOne({ userId: user.id }); + // Compare password - const same = await bcrypt.compare(ps.currentPassword, user.password); + const same = await bcrypt.compare(ps.currentPassword, profile.password); if (!same) { throw new Error('incorrect password'); @@ -31,7 +33,7 @@ export default define(meta, async (ps, user) => { const salt = await bcrypt.genSalt(8); const hash = await bcrypt.hash(ps.newPassword, salt); - await Users.update(user.id, { + await UserProfiles.update({ userId: user.id }, { password: hash }); }); diff --git a/src/server/api/endpoints/i/delete-account.ts b/src/server/api/endpoints/i/delete-account.ts index 5aff74e0cc..7ef7aa5fac 100644 --- a/src/server/api/endpoints/i/delete-account.ts +++ b/src/server/api/endpoints/i/delete-account.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; import define from '../../define'; -import { Users } from '../../../../models'; +import { Users, UserProfiles } from '../../../../models'; export const meta = { requireCredential: true, @@ -16,8 +16,10 @@ export const meta = { }; export default define(meta, async (ps, user) => { + const profile = await UserProfiles.findOne({ userId: user.id }); + // Compare password - const same = await bcrypt.compare(ps.password, user.password); + const same = await bcrypt.compare(ps.password, profile.password); if (!same) { throw new Error('incorrect password'); diff --git a/src/server/api/endpoints/i/regenerate-token.ts b/src/server/api/endpoints/i/regenerate-token.ts index 729c1a300a..ec53bca979 100644 --- a/src/server/api/endpoints/i/regenerate-token.ts +++ b/src/server/api/endpoints/i/regenerate-token.ts @@ -3,7 +3,7 @@ import * as bcrypt from 'bcryptjs'; import { publishMainStream } from '../../../../services/stream'; import generateUserToken from '../../common/generate-native-user-token'; import define from '../../define'; -import { Users } from '../../../../models'; +import { Users, UserProfiles } from '../../../../models'; export const meta = { requireCredential: true, @@ -18,8 +18,10 @@ export const meta = { }; export default define(meta, async (ps, user) => { + const profile = await UserProfiles.findOne({ userId: user.id }); + // Compare password - const same = await bcrypt.compare(ps.password, user.password); + const same = await bcrypt.compare(ps.password, profile.password); if (!same) { throw new Error('incorrect password'); diff --git a/src/server/api/endpoints/i/update-client-setting.ts b/src/server/api/endpoints/i/update-client-setting.ts index edbfe28f35..49bcb35ae6 100644 --- a/src/server/api/endpoints/i/update-client-setting.ts +++ b/src/server/api/endpoints/i/update-client-setting.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import { publishMainStream } from '../../../../services/stream'; import define from '../../define'; -import { Users } from '../../../../models'; +import { UserProfiles } from '../../../../models'; export const meta = { requireCredential: true, @@ -20,7 +20,7 @@ export const meta = { }; export default define(meta, async (ps, user) => { - await Users.createQueryBuilder().update() + await UserProfiles.createQueryBuilder().update() .set({ clientData: { [ps.name]: ps.value diff --git a/src/server/api/endpoints/i/update-email.ts b/src/server/api/endpoints/i/update-email.ts index 253017535f..d98f0d753e 100644 --- a/src/server/api/endpoints/i/update-email.ts +++ b/src/server/api/endpoints/i/update-email.ts @@ -8,7 +8,7 @@ import config from '../../../../config'; import * as ms from 'ms'; import * as bcrypt from 'bcryptjs'; import { apiLogger } from '../../logger'; -import { Users } from '../../../../models'; +import { Users, UserProfiles } from '../../../../models'; export const meta = { requireCredential: true, @@ -32,14 +32,16 @@ export const meta = { }; export default define(meta, async (ps, user) => { + const profile = await UserProfiles.findOne({ userId: user.id }); + // Compare password - const same = await bcrypt.compare(ps.password, user.password); + const same = await bcrypt.compare(ps.password, profile.password); if (!same) { throw new Error('incorrect password'); } - await Users.update(user.id, { + await UserProfiles.update({ userId: user.id }, { email: ps.email, emailVerified: false, emailVerifyCode: null @@ -56,7 +58,7 @@ export default define(meta, async (ps, user) => { if (ps.email != null) { const code = rndstr('a-z0-9', 16); - await Users.update(user.id, { + await UserProfiles.update({ userId: user.id }, { emailVerifyCode: code }); diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts index 54e7f33bdb..ffc90b2f51 100644 --- a/src/server/api/endpoints/i/update.ts +++ b/src/server/api/endpoints/i/update.ts @@ -10,7 +10,9 @@ import extractHashtags from '../../../../misc/extract-hashtags'; import * as langmap from 'langmap'; import { updateHashtag } from '../../../../services/update-hashtag'; import { ApiError } from '../../error'; -import { Users, DriveFiles } from '../../../../models'; +import { Users, DriveFiles, UserProfiles } from '../../../../models'; +import { User } from '../../../../models/entities/user'; +import { UserProfile } from '../../../../models/entities/user-profile'; export const meta = { desc: { @@ -154,22 +156,23 @@ export const meta = { export default define(meta, async (ps, user, app) => { const isSecure = user != null && app == null; - const updates = {} as any; + const updates = {} as Partial; + const profile = {} as Partial; if (ps.name !== undefined) updates.name = ps.name; - if (ps.description !== undefined) updates.description = ps.description; - if (ps.lang !== undefined) updates.lang = ps.lang; - if (ps.location !== undefined) updates.location = ps.location; - if (ps.birthday !== undefined) updates.birthday = ps.birthday; + if (ps.description !== undefined) profile.description = ps.description; + //if (ps.lang !== undefined) updates.lang = ps.lang; + if (ps.location !== undefined) profile.location = ps.location; + if (ps.birthday !== undefined) profile.birthday = ps.birthday; if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId; if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId; if (typeof ps.isLocked == 'boolean') updates.isLocked = ps.isLocked; if (typeof ps.isBot == 'boolean') updates.isBot = ps.isBot; - if (typeof ps.carefulBot == 'boolean') updates.carefulBot = ps.carefulBot; - if (typeof ps.autoAcceptFollowed == 'boolean') updates.autoAcceptFollowed = ps.autoAcceptFollowed; + if (typeof ps.carefulBot == 'boolean') profile.carefulBot = ps.carefulBot; + if (typeof ps.autoAcceptFollowed == 'boolean') profile.autoAcceptFollowed = ps.autoAcceptFollowed; if (typeof ps.isCat == 'boolean') updates.isCat = ps.isCat; - if (typeof ps.autoWatch == 'boolean') updates.autoWatch = ps.autoWatch; - if (typeof ps.alwaysMarkNsfw == 'boolean') updates.alwaysMarkNsfw = ps.alwaysMarkNsfw; + if (typeof ps.autoWatch == 'boolean') profile.autoWatch = ps.autoWatch; + if (typeof ps.alwaysMarkNsfw == 'boolean') profile.alwaysMarkNsfw = ps.alwaysMarkNsfw; if (ps.avatarId) { const avatar = await DriveFiles.findOne(ps.avatarId); @@ -206,8 +209,8 @@ export default define(meta, async (ps, user, app) => { emojis = emojis.concat(extractEmojis(tokens)); } - if (updates.description != null) { - const tokens = parse(updates.description); + if (profile.description != null) { + const tokens = parse(profile.description); emojis = emojis.concat(extractEmojis(tokens)); tags = extractHashtags(tokens).map(tag => tag.toLowerCase()); } @@ -221,6 +224,7 @@ export default define(meta, async (ps, user, app) => { //#endregion await Users.update(user.id, updates); + await UserProfiles.update({ userId: user.id }, profile); const iObj = await Users.pack(user.id, user, { detail: true, diff --git a/src/server/api/endpoints/notes/polls/vote.ts b/src/server/api/endpoints/notes/polls/vote.ts index 7d0ed6e4f9..d868234dc9 100644 --- a/src/server/api/endpoints/notes/polls/vote.ts +++ b/src/server/api/endpoints/notes/polls/vote.ts @@ -10,7 +10,7 @@ import { deliver } from '../../../../../queue'; import { renderActivity } from '../../../../../remote/activitypub/renderer'; import renderVote from '../../../../../remote/activitypub/renderer/vote'; import { deliverQuestionUpdate } from '../../../../../services/note/polls/update'; -import { PollVotes, NoteWatchings, Users, Polls } from '../../../../../models'; +import { PollVotes, NoteWatchings, Users, Polls, UserProfiles } from '../../../../../models'; import { Not } from 'typeorm'; import { IRemoteUser } from '../../../../../models/entities/user'; import { genId } from '../../../../../misc/gen-id'; @@ -149,8 +149,10 @@ export default define(meta, async (ps, user) => { } }); + const profile = await UserProfiles.findOne({ userId: user.id }); + // この投稿をWatchする - if (user.autoWatch !== false) { + if (profile.autoWatch !== false) { watch(user.id, note); } diff --git a/src/server/api/private/signin.ts b/src/server/api/private/signin.ts index c1fd908d8a..fe2e5577c2 100644 --- a/src/server/api/private/signin.ts +++ b/src/server/api/private/signin.ts @@ -4,7 +4,7 @@ import * as speakeasy from 'speakeasy'; import { publishMainStream } from '../../../services/stream'; import signin from '../common/signin'; import config from '../../../config'; -import { Users, Signins } from '../../../models'; +import { Users, Signins, UserProfiles } from '../../../models'; import { ILocalUser } from '../../../models/entities/user'; import { genId } from '../../../misc/gen-id'; @@ -45,13 +45,15 @@ export default async (ctx: Koa.BaseContext) => { return; } + const profile = await UserProfiles.findOne({ userId: user.id }); + // Compare password - const same = await bcrypt.compare(password, user.password); + const same = await bcrypt.compare(password, profile.password); if (same) { - if (user.twoFactorEnabled) { + if (profile.twoFactorEnabled) { const verified = (speakeasy as any).totp.verify({ - secret: user.twoFactorSecret, + secret: profile.twoFactorSecret, encoding: 'base32', token: token }); diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts index 657e54decd..5ed25fa411 100644 --- a/src/server/api/private/signup.ts +++ b/src/server/api/private/signup.ts @@ -5,13 +5,13 @@ import generateUserToken from '../common/generate-native-user-token'; import config from '../../../config'; import fetchMeta from '../../../misc/fetch-meta'; import * as recaptcha from 'recaptcha-promise'; -import { Users, RegistrationTickets, UserServiceLinkings, UserKeypairs } from '../../../models'; +import { Users, RegistrationTickets, UserProfiles, UserKeypairs } from '../../../models'; import { genId } from '../../../misc/gen-id'; import { usersChart } from '../../../services/chart'; -import { UserServiceLinking } from '../../../models/entities/user-service-linking'; import { User } from '../../../models/entities/user'; import { UserKeypair } from '../../../models/entities/user-keypair'; import { toPuny } from '../../../misc/convert-host'; +import { UserProfile } from '../../../models/entities/user-profile'; export default async (ctx: Koa.BaseContext) => { const body = ctx.request.body as any; @@ -106,23 +106,21 @@ export default async (ctx: Koa.BaseContext) => { usernameLower: username.toLowerCase(), host: toPuny(host), token: secret, - password: hash, isAdmin: config.autoAdmin && usersCount === 0, - autoAcceptFollowed: true, - autoWatch: false } as User); await UserKeypairs.save({ - id: genId(), publicKey: keyPair[0], privateKey: keyPair[1], userId: account.id } as UserKeypair); - await UserServiceLinkings.save({ - id: genId(), - userId: account.id - } as UserServiceLinking); + await UserProfiles.save({ + userId: account.id, + autoAcceptFollowed: true, + autoWatch: false, + password: hash, + } as Partial); usersChart.update(account, true); diff --git a/src/server/api/service/discord.ts b/src/server/api/service/discord.ts index 4290e1ff9d..879b8b4849 100644 --- a/src/server/api/service/discord.ts +++ b/src/server/api/service/discord.ts @@ -8,7 +8,7 @@ import redis from '../../../db/redis'; import * as uuid from 'uuid'; import signin from '../common/signin'; import fetchMeta from '../../../misc/fetch-meta'; -import { Users, UserServiceLinkings } from '../../../models'; +import { Users, UserProfiles } from '../../../models'; import { ILocalUser } from '../../../models/entities/user'; function getUserToken(ctx: Koa.BaseContext) { @@ -45,7 +45,7 @@ router.get('/disconnect/discord', async ctx => { token: userToken }); - await UserServiceLinkings.update({ + await UserProfiles.update({ userId: user.id }, { discord: false, @@ -202,7 +202,7 @@ router.get('/dc/cb', async ctx => { return; } - const link = await UserServiceLinkings.createQueryBuilder() + const profile = await UserProfiles.createQueryBuilder() .where('discord @> :discord', { discord: { id: id, @@ -211,12 +211,12 @@ router.get('/dc/cb', async ctx => { .andWhere('userHost IS NULL') .getOne(); - if (link == null) { + if (profile == null) { ctx.throw(404, `@${username}#${discriminator}と連携しているMisskeyアカウントはありませんでした...`); return; } - await UserServiceLinkings.update(link.id, { + await UserProfiles.update({ userId: profile.userId }, { discord: true, discordAccessToken: accessToken, discordRefreshToken: refreshToken, @@ -225,7 +225,7 @@ router.get('/dc/cb', async ctx => { discordDiscriminator: discriminator }); - signin(ctx, await Users.findOne(link.userId) as ILocalUser, true); + signin(ctx, await Users.findOne(profile.userId) as ILocalUser, true); } else { const code = ctx.query.code; @@ -289,7 +289,7 @@ router.get('/dc/cb', async ctx => { token: userToken }); - await UserServiceLinkings.update({ userId: user.id }, { + await UserProfiles.update({ userId: user.id }, { discord: true, discordAccessToken: accessToken, discordRefreshToken: refreshToken, diff --git a/src/server/api/service/github.ts b/src/server/api/service/github.ts index e59b149d19..580947811b 100644 --- a/src/server/api/service/github.ts +++ b/src/server/api/service/github.ts @@ -8,7 +8,7 @@ import redis from '../../../db/redis'; import * as uuid from 'uuid'; import signin from '../common/signin'; import fetchMeta from '../../../misc/fetch-meta'; -import { Users, UserServiceLinkings } from '../../../models'; +import { Users, UserProfiles } from '../../../models'; import { ILocalUser } from '../../../models/entities/user'; function getUserToken(ctx: Koa.BaseContext) { @@ -45,7 +45,7 @@ router.get('/disconnect/github', async ctx => { token: userToken }); - await UserServiceLinkings.update({ + await UserProfiles.update({ userId: user.id }, { github: false, @@ -191,7 +191,7 @@ router.get('/gh/cb', async ctx => { return; } - const link = await UserServiceLinkings.createQueryBuilder() + const link = await UserProfiles.createQueryBuilder() .where('github @> :github', { github: { id: id, @@ -263,7 +263,7 @@ router.get('/gh/cb', async ctx => { token: userToken }); - await UserServiceLinkings.update({ userId: user.id }, { + await UserProfiles.update({ userId: user.id }, { github: true, githubAccessToken: accessToken, githubId: id, diff --git a/src/server/api/service/twitter.ts b/src/server/api/service/twitter.ts index 77cf71395b..c0c762c6c3 100644 --- a/src/server/api/service/twitter.ts +++ b/src/server/api/service/twitter.ts @@ -7,7 +7,7 @@ import { publishMainStream } from '../../../services/stream'; import config from '../../../config'; import signin from '../common/signin'; import fetchMeta from '../../../misc/fetch-meta'; -import { Users, UserServiceLinkings } from '../../../models'; +import { Users, UserProfiles } from '../../../models'; import { ILocalUser } from '../../../models/entities/user'; function getUserToken(ctx: Koa.BaseContext) { @@ -44,7 +44,7 @@ router.get('/disconnect/twitter', async ctx => { token: userToken }); - await UserServiceLinkings.update({ + await UserProfiles.update({ userId: user.id }, { twitter: false, @@ -139,7 +139,7 @@ router.get('/tw/cb', async ctx => { const result = await twAuth.done(JSON.parse(twCtx), ctx.query.oauth_verifier); - const link = await UserServiceLinkings.createQueryBuilder() + const link = await UserProfiles.createQueryBuilder() .where('twitter @> :twitter', { twitter: { userId: result.userId, @@ -177,7 +177,7 @@ router.get('/tw/cb', async ctx => { token: userToken }); - await UserServiceLinkings.update({ userId: user.id }, { + await UserProfiles.update({ userId: user.id }, { twitter: true, twitterAccessToken: result.accessToken, twitterAccessTokenSecret: result.accessTokenSecret, diff --git a/src/server/index.ts b/src/server/index.ts index 563117773e..9c153f0167 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -23,7 +23,7 @@ import apiServer from './api'; import { sum } from '../prelude/array'; import Logger from '../services/logger'; import { program } from '../argv'; -import { Users } from '../models'; +import { UserProfiles } from '../models'; import { networkChart } from '../services/chart'; export const serverLogger = new Logger('server', 'gray', false); @@ -73,15 +73,15 @@ router.use(nodeinfo.routes()); router.use(wellKnown.routes()); router.get('/verify-email/:code', async ctx => { - const user = await Users.findOne({ + const profile = await UserProfiles.findOne({ emailVerifyCode: ctx.params.code }); - if (user != null) { + if (profile != null) { ctx.body = 'Verify succeeded!'; ctx.status = 200; - Users.update(user.id, { + UserProfiles.update({ userId: profile.userId }, { emailVerified: true, emailVerifyCode: null }); diff --git a/src/server/web/feed.ts b/src/server/web/feed.ts index 4b4ea87973..94f0643f75 100644 --- a/src/server/web/feed.ts +++ b/src/server/web/feed.ts @@ -1,7 +1,7 @@ import { Feed } from 'feed'; import config from '../../config'; import { User } from '../../models/entities/user'; -import { Notes, DriveFiles } from '../../models'; +import { Notes, DriveFiles, UserProfiles } from '../../models'; import { In } from 'typeorm'; export default async function(user: User) { @@ -10,6 +10,8 @@ export default async function(user: User) { name: user.name || user.username }; + const profile = await UserProfiles.findOne({ userId: user.id }); + const notes = await Notes.find({ where: { userId: user.id, @@ -25,7 +27,7 @@ export default async function(user: User) { title: `${author.name} (@${user.username}@${config.host})`, updated: notes[0].createdAt, generator: 'Misskey', - description: `${user.notesCount} Notes, ${user.followingCount} Following, ${user.followersCount} Followers${user.description ? ` · ${user.description}` : ''}`, + description: `${user.notesCount} Notes, ${user.followingCount} Following, ${user.followersCount} Followers${profile.description ? ` · ${profile.description}` : ''}`, link: author.link, image: user.avatarUrl, feedLinks: { diff --git a/src/services/drive/add-file.ts b/src/services/drive/add-file.ts index 0a84b88fb8..b83c3558d3 100644 --- a/src/services/drive/add-file.ts +++ b/src/services/drive/add-file.ts @@ -15,7 +15,7 @@ import { driveLogger } from './logger'; import { IImage, ConvertToJpeg, ConvertToWebp, ConvertToPng } from './image-processor'; import { contentDisposition } from '../../misc/content-disposition'; import { detectMine } from '../../misc/detect-mine'; -import { DriveFiles, DriveFolders, Users, Instances } from '../../models'; +import { DriveFiles, DriveFolders, Users, Instances, UserProfiles } from '../../models'; import { InternalStorage } from './internal-storage'; import { DriveFile } from '../../models/entities/drive-file'; import { IRemoteUser, User } from '../../models/entities/user'; @@ -365,6 +365,8 @@ export default async function( propPromises = [calcWh(), calcAvg()]; } + const profile = await UserProfiles.findOne({ userId: user.id }); + const [folder] = await Promise.all([fetchFolder(), Promise.all(propPromises)]); let file = new DriveFile(); @@ -376,7 +378,7 @@ export default async function( file.comment = comment; file.properties = properties; file.isLink = isLink; - file.isSensitive = Users.isLocalUser(user) && user.alwaysMarkNsfw ? true : + file.isSensitive = Users.isLocalUser(user) && profile.alwaysMarkNsfw ? true : (sensitive !== null && sensitive !== undefined) ? sensitive : false; diff --git a/src/services/following/create.ts b/src/services/following/create.ts index 28e4ba3c12..57bb61fd92 100644 --- a/src/services/following/create.ts +++ b/src/services/following/create.ts @@ -9,7 +9,7 @@ import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc'; import Logger from '../logger'; import { IdentifiableError } from '../../misc/identifiable-error'; import { User } from '../../models/entities/user'; -import { Followings, Users, FollowRequests, Blockings, Instances } from '../../models'; +import { Followings, Users, FollowRequests, Blockings, Instances, UserProfiles } from '../../models'; import { instanceChart, perUserFollowingChart } from '../chart'; import { genId } from '../../misc/gen-id'; import { createNotification } from '../create-notification'; @@ -115,11 +115,13 @@ export default async function(follower: User, followee: User, requestId?: string if (blocked != null) throw new IdentifiableError('3338392a-f764-498d-8855-db939dcf8c48', 'blocked'); } + const followeeProfile = await UserProfiles.findOne({ userId: followee.id }); + // フォロー対象が鍵アカウントである or // フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or // フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである // 上記のいずれかに当てはまる場合はすぐフォローせずにフォローリクエストを発行しておく - if (followee.isLocked || (followee.carefulBot && follower.isBot) || (Users.isLocalUser(follower) && Users.isRemoteUser(followee))) { + if (followee.isLocked || (followeeProfile.carefulBot && follower.isBot) || (Users.isLocalUser(follower) && Users.isRemoteUser(followee))) { let autoAccept = false; // 鍵アカウントであっても、既にフォローされていた場合はスルー @@ -132,7 +134,7 @@ export default async function(follower: User, followee: User, requestId?: string } // フォローしているユーザーは自動承認オプション - if (!autoAccept && (Users.isLocalUser(followee) && followee.autoAcceptFollowed)) { + if (!autoAccept && (Users.isLocalUser(followee) && followeeProfile.autoAcceptFollowed)) { const followed = await Followings.findOne({ followerId: followee.id, followeeId: follower.id diff --git a/src/services/note/create.ts b/src/services/note/create.ts index 05837a4daf..6058fada16 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -17,7 +17,7 @@ import extractMentions from '../../misc/extract-mentions'; import extractEmojis from '../../misc/extract-emojis'; import extractHashtags from '../../misc/extract-hashtags'; import { Note } from '../../models/entities/note'; -import { Mutings, Users, NoteWatchings, Followings, Notes, Instances, Polls } from '../../models'; +import { Mutings, Users, NoteWatchings, Followings, Notes, Instances, Polls, UserProfiles } from '../../models'; import { DriveFile } from '../../models/entities/drive-file'; import { App } from '../../models/entities/app'; import { Not } from 'typeorm'; @@ -256,13 +256,15 @@ export default async (user: User, data: Option, silent = false) => new Promise new Promise new Promise(async (re } }); + const profile = await UserProfiles.findOne({ userId: user.id }); + // ローカルユーザーが投票した場合この投稿をWatchする - if (Users.isLocalUser(user) && user.autoWatch) { + if (Users.isLocalUser(user) && profile.autoWatch) { watch(user.id, note); } }); diff --git a/src/services/note/reaction/create.ts b/src/services/note/reaction/create.ts index 437b213ded..1b026cc9cc 100644 --- a/src/services/note/reaction/create.ts +++ b/src/services/note/reaction/create.ts @@ -8,7 +8,7 @@ import { toDbReaction } from '../../../misc/reaction-lib'; import fetchMeta from '../../../misc/fetch-meta'; import { User } from '../../../models/entities/user'; import { Note } from '../../../models/entities/note'; -import { NoteReactions, Users, NoteWatchings, Notes } from '../../../models'; +import { NoteReactions, Users, NoteWatchings, Notes, UserProfiles } from '../../../models'; import { Not } from 'typeorm'; import { perUserReactionsChart } from '../../chart'; import { genId } from '../../../misc/gen-id'; @@ -79,8 +79,10 @@ export default async (user: User, note: Note, reaction: string) => { } }); + const profile = await UserProfiles.findOne({ userId: user.id }); + // ユーザーがローカルユーザーかつ自動ウォッチ設定がオンならばこの投稿をWatchする - if (Users.isLocalUser(user) && user.autoWatch !== false) { + if (Users.isLocalUser(user) && profile.autoWatch) { watch(user.id, note); } -- cgit v1.2.3-freya From dda5f6559de331e624824647e01314ea00ee07d3 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 10 Apr 2019 18:10:09 +0900 Subject: Fix bug --- src/models/repositories/user.ts | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/models') diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts index 9e11b2aa88..3d2a0f31fd 100644 --- a/src/models/repositories/user.ts +++ b/src/models/repositories/user.ts @@ -154,6 +154,12 @@ export class UserRepository extends Repository { }), } : {}), + ...(opts.includeSecrets ? { + clientData: profile.clientData, + email: profile.email, + emailVerified: profile.emailVerified, + } : {}), + ...(relation ? { isFollowing: relation.isFollowing, isFollowed: relation.isFollowed, -- cgit v1.2.3-freya From c28f4ffb3f5d67125abe3da9af60f05233f663a7 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 10 Apr 2019 20:07:36 +0900 Subject: Clean up --- src/models/entities/poll.ts | 4 ---- src/models/entities/user-keypair.ts | 3 +-- src/models/entities/user-profile.ts | 1 - src/models/entities/user-publickey.ts | 1 - src/remote/activitypub/models/question.ts | 2 +- src/server/api/endpoints/notes/polls/vote.ts | 2 +- src/services/note/create.ts | 1 - src/services/note/polls/vote.ts | 2 +- 8 files changed, 4 insertions(+), 12 deletions(-) (limited to 'src/models') diff --git a/src/models/entities/poll.ts b/src/models/entities/poll.ts index 204f102f51..894f07e689 100644 --- a/src/models/entities/poll.ts +++ b/src/models/entities/poll.ts @@ -6,10 +6,6 @@ import { User } from './user'; @Entity() export class Poll { @PrimaryColumn(id()) - public id: string; - - @Index({ unique: true }) - @Column(id()) public noteId: Note['id']; @OneToOne(type => Note, { diff --git a/src/models/entities/user-keypair.ts b/src/models/entities/user-keypair.ts index 9181abf8cb..808985f473 100644 --- a/src/models/entities/user-keypair.ts +++ b/src/models/entities/user-keypair.ts @@ -1,10 +1,9 @@ -import { PrimaryColumn, Entity, Index, JoinColumn, Column, OneToOne } from 'typeorm'; +import { PrimaryColumn, Entity, JoinColumn, Column, OneToOne } from 'typeorm'; import { User } from './user'; import { id } from '../id'; @Entity() export class UserKeypair { - @Index({ unique: true }) @PrimaryColumn(id()) public userId: User['id']; diff --git a/src/models/entities/user-profile.ts b/src/models/entities/user-profile.ts index 24b92231f5..421d17c59e 100644 --- a/src/models/entities/user-profile.ts +++ b/src/models/entities/user-profile.ts @@ -4,7 +4,6 @@ import { User } from './user'; @Entity() export class UserProfile { - @Index({ unique: true }) @PrimaryColumn(id()) public userId: User['id']; diff --git a/src/models/entities/user-publickey.ts b/src/models/entities/user-publickey.ts index 81c42404fa..26b694407d 100644 --- a/src/models/entities/user-publickey.ts +++ b/src/models/entities/user-publickey.ts @@ -4,7 +4,6 @@ import { id } from '../id'; @Entity() export class UserPublickey { - @Index({ unique: true }) @PrimaryColumn(id()) public userId: User['id']; diff --git a/src/remote/activitypub/models/question.ts b/src/remote/activitypub/models/question.ts index a5091a6d96..68f0cb3596 100644 --- a/src/remote/activitypub/models/question.ts +++ b/src/remote/activitypub/models/question.ts @@ -72,7 +72,7 @@ export async function updateQuestion(value: any) { updatedAt: new Date(), }); - await Polls.update(poll.id, { + await Polls.update({ noteId: note.id }, { votes: poll.votes }); diff --git a/src/server/api/endpoints/notes/polls/vote.ts b/src/server/api/endpoints/notes/polls/vote.ts index d868234dc9..dd4d93c7ac 100644 --- a/src/server/api/endpoints/notes/polls/vote.ts +++ b/src/server/api/endpoints/notes/polls/vote.ts @@ -123,7 +123,7 @@ export default define(meta, async (ps, user) => { // Increment votes count const index = ps.choice + 1; // In SQL, array index is 1 based - await Polls.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE id = '${poll.id}'`); + await Polls.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE noteId = '${poll.noteId}'`); publishNoteStream(note.id, 'pollVoted', { choice: ps.choice, diff --git a/src/services/note/create.ts b/src/services/note/create.ts index 6058fada16..bc1225cd4d 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -398,7 +398,6 @@ async function insertNote(user: User, data: Option, tags: string[], emojis: stri if (note.hasPoll) { await Polls.save({ - id: genId(), noteId: note.id, choices: data.poll.choices, expiresAt: data.poll.expiresAt, diff --git a/src/services/note/polls/vote.ts b/src/services/note/polls/vote.ts index 95e5b3ef3a..3051ff42d9 100644 --- a/src/services/note/polls/vote.ts +++ b/src/services/note/polls/vote.ts @@ -40,7 +40,7 @@ export default (user: User, note: Note, choice: number) => new Promise(async (re // Increment votes count const index = choice + 1; // In SQL, array index is 1 based - await Polls.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE id = '${poll.id}'`); + await Polls.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE noteId = '${poll.noteId}'`); publishNoteStream(note.id, 'pollVoted', { choice: choice, -- cgit v1.2.3-freya From 5c50989970955b93f5a7e4aee71a4eb9403cdf15 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 11 Apr 2019 12:59:09 +0900 Subject: Fix bug --- src/models/repositories/user.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'src/models') diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts index 3d2a0f31fd..beb570a310 100644 --- a/src/models/repositories/user.ts +++ b/src/models/repositories/user.ts @@ -95,6 +95,7 @@ export class UserRepository extends Repository { isAdmin: user.isAdmin, isBot: user.isBot, isCat: user.isCat, + isVerified: user.isVerified, // カスタム絵文字添付 emojis: user.emojis.length > 0 ? Emojis.find({ -- cgit v1.2.3-freya From 46b78cb4ffb17ab372ef5625a9c1b24d5b100972 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 11 Apr 2019 19:03:49 +0900 Subject: Increase url column length --- src/models/entities/app.ts | 2 +- src/models/entities/emoji.ts | 4 ++-- src/models/entities/follow-request.ts | 8 ++++---- src/models/entities/following.ts | 8 ++++---- src/models/entities/meta.ts | 8 ++++---- src/models/entities/note.ts | 2 +- src/models/entities/sw-subscription.ts | 2 +- src/models/entities/user.ts | 18 +++++++++--------- 8 files changed, 26 insertions(+), 26 deletions(-) (limited to 'src/models') diff --git a/src/models/entities/app.ts b/src/models/entities/app.ts index d0c89000fc..ea87546311 100644 --- a/src/models/entities/app.ts +++ b/src/models/entities/app.ts @@ -53,7 +53,7 @@ export class App { public permission: string[]; @Column('varchar', { - length: 256, nullable: true, + length: 512, nullable: true, comment: 'The callbackUrl of the App.' }) public callbackUrl: string | null; diff --git a/src/models/entities/emoji.ts b/src/models/entities/emoji.ts index da04da897e..020636a7fb 100644 --- a/src/models/entities/emoji.ts +++ b/src/models/entities/emoji.ts @@ -25,12 +25,12 @@ export class Emoji { public host: string | null; @Column('varchar', { - length: 256, + length: 512, }) public url: string; @Column('varchar', { - length: 256, nullable: true + length: 512, nullable: true }) public uri: string | null; diff --git a/src/models/entities/follow-request.ts b/src/models/entities/follow-request.ts index 80a71fe482..22ec263962 100644 --- a/src/models/entities/follow-request.ts +++ b/src/models/entities/follow-request.ts @@ -53,13 +53,13 @@ export class FollowRequest { public followerHost: string | null; @Column('varchar', { - length: 256, nullable: true, + length: 512, nullable: true, comment: '[Denormalized]' }) public followerInbox: string | null; @Column('varchar', { - length: 256, nullable: true, + length: 512, nullable: true, comment: '[Denormalized]' }) public followerSharedInbox: string | null; @@ -71,13 +71,13 @@ export class FollowRequest { public followeeHost: string | null; @Column('varchar', { - length: 256, nullable: true, + length: 512, nullable: true, comment: '[Denormalized]' }) public followeeInbox: string | null; @Column('varchar', { - length: 256, nullable: true, + length: 512, nullable: true, comment: '[Denormalized]' }) public followeeSharedInbox: string | null; diff --git a/src/models/entities/following.ts b/src/models/entities/following.ts index 963873d112..ee3286a1a1 100644 --- a/src/models/entities/following.ts +++ b/src/models/entities/following.ts @@ -48,13 +48,13 @@ export class Following { public followerHost: string | null; @Column('varchar', { - length: 256, nullable: true, + length: 512, nullable: true, comment: '[Denormalized]' }) public followerInbox: string | null; @Column('varchar', { - length: 256, nullable: true, + length: 512, nullable: true, comment: '[Denormalized]' }) public followerSharedInbox: string | null; @@ -66,13 +66,13 @@ export class Following { public followeeHost: string | null; @Column('varchar', { - length: 256, nullable: true, + length: 512, nullable: true, comment: '[Denormalized]' }) public followeeInbox: string | null; @Column('varchar', { - length: 256, nullable: true, + length: 512, nullable: true, comment: '[Denormalized]' }) public followeeSharedInbox: string | null; diff --git a/src/models/entities/meta.ts b/src/models/entities/meta.ts index c34f5b6904..00a71cd82f 100644 --- a/src/models/entities/meta.ts +++ b/src/models/entities/meta.ts @@ -78,27 +78,27 @@ export class Meta { public blockedHosts: string[]; @Column('varchar', { - length: 256, + length: 512, nullable: true, default: '/assets/ai.png' }) public mascotImageUrl: string | null; @Column('varchar', { - length: 256, + length: 512, nullable: true }) public bannerUrl: string | null; @Column('varchar', { - length: 256, + length: 512, nullable: true, default: 'https://ai.misskey.xyz/aiart/yubitun.png' }) public errorImageUrl: string | null; @Column('varchar', { - length: 256, + length: 512, nullable: true }) public iconUrl: string | null; diff --git a/src/models/entities/note.ts b/src/models/entities/note.ts index 4c481964b6..5e83978197 100644 --- a/src/models/entities/note.ts +++ b/src/models/entities/note.ts @@ -126,7 +126,7 @@ export class Note { @Index({ unique: true }) @Column('varchar', { - length: 256, nullable: true, + length: 512, nullable: true, comment: 'The URI of a note. it will be null when the note is local.' }) public uri: string | null; diff --git a/src/models/entities/sw-subscription.ts b/src/models/entities/sw-subscription.ts index f0f2a69f1b..7c3f6f0a6c 100644 --- a/src/models/entities/sw-subscription.ts +++ b/src/models/entities/sw-subscription.ts @@ -21,7 +21,7 @@ export class SwSubscription { public user: User | null; @Column('varchar', { - length: 256, + length: 512, }) public endpoint: string; diff --git a/src/models/entities/user.ts b/src/models/entities/user.ts index 40d27a42bb..ebf07ff3ff 100644 --- a/src/models/entities/user.ts +++ b/src/models/entities/user.ts @@ -96,12 +96,12 @@ export class User { public tags: string[]; @Column('varchar', { - length: 256, nullable: true, + length: 512, nullable: true, }) public avatarUrl: string | null; @Column('varchar', { - length: 256, nullable: true, + length: 512, nullable: true, }) public bannerUrl: string | null; @@ -175,26 +175,26 @@ export class User { public host: string | null; @Column('varchar', { - length: 256, nullable: true, - comment: 'The inbox of the User. It will be null if the origin of the user is local.' + length: 512, nullable: true, + comment: 'The inbox URL of the User. It will be null if the origin of the user is local.' }) public inbox: string | null; @Column('varchar', { - length: 256, nullable: true, - comment: 'The sharedInbox of the User. It will be null if the origin of the user is local.' + length: 512, nullable: true, + comment: 'The sharedInbox URL of the User. It will be null if the origin of the user is local.' }) public sharedInbox: string | null; @Column('varchar', { - length: 256, nullable: true, - comment: 'The featured of the User. It will be null if the origin of the user is local.' + length: 512, nullable: true, + comment: 'The featured URL of the User. It will be null if the origin of the user is local.' }) public featured: string | null; @Index() @Column('varchar', { - length: 256, nullable: true, + length: 512, nullable: true, comment: 'The URI of the User. It will be null if the origin of the user is local.' }) public uri: string | null; -- cgit v1.2.3-freya From 11c30eccb339ea4c29eacb3178a62744e7184519 Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 12 Apr 2019 00:42:39 +0900 Subject: 非正規化カラムを削除 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 非正規化するほどの情報じゃない --- src/models/entities/note.ts | 6 ------ src/services/note/create.ts | 9 ++++++--- src/services/note/reaction/create.ts | 4 +++- src/services/note/reaction/delete.ts | 4 +++- 4 files changed, 12 insertions(+), 11 deletions(-) (limited to 'src/models') diff --git a/src/models/entities/note.ts b/src/models/entities/note.ts index 5e83978197..253d157b8c 100644 --- a/src/models/entities/note.ts +++ b/src/models/entities/note.ts @@ -195,12 +195,6 @@ export class Note { }) public userHost: string | null; - @Column('varchar', { - length: 128, nullable: true, - comment: '[Denormalized]' - }) - public userInbox: string | null; - @Column({ ...id(), nullable: true, diff --git a/src/services/note/create.ts b/src/services/note/create.ts index bc1225cd4d..9f79bf7a63 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -329,12 +329,16 @@ async function publish(user: User, note: Note, reply: Note, renote: Note, noteAc if (Users.isLocalUser(user)) { // 投稿がリプライかつ投稿者がローカルユーザーかつリプライ先の投稿の投稿者がリモートユーザーなら配送 if (reply && reply.userHost !== null) { - deliver(user, noteActivity, reply.userInbox); + Users.findOne(reply.userId).then(u => { + deliver(user, noteActivity, u.inbox); + }); } // 投稿がRenoteかつ投稿者がローカルユーザーかつRenote元の投稿の投稿者がリモートユーザーなら配送 if (renote && renote.userHost !== null) { - deliver(user, noteActivity, renote.userInbox); + Users.findOne(renote.userId).then(u => { + deliver(user, noteActivity, u.inbox); + }); } } @@ -377,7 +381,6 @@ async function insertNote(user: User, data: Option, tags: string[], emojis: stri renoteUserId: data.renote ? data.renote.userId : null, renoteUserHost: data.renote ? data.renote.userHost : null, userHost: user.host, - userInbox: user.inbox, }; if (data.uri != null) insert.uri = data.uri; diff --git a/src/services/note/reaction/create.ts b/src/services/note/reaction/create.ts index 1b026cc9cc..55f9bcd084 100644 --- a/src/services/note/reaction/create.ts +++ b/src/services/note/reaction/create.ts @@ -90,7 +90,9 @@ export default async (user: User, note: Note, reaction: string) => { // リアクターがローカルユーザーかつリアクション対象がリモートユーザーの投稿なら配送 if (Users.isLocalUser(user) && note.userHost !== null) { const content = renderActivity(renderLike(user, note, reaction)); - deliver(user, content, note.userInbox); + Users.findOne(note.userId).then(u => { + deliver(user, content, u.inbox); + }); } //#endregion }; diff --git a/src/services/note/reaction/delete.ts b/src/services/note/reaction/delete.ts index ce180aaeca..76f5ed631e 100644 --- a/src/services/note/reaction/delete.ts +++ b/src/services/note/reaction/delete.ts @@ -41,7 +41,9 @@ export default async (user: User, note: Note) => { // リアクターがローカルユーザーかつリアクション対象がリモートユーザーの投稿なら配送 if (Users.isLocalUser(user) && (note.userHost !== null)) { const content = renderActivity(renderUndo(renderLike(user, note, exist.reaction), user)); - deliver(user, content, note.userInbox); + Users.findOne(note.userId).then(u => { + deliver(user, content, u.inbox); + }); } //#endregion }; -- cgit v1.2.3-freya From d7df75ae6cdfd7743faf33e3bb4a64c8d6df3b78 Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 12 Apr 2019 01:01:25 +0900 Subject: Clean up --- src/models/entities/note.ts | 7 ------- src/remote/activitypub/models/question.ts | 4 ---- src/services/note/polls/update.ts | 18 ------------------ 3 files changed, 29 deletions(-) (limited to 'src/models') diff --git a/src/models/entities/note.ts b/src/models/entities/note.ts index 253d157b8c..176fc626b3 100644 --- a/src/models/entities/note.ts +++ b/src/models/entities/note.ts @@ -15,13 +15,6 @@ export class Note { }) public createdAt: Date; - @Index() - @Column('timestamp with time zone', { - nullable: true, - comment: 'The updated date of the Note.' - }) - public updatedAt: Date | null; - @Index() @Column({ ...id(), diff --git a/src/remote/activitypub/models/question.ts b/src/remote/activitypub/models/question.ts index 68f0cb3596..2ff8e21ab5 100644 --- a/src/remote/activitypub/models/question.ts +++ b/src/remote/activitypub/models/question.ts @@ -68,10 +68,6 @@ export async function updateQuestion(value: any) { } } - await Notes.update(note.id, { - updatedAt: new Date(), - }); - await Polls.update({ noteId: note.id }, { votes: poll.votes }); diff --git a/src/services/note/polls/update.ts b/src/services/note/polls/update.ts index ff8e8d59ef..beb35cc278 100644 --- a/src/services/note/polls/update.ts +++ b/src/services/note/polls/update.ts @@ -1,6 +1,3 @@ -import { updateQuestion } from '../../../remote/activitypub/models/question'; -import ms = require('ms'); -import Logger from '../../logger'; import renderUpdate from '../../../remote/activitypub/renderer/update'; import { renderActivity } from '../../../remote/activitypub/renderer'; import { deliver } from '../../../queue'; @@ -8,21 +5,6 @@ import renderNote from '../../../remote/activitypub/renderer/note'; import { Users, Notes, Followings } from '../../../models'; import { Note } from '../../../models/entities/note'; -const logger = new Logger('pollsUpdate'); - -export async function triggerUpdate(note: Note) { - if (!note.updatedAt || Date.now() - new Date(note.updatedAt).getTime() > ms('1min')) { - logger.info(`Updating ${note.id}`); - - try { - const updated = await updateQuestion(note.uri); - logger.info(`Updated ${note.id} ${updated ? 'changed' : 'nochange'}`); - } catch (e) { - logger.error(e); - } - } -} - export async function deliverQuestionUpdate(noteId: Note['id']) { const note = await Notes.findOne(noteId); -- cgit v1.2.3-freya From 2b6389b4dcb7f0393e8441981749df7a40d878d5 Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 12 Apr 2019 01:03:40 +0900 Subject: Fix bug --- src/models/repositories/user.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/models') diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts index beb570a310..ac18b18996 100644 --- a/src/models/repositories/user.ts +++ b/src/models/repositories/user.ts @@ -118,6 +118,8 @@ export class UserRepository extends Repository { } : {}), ...(opts.detail ? { + createdAt: user.createdAt, + updatedAt: user.updatedAt, description: profile.description, location: profile.location, birthday: profile.birthday, -- cgit v1.2.3-freya From 4198246351a64c3f3c0cd05bbd1e7146d95fcd32 Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 12 Apr 2019 01:30:10 +0900 Subject: トランザクションを使用してアンケートレコードの挿入に失敗した場合に投稿レコードの挿入もなかったことにするように MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/models/entities/note.ts | 8 ++++++++ src/models/entities/poll.ts | 8 ++++++++ src/services/note/create.ts | 42 +++++++++++++++++++++++++----------------- 3 files changed, 41 insertions(+), 17 deletions(-) (limited to 'src/models') diff --git a/src/models/entities/note.ts b/src/models/entities/note.ts index 176fc626b3..6c7aebdd0d 100644 --- a/src/models/entities/note.ts +++ b/src/models/entities/note.ts @@ -214,6 +214,14 @@ export class Note { }) public renoteUserHost: string | null; //#endregion + + constructor(data: Partial) { + if (data == null) return; + + for (const [k, v] of Object.entries(data)) { + (this as any)[k] = v; + } + } } export type IMentionedRemoteUsers = { diff --git a/src/models/entities/poll.ts b/src/models/entities/poll.ts index 894f07e689..c0ad5547bd 100644 --- a/src/models/entities/poll.ts +++ b/src/models/entities/poll.ts @@ -53,6 +53,14 @@ export class Poll { }) public userHost: string | null; //#endregion + + constructor(data: Partial) { + if (data == null) return; + + for (const [k, v] of Object.entries(data)) { + (this as any)[k] = v; + } + } } export type IPoll = { diff --git a/src/services/note/create.ts b/src/services/note/create.ts index 9f79bf7a63..b66f61ae8e 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -17,10 +17,10 @@ import extractMentions from '../../misc/extract-mentions'; import extractEmojis from '../../misc/extract-emojis'; import extractHashtags from '../../misc/extract-hashtags'; import { Note } from '../../models/entities/note'; -import { Mutings, Users, NoteWatchings, Followings, Notes, Instances, Polls, UserProfiles } from '../../models'; +import { Mutings, Users, NoteWatchings, Followings, Notes, Instances, UserProfiles } from '../../models'; import { DriveFile } from '../../models/entities/drive-file'; import { App } from '../../models/entities/app'; -import { Not } from 'typeorm'; +import { Not, getConnection } from 'typeorm'; import { User, ILocalUser, IRemoteUser } from '../../models/entities/user'; import { genId } from '../../misc/gen-id'; import { notesChart, perUserNotesChart, activeUsersChart, instanceChart } from '../chart'; @@ -349,7 +349,7 @@ async function publish(user: User, note: Note, reply: Note, renote: Note, noteAc } async function insertNote(user: User, data: Option, tags: string[], emojis: string[], mentionedUsers: User[]) { - const insert: Partial = { + const insert = new Note({ id: genId(data.createdAt), createdAt: data.createdAt, fileIds: data.files ? data.files.map(file => file.id) : [], @@ -381,7 +381,7 @@ async function insertNote(user: User, data: Option, tags: string[], emojis: stri renoteUserId: data.renote ? data.renote.userId : null, renoteUserHost: data.renote ? data.renote.userHost : null, userHost: user.host, - }; + }); if (data.uri != null) insert.uri = data.uri; @@ -397,19 +397,27 @@ async function insertNote(user: User, data: Option, tags: string[], emojis: stri // 投稿を作成 try { - const note = await Notes.save(insert); - - if (note.hasPoll) { - await Polls.save({ - noteId: note.id, - choices: data.poll.choices, - expiresAt: data.poll.expiresAt, - multiple: data.poll.multiple, - votes: new Array(data.poll.choices.length).fill(0), - noteVisibility: note.visibility, - userId: user.id, - userHost: user.host - } as Poll); + let note: Note; + if (insert.hasPoll) { + // Start transaction + await getConnection().transaction(async transactionalEntityManager => { + note = await transactionalEntityManager.save(insert); + + const poll = new Poll({ + noteId: note.id, + choices: data.poll.choices, + expiresAt: data.poll.expiresAt, + multiple: data.poll.multiple, + votes: new Array(data.poll.choices.length).fill(0), + noteVisibility: note.visibility, + userId: user.id, + userHost: user.host + }); + + await transactionalEntityManager.save(poll); + }); + } else { + note = await Notes.save(insert); } return note; -- cgit v1.2.3-freya From 2ff3069d23aa688cea5a3bd204236f2f2f64c201 Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 12 Apr 2019 01:52:25 +0900 Subject: トランザクションを使うようにしたり MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/common/views/deck/deck.user-column.vue | 2 +- src/client/app/desktop/views/home/user/index.vue | 2 +- src/client/app/mobile/views/pages/user/index.vue | 2 +- src/models/entities/user-keypair.ts | 8 +++ src/models/entities/user-profile.ts | 14 +++++ src/models/entities/user-publickey.ts | 8 +++ src/models/entities/user.ts | 8 +++ src/models/repositories/user.ts | 2 +- src/remote/activitypub/models/person.ts | 71 +++++++++++----------- src/server/api/private/signup.ts | 50 ++++++++------- src/server/web/index.ts | 7 ++- src/server/web/views/user.pug | 14 ++--- 12 files changed, 119 insertions(+), 69 deletions(-) (limited to 'src/models') diff --git a/src/client/app/common/views/deck/deck.user-column.vue b/src/client/app/common/views/deck/deck.user-column.vue index e18a111147..fb50d880eb 100644 --- a/src/client/app/common/views/deck/deck.user-column.vue +++ b/src/client/app/common/views/deck/deck.user-column.vue @@ -8,7 +8,7 @@
diff --git a/src/client/app/desktop/views/home/user/index.vue b/src/client/app/desktop/views/home/user/index.vue index 17a34a30cc..338cd1c59d 100644 --- a/src/client/app/desktop/views/home/user/index.vue +++ b/src/client/app/desktop/views/home/user/index.vue @@ -4,7 +4,7 @@ {{ $t('@.user-suspended') }}
- {{ $t('@.is-remote-user') }}{{ $t('@.view-on-remote') }} + {{ $t('@.is-remote-user') }}{{ $t('@.view-on-remote') }}
diff --git a/src/client/app/mobile/views/pages/user/index.vue b/src/client/app/mobile/views/pages/user/index.vue index 1376f39657..cc53ae0cfe 100644 --- a/src/client/app/mobile/views/pages/user/index.vue +++ b/src/client/app/mobile/views/pages/user/index.vue @@ -5,7 +5,7 @@

{{ $t('@.user-suspended') }}

-

{{ $t('@.is-remote-user') }}{{ $t('@.view-on-remote') }}

+

{{ $t('@.is-remote-user') }}{{ $t('@.view-on-remote') }}

diff --git a/src/models/entities/user-keypair.ts b/src/models/entities/user-keypair.ts index 808985f473..603321d758 100644 --- a/src/models/entities/user-keypair.ts +++ b/src/models/entities/user-keypair.ts @@ -22,4 +22,12 @@ export class UserKeypair { length: 4096, }) public privateKey: string; + + constructor(data: Partial) { + if (data == null) return; + + for (const [k, v] of Object.entries(data)) { + (this as any)[k] = v; + } + } } diff --git a/src/models/entities/user-profile.ts b/src/models/entities/user-profile.ts index 421d17c59e..a2d7b8d2c2 100644 --- a/src/models/entities/user-profile.ts +++ b/src/models/entities/user-profile.ts @@ -39,6 +39,12 @@ export class UserProfile { value: string; }[]; + @Column('varchar', { + length: 512, nullable: true, + comment: 'Remote URL of the user.' + }) + public url: string | null; + @Column('varchar', { length: 128, nullable: true, comment: 'The email address of the User.' @@ -192,4 +198,12 @@ export class UserProfile { }) public userHost: string | null; //#endregion + + constructor(data: Partial) { + if (data == null) return; + + for (const [k, v] of Object.entries(data)) { + (this as any)[k] = v; + } + } } diff --git a/src/models/entities/user-publickey.ts b/src/models/entities/user-publickey.ts index 26b694407d..21edc3e9e2 100644 --- a/src/models/entities/user-publickey.ts +++ b/src/models/entities/user-publickey.ts @@ -23,4 +23,12 @@ export class UserPublickey { length: 4096, }) public keyPem: string; + + constructor(data: Partial) { + if (data == null) return; + + for (const [k, v] of Object.entries(data)) { + (this as any)[k] = v; + } + } } diff --git a/src/models/entities/user.ts b/src/models/entities/user.ts index ebf07ff3ff..e40c32a76f 100644 --- a/src/models/entities/user.ts +++ b/src/models/entities/user.ts @@ -205,6 +205,14 @@ export class User { comment: 'The native access token of the User. It will be null if the origin of the user is local.' }) public token: string | null; + + constructor(data: Partial) { + if (data == null) return; + + for (const [k, v] of Object.entries(data)) { + (this as any)[k] = v; + } + } } export interface ILocalUser extends User { diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts index ac18b18996..d7f2c3d040 100644 --- a/src/models/repositories/user.ts +++ b/src/models/repositories/user.ts @@ -87,7 +87,6 @@ export class UserRepository extends Repository { name: user.name, username: user.username, host: user.host, - uri: user.uri, avatarUrl: user.avatarUrl, bannerUrl: user.bannerUrl, avatarColor: user.avatarColor, @@ -118,6 +117,7 @@ export class UserRepository extends Repository { } : {}), ...(opts.detail ? { + url: profile.url, createdAt: user.createdAt, updatedAt: user.updatedAt, description: profile.description, diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts index b2edf39734..58aca48eb0 100644 --- a/src/remote/activitypub/models/person.ts +++ b/src/remote/activitypub/models/person.ts @@ -25,6 +25,7 @@ import { isDuplicateKeyValueError } from '../../../misc/is-duplicate-key-value-e import { toPuny } from '../../../misc/convert-host'; import { UserProfile } from '../../../models/entities/user-profile'; import { validActor } from '../../../remote/activitypub/type'; +import { getConnection } from 'typeorm'; const logger = apLogger; /** @@ -136,27 +137,42 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise) as IRemoteUser; + // Start transaction + await getConnection().transaction(async transactionalEntityManager => { + user = await transactionalEntityManager.save(new User({ + id: genId(), + avatarId: null, + bannerId: null, + createdAt: new Date(person.published) || new Date(), + lastFetchedAt: new Date(), + name: person.name, + isLocked: person.manuallyApprovesFollowers, + username: person.preferredUsername, + usernameLower: person.preferredUsername.toLowerCase(), + host, + inbox: person.inbox, + sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined), + featured: person.featured, + uri: person.id, + tags, + isBot, + isCat: (person as any).isCat === true + })) as IRemoteUser; + + await transactionalEntityManager.save(new UserProfile({ + userId: user.id, + description: fromHtml(person.summary), + url: person.url, + fields, + userHost: host + })); + + await transactionalEntityManager.save(new UserPublickey({ + userId: user.id, + keyId: person.publicKey.id, + keyPem: person.publicKey.publicKeyPem + })); + }); } catch (e) { // duplicate key error if (isDuplicateKeyValueError(e)) { @@ -167,19 +183,6 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise); - - await UserPublickeys.save({ - userId: user.id, - keyId: person.publicKey.id, - keyPem: person.publicKey.publicKeyPem - } as UserPublickey); - // Register host registerOrFetchInstanceDoc(host).then(i => { Instances.increment({ id: i.id }, 'usersCount', 1); diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts index 5ed25fa411..ed4a533dec 100644 --- a/src/server/api/private/signup.ts +++ b/src/server/api/private/signup.ts @@ -12,6 +12,7 @@ import { User } from '../../../models/entities/user'; import { UserKeypair } from '../../../models/entities/user-keypair'; import { toPuny } from '../../../misc/convert-host'; import { UserProfile } from '../../../models/entities/user-profile'; +import { getConnection } from 'typeorm'; export default async (ctx: Koa.BaseContext) => { const body = ctx.request.body as any; @@ -99,28 +100,33 @@ export default async (ctx: Koa.BaseContext) => { e ? j(e) : s([publicKey, privateKey]) )); - const account = await Users.save({ - id: genId(), - createdAt: new Date(), - username: username, - usernameLower: username.toLowerCase(), - host: toPuny(host), - token: secret, - isAdmin: config.autoAdmin && usersCount === 0, - } as User); - - await UserKeypairs.save({ - publicKey: keyPair[0], - privateKey: keyPair[1], - userId: account.id - } as UserKeypair); - - await UserProfiles.save({ - userId: account.id, - autoAcceptFollowed: true, - autoWatch: false, - password: hash, - } as Partial); + let account: User; + + // Start transaction + await getConnection().transaction(async transactionalEntityManager => { + account = await transactionalEntityManager.save(new User({ + id: genId(), + createdAt: new Date(), + username: username, + usernameLower: username.toLowerCase(), + host: toPuny(host), + token: secret, + isAdmin: config.autoAdmin && usersCount === 0, + })); + + await transactionalEntityManager.save(new UserKeypair({ + publicKey: keyPair[0], + privateKey: keyPair[1], + userId: account.id + })); + + await transactionalEntityManager.save(new UserProfile({ + userId: account.id, + autoAcceptFollowed: true, + autoWatch: false, + password: hash, + })); + }); usersChart.update(account, true); diff --git a/src/server/web/index.ts b/src/server/web/index.ts index de0d65cf33..d1a15e3929 100644 --- a/src/server/web/index.ts +++ b/src/server/web/index.ts @@ -16,7 +16,7 @@ import fetchMeta from '../../misc/fetch-meta'; import * as pkg from '../../../package.json'; import { genOpenapiSpec } from '../api/openapi/gen-spec'; import config from '../../config'; -import { Users, Notes, Emojis } from '../../models'; +import { Users, Notes, Emojis, UserProfiles } from '../../models'; import parseAcct from '../../misc/acct/parse'; import getNoteSummary from '../../misc/get-note-summary'; @@ -149,11 +149,14 @@ router.get('/@:user', async (ctx, next) => { usernameLower: username.toLowerCase(), host }); + const profile = await UserProfiles.findOne({ + userId: user.id + }); if (user != null) { const meta = await fetchMeta(); await ctx.render('user', { - user, + user, profile, instanceName: meta.name || 'Misskey' }); ctx.set('Cache-Control', 'public, max-age=180'); diff --git a/src/server/web/views/user.pug b/src/server/web/views/user.pug index 3f32933f52..bff98ba80f 100644 --- a/src/server/web/views/user.pug +++ b/src/server/web/views/user.pug @@ -9,12 +9,12 @@ block title = `${title} | ${instanceName}` block desc - meta(name='description' content= user.description) + meta(name='description' content= profile.description) block og meta(property='og:type' content='blog') meta(property='og:title' content= title) - meta(property='og:description' content= user.description) + meta(property='og:description' content= profile.description) meta(property='og:url' content= url) meta(property='og:image' content= img) @@ -24,12 +24,12 @@ block meta meta(name='twitter:card' content='summary') - if user.twitter - meta(name='twitter:creator' content=`@${user.twitter.screenName}`) + if profile.twitter + meta(name='twitter:creator' content=`@${profile.twitter.screenName}`) if !user.host - link(rel='alternate' href=`${config.url}/users/${user._id}` type='application/activity+json') + link(rel='alternate' href=`${config.url}/users/${user.id}` type='application/activity+json') if user.uri link(rel='alternate' href=user.uri type='application/activity+json') - if user.url - link(rel='alternate' href=user.url type='text/html') + if profile.url + link(rel='alternate' href=profile.url type='text/html') -- cgit v1.2.3-freya From 4ee40c33457b929caa654b60a98ad460ea673144 Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 12 Apr 2019 14:24:56 +0900 Subject: Fix bug --- src/models/repositories/note.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'src/models') diff --git a/src/models/repositories/note.ts b/src/models/repositories/note.ts index 80514c3752..01bf8cf87c 100644 --- a/src/models/repositories/note.ts +++ b/src/models/repositories/note.ts @@ -182,6 +182,7 @@ export class NoteRepository extends Repository { files: DriveFiles.packMany(note.fileIds), replyId: note.replyId, renoteId: note.renoteId, + uri: note.uri, ...(opts.detail ? { reply: note.replyId ? this.pack(note.replyId, meId, { -- cgit v1.2.3-freya From 987168b863c52d0548050ffbac569782bb9a8cef Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 13 Apr 2019 01:43:22 +0900 Subject: strictNullChecks (#4666) * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip --- gulpfile.ts | 2 +- package.json | 2 +- src/boot/master.ts | 2 +- src/boot/worker.ts | 2 +- src/config/load.ts | 2 +- src/daemons/notes-stats-child.ts | 2 +- src/db/postgre.ts | 4 +- src/games/reversi/core.ts | 16 +-- src/mfm/fromHtml.ts | 2 - src/mfm/parse.ts | 4 +- src/mfm/toHtml.ts | 2 +- src/misc/acct/type.ts | 2 +- src/misc/convert-host.ts | 3 +- src/misc/detect-mine.ts | 2 +- src/misc/donwload-url.ts | 3 +- src/misc/fetch-meta.ts | 1 - src/misc/fetch-proxy-account.ts | 3 +- src/misc/identifiable-error.ts | 2 +- src/misc/nyaize.ts | 4 +- src/misc/schema.ts | 4 +- src/models/entities/poll.ts | 2 +- src/models/repositories/abuse-user-report.ts | 3 +- src/models/repositories/app.ts | 3 +- src/models/repositories/auth-session.ts | 3 +- src/models/repositories/blocking.ts | 3 +- src/models/repositories/drive-file.ts | 5 +- src/models/repositories/drive-folder.ts | 3 +- src/models/repositories/follow-request.ts | 3 +- src/models/repositories/following.ts | 43 ++++++- src/models/repositories/games/reversi/game.ts | 3 +- src/models/repositories/games/reversi/matching.ts | 3 +- src/models/repositories/messaging-message.ts | 3 +- src/models/repositories/muting.ts | 3 +- src/models/repositories/note-favorite.ts | 3 +- src/models/repositories/note-reaction.ts | 3 +- src/models/repositories/note.ts | 17 +-- src/models/repositories/notification.ts | 15 +-- src/models/repositories/user-list.ts | 5 +- src/models/repositories/user.ts | 29 ++--- src/prelude/ensure.ts | 7 ++ src/queue/index.ts | 4 +- src/queue/processors/db/delete-drive-files.ts | 8 +- src/queue/processors/db/export-blocking.ts | 12 +- src/queue/processors/db/export-following.ts | 12 +- src/queue/processors/db/export-mute.ts | 12 +- src/queue/processors/db/export-notes.ts | 15 ++- src/queue/processors/db/export-user-lists.ts | 8 +- src/queue/processors/db/import-following.ts | 16 ++- src/queue/processors/db/import-user-lists.ts | 22 ++-- src/queue/processors/deliver.ts | 2 +- src/queue/processors/inbox.ts | 9 +- src/remote/activitypub/kernel/accept/follow.ts | 3 +- src/remote/activitypub/kernel/add/index.ts | 1 + src/remote/activitypub/kernel/announce/note.ts | 9 +- src/remote/activitypub/kernel/block/index.ts | 3 +- src/remote/activitypub/kernel/follow.ts | 3 +- src/remote/activitypub/kernel/index.ts | 2 +- src/remote/activitypub/kernel/like.ts | 1 + src/remote/activitypub/kernel/reject/follow.ts | 3 +- src/remote/activitypub/kernel/remove/index.ts | 1 + src/remote/activitypub/kernel/undo/block.ts | 3 +- src/remote/activitypub/kernel/undo/follow.ts | 3 +- src/remote/activitypub/kernel/undo/index.ts | 2 - src/remote/activitypub/kernel/undo/like.ts | 1 + src/remote/activitypub/models/image.ts | 17 +-- src/remote/activitypub/models/note.ts | 130 +++++++++++---------- src/remote/activitypub/models/person.ts | 81 ++++++------- src/remote/activitypub/models/question.ts | 8 +- src/remote/activitypub/models/tag.ts | 8 +- src/remote/activitypub/renderer/block.ts | 2 +- src/remote/activitypub/renderer/follow-user.ts | 3 +- src/remote/activitypub/renderer/note.ts | 19 ++- .../renderer/ordered-collection-page.ts | 2 +- src/remote/activitypub/renderer/person.ts | 15 ++- src/remote/activitypub/request.ts | 9 +- src/remote/resolve-user.ts | 31 ++++- src/remote/webfinger.ts | 2 +- src/server/activitypub.ts | 13 +-- src/server/activitypub/featured.ts | 6 +- src/server/activitypub/followers.ts | 6 +- src/server/activitypub/following.ts | 6 +- src/server/activitypub/outbox.ts | 9 +- src/server/api/api-handler.ts | 14 +-- src/server/api/authenticate.ts | 2 +- src/server/api/call.ts | 10 +- src/server/api/common/make-pagination-query.ts | 2 +- src/server/api/define.ts | 10 +- .../api/endpoints/admin/abuse-user-reports.ts | 2 +- src/server/api/endpoints/admin/drive/files.ts | 4 +- src/server/api/endpoints/admin/emoji/list.ts | 2 +- .../admin/federation/remove-all-following.ts | 7 +- src/server/api/endpoints/admin/logs.ts | 2 +- src/server/api/endpoints/admin/queue/jobs.ts | 4 +- src/server/api/endpoints/admin/show-users.ts | 2 +- .../api/endpoints/admin/update-remote-user.ts | 2 +- src/server/api/endpoints/ap/show.ts | 4 +- src/server/api/endpoints/auth/accept.ts | 3 +- src/server/api/endpoints/auth/session/userkey.ts | 3 +- src/server/api/endpoints/blocking/list.ts | 2 +- src/server/api/endpoints/charts/active-users.ts | 2 +- src/server/api/endpoints/charts/drive.ts | 2 +- src/server/api/endpoints/charts/federation.ts | 2 +- src/server/api/endpoints/charts/hashtag.ts | 2 +- src/server/api/endpoints/charts/instance.ts | 2 +- src/server/api/endpoints/charts/network.ts | 2 +- src/server/api/endpoints/charts/notes.ts | 2 +- src/server/api/endpoints/charts/user/drive.ts | 2 +- src/server/api/endpoints/charts/user/following.ts | 2 +- src/server/api/endpoints/charts/user/notes.ts | 2 +- src/server/api/endpoints/charts/user/reactions.ts | 2 +- src/server/api/endpoints/charts/users.ts | 2 +- src/server/api/endpoints/drive/files.ts | 2 +- src/server/api/endpoints/drive/files/create.ts | 2 +- src/server/api/endpoints/drive/files/show.ts | 10 +- src/server/api/endpoints/drive/folders.ts | 2 +- src/server/api/endpoints/drive/folders/update.ts | 6 +- src/server/api/endpoints/drive/stream.ts | 2 +- src/server/api/endpoints/federation/instances.ts | 2 +- src/server/api/endpoints/games/reversi/games.ts | 2 +- src/server/api/endpoints/games/reversi/match.ts | 4 +- src/server/api/endpoints/hashtags/list.ts | 2 +- src/server/api/endpoints/hashtags/search.ts | 2 +- src/server/api/endpoints/hashtags/users.ts | 2 +- src/server/api/endpoints/i/2fa/done.ts | 3 +- src/server/api/endpoints/i/2fa/register.ts | 5 +- src/server/api/endpoints/i/2fa/unregister.ts | 5 +- src/server/api/endpoints/i/authorized-apps.ts | 2 +- src/server/api/endpoints/i/change-password.ts | 5 +- src/server/api/endpoints/i/delete-account.ts | 5 +- src/server/api/endpoints/i/favorites.ts | 2 +- src/server/api/endpoints/i/notifications.ts | 6 +- src/server/api/endpoints/i/regenerate-token.ts | 5 +- src/server/api/endpoints/i/signin-history.ts | 2 +- src/server/api/endpoints/i/update-email.ts | 5 +- src/server/api/endpoints/i/update.ts | 6 +- src/server/api/endpoints/messaging/history.ts | 2 +- src/server/api/endpoints/mute/list.ts | 2 +- src/server/api/endpoints/my/apps.ts | 2 +- src/server/api/endpoints/notes.ts | 2 +- src/server/api/endpoints/notes/children.ts | 2 +- src/server/api/endpoints/notes/conversation.ts | 5 +- src/server/api/endpoints/notes/create.ts | 27 ++--- src/server/api/endpoints/notes/delete.ts | 3 +- src/server/api/endpoints/notes/featured.ts | 2 +- src/server/api/endpoints/notes/global-timeline.ts | 2 +- src/server/api/endpoints/notes/hybrid-timeline.ts | 2 +- src/server/api/endpoints/notes/local-timeline.ts | 8 +- src/server/api/endpoints/notes/mentions.ts | 2 +- .../api/endpoints/notes/polls/recommendation.ts | 2 +- src/server/api/endpoints/notes/polls/vote.ts | 7 +- src/server/api/endpoints/notes/reactions.ts | 2 +- src/server/api/endpoints/notes/renotes.ts | 2 +- src/server/api/endpoints/notes/replies.ts | 2 +- src/server/api/endpoints/notes/search-by-tag.ts | 4 +- src/server/api/endpoints/notes/search.ts | 2 +- src/server/api/endpoints/notes/timeline.ts | 2 +- .../api/endpoints/notes/user-list-timeline.ts | 2 +- src/server/api/endpoints/users.ts | 2 +- src/server/api/endpoints/users/followers.ts | 4 +- src/server/api/endpoints/users/following.ts | 4 +- .../users/get-frequently-replied-users.ts | 2 +- src/server/api/endpoints/users/notes.ts | 8 +- src/server/api/endpoints/users/recommendation.ts | 2 +- src/server/api/endpoints/users/search.ts | 6 +- src/server/api/endpoints/users/show.ts | 6 +- src/server/api/error.ts | 4 +- src/server/api/limiter.ts | 6 +- src/server/api/private/signin.ts | 5 +- src/server/api/private/signup.ts | 4 +- src/server/api/service/discord.ts | 91 ++++++++------- src/server/api/service/github.ts | 45 ++++--- src/server/api/service/twitter.ts | 25 ++-- src/server/api/stream/channels/admin.ts | 2 +- src/server/api/stream/channels/drive.ts | 2 +- .../api/stream/channels/games/reversi-game.ts | 80 +++++++------ src/server/api/stream/channels/games/reversi.ts | 4 +- src/server/api/stream/channels/home-timeline.ts | 8 +- src/server/api/stream/channels/hybrid-timeline.ts | 10 +- src/server/api/stream/channels/main.ts | 4 +- src/server/api/stream/channels/messaging-index.ts | 2 +- src/server/api/stream/channels/messaging.ts | 4 +- src/server/api/stream/index.ts | 20 ++-- src/server/file/send-drive-file.ts | 2 +- src/server/index.ts | 2 +- src/server/web/docs.ts | 6 +- src/server/web/feed.ts | 9 +- src/server/web/index.ts | 5 +- src/server/web/url-preview.ts | 2 +- src/server/well-known.ts | 4 +- src/services/blocking/create.ts | 2 +- src/services/chart/core.ts | 36 +++--- src/services/create-notification.ts | 1 + src/services/drive/add-file.ts | 36 +++--- src/services/drive/delete-file.ts | 16 +-- src/services/drive/image-processor.ts | 2 +- src/services/drive/upload-from-url.ts | 11 +- src/services/following/create.ts | 3 +- src/services/following/requests/accept-all.ts | 3 +- src/services/following/requests/accept.ts | 2 +- src/services/following/requests/reject.ts | 2 +- src/services/i/pin.ts | 15 +-- src/services/i/update.ts | 7 +- src/services/logger.ts | 16 +-- src/services/note/create.ts | 101 ++++++++-------- src/services/note/polls/update.ts | 11 +- src/services/note/polls/vote.ts | 16 +-- src/services/note/reaction/create.ts | 4 +- src/services/note/reaction/delete.ts | 2 +- src/services/push-notification.ts | 23 ++-- src/services/register-or-fetch-instance-doc.ts | 2 - src/services/stream.ts | 6 +- src/tools/clean-remote-files.ts | 3 +- src/tools/show-signin-history.ts | 4 +- tsconfig.json | 3 +- 214 files changed, 940 insertions(+), 786 deletions(-) create mode 100644 src/prelude/ensure.ts (limited to 'src/models') diff --git a/gulpfile.ts b/gulpfile.ts index b2956c2403..2242843db1 100644 --- a/gulpfile.ts +++ b/gulpfile.ts @@ -120,7 +120,7 @@ gulp.task('copy:client', () => ]) .pipe(isProduction ? (imagemin as any)() : gutil.noop()) .pipe(rename(path => { - path.dirname = path.dirname.replace('assets', '.'); + path.dirname = path.dirname!.replace('assets', '.'); })) .pipe(gulp.dest('./built/client/assets/')) ); diff --git a/package.json b/package.json index e89f98043c..e08a49462d 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,7 @@ "bcryptjs": "2.4.3", "bootstrap-vue": "2.0.0-rc.13", "bull": "3.7.0", - "cafy": "15.1.0", + "cafy": "15.1.1", "chai": "4.2.0", "chai-http": "4.2.1", "chalk": "2.4.2", diff --git a/src/boot/master.ts b/src/boot/master.ts index 2d4080fdb0..4d360c7265 100644 --- a/src/boot/master.ts +++ b/src/boot/master.ts @@ -44,7 +44,7 @@ function greet() { export async function masterMain() { greet(); - let config: Config; + let config!: Config; try { // initialize app diff --git a/src/boot/worker.ts b/src/boot/worker.ts index ca3716972a..362fa3f26b 100644 --- a/src/boot/worker.ts +++ b/src/boot/worker.ts @@ -15,6 +15,6 @@ export async function workerMain() { if (cluster.isWorker) { // Send a 'ready' message to parent process - process.send('ready'); + process.send!('ready'); } } diff --git a/src/config/load.ts b/src/config/load.ts index 6200faf12a..26b25eab4e 100644 --- a/src/config/load.ts +++ b/src/config/load.ts @@ -29,7 +29,7 @@ export default function load() { config.url = url.origin; - config.port = config.port || parseInt(process.env.PORT, 10); + config.port = config.port || parseInt(process.env.PORT || '', 10); mixin.host = url.host; mixin.hostname = url.hostname; diff --git a/src/daemons/notes-stats-child.ts b/src/daemons/notes-stats-child.ts index c491aed4cd..b60f5badfd 100644 --- a/src/daemons/notes-stats-child.ts +++ b/src/daemons/notes-stats-child.ts @@ -19,7 +19,7 @@ initDb().then(() => { all, local }; - process.send(stats); + process.send!(stats); } tick(); diff --git a/src/db/postgre.ts b/src/db/postgre.ts index e5726e9c87..641a552c09 100644 --- a/src/db/postgre.ts +++ b/src/db/postgre.ts @@ -76,7 +76,7 @@ class MyCustomLogger implements Logger { } export function initDb(justBorrow = false, sync = false, log = false) { - const enableLogging = log || !['production', 'test'].includes(process.env.NODE_ENV); + const enableLogging = log || !['production', 'test'].includes(process.env.NODE_ENV || ''); try { const conn = getConnection(); @@ -93,7 +93,7 @@ export function initDb(justBorrow = false, sync = false, log = false) { synchronize: process.env.NODE_ENV === 'test' || sync, dropSchema: process.env.NODE_ENV === 'test' && !justBorrow, logging: enableLogging, - logger: enableLogging ? new MyCustomLogger() : null, + logger: enableLogging ? new MyCustomLogger() : undefined, entities: [ Meta, Instance, diff --git a/src/games/reversi/core.ts b/src/games/reversi/core.ts index bb27d6f803..cf8986263b 100644 --- a/src/games/reversi/core.ts +++ b/src/games/reversi/core.ts @@ -37,7 +37,7 @@ export type Undo = { /** * ターン */ - turn: Color; + turn: Color | null; }; /** @@ -47,12 +47,12 @@ export default class Reversi { public map: MapPixel[]; public mapWidth: number; public mapHeight: number; - public board: Color[]; - public turn: Color = BLACK; + public board: (Color | null | undefined)[]; + public turn: Color | null = BLACK; public opts: Options; public prevPos = -1; - public prevColor: Color = null; + public prevColor: Color | null = null; private logs: Undo[] = []; @@ -145,12 +145,12 @@ export default class Reversi { // ターン計算 this.turn = this.canPutSomewhere(!this.prevColor) ? !this.prevColor : - this.canPutSomewhere(this.prevColor) ? this.prevColor : + this.canPutSomewhere(this.prevColor!) ? this.prevColor : null; } public undo() { - const undo = this.logs.pop(); + const undo = this.logs.pop()!; this.prevColor = undo.color; this.prevPos = undo.pos; this.board[undo.pos] = null; @@ -254,10 +254,10 @@ export default class Reversi { /** * ゲームの勝者 (null = 引き分け) */ - public get winner(): Color { + public get winner(): Color | null { return this.isEnded ? this.blackCount == this.whiteCount ? null : this.opts.isLlotheo === this.blackCount > this.whiteCount ? WHITE : BLACK : - undefined; + undefined as never; } } diff --git a/src/mfm/fromHtml.ts b/src/mfm/fromHtml.ts index 369f9de0ee..5fc4a16416 100644 --- a/src/mfm/fromHtml.ts +++ b/src/mfm/fromHtml.ts @@ -3,8 +3,6 @@ import { URL } from 'url'; import { urlRegex } from './prelude'; export function fromHtml(html: string): string { - if (html == null) return null; - const dom = parseFragment(html) as DefaultTreeDocumentFragment; let text = ''; diff --git a/src/mfm/parse.ts b/src/mfm/parse.ts index 9d60771708..f8464121f3 100644 --- a/src/mfm/parse.ts +++ b/src/mfm/parse.ts @@ -2,7 +2,7 @@ import { mfmLanguage } from './language'; import { MfmForest } from './prelude'; import { normalize } from './normalize'; -export function parse(source: string): MfmForest { +export function parse(source: string | null): MfmForest | null { if (source == null || source == '') { return null; } @@ -10,7 +10,7 @@ export function parse(source: string): MfmForest { return normalize(mfmLanguage.root.tryParse(source)); } -export function parsePlain(source: string): MfmForest { +export function parsePlain(source: string | null): MfmForest | null { if (source == null || source == '') { return null; } diff --git a/src/mfm/toHtml.ts b/src/mfm/toHtml.ts index 3cd7987624..58976fc2c3 100644 --- a/src/mfm/toHtml.ts +++ b/src/mfm/toHtml.ts @@ -4,7 +4,7 @@ import { intersperse } from '../prelude/array'; import { MfmForest, MfmTree } from './prelude'; import { IMentionedRemoteUsers } from '../models/entities/note'; -export function toHtml(tokens: MfmForest, mentionedRemoteUsers: IMentionedRemoteUsers = []) { +export function toHtml(tokens: MfmForest | null, mentionedRemoteUsers: IMentionedRemoteUsers = []) { if (tokens == null) { return null; } diff --git a/src/misc/acct/type.ts b/src/misc/acct/type.ts index c88a920c69..7f31257400 100644 --- a/src/misc/acct/type.ts +++ b/src/misc/acct/type.ts @@ -1,6 +1,6 @@ type Acct = { username: string; - host: string; + host: string | null; }; export default Acct; diff --git a/src/misc/convert-host.ts b/src/misc/convert-host.ts index f7feebd55c..dbf7864550 100644 --- a/src/misc/convert-host.ts +++ b/src/misc/convert-host.ts @@ -2,7 +2,7 @@ import config from '../config'; import { toASCII } from 'punycode'; import { URL } from 'url'; -export function getFullApAccount(username: string, host: string) { +export function getFullApAccount(username: string, host: string | null) { return host ? `${username}@${toPuny(host)}` : `${username}@${toPuny(config.host)}`; } @@ -17,6 +17,5 @@ export function extractDbHost(uri: string) { } export function toPuny(host: string) { - if (host == null) return null; return toASCII(host.toLowerCase()); } diff --git a/src/misc/detect-mine.ts b/src/misc/detect-mine.ts index bbf49efc10..70d58ffe21 100644 --- a/src/misc/detect-mine.ts +++ b/src/misc/detect-mine.ts @@ -3,7 +3,7 @@ import fileType from 'file-type'; import checkSvg from '../misc/check-svg'; export async function detectMine(path: string) { - return new Promise<[string, string]>((res, rej) => { + return new Promise<[string, string | null]>((res, rej) => { const readable = fs.createReadStream(path); readable .on('error', rej) diff --git a/src/misc/donwload-url.ts b/src/misc/donwload-url.ts index 0dd4e4ef5d..167e01fdd1 100644 --- a/src/misc/donwload-url.ts +++ b/src/misc/donwload-url.ts @@ -1,5 +1,4 @@ import * as fs from 'fs'; -import * as URL from 'url'; import * as request from 'request'; import config from '../config'; import chalk from 'chalk'; @@ -26,7 +25,7 @@ export async function downloadUrl(url: string, path: string) { rej(error); }); - const requestUrl = URL.parse(url).pathname.match(/[^\u0021-\u00ff]/) ? encodeURI(url) : url; + const requestUrl = new URL(url).pathname.match(/[^\u0021-\u00ff]/) ? encodeURI(url) : url; const req = request({ url: requestUrl, diff --git a/src/misc/fetch-meta.ts b/src/misc/fetch-meta.ts index d1483e9edb..cf1fc474ce 100644 --- a/src/misc/fetch-meta.ts +++ b/src/misc/fetch-meta.ts @@ -9,7 +9,6 @@ export default async function(): Promise { } else { return Metas.save({ id: genId(), - hiddenTags: [] } as Meta); } } diff --git a/src/misc/fetch-proxy-account.ts b/src/misc/fetch-proxy-account.ts index d60fa9b313..17b021e91e 100644 --- a/src/misc/fetch-proxy-account.ts +++ b/src/misc/fetch-proxy-account.ts @@ -1,8 +1,9 @@ import fetchMeta from './fetch-meta'; import { ILocalUser } from '../models/entities/user'; import { Users } from '../models'; +import { ensure } from '../prelude/ensure'; export async function fetchProxyAccount(): Promise { const meta = await fetchMeta(); - return await Users.findOne({ username: meta.proxyAccount, host: null }) as ILocalUser; + return await Users.findOne({ username: meta.proxyAccount!, host: null }).then(ensure) as ILocalUser; } diff --git a/src/misc/identifiable-error.ts b/src/misc/identifiable-error.ts index 1edd26cd18..2d7c6bd0c6 100644 --- a/src/misc/identifiable-error.ts +++ b/src/misc/identifiable-error.ts @@ -7,7 +7,7 @@ export class IdentifiableError extends Error { constructor(id: string, message?: string) { super(message); - this.message = message; + this.message = message || ''; this.id = id; } } diff --git a/src/misc/nyaize.ts b/src/misc/nyaize.ts index 8b06300eab..918e7d63fd 100644 --- a/src/misc/nyaize.ts +++ b/src/misc/nyaize.ts @@ -3,7 +3,7 @@ export function nyaize(text: string): string { // ja-JP .replace(/な/g, 'にゃ').replace(/ナ/g, 'ニャ').replace(/ナ/g, 'ニャ') // ko-KR - .replace(/[나-낳]/g, (match: string) => String.fromCharCode( - match.codePointAt(0) + '냐'.charCodeAt(0) - '나'.charCodeAt(0) + .replace(/[나-낳]/g, match => String.fromCharCode( + match.codePointAt(0)! + '냐'.charCodeAt(0) - '나'.charCodeAt(0) )); } diff --git a/src/misc/schema.ts b/src/misc/schema.ts index e5c24dd468..7c17953d97 100644 --- a/src/misc/schema.ts +++ b/src/misc/schema.ts @@ -19,8 +19,8 @@ type MyType = { export type SchemaType

= p['type'] extends 'number' ? number : p['type'] extends 'string' ? string : - p['type'] extends 'array' ? MyType[] : - p['type'] extends 'object' ? ObjType : + p['type'] extends 'array' ? MyType>[] : + p['type'] extends 'object' ? ObjType> : any; export function convertOpenApiSchema(schema: Schema) { diff --git a/src/models/entities/poll.ts b/src/models/entities/poll.ts index c0ad5547bd..6bb67163a2 100644 --- a/src/models/entities/poll.ts +++ b/src/models/entities/poll.ts @@ -67,5 +67,5 @@ export type IPoll = { choices: string[]; votes?: number[]; multiple: boolean; - expiresAt: Date; + expiresAt: Date | null; }; diff --git a/src/models/repositories/abuse-user-report.ts b/src/models/repositories/abuse-user-report.ts index c72a582c04..f619d6e37f 100644 --- a/src/models/repositories/abuse-user-report.ts +++ b/src/models/repositories/abuse-user-report.ts @@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { Users } from '..'; import rap from '@prezzemolo/rap'; import { AbuseUserReport } from '../entities/abuse-user-report'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(AbuseUserReport) export class AbuseUserReportRepository extends Repository { @@ -14,7 +15,7 @@ export class AbuseUserReportRepository extends Repository { public async pack( src: AbuseUserReport['id'] | AbuseUserReport, ) { - const report = typeof src === 'object' ? src : await this.findOne(src); + const report = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return await rap({ id: report.id, diff --git a/src/models/repositories/app.ts b/src/models/repositories/app.ts index 2e3323baf8..a0c0cf68cb 100644 --- a/src/models/repositories/app.ts +++ b/src/models/repositories/app.ts @@ -1,6 +1,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { App } from '../entities/app'; import { AccessTokens } from '..'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(App) export class AppRepository extends Repository { @@ -19,7 +20,7 @@ export class AppRepository extends Repository { includeProfileImageIds: false }, options); - const app = typeof src === 'object' ? src : await this.findOne(src); + const app = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return { id: app.id, diff --git a/src/models/repositories/auth-session.ts b/src/models/repositories/auth-session.ts index 76e3ddf9ab..540c5466f5 100644 --- a/src/models/repositories/auth-session.ts +++ b/src/models/repositories/auth-session.ts @@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { Apps } from '..'; import rap from '@prezzemolo/rap'; import { AuthSession } from '../entities/auth-session'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(AuthSession) export class AuthSessionRepository extends Repository { @@ -9,7 +10,7 @@ export class AuthSessionRepository extends Repository { src: AuthSession['id'] | AuthSession, me?: any ) { - const session = typeof src === 'object' ? src : await this.findOne(src); + const session = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return await rap({ id: session.id, diff --git a/src/models/repositories/blocking.ts b/src/models/repositories/blocking.ts index 81f3866131..e18aa591f3 100644 --- a/src/models/repositories/blocking.ts +++ b/src/models/repositories/blocking.ts @@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { Users } from '..'; import rap from '@prezzemolo/rap'; import { Blocking } from '../entities/blocking'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(Blocking) export class BlockingRepository extends Repository { @@ -16,7 +17,7 @@ export class BlockingRepository extends Repository { src: Blocking['id'] | Blocking, me?: any ) { - const blocking = typeof src === 'object' ? src : await this.findOne(src); + const blocking = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return await rap({ id: blocking.id, diff --git a/src/models/repositories/drive-file.ts b/src/models/repositories/drive-file.ts index 817677fa3b..003e350713 100644 --- a/src/models/repositories/drive-file.ts +++ b/src/models/repositories/drive-file.ts @@ -4,6 +4,7 @@ import { Users, DriveFolders } from '..'; import rap from '@prezzemolo/rap'; import { User } from '../entities/user'; import { toPuny } from '../../misc/convert-host'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(DriveFile) export class DriveFileRepository extends Repository { @@ -91,7 +92,7 @@ export class DriveFileRepository extends Repository { self: false }, options); - const file = typeof src === 'object' ? src : await this.findOne(src); + const file = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return await rap({ id: file.id, @@ -108,7 +109,7 @@ export class DriveFileRepository extends Repository { folder: opts.detail && file.folderId ? DriveFolders.pack(file.folderId, { detail: true }) : null, - user: opts.withUser ? Users.pack(file.userId) : null + user: opts.withUser ? Users.pack(file.userId!) : null }); } } diff --git a/src/models/repositories/drive-folder.ts b/src/models/repositories/drive-folder.ts index faf0f353aa..ce88adefa4 100644 --- a/src/models/repositories/drive-folder.ts +++ b/src/models/repositories/drive-folder.ts @@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { DriveFolders, DriveFiles } from '..'; import rap from '@prezzemolo/rap'; import { DriveFolder } from '../entities/drive-folder'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(DriveFolder) export class DriveFolderRepository extends Repository { @@ -22,7 +23,7 @@ export class DriveFolderRepository extends Repository { detail: false }, options); - const folder = typeof src === 'object' ? src : await this.findOne(src); + const folder = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return await rap({ id: folder.id, diff --git a/src/models/repositories/follow-request.ts b/src/models/repositories/follow-request.ts index bead093b21..451ed8e2d5 100644 --- a/src/models/repositories/follow-request.ts +++ b/src/models/repositories/follow-request.ts @@ -1,6 +1,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { FollowRequest } from '../entities/follow-request'; import { Users } from '..'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(FollowRequest) export class FollowRequestRepository extends Repository { @@ -8,7 +9,7 @@ export class FollowRequestRepository extends Repository { src: FollowRequest['id'] | FollowRequest, me?: any ) { - const request = typeof src === 'object' ? src : await this.findOne(src); + const request = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return { id: request.id, diff --git a/src/models/repositories/following.ts b/src/models/repositories/following.ts index 02253d272d..3fff57866f 100644 --- a/src/models/repositories/following.ts +++ b/src/models/repositories/following.ts @@ -2,9 +2,50 @@ import { EntityRepository, Repository } from 'typeorm'; import { Users } from '..'; import rap from '@prezzemolo/rap'; import { Following } from '../entities/following'; +import { ensure } from '../../prelude/ensure'; + +type LocalFollowerFollowing = Following & { + followerHost: null; + followerInbox: null; + followerSharedInbox: null; +}; + +type RemoteFollowerFollowing = Following & { + followerHost: string; + followerInbox: string; + followerSharedInbox: string; +}; + +type LocalFolloweeFollowing = Following & { + followeeHost: null; + followeeInbox: null; + followeeSharedInbox: null; +}; + +type RemoteFolloweeFollowing = Following & { + followeeHost: string; + followeeInbox: string; + followeeSharedInbox: string; +}; @EntityRepository(Following) export class FollowingRepository extends Repository { + public isLocalFollower(following: Following): following is LocalFollowerFollowing { + return following.followerHost == null; + } + + public isRemoteFollower(following: Following): following is RemoteFollowerFollowing { + return following.followerHost != null; + } + + public isLocalFollowee(following: Following): following is LocalFolloweeFollowing { + return following.followeeHost == null; + } + + public isRemoteFollowee(following: Following): following is RemoteFolloweeFollowing { + return following.followeeHost != null; + } + public packMany( followings: any[], me?: any, @@ -24,7 +65,7 @@ export class FollowingRepository extends Repository { populateFollower?: boolean; } ) { - const following = typeof src === 'object' ? src : await this.findOne(src); + const following = typeof src === 'object' ? src : await this.findOne(src).then(ensure); if (opts == null) opts = {}; diff --git a/src/models/repositories/games/reversi/game.ts b/src/models/repositories/games/reversi/game.ts index f0cb6ff905..c380f5251e 100644 --- a/src/models/repositories/games/reversi/game.ts +++ b/src/models/repositories/games/reversi/game.ts @@ -1,6 +1,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { Users } from '../../..'; import { ReversiGame } from '../../../entities/games/reversi/game'; +import { ensure } from '../../../../prelude/ensure'; @EntityRepository(ReversiGame) export class ReversiGameRepository extends Repository { @@ -15,7 +16,7 @@ export class ReversiGameRepository extends Repository { detail: true }, options); - const game = typeof src === 'object' ? src : await this.findOne(src); + const game = typeof src === 'object' ? src : await this.findOne(src).then(ensure); const meId = me ? typeof me === 'string' ? me : me.id : null; return { diff --git a/src/models/repositories/games/reversi/matching.ts b/src/models/repositories/games/reversi/matching.ts index 3612ac5c47..4d99c6ef76 100644 --- a/src/models/repositories/games/reversi/matching.ts +++ b/src/models/repositories/games/reversi/matching.ts @@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm'; import rap from '@prezzemolo/rap'; import { ReversiMatching } from '../../../entities/games/reversi/matching'; import { Users } from '../../..'; +import { ensure } from '../../../../prelude/ensure'; @EntityRepository(ReversiMatching) export class ReversiMatchingRepository extends Repository { @@ -9,7 +10,7 @@ export class ReversiMatchingRepository extends Repository { src: ReversiMatching['id'] | ReversiMatching, me: any ) { - const matching = typeof src === 'object' ? src : await this.findOne(src); + const matching = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return await rap({ id: matching.id, diff --git a/src/models/repositories/messaging-message.ts b/src/models/repositories/messaging-message.ts index b87b30388a..6659273539 100644 --- a/src/models/repositories/messaging-message.ts +++ b/src/models/repositories/messaging-message.ts @@ -1,6 +1,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { MessagingMessage } from '../entities/messaging-message'; import { Users, DriveFiles } from '..'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(MessagingMessage) export class MessagingMessageRepository extends Repository { @@ -19,7 +20,7 @@ export class MessagingMessageRepository extends Repository { populateRecipient: true }; - const message = typeof src === 'object' ? src : await this.findOne(src); + const message = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return { id: message.id, diff --git a/src/models/repositories/muting.ts b/src/models/repositories/muting.ts index cd98cb4fec..1812e2e713 100644 --- a/src/models/repositories/muting.ts +++ b/src/models/repositories/muting.ts @@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { Users } from '..'; import rap from '@prezzemolo/rap'; import { Muting } from '../entities/muting'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(Muting) export class MutingRepository extends Repository { @@ -16,7 +17,7 @@ export class MutingRepository extends Repository { src: Muting['id'] | Muting, me?: any ) { - const muting = typeof src === 'object' ? src : await this.findOne(src); + const muting = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return await rap({ id: muting.id, diff --git a/src/models/repositories/note-favorite.ts b/src/models/repositories/note-favorite.ts index 4526461e69..f428903c13 100644 --- a/src/models/repositories/note-favorite.ts +++ b/src/models/repositories/note-favorite.ts @@ -1,6 +1,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { NoteFavorite } from '../entities/note-favorite'; import { Notes } from '..'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(NoteFavorite) export class NoteFavoriteRepository extends Repository { @@ -15,7 +16,7 @@ export class NoteFavoriteRepository extends Repository { src: NoteFavorite['id'] | NoteFavorite, me?: any ) { - const favorite = typeof src === 'object' ? src : await this.findOne(src); + const favorite = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return { id: favorite.id, diff --git a/src/models/repositories/note-reaction.ts b/src/models/repositories/note-reaction.ts index 7189da8e20..28191d4ab0 100644 --- a/src/models/repositories/note-reaction.ts +++ b/src/models/repositories/note-reaction.ts @@ -1,6 +1,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { NoteReaction } from '../entities/note-reaction'; import { Users } from '..'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(NoteReaction) export class NoteReactionRepository extends Repository { @@ -8,7 +9,7 @@ export class NoteReactionRepository extends Repository { src: NoteReaction['id'] | NoteReaction, me?: any ) { - const reaction = typeof src === 'object' ? src : await this.findOne(src); + const reaction = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return { id: reaction.id, diff --git a/src/models/repositories/note.ts b/src/models/repositories/note.ts index 01bf8cf87c..77cf00849f 100644 --- a/src/models/repositories/note.ts +++ b/src/models/repositories/note.ts @@ -5,6 +5,7 @@ import { unique, concat } from '../../prelude/array'; import { nyaize } from '../../misc/nyaize'; import { Emojis, Users, Apps, PollVotes, DriveFiles, NoteReactions, Followings, Polls } from '..'; import rap from '@prezzemolo/rap'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(Note) export class NoteRepository extends Repository { @@ -12,7 +13,7 @@ export class NoteRepository extends Repository { return x.trim().length <= 100; } - private async hideNote(packedNote: any, meId: User['id']) { + private async hideNote(packedNote: any, meId: User['id'] | null) { let hide = false; // visibility が specified かつ自分が指定されていなかったら非表示 @@ -75,7 +76,7 @@ export class NoteRepository extends Repository { public packMany( notes: (Note['id'] | Note)[], - me?: User['id'] | User, + me?: User['id'] | User | null | undefined, options?: { detail?: boolean; skipHide?: boolean; @@ -86,7 +87,7 @@ export class NoteRepository extends Repository { public async pack( src: Note['id'] | Note, - me?: User['id'] | User, + me?: User['id'] | User | null | undefined, options?: { detail?: boolean; skipHide?: boolean; @@ -98,11 +99,11 @@ export class NoteRepository extends Repository { }, options); const meId = me ? typeof me === 'string' ? me : me.id : null; - const note = typeof src === 'object' ? src : await this.findOne(src); + const note = typeof src === 'object' ? src : await this.findOne(src).then(ensure); const host = note.userHost; async function populatePoll() { - const poll = await Polls.findOne({ noteId: note.id }); + const poll = await Polls.findOne({ noteId: note.id }).then(ensure); const choices = poll.choices.map(c => ({ text: c, votes: poll.votes[poll.choices.indexOf(c)], @@ -111,7 +112,7 @@ export class NoteRepository extends Repository { if (poll.multiple) { const votes = await PollVotes.find({ - userId: meId, + userId: meId!, noteId: note.id }); @@ -121,7 +122,7 @@ export class NoteRepository extends Repository { } } else { const vote = await PollVotes.findOne({ - userId: meId, + userId: meId!, noteId: note.id }); @@ -139,7 +140,7 @@ export class NoteRepository extends Repository { async function populateMyReaction() { const reaction = await NoteReactions.findOne({ - userId: meId, + userId: meId!, noteId: note.id, }); diff --git a/src/models/repositories/notification.ts b/src/models/repositories/notification.ts index 9bc569cd3f..4781d4c065 100644 --- a/src/models/repositories/notification.ts +++ b/src/models/repositories/notification.ts @@ -2,6 +2,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { Users, Notes } from '..'; import rap from '@prezzemolo/rap'; import { Notification } from '../entities/notification'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(Notification) export class NotificationRepository extends Repository { @@ -14,7 +15,7 @@ export class NotificationRepository extends Repository { public async pack( src: Notification['id'] | Notification, ) { - const notification = typeof src === 'object' ? src : await this.findOne(src); + const notification = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return await rap({ id: notification.id, @@ -23,23 +24,23 @@ export class NotificationRepository extends Repository { userId: notification.notifierId, user: Users.pack(notification.notifier || notification.notifierId), ...(notification.type === 'mention' ? { - note: Notes.pack(notification.note || notification.noteId), + note: Notes.pack(notification.note || notification.noteId!), } : {}), ...(notification.type === 'reply' ? { - note: Notes.pack(notification.note || notification.noteId), + note: Notes.pack(notification.note || notification.noteId!), } : {}), ...(notification.type === 'renote' ? { - note: Notes.pack(notification.note || notification.noteId), + note: Notes.pack(notification.note || notification.noteId!), } : {}), ...(notification.type === 'quote' ? { - note: Notes.pack(notification.note || notification.noteId), + note: Notes.pack(notification.note || notification.noteId!), } : {}), ...(notification.type === 'reaction' ? { - note: Notes.pack(notification.note || notification.noteId), + note: Notes.pack(notification.note || notification.noteId!), reaction: notification.reaction } : {}), ...(notification.type === 'pollVote' ? { - note: Notes.pack(notification.note || notification.noteId), + note: Notes.pack(notification.note || notification.noteId!), choice: notification.choice } : {}) }); diff --git a/src/models/repositories/user-list.ts b/src/models/repositories/user-list.ts index 921c18ca7a..fbf81b8886 100644 --- a/src/models/repositories/user-list.ts +++ b/src/models/repositories/user-list.ts @@ -1,12 +1,13 @@ import { EntityRepository, Repository } from 'typeorm'; import { UserList } from '../entities/user-list'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(UserList) export class UserListRepository extends Repository { public async pack( - src: any, + src: UserList['id'] | UserList, ) { - const userList = typeof src === 'object' ? src : await this.findOne(src); + const userList = typeof src === 'object' ? src : await this.findOne(src).then(ensure); return { id: userList.id, diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts index d7f2c3d040..cddb77ffae 100644 --- a/src/models/repositories/user.ts +++ b/src/models/repositories/user.ts @@ -2,6 +2,7 @@ import { EntityRepository, Repository, In } from 'typeorm'; import { User, ILocalUser, IRemoteUser } from '../entities/user'; import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles } from '..'; import rap from '@prezzemolo/rap'; +import { ensure } from '../../prelude/ensure'; @EntityRepository(User) export class UserRepository extends Repository { @@ -51,7 +52,7 @@ export class UserRepository extends Repository { public packMany( users: (User['id'] | User)[], - me?: User['id'] | User, + me?: User['id'] | User | null | undefined, options?: { detail?: boolean, includeSecrets?: boolean, @@ -63,7 +64,7 @@ export class UserRepository extends Repository { public async pack( src: User['id'] | User, - me?: User['id'] | User, + me?: User['id'] | User | null | undefined, options?: { detail?: boolean, includeSecrets?: boolean, @@ -75,12 +76,12 @@ export class UserRepository extends Repository { includeSecrets: false }, options); - const user = typeof src === 'object' ? src : await this.findOne(src); + const user = typeof src === 'object' ? src : await this.findOne(src).then(ensure); const meId = me ? typeof me === 'string' ? me : me.id : null; const relation = meId && (meId !== user.id) && opts.detail ? await this.getRelation(meId, user.id) : null; const pins = opts.detail ? await UserNotePinings.find({ userId: user.id }) : []; - const profile = opts.detail ? await UserProfiles.findOne({ userId: user.id }) : null; + const profile = opts.detail ? await UserProfiles.findOne({ userId: user.id }).then(ensure) : null; return await rap({ id: user.id, @@ -117,12 +118,12 @@ export class UserRepository extends Repository { } : {}), ...(opts.detail ? { - url: profile.url, + url: profile!.url, createdAt: user.createdAt, updatedAt: user.updatedAt, - description: profile.description, - location: profile.location, - birthday: profile.birthday, + description: profile!.description, + location: profile!.location, + birthday: profile!.birthday, followersCount: user.followersCount, followingCount: user.followingCount, notesCount: user.notesCount, @@ -135,9 +136,9 @@ export class UserRepository extends Repository { ...(opts.detail && meId === user.id ? { avatarId: user.avatarId, bannerId: user.bannerId, - autoWatch: profile.autoWatch, - alwaysMarkNsfw: profile.alwaysMarkNsfw, - carefulBot: profile.carefulBot, + autoWatch: profile!.autoWatch, + alwaysMarkNsfw: profile!.alwaysMarkNsfw, + carefulBot: profile!.carefulBot, hasUnreadMessagingMessage: MessagingMessages.count({ where: { recipientId: user.id, @@ -158,9 +159,9 @@ export class UserRepository extends Repository { } : {}), ...(opts.includeSecrets ? { - clientData: profile.clientData, - email: profile.email, - emailVerified: profile.emailVerified, + clientData: profile!.clientData, + email: profile!.email, + emailVerified: profile!.emailVerified, } : {}), ...(relation ? { diff --git a/src/prelude/ensure.ts b/src/prelude/ensure.ts new file mode 100644 index 0000000000..90bf05538a --- /dev/null +++ b/src/prelude/ensure.ts @@ -0,0 +1,7 @@ +export function ensure(x: T): NonNullable { + if (x == null) { + throw 'ぬるぽ'; + } else { + return x!; + } +} diff --git a/src/queue/index.ts b/src/queue/index.ts index 728c43c6ac..1ab59fd18f 100644 --- a/src/queue/index.ts +++ b/src/queue/index.ts @@ -12,7 +12,7 @@ import { queueLogger } from './logger'; import { DriveFile } from '../models/entities/drive-file'; function initializeQueue(name: string) { - return new Queue(name, config.redis != null ? { + return new Queue(name, { redis: { port: config.redis.port, host: config.redis.host, @@ -20,7 +20,7 @@ function initializeQueue(name: string) { db: config.redis.db || 0, }, prefix: config.redis.prefix ? `${config.redis.prefix}:queue` : 'queue' - } : null); + }); } export const deliverQueue = initializeQueue('deliver'); diff --git a/src/queue/processors/db/delete-drive-files.ts b/src/queue/processors/db/delete-drive-files.ts index 5f347fb588..491734acc6 100644 --- a/src/queue/processors/db/delete-drive-files.ts +++ b/src/queue/processors/db/delete-drive-files.ts @@ -10,9 +10,11 @@ const logger = queueLogger.createSubLogger('delete-drive-files'); export async function deleteDriveFiles(job: Bull.Job, done: any): Promise { logger.info(`Deleting drive files of ${job.data.user.id} ...`); - const user = await Users.findOne({ - id: job.data.user.id - }); + const user = await Users.findOne(job.data.user.id); + if (user == null) { + done(); + return; + } let deletedCount = 0; let ended = false; diff --git a/src/queue/processors/db/export-blocking.ts b/src/queue/processors/db/export-blocking.ts index c12aa4fca3..44025ec960 100644 --- a/src/queue/processors/db/export-blocking.ts +++ b/src/queue/processors/db/export-blocking.ts @@ -14,9 +14,11 @@ const logger = queueLogger.createSubLogger('export-blocking'); export async function exportBlocking(job: Bull.Job, done: any): Promise { logger.info(`Exporting blocking of ${job.data.user.id} ...`); - const user = await Users.findOne({ - id: job.data.user.id - }); + const user = await Users.findOne(job.data.user.id); + if (user == null) { + done(); + return; + } // Create temp file const [path, cleanup] = await new Promise<[string, any]>((res, rej) => { @@ -56,6 +58,10 @@ export async function exportBlocking(job: Bull.Job, done: any): Promise { for (const block of blockings) { const u = await Users.findOne({ id: block.blockeeId }); + if (u == null) { + exportedCount++; continue; + } + const content = getFullApAccount(u.username, u.host); await new Promise((res, rej) => { stream.write(content + '\n', err => { diff --git a/src/queue/processors/db/export-following.ts b/src/queue/processors/db/export-following.ts index fb30df79fe..81dcf8f93e 100644 --- a/src/queue/processors/db/export-following.ts +++ b/src/queue/processors/db/export-following.ts @@ -14,9 +14,11 @@ const logger = queueLogger.createSubLogger('export-following'); export async function exportFollowing(job: Bull.Job, done: any): Promise { logger.info(`Exporting following of ${job.data.user.id} ...`); - const user = await Users.findOne({ - id: job.data.user.id - }); + const user = await Users.findOne(job.data.user.id); + if (user == null) { + done(); + return; + } // Create temp file const [path, cleanup] = await new Promise<[string, any]>((res, rej) => { @@ -56,6 +58,10 @@ export async function exportFollowing(job: Bull.Job, done: any): Promise { for (const following of followings) { const u = await Users.findOne({ id: following.followeeId }); + if (u == null) { + exportedCount++; continue; + } + const content = getFullApAccount(u.username, u.host); await new Promise((res, rej) => { stream.write(content + '\n', err => { diff --git a/src/queue/processors/db/export-mute.ts b/src/queue/processors/db/export-mute.ts index 3aed526dc5..f810b6ee8d 100644 --- a/src/queue/processors/db/export-mute.ts +++ b/src/queue/processors/db/export-mute.ts @@ -14,9 +14,11 @@ const logger = queueLogger.createSubLogger('export-mute'); export async function exportMute(job: Bull.Job, done: any): Promise { logger.info(`Exporting mute of ${job.data.user.id} ...`); - const user = await Users.findOne({ - id: job.data.user.id - }); + const user = await Users.findOne(job.data.user.id); + if (user == null) { + done(); + return; + } // Create temp file const [path, cleanup] = await new Promise<[string, any]>((res, rej) => { @@ -56,6 +58,10 @@ export async function exportMute(job: Bull.Job, done: any): Promise { for (const mute of mutes) { const u = await Users.findOne({ id: mute.muteeId }); + if (u == null) { + exportedCount++; continue; + } + const content = getFullApAccount(u.username, u.host); await new Promise((res, rej) => { stream.write(content + '\n', err => { diff --git a/src/queue/processors/db/export-notes.ts b/src/queue/processors/db/export-notes.ts index 92867ad82e..eaa5caf63f 100644 --- a/src/queue/processors/db/export-notes.ts +++ b/src/queue/processors/db/export-notes.ts @@ -9,15 +9,18 @@ import { Users, Notes, Polls } from '../../../models'; import { MoreThan } from 'typeorm'; import { Note } from '../../../models/entities/note'; import { Poll } from '../../../models/entities/poll'; +import { ensure } from '../../../prelude/ensure'; const logger = queueLogger.createSubLogger('export-notes'); export async function exportNotes(job: Bull.Job, done: any): Promise { logger.info(`Exporting notes of ${job.data.user.id} ...`); - const user = await Users.findOne({ - id: job.data.user.id - }); + const user = await Users.findOne(job.data.user.id); + if (user == null) { + done(); + return; + } // Create temp file const [path, cleanup] = await new Promise<[string, any]>((res, rej) => { @@ -67,9 +70,9 @@ export async function exportNotes(job: Bull.Job, done: any): Promise { cursor = notes[notes.length - 1].id; for (const note of notes) { - let poll: Poll; + let poll: Poll | undefined; if (note.hasPoll) { - poll = await Polls.findOne({ noteId: note.id }); + poll = await Polls.findOne({ noteId: note.id }).then(ensure); } const content = JSON.stringify(serialize(note, poll)); await new Promise((res, rej) => { @@ -114,7 +117,7 @@ export async function exportNotes(job: Bull.Job, done: any): Promise { done(); } -function serialize(note: Note, poll: Poll): any { +function serialize(note: Note, poll: Poll | null = null): any { return { id: note.id, text: note.text, diff --git a/src/queue/processors/db/export-user-lists.ts b/src/queue/processors/db/export-user-lists.ts index f3987cb0d2..5cd978c1aa 100644 --- a/src/queue/processors/db/export-user-lists.ts +++ b/src/queue/processors/db/export-user-lists.ts @@ -14,9 +14,11 @@ const logger = queueLogger.createSubLogger('export-user-lists'); export async function exportUserLists(job: Bull.Job, done: any): Promise { logger.info(`Exporting user lists of ${job.data.user.id} ...`); - const user = await Users.findOne({ - id: job.data.user.id - }); + const user = await Users.findOne(job.data.user.id); + if (user == null) { + done(); + return; + } const lists = await UserLists.find({ userId: user.id diff --git a/src/queue/processors/db/import-following.ts b/src/queue/processors/db/import-following.ts index aae24b22d6..8de3193e46 100644 --- a/src/queue/processors/db/import-following.ts +++ b/src/queue/processors/db/import-following.ts @@ -13,13 +13,19 @@ const logger = queueLogger.createSubLogger('import-following'); export async function importFollowing(job: Bull.Job, done: any): Promise { logger.info(`Importing following of ${job.data.user.id} ...`); - const user = await Users.findOne({ - id: job.data.user.id - }); + const user = await Users.findOne(job.data.user.id); + if (user == null) { + done(); + return; + } const file = await DriveFiles.findOne({ id: job.data.fileId }); + if (file == null) { + done(); + return; + } const csv = await downloadTextFile(file.url); @@ -31,11 +37,11 @@ export async function importFollowing(job: Bull.Job, done: any): Promise { try { const { username, host } = parseAcct(line.trim()); - let target = isSelfHost(host) ? await Users.findOne({ + let target = isSelfHost(host!) ? await Users.findOne({ host: null, usernameLower: username.toLowerCase() }) : await Users.findOne({ - host: toPuny(host), + host: toPuny(host!), usernameLower: username.toLowerCase() }); diff --git a/src/queue/processors/db/import-user-lists.ts b/src/queue/processors/db/import-user-lists.ts index c7273ea6b4..1e852be945 100644 --- a/src/queue/processors/db/import-user-lists.ts +++ b/src/queue/processors/db/import-user-lists.ts @@ -14,13 +14,19 @@ const logger = queueLogger.createSubLogger('import-user-lists'); export async function importUserLists(job: Bull.Job, done: any): Promise { logger.info(`Importing user lists of ${job.data.user.id} ...`); - const user = await Users.findOne({ - id: job.data.user.id - }); + const user = await Users.findOne(job.data.user.id); + if (user == null) { + done(); + return; + } const file = await DriveFiles.findOne({ id: job.data.fileId }); + if (file == null) { + done(); + return; + } const csv = await downloadTextFile(file.url); @@ -43,22 +49,20 @@ export async function importUserLists(job: Bull.Job, done: any): Promise { }); } - let target = isSelfHost(host) ? await Users.findOne({ + let target = isSelfHost(host!) ? await Users.findOne({ host: null, usernameLower: username.toLowerCase() }) : await Users.findOne({ - host: toPuny(host), + host: toPuny(host!), usernameLower: username.toLowerCase() }); - if (host == null && target == null) continue; - - if (await UserListJoinings.findOne({ userListId: list.id, userId: target.id }) != null) continue; - if (target == null) { target = await resolveUser(username, host); } + if (await UserListJoinings.findOne({ userListId: list.id, userId: target.id }) != null) continue; + pushUserToUserList(target, list); } diff --git a/src/queue/processors/deliver.ts b/src/queue/processors/deliver.ts index b9701c0c65..8837c80d87 100644 --- a/src/queue/processors/deliver.ts +++ b/src/queue/processors/deliver.ts @@ -7,7 +7,7 @@ import { instanceChart } from '../../services/chart'; const logger = new Logger('deliver'); -let latest: string = null; +let latest: string | null = null; export default async (job: Bull.Job) => { const { host } = new URL(job.data.to); diff --git a/src/queue/processors/inbox.ts b/src/queue/processors/inbox.ts index 35b0ce5386..4deaef2ae3 100644 --- a/src/queue/processors/inbox.ts +++ b/src/queue/processors/inbox.ts @@ -14,6 +14,7 @@ import { UserPublickey } from '../../models/entities/user-publickey'; import fetchMeta from '../../misc/fetch-meta'; import { toPuny } from '../../misc/convert-host'; import { validActor } from '../../remote/activitypub/type'; +import { ensure } from '../../prelude/ensure'; const logger = new Logger('inbox'); @@ -35,7 +36,7 @@ export default async (job: Bull.Job): Promise => { if (keyIdLower.startsWith('acct:')) { const acct = parseAcct(keyIdLower.slice('acct:'.length)); - const host = toPuny(acct.host); + const host = acct.host ? toPuny(acct.host) : null; const username = toPuny(acct.username); if (host === null) { @@ -64,9 +65,7 @@ export default async (job: Bull.Job): Promise => { host: host }) as IRemoteUser; - key = await UserPublickeys.findOne({ - userId: user.id - }); + key = await UserPublickeys.findOne(user.id).then(ensure); } else { // アクティビティ内のホストの検証 const host = toPuny(new URL(signature.keyId).hostname); @@ -87,7 +86,7 @@ export default async (job: Bull.Job): Promise => { key = await UserPublickeys.findOne({ keyId: signature.keyId - }); + }).then(ensure); user = await Users.findOne(key.userId) as IRemoteUser; } diff --git a/src/remote/activitypub/kernel/accept/follow.ts b/src/remote/activitypub/kernel/accept/follow.ts index 816fcbadbf..f3e517ad9f 100644 --- a/src/remote/activitypub/kernel/accept/follow.ts +++ b/src/remote/activitypub/kernel/accept/follow.ts @@ -6,9 +6,10 @@ import { Users } from '../../../../models'; export default async (actor: IRemoteUser, activity: IFollow): Promise => { const id = typeof activity.actor == 'string' ? activity.actor : activity.actor.id; + if (id == null) throw 'missing id'; if (!id.startsWith(config.url + '/')) { - return null; + return; } const follower = await Users.findOne({ diff --git a/src/remote/activitypub/kernel/add/index.ts b/src/remote/activitypub/kernel/add/index.ts index d16f0a4a0d..a5b2687416 100644 --- a/src/remote/activitypub/kernel/add/index.ts +++ b/src/remote/activitypub/kernel/add/index.ts @@ -14,6 +14,7 @@ export default async (actor: IRemoteUser, activity: IAdd): Promise => { if (activity.target === actor.featured) { const note = await resolveNote(activity.object); + if (note == null) throw new Error('note not found'); await addPinned(actor, note.id); return; } diff --git a/src/remote/activitypub/kernel/announce/note.ts b/src/remote/activitypub/kernel/announce/note.ts index 403fc66bed..f9822c5187 100644 --- a/src/remote/activitypub/kernel/announce/note.ts +++ b/src/remote/activitypub/kernel/announce/note.ts @@ -53,16 +53,16 @@ export default async function(resolver: Resolver, actor: IRemoteUser, activity: logger.info(`Creating the (Re)Note: ${uri}`); //#region Visibility - const visibility = getVisibility(activity.to, activity.cc, actor); + const visibility = getVisibility(activity.to || [], activity.cc || [], actor); let visibleUsers: User[] = []; if (visibility == 'specified') { - visibleUsers = await Promise.all(note.to.map(uri => resolvePerson(uri))); + visibleUsers = await Promise.all((note.to || []).map(uri => resolvePerson(uri))); } //#endergion await post(actor, { - createdAt: new Date(activity.published), + createdAt: activity.published ? new Date(activity.published) : null, renote, visibility, visibleUsers, @@ -75,9 +75,6 @@ type visibility = 'public' | 'home' | 'followers' | 'specified'; function getVisibility(to: string[], cc: string[], actor: IRemoteUser): visibility { const PUBLIC = 'https://www.w3.org/ns/activitystreams#Public'; - to = to || []; - cc = cc || []; - if (to.includes(PUBLIC)) { return 'public'; } else if (cc.includes(PUBLIC)) { diff --git a/src/remote/activitypub/kernel/block/index.ts b/src/remote/activitypub/kernel/block/index.ts index 48e251dd9b..19e33eb7dd 100644 --- a/src/remote/activitypub/kernel/block/index.ts +++ b/src/remote/activitypub/kernel/block/index.ts @@ -9,13 +9,14 @@ const logger = apLogger; export default async (actor: IRemoteUser, activity: IBlock): Promise => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; + if (id == null) throw 'missing id'; const uri = activity.id || activity; logger.info(`Block: ${uri}`); if (!id.startsWith(config.url + '/')) { - return null; + return; } const blockee = await Users.findOne(id.split('/').pop()); diff --git a/src/remote/activitypub/kernel/follow.ts b/src/remote/activitypub/kernel/follow.ts index e6c8833f3a..d37404502f 100644 --- a/src/remote/activitypub/kernel/follow.ts +++ b/src/remote/activitypub/kernel/follow.ts @@ -6,9 +6,10 @@ import { Users } from '../../../models'; export default async (actor: IRemoteUser, activity: IFollow): Promise => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; + if (id == null) throw 'missing id'; if (!id.startsWith(config.url + '/')) { - return null; + return; } const followee = await Users.findOne(id.split('/').pop()); diff --git a/src/remote/activitypub/kernel/index.ts b/src/remote/activitypub/kernel/index.ts index 4a57d0675e..d1251817fa 100644 --- a/src/remote/activitypub/kernel/index.ts +++ b/src/remote/activitypub/kernel/index.ts @@ -71,7 +71,7 @@ const self = async (actor: IRemoteUser, activity: Object): Promise => { default: apLogger.warn(`unknown activity type: ${(activity as any).type}`); - return null; + return; } }; diff --git a/src/remote/activitypub/kernel/like.ts b/src/remote/activitypub/kernel/like.ts index 86dd8fb33d..d4fa7bf387 100644 --- a/src/remote/activitypub/kernel/like.ts +++ b/src/remote/activitypub/kernel/like.ts @@ -5,6 +5,7 @@ import { Notes } from '../../../models'; export default async (actor: IRemoteUser, activity: ILike) => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; + if (id == null) throw 'missing id'; // Transform: // https://misskey.ex/notes/xxxx to diff --git a/src/remote/activitypub/kernel/reject/follow.ts b/src/remote/activitypub/kernel/reject/follow.ts index b06ae6fb96..91689339ab 100644 --- a/src/remote/activitypub/kernel/reject/follow.ts +++ b/src/remote/activitypub/kernel/reject/follow.ts @@ -6,9 +6,10 @@ import { Users } from '../../../../models'; export default async (actor: IRemoteUser, activity: IFollow): Promise => { const id = typeof activity.actor == 'string' ? activity.actor : activity.actor.id; + if (id == null) throw 'missing id'; if (!id.startsWith(config.url + '/')) { - return null; + return; } const follower = await Users.findOne(id.split('/').pop()); diff --git a/src/remote/activitypub/kernel/remove/index.ts b/src/remote/activitypub/kernel/remove/index.ts index ae33be59dc..32b8d66471 100644 --- a/src/remote/activitypub/kernel/remove/index.ts +++ b/src/remote/activitypub/kernel/remove/index.ts @@ -14,6 +14,7 @@ export default async (actor: IRemoteUser, activity: IRemove): Promise => { if (activity.target === actor.featured) { const note = await resolveNote(activity.object); + if (note == null) throw new Error('note not found'); await removePinned(actor, note.id); return; } diff --git a/src/remote/activitypub/kernel/undo/block.ts b/src/remote/activitypub/kernel/undo/block.ts index c916a00737..9c277ed7d2 100644 --- a/src/remote/activitypub/kernel/undo/block.ts +++ b/src/remote/activitypub/kernel/undo/block.ts @@ -9,13 +9,14 @@ const logger = apLogger; export default async (actor: IRemoteUser, activity: IBlock): Promise => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; + if (id == null) throw 'missing id'; const uri = activity.id || activity; logger.info(`UnBlock: ${uri}`); if (!id.startsWith(config.url + '/')) { - return null; + return; } const blockee = await Users.findOne(id.split('/').pop()); diff --git a/src/remote/activitypub/kernel/undo/follow.ts b/src/remote/activitypub/kernel/undo/follow.ts index cc63a740b1..ce84d0c791 100644 --- a/src/remote/activitypub/kernel/undo/follow.ts +++ b/src/remote/activitypub/kernel/undo/follow.ts @@ -7,9 +7,10 @@ import { Users, FollowRequests, Followings } from '../../../../models'; export default async (actor: IRemoteUser, activity: IFollow): Promise => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; + if (id == null) throw 'missing id'; if (!id.startsWith(config.url + '/')) { - return null; + return; } const followee = await Users.findOne(id.split('/').pop()); diff --git a/src/remote/activitypub/kernel/undo/index.ts b/src/remote/activitypub/kernel/undo/index.ts index 6376ab93a8..5f2e58c3bf 100644 --- a/src/remote/activitypub/kernel/undo/index.ts +++ b/src/remote/activitypub/kernel/undo/index.ts @@ -39,6 +39,4 @@ export default async (actor: IRemoteUser, activity: IUndo): Promise => { undoLike(actor, object as ILike); break; } - - return null; }; diff --git a/src/remote/activitypub/kernel/undo/like.ts b/src/remote/activitypub/kernel/undo/like.ts index f337a0173e..75879d697a 100644 --- a/src/remote/activitypub/kernel/undo/like.ts +++ b/src/remote/activitypub/kernel/undo/like.ts @@ -8,6 +8,7 @@ import { Notes } from '../../../../models'; */ export default async (actor: IRemoteUser, activity: ILike): Promise => { const id = typeof activity.object == 'string' ? activity.object : activity.object.id; + if (id == null) throw 'missing id'; const noteId = id.split('/').pop(); diff --git a/src/remote/activitypub/models/image.ts b/src/remote/activitypub/models/image.ts index c9991dba3b..f8b35ea21c 100644 --- a/src/remote/activitypub/models/image.ts +++ b/src/remote/activitypub/models/image.ts @@ -5,6 +5,7 @@ import fetchMeta from '../../../misc/fetch-meta'; import { apLogger } from '../logger'; import { DriveFile } from '../../../models/entities/drive-file'; import { DriveFiles } from '../../../models'; +import { ensure } from '../../../prelude/ensure'; const logger = apLogger; @@ -14,7 +15,7 @@ const logger = apLogger; export async function createImage(actor: IRemoteUser, value: any): Promise { // 投稿者が凍結されていたらスキップ if (actor.isSuspended) { - return null; + throw new Error('actor has been suspended'); } const image = await new Resolver().resolve(value) as any; @@ -28,17 +29,7 @@ export async function createImage(actor: IRemoteUser, value: any): Promise= 400 && e < 500) { - logger.warn(`Ignored image: ${image.url} - ${e}`); - return null; - } - throw e; - } + let file = await uploadFromUrl(image.url, actor, null, image.url, image.sensitive, false, !cache); if (file.isLink) { // URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、 @@ -49,7 +40,7 @@ export async function createImage(actor: IRemoteUser, value: any): Promise { +export async function fetchNote(value: string | IObject, resolver?: Resolver): Promise { const uri = typeof value == 'string' ? value : value.id; + if (uri == null) throw 'missing uri'; // URIがこのサーバーを指しているならデータベースからフェッチ if (uri.startsWith(config.url + '/')) { const id = uri.split('/').pop(); - return await Notes.findOne(id); + return await Notes.findOne(id).then(x => x || null); } //#region このサーバーに既に登録されていたらそれを返す @@ -52,7 +54,7 @@ export async function fetchNote(value: string | IObject, resolver?: Resolver): P /** * Noteを作成します。 */ -export async function createNote(value: any, resolver?: Resolver, silent = false): Promise { +export async function createNote(value: any, resolver?: Resolver, silent = false): Promise { if (resolver == null) resolver = new Resolver(); const object: any = await resolver.resolve(value); @@ -65,7 +67,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false value: value, object: object }); - return null; + throw 'invalid note'; } const note: INote = object; @@ -75,11 +77,11 @@ export async function createNote(value: any, resolver?: Resolver, silent = false logger.info(`Creating the Note: ${note.id}`); // 投稿者をフェッチ - const actor = await resolvePerson(note.attributedTo, null, resolver) as IRemoteUser; + const actor = await resolvePerson(note.attributedTo, resolver) as IRemoteUser; // 投稿者が凍結されていたらスキップ if (actor.isSuspended) { - return null; + throw 'actor has been suspended'; } //#region Visibility @@ -95,9 +97,9 @@ export async function createNote(value: any, resolver?: Resolver, silent = false visibility = 'followers'; } else { visibility = 'specified'; - visibleUsers = await Promise.all(note.to.map(uri => resolvePerson(uri, null, resolver))); + visibleUsers = await Promise.all(note.to.map(uri => resolvePerson(uri, resolver))); } -} + } //#endergion const apMentions = await extractMentionedUsers(actor, note.to, note.cc, resolver); @@ -118,7 +120,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false : []; // リプライ - const reply: Note = note.inReplyTo + const reply: Note | undefined | null = note.inReplyTo ? await resolveNote(note.inReplyTo, resolver).catch(e => { // 4xxの場合はリプライしてないことにする if (e.statusCode >= 400 && e.statusCode < 500) { @@ -131,7 +133,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false : null; // 引用 - let quote: Note; + let quote: Note | undefined | null; if (note._misskey_quote && typeof note._misskey_quote == 'string') { quote = await resolveNote(note._misskey_quote).catch(e => { @@ -152,7 +154,8 @@ export async function createNote(value: any, resolver?: Resolver, silent = false // vote if (reply && reply.hasPoll) { - const poll = await Polls.findOne({ noteId: reply.id }); + const poll = await Polls.findOne({ noteId: reply.id }).then(ensure); + const tryCreateVote = async (name: string, index: number): Promise => { if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) { logger.warn(`vote to expired poll from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`); @@ -180,7 +183,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false } } - const emojis = await extractEmojis(note.tag, actor.host).catch(e => { + const emojis = await extractEmojis(note.tag || [], actor.host).catch(e => { logger.info(`extractEmojis: ${e}`); return [] as Emoji[]; }); @@ -196,7 +199,7 @@ export async function createNote(value: any, resolver?: Resolver, silent = false } return await post(actor, { - createdAt: new Date(note.published), + createdAt: note.published ? new Date(note.published) : null, files, reply, renote: quote, @@ -223,8 +226,9 @@ export async function createNote(value: any, resolver?: Resolver, silent = false * Misskeyに対象のNoteが登録されていればそれを返し、そうでなければ * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 */ -export async function resolveNote(value: string | IObject, resolver?: Resolver): Promise { +export async function resolveNote(value: string | IObject, resolver?: Resolver): Promise { const uri = typeof value == 'string' ? value : value.id; + if (uri == null) throw 'missing uri'; // ブロックしてたら中断 // TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく @@ -244,75 +248,79 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver): // 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。 return await createNote(uri, resolver).catch(e => { if (e.name === 'duplicated') { - return fetchNote(uri); + return fetchNote(uri).then(note => { + if (note == null) { + throw 'something happened'; + } else { + return note; + } + }); } else { throw e; } }); } -export async function extractEmojis(tags: ITag[], host: string) { +export async function extractEmojis(tags: ITag[], host: string): Promise { host = toPuny(host); if (!tags) return []; - const eomjiTags = tags.filter(tag => tag.type === 'Emoji' && tag.icon && tag.icon.url); - - return await Promise.all( - eomjiTags.map(async tag => { - const name = tag.name.replace(/^:/, '').replace(/:$/, ''); + const eomjiTags = tags.filter(tag => tag.type === 'Emoji' && tag.icon && tag.icon.url && tag.name); - const exists = await Emojis.findOne({ - host, - name - }); + return await Promise.all(eomjiTags.map(async tag => { + const name = tag.name!.replace(/^:/, '').replace(/:$/, ''); - if (exists) { - if ((tag.updated != null && exists.updatedAt == null) - || (tag.id != null && exists.uri == null) - || (tag.updated != null && exists.updatedAt != null && new Date(tag.updated) > exists.updatedAt) - ) { - await Emojis.update({ - host, - name, - }, { - uri: tag.id, - url: tag.icon.url, - updatedAt: new Date(tag.updated), - }); - - return await Emojis.findOne({ - host, - name - }); - } + const exists = await Emojis.findOne({ + host, + name + }); - return exists; + if (exists) { + if ((tag.updated != null && exists.updatedAt == null) + || (tag.id != null && exists.uri == null) + || (tag.updated != null && exists.updatedAt != null && new Date(tag.updated) > exists.updatedAt) + ) { + await Emojis.update({ + host, + name, + }, { + uri: tag.id, + url: tag.icon!.url, + updatedAt: new Date(tag.updated!), + }); + + return await Emojis.findOne({ + host, + name + }) as Emoji; } - logger.info(`register emoji host=${host}, name=${name}`); - - return await Emojis.save({ - id: genId(), - host, - name, - uri: tag.id, - url: tag.icon.url, - updatedAt: tag.updated ? new Date(tag.updated) : undefined, - aliases: [] - } as Emoji); - }) - ); + return exists; + } + + logger.info(`register emoji host=${host}, name=${name}`); + + return await Emojis.save({ + id: genId(), + host, + name, + uri: tag.id, + url: tag.icon!.url, + updatedAt: tag.updated ? new Date(tag.updated) : undefined, + aliases: [] + } as Partial); + })); } async function extractMentionedUsers(actor: IRemoteUser, to: string[], cc: string[], resolver: Resolver) { const ignoreUris = ['https://www.w3.org/ns/activitystreams#Public', `${actor.uri}/followers`]; const uris = difference(unique(concat([to || [], cc || []])), ignoreUris); - const limit = promiseLimit(2); + const limit = promiseLimit(2); const users = await Promise.all( - uris.map(uri => limit(() => resolvePerson(uri, null, resolver).catch(() => null)) as Promise) + uris.map(uri => limit(() => resolvePerson(uri, resolver).catch(() => null)) as Promise) ); - return users.filter(x => x != null); + return users.filter(x => x != null) as User[]; } diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts index e7021956de..9465cf0cd0 100644 --- a/src/remote/activitypub/models/person.ts +++ b/src/remote/activitypub/models/person.ts @@ -26,6 +26,7 @@ import { toPuny } from '../../../misc/convert-host'; import { UserProfile } from '../../../models/entities/user-profile'; import { validActor } from '../../../remote/activitypub/type'; import { getConnection } from 'typeorm'; +import { ensure } from '../../../prelude/ensure'; const logger = apLogger; /** @@ -86,13 +87,13 @@ function validatePerson(x: any, uri: string) { * * Misskeyに対象のPersonが登録されていればそれを返します。 */ -export async function fetchPerson(uri: string, resolver?: Resolver): Promise { +export async function fetchPerson(uri: string, resolver?: Resolver): Promise { if (typeof uri !== 'string') throw 'uri is not string'; // URIがこのサーバーを指しているならデータベースからフェッチ if (uri.startsWith(config.url + '/')) { const id = uri.split('/').pop(); - return await Users.findOne(id); + return await Users.findOne(id).then(x => x || null); } //#region このサーバーに既に登録されていたらそれを返す @@ -128,7 +129,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise tag.toLowerCase()); @@ -161,7 +162,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise !tags.includes(x))) updateHashtag(user, tag, true, false); + for (const tag of tags) updateHashtag(user!, tag, true, true); + for (const tag of (user!.tags || []).filter(x => !tags.includes(x))) updateHashtag(user!, tag, true, false); //#region アイコンとヘッダー画像をフェッチ - const [avatar, banner] = (await Promise.all([ + const [avatar, banner] = (await Promise.all([ person.icon, person.image ].map(img => img == null ? Promise.resolve(null) - : resolveImage(user, img).catch(() => null) + : resolveImage(user!, img).catch(() => null) ))); const avatarId = avatar ? avatar.id : null; @@ -210,9 +211,9 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise { + const emojis = await extractEmojis(person.tag || [], host).catch(e => { logger.info(`extractEmojis: ${e}`); return [] as Emoji[]; }); const emojiNames = emojis.map(emoji => emoji.name); - await Users.update(user.id, { + await Users.update(user!.id, { emojis: emojiNames }); //#endregion - await updateFeatured(user.id).catch(err => logger.error(err)); + await updateFeatured(user!.id).catch(err => logger.error(err)); - return user; + return user!; } /** @@ -254,7 +255,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise { +export async function updatePerson(uri: string, resolver?: Resolver | null, hint?: object): Promise { if (typeof uri !== 'string') throw 'uri is not string'; // URIがこのサーバーを指しているならスキップ @@ -290,7 +291,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje logger.info(`Updating the Person: ${person.id}`); // アイコンとヘッダー画像をフェッチ - const [avatar, banner] = (await Promise.all([ + const [avatar, banner] = (await Promise.all([ person.icon, person.image ].map(img => @@ -300,14 +301,14 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje ))); // カスタム絵文字取得 - const emojis = await extractEmojis(person.tag, exist.host).catch(e => { + const emojis = await extractEmojis(person.tag || [], exist.host).catch(e => { logger.info(`extractEmojis: ${e}`); return [] as Emoji[]; }); const emojiNames = emojis.map(emoji => emoji.name); - const { fields, services } = analyzeAttachments(person.attachment); + const { fields, services } = analyzeAttachments(person.attachment || []); const tags = extractHashtags(person.tag).map(tag => tag.toLowerCase()); @@ -317,7 +318,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined), featured: person.featured, emojis: emojiNames, - description: fromHtml(person.summary), + description: person.summary ? fromHtml(person.summary) : null, name: person.name, url: person.url, endpoints: person.endpoints, @@ -326,7 +327,6 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje isBot: object.type == 'Service', isCat: (person as any).isCat === true, isLocked: person.manuallyApprovesFollowers, - createdAt: new Date(Date.parse(person.published)) || null, } as Partial; if (avatar) { @@ -379,7 +379,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje * Misskeyに対象のPersonが登録されていればそれを返し、そうでなければ * リモートサーバーからフェッチしてMisskeyに登録しそれを返します。 */ -export async function resolvePerson(uri: string, verifier?: string, resolver?: Resolver): Promise { +export async function resolvePerson(uri: string, resolver?: Resolver): Promise { if (typeof uri !== 'string') throw 'uri is not string'; //#region このサーバーに既に登録されていたらそれを返す @@ -439,21 +439,24 @@ export function analyzeAttachments(attachments: ITag[]) { }[] = []; const services: { [x: string]: any } = {}; - if (Array.isArray(attachments)) - for (const attachment of attachments.filter(isPropertyValue)) - if (isPropertyValue(attachment.identifier)) - addService(services, attachment.identifier); - else + if (Array.isArray(attachments)) { + for (const attachment of attachments.filter(isPropertyValue)) { + if (isPropertyValue(attachment.identifier!)) { + addService(services, attachment.identifier!); + } else { fields.push({ - name: attachment.name, - value: fromHtml(attachment.value) + name: attachment.name!, + value: fromHtml(attachment.value!) }); + } + } + } return { fields, services }; } export async function updateFeatured(userId: User['id']) { - const user = await Users.findOne(userId); + const user = await Users.findOne(userId).then(ensure); if (!Users.isRemoteUser(user)) return; if (!user.featured) return; @@ -471,18 +474,18 @@ export async function updateFeatured(userId: User['id']) { if (!Array.isArray(items)) throw new Error(`Collection items is not an array`); // Resolve and regist Notes - const limit = promiseLimit(2); + const limit = promiseLimit(2); const featuredNotes = await Promise.all(items .filter(item => item.type === 'Note') .slice(0, 5) - .map(item => limit(() => resolveNote(item, resolver)) as Promise)); + .map(item => limit(() => resolveNote(item, resolver)))); for (const note of featuredNotes.filter(note => note != null)) { UserNotePinings.save({ id: genId(), createdAt: new Date(), userId: user.id, - noteId: note.id + noteId: note!.id } as UserNotePining); } } diff --git a/src/remote/activitypub/models/question.ts b/src/remote/activitypub/models/question.ts index 2ff8e21ab5..708cdc2a66 100644 --- a/src/remote/activitypub/models/question.ts +++ b/src/remote/activitypub/models/question.ts @@ -14,10 +14,10 @@ export async function extractPollFromQuestion(source: string | IQuestion): Promi throw 'invalid question'; } - const choices = question[multiple ? 'anyOf' : 'oneOf'] - .map((x, i) => x.name); + const choices = question[multiple ? 'anyOf' : 'oneOf']! + .map((x, i) => x.name!); - const votes = question[multiple ? 'anyOf' : 'oneOf'] + const votes = question[multiple ? 'anyOf' : 'oneOf']! .map((x, i) => x.replies && x.replies.totalItems || x._misskey_votes || 0); return { @@ -60,7 +60,7 @@ export async function updateQuestion(value: any) { for (const choice of poll.choices) { const oldCount = poll.votes[poll.choices.indexOf(choice)]; - const newCount = apChoices.filter(ap => ap.name === choice)[0].replies.totalItems; + const newCount = apChoices!.filter(ap => ap.name === choice)[0].replies!.totalItems; if (oldCount != newCount) { changed = true; diff --git a/src/remote/activitypub/models/tag.ts b/src/remote/activitypub/models/tag.ts index 0a1e6e29f9..8d2008d1d9 100644 --- a/src/remote/activitypub/models/tag.ts +++ b/src/remote/activitypub/models/tag.ts @@ -14,13 +14,13 @@ export type ITag = { identifier?: IIdentifier; }; -export function extractHashtags(tags: ITag[]) { - if (!tags) return []; +export function extractHashtags(tags: ITag[] | null | undefined): string[] { + if (tags == null) return []; const hashtags = tags.filter(tag => tag.type === 'Hashtag' && typeof tag.name == 'string'); return hashtags.map(tag => { - const m = tag.name.match(/^#(.+)/); + const m = tag.name ? tag.name.match(/^#(.+)/) : null; return m ? m[1] : null; - }).filter(x => x != null); + }).filter(x => x != null) as string[]; } diff --git a/src/remote/activitypub/renderer/block.ts b/src/remote/activitypub/renderer/block.ts index 946c45a813..c29a9aea82 100644 --- a/src/remote/activitypub/renderer/block.ts +++ b/src/remote/activitypub/renderer/block.ts @@ -1,7 +1,7 @@ import config from '../../../config'; import { ILocalUser, IRemoteUser } from '../../../models/entities/user'; -export default (blocker?: ILocalUser, blockee?: IRemoteUser) => ({ +export default (blocker: ILocalUser, blockee: IRemoteUser) => ({ type: 'Block', actor: `${config.url}/users/${blocker.id}`, object: blockee.uri diff --git a/src/remote/activitypub/renderer/follow-user.ts b/src/remote/activitypub/renderer/follow-user.ts index 9446be3c86..6d354803e5 100644 --- a/src/remote/activitypub/renderer/follow-user.ts +++ b/src/remote/activitypub/renderer/follow-user.ts @@ -1,12 +1,13 @@ import config from '../../../config'; import { Users } from '../../../models'; import { User } from '../../../models/entities/user'; +import { ensure } from '../../../prelude/ensure'; /** * Convert (local|remote)(Follower|Followee)ID to URL * @param id Follower|Followee ID */ export default async function renderFollowUser(id: User['id']): Promise { - const user = await Users.findOne(id); + const user = await Users.findOne(id).then(ensure); return Users.isLocalUser(user) ? `${config.url}/users/${user.id}` : user.uri; } diff --git a/src/remote/activitypub/renderer/note.ts b/src/remote/activitypub/renderer/note.ts index 58ee4fb52c..c66af2667b 100644 --- a/src/remote/activitypub/renderer/note.ts +++ b/src/remote/activitypub/renderer/note.ts @@ -10,6 +10,7 @@ import { DriveFiles, Notes, Users, Emojis, Polls } from '../../../models'; import { In } from 'typeorm'; import { Emoji } from '../../../models/entities/emoji'; import { Poll } from '../../../models/entities/poll'; +import { ensure } from '../../../prelude/ensure'; export default async function renderNote(note: Note, dive = true): Promise { const promisedFiles: Promise = note.fileIds.length > 0 @@ -17,15 +18,15 @@ export default async function renderNote(note: Note, dive = true): Promise : Promise.resolve([]); let inReplyTo; - let inReplyToNote: Note; + let inReplyToNote: Note | undefined; if (note.replyId) { inReplyToNote = await Notes.findOne(note.replyId); - if (inReplyToNote !== null) { + if (inReplyToNote != null) { const inReplyToUser = await Users.findOne(inReplyToNote.userId); - if (inReplyToUser !== null) { + if (inReplyToUser != null) { if (inReplyToNote.uri) { inReplyTo = inReplyToNote.uri; } else { @@ -51,9 +52,7 @@ export default async function renderNote(note: Note, dive = true): Promise } } - const user = await Users.findOne({ - id: note.userId - }); + const user = await Users.findOne(note.userId).then(ensure); const attributedTo = `${config.url}/users/${user.id}`; @@ -85,13 +84,13 @@ export default async function renderNote(note: Note, dive = true): Promise const files = await promisedFiles; let text = note.text; - let poll: Poll; + let poll: Poll | undefined; if (note.hasPoll) { poll = await Polls.findOne({ noteId: note.id }); } - let question: string; + let question: string | undefined; if (poll) { if (text == null) text = ''; const url = `${config.url}/notes/${note.id}`; @@ -144,7 +143,7 @@ export default async function renderNote(note: Note, dive = true): Promise name: text, replies: { type: 'Collection', - totalItems: poll.votes[i] + totalItems: poll!.votes[i] } })) } : {}; @@ -179,5 +178,5 @@ export async function getEmojis(names: string[]): Promise { })) ); - return emojis.filter(emoji => emoji != null); + return emojis.filter(emoji => emoji != null) as Emoji[]; } diff --git a/src/remote/activitypub/renderer/ordered-collection-page.ts b/src/remote/activitypub/renderer/ordered-collection-page.ts index 83af07870e..2433358646 100644 --- a/src/remote/activitypub/renderer/ordered-collection-page.ts +++ b/src/remote/activitypub/renderer/ordered-collection-page.ts @@ -7,7 +7,7 @@ * @param prev URL of prev page (optional) * @param next URL of next page (optional) */ -export default function(id: string, totalItems: any, orderedItems: any, partOf: string, prev: string, next: string) { +export default function(id: string, totalItems: any, orderedItems: any, partOf: string, prev?: string, next?: string) { const page = { id, partOf, diff --git a/src/remote/activitypub/renderer/person.ts b/src/remote/activitypub/renderer/person.ts index e561e47c68..3fb164ef4e 100644 --- a/src/remote/activitypub/renderer/person.ts +++ b/src/remote/activitypub/renderer/person.ts @@ -9,14 +9,15 @@ import renderEmoji from './emoji'; import { IIdentifier } from '../models/identifier'; import renderHashtag from './hashtag'; import { DriveFiles, UserProfiles, UserKeypairs } from '../../../models'; +import { ensure } from '../../../prelude/ensure'; export async function renderPerson(user: ILocalUser) { const id = `${config.url}/users/${user.id}`; const [avatar, banner, profile] = await Promise.all([ - DriveFiles.findOne(user.avatarId), - DriveFiles.findOne(user.bannerId), - UserProfiles.findOne({ userId: user.id }) + user.avatarId ? DriveFiles.findOne(user.avatarId) : Promise.resolve(undefined), + user.bannerId ? DriveFiles.findOne(user.bannerId) : Promise.resolve(undefined), + UserProfiles.findOne({ userId: user.id }).then(ensure) ]); const attachment: { @@ -76,9 +77,7 @@ export async function renderPerson(user: ILocalUser) { ...hashtagTags, ]; - const keypair = await UserKeypairs.findOne({ - userId: user.id - }); + const keypair = await UserKeypairs.findOne(user.id).then(ensure); return { type: user.isBot ? 'Service' : 'Person', @@ -94,8 +93,8 @@ export async function renderPerson(user: ILocalUser) { preferredUsername: user.username, name: user.name, summary: toHtml(parse(profile.description)), - icon: user.avatarId && renderImage(avatar), - image: user.bannerId && renderImage(banner), + icon: avatar ? renderImage(avatar) : null, + image: banner ? renderImage(banner) : null, tag, manuallyApprovesFollowers: user.isLocked, publicKey: renderKey(user, keypair), diff --git a/src/remote/activitypub/request.ts b/src/remote/activitypub/request.ts index 8aca5e8102..7dc48c15e3 100644 --- a/src/remote/activitypub/request.ts +++ b/src/remote/activitypub/request.ts @@ -12,6 +12,7 @@ import { apLogger } from './logger'; import { UserKeypairs } from '../../models'; import fetchMeta from '../../misc/fetch-meta'; import { toPuny } from '../../misc/convert-host'; +import { ensure } from '../../prelude/ensure'; export const logger = apLogger.createSubLogger('deliver'); @@ -38,7 +39,7 @@ export default async (user: ILocalUser, url: string, object: any) => { const keypair = await UserKeypairs.findOne({ userId: user.id - }); + }).then(ensure); const _ = new Promise((resolve, reject) => { const req = request({ @@ -56,7 +57,7 @@ export default async (user: ILocalUser, url: string, object: any) => { 'Digest': `SHA-256=${hash}` } }, res => { - if (res.statusCode >= 400) { + if (res.statusCode! >= 400) { logger.warn(`${url} --> ${res.statusCode}`); reject(res); } else { @@ -73,7 +74,7 @@ export default async (user: ILocalUser, url: string, object: any) => { }); // Signature: Signature ... => Signature: ... - let sig = req.getHeader('Signature').toString(); + let sig = req.getHeader('Signature')!.toString(); sig = sig.replace(/^Signature /, ''); req.setHeader('Signature', sig); @@ -112,7 +113,7 @@ async function resolveAddr(domain: string) { function resolveAddrInner(domain: string, options: IRunOptions = {}): Promise { return new Promise((res, rej) => { - lookup(domain, options, (error: any, address: string | string[]) => { + lookup(domain, options, (error, address) => { if (error) return rej(error); return res(Array.isArray(address) ? address[0] : address); }); diff --git a/src/remote/resolve-user.ts b/src/remote/resolve-user.ts index 6a8ce45c91..9b518f5e81 100644 --- a/src/remote/resolve-user.ts +++ b/src/remote/resolve-user.ts @@ -10,18 +10,31 @@ import { toPuny } from '../misc/convert-host'; const logger = remoteLogger.createSubLogger('resolve-user'); -export async function resolveUser(username: string, host: string, option?: any, resync = false): Promise { +export async function resolveUser(username: string, host: string | null, option?: any, resync = false): Promise { const usernameLower = username.toLowerCase(); - host = toPuny(host); if (host == null) { logger.info(`return local user: ${usernameLower}`); - return await Users.findOne({ usernameLower, host: null }); + return await Users.findOne({ usernameLower, host: null }).then(u => { + if (u == null) { + throw 'user not found'; + } else { + return u; + } + }); } + host = toPuny(host); + if (config.host == host) { logger.info(`return local user: ${usernameLower}`); - return await Users.findOne({ usernameLower, host: null }); + return await Users.findOne({ usernameLower, host: null }).then(u => { + if (u == null) { + throw 'user not found'; + } else { + return u; + } + }); } const user = await Users.findOne({ usernameLower, host }, option); @@ -63,7 +76,13 @@ export async function resolveUser(username: string, host: string, option?: any, await updatePerson(self.href); logger.info(`return resynced remote user: ${acctLower}`); - return await Users.findOne({ uri: self.href }); + return await Users.findOne({ uri: self.href }).then(u => { + if (u == null) { + throw 'user not found'; + } else { + return u; + } + }); } logger.info(`return existing remote user: ${acctLower}`); @@ -76,7 +95,7 @@ async function resolveSelf(acctLower: string) { logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${ e.statusCode || e.message }`); throw new Error(`Failed to WebFinger for ${acctLower}: ${ e.statusCode || e.message }`); }); - const self = finger.links.find(link => link.rel && link.rel.toLowerCase() === 'self'); + const self = finger.links.find(link => link.rel != null && link.rel.toLowerCase() === 'self'); if (!self) { logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: self link not found`); throw new Error('self link not found'); diff --git a/src/remote/webfinger.ts b/src/remote/webfinger.ts index 67535e37db..800673943b 100644 --- a/src/remote/webfinger.ts +++ b/src/remote/webfinger.ts @@ -5,7 +5,7 @@ import { query as urlQuery } from '../prelude/url'; type ILink = { href: string; - rel: string; + rel?: string; }; type IWebFinger = { diff --git a/src/server/activitypub.ts b/src/server/activitypub.ts index 3b39977d47..12fccbfa7d 100644 --- a/src/server/activitypub.ts +++ b/src/server/activitypub.ts @@ -17,6 +17,7 @@ import { isSelfHost } from '../misc/convert-host'; import { Notes, Users, Emojis, UserKeypairs, Polls } from '../models'; import { ILocalUser, User } from '../models/entities/user'; import { In } from 'typeorm'; +import { ensure } from '../prelude/ensure'; // Init router const router = new Router(); @@ -123,8 +124,8 @@ router.get('/questions/:question', async (ctx, next) => { return; } - const user = await Users.findOne(pollNote.userId); - const poll = await Polls.findOne({ noteId: pollNote.id }); + const user = await Users.findOne(pollNote.userId).then(ensure); + const poll = await Polls.findOne({ noteId: pollNote.id }).then(ensure); ctx.body = renderActivity(await renderQuestion(user as ILocalUser, pollNote, poll)); setResponseType(ctx); @@ -156,9 +157,7 @@ router.get('/users/:user/publickey', async ctx => { return; } - const keypair = await UserKeypairs.findOne({ - userId: user.id - }); + const keypair = await UserKeypairs.findOne(user.id).then(ensure); if (Users.isLocalUser(user)) { ctx.body = renderActivity(renderKey(user, keypair)); @@ -189,7 +188,7 @@ router.get('/users/:user', async (ctx, next) => { const user = await Users.findOne({ id: userId, host: null - }); + }).then(ensure); await userInfo(ctx, user); }); @@ -200,7 +199,7 @@ router.get('/@:user', async (ctx, next) => { const user = await Users.findOne({ usernameLower: ctx.params.user.toLowerCase(), host: null - }); + }).then(ensure); await userInfo(ctx, user); }); diff --git a/src/server/activitypub/featured.ts b/src/server/activitypub/featured.ts index f43312d79a..86ec1000c7 100644 --- a/src/server/activitypub/featured.ts +++ b/src/server/activitypub/featured.ts @@ -5,6 +5,7 @@ import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-c import { setResponseType } from '../activitypub'; import renderNote from '../../remote/activitypub/renderer/note'; import { Users, Notes, UserNotePinings } from '../../models'; +import { ensure } from '../../prelude/ensure'; export default async (ctx: Router.IRouterContext) => { const userId = ctx.params.user; @@ -22,13 +23,14 @@ export default async (ctx: Router.IRouterContext) => { const pinings = await UserNotePinings.find({ userId: user.id }); - const pinnedNotes = await Promise.all(pinings.map(pining => Notes.findOne(pining.noteId))); + const pinnedNotes = await Promise.all(pinings.map(pining => + Notes.findOne(pining.noteId).then(ensure))); const renderedNotes = await Promise.all(pinnedNotes.map(note => renderNote(note))); const rendered = renderOrderedCollection( `${config.url}/users/${userId}/collections/featured`, - renderedNotes.length, null, null, renderedNotes + renderedNotes.length, undefined, undefined, renderedNotes ); ctx.body = renderActivity(rendered); diff --git a/src/server/activitypub/followers.ts b/src/server/activitypub/followers.ts index 62c54399ed..e48dc57f7a 100644 --- a/src/server/activitypub/followers.ts +++ b/src/server/activitypub/followers.ts @@ -69,18 +69,18 @@ export default async (ctx: Router.IRouterContext) => { cursor })}`, user.followersCount, renderedFollowers, partOf, - null, + undefined, inStock ? `${partOf}?${url.query({ page: 'true', cursor: followings[followings.length - 1].id - })}` : null + })}` : undefined ); ctx.body = renderActivity(rendered); setResponseType(ctx); } else { // index page - const rendered = renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`, null); + const rendered = renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`); ctx.body = renderActivity(rendered); ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); setResponseType(ctx); diff --git a/src/server/activitypub/following.ts b/src/server/activitypub/following.ts index 4894aac1f8..4a7314e0ce 100644 --- a/src/server/activitypub/following.ts +++ b/src/server/activitypub/following.ts @@ -70,18 +70,18 @@ export default async (ctx: Router.IRouterContext) => { cursor })}`, user.followingCount, renderedFollowees, partOf, - null, + undefined, inStock ? `${partOf}?${url.query({ page: 'true', cursor: followings[followings.length - 1].id - })}` : null + })}` : undefined ); ctx.body = renderActivity(rendered); setResponseType(ctx); } else { // index page - const rendered = renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`, null); + const rendered = renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`); ctx.body = renderActivity(rendered); ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); setResponseType(ctx); diff --git a/src/server/activitypub/outbox.ts b/src/server/activitypub/outbox.ts index 377f43c986..118d8f00a9 100644 --- a/src/server/activitypub/outbox.ts +++ b/src/server/activitypub/outbox.ts @@ -15,6 +15,7 @@ import { Users, Notes } from '../../models'; import { makePaginationQuery } from '../api/common/make-pagination-query'; import { Brackets } from 'typeorm'; import { Note } from '../../models/entities/note'; +import { ensure } from '../../prelude/ensure'; export default async (ctx: Router.IRouterContext) => { const userId = ctx.params.user; @@ -73,11 +74,11 @@ export default async (ctx: Router.IRouterContext) => { notes.length ? `${partOf}?${url.query({ page: 'true', since_id: notes[0].id - })}` : null, + })}` : undefined, notes.length ? `${partOf}?${url.query({ page: 'true', until_id: notes[notes.length - 1].id - })}` : null + })}` : undefined ); ctx.body = renderActivity(rendered); @@ -99,9 +100,9 @@ export default async (ctx: Router.IRouterContext) => { * Pack Create or Announce Activity * @param note Note */ -export async function packActivity(note: Note): Promise { +export async function packActivity(note: Note): Promise { if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length == 0)) { - const renote = await Notes.findOne(note.renoteId); + const renote = await Notes.findOne(note.renoteId).then(ensure); return renderAnnounce(renote.uri ? renote.uri : `${config.url}/notes/${renote.id}`, note); } diff --git a/src/server/api/api-handler.ts b/src/server/api/api-handler.ts index 827aecdf25..2de6994f32 100644 --- a/src/server/api/api-handler.ts +++ b/src/server/api/api-handler.ts @@ -15,11 +15,11 @@ export default (endpoint: IEndpoint, ctx: Koa.BaseContext) => new Promise((res) ctx.status = x; ctx.body = { error: { - message: y.message, - code: y.code, - id: y.id, - kind: y.kind, - ...(y.info ? { info: y.info } : {}) + message: y!.message, + code: y!.code, + id: y!.id, + kind: y!.kind, + ...(y!.info ? { info: y!.info } : {}) } }; } else { @@ -31,9 +31,9 @@ export default (endpoint: IEndpoint, ctx: Koa.BaseContext) => new Promise((res) // Authentication authenticate(body['i']).then(([user, app]) => { // API invoking - call(endpoint.name, user, app, body, (ctx.req as any).file).then(res => { + call(endpoint.name, user, app, body, (ctx.req as any).file).then((res: any) => { reply(res); - }).catch(e => { + }).catch((e: ApiError) => { reply(e.httpStatusCode ? e.httpStatusCode : e.kind == 'client' ? 400 : 500, e); }); }).catch(() => { diff --git a/src/server/api/authenticate.ts b/src/server/api/authenticate.ts index e293e3fed0..5c2b7e45cf 100644 --- a/src/server/api/authenticate.ts +++ b/src/server/api/authenticate.ts @@ -3,7 +3,7 @@ import { User } from '../../models/entities/user'; import { App } from '../../models/entities/app'; import { Users, AccessTokens, Apps } from '../../models'; -export default async (token: string): Promise<[User, App]> => { +export default async (token: string): Promise<[User | null | undefined, App | null | undefined]> => { if (token == null) { return [null, null]; } diff --git a/src/server/api/call.ts b/src/server/api/call.ts index d72f2cc9e6..c79f8eef5d 100644 --- a/src/server/api/call.ts +++ b/src/server/api/call.ts @@ -12,7 +12,7 @@ const accessDenied = { id: '56f35758-7dd5-468b-8439-5d6fb8ec9b8e' }; -export default async (endpoint: string, user: User, app: App, data: any, file?: any) => { +export default async (endpoint: string, user: User | null | undefined, app: App | null | undefined, data: any, file?: any) => { const isSecure = user != null && app == null; const ep = endpoints.find(e => e.name === endpoint); @@ -39,15 +39,15 @@ export default async (endpoint: string, user: User, app: App, data: any, file?: }); } - if (ep.meta.requireCredential && user.isSuspended) { + if (ep.meta.requireCredential && user!.isSuspended) { throw new ApiError(accessDenied, { reason: 'Your account has been suspended.' }); } - if (ep.meta.requireAdmin && !user.isAdmin) { + if (ep.meta.requireAdmin && !user!.isAdmin) { throw new ApiError(accessDenied, { reason: 'You are not the admin.' }); } - if (ep.meta.requireModerator && !user.isAdmin && !user.isModerator) { + if (ep.meta.requireModerator && !user!.isAdmin && !user!.isModerator) { throw new ApiError(accessDenied, { reason: 'You are not a moderator.' }); } @@ -61,7 +61,7 @@ export default async (endpoint: string, user: User, app: App, data: any, file?: if (ep.meta.requireCredential && ep.meta.limit) { // Rate limit - await limiter(ep, user).catch(e => { + await limiter(ep, user!).catch(e => { throw new ApiError({ message: 'Rate limit exceeded. Please try again later.', code: 'RATE_LIMIT_EXCEEDED', diff --git a/src/server/api/common/make-pagination-query.ts b/src/server/api/common/make-pagination-query.ts index 0c859a4f8d..51c11e5dff 100644 --- a/src/server/api/common/make-pagination-query.ts +++ b/src/server/api/common/make-pagination-query.ts @@ -1,6 +1,6 @@ import { SelectQueryBuilder } from 'typeorm'; -export function makePaginationQuery(q: SelectQueryBuilder, sinceId: string, untilId: string, sinceDate?: number, untilDate?: number) { +export function makePaginationQuery(q: SelectQueryBuilder, sinceId?: string, untilId?: string, sinceDate?: number, untilDate?: number) { if (sinceId && untilId) { q.andWhere(`${q.alias}.id > :sinceId`, { sinceId: sinceId }); q.andWhere(`${q.alias}.id < :untilId`, { untilId: untilId }); diff --git a/src/server/api/define.ts b/src/server/api/define.ts index a18419bcf6..990cbf2a86 100644 --- a/src/server/api/define.ts +++ b/src/server/api/define.ts @@ -5,9 +5,9 @@ import { ApiError } from './error'; import { App } from '../../models/entities/app'; type Params = { - [P in keyof T['params']]: T['params'][P]['transform'] extends Function - ? ReturnType - : ReturnType[0]; + [P in keyof T['params']]: NonNullable[P]['transform'] extends Function + ? ReturnType[P]['transform']> + : ReturnType[P]['validator']['get']>[0]; }; export type Response = Record | void; @@ -34,11 +34,11 @@ export default function (meta: T, cb: (params: Params(defs: T, params: any): [Params, ApiError] { +function getParams(defs: T, params: any): [Params, ApiError | null] { if (defs.params == null) return [params, null]; const x: any = {}; - let err: ApiError = null; + let err: ApiError | null = null; Object.entries(defs.params).some(([k, def]) => { const [v, e] = def.validator.get(params[k]); if (e) { diff --git a/src/server/api/endpoints/admin/abuse-user-reports.ts b/src/server/api/endpoints/admin/abuse-user-reports.ts index 5c5a734c1d..63d1dd795c 100644 --- a/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -29,7 +29,7 @@ export const meta = { export default define(meta, async (ps) => { const query = makePaginationQuery(AbuseUserReports.createQueryBuilder('report'), ps.sinceId, ps.untilId); - const reports = await query.take(ps.limit).getMany(); + const reports = await query.take(ps.limit!).getMany(); return await AbuseUserReports.packMany(reports); }); diff --git a/src/server/api/endpoints/admin/drive/files.ts b/src/server/api/endpoints/admin/drive/files.ts index 1ccabc92d9..7c6672e6de 100644 --- a/src/server/api/endpoints/admin/drive/files.ts +++ b/src/server/api/endpoints/admin/drive/files.ts @@ -56,8 +56,8 @@ export default define(meta, async (ps, me) => { const files = await DriveFiles.find({ where: q, - take: ps.limit, - order: sort[ps.sort] || sort[fallback], + take: ps.limit!, + order: sort[ps.sort!] || sort[fallback], skip: ps.offset }); diff --git a/src/server/api/endpoints/admin/emoji/list.ts b/src/server/api/endpoints/admin/emoji/list.ts index 26385d4e23..cf73e4cc75 100644 --- a/src/server/api/endpoints/admin/emoji/list.ts +++ b/src/server/api/endpoints/admin/emoji/list.ts @@ -23,7 +23,7 @@ export const meta = { export default define(meta, async (ps) => { const emojis = await Emojis.find({ - host: toPuny(ps.host) + host: ps.host ? toPuny(ps.host) : null }); return emojis.map(e => ({ diff --git a/src/server/api/endpoints/admin/federation/remove-all-following.ts b/src/server/api/endpoints/admin/federation/remove-all-following.ts index fca76e7086..25aae6db88 100644 --- a/src/server/api/endpoints/admin/federation/remove-all-following.ts +++ b/src/server/api/endpoints/admin/federation/remove-all-following.ts @@ -2,6 +2,7 @@ import $ from 'cafy'; import define from '../../../define'; import deleteFollowing from '../../../../../services/following/delete'; import { Followings, Users } from '../../../../../models'; +import { ensure } from '../../../../../prelude/ensure'; export const meta = { tags: ['admin'], @@ -22,13 +23,11 @@ export default define(meta, async (ps, me) => { }); const pairs = await Promise.all(followings.map(f => Promise.all([ - Users.findOne(f.followerId), - Users.findOne(f.followeeId) + Users.findOne(f.followerId).then(ensure), + Users.findOne(f.followeeId).then(ensure) ]))); for (const pair of pairs) { deleteFollowing(pair[0], pair[1]); } - - return; }); diff --git a/src/server/api/endpoints/admin/logs.ts b/src/server/api/endpoints/admin/logs.ts index 907b1fdc14..e1419bdfe1 100644 --- a/src/server/api/endpoints/admin/logs.ts +++ b/src/server/api/endpoints/admin/logs.ts @@ -65,7 +65,7 @@ export default define(meta, async (ps) => { } } - const logs = await query.orderBy('log.createdAt', 'DESC').take(ps.limit).getMany(); + const logs = await query.orderBy('log.createdAt', 'DESC').take(ps.limit!).getMany(); return logs; }); diff --git a/src/server/api/endpoints/admin/queue/jobs.ts b/src/server/api/endpoints/admin/queue/jobs.ts index c2496d7ef7..4e47775692 100644 --- a/src/server/api/endpoints/admin/queue/jobs.ts +++ b/src/server/api/endpoints/admin/queue/jobs.ts @@ -28,9 +28,9 @@ export default define(meta, async (ps) => { const queue = ps.domain === 'deliver' ? deliverQueue : ps.domain === 'inbox' ? inboxQueue : - null; + null as never; - const jobs = await queue.getJobs([ps.state], 0, ps.limit); + const jobs = await queue.getJobs([ps.state], 0, ps.limit!); return jobs.map(job => ({ id: job.id, diff --git a/src/server/api/endpoints/admin/show-users.ts b/src/server/api/endpoints/admin/show-users.ts index 73976b9872..97760ae797 100644 --- a/src/server/api/endpoints/admin/show-users.ts +++ b/src/server/api/endpoints/admin/show-users.ts @@ -82,7 +82,7 @@ export default define(meta, async (ps, me) => { default: query.orderBy('user.id', 'ASC'); break; } - query.take(ps.limit); + query.take(ps.limit!); query.skip(ps.offset); const users = await query.getMany(); diff --git a/src/server/api/endpoints/admin/update-remote-user.ts b/src/server/api/endpoints/admin/update-remote-user.ts index 0be9047d5a..f9716328d5 100644 --- a/src/server/api/endpoints/admin/update-remote-user.ts +++ b/src/server/api/endpoints/admin/update-remote-user.ts @@ -28,5 +28,5 @@ export const meta = { export default define(meta, async (ps) => { const user = await getRemoteUser(ps.userId); - await updatePerson(user.uri); + await updatePerson(user.uri!); }); diff --git a/src/server/api/endpoints/ap/show.ts b/src/server/api/endpoints/ap/show.ts index 35c5dd318d..1b992eeaa7 100644 --- a/src/server/api/endpoints/ap/show.ts +++ b/src/server/api/endpoints/ap/show.ts @@ -123,14 +123,14 @@ async function fetchAny(uri: string) { const note = await createNote(object.id); return { type: 'Note', - object: await Notes.pack(note, null, { detail: true }) + object: await Notes.pack(note!, null, { detail: true }) }; } return null; } -async function mergePack(user: User, note: Note) { +async function mergePack(user: User | null | undefined, note: Note | null | undefined) { if (user != null) { return { type: 'User', diff --git a/src/server/api/endpoints/auth/accept.ts b/src/server/api/endpoints/auth/accept.ts index 0baa6b111c..55898b59db 100644 --- a/src/server/api/endpoints/auth/accept.ts +++ b/src/server/api/endpoints/auth/accept.ts @@ -5,6 +5,7 @@ import define from '../../define'; import { ApiError } from '../../error'; import { AuthSessions, AccessTokens, Apps } from '../../../../models'; import { genId } from '../../../../misc/gen-id'; +import { ensure } from '../../../../prelude/ensure'; export const meta = { tags: ['auth'], @@ -48,7 +49,7 @@ export default define(meta, async (ps, user) => { if (exist == null) { // Lookup app - const app = await Apps.findOne(session.appId); + const app = await Apps.findOne(session.appId).then(ensure); // Generate Hash const sha256 = crypto.createHash('sha256'); diff --git a/src/server/api/endpoints/auth/session/userkey.ts b/src/server/api/endpoints/auth/session/userkey.ts index 8524b96f94..7126ac52c1 100644 --- a/src/server/api/endpoints/auth/session/userkey.ts +++ b/src/server/api/endpoints/auth/session/userkey.ts @@ -2,6 +2,7 @@ import $ from 'cafy'; import define from '../../../define'; import { ApiError } from '../../../error'; import { Apps, AuthSessions, AccessTokens, Users } from '../../../../../models'; +import { ensure } from '../../../../../prelude/ensure'; export const meta = { tags: ['auth'], @@ -90,7 +91,7 @@ export default define(meta, async (ps) => { const accessToken = await AccessTokens.findOne({ appId: app.id, userId: session.userId - }); + }).then(ensure); // Delete session AuthSessions.delete(session.id); diff --git a/src/server/api/endpoints/blocking/list.ts b/src/server/api/endpoints/blocking/list.ts index a078891ab0..97f353579d 100644 --- a/src/server/api/endpoints/blocking/list.ts +++ b/src/server/api/endpoints/blocking/list.ts @@ -44,7 +44,7 @@ export default define(meta, async (ps, me) => { .andWhere(`blocking.blockerId = :meId`, { meId: me.id }); const blockings = await query - .take(ps.limit) + .take(ps.limit!) .getMany(); return await Blockings.packMany(blockings, me); diff --git a/src/server/api/endpoints/charts/active-users.ts b/src/server/api/endpoints/charts/active-users.ts index 60fa72c5c7..f0349b17f3 100644 --- a/src/server/api/endpoints/charts/active-users.ts +++ b/src/server/api/endpoints/charts/active-users.ts @@ -33,5 +33,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await activeUsersChart.getChart(ps.span as any, ps.limit); + return await activeUsersChart.getChart(ps.span as any, ps.limit!); }); diff --git a/src/server/api/endpoints/charts/drive.ts b/src/server/api/endpoints/charts/drive.ts index a9676e1586..ae6d894407 100644 --- a/src/server/api/endpoints/charts/drive.ts +++ b/src/server/api/endpoints/charts/drive.ts @@ -33,5 +33,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await driveChart.getChart(ps.span as any, ps.limit); + return await driveChart.getChart(ps.span as any, ps.limit!); }); diff --git a/src/server/api/endpoints/charts/federation.ts b/src/server/api/endpoints/charts/federation.ts index 95f16c76a7..34e9bfee5f 100644 --- a/src/server/api/endpoints/charts/federation.ts +++ b/src/server/api/endpoints/charts/federation.ts @@ -33,5 +33,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await federationChart.getChart(ps.span as any, ps.limit); + return await federationChart.getChart(ps.span as any, ps.limit!); }); diff --git a/src/server/api/endpoints/charts/hashtag.ts b/src/server/api/endpoints/charts/hashtag.ts index a7ec12707e..eceb0b275c 100644 --- a/src/server/api/endpoints/charts/hashtag.ts +++ b/src/server/api/endpoints/charts/hashtag.ts @@ -40,5 +40,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await hashtagChart.getChart(ps.span as any, ps.limit, ps.tag); + return await hashtagChart.getChart(ps.span as any, ps.limit!, ps.tag); }); diff --git a/src/server/api/endpoints/charts/instance.ts b/src/server/api/endpoints/charts/instance.ts index cf3094f7e1..e99c17ae65 100644 --- a/src/server/api/endpoints/charts/instance.ts +++ b/src/server/api/endpoints/charts/instance.ts @@ -41,5 +41,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await instanceChart.getChart(ps.span as any, ps.limit, ps.host); + return await instanceChart.getChart(ps.span as any, ps.limit!, ps.host); }); diff --git a/src/server/api/endpoints/charts/network.ts b/src/server/api/endpoints/charts/network.ts index c0fcd95fe9..648588fbe5 100644 --- a/src/server/api/endpoints/charts/network.ts +++ b/src/server/api/endpoints/charts/network.ts @@ -33,5 +33,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await networkChart.getChart(ps.span as any, ps.limit); + return await networkChart.getChart(ps.span as any, ps.limit!); }); diff --git a/src/server/api/endpoints/charts/notes.ts b/src/server/api/endpoints/charts/notes.ts index 86f30e4b89..074c4978cd 100644 --- a/src/server/api/endpoints/charts/notes.ts +++ b/src/server/api/endpoints/charts/notes.ts @@ -33,5 +33,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await notesChart.getChart(ps.span as any, ps.limit); + return await notesChart.getChart(ps.span as any, ps.limit!); }); diff --git a/src/server/api/endpoints/charts/user/drive.ts b/src/server/api/endpoints/charts/user/drive.ts index e3696dfda1..918fb62c6a 100644 --- a/src/server/api/endpoints/charts/user/drive.ts +++ b/src/server/api/endpoints/charts/user/drive.ts @@ -42,5 +42,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await perUserDriveChart.getChart(ps.span as any, ps.limit, ps.userId); + return await perUserDriveChart.getChart(ps.span as any, ps.limit!, ps.userId); }); diff --git a/src/server/api/endpoints/charts/user/following.ts b/src/server/api/endpoints/charts/user/following.ts index 8feba0bd16..5d86e85d31 100644 --- a/src/server/api/endpoints/charts/user/following.ts +++ b/src/server/api/endpoints/charts/user/following.ts @@ -42,5 +42,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await perUserFollowingChart.getChart(ps.span as any, ps.limit, ps.userId); + return await perUserFollowingChart.getChart(ps.span as any, ps.limit!, ps.userId); }); diff --git a/src/server/api/endpoints/charts/user/notes.ts b/src/server/api/endpoints/charts/user/notes.ts index 8c1db54f76..d39a20df16 100644 --- a/src/server/api/endpoints/charts/user/notes.ts +++ b/src/server/api/endpoints/charts/user/notes.ts @@ -42,5 +42,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await perUserNotesChart.getChart(ps.span as any, ps.limit, ps.userId); + return await perUserNotesChart.getChart(ps.span as any, ps.limit!, ps.userId); }); diff --git a/src/server/api/endpoints/charts/user/reactions.ts b/src/server/api/endpoints/charts/user/reactions.ts index 7c9b2508ae..5b88a1d715 100644 --- a/src/server/api/endpoints/charts/user/reactions.ts +++ b/src/server/api/endpoints/charts/user/reactions.ts @@ -42,5 +42,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await perUserReactionsChart.getChart(ps.span as any, ps.limit, ps.userId); + return await perUserReactionsChart.getChart(ps.span as any, ps.limit!, ps.userId); }); diff --git a/src/server/api/endpoints/charts/users.ts b/src/server/api/endpoints/charts/users.ts index 3ed5e09349..17de5756da 100644 --- a/src/server/api/endpoints/charts/users.ts +++ b/src/server/api/endpoints/charts/users.ts @@ -33,5 +33,5 @@ export const meta = { }; export default define(meta, async (ps) => { - return await usersChart.getChart(ps.span as any, ps.limit); + return await usersChart.getChart(ps.span as any, ps.limit!); }); diff --git a/src/server/api/endpoints/drive/files.ts b/src/server/api/endpoints/drive/files.ts index 400b73d3b7..4e4db6c780 100644 --- a/src/server/api/endpoints/drive/files.ts +++ b/src/server/api/endpoints/drive/files.ts @@ -66,7 +66,7 @@ export default define(meta, async (ps, user) => { } } - const files = await query.take(ps.limit).getMany(); + const files = await query.take(ps.limit!).getMany(); return await DriveFiles.packMany(files, { detail: false, self: true }); }); diff --git a/src/server/api/endpoints/drive/files/create.ts b/src/server/api/endpoints/drive/files/create.ts index 5702c70fc0..340a39a41c 100644 --- a/src/server/api/endpoints/drive/files/create.ts +++ b/src/server/api/endpoints/drive/files/create.ts @@ -92,6 +92,6 @@ export default define(meta, async (ps, user, app, file, cleanup) => { apiLogger.error(e); throw new ApiError(); } finally { - cleanup(); + cleanup!(); } }); diff --git a/src/server/api/endpoints/drive/files/show.ts b/src/server/api/endpoints/drive/files/show.ts index b516ec2df6..e8c0e683c9 100644 --- a/src/server/api/endpoints/drive/files/show.ts +++ b/src/server/api/endpoints/drive/files/show.ts @@ -63,7 +63,7 @@ export const meta = { }; export default define(meta, async (ps, user) => { - let file: DriveFile; + let file: DriveFile | undefined; if (ps.fileId) { file = await DriveFiles.findOne(ps.fileId); @@ -81,14 +81,14 @@ export default define(meta, async (ps, user) => { throw new ApiError(meta.errors.fileIdOrUrlRequired); } - if (!user.isAdmin && !user.isModerator && (file.userId !== user.id)) { - throw new ApiError(meta.errors.accessDenied); - } - if (file == null) { throw new ApiError(meta.errors.noSuchFile); } + if (!user.isAdmin && !user.isModerator && (file.userId !== user.id)) { + throw new ApiError(meta.errors.accessDenied); + } + return await DriveFiles.pack(file, { detail: true, self: true diff --git a/src/server/api/endpoints/drive/folders.ts b/src/server/api/endpoints/drive/folders.ts index f5c3816407..08ae2ff709 100644 --- a/src/server/api/endpoints/drive/folders.ts +++ b/src/server/api/endpoints/drive/folders.ts @@ -54,7 +54,7 @@ export default define(meta, async (ps, user) => { query.andWhere('folder.parentId IS NULL'); } - const folders = await query.take(ps.limit).getMany(); + const folders = await query.take(ps.limit!).getMany(); return await Promise.all(folders.map(folder => DriveFolders.pack(folder))); }); diff --git a/src/server/api/endpoints/drive/folders/update.ts b/src/server/api/endpoints/drive/folders/update.ts index 90129bed63..7d3ece00a3 100644 --- a/src/server/api/endpoints/drive/folders/update.ts +++ b/src/server/api/endpoints/drive/folders/update.ts @@ -102,10 +102,10 @@ export default define(meta, async (ps, user) => { id: folderId }); - if (folder2.id === folder.id) { + if (folder2!.id === folder!.id) { return true; - } else if (folder2.parentId) { - return await checkCircle(folder2.parentId); + } else if (folder2!.parentId) { + return await checkCircle(folder2!.parentId); } else { return false; } diff --git a/src/server/api/endpoints/drive/stream.ts b/src/server/api/endpoints/drive/stream.ts index 9a84627767..96d9f82421 100644 --- a/src/server/api/endpoints/drive/stream.ts +++ b/src/server/api/endpoints/drive/stream.ts @@ -50,7 +50,7 @@ export default define(meta, async (ps, user) => { } } - const files = await query.take(ps.limit).getMany(); + const files = await query.take(ps.limit!).getMany(); return await DriveFiles.packMany(files, { detail: false, self: true }); }); diff --git a/src/server/api/endpoints/federation/instances.ts b/src/server/api/endpoints/federation/instances.ts index 1946d26dec..301338ed96 100644 --- a/src/server/api/endpoints/federation/instances.ts +++ b/src/server/api/endpoints/federation/instances.ts @@ -86,7 +86,7 @@ export default define(meta, async (ps, me) => { } } - const instances = await query.take(ps.limit).skip(ps.offset).getMany(); + const instances = await query.take(ps.limit!).skip(ps.offset).getMany(); return instances; }); diff --git a/src/server/api/endpoints/games/reversi/games.ts b/src/server/api/endpoints/games/reversi/games.ts index 07736e0424..7267157e0e 100644 --- a/src/server/api/endpoints/games/reversi/games.ts +++ b/src/server/api/endpoints/games/reversi/games.ts @@ -41,7 +41,7 @@ export default define(meta, async (ps, user) => { } // Fetch games - const games = await query.take(ps.limit).getMany(); + const games = await query.take(ps.limit!).getMany(); return await Promise.all(games.map((g) => ReversiGames.pack(g, user, { detail: false diff --git a/src/server/api/endpoints/games/reversi/match.ts b/src/server/api/endpoints/games/reversi/match.ts index e34d3c67f4..da367b5978 100644 --- a/src/server/api/endpoints/games/reversi/match.ts +++ b/src/server/api/endpoints/games/reversi/match.ts @@ -70,7 +70,7 @@ export default define(meta, async (ps, user) => { map: eighteight.data, bw: 'random', isLlotheo: false - } as ReversiGame); + } as Partial); publishReversiStream(exist.parentId, 'matched', await ReversiGames.pack(game, exist.parentId)); @@ -107,6 +107,6 @@ export default define(meta, async (ps, user) => { publishReversiStream(child.id, 'invited', packed); publishMainStream(child.id, 'reversiInvited', packed); - return null; + return; } }); diff --git a/src/server/api/endpoints/hashtags/list.ts b/src/server/api/endpoints/hashtags/list.ts index 7996c81669..2998bc1a13 100644 --- a/src/server/api/endpoints/hashtags/list.ts +++ b/src/server/api/endpoints/hashtags/list.ts @@ -86,7 +86,7 @@ export default define(meta, async (ps, me) => { 'tag.attachedRemoteUsersCount', ]); - const tags = await query.take(ps.limit).getMany(); + const tags = await query.take(ps.limit!).getMany(); return tags; }); diff --git a/src/server/api/endpoints/hashtags/search.ts b/src/server/api/endpoints/hashtags/search.ts index fd91a2ebcc..6a9a2df6ef 100644 --- a/src/server/api/endpoints/hashtags/search.ts +++ b/src/server/api/endpoints/hashtags/search.ts @@ -48,7 +48,7 @@ export default define(meta, async (ps) => { const hashtags = await Hashtags.createQueryBuilder('tag') .where('tag.name like :q', { q: ps.query.toLowerCase() + '%' }) .orderBy('tag.count', 'DESC') - .take(ps.limit) + .take(ps.limit!) .skip(ps.offset) .getMany(); diff --git a/src/server/api/endpoints/hashtags/users.ts b/src/server/api/endpoints/hashtags/users.ts index 20cba96d0e..fa58f2f2c0 100644 --- a/src/server/api/endpoints/hashtags/users.ts +++ b/src/server/api/endpoints/hashtags/users.ts @@ -79,7 +79,7 @@ export default define(meta, async (ps, me) => { case '-updatedAt': query.orderBy('user.updatedAt', 'ASC'); break; } - const users = await query.take(ps.limit).getMany(); + const users = await query.take(ps.limit!).getMany(); return await Users.packMany(users, me, { detail: true }); }); diff --git a/src/server/api/endpoints/i/2fa/done.ts b/src/server/api/endpoints/i/2fa/done.ts index edc7cefd26..e23678dcbb 100644 --- a/src/server/api/endpoints/i/2fa/done.ts +++ b/src/server/api/endpoints/i/2fa/done.ts @@ -2,6 +2,7 @@ import $ from 'cafy'; import * as speakeasy from 'speakeasy'; import define from '../../../define'; import { UserProfiles } from '../../../../../models'; +import { ensure } from '../../../../../prelude/ensure'; export const meta = { requireCredential: true, @@ -18,7 +19,7 @@ export const meta = { export default define(meta, async (ps, user) => { const token = ps.token.replace(/\s/g, ''); - const profile = await UserProfiles.findOne({ userId: user.id }); + const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); if (profile.twoFactorTempSecret == null) { throw new Error('二段階認証の設定が開始されていません'); diff --git a/src/server/api/endpoints/i/2fa/register.ts b/src/server/api/endpoints/i/2fa/register.ts index db9a2fe944..76d79b3a49 100644 --- a/src/server/api/endpoints/i/2fa/register.ts +++ b/src/server/api/endpoints/i/2fa/register.ts @@ -5,6 +5,7 @@ import * as QRCode from 'qrcode'; import config from '../../../../../config'; import define from '../../../define'; import { UserProfiles } from '../../../../../models'; +import { ensure } from '../../../../../prelude/ensure'; export const meta = { requireCredential: true, @@ -19,10 +20,10 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const profile = await UserProfiles.findOne({ userId: user.id }); + const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); // Compare password - const same = await bcrypt.compare(ps.password, profile.password); + const same = await bcrypt.compare(ps.password, profile.password!); if (!same) { throw new Error('incorrect password'); diff --git a/src/server/api/endpoints/i/2fa/unregister.ts b/src/server/api/endpoints/i/2fa/unregister.ts index fa25b74391..9c7857e7ef 100644 --- a/src/server/api/endpoints/i/2fa/unregister.ts +++ b/src/server/api/endpoints/i/2fa/unregister.ts @@ -2,6 +2,7 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; import define from '../../../define'; import { UserProfiles } from '../../../../../models'; +import { ensure } from '../../../../../prelude/ensure'; export const meta = { requireCredential: true, @@ -16,10 +17,10 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const profile = await UserProfiles.findOne({ userId: user.id }); + const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); // Compare password - const same = await bcrypt.compare(ps.password, profile.password); + const same = await bcrypt.compare(ps.password, profile.password!); if (!same) { throw new Error('incorrect password'); diff --git a/src/server/api/endpoints/i/authorized-apps.ts b/src/server/api/endpoints/i/authorized-apps.ts index ebf04fcb58..3e9fea19e2 100644 --- a/src/server/api/endpoints/i/authorized-apps.ts +++ b/src/server/api/endpoints/i/authorized-apps.ts @@ -31,7 +31,7 @@ export default define(meta, async (ps, user) => { where: { userId: user.id }, - take: ps.limit, + take: ps.limit!, skip: ps.offset, order: { id: ps.sort == 'asc' ? 1 : -1 diff --git a/src/server/api/endpoints/i/change-password.ts b/src/server/api/endpoints/i/change-password.ts index d0e0695e18..0dda125b9c 100644 --- a/src/server/api/endpoints/i/change-password.ts +++ b/src/server/api/endpoints/i/change-password.ts @@ -2,6 +2,7 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; import define from '../../define'; import { UserProfiles } from '../../../../models'; +import { ensure } from '../../../../prelude/ensure'; export const meta = { requireCredential: true, @@ -20,10 +21,10 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const profile = await UserProfiles.findOne({ userId: user.id }); + const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); // Compare password - const same = await bcrypt.compare(ps.currentPassword, profile.password); + const same = await bcrypt.compare(ps.currentPassword, profile.password!); if (!same) { throw new Error('incorrect password'); diff --git a/src/server/api/endpoints/i/delete-account.ts b/src/server/api/endpoints/i/delete-account.ts index 7ef7aa5fac..389d0b3212 100644 --- a/src/server/api/endpoints/i/delete-account.ts +++ b/src/server/api/endpoints/i/delete-account.ts @@ -2,6 +2,7 @@ import $ from 'cafy'; import * as bcrypt from 'bcryptjs'; import define from '../../define'; import { Users, UserProfiles } from '../../../../models'; +import { ensure } from '../../../../prelude/ensure'; export const meta = { requireCredential: true, @@ -16,10 +17,10 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const profile = await UserProfiles.findOne({ userId: user.id }); + const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); // Compare password - const same = await bcrypt.compare(ps.password, profile.password); + const same = await bcrypt.compare(ps.password, profile.password!); if (!same) { throw new Error('incorrect password'); diff --git a/src/server/api/endpoints/i/favorites.ts b/src/server/api/endpoints/i/favorites.ts index d2d149b2d1..2c25250bea 100644 --- a/src/server/api/endpoints/i/favorites.ts +++ b/src/server/api/endpoints/i/favorites.ts @@ -38,7 +38,7 @@ export default define(meta, async (ps, user) => { .leftJoinAndSelect('favorite.note', 'note'); const favorites = await query - .take(ps.limit) + .take(ps.limit!) .getMany(); return await NoteFavorites.packMany(favorites, user); diff --git a/src/server/api/endpoints/i/notifications.ts b/src/server/api/endpoints/i/notifications.ts index 9b016e0a2d..56074c9d00 100644 --- a/src/server/api/endpoints/i/notifications.ts +++ b/src/server/api/endpoints/i/notifications.ts @@ -81,13 +81,13 @@ export default define(meta, async (ps, user) => { query.setParameters(followingQuery.getParameters()); } - if (ps.includeTypes.length > 0) { + if (ps.includeTypes!.length > 0) { query.andWhere(`notification.type IN (:...includeTypes)`, { includeTypes: ps.includeTypes }); - } else if (ps.excludeTypes.length > 0) { + } else if (ps.excludeTypes!.length > 0) { query.andWhere(`notification.type NOT IN (:...excludeTypes)`, { excludeTypes: ps.excludeTypes }); } - const notifications = await query.take(ps.limit).getMany(); + const notifications = await query.take(ps.limit!).getMany(); // Mark all as read if (notifications.length > 0 && ps.markAsRead) { diff --git a/src/server/api/endpoints/i/regenerate-token.ts b/src/server/api/endpoints/i/regenerate-token.ts index ec53bca979..56c0362c88 100644 --- a/src/server/api/endpoints/i/regenerate-token.ts +++ b/src/server/api/endpoints/i/regenerate-token.ts @@ -4,6 +4,7 @@ import { publishMainStream } from '../../../../services/stream'; import generateUserToken from '../../common/generate-native-user-token'; import define from '../../define'; import { Users, UserProfiles } from '../../../../models'; +import { ensure } from '../../../../prelude/ensure'; export const meta = { requireCredential: true, @@ -18,10 +19,10 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const profile = await UserProfiles.findOne({ userId: user.id }); + const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); // Compare password - const same = await bcrypt.compare(ps.password, profile.password); + const same = await bcrypt.compare(ps.password, profile.password!); if (!same) { throw new Error('incorrect password'); diff --git a/src/server/api/endpoints/i/signin-history.ts b/src/server/api/endpoints/i/signin-history.ts index e9ae19d734..74648951fd 100644 --- a/src/server/api/endpoints/i/signin-history.ts +++ b/src/server/api/endpoints/i/signin-history.ts @@ -29,7 +29,7 @@ export default define(meta, async (ps, user) => { const query = makePaginationQuery(Signins.createQueryBuilder('signin'), ps.sinceId, ps.untilId) .andWhere(`signin.userId = :meId`, { meId: user.id }); - const history = await query.take(ps.limit).getMany(); + const history = await query.take(ps.limit!).getMany(); return await Promise.all(history.map(record => Signins.pack(record))); }); diff --git a/src/server/api/endpoints/i/update-email.ts b/src/server/api/endpoints/i/update-email.ts index d98f0d753e..8b05f3c8cb 100644 --- a/src/server/api/endpoints/i/update-email.ts +++ b/src/server/api/endpoints/i/update-email.ts @@ -9,6 +9,7 @@ import * as ms from 'ms'; import * as bcrypt from 'bcryptjs'; import { apiLogger } from '../../logger'; import { Users, UserProfiles } from '../../../../models'; +import { ensure } from '../../../../prelude/ensure'; export const meta = { requireCredential: true, @@ -32,10 +33,10 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const profile = await UserProfiles.findOne({ userId: user.id }); + const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); // Compare password - const same = await bcrypt.compare(ps.password, profile.password); + const same = await bcrypt.compare(ps.password, profile.password!); if (!same) { throw new Error('incorrect password'); diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts index 3cb514d939..d06ab621c6 100644 --- a/src/server/api/endpoints/i/update.ts +++ b/src/server/api/endpoints/i/update.ts @@ -206,13 +206,13 @@ export default define(meta, async (ps, user, app) => { if (updates.name != null) { const tokens = parsePlain(updates.name); - emojis = emojis.concat(extractEmojis(tokens)); + emojis = emojis.concat(extractEmojis(tokens!)); } if (profile.description != null) { const tokens = parse(profile.description); - emojis = emojis.concat(extractEmojis(tokens)); - tags = extractHashtags(tokens).map(tag => tag.toLowerCase()); + emojis = emojis.concat(extractEmojis(tokens!)); + tags = extractHashtags(tokens!).map(tag => tag.toLowerCase()); } updates.emojis = emojis; diff --git a/src/server/api/endpoints/messaging/history.ts b/src/server/api/endpoints/messaging/history.ts index c0aec61212..c12378eb7e 100644 --- a/src/server/api/endpoints/messaging/history.ts +++ b/src/server/api/endpoints/messaging/history.ts @@ -38,7 +38,7 @@ export default define(meta, async (ps, user) => { const history: MessagingMessage[] = []; - for (let i = 0; i < ps.limit; i++) { + for (let i = 0; i < ps.limit!; i++) { const found = history.map(m => (m.userId === user.id) ? m.recipientId : m.userId); const query = MessagingMessages.createQueryBuilder('message') diff --git a/src/server/api/endpoints/mute/list.ts b/src/server/api/endpoints/mute/list.ts index 5f2d184472..0fd8a4860d 100644 --- a/src/server/api/endpoints/mute/list.ts +++ b/src/server/api/endpoints/mute/list.ts @@ -44,7 +44,7 @@ export default define(meta, async (ps, me) => { .andWhere(`muting.muterId = :meId`, { meId: me.id }); const mutings = await query - .take(ps.limit) + .take(ps.limit!) .getMany(); return await Mutings.packMany(mutings, me); diff --git a/src/server/api/endpoints/my/apps.ts b/src/server/api/endpoints/my/apps.ts index d205d1674c..e8b26362a4 100644 --- a/src/server/api/endpoints/my/apps.ts +++ b/src/server/api/endpoints/my/apps.ts @@ -32,7 +32,7 @@ export default define(meta, async (ps, user) => { const apps = await Apps.find({ where: query, - take: ps.limit, + take: ps.limit!, skip: ps.offset, }); diff --git a/src/server/api/endpoints/notes.ts b/src/server/api/endpoints/notes.ts index 10f6e39845..17ba969350 100644 --- a/src/server/api/endpoints/notes.ts +++ b/src/server/api/endpoints/notes.ts @@ -100,7 +100,7 @@ export default define(meta, async (ps) => { // query.isBot = bot; //} - const notes = await query.take(ps.limit).getMany(); + const notes = await query.take(ps.limit!).getMany(); return await Notes.packMany(notes); }); diff --git a/src/server/api/endpoints/notes/children.ts b/src/server/api/endpoints/notes/children.ts index 72f2c39d6a..2b4ae2a312 100644 --- a/src/server/api/endpoints/notes/children.ts +++ b/src/server/api/endpoints/notes/children.ts @@ -66,7 +66,7 @@ export default define(meta, async (ps, user) => { if (user) generateVisibilityQuery(query, user); if (user) generateMuteQuery(query, user); - const notes = await query.take(ps.limit).getMany(); + const notes = await query.take(ps.limit!).getMany(); return await Notes.packMany(notes, user); }); diff --git a/src/server/api/endpoints/notes/conversation.ts b/src/server/api/endpoints/notes/conversation.ts index 6defd79042..6b26e31c07 100644 --- a/src/server/api/endpoints/notes/conversation.ts +++ b/src/server/api/endpoints/notes/conversation.ts @@ -64,12 +64,13 @@ export default define(meta, async (ps, user) => { async function get(id: any) { i++; const p = await Notes.findOne(id); + if (p == null) return; - if (i > ps.offset) { + if (i > ps.offset!) { conversation.push(p); } - if (conversation.length == ps.limit) { + if (conversation.length == ps.limit!) { return; } diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts index 138f05fb3b..83649015d8 100644 --- a/src/server/api/endpoints/notes/create.ts +++ b/src/server/api/endpoints/notes/create.ts @@ -225,23 +225,24 @@ export const meta = { export default define(meta, async (ps, user, app) => { let visibleUsers: User[] = []; if (ps.visibleUserIds) { - visibleUsers = await Promise.all(ps.visibleUserIds.map(id => Users.findOne(id))); + visibleUsers = (await Promise.all(ps.visibleUserIds.map(id => Users.findOne(id)))) + .filter(x => x != null) as User[]; } let files: DriveFile[] = []; const fileIds = ps.fileIds != null ? ps.fileIds : ps.mediaIds != null ? ps.mediaIds : null; if (fileIds != null) { - files = await Promise.all(fileIds.map(fileId => { - return DriveFiles.findOne({ + files = (await Promise.all(fileIds.map(fileId => + DriveFiles.findOne({ id: fileId, userId: user.id - }); - })); + }) + ))).filter(file => file != null) as DriveFile[]; - files = files.filter(file => file != null); + files = files; } - let renote: Note = null; + let renote: Note | undefined; if (ps.renoteId != null) { // Fetch renote to note renote = await Notes.findOne(ps.renoteId); @@ -253,7 +254,7 @@ export default define(meta, async (ps, user, app) => { } } - let reply: Note = null; + let reply: Note | undefined; if (ps.replyId != null) { // Fetch reply reply = await Notes.findOne(ps.replyId); @@ -290,8 +291,8 @@ export default define(meta, async (ps, user, app) => { choices: ps.poll.choices, multiple: ps.poll.multiple || false, expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null - } : null, - text: ps.text, + } : undefined, + text: ps.text || undefined, reply, renote, cw: ps.cw, @@ -300,9 +301,9 @@ export default define(meta, async (ps, user, app) => { localOnly: ps.localOnly, visibility: ps.visibility, visibleUsers, - apMentions: ps.noExtractMentions ? [] : null, - apHashtags: ps.noExtractHashtags ? [] : null, - apEmojis: ps.noExtractEmojis ? [] : null, + apMentions: ps.noExtractMentions ? [] : undefined, + apHashtags: ps.noExtractHashtags ? [] : undefined, + apEmojis: ps.noExtractEmojis ? [] : undefined, geo: ps.geo }); diff --git a/src/server/api/endpoints/notes/delete.ts b/src/server/api/endpoints/notes/delete.ts index dbaf91bca3..17d44c424d 100644 --- a/src/server/api/endpoints/notes/delete.ts +++ b/src/server/api/endpoints/notes/delete.ts @@ -6,6 +6,7 @@ import * as ms from 'ms'; import { getNote } from '../../common/getters'; import { ApiError } from '../../error'; import { Users } from '../../../../models'; +import { ensure } from '../../../../prelude/ensure'; export const meta = { stability: 'stable', @@ -63,5 +64,5 @@ export default define(meta, async (ps, user) => { } // この操作を行うのが投稿者とは限らない(例えばモデレーター)ため - await deleteNote(await Users.findOne(note.userId), note); + await deleteNote(await Users.findOne(note.userId).then(ensure), note); }); diff --git a/src/server/api/endpoints/notes/featured.ts b/src/server/api/endpoints/notes/featured.ts index c44a5275bb..5d2e5b7d44 100644 --- a/src/server/api/endpoints/notes/featured.ts +++ b/src/server/api/endpoints/notes/featured.ts @@ -41,7 +41,7 @@ export default define(meta, async (ps, user) => { if (user) generateMuteQuery(query, user); - const notes = await query.orderBy('note.score', 'DESC').take(ps.limit).getMany(); + const notes = await query.orderBy('note.score', 'DESC').take(ps.limit!).getMany(); return await Notes.packMany(notes, user); }); diff --git a/src/server/api/endpoints/notes/global-timeline.ts b/src/server/api/endpoints/notes/global-timeline.ts index 7bf62f366b..ceffb1cf4a 100644 --- a/src/server/api/endpoints/notes/global-timeline.ts +++ b/src/server/api/endpoints/notes/global-timeline.ts @@ -84,7 +84,7 @@ export default define(meta, async (ps, user) => { } //#endregion - const timeline = await query.take(ps.limit).getMany(); + const timeline = await query.take(ps.limit!).getMany(); if (user) { activeUsersChart.update(user); diff --git a/src/server/api/endpoints/notes/hybrid-timeline.ts b/src/server/api/endpoints/notes/hybrid-timeline.ts index 76f1682a95..6dfb143003 100644 --- a/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -185,7 +185,7 @@ export default define(meta, async (ps, user) => { } //#endregion - const timeline = await query.take(ps.limit).getMany(); + const timeline = await query.take(ps.limit!).getMany(); if (user) { activeUsersChart.update(user); diff --git a/src/server/api/endpoints/notes/local-timeline.ts b/src/server/api/endpoints/notes/local-timeline.ts index cd07341342..c10c0d7482 100644 --- a/src/server/api/endpoints/notes/local-timeline.ts +++ b/src/server/api/endpoints/notes/local-timeline.ts @@ -100,11 +100,11 @@ export default define(meta, async (ps, user) => { query.andWhere('note.fileIds != \'{}\''); } - if (ps.fileType) { + if (ps.fileType != null) { query.andWhere('note.fileIds != \'{}\''); query.andWhere(new Brackets(qb => { - for (const type of ps.fileType) { - const i = ps.fileType.indexOf(type); + for (const type of ps.fileType!) { + const i = ps.fileType!.indexOf(type); qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, { [`type${i}`]: type }); } })); @@ -120,7 +120,7 @@ export default define(meta, async (ps, user) => { } //#endregion - const timeline = await query.take(ps.limit).getMany(); + const timeline = await query.take(ps.limit!).getMany(); if (user) { activeUsersChart.update(user); diff --git a/src/server/api/endpoints/notes/mentions.ts b/src/server/api/endpoints/notes/mentions.ts index 0bbe7d3327..b7f614915b 100644 --- a/src/server/api/endpoints/notes/mentions.ts +++ b/src/server/api/endpoints/notes/mentions.ts @@ -74,7 +74,7 @@ export default define(meta, async (ps, user) => { query.setParameters(followingQuery.getParameters()); } - const mentions = await query.take(ps.limit).getMany(); + const mentions = await query.take(ps.limit!).getMany(); for (const note of mentions) { read(user.id, note.id); diff --git a/src/server/api/endpoints/notes/polls/recommendation.ts b/src/server/api/endpoints/notes/polls/recommendation.ts index ff838d4f4f..cbd4d35cd4 100644 --- a/src/server/api/endpoints/notes/polls/recommendation.ts +++ b/src/server/api/endpoints/notes/polls/recommendation.ts @@ -58,7 +58,7 @@ export default define(meta, async (ps, user) => { query.setParameters(mutingQuery.getParameters()); //#endregion - const polls = await query.take(ps.limit).skip(ps.offset).getMany(); + const polls = await query.take(ps.limit!).skip(ps.offset).getMany(); if (polls.length === 0) return []; diff --git a/src/server/api/endpoints/notes/polls/vote.ts b/src/server/api/endpoints/notes/polls/vote.ts index dd4d93c7ac..68dee66828 100644 --- a/src/server/api/endpoints/notes/polls/vote.ts +++ b/src/server/api/endpoints/notes/polls/vote.ts @@ -14,6 +14,7 @@ import { PollVotes, NoteWatchings, Users, Polls, UserProfiles } from '../../../. import { Not } from 'typeorm'; import { IRemoteUser } from '../../../../../models/entities/user'; import { genId } from '../../../../../misc/gen-id'; +import { ensure } from '../../../../../prelude/ensure'; export const meta = { desc: { @@ -87,7 +88,7 @@ export default define(meta, async (ps, user) => { throw new ApiError(meta.errors.noPoll); } - const poll = await Polls.findOne({ noteId: note.id }); + const poll = await Polls.findOne({ noteId: note.id }).then(ensure); if (poll.expiresAt && poll.expiresAt < createdAt) { throw new ApiError(meta.errors.alreadyExpired); @@ -149,7 +150,7 @@ export default define(meta, async (ps, user) => { } }); - const profile = await UserProfiles.findOne({ userId: user.id }); + const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); // この投稿をWatchする if (profile.autoWatch !== false) { @@ -158,7 +159,7 @@ export default define(meta, async (ps, user) => { // リモート投票の場合リプライ送信 if (note.userHost != null) { - const pollOwner: IRemoteUser = await Users.findOne(note.userId); + const pollOwner = await Users.findOne(note.userId).then(ensure) as IRemoteUser; deliver(user, renderActivity(await renderVote(user, vote, note, poll, pollOwner)), pollOwner.inbox); } diff --git a/src/server/api/endpoints/notes/reactions.ts b/src/server/api/endpoints/notes/reactions.ts index b1b5ca9d33..bcb0b6d1ec 100644 --- a/src/server/api/endpoints/notes/reactions.ts +++ b/src/server/api/endpoints/notes/reactions.ts @@ -71,7 +71,7 @@ export default define(meta, async (ps, user) => { const reactions = await NoteReactions.find({ where: query, - take: ps.limit, + take: ps.limit!, skip: ps.offset, order: { id: -1 diff --git a/src/server/api/endpoints/notes/renotes.ts b/src/server/api/endpoints/notes/renotes.ts index 81b899836d..74a8ea918e 100644 --- a/src/server/api/endpoints/notes/renotes.ts +++ b/src/server/api/endpoints/notes/renotes.ts @@ -70,7 +70,7 @@ export default define(meta, async (ps, user) => { if (user) generateVisibilityQuery(query, user); if (user) generateMuteQuery(query, user); - const renotes = await query.take(ps.limit).getMany(); + const renotes = await query.take(ps.limit!).getMany(); return await Notes.packMany(renotes, user); }); diff --git a/src/server/api/endpoints/notes/replies.ts b/src/server/api/endpoints/notes/replies.ts index 09b0f17164..980ff2446e 100644 --- a/src/server/api/endpoints/notes/replies.ts +++ b/src/server/api/endpoints/notes/replies.ts @@ -61,7 +61,7 @@ export default define(meta, async (ps, user) => { if (user) generateVisibilityQuery(query, user); if (user) generateMuteQuery(query, user); - const timeline = await query.take(ps.limit).getMany(); + const timeline = await query.take(ps.limit!).getMany(); return await Notes.packMany(timeline, user); }); diff --git a/src/server/api/endpoints/notes/search-by-tag.ts b/src/server/api/endpoints/notes/search-by-tag.ts index 48de88d36e..cba3724b6f 100644 --- a/src/server/api/endpoints/notes/search-by-tag.ts +++ b/src/server/api/endpoints/notes/search-by-tag.ts @@ -100,7 +100,7 @@ export default define(meta, async (ps, me) => { } else { let i = 0; query.andWhere(new Brackets(qb => { - for (const tags of ps.query) { + for (const tags of ps.query!) { qb.orWhere(new Brackets(qb => { for (const tag of tags) { qb.andWhere(`:tag${i} = ANY(note.tags)`, { [`tag${i}`]: tag }); @@ -140,7 +140,7 @@ export default define(meta, async (ps, me) => { } // Search notes - const notes = await query.take(ps.limit).getMany(); + const notes = await query.take(ps.limit!).getMany(); return await Notes.packMany(notes, me); }); diff --git a/src/server/api/endpoints/notes/search.ts b/src/server/api/endpoints/notes/search.ts index cc88fb9380..4d5ac6fbe0 100644 --- a/src/server/api/endpoints/notes/search.ts +++ b/src/server/api/endpoints/notes/search.ts @@ -54,7 +54,7 @@ export default define(meta, async (ps, me) => { index: 'misskey', type: 'note', body: { - size: ps.limit, + size: ps.limit!, from: ps.offset, query: { simple_query_string: { diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts index e22db4d1b0..c27f3df1b7 100644 --- a/src/server/api/endpoints/notes/timeline.ts +++ b/src/server/api/endpoints/notes/timeline.ts @@ -171,7 +171,7 @@ export default define(meta, async (ps, user) => { } //#endregion - const timeline = await query.take(ps.limit).getMany(); + const timeline = await query.take(ps.limit!).getMany(); activeUsersChart.update(user); diff --git a/src/server/api/endpoints/notes/user-list-timeline.ts b/src/server/api/endpoints/notes/user-list-timeline.ts index deda04acb4..05f171af8b 100644 --- a/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/src/server/api/endpoints/notes/user-list-timeline.ts @@ -189,7 +189,7 @@ export default define(meta, async (ps, user) => { } //#endregion - const timeline = await query.take(ps.limit).getMany(); + const timeline = await query.take(ps.limit!).getMany(); activeUsersChart.update(user); diff --git a/src/server/api/endpoints/users.ts b/src/server/api/endpoints/users.ts index f99165f3d5..18af0a2685 100644 --- a/src/server/api/endpoints/users.ts +++ b/src/server/api/endpoints/users.ts @@ -88,7 +88,7 @@ export default define(meta, async (ps, me) => { if (me) generateMuteQueryForUsers(query, me); - query.take(ps.limit); + query.take(ps.limit!); query.skip(ps.offset); const users = await query.getMany(); diff --git a/src/server/api/endpoints/users/followers.ts b/src/server/api/endpoints/users/followers.ts index 64d63e2d03..88a474be7c 100644 --- a/src/server/api/endpoints/users/followers.ts +++ b/src/server/api/endpoints/users/followers.ts @@ -66,7 +66,7 @@ export const meta = { export default define(meta, async (ps, me) => { const user = await Users.findOne(ps.userId != null ? { id: ps.userId } - : { usernameLower: ps.username.toLowerCase(), host: toPuny(ps.host) }); + : { usernameLower: ps.username!.toLowerCase(), host: toPuny(ps.host!) }); if (user == null) { throw new ApiError(meta.errors.noSuchUser); @@ -76,7 +76,7 @@ export default define(meta, async (ps, me) => { .andWhere(`following.followeeId = :userId`, { userId: user.id }); const followings = await query - .take(ps.limit) + .take(ps.limit!) .getMany(); return await Followings.packMany(followings, me, { populateFollower: true }); diff --git a/src/server/api/endpoints/users/following.ts b/src/server/api/endpoints/users/following.ts index 0e28001680..5e017150e8 100644 --- a/src/server/api/endpoints/users/following.ts +++ b/src/server/api/endpoints/users/following.ts @@ -66,7 +66,7 @@ export const meta = { export default define(meta, async (ps, me) => { const user = await Users.findOne(ps.userId != null ? { id: ps.userId } - : { usernameLower: ps.username.toLowerCase(), host: toPuny(ps.host) }); + : { usernameLower: ps.username!.toLowerCase(), host: toPuny(ps.host!) }); if (user == null) { throw new ApiError(meta.errors.noSuchUser); @@ -76,7 +76,7 @@ export default define(meta, async (ps, me) => { .andWhere(`following.followerId = :userId`, { userId: user.id }); const followings = await query - .take(ps.limit) + .take(ps.limit!) .getMany(); return await Followings.packMany(followings, me, { populateFollowee: true }); diff --git a/src/server/api/endpoints/users/get-frequently-replied-users.ts b/src/server/api/endpoints/users/get-frequently-replied-users.ts index f82f437629..a1d140c6c9 100644 --- a/src/server/api/endpoints/users/get-frequently-replied-users.ts +++ b/src/server/api/endpoints/users/get-frequently-replied-users.ts @@ -94,7 +94,7 @@ export default define(meta, async (ps, me) => { const repliedUsersSorted = Object.keys(repliedUsers).sort((a, b) => repliedUsers[b] - repliedUsers[a]); // Extract top replied users - const topRepliedUsers = repliedUsersSorted.slice(0, ps.limit); + const topRepliedUsers = repliedUsersSorted.slice(0, ps.limit!); // Make replies object (includes weights) const repliesObj = await Promise.all(topRepliedUsers.map(async (user) => ({ diff --git a/src/server/api/endpoints/users/notes.ts b/src/server/api/endpoints/users/notes.ts index 6df394cbb1..da23be3c55 100644 --- a/src/server/api/endpoints/users/notes.ts +++ b/src/server/api/endpoints/users/notes.ts @@ -153,11 +153,11 @@ export default define(meta, async (ps, me) => { query.andWhere('note.fileIds != \'{}\''); } - if (ps.fileType) { + if (ps.fileType != null) { query.andWhere('note.fileIds != \'{}\''); query.andWhere(new Brackets(qb => { - for (const type of ps.fileType) { - const i = ps.fileType.indexOf(type); + for (const type of ps.fileType!) { + const i = ps.fileType!.indexOf(type); qb.orWhere(`:type${i} = ANY(note.attachedFileTypes)`, { [`type${i}`]: type }); } })); @@ -194,7 +194,7 @@ export default define(meta, async (ps, me) => { //#endregion - const timeline = await query.take(ps.limit).getMany(); + const timeline = await query.take(ps.limit!).getMany(); return await Notes.packMany(timeline, user); }); diff --git a/src/server/api/endpoints/users/recommendation.ts b/src/server/api/endpoints/users/recommendation.ts index 2c82d6613e..9e16e34e39 100644 --- a/src/server/api/endpoints/users/recommendation.ts +++ b/src/server/api/endpoints/users/recommendation.ts @@ -53,7 +53,7 @@ export default define(meta, async (ps, me) => { query.setParameters(followingQuery.getParameters()); - const users = await query.take(ps.limit).skip(ps.offset).getMany(); + const users = await query.take(ps.limit!).skip(ps.offset).getMany(); return await Users.packMany(users, me, { detail: true }); }); diff --git a/src/server/api/endpoints/users/search.ts b/src/server/api/endpoints/users/search.ts index 443bd507f3..96da221d97 100644 --- a/src/server/api/endpoints/users/search.ts +++ b/src/server/api/endpoints/users/search.ts @@ -71,16 +71,16 @@ export default define(meta, async (ps, me) => { .where('user.host IS NULL') .andWhere('user.isSuspended = FALSE') .andWhere('user.usernameLower like :username', { username: ps.query.replace('@', '').toLowerCase() + '%' }) - .take(ps.limit) + .take(ps.limit!) .skip(ps.offset) .getMany(); - if (users.length < ps.limit && !ps.localOnly) { + if (users.length < ps.limit! && !ps.localOnly) { const otherUsers = await Users.createQueryBuilder('user') .where('user.host IS NOT NULL') .andWhere('user.isSuspended = FALSE') .andWhere('user.usernameLower like :username', { username: ps.query.replace('@', '').toLowerCase() + '%' }) - .take(ps.limit - users.length) + .take(ps.limit! - users.length) .getMany(); users = users.concat(otherUsers); diff --git a/src/server/api/endpoints/users/show.ts b/src/server/api/endpoints/users/show.ts index ae93e40eaa..2be193f89b 100644 --- a/src/server/api/endpoints/users/show.ts +++ b/src/server/api/endpoints/users/show.ts @@ -74,7 +74,7 @@ export default define(meta, async (ps, me) => { }))); } else { // Lookup user - if (typeof ps.host === 'string') { + if (typeof ps.host === 'string' && typeof ps.username === 'string') { user = await resolveUser(ps.username, ps.host).catch(e => { apiLogger.warn(`failed to resolve remote user: ${e}`); throw new ApiError(meta.errors.failedToResolveRemoteUser); @@ -82,7 +82,7 @@ export default define(meta, async (ps, me) => { } else { const q: any = ps.userId != null ? { id: ps.userId } - : { usernameLower: ps.username.toLowerCase(), host: null }; + : { usernameLower: ps.username!.toLowerCase(), host: null }; user = await Users.findOne(q); } @@ -94,7 +94,7 @@ export default define(meta, async (ps, me) => { // ユーザー情報更新 if (Users.isRemoteUser(user)) { if (user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { - resolveUser(ps.username, ps.host, { }, true); + resolveUser(user.username, user.host, { }, true); } } diff --git a/src/server/api/error.ts b/src/server/api/error.ts index ca441d5811..cb0bdc9f47 100644 --- a/src/server/api/error.ts +++ b/src/server/api/error.ts @@ -1,3 +1,5 @@ +type E = { message: string, code: string, id: string, kind?: 'client' | 'server', httpStatusCode?: number }; + export class ApiError extends Error { public message: string; public code: string; @@ -6,7 +8,7 @@ export class ApiError extends Error { public httpStatusCode?: number; public info?: any; - constructor(e?: { message: string, code: string, id: string, kind?: 'client' | 'server', httpStatusCode?: number }, info?: any) { + constructor(e?: E | null | undefined, info?: any | null | undefined) { if (e == null) e = { message: 'Internal error occurred. Please contact us if the error persists.', code: 'INTERNAL_ERROR', diff --git a/src/server/api/limiter.ts b/src/server/api/limiter.ts index e29c061337..8a6c94458e 100644 --- a/src/server/api/limiter.ts +++ b/src/server/api/limiter.ts @@ -14,7 +14,7 @@ export default (endpoint: IEndpoint, user: User) => new Promise((ok, reject) => return; } - const limitation = endpoint.meta.limit; + const limitation = endpoint.meta.limit!; const key = limitation.hasOwnProperty('key') ? limitation.key @@ -41,7 +41,7 @@ export default (endpoint: IEndpoint, user: User) => new Promise((ok, reject) => id: `${user.id}:${key}:min`, duration: limitation.minInterval, max: 1, - db: limiterDB + db: limiterDB! }); minIntervalLimiter.get((err, info) => { @@ -69,7 +69,7 @@ export default (endpoint: IEndpoint, user: User) => new Promise((ok, reject) => id: `${user.id}:${key}`, duration: limitation.duration, max: limitation.max, - db: limiterDB + db: limiterDB! }); limiter.get((err, info) => { diff --git a/src/server/api/private/signin.ts b/src/server/api/private/signin.ts index fe2e5577c2..676546f2aa 100644 --- a/src/server/api/private/signin.ts +++ b/src/server/api/private/signin.ts @@ -7,6 +7,7 @@ import config from '../../../config'; import { Users, Signins, UserProfiles } from '../../../models'; import { ILocalUser } from '../../../models/entities/user'; import { genId } from '../../../misc/gen-id'; +import { ensure } from '../../../prelude/ensure'; export default async (ctx: Koa.BaseContext) => { ctx.set('Access-Control-Allow-Origin', config.url); @@ -45,10 +46,10 @@ export default async (ctx: Koa.BaseContext) => { return; } - const profile = await UserProfiles.findOne({ userId: user.id }); + const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); // Compare password - const same = await bcrypt.compare(password, profile.password); + const same = await bcrypt.compare(password, profile.password!); if (same) { if (profile.twoFactorEnabled) { diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts index 03d83efd94..ea4df060f8 100644 --- a/src/server/api/private/signup.ts +++ b/src/server/api/private/signup.ts @@ -21,7 +21,7 @@ export default async (ctx: Koa.BaseContext) => { // Verify recaptcha // ただしテスト時はこの機構は障害となるため無効にする - if (process.env.NODE_ENV !== 'test' && instance.enableRecaptcha) { + if (process.env.NODE_ENV !== 'test' && instance.enableRecaptcha && instance.recaptchaSecretKey) { recaptcha.init({ secret_key: instance.recaptchaSecretKey }); @@ -100,7 +100,7 @@ export default async (ctx: Koa.BaseContext) => { e ? j(e) : s([publicKey, privateKey]) )); - let account: User; + let account!: User; // Start transaction await getConnection().transaction(async transactionalEntityManager => { diff --git a/src/server/api/service/discord.ts b/src/server/api/service/discord.ts index 879b8b4849..b2561dee61 100644 --- a/src/server/api/service/discord.ts +++ b/src/server/api/service/discord.ts @@ -10,6 +10,7 @@ import signin from '../common/signin'; import fetchMeta from '../../../misc/fetch-meta'; import { Users, UserProfiles } from '../../../models'; import { ILocalUser } from '../../../models/entities/user'; +import { ensure } from '../../../prelude/ensure'; function getUserToken(ctx: Koa.BaseContext) { return ((ctx.headers['cookie'] || '').match(/i=(!\w+)/) || [null, null])[1]; @@ -43,7 +44,7 @@ router.get('/disconnect/discord', async ctx => { const user = await Users.findOne({ host: null, token: userToken - }); + }).then(ensure); await UserProfiles.update({ userId: user.id @@ -71,8 +72,8 @@ async function getOAuth2() { if (meta.enableDiscordIntegration) { return new OAuth2( - meta.discordClientId, - meta.discordClientSecret, + meta.discordClientId!, + meta.discordClientSecret!, 'https://discordapp.com/', 'api/oauth2/authorize', 'api/oauth2/token'); @@ -82,6 +83,8 @@ async function getOAuth2() { } router.get('/connect/discord', async ctx => { + if (redis == null) return; + if (!compareOrigin(ctx)) { ctx.throw(400, 'invalid origin'); return; @@ -103,10 +106,12 @@ router.get('/connect/discord', async ctx => { redis.set(userToken, JSON.stringify(params)); const oauth2 = await getOAuth2(); - ctx.redirect(oauth2.getAuthorizeUrl(params)); + ctx.redirect(oauth2!.getAuthorizeUrl(params)); }); router.get('/signin/discord', async ctx => { + if (redis == null) return; + const sessid = uuid(); const params = { @@ -129,10 +134,12 @@ router.get('/signin/discord', async ctx => { redis.set(sessid, JSON.stringify(params)); const oauth2 = await getOAuth2(); - ctx.redirect(oauth2.getAuthorizeUrl(params)); + ctx.redirect(oauth2!.getAuthorizeUrl(params)); }); router.get('/dc/cb', async ctx => { + if (redis == null) return; + const userToken = getUserToken(ctx); const oauth2 = await getOAuth2(); @@ -153,7 +160,7 @@ router.get('/dc/cb', async ctx => { } const { redirect_uri, state } = await new Promise((res, rej) => { - redis.get(sessid, async (_, state) => { + redis!.get(sessid, async (_, state) => { res(JSON.parse(state)); }); }); @@ -164,24 +171,22 @@ router.get('/dc/cb', async ctx => { } const { accessToken, refreshToken, expiresDate } = await new Promise((res, rej) => - oauth2.getOAuthAccessToken( - code, - { - grant_type: 'authorization_code', - redirect_uri - }, - (err, accessToken, refreshToken, result) => { - if (err) - rej(err); - else if (result.error) - rej(result.error); - else + oauth2!.getOAuthAccessToken(code, { + grant_type: 'authorization_code', + redirect_uri + }, (err, accessToken, refreshToken, result) => { + if (err) { + rej(err); + } else if (result.error) { + rej(result.error); + } else { res({ accessToken, refreshToken, expiresDate: Date.now() + Number(result.expires_in) * 1000 }); - })); + } + })); const { id, username, discriminator } = await new Promise((res, rej) => request({ @@ -191,10 +196,11 @@ router.get('/dc/cb', async ctx => { 'User-Agent': config.userAgent } }, (err, response, body) => { - if (err) + if (err) { rej(err); - else + } else { res(JSON.parse(body)); + } })); if (!id || !username || !discriminator) { @@ -235,7 +241,7 @@ router.get('/dc/cb', async ctx => { } const { redirect_uri, state } = await new Promise((res, rej) => { - redis.get(userToken, async (_, state) => { + redis!.get(userToken, async (_, state) => { res(JSON.parse(state)); }); }); @@ -246,24 +252,22 @@ router.get('/dc/cb', async ctx => { } const { accessToken, refreshToken, expiresDate } = await new Promise((res, rej) => - oauth2.getOAuthAccessToken( - code, - { - grant_type: 'authorization_code', - redirect_uri - }, - (err, accessToken, refreshToken, result) => { - if (err) - rej(err); - else if (result.error) - rej(result.error); - else - res({ - accessToken, - refreshToken, - expiresDate: Date.now() + Number(result.expires_in) * 1000 - }); - })); + oauth2!.getOAuthAccessToken(code, { + grant_type: 'authorization_code', + redirect_uri + }, (err, accessToken, refreshToken, result) => { + if (err) { + rej(err); + } else if (result.error) { + rej(result.error); + } else { + res({ + accessToken, + refreshToken, + expiresDate: Date.now() + Number(result.expires_in) * 1000 + }); + } + })); const { id, username, discriminator } = await new Promise((res, rej) => request({ @@ -273,10 +277,11 @@ router.get('/dc/cb', async ctx => { 'User-Agent': config.userAgent } }, (err, response, body) => { - if (err) + if (err) { rej(err); - else + } else { res(JSON.parse(body)); + } })); if (!id || !username || !discriminator) { @@ -287,7 +292,7 @@ router.get('/dc/cb', async ctx => { const user = await Users.findOne({ host: null, token: userToken - }); + }).then(ensure); await UserProfiles.update({ userId: user.id }, { discord: true, diff --git a/src/server/api/service/github.ts b/src/server/api/service/github.ts index 580947811b..4878d8c529 100644 --- a/src/server/api/service/github.ts +++ b/src/server/api/service/github.ts @@ -10,6 +10,7 @@ import signin from '../common/signin'; import fetchMeta from '../../../misc/fetch-meta'; import { Users, UserProfiles } from '../../../models'; import { ILocalUser } from '../../../models/entities/user'; +import { ensure } from '../../../prelude/ensure'; function getUserToken(ctx: Koa.BaseContext) { return ((ctx.headers['cookie'] || '').match(/i=(!\w+)/) || [null, null])[1]; @@ -43,7 +44,7 @@ router.get('/disconnect/github', async ctx => { const user = await Users.findOne({ host: null, token: userToken - }); + }).then(ensure); await UserProfiles.update({ userId: user.id @@ -66,7 +67,7 @@ router.get('/disconnect/github', async ctx => { async function getOath2() { const meta = await fetchMeta(); - if (meta.enableGithubIntegration) { + if (meta.enableGithubIntegration && meta.githubClientId && meta.githubClientSecret) { return new OAuth2( meta.githubClientId, meta.githubClientSecret, @@ -79,6 +80,8 @@ async function getOath2() { } router.get('/connect/github', async ctx => { + if (redis == null) return; + if (!compareOrigin(ctx)) { ctx.throw(400, 'invalid origin'); return; @@ -99,10 +102,12 @@ router.get('/connect/github', async ctx => { redis.set(userToken, JSON.stringify(params)); const oauth2 = await getOath2(); - ctx.redirect(oauth2.getAuthorizeUrl(params)); + ctx.redirect(oauth2!.getAuthorizeUrl(params)); }); router.get('/signin/github', async ctx => { + if (redis == null) return; + const sessid = uuid(); const params = { @@ -124,10 +129,12 @@ router.get('/signin/github', async ctx => { redis.set(sessid, JSON.stringify(params)); const oauth2 = await getOath2(); - ctx.redirect(oauth2.getAuthorizeUrl(params)); + ctx.redirect(oauth2!.getAuthorizeUrl(params)); }); router.get('/gh/cb', async ctx => { + if (redis == null) return; + const userToken = getUserToken(ctx); const oauth2 = await getOath2(); @@ -148,7 +155,7 @@ router.get('/gh/cb', async ctx => { } const { redirect_uri, state } = await new Promise((res, rej) => { - redis.get(sessid, async (_, state) => { + redis!.get(sessid, async (_, state) => { res(JSON.parse(state)); }); }); @@ -159,17 +166,17 @@ router.get('/gh/cb', async ctx => { } const { accessToken } = await new Promise((res, rej) => - oauth2.getOAuthAccessToken( - code, - { redirect_uri }, - (err, accessToken, refresh, result) => { - if (err) - rej(err); - else if (result.error) - rej(result.error); - else - res({ accessToken }); - })); + oauth2!.getOAuthAccessToken(code, { + redirect_uri + }, (err, accessToken, refresh, result) => { + if (err) { + rej(err); + } else if (result.error) { + rej(result.error); + } else { + res({ accessToken }); + } + })); const { login, id } = await new Promise((res, rej) => request({ @@ -215,7 +222,7 @@ router.get('/gh/cb', async ctx => { } const { redirect_uri, state } = await new Promise((res, rej) => { - redis.get(userToken, async (_, state) => { + redis!.get(userToken, async (_, state) => { res(JSON.parse(state)); }); }); @@ -226,7 +233,7 @@ router.get('/gh/cb', async ctx => { } const { accessToken } = await new Promise((res, rej) => - oauth2.getOAuthAccessToken( + oauth2!.getOAuthAccessToken( code, { redirect_uri }, (err, accessToken, refresh, result) => { @@ -261,7 +268,7 @@ router.get('/gh/cb', async ctx => { const user = await Users.findOne({ host: null, token: userToken - }); + }).then(ensure); await UserProfiles.update({ userId: user.id }, { github: true, diff --git a/src/server/api/service/twitter.ts b/src/server/api/service/twitter.ts index c0c762c6c3..b35ee8c479 100644 --- a/src/server/api/service/twitter.ts +++ b/src/server/api/service/twitter.ts @@ -9,6 +9,7 @@ import signin from '../common/signin'; import fetchMeta from '../../../misc/fetch-meta'; import { Users, UserProfiles } from '../../../models'; import { ILocalUser } from '../../../models/entities/user'; +import { ensure } from '../../../prelude/ensure'; function getUserToken(ctx: Koa.BaseContext) { return ((ctx.headers['cookie'] || '').match(/i=(!\w+)/) || [null, null])[1]; @@ -42,7 +43,7 @@ router.get('/disconnect/twitter', async ctx => { const user = await Users.findOne({ host: null, token: userToken - }); + }).then(ensure); await UserProfiles.update({ userId: user.id @@ -66,7 +67,7 @@ router.get('/disconnect/twitter', async ctx => { async function getTwAuth() { const meta = await fetchMeta(); - if (meta.enableTwitterIntegration) { + if (meta.enableTwitterIntegration && meta.twitterConsumerKey && meta.twitterConsumerSecret) { return autwh({ consumerKey: meta.twitterConsumerKey, consumerSecret: meta.twitterConsumerSecret, @@ -78,6 +79,8 @@ async function getTwAuth() { } router.get('/connect/twitter', async ctx => { + if (redis == null) return; + if (!compareOrigin(ctx)) { ctx.throw(400, 'invalid origin'); return; @@ -90,14 +93,16 @@ router.get('/connect/twitter', async ctx => { } const twAuth = await getTwAuth(); - const twCtx = await twAuth.begin(); + const twCtx = await twAuth!.begin(); redis.set(userToken, JSON.stringify(twCtx)); ctx.redirect(twCtx.url); }); router.get('/signin/twitter', async ctx => { + if (redis == null) return; + const twAuth = await getTwAuth(); - const twCtx = await twAuth.begin(); + const twCtx = await twAuth!.begin(); const sessid = uuid(); @@ -117,6 +122,8 @@ router.get('/signin/twitter', async ctx => { }); router.get('/tw/cb', async ctx => { + if (redis == null) return; + const userToken = getUserToken(ctx); const twAuth = await getTwAuth(); @@ -130,14 +137,14 @@ router.get('/tw/cb', async ctx => { } const get = new Promise((res, rej) => { - redis.get(sessid, async (_, twCtx) => { + redis!.get(sessid, async (_, twCtx) => { res(twCtx); }); }); const twCtx = await get; - const result = await twAuth.done(JSON.parse(twCtx), ctx.query.oauth_verifier); + const result = await twAuth!.done(JSON.parse(twCtx), ctx.query.oauth_verifier); const link = await UserProfiles.createQueryBuilder() .where('twitter @> :twitter', { @@ -163,19 +170,19 @@ router.get('/tw/cb', async ctx => { } const get = new Promise((res, rej) => { - redis.get(userToken, async (_, twCtx) => { + redis!.get(userToken, async (_, twCtx) => { res(twCtx); }); }); const twCtx = await get; - const result = await twAuth.done(JSON.parse(twCtx), verifier); + const result = await twAuth!.done(JSON.parse(twCtx), verifier); const user = await Users.findOne({ host: null, token: userToken - }); + }).then(ensure); await UserProfiles.update({ userId: user.id }, { twitter: true, diff --git a/src/server/api/stream/channels/admin.ts b/src/server/api/stream/channels/admin.ts index e2eba10f78..1ff932d1dd 100644 --- a/src/server/api/stream/channels/admin.ts +++ b/src/server/api/stream/channels/admin.ts @@ -9,7 +9,7 @@ export default class extends Channel { @autobind public async init(params: any) { // Subscribe admin stream - this.subscriber.on(`adminStream:${this.user.id}`, data => { + this.subscriber.on(`adminStream:${this.user!.id}`, data => { this.send(data); }); } diff --git a/src/server/api/stream/channels/drive.ts b/src/server/api/stream/channels/drive.ts index 671aad4366..4112dd9b04 100644 --- a/src/server/api/stream/channels/drive.ts +++ b/src/server/api/stream/channels/drive.ts @@ -9,7 +9,7 @@ export default class extends Channel { @autobind public async init(params: any) { // Subscribe drive stream - this.subscriber.on(`driveStream:${this.user.id}`, data => { + this.subscriber.on(`driveStream:${this.user!.id}`, data => { this.send(data); }); } diff --git a/src/server/api/stream/channels/games/reversi-game.ts b/src/server/api/stream/channels/games/reversi-game.ts index 158f108c4e..d708eae9f7 100644 --- a/src/server/api/stream/channels/games/reversi-game.ts +++ b/src/server/api/stream/channels/games/reversi-game.ts @@ -12,7 +12,7 @@ export default class extends Channel { public static shouldShare = false; public static requireCredential = false; - private gameId: ReversiGame['id']; + private gameId: ReversiGame['id'] | null = null; @autobind public async init(params: any) { @@ -40,7 +40,10 @@ export default class extends Channel { @autobind private async updateSettings(key: string, value: any) { - const game = await ReversiGames.findOne(this.gameId); + if (this.user == null) return; + + const game = await ReversiGames.findOne(this.gameId!); + if (game == null) throw 'game not found'; if (game.isStarted) return; if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; @@ -49,11 +52,11 @@ export default class extends Channel { if (!['map', 'bw', 'isLlotheo', 'canPutEverywhere', 'loopedBoard'].includes(key)) return; - await ReversiGames.update({ id: this.gameId }, { + await ReversiGames.update(this.gameId!, { [key]: value }); - publishReversiGameStream(this.gameId, 'updateSettings', { + publishReversiGameStream(this.gameId!, 'updateSettings', { key: key, value: value }); @@ -61,7 +64,10 @@ export default class extends Channel { @autobind private async initForm(form: any) { - const game = await ReversiGames.findOne(this.gameId); + if (this.user == null) return; + + const game = await ReversiGames.findOne(this.gameId!); + if (game == null) throw 'game not found'; if (game.isStarted) return; if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; @@ -72,9 +78,9 @@ export default class extends Channel { form2: form }; - await ReversiGames.update({ id: this.gameId }, set); + await ReversiGames.update(this.gameId!, set); - publishReversiGameStream(this.gameId, 'initForm', { + publishReversiGameStream(this.gameId!, 'initForm', { userId: this.user.id, form }); @@ -82,7 +88,10 @@ export default class extends Channel { @autobind private async updateForm(id: string, value: any) { - const game = await ReversiGames.findOne({ id: this.gameId }); + if (this.user == null) return; + + const game = await ReversiGames.findOne(this.gameId!); + if (game == null) throw 'game not found'; if (game.isStarted) return; if ((game.user1Id !== this.user.id) && (game.user2Id !== this.user.id)) return; @@ -101,9 +110,9 @@ export default class extends Channel { form1: form }; - await ReversiGames.update({ id: this.gameId }, set); + await ReversiGames.update(this.gameId!, set); - publishReversiGameStream(this.gameId, 'updateForm', { + publishReversiGameStream(this.gameId!, 'updateForm', { userId: this.user.id, id, value @@ -112,8 +121,10 @@ export default class extends Channel { @autobind private async message(message: any) { + if (this.user == null) return; + message.id = Math.random(); - publishReversiGameStream(this.gameId, 'message', { + publishReversiGameStream(this.gameId!, 'message', { userId: this.user.id, message }); @@ -121,29 +132,32 @@ export default class extends Channel { @autobind private async accept(accept: boolean) { - const game = await ReversiGames.findOne(this.gameId); + if (this.user == null) return; + + const game = await ReversiGames.findOne(this.gameId!); + if (game == null) throw 'game not found'; if (game.isStarted) return; let bothAccepted = false; if (game.user1Id === this.user.id) { - await ReversiGames.update({ id: this.gameId }, { + await ReversiGames.update(this.gameId!, { user1Accepted: accept }); - publishReversiGameStream(this.gameId, 'changeAccepts', { + publishReversiGameStream(this.gameId!, 'changeAccepts', { user1: accept, user2: game.user2Accepted }); if (accept && game.user2Accepted) bothAccepted = true; } else if (game.user2Id === this.user.id) { - await ReversiGames.update({ id: this.gameId }, { + await ReversiGames.update(this.gameId!, { user2Accepted: accept }); - publishReversiGameStream(this.gameId, 'changeAccepts', { + publishReversiGameStream(this.gameId!, 'changeAccepts', { user1: game.user1Accepted, user2: accept }); @@ -156,7 +170,7 @@ export default class extends Channel { if (bothAccepted) { // 3秒後、まだacceptされていたらゲーム開始 setTimeout(async () => { - const freshGame = await ReversiGames.findOne(this.gameId); + const freshGame = await ReversiGames.findOne(this.gameId!); if (freshGame == null || freshGame.isStarted || freshGame.isEnded) return; if (!freshGame.user1Accepted || !freshGame.user2Accepted) return; @@ -175,7 +189,7 @@ export default class extends Channel { const map = freshGame.map != null ? freshGame.map : getRandomMap(); - await ReversiGames.update({ id: this.gameId }, { + await ReversiGames.update(this.gameId!, { startedAt: new Date(), isStarted: true, black: bw, @@ -199,22 +213,20 @@ export default class extends Channel { winner = null; } - await ReversiGames.update({ - id: this.gameId - }, { + await ReversiGames.update(this.gameId!, { isEnded: true, winnerId: winner }); - publishReversiGameStream(this.gameId, 'ended', { + publishReversiGameStream(this.gameId!, 'ended', { winnerId: winner, - game: await ReversiGames.pack(this.gameId, this.user) + game: await ReversiGames.pack(this.gameId!, this.user) }); } //#endregion - publishReversiGameStream(this.gameId, 'started', - await ReversiGames.pack(this.gameId, this.user)); + publishReversiGameStream(this.gameId!, 'started', + await ReversiGames.pack(this.gameId!, this.user)); }, 3000); } } @@ -222,7 +234,10 @@ export default class extends Channel { // 石を打つ @autobind private async set(pos: number) { - const game = await ReversiGames.findOne(this.gameId); + if (this.user == null) return; + + const game = await ReversiGames.findOne(this.gameId!); + if (game == null) throw 'game not found'; if (!game.isStarted) return; if (game.isEnded) return; @@ -267,30 +282,29 @@ export default class extends Channel { game.logs.push(log); - await ReversiGames.update({ - id: this.gameId - }, { + await ReversiGames.update(this.gameId!, { crc32, isEnded: o.isEnded, winnerId: winner, logs: game.logs }); - publishReversiGameStream(this.gameId, 'set', Object.assign(log, { + publishReversiGameStream(this.gameId!, 'set', Object.assign(log, { next: o.turn })); if (o.isEnded) { - publishReversiGameStream(this.gameId, 'ended', { + publishReversiGameStream(this.gameId!, 'ended', { winnerId: winner, - game: await ReversiGames.pack(this.gameId, this.user) + game: await ReversiGames.pack(this.gameId!, this.user) }); } } @autobind private async check(crc32: string) { - const game = await ReversiGames.findOne(this.gameId); + const game = await ReversiGames.findOne(this.gameId!); + if (game == null) throw 'game not found'; if (!game.isStarted) return; diff --git a/src/server/api/stream/channels/games/reversi.ts b/src/server/api/stream/channels/games/reversi.ts index 0498e5e017..3db338386a 100644 --- a/src/server/api/stream/channels/games/reversi.ts +++ b/src/server/api/stream/channels/games/reversi.ts @@ -11,7 +11,7 @@ export default class extends Channel { @autobind public async init(params: any) { // Subscribe reversi stream - this.subscriber.on(`reversiStream:${this.user.id}`, data => { + this.subscriber.on(`reversiStream:${this.user!.id}`, data => { this.send(data); }); } @@ -22,7 +22,7 @@ export default class extends Channel { case 'ping': if (body.id == null) return; const matching = await ReversiMatchings.findOne({ - parentId: this.user.id, + parentId: this.user!.id, childId: body.id }); if (matching == null) return; diff --git a/src/server/api/stream/channels/home-timeline.ts b/src/server/api/stream/channels/home-timeline.ts index 2cece0947f..61960657b4 100644 --- a/src/server/api/stream/channels/home-timeline.ts +++ b/src/server/api/stream/channels/home-timeline.ts @@ -17,10 +17,10 @@ export default class extends Channel { @autobind private async onNote(note: any) { // その投稿のユーザーをフォローしていなかったら弾く - if (this.user.id !== note.userId && !this.following.includes(note.userId)) return; + if (this.user!.id !== note.userId && !this.following.includes(note.userId)) return; if (['followers', 'specified'].includes(note.visibility)) { - note = await Notes.pack(note.id, this.user, { + note = await Notes.pack(note.id, this.user!, { detail: true }); @@ -30,13 +30,13 @@ export default class extends Channel { } else { // リプライなら再pack if (note.replyId != null) { - note.reply = await Notes.pack(note.replyId, this.user, { + note.reply = await Notes.pack(note.replyId, this.user!, { detail: true }); } // Renoteなら再pack if (note.renoteId != null) { - note.renote = await Notes.pack(note.renoteId, this.user, { + note.renote = await Notes.pack(note.renoteId, this.user!, { detail: true }); } diff --git a/src/server/api/stream/channels/hybrid-timeline.ts b/src/server/api/stream/channels/hybrid-timeline.ts index 30643aeda8..18e6aa8350 100644 --- a/src/server/api/stream/channels/hybrid-timeline.ts +++ b/src/server/api/stream/channels/hybrid-timeline.ts @@ -12,7 +12,7 @@ export default class extends Channel { @autobind public async init(params: any) { const meta = await fetchMeta(); - if (meta.disableLocalTimeline && !this.user.isAdmin && !this.user.isModerator) return; + if (meta.disableLocalTimeline && !this.user!.isAdmin && !this.user!.isModerator) return; // Subscribe events this.subscriber.on('notesStream', this.onNote); @@ -22,13 +22,13 @@ export default class extends Channel { private async onNote(note: any) { // 自分自身の投稿 または その投稿のユーザーをフォローしている または ローカルの投稿 の場合だけ if (!( - this.user.id === note.userId || + this.user!.id === note.userId || this.following.includes(note.userId) || note.user.host == null )) return; if (['followers', 'specified'].includes(note.visibility)) { - note = await Notes.pack(note.id, this.user, { + note = await Notes.pack(note.id, this.user!, { detail: true }); @@ -38,13 +38,13 @@ export default class extends Channel { } else { // リプライなら再pack if (note.replyId != null) { - note.reply = await Notes.pack(note.replyId, this.user, { + note.reply = await Notes.pack(note.replyId, this.user!, { detail: true }); } // Renoteなら再pack if (note.renoteId != null) { - note.renote = await Notes.pack(note.renoteId, this.user, { + note.renote = await Notes.pack(note.renoteId, this.user!, { detail: true }); } diff --git a/src/server/api/stream/channels/main.ts b/src/server/api/stream/channels/main.ts index 0d9bf3149d..1100f87acb 100644 --- a/src/server/api/stream/channels/main.ts +++ b/src/server/api/stream/channels/main.ts @@ -9,10 +9,10 @@ export default class extends Channel { @autobind public async init(params: any) { - const mute = await Mutings.find({ muterId: this.user.id }); + const mute = await Mutings.find({ muterId: this.user!.id }); // Subscribe main stream channel - this.subscriber.on(`mainStream:${this.user.id}`, async data => { + this.subscriber.on(`mainStream:${this.user!.id}`, async data => { const { type, body } = data; switch (type) { diff --git a/src/server/api/stream/channels/messaging-index.ts b/src/server/api/stream/channels/messaging-index.ts index 648badc1dc..0c495398ab 100644 --- a/src/server/api/stream/channels/messaging-index.ts +++ b/src/server/api/stream/channels/messaging-index.ts @@ -9,7 +9,7 @@ export default class extends Channel { @autobind public async init(params: any) { // Subscribe messaging index stream - this.subscriber.on(`messagingIndexStream:${this.user.id}`, data => { + this.subscriber.on(`messagingIndexStream:${this.user!.id}`, data => { this.send(data); }); } diff --git a/src/server/api/stream/channels/messaging.ts b/src/server/api/stream/channels/messaging.ts index b81fbb9d4c..8397f849ff 100644 --- a/src/server/api/stream/channels/messaging.ts +++ b/src/server/api/stream/channels/messaging.ts @@ -14,7 +14,7 @@ export default class extends Channel { this.otherpartyId = params.otherparty as string; // Subscribe messaging stream - this.subscriber.on(`messagingStream:${this.user.id}-${this.otherpartyId}`, data => { + this.subscriber.on(`messagingStream:${this.user!.id}-${this.otherpartyId}`, data => { this.send(data); }); } @@ -23,7 +23,7 @@ export default class extends Channel { public onMessage(type: string, body: any) { switch (type) { case 'read': - read(this.user.id, this.otherpartyId, body.id); + read(this.user!.id, this.otherpartyId, body.id); break; } } diff --git a/src/server/api/stream/index.ts b/src/server/api/stream/index.ts index abbd91ec81..f73f3229d5 100644 --- a/src/server/api/stream/index.ts +++ b/src/server/api/stream/index.ts @@ -28,13 +28,13 @@ export default class Connection { constructor( wsConnection: websocket.connection, subscriber: EventEmitter, - user: User, - app: App + user: User | null | undefined, + app: App | null | undefined ) { this.wsConnection = wsConnection; - this.user = user; - this.app = app; this.subscriber = subscriber; + if (user) this.user = user; + if (app) this.app = app; this.wsConnection.on('message', this.onWsConnectionMessage); @@ -52,6 +52,8 @@ export default class Connection { */ @autobind private async onWsConnectionMessage(data: websocket.IMessage) { + if (data.utf8Data == null) return; + const { type, body } = JSON.parse(data.utf8Data); switch (type) { @@ -89,7 +91,7 @@ export default class Connection { @autobind private onReadNotification(payload: any) { if (!payload.id) return; - readNotification(this.user.id, [payload.id]); + readNotification(this.user!.id, [payload.id]); } /** @@ -109,7 +111,7 @@ export default class Connection { this.subscriber.on(`noteStream:${payload.id}`, this.onNoteStreamMessage); } - if (payload.read) { + if (payload.read && this.user) { readNote(this.user.id, payload.id); } } @@ -221,7 +223,7 @@ export default class Connection { private async updateFollowing() { const followings = await Followings.find({ where: { - followerId: this.user.id + followerId: this.user!.id }, select: ['followeeId'] }); @@ -233,7 +235,7 @@ export default class Connection { private async updateMuting() { const mutings = await Mutings.find({ where: { - muterId: this.user.id + muterId: this.user!.id }, select: ['muteeId'] }); @@ -247,7 +249,7 @@ export default class Connection { @autobind public dispose() { for (const c of this.channels.filter(c => c.dispose)) { - c.dispose(); + if (c.dispose) c.dispose(); } if (this.followingClock) clearInterval(this.followingClock); diff --git a/src/server/file/send-drive-file.ts b/src/server/file/send-drive-file.ts index f9b067b79c..5da3d79eb5 100644 --- a/src/server/file/send-drive-file.ts +++ b/src/server/file/send-drive-file.ts @@ -50,7 +50,7 @@ export default async function(ctx: Koa.BaseContext) { ctx.set('Content-Disposition', contentDisposition('attachment', `${file.name}`)); } - const readable = InternalStorage.read(file.accessKey); + const readable = InternalStorage.read(file.accessKey!); readable.on('error', commonReadableHandlerGenerator(ctx)); ctx.set('Content-Type', file.type); ctx.body = readable; diff --git a/src/server/index.ts b/src/server/index.ts index 9c153f0167..601e288f3b 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -32,7 +32,7 @@ export const serverLogger = new Logger('server', 'gray', false); const app = new Koa(); app.proxy = true; -if (!['production', 'test'].includes(process.env.NODE_ENV)) { +if (!['production', 'test'].includes(process.env.NODE_ENV || '')) { // Logger app.use(koaLogger(str => { serverLogger.info(str); diff --git a/src/server/web/docs.ts b/src/server/web/docs.ts index d9ba14a8ed..374dbf3bd2 100644 --- a/src/server/web/docs.ts +++ b/src/server/web/docs.ts @@ -34,14 +34,14 @@ async function genVars(lang: string): Promise<{ [key: string]: any }> { const docs = glob.sync(`src/docs/**/*.${lang}.md`, { cwd }); vars['docs'] = {}; for (const x of docs) { - const [, name] = x.match(/docs\/(.+?)\.(.+?)\.md$/); + const [, name] = x.match(/docs\/(.+?)\.(.+?)\.md$/)!; if (vars['docs'][name] == null) { vars['docs'][name] = { name, title: {} }; } - vars['docs'][name]['title'][lang] = fs.readFileSync(cwd + x, 'utf-8').match(/^# (.+?)\r?\n/)[1]; + vars['docs'][name]['title'][lang] = fs.readFileSync(cwd + x, 'utf-8').match(/^# (.+?)\r?\n/)![1]; } vars['kebab'] = (string: string) => string.replace(/([a-z])([A-Z])/g, '$1-$2').replace(/\s+/g, '-').toLowerCase(); @@ -97,7 +97,7 @@ router.get('/*/*', async ctx => { await ctx.render('../../../../src/docs/article', Object.assign({ id: doc, html: conv.makeHtml(md), - title: md.match(/^# (.+?)\r?\n/)[1], + title: md.match(/^# (.+?)\r?\n/)![1], src: `https://github.com/syuilo/misskey/tree/master/src/docs/${doc}.${lang}.md` }, await genVars(lang))); diff --git a/src/server/web/feed.ts b/src/server/web/feed.ts index 94f0643f75..285c20fcdb 100644 --- a/src/server/web/feed.ts +++ b/src/server/web/feed.ts @@ -3,6 +3,7 @@ import config from '../../config'; import { User } from '../../models/entities/user'; import { Notes, DriveFiles, UserProfiles } from '../../models'; import { In } from 'typeorm'; +import { ensure } from '../../prelude/ensure'; export default async function(user: User) { const author: Author = { @@ -10,7 +11,7 @@ export default async function(user: User) { name: user.name || user.username }; - const profile = await UserProfiles.findOne({ userId: user.id }); + const profile = await UserProfiles.findOne({ userId: user.id }).then(ensure); const notes = await Notes.find({ where: { @@ -47,9 +48,9 @@ export default async function(user: User) { title: `New note by ${author.name}`, link: `${config.url}/notes/${note.id}`, date: note.createdAt, - description: note.cw, - content: note.text, - image: file ? DriveFiles.getPublicUrl(file) : null + description: note.cw || undefined, + content: note.text || undefined, + image: file ? DriveFiles.getPublicUrl(file) : undefined }); } diff --git a/src/server/web/index.ts b/src/server/web/index.ts index d1a15e3929..5cadf1b124 100644 --- a/src/server/web/index.ts +++ b/src/server/web/index.ts @@ -19,6 +19,7 @@ import config from '../../config'; import { Users, Notes, Emojis, UserProfiles } from '../../models'; import parseAcct from '../../misc/acct/parse'; import getNoteSummary from '../../misc/get-note-summary'; +import { ensure } from '../../prelude/ensure'; const client = `${__dirname}/../../client/`; @@ -149,11 +150,9 @@ router.get('/@:user', async (ctx, next) => { usernameLower: username.toLowerCase(), host }); - const profile = await UserProfiles.findOne({ - userId: user.id - }); if (user != null) { + const profile = await UserProfiles.findOne(user.id).then(ensure); const meta = await fetchMeta(); await ctx.render('user', { user, profile, diff --git a/src/server/web/url-preview.ts b/src/server/web/url-preview.ts index aed475e6ff..7d0080b4d2 100644 --- a/src/server/web/url-preview.ts +++ b/src/server/web/url-preview.ts @@ -43,7 +43,7 @@ module.exports = async (ctx: Koa.BaseContext) => { } }; -function wrap(url: string): string { +function wrap(url?: string): string | null { return url != null ? url.match(/^https?:\/\//) ? `${config.url}/proxy/preview.jpg?${query({ diff --git a/src/server/well-known.ts b/src/server/well-known.ts index 7c5684d2ce..d29b7a8888 100644 --- a/src/server/well-known.ts +++ b/src/server/well-known.ts @@ -54,9 +54,9 @@ router.get(webFingerPath, async ctx => { const generateQuery = (resource: string) => resource.startsWith(`${config.url.toLowerCase()}/users/`) ? - fromId(resource.split('/').pop()) : + fromId(resource.split('/').pop()!) : fromAcct(parseAcct( - resource.startsWith(`${config.url.toLowerCase()}/@`) ? resource.split('/').pop() : + resource.startsWith(`${config.url.toLowerCase()}/@`) ? resource.split('/').pop()! : resource.startsWith('acct:') ? resource.slice('acct:'.length) : resource)); diff --git a/src/services/blocking/create.ts b/src/services/blocking/create.ts index 79ca0d59f1..def4f33585 100644 --- a/src/services/blocking/create.ts +++ b/src/services/blocking/create.ts @@ -66,7 +66,7 @@ async function cancelRequest(follower: User, followee: User) { // リモートからフォローリクエストを受けていたらReject送信 if (Users.isRemoteUser(follower) && Users.isLocalUser(followee)) { - const content = renderActivity(renderReject(renderFollow(follower, followee, request.requestId), followee)); + const content = renderActivity(renderReject(renderFollow(follower, followee, request.requestId!), followee)); deliver(followee, content, follower.inbox); } } diff --git a/src/services/chart/core.ts b/src/services/chart/core.ts index 2a60b1a0a3..0a9ec8dae0 100644 --- a/src/services/chart/core.ts +++ b/src/services/chart/core.ts @@ -84,7 +84,7 @@ export default abstract class Chart> { } } }; - flatColumns(schema.properties); + flatColumns(schema.properties!); return columns; } @@ -182,7 +182,7 @@ export default abstract class Chart> { } @autobind - private getNewLog(latest?: T): T { + private getNewLog(latest: T | null): T { const log = latest ? this.genNewLog(latest) : {}; const flatColumns = (x: Obj, path?: string) => { for (const [k, v] of Object.entries(x)) { @@ -196,7 +196,7 @@ export default abstract class Chart> { } } }; - flatColumns(this.schema.properties); + flatColumns(this.schema.properties!); return log as T; } @@ -213,7 +213,7 @@ export default abstract class Chart> { } @autobind - private getLatestLog(span: Span, group: string = null): Promise { + private getLatestLog(span: Span, group: string | null = null): Promise { return this.repository.findOne({ group: group, span: span @@ -221,17 +221,17 @@ export default abstract class Chart> { order: { date: -1 } - }); + }).then(x => x || null); } @autobind - private async getCurrentLog(span: Span, group: string = null): Promise { + private async getCurrentLog(span: Span, group: string | null = null): Promise { const [y, m, d, h] = this.getCurrentDate(); const current = span == 'day' ? utc([y, m, d]) : span == 'hour' ? utc([y, m, d, h]) : - null; + null as never; // 現在(今日または今のHour)のログ const currentLog = await this.repository.findOne({ @@ -285,7 +285,7 @@ export default abstract class Chart> { // 並列動作している他のチャートエンジンプロセスと処理が重なる場合がある // その場合は再度最も新しいログを持ってくる if (isDuplicateKeyValueError(e)) { - log = await this.getLatestLog(span, group); + log = await this.getLatestLog(span, group) as Log; } else { logger.error(e); throw e; @@ -296,17 +296,17 @@ export default abstract class Chart> { } @autobind - protected commit(query: Record, group: string = null, uniqueKey?: string, uniqueValue?: string): Promise { + protected commit(query: Record, group: string | null = null, uniqueKey?: string, uniqueValue?: string): Promise { const update = async (log: Log) => { // ユニークインクリメントの場合、指定のキーに指定の値が既に存在していたら弾く if ( - uniqueKey && + uniqueKey && log.unique && log.unique[uniqueKey] && log.unique[uniqueKey].includes(uniqueValue) ) return; // ユニークインクリメントの指定のキーに値を追加 - if (uniqueKey) { + if (uniqueKey && log.unique) { if (log.unique[uniqueKey]) { const sql = `jsonb_set("unique", '{${uniqueKey}}', ("unique"->>'${uniqueKey}')::jsonb || '["${uniqueValue}"]'::jsonb)`; query['unique'] = () => sql; @@ -331,23 +331,23 @@ export default abstract class Chart> { } @autobind - protected async inc(inc: DeepPartial, group: string = null): Promise { + protected async inc(inc: DeepPartial, group: string | null = null): Promise { await this.commit(Chart.convertQuery(inc as any), group); } @autobind - protected async incIfUnique(inc: DeepPartial, key: string, value: string, group: string = null): Promise { + protected async incIfUnique(inc: DeepPartial, key: string, value: string, group: string | null = null): Promise { await this.commit(Chart.convertQuery(inc as any), group, key, value); } @autobind - public async getChart(span: Span, range: number, group: string = null): Promise> { + public async getChart(span: Span, range: number, group: string | null = null): Promise> { const [y, m, d, h] = this.getCurrentDate(); const gt = span == 'day' ? utc([y, m, d]).subtract(range, 'days') : span == 'hour' ? utc([y, m, d, h]).subtract(range, 'hours') : - null; + null as never; // ログ取得 let logs = await this.repository.find({ @@ -404,7 +404,7 @@ export default abstract class Chart> { const current = span == 'day' ? utc([y, m, d]).subtract(i, 'days') : span == 'hour' ? utc([y, m, d, h]).subtract(i, 'hours') : - null; + null as never; const log = logs.find(l => utc(l.date * 1000).isSame(current)); @@ -452,8 +452,8 @@ export function convertLog(logSchema: Schema): Schema { type: 'number' }; } else if (v.type === 'object') { - for (const k of Object.keys(v.properties)) { - v.properties[k] = convertLog(v.properties[k]); + for (const k of Object.keys(v.properties!)) { + v.properties![k] = convertLog(v.properties![k]); } } return v; diff --git a/src/services/create-notification.ts b/src/services/create-notification.ts index bcb8214c56..5bff8adfd4 100644 --- a/src/services/create-notification.ts +++ b/src/services/create-notification.ts @@ -46,6 +46,7 @@ export async function createNotification( // 2秒経っても(今回作成した)通知が既読にならなかったら「未読の通知がありますよ」イベントを発行する setTimeout(async () => { const fresh = await Notifications.findOne(notification.id); + if (fresh == null) return; // 既に削除されているかもしれない if (!fresh.isRead) { //#region ただしミュートしているユーザーからの通知なら無視 const mutings = await Mutings.find({ diff --git a/src/services/drive/add-file.ts b/src/services/drive/add-file.ts index b83c3558d3..94b97fed61 100644 --- a/src/services/drive/add-file.ts +++ b/src/services/drive/add-file.ts @@ -55,10 +55,10 @@ async function save(file: DriveFile, path: string, name: string, type: string, h const url = `${ baseUrl }/${ key }`; // for alts - let webpublicKey: string = null; - let webpublicUrl: string = null; - let thumbnailKey: string = null; - let thumbnailUrl: string = null; + let webpublicKey: string | null = null; + let webpublicUrl: string | null = null; + let thumbnailKey: string | null = null; + let thumbnailUrl: string | null = null; //#endregion //#region Uploads @@ -106,8 +106,8 @@ async function save(file: DriveFile, path: string, name: string, type: string, h const url = InternalStorage.saveFromPath(accessKey, path); - let thumbnailUrl: string; - let webpublicUrl: string; + let thumbnailUrl: string | null = null; + let webpublicUrl: string | null = null; if (alts.thumbnail) { thumbnailUrl = InternalStorage.saveFromBuffer(thumbnailAccessKey, alts.thumbnail.data); @@ -143,7 +143,7 @@ async function save(file: DriveFile, path: string, name: string, type: string, h */ export async function generateAlts(path: string, type: string, generateWeb: boolean) { // #region webpublic - let webpublic: IImage; + let webpublic: IImage | null = null; if (generateWeb) { logger.info(`creating web image`); @@ -163,7 +163,7 @@ export async function generateAlts(path: string, type: string, generateWeb: bool // #endregion webpublic // #region thumbnail - let thumbnail: IImage; + let thumbnail: IImage | null = null; if (['image/jpeg', 'image/webp'].includes(type)) { thumbnail = await ConvertToJpeg(path, 498, 280); @@ -188,7 +188,7 @@ export async function generateAlts(path: string, type: string, generateWeb: bool * Upload to ObjectStorage */ async function upload(key: string, stream: fs.ReadStream | Buffer, type: string, filename?: string) { - const minio = new Minio.Client(config.drive.config); + const minio = new Minio.Client(config.drive!.config); const metadata = { 'Content-Type': type, @@ -197,7 +197,7 @@ async function upload(key: string, stream: fs.ReadStream | Buffer, type: string, if (filename) metadata['Content-Disposition'] = contentDisposition('inline', filename); - await minio.putObject(config.drive.bucket, key, stream, null, metadata); + await minio.putObject(config.drive!.bucket!, key, stream, undefined, metadata); } async function deleteOldFile(user: IRemoteUser) { @@ -232,14 +232,14 @@ async function deleteOldFile(user: IRemoteUser) { export default async function( user: User, path: string, - name: string = null, - comment: string = null, + name: string | null = null, + comment: string | null = null, folderId: any = null, force: boolean = false, isLink: boolean = false, - url: string = null, - uri: string = null, - sensitive: boolean = null + url: string | null = null, + uri: string | null = null, + sensitive: boolean | null = null ): Promise { // Calc md5 hash const calcHash = new Promise((res, rej) => { @@ -300,7 +300,7 @@ export default async function( throw 'no-free-space'; } else { // (アバターまたはバナーを含まず)最も古いファイルを削除する - deleteOldFile(user); + deleteOldFile(user as IRemoteUser); } } } @@ -378,7 +378,7 @@ export default async function( file.comment = comment; file.properties = properties; file.isLink = isLink; - file.isSensitive = Users.isLocalUser(user) && profile.alwaysMarkNsfw ? true : + file.isSensitive = Users.isLocalUser(user) && profile!.alwaysMarkNsfw ? true : (sensitive !== null && sensitive !== undefined) ? sensitive : false; @@ -411,7 +411,7 @@ export default async function( file = await DriveFiles.findOne({ uri: file.uri, userId: user.id - }); + }) as DriveFile; } else { logger.error(e); throw e; diff --git a/src/services/drive/delete-file.ts b/src/services/drive/delete-file.ts index b7fa99391e..bba453b982 100644 --- a/src/services/drive/delete-file.ts +++ b/src/services/drive/delete-file.ts @@ -7,31 +7,31 @@ import { driveChart, perUserDriveChart, instanceChart } from '../chart'; export default async function(file: DriveFile, isExpired = false) { if (file.storedInternal) { - InternalStorage.del(file.accessKey); + InternalStorage.del(file.accessKey!); if (file.thumbnailUrl) { - InternalStorage.del(file.thumbnailAccessKey); + InternalStorage.del(file.thumbnailAccessKey!); } if (file.webpublicUrl) { - InternalStorage.del(file.webpublicAccessKey); + InternalStorage.del(file.webpublicAccessKey!); } } else if (!file.isLink) { - const minio = new Minio.Client(config.drive.config); + const minio = new Minio.Client(config.drive!.config); - await minio.removeObject(config.drive.bucket, file.accessKey); + await minio.removeObject(config.drive!.bucket!, file.accessKey!); if (file.thumbnailUrl) { - await minio.removeObject(config.drive.bucket, file.thumbnailAccessKey); + await minio.removeObject(config.drive!.bucket!, file.thumbnailAccessKey!); } if (file.webpublicUrl) { - await minio.removeObject(config.drive.bucket, file.webpublicAccessKey); + await minio.removeObject(config.drive!.bucket!, file.webpublicAccessKey!); } } // リモートファイル期限切れ削除後は直リンクにする - if (isExpired && file.userHost !== null) { + if (isExpired && file.userHost !== null && file.uri != null) { DriveFiles.update(file.id, { isLink: true, url: file.uri, diff --git a/src/services/drive/image-processor.ts b/src/services/drive/image-processor.ts index 3c538390b0..89ac331ca1 100644 --- a/src/services/drive/image-processor.ts +++ b/src/services/drive/image-processor.ts @@ -2,7 +2,7 @@ import * as sharp from 'sharp'; export type IImage = { data: Buffer; - ext: string; + ext: string | null; type: string; }; diff --git a/src/services/drive/upload-from-url.ts b/src/services/drive/upload-from-url.ts index a7fe1fbd26..bfe8c5c3b2 100644 --- a/src/services/drive/upload-from-url.ts +++ b/src/services/drive/upload-from-url.ts @@ -1,4 +1,3 @@ -import * as URL from 'url'; import create from './add-file'; import { User } from '../../models/entities/user'; import { driveLogger } from './logger'; @@ -13,14 +12,14 @@ const logger = driveLogger.createSubLogger('downloader'); export default async ( url: string, user: User, - folderId: DriveFolder['id'] = null, - uri: string = null, + folderId: DriveFolder['id'] | null = null, + uri: string | null = null, sensitive = false, force = false, link = false ): Promise => { - let name = URL.parse(url).pathname.split('/').pop(); - if (!DriveFiles.validateFileName(name)) { + let name = new URL(url).pathname.split('/').pop() || null; + if (name == null || !DriveFiles.validateFileName(name)) { name = null; } @@ -50,6 +49,6 @@ export default async ( if (error) { throw error; } else { - return driveFile; + return driveFile!; } }; diff --git a/src/services/following/create.ts b/src/services/following/create.ts index 57bb61fd92..b69dfe42b9 100644 --- a/src/services/following/create.ts +++ b/src/services/following/create.ts @@ -14,6 +14,7 @@ import { instanceChart, perUserFollowingChart } from '../chart'; import { genId } from '../../misc/gen-id'; import { createNotification } from '../create-notification'; import { isDuplicateKeyValueError } from '../../misc/is-duplicate-key-value-error'; +import { ensure } from '../../prelude/ensure'; const logger = new Logger('following/create'); @@ -115,7 +116,7 @@ export default async function(follower: User, followee: User, requestId?: string if (blocked != null) throw new IdentifiableError('3338392a-f764-498d-8855-db939dcf8c48', 'blocked'); } - const followeeProfile = await UserProfiles.findOne({ userId: followee.id }); + const followeeProfile = await UserProfiles.findOne(followee.id).then(ensure); // フォロー対象が鍵アカウントである or // フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or diff --git a/src/services/following/requests/accept-all.ts b/src/services/following/requests/accept-all.ts index b61c31a513..70e7448aad 100644 --- a/src/services/following/requests/accept-all.ts +++ b/src/services/following/requests/accept-all.ts @@ -1,6 +1,7 @@ import accept from './accept'; import { User } from '../../../models/entities/user'; import { FollowRequests, Users } from '../../../models'; +import { ensure } from '../../../prelude/ensure'; /** * 指定したユーザー宛てのフォローリクエストをすべて承認 @@ -12,7 +13,7 @@ export default async function(user: User) { }); for (const request of requests) { - const follower = await Users.findOne(request.followerId); + const follower = await Users.findOne(request.followerId).then(ensure); accept(user, follower); } } diff --git a/src/services/following/requests/accept.ts b/src/services/following/requests/accept.ts index 0be8e24e1a..1397514ad1 100644 --- a/src/services/following/requests/accept.ts +++ b/src/services/following/requests/accept.ts @@ -16,7 +16,7 @@ export default async function(followee: User, follower: User) { await insertFollowingDoc(followee, follower); if (Users.isRemoteUser(follower) && request) { - const content = renderActivity(renderAccept(renderFollow(follower, followee, request.requestId), followee as ILocalUser)); + const content = renderActivity(renderAccept(renderFollow(follower, followee, request.requestId!), followee as ILocalUser)); deliver(followee as ILocalUser, content, follower.inbox); } diff --git a/src/services/following/requests/reject.ts b/src/services/following/requests/reject.ts index c590edcfd8..d5b5e48c41 100644 --- a/src/services/following/requests/reject.ts +++ b/src/services/following/requests/reject.ts @@ -13,7 +13,7 @@ export default async function(followee: User, follower: User) { followerId: follower.id }); - const content = renderActivity(renderReject(renderFollow(follower, followee, request.requestId), followee as ILocalUser)); + const content = renderActivity(renderReject(renderFollow(follower, followee, request!.requestId!), followee as ILocalUser)); deliver(followee as ILocalUser, content, follower.inbox); } diff --git a/src/services/i/pin.ts b/src/services/i/pin.ts index 4e43421bdc..f5c957c742 100644 --- a/src/services/i/pin.ts +++ b/src/services/i/pin.ts @@ -77,9 +77,8 @@ export async function removePinned(user: User, noteId: Note['id']) { } export async function deliverPinnedChange(userId: User['id'], noteId: Note['id'], isAddition: boolean) { - const user = await Users.findOne({ - id: userId - }); + const user = await Users.findOne(userId); + if (user == null) throw 'user not found'; if (!Users.isLocalUser(user)) return; @@ -108,14 +107,8 @@ async function CreateRemoteInboxes(user: ILocalUser): Promise { const queue: string[] = []; for (const following of followers) { - const follower = { - host: following.followerHost, - inbox: following.followerInbox, - sharedInbox: following.followerSharedInbox, - }; - - if (follower.host !== null) { - const inbox = follower.sharedInbox || follower.inbox; + if (Followings.isRemoteFollower(following)) { + const inbox = following.followerSharedInbox || following.followerInbox; if (!queue.includes(inbox)) queue.push(inbox); } } diff --git a/src/services/i/update.ts b/src/services/i/update.ts index 7dba472e78..2bb5c7a100 100644 --- a/src/services/i/update.ts +++ b/src/services/i/update.ts @@ -6,9 +6,8 @@ import { User } from '../../models/entities/user'; import { renderPerson } from '../../remote/activitypub/renderer/person'; export async function publishToFollowers(userId: User['id']) { - const user = await Users.findOne({ - id: userId - }); + const user = await Users.findOne(userId); + if (user == null) throw 'user not found'; const followers = await Followings.find({ followeeId: user.id @@ -19,7 +18,7 @@ export async function publishToFollowers(userId: User['id']) { // フォロワーがリモートユーザーかつ投稿者がローカルユーザーならUpdateを配信 if (Users.isLocalUser(user)) { for (const following of followers) { - if (following.followerHost !== null) { + if (Followings.isRemoteFollower(following)) { const inbox = following.followerSharedInbox || following.followerInbox; if (!queue.includes(inbox)) queue.push(inbox); } diff --git a/src/services/logger.ts b/src/services/logger.ts index e6a54e626d..190bbdd253 100644 --- a/src/services/logger.ts +++ b/src/services/logger.ts @@ -9,14 +9,14 @@ import { genId } from '../misc/gen-id'; type Domain = { name: string; - color: string; + color?: string; }; type Level = 'error' | 'success' | 'warning' | 'debug' | 'info'; export default class Logger { private domain: Domain; - private parentLogger: Logger; + private parentLogger: Logger | null = null; private store: boolean; constructor(domain: string, color?: string, store = true) { @@ -33,7 +33,7 @@ export default class Logger { return logger; } - private log(level: Level, message: string, data: Record, important = false, subDomains: Domain[] = [], store = true): void { + private log(level: Level, message: string, data?: Record | null, important = false, subDomains: Domain[] = [], store = true): void { if (program.quiet) return; if (!this.store) store = false; @@ -80,7 +80,7 @@ export default class Logger { } } - public error(x: string | Error, data?: Record, important = false): void { // 実行を継続できない状況で使う + public error(x: string | Error, data?: Record | null, important = false): void { // 実行を継続できない状況で使う if (x instanceof Error) { data = data || {}; data.e = x; @@ -90,21 +90,21 @@ export default class Logger { } } - public warn(message: string, data?: Record, important = false): void { // 実行を継続できるが改善すべき状況で使う + public warn(message: string, data?: Record | null, important = false): void { // 実行を継続できるが改善すべき状況で使う this.log('warning', message, data, important); } - public succ(message: string, data?: Record, important = false): void { // 何かに成功した状況で使う + public succ(message: string, data?: Record | null, important = false): void { // 何かに成功した状況で使う this.log('success', message, data, important); } - public debug(message: string, data?: Record, important = false): void { // デバッグ用に使う(開発者に必要だが利用者に不要な情報) + public debug(message: string, data?: Record | null, important = false): void { // デバッグ用に使う(開発者に必要だが利用者に不要な情報) if (process.env.NODE_ENV != 'production' || program.verbose) { this.log('debug', message, data, important); } } - public info(message: string, data?: Record, important = false): void { // それ以外 + public info(message: string, data?: Record | null, important = false): void { // それ以外 this.log('info', message, data, important); } } diff --git a/src/services/note/create.ts b/src/services/note/create.ts index 6906bc71ce..9c5989696a 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -10,7 +10,7 @@ import { parse } from '../../mfm/parse'; import { resolveUser } from '../../remote/resolve-user'; import config from '../../config'; import { updateHashtag } from '../update-hashtag'; -import { erase, concat } from '../../prelude/array'; +import { concat } from '../../prelude/array'; import insertNoteUnread from './unread'; import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc'; import extractMentions from '../../misc/extract-mentions'; @@ -27,6 +27,7 @@ import { notesChart, perUserNotesChart, activeUsersChart, instanceChart } from ' import { Poll, IPoll } from '../../models/entities/poll'; import { createNotification } from '../create-notification'; import { isDuplicateKeyValueError } from '../../misc/is-duplicate-key-value-error'; +import { ensure } from '../../prelude/ensure'; type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; @@ -83,25 +84,25 @@ class NotificationManager { } type Option = { - createdAt?: Date; - name?: string; - text?: string; - reply?: Note; - renote?: Note; - files?: DriveFile[]; - geo?: any; - poll?: IPoll; - viaMobile?: boolean; - localOnly?: boolean; - cw?: string; + createdAt?: Date | null; + name?: string | null; + text?: string | null; + reply?: Note | null; + renote?: Note | null; + files?: DriveFile[] | null; + geo?: any | null; + poll?: IPoll | null; + viaMobile?: boolean | null; + localOnly?: boolean | null; + cw?: string | null; visibility?: string; - visibleUsers?: User[]; - apMentions?: User[]; - apHashtags?: string[]; - apEmojis?: string[]; - questionUri?: string; - uri?: string; - app?: App; + visibleUsers?: User[] | null; + apMentions?: User[] | null; + apHashtags?: string[] | null; + apEmojis?: string[] | null; + questionUri?: string | null; + uri?: string | null; + app?: App | null; }; export default async (user: User, data: Option, silent = false) => new Promise(async (res, rej) => { @@ -117,10 +118,6 @@ export default async (user: User, data: Option, silent = false) => new Promise new Promise parse(choice))) + ? concat(data.poll.choices.map(choice => parse(choice)!)) : []; const combinedTokens = tokens.concat(cwTokens).concat(choiceTokens); @@ -173,19 +170,21 @@ export default async (user: User, data: Option, silent = false) => new Promise tag.length <= 100); - if (data.reply && (user.id !== data.reply.userId) && !mentionedUsers.some(u => u.id === data.reply.userId)) { - mentionedUsers.push(await Users.findOne(data.reply.userId)); + if (data.reply && (user.id !== data.reply.userId) && !mentionedUsers.some(u => u.id === data.reply!.userId)) { + mentionedUsers.push(await Users.findOne(data.reply.userId).then(ensure)); } if (data.visibility == 'specified') { + if (data.visibleUsers == null) throw 'invalid param'; + for (const u of data.visibleUsers) { if (!mentionedUsers.some(x => x.id === u.id)) { mentionedUsers.push(u); } } - if (data.reply && !data.visibleUsers.some(x => x.id === data.reply.userId)) { - data.visibleUsers.push(await Users.findOne(data.reply.userId)); + if (data.reply && !data.visibleUsers.some(x => x.id === data.reply!.userId)) { + data.visibleUsers.push(await Users.findOne(data.reply.userId).then(ensure)); } } @@ -215,6 +214,8 @@ export default async (user: User, data: Option, silent = false) => new Promise new Promise { + Users.findOne(reply.userId).then(ensure).then(u => { deliver(user, noteActivity, u.inbox); }); } // 投稿がRenoteかつ投稿者がローカルユーザーかつRenote元の投稿の投稿者がリモートユーザーなら配送 if (renote && renote.userHost !== null) { - Users.findOne(renote.userId).then(u => { + Users.findOne(renote.userId).then(ensure).then(u => { deliver(user, noteActivity, u.inbox); }); } @@ -340,14 +341,14 @@ async function publish(user: User, note: Note, reply: Note, renote: Note, noteAc if (['public', 'home', 'followers'].includes(note.visibility)) { // フォロワーに配信 - publishToFollowers(note, user, noteActivity, reply); + publishToFollowers(note, user, noteActivity); } } async function insertNote(user: User, data: Option, tags: string[], emojis: string[], mentionedUsers: User[]) { const insert = new Note({ - id: genId(data.createdAt), - createdAt: data.createdAt, + id: genId(data.createdAt!), + createdAt: data.createdAt!, fileIds: data.files ? data.files.map(file => file.id) : [], replyId: data.reply ? data.reply.id : null, renoteId: data.renote ? data.renote.id : null, @@ -358,8 +359,8 @@ async function insertNote(user: User, data: Option, tags: string[], emojis: stri tags: tags.map(tag => tag.toLowerCase()), emojis, userId: user.id, - viaMobile: data.viaMobile, - localOnly: data.localOnly, + viaMobile: data.viaMobile!, + localOnly: data.localOnly!, geo: data.geo || null, appId: data.app ? data.app.id : null, visibility: data.visibility as any, @@ -401,10 +402,10 @@ async function insertNote(user: User, data: Option, tags: string[], emojis: stri const poll = new Poll({ noteId: note.id, - choices: data.poll.choices, - expiresAt: data.poll.expiresAt, - multiple: data.poll.multiple, - votes: new Array(data.poll.choices.length).fill(0), + choices: data.poll!.choices, + expiresAt: data.poll!.expiresAt, + multiple: data.poll!.multiple, + votes: new Array(data.poll!.choices.length).fill(0), noteVisibility: note.visibility, userId: user.id, userHost: user.host @@ -416,7 +417,7 @@ async function insertNote(user: User, data: Option, tags: string[], emojis: stri note = await Notes.save(insert); } - return note; + return note!; } catch (e) { // duplicate key error if (isDuplicateKeyValueError(e)) { @@ -434,7 +435,7 @@ async function insertNote(user: User, data: Option, tags: string[], emojis: stri function index(note: Note) { if (note.text == null || config.elasticsearch == null) return; - es.index({ + es!.index({ index: 'misskey', type: 'note', id: note.id.toString(), @@ -466,7 +467,7 @@ async function notifyToWatchersOfReplyee(reply: Note, user: User, nm: Notificati } } -async function publishToFollowers(note: Note, user: User, noteActivity: any, reply: Note) { +async function publishToFollowers(note: Note, user: User, noteActivity: any) { const followers = await Followings.find({ followeeId: note.userId }); @@ -474,7 +475,7 @@ async function publishToFollowers(note: Note, user: User, noteActivity: any, rep const queue: string[] = []; for (const following of followers) { - if (following.followerHost !== null) { + if (Followings.isRemoteFollower(following)) { // フォロワーがリモートユーザーかつ投稿者がローカルユーザーなら投稿を配信 if (Users.isLocalUser(user)) { const inbox = following.followerSharedInbox || following.followerInbox; @@ -523,11 +524,9 @@ async function extractMentionedUsers(user: User, tokens: ReturnType + let mentionedUsers = (await Promise.all(mentions.map(m => resolveUser(m.username, m.host || user.host).catch(() => null) - )); - - mentionedUsers = mentionedUsers.filter(x => x != null); + ))).filter(x => x != null) as User[]; // Drop duplicate users mentionedUsers = mentionedUsers.filter((u, i, self) => diff --git a/src/services/note/polls/update.ts b/src/services/note/polls/update.ts index beb35cc278..277ace204d 100644 --- a/src/services/note/polls/update.ts +++ b/src/services/note/polls/update.ts @@ -7,8 +7,10 @@ import { Note } from '../../../models/entities/note'; export async function deliverQuestionUpdate(noteId: Note['id']) { const note = await Notes.findOne(noteId); + if (note == null) throw 'note not found'; const user = await Users.findOne(note.userId); + if (user == null) throw 'note not found'; const followers = await Followings.find({ followeeId: user.id @@ -19,13 +21,8 @@ export async function deliverQuestionUpdate(noteId: Note['id']) { // フォロワーがリモートユーザーかつ投稿者がローカルユーザーならUpdateを配信 if (Users.isLocalUser(user)) { for (const following of followers) { - const follower = { - inbox: following.followerInbox, - sharedInbox: following.followerSharedInbox - }; - - if (following.followerHost !== null) { - const inbox = follower.sharedInbox || follower.inbox; + if (Followings.isRemoteFollower(following)) { + const inbox = following.followerSharedInbox || following.followerInbox; if (!queue.includes(inbox)) queue.push(inbox); } } diff --git a/src/services/note/polls/vote.ts b/src/services/note/polls/vote.ts index 3051ff42d9..3bae617b4f 100644 --- a/src/services/note/polls/vote.ts +++ b/src/services/note/polls/vote.ts @@ -7,11 +7,13 @@ import { Not } from 'typeorm'; import { genId } from '../../../misc/gen-id'; import { createNotification } from '../../create-notification'; -export default (user: User, note: Note, choice: number) => new Promise(async (res, rej) => { +export default async function(user: User, note: Note, choice: number) { const poll = await Polls.findOne({ noteId: note.id }); + if (poll == null) throw 'poll not found'; + // Check whether is valid choice - if (poll.choices[choice] == null) return rej('invalid choice param'); + if (poll.choices[choice] == null) throw new Error('invalid choice param'); // if already voted const exist = await PollVotes.find({ @@ -21,10 +23,10 @@ export default (user: User, note: Note, choice: number) => new Promise(async (re if (poll.multiple) { if (exist.some(x => x.choice === choice)) { - return rej('already voted'); + throw new Error('already voted'); } } else if (exist.length !== 0) { - return rej('already voted'); + throw new Error('already voted'); } // Create vote @@ -36,8 +38,6 @@ export default (user: User, note: Note, choice: number) => new Promise(async (re choice: choice }); - res(); - // Increment votes count 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}'`); @@ -70,7 +70,7 @@ export default (user: User, note: Note, choice: number) => new Promise(async (re const profile = await UserProfiles.findOne({ userId: user.id }); // ローカルユーザーが投票した場合この投稿をWatchする - if (Users.isLocalUser(user) && profile.autoWatch) { + if (Users.isLocalUser(user) && profile!.autoWatch) { watch(user.id, note); } -}); +} diff --git a/src/services/note/reaction/create.ts b/src/services/note/reaction/create.ts index 55f9bcd084..ea2108430a 100644 --- a/src/services/note/reaction/create.ts +++ b/src/services/note/reaction/create.ts @@ -82,7 +82,7 @@ export default async (user: User, note: Note, reaction: string) => { const profile = await UserProfiles.findOne({ userId: user.id }); // ユーザーがローカルユーザーかつ自動ウォッチ設定がオンならばこの投稿をWatchする - if (Users.isLocalUser(user) && profile.autoWatch) { + if (Users.isLocalUser(user) && profile!.autoWatch) { watch(user.id, note); } @@ -91,7 +91,7 @@ export default async (user: User, note: Note, reaction: string) => { if (Users.isLocalUser(user) && note.userHost !== null) { const content = renderActivity(renderLike(user, note, reaction)); Users.findOne(note.userId).then(u => { - deliver(user, content, u.inbox); + deliver(user, content, u!.inbox); }); } //#endregion diff --git a/src/services/note/reaction/delete.ts b/src/services/note/reaction/delete.ts index 76f5ed631e..355e1c5466 100644 --- a/src/services/note/reaction/delete.ts +++ b/src/services/note/reaction/delete.ts @@ -42,7 +42,7 @@ export default async (user: User, note: Note) => { if (Users.isLocalUser(user) && (note.userHost !== null)) { const content = renderActivity(renderUndo(renderLike(user, note, exist.reaction), user)); Users.findOne(note.userId).then(u => { - deliver(user, content, u.inbox); + deliver(user, content, u!.inbox); }); } //#endregion diff --git a/src/services/push-notification.ts b/src/services/push-notification.ts index defd4d6e2d..1830cad623 100644 --- a/src/services/push-notification.ts +++ b/src/services/push-notification.ts @@ -1,26 +1,17 @@ import * as push from 'web-push'; import config from '../config'; import { SwSubscriptions } from '../models'; -import { Meta } from '../models/entities/meta'; import fetchMeta from '../misc/fetch-meta'; -let meta: Meta = null; - -setInterval(() => { - fetchMeta().then(m => { - meta = m; +export default async function(userId: string, type: string, body?: any) { + const meta = await fetchMeta(); - if (meta.enableServiceWorker) { - // アプリケーションの連絡先と、サーバーサイドの鍵ペアの情報を登録 - push.setVapidDetails(config.url, - meta.swPublicKey, - meta.swPrivateKey); - } - }); -}, 3000); + if (!meta.enableServiceWorker || meta.swPublicKey == null || meta.swPrivateKey == null) return; -export default async function(userId: string, type: string, body?: any) { - if (!meta.enableServiceWorker) return; + // アプリケーションの連絡先と、サーバーサイドの鍵ペアの情報を登録 + push.setVapidDetails(config.url, + meta.swPublicKey, + meta.swPrivateKey); // Fetch const subscriptions = await SwSubscriptions.find({ diff --git a/src/services/register-or-fetch-instance-doc.ts b/src/services/register-or-fetch-instance-doc.ts index 459f538e96..9957edd3db 100644 --- a/src/services/register-or-fetch-instance-doc.ts +++ b/src/services/register-or-fetch-instance-doc.ts @@ -5,8 +5,6 @@ import { genId } from '../misc/gen-id'; import { toPuny } from '../misc/convert-host'; export async function registerOrFetchInstanceDoc(host: string): Promise { - if (host == null) return null; - host = toPuny(host); const index = await Instances.findOne({ host }); diff --git a/src/services/stream.ts b/src/services/stream.ts index c1d14b2779..c52af48b59 100644 --- a/src/services/stream.ts +++ b/src/services/stream.ts @@ -6,7 +6,7 @@ import { UserList } from '../models/entities/user-list'; import { ReversiGame } from '../models/entities/games/reversi/game'; class Publisher { - private ev: Xev; + private ev: Xev | null = null; constructor() { // Redisがインストールされてないときはプロセス間通信を使う @@ -15,7 +15,7 @@ class Publisher { } } - private publish = (channel: string, type: string, value?: any): void => { + private publish = (channel: string, type: string | null, value?: any): void => { const message = type == null ? value : value == null ? { type: type, body: null } : { type: type, body: value }; @@ -23,7 +23,7 @@ class Publisher { if (this.ev) { this.ev.emit(channel, message); } else { - redis.publish('misskey', JSON.stringify({ + redis!.publish('misskey', JSON.stringify({ channel: channel, message: message })); diff --git a/src/tools/clean-remote-files.ts b/src/tools/clean-remote-files.ts index f64affea97..e722552e14 100644 --- a/src/tools/clean-remote-files.ts +++ b/src/tools/clean-remote-files.ts @@ -3,6 +3,7 @@ import del from '../services/drive/delete-file'; import { DriveFiles } from '../models'; import { Not } from 'typeorm'; import { DriveFile } from '../models/entities/drive-file'; +import { ensure } from '../prelude/ensure'; const limit = promiseLimit(16); @@ -17,7 +18,7 @@ DriveFiles.find({ }); async function job(file: DriveFile): Promise { - file = await DriveFiles.findOne(file.id); + file = await DriveFiles.findOne(file.id).then(ensure); await del(file, true); diff --git a/src/tools/show-signin-history.ts b/src/tools/show-signin-history.ts index 584bece6bb..9e6e849f5d 100644 --- a/src/tools/show-signin-history.ts +++ b/src/tools/show-signin-history.ts @@ -9,7 +9,7 @@ import { Users, Signins } from '../models'; // node built/tools/show-signin-history username all // with full request headers -async function main(username: string, headers: string[]) { +async function main(username: string, headers?: string[]) { const user = await Users.findOne({ host: null, usernameLower: username.toLowerCase(), @@ -39,7 +39,7 @@ async function main(username: string, headers: string[]) { const args = process.argv.slice(2); let username = args[0]; -let headers: string[]; +let headers: string[] | undefined; if (args[1] != null) { headers = args[1].split(/,/).map(header => header.toLowerCase()); diff --git a/tsconfig.json b/tsconfig.json index 6bd8571207..4f1d1b9cd5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,7 +14,8 @@ "removeComments": false, "noLib": false, "strict": true, - "strictNullChecks": false, + "strictNullChecks": true, + "strictPropertyInitialization": false, "experimentalDecorators": true, "emitDecoratorMetadata": true, "resolveJsonModule": true, -- cgit v1.2.3-freya From 85d3023cd5ee467f394d00657f3f2dffa14cdc62 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 13 Apr 2019 14:55:59 +0900 Subject: Clean packed responses --- src/models/repositories/note.ts | 14 +++++++------- src/models/repositories/user.ts | 8 ++++---- 2 files changed, 11 insertions(+), 11 deletions(-) (limited to 'src/models') diff --git a/src/models/repositories/note.ts b/src/models/repositories/note.ts index 77cf00849f..a2757d3cc8 100644 --- a/src/models/repositories/note.ts +++ b/src/models/repositories/note.ts @@ -162,15 +162,15 @@ export class NoteRepository extends Repository { const packed = await rap({ id: note.id, createdAt: note.createdAt, - app: note.appId ? Apps.pack(note.appId) : null, + app: note.appId ? Apps.pack(note.appId) : undefined, userId: note.userId, user: Users.pack(note.user || note.userId, meId), text: text, cw: note.cw, visibility: note.visibility, - localOnly: note.localOnly, - visibleUserIds: note.visibleUserIds, - viaMobile: note.viaMobile, + localOnly: note.localOnly || undefined, + visibleUserIds: note.visibility === 'specified' ? note.visibleUserIds : undefined, + viaMobile: note.viaMobile || undefined, renoteCount: note.renoteCount, repliesCount: note.repliesCount, reactions: note.reactions, @@ -188,13 +188,13 @@ export class NoteRepository extends Repository { ...(opts.detail ? { reply: note.replyId ? this.pack(note.replyId, meId, { detail: false - }) : null, + }) : undefined, renote: note.renoteId ? this.pack(note.renoteId, meId, { detail: true - }) : null, + }) : undefined, - poll: note.hasPoll ? populatePoll() : null, + poll: note.hasPoll ? populatePoll() : undefined, ...(meId ? { myReaction: populateMyReaction() diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts index cddb77ffae..fb11778324 100644 --- a/src/models/repositories/user.ts +++ b/src/models/repositories/user.ts @@ -92,10 +92,10 @@ export class UserRepository extends Repository { bannerUrl: user.bannerUrl, avatarColor: user.avatarColor, bannerColor: user.bannerColor, - isAdmin: user.isAdmin, - isBot: user.isBot, - isCat: user.isCat, - isVerified: user.isVerified, + isAdmin: user.isAdmin || undefined, + isBot: user.isBot || undefined, + isCat: user.isCat || undefined, + isVerified: user.isVerified || undefined, // カスタム絵文字添付 emojis: user.emojis.length > 0 ? Emojis.find({ -- cgit v1.2.3-freya From 83161866952dd0bd617da9e121540f6456f3c600 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 13 Apr 2019 15:02:15 +0900 Subject: Clean packed responses --- src/models/repositories/note.ts | 2 +- src/models/repositories/user.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src/models') diff --git a/src/models/repositories/note.ts b/src/models/repositories/note.ts index a2757d3cc8..7a48d24e13 100644 --- a/src/models/repositories/note.ts +++ b/src/models/repositories/note.ts @@ -148,7 +148,7 @@ export class NoteRepository extends Repository { return reaction.reaction; } - return null; + return undefined; } let text = note.text; diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts index fb11778324..87f1589b2f 100644 --- a/src/models/repositories/user.ts +++ b/src/models/repositories/user.ts @@ -89,9 +89,7 @@ export class UserRepository extends Repository { username: user.username, host: user.host, avatarUrl: user.avatarUrl, - bannerUrl: user.bannerUrl, avatarColor: user.avatarColor, - bannerColor: user.bannerColor, isAdmin: user.isAdmin || undefined, isBot: user.isBot || undefined, isCat: user.isCat || undefined, @@ -121,6 +119,8 @@ export class UserRepository extends Repository { url: profile!.url, createdAt: user.createdAt, updatedAt: user.updatedAt, + bannerUrl: user.bannerUrl, + bannerColor: user.bannerColor, description: profile!.description, location: profile!.location, birthday: profile!.birthday, -- cgit v1.2.3-freya From 8c72e011d26e38c779c26e20e4754502e56e3c0f Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 13 Apr 2019 17:26:38 +0900 Subject: Fix bug --- src/models/repositories/user-list.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src/models') diff --git a/src/models/repositories/user-list.ts b/src/models/repositories/user-list.ts index fbf81b8886..e591794b8b 100644 --- a/src/models/repositories/user-list.ts +++ b/src/models/repositories/user-list.ts @@ -1,6 +1,7 @@ import { EntityRepository, Repository } from 'typeorm'; import { UserList } from '../entities/user-list'; import { ensure } from '../../prelude/ensure'; +import { UserListJoinings } from '..'; @EntityRepository(UserList) export class UserListRepository extends Repository { @@ -9,9 +10,14 @@ export class UserListRepository extends Repository { ) { const userList = typeof src === 'object' ? src : await this.findOne(src).then(ensure); + const users = await UserListJoinings.find({ + userListId: userList.id + }); + return { id: userList.id, - name: userList.name + name: userList.name, + userIds: users.map(x => x.userId) }; } } -- cgit v1.2.3-freya From 343f2f1f332f9e5328dbd01166b7acc00927b8ce Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 13 Apr 2019 20:02:31 +0900 Subject: Fix bug --- src/models/entities/auth-session.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'src/models') diff --git a/src/models/entities/auth-session.ts b/src/models/entities/auth-session.ts index 83f8365630..4eec27e3f6 100644 --- a/src/models/entities/auth-session.ts +++ b/src/models/entities/auth-session.ts @@ -19,11 +19,15 @@ export class AuthSession { }) public token: string; - @Column(id()) + @Column({ + ...id(), + nullable: true + }) public userId: User['id']; @ManyToOne(type => User, { - onDelete: 'CASCADE' + onDelete: 'CASCADE', + nullable: true }) @JoinColumn() public user: User | null; -- cgit v1.2.3-freya From 61455ffe2905a9d6a13952086bf3ee978177eab4 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 14 Apr 2019 11:58:10 +0900 Subject: Fix bug --- src/models/repositories/user.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'src/models') diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts index 87f1589b2f..7e67ead72a 100644 --- a/src/models/repositories/user.ts +++ b/src/models/repositories/user.ts @@ -121,6 +121,7 @@ export class UserRepository extends Repository { updatedAt: user.updatedAt, bannerUrl: user.bannerUrl, bannerColor: user.bannerColor, + isLocked: user.isLocked, description: profile!.description, location: profile!.location, birthday: profile!.birthday, -- cgit v1.2.3-freya From 487a3079cd38e6332fd970ab825c569f9f660822 Mon Sep 17 00:00:00 2001 From: tamaina Date: Sun, 14 Apr 2019 17:12:04 +0900 Subject: Improve post form attaches; Fix #4673, Resolve #2327, Resolve #2145 [v11] (#4674) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * improve post form attaches * Fix * chain * 右クリックでも反応するように --- locales/ja-JP.yml | 6 +- src/client/app/admin/views/drive.vue | 18 ++- .../views/components/drive-file-thumbnail.vue | 4 +- .../common/views/components/post-form-attaches.vue | 139 +++++++++++++++++++++ src/client/app/common/views/widgets/post-form.vue | 45 +------ src/client/app/desktop/views/components/drive.vue | 1 - .../app/desktop/views/components/post-form.vue | 58 +-------- .../app/mobile/views/components/post-form.vue | 42 +------ src/models/repositories/drive-file.ts | 8 +- src/server/web/feed.ts | 2 +- 10 files changed, 169 insertions(+), 154 deletions(-) create mode 100644 src/client/app/common/views/components/post-form-attaches.vue (limited to 'src/models') diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index abfe37416d..cc6fe2b086 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -964,7 +964,6 @@ desktop/views/components/post-form.vue: posting: "投稿中" attach-media-from-local: "PCからメディアを添付" attach-media-from-drive: "ドライブからメディアを添付" - attach-cancel: "添付取り消し" insert-a-kao: "v('ω')v" create-poll: "アンケートを作成" text-remain: "残り{}文字" @@ -1070,6 +1069,11 @@ common/views/components/password-settings.vue: changed: "パスワードを変更しました" failed: "パスワード変更に失敗しました" +common/views/components/post-form-attaches.vue: + attach-cancel: "添付取り消し" + mark-as-sensitive: "閲覧注意に設定" + unmark-as-sensitive: "閲覧注意を解除" + desktop/views/components/sub-note-content.vue: private: "この投稿は非公開です" deleted: "この投稿は削除されました" diff --git a/src/client/app/admin/views/drive.vue b/src/client/app/admin/views/drive.vue index 1f35de100e..e4565b78fe 100644 --- a/src/client/app/admin/views/drive.vue +++ b/src/client/app/admin/views/drive.vue @@ -38,7 +38,7 @@
-
+
@@ -75,10 +75,15 @@ import Vue from 'vue'; import i18n from '../../i18n'; import { faCloud, faTerminal, faSearch } from '@fortawesome/free-solid-svg-icons'; import { faTrashAlt, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons'; +import XFileThumbnail from '../../common/views/components/drive-file-thumbnail.vue'; export default Vue.extend({ i18n: i18n('admin/views/drive.vue'), + components: { + XFileThumbnail + }, + data() { return { file: null, @@ -151,13 +156,6 @@ export default Vue.extend({ }); }, - thumbnail(file: any): any { - return { - 'background-color': file.properties.avgColor || 'transparent', - 'background-image': `url(${file.thumbnailUrl})` - }; - }, - async del(file: any) { const process = async () => { await this.$root.api('drive/files/delete', { fileId: file.id }); @@ -179,9 +177,9 @@ export default Vue.extend({ this.$root.api('drive/files/update', { fileId: file.id, isSensitive: !file.isSensitive + }).then(() => { + file.isSensitive = !file.isSensitive; }); - - file.isSensitive = !file.isSensitive; }, async show() { diff --git a/src/client/app/common/views/components/drive-file-thumbnail.vue b/src/client/app/common/views/components/drive-file-thumbnail.vue index c432de1721..1a3ef37193 100644 --- a/src/client/app/common/views/components/drive-file-thumbnail.vue +++ b/src/client/app/common/views/components/drive-file-thumbnail.vue @@ -105,9 +105,7 @@ export default Vue.extend({ }, isThumbnailAvailable(): boolean { return this.file.thumbnailUrl - ? this.file.thumbnailUrl.endsWith('?thumbnail') - ? (this.is === 'image' || this.is === 'video') - : true + ? (this.is === 'image' || this.is === 'video') : false; }, background(): string { diff --git a/src/client/app/common/views/components/post-form-attaches.vue b/src/client/app/common/views/components/post-form-attaches.vue new file mode 100644 index 0000000000..467e430ccf --- /dev/null +++ b/src/client/app/common/views/components/post-form-attaches.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/src/client/app/common/views/widgets/post-form.vue b/src/client/app/common/views/widgets/post-form.vue index f1826cc59f..b30168b879 100644 --- a/src/client/app/common/views/widgets/post-form.vue +++ b/src/client/app/common/views/widgets/post-form.vue @@ -21,14 +21,7 @@
-
- -
-
- -
-
-
+
@@ -45,7 +38,7 @@ import define from '../../../common/define-widget'; import i18n from '../../../i18n'; import insertTextAtCursor from 'insert-text-at-cursor'; -import * as XDraggable from 'vuedraggable'; +import XPostFormAttaches from '../components/post-form-attaches.vue'; export default define({ name: 'post-form', @@ -56,7 +49,7 @@ export default define({ i18n: i18n('desktop/views/widgets/post-form.vue'), components: { - XDraggable + XPostFormAttaches }, data() { @@ -249,38 +242,6 @@ export default define({ & + .emoji opacity 0.7 - > .files - > div - padding 4px - - &:after - content "" - display block - clear both - - > div - float left - border solid 4px transparent - cursor move - - &:hover > .remove - display block - - > .img - width 64px - height 64px - background-size cover - background-position center center - - > .remove - display none - position absolute - top -6px - right -6px - width 16px - height 16px - cursor pointer - > input[type=file] display none diff --git a/src/client/app/desktop/views/components/drive.vue b/src/client/app/desktop/views/components/drive.vue index 7513f002a7..fcabb4b8eb 100644 --- a/src/client/app/desktop/views/components/drive.vue +++ b/src/client/app/desktop/views/components/drive.vue @@ -769,7 +769,6 @@ export default Vue.extend({ > .mk-uploader height 100px padding 16px - background #fff > input display none diff --git a/src/client/app/desktop/views/components/post-form.vue b/src/client/app/desktop/views/components/post-form.vue index 6ba4d47087..fe39a17e67 100644 --- a/src/client/app/desktop/views/components/post-form.vue +++ b/src/client/app/desktop/views/components/post-form.vue @@ -27,15 +27,7 @@ -
- -
-
- -
-
-

{{ 4 - files.length }}/4

-
+
@@ -65,7 +57,6 @@ import Vue from 'vue'; import i18n from '../../../i18n'; import insertTextAtCursor from 'insert-text-at-cursor'; -import * as XDraggable from 'vuedraggable'; import getFace from '../../../common/scripts/get-face'; import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue'; import { parse } from '../../../../../mfm/parse'; @@ -74,13 +65,14 @@ import { erase, unique } from '../../../../../prelude/array'; import { length } from 'stringz'; import { toASCII } from 'punycode'; import extractMentions from '../../../../../misc/extract-mentions'; +import XPostFormAttaches from '../../../common/views/components/post-form-attaches.vue'; export default Vue.extend({ i18n: i18n('desktop/views/components/post-form.vue'), components: { - XDraggable, - MkVisibilityChooser + MkVisibilityChooser, + XPostFormAttaches }, props: { @@ -513,7 +505,7 @@ export default Vue.extend({ kao() { this.text += getFace(); - } + }, } }); @@ -618,46 +610,6 @@ export default Vue.extend({ border-bottom solid 1px var(--primaryAlpha01) !important border-radius 0 - > .remain - display block - position absolute - top 8px - right 8px - margin 0 - padding 0 - color var(--primaryAlpha04) - - > div - padding 4px - - &:after - content "" - display block - clear both - - > div - float left - border solid 4px transparent - cursor move - - &:hover > .remove - display block - - > .img - width 64px - height 64px - background-size cover - background-position center center - - > .remove - display none - position absolute - top -6px - right -6px - width 16px - height 16px - cursor pointer - > .mk-poll-editor background var(--desktopPostFormTextareaBg) border solid 1px var(--primaryAlpha01) diff --git a/src/client/app/mobile/views/components/post-form.vue b/src/client/app/mobile/views/components/post-form.vue index 9c1072b4a3..6b26cdf890 100644 --- a/src/client/app/mobile/views/components/post-form.vue +++ b/src/client/app/mobile/views/components/post-form.vue @@ -21,13 +21,7 @@ -
- -
-
-
-
-
+