summaryrefslogtreecommitdiff
path: root/src/models
diff options
context:
space:
mode:
authorsyuilo <syuilotan@yahoo.co.jp>2019-05-18 20:36:33 +0900
committersyuilo <syuilotan@yahoo.co.jp>2019-05-18 20:36:33 +0900
commitc7cc3dcdfd2c0962a39e7186852a17dbd09b6a5b (patch)
treec2e1671787c00daa8963c879dba6fbdab6f02d66 /src/models
parentFix bug (diff)
downloadsharkey-c7cc3dcdfd2c0962a39e7186852a17dbd09b6a5b.tar.gz
sharkey-c7cc3dcdfd2c0962a39e7186852a17dbd09b6a5b.tar.bz2
sharkey-c7cc3dcdfd2c0962a39e7186852a17dbd09b6a5b.zip
ユーザーグループ
Resolve #3218
Diffstat (limited to 'src/models')
-rw-r--r--src/models/entities/messaging-message.ts24
-rw-r--r--src/models/entities/user-group-joining.ts41
-rw-r--r--src/models/entities/user-group.ts46
-rw-r--r--src/models/index.ts6
-rw-r--r--src/models/repositories/messaging-message.ts38
-rw-r--r--src/models/repositories/user-group.ts61
-rw-r--r--src/models/repositories/user.ts35
7 files changed, 233 insertions, 18 deletions
diff --git a/src/models/entities/messaging-message.ts b/src/models/entities/messaging-message.ts
index d3c3eab3a2..c18897a37d 100644
--- a/src/models/entities/messaging-message.ts
+++ b/src/models/entities/messaging-message.ts
@@ -2,6 +2,7 @@ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typ
import { User } from './user';
import { DriveFile } from './drive-file';
import { id } from '../id';
+import { UserGroup } from './user-group';
@Entity()
export class MessagingMessage {
@@ -29,10 +30,10 @@ export class MessagingMessage {
@Index()
@Column({
- ...id(),
+ ...id(), nullable: true,
comment: 'The recipient user ID.'
})
- public recipientId: User['id'];
+ public recipientId: User['id'] | null;
@ManyToOne(type => User, {
onDelete: 'CASCADE'
@@ -40,6 +41,19 @@ export class MessagingMessage {
@JoinColumn()
public recipient: User | null;
+ @Index()
+ @Column({
+ ...id(), nullable: true,
+ comment: 'The recipient group ID.'
+ })
+ public groupId: UserGroup['id'] | null;
+
+ @ManyToOne(type => UserGroup, {
+ onDelete: 'CASCADE'
+ })
+ @JoinColumn()
+ public group: UserGroup | null;
+
@Column('varchar', {
length: 4096, nullable: true
})
@@ -52,6 +66,12 @@ export class MessagingMessage {
@Column({
...id(),
+ array: true, default: '{}'
+ })
+ public reads: User['id'][];
+
+ @Column({
+ ...id(),
nullable: true,
})
public fileId: DriveFile['id'] | null;
diff --git a/src/models/entities/user-group-joining.ts b/src/models/entities/user-group-joining.ts
new file mode 100644
index 0000000000..17b534f42f
--- /dev/null
+++ b/src/models/entities/user-group-joining.ts
@@ -0,0 +1,41 @@
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
+import { User } from './user';
+import { UserGroup } from './user-group';
+import { id } from '../id';
+
+@Entity()
+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;
+}
diff --git a/src/models/entities/user-group.ts b/src/models/entities/user-group.ts
new file mode 100644
index 0000000000..f4bac03223
--- /dev/null
+++ b/src/models/entities/user-group.ts
@@ -0,0 +1,46 @@
+import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm';
+import { User } from './user';
+import { id } from '../id';
+
+@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;
+ }
+ }
+}
diff --git a/src/models/index.ts b/src/models/index.ts
index a63bb2c2b5..c05d7febe5 100644
--- a/src/models/index.ts
+++ b/src/models/index.ts
@@ -6,7 +6,6 @@ import { PollVote } from './entities/poll-vote';
import { Meta } from './entities/meta';
import { SwSubscription } from './entities/sw-subscription';
import { NoteWatching } from './entities/note-watching';
-import { UserListJoining } from './entities/user-list-joining';
import { NoteUnread } from './entities/note-unread';
import { RegistrationTicket } from './entities/registration-tickets';
import { UserRepository } from './repositories/user';
@@ -20,6 +19,9 @@ import { SigninRepository } from './repositories/signin';
import { MessagingMessageRepository } from './repositories/messaging-message';
import { ReversiGameRepository } from './repositories/games/reversi/game';
import { UserListRepository } from './repositories/user-list';
+import { UserListJoining } from './entities/user-list-joining';
+import { UserGroupRepository } from './repositories/user-group';
+import { UserGroupJoining } from './entities/user-group-joining';
import { FollowRequestRepository } from './repositories/follow-request';
import { MutingRepository } from './repositories/muting';
import { BlockingRepository } from './repositories/blocking';
@@ -52,6 +54,8 @@ export const UserKeypairs = getRepository(UserKeypair);
export const UserPublickeys = getRepository(UserPublickey);
export const UserLists = getCustomRepository(UserListRepository);
export const UserListJoinings = getRepository(UserListJoining);
+export const UserGroups = getCustomRepository(UserGroupRepository);
+export const UserGroupJoinings = getRepository(UserGroupJoining);
export const UserNotePinings = getRepository(UserNotePining);
export const Followings = getCustomRepository(FollowingRepository);
export const FollowRequests = getCustomRepository(FollowRequestRepository);
diff --git a/src/models/repositories/messaging-message.ts b/src/models/repositories/messaging-message.ts
index 33f95bbd5f..a64ed07328 100644
--- a/src/models/repositories/messaging-message.ts
+++ b/src/models/repositories/messaging-message.ts
@@ -1,6 +1,6 @@
import { EntityRepository, Repository } from 'typeorm';
import { MessagingMessage } from '../entities/messaging-message';
-import { Users, DriveFiles } from '..';
+import { Users, DriveFiles, UserGroups } from '..';
import { ensure } from '../../prelude/ensure';
import { types, bool, SchemaType } from '../../misc/schema';
@@ -16,11 +16,13 @@ export class MessagingMessageRepository extends Repository<MessagingMessage> {
src: MessagingMessage['id'] | MessagingMessage,
me?: any,
options?: {
- populateRecipient: boolean
+ populateRecipient?: boolean,
+ populateGroup?: boolean,
}
): Promise<PackedMessagingMessage> {
const opts = options || {
- populateRecipient: true
+ populateRecipient: true,
+ populateGroup: true,
};
const message = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
@@ -32,10 +34,13 @@ export class MessagingMessageRepository extends Repository<MessagingMessage> {
userId: message.userId,
user: await Users.pack(message.user || message.userId, me),
recipientId: message.recipientId,
- recipient: opts.populateRecipient ? await Users.pack(message.recipient || message.recipientId, me) : undefined,
+ recipient: message.recipientId && opts.populateRecipient ? await Users.pack(message.recipient || message.recipientId, me) : undefined,
+ groupId: message.recipientId,
+ group: message.groupId && opts.populateGroup ? await UserGroups.pack(message.group || message.groupId) : undefined,
fileId: message.fileId,
file: message.fileId ? await DriveFiles.pack(message.fileId) : null,
- isRead: message.isRead
+ isRead: message.isRead,
+ reads: message.reads,
};
}
}
@@ -83,17 +88,36 @@ export const packedMessagingMessageSchema = {
},
recipientId: {
type: types.string,
- optional: bool.false, nullable: bool.false,
+ optional: bool.false, nullable: bool.true,
format: 'id',
},
recipient: {
type: types.object,
- optional: bool.true, nullable: bool.false,
+ optional: bool.true, nullable: bool.true,
ref: 'User'
},
+ groupId: {
+ type: types.string,
+ optional: bool.false, nullable: bool.true,
+ format: 'id',
+ },
+ group: {
+ type: types.object,
+ optional: bool.true, nullable: bool.true,
+ ref: 'UserGroup'
+ },
isRead: {
type: types.boolean,
optional: bool.true, nullable: bool.false,
},
+ reads: {
+ type: types.array,
+ optional: bool.true, nullable: bool.false,
+ items: {
+ type: types.string,
+ optional: bool.false, nullable: bool.false,
+ format: 'id'
+ }
+ },
},
};
diff --git a/src/models/repositories/user-group.ts b/src/models/repositories/user-group.ts
new file mode 100644
index 0000000000..8bb1ae8330
--- /dev/null
+++ b/src/models/repositories/user-group.ts
@@ -0,0 +1,61 @@
+import { EntityRepository, Repository } from 'typeorm';
+import { UserGroup } from '../entities/user-group';
+import { ensure } from '../../prelude/ensure';
+import { UserGroupJoinings } from '..';
+import { bool, types, SchemaType } from '../../misc/schema';
+
+export type PackedUserGroup = SchemaType<typeof packedUserGroupSchema>;
+
+@EntityRepository(UserGroup)
+export class UserGroupRepository extends Repository<UserGroup> {
+ public async pack(
+ src: UserGroup['id'] | UserGroup,
+ ): Promise<PackedUserGroup> {
+ const userGroup = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
+
+ const users = await UserGroupJoinings.find({
+ userGroupId: userGroup.id
+ });
+
+ return {
+ id: userGroup.id,
+ createdAt: userGroup.createdAt.toISOString(),
+ name: userGroup.name,
+ userIds: users.map(x => x.userId)
+ };
+ }
+}
+
+export const packedUserGroupSchema = {
+ type: types.object,
+ optional: bool.false, nullable: bool.false,
+ properties: {
+ id: {
+ type: types.string,
+ optional: bool.false, nullable: bool.false,
+ format: 'id',
+ description: 'The unique identifier for this UserGroup.',
+ example: 'xxxxxxxxxx',
+ },
+ createdAt: {
+ type: types.string,
+ optional: bool.false, nullable: bool.false,
+ format: 'date-time',
+ description: 'The date that the UserGroup was created.'
+ },
+ name: {
+ type: types.string,
+ optional: bool.false, nullable: bool.false,
+ description: 'The name of the UserGroup.'
+ },
+ userIds: {
+ type: types.array,
+ nullable: bool.false, optional: bool.true,
+ items: {
+ type: types.string,
+ nullable: bool.false, optional: bool.false,
+ format: 'id',
+ }
+ },
+ },
+};
diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts
index 330220fb72..f81fa6bc77 100644
--- a/src/models/repositories/user.ts
+++ b/src/models/repositories/user.ts
@@ -1,6 +1,6 @@
import { EntityRepository, Repository, In } from 'typeorm';
import { User, ILocalUser, IRemoteUser } from '../entities/user';
-import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles } from '..';
+import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserGroupJoinings } from '..';
import { ensure } from '../../prelude/ensure';
import config from '../../config';
import { SchemaType, bool, types } from '../../misc/schema';
@@ -54,6 +54,31 @@ export class UserRepository extends Repository<User> {
};
}
+ public async getHasUnreadMessagingMessage(userId: User['id']): Promise<boolean> {
+ const joinings = await UserGroupJoinings.find({ userId: userId });
+
+ const groupQs = Promise.all(joinings.map(j => MessagingMessages.createQueryBuilder('message')
+ .where(`message.groupId = :groupId`, { groupId: j.userGroupId })
+ .andWhere('message.userId != :userId', { userId: userId })
+ .andWhere('NOT (:userId = ANY(message.reads))', { userId: userId })
+ .andWhere('message.createdAt > :joinedAt', { joinedAt: j.createdAt }) // 自分が加入する前の会話については、未読扱いしない
+ .getOne().then(x => x != null)));
+
+ const [withUser, withGroups] = await Promise.all([
+ // TODO: ミュートを考慮
+ MessagingMessages.count({
+ where: {
+ recipientId: userId,
+ isRead: false
+ },
+ take: 1
+ }).then(count => count > 0),
+ groupQs
+ ]);
+
+ return withUser || withGroups.some(x => x);
+ }
+
public async pack(
src: User['id'] | User,
me?: User['id'] | User | null | undefined,
@@ -151,13 +176,7 @@ export class UserRepository extends Repository<User> {
autoWatch: profile!.autoWatch,
alwaysMarkNsfw: profile!.alwaysMarkNsfw,
carefulBot: profile!.carefulBot,
- hasUnreadMessagingMessage: MessagingMessages.count({
- where: {
- recipientId: user.id,
- isRead: false
- },
- take: 1
- }).then(count => count > 0),
+ hasUnreadMessagingMessage: this.getHasUnreadMessagingMessage(user.id),
hasUnreadNotification: Notifications.count({
where: {
notifieeId: user.id,