mirror of
https://github.com/kokonect-link/cherrypick
synced 2024-12-04 01:38:54 +09:00
parent
520b072c09
commit
8373e6642d
@ -430,15 +430,24 @@ markAsReadAllTalkMessages: "すべてのチャットを既読にする"
|
||||
help: "ヘルプ"
|
||||
inputMessageHere: "ここにメッセージを入力"
|
||||
close: "閉じる"
|
||||
group: "グループ"
|
||||
groups: "グループ"
|
||||
createGroup: "グループを作成"
|
||||
ownedGroups: "所有グループ"
|
||||
joinedGroups: "参加しているグループ"
|
||||
invites: "招待"
|
||||
groupName: "グループ名"
|
||||
members: "メンバー"
|
||||
transfer: "譲渡"
|
||||
messagingWithUser: "ユーザーとチャット"
|
||||
messagingWithGroup: "グループでチャット"
|
||||
title: "タイトル"
|
||||
text: "テキスト"
|
||||
enable: "有効にする"
|
||||
next: "次"
|
||||
retype: "再入力"
|
||||
noteOf: "{user}のノート"
|
||||
inviteToGroup: "グループに招待"
|
||||
quoteAttached: "引用付き"
|
||||
quoteQuestion: "引用として添付しますか?"
|
||||
noMessagesYet: "まだチャットはありません"
|
||||
@ -464,10 +473,13 @@ tapSecurityKey: "セキュリティキーにタッチ"
|
||||
or: "もしくは"
|
||||
language: "言語"
|
||||
uiLanguage: "UIの表示言語"
|
||||
groupInvited: "グループに招待されました"
|
||||
aboutX: "{x}について"
|
||||
emojiStyle: "絵文字のスタイル"
|
||||
native: "ネイティブ"
|
||||
disableDrawer: "メニューをドロワーで表示しない"
|
||||
youHaveNoGroups: "グループがありません"
|
||||
joinOrCreateGroup: "既存のグループに招待してもらうか、新しくグループを作成してください。"
|
||||
noHistory: "履歴はありません"
|
||||
signinHistory: "ログイン履歴"
|
||||
enableAdvancedMfm: "高度なMFMを有効にする"
|
||||
@ -842,6 +854,8 @@ deleteAccountConfirm: "アカウントが削除されます。よろしいです
|
||||
incorrectPassword: "パスワードが間違っています。"
|
||||
voteConfirm: "「{choice}」に投票しますか?"
|
||||
hide: "隠す"
|
||||
leaveGroup: "グループから抜ける"
|
||||
leaveGroupConfirm: "「{name}」から抜けますか?"
|
||||
useDrawerReactionPickerForMobile: "モバイルデバイスのときドロワーで表示"
|
||||
welcomeBackWithName: "おかえりなさい、{name}さん"
|
||||
clickToFinishEmailVerification: "[{ok}]を押して、メールアドレスの確認を完了してください。"
|
||||
@ -1673,6 +1687,7 @@ _antennaSources:
|
||||
homeTimeline: "フォローしているユーザーのノート"
|
||||
users: "指定した一人または複数のユーザーのノート"
|
||||
userList: "指定したリストのユーザーのノート"
|
||||
userGroup: "指定したグループのユーザーのノート"
|
||||
|
||||
_weekday:
|
||||
sunday: "日曜日"
|
||||
@ -1902,9 +1917,12 @@ _notification:
|
||||
youGotReply: "{name}からのリプライ"
|
||||
youGotQuote: "{name}による引用"
|
||||
youRenoted: "{name}がRenoteしました"
|
||||
youGotMessagingMessageFromUser: "{name}からのチャットがあります"
|
||||
youGotMessagingMessageFromGroup: "{name}のチャットがあります"
|
||||
youWereFollowed: "フォローされました"
|
||||
youReceivedFollowRequest: "フォローリクエストが来ました"
|
||||
yourFollowRequestAccepted: "フォローリクエストが承認されました"
|
||||
youWereInvitedToGroup: "{userName}があなたをグループに招待しました"
|
||||
pollEnded: "アンケートの結果が出ました"
|
||||
unreadAntennaNote: "アンテナ {name}"
|
||||
emptyPushNotificationMessage: "プッシュ通知の更新をしました"
|
||||
@ -1921,6 +1939,7 @@ _notification:
|
||||
pollEnded: "アンケートが終了"
|
||||
receiveFollowRequest: "フォロー申請を受け取った"
|
||||
followRequestAccepted: "フォローが受理された"
|
||||
groupInvited: "グループに招待された"
|
||||
app: "連携アプリからの通知"
|
||||
|
||||
_actions:
|
||||
|
@ -1,47 +0,0 @@
|
||||
export class dropGroup1676434944993 {
|
||||
name = 'dropGroup1676434944993'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "antenna" DROP CONSTRAINT "FK_ccbf5a8c0be4511133dcc50ddeb"`);
|
||||
await queryRunner.query(`ALTER TABLE "notification" DROP CONSTRAINT "FK_8fe87814e978053a53b1beb7e98"`);
|
||||
await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "userGroupJoiningId"`);
|
||||
await queryRunner.query(`ALTER TABLE "notification" DROP COLUMN "userGroupInvitationId"`);
|
||||
await queryRunner.query(`ALTER TYPE "public"."antenna_src_enum" RENAME TO "antenna_src_enum_old"`);
|
||||
await queryRunner.query(`CREATE TYPE "public"."antenna_src_enum" AS ENUM('home', 'all', 'users', 'list')`);
|
||||
await queryRunner.query(`ALTER TABLE "antenna" ALTER COLUMN "src" TYPE "public"."antenna_src_enum" USING "src"::"text"::"public"."antenna_src_enum"`);
|
||||
await queryRunner.query(`DROP TYPE "public"."antenna_src_enum_old"`);
|
||||
await queryRunner.query(`ALTER TYPE "public"."notification_type_enum" RENAME TO "notification_type_enum_old"`);
|
||||
await queryRunner.query(`CREATE TYPE "public"."notification_type_enum" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app')`);
|
||||
await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "public"."notification_type_enum" USING "type"::"text"::"public"."notification_type_enum"`);
|
||||
await queryRunner.query(`DROP TYPE "public"."notification_type_enum_old"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "emailNotificationTypes" SET DEFAULT '["follow","receiveFollowRequest"]'`);
|
||||
await queryRunner.query(`ALTER TYPE "public"."user_profile_mutingnotificationtypes_enum" RENAME TO "user_profile_mutingnotificationtypes_enum_old"`);
|
||||
await queryRunner.query(`CREATE TYPE "public"."user_profile_mutingnotificationtypes_enum" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app')`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" DROP DEFAULT`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" TYPE "public"."user_profile_mutingnotificationtypes_enum"[] USING "mutingNotificationTypes"::"text"::"public"."user_profile_mutingnotificationtypes_enum"[]`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" SET DEFAULT '{}'`);
|
||||
await queryRunner.query(`DROP TYPE "public"."user_profile_mutingnotificationtypes_enum_old"`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`CREATE TYPE "public"."user_profile_mutingnotificationtypes_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'achievementEarned', 'app')`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" DROP DEFAULT`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" TYPE "public"."user_profile_mutingnotificationtypes_enum_old"[] USING "mutingNotificationTypes"::"text"::"public"."user_profile_mutingnotificationtypes_enum_old"[]`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "mutingNotificationTypes" SET DEFAULT '{}'`);
|
||||
await queryRunner.query(`DROP TYPE "public"."user_profile_mutingnotificationtypes_enum"`);
|
||||
await queryRunner.query(`ALTER TYPE "public"."user_profile_mutingnotificationtypes_enum_old" RENAME TO "user_profile_mutingnotificationtypes_enum"`);
|
||||
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "emailNotificationTypes" SET DEFAULT '["follow", "receiveFollowRequest", "groupInvited"]'`);
|
||||
await queryRunner.query(`CREATE TYPE "public"."notification_type_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'achievementEarned', 'app')`);
|
||||
await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "public"."notification_type_enum_old" USING "type"::"text"::"public"."notification_type_enum_old"`);
|
||||
await queryRunner.query(`DROP TYPE "public"."notification_type_enum"`);
|
||||
await queryRunner.query(`ALTER TYPE "public"."notification_type_enum_old" RENAME TO "notification_type_enum"`);
|
||||
await queryRunner.query(`CREATE TYPE "public"."antenna_src_enum_old" AS ENUM('home', 'all', 'users', 'list', 'group')`);
|
||||
await queryRunner.query(`ALTER TABLE "antenna" ALTER COLUMN "src" TYPE "public"."antenna_src_enum_old" USING "src"::"text"::"public"."antenna_src_enum_old"`);
|
||||
await queryRunner.query(`DROP TYPE "public"."antenna_src_enum"`);
|
||||
await queryRunner.query(`ALTER TYPE "public"."antenna_src_enum_old" RENAME TO "antenna_src_enum"`);
|
||||
await queryRunner.query(`ALTER TABLE "notification" ADD "userGroupInvitationId" character varying(32)`);
|
||||
await queryRunner.query(`ALTER TABLE "antenna" ADD "userGroupJoiningId" character varying(32)`);
|
||||
await queryRunner.query(`ALTER TABLE "notification" ADD CONSTRAINT "FK_8fe87814e978053a53b1beb7e98" FOREIGN KEY ("userGroupInvitationId") REFERENCES "user_group_invitation"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
await queryRunner.query(`ALTER TABLE "antenna" ADD CONSTRAINT "FK_ccbf5a8c0be4511133dcc50ddeb" FOREIGN KEY ("userGroupJoiningId") REFERENCES "user_group_joining"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@ import { PushNotificationService } from '@/core/PushNotificationService.js';
|
||||
import * as Acct from '@/misc/acct.js';
|
||||
import type { Packed } from '@/misc/schema.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { MutingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserListJoiningsRepository } from '@/models/index.js';
|
||||
import type { MutingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository, UserListJoiningsRepository } from '@/models/index.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { StreamMessages } from '@/server/api/stream/types.js';
|
||||
@ -39,6 +39,9 @@ export class AntennaService implements OnApplicationShutdown {
|
||||
@Inject(DI.antennasRepository)
|
||||
private antennasRepository: AntennasRepository,
|
||||
|
||||
@Inject(DI.userGroupJoiningsRepository)
|
||||
private userGroupJoiningsRepository: UserGroupJoiningsRepository,
|
||||
|
||||
@Inject(DI.userListJoiningsRepository)
|
||||
private userListJoiningsRepository: UserListJoiningsRepository,
|
||||
|
||||
@ -157,6 +160,14 @@ export class AntennaService implements OnApplicationShutdown {
|
||||
})).map(x => x.userId);
|
||||
|
||||
if (!listUsers.includes(note.userId)) return false;
|
||||
} else if (antenna.src === 'group') {
|
||||
const joining = await this.userGroupJoiningsRepository.findOneByOrFail({ id: antenna.userGroupJoiningId! });
|
||||
|
||||
const groupUsers = (await this.userGroupJoiningsRepository.findBy({
|
||||
userGroupId: joining.userGroupId,
|
||||
})).map(x => x.userId);
|
||||
|
||||
if (!groupUsers.includes(note.userId)) return false;
|
||||
} else if (antenna.src === 'users') {
|
||||
const accts = antenna.users.map(x => {
|
||||
const { username, host } = Acct.parse(x);
|
||||
|
@ -91,6 +91,8 @@ import { PageEntityService } from './entities/PageEntityService.js';
|
||||
import { PageLikeEntityService } from './entities/PageLikeEntityService.js';
|
||||
import { SigninEntityService } from './entities/SigninEntityService.js';
|
||||
import { UserEntityService } from './entities/UserEntityService.js';
|
||||
import { UserGroupEntityService } from './entities/UserGroupEntityService.js';
|
||||
import { UserGroupInvitationEntityService } from './entities/UserGroupInvitationEntityService.js';
|
||||
import { UserListEntityService } from './entities/UserListEntityService.js';
|
||||
import { FlashEntityService } from './entities/FlashEntityService.js';
|
||||
import { FlashLikeEntityService } from './entities/FlashLikeEntityService.js';
|
||||
@ -212,6 +214,8 @@ const $PageEntityService: Provider = { provide: 'PageEntityService', useExisting
|
||||
const $PageLikeEntityService: Provider = { provide: 'PageLikeEntityService', useExisting: PageLikeEntityService };
|
||||
const $SigninEntityService: Provider = { provide: 'SigninEntityService', useExisting: SigninEntityService };
|
||||
const $UserEntityService: Provider = { provide: 'UserEntityService', useExisting: UserEntityService };
|
||||
const $UserGroupEntityService: Provider = { provide: 'UserGroupEntityService', useExisting: UserGroupEntityService };
|
||||
const $UserGroupInvitationEntityService: Provider = { provide: 'UserGroupInvitationEntityService', useExisting: UserGroupInvitationEntityService };
|
||||
const $UserListEntityService: Provider = { provide: 'UserListEntityService', useExisting: UserListEntityService };
|
||||
const $FlashEntityService: Provider = { provide: 'FlashEntityService', useExisting: FlashEntityService };
|
||||
const $FlashLikeEntityService: Provider = { provide: 'FlashLikeEntityService', useExisting: FlashLikeEntityService };
|
||||
@ -334,6 +338,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||
PageLikeEntityService,
|
||||
SigninEntityService,
|
||||
UserEntityService,
|
||||
UserGroupEntityService,
|
||||
UserGroupInvitationEntityService,
|
||||
UserListEntityService,
|
||||
FlashEntityService,
|
||||
FlashLikeEntityService,
|
||||
@ -451,6 +457,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||
$PageLikeEntityService,
|
||||
$SigninEntityService,
|
||||
$UserEntityService,
|
||||
$UserGroupEntityService,
|
||||
$UserGroupInvitationEntityService,
|
||||
$UserListEntityService,
|
||||
$FlashEntityService,
|
||||
$FlashLikeEntityService,
|
||||
@ -568,6 +576,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||
PageLikeEntityService,
|
||||
SigninEntityService,
|
||||
UserEntityService,
|
||||
UserGroupEntityService,
|
||||
UserGroupInvitationEntityService,
|
||||
UserListEntityService,
|
||||
FlashEntityService,
|
||||
FlashLikeEntityService,
|
||||
@ -684,6 +694,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
||||
$PageLikeEntityService,
|
||||
$SigninEntityService,
|
||||
$UserEntityService,
|
||||
$UserGroupEntityService,
|
||||
$UserGroupInvitationEntityService,
|
||||
$UserListEntityService,
|
||||
$FlashEntityService,
|
||||
$FlashLikeEntityService,
|
||||
|
@ -3,6 +3,7 @@ import Redis from 'ioredis';
|
||||
import type { User } from '@/models/entities/User.js';
|
||||
import type { Note } from '@/models/entities/Note.js';
|
||||
import type { UserList } from '@/models/entities/UserList.js';
|
||||
import type { UserGroup } from '@/models/entities/UserGroup.js';
|
||||
import type { Antenna } from '@/models/entities/Antenna.js';
|
||||
import type { Channel } from '@/models/entities/Channel.js';
|
||||
import type {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { AntennaNotesRepository, AntennasRepository } from '@/models/index.js';
|
||||
import type { AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository } from '@/models/index.js';
|
||||
import { awaitAll } from '@/misc/prelude/await-all.js';
|
||||
import type { Packed } from '@/misc/schema.js';
|
||||
import type { Antenna } from '@/models/entities/Antenna.js';
|
||||
@ -14,6 +14,9 @@ export class AntennaEntityService {
|
||||
|
||||
@Inject(DI.antennaNotesRepository)
|
||||
private antennaNotesRepository: AntennaNotesRepository,
|
||||
|
||||
@Inject(DI.userGroupJoiningsRepository)
|
||||
private userGroupJoiningsRepository: UserGroupJoiningsRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
@ -24,6 +27,7 @@ export class AntennaEntityService {
|
||||
const antenna = typeof src === 'object' ? src : await this.antennasRepository.findOneByOrFail({ id: src });
|
||||
|
||||
const hasUnreadNote = (await this.antennaNotesRepository.findOneBy({ antennaId: antenna.id, read: false })) != null;
|
||||
const userGroupJoining = antenna.userGroupJoiningId ? await this.userGroupJoiningsRepository.findOneBy({ id: antenna.userGroupJoiningId }) : null;
|
||||
|
||||
return {
|
||||
id: antenna.id,
|
||||
@ -33,6 +37,7 @@ export class AntennaEntityService {
|
||||
excludeKeywords: antenna.excludeKeywords,
|
||||
src: antenna.src,
|
||||
userListId: antenna.userListId,
|
||||
userGroupId: userGroupJoining ? userGroupJoining.userGroupId : null,
|
||||
users: antenna.users,
|
||||
caseSensitive: antenna.caseSensitive,
|
||||
notify: antenna.notify,
|
||||
|
@ -13,11 +13,13 @@ import type { OnModuleInit } from '@nestjs/common';
|
||||
import type { CustomEmojiService } from '../CustomEmojiService.js';
|
||||
import type { UserEntityService } from './UserEntityService.js';
|
||||
import type { NoteEntityService } from './NoteEntityService.js';
|
||||
import type { UserGroupInvitationEntityService } from './UserGroupInvitationEntityService.js';
|
||||
|
||||
@Injectable()
|
||||
export class NotificationEntityService implements OnModuleInit {
|
||||
private userEntityService: UserEntityService;
|
||||
private noteEntityService: NoteEntityService;
|
||||
private userGroupInvitationEntityService: UserGroupInvitationEntityService;
|
||||
private customEmojiService: CustomEmojiService;
|
||||
|
||||
constructor(
|
||||
@ -34,6 +36,7 @@ export class NotificationEntityService implements OnModuleInit {
|
||||
|
||||
//private userEntityService: UserEntityService,
|
||||
//private noteEntityService: NoteEntityService,
|
||||
//private userGroupInvitationEntityService: UserGroupInvitationEntityService,
|
||||
//private customEmojiService: CustomEmojiService,
|
||||
) {
|
||||
}
|
||||
@ -41,6 +44,7 @@ export class NotificationEntityService implements OnModuleInit {
|
||||
onModuleInit() {
|
||||
this.userEntityService = this.moduleRef.get('UserEntityService');
|
||||
this.noteEntityService = this.moduleRef.get('NoteEntityService');
|
||||
this.userGroupInvitationEntityService = this.moduleRef.get('UserGroupInvitationEntityService');
|
||||
this.customEmojiService = this.moduleRef.get('CustomEmojiService');
|
||||
}
|
||||
|
||||
@ -107,6 +111,9 @@ export class NotificationEntityService implements OnModuleInit {
|
||||
_hint_: options._hintForEachNotes_,
|
||||
}),
|
||||
} : {}),
|
||||
...(notification.type === 'groupInvited' ? {
|
||||
invitation: this.userGroupInvitationEntityService.pack(notification.userGroupInvitationId!),
|
||||
} : {}),
|
||||
...(notification.type === 'achievementEarned' ? {
|
||||
achievement: notification.achievement,
|
||||
} : {}),
|
||||
|
@ -12,7 +12,7 @@ import { Cache } from '@/misc/cache.js';
|
||||
import type { Instance } from '@/models/entities/Instance.js';
|
||||
import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js';
|
||||
import { birthdaySchema, descriptionSchema, localUsernameSchema, locationSchema, nameSchema, passwordSchema } from '@/models/entities/User.js';
|
||||
import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, NotificationsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, AnnouncementsRepository, AntennaNotesRepository, PagesRepository, UserProfile } from '@/models/index.js';
|
||||
import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, NotificationsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, UserGroupJoiningsRepository, AnnouncementsRepository, AntennaNotesRepository, PagesRepository, UserProfile } from '@/models/index.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import type { OnModuleInit } from '@nestjs/common';
|
||||
@ -102,6 +102,9 @@ export class UserEntityService implements OnModuleInit {
|
||||
@Inject(DI.announcementReadsRepository)
|
||||
private announcementReadsRepository: AnnouncementReadsRepository,
|
||||
|
||||
@Inject(DI.userGroupJoiningsRepository)
|
||||
private userGroupJoiningsRepository: UserGroupJoiningsRepository,
|
||||
|
||||
@Inject(DI.announcementsRepository)
|
||||
private announcementsRepository: AnnouncementsRepository,
|
||||
|
||||
|
44
packages/backend/src/core/entities/UserGroupEntityService.ts
Normal file
44
packages/backend/src/core/entities/UserGroupEntityService.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { UserGroupJoiningsRepository, UserGroupsRepository } from '@/models/index.js';
|
||||
import { awaitAll } from '@/misc/prelude/await-all.js';
|
||||
import type { Packed } from '@/misc/schema.js';
|
||||
import type { } from '@/models/entities/Blocking.js';
|
||||
import type { User } from '@/models/entities/User.js';
|
||||
import type { UserGroup } from '@/models/entities/UserGroup.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
@Injectable()
|
||||
export class UserGroupEntityService {
|
||||
constructor(
|
||||
@Inject(DI.userGroupsRepository)
|
||||
private userGroupsRepository: UserGroupsRepository,
|
||||
|
||||
@Inject(DI.userGroupJoiningsRepository)
|
||||
private userGroupJoiningsRepository: UserGroupJoiningsRepository,
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async pack(
|
||||
src: UserGroup['id'] | UserGroup,
|
||||
): Promise<Packed<'UserGroup'>> {
|
||||
const userGroup = typeof src === 'object' ? src : await this.userGroupsRepository.findOneByOrFail({ id: src });
|
||||
|
||||
const users = await this.userGroupJoiningsRepository.findBy({
|
||||
userGroupId: userGroup.id,
|
||||
});
|
||||
|
||||
return {
|
||||
id: userGroup.id,
|
||||
createdAt: userGroup.createdAt.toISOString(),
|
||||
name: userGroup.name,
|
||||
ownerId: userGroup.userId,
|
||||
userIds: users.map(x => x.userId),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,42 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { UserGroupInvitationsRepository } from '@/models/index.js';
|
||||
import { awaitAll } from '@/misc/prelude/await-all.js';
|
||||
import type { Packed } from '@/misc/schema.js';
|
||||
import type { } from '@/models/entities/Blocking.js';
|
||||
import type { User } from '@/models/entities/User.js';
|
||||
import type { UserGroupInvitation } from '@/models/entities/UserGroupInvitation.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
import { UserGroupEntityService } from './UserGroupEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
@Injectable()
|
||||
export class UserGroupInvitationEntityService {
|
||||
constructor(
|
||||
@Inject(DI.userGroupInvitationsRepository)
|
||||
private userGroupInvitationsRepository: UserGroupInvitationsRepository,
|
||||
|
||||
private userGroupEntityService: UserGroupEntityService,
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async pack(
|
||||
src: UserGroupInvitation['id'] | UserGroupInvitation,
|
||||
) {
|
||||
const invitation = typeof src === 'object' ? src : await this.userGroupInvitationsRepository.findOneByOrFail({ id: src });
|
||||
|
||||
return {
|
||||
id: invitation.id,
|
||||
group: await this.userGroupEntityService.pack(invitation.userGroup ?? invitation.userGroupId),
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public packMany(
|
||||
invitations: any[],
|
||||
) {
|
||||
return Promise.all(invitations.map(x => this.pack(x)));
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,9 @@ export const DI = {
|
||||
userPublickeysRepository: Symbol('userPublickeysRepository'),
|
||||
userListsRepository: Symbol('userListsRepository'),
|
||||
userListJoiningsRepository: Symbol('userListJoiningsRepository'),
|
||||
userGroupsRepository: Symbol('userGroupsRepository'),
|
||||
userGroupJoiningsRepository: Symbol('userGroupJoiningsRepository'),
|
||||
userGroupInvitationsRepository: Symbol('userGroupInvitationsRepository'),
|
||||
userNotePiningsRepository: Symbol('userNotePiningsRepository'),
|
||||
userIpsRepository: Symbol('userIpsRepository'),
|
||||
usedUsernamesRepository: Symbol('usedUsernamesRepository'),
|
||||
|
@ -19,6 +19,7 @@ import { packedBlockingSchema } from '@/models/schema/blocking.js';
|
||||
import { packedNoteReactionSchema } from '@/models/schema/note-reaction.js';
|
||||
import { packedHashtagSchema } from '@/models/schema/hashtag.js';
|
||||
import { packedPageSchema } from '@/models/schema/page.js';
|
||||
import { packedUserGroupSchema } from '@/models/schema/user-group.js';
|
||||
import { packedNoteFavoriteSchema } from '@/models/schema/note-favorite.js';
|
||||
import { packedChannelSchema } from '@/models/schema/channel.js';
|
||||
import { packedAntennaSchema } from '@/models/schema/antenna.js';
|
||||
@ -38,6 +39,7 @@ export const refs = {
|
||||
User: packedUserSchema,
|
||||
|
||||
UserList: packedUserListSchema,
|
||||
UserGroup: packedUserGroupSchema,
|
||||
App: packedAppSchema,
|
||||
Note: packedNoteSchema,
|
||||
NoteReaction: packedNoteReactionSchema,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Notification, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, AntennaNote, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelNotePining, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment } from './index.js';
|
||||
import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Notification, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserGroup, UserGroupJoining, UserGroupInvitation, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, AntennaNote, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelNotePining, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment } from './index.js';
|
||||
import type { DataSource } from 'typeorm';
|
||||
import type { Provider } from '@nestjs/common';
|
||||
|
||||
@ -118,6 +118,24 @@ const $userListJoiningsRepository: Provider = {
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $userGroupsRepository: Provider = {
|
||||
provide: DI.userGroupsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(UserGroup),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $userGroupJoiningsRepository: Provider = {
|
||||
provide: DI.userGroupJoiningsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(UserGroupJoining),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $userGroupInvitationsRepository: Provider = {
|
||||
provide: DI.userGroupInvitationsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(UserGroupInvitation),
|
||||
inject: [DI.db],
|
||||
};
|
||||
|
||||
const $userNotePiningsRepository: Provider = {
|
||||
provide: DI.userNotePiningsRepository,
|
||||
useFactory: (db: DataSource) => db.getRepository(UserNotePining),
|
||||
@ -411,6 +429,9 @@ const $roleAssignmentsRepository: Provider = {
|
||||
$userPublickeysRepository,
|
||||
$userListsRepository,
|
||||
$userListJoiningsRepository,
|
||||
$userGroupsRepository,
|
||||
$userGroupJoiningsRepository,
|
||||
$userGroupInvitationsRepository,
|
||||
$userNotePiningsRepository,
|
||||
$userIpsRepository,
|
||||
$usedUsernamesRepository,
|
||||
@ -477,6 +498,9 @@ const $roleAssignmentsRepository: Provider = {
|
||||
$userPublickeysRepository,
|
||||
$userListsRepository,
|
||||
$userListJoiningsRepository,
|
||||
$userGroupsRepository,
|
||||
$userGroupJoiningsRepository,
|
||||
$userGroupInvitationsRepository,
|
||||
$userNotePiningsRepository,
|
||||
$userIpsRepository,
|
||||
$usedUsernamesRepository,
|
||||
|
@ -2,6 +2,7 @@ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typ
|
||||
import { id } from '../id.js';
|
||||
import { User } from './User.js';
|
||||
import { UserList } from './UserList.js';
|
||||
import { UserGroupJoining } from './UserGroupJoining.js';
|
||||
|
||||
@Entity()
|
||||
export class Antenna {
|
||||
@ -32,8 +33,8 @@ export class Antenna {
|
||||
})
|
||||
public name: string;
|
||||
|
||||
@Column('enum', { enum: ['home', 'all', 'users', 'list'] })
|
||||
public src: 'home' | 'all' | 'users' | 'list';
|
||||
@Column('enum', { enum: ['home', 'all', 'users', 'list', 'group'] })
|
||||
public src: 'home' | 'all' | 'users' | 'list' | 'group';
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
@ -47,6 +48,18 @@ export class Antenna {
|
||||
@JoinColumn()
|
||||
public userList: UserList | null;
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true,
|
||||
})
|
||||
public userGroupJoiningId: UserGroupJoining['id'] | null;
|
||||
|
||||
@ManyToOne(type => UserGroupJoining, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public userGroupJoining: UserGroupJoining | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024, array: true,
|
||||
default: '{}',
|
||||
|
@ -4,6 +4,7 @@ import { id } from '../id.js';
|
||||
import { User } from './User.js';
|
||||
import { Note } from './Note.js';
|
||||
import { FollowRequest } from './FollowRequest.js';
|
||||
import { UserGroupInvitation } from './UserGroupInvitation.js';
|
||||
import { AccessToken } from './AccessToken.js';
|
||||
|
||||
@Entity()
|
||||
@ -62,6 +63,7 @@ export class Notification {
|
||||
* pollEnded - 自分のアンケートもしくは自分が投票したアンケートが終了した
|
||||
* receiveFollowRequest - フォローリクエストされた
|
||||
* followRequestAccepted - 自分の送ったフォローリクエストが承認された
|
||||
* groupInvited - グループに招待された
|
||||
* achievementEarned - 実績を獲得
|
||||
* app - アプリ通知
|
||||
*/
|
||||
@ -106,6 +108,18 @@ export class Notification {
|
||||
@JoinColumn()
|
||||
public followRequest: FollowRequest | null;
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true,
|
||||
})
|
||||
public userGroupInvitationId: UserGroupInvitation['id'] | null;
|
||||
|
||||
@ManyToOne(type => UserGroupInvitation, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public userGroupInvitation: UserGroupInvitation | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
})
|
||||
|
46
packages/backend/src/models/entities/UserGroup.ts
Normal file
46
packages/backend/src/models/entities/UserGroup.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm';
|
||||
import { id } from '../id.js';
|
||||
import { User } from './User.js';
|
||||
|
||||
@Entity()
|
||||
export class UserGroup {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the UserGroup.',
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256,
|
||||
})
|
||||
public name: string;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The ID of owner.',
|
||||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public isPrivate: boolean;
|
||||
|
||||
constructor(data: Partial<UserGroup>) {
|
||||
if (data == null) return;
|
||||
|
||||
for (const [k, v] of Object.entries(data)) {
|
||||
(this as any)[k] = v;
|
||||
}
|
||||
}
|
||||
}
|
42
packages/backend/src/models/entities/UserGroupInvitation.ts
Normal file
42
packages/backend/src/models/entities/UserGroupInvitation.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { id } from '../id.js';
|
||||
import { User } from './User.js';
|
||||
import { UserGroup } from './UserGroup.js';
|
||||
|
||||
@Entity()
|
||||
@Index(['userId', 'userGroupId'], { unique: true })
|
||||
export class UserGroupInvitation {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the UserGroupInvitation.',
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The user ID.',
|
||||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The group ID.',
|
||||
})
|
||||
public userGroupId: UserGroup['id'];
|
||||
|
||||
@ManyToOne(type => UserGroup, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public userGroup: UserGroup | null;
|
||||
}
|
42
packages/backend/src/models/entities/UserGroupJoining.ts
Normal file
42
packages/backend/src/models/entities/UserGroupJoining.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { id } from '../id.js';
|
||||
import { User } from './User.js';
|
||||
import { UserGroup } from './UserGroup.js';
|
||||
|
||||
@Entity()
|
||||
@Index(['userId', 'userGroupId'], { unique: true })
|
||||
export class UserGroupJoining {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the UserGroupJoining.',
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The user ID.',
|
||||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The group ID.',
|
||||
})
|
||||
public userGroupId: UserGroup['id'];
|
||||
|
||||
@ManyToOne(type => UserGroup, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn()
|
||||
public userGroup: UserGroup | null;
|
||||
}
|
@ -71,7 +71,7 @@ export class UserProfile {
|
||||
public emailVerified: boolean;
|
||||
|
||||
@Column('jsonb', {
|
||||
default: ['follow', 'receiveFollowRequest'],
|
||||
default: ['follow', 'receiveFollowRequest', 'groupInvited'],
|
||||
})
|
||||
public emailNotificationTypes: string[];
|
||||
|
||||
|
@ -46,6 +46,9 @@ import { Signin } from '@/models/entities/Signin.js';
|
||||
import { SwSubscription } from '@/models/entities/SwSubscription.js';
|
||||
import { UsedUsername } from '@/models/entities/UsedUsername.js';
|
||||
import { User } from '@/models/entities/User.js';
|
||||
import { UserGroup } from '@/models/entities/UserGroup.js';
|
||||
import { UserGroupInvitation } from '@/models/entities/UserGroupInvitation.js';
|
||||
import { UserGroupJoining } from '@/models/entities/UserGroupJoining.js';
|
||||
import { UserIp } from '@/models/entities/UserIp.js';
|
||||
import { UserKeypair } from '@/models/entities/UserKeypair.js';
|
||||
import { UserList } from '@/models/entities/UserList.js';
|
||||
@ -113,6 +116,9 @@ export {
|
||||
SwSubscription,
|
||||
UsedUsername,
|
||||
User,
|
||||
UserGroup,
|
||||
UserGroupInvitation,
|
||||
UserGroupJoining,
|
||||
UserIp,
|
||||
UserKeypair,
|
||||
UserList,
|
||||
@ -179,6 +185,9 @@ export type SigninsRepository = Repository<Signin>;
|
||||
export type SwSubscriptionsRepository = Repository<SwSubscription>;
|
||||
export type UsedUsernamesRepository = Repository<UsedUsername>;
|
||||
export type UsersRepository = Repository<User>;
|
||||
export type UserGroupsRepository = Repository<UserGroup>;
|
||||
export type UserGroupInvitationsRepository = Repository<UserGroupInvitation>;
|
||||
export type UserGroupJoiningsRepository = Repository<UserGroupJoining>;
|
||||
export type UserIpsRepository = Repository<UserIp>;
|
||||
export type UserKeypairsRepository = Repository<UserKeypair>;
|
||||
export type UserListsRepository = Repository<UserList>;
|
||||
|
@ -42,13 +42,18 @@ export const packedAntennaSchema = {
|
||||
src: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
enum: ['home', 'all', 'users', 'list'],
|
||||
enum: ['home', 'all', 'users', 'list', 'group'],
|
||||
},
|
||||
userListId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
format: 'id',
|
||||
},
|
||||
userGroupId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
format: 'id',
|
||||
},
|
||||
users: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
|
34
packages/backend/src/models/schema/user-group.ts
Normal file
34
packages/backend/src/models/schema/user-group.ts
Normal file
@ -0,0 +1,34 @@
|
||||
export const packedUserGroupSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
example: 'xxxxxxxxxx',
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'date-time',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
ownerId: {
|
||||
type: 'string',
|
||||
nullable: false, optional: false,
|
||||
format: 'id',
|
||||
},
|
||||
userIds: {
|
||||
type: 'array',
|
||||
nullable: false, optional: true,
|
||||
items: {
|
||||
type: 'string',
|
||||
nullable: false, optional: false,
|
||||
format: 'id',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
@ -54,6 +54,9 @@ import { Signin } from '@/models/entities/Signin.js';
|
||||
import { SwSubscription } from '@/models/entities/SwSubscription.js';
|
||||
import { UsedUsername } from '@/models/entities/UsedUsername.js';
|
||||
import { User } from '@/models/entities/User.js';
|
||||
import { UserGroup } from '@/models/entities/UserGroup.js';
|
||||
import { UserGroupInvitation } from '@/models/entities/UserGroupInvitation.js';
|
||||
import { UserGroupJoining } from '@/models/entities/UserGroupJoining.js';
|
||||
import { UserIp } from '@/models/entities/UserIp.js';
|
||||
import { UserKeypair } from '@/models/entities/UserKeypair.js';
|
||||
import { UserList } from '@/models/entities/UserList.js';
|
||||
@ -133,6 +136,9 @@ export const entities = [
|
||||
UserPublickey,
|
||||
UserList,
|
||||
UserListJoining,
|
||||
UserGroup,
|
||||
UserGroupJoining,
|
||||
UserGroupInvitation,
|
||||
UserNotePining,
|
||||
UserSecurityKey,
|
||||
UsedUsername,
|
||||
|
@ -211,6 +211,7 @@ import * as ep___i_signinHistory from './endpoints/i/signin-history.js';
|
||||
import * as ep___i_unpin from './endpoints/i/unpin.js';
|
||||
import * as ep___i_updateEmail from './endpoints/i/update-email.js';
|
||||
import * as ep___i_update from './endpoints/i/update.js';
|
||||
import * as ep___i_userGroupInvites from './endpoints/i/user-group-invites.js';
|
||||
import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js';
|
||||
import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js';
|
||||
import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
|
||||
@ -293,6 +294,18 @@ import * as ep___users_followers from './endpoints/users/followers.js';
|
||||
import * as ep___users_following from './endpoints/users/following.js';
|
||||
import * as ep___users_gallery_posts from './endpoints/users/gallery/posts.js';
|
||||
import * as ep___users_getFrequentlyRepliedUsers from './endpoints/users/get-frequently-replied-users.js';
|
||||
import * as ep___users_groups_create from './endpoints/users/groups/create.js';
|
||||
import * as ep___users_groups_delete from './endpoints/users/groups/delete.js';
|
||||
import * as ep___users_groups_invitations_accept from './endpoints/users/groups/invitations/accept.js';
|
||||
import * as ep___users_groups_invitations_reject from './endpoints/users/groups/invitations/reject.js';
|
||||
import * as ep___users_groups_invite from './endpoints/users/groups/invite.js';
|
||||
import * as ep___users_groups_joined from './endpoints/users/groups/joined.js';
|
||||
import * as ep___users_groups_leave from './endpoints/users/groups/leave.js';
|
||||
import * as ep___users_groups_owned from './endpoints/users/groups/owned.js';
|
||||
import * as ep___users_groups_pull from './endpoints/users/groups/pull.js';
|
||||
import * as ep___users_groups_show from './endpoints/users/groups/show.js';
|
||||
import * as ep___users_groups_transfer from './endpoints/users/groups/transfer.js';
|
||||
import * as ep___users_groups_update from './endpoints/users/groups/update.js';
|
||||
import * as ep___users_lists_create from './endpoints/users/lists/create.js';
|
||||
import * as ep___users_lists_delete from './endpoints/users/lists/delete.js';
|
||||
import * as ep___users_lists_list from './endpoints/users/lists/list.js';
|
||||
@ -527,6 +540,7 @@ const $i_signinHistory: Provider = { provide: 'ep:i/signin-history', useClass: e
|
||||
const $i_unpin: Provider = { provide: 'ep:i/unpin', useClass: ep___i_unpin.default };
|
||||
const $i_updateEmail: Provider = { provide: 'ep:i/update-email', useClass: ep___i_updateEmail.default };
|
||||
const $i_update: Provider = { provide: 'ep:i/update', useClass: ep___i_update.default };
|
||||
const $i_userGroupInvites: Provider = { provide: 'ep:i/user-group-invites', useClass: ep___i_userGroupInvites.default };
|
||||
const $i_webhooks_create: Provider = { provide: 'ep:i/webhooks/create', useClass: ep___i_webhooks_create.default };
|
||||
const $i_webhooks_list: Provider = { provide: 'ep:i/webhooks/list', useClass: ep___i_webhooks_list.default };
|
||||
const $i_webhooks_show: Provider = { provide: 'ep:i/webhooks/show', useClass: ep___i_webhooks_show.default };
|
||||
@ -609,6 +623,18 @@ const $users_followers: Provider = { provide: 'ep:users/followers', useClass: ep
|
||||
const $users_following: Provider = { provide: 'ep:users/following', useClass: ep___users_following.default };
|
||||
const $users_gallery_posts: Provider = { provide: 'ep:users/gallery/posts', useClass: ep___users_gallery_posts.default };
|
||||
const $users_getFrequentlyRepliedUsers: Provider = { provide: 'ep:users/get-frequently-replied-users', useClass: ep___users_getFrequentlyRepliedUsers.default };
|
||||
const $users_groups_create: Provider = { provide: 'ep:users/groups/create', useClass: ep___users_groups_create.default };
|
||||
const $users_groups_delete: Provider = { provide: 'ep:users/groups/delete', useClass: ep___users_groups_delete.default };
|
||||
const $users_groups_invitations_accept: Provider = { provide: 'ep:users/groups/invitations/accept', useClass: ep___users_groups_invitations_accept.default };
|
||||
const $users_groups_invitations_reject: Provider = { provide: 'ep:users/groups/invitations/reject', useClass: ep___users_groups_invitations_reject.default };
|
||||
const $users_groups_invite: Provider = { provide: 'ep:users/groups/invite', useClass: ep___users_groups_invite.default };
|
||||
const $users_groups_joined: Provider = { provide: 'ep:users/groups/joined', useClass: ep___users_groups_joined.default };
|
||||
const $users_groups_leave: Provider = { provide: 'ep:users/groups/leave', useClass: ep___users_groups_leave.default };
|
||||
const $users_groups_owned: Provider = { provide: 'ep:users/groups/owned', useClass: ep___users_groups_owned.default };
|
||||
const $users_groups_pull: Provider = { provide: 'ep:users/groups/pull', useClass: ep___users_groups_pull.default };
|
||||
const $users_groups_show: Provider = { provide: 'ep:users/groups/show', useClass: ep___users_groups_show.default };
|
||||
const $users_groups_transfer: Provider = { provide: 'ep:users/groups/transfer', useClass: ep___users_groups_transfer.default };
|
||||
const $users_groups_update: Provider = { provide: 'ep:users/groups/update', useClass: ep___users_groups_update.default };
|
||||
const $users_lists_create: Provider = { provide: 'ep:users/lists/create', useClass: ep___users_lists_create.default };
|
||||
const $users_lists_delete: Provider = { provide: 'ep:users/lists/delete', useClass: ep___users_lists_delete.default };
|
||||
const $users_lists_list: Provider = { provide: 'ep:users/lists/list', useClass: ep___users_lists_list.default };
|
||||
@ -847,6 +873,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$i_unpin,
|
||||
$i_updateEmail,
|
||||
$i_update,
|
||||
$i_userGroupInvites,
|
||||
$i_webhooks_create,
|
||||
$i_webhooks_list,
|
||||
$i_webhooks_show,
|
||||
@ -929,6 +956,18 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$users_following,
|
||||
$users_gallery_posts,
|
||||
$users_getFrequentlyRepliedUsers,
|
||||
$users_groups_create,
|
||||
$users_groups_delete,
|
||||
$users_groups_invitations_accept,
|
||||
$users_groups_invitations_reject,
|
||||
$users_groups_invite,
|
||||
$users_groups_joined,
|
||||
$users_groups_leave,
|
||||
$users_groups_owned,
|
||||
$users_groups_pull,
|
||||
$users_groups_show,
|
||||
$users_groups_transfer,
|
||||
$users_groups_update,
|
||||
$users_lists_create,
|
||||
$users_lists_delete,
|
||||
$users_lists_list,
|
||||
@ -1161,6 +1200,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$i_unpin,
|
||||
$i_updateEmail,
|
||||
$i_update,
|
||||
$i_userGroupInvites,
|
||||
$i_webhooks_create,
|
||||
$i_webhooks_list,
|
||||
$i_webhooks_show,
|
||||
@ -1241,6 +1281,18 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||
$users_following,
|
||||
$users_gallery_posts,
|
||||
$users_getFrequentlyRepliedUsers,
|
||||
$users_groups_create,
|
||||
$users_groups_delete,
|
||||
$users_groups_invitations_accept,
|
||||
$users_groups_invitations_reject,
|
||||
$users_groups_invite,
|
||||
$users_groups_joined,
|
||||
$users_groups_leave,
|
||||
$users_groups_owned,
|
||||
$users_groups_pull,
|
||||
$users_groups_show,
|
||||
$users_groups_transfer,
|
||||
$users_groups_update,
|
||||
$users_lists_create,
|
||||
$users_lists_delete,
|
||||
$users_lists_list,
|
||||
|
@ -210,6 +210,7 @@ import * as ep___i_signinHistory from './endpoints/i/signin-history.js';
|
||||
import * as ep___i_unpin from './endpoints/i/unpin.js';
|
||||
import * as ep___i_updateEmail from './endpoints/i/update-email.js';
|
||||
import * as ep___i_update from './endpoints/i/update.js';
|
||||
import * as ep___i_userGroupInvites from './endpoints/i/user-group-invites.js';
|
||||
import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js';
|
||||
import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js';
|
||||
import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
|
||||
@ -292,6 +293,18 @@ import * as ep___users_followers from './endpoints/users/followers.js';
|
||||
import * as ep___users_following from './endpoints/users/following.js';
|
||||
import * as ep___users_gallery_posts from './endpoints/users/gallery/posts.js';
|
||||
import * as ep___users_getFrequentlyRepliedUsers from './endpoints/users/get-frequently-replied-users.js';
|
||||
import * as ep___users_groups_create from './endpoints/users/groups/create.js';
|
||||
import * as ep___users_groups_delete from './endpoints/users/groups/delete.js';
|
||||
import * as ep___users_groups_invitations_accept from './endpoints/users/groups/invitations/accept.js';
|
||||
import * as ep___users_groups_invitations_reject from './endpoints/users/groups/invitations/reject.js';
|
||||
import * as ep___users_groups_invite from './endpoints/users/groups/invite.js';
|
||||
import * as ep___users_groups_joined from './endpoints/users/groups/joined.js';
|
||||
import * as ep___users_groups_leave from './endpoints/users/groups/leave.js';
|
||||
import * as ep___users_groups_owned from './endpoints/users/groups/owned.js';
|
||||
import * as ep___users_groups_pull from './endpoints/users/groups/pull.js';
|
||||
import * as ep___users_groups_show from './endpoints/users/groups/show.js';
|
||||
import * as ep___users_groups_transfer from './endpoints/users/groups/transfer.js';
|
||||
import * as ep___users_groups_update from './endpoints/users/groups/update.js';
|
||||
import * as ep___users_lists_create from './endpoints/users/lists/create.js';
|
||||
import * as ep___users_lists_delete from './endpoints/users/lists/delete.js';
|
||||
import * as ep___users_lists_list from './endpoints/users/lists/list.js';
|
||||
@ -524,6 +537,7 @@ const eps = [
|
||||
['i/unpin', ep___i_unpin],
|
||||
['i/update-email', ep___i_updateEmail],
|
||||
['i/update', ep___i_update],
|
||||
['i/user-group-invites', ep___i_userGroupInvites],
|
||||
['i/webhooks/create', ep___i_webhooks_create],
|
||||
['i/webhooks/list', ep___i_webhooks_list],
|
||||
['i/webhooks/show', ep___i_webhooks_show],
|
||||
@ -606,6 +620,18 @@ const eps = [
|
||||
['users/following', ep___users_following],
|
||||
['users/gallery/posts', ep___users_gallery_posts],
|
||||
['users/get-frequently-replied-users', ep___users_getFrequentlyRepliedUsers],
|
||||
['users/groups/create', ep___users_groups_create],
|
||||
['users/groups/delete', ep___users_groups_delete],
|
||||
['users/groups/invitations/accept', ep___users_groups_invitations_accept],
|
||||
['users/groups/invitations/reject', ep___users_groups_invitations_reject],
|
||||
['users/groups/invite', ep___users_groups_invite],
|
||||
['users/groups/joined', ep___users_groups_joined],
|
||||
['users/groups/leave', ep___users_groups_leave],
|
||||
['users/groups/owned', ep___users_groups_owned],
|
||||
['users/groups/pull', ep___users_groups_pull],
|
||||
['users/groups/show', ep___users_groups_show],
|
||||
['users/groups/transfer', ep___users_groups_transfer],
|
||||
['users/groups/update', ep___users_groups_update],
|
||||
['users/lists/create', ep___users_lists_create],
|
||||
['users/lists/delete', ep___users_lists_delete],
|
||||
['users/lists/list', ep___users_lists_list],
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import type { UserListsRepository, AntennasRepository } from '@/models/index.js';
|
||||
import type { UserListsRepository, UserGroupJoiningsRepository, AntennasRepository } from '@/models/index.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
@ -22,6 +22,12 @@ export const meta = {
|
||||
id: '95063e93-a283-4b8b-9aa5-bcdb8df69a7f',
|
||||
},
|
||||
|
||||
noSuchUserGroup: {
|
||||
message: 'No such user group.',
|
||||
code: 'NO_SUCH_USER_GROUP',
|
||||
id: 'aa3c0b9a-8cae-47c0-92ac-202ce5906682',
|
||||
},
|
||||
|
||||
tooManyAntennas: {
|
||||
message: 'You cannot create antenna any more.',
|
||||
code: 'TOO_MANY_ANTENNAS',
|
||||
@ -40,8 +46,9 @@ export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', minLength: 1, maxLength: 100 },
|
||||
src: { type: 'string', enum: ['home', 'all', 'users', 'list'] },
|
||||
src: { type: 'string', enum: ['home', 'all', 'users', 'list', 'group'] },
|
||||
userListId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
userGroupId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
keywords: { type: 'array', items: {
|
||||
type: 'array', items: {
|
||||
type: 'string',
|
||||
@ -73,6 +80,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
@Inject(DI.userListsRepository)
|
||||
private userListsRepository: UserListsRepository,
|
||||
|
||||
@Inject(DI.userGroupJoiningsRepository)
|
||||
private userGroupJoiningsRepository: UserGroupJoiningsRepository,
|
||||
|
||||
private antennaEntityService: AntennaEntityService,
|
||||
private roleService: RoleService,
|
||||
private idService: IdService,
|
||||
@ -87,6 +97,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
}
|
||||
|
||||
let userList;
|
||||
let userGroupJoining;
|
||||
|
||||
if (ps.src === 'list' && ps.userListId) {
|
||||
userList = await this.userListsRepository.findOneBy({
|
||||
@ -97,6 +108,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
if (userList == null) {
|
||||
throw new ApiError(meta.errors.noSuchUserList);
|
||||
}
|
||||
} else if (ps.src === 'group' && ps.userGroupId) {
|
||||
userGroupJoining = await this.userGroupJoiningsRepository.findOneBy({
|
||||
userGroupId: ps.userGroupId,
|
||||
userId: me.id,
|
||||
});
|
||||
|
||||
if (userGroupJoining == null) {
|
||||
throw new ApiError(meta.errors.noSuchUserGroup);
|
||||
}
|
||||
}
|
||||
|
||||
const antenna = await this.antennasRepository.insert({
|
||||
@ -106,6 +126,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
name: ps.name,
|
||||
src: ps.src,
|
||||
userListId: userList ? userList.id : null,
|
||||
userGroupJoiningId: userGroupJoining ? userGroupJoining.id : null,
|
||||
keywords: ps.keywords,
|
||||
excludeKeywords: ps.excludeKeywords,
|
||||
users: ps.users,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { AntennasRepository, UserListsRepository } from '@/models/index.js';
|
||||
import type { AntennasRepository, UserListsRepository, UserGroupJoiningsRepository } from '@/models/index.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { AntennaEntityService } from '@/core/entities/AntennaEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
@ -25,6 +25,12 @@ export const meta = {
|
||||
code: 'NO_SUCH_USER_LIST',
|
||||
id: '1c6b35c9-943e-48c2-81e4-2844989407f7',
|
||||
},
|
||||
|
||||
noSuchUserGroup: {
|
||||
message: 'No such user group.',
|
||||
code: 'NO_SUCH_USER_GROUP',
|
||||
id: '109ed789-b6eb-456e-b8a9-6059d567d385',
|
||||
},
|
||||
},
|
||||
|
||||
res: {
|
||||
@ -39,8 +45,9 @@ export const paramDef = {
|
||||
properties: {
|
||||
antennaId: { type: 'string', format: 'misskey:id' },
|
||||
name: { type: 'string', minLength: 1, maxLength: 100 },
|
||||
src: { type: 'string', enum: ['home', 'all', 'users', 'list'] },
|
||||
src: { type: 'string', enum: ['home', 'all', 'users', 'list', 'group'] },
|
||||
userListId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
userGroupId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
keywords: { type: 'array', items: {
|
||||
type: 'array', items: {
|
||||
type: 'string',
|
||||
@ -71,6 +78,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
|
||||
@Inject(DI.userListsRepository)
|
||||
private userListsRepository: UserListsRepository,
|
||||
|
||||
@Inject(DI.userGroupJoiningsRepository)
|
||||
private userGroupJoiningsRepository: UserGroupJoiningsRepository,
|
||||
|
||||
private antennaEntityService: AntennaEntityService,
|
||||
private globalEventService: GlobalEventService,
|
||||
@ -87,6 +97,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
}
|
||||
|
||||
let userList;
|
||||
let userGroupJoining;
|
||||
|
||||
if (ps.src === 'list' && ps.userListId) {
|
||||
userList = await this.userListsRepository.findOneBy({
|
||||
@ -97,12 +108,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
if (userList == null) {
|
||||
throw new ApiError(meta.errors.noSuchUserList);
|
||||
}
|
||||
} else if (ps.src === 'group' && ps.userGroupId) {
|
||||
userGroupJoining = await this.userGroupJoiningsRepository.findOneBy({
|
||||
userGroupId: ps.userGroupId,
|
||||
userId: me.id,
|
||||
});
|
||||
|
||||
if (userGroupJoining == null) {
|
||||
throw new ApiError(meta.errors.noSuchUserGroup);
|
||||
}
|
||||
}
|
||||
|
||||
await this.antennasRepository.update(antenna.id, {
|
||||
name: ps.name,
|
||||
src: ps.src,
|
||||
userListId: userList ? userList.id : null,
|
||||
userGroupJoiningId: userGroupJoining ? userGroupJoining.id : null,
|
||||
keywords: ps.keywords,
|
||||
excludeKeywords: ps.excludeKeywords,
|
||||
users: ps.users,
|
||||
|
@ -0,0 +1,69 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { 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'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'read:user-groups',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
group: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'UserGroup',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: [],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.userGroupInvitationsRepository)
|
||||
private userGroupInvitationsRepository: UserGroupInvitationsRepository,
|
||||
|
||||
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');
|
||||
|
||||
const invitations = await query
|
||||
.take(ps.limit)
|
||||
.getMany();
|
||||
|
||||
return await this.userGroupInvitationEntityService.packMany(invitations);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import type { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import type { UserGroup } from '@/models/entities/UserGroup.js';
|
||||
import type { UserGroupJoining } from '@/models/entities/UserGroupJoining.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['groups'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:user-groups',
|
||||
|
||||
description: 'Create a new group.',
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 10,
|
||||
},
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'UserGroup',
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', minLength: 1, maxLength: 100 },
|
||||
},
|
||||
required: ['name'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.userGroupsRepository)
|
||||
private userGroupsRepository: UserGroupsRepository,
|
||||
|
||||
@Inject(DI.userGroupJoiningsRepository)
|
||||
private userGroupJoiningsRepository: UserGroupJoiningsRepository,
|
||||
|
||||
private userGroupEntityService: UserGroupEntityService,
|
||||
private idService: IdService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const userGroup = await this.userGroupsRepository.insert({
|
||||
id: this.idService.genId(),
|
||||
createdAt: new Date(),
|
||||
userId: me.id,
|
||||
name: ps.name,
|
||||
} as UserGroup).then(x => this.userGroupsRepository.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
// Push the owner
|
||||
await this.userGroupJoiningsRepository.insert({
|
||||
id: this.idService.genId(),
|
||||
createdAt: new Date(),
|
||||
userId: me.id,
|
||||
userGroupId: userGroup.id,
|
||||
} as UserGroupJoining);
|
||||
|
||||
return await this.userGroupEntityService.pack(userGroup);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UserGroupsRepository } from '@/models/index.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['groups'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:user-groups',
|
||||
|
||||
description: 'Delete an existing group.',
|
||||
|
||||
errors: {
|
||||
noSuchGroup: {
|
||||
message: 'No such group.',
|
||||
code: 'NO_SUCH_GROUP',
|
||||
id: '63dbd64c-cd77-413f-8e08-61781e210b38',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
groupId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['groupId'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.userGroupsRepository)
|
||||
private userGroupsRepository: UserGroupsRepository,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const userGroup = await this.userGroupsRepository.findOneBy({
|
||||
id: ps.groupId,
|
||||
userId: me.id,
|
||||
});
|
||||
|
||||
if (userGroup == null) {
|
||||
throw new ApiError(meta.errors.noSuchGroup);
|
||||
}
|
||||
|
||||
await this.userGroupsRepository.delete(userGroup.id);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UserGroupInvitationsRepository, UserGroupJoiningsRepository } from '@/models/index.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import type { UserGroupJoining } from '@/models/entities/UserGroupJoining.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['groups', 'users'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:user-groups',
|
||||
|
||||
description: 'Join a group the authenticated user has been invited to.',
|
||||
|
||||
errors: {
|
||||
noSuchInvitation: {
|
||||
message: 'No such invitation.',
|
||||
code: 'NO_SUCH_INVITATION',
|
||||
id: '98c11eca-c890-4f42-9806-c8c8303ebb5e',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
invitationId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['invitationId'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.userGroupInvitationsRepository)
|
||||
private userGroupInvitationsRepository: UserGroupInvitationsRepository,
|
||||
|
||||
@Inject(DI.userGroupJoiningsRepository)
|
||||
private userGroupJoiningsRepository: UserGroupJoiningsRepository,
|
||||
|
||||
private idService: IdService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
// Fetch the invitation
|
||||
const invitation = await this.userGroupInvitationsRepository.findOneBy({
|
||||
id: ps.invitationId,
|
||||
});
|
||||
|
||||
if (invitation == null) {
|
||||
throw new ApiError(meta.errors.noSuchInvitation);
|
||||
}
|
||||
|
||||
if (invitation.userId !== me.id) {
|
||||
throw new ApiError(meta.errors.noSuchInvitation);
|
||||
}
|
||||
|
||||
// Push the user
|
||||
await this.userGroupJoiningsRepository.insert({
|
||||
id: this.idService.genId(),
|
||||
createdAt: new Date(),
|
||||
userId: me.id,
|
||||
userGroupId: invitation.userGroupId,
|
||||
} as UserGroupJoining);
|
||||
|
||||
this.userGroupInvitationsRepository.delete(invitation.id);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UserGroupInvitationsRepository } from '@/models/index.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['groups', 'users'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:user-groups',
|
||||
|
||||
description: 'Delete an existing group invitation for the authenticated user without joining the group.',
|
||||
|
||||
errors: {
|
||||
noSuchInvitation: {
|
||||
message: 'No such invitation.',
|
||||
code: 'NO_SUCH_INVITATION',
|
||||
id: 'ad7471d4-2cd9-44b4-ac68-e7136b4ce656',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
invitationId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['invitationId'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.userGroupInvitationsRepository)
|
||||
private userGroupInvitationsRepository: UserGroupInvitationsRepository,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
// Fetch the invitation
|
||||
const invitation = await this.userGroupInvitationsRepository.findOneBy({
|
||||
id: ps.invitationId,
|
||||
});
|
||||
|
||||
if (invitation == null) {
|
||||
throw new ApiError(meta.errors.noSuchInvitation);
|
||||
}
|
||||
|
||||
if (invitation.userId !== me.id) {
|
||||
throw new ApiError(meta.errors.noSuchInvitation);
|
||||
}
|
||||
|
||||
await this.userGroupInvitationsRepository.delete(invitation.id);
|
||||
});
|
||||
}
|
||||
}
|
122
packages/backend/src/server/api/endpoints/users/groups/invite.ts
Normal file
122
packages/backend/src/server/api/endpoints/users/groups/invite.ts
Normal file
@ -0,0 +1,122 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UserGroupsRepository, UserGroupJoiningsRepository, UserGroupInvitationsRepository } from '@/models/index.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import type { UserGroupInvitation } from '@/models/entities/UserGroupInvitation.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { CreateNotificationService } from '@/core/CreateNotificationService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['groups', 'users'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:user-groups',
|
||||
|
||||
description: 'Invite a user to an existing group.',
|
||||
|
||||
errors: {
|
||||
noSuchGroup: {
|
||||
message: 'No such group.',
|
||||
code: 'NO_SUCH_GROUP',
|
||||
id: '583f8bc0-8eee-4b78-9299-1e14fc91e409',
|
||||
},
|
||||
|
||||
noSuchUser: {
|
||||
message: 'No such user.',
|
||||
code: 'NO_SUCH_USER',
|
||||
id: 'da52de61-002c-475b-90e1-ba64f9cf13a8',
|
||||
},
|
||||
|
||||
alreadyAdded: {
|
||||
message: 'That user has already been added to that group.',
|
||||
code: 'ALREADY_ADDED',
|
||||
id: '7e35c6a0-39b2-4488-aea6-6ee20bd5da2c',
|
||||
},
|
||||
|
||||
alreadyInvited: {
|
||||
message: 'That user has already been invited to that group.',
|
||||
code: 'ALREADY_INVITED',
|
||||
id: 'ee0f58b4-b529-4d13-b761-b9a3e69f97e6',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
groupId: { type: 'string', format: 'misskey:id' },
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['groupId', 'userId'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.userGroupsRepository)
|
||||
private userGroupsRepository: UserGroupsRepository,
|
||||
|
||||
@Inject(DI.userGroupInvitationsRepository)
|
||||
private userGroupInvitationsRepository: UserGroupInvitationsRepository,
|
||||
|
||||
@Inject(DI.userGroupJoiningsRepository)
|
||||
private userGroupJoiningsRepository: UserGroupJoiningsRepository,
|
||||
|
||||
private idService: IdService,
|
||||
private getterService: GetterService,
|
||||
private createNotificationService: CreateNotificationService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
// Fetch the group
|
||||
const userGroup = await this.userGroupsRepository.findOneBy({
|
||||
id: ps.groupId,
|
||||
userId: me.id,
|
||||
});
|
||||
|
||||
if (userGroup == null) {
|
||||
throw new ApiError(meta.errors.noSuchGroup);
|
||||
}
|
||||
|
||||
// Fetch the user
|
||||
const user = await this.getterService.getUser(ps.userId).catch(err => {
|
||||
if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
|
||||
throw err;
|
||||
});
|
||||
|
||||
const joining = await this.userGroupJoiningsRepository.findOneBy({
|
||||
userGroupId: userGroup.id,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (joining) {
|
||||
throw new ApiError(meta.errors.alreadyAdded);
|
||||
}
|
||||
|
||||
const existInvitation = await this.userGroupInvitationsRepository.findOneBy({
|
||||
userGroupId: userGroup.id,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (existInvitation) {
|
||||
throw new ApiError(meta.errors.alreadyInvited);
|
||||
}
|
||||
|
||||
const invitation = await this.userGroupInvitationsRepository.insert({
|
||||
id: this.idService.genId(),
|
||||
createdAt: new Date(),
|
||||
userId: user.id,
|
||||
userGroupId: userGroup.id,
|
||||
} as UserGroupInvitation).then(x => this.userGroupInvitationsRepository.findOneByOrFail(x.identifiers[0]));
|
||||
|
||||
// 通知を作成
|
||||
this.createNotificationService.createNotification(user.id, 'groupInvited', {
|
||||
notifierId: me.id,
|
||||
userGroupInvitationId: invitation.id,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
import { Not, In } from 'typeorm';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['groups', 'account'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'read:user-groups',
|
||||
|
||||
description: 'List the groups that the authenticated user is a member of.',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'UserGroup',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
required: [],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.userGroupsRepository)
|
||||
private userGroupsRepository: UserGroupsRepository,
|
||||
|
||||
@Inject(DI.userGroupJoiningsRepository)
|
||||
private userGroupJoiningsRepository: UserGroupJoiningsRepository,
|
||||
|
||||
private userGroupEntityService: UserGroupEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const ownedGroups = await this.userGroupsRepository.findBy({
|
||||
userId: me.id,
|
||||
});
|
||||
|
||||
const joinings = await this.userGroupJoiningsRepository.findBy({
|
||||
userId: me.id,
|
||||
...(ownedGroups.length > 0 ? {
|
||||
userGroupId: Not(In(ownedGroups.map(x => x.id))),
|
||||
} : {}),
|
||||
});
|
||||
|
||||
return await Promise.all(joinings.map(x => this.userGroupEntityService.pack(x.userGroupId)));
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['groups', 'users'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:user-groups',
|
||||
|
||||
description: 'Leave a group. The owner of a group can not leave. They must transfer ownership or delete the group instead.',
|
||||
|
||||
errors: {
|
||||
noSuchGroup: {
|
||||
message: 'No such group.',
|
||||
code: 'NO_SUCH_GROUP',
|
||||
id: '62780270-1f67-5dc0-daca-3eb510612e31',
|
||||
},
|
||||
|
||||
youAreOwner: {
|
||||
message: 'Your are the owner.',
|
||||
code: 'YOU_ARE_OWNER',
|
||||
id: 'b6d6e0c2-ef8a-9bb8-653d-79f4a3107c69',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
groupId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['groupId'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.userGroupsRepository)
|
||||
private userGroupsRepository: UserGroupsRepository,
|
||||
|
||||
@Inject(DI.userGroupJoiningsRepository)
|
||||
private userGroupJoiningsRepository: UserGroupJoiningsRepository,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
// Fetch the group
|
||||
const userGroup = await this.userGroupsRepository.findOneBy({
|
||||
id: ps.groupId,
|
||||
});
|
||||
|
||||
if (userGroup == null) {
|
||||
throw new ApiError(meta.errors.noSuchGroup);
|
||||
}
|
||||
|
||||
if (me.id === userGroup.userId) {
|
||||
throw new ApiError(meta.errors.youAreOwner);
|
||||
}
|
||||
|
||||
await this.userGroupJoiningsRepository.delete({ userGroupId: userGroup.id, userId: me.id });
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UserGroupsRepository } from '@/models/index.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['groups', 'account'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'read:user-groups',
|
||||
|
||||
description: 'List the groups that the authenticated user is the owner of.',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'UserGroup',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
required: [],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.userGroupsRepository)
|
||||
private userGroupsRepository: UserGroupsRepository,
|
||||
|
||||
private userGroupEntityService: UserGroupEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const userGroups = await this.userGroupsRepository.findBy({
|
||||
userId: me.id,
|
||||
});
|
||||
|
||||
return await Promise.all(userGroups.map(x => this.userGroupEntityService.pack(x)));
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['groups', 'users'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:user-groups',
|
||||
|
||||
description: 'Removes a specified user from a group. The owner can not be removed.',
|
||||
|
||||
errors: {
|
||||
noSuchGroup: {
|
||||
message: 'No such group.',
|
||||
code: 'NO_SUCH_GROUP',
|
||||
id: '4662487c-05b1-4b78-86e5-fd46998aba74',
|
||||
},
|
||||
|
||||
noSuchUser: {
|
||||
message: 'No such user.',
|
||||
code: 'NO_SUCH_USER',
|
||||
id: '0b5cc374-3681-41da-861e-8bc1146f7a55',
|
||||
},
|
||||
|
||||
isOwner: {
|
||||
message: 'The user is the owner.',
|
||||
code: 'IS_OWNER',
|
||||
id: '1546eed5-4414-4dea-81c1-b0aec4f6d2af',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
groupId: { type: 'string', format: 'misskey:id' },
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['groupId', 'userId'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.userGroupsRepository)
|
||||
private userGroupsRepository: UserGroupsRepository,
|
||||
|
||||
@Inject(DI.userGroupJoiningsRepository)
|
||||
private userGroupJoiningsRepository: UserGroupJoiningsRepository,
|
||||
|
||||
private getterService: GetterService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
// Fetch the group
|
||||
const userGroup = await this.userGroupsRepository.findOneBy({
|
||||
id: ps.groupId,
|
||||
userId: me.id,
|
||||
});
|
||||
|
||||
if (userGroup == null) {
|
||||
throw new ApiError(meta.errors.noSuchGroup);
|
||||
}
|
||||
|
||||
// Fetch the user
|
||||
const user = await this.getterService.getUser(ps.userId).catch(err => {
|
||||
if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
|
||||
throw err;
|
||||
});
|
||||
|
||||
if (user.id === userGroup.userId) {
|
||||
throw new ApiError(meta.errors.isOwner);
|
||||
}
|
||||
|
||||
// Pull the user
|
||||
await this.userGroupJoiningsRepository.delete({ userGroupId: userGroup.id, userId: user.id });
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['groups', 'account'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'read:user-groups',
|
||||
|
||||
description: 'Show the properties of a group.',
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'UserGroup',
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchGroup: {
|
||||
message: 'No such group.',
|
||||
code: 'NO_SUCH_GROUP',
|
||||
id: 'ea04751e-9b7e-487b-a509-330fb6bd6b9b',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
groupId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['groupId'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.userGroupsRepository)
|
||||
private userGroupsRepository: UserGroupsRepository,
|
||||
|
||||
@Inject(DI.userGroupJoiningsRepository)
|
||||
private userGroupJoiningsRepository: UserGroupJoiningsRepository,
|
||||
|
||||
private userGroupEntityService: UserGroupEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
// Fetch the group
|
||||
const userGroup = await this.userGroupsRepository.findOneBy({
|
||||
id: ps.groupId,
|
||||
});
|
||||
|
||||
if (userGroup == null) {
|
||||
throw new ApiError(meta.errors.noSuchGroup);
|
||||
}
|
||||
|
||||
const joining = await this.userGroupJoiningsRepository.findOneBy({
|
||||
userId: me.id,
|
||||
userGroupId: userGroup.id,
|
||||
});
|
||||
|
||||
if (joining == null && userGroup.userId !== me.id) {
|
||||
throw new ApiError(meta.errors.noSuchGroup);
|
||||
}
|
||||
|
||||
return await this.userGroupEntityService.pack(userGroup);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UserGroupsRepository, UserGroupJoiningsRepository } from '@/models/index.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['groups', 'users'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:user-groups',
|
||||
|
||||
description: 'Transfer ownership of a group from the authenticated user to another user.',
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'UserGroup',
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchGroup: {
|
||||
message: 'No such group.',
|
||||
code: 'NO_SUCH_GROUP',
|
||||
id: '8e31d36b-2f88-4ccd-a438-e2d78a9162db',
|
||||
},
|
||||
|
||||
noSuchUser: {
|
||||
message: 'No such user.',
|
||||
code: 'NO_SUCH_USER',
|
||||
id: '711f7ebb-bbb9-4dfa-b540-b27809fed5e9',
|
||||
},
|
||||
|
||||
noSuchGroupMember: {
|
||||
message: 'No such group member.',
|
||||
code: 'NO_SUCH_GROUP_MEMBER',
|
||||
id: 'd31bebee-196d-42c2-9a3e-9474d4be6cc4',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
groupId: { type: 'string', format: 'misskey:id' },
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['groupId', 'userId'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.userGroupsRepository)
|
||||
private userGroupsRepository: UserGroupsRepository,
|
||||
|
||||
@Inject(DI.userGroupJoiningsRepository)
|
||||
private userGroupJoiningsRepository: UserGroupJoiningsRepository,
|
||||
|
||||
private userGroupEntityService: UserGroupEntityService,
|
||||
private getterService: GetterService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
// Fetch the group
|
||||
const userGroup = await this.userGroupsRepository.findOneBy({
|
||||
id: ps.groupId,
|
||||
userId: me.id,
|
||||
});
|
||||
|
||||
if (userGroup == null) {
|
||||
throw new ApiError(meta.errors.noSuchGroup);
|
||||
}
|
||||
|
||||
// Fetch the user
|
||||
const user = await this.getterService.getUser(ps.userId).catch(err => {
|
||||
if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
|
||||
throw err;
|
||||
});
|
||||
|
||||
const joining = await this.userGroupJoiningsRepository.findOneBy({
|
||||
userGroupId: userGroup.id,
|
||||
userId: user.id,
|
||||
});
|
||||
|
||||
if (joining == null) {
|
||||
throw new ApiError(meta.errors.noSuchGroupMember);
|
||||
}
|
||||
|
||||
await this.userGroupsRepository.update(userGroup.id, {
|
||||
userId: ps.userId,
|
||||
});
|
||||
|
||||
return await this.userGroupEntityService.pack(userGroup.id);
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { UserGroupsRepository } from '@/models/index.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { UserGroupEntityService } from '@/core/entities/UserGroupEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['groups'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:user-groups',
|
||||
|
||||
description: 'Update the properties of a group.',
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'UserGroup',
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchGroup: {
|
||||
message: 'No such group.',
|
||||
code: 'NO_SUCH_GROUP',
|
||||
id: '9081cda3-7a9e-4fac-a6ce-908d70f282f6',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
groupId: { type: 'string', format: 'misskey:id' },
|
||||
name: { type: 'string', minLength: 1, maxLength: 100 },
|
||||
},
|
||||
required: ['groupId', 'name'],
|
||||
} as const;
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||
constructor(
|
||||
@Inject(DI.userGroupsRepository)
|
||||
private userGroupsRepository: UserGroupsRepository,
|
||||
|
||||
private userGroupEntityService: UserGroupEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
// Fetch the group
|
||||
const userGroup = await this.userGroupsRepository.findOneBy({
|
||||
id: ps.groupId,
|
||||
userId: me.id,
|
||||
});
|
||||
|
||||
if (userGroup == null) {
|
||||
throw new ApiError(meta.errors.noSuchGroup);
|
||||
}
|
||||
|
||||
await this.userGroupsRepository.update(userGroup.id, {
|
||||
name: ps.name,
|
||||
});
|
||||
|
||||
return await this.userGroupEntityService.pack(userGroup.id);
|
||||
});
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ import type { Channel as ChannelModel } from '@/models/entities/Channel.js';
|
||||
import type { FollowingsRepository, MutingsRepository, UserProfilesRepository, ChannelFollowingsRepository, BlockingsRepository } from '@/models/index.js';
|
||||
import type { AccessToken } from '@/models/entities/AccessToken.js';
|
||||
import type { UserProfile } from '@/models/entities/UserProfile.js';
|
||||
import type { UserGroup } from '@/models/entities/UserGroup.js';
|
||||
import type { Packed } from '@/misc/schema.js';
|
||||
import type { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import type { NoteReadService } from '@/core/NoteReadService.js';
|
||||
|
@ -6,6 +6,7 @@ import type { Antenna } from '@/models/entities/Antenna.js';
|
||||
import type { DriveFile } from '@/models/entities/DriveFile.js';
|
||||
import type { DriveFolder } from '@/models/entities/DriveFolder.js';
|
||||
import type { UserList } from '@/models/entities/UserList.js';
|
||||
import type { UserGroup } from '@/models/entities/UserGroup.js';
|
||||
import type { AbuseUserReport } from '@/models/entities/AbuseUserReport.js';
|
||||
import type { Signin } from '@/models/entities/Signin.js';
|
||||
import type { Page } from '@/models/entities/Page.js';
|
||||
|
@ -1,4 +1,4 @@
|
||||
export const notificationTypes = ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app'] as const;
|
||||
export const notificationTypes = ['follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'achievementEarned', 'app'] as const;
|
||||
|
||||
export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const;
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
<i v-if="notification.type === 'follow'" class="ti ti-plus"></i>
|
||||
<i v-else-if="notification.type === 'receiveFollowRequest'" class="ti ti-clock"></i>
|
||||
<i v-else-if="notification.type === 'followRequestAccepted'" class="ti ti-check"></i>
|
||||
<i v-else-if="notification.type === 'groupInvited'" class="ti ti-certificate-2"></i>
|
||||
<i v-else-if="notification.type === 'renote'" class="ti ti-repeat"></i>
|
||||
<i v-else-if="notification.type === 'reply'" class="ti ti-arrow-back-up"></i>
|
||||
<i v-else-if="notification.type === 'mention'" class="ti ti-at"></i>
|
||||
@ -73,6 +74,12 @@
|
||||
<button class="_textButton" @click="acceptFollowRequest()">{{ i18n.ts.accept }}</button> | <button class="_textButton" @click="rejectFollowRequest()">{{ i18n.ts.reject }}</button>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="notification.type === 'groupInvited'">
|
||||
<span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.groupInvited }}: <b>{{ notification.invitation.group.name }}</b></span>
|
||||
<div v-if="full && !groupInviteDone">
|
||||
<button class="_textButton" @click="acceptGroupInvitation()">{{ i18n.ts.accept }}</button> | <button class="_textButton" @click="rejectGroupInvitation()">{{ i18n.ts.reject }}</button>
|
||||
</div>
|
||||
</template>
|
||||
<span v-else-if="notification.type === 'app'" :class="$style.text">
|
||||
<Mfm :text="notification.body" :nowrap="false"/>
|
||||
</span>
|
||||
@ -138,6 +145,7 @@ onUnmounted(() => {
|
||||
});
|
||||
|
||||
const followRequestDone = ref(false);
|
||||
const groupInviteDone = ref(false);
|
||||
|
||||
const acceptFollowRequest = () => {
|
||||
followRequestDone.value = true;
|
||||
@ -149,6 +157,16 @@ const rejectFollowRequest = () => {
|
||||
os.api('following/requests/reject', { userId: props.notification.user.id });
|
||||
};
|
||||
|
||||
const acceptGroupInvitation = () => {
|
||||
groupInviteDone.value = true;
|
||||
os.apiWithDialog('users/groups/invitations/accept', { invitationId: props.notification.invitation.id });
|
||||
};
|
||||
|
||||
const rejectGroupInvitation = () => {
|
||||
groupInviteDone.value = true;
|
||||
os.api('users/groups/invitations/reject', { invitationId: props.notification.invitation.id });
|
||||
};
|
||||
|
||||
useTooltip(reactionRef, (showing) => {
|
||||
os.popup(XReactionTooltip, {
|
||||
showing,
|
||||
@ -206,7 +224,7 @@ useTooltip(reactionRef, (showing) => {
|
||||
}
|
||||
}
|
||||
|
||||
.t_follow, .t_followRequestAccepted, .t_receiveFollowRequest {
|
||||
.t_follow, .t_followRequestAccepted, .t_receiveFollowRequest, .t_groupInvited {
|
||||
padding: 3px;
|
||||
background: #36aed2;
|
||||
pointer-events: none;
|
||||
|
@ -50,6 +50,14 @@ export const navbarItemDef = reactive({
|
||||
show: computed(() => $i != null),
|
||||
to: '/my/lists',
|
||||
},
|
||||
/*
|
||||
groups: {
|
||||
title: i18n.ts.groups,
|
||||
icon: 'ti ti-users',
|
||||
show: computed(() => $i != null),
|
||||
to: '/my/groups',
|
||||
},
|
||||
*/
|
||||
antennas: {
|
||||
title: i18n.ts.antennas,
|
||||
icon: 'ti ti-antenna',
|
||||
|
@ -17,6 +17,7 @@ let draft = $ref({
|
||||
name: '',
|
||||
src: 'all',
|
||||
userListId: null,
|
||||
userGroupId: null,
|
||||
users: [],
|
||||
keywords: [],
|
||||
excludeKeywords: [],
|
||||
|
@ -11,11 +11,16 @@
|
||||
<!--<option value="home">{{ i18n.ts._antennaSources.homeTimeline }}</option>-->
|
||||
<option value="users">{{ i18n.ts._antennaSources.users }}</option>
|
||||
<!--<option value="list">{{ i18n.ts._antennaSources.userList }}</option>-->
|
||||
<!--<option value="group">{{ i18n.ts._antennaSources.userGroup }}</option>-->
|
||||
</MkSelect>
|
||||
<MkSelect v-if="src === 'list'" v-model="userListId">
|
||||
<template #label>{{ i18n.ts.userList }}</template>
|
||||
<option v-for="list in userLists" :key="list.id" :value="list.id">{{ list.name }}</option>
|
||||
</MkSelect>
|
||||
<MkSelect v-else-if="src === 'group'" v-model="userGroupId">
|
||||
<template #label>{{ i18n.ts.userGroup }}</template>
|
||||
<option v-for="group in userGroups" :key="group.id" :value="group.id">{{ group.name }}</option>
|
||||
</MkSelect>
|
||||
<MkTextarea v-else-if="src === 'users'" v-model="users">
|
||||
<template #label>{{ i18n.ts.users }}</template>
|
||||
<template #caption>{{ i18n.ts.antennaUsersDescription }} <button class="_textButton" @click="addUser">{{ i18n.ts.addUser }}</button></template>
|
||||
@ -65,6 +70,7 @@ const emit = defineEmits<{
|
||||
let name: string = $ref(props.antenna.name);
|
||||
let src: string = $ref(props.antenna.src);
|
||||
let userListId: any = $ref(props.antenna.userListId);
|
||||
let userGroupId: any = $ref(props.antenna.userGroupId);
|
||||
let users: string = $ref(props.antenna.users.join('\n'));
|
||||
let keywords: string = $ref(props.antenna.keywords.map(x => x.join(' ')).join('\n'));
|
||||
let excludeKeywords: string = $ref(props.antenna.excludeKeywords.map(x => x.join(' ')).join('\n'));
|
||||
@ -73,11 +79,19 @@ let withReplies: boolean = $ref(props.antenna.withReplies);
|
||||
let withFile: boolean = $ref(props.antenna.withFile);
|
||||
let notify: boolean = $ref(props.antenna.notify);
|
||||
let userLists: any = $ref(null);
|
||||
let userGroups: any = $ref(null);
|
||||
|
||||
watch(() => src, async () => {
|
||||
if (src === 'list' && userLists === null) {
|
||||
userLists = await os.api('users/lists/list');
|
||||
}
|
||||
|
||||
if (src === 'group' && userGroups === null) {
|
||||
const groups1 = await os.api('users/groups/owned');
|
||||
const groups2 = await os.api('users/groups/joined');
|
||||
|
||||
userGroups = [...groups1, ...groups2];
|
||||
}
|
||||
});
|
||||
|
||||
async function saveAntenna() {
|
||||
@ -85,6 +99,7 @@ async function saveAntenna() {
|
||||
name,
|
||||
src,
|
||||
userListId,
|
||||
userGroupId,
|
||||
withReplies,
|
||||
withFile,
|
||||
notify,
|
||||
|
@ -34,6 +34,9 @@
|
||||
<MkSwitch v-model="emailNotification_receiveFollowRequest">
|
||||
{{ i18n.ts._notification._types.receiveFollowRequest }}
|
||||
</MkSwitch>
|
||||
<MkSwitch v-model="emailNotification_groupInvited">
|
||||
{{ i18n.ts._notification._types.groupInvited }}
|
||||
</MkSwitch>
|
||||
</div>
|
||||
</FormSection>
|
||||
</div>
|
||||
@ -75,6 +78,7 @@ const emailNotification_reply = ref($i!.emailNotificationTypes.includes('reply')
|
||||
const emailNotification_quote = ref($i!.emailNotificationTypes.includes('quote'));
|
||||
const emailNotification_follow = ref($i!.emailNotificationTypes.includes('follow'));
|
||||
const emailNotification_receiveFollowRequest = ref($i!.emailNotificationTypes.includes('receiveFollowRequest'));
|
||||
const emailNotification_groupInvited = ref($i!.emailNotificationTypes.includes('groupInvited'));
|
||||
|
||||
const saveNotificationSettings = () => {
|
||||
os.api('i/update', {
|
||||
@ -84,11 +88,12 @@ const saveNotificationSettings = () => {
|
||||
...[emailNotification_quote.value ? 'quote' : null],
|
||||
...[emailNotification_follow.value ? 'follow' : null],
|
||||
...[emailNotification_receiveFollowRequest.value ? 'receiveFollowRequest' : null],
|
||||
...[emailNotification_groupInvited.value ? 'groupInvited' : null],
|
||||
].filter(x => x != null),
|
||||
});
|
||||
};
|
||||
|
||||
watch([emailNotification_mention, emailNotification_reply, emailNotification_quote, emailNotification_follow, emailNotification_receiveFollowRequest], () => {
|
||||
watch([emailNotification_mention, emailNotification_reply, emailNotification_quote, emailNotification_follow, emailNotification_receiveFollowRequest, emailNotification_groupInvited], () => {
|
||||
saveNotificationSettings();
|
||||
});
|
||||
|
||||
|
@ -35,6 +35,28 @@ export function getUserMenu(user, router: Router = mainRouter) {
|
||||
});
|
||||
}
|
||||
|
||||
async function inviteGroup() {
|
||||
const groups = await os.api('users/groups/owned');
|
||||
if (groups.length === 0) {
|
||||
os.alert({
|
||||
type: 'error',
|
||||
text: i18n.ts.youHaveNoGroups,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const { canceled, result: groupId } = await os.select({
|
||||
title: i18n.ts.group,
|
||||
items: groups.map(group => ({
|
||||
value: group.id, text: group.name,
|
||||
})),
|
||||
});
|
||||
if (canceled) return;
|
||||
os.apiWithDialog('users/groups/invite', {
|
||||
groupId: groupId,
|
||||
userId: user.id,
|
||||
});
|
||||
}
|
||||
|
||||
async function toggleMute() {
|
||||
if (user.isMuted) {
|
||||
os.apiWithDialog('mute/delete', {
|
||||
@ -134,11 +156,20 @@ export function getUserMenu(user, router: Router = mainRouter) {
|
||||
action: () => {
|
||||
os.post({ specified: user });
|
||||
},
|
||||
}, null, {
|
||||
}, meId !== user.id ? {
|
||||
type: 'link',
|
||||
icon: 'ti ti-messages',
|
||||
text: i18n.ts.startMessaging,
|
||||
to: '/my/messaging/' + Acct.toString(user),
|
||||
} : undefined, null, {
|
||||
icon: 'ti ti-list',
|
||||
text: i18n.ts.addToList,
|
||||
action: pushList,
|
||||
}] as any;
|
||||
}, meId !== user.id ? {
|
||||
icon: 'ti ti-users',
|
||||
text: i18n.ts.inviteToGroup,
|
||||
action: inviteGroup,
|
||||
} : undefined] as any;
|
||||
|
||||
if ($i && meId !== user.id) {
|
||||
menu = menu.concat([null, {
|
||||
|
@ -209,6 +209,23 @@ async function composeNotification(data: pushNotificationDataMap[keyof pushNotif
|
||||
data,
|
||||
}];
|
||||
|
||||
case 'groupInvited':
|
||||
return [t('_notification.youWereInvitedToGroup', { userName: getUserName(data.body.user) }), {
|
||||
body: data.body.invitation.group.name,
|
||||
badge: iconUrl('users'),
|
||||
data,
|
||||
actions: [
|
||||
{
|
||||
action: 'accept',
|
||||
title: t('accept'),
|
||||
},
|
||||
{
|
||||
action: 'reject',
|
||||
title: t('reject'),
|
||||
},
|
||||
],
|
||||
}];
|
||||
|
||||
case 'app':
|
||||
return [data.body.header ?? data.body.body, {
|
||||
body: data.body.header ? data.body.body : '',
|
||||
|
@ -32,10 +32,18 @@ export function openAntenna(antennaId: string, loginId: string) {
|
||||
return openClient('push', `/timeline/antenna/${antennaId}`, loginId, { antennaId });
|
||||
}
|
||||
|
||||
export async function openChat(body: any, loginId: string) {
|
||||
if (body.groupId === null) {
|
||||
return openClient('push', `/my/messaging/${getAcct(body.user)}`, loginId, { body });
|
||||
} else {
|
||||
return openClient('push', `/my/messaging/group/${body.groupId}`, loginId, { body });
|
||||
}
|
||||
}
|
||||
|
||||
// post-formのオプションから投稿フォームを開く
|
||||
export async function openPost(options: any, loginId: string) {
|
||||
// クエリを作成しておく
|
||||
let url = '/share?';
|
||||
let url = `/share?`;
|
||||
if (options.initialText) url += `text=${options.initialText}&`;
|
||||
if (options.reply) url += `replyId=${options.reply.id}&`;
|
||||
if (options.renote) url += `renoteId=${options.renote.id}&`;
|
||||
@ -56,7 +64,7 @@ export async function openClient(order: swMessageOrderType, url: string, loginId
|
||||
|
||||
export async function findClient() {
|
||||
const clients = await self.clients.matchAll({
|
||||
type: 'window',
|
||||
type: 'window'
|
||||
});
|
||||
for (const c of clients) {
|
||||
if (c.url.indexOf('?zen') < 0) return c;
|
||||
|
@ -118,6 +118,9 @@ globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEv
|
||||
case 'receiveFollowRequest':
|
||||
await swos.api('following/requests/accept', loginId, { userId: data.body.userId });
|
||||
break;
|
||||
case 'groupInvited':
|
||||
await swos.api('users/groups/invitations/accept', loginId, { invitationId: data.body.invitation.id });
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'reject':
|
||||
@ -125,6 +128,9 @@ globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEv
|
||||
case 'receiveFollowRequest':
|
||||
await swos.api('following/requests/reject', loginId, { userId: data.body.userId });
|
||||
break;
|
||||
case 'groupInvited':
|
||||
await swos.api('users/groups/invitations/reject', loginId, { invitationId: data.body.invitation.id });
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'showFollowRequests':
|
||||
@ -135,6 +141,9 @@ globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEv
|
||||
case 'receiveFollowRequest':
|
||||
client = await swos.openClient('push', '/my/follow-requests', loginId);
|
||||
break;
|
||||
case 'groupInvited':
|
||||
client = await swos.openClient('push', '/my/groups', loginId);
|
||||
break;
|
||||
case 'reaction':
|
||||
client = await swos.openNote(data.body.note.id, loginId);
|
||||
break;
|
||||
|
Loading…
Reference in New Issue
Block a user