From 8d94a6a8df28f2cc9ddec40a51ff0d3ae56d9174 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 13 Apr 2019 01:43:22 +0900 Subject: [PATCH] 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 +- .../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 +- .../activitypub/kernel/accept/follow.ts | 3 +- src/remote/activitypub/kernel/add/index.ts | 1 + .../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 + .../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 | 126 ++++++++++-------- 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 +- .../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 +- .../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 +- .../api/endpoints/auth/session/userkey.ts | 3 +- src/server/api/endpoints/blocking/list.ts | 2 +- .../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 +- .../api/endpoints/charts/user/following.ts | 2 +- src/server/api/endpoints/charts/user/notes.ts | 2 +- .../api/endpoints/charts/user/reactions.ts | 2 +- src/server/api/endpoints/charts/users.ts | 2 +- src/server/api/endpoints/drive/files.ts | 2 +- .../api/endpoints/drive/files/create.ts | 2 +- src/server/api/endpoints/drive/files/show.ts | 10 +- src/server/api/endpoints/drive/folders.ts | 2 +- .../api/endpoints/drive/folders/update.ts | 6 +- src/server/api/endpoints/drive/stream.ts | 2 +- .../api/endpoints/federation/instances.ts | 2 +- .../api/endpoints/games/reversi/games.ts | 2 +- .../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 +- .../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 +- .../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 +- .../api/endpoints/notes/global-timeline.ts | 2 +- .../api/endpoints/notes/hybrid-timeline.ts | 2 +- .../api/endpoints/notes/local-timeline.ts | 8 +- src/server/api/endpoints/notes/mentions.ts | 2 +- .../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 +- .../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 +- .../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 ++++++----- .../api/stream/channels/games/reversi.ts | 4 +- .../api/stream/channels/home-timeline.ts | 8 +- .../api/stream/channels/hybrid-timeline.ts | 10 +- src/server/api/stream/channels/main.ts | 4 +- .../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 | 25 ++-- .../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, 939 insertions(+), 785 deletions(-) create mode 100644 src/prelude/ensure.ts diff --git a/gulpfile.ts b/gulpfile.ts index b2956c240..2242843db 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 e89f98043..e08a49462 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 2d4080fdb..4d360c726 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 ca3716972..362fa3f26 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 6200faf12..26b25eab4 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 c491aed4c..b60f5badf 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 e5726e9c8..641a552c0 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 bb27d6f80..cf8986263 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 369f9de0e..5fc4a1641 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 9d6077170..f8464121f 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 3cd798762..58976fc2c 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 c88a920c6..7f3125740 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 f7feebd55..dbf786455 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 bbf49efc1..70d58ffe2 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 0dd4e4ef5..167e01fdd 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 d1483e9ed..cf1fc474c 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 d60fa9b31..17b021e91 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 1edd26cd1..2d7c6bd0c 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 8b06300ea..918e7d63f 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 e5c24dd46..7c17953d9 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 c0ad5547b..6bb67163a 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 c72a582c0..f619d6e37 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 2e3323baf..a0c0cf68c 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 76e3ddf9a..540c5466f 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 81f386613..e18aa591f 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 817677fa3..003e35071 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 faf0f353a..ce88adefa 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 bead093b2..451ed8e2d 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 02253d272..3fff57866 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 f0cb6ff90..c380f5251 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 3612ac5c4..4d99c6ef7 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 b87b30388..665927353 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 cd98cb4fe..1812e2e71 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 4526461e6..f428903c1 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 7189da8e2..28191d4ab 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 01bf8cf87..77cf00849 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 9bc569cd3..4781d4c06 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 921c18ca7..fbf81b888 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 d7f2c3d04..cddb77ffa 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 000000000..90bf05538 --- /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 728c43c6a..1ab59fd18 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 5f347fb58..491734acc 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 c12aa4fca..44025ec96 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 fb30df79f..81dcf8f93 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 3aed526dc..f810b6ee8 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 92867ad82..eaa5caf63 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 f3987cb0d..5cd978c1a 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 aae24b22d..8de3193e4 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 c7273ea6b..1e852be94 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 b9701c0c6..8837c80d8 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 35b0ce538..4deaef2ae 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 816fcbadb..f3e517ad9 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 d16f0a4a0..a5b268741 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 403fc66be..f9822c518 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 48e251dd9..19e33eb7d 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 e6c8833f3..d37404502 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 4a57d0675..d1251817f 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 86dd8fb33..d4fa7bf38 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 b06ae6fb9..91689339a 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 ae33be59d..32b8d6647 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 c916a0073..9c277ed7d 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 cc63a740b..ce84d0c79 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 6376ab93a..5f2e58c3b 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 f337a0173..75879d697 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 c9991dba3..f8b35ea21 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); + const eomjiTags = tags.filter(tag => tag.type === 'Emoji' && tag.icon && tag.icon.url && tag.name); - return await Promise.all( - eomjiTags.map(async tag => { - const name = tag.name.replace(/^:/, '').replace(/:$/, ''); + return await Promise.all(eomjiTags.map(async tag => { + const name = tag.name!.replace(/^:/, '').replace(/:$/, ''); - const exists = await Emojis.findOne({ - host, - name - }); + const exists = await Emojis.findOne({ + host, + name + }); - 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), - }); + 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 - }); - } - - return exists; + return await Emojis.findOne({ + host, + name + }) as Emoji; } - logger.info(`register emoji host=${host}, name=${name}`); + return exists; + } - 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); - }) - ); + 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 e7021956d..9465cf0cd 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 2ff8e21ab..708cdc2a6 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 0a1e6e29f..8d2008d1d 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 946c45a81..c29a9aea8 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 9446be3c8..6d354803e 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 58ee4fb52..c66af2667 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 83af07870..243335864 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 e561e47c6..3fb164ef4 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 8aca5e810..7dc48c15e 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 6a8ce45c9..9b518f5e8 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 67535e37d..800673943 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 3b39977d4..12fccbfa7 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 f43312d79..86ec1000c 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 62c54399e..e48dc57f7 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 4894aac1f..4a7314e0c 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 377f43c98..118d8f00a 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 827aecdf2..2de6994f3 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 e293e3fed..5c2b7e45c 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 d72f2cc9e..c79f8eef5 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 0c859a4f8..51c11e5df 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 a18419bcf..990cbf2a8 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 5c5a734c1..63d1dd795 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 1ccabc92d..7c6672e6d 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 26385d4e2..cf73e4cc7 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 fca76e708..25aae6db8 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 907b1fdc1..e1419bdfe 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 c2496d7ef..4e4777569 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 73976b987..97760ae79 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 0be9047d5..f9716328d 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 35c5dd318..1b992eeaa 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 0baa6b111..55898b59d 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 8524b96f9..7126ac52c 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 a078891ab..97f353579 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 60fa72c5c..f0349b17f 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 a9676e158..ae6d89440 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 95f16c76a..34e9bfee5 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 a7ec12707..eceb0b275 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 cf3094f7e..e99c17ae6 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 c0fcd95fe..648588fbe 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 86f30e4b8..074c4978c 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 e3696dfda..918fb62c6 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 8feba0bd1..5d86e85d3 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 8c1db54f7..d39a20df1 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 7c9b2508a..5b88a1d71 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 3ed5e0934..17de5756d 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 400b73d3b..4e4db6c78 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 5702c70fc..340a39a41 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 b516ec2df..e8c0e683c 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 f5c381640..08ae2ff70 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 90129bed6..7d3ece00a 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 9a8462776..96d9f8242 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 1946d26de..301338ed9 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 07736e042..7267157e0 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 e34d3c67f..da367b597 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 7996c8166..2998bc1a1 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 fd91a2ebc..6a9a2df6e 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 20cba96d0..fa58f2f2c 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 edc7cefd2..e23678dcb 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 db9a2fe94..76d79b3a4 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 fa25b7439..9c7857e7e 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 ebf04fcb5..3e9fea19e 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 d0e0695e1..0dda125b9 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 7ef7aa5fa..389d0b321 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 d2d149b2d..2c25250be 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 9b016e0a2..56074c9d0 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 ec53bca97..56c0362c8 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 e9ae19d73..74648951f 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 d98f0d753..8b05f3c8c 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 3cb514d93..d06ab621c 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 c0aec6121..c12378eb7 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 5f2d18447..0fd8a4860 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 d205d1674..e8b26362a 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 10f6e3984..17ba96935 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 72f2c39d6..2b4ae2a31 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 6defd7904..6b26e31c0 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 138f05fb3..83649015d 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 dbaf91bca..17d44c424 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 c44a5275b..5d2e5b7d4 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 7bf62f366..ceffb1cf4 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 76f1682a9..6dfb14300 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 cd0734134..c10c0d748 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 0bbe7d332..b7f614915 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 ff838d4f4..cbd4d35cd 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 dd4d93c7a..68dee6682 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 b1b5ca9d3..bcb0b6d1e 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 81b899836..74a8ea918 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 09b0f1716..980ff2446 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 48de88d36..cba3724b6 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 cc88fb938..4d5ac6fbe 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 e22db4d1b..c27f3df1b 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 deda04acb..05f171af8 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 f99165f3d..18af0a268 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 64d63e2d0..88a474be7 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 0e2800168..5e017150e 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 f82f43762..a1d140c6c 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 6df394cbb..da23be3c5 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 2c82d6613..9e16e34e3 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 443bd507f..96da221d9 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 ae93e40ea..2be193f89 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 ca441d581..cb0bdc9f4 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 e29c06133..8a6c94458 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 fe2e5577c..676546f2a 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 03d83efd9..ea4df060f 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 879b8b484..b2561dee6 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 580947811..4878d8c52 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 c0c762c6c..b35ee8c47 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 e2eba10f7..1ff932d1d 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 671aad436..4112dd9b0 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 158f108c4..d708eae9f 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 0498e5e01..3db338386 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 2cece0947..61960657b 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 30643aeda..18e6aa835 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 0d9bf3149..1100f87ac 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 648badc1d..0c495398a 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 b81fbb9d4..8397f849f 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 abbd91ec8..f73f3229d 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 f9b067b79..5da3d79eb 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 9c153f016..601e288f3 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 d9ba14a8e..374dbf3bd 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 94f0643f7..285c20fcd 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 d1a15e392..5cadf1b12 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 aed475e6f..7d0080b4d 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 7c5684d2c..d29b7a888 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 79ca0d59f..def4f3358 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 2a60b1a0a..0a9ec8dae 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 bcb8214c5..5bff8adfd 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 b83c3558d..94b97fed6 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 b7fa99391..bba453b98 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 3c538390b..89ac331ca 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 a7fe1fbd2..bfe8c5c3b 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 57bb61fd9..b69dfe42b 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 b61c31a51..70e7448aa 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 0be8e24e1..1397514ad 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 c590edcfd..d5b5e48c4 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 4e43421bd..f5c957c74 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 7dba472e7..2bb5c7a10 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 e6a54e626..190bbdd25 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 6906bc71c..9c5989696 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 beb35cc27..277ace204 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 3051ff42d..3bae617b4 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 55f9bcd08..ea2108430 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 76f5ed631..355e1c546 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 defd4d6e2..1830cad62 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; - - if (meta.enableServiceWorker) { - // アプリケーションの連絡先と、サーバーサイドの鍵ペアの情報を登録 - push.setVapidDetails(config.url, - meta.swPublicKey, - meta.swPrivateKey); - } - }); -}, 3000); - export default async function(userId: string, type: string, body?: any) { - if (!meta.enableServiceWorker) return; + const meta = await fetchMeta(); + + if (!meta.enableServiceWorker || meta.swPublicKey == null || meta.swPrivateKey == null) 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 459f538e9..9957edd3d 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 c1d14b277..c52af48b5 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 f64affea9..e722552e1 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 584bece6b..9e6e849f5 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 6bd857120..4f1d1b9cd 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,