From 0e4a111f81cceed275d9bec2695f6e401fb654d8 Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 12 Nov 2021 02:02:25 +0900 Subject: refactoring Resolve #7779 --- .../backend/src/server/api/endpoints/i/2fa/done.ts | 41 +++ .../src/server/api/endpoints/i/2fa/key-done.ts | 150 +++++++++++ .../server/api/endpoints/i/2fa/password-less.ts | 21 ++ .../src/server/api/endpoints/i/2fa/register-key.ts | 59 +++++ .../src/server/api/endpoints/i/2fa/register.ts | 54 ++++ .../src/server/api/endpoints/i/2fa/remove-key.ts | 45 ++++ .../src/server/api/endpoints/i/2fa/unregister.ts | 32 +++ .../backend/src/server/api/endpoints/i/apps.ts | 43 +++ .../src/server/api/endpoints/i/authorized-apps.ts | 44 +++ .../src/server/api/endpoints/i/change-password.ts | 39 +++ .../src/server/api/endpoints/i/delete-account.ts | 48 ++++ .../src/server/api/endpoints/i/export-blocking.ts | 16 ++ .../src/server/api/endpoints/i/export-following.ts | 16 ++ .../src/server/api/endpoints/i/export-mute.ts | 16 ++ .../src/server/api/endpoints/i/export-notes.ts | 16 ++ .../server/api/endpoints/i/export-user-lists.ts | 16 ++ .../src/server/api/endpoints/i/favorites.ts | 50 ++++ .../src/server/api/endpoints/i/gallery/likes.ts | 57 ++++ .../src/server/api/endpoints/i/gallery/posts.ts | 49 ++++ .../api/endpoints/i/get-word-muted-notes-count.ts | 33 +++ .../src/server/api/endpoints/i/import-blocking.ts | 60 +++++ .../src/server/api/endpoints/i/import-following.ts | 59 +++++ .../src/server/api/endpoints/i/import-muting.ts | 60 +++++ .../server/api/endpoints/i/import-user-lists.ts | 59 +++++ .../src/server/api/endpoints/i/notifications.ts | 138 ++++++++++ .../src/server/api/endpoints/i/page-likes.ts | 57 ++++ .../backend/src/server/api/endpoints/i/pages.ts | 49 ++++ packages/backend/src/server/api/endpoints/i/pin.ts | 59 +++++ .../api/endpoints/i/read-all-messaging-messages.ts | 37 +++ .../api/endpoints/i/read-all-unread-notes.ts | 25 ++ .../server/api/endpoints/i/read-announcement.ts | 60 +++++ .../src/server/api/endpoints/i/regenerate-token.ts | 44 +++ .../src/server/api/endpoints/i/registry/get-all.ts | 33 +++ .../server/api/endpoints/i/registry/get-detail.ts | 48 ++++ .../src/server/api/endpoints/i/registry/get.ts | 45 ++++ .../api/endpoints/i/registry/keys-with-type.ts | 41 +++ .../src/server/api/endpoints/i/registry/keys.ts | 28 ++ .../src/server/api/endpoints/i/registry/remove.ts | 45 ++++ .../src/server/api/endpoints/i/registry/scopes.ts | 29 ++ .../src/server/api/endpoints/i/registry/set.ts | 61 +++++ .../src/server/api/endpoints/i/revoke-token.ts | 31 +++ .../src/server/api/endpoints/i/signin-history.ts | 35 +++ .../backend/src/server/api/endpoints/i/unpin.ts | 45 ++++ .../src/server/api/endpoints/i/update-email.ts | 94 +++++++ .../backend/src/server/api/endpoints/i/update.ts | 294 +++++++++++++++++++++ .../server/api/endpoints/i/user-group-invites.ts | 61 +++++ 46 files changed, 2442 insertions(+) create mode 100644 packages/backend/src/server/api/endpoints/i/2fa/done.ts create mode 100644 packages/backend/src/server/api/endpoints/i/2fa/key-done.ts create mode 100644 packages/backend/src/server/api/endpoints/i/2fa/password-less.ts create mode 100644 packages/backend/src/server/api/endpoints/i/2fa/register-key.ts create mode 100644 packages/backend/src/server/api/endpoints/i/2fa/register.ts create mode 100644 packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts create mode 100644 packages/backend/src/server/api/endpoints/i/2fa/unregister.ts create mode 100644 packages/backend/src/server/api/endpoints/i/apps.ts create mode 100644 packages/backend/src/server/api/endpoints/i/authorized-apps.ts create mode 100644 packages/backend/src/server/api/endpoints/i/change-password.ts create mode 100644 packages/backend/src/server/api/endpoints/i/delete-account.ts create mode 100644 packages/backend/src/server/api/endpoints/i/export-blocking.ts create mode 100644 packages/backend/src/server/api/endpoints/i/export-following.ts create mode 100644 packages/backend/src/server/api/endpoints/i/export-mute.ts create mode 100644 packages/backend/src/server/api/endpoints/i/export-notes.ts create mode 100644 packages/backend/src/server/api/endpoints/i/export-user-lists.ts create mode 100644 packages/backend/src/server/api/endpoints/i/favorites.ts create mode 100644 packages/backend/src/server/api/endpoints/i/gallery/likes.ts create mode 100644 packages/backend/src/server/api/endpoints/i/gallery/posts.ts create mode 100644 packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts create mode 100644 packages/backend/src/server/api/endpoints/i/import-blocking.ts create mode 100644 packages/backend/src/server/api/endpoints/i/import-following.ts create mode 100644 packages/backend/src/server/api/endpoints/i/import-muting.ts create mode 100644 packages/backend/src/server/api/endpoints/i/import-user-lists.ts create mode 100644 packages/backend/src/server/api/endpoints/i/notifications.ts create mode 100644 packages/backend/src/server/api/endpoints/i/page-likes.ts create mode 100644 packages/backend/src/server/api/endpoints/i/pages.ts create mode 100644 packages/backend/src/server/api/endpoints/i/pin.ts create mode 100644 packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts create mode 100644 packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts create mode 100644 packages/backend/src/server/api/endpoints/i/read-announcement.ts create mode 100644 packages/backend/src/server/api/endpoints/i/regenerate-token.ts create mode 100644 packages/backend/src/server/api/endpoints/i/registry/get-all.ts create mode 100644 packages/backend/src/server/api/endpoints/i/registry/get-detail.ts create mode 100644 packages/backend/src/server/api/endpoints/i/registry/get.ts create mode 100644 packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts create mode 100644 packages/backend/src/server/api/endpoints/i/registry/keys.ts create mode 100644 packages/backend/src/server/api/endpoints/i/registry/remove.ts create mode 100644 packages/backend/src/server/api/endpoints/i/registry/scopes.ts create mode 100644 packages/backend/src/server/api/endpoints/i/registry/set.ts create mode 100644 packages/backend/src/server/api/endpoints/i/revoke-token.ts create mode 100644 packages/backend/src/server/api/endpoints/i/signin-history.ts create mode 100644 packages/backend/src/server/api/endpoints/i/unpin.ts create mode 100644 packages/backend/src/server/api/endpoints/i/update-email.ts create mode 100644 packages/backend/src/server/api/endpoints/i/update.ts create mode 100644 packages/backend/src/server/api/endpoints/i/user-group-invites.ts (limited to 'packages/backend/src/server/api/endpoints/i') diff --git a/packages/backend/src/server/api/endpoints/i/2fa/done.ts b/packages/backend/src/server/api/endpoints/i/2fa/done.ts new file mode 100644 index 0000000000..2bd2128cce --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/2fa/done.ts @@ -0,0 +1,41 @@ +import $ from 'cafy'; +import * as speakeasy from 'speakeasy'; +import define from '../../../define'; +import { UserProfiles } from '@/models/index'; + +export const meta = { + requireCredential: true as const, + + secure: true, + + params: { + token: { + validator: $.str + } + } +}; + +export default define(meta, async (ps, user) => { + const token = ps.token.replace(/\s/g, ''); + + const profile = await UserProfiles.findOneOrFail(user.id); + + if (profile.twoFactorTempSecret == null) { + throw new Error('二段階認証の設定が開始されていません'); + } + + const verified = (speakeasy as any).totp.verify({ + secret: profile.twoFactorTempSecret, + encoding: 'base32', + token: token + }); + + if (!verified) { + throw new Error('not verified'); + } + + await UserProfiles.update(user.id, { + twoFactorSecret: profile.twoFactorTempSecret, + twoFactorEnabled: true + }); +}); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts new file mode 100644 index 0000000000..b4d3af235a --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts @@ -0,0 +1,150 @@ +import $ from 'cafy'; +import * as bcrypt from 'bcryptjs'; +import { promisify } from 'util'; +import * as cbor from 'cbor'; +import define from '../../../define'; +import { + UserProfiles, + UserSecurityKeys, + AttestationChallenges, + Users +} from '@/models/index'; +import config from '@/config/index'; +import { procedures, hash } from '../../../2fa'; +import { publishMainStream } from '@/services/stream'; + +const cborDecodeFirst = promisify(cbor.decodeFirst) as any; + +export const meta = { + requireCredential: true as const, + + secure: true, + + params: { + clientDataJSON: { + validator: $.str + }, + attestationObject: { + validator: $.str + }, + password: { + validator: $.str + }, + challengeId: { + validator: $.str + }, + name: { + validator: $.str + } + } +}; + +const rpIdHashReal = hash(Buffer.from(config.hostname, 'utf-8')); + +export default define(meta, async (ps, user) => { + const profile = await UserProfiles.findOneOrFail(user.id); + + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); + + if (!same) { + throw new Error('incorrect password'); + } + + if (!profile.twoFactorEnabled) { + throw new Error('2fa not enabled'); + } + + const clientData = JSON.parse(ps.clientDataJSON); + + if (clientData.type != 'webauthn.create') { + throw new Error('not a creation attestation'); + } + if (clientData.origin != config.scheme + '://' + config.host) { + throw new Error('origin mismatch'); + } + + const clientDataJSONHash = hash(Buffer.from(ps.clientDataJSON, 'utf-8')); + + const attestation = await cborDecodeFirst(ps.attestationObject); + + const rpIdHash = attestation.authData.slice(0, 32); + if (!rpIdHashReal.equals(rpIdHash)) { + throw new Error('rpIdHash mismatch'); + } + + const flags = attestation.authData[32]; + + // tslint:disable-next-line:no-bitwise + if (!(flags & 1)) { + throw new Error('user not present'); + } + + const authData = Buffer.from(attestation.authData); + const credentialIdLength = authData.readUInt16BE(53); + const credentialId = authData.slice(55, 55 + credentialIdLength); + const publicKeyData = authData.slice(55 + credentialIdLength); + const publicKey: Map = await cborDecodeFirst(publicKeyData); + if (publicKey.get(3) != -7) { + throw new Error('alg mismatch'); + } + + if (!(procedures as any)[attestation.fmt]) { + throw new Error('unsupported fmt'); + } + + const verificationData = (procedures as any)[attestation.fmt].verify({ + attStmt: attestation.attStmt, + authenticatorData: authData, + clientDataHash: clientDataJSONHash, + credentialId, + publicKey, + rpIdHash + }); + if (!verificationData.valid) throw new Error('signature invalid'); + + const attestationChallenge = await AttestationChallenges.findOne({ + userId: user.id, + id: ps.challengeId, + registrationChallenge: true, + challenge: hash(clientData.challenge).toString('hex') + }); + + if (!attestationChallenge) { + throw new Error('non-existent challenge'); + } + + await AttestationChallenges.delete({ + userId: user.id, + id: ps.challengeId + }); + + // Expired challenge (> 5min old) + if ( + new Date().getTime() - attestationChallenge.createdAt.getTime() >= + 5 * 60 * 1000 + ) { + throw new Error('expired challenge'); + } + + const credentialIdString = credentialId.toString('hex'); + + await UserSecurityKeys.save({ + userId: user.id, + id: credentialIdString, + lastUsed: new Date(), + name: ps.name, + publicKey: verificationData.publicKey.toString('hex') + }); + + // Publish meUpdated event + publishMainStream(user.id, 'meUpdated', await Users.pack(user.id, user, { + detail: true, + includeSecrets: true + })); + + return { + id: credentialIdString, + name: ps.name + }; +}); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts new file mode 100644 index 0000000000..064828b638 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts @@ -0,0 +1,21 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { UserProfiles } from '@/models/index'; + +export const meta = { + requireCredential: true as const, + + secure: true, + + params: { + value: { + validator: $.boolean + } + } +}; + +export default define(meta, async (ps, user) => { + await UserProfiles.update(user.id, { + usePasswordLessLogin: ps.value + }); +}); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts new file mode 100644 index 0000000000..1b385a10ee --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts @@ -0,0 +1,59 @@ +import $ from 'cafy'; +import * as bcrypt from 'bcryptjs'; +import define from '../../../define'; +import { UserProfiles, AttestationChallenges } from '@/models/index'; +import { promisify } from 'util'; +import * as crypto from 'crypto'; +import { genId } from '@/misc/gen-id'; +import { hash } from '../../../2fa'; + +const randomBytes = promisify(crypto.randomBytes); + +export const meta = { + requireCredential: true as const, + + secure: true, + + params: { + password: { + validator: $.str + } + } +}; + +export default define(meta, async (ps, user) => { + const profile = await UserProfiles.findOneOrFail(user.id); + + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); + + if (!same) { + throw new Error('incorrect password'); + } + + if (!profile.twoFactorEnabled) { + throw new Error('2fa not enabled'); + } + + // 32 byte challenge + const entropy = await randomBytes(32); + const challenge = entropy.toString('base64') + .replace(/=/g, '') + .replace(/\+/g, '-') + .replace(/\//g, '_'); + + const challengeId = genId(); + + await AttestationChallenges.save({ + userId: user.id, + id: challengeId, + challenge: hash(Buffer.from(challenge, 'utf-8')).toString('hex'), + createdAt: new Date(), + registrationChallenge: true + }); + + return { + challengeId, + challenge + }; +}); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts new file mode 100644 index 0000000000..b03b98188a --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts @@ -0,0 +1,54 @@ +import $ from 'cafy'; +import * as bcrypt from 'bcryptjs'; +import * as speakeasy from 'speakeasy'; +import * as QRCode from 'qrcode'; +import config from '@/config/index'; +import define from '../../../define'; +import { UserProfiles } from '@/models/index'; + +export const meta = { + requireCredential: true as const, + + secure: true, + + params: { + password: { + validator: $.str + } + } +}; + +export default define(meta, async (ps, user) => { + const profile = await UserProfiles.findOneOrFail(user.id); + + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); + + if (!same) { + throw new Error('incorrect password'); + } + + // Generate user's secret key + const secret = speakeasy.generateSecret({ + length: 32 + }); + + await UserProfiles.update(user.id, { + twoFactorTempSecret: secret.base32 + }); + + // Get the data URL of the authenticator URL + const dataUrl = await QRCode.toDataURL(speakeasy.otpauthURL({ + secret: secret.base32, + encoding: 'base32', + label: user.username, + issuer: config.host + })); + + return { + qr: dataUrl, + secret: secret.base32, + label: user.username, + issuer: config.host + }; +}); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts new file mode 100644 index 0000000000..dea56301ab --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts @@ -0,0 +1,45 @@ +import $ from 'cafy'; +import * as bcrypt from 'bcryptjs'; +import define from '../../../define'; +import { UserProfiles, UserSecurityKeys, Users } from '@/models/index'; +import { publishMainStream } from '@/services/stream'; + +export const meta = { + requireCredential: true as const, + + secure: true, + + params: { + password: { + validator: $.str + }, + credentialId: { + validator: $.str + }, + } +}; + +export default define(meta, async (ps, user) => { + const profile = await UserProfiles.findOneOrFail(user.id); + + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); + + if (!same) { + throw new Error('incorrect password'); + } + + // Make sure we only delete the user's own creds + await UserSecurityKeys.delete({ + userId: user.id, + id: ps.credentialId + }); + + // Publish meUpdated event + publishMainStream(user.id, 'meUpdated', await Users.pack(user.id, user, { + detail: true, + includeSecrets: true + })); + + return {}; +}); diff --git a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts new file mode 100644 index 0000000000..af53033daa --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts @@ -0,0 +1,32 @@ +import $ from 'cafy'; +import * as bcrypt from 'bcryptjs'; +import define from '../../../define'; +import { UserProfiles } from '@/models/index'; + +export const meta = { + requireCredential: true as const, + + secure: true, + + params: { + password: { + validator: $.str + } + } +}; + +export default define(meta, async (ps, user) => { + const profile = await UserProfiles.findOneOrFail(user.id); + + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); + + if (!same) { + throw new Error('incorrect password'); + } + + await UserProfiles.update(user.id, { + twoFactorSecret: null, + twoFactorEnabled: false + }); +}); diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts new file mode 100644 index 0000000000..994528e5c9 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/apps.ts @@ -0,0 +1,43 @@ +import $ from 'cafy'; +import define from '../../define'; +import { AccessTokens } from '@/models/index'; + +export const meta = { + requireCredential: true as const, + + secure: true, + + params: { + sort: { + validator: $.optional.str.or([ + '+createdAt', + '-createdAt', + '+lastUsedAt', + '-lastUsedAt', + ]), + }, + } +}; + +export default define(meta, async (ps, user) => { + const query = AccessTokens.createQueryBuilder('token') + .where('token.userId = :userId', { userId: user.id }); + + switch (ps.sort) { + case '+createdAt': query.orderBy('token.createdAt', 'DESC'); break; + case '-createdAt': query.orderBy('token.createdAt', 'ASC'); break; + case '+lastUsedAt': query.orderBy('token.lastUsedAt', 'DESC'); break; + case '-lastUsedAt': query.orderBy('token.lastUsedAt', 'ASC'); break; + default: query.orderBy('token.id', 'ASC'); break; + } + + const tokens = await query.getMany(); + + return await Promise.all(tokens.map(token => ({ + id: token.id, + name: token.name, + createdAt: token.createdAt, + lastUsedAt: token.lastUsedAt, + permission: token.permission, + }))); +}); diff --git a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts new file mode 100644 index 0000000000..042fcd14e8 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts @@ -0,0 +1,44 @@ +import $ from 'cafy'; +import define from '../../define'; +import { AccessTokens, Apps } from '@/models/index'; + +export const meta = { + requireCredential: true as const, + + secure: true, + + params: { + limit: { + validator: $.optional.num.range(1, 100), + default: 10, + }, + + offset: { + validator: $.optional.num.min(0), + default: 0, + }, + + sort: { + validator: $.optional.str.or('desc|asc'), + default: 'desc', + } + } +}; + +export default define(meta, async (ps, user) => { + // Get tokens + const tokens = await AccessTokens.find({ + where: { + userId: user.id + }, + take: ps.limit!, + skip: ps.offset, + order: { + id: ps.sort == 'asc' ? 1 : -1 + } + }); + + return await Promise.all(tokens.map(token => Apps.pack(token.appId, user, { + detail: true + }))); +}); diff --git a/packages/backend/src/server/api/endpoints/i/change-password.ts b/packages/backend/src/server/api/endpoints/i/change-password.ts new file mode 100644 index 0000000000..7ea5f8c488 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/change-password.ts @@ -0,0 +1,39 @@ +import $ from 'cafy'; +import * as bcrypt from 'bcryptjs'; +import define from '../../define'; +import { UserProfiles } from '@/models/index'; + +export const meta = { + requireCredential: true as const, + + secure: true, + + params: { + currentPassword: { + validator: $.str + }, + + newPassword: { + validator: $.str + } + } +}; + +export default define(meta, async (ps, user) => { + const profile = await UserProfiles.findOneOrFail(user.id); + + // Compare password + const same = await bcrypt.compare(ps.currentPassword, profile.password!); + + if (!same) { + throw new Error('incorrect password'); + } + + // Generate hash of password + const salt = await bcrypt.genSalt(8); + const hash = await bcrypt.hash(ps.newPassword, salt); + + await UserProfiles.update(user.id, { + password: hash + }); +}); diff --git a/packages/backend/src/server/api/endpoints/i/delete-account.ts b/packages/backend/src/server/api/endpoints/i/delete-account.ts new file mode 100644 index 0000000000..10e5adf64a --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts @@ -0,0 +1,48 @@ +import $ from 'cafy'; +import * as bcrypt from 'bcryptjs'; +import define from '../../define'; +import { UserProfiles, Users } from '@/models/index'; +import { doPostSuspend } from '@/services/suspend-user'; +import { publishUserEvent } from '@/services/stream'; +import { createDeleteAccountJob } from '@/queue'; + +export const meta = { + requireCredential: true as const, + + secure: true, + + params: { + password: { + validator: $.str + }, + } +}; + +export default define(meta, async (ps, user) => { + const profile = await UserProfiles.findOneOrFail(user.id); + const userDetailed = await Users.findOneOrFail(user.id); + if (userDetailed.isDeleted) { + return; + } + + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); + + if (!same) { + throw new Error('incorrect password'); + } + + // 物理削除する前にDelete activityを送信する + await doPostSuspend(user).catch(e => {}); + + createDeleteAccountJob(user, { + soft: false + }); + + await Users.update(user.id, { + isDeleted: true, + }); + + // Terminate streaming + publishUserEvent(user.id, 'terminate', {}); +}); diff --git a/packages/backend/src/server/api/endpoints/i/export-blocking.ts b/packages/backend/src/server/api/endpoints/i/export-blocking.ts new file mode 100644 index 0000000000..e4797da0c1 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/export-blocking.ts @@ -0,0 +1,16 @@ +import define from '../../define'; +import { createExportBlockingJob } from '@/queue/index'; +import * as ms from 'ms'; + +export const meta = { + secure: true, + requireCredential: true as const, + limit: { + duration: ms('1hour'), + max: 1, + }, +}; + +export default define(meta, async (ps, user) => { + createExportBlockingJob(user); +}); diff --git a/packages/backend/src/server/api/endpoints/i/export-following.ts b/packages/backend/src/server/api/endpoints/i/export-following.ts new file mode 100644 index 0000000000..b0f154cda8 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/export-following.ts @@ -0,0 +1,16 @@ +import define from '../../define'; +import { createExportFollowingJob } from '@/queue/index'; +import * as ms from 'ms'; + +export const meta = { + secure: true, + requireCredential: true as const, + limit: { + duration: ms('1hour'), + max: 1, + }, +}; + +export default define(meta, async (ps, user) => { + createExportFollowingJob(user); +}); diff --git a/packages/backend/src/server/api/endpoints/i/export-mute.ts b/packages/backend/src/server/api/endpoints/i/export-mute.ts new file mode 100644 index 0000000000..46d547fa53 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/export-mute.ts @@ -0,0 +1,16 @@ +import define from '../../define'; +import { createExportMuteJob } from '@/queue/index'; +import * as ms from 'ms'; + +export const meta = { + secure: true, + requireCredential: true as const, + limit: { + duration: ms('1hour'), + max: 1, + }, +}; + +export default define(meta, async (ps, user) => { + createExportMuteJob(user); +}); diff --git a/packages/backend/src/server/api/endpoints/i/export-notes.ts b/packages/backend/src/server/api/endpoints/i/export-notes.ts new file mode 100644 index 0000000000..441bf16896 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/export-notes.ts @@ -0,0 +1,16 @@ +import define from '../../define'; +import { createExportNotesJob } from '@/queue/index'; +import * as ms from 'ms'; + +export const meta = { + secure: true, + requireCredential: true as const, + limit: { + duration: ms('1day'), + max: 1, + }, +}; + +export default define(meta, async (ps, user) => { + createExportNotesJob(user); +}); diff --git a/packages/backend/src/server/api/endpoints/i/export-user-lists.ts b/packages/backend/src/server/api/endpoints/i/export-user-lists.ts new file mode 100644 index 0000000000..24043a862a --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/export-user-lists.ts @@ -0,0 +1,16 @@ +import define from '../../define'; +import { createExportUserListsJob } from '@/queue/index'; +import * as ms from 'ms'; + +export const meta = { + secure: true, + requireCredential: true as const, + limit: { + duration: ms('1min'), + max: 1, + }, +}; + +export default define(meta, async (ps, user) => { + createExportUserListsJob(user); +}); diff --git a/packages/backend/src/server/api/endpoints/i/favorites.ts b/packages/backend/src/server/api/endpoints/i/favorites.ts new file mode 100644 index 0000000000..b79d68ae73 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/favorites.ts @@ -0,0 +1,50 @@ +import $ from 'cafy'; +import { ID } from '@/misc/cafy-id'; +import define from '../../define'; +import { NoteFavorites } from '@/models/index'; +import { makePaginationQuery } from '../../common/make-pagination-query'; + +export const meta = { + tags: ['account', 'notes', 'favorites'], + + requireCredential: true as const, + + kind: 'read:favorites', + + params: { + limit: { + validator: $.optional.num.range(1, 100), + default: 10 + }, + + sinceId: { + validator: $.optional.type(ID), + }, + + untilId: { + validator: $.optional.type(ID), + }, + }, + + res: { + type: 'array' as const, + optional: false as const, nullable: false as const, + items: { + type: 'object' as const, + optional: false as const, nullable: false as const, + ref: 'NoteFavorite', + } + }, +}; + +export default define(meta, async (ps, user) => { + const query = makePaginationQuery(NoteFavorites.createQueryBuilder('favorite'), ps.sinceId, ps.untilId) + .andWhere(`favorite.userId = :meId`, { meId: user.id }) + .leftJoinAndSelect('favorite.note', 'note'); + + const favorites = await query + .take(ps.limit!) + .getMany(); + + return await NoteFavorites.packMany(favorites, user); +}); diff --git a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts new file mode 100644 index 0000000000..7a2935a5ec --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts @@ -0,0 +1,57 @@ +import $ from 'cafy'; +import { ID } from '@/misc/cafy-id'; +import define from '../../../define'; +import { GalleryLikes } from '@/models/index'; +import { makePaginationQuery } from '../../../common/make-pagination-query'; + +export const meta = { + tags: ['account', 'gallery'], + + requireCredential: true as const, + + kind: 'read:gallery-likes', + + params: { + limit: { + validator: $.optional.num.range(1, 100), + default: 10 + }, + + sinceId: { + validator: $.optional.type(ID), + }, + + untilId: { + validator: $.optional.type(ID), + }, + }, + + res: { + type: 'object' as const, + optional: false as const, nullable: false as const, + properties: { + id: { + type: 'string' as const, + optional: false as const, nullable: false as const, + format: 'id' + }, + page: { + type: 'object' as const, + optional: false as const, nullable: false as const, + ref: 'GalleryPost' + } + } + } +}; + +export default define(meta, async (ps, user) => { + const query = makePaginationQuery(GalleryLikes.createQueryBuilder('like'), ps.sinceId, ps.untilId) + .andWhere(`like.userId = :meId`, { meId: user.id }) + .leftJoinAndSelect('like.post', 'post'); + + const likes = await query + .take(ps.limit!) + .getMany(); + + return await GalleryLikes.packMany(likes, user); +}); diff --git a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts new file mode 100644 index 0000000000..21bb8759fc --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts @@ -0,0 +1,49 @@ +import $ from 'cafy'; +import { ID } from '@/misc/cafy-id'; +import define from '../../../define'; +import { GalleryPosts } from '@/models/index'; +import { makePaginationQuery } from '../../../common/make-pagination-query'; + +export const meta = { + tags: ['account', 'gallery'], + + requireCredential: true as const, + + kind: 'read:gallery', + + params: { + limit: { + validator: $.optional.num.range(1, 100), + default: 10 + }, + + sinceId: { + validator: $.optional.type(ID), + }, + + untilId: { + validator: $.optional.type(ID), + }, + }, + + res: { + type: 'array' as const, + optional: false as const, nullable: false as const, + items: { + type: 'object' as const, + optional: false as const, nullable: false as const, + ref: 'GalleryPost' + } + } +}; + +export default define(meta, async (ps, user) => { + const query = makePaginationQuery(GalleryPosts.createQueryBuilder('post'), ps.sinceId, ps.untilId) + .andWhere(`post.userId = :meId`, { meId: user.id }); + + const posts = await query + .take(ps.limit!) + .getMany(); + + return await GalleryPosts.packMany(posts, user); +}); diff --git a/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts new file mode 100644 index 0000000000..6b9be98582 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts @@ -0,0 +1,33 @@ +import define from '../../define'; +import { MutedNotes } from '@/models/index'; + +export const meta = { + tags: ['account'], + + requireCredential: true as const, + + kind: 'read:account', + + params: { + }, + + res: { + type: 'object' as const, + optional: false as const, nullable: false as const, + properties: { + count: { + type: 'number' as const, + optional: false as const, nullable: false as const + } + } + } +}; + +export default define(meta, async (ps, user) => { + return { + count: await MutedNotes.count({ + userId: user.id, + reason: 'word' + }) + }; +}); diff --git a/packages/backend/src/server/api/endpoints/i/import-blocking.ts b/packages/backend/src/server/api/endpoints/i/import-blocking.ts new file mode 100644 index 0000000000..d44d0b6077 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts @@ -0,0 +1,60 @@ +import $ from 'cafy'; +import { ID } from '@/misc/cafy-id'; +import define from '../../define'; +import { createImportBlockingJob } from '@/queue/index'; +import * as ms from 'ms'; +import { ApiError } from '../../error'; +import { DriveFiles } from '@/models/index'; + +export const meta = { + secure: true, + requireCredential: true as const, + + limit: { + duration: ms('1hour'), + max: 1, + }, + + params: { + fileId: { + validator: $.type(ID), + } + }, + + errors: { + noSuchFile: { + message: 'No such file.', + code: 'NO_SUCH_FILE', + id: 'ebb53e5f-6574-9c0c-0b92-7ca6def56d7e' + }, + + unexpectedFileType: { + message: 'We need csv file.', + code: 'UNEXPECTED_FILE_TYPE', + id: 'b6fab7d6-d945-d67c-dfdb-32da1cd12cfe' + }, + + tooBigFile: { + message: 'That file is too big.', + code: 'TOO_BIG_FILE', + id: 'b7fbf0b1-aeef-3b21-29ef-fadd4cb72ccf' + }, + + emptyFile: { + message: 'That file is empty.', + code: 'EMPTY_FILE', + id: '6f3a4dcc-f060-a707-4950-806fbdbe60d6' + }, + } +}; + +export default define(meta, async (ps, user) => { + const file = await DriveFiles.findOne(ps.fileId); + + if (file == null) throw new ApiError(meta.errors.noSuchFile); + //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); + if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile); + if (file.size === 0) throw new ApiError(meta.errors.emptyFile); + + createImportBlockingJob(user, file.id); +}); diff --git a/packages/backend/src/server/api/endpoints/i/import-following.ts b/packages/backend/src/server/api/endpoints/i/import-following.ts new file mode 100644 index 0000000000..b3de397661 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/import-following.ts @@ -0,0 +1,59 @@ +import $ from 'cafy'; +import { ID } from '@/misc/cafy-id'; +import define from '../../define'; +import { createImportFollowingJob } from '@/queue/index'; +import * as ms from 'ms'; +import { ApiError } from '../../error'; +import { DriveFiles } from '@/models/index'; + +export const meta = { + secure: true, + requireCredential: true as const, + limit: { + duration: ms('1hour'), + max: 1, + }, + + params: { + fileId: { + validator: $.type(ID), + } + }, + + errors: { + noSuchFile: { + message: 'No such file.', + code: 'NO_SUCH_FILE', + id: 'b98644cf-a5ac-4277-a502-0b8054a709a3' + }, + + unexpectedFileType: { + message: 'We need csv file.', + code: 'UNEXPECTED_FILE_TYPE', + id: '660f3599-bce0-4f95-9dde-311fd841c183' + }, + + tooBigFile: { + message: 'That file is too big.', + code: 'TOO_BIG_FILE', + id: 'dee9d4ed-ad07-43ed-8b34-b2856398bc60' + }, + + emptyFile: { + message: 'That file is empty.', + code: 'EMPTY_FILE', + id: '31a1b42c-06f7-42ae-8a38-a661c5c9f691' + }, + } +}; + +export default define(meta, async (ps, user) => { + const file = await DriveFiles.findOne(ps.fileId); + + if (file == null) throw new ApiError(meta.errors.noSuchFile); + //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); + if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile); + if (file.size === 0) throw new ApiError(meta.errors.emptyFile); + + createImportFollowingJob(user, file.id); +}); diff --git a/packages/backend/src/server/api/endpoints/i/import-muting.ts b/packages/backend/src/server/api/endpoints/i/import-muting.ts new file mode 100644 index 0000000000..c17434c587 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts @@ -0,0 +1,60 @@ +import $ from 'cafy'; +import { ID } from '@/misc/cafy-id'; +import define from '../../define'; +import { createImportMutingJob } from '@/queue/index'; +import * as ms from 'ms'; +import { ApiError } from '../../error'; +import { DriveFiles } from '@/models/index'; + +export const meta = { + secure: true, + requireCredential: true as const, + + limit: { + duration: ms('1hour'), + max: 1, + }, + + params: { + fileId: { + validator: $.type(ID), + } + }, + + errors: { + noSuchFile: { + message: 'No such file.', + code: 'NO_SUCH_FILE', + id: 'e674141e-bd2a-ba85-e616-aefb187c9c2a' + }, + + unexpectedFileType: { + message: 'We need csv file.', + code: 'UNEXPECTED_FILE_TYPE', + id: '568c6e42-c86c-ba09-c004-517f83f9f1a8' + }, + + tooBigFile: { + message: 'That file is too big.', + code: 'TOO_BIG_FILE', + id: '9b4ada6d-d7f7-0472-0713-4f558bd1ec9c' + }, + + emptyFile: { + message: 'That file is empty.', + code: 'EMPTY_FILE', + id: 'd2f12af1-e7b4-feac-86a3-519548f2728e' + }, + } +}; + +export default define(meta, async (ps, user) => { + const file = await DriveFiles.findOne(ps.fileId); + + if (file == null) throw new ApiError(meta.errors.noSuchFile); + //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); + if (file.size > 50000) throw new ApiError(meta.errors.tooBigFile); + if (file.size === 0) throw new ApiError(meta.errors.emptyFile); + + createImportMutingJob(user, file.id); +}); diff --git a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts new file mode 100644 index 0000000000..9069a019a9 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts @@ -0,0 +1,59 @@ +import $ from 'cafy'; +import { ID } from '@/misc/cafy-id'; +import define from '../../define'; +import { createImportUserListsJob } from '@/queue/index'; +import * as ms from 'ms'; +import { ApiError } from '../../error'; +import { DriveFiles } from '@/models/index'; + +export const meta = { + secure: true, + requireCredential: true as const, + limit: { + duration: ms('1hour'), + max: 1, + }, + + params: { + fileId: { + validator: $.type(ID), + } + }, + + errors: { + noSuchFile: { + message: 'No such file.', + code: 'NO_SUCH_FILE', + id: 'ea9cc34f-c415-4bc6-a6fe-28ac40357049' + }, + + unexpectedFileType: { + message: 'We need csv file.', + code: 'UNEXPECTED_FILE_TYPE', + id: 'a3c9edda-dd9b-4596-be6a-150ef813745c' + }, + + tooBigFile: { + message: 'That file is too big.', + code: 'TOO_BIG_FILE', + id: 'ae6e7a22-971b-4b52-b2be-fc0b9b121fe9' + }, + + emptyFile: { + message: 'That file is empty.', + code: 'EMPTY_FILE', + id: '99efe367-ce6e-4d44-93f8-5fae7b040356' + }, + } +}; + +export default define(meta, async (ps, user) => { + const file = await DriveFiles.findOne(ps.fileId); + + if (file == null) throw new ApiError(meta.errors.noSuchFile); + //if (!file.type.endsWith('/csv')) throw new ApiError(meta.errors.unexpectedFileType); + if (file.size > 30000) throw new ApiError(meta.errors.tooBigFile); + if (file.size === 0) throw new ApiError(meta.errors.emptyFile); + + createImportUserListsJob(user, file.id); +}); diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts new file mode 100644 index 0000000000..56668d03b7 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/notifications.ts @@ -0,0 +1,138 @@ +import $ from 'cafy'; +import { ID } from '@/misc/cafy-id'; +import { readNotification } from '../../common/read-notification'; +import define from '../../define'; +import { makePaginationQuery } from '../../common/make-pagination-query'; +import { Notifications, Followings, Mutings, Users } from '@/models/index'; +import { notificationTypes } from '@/types'; +import read from '@/services/note/read'; +import { Brackets } from 'typeorm'; + +export const meta = { + tags: ['account', 'notifications'], + + requireCredential: true as const, + + kind: 'read:notifications', + + params: { + limit: { + validator: $.optional.num.range(1, 100), + default: 10 + }, + + sinceId: { + validator: $.optional.type(ID), + }, + + untilId: { + validator: $.optional.type(ID), + }, + + following: { + validator: $.optional.bool, + default: false + }, + + unreadOnly: { + validator: $.optional.bool, + default: false + }, + + markAsRead: { + validator: $.optional.bool, + default: true + }, + + includeTypes: { + validator: $.optional.arr($.str.or(notificationTypes as unknown as string[])), + }, + + excludeTypes: { + validator: $.optional.arr($.str.or(notificationTypes as unknown as string[])), + } + }, + + res: { + type: 'array' as const, + optional: false as const, nullable: false as const, + items: { + type: 'object' as const, + optional: false as const, nullable: false as const, + ref: 'Notification', + } + }, +}; + +export default define(meta, async (ps, user) => { + // includeTypes が空の場合はクエリしない + if (ps.includeTypes && ps.includeTypes.length === 0) { + return []; + } + // excludeTypes に全指定されている場合はクエリしない + if (notificationTypes.every(type => ps.excludeTypes?.includes(type))) { + return []; + } + const followingQuery = Followings.createQueryBuilder('following') + .select('following.followeeId') + .where('following.followerId = :followerId', { followerId: user.id }); + + const mutingQuery = Mutings.createQueryBuilder('muting') + .select('muting.muteeId') + .where('muting.muterId = :muterId', { muterId: user.id }); + + const suspendedQuery = Users.createQueryBuilder('users') + .select('users.id') + .where('users.isSuspended = TRUE'); + + const query = makePaginationQuery(Notifications.createQueryBuilder('notification'), ps.sinceId, ps.untilId) + .andWhere(`notification.notifieeId = :meId`, { meId: user.id }) + .leftJoinAndSelect('notification.notifier', 'notifier') + .leftJoinAndSelect('notification.note', 'note') + .leftJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('note.reply', 'reply') + .leftJoinAndSelect('note.renote', 'renote') + .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('renote.user', 'renoteUser'); + + query.andWhere(new Brackets(qb => { qb + .where(`notification.notifierId NOT IN (${ mutingQuery.getQuery() })`) + .orWhere('notification.notifierId IS NULL'); + })); + query.setParameters(mutingQuery.getParameters()); + + query.andWhere(new Brackets(qb => { qb + .where(`notification.notifierId NOT IN (${ suspendedQuery.getQuery() })`) + .orWhere('notification.notifierId IS NULL'); + })); + + if (ps.following) { + query.andWhere(`((notification.notifierId IN (${ followingQuery.getQuery() })) OR (notification.notifierId = :meId))`, { meId: user.id }); + query.setParameters(followingQuery.getParameters()); + } + + if (ps.includeTypes && ps.includeTypes.length > 0) { + query.andWhere(`notification.type IN (:...includeTypes)`, { includeTypes: ps.includeTypes }); + } else if (ps.excludeTypes && ps.excludeTypes.length > 0) { + query.andWhere(`notification.type NOT IN (:...excludeTypes)`, { excludeTypes: ps.excludeTypes }); + } + + if (ps.unreadOnly) { + query.andWhere(`notification.isRead = false`); + } + + const notifications = await query.take(ps.limit!).getMany(); + + // Mark all as read + if (notifications.length > 0 && ps.markAsRead) { + readNotification(user.id, notifications.map(x => x.id)); + } + + const notes = notifications.filter(notification => ['mention', 'reply', 'quote'].includes(notification.type)).map(notification => notification.note!); + + if (notes.length > 0) { + read(user.id, notes); + } + + return await Notifications.packMany(notifications, user.id); +}); diff --git a/packages/backend/src/server/api/endpoints/i/page-likes.ts b/packages/backend/src/server/api/endpoints/i/page-likes.ts new file mode 100644 index 0000000000..fa2bc31730 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/page-likes.ts @@ -0,0 +1,57 @@ +import $ from 'cafy'; +import { ID } from '@/misc/cafy-id'; +import define from '../../define'; +import { PageLikes } from '@/models/index'; +import { makePaginationQuery } from '../../common/make-pagination-query'; + +export const meta = { + tags: ['account', 'pages'], + + requireCredential: true as const, + + kind: 'read:page-likes', + + params: { + limit: { + validator: $.optional.num.range(1, 100), + default: 10 + }, + + sinceId: { + validator: $.optional.type(ID), + }, + + untilId: { + validator: $.optional.type(ID), + }, + }, + + res: { + type: 'object' as const, + optional: false as const, nullable: false as const, + properties: { + id: { + type: 'string' as const, + optional: false as const, nullable: false as const, + format: 'id' + }, + page: { + type: 'object' as const, + optional: false as const, nullable: false as const, + ref: 'Page' + } + } + } +}; + +export default define(meta, async (ps, user) => { + const query = makePaginationQuery(PageLikes.createQueryBuilder('like'), ps.sinceId, ps.untilId) + .andWhere(`like.userId = :meId`, { meId: user.id }) + .leftJoinAndSelect('like.page', 'page'); + + const likes = await query + .take(ps.limit!) + .getMany(); + + return await PageLikes.packMany(likes, user); +}); diff --git a/packages/backend/src/server/api/endpoints/i/pages.ts b/packages/backend/src/server/api/endpoints/i/pages.ts new file mode 100644 index 0000000000..ee87fffa2d --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/pages.ts @@ -0,0 +1,49 @@ +import $ from 'cafy'; +import { ID } from '@/misc/cafy-id'; +import define from '../../define'; +import { Pages } from '@/models/index'; +import { makePaginationQuery } from '../../common/make-pagination-query'; + +export const meta = { + tags: ['account', 'pages'], + + requireCredential: true as const, + + kind: 'read:pages', + + params: { + limit: { + validator: $.optional.num.range(1, 100), + default: 10 + }, + + sinceId: { + validator: $.optional.type(ID), + }, + + untilId: { + validator: $.optional.type(ID), + }, + }, + + res: { + type: 'array' as const, + optional: false as const, nullable: false as const, + items: { + type: 'object' as const, + optional: false as const, nullable: false as const, + ref: 'Page' + } + } +}; + +export default define(meta, async (ps, user) => { + const query = makePaginationQuery(Pages.createQueryBuilder('page'), ps.sinceId, ps.untilId) + .andWhere(`page.userId = :meId`, { meId: user.id }); + + const pages = await query + .take(ps.limit!) + .getMany(); + + return await Pages.packMany(pages); +}); diff --git a/packages/backend/src/server/api/endpoints/i/pin.ts b/packages/backend/src/server/api/endpoints/i/pin.ts new file mode 100644 index 0000000000..de94220ba9 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/pin.ts @@ -0,0 +1,59 @@ +import $ from 'cafy'; +import { ID } from '@/misc/cafy-id'; +import { addPinned } from '@/services/i/pin'; +import define from '../../define'; +import { ApiError } from '../../error'; +import { Users } from '@/models/index'; + +export const meta = { + tags: ['account', 'notes'], + + requireCredential: true as const, + + kind: 'write:account', + + params: { + noteId: { + validator: $.type(ID), + } + }, + + errors: { + noSuchNote: { + message: 'No such note.', + code: 'NO_SUCH_NOTE', + id: '56734f8b-3928-431e-bf80-6ff87df40cb3' + }, + + pinLimitExceeded: { + message: 'You can not pin notes any more.', + code: 'PIN_LIMIT_EXCEEDED', + id: '72dab508-c64d-498f-8740-a8eec1ba385a' + }, + + alreadyPinned: { + message: 'That note has already been pinned.', + code: 'ALREADY_PINNED', + id: '8b18c2b7-68fe-4edb-9892-c0cbaeb6c913' + }, + }, + + res: { + type: 'object' as const, + optional: false as const, nullable: false as const, + ref: 'User' + } +}; + +export default define(meta, async (ps, user) => { + await addPinned(user, ps.noteId).catch(e => { + if (e.id === '70c4e51f-5bea-449c-a030-53bee3cce202') throw new ApiError(meta.errors.noSuchNote); + if (e.id === '15a018eb-58e5-4da1-93be-330fcc5e4e1a') throw new ApiError(meta.errors.pinLimitExceeded); + if (e.id === '23f0cf4e-59a3-4276-a91d-61a5891c1514') throw new ApiError(meta.errors.alreadyPinned); + throw e; + }); + + return await Users.pack(user.id, user, { + detail: true + }); +}); diff --git a/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts b/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts new file mode 100644 index 0000000000..9aca7611c9 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts @@ -0,0 +1,37 @@ +import { publishMainStream } from '@/services/stream'; +import define from '../../define'; +import { MessagingMessages, UserGroupJoinings } from '@/models/index'; + +export const meta = { + tags: ['account', 'messaging'], + + requireCredential: true as const, + + kind: 'write:account', + + params: { + } +}; + +export default define(meta, async (ps, user) => { + // Update documents + await MessagingMessages.update({ + recipientId: user.id, + isRead: false + }, { + isRead: true + }); + + const joinings = await UserGroupJoinings.find({ userId: user.id }); + + await Promise.all(joinings.map(j => MessagingMessages.createQueryBuilder().update() + .set({ + reads: (() => `array_append("reads", '${user.id}')`) as any + }) + .where(`groupId = :groupId`, { groupId: j.userGroupId }) + .andWhere('userId != :userId', { userId: user.id }) + .andWhere('NOT (:userId = ANY(reads))', { userId: user.id }) + .execute())); + + publishMainStream(user.id, 'readAllMessagingMessages'); +}); diff --git a/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts b/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts new file mode 100644 index 0000000000..2a7102a590 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts @@ -0,0 +1,25 @@ +import { publishMainStream } from '@/services/stream'; +import define from '../../define'; +import { NoteUnreads } from '@/models/index'; + +export const meta = { + tags: ['account'], + + requireCredential: true as const, + + kind: 'write:account', + + params: { + } +}; + +export default define(meta, async (ps, user) => { + // Remove documents + await NoteUnreads.delete({ + userId: user.id + }); + + // 全て既読になったイベントを発行 + publishMainStream(user.id, 'readAllUnreadMentions'); + publishMainStream(user.id, 'readAllUnreadSpecifiedNotes'); +}); diff --git a/packages/backend/src/server/api/endpoints/i/read-announcement.ts b/packages/backend/src/server/api/endpoints/i/read-announcement.ts new file mode 100644 index 0000000000..2f5036f953 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/read-announcement.ts @@ -0,0 +1,60 @@ +import $ from 'cafy'; +import { ID } from '@/misc/cafy-id'; +import define from '../../define'; +import { ApiError } from '../../error'; +import { genId } from '@/misc/gen-id'; +import { AnnouncementReads, Announcements, Users } from '@/models/index'; +import { publishMainStream } from '@/services/stream'; + +export const meta = { + tags: ['account'], + + requireCredential: true as const, + + kind: 'write:account', + + params: { + announcementId: { + validator: $.type(ID), + }, + }, + + errors: { + noSuchAnnouncement: { + message: 'No such announcement.', + code: 'NO_SUCH_ANNOUNCEMENT', + id: '184663db-df88-4bc2-8b52-fb85f0681939' + }, + } +}; + +export default define(meta, async (ps, user) => { + // Check if announcement exists + const announcement = await Announcements.findOne(ps.announcementId); + + if (announcement == null) { + throw new ApiError(meta.errors.noSuchAnnouncement); + } + + // Check if already read + const read = await AnnouncementReads.findOne({ + announcementId: ps.announcementId, + userId: user.id + }); + + if (read != null) { + return; + } + + // Create read + await AnnouncementReads.insert({ + id: genId(), + createdAt: new Date(), + announcementId: ps.announcementId, + userId: user.id, + }); + + if (!await Users.getHasUnreadAnnouncement(user.id)) { + publishMainStream(user.id, 'readAllAnnouncements'); + } +}); diff --git a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts new file mode 100644 index 0000000000..1cce2d37be --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts @@ -0,0 +1,44 @@ +import $ from 'cafy'; +import * as bcrypt from 'bcryptjs'; +import { publishMainStream, publishUserEvent } from '@/services/stream'; +import generateUserToken from '../../common/generate-native-user-token'; +import define from '../../define'; +import { Users, UserProfiles } from '@/models/index'; + +export const meta = { + requireCredential: true as const, + + secure: true, + + params: { + password: { + validator: $.str + } + } +}; + +export default define(meta, async (ps, user) => { + const profile = await UserProfiles.findOneOrFail(user.id); + + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); + + if (!same) { + throw new Error('incorrect password'); + } + + // Generate secret + const secret = generateUserToken(); + + await Users.update(user.id, { + token: secret + }); + + // Publish event + publishMainStream(user.id, 'myTokenRegenerated'); + + // Terminate streaming + setTimeout(() => { + publishUserEvent(user.id, 'terminate', {}); + }, 5000); +}); diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-all.ts b/packages/backend/src/server/api/endpoints/i/registry/get-all.ts new file mode 100644 index 0000000000..c8eaf83a25 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/registry/get-all.ts @@ -0,0 +1,33 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { RegistryItems } from '@/models/index'; + +export const meta = { + requireCredential: true as const, + + secure: true, + + params: { + scope: { + validator: $.optional.arr($.str.match(/^[a-zA-Z0-9_]+$/)), + default: [], + }, + } +}; + +export default define(meta, async (ps, user) => { + const query = RegistryItems.createQueryBuilder('item') + .where('item.domain IS NULL') + .andWhere('item.userId = :userId', { userId: user.id }) + .andWhere('item.scope = :scope', { scope: ps.scope }); + + const items = await query.getMany(); + + const res = {} as Record; + + for (const item of items) { + res[item.key] = item.value; + } + + return res; +}); diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts new file mode 100644 index 0000000000..992800c44c --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts @@ -0,0 +1,48 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { RegistryItems } from '@/models/index'; +import { ApiError } from '../../../error'; + +export const meta = { + requireCredential: true as const, + + secure: true, + + params: { + key: { + validator: $.str + }, + + scope: { + validator: $.optional.arr($.str.match(/^[a-zA-Z0-9_]+$/)), + default: [], + }, + }, + + errors: { + noSuchKey: { + message: 'No such key.', + code: 'NO_SUCH_KEY', + id: '97a1e8e7-c0f7-47d2-957a-92e61256e01a' + }, + }, +}; + +export default define(meta, async (ps, user) => { + const query = RegistryItems.createQueryBuilder('item') + .where('item.domain IS NULL') + .andWhere('item.userId = :userId', { userId: user.id }) + .andWhere('item.key = :key', { key: ps.key }) + .andWhere('item.scope = :scope', { scope: ps.scope }); + + const item = await query.getOne(); + + if (item == null) { + throw new ApiError(meta.errors.noSuchKey); + } + + return { + updatedAt: item.updatedAt, + value: item.value, + }; +}); diff --git a/packages/backend/src/server/api/endpoints/i/registry/get.ts b/packages/backend/src/server/api/endpoints/i/registry/get.ts new file mode 100644 index 0000000000..569c3a9280 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/registry/get.ts @@ -0,0 +1,45 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { RegistryItems } from '@/models/index'; +import { ApiError } from '../../../error'; + +export const meta = { + requireCredential: true as const, + + secure: true, + + params: { + key: { + validator: $.str + }, + + scope: { + validator: $.optional.arr($.str.match(/^[a-zA-Z0-9_]+$/)), + default: [], + }, + }, + + errors: { + noSuchKey: { + message: 'No such key.', + code: 'NO_SUCH_KEY', + id: 'ac3ed68a-62f0-422b-a7bc-d5e09e8f6a6a' + }, + }, +}; + +export default define(meta, async (ps, user) => { + const query = RegistryItems.createQueryBuilder('item') + .where('item.domain IS NULL') + .andWhere('item.userId = :userId', { userId: user.id }) + .andWhere('item.key = :key', { key: ps.key }) + .andWhere('item.scope = :scope', { scope: ps.scope }); + + const item = await query.getOne(); + + if (item == null) { + throw new ApiError(meta.errors.noSuchKey); + } + + return item.value; +}); diff --git a/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts b/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts new file mode 100644 index 0000000000..16a4fee374 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts @@ -0,0 +1,41 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { RegistryItems } from '@/models/index'; + +export const meta = { + requireCredential: true as const, + + secure: true, + + params: { + scope: { + validator: $.optional.arr($.str.match(/^[a-zA-Z0-9_]+$/)), + default: [], + }, + } +}; + +export default define(meta, async (ps, user) => { + const query = RegistryItems.createQueryBuilder('item') + .where('item.domain IS NULL') + .andWhere('item.userId = :userId', { userId: user.id }) + .andWhere('item.scope = :scope', { scope: ps.scope }); + + const items = await query.getMany(); + + const res = {} as Record; + + for (const item of items) { + const type = typeof item.value; + res[item.key] = + item.value === null ? 'null' : + Array.isArray(item.value) ? 'array' : + type === 'number' ? 'number' : + type === 'string' ? 'string' : + type === 'boolean' ? 'boolean' : + type === 'object' ? 'object' : + null as never; + } + + return res; +}); diff --git a/packages/backend/src/server/api/endpoints/i/registry/keys.ts b/packages/backend/src/server/api/endpoints/i/registry/keys.ts new file mode 100644 index 0000000000..3a8aeaa195 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/registry/keys.ts @@ -0,0 +1,28 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { RegistryItems } from '@/models/index'; + +export const meta = { + requireCredential: true as const, + + secure: true, + + params: { + scope: { + validator: $.optional.arr($.str.match(/^[a-zA-Z0-9_]+$/)), + default: [], + }, + } +}; + +export default define(meta, async (ps, user) => { + const query = RegistryItems.createQueryBuilder('item') + .select('item.key') + .where('item.domain IS NULL') + .andWhere('item.userId = :userId', { userId: user.id }) + .andWhere('item.scope = :scope', { scope: ps.scope }); + + const items = await query.getMany(); + + return items.map(x => x.key); +}); diff --git a/packages/backend/src/server/api/endpoints/i/registry/remove.ts b/packages/backend/src/server/api/endpoints/i/registry/remove.ts new file mode 100644 index 0000000000..07bc23d4a6 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/registry/remove.ts @@ -0,0 +1,45 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { RegistryItems } from '@/models/index'; +import { ApiError } from '../../../error'; + +export const meta = { + requireCredential: true as const, + + secure: true, + + params: { + key: { + validator: $.str + }, + + scope: { + validator: $.optional.arr($.str.match(/^[a-zA-Z0-9_]+$/)), + default: [], + }, + }, + + errors: { + noSuchKey: { + message: 'No such key.', + code: 'NO_SUCH_KEY', + id: '1fac4e8a-a6cd-4e39-a4a5-3a7e11f1b019' + }, + }, +}; + +export default define(meta, async (ps, user) => { + const query = RegistryItems.createQueryBuilder('item') + .where('item.domain IS NULL') + .andWhere('item.userId = :userId', { userId: user.id }) + .andWhere('item.key = :key', { key: ps.key }) + .andWhere('item.scope = :scope', { scope: ps.scope }); + + const item = await query.getOne(); + + if (item == null) { + throw new ApiError(meta.errors.noSuchKey); + } + + await RegistryItems.remove(item); +}); diff --git a/packages/backend/src/server/api/endpoints/i/registry/scopes.ts b/packages/backend/src/server/api/endpoints/i/registry/scopes.ts new file mode 100644 index 0000000000..ecbdb05a8e --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/registry/scopes.ts @@ -0,0 +1,29 @@ +import define from '../../../define'; +import { RegistryItems } from '@/models/index'; + +export const meta = { + requireCredential: true as const, + + secure: true, + + params: { + } +}; + +export default define(meta, async (ps, user) => { + const query = RegistryItems.createQueryBuilder('item') + .select('item.scope') + .where('item.domain IS NULL') + .andWhere('item.userId = :userId', { userId: user.id }); + + const items = await query.getMany(); + + const res = [] as string[][]; + + for (const item of items) { + if (res.some(scope => scope.join('.') === item.scope.join('.'))) continue; + res.push(item.scope); + } + + return res; +}); diff --git a/packages/backend/src/server/api/endpoints/i/registry/set.ts b/packages/backend/src/server/api/endpoints/i/registry/set.ts new file mode 100644 index 0000000000..f129ee1b70 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/registry/set.ts @@ -0,0 +1,61 @@ +import $ from 'cafy'; +import { publishMainStream } from '@/services/stream'; +import define from '../../../define'; +import { RegistryItems } from '@/models/index'; +import { genId } from '@/misc/gen-id'; + +export const meta = { + requireCredential: true as const, + + secure: true, + + params: { + key: { + validator: $.str.min(1) + }, + + value: { + validator: $.nullable.any + }, + + scope: { + validator: $.optional.arr($.str.match(/^[a-zA-Z0-9_]+$/)), + default: [], + }, + } +}; + +export default define(meta, async (ps, user) => { + const query = RegistryItems.createQueryBuilder('item') + .where('item.domain IS NULL') + .andWhere('item.userId = :userId', { userId: user.id }) + .andWhere('item.key = :key', { key: ps.key }) + .andWhere('item.scope = :scope', { scope: ps.scope }); + + const existingItem = await query.getOne(); + + if (existingItem) { + await RegistryItems.update(existingItem.id, { + updatedAt: new Date(), + value: ps.value + }); + } else { + await RegistryItems.insert({ + id: genId(), + createdAt: new Date(), + updatedAt: new Date(), + userId: user.id, + domain: null, + scope: ps.scope, + key: ps.key, + value: ps.value + }); + } + + // TODO: サードパーティアプリが傍受出来てしまうのでどうにかする + publishMainStream(user.id, 'registryUpdated', { + scope: ps.scope, + key: ps.key, + value: ps.value + }); +}); diff --git a/packages/backend/src/server/api/endpoints/i/revoke-token.ts b/packages/backend/src/server/api/endpoints/i/revoke-token.ts new file mode 100644 index 0000000000..bed868def4 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/revoke-token.ts @@ -0,0 +1,31 @@ +import $ from 'cafy'; +import define from '../../define'; +import { AccessTokens } from '@/models/index'; +import { ID } from '@/misc/cafy-id'; +import { publishUserEvent } from '@/services/stream'; + +export const meta = { + requireCredential: true as const, + + secure: true, + + params: { + tokenId: { + validator: $.type(ID) + } + } +}; + +export default define(meta, async (ps, user) => { + const token = await AccessTokens.findOne(ps.tokenId); + + if (token) { + await AccessTokens.delete({ + id: ps.tokenId, + userId: user.id, + }); + + // Terminate streaming + publishUserEvent(user.id, 'terminate'); + } +}); diff --git a/packages/backend/src/server/api/endpoints/i/signin-history.ts b/packages/backend/src/server/api/endpoints/i/signin-history.ts new file mode 100644 index 0000000000..a2c10148c6 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/signin-history.ts @@ -0,0 +1,35 @@ +import $ from 'cafy'; +import { ID } from '@/misc/cafy-id'; +import define from '../../define'; +import { Signins } from '@/models/index'; +import { makePaginationQuery } from '../../common/make-pagination-query'; + +export const meta = { + requireCredential: true as const, + + secure: true, + + params: { + limit: { + validator: $.optional.num.range(1, 100), + default: 10 + }, + + sinceId: { + validator: $.optional.type(ID), + }, + + untilId: { + validator: $.optional.type(ID), + } + } +}; + +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(); + + return await Promise.all(history.map(record => Signins.pack(record))); +}); diff --git a/packages/backend/src/server/api/endpoints/i/unpin.ts b/packages/backend/src/server/api/endpoints/i/unpin.ts new file mode 100644 index 0000000000..dc79e255ab --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/unpin.ts @@ -0,0 +1,45 @@ +import $ from 'cafy'; +import { ID } from '@/misc/cafy-id'; +import { removePinned } from '@/services/i/pin'; +import define from '../../define'; +import { ApiError } from '../../error'; +import { Users } from '@/models/index'; + +export const meta = { + tags: ['account', 'notes'], + + requireCredential: true as const, + + kind: 'write:account', + + params: { + noteId: { + validator: $.type(ID), + } + }, + + errors: { + noSuchNote: { + message: 'No such note.', + code: 'NO_SUCH_NOTE', + id: '454170ce-9d63-4a43-9da1-ea10afe81e21' + }, + }, + + res: { + type: 'object' as const, + optional: false as const, nullable: false as const, + ref: 'User' + } +}; + +export default define(meta, async (ps, user) => { + await removePinned(user, ps.noteId).catch(e => { + if (e.id === 'b302d4cf-c050-400a-bbb3-be208681f40c') throw new ApiError(meta.errors.noSuchNote); + throw e; + }); + + return await Users.pack(user.id, user, { + detail: true + }); +}); diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts new file mode 100644 index 0000000000..9b6fb9c410 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -0,0 +1,94 @@ +import $ from 'cafy'; +import { publishMainStream } from '@/services/stream'; +import define from '../../define'; +import rndstr from 'rndstr'; +import config from '@/config/index'; +import * as ms from 'ms'; +import * as bcrypt from 'bcryptjs'; +import { Users, UserProfiles } from '@/models/index'; +import { sendEmail } from '@/services/send-email'; +import { ApiError } from '../../error'; +import { validateEmailForAccount } from '@/services/validate-email-for-account'; + +export const meta = { + requireCredential: true as const, + + secure: true, + + limit: { + duration: ms('1hour'), + max: 3 + }, + + params: { + password: { + validator: $.str + }, + + email: { + validator: $.optional.nullable.str + }, + }, + + errors: { + incorrectPassword: { + message: 'Incorrect password.', + code: 'INCORRECT_PASSWORD', + id: 'e54c1d7e-e7d6-4103-86b6-0a95069b4ad3' + }, + + unavailable: { + message: 'Unavailable email address.', + code: 'UNAVAILABLE', + id: 'a2defefb-f220-8849-0af6-17f816099323' + }, + } +}; + +export default define(meta, async (ps, user) => { + const profile = await UserProfiles.findOneOrFail(user.id); + + // Compare password + const same = await bcrypt.compare(ps.password, profile.password!); + + if (!same) { + throw new ApiError(meta.errors.incorrectPassword); + } + + if (ps.email != null) { + const available = await validateEmailForAccount(ps.email); + if (!available) { + throw new ApiError(meta.errors.unavailable); + } + } + + await UserProfiles.update(user.id, { + email: ps.email, + emailVerified: false, + emailVerifyCode: null + }); + + const iObj = await Users.pack(user.id, user, { + detail: true, + includeSecrets: true + }); + + // Publish meUpdated event + publishMainStream(user.id, 'meUpdated', iObj); + + if (ps.email != null) { + const code = rndstr('a-z0-9', 16); + + await UserProfiles.update(user.id, { + emailVerifyCode: code + }); + + const link = `${config.url}/verify-email/${code}`; + + sendEmail(ps.email, 'Email verification', + `To verify email, please click this link:
${link}`, + `To verify email, please click this link: ${link}`); + } + + return iObj; +}); diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts new file mode 100644 index 0000000000..d0f201ab60 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -0,0 +1,294 @@ +import $ from 'cafy'; +import * as mfm from 'mfm-js'; +import { ID } from '@/misc/cafy-id'; +import { publishMainStream, publishUserEvent } from '@/services/stream'; +import acceptAllFollowRequests from '@/services/following/requests/accept-all'; +import { publishToFollowers } from '@/services/i/update'; +import define from '../../define'; +import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm'; +import { extractHashtags } from '@/misc/extract-hashtags'; +import * as langmap from 'langmap'; +import { updateUsertags } from '@/services/update-hashtag'; +import { ApiError } from '../../error'; +import { Users, DriveFiles, UserProfiles, Pages } from '@/models/index'; +import { User } from '@/models/entities/user'; +import { UserProfile } from '@/models/entities/user-profile'; +import { notificationTypes } from '@/types'; +import { normalizeForSearch } from '@/misc/normalize-for-search'; + +export const meta = { + tags: ['account'], + + requireCredential: true as const, + + kind: 'write:account', + + params: { + name: { + validator: $.optional.nullable.use(Users.validateName), + }, + + description: { + validator: $.optional.nullable.use(Users.validateDescription), + }, + + lang: { + validator: $.optional.nullable.str.or(Object.keys(langmap)), + }, + + location: { + validator: $.optional.nullable.use(Users.validateLocation), + }, + + birthday: { + validator: $.optional.nullable.use(Users.validateBirthday), + }, + + avatarId: { + validator: $.optional.nullable.type(ID), + }, + + bannerId: { + validator: $.optional.nullable.type(ID), + }, + + fields: { + validator: $.optional.arr($.object()).range(1, 4), + }, + + isLocked: { + validator: $.optional.bool, + }, + + isExplorable: { + validator: $.optional.bool, + }, + + hideOnlineStatus: { + validator: $.optional.bool, + }, + + publicReactions: { + validator: $.optional.bool, + }, + + ffVisibility: { + validator: $.optional.str, + }, + + carefulBot: { + validator: $.optional.bool, + }, + + autoAcceptFollowed: { + validator: $.optional.bool, + }, + + noCrawle: { + validator: $.optional.bool, + }, + + isBot: { + validator: $.optional.bool, + }, + + isCat: { + validator: $.optional.bool, + }, + + injectFeaturedNote: { + validator: $.optional.bool, + }, + + receiveAnnouncementEmail: { + validator: $.optional.bool, + }, + + alwaysMarkNsfw: { + validator: $.optional.bool, + }, + + pinnedPageId: { + validator: $.optional.nullable.type(ID), + }, + + mutedWords: { + validator: $.optional.arr($.arr($.str)) + }, + + mutingNotificationTypes: { + validator: $.optional.arr($.str.or(notificationTypes as unknown as string[])) + }, + + emailNotificationTypes: { + validator: $.optional.arr($.str) + }, + }, + + errors: { + noSuchAvatar: { + message: 'No such avatar file.', + code: 'NO_SUCH_AVATAR', + id: '539f3a45-f215-4f81-a9a8-31293640207f' + }, + + noSuchBanner: { + message: 'No such banner file.', + code: 'NO_SUCH_BANNER', + id: '0d8f5629-f210-41c2-9433-735831a58595' + }, + + avatarNotAnImage: { + message: 'The file specified as an avatar is not an image.', + code: 'AVATAR_NOT_AN_IMAGE', + id: 'f419f9f8-2f4d-46b1-9fb4-49d3a2fd7191' + }, + + bannerNotAnImage: { + message: 'The file specified as a banner is not an image.', + code: 'BANNER_NOT_AN_IMAGE', + id: '75aedb19-2afd-4e6d-87fc-67941256fa60' + }, + + noSuchPage: { + message: 'No such page.', + code: 'NO_SUCH_PAGE', + id: '8e01b590-7eb9-431b-a239-860e086c408e' + }, + }, + + res: { + type: 'object' as const, + optional: false as const, nullable: false as const, + ref: 'User' + } +}; + +export default define(meta, async (ps, _user, token) => { + const user = await Users.findOneOrFail(_user.id); + const isSecure = token == null; + + const updates = {} as Partial; + const profileUpdates = {} as Partial; + + const profile = await UserProfiles.findOneOrFail(user.id); + + if (ps.name !== undefined) updates.name = ps.name; + if (ps.description !== undefined) profileUpdates.description = ps.description; + if (ps.lang !== undefined) profileUpdates.lang = ps.lang; + if (ps.location !== undefined) profileUpdates.location = ps.location; + if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday; + if (ps.ffVisibility !== undefined) profileUpdates.ffVisibility = ps.ffVisibility; + if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId; + if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId; + if (ps.mutedWords !== undefined) { + profileUpdates.mutedWords = ps.mutedWords; + profileUpdates.enableWordMute = ps.mutedWords.length > 0; + } + if (ps.mutingNotificationTypes !== undefined) profileUpdates.mutingNotificationTypes = ps.mutingNotificationTypes as typeof notificationTypes[number][]; + if (typeof ps.isLocked === 'boolean') updates.isLocked = ps.isLocked; + if (typeof ps.isExplorable === 'boolean') updates.isExplorable = ps.isExplorable; + if (typeof ps.hideOnlineStatus === 'boolean') updates.hideOnlineStatus = ps.hideOnlineStatus; + if (typeof ps.publicReactions === 'boolean') profileUpdates.publicReactions = ps.publicReactions; + if (typeof ps.isBot === 'boolean') updates.isBot = ps.isBot; + if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot; + if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed; + if (typeof ps.noCrawle === 'boolean') profileUpdates.noCrawle = ps.noCrawle; + if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat; + if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote; + if (typeof ps.receiveAnnouncementEmail === 'boolean') profileUpdates.receiveAnnouncementEmail = ps.receiveAnnouncementEmail; + if (typeof ps.alwaysMarkNsfw === 'boolean') profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw; + if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes; + + if (ps.avatarId) { + const avatar = await DriveFiles.findOne(ps.avatarId); + + if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar); + if (!avatar.type.startsWith('image/')) throw new ApiError(meta.errors.avatarNotAnImage); + + updates.avatarUrl = DriveFiles.getPublicUrl(avatar, true); + + if (avatar.blurhash) { + updates.avatarBlurhash = avatar.blurhash; + } + } + + if (ps.bannerId) { + const banner = await DriveFiles.findOne(ps.bannerId); + + if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner); + if (!banner.type.startsWith('image/')) throw new ApiError(meta.errors.bannerNotAnImage); + + updates.bannerUrl = DriveFiles.getPublicUrl(banner, false); + + if (banner.blurhash) { + updates.bannerBlurhash = banner.blurhash; + } + } + + if (ps.pinnedPageId) { + const page = await Pages.findOne(ps.pinnedPageId); + + if (page == null || page.userId !== user.id) throw new ApiError(meta.errors.noSuchPage); + + profileUpdates.pinnedPageId = page.id; + } else if (ps.pinnedPageId === null) { + profileUpdates.pinnedPageId = null; + } + + if (ps.fields) { + profileUpdates.fields = ps.fields + .filter(x => typeof x.name === 'string' && x.name !== '' && typeof x.value === 'string' && x.value !== '') + .map(x => { + return { name: x.name, value: x.value }; + }); + } + + //#region emojis/tags + + let emojis = [] as string[]; + let tags = [] as string[]; + + const newName = updates.name === undefined ? user.name : updates.name; + const newDescription = profileUpdates.description === undefined ? profile.description : profileUpdates.description; + + if (newName != null) { + const tokens = mfm.parsePlain(newName); + emojis = emojis.concat(extractCustomEmojisFromMfm(tokens!)); + } + + if (newDescription != null) { + const tokens = mfm.parse(newDescription); + emojis = emojis.concat(extractCustomEmojisFromMfm(tokens!)); + tags = extractHashtags(tokens!).map(tag => normalizeForSearch(tag)).splice(0, 32); + } + + updates.emojis = emojis; + updates.tags = tags; + + // ハッシュタグ更新 + updateUsertags(user, tags); + //#endregion + + if (Object.keys(updates).length > 0) await Users.update(user.id, updates); + if (Object.keys(profileUpdates).length > 0) await UserProfiles.update(user.id, profileUpdates); + + const iObj = await Users.pack(user.id, user, { + detail: true, + includeSecrets: isSecure + }); + + // Publish meUpdated event + publishMainStream(user.id, 'meUpdated', iObj); + publishUserEvent(user.id, 'updateUserProfile', await UserProfiles.findOne(user.id)); + + // 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認 + if (user.isLocked && ps.isLocked === false) { + acceptAllFollowRequests(user); + } + + // フォロワーにUpdateを配信 + publishToFollowers(user.id); + + return iObj; +}); diff --git a/packages/backend/src/server/api/endpoints/i/user-group-invites.ts b/packages/backend/src/server/api/endpoints/i/user-group-invites.ts new file mode 100644 index 0000000000..1ebde243ca --- /dev/null +++ b/packages/backend/src/server/api/endpoints/i/user-group-invites.ts @@ -0,0 +1,61 @@ +import $ from 'cafy'; +import { ID } from '@/misc/cafy-id'; +import define from '../../define'; +import { UserGroupInvitations } from '@/models/index'; +import { makePaginationQuery } from '../../common/make-pagination-query'; + +export const meta = { + tags: ['account', 'groups'], + + requireCredential: true as const, + + kind: 'read:user-groups', + + params: { + limit: { + validator: $.optional.num.range(1, 100), + default: 10 + }, + + sinceId: { + validator: $.optional.type(ID), + }, + + untilId: { + validator: $.optional.type(ID), + }, + }, + + res: { + type: 'array' as const, + optional: false as const, nullable: false as const, + items: { + type: 'object' as const, + optional: false as const, nullable: false as const, + properties: { + id: { + type: 'string' as const, + optional: false as const, nullable: false as const, + format: 'id' + }, + group: { + type: 'object' as const, + optional: false as const, nullable: false as const, + ref: 'UserGroup' + } + } + } + } +}; + +export default define(meta, async (ps, user) => { + const query = makePaginationQuery(UserGroupInvitations.createQueryBuilder('invitation'), ps.sinceId, ps.untilId) + .andWhere(`invitation.userId = :meId`, { meId: user.id }) + .leftJoinAndSelect('invitation.userGroup', 'user_group'); + + const invitations = await query + .take(ps.limit!) + .getMany(); + + return await UserGroupInvitations.packMany(invitations); +}); -- cgit v1.3.1-freya From b9eaf906e7b7202d06c9fea72b6d3c422a03f81e Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 12 Nov 2021 10:52:10 +0900 Subject: fix lint errors --- packages/backend/.eslintrc.js | 3 ++ packages/backend/@types/jsrsasign.d.ts | 2 +- packages/backend/src/mfm/from-html.ts | 3 +- packages/backend/src/misc/cafy-id.ts | 1 + packages/backend/src/misc/gen-avatar.ts | 2 +- packages/backend/src/prelude/array.ts | 2 +- packages/backend/src/prelude/url.ts | 2 +- .../object-storage/clean-remote-files.ts | 2 +- .../backend/src/queue/processors/system/index.ts | 4 +-- .../src/queue/processors/system/resync-charts.ts | 2 +- packages/backend/src/queue/queues.ts | 2 +- packages/backend/src/queue/types.ts | 2 +- .../src/remote/activitypub/models/person.ts | 2 +- .../activitypub/renderer/ordered-collection.ts | 2 +- packages/backend/src/server/api/define.ts | 6 ++-- .../src/server/api/endpoints/channels/update.ts | 2 +- .../src/server/api/endpoints/i/2fa/key-done.ts | 2 +- packages/backend/src/server/api/limiter.ts | 8 ++--- .../server/api/stream/channels/games/reversi.ts | 3 +- packages/backend/src/server/api/stream/types.ts | 2 +- packages/backend/src/services/chart/core.ts | 16 +++++----- packages/backend/src/services/stream.ts | 34 +++++++++++----------- 22 files changed, 55 insertions(+), 49 deletions(-) (limited to 'packages/backend/src/server/api/endpoints/i') diff --git a/packages/backend/.eslintrc.js b/packages/backend/.eslintrc.js index 952a2ee9ef..bafd0c9b63 100644 --- a/packages/backend/.eslintrc.js +++ b/packages/backend/.eslintrc.js @@ -22,6 +22,7 @@ module.exports = { 'eol-last': ['error', 'always'], 'semi': ['error', 'always'], 'quotes': ['warn', 'single'], + 'comma-dangle': ['warn', 'always-multiline'], 'keyword-spacing': ['error', { 'before': true, 'after': true, @@ -44,6 +45,8 @@ module.exports = { 'no-multi-spaces': ['warn'], 'no-control-regex': ['warn'], 'no-empty': ['warn'], + 'no-inner-declarations': ['off'], + 'no-sparse-arrays': ['off'], '@typescript-eslint/no-var-requires': ['warn'], '@typescript-eslint/no-inferrable-types': ['warn'], '@typescript-eslint/no-empty-function': ['off'], diff --git a/packages/backend/@types/jsrsasign.d.ts b/packages/backend/@types/jsrsasign.d.ts index bc9d746f7e..bb52f8f64e 100644 --- a/packages/backend/@types/jsrsasign.d.ts +++ b/packages/backend/@types/jsrsasign.d.ts @@ -171,7 +171,7 @@ declare module 'jsrsasign' { public static getTLVbyList(h: ASN1S, currentIndex: Idx, nthList: Mutable, checkingTag?: string): ASN1TLV; - // tslint:disable-next-line:bool-param-default + // eslint:disable-next-line:bool-param-default public static getVbyList(h: ASN1S, currentIndex: Idx, nthList: Mutable, checkingTag?: string, removeUnusedbits?: boolean): ASN1V; public static hextooidstr(hex: ASN1OIDV): OID; diff --git a/packages/backend/src/mfm/from-html.ts b/packages/backend/src/mfm/from-html.ts index de6aa3d0cc..43e16d80c5 100644 --- a/packages/backend/src/mfm/from-html.ts +++ b/packages/backend/src/mfm/from-html.ts @@ -48,9 +48,10 @@ export function fromHtml(html: string, hashtagNames?: string[]): string | null { if (!treeAdapter.isElementNode(node)) return; switch (node.nodeName) { - case 'br': + case 'br': { text += '\n'; break; + } case 'a': { diff --git a/packages/backend/src/misc/cafy-id.ts b/packages/backend/src/misc/cafy-id.ts index 39886611e1..dd81c5c4cf 100644 --- a/packages/backend/src/misc/cafy-id.ts +++ b/packages/backend/src/misc/cafy-id.ts @@ -1,5 +1,6 @@ import { Context } from 'cafy'; +// eslint-disable-next-line @typescript-eslint/ban-types export class ID extends Context { public readonly name = 'ID'; diff --git a/packages/backend/src/misc/gen-avatar.ts b/packages/backend/src/misc/gen-avatar.ts index f03ca9f96d..8838ec8d15 100644 --- a/packages/backend/src/misc/gen-avatar.ts +++ b/packages/backend/src/misc/gen-avatar.ts @@ -56,7 +56,7 @@ export function genAvatar(seed: string, stream: WriteStream): Promise { // 1*n (filled by false) const center: boolean[] = new Array(n).fill(false); - // tslint:disable-next-line:prefer-for-of + // eslint:disable-next-line:prefer-for-of for (let x = 0; x < side.length; x++) { for (let y = 0; y < side[x].length; y++) { side[x][y] = rand(3) === 0; diff --git a/packages/backend/src/prelude/array.ts b/packages/backend/src/prelude/array.ts index d63f0475d0..1e9e62b895 100644 --- a/packages/backend/src/prelude/array.ts +++ b/packages/backend/src/prelude/array.ts @@ -87,7 +87,7 @@ export function groupOn(f: (x: T) => S, xs: T[]): T[][] { export function groupByX(collections: T[], keySelector: (x: T) => string) { return collections.reduce((obj: Record, item: T) => { const key = keySelector(item); - if (!obj.hasOwnProperty(key)) { + if (!Object.prototype.hasOwnProperty.call(obj, key)) { obj[key] = []; } diff --git a/packages/backend/src/prelude/url.ts b/packages/backend/src/prelude/url.ts index c7f2b7c1e7..a4f2f7f5a8 100644 --- a/packages/backend/src/prelude/url.ts +++ b/packages/backend/src/prelude/url.ts @@ -1,4 +1,4 @@ -export function query(obj: {}): string { +export function query(obj: Record): string { const params = Object.entries(obj) .filter(([, v]) => Array.isArray(v) ? v.length : v !== undefined) .reduce((a, [k, v]) => (a[k] = v, a), {} as Record); diff --git a/packages/backend/src/queue/processors/object-storage/clean-remote-files.ts b/packages/backend/src/queue/processors/object-storage/clean-remote-files.ts index 3b2e4ea939..a094c39d5d 100644 --- a/packages/backend/src/queue/processors/object-storage/clean-remote-files.ts +++ b/packages/backend/src/queue/processors/object-storage/clean-remote-files.ts @@ -7,7 +7,7 @@ import { MoreThan, Not, IsNull } from 'typeorm'; const logger = queueLogger.createSubLogger('clean-remote-files'); -export default async function cleanRemoteFiles(job: Bull.Job<{}>, done: any): Promise { +export default async function cleanRemoteFiles(job: Bull.Job>, done: any): Promise { logger.info(`Deleting cached remote files...`); let deletedCount = 0; diff --git a/packages/backend/src/queue/processors/system/index.ts b/packages/backend/src/queue/processors/system/index.ts index 52b7868105..8460ea0a9b 100644 --- a/packages/backend/src/queue/processors/system/index.ts +++ b/packages/backend/src/queue/processors/system/index.ts @@ -3,9 +3,9 @@ import { resyncCharts } from './resync-charts'; const jobs = { resyncCharts, -} as Record | Bull.ProcessPromiseFunction<{}>>; +} as Record> | Bull.ProcessPromiseFunction>>; -export default function(dbQueue: Bull.Queue<{}>) { +export default function(dbQueue: Bull.Queue>) { for (const [k, v] of Object.entries(jobs)) { dbQueue.process(k, v); } diff --git a/packages/backend/src/queue/processors/system/resync-charts.ts b/packages/backend/src/queue/processors/system/resync-charts.ts index b36b024cfb..78a70bb981 100644 --- a/packages/backend/src/queue/processors/system/resync-charts.ts +++ b/packages/backend/src/queue/processors/system/resync-charts.ts @@ -5,7 +5,7 @@ import { driveChart, notesChart, usersChart } from '@/services/chart/index'; const logger = queueLogger.createSubLogger('resync-charts'); -export default async function resyncCharts(job: Bull.Job<{}>, done: any): Promise { +export async function resyncCharts(job: Bull.Job>, done: any): Promise { logger.info(`Resync charts...`); // TODO: ユーザーごとのチャートも更新する diff --git a/packages/backend/src/queue/queues.ts b/packages/backend/src/queue/queues.ts index a66a7ca451..b1d790fcb1 100644 --- a/packages/backend/src/queue/queues.ts +++ b/packages/backend/src/queue/queues.ts @@ -2,7 +2,7 @@ import config from '@/config/index'; import { initialize as initializeQueue } from './initialize'; import { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData } from './types'; -export const systemQueue = initializeQueue<{}>('system'); +export const systemQueue = initializeQueue>('system'); export const deliverQueue = initializeQueue('deliver', config.deliverJobPerSec || 128); export const inboxQueue = initializeQueue('inbox', config.inboxJobPerSec || 16); export const dbQueue = initializeQueue('db'); diff --git a/packages/backend/src/queue/types.ts b/packages/backend/src/queue/types.ts index 39cab29966..c8c7147152 100644 --- a/packages/backend/src/queue/types.ts +++ b/packages/backend/src/queue/types.ts @@ -33,7 +33,7 @@ export type DbUserImportJobData = { fileId: DriveFile['id']; }; -export type ObjectStorageJobData = ObjectStorageFileJobData | {}; +export type ObjectStorageJobData = ObjectStorageFileJobData | Record; export type ObjectStorageFileJobData = { key: string; diff --git a/packages/backend/src/remote/activitypub/models/person.ts b/packages/backend/src/remote/activitypub/models/person.ts index eb8c00a10b..95db46bff2 100644 --- a/packages/backend/src/remote/activitypub/models/person.ts +++ b/packages/backend/src/remote/activitypub/models/person.ts @@ -274,7 +274,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise { +export async function updatePerson(uri: string, resolver?: Resolver | null, hint?: Record): Promise { if (typeof uri !== 'string') throw new Error('uri is not string'); // URIがこのサーバーを指しているならスキップ diff --git a/packages/backend/src/remote/activitypub/renderer/ordered-collection.ts b/packages/backend/src/remote/activitypub/renderer/ordered-collection.ts index 68870a0ecd..c4b4337af8 100644 --- a/packages/backend/src/remote/activitypub/renderer/ordered-collection.ts +++ b/packages/backend/src/remote/activitypub/renderer/ordered-collection.ts @@ -6,7 +6,7 @@ * @param last URL of last page (optional) * @param orderedItems attached objects (optional) */ -export default function(id: string | null, totalItems: any, first?: string, last?: string, orderedItems?: object) { +export default function(id: string | null, totalItems: any, first?: string, last?: string, orderedItems?: Record) { const page: any = { id, type: 'OrderedCollection', diff --git a/packages/backend/src/server/api/define.ts b/packages/backend/src/server/api/define.ts index 4bd8f95e31..48253e78e0 100644 --- a/packages/backend/src/server/api/define.ts +++ b/packages/backend/src/server/api/define.ts @@ -20,7 +20,7 @@ type SimpleUserInfo = { }; type Params = { - [P in keyof T['params']]: NonNullable[P]['transform'] extends Function + [P in keyof T['params']]: NonNullable[P]['transform'] extends () => any ? ReturnType[P]['transform']> : NonNullable[P]['default'] extends null | number | string ? NonOptional[P]['validator']['get']>[0]> @@ -30,7 +30,7 @@ type Params = { export type Response = Record | void; type executor = - (params: Params, user: T['requireCredential'] extends true ? SimpleUserInfo : SimpleUserInfo | null, token: AccessToken | null, file?: any, cleanup?: Function) => + (params: Params, user: T['requireCredential'] extends true ? SimpleUserInfo : SimpleUserInfo | null, token: AccessToken | null, file?: any, cleanup?: () => any) => Promise>>; export default function (meta: T, cb: executor) @@ -74,7 +74,7 @@ function getParams(defs: T, params: any): [Params, A }); return true; } else { - if (v === undefined && def.hasOwnProperty('default')) { + if (v === undefined && Object.prototype.hasOwnProperty.call(def, 'default')) { x[k] = def.default; } else { x[k] = v; diff --git a/packages/backend/src/server/api/endpoints/channels/update.ts b/packages/backend/src/server/api/endpoints/channels/update.ts index 9b447bd04b..05f279d6ac 100644 --- a/packages/backend/src/server/api/endpoints/channels/update.ts +++ b/packages/backend/src/server/api/endpoints/channels/update.ts @@ -69,7 +69,7 @@ export default define(meta, async (ps, me) => { throw new ApiError(meta.errors.accessDenied); } - // tslint:disable-next-line:no-unnecessary-initializer + // eslint:disable-next-line:no-unnecessary-initializer let banner = undefined; if (ps.bannerId != null) { banner = await DriveFiles.findOne({ diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts index b4d3af235a..e06d0a9f68 100644 --- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts +++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts @@ -75,7 +75,7 @@ export default define(meta, async (ps, user) => { const flags = attestation.authData[32]; - // tslint:disable-next-line:no-bitwise + // eslint:disable-next-line:no-bitwise if (!(flags & 1)) { throw new Error('user not present'); } diff --git a/packages/backend/src/server/api/limiter.ts b/packages/backend/src/server/api/limiter.ts index 1e2fe5bcb3..82a8613c90 100644 --- a/packages/backend/src/server/api/limiter.ts +++ b/packages/backend/src/server/api/limiter.ts @@ -10,16 +10,16 @@ const logger = new Logger('limiter'); export default (endpoint: IEndpoint, user: User) => new Promise((ok, reject) => { const limitation = endpoint.meta.limit!; - const key = limitation.hasOwnProperty('key') + const key = Object.prototype.hasOwnProperty.call(limitation, 'key') ? limitation.key : endpoint.name; const hasShortTermLimit = - limitation.hasOwnProperty('minInterval'); + Object.prototype.hasOwnProperty.call(limitation, 'minInterval'); const hasLongTermLimit = - limitation.hasOwnProperty('duration') && - limitation.hasOwnProperty('max'); + Object.prototype.hasOwnProperty.call(limitation, 'duration') && + Object.prototype.hasOwnProperty.call(limitation, 'max'); if (hasShortTermLimit) { min(); diff --git a/packages/backend/src/server/api/stream/channels/games/reversi.ts b/packages/backend/src/server/api/stream/channels/games/reversi.ts index 3b89aac35c..399750c26a 100644 --- a/packages/backend/src/server/api/stream/channels/games/reversi.ts +++ b/packages/backend/src/server/api/stream/channels/games/reversi.ts @@ -19,7 +19,7 @@ export default class extends Channel { @autobind public async onMessage(type: string, body: any) { switch (type) { - case 'ping': + case 'ping': { if (body.id == null) return; const matching = await ReversiMatchings.findOne({ parentId: this.user!.id, @@ -28,6 +28,7 @@ export default class extends Channel { if (matching == null) return; publishMainStream(matching.childId, 'reversiInvited', await ReversiMatchings.pack(matching, { id: matching.childId })); break; + } } } } diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts index 70eb5c5ce5..f4302f64a0 100644 --- a/packages/backend/src/server/api/stream/types.ts +++ b/packages/backend/src/server/api/stream/types.ts @@ -31,7 +31,7 @@ export interface BroadcastTypes { } export interface UserStreamTypes { - terminate: {}; + terminate: Record; followChannel: Channel; unfollowChannel: Channel; updateUserProfile: UserProfile; diff --git a/packages/backend/src/services/chart/core.ts b/packages/backend/src/services/chart/core.ts index c0d3280c2b..78b7dd1359 100644 --- a/packages/backend/src/services/chart/core.ts +++ b/packages/backend/src/services/chart/core.ts @@ -70,7 +70,7 @@ export default abstract class Chart> { @autobind private static convertSchemaToFlatColumnDefinitions(schema: SimpleSchema) { - const columns = {} as any; + const columns = {} as Record; const flatColumns = (x: Obj, path?: string) => { for (const [k, v] of Object.entries(x)) { const p = path ? `${path}${this.columnDot}${k}` : k; @@ -93,8 +93,8 @@ export default abstract class Chart> { } @autobind - private static convertFlattenColumnsToObject(x: Record): Record { - const obj = {} as any; + private static convertFlattenColumnsToObject(x: Record): Record { + const obj = {} as Record; for (const k of Object.keys(x).filter(k => k.startsWith(Chart.columnPrefix))) { // now k is ___x_y_z const path = k.substr(Chart.columnPrefix.length).split(Chart.columnDot).join('.'); @@ -104,7 +104,7 @@ export default abstract class Chart> { } @autobind - private static convertObjectToFlattenColumns(x: Record) { + private static convertObjectToFlattenColumns(x: Record) { const columns = {} as Record; const flatten = (x: Obj, path?: string) => { for (const [k, v] of Object.entries(x)) { @@ -121,9 +121,9 @@ export default abstract class Chart> { } @autobind - private static countUniqueFields(x: Record) { + private static countUniqueFields(x: Record) { const exec = (x: Obj) => { - const res = {} as Record; + const res = {} as Record; for (const [k, v] of Object.entries(x)) { if (typeof v === 'object' && !Array.isArray(v)) { res[k] = exec(v); @@ -140,7 +140,7 @@ export default abstract class Chart> { @autobind private static convertQuery(diff: Record) { - const query: Record = {}; + const query: Record string> = {}; for (const [k, v] of Object.entries(diff)) { if (typeof v === 'number') { @@ -337,7 +337,7 @@ export default abstract class Chart> { } @autobind - public async save() { + public async save(): Promise { if (this.buffer.length === 0) { logger.info(`${this.name}: Write skipped`); return; diff --git a/packages/backend/src/services/stream.ts b/packages/backend/src/services/stream.ts index 2c308a1b54..0901857c33 100644 --- a/packages/backend/src/services/stream.ts +++ b/packages/backend/src/services/stream.ts @@ -37,74 +37,74 @@ class Publisher { channel: channel, message: message })); - } + }; public publishInternalEvent = (type: K, value?: InternalStreamTypes[K]): void => { this.publish('internal', type, typeof value === 'undefined' ? null : value); - } + }; public publishUserEvent = (userId: User['id'], type: K, value?: UserStreamTypes[K]): void => { this.publish(`user:${userId}`, type, typeof value === 'undefined' ? null : value); - } + }; public publishBroadcastStream = (type: K, value?: BroadcastTypes[K]): void => { this.publish('broadcast', type, typeof value === 'undefined' ? null : value); - } + }; public publishMainStream = (userId: User['id'], type: K, value?: MainStreamTypes[K]): void => { this.publish(`mainStream:${userId}`, type, typeof value === 'undefined' ? null : value); - } + }; public publishDriveStream = (userId: User['id'], type: K, value?: DriveStreamTypes[K]): void => { this.publish(`driveStream:${userId}`, type, typeof value === 'undefined' ? null : value); - } + }; public publishNoteStream = (noteId: Note['id'], type: K, value?: NoteStreamTypes[K]): void => { this.publish(`noteStream:${noteId}`, type, { id: noteId, body: value }); - } + }; public publishChannelStream = (channelId: Channel['id'], type: K, value?: ChannelStreamTypes[K]): void => { this.publish(`channelStream:${channelId}`, type, typeof value === 'undefined' ? null : value); - } + }; public publishUserListStream = (listId: UserList['id'], type: K, value?: UserListStreamTypes[K]): void => { this.publish(`userListStream:${listId}`, type, typeof value === 'undefined' ? null : value); - } + }; public publishAntennaStream = (antennaId: Antenna['id'], type: K, value?: AntennaStreamTypes[K]): void => { this.publish(`antennaStream:${antennaId}`, type, typeof value === 'undefined' ? null : value); - } + }; public publishMessagingStream = (userId: User['id'], otherpartyId: User['id'], type: K, value?: MessagingStreamTypes[K]): void => { this.publish(`messagingStream:${userId}-${otherpartyId}`, type, typeof value === 'undefined' ? null : value); - } + }; public publishGroupMessagingStream = (groupId: UserGroup['id'], type: K, value?: GroupMessagingStreamTypes[K]): void => { this.publish(`messagingStream:${groupId}`, type, typeof value === 'undefined' ? null : value); - } + }; public publishMessagingIndexStream = (userId: User['id'], type: K, value?: MessagingIndexStreamTypes[K]): void => { this.publish(`messagingIndexStream:${userId}`, type, typeof value === 'undefined' ? null : value); - } + }; public publishReversiStream = (userId: User['id'], type: K, value?: ReversiStreamTypes[K]): void => { this.publish(`reversiStream:${userId}`, type, typeof value === 'undefined' ? null : value); - } + }; public publishReversiGameStream = (gameId: ReversiGame['id'], type: K, value?: ReversiGameStreamTypes[K]): void => { this.publish(`reversiGameStream:${gameId}`, type, typeof value === 'undefined' ? null : value); - } + }; public publishNotesStream = (note: Packed<'Note'>): void => { this.publish('notesStream', null, note); - } + }; public publishAdminStream = (userId: User['id'], type: K, value?: AdminStreamTypes[K]): void => { this.publish(`adminStream:${userId}`, type, typeof value === 'undefined' ? null : value); - } + }; } const publisher = new Publisher(); -- cgit v1.3.1-freya From 9b876b30b27d6a02129447f6584d95a497d94c32 Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 12 Nov 2021 19:47:04 +0900 Subject: update ms to 3.0.0 --- packages/backend/@types/ms.d.ts | 12 ------------ packages/backend/package.json | 2 +- packages/backend/src/server/api/endpoints/ap/get.ts | 2 +- packages/backend/src/server/api/endpoints/ap/show.ts | 2 +- packages/backend/src/server/api/endpoints/blocking/create.ts | 2 +- packages/backend/src/server/api/endpoints/blocking/delete.ts | 2 +- .../backend/src/server/api/endpoints/drive/files/create.ts | 2 +- .../src/server/api/endpoints/drive/files/upload-from-url.ts | 2 +- .../backend/src/server/api/endpoints/following/create.ts | 2 +- .../backend/src/server/api/endpoints/following/delete.ts | 2 +- .../backend/src/server/api/endpoints/gallery/posts/create.ts | 2 +- .../backend/src/server/api/endpoints/gallery/posts/update.ts | 2 +- .../backend/src/server/api/endpoints/i/export-blocking.ts | 2 +- .../backend/src/server/api/endpoints/i/export-following.ts | 2 +- packages/backend/src/server/api/endpoints/i/export-mute.ts | 2 +- packages/backend/src/server/api/endpoints/i/export-notes.ts | 2 +- .../backend/src/server/api/endpoints/i/export-user-lists.ts | 2 +- .../backend/src/server/api/endpoints/i/import-blocking.ts | 2 +- .../backend/src/server/api/endpoints/i/import-following.ts | 2 +- packages/backend/src/server/api/endpoints/i/import-muting.ts | 2 +- .../backend/src/server/api/endpoints/i/import-user-lists.ts | 2 +- packages/backend/src/server/api/endpoints/i/update-email.ts | 2 +- .../src/server/api/endpoints/messaging/messages/delete.ts | 2 +- packages/backend/src/server/api/endpoints/notes/create.ts | 2 +- packages/backend/src/server/api/endpoints/notes/delete.ts | 2 +- .../src/server/api/endpoints/notes/reactions/delete.ts | 2 +- packages/backend/src/server/api/endpoints/notes/unrenote.ts | 2 +- packages/backend/src/server/api/endpoints/pages/create.ts | 2 +- packages/backend/src/server/api/endpoints/pages/update.ts | 2 +- .../src/server/api/endpoints/request-reset-password.ts | 2 +- .../backend/src/server/api/endpoints/users/recommendation.ts | 2 +- packages/backend/src/server/web/index.ts | 2 +- packages/backend/yarn.lock | 5 +++++ 33 files changed, 36 insertions(+), 43 deletions(-) delete mode 100644 packages/backend/@types/ms.d.ts (limited to 'packages/backend/src/server/api/endpoints/i') diff --git a/packages/backend/@types/ms.d.ts b/packages/backend/@types/ms.d.ts deleted file mode 100644 index 2f0156d104..0000000000 --- a/packages/backend/@types/ms.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -declare module 'ms' { - interface IMSOptions { - long: boolean; - } - - function ms(value: string): number; - function ms(value: number, options?: IMSOptions): string; - - namespace ms {} // Hack - - export = ms; -} diff --git a/packages/backend/package.json b/packages/backend/package.json index 4376dc4887..b1337489fa 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -135,7 +135,7 @@ "mfm-js": "0.20.0", "misskey-js": "0.0.8", "mocha": "8.4.0", - "ms": "2.1.3", + "ms": "3.0.0-canary.1", "multer": "1.4.3", "nested-property": "4.0.0", "node-fetch": "2.6.1", diff --git a/packages/backend/src/server/api/endpoints/ap/get.ts b/packages/backend/src/server/api/endpoints/ap/get.ts index 78919f43b0..2f97a24774 100644 --- a/packages/backend/src/server/api/endpoints/ap/get.ts +++ b/packages/backend/src/server/api/endpoints/ap/get.ts @@ -2,7 +2,7 @@ import $ from 'cafy'; import define from '../../define'; import Resolver from '@/remote/activitypub/resolver'; import { ApiError } from '../../error'; -import * as ms from 'ms'; +import ms from 'ms'; export const meta = { tags: ['federation'], diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index 2280d93724..32685d44bd 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -11,7 +11,7 @@ import { Note } from '@/models/entities/note'; import { User } from '@/models/entities/user'; import { fetchMeta } from '@/misc/fetch-meta'; import { isActor, isPost, getApId } from '@/remote/activitypub/type'; -import * as ms from 'ms'; +import ms from 'ms'; export const meta = { tags: ['federation'], diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts index 2953252394..4d33c09479 100644 --- a/packages/backend/src/server/api/endpoints/blocking/create.ts +++ b/packages/backend/src/server/api/endpoints/blocking/create.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import { ID } from '@/misc/cafy-id'; -import * as ms from 'ms'; +import ms from 'ms'; import create from '@/services/blocking/create'; import define from '../../define'; import { ApiError } from '../../error'; diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts index a66e46fdf0..ae5dab020a 100644 --- a/packages/backend/src/server/api/endpoints/blocking/delete.ts +++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import { ID } from '@/misc/cafy-id'; -import * as ms from 'ms'; +import ms from 'ms'; import deleteBlocking from '@/services/blocking/delete'; import define from '../../define'; import { ApiError } from '../../error'; diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts index 2abc104e6c..89755d9498 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts @@ -1,4 +1,4 @@ -import * as ms from 'ms'; +import ms from 'ms'; import $ from 'cafy'; import { ID } from '@/misc/cafy-id'; import create from '@/services/drive/add-file'; diff --git a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts index 9f10a42d24..adb5126fbe 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import { ID } from '@/misc/cafy-id'; -import * as ms from 'ms'; +import ms from 'ms'; import uploadFromUrl from '@/services/drive/upload-from-url'; import define from '../../../define'; import { DriveFiles } from '@/models/index'; diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts index ba9ca1092d..92e86bf6b2 100644 --- a/packages/backend/src/server/api/endpoints/following/create.ts +++ b/packages/backend/src/server/api/endpoints/following/create.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import { ID } from '@/misc/cafy-id'; -import * as ms from 'ms'; +import ms from 'ms'; import create from '@/services/following/create'; import define from '../../define'; import { ApiError } from '../../error'; diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts index 0b0158b86e..030d30c9b5 100644 --- a/packages/backend/src/server/api/endpoints/following/delete.ts +++ b/packages/backend/src/server/api/endpoints/following/delete.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import { ID } from '@/misc/cafy-id'; -import * as ms from 'ms'; +import ms from 'ms'; import deleteFollowing from '@/services/following/delete'; import define from '../../define'; import { ApiError } from '../../error'; diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts index 38b487e6ea..34af72695d 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; -import * as ms from 'ms'; +import ms from 'ms'; import define from '../../../define'; import { ID } from '../../../../../misc/cafy-id'; import { DriveFiles, GalleryPosts } from '@/models/index'; diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts index 54eea130d3..f94606acf2 100644 --- a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts +++ b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; -import * as ms from 'ms'; +import ms from 'ms'; import define from '../../../define'; import { ID } from '../../../../../misc/cafy-id'; import { DriveFiles, GalleryPosts } from '@/models/index'; diff --git a/packages/backend/src/server/api/endpoints/i/export-blocking.ts b/packages/backend/src/server/api/endpoints/i/export-blocking.ts index e4797da0c1..e276ecf384 100644 --- a/packages/backend/src/server/api/endpoints/i/export-blocking.ts +++ b/packages/backend/src/server/api/endpoints/i/export-blocking.ts @@ -1,6 +1,6 @@ import define from '../../define'; import { createExportBlockingJob } from '@/queue/index'; -import * as ms from 'ms'; +import ms from 'ms'; export const meta = { secure: true, diff --git a/packages/backend/src/server/api/endpoints/i/export-following.ts b/packages/backend/src/server/api/endpoints/i/export-following.ts index b0f154cda8..a351161111 100644 --- a/packages/backend/src/server/api/endpoints/i/export-following.ts +++ b/packages/backend/src/server/api/endpoints/i/export-following.ts @@ -1,6 +1,6 @@ import define from '../../define'; import { createExportFollowingJob } from '@/queue/index'; -import * as ms from 'ms'; +import ms from 'ms'; export const meta = { secure: true, diff --git a/packages/backend/src/server/api/endpoints/i/export-mute.ts b/packages/backend/src/server/api/endpoints/i/export-mute.ts index 46d547fa53..b176c7ee8d 100644 --- a/packages/backend/src/server/api/endpoints/i/export-mute.ts +++ b/packages/backend/src/server/api/endpoints/i/export-mute.ts @@ -1,6 +1,6 @@ import define from '../../define'; import { createExportMuteJob } from '@/queue/index'; -import * as ms from 'ms'; +import ms from 'ms'; export const meta = { secure: true, diff --git a/packages/backend/src/server/api/endpoints/i/export-notes.ts b/packages/backend/src/server/api/endpoints/i/export-notes.ts index 441bf16896..8cba04552e 100644 --- a/packages/backend/src/server/api/endpoints/i/export-notes.ts +++ b/packages/backend/src/server/api/endpoints/i/export-notes.ts @@ -1,6 +1,6 @@ import define from '../../define'; import { createExportNotesJob } from '@/queue/index'; -import * as ms from 'ms'; +import ms from 'ms'; export const meta = { secure: true, diff --git a/packages/backend/src/server/api/endpoints/i/export-user-lists.ts b/packages/backend/src/server/api/endpoints/i/export-user-lists.ts index 24043a862a..44d43c0bea 100644 --- a/packages/backend/src/server/api/endpoints/i/export-user-lists.ts +++ b/packages/backend/src/server/api/endpoints/i/export-user-lists.ts @@ -1,6 +1,6 @@ import define from '../../define'; import { createExportUserListsJob } from '@/queue/index'; -import * as ms from 'ms'; +import ms from 'ms'; export const meta = { secure: true, diff --git a/packages/backend/src/server/api/endpoints/i/import-blocking.ts b/packages/backend/src/server/api/endpoints/i/import-blocking.ts index d44d0b6077..4822bd5868 100644 --- a/packages/backend/src/server/api/endpoints/i/import-blocking.ts +++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts @@ -2,7 +2,7 @@ import $ from 'cafy'; import { ID } from '@/misc/cafy-id'; import define from '../../define'; import { createImportBlockingJob } from '@/queue/index'; -import * as ms from 'ms'; +import ms from 'ms'; import { ApiError } from '../../error'; import { DriveFiles } from '@/models/index'; diff --git a/packages/backend/src/server/api/endpoints/i/import-following.ts b/packages/backend/src/server/api/endpoints/i/import-following.ts index b3de397661..19aa2fa933 100644 --- a/packages/backend/src/server/api/endpoints/i/import-following.ts +++ b/packages/backend/src/server/api/endpoints/i/import-following.ts @@ -2,7 +2,7 @@ import $ from 'cafy'; import { ID } from '@/misc/cafy-id'; import define from '../../define'; import { createImportFollowingJob } from '@/queue/index'; -import * as ms from 'ms'; +import ms from 'ms'; import { ApiError } from '../../error'; import { DriveFiles } from '@/models/index'; diff --git a/packages/backend/src/server/api/endpoints/i/import-muting.ts b/packages/backend/src/server/api/endpoints/i/import-muting.ts index c17434c587..c474dd7186 100644 --- a/packages/backend/src/server/api/endpoints/i/import-muting.ts +++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts @@ -2,7 +2,7 @@ import $ from 'cafy'; import { ID } from '@/misc/cafy-id'; import define from '../../define'; import { createImportMutingJob } from '@/queue/index'; -import * as ms from 'ms'; +import ms from 'ms'; import { ApiError } from '../../error'; import { DriveFiles } from '@/models/index'; diff --git a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts index 9069a019a9..ceccdd1852 100644 --- a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts +++ b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts @@ -2,7 +2,7 @@ import $ from 'cafy'; import { ID } from '@/misc/cafy-id'; import define from '../../define'; import { createImportUserListsJob } from '@/queue/index'; -import * as ms from 'ms'; +import ms from 'ms'; import { ApiError } from '../../error'; import { DriveFiles } from '@/models/index'; diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts index 9b6fb9c410..ff5a9f292c 100644 --- a/packages/backend/src/server/api/endpoints/i/update-email.ts +++ b/packages/backend/src/server/api/endpoints/i/update-email.ts @@ -3,7 +3,7 @@ import { publishMainStream } from '@/services/stream'; import define from '../../define'; import rndstr from 'rndstr'; import config from '@/config/index'; -import * as ms from 'ms'; +import ms from 'ms'; import * as bcrypt from 'bcryptjs'; import { Users, UserProfiles } from '@/models/index'; import { sendEmail } from '@/services/send-email'; diff --git a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts index bd4890fc8a..25bf676383 100644 --- a/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts +++ b/packages/backend/src/server/api/endpoints/messaging/messages/delete.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import { ID } from '@/misc/cafy-id'; import define from '../../../define'; -import * as ms from 'ms'; +import ms from 'ms'; import { ApiError } from '../../../error'; import { MessagingMessages } from '@/models/index'; import { deleteMessage } from '@/services/messages/delete'; diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts index 8257ea1d0f..e9584e7b8b 100644 --- a/packages/backend/src/server/api/endpoints/notes/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/create.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; -import * as ms from 'ms'; +import ms from 'ms'; import { length } from 'stringz'; import create from '@/services/note/create'; import define from '../../define'; diff --git a/packages/backend/src/server/api/endpoints/notes/delete.ts b/packages/backend/src/server/api/endpoints/notes/delete.ts index 7163a2b9d2..7f0d59669e 100644 --- a/packages/backend/src/server/api/endpoints/notes/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/delete.ts @@ -2,7 +2,7 @@ import $ from 'cafy'; import { ID } from '@/misc/cafy-id'; import deleteNote from '@/services/note/delete'; import define from '../../define'; -import * as ms from 'ms'; +import ms from 'ms'; import { getNote } from '../../common/getters'; import { ApiError } from '../../error'; import { Users } from '@/models/index'; diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts index 69550f96de..ea851458d2 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import { ID } from '@/misc/cafy-id'; import define from '../../../define'; -import * as ms from 'ms'; +import ms from 'ms'; import deleteReaction from '@/services/note/reaction/delete'; import { getNote } from '../../../common/getters'; import { ApiError } from '../../../error'; diff --git a/packages/backend/src/server/api/endpoints/notes/unrenote.ts b/packages/backend/src/server/api/endpoints/notes/unrenote.ts index dce43d9d9c..d3fba66095 100644 --- a/packages/backend/src/server/api/endpoints/notes/unrenote.ts +++ b/packages/backend/src/server/api/endpoints/notes/unrenote.ts @@ -2,7 +2,7 @@ import $ from 'cafy'; import { ID } from '@/misc/cafy-id'; import deleteNote from '@/services/note/delete'; import define from '../../define'; -import * as ms from 'ms'; +import ms from 'ms'; import { getNote } from '../../common/getters'; import { ApiError } from '../../error'; import { Notes, Users } from '@/models/index'; diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts index c23978f093..0ec287c592 100644 --- a/packages/backend/src/server/api/endpoints/pages/create.ts +++ b/packages/backend/src/server/api/endpoints/pages/create.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; -import * as ms from 'ms'; +import ms from 'ms'; import define from '../../define'; import { ID } from '@/misc/cafy-id'; import { Pages, DriveFiles } from '@/models/index'; diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts index b3a7f26963..4aaf2aed5d 100644 --- a/packages/backend/src/server/api/endpoints/pages/update.ts +++ b/packages/backend/src/server/api/endpoints/pages/update.ts @@ -1,5 +1,5 @@ import $ from 'cafy'; -import * as ms from 'ms'; +import ms from 'ms'; import define from '../../define'; import { ApiError } from '../../error'; import { Pages, DriveFiles } from '@/models/index'; diff --git a/packages/backend/src/server/api/endpoints/request-reset-password.ts b/packages/backend/src/server/api/endpoints/request-reset-password.ts index f9928c2ee6..7bb50bf6a6 100644 --- a/packages/backend/src/server/api/endpoints/request-reset-password.ts +++ b/packages/backend/src/server/api/endpoints/request-reset-password.ts @@ -3,7 +3,7 @@ import { publishMainStream } from '@/services/stream'; import define from '../define'; import rndstr from 'rndstr'; import config from '@/config/index'; -import * as ms from 'ms'; +import ms from 'ms'; import { Users, UserProfiles, PasswordResetRequests } from '@/models/index'; import { sendEmail } from '@/services/send-email'; import { ApiError } from '../error'; diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts index dde6bb1037..7c775c4dcf 100644 --- a/packages/backend/src/server/api/endpoints/users/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts @@ -1,4 +1,4 @@ -import * as ms from 'ms'; +import ms from 'ms'; import $ from 'cafy'; import define from '../../define'; import { Users, Followings } from '@/models/index'; diff --git a/packages/backend/src/server/web/index.ts b/packages/backend/src/server/web/index.ts index fc95a36a87..d80d73f252 100644 --- a/packages/backend/src/server/web/index.ts +++ b/packages/backend/src/server/web/index.ts @@ -3,7 +3,7 @@ */ import { dirname } from 'path'; -import * as ms from 'ms'; +import ms from 'ms'; import * as Koa from 'koa'; import * as Router from '@koa/router'; import * as send from 'koa-send'; diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock index 0346380ee1..311530db66 100644 --- a/packages/backend/yarn.lock +++ b/packages/backend/yarn.lock @@ -5421,6 +5421,11 @@ ms@2.1.3, ms@^2.0.0, ms@^2.1.1: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +ms@3.0.0-canary.1: + version "3.0.0-canary.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-3.0.0-canary.1.tgz#c7b34fbce381492fd0b345d1cf56e14d67b77b80" + integrity sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g== + multer@1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/multer/-/multer-1.4.3.tgz#4db352d6992e028ac0eacf7be45c6efd0264297b" -- cgit v1.3.1-freya