summaryrefslogtreecommitdiff
path: root/packages/backend/src/core
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-11-03 13:23:03 +0900
committerGitHub <noreply@github.com>2023-11-03 13:23:03 +0900
commit79346272f8792d35955efd3aaaa1e42e0cd2a6e3 (patch)
tree6f84fea79208862777db23aac434551494817595 /packages/backend/src/core
parentCWを使用する場合、注釈を空にすることを許可しない (diff)
downloadmisskey-79346272f8792d35955efd3aaaa1e42e0cd2a6e3.tar.gz
misskey-79346272f8792d35955efd3aaaa1e42e0cd2a6e3.tar.bz2
misskey-79346272f8792d35955efd3aaaa1e42e0cd2a6e3.zip
feat: レジストリAPIをサードパーティから利用可能に (#12229)
* wip * wip * Update remove.ts * refactor
Diffstat (limited to 'packages/backend/src/core')
-rw-r--r--packages/backend/src/core/CoreModule.ts6
-rw-r--r--packages/backend/src/core/RegistryApiService.ts147
2 files changed, 153 insertions, 0 deletions
diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts
index c17ea9999a..9fb29e0e68 100644
--- a/packages/backend/src/core/CoreModule.ts
+++ b/packages/backend/src/core/CoreModule.ts
@@ -64,6 +64,7 @@ import { ClipService } from './ClipService.js';
import { FeaturedService } from './FeaturedService.js';
import { FunoutTimelineService } from './FunoutTimelineService.js';
import { ChannelFollowingService } from './ChannelFollowingService.js';
+import { RegistryApiService } from './RegistryApiService.js';
import { ChartLoggerService } from './chart/ChartLoggerService.js';
import FederationChart from './chart/charts/federation.js';
import NotesChart from './chart/charts/notes.js';
@@ -195,6 +196,7 @@ const $ClipService: Provider = { provide: 'ClipService', useExisting: ClipServic
const $FeaturedService: Provider = { provide: 'FeaturedService', useExisting: FeaturedService };
const $FunoutTimelineService: Provider = { provide: 'FunoutTimelineService', useExisting: FunoutTimelineService };
const $ChannelFollowingService: Provider = { provide: 'ChannelFollowingService', useExisting: ChannelFollowingService };
+const $RegistryApiService: Provider = { provide: 'RegistryApiService', useExisting: RegistryApiService };
const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService };
const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart };
@@ -330,6 +332,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
FeaturedService,
FunoutTimelineService,
ChannelFollowingService,
+ RegistryApiService,
ChartLoggerService,
FederationChart,
NotesChart,
@@ -458,6 +461,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$FeaturedService,
$FunoutTimelineService,
$ChannelFollowingService,
+ $RegistryApiService,
$ChartLoggerService,
$FederationChart,
$NotesChart,
@@ -587,6 +591,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
FeaturedService,
FunoutTimelineService,
ChannelFollowingService,
+ RegistryApiService,
FederationChart,
NotesChart,
UsersChart,
@@ -714,6 +719,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$FeaturedService,
$FunoutTimelineService,
$ChannelFollowingService,
+ $RegistryApiService,
$FederationChart,
$NotesChart,
$UsersChart,
diff --git a/packages/backend/src/core/RegistryApiService.ts b/packages/backend/src/core/RegistryApiService.ts
new file mode 100644
index 0000000000..d340c5e480
--- /dev/null
+++ b/packages/backend/src/core/RegistryApiService.ts
@@ -0,0 +1,147 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import { DI } from '@/di-symbols.js';
+import type { MiRegistryItem, RegistryItemsRepository } from '@/models/_.js';
+import { IdentifiableError } from '@/misc/identifiable-error.js';
+import type { MiUser } from '@/models/User.js';
+import { IdService } from '@/core/IdService.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { bindThis } from '@/decorators.js';
+
+@Injectable()
+export class RegistryApiService {
+ constructor(
+ @Inject(DI.registryItemsRepository)
+ private registryItemsRepository: RegistryItemsRepository,
+
+ private idService: IdService,
+ private globalEventService: GlobalEventService,
+ ) {
+ }
+
+ @bindThis
+ public async set(userId: MiUser['id'], domain: string | null, scope: string[], key: string, value: any) {
+ // TODO: 作成できるキーの数を制限する
+
+ const query = this.registryItemsRepository.createQueryBuilder('item');
+ if (domain) {
+ query.where('item.domain = :domain', { domain: domain });
+ } else {
+ query.where('item.domain IS NULL');
+ }
+ query.andWhere('item.userId = :userId', { userId: userId });
+ query.andWhere('item.key = :key', { key: key });
+ query.andWhere('item.scope = :scope', { scope: scope });
+
+ const existingItem = await query.getOne();
+
+ if (existingItem) {
+ await this.registryItemsRepository.update(existingItem.id, {
+ updatedAt: new Date(),
+ value: value,
+ });
+ } else {
+ await this.registryItemsRepository.insert({
+ id: this.idService.gen(),
+ updatedAt: new Date(),
+ userId: userId,
+ domain: domain,
+ scope: scope,
+ key: key,
+ value: value,
+ });
+ }
+
+ if (domain == null) {
+ // TODO: サードパーティアプリが傍受出来てしまうのでどうにかする
+ this.globalEventService.publishMainStream(userId, 'registryUpdated', {
+ scope: scope,
+ key: key,
+ value: value,
+ });
+ }
+ }
+
+ @bindThis
+ public async getItem(userId: MiUser['id'], domain: string | null, scope: string[], key: string): Promise<MiRegistryItem | null> {
+ const query = this.registryItemsRepository.createQueryBuilder('item')
+ .where(domain == null ? 'item.domain IS NULL' : 'item.domain = :domain', { domain: domain })
+ .andWhere('item.userId = :userId', { userId: userId })
+ .andWhere('item.key = :key', { key: key })
+ .andWhere('item.scope = :scope', { scope: scope });
+
+ const item = await query.getOne();
+
+ return item;
+ }
+
+ @bindThis
+ public async getAllItemsOfScope(userId: MiUser['id'], domain: string | null, scope: string[]): Promise<MiRegistryItem[]> {
+ const query = this.registryItemsRepository.createQueryBuilder('item');
+ query.where(domain == null ? 'item.domain IS NULL' : 'item.domain = :domain', { domain: domain });
+ query.andWhere('item.userId = :userId', { userId: userId });
+ query.andWhere('item.scope = :scope', { scope: scope });
+
+ const items = await query.getMany();
+
+ return items;
+ }
+
+ @bindThis
+ public async getAllKeysOfScope(userId: MiUser['id'], domain: string | null, scope: string[]): Promise<string[]> {
+ const query = this.registryItemsRepository.createQueryBuilder('item');
+ query.select('item.key');
+ query.where(domain == null ? 'item.domain IS NULL' : 'item.domain = :domain', { domain: domain });
+ query.andWhere('item.userId = :userId', { userId: userId });
+ query.andWhere('item.scope = :scope', { scope: scope });
+
+ const items = await query.getMany();
+
+ return items.map(x => x.key);
+ }
+
+ @bindThis
+ public async getAllScopeAndDomains(userId: MiUser['id']): Promise<{ domain: string | null; scopes: string[][] }[]> {
+ const query = this.registryItemsRepository.createQueryBuilder('item')
+ .select(['item.scope', 'item.domain'])
+ .where('item.userId = :userId', { userId: userId });
+
+ const items = await query.getMany();
+
+ const res = [] as { domain: string | null; scopes: string[][] }[];
+
+ for (const item of items) {
+ const target = res.find(x => x.domain === item.domain);
+ if (target) {
+ if (target.scopes.some(scope => scope.join('.') === item.scope.join('.'))) continue;
+ target.scopes.push(item.scope);
+ } else {
+ res.push({
+ domain: item.domain,
+ scopes: [item.scope],
+ });
+ }
+ }
+
+ return res;
+ }
+
+ @bindThis
+ public async remove(userId: MiUser['id'], domain: string | null, scope: string[], key: string) {
+ const query = this.registryItemsRepository.createQueryBuilder().delete();
+ if (domain) {
+ query.where('domain = :domain', { domain: domain });
+ } else {
+ query.where('domain IS NULL');
+ }
+ query.andWhere('userId = :userId', { userId: userId });
+ query.andWhere('key = :key', { key: key });
+ query.andWhere('scope = :scope', { scope: scope });
+
+ await query.execute();
+ }
+}