summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/api/endpoints/i
diff options
context:
space:
mode:
Diffstat (limited to 'packages/backend/src/server/api/endpoints/i')
-rw-r--r--packages/backend/src/server/api/endpoints/i/2fa/done.ts52
-rw-r--r--packages/backend/src/server/api/endpoints/i/2fa/key-done.ts216
-rw-r--r--packages/backend/src/server/api/endpoints/i/2fa/password-less.ts24
-rw-r--r--packages/backend/src/server/api/endpoints/i/2fa/register-key.ts84
-rw-r--r--packages/backend/src/server/api/endpoints/i/2fa/register.ts79
-rw-r--r--packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts62
-rw-r--r--packages/backend/src/server/api/endpoints/i/2fa/unregister.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/i/apps.ts52
-rw-r--r--packages/backend/src/server/api/endpoints/i/authorized-apps.ts51
-rw-r--r--packages/backend/src/server/api/endpoints/i/change-password.ts42
-rw-r--r--packages/backend/src/server/api/endpoints/i/delete-account.ts47
-rw-r--r--packages/backend/src/server/api/endpoints/i/export-blocking.ts18
-rw-r--r--packages/backend/src/server/api/endpoints/i/export-following.ts18
-rw-r--r--packages/backend/src/server/api/endpoints/i/export-mute.ts18
-rw-r--r--packages/backend/src/server/api/endpoints/i/export-notes.ts18
-rw-r--r--packages/backend/src/server/api/endpoints/i/export-user-lists.ts18
-rw-r--r--packages/backend/src/server/api/endpoints/i/favorites.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/i/gallery/likes.ts40
-rw-r--r--packages/backend/src/server/api/endpoints/i/gallery/posts.ts36
-rw-r--r--packages/backend/src/server/api/endpoints/i/get-word-muted-notes-count.ts30
-rw-r--r--packages/backend/src/server/api/endpoints/i/import-blocking.ts34
-rw-r--r--packages/backend/src/server/api/endpoints/i/import-following.ts34
-rw-r--r--packages/backend/src/server/api/endpoints/i/import-muting.ts34
-rw-r--r--packages/backend/src/server/api/endpoints/i/import-user-lists.ts34
-rw-r--r--packages/backend/src/server/api/endpoints/i/notifications.ts194
-rw-r--r--packages/backend/src/server/api/endpoints/i/page-likes.ts40
-rw-r--r--packages/backend/src/server/api/endpoints/i/pages.ts36
-rw-r--r--packages/backend/src/server/api/endpoints/i/pin.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/i/read-all-messaging-messages.ts59
-rw-r--r--packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts36
-rw-r--r--packages/backend/src/server/api/endpoints/i/read-announcement.ts75
-rw-r--r--packages/backend/src/server/api/endpoints/i/regenerate-token.ts65
-rw-r--r--packages/backend/src/server/api/endpoints/i/registry/get-all.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/i/registry/get-detail.ts44
-rw-r--r--packages/backend/src/server/api/endpoints/i/registry/get.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts40
-rw-r--r--packages/backend/src/server/api/endpoints/i/registry/keys.ts32
-rw-r--r--packages/backend/src/server/api/endpoints/i/registry/remove.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/i/registry/scopes.ts40
-rw-r--r--packages/backend/src/server/api/endpoints/i/registry/set.ts81
-rw-r--r--packages/backend/src/server/api/endpoints/i/revoke-token.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/i/signin-history.ts32
-rw-r--r--packages/backend/src/server/api/endpoints/i/unpin.ts34
-rw-r--r--packages/backend/src/server/api/endpoints/i/update-email.ts104
-rw-r--r--packages/backend/src/server/api/endpoints/i/update.ts261
-rw-r--r--packages/backend/src/server/api/endpoints/i/user-group-invites.ts38
-rw-r--r--packages/backend/src/server/api/endpoints/i/webhooks/create.ts51
-rw-r--r--packages/backend/src/server/api/endpoints/i/webhooks/delete.ts42
-rw-r--r--packages/backend/src/server/api/endpoints/i/webhooks/list.ts26
-rw-r--r--packages/backend/src/server/api/endpoints/i/webhooks/show.ts34
-rw-r--r--packages/backend/src/server/api/endpoints/i/webhooks/update.ts56
51 files changed, 1692 insertions, 1035 deletions
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/done.ts b/packages/backend/src/server/api/endpoints/i/2fa/done.ts
index 35806b2bc3..bcf3931b04 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/done.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/done.ts
@@ -1,6 +1,8 @@
import * as speakeasy from 'speakeasy';
-import define from '../../../define.js';
-import { UserProfiles } from '@/models/index.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserProfilesRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
requireCredential: true,
@@ -17,27 +19,35 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const token = ps.token.replace(/\s/g, '');
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const token = ps.token.replace(/\s/g, '');
- const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
+ const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
- if (profile.twoFactorTempSecret == null) {
- throw new Error('二段階認証の設定が開始されていません');
- }
+ if (profile.twoFactorTempSecret == null) {
+ throw new Error('二段階認証の設定が開始されていません');
+ }
- const verified = (speakeasy as any).totp.verify({
- secret: profile.twoFactorTempSecret,
- encoding: 'base32',
- token: token,
- });
+ const verified = (speakeasy as any).totp.verify({
+ secret: profile.twoFactorTempSecret,
+ encoding: 'base32',
+ token: token,
+ });
- if (!verified) {
- throw new Error('not verified');
- }
+ if (!verified) {
+ throw new Error('not verified');
+ }
- await UserProfiles.update(user.id, {
- twoFactorSecret: profile.twoFactorTempSecret,
- twoFactorEnabled: true,
- });
-});
+ await this.userProfilesRepository.update(me.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
index 1afb34bfda..f2f4c2044e 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
@@ -1,19 +1,16 @@
-import bcrypt from 'bcryptjs';
import { promisify } from 'node:util';
+import bcrypt from 'bcryptjs';
import * as cbor from 'cbor';
-import define from '../../../define.js';
-import {
- UserProfiles,
- UserSecurityKeys,
- AttestationChallenges,
- Users,
-} from '@/models/index.js';
-import config from '@/config/index.js';
-import { procedures, hash } from '../../../2fa.js';
-import { publishMainStream } from '@/services/stream.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { Config } from '@/config.js';
+import { DI } from '@/di-symbols.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js';
+import { AttestationChallengesRepository, UserProfilesRepository, UserSecurityKeysRepository } from '@/models/index.js';
const cborDecodeFirst = promisify(cbor.decodeFirst) as any;
-const rpIdHashReal = hash(Buffer.from(config.hostname, 'utf-8'));
export const meta = {
requireCredential: true,
@@ -34,110 +31,135 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.config)
+ private config: Config,
- // Compare password
- const same = await bcrypt.compare(ps.password, profile.password!);
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
- if (!same) {
- throw new Error('incorrect password');
- }
+ @Inject(DI.userSecurityKeysRepository)
+ private userSecurityKeysRepository: UserSecurityKeysRepository,
- if (!profile.twoFactorEnabled) {
- throw new Error('2fa not enabled');
- }
+ @Inject(DI.attestationChallengesRepository)
+ private attestationChallengesRepository: AttestationChallengesRepository,
- const clientData = JSON.parse(ps.clientDataJSON);
+ private userEntityService: UserEntityService,
+ private globalEventService: GlobalEventService,
+ private twoFactorAuthenticationService: TwoFactorAuthenticationService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const rpIdHashReal = this.twoFactorAuthenticationService.hash(Buffer.from(this.config.hostname, 'utf-8'));
- 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 profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
- const clientDataJSONHash = hash(Buffer.from(ps.clientDataJSON, 'utf-8'));
+ // Compare password
+ const same = await bcrypt.compare(ps.password, profile.password!);
- const attestation = await cborDecodeFirst(ps.attestationObject);
+ if (!same) {
+ throw new Error('incorrect password');
+ }
- const rpIdHash = attestation.authData.slice(0, 32);
- if (!rpIdHashReal.equals(rpIdHash)) {
- throw new Error('rpIdHash mismatch');
- }
+ if (!profile.twoFactorEnabled) {
+ throw new Error('2fa not enabled');
+ }
- const flags = attestation.authData[32];
+ const clientData = JSON.parse(ps.clientDataJSON);
- // eslint:disable-next-line:no-bitwise
- if (!(flags & 1)) {
- throw new Error('user not present');
- }
+ if (clientData.type !== 'webauthn.create') {
+ throw new Error('not a creation attestation');
+ }
+ if (clientData.origin !== this.config.scheme + '://' + this.config.host) {
+ throw new Error('origin mismatch');
+ }
- 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<number, any> = await cborDecodeFirst(publicKeyData);
- if (publicKey.get(3) !== -7) {
- throw new Error('alg mismatch');
- }
+ const clientDataJSONHash = this.twoFactorAuthenticationService.hash(Buffer.from(ps.clientDataJSON, 'utf-8'));
- if (!(procedures as any)[attestation.fmt]) {
- throw new Error('unsupported fmt');
- }
+ const attestation = await cborDecodeFirst(ps.attestationObject);
- 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 rpIdHash = attestation.authData.slice(0, 32);
+ if (!rpIdHashReal.equals(rpIdHash)) {
+ throw new Error('rpIdHash mismatch');
+ }
- const attestationChallenge = await AttestationChallenges.findOneBy({
- userId: user.id,
- id: ps.challengeId,
- registrationChallenge: true,
- challenge: hash(clientData.challenge).toString('hex'),
- });
+ const flags = attestation.authData[32];
- if (!attestationChallenge) {
- throw new Error('non-existent challenge');
- }
+ // eslint: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<number, any> = await cborDecodeFirst(publicKeyData);
+ if (publicKey.get(3) !== -7) {
+ throw new Error('alg mismatch');
+ }
+
+ const procedures = this.twoFactorAuthenticationService.getProcedures();
+
+ 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');
- await AttestationChallenges.delete({
- userId: user.id,
- id: ps.challengeId,
- });
+ const attestationChallenge = await this.attestationChallengesRepository.findOneBy({
+ userId: me.id,
+ id: ps.challengeId,
+ registrationChallenge: true,
+ challenge: this.twoFactorAuthenticationService.hash(clientData.challenge).toString('hex'),
+ });
- // Expired challenge (> 5min old)
- if (
- new Date().getTime() - attestationChallenge.createdAt.getTime() >=
+ if (!attestationChallenge) {
+ throw new Error('non-existent challenge');
+ }
+
+ await this.attestationChallengesRepository.delete({
+ userId: me.id,
+ id: ps.challengeId,
+ });
+
+ // Expired challenge (> 5min old)
+ if (
+ new Date().getTime() - attestationChallenge.createdAt.getTime() >=
5 * 60 * 1000
- ) {
- throw new Error('expired challenge');
- }
+ ) {
+ throw new Error('expired challenge');
+ }
- const credentialIdString = credentialId.toString('hex');
+ const credentialIdString = credentialId.toString('hex');
- await UserSecurityKeys.insert({
- userId: user.id,
- id: credentialIdString,
- lastUsed: new Date(),
- name: ps.name,
- publicKey: verificationData.publicKey.toString('hex'),
- });
+ await this.userSecurityKeysRepository.insert({
+ userId: me.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,
- }));
+ // Publish meUpdated event
+ this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, {
+ detail: true,
+ includeSecrets: true,
+ }));
- return {
- id: credentialIdString,
- name: ps.name,
- };
-});
+ 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
index 4bfa24f97f..3eb9f43c2b 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts
@@ -1,5 +1,7 @@
-import define from '../../../define.js';
-import { UserProfiles } from '@/models/index.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserProfilesRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
requireCredential: true,
@@ -16,8 +18,16 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- await UserProfiles.update(user.id, {
- usePasswordLessLogin: ps.value,
- });
-});
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ await this.userProfilesRepository.update(me.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
index e906b82043..df37db4c6a 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts
@@ -1,10 +1,12 @@
-import bcrypt from 'bcryptjs';
-import define from '../../../define.js';
-import { UserProfiles, AttestationChallenges } from '@/models/index.js';
import { promisify } from 'node:util';
import * as crypto from 'node:crypto';
-import { genId } from '@/misc/gen-id.js';
-import { hash } from '../../../2fa.js';
+import bcrypt from 'bcryptjs';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserProfilesRepository, AttestationChallengesRepository } from '@/models/index.js';
+import { IdService } from '@/core/IdService.js';
+import { TwoFactorAuthenticationService } from '@/core/TwoFactorAuthenticationService.js';
+import { DI } from '@/di-symbols.js';
const randomBytes = promisify(crypto.randomBytes);
@@ -23,39 +25,53 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
- // Compare password
- const same = await bcrypt.compare(ps.password, profile.password!);
+ @Inject(DI.attestationChallengesRepository)
+ private attestationChallengesRepository: AttestationChallengesRepository,
- if (!same) {
- throw new Error('incorrect password');
- }
+ private idService: IdService,
+ private twoFactorAuthenticationService: TwoFactorAuthenticationService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
- if (!profile.twoFactorEnabled) {
- throw new Error('2fa not enabled');
- }
+ // Compare password
+ const same = await bcrypt.compare(ps.password, profile.password!);
- // 32 byte challenge
- const entropy = await randomBytes(32);
- const challenge = entropy.toString('base64')
- .replace(/=/g, '')
- .replace(/\+/g, '-')
- .replace(/\//g, '_');
+ if (!same) {
+ throw new Error('incorrect password');
+ }
- const challengeId = genId();
+ if (!profile.twoFactorEnabled) {
+ throw new Error('2fa not enabled');
+ }
- await AttestationChallenges.insert({
- userId: user.id,
- id: challengeId,
- challenge: hash(Buffer.from(challenge, 'utf-8')).toString('hex'),
- createdAt: new Date(),
- registrationChallenge: true,
- });
+ // 32 byte challenge
+ const entropy = await randomBytes(32);
+ const challenge = entropy.toString('base64')
+ .replace(/=/g, '')
+ .replace(/\+/g, '-')
+ .replace(/\//g, '_');
- return {
- challengeId,
- challenge,
- };
-});
+ const challengeId = this.idService.genId();
+
+ await this.attestationChallengesRepository.insert({
+ userId: me.id,
+ id: challengeId,
+ challenge: this.twoFactorAuthenticationService.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
index 33f5717728..e20911f35e 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts
@@ -1,9 +1,11 @@
import bcrypt from 'bcryptjs';
import * as speakeasy from 'speakeasy';
import * as QRCode from 'qrcode';
-import config from '@/config/index.js';
-import { UserProfiles } from '@/models/index.js';
-import define from '../../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UserProfilesRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { DI } from '@/di-symbols.js';
+import { Config } from '@/config.js';
export const meta = {
requireCredential: true,
@@ -20,39 +22,50 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.config)
+ private config: Config,
- // Compare password
- const same = await bcrypt.compare(ps.password, profile.password!);
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
- if (!same) {
- throw new Error('incorrect password');
- }
+ // 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,
- });
+ // Generate user's secret key
+ const secret = speakeasy.generateSecret({
+ length: 32,
+ });
- await UserProfiles.update(user.id, {
- twoFactorTempSecret: secret.base32,
- });
+ await this.userProfilesRepository.update(me.id, {
+ twoFactorTempSecret: secret.base32,
+ });
- // Get the data URL of the authenticator URL
- const url = speakeasy.otpauthURL({
- secret: secret.base32,
- encoding: 'base32',
- label: user.username,
- issuer: config.host,
- });
- const dataUrl = await QRCode.toDataURL(url);
+ // Get the data URL of the authenticator URL
+ const url = speakeasy.otpauthURL({
+ secret: secret.base32,
+ encoding: 'base32',
+ label: me.username,
+ issuer: this.config.host,
+ });
+ const dataUrl = await QRCode.toDataURL(url);
- return {
- qr: dataUrl,
- url,
- secret: secret.base32,
- label: user.username,
- issuer: config.host,
- };
-});
+ return {
+ qr: dataUrl,
+ url,
+ secret: secret.base32,
+ label: me.username,
+ issuer: this.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
index eb2f75308d..1889dd7893 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts
@@ -1,7 +1,11 @@
import bcrypt from 'bcryptjs';
-import define from '../../../define.js';
-import { UserProfiles, UserSecurityKeys, Users } from '@/models/index.js';
-import { publishMainStream } from '@/services/stream.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserProfilesRepository, UserSecurityKeysRepository } from '@/models/index.js';
+import type { UsersRepository } from '@/models/index.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
requireCredential: true,
@@ -19,27 +23,41 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userSecurityKeysRepository)
+ private userSecurityKeysRepository: UserSecurityKeysRepository,
- // Compare password
- const same = await bcrypt.compare(ps.password, profile.password!);
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
- if (!same) {
- throw new Error('incorrect password');
- }
+ private userEntityService: UserEntityService,
+ private globalEventService: GlobalEventService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
+
+ // Compare password
+ const same = await bcrypt.compare(ps.password, profile.password!);
- // Make sure we only delete the user's own creds
- await UserSecurityKeys.delete({
- userId: user.id,
- id: ps.credentialId,
- });
+ if (!same) {
+ throw new Error('incorrect password');
+ }
- // Publish meUpdated event
- publishMainStream(user.id, 'meUpdated', await Users.pack(user.id, user, {
- detail: true,
- includeSecrets: true,
- }));
+ // Make sure we only delete the user's own creds
+ await this.userSecurityKeysRepository.delete({
+ userId: me.id,
+ id: ps.credentialId,
+ });
- return {};
-});
+ // Publish meUpdated event
+ this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, {
+ 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
index 45e7a98639..4607e5d981 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts
@@ -1,6 +1,8 @@
import bcrypt from 'bcryptjs';
-import define from '../../../define.js';
-import { UserProfiles } from '@/models/index.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserProfilesRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
requireCredential: true,
@@ -17,18 +19,26 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
- // Compare password
- const same = await bcrypt.compare(ps.password, profile.password!);
+ // Compare password
+ const same = await bcrypt.compare(ps.password, profile.password!);
- if (!same) {
- throw new Error('incorrect password');
- }
+ if (!same) {
+ throw new Error('incorrect password');
+ }
- await UserProfiles.update(user.id, {
- twoFactorSecret: null,
- twoFactorEnabled: false,
- });
-});
+ await this.userProfilesRepository.update(me.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
index eca9558847..8d5851659b 100644
--- a/packages/backend/src/server/api/endpoints/i/apps.ts
+++ b/packages/backend/src/server/api/endpoints/i/apps.ts
@@ -1,5 +1,7 @@
-import define from '../../define.js';
-import { AccessTokens } from '@/models/index.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { AccessTokensRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
requireCredential: true,
@@ -16,25 +18,33 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const query = AccessTokens.createQueryBuilder('token')
- .where('token.userId = :userId', { userId: user.id });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.accessTokensRepository)
+ private accessTokensRepository: AccessTokensRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.accessTokensRepository.createQueryBuilder('token')
+ .where('token.userId = :userId', { userId: me.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;
- }
+ 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();
+ 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,
- })));
-});
+ 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
index 68bd103a6d..a5592d20de 100644
--- a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts
+++ b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts
@@ -1,5 +1,8 @@
-import define from '../../define.js';
-import { AccessTokens, Apps } from '@/models/index.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { AccessTokensRepository } from '@/models/index.js';
+import { AppEntityService } from '@/core/entities/AppEntityService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
requireCredential: true,
@@ -12,26 +15,36 @@ export const paramDef = {
properties: {
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
offset: { type: 'integer', default: 0 },
- sort: { type: 'string', enum: ['desc', 'asc'], default: "desc" },
+ sort: { type: 'string', enum: ['desc', 'asc'], default: 'desc' },
},
required: [],
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, 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,
- },
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.accessTokensRepository)
+ private accessTokensRepository: AccessTokensRepository,
- return await Promise.all(tokens.map(token => Apps.pack(token.appId, user, {
- detail: true,
- })));
-});
+ private appEntityService: AppEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // Get tokens
+ const tokens = await this.accessTokensRepository.find({
+ where: {
+ userId: me.id,
+ },
+ take: ps.limit,
+ skip: ps.offset,
+ order: {
+ id: ps.sort === 'asc' ? 1 : -1,
+ },
+ });
+
+ return await Promise.all(tokens.map(token => this.appEntityService.pack(token.appId, me, {
+ 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
index f9f6a33a80..cc5b712ecf 100644
--- a/packages/backend/src/server/api/endpoints/i/change-password.ts
+++ b/packages/backend/src/server/api/endpoints/i/change-password.ts
@@ -1,6 +1,8 @@
import bcrypt from 'bcryptjs';
-import define from '../../define.js';
-import { UserProfiles } from '@/models/index.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserProfilesRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
requireCredential: true,
@@ -18,21 +20,29 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
- // Compare password
- const same = await bcrypt.compare(ps.currentPassword, profile.password!);
+ // Compare password
+ const same = await bcrypt.compare(ps.currentPassword, profile.password!);
- if (!same) {
- throw new Error('incorrect 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);
+ // 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,
- });
-});
+ await this.userProfilesRepository.update(me.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
index ede4a9d03b..a1804599df 100644
--- a/packages/backend/src/server/api/endpoints/i/delete-account.ts
+++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts
@@ -1,7 +1,9 @@
import bcrypt from 'bcryptjs';
-import { UserProfiles, Users } from '@/models/index.js';
-import { deleteAccount } from '@/services/delete-account.js';
-import define from '../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UsersRepository, UserProfilesRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { DeleteAccountService } from '@/core/DeleteAccountService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
requireCredential: true,
@@ -18,19 +20,32 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
- const userDetailed = await Users.findOneByOrFail({ id: user.id });
- if (userDetailed.isDeleted) {
- return;
- }
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- // Compare password
- const same = await bcrypt.compare(ps.password, profile.password!);
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
- if (!same) {
- throw new Error('incorrect password');
- }
+ private deleteAccountService: DeleteAccountService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
+ const userDetailed = await this.usersRepository.findOneByOrFail({ id: me.id });
+ if (userDetailed.isDeleted) {
+ return;
+ }
+
+ // Compare password
+ const same = await bcrypt.compare(ps.password, profile.password!);
- await deleteAccount(user);
-});
+ if (!same) {
+ throw new Error('incorrect password');
+ }
+
+ await this.deleteAccountService.deleteAccount(me);
+ });
+ }
+}
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 aed4c2e0a3..770708e685 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,7 @@
-import define from '../../define.js';
-import { createExportBlockingJob } from '@/queue/index.js';
+import { Inject, Injectable } from '@nestjs/common';
import ms from 'ms';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QueueService } from '@/core/QueueService.js';
export const meta = {
secure: true,
@@ -18,6 +19,13 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- createExportBlockingJob(user);
-});
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ private queueService: QueueService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ this.queueService.createExportBlockingJob(me);
+ });
+ }
+}
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 058d77b3c2..fcaa59b12d 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,7 @@
-import define from '../../define.js';
-import { createExportFollowingJob } from '@/queue/index.js';
+import { Inject, Injectable } from '@nestjs/common';
import ms from 'ms';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QueueService } from '@/core/QueueService.js';
export const meta = {
secure: true,
@@ -21,6 +22,13 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- createExportFollowingJob(user, ps.excludeMuting, ps.excludeInactive);
-});
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ private queueService: QueueService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ this.queueService.createExportFollowingJob(me, ps.excludeMuting, ps.excludeInactive);
+ });
+ }
+}
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 c0216fac0c..37bef0a117 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,7 @@
-import define from '../../define.js';
-import { createExportMuteJob } from '@/queue/index.js';
+import { Inject, Injectable } from '@nestjs/common';
import ms from 'ms';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QueueService } from '@/core/QueueService.js';
export const meta = {
secure: true,
@@ -18,6 +19,13 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- createExportMuteJob(user);
-});
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ private queueService: QueueService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ this.queueService.createExportMuteJob(me);
+ });
+ }
+}
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 4b85a45554..9d2505e403 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,7 @@
-import define from '../../define.js';
-import { createExportNotesJob } from '@/queue/index.js';
+import { Inject, Injectable } from '@nestjs/common';
import ms from 'ms';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QueueService } from '@/core/QueueService.js';
export const meta = {
secure: true,
@@ -18,6 +19,13 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- createExportNotesJob(user);
-});
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ private queueService: QueueService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ this.queueService.createExportNotesJob(me);
+ });
+ }
+}
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 fa5c1f5e5a..0f8e4bca76 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,7 @@
-import define from '../../define.js';
-import { createExportUserListsJob } from '@/queue/index.js';
+import { Inject, Injectable } from '@nestjs/common';
import ms from 'ms';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QueueService } from '@/core/QueueService.js';
export const meta = {
secure: true,
@@ -18,6 +19,13 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- createExportUserListsJob(user);
-});
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ private queueService: QueueService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ this.queueService.createExportUserListsJob(me);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/i/favorites.ts b/packages/backend/src/server/api/endpoints/i/favorites.ts
index 3c420e4d0f..350abd9f7b 100644
--- a/packages/backend/src/server/api/endpoints/i/favorites.ts
+++ b/packages/backend/src/server/api/endpoints/i/favorites.ts
@@ -1,6 +1,9 @@
-import define from '../../define.js';
-import { NoteFavorites } from '@/models/index.js';
-import { makePaginationQuery } from '../../common/make-pagination-query.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { NoteFavoritesRepository } from '@/models/index.js';
+import { QueryService } from '@/core/QueryService.js';
+import { NoteFavoriteEntityService } from '@/core/entities/NoteFavoriteEntityService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['account', 'notes', 'favorites'],
@@ -31,14 +34,25 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const query = makePaginationQuery(NoteFavorites.createQueryBuilder('favorite'), ps.sinceId, ps.untilId)
- .andWhere(`favorite.userId = :meId`, { meId: user.id })
- .leftJoinAndSelect('favorite.note', 'note');
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.noteFavoritesRepository)
+ private noteFavoritesRepository: NoteFavoritesRepository,
- const favorites = await query
- .take(ps.limit)
- .getMany();
+ private noteFavoriteEntityService: NoteFavoriteEntityService,
+ private queryService: QueryService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.queryService.makePaginationQuery(this.noteFavoritesRepository.createQueryBuilder('favorite'), ps.sinceId, ps.untilId)
+ .andWhere('favorite.userId = :meId', { meId: me.id })
+ .leftJoinAndSelect('favorite.note', 'note');
- return await NoteFavorites.packMany(favorites, user);
-});
+ const favorites = await query
+ .take(ps.limit)
+ .getMany();
+
+ return await this.noteFavoriteEntityService.packMany(favorites, me);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts
index a38383f30e..ff6bcc01ab 100644
--- a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts
+++ b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts
@@ -1,6 +1,9 @@
-import define from '../../../define.js';
-import { GalleryLikes } from '@/models/index.js';
-import { makePaginationQuery } from '../../../common/make-pagination-query.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { GalleryLikesRepository } from '@/models/index.js';
+import { QueryService } from '@/core/QueryService.js';
+import { GalleryLikeEntityService } from '@/core/entities/GalleryLikeEntityService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['account', 'gallery'],
@@ -27,7 +30,7 @@ export const meta = {
ref: 'GalleryPost',
},
},
- }
+ },
},
} as const;
@@ -42,14 +45,25 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const query = makePaginationQuery(GalleryLikes.createQueryBuilder('like'), ps.sinceId, ps.untilId)
- .andWhere(`like.userId = :meId`, { meId: user.id })
- .leftJoinAndSelect('like.post', 'post');
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.galleryLikesRepository)
+ private galleryLikesRepository: GalleryLikesRepository,
- const likes = await query
- .take(ps.limit)
- .getMany();
+ private galleryLikeEntityService: GalleryLikeEntityService,
+ private queryService: QueryService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.queryService.makePaginationQuery(this.galleryLikesRepository.createQueryBuilder('like'), ps.sinceId, ps.untilId)
+ .andWhere('like.userId = :meId', { meId: me.id })
+ .leftJoinAndSelect('like.post', 'post');
- return await GalleryLikes.packMany(likes, user);
-});
+ const likes = await query
+ .take(ps.limit)
+ .getMany();
+
+ return await this.galleryLikeEntityService.packMany(likes, me);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts
index b4edb5f73d..927be51f79 100644
--- a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts
+++ b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts
@@ -1,6 +1,9 @@
-import define from '../../../define.js';
-import { GalleryPosts } from '@/models/index.js';
-import { makePaginationQuery } from '../../../common/make-pagination-query.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { GalleryPostsRepository } from '@/models/index.js';
+import { QueryService } from '@/core/QueryService.js';
+import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['account', 'gallery'],
@@ -31,13 +34,24 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const query = makePaginationQuery(GalleryPosts.createQueryBuilder('post'), ps.sinceId, ps.untilId)
- .andWhere(`post.userId = :meId`, { meId: user.id });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.galleryPostsRepository)
+ private galleryPostsRepository: GalleryPostsRepository,
- const posts = await query
- .take(ps.limit)
- .getMany();
+ private galleryPostEntityService: GalleryPostEntityService,
+ private queryService: QueryService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.queryService.makePaginationQuery(this.galleryPostsRepository.createQueryBuilder('post'), ps.sinceId, ps.untilId)
+ .andWhere('post.userId = :meId', { meId: me.id });
- return await GalleryPosts.packMany(posts, user);
-});
+ const posts = await query
+ .take(ps.limit)
+ .getMany();
+
+ return await this.galleryPostEntityService.packMany(posts, me);
+ });
+ }
+}
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
index e7d7518c5b..0695abdd85 100644
--- 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
@@ -1,5 +1,7 @@
-import define from '../../define.js';
-import { MutedNotes } from '@/models/index.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { MutedNotesRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['account'],
@@ -27,11 +29,19 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- return {
- count: await MutedNotes.countBy({
- userId: user.id,
- reason: 'word',
- }),
- };
-});
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.mutedNotesRepository)
+ private mutedNotesRepository: MutedNotesRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ return {
+ count: await this.mutedNotesRepository.countBy({
+ userId: me.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
index 0bcbf37ddd..bfba1fc36f 100644
--- a/packages/backend/src/server/api/endpoints/i/import-blocking.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts
@@ -1,8 +1,10 @@
-import define from '../../define.js';
-import { createImportBlockingJob } from '@/queue/index.js';
+import { Inject, Injectable } from '@nestjs/common';
import ms from 'ms';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QueueService } from '@/core/QueueService.js';
+import { DriveFilesRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
-import { DriveFiles } from '@/models/index.js';
export const meta = {
secure: true,
@@ -49,13 +51,23 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const file = await DriveFiles.findOneBy({ id: ps.fileId });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.driveFilesRepository)
+ private driveFilesRepository: DriveFilesRepository,
- 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);
+ private queueService: QueueService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
- createImportBlockingJob(user, file.id);
-});
+ 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);
+
+ this.queueService.createImportBlockingJob(me, 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
index ee2abbea19..c7cb2e0330 100644
--- a/packages/backend/src/server/api/endpoints/i/import-following.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-following.ts
@@ -1,8 +1,10 @@
-import define from '../../define.js';
-import { createImportFollowingJob } from '@/queue/index.js';
+import { Inject, Injectable } from '@nestjs/common';
import ms from 'ms';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QueueService } from '@/core/QueueService.js';
+import { DriveFilesRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
-import { DriveFiles } from '@/models/index.js';
export const meta = {
secure: true,
@@ -48,13 +50,23 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const file = await DriveFiles.findOneBy({ id: ps.fileId });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.driveFilesRepository)
+ private driveFilesRepository: DriveFilesRepository,
- 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);
+ private queueService: QueueService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
- createImportFollowingJob(user, file.id);
-});
+ 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);
+
+ this.queueService.createImportFollowingJob(me, 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
index b3b3b39238..060c37c13f 100644
--- a/packages/backend/src/server/api/endpoints/i/import-muting.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts
@@ -1,8 +1,10 @@
-import define from '../../define.js';
-import { createImportMutingJob } from '@/queue/index.js';
+import { Inject, Injectable } from '@nestjs/common';
import ms from 'ms';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QueueService } from '@/core/QueueService.js';
+import { DriveFilesRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
-import { DriveFiles } from '@/models/index.js';
export const meta = {
secure: true,
@@ -49,13 +51,23 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const file = await DriveFiles.findOneBy({ id: ps.fileId });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.driveFilesRepository)
+ private driveFilesRepository: DriveFilesRepository,
- 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);
+ private queueService: QueueService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
- createImportMutingJob(user, file.id);
-});
+ 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);
+
+ this.queueService.createImportMutingJob(me, 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
index 64f5ec05fd..a5e17283e5 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
@@ -1,8 +1,10 @@
-import define from '../../define.js';
-import { createImportUserListsJob } from '@/queue/index.js';
+import { Inject, Injectable } from '@nestjs/common';
import ms from 'ms';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QueueService } from '@/core/QueueService.js';
+import { DriveFilesRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
-import { DriveFiles } from '@/models/index.js';
export const meta = {
secure: true,
@@ -48,13 +50,23 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const file = await DriveFiles.findOneBy({ id: ps.fileId });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.driveFilesRepository)
+ private driveFilesRepository: DriveFilesRepository,
- 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);
+ private queueService: QueueService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
- createImportUserListsJob(user, file.id);
-});
+ 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);
+
+ this.queueService.createImportUserListsJob(me, file.id);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts
index 2b343dabdd..96927dad49 100644
--- a/packages/backend/src/server/api/endpoints/i/notifications.ts
+++ b/packages/backend/src/server/api/endpoints/i/notifications.ts
@@ -1,10 +1,13 @@
import { Brackets } from 'typeorm';
-import { Notifications, Followings, Mutings, Users, UserProfiles } from '@/models/index.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { UsersRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository, NotificationsRepository } from '@/models/index.js';
import { notificationTypes } from '@/types.js';
-import read from '@/services/note/read.js';
-import { readNotification } from '../../common/read-notification.js';
-import define from '../../define.js';
-import { makePaginationQuery } from '../../common/make-pagination-query.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QueryService } from '@/core/QueryService.js';
+import { NoteReadService } from '@/core/NoteReadService.js';
+import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js';
+import { NotificationService } from '@/core/NotificationService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['account', 'notifications'],
@@ -49,96 +52,121 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- const mutingQuery = Mutings.createQueryBuilder('muting')
- .select('muting.muteeId')
- .where('muting.muterId = :muterId', { muterId: user.id });
+ @Inject(DI.followingsRepository)
+ private followingsRepository: FollowingsRepository,
- const mutingInstanceQuery = UserProfiles.createQueryBuilder('user_profile')
- .select('user_profile.mutedInstances')
- .where('user_profile.userId = :muterId', { muterId: user.id });
+ @Inject(DI.mutingsRepository)
+ private mutingsRepository: MutingsRepository,
- const suspendedQuery = Users.createQueryBuilder('users')
- .select('users.id')
- .where('users.isSuspended = TRUE');
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
- 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('notifier.avatar', 'notifierAvatar')
- .leftJoinAndSelect('notifier.banner', 'notifierBanner')
- .leftJoinAndSelect('note.user', 'user')
- .leftJoinAndSelect('user.avatar', 'avatar')
- .leftJoinAndSelect('user.banner', 'banner')
- .leftJoinAndSelect('note.reply', 'reply')
- .leftJoinAndSelect('note.renote', 'renote')
- .leftJoinAndSelect('reply.user', 'replyUser')
- .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
- .leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
- .leftJoinAndSelect('renote.user', 'renoteUser')
- .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
- .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
+ @Inject(DI.notificationsRepository)
+ private notificationsRepository: NotificationsRepository,
- // muted users
- query.andWhere(new Brackets(qb => { qb
- .where(`notification.notifierId NOT IN (${ mutingQuery.getQuery() })`)
- .orWhere('notification.notifierId IS NULL');
- }));
- query.setParameters(mutingQuery.getParameters());
+ private notificationEntityService: NotificationEntityService,
+ private notificationService: NotificationService,
+ private queryService: QueryService,
+ private noteReadService: NoteReadService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // includeTypes が空の場合はクエリしない
+ if (ps.includeTypes && ps.includeTypes.length === 0) {
+ return [];
+ }
+ // excludeTypes に全指定されている場合はクエリしない
+ if (notificationTypes.every(type => ps.excludeTypes?.includes(type))) {
+ return [];
+ }
+ const followingQuery = this.followingsRepository.createQueryBuilder('following')
+ .select('following.followeeId')
+ .where('following.followerId = :followerId', { followerId: me.id });
- // muted instances
- query.andWhere(new Brackets(qb => { qb
- .andWhere('notifier.host IS NULL')
- .orWhere(`NOT (( ${mutingInstanceQuery.getQuery()} )::jsonb ? notifier.host)`);
- }));
- query.setParameters(mutingInstanceQuery.getParameters());
+ const mutingQuery = this.mutingsRepository.createQueryBuilder('muting')
+ .select('muting.muteeId')
+ .where('muting.muterId = :muterId', { muterId: me.id });
- // suspended users
- query.andWhere(new Brackets(qb => { qb
- .where(`notification.notifierId NOT IN (${ suspendedQuery.getQuery() })`)
- .orWhere('notification.notifierId IS NULL');
- }));
+ const mutingInstanceQuery = this.userProfilesRepository.createQueryBuilder('user_profile')
+ .select('user_profile.mutedInstances')
+ .where('user_profile.userId = :muterId', { muterId: me.id });
- if (ps.following) {
- query.andWhere(`((notification.notifierId IN (${ followingQuery.getQuery() })) OR (notification.notifierId = :meId))`, { meId: user.id });
- query.setParameters(followingQuery.getParameters());
- }
+ const suspendedQuery = this.usersRepository.createQueryBuilder('users')
+ .select('users.id')
+ .where('users.isSuspended = TRUE');
- 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 });
- }
+ const query = this.queryService.makePaginationQuery(this.notificationsRepository.createQueryBuilder('notification'), ps.sinceId, ps.untilId)
+ .andWhere('notification.notifieeId = :meId', { meId: me.id })
+ .leftJoinAndSelect('notification.notifier', 'notifier')
+ .leftJoinAndSelect('notification.note', 'note')
+ .leftJoinAndSelect('notifier.avatar', 'notifierAvatar')
+ .leftJoinAndSelect('notifier.banner', 'notifierBanner')
+ .leftJoinAndSelect('note.user', 'user')
+ .leftJoinAndSelect('user.avatar', 'avatar')
+ .leftJoinAndSelect('user.banner', 'banner')
+ .leftJoinAndSelect('note.reply', 'reply')
+ .leftJoinAndSelect('note.renote', 'renote')
+ .leftJoinAndSelect('reply.user', 'replyUser')
+ .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar')
+ .leftJoinAndSelect('replyUser.banner', 'replyUserBanner')
+ .leftJoinAndSelect('renote.user', 'renoteUser')
+ .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar')
+ .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner');
- if (ps.unreadOnly) {
- query.andWhere('notification.isRead = false');
- }
+ // muted users
+ query.andWhere(new Brackets(qb => { qb
+ .where(`notification.notifierId NOT IN (${ mutingQuery.getQuery() })`)
+ .orWhere('notification.notifierId IS NULL');
+ }));
+ query.setParameters(mutingQuery.getParameters());
- const notifications = await query.take(ps.limit).getMany();
+ // muted instances
+ query.andWhere(new Brackets(qb => { qb
+ .andWhere('notifier.host IS NULL')
+ .orWhere(`NOT (( ${mutingInstanceQuery.getQuery()} )::jsonb ? notifier.host)`);
+ }));
+ query.setParameters(mutingInstanceQuery.getParameters());
- // Mark all as read
- if (notifications.length > 0 && ps.markAsRead) {
- readNotification(user.id, notifications.map(x => x.id));
- }
+ // suspended users
+ query.andWhere(new Brackets(qb => { qb
+ .where(`notification.notifierId NOT IN (${ suspendedQuery.getQuery() })`)
+ .orWhere('notification.notifierId IS NULL');
+ }));
- const notes = notifications.filter(notification => ['mention', 'reply', 'quote'].includes(notification.type)).map(notification => notification.note!);
+ if (ps.following) {
+ query.andWhere(`((notification.notifierId IN (${ followingQuery.getQuery() })) OR (notification.notifierId = :meId))`, { meId: me.id });
+ query.setParameters(followingQuery.getParameters());
+ }
- if (notes.length > 0) {
- read(user.id, notes);
- }
+ 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 });
+ }
- return await Notifications.packMany(notifications, user.id);
-});
+ 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) {
+ this.notificationService.readNotification(me.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) {
+ this.noteReadService.read(me.id, notes);
+ }
+
+ return await this.notificationEntityService.packMany(notifications, me.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
index 71e326e2f0..9a909eedf4 100644
--- a/packages/backend/src/server/api/endpoints/i/page-likes.ts
+++ b/packages/backend/src/server/api/endpoints/i/page-likes.ts
@@ -1,6 +1,9 @@
-import define from '../../define.js';
-import { PageLikes } from '@/models/index.js';
-import { makePaginationQuery } from '../../common/make-pagination-query.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { PageLikesRepository } from '@/models/index.js';
+import { QueryService } from '@/core/QueryService.js';
+import { PageLikeEntityService } from '@/core/entities/PageLikeEntityService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['account', 'pages'],
@@ -26,7 +29,7 @@ export const meta = {
ref: 'Page',
},
},
- }
+ },
},
} as const;
@@ -41,14 +44,25 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const query = makePaginationQuery(PageLikes.createQueryBuilder('like'), ps.sinceId, ps.untilId)
- .andWhere(`like.userId = :meId`, { meId: user.id })
- .leftJoinAndSelect('like.page', 'page');
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.pageLikesRepository)
+ private pageLikesRepository: PageLikesRepository,
- const likes = await query
- .take(ps.limit)
- .getMany();
+ private pageLikeEntityService: PageLikeEntityService,
+ private queryService: QueryService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.queryService.makePaginationQuery(this.pageLikesRepository.createQueryBuilder('like'), ps.sinceId, ps.untilId)
+ .andWhere('like.userId = :meId', { meId: me.id })
+ .leftJoinAndSelect('like.page', 'page');
- return PageLikes.packMany(likes, user);
-});
+ const likes = await query
+ .take(ps.limit)
+ .getMany();
+
+ return this.pageLikeEntityService.packMany(likes, me);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/i/pages.ts b/packages/backend/src/server/api/endpoints/i/pages.ts
index f28aed3fd4..7c4e4a6c7d 100644
--- a/packages/backend/src/server/api/endpoints/i/pages.ts
+++ b/packages/backend/src/server/api/endpoints/i/pages.ts
@@ -1,6 +1,9 @@
-import define from '../../define.js';
-import { Pages } from '@/models/index.js';
-import { makePaginationQuery } from '../../common/make-pagination-query.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { PagesRepository } from '@/models/index.js';
+import { QueryService } from '@/core/QueryService.js';
+import { PageEntityService } from '@/core/entities/PageEntityService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['account', 'pages'],
@@ -31,13 +34,24 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const query = makePaginationQuery(Pages.createQueryBuilder('page'), ps.sinceId, ps.untilId)
- .andWhere(`page.userId = :meId`, { meId: user.id });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.pagesRepository)
+ private pagesRepository: PagesRepository,
- const pages = await query
- .take(ps.limit)
- .getMany();
+ private pageEntityService: PageEntityService,
+ private queryService: QueryService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.queryService.makePaginationQuery(this.pagesRepository.createQueryBuilder('page'), ps.sinceId, ps.untilId)
+ .andWhere('page.userId = :meId', { meId: me.id });
- return await Pages.packMany(pages);
-});
+ const pages = await query
+ .take(ps.limit)
+ .getMany();
+
+ return await this.pageEntityService.packMany(pages);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/i/pin.ts b/packages/backend/src/server/api/endpoints/i/pin.ts
index 67b7026be1..f31b0dc35e 100644
--- a/packages/backend/src/server/api/endpoints/i/pin.ts
+++ b/packages/backend/src/server/api/endpoints/i/pin.ts
@@ -1,7 +1,9 @@
-import { addPinned } from '@/services/i/pin.js';
-import define from '../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import type { UsersRepository } from '@/models/index.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { NotePiningService } from '@/core/NotePiningService.js';
import { ApiError } from '../../error.js';
-import { Users } from '@/models/index.js';
export const meta = {
tags: ['account', 'notes'],
@@ -46,15 +48,23 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, 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;
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ private userEntityService: UserEntityService,
+ private notePiningService: NotePiningService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ await this.notePiningService.addPinned(me, ps.noteId).catch(err => {
+ if (err.id === '70c4e51f-5bea-449c-a030-53bee3cce202') throw new ApiError(meta.errors.noSuchNote);
+ if (err.id === '15a018eb-58e5-4da1-93be-330fcc5e4e1a') throw new ApiError(meta.errors.pinLimitExceeded);
+ if (err.id === '23f0cf4e-59a3-4276-a91d-61a5891c1514') throw new ApiError(meta.errors.alreadyPinned);
+ throw err;
+ });
- return await Users.pack<true, true>(user.id, user, {
- detail: true,
- });
-});
+ return await this.userEntityService.pack<true, true>(me.id, me, {
+ 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
index 7ff6409caf..36c3566f55 100644
--- 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
@@ -1,6 +1,8 @@
-import { publishMainStream } from '@/services/stream.js';
-import define from '../../define.js';
-import { MessagingMessages, UserGroupJoinings } from '@/models/index.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { MessagingMessagesRepository, UserGroupJoiningsRepository } from '@/models/index.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['account', 'messaging'],
@@ -17,25 +19,38 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- // Update documents
- await MessagingMessages.update({
- recipientId: user.id,
- isRead: false,
- }, {
- isRead: true,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.messagingMessagesRepository)
+ private messagingMessagesRepository: MessagingMessagesRepository,
- const joinings = await UserGroupJoinings.findBy({ userId: user.id });
+ @Inject(DI.userGroupJoiningsRepository)
+ private userGroupJoiningsRepository: UserGroupJoiningsRepository,
- 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()));
+ private globalEventService: GlobalEventService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // Update documents
+ await this.messagingMessagesRepository.update({
+ recipientId: me.id,
+ isRead: false,
+ }, {
+ isRead: true,
+ });
- publishMainStream(user.id, 'readAllMessagingMessages');
-});
+ const joinings = await this.userGroupJoiningsRepository.findBy({ userId: me.id });
+
+ await Promise.all(joinings.map(j => this.messagingMessagesRepository.createQueryBuilder().update()
+ .set({
+ reads: (() => `array_append("reads", '${me.id}')`) as any,
+ })
+ .where('groupId = :groupId', { groupId: j.userGroupId })
+ .andWhere('userId != :userId', { userId: me.id })
+ .andWhere('NOT (:userId = ANY(reads))', { userId: me.id })
+ .execute()));
+
+ this.globalEventService.publishMainStream(me.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
index 49f3deb331..b4bb83c6eb 100644
--- 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
@@ -1,6 +1,8 @@
-import { publishMainStream } from '@/services/stream.js';
-import define from '../../define.js';
-import { NoteUnreads } from '@/models/index.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { NoteUnreadsRepository } from '@/models/index.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['account'],
@@ -17,13 +19,23 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- // Remove documents
- await NoteUnreads.delete({
- userId: user.id,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.noteUnreadsRepository)
+ private noteUnreadsRepository: NoteUnreadsRepository,
- // 全て既読になったイベントを発行
- publishMainStream(user.id, 'readAllUnreadMentions');
- publishMainStream(user.id, 'readAllUnreadSpecifiedNotes');
-});
+ private globalEventService: GlobalEventService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // Remove documents
+ await this.noteUnreadsRepository.delete({
+ userId: me.id,
+ });
+
+ // 全て既読になったイベントを発行
+ this.globalEventService.publishMainStream(me.id, 'readAllUnreadMentions');
+ this.globalEventService.publishMainStream(me.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
index 45b6e98c86..5a7909674f 100644
--- a/packages/backend/src/server/api/endpoints/i/read-announcement.ts
+++ b/packages/backend/src/server/api/endpoints/i/read-announcement.ts
@@ -1,8 +1,12 @@
-import define from '../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { IdService } from '@/core/IdService.js';
+import { AnnouncementReadsRepository, AnnouncementsRepository } from '@/models/index.js';
+import type { UsersRepository } from '@/models/index.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
-import { genId } from '@/misc/gen-id.js';
-import { AnnouncementReads, Announcements, Users } from '@/models/index.js';
-import { publishMainStream } from '@/services/stream.js';
export const meta = {
tags: ['account'],
@@ -29,33 +33,48 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- // Check if announcement exists
- const announcement = await Announcements.findOneBy({ id: ps.announcementId });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.announcementsRepository)
+ private announcementsRepository: AnnouncementsRepository,
- if (announcement == null) {
- throw new ApiError(meta.errors.noSuchAnnouncement);
- }
+ @Inject(DI.announcementReadsRepository)
+ private announcementReadsRepository: AnnouncementReadsRepository,
- // Check if already read
- const read = await AnnouncementReads.findOneBy({
- announcementId: ps.announcementId,
- userId: user.id,
- });
+ private userEntityService: UserEntityService,
+ private idService: IdService,
+ private globalEventService: GlobalEventService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ // Check if announcement exists
+ const announcement = await this.announcementsRepository.findOneBy({ id: ps.announcementId });
- if (read != null) {
- return;
- }
+ if (announcement == null) {
+ throw new ApiError(meta.errors.noSuchAnnouncement);
+ }
+
+ // Check if already read
+ const read = await this.announcementReadsRepository.findOneBy({
+ announcementId: ps.announcementId,
+ userId: me.id,
+ });
+
+ if (read != null) {
+ return;
+ }
- // Create read
- await AnnouncementReads.insert({
- id: genId(),
- createdAt: new Date(),
- announcementId: ps.announcementId,
- userId: user.id,
- });
+ // Create read
+ await this.announcementReadsRepository.insert({
+ id: this.idService.genId(),
+ createdAt: new Date(),
+ announcementId: ps.announcementId,
+ userId: me.id,
+ });
- if (!await Users.getHasUnreadAnnouncement(user.id)) {
- publishMainStream(user.id, 'readAllAnnouncements');
+ if (!await this.userEntityService.getHasUnreadAnnouncement(me.id)) {
+ this.globalEventService.publishMainStream(me.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
index af929b04e8..7796fd97cb 100644
--- a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
+++ b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
@@ -1,8 +1,10 @@
import bcrypt from 'bcryptjs';
-import { publishInternalEvent, publishMainStream, publishUserEvent } from '@/services/stream.js';
-import generateUserToken from '../../common/generate-native-user-token.js';
-import define from '../../define.js';
-import { Users, UserProfiles } from '@/models/index.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UsersRepository, UserProfilesRepository } from '@/models/index.js';
+import generateUserToken from '@/misc/generate-native-user-token.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
requireCredential: true,
@@ -19,31 +21,44 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const freshUser = await Users.findOneByOrFail({ id: user.id });
- const oldToken = freshUser.token;
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
- // Compare password
- const same = await bcrypt.compare(ps.password, profile.password!);
+ private globalEventService: GlobalEventService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const freshUser = await this.usersRepository.findOneByOrFail({ id: me.id });
+ const oldToken = freshUser.token;
- if (!same) {
- throw new Error('incorrect password');
- }
+ const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
+
+ // Compare password
+ const same = await bcrypt.compare(ps.password, profile.password!);
- const newToken = generateUserToken();
+ if (!same) {
+ throw new Error('incorrect password');
+ }
- await Users.update(user.id, {
- token: newToken,
- });
+ const newToken = generateUserToken();
- // Publish event
- publishInternalEvent('userTokenRegenerated', { id: user.id, oldToken, newToken });
- publishMainStream(user.id, 'myTokenRegenerated');
+ await this.usersRepository.update(me.id, {
+ token: newToken,
+ });
- // Terminate streaming
- setTimeout(() => {
- publishUserEvent(user.id, 'terminate', {});
- }, 5000);
-});
+ // Publish event
+ this.globalEventService.publishInternalEvent('userTokenRegenerated', { id: me.id, oldToken, newToken });
+ this.globalEventService.publishMainStream(me.id, 'myTokenRegenerated');
+
+ // Terminate streaming
+ setTimeout(() => {
+ this.globalEventService.publishUserEvent(me.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
index d0b16dbc48..3b4db5fae3 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/get-all.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/get-all.ts
@@ -1,5 +1,7 @@
-import define from '../../../define.js';
-import { RegistryItems } from '@/models/index.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { RegistryItemsRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
requireCredential: true,
@@ -18,19 +20,27 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.registryItemsRepository)
+ private registryItemsRepository: RegistryItemsRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.registryItemsRepository.createQueryBuilder('item')
+ .where('item.domain IS NULL')
+ .andWhere('item.userId = :userId', { userId: me.id })
+ .andWhere('item.scope = :scope', { scope: ps.scope });
- const items = await query.getMany();
+ const items = await query.getMany();
- const res = {} as Record<string, any>;
+ const res = {} as Record<string, any>;
- for (const item of items) {
- res[item.key] = item.value;
- }
+ for (const item of items) {
+ res[item.key] = item.value;
+ }
- return res;
-});
+ 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
index cc5d5a8c6f..d24dff95b0 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts
@@ -1,5 +1,7 @@
-import define from '../../../define.js';
-import { RegistryItems } from '@/models/index.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { RegistryItemsRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../error.js';
export const meta = {
@@ -28,21 +30,29 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.registryItemsRepository)
+ private registryItemsRepository: RegistryItemsRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.registryItemsRepository.createQueryBuilder('item')
+ .where('item.domain IS NULL')
+ .andWhere('item.userId = :userId', { userId: me.id })
+ .andWhere('item.key = :key', { key: ps.key })
+ .andWhere('item.scope = :scope', { scope: ps.scope });
- const item = await query.getOne();
+ const item = await query.getOne();
- if (item == null) {
- throw new ApiError(meta.errors.noSuchKey);
- }
+ if (item == null) {
+ throw new ApiError(meta.errors.noSuchKey);
+ }
- return {
- updatedAt: item.updatedAt,
- value: item.value,
- };
-});
+ 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
index a79319744c..98d94a4c02 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/get.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/get.ts
@@ -1,5 +1,7 @@
-import define from '../../../define.js';
-import { RegistryItems } from '@/models/index.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { RegistryItemsRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../error.js';
export const meta = {
@@ -28,18 +30,26 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.registryItemsRepository)
+ private registryItemsRepository: RegistryItemsRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.registryItemsRepository.createQueryBuilder('item')
+ .where('item.domain IS NULL')
+ .andWhere('item.userId = :userId', { userId: me.id })
+ .andWhere('item.key = :key', { key: ps.key })
+ .andWhere('item.scope = :scope', { scope: ps.scope });
- const item = await query.getOne();
+ const item = await query.getOne();
- if (item == null) {
- throw new ApiError(meta.errors.noSuchKey);
- }
+ if (item == null) {
+ throw new ApiError(meta.errors.noSuchKey);
+ }
- return item.value;
-});
+ 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
index ac209c06a6..d1a05d9d06 100644
--- 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
@@ -1,5 +1,7 @@
-import define from '../../../define.js';
-import { RegistryItems } from '@/models/index.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { RegistryItemsRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
requireCredential: true,
@@ -18,19 +20,25 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.registryItemsRepository)
+ private registryItemsRepository: RegistryItemsRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.registryItemsRepository.createQueryBuilder('item')
+ .where('item.domain IS NULL')
+ .andWhere('item.userId = :userId', { userId: me.id })
+ .andWhere('item.scope = :scope', { scope: ps.scope });
- const items = await query.getMany();
+ const items = await query.getMany();
- const res = {} as Record<string, string>;
+ const res = {} as Record<string, string>;
- for (const item of items) {
- const type = typeof item.value;
- res[item.key] =
+ 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' :
@@ -38,7 +46,9 @@ export default define(meta, paramDef, async (ps, user) => {
type === 'boolean' ? 'boolean' :
type === 'object' ? 'object' :
null as never;
- }
+ }
- return res;
-});
+ 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
index 5ea1a9d344..6df5f4ecc3 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/keys.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/keys.ts
@@ -1,5 +1,7 @@
-import define from '../../../define.js';
-import { RegistryItems } from '@/models/index.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { RegistryItemsRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
requireCredential: true,
@@ -18,14 +20,22 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.registryItemsRepository)
+ private registryItemsRepository: RegistryItemsRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.registryItemsRepository.createQueryBuilder('item')
+ .select('item.key')
+ .where('item.domain IS NULL')
+ .andWhere('item.userId = :userId', { userId: me.id })
+ .andWhere('item.scope = :scope', { scope: ps.scope });
- const items = await query.getMany();
+ const items = await query.getMany();
- return items.map(x => x.key);
-});
+ 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
index 92473654c6..b5870f099d 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/remove.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/remove.ts
@@ -1,5 +1,7 @@
-import define from '../../../define.js';
-import { RegistryItems } from '@/models/index.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { RegistryItemsRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../error.js';
export const meta = {
@@ -28,18 +30,26 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.registryItemsRepository)
+ private registryItemsRepository: RegistryItemsRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.registryItemsRepository.createQueryBuilder('item')
+ .where('item.domain IS NULL')
+ .andWhere('item.userId = :userId', { userId: me.id })
+ .andWhere('item.key = :key', { key: ps.key })
+ .andWhere('item.scope = :scope', { scope: ps.scope });
- const item = await query.getOne();
+ const item = await query.getOne();
- if (item == null) {
- throw new ApiError(meta.errors.noSuchKey);
- }
+ if (item == null) {
+ throw new ApiError(meta.errors.noSuchKey);
+ }
- await RegistryItems.remove(item);
-});
+ await this.registryItemsRepository.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
index de4b313e25..58085ddbc5 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/scopes.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/scopes.ts
@@ -1,5 +1,7 @@
-import define from '../../../define.js';
-import { RegistryItems } from '@/models/index.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { RegistryItemsRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
requireCredential: true,
@@ -14,20 +16,28 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const query = RegistryItems.createQueryBuilder('item')
- .select('item.scope')
- .where('item.domain IS NULL')
- .andWhere('item.userId = :userId', { userId: user.id });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.registryItemsRepository)
+ private registryItemsRepository: RegistryItemsRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.registryItemsRepository.createQueryBuilder('item')
+ .select('item.scope')
+ .where('item.domain IS NULL')
+ .andWhere('item.userId = :userId', { userId: me.id });
- const items = await query.getMany();
+ const items = await query.getMany();
- const res = [] as string[][];
+ const res = [] as string[][];
- for (const item of items) {
- if (res.some(scope => scope.join('.') === item.scope.join('.'))) continue;
- res.push(item.scope);
- }
+ for (const item of items) {
+ if (res.some(scope => scope.join('.') === item.scope.join('.'))) continue;
+ res.push(item.scope);
+ }
- return res;
-});
+ 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
index d380b428a3..585aac2e01 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/set.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/set.ts
@@ -1,7 +1,9 @@
-import { publishMainStream } from '@/services/stream.js';
-import define from '../../../define.js';
-import { RegistryItems } from '@/models/index.js';
-import { genId } from '@/misc/gen-id.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { RegistryItemsRepository } from '@/models/index.js';
+import { IdService } from '@/core/IdService.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
requireCredential: true,
@@ -22,37 +24,48 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, 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 });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.registryItemsRepository)
+ private registryItemsRepository: RegistryItemsRepository,
- const existingItem = await query.getOne();
+ private idService: IdService,
+ private globalEventService: GlobalEventService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.registryItemsRepository.createQueryBuilder('item')
+ .where('item.domain IS NULL')
+ .andWhere('item.userId = :userId', { userId: me.id })
+ .andWhere('item.key = :key', { key: ps.key })
+ .andWhere('item.scope = :scope', { scope: ps.scope });
- 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,
+ const existingItem = await query.getOne();
+
+ if (existingItem) {
+ await this.registryItemsRepository.update(existingItem.id, {
+ updatedAt: new Date(),
+ value: ps.value,
+ });
+ } else {
+ await this.registryItemsRepository.insert({
+ id: this.idService.genId(),
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ userId: me.id,
+ domain: null,
+ scope: ps.scope,
+ key: ps.key,
+ value: ps.value,
+ });
+ }
+
+ // TODO: サードパーティアプリが傍受出来てしまうのでどうにかする
+ this.globalEventService.publishMainStream(me.id, 'registryUpdated', {
+ 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
index c692453794..86a82e6a6c 100644
--- a/packages/backend/src/server/api/endpoints/i/revoke-token.ts
+++ b/packages/backend/src/server/api/endpoints/i/revoke-token.ts
@@ -1,6 +1,8 @@
-import define from '../../define.js';
-import { AccessTokens } from '@/models/index.js';
-import { publishUserEvent } from '@/services/stream.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { AccessTokensRepository } from '@/models/index.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
requireCredential: true,
@@ -17,16 +19,26 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const token = await AccessTokens.findOneBy({ id: ps.tokenId });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.accessTokensRepository)
+ private accessTokensRepository: AccessTokensRepository,
- if (token) {
- await AccessTokens.delete({
- id: ps.tokenId,
- userId: user.id,
- });
+ private globalEventService: GlobalEventService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const token = await this.accessTokensRepository.findOneBy({ id: ps.tokenId });
+
+ if (token) {
+ await this.accessTokensRepository.delete({
+ id: ps.tokenId,
+ userId: me.id,
+ });
- // Terminate streaming
- publishUserEvent(user.id, 'terminate');
+ // Terminate streaming
+ this.globalEventService.publishUserEvent(me.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
index ca37411662..410cd72065 100644
--- a/packages/backend/src/server/api/endpoints/i/signin-history.ts
+++ b/packages/backend/src/server/api/endpoints/i/signin-history.ts
@@ -1,6 +1,9 @@
-import define from '../../define.js';
-import { Signins } from '@/models/index.js';
-import { makePaginationQuery } from '../../common/make-pagination-query.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { SigninsRepository } from '@/models/index.js';
+import { QueryService } from '@/core/QueryService.js';
+import { SigninEntityService } from '@/core/entities/SigninEntityService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
requireCredential: true,
@@ -19,11 +22,22 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const query = makePaginationQuery(Signins.createQueryBuilder('signin'), ps.sinceId, ps.untilId)
- .andWhere(`signin.userId = :meId`, { meId: user.id });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.signinsRepository)
+ private signinsRepository: SigninsRepository,
- const history = await query.take(ps.limit).getMany();
+ private signinEntityService: SigninEntityService,
+ private queryService: QueryService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.queryService.makePaginationQuery(this.signinsRepository.createQueryBuilder('signin'), ps.sinceId, ps.untilId)
+ .andWhere('signin.userId = :meId', { meId: me.id });
- return await Promise.all(history.map(record => Signins.pack(record)));
-});
+ const history = await query.take(ps.limit).getMany();
+
+ return await Promise.all(history.map(record => this.signinEntityService.pack(record)));
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/i/unpin.ts b/packages/backend/src/server/api/endpoints/i/unpin.ts
index 9912689da5..9a735e1168 100644
--- a/packages/backend/src/server/api/endpoints/i/unpin.ts
+++ b/packages/backend/src/server/api/endpoints/i/unpin.ts
@@ -1,7 +1,9 @@
-import { removePinned } from '@/services/i/pin.js';
-import define from '../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import type { UsersRepository } from '@/models/index.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { NotePiningService } from '@/core/NotePiningService.js';
import { ApiError } from '../../error.js';
-import { Users } from '@/models/index.js';
export const meta = {
tags: ['account', 'notes'],
@@ -34,13 +36,21 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, 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;
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ private userEntityService: UserEntityService,
+ private notePiningService: NotePiningService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ await this.notePiningService.removePinned(me, ps.noteId).catch(err => {
+ if (err.id === 'b302d4cf-c050-400a-bbb3-be208681f40c') throw new ApiError(meta.errors.noSuchNote);
+ throw err;
+ });
- return await Users.pack<true, true>(user.id, user, {
- detail: true,
- });
-});
+ return await this.userEntityService.pack<true, true>(me.id, me, {
+ 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
index 3318078523..719cc14f09 100644
--- a/packages/backend/src/server/api/endpoints/i/update-email.ts
+++ b/packages/backend/src/server/api/endpoints/i/update-email.ts
@@ -1,13 +1,15 @@
-import { publishMainStream } from '@/services/stream.js';
-import define from '../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
import rndstr from 'rndstr';
-import config from '@/config/index.js';
import ms from 'ms';
import bcrypt from 'bcryptjs';
-import { Users, UserProfiles } from '@/models/index.js';
-import { sendEmail } from '@/services/send-email.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UsersRepository, UserProfilesRepository } from '@/models/index.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { EmailService } from '@/core/EmailService.js';
+import { Config } from '@/config.js';
+import { DI } from '@/di-symbols.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
import { ApiError } from '../../error.js';
-import { validateEmailForAccount } from '@/services/validate-email-for-account.js';
export const meta = {
requireCredential: true,
@@ -44,50 +46,68 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.config)
+ private config: Config,
- // Compare password
- const same = await bcrypt.compare(ps.password, profile.password!);
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- if (!same) {
- throw new ApiError(meta.errors.incorrectPassword);
- }
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
- if (ps.email != null) {
- const available = await validateEmailForAccount(ps.email);
- if (!available) {
- throw new ApiError(meta.errors.unavailable);
- }
- }
+ private userEntityService: UserEntityService,
+ private emailService: EmailService,
+ private globalEventService: GlobalEventService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const profile = await this.userProfilesRepository.findOneByOrFail({ userId: me.id });
- await UserProfiles.update(user.id, {
- email: ps.email,
- emailVerified: false,
- emailVerifyCode: null,
- });
+ // Compare password
+ const same = await bcrypt.compare(ps.password, profile.password!);
- const iObj = await Users.pack(user.id, user, {
- detail: true,
- includeSecrets: true,
- });
+ if (!same) {
+ throw new ApiError(meta.errors.incorrectPassword);
+ }
- // Publish meUpdated event
- publishMainStream(user.id, 'meUpdated', iObj);
+ if (ps.email != null) {
+ const available = await this.emailService.validateEmailForAccount(ps.email);
+ if (!available) {
+ throw new ApiError(meta.errors.unavailable);
+ }
+ }
- if (ps.email != null) {
- const code = rndstr('a-z0-9', 16);
+ await this.userProfilesRepository.update(me.id, {
+ email: ps.email,
+ emailVerified: false,
+ emailVerifyCode: null,
+ });
- await UserProfiles.update(user.id, {
- emailVerifyCode: code,
- });
+ const iObj = await this.userEntityService.pack(me.id, me, {
+ detail: true,
+ includeSecrets: true,
+ });
- const link = `${config.url}/verify-email/${code}`;
+ // Publish meUpdated event
+ this.globalEventService.publishMainStream(me.id, 'meUpdated', iObj);
- sendEmail(ps.email, 'Email verification',
- `To verify email, please click this link:<br><a href="${link}">${link}</a>`,
- `To verify email, please click this link: ${link}`);
- }
+ if (ps.email != null) {
+ const code = rndstr('a-z0-9', 16);
+
+ await this.userProfilesRepository.update(me.id, {
+ emailVerifyCode: code,
+ });
- return iObj;
-});
+ const link = `${this.config.url}/verify-email/${code}`;
+
+ this.emailService.sendEmail(ps.email, 'Email verification',
+ `To verify email, please click this link:<br><a href="${link}">${link}</a>`,
+ `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
index 3c2f1cea0d..4b904d4696 100644
--- a/packages/backend/src/server/api/endpoints/i/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/update.ts
@@ -1,19 +1,23 @@
import RE2 from 're2';
import * as mfm from 'mfm-js';
-import { publishMainStream, publishUserEvent } from '@/services/stream.js';
-import acceptAllFollowRequests from '@/services/following/requests/accept-all.js';
-import { publishToFollowers } from '@/services/i/update.js';
+import { Inject, Injectable } from '@nestjs/common';
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
import { extractHashtags } from '@/misc/extract-hashtags.js';
-import { updateUsertags } from '@/services/update-hashtag.js';
-import { Users, DriveFiles, UserProfiles, Pages } from '@/models/index.js';
-import { User } from '@/models/entities/user.js';
-import { UserProfile } from '@/models/entities/user-profile.js';
+import { UsersRepository, DriveFilesRepository, UserProfilesRepository, PagesRepository } from '@/models/index.js';
+import type { User } from '@/models/entities/User.js';
+import { birthdaySchema, descriptionSchema, locationSchema, nameSchema } from '@/models/entities/User.js';
+import type { UserProfile } from '@/models/entities/UserProfile.js';
import { notificationTypes } from '@/types.js';
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import { langmap } from '@/misc/langmap.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { UserFollowingService } from '@/core/UserFollowingService.js';
+import { AccountUpdateService } from '@/core/AccountUpdateService.js';
+import { HashtagService } from '@/core/HashtagService.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
-import define from '../../define.js';
export const meta = {
tags: ['account'],
@@ -70,10 +74,10 @@ export const meta = {
export const paramDef = {
type: 'object',
properties: {
- name: { ...Users.nameSchema, nullable: true },
- description: { ...Users.descriptionSchema, nullable: true },
- location: { ...Users.locationSchema, nullable: true },
- birthday: { ...Users.birthdaySchema, nullable: true },
+ name: { ...nameSchema, nullable: true },
+ description: { ...descriptionSchema, nullable: true },
+ location: { ...locationSchema, nullable: true },
+ birthday: { ...birthdaySchema, nullable: true },
lang: { type: 'string', enum: [null, ...Object.keys(langmap)], nullable: true },
avatarId: { type: 'string', format: 'misskey:id', nullable: true },
bannerId: { type: 'string', format: 'misskey:id', nullable: true },
@@ -122,134 +126,157 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, _user, token) => {
- const user = await Users.findOneByOrFail({ id: _user.id });
- const isSecure = token == null;
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.usersRepository)
+ private usersRepository: UsersRepository,
- const updates = {} as Partial<User>;
- const profileUpdates = {} as Partial<UserProfile>;
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
- const profile = await UserProfiles.findOneByOrFail({ userId: user.id });
+ @Inject(DI.driveFilesRepository)
+ private driveFilesRepository: DriveFilesRepository,
- 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) {
- // validate regular expression syntax
- ps.mutedWords.filter(x => !Array.isArray(x)).forEach(x => {
- const regexp = x.match(/^\/(.+)\/(.*)$/);
- if (!regexp) throw new ApiError(meta.errors.invalidRegexp);
+ @Inject(DI.pagesRepository)
+ private pagesRepository: PagesRepository,
- try {
- new RE2(regexp[1], regexp[2]);
- } catch (err) {
- throw new ApiError(meta.errors.invalidRegexp);
- }
- });
+ private userEntityService: UserEntityService,
+ private globalEventService: GlobalEventService,
+ private userFollowingService: UserFollowingService,
+ private accountUpdateService: AccountUpdateService,
+ private hashtagService: HashtagService,
+ ) {
+ super(meta, paramDef, async (ps, _user, token) => {
+ const user = await this.usersRepository.findOneByOrFail({ id: _user.id });
+ const isSecure = token == null;
- profileUpdates.mutedWords = ps.mutedWords;
- profileUpdates.enableWordMute = ps.mutedWords.length > 0;
- }
- if (ps.mutedInstances !== undefined) profileUpdates.mutedInstances = ps.mutedInstances;
- 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.showTimelineReplies === 'boolean') updates.showTimelineReplies = ps.showTimelineReplies;
- 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 (typeof ps.autoSensitive === 'boolean') profileUpdates.autoSensitive = ps.autoSensitive;
- if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes;
+ const updates = {} as Partial<User>;
+ const profileUpdates = {} as Partial<UserProfile>;
- if (ps.avatarId) {
- const avatar = await DriveFiles.findOneBy({ id: ps.avatarId });
+ const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
- if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar);
- if (!avatar.type.startsWith('image/')) throw new ApiError(meta.errors.avatarNotAnImage);
- }
+ 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) {
+ // validate regular expression syntax
+ ps.mutedWords.filter(x => !Array.isArray(x)).forEach(x => {
+ const regexp = x.match(/^\/(.+)\/(.*)$/);
+ if (!regexp) throw new ApiError(meta.errors.invalidRegexp);
- if (ps.bannerId) {
- const banner = await DriveFiles.findOneBy({ id: ps.bannerId });
+ try {
+ new RE2(regexp[1], regexp[2]);
+ } catch (err) {
+ throw new ApiError(meta.errors.invalidRegexp);
+ }
+ });
- if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner);
- if (!banner.type.startsWith('image/')) throw new ApiError(meta.errors.bannerNotAnImage);
- }
+ profileUpdates.mutedWords = ps.mutedWords;
+ profileUpdates.enableWordMute = ps.mutedWords.length > 0;
+ }
+ if (ps.mutedInstances !== undefined) profileUpdates.mutedInstances = ps.mutedInstances;
+ 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.showTimelineReplies === 'boolean') updates.showTimelineReplies = ps.showTimelineReplies;
+ 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 (typeof ps.autoSensitive === 'boolean') profileUpdates.autoSensitive = ps.autoSensitive;
+ if (ps.emailNotificationTypes !== undefined) profileUpdates.emailNotificationTypes = ps.emailNotificationTypes;
- if (ps.pinnedPageId) {
- const page = await Pages.findOneBy({ id: ps.pinnedPageId });
+ if (ps.avatarId) {
+ const avatar = await this.driveFilesRepository.findOneBy({ id: ps.avatarId });
- if (page == null || page.userId !== user.id) throw new ApiError(meta.errors.noSuchPage);
+ if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar);
+ if (!avatar.type.startsWith('image/')) throw new ApiError(meta.errors.avatarNotAnImage);
+ }
- profileUpdates.pinnedPageId = page.id;
- } else if (ps.pinnedPageId === null) {
- profileUpdates.pinnedPageId = null;
- }
+ if (ps.bannerId) {
+ const banner = await this.driveFilesRepository.findOneBy({ id: ps.bannerId });
- 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 };
- });
- }
+ if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner);
+ if (!banner.type.startsWith('image/')) throw new ApiError(meta.errors.bannerNotAnImage);
+ }
- //#region emojis/tags
+ if (ps.pinnedPageId) {
+ const page = await this.pagesRepository.findOneBy({ id: ps.pinnedPageId });
- let emojis = [] as string[];
- let tags = [] as string[];
+ if (page == null || page.userId !== user.id) throw new ApiError(meta.errors.noSuchPage);
- const newName = updates.name === undefined ? user.name : updates.name;
- const newDescription = profileUpdates.description === undefined ? profile.description : profileUpdates.description;
+ profileUpdates.pinnedPageId = page.id;
+ } else if (ps.pinnedPageId === null) {
+ profileUpdates.pinnedPageId = null;
+ }
- if (newName != null) {
- const tokens = mfm.parseSimple(newName);
- emojis = emojis.concat(extractCustomEmojisFromMfm(tokens!));
- }
+ 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 };
+ });
+ }
- if (newDescription != null) {
- const tokens = mfm.parse(newDescription);
- emojis = emojis.concat(extractCustomEmojisFromMfm(tokens!));
- tags = extractHashtags(tokens!).map(tag => normalizeForSearch(tag)).splice(0, 32);
- }
+ //#region emojis/tags
- updates.emojis = emojis;
- updates.tags = tags;
+ let emojis = [] as string[];
+ let tags = [] as string[];
- // ハッシュタグ更新
- updateUsertags(user, tags);
- //#endregion
+ const newName = updates.name === undefined ? user.name : updates.name;
+ const newDescription = profileUpdates.description === undefined ? profile.description : profileUpdates.description;
- if (Object.keys(updates).length > 0) await Users.update(user.id, updates);
- if (Object.keys(profileUpdates).length > 0) await UserProfiles.update(user.id, profileUpdates);
+ if (newName != null) {
+ const tokens = mfm.parseSimple(newName);
+ emojis = emojis.concat(extractCustomEmojisFromMfm(tokens!));
+ }
- const iObj = await Users.pack<true, true>(user.id, user, {
- detail: true,
- includeSecrets: isSecure,
- });
+ if (newDescription != null) {
+ const tokens = mfm.parse(newDescription);
+ emojis = emojis.concat(extractCustomEmojisFromMfm(tokens!));
+ tags = extractHashtags(tokens!).map(tag => normalizeForSearch(tag)).splice(0, 32);
+ }
- // Publish meUpdated event
- publishMainStream(user.id, 'meUpdated', iObj);
- publishUserEvent(user.id, 'updateUserProfile', await UserProfiles.findOneBy({ userId: user.id }));
+ updates.emojis = emojis;
+ updates.tags = tags;
- // 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認
- if (user.isLocked && ps.isLocked === false) {
- acceptAllFollowRequests(user);
- }
+ // ハッシュタグ更新
+ this.hashtagService.updateUsertags(user, tags);
+ //#endregion
+
+ if (Object.keys(updates).length > 0) await this.usersRepository.update(user.id, updates);
+ if (Object.keys(profileUpdates).length > 0) await this.userProfilesRepository.update(user.id, profileUpdates);
- // フォロワーにUpdateを配信
- publishToFollowers(user.id);
+ const iObj = await this.userEntityService.pack<true, true>(user.id, user, {
+ detail: true,
+ includeSecrets: isSecure,
+ });
- return iObj;
-});
+ // Publish meUpdated event
+ this.globalEventService.publishMainStream(user.id, 'meUpdated', iObj);
+ this.globalEventService.publishUserEvent(user.id, 'updateUserProfile', await this.userProfilesRepository.findOneBy({ userId: user.id }));
+
+ // 鍵垢を解除したとき、溜まっていたフォローリクエストがあるならすべて承認
+ if (user.isLocked && ps.isLocked === false) {
+ this.userFollowingService.acceptAllFollowRequests(user);
+ }
+
+ // フォロワーにUpdateを配信
+ this.accountUpdateService.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
index 1d7e4a16b3..6dd1626bb8 100644
--- a/packages/backend/src/server/api/endpoints/i/user-group-invites.ts
+++ b/packages/backend/src/server/api/endpoints/i/user-group-invites.ts
@@ -1,6 +1,9 @@
-import define from '../../define.js';
-import { UserGroupInvitations } from '@/models/index.js';
-import { makePaginationQuery } from '../../common/make-pagination-query.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { UserGroupInvitationsRepository } from '@/models/index.js';
+import { QueryService } from '@/core/QueryService.js';
+import { UserGroupInvitationEntityService } from '@/core/entities/UserGroupInvitationEntityService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['account', 'groups'],
@@ -42,14 +45,25 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, 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');
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.userGroupInvitationsRepository)
+ private userGroupInvitationsRepository: UserGroupInvitationsRepository,
- const invitations = await query
- .take(ps.limit)
- .getMany();
+ private userGroupInvitationEntityService: UserGroupInvitationEntityService,
+ private queryService: QueryService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.queryService.makePaginationQuery(this.userGroupInvitationsRepository.createQueryBuilder('invitation'), ps.sinceId, ps.untilId)
+ .andWhere('invitation.userId = :meId', { meId: me.id })
+ .leftJoinAndSelect('invitation.userGroup', 'user_group');
- return await UserGroupInvitations.packMany(invitations);
-});
+ const invitations = await query
+ .take(ps.limit)
+ .getMany();
+
+ return await this.userGroupInvitationEntityService.packMany(invitations);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts
index 2e2fd00b8c..016b1b5d6a 100644
--- a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts
@@ -1,8 +1,10 @@
-import define from '../../../define.js';
-import { genId } from '@/misc/gen-id.js';
-import { Webhooks } from '@/models/index.js';
-import { publishInternalEvent } from '@/services/stream.js';
-import { webhookEventTypes } from '@/models/entities/webhook.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { IdService } from '@/core/IdService.js';
+import { WebhooksRepository } from '@/models/index.js';
+import { webhookEventTypes } from '@/models/entities/Webhook.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['webhooks'],
@@ -25,19 +27,32 @@ export const paramDef = {
required: ['name', 'url', 'secret', 'on'],
} as const;
+// TODO: ロジックをサービスに切り出す
+
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const webhook = await Webhooks.insert({
- id: genId(),
- createdAt: new Date(),
- userId: user.id,
- name: ps.name,
- url: ps.url,
- secret: ps.secret,
- on: ps.on,
- }).then(x => Webhooks.findOneByOrFail(x.identifiers[0]));
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.webhooksRepository)
+ private webhooksRepository: WebhooksRepository,
+
+ private idService: IdService,
+ private globalEventService: GlobalEventService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const webhook = await this.webhooksRepository.insert({
+ id: this.idService.genId(),
+ createdAt: new Date(),
+ userId: me.id,
+ name: ps.name,
+ url: ps.url,
+ secret: ps.secret,
+ on: ps.on,
+ }).then(x => this.webhooksRepository.findOneByOrFail(x.identifiers[0]));
- publishInternalEvent('webhookCreated', webhook);
+ this.globalEventService.publishInternalEvent('webhookCreated', webhook);
- return webhook;
-});
+ return webhook;
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts b/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts
index 2821eaa5f1..53b553b43e 100644
--- a/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts
@@ -1,7 +1,9 @@
-import define from '../../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { WebhooksRepository } from '@/models/index.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../error.js';
-import { Webhooks } from '@/models/index.js';
-import { publishInternalEvent } from '@/services/stream.js';
export const meta = {
tags: ['webhooks'],
@@ -27,18 +29,30 @@ export const paramDef = {
required: ['webhookId'],
} as const;
+// TODO: ロジックをサービスに切り出す
+
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const webhook = await Webhooks.findOneBy({
- id: ps.webhookId,
- userId: user.id,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.webhooksRepository)
+ private webhooksRepository: WebhooksRepository,
- if (webhook == null) {
- throw new ApiError(meta.errors.noSuchWebhook);
- }
+ private globalEventService: GlobalEventService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const webhook = await this.webhooksRepository.findOneBy({
+ id: ps.webhookId,
+ userId: me.id,
+ });
- await Webhooks.delete(webhook.id);
+ if (webhook == null) {
+ throw new ApiError(meta.errors.noSuchWebhook);
+ }
- publishInternalEvent('webhookDeleted', webhook);
-});
+ await this.webhooksRepository.delete(webhook.id);
+
+ this.globalEventService.publishInternalEvent('webhookDeleted', webhook);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts
index 54e4563732..8e4aff45dd 100644
--- a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts
@@ -1,5 +1,7 @@
-import define from '../../../define.js';
-import { Webhooks } from '@/models/index.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { WebhooksRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
export const meta = {
tags: ['webhooks', 'account'],
@@ -16,10 +18,18 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, me) => {
- const webhooks = await Webhooks.findBy({
- userId: me.id,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.webhooksRepository)
+ private webhooksRepository: WebhooksRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const webhooks = await this.webhooksRepository.findBy({
+ userId: me.id,
+ });
- return webhooks;
-});
+ return webhooks;
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts
index 02fa1edb5e..622c2ade98 100644
--- a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts
@@ -1,6 +1,8 @@
-import define from '../../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { WebhooksRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../error.js';
-import { Webhooks } from '@/models/index.js';
export const meta = {
tags: ['webhooks'],
@@ -27,15 +29,23 @@ export const paramDef = {
} as const;
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const webhook = await Webhooks.findOneBy({
- id: ps.webhookId,
- userId: user.id,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.webhooksRepository)
+ private webhooksRepository: WebhooksRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const webhook = await this.webhooksRepository.findOneBy({
+ id: ps.webhookId,
+ userId: me.id,
+ });
- if (webhook == null) {
- throw new ApiError(meta.errors.noSuchWebhook);
- }
+ if (webhook == null) {
+ throw new ApiError(meta.errors.noSuchWebhook);
+ }
- return webhook;
-});
+ return webhook;
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/update.ts b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts
index f87b9753fb..3a0ef1a526 100644
--- a/packages/backend/src/server/api/endpoints/i/webhooks/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts
@@ -1,8 +1,10 @@
-import define from '../../../define.js';
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { WebhooksRepository } from '@/models/index.js';
+import { webhookEventTypes } from '@/models/entities/Webhook.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { DI } from '@/di-symbols.js';
import { ApiError } from '../../../error.js';
-import { Webhooks } from '@/models/index.js';
-import { publishInternalEvent } from '@/services/stream.js';
-import { webhookEventTypes } from '@/models/entities/webhook.js';
export const meta = {
tags: ['webhooks'],
@@ -36,24 +38,36 @@ export const paramDef = {
required: ['webhookId', 'name', 'url', 'secret', 'on', 'active'],
} as const;
+// TODO: ロジックをサービスに切り出す
+
// eslint-disable-next-line import/no-default-export
-export default define(meta, paramDef, async (ps, user) => {
- const webhook = await Webhooks.findOneBy({
- id: ps.webhookId,
- userId: user.id,
- });
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.webhooksRepository)
+ private webhooksRepository: WebhooksRepository,
- if (webhook == null) {
- throw new ApiError(meta.errors.noSuchWebhook);
- }
+ private globalEventService: GlobalEventService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const webhook = await this.webhooksRepository.findOneBy({
+ id: ps.webhookId,
+ userId: me.id,
+ });
- await Webhooks.update(webhook.id, {
- name: ps.name,
- url: ps.url,
- secret: ps.secret,
- on: ps.on,
- active: ps.active,
- });
+ if (webhook == null) {
+ throw new ApiError(meta.errors.noSuchWebhook);
+ }
- publishInternalEvent('webhookUpdated', webhook);
-});
+ await this.webhooksRepository.update(webhook.id, {
+ name: ps.name,
+ url: ps.url,
+ secret: ps.secret,
+ on: ps.on,
+ active: ps.active,
+ });
+
+ this.globalEventService.publishInternalEvent('webhookUpdated', webhook);
+ });
+ }
+}