* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wop

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* add notes

* wip

* wip

* wip

* wip

* sound

* wip

* add kick_gaba2

* wip
This commit is contained in:
syuilo 2020-08-18 22:44:21 +09:00 committed by GitHub
parent 122076e8ea
commit 9855405b89
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
70 changed files with 2191 additions and 184 deletions

View file

@ -0,0 +1,43 @@
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
import { User } from './user';
import { id } from '../id';
import { Channel } from './channel';
@Entity()
@Index(['followerId', 'followeeId'], { unique: true })
export class ChannelFollowing {
@PrimaryColumn(id())
public id: string;
@Index()
@Column('timestamp with time zone', {
comment: 'The created date of the ChannelFollowing.'
})
public createdAt: Date;
@Index()
@Column({
...id(),
comment: 'The followee channel ID.'
})
public followeeId: Channel['id'];
@ManyToOne(type => Channel, {
onDelete: 'CASCADE'
})
@JoinColumn()
public followee: Channel | null;
@Index()
@Column({
...id(),
comment: 'The follower user ID.'
})
public followerId: User['id'];
@ManyToOne(type => User, {
onDelete: 'CASCADE'
})
@JoinColumn()
public follower: User | null;
}

View file

@ -0,0 +1,35 @@
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
import { Note } from './note';
import { Channel } from './channel';
import { id } from '../id';
@Entity()
@Index(['channelId', 'noteId'], { unique: true })
export class ChannelNotePining {
@PrimaryColumn(id())
public id: string;
@Column('timestamp with time zone', {
comment: 'The created date of the ChannelNotePining.'
})
public createdAt: Date;
@Index()
@Column(id())
public channelId: Channel['id'];
@ManyToOne(type => Channel, {
onDelete: 'CASCADE'
})
@JoinColumn()
public channel: Channel | null;
@Column(id())
public noteId: Note['id'];
@ManyToOne(type => Note, {
onDelete: 'CASCADE'
})
@JoinColumn()
public note: Note | null;
}

View file

@ -0,0 +1,74 @@
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
import { User } from './user';
import { id } from '../id';
import { DriveFile } from './drive-file';
@Entity()
export class Channel {
@PrimaryColumn(id())
public id: string;
@Index()
@Column('timestamp with time zone', {
comment: 'The created date of the Channel.'
})
public createdAt: Date;
@Index()
@Column('timestamp with time zone', {
nullable: true
})
public lastNotedAt: Date | null;
@Index()
@Column({
...id(),
comment: 'The owner ID.'
})
public userId: User['id'];
@ManyToOne(type => User, {
onDelete: 'SET NULL'
})
@JoinColumn()
public user: User | null;
@Column('varchar', {
length: 128,
comment: 'The name of the Channel.'
})
public name: string;
@Column('varchar', {
length: 2048, nullable: true,
comment: 'The description of the Channel.'
})
public description: string | null;
@Column({
...id(),
nullable: true,
comment: 'The ID of banner Channel.'
})
public bannerId: DriveFile['id'] | null;
@ManyToOne(type => DriveFile, {
onDelete: 'SET NULL'
})
@JoinColumn()
public banner: DriveFile | null;
@Index()
@Column('integer', {
default: 0,
comment: 'The count of notes.'
})
public notesCount: number;
@Index()
@Column('integer', {
default: 0,
comment: 'The count of users.'
})
public usersCount: number;
}

View file

@ -2,6 +2,7 @@ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typ
import { User } from './user';
import { Note } from './note';
import { id } from '../id';
import { Channel } from './channel';
@Entity()
@Index(['userId', 'noteId'], { unique: true })
@ -29,15 +30,34 @@ export class NoteUnread {
@JoinColumn()
public note: Note | null;
/**
*
*/
@Index()
@Column('boolean')
public isMentioned: boolean;
/**
* 稿
*/
@Index()
@Column('boolean')
public isSpecified: boolean;
//#region Denormalized fields
@Index()
@Column({
...id(),
comment: '[Denormalized]'
})
public noteUserId: User['id'];
/**
* 稿
*/
@Column('boolean')
public isSpecified: boolean;
@Index()
@Column({
...id(),
nullable: true,
comment: '[Denormalized]'
})
public noteChannelId: Channel['id'] | null;
//#endregion
}

View file

@ -3,7 +3,7 @@ import { User } from './user';
import { DriveFile } from './drive-file';
import { id } from '../id';
import { noteVisibilities } from '../../types';
import { Channel } from './channel';
@Entity()
@Index('IDX_NOTE_TAGS', { synchronize: false })
@ -173,6 +173,20 @@ export class Note {
})
public hasPoll: boolean;
@Index()
@Column({
...id(),
nullable: true, default: null,
comment: 'The ID of source channel.'
})
public channelId: Channel['id'] | null;
@ManyToOne(type => Channel, {
onDelete: 'CASCADE'
})
@JoinColumn()
public channel: Channel | null;
//#region Denormalized fields
@Index()
@Column('varchar', {

View file

@ -15,7 +15,7 @@ import { DriveFileRepository } from './repositories/drive-file';
import { DriveFolderRepository } from './repositories/drive-folder';
import { Log } from './entities/log';
import { AccessToken } from './entities/access-token';
import { UserNotePining } from './entities/user-note-pinings';
import { UserNotePining } from './entities/user-note-pining';
import { SigninRepository } from './repositories/signin';
import { MessagingMessageRepository } from './repositories/messaging-message';
import { ReversiGameRepository } from './repositories/games/reversi/game';
@ -53,7 +53,10 @@ import { PromoNote } from './entities/promo-note';
import { PromoRead } from './entities/promo-read';
import { EmojiRepository } from './repositories/emoji';
import { RelayRepository } from './repositories/relay';
import { ChannelRepository } from './repositories/channel';
import { MutedNote } from './entities/muted-note';
import { ChannelFollowing } from './entities/channel-following';
import { ChannelNotePining } from './entities/channel-note-pining';
export const Announcements = getRepository(Announcement);
export const AnnouncementReads = getRepository(AnnouncementRead);
@ -110,3 +113,6 @@ export const PromoNotes = getRepository(PromoNote);
export const PromoReads = getRepository(PromoRead);
export const Relays = getCustomRepository(RelayRepository);
export const MutedNotes = getRepository(MutedNote);
export const Channels = getCustomRepository(ChannelRepository);
export const ChannelFollowings = getRepository(ChannelFollowing);
export const ChannelNotePinings = getRepository(ChannelNotePining);

View file

@ -0,0 +1,101 @@
import { EntityRepository, Repository } from 'typeorm';
import { Channel } from '../entities/channel';
import { ensure } from '../../prelude/ensure';
import { SchemaType } from '../../misc/schema';
import { DriveFiles, ChannelFollowings, NoteUnreads } from '..';
import { User } from '../entities/user';
export type PackedChannel = SchemaType<typeof packedChannelSchema>;
@EntityRepository(Channel)
export class ChannelRepository extends Repository<Channel> {
public async pack(
src: Channel['id'] | Channel,
me?: User['id'] | User | null | undefined,
): Promise<PackedChannel> {
const channel = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
const meId = me ? typeof me === 'string' ? me : me.id : null;
const banner = channel.bannerId ? await DriveFiles.findOne(channel.bannerId) : null;
const hasUnreadNote = me ? (await NoteUnreads.findOne({ noteChannelId: channel.id, userId: meId })) != null : undefined;
const following = await ChannelFollowings.findOne({
followerId: meId,
followeeId: channel.id,
});
return {
id: channel.id,
createdAt: channel.createdAt.toISOString(),
lastNotedAt: channel.lastNotedAt ? channel.lastNotedAt.toISOString() : null,
name: channel.name,
description: channel.description,
userId: channel.userId,
bannerUrl: banner ? DriveFiles.getPublicUrl(banner, false) : null,
usersCount: channel.usersCount,
notesCount: channel.notesCount,
...(me ? {
isFollowing: following != null,
hasUnreadNote,
} : {})
};
}
}
export const packedChannelSchema = {
type: 'object' as const,
optional: false as const, nullable: false as const,
properties: {
id: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'id',
description: 'The unique identifier for this Channel.',
example: 'xxxxxxxxxx',
},
createdAt: {
type: 'string' as const,
optional: false as const, nullable: false as const,
format: 'date-time',
description: 'The date that the Channel was created.'
},
lastNotedAt: {
type: 'string' as const,
optional: false as const, nullable: true as const,
format: 'date-time',
},
name: {
type: 'string' as const,
optional: false as const, nullable: false as const,
description: 'The name of the Channel.'
},
description: {
type: 'string' as const,
nullable: true as const, optional: false as const,
},
bannerUrl: {
type: 'string' as const,
format: 'url',
nullable: true as const, optional: false as const,
},
notesCount: {
type: 'number' as const,
nullable: false as const, optional: false as const,
},
usersCount: {
type: 'number' as const,
nullable: false as const, optional: false as const,
},
isFollowing: {
type: 'boolean' as const,
optional: true as const, nullable: false as const,
},
userId: {
type: 'string' as const,
nullable: false as const, optional: false as const,
format: 'id',
},
},
};

View file

@ -1,7 +1,7 @@
import { EntityRepository, Repository, In } from 'typeorm';
import { Note } from '../entities/note';
import { User } from '../entities/user';
import { Emojis, Users, PollVotes, DriveFiles, NoteReactions, Followings, Polls } from '..';
import { Emojis, Users, PollVotes, DriveFiles, NoteReactions, Followings, Polls, Channels } from '..';
import { ensure } from '../../prelude/ensure';
import { SchemaType } from '../../misc/schema';
import { awaitAll } from '../../prelude/await-all';
@ -207,6 +207,12 @@ export class NoteRepository extends Repository<Note> {
text = `${note.name}\n${(note.text || '').trim()}\n\n${note.url || note.uri}`;
}
const channel = note.channelId
? note.channel
? note.channel
: await Channels.findOne(note.channelId)
: null;
const packed = await awaitAll({
id: note.id,
createdAt: note.createdAt.toISOString(),
@ -227,6 +233,11 @@ export class NoteRepository extends Repository<Note> {
files: DriveFiles.packMany(note.fileIds),
replyId: note.replyId,
renoteId: note.renoteId,
channelId: note.channelId || undefined,
channel: channel ? {
id: channel.id,
name: channel.name,
} : undefined,
mentions: note.mentions.length > 0 ? note.mentions : undefined,
uri: note.uri || undefined,
url: note.url || undefined,
@ -391,6 +402,16 @@ export const packedNoteSchema = {
type: 'object' as const,
optional: true as const, nullable: true as const,
},
channelId: {
type: 'string' as const,
optional: true as const, nullable: true as const,
format: 'id',
example: 'xxxxxxxxxx',
},
channel: {
type: 'object' as const,
optional: true as const, nullable: true as const,
ref: 'Channel'
},
},
};

View file

@ -1,7 +1,7 @@
import $ from 'cafy';
import { EntityRepository, Repository, In, Not } from 'typeorm';
import { User, ILocalUser, IRemoteUser } from '../entities/user';
import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages, Announcements, AnnouncementReads, Antennas, AntennaNotes } from '..';
import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages, Announcements, AnnouncementReads, Antennas, AntennaNotes, ChannelFollowings } from '..';
import { ensure } from '../../prelude/ensure';
import config from '../../config';
import { SchemaType } from '../../misc/schema';
@ -107,6 +107,17 @@ export class UserRepository extends Repository<User> {
return unread != null;
}
public async getHasUnreadChannel(userId: User['id']): Promise<boolean> {
const channels = await ChannelFollowings.find({ followerId: userId });
const unread = channels.length > 0 ? await NoteUnreads.findOne({
userId: userId,
noteChannelId: In(channels.map(x => x.id)),
}) : null;
return unread != null;
}
public async getHasUnreadNotification(userId: User['id']): Promise<boolean> {
const mute = await Mutings.find({
muterId: userId
@ -139,7 +150,6 @@ export class UserRepository extends Repository<User> {
options?: {
detail?: boolean,
includeSecrets?: boolean,
includeHasUnreadNotes?: boolean
}
): Promise<PackedUser> {
const opts = Object.assign({
@ -181,17 +191,6 @@ export class UserRepository extends Repository<User> {
select: ['name', 'host', 'url', 'aliases']
}) : [],
...(opts.includeHasUnreadNotes ? {
hasUnreadSpecifiedNotes: NoteUnreads.count({
where: { userId: user.id, isSpecified: true },
take: 1
}).then(count => count > 0),
hasUnreadMentions: NoteUnreads.count({
where: { userId: user.id },
take: 1
}).then(count => count > 0),
} : {}),
...(opts.detail ? {
url: profile!.url,
createdAt: user.createdAt.toISOString(),
@ -233,8 +232,17 @@ export class UserRepository extends Repository<User> {
alwaysMarkNsfw: profile!.alwaysMarkNsfw,
carefulBot: profile!.carefulBot,
autoAcceptFollowed: profile!.autoAcceptFollowed,
hasUnreadSpecifiedNotes: NoteUnreads.count({
where: { userId: user.id, isSpecified: true },
take: 1
}).then(count => count > 0),
hasUnreadMentions: NoteUnreads.count({
where: { userId: user.id, isMentioned: true },
take: 1
}).then(count => count > 0),
hasUnreadAnnouncement: this.getHasUnreadAnnouncement(user.id),
hasUnreadAntenna: this.getHasUnreadAntenna(user.id),
hasUnreadChannel: this.getHasUnreadChannel(user.id),
hasUnreadMessagingMessage: this.getHasUnreadMessagingMessage(user.id),
hasUnreadNotification: this.getHasUnreadNotification(user.id),
hasPendingReceivedFollowRequest: this.getHasPendingReceivedFollowRequest(user.id),
@ -276,7 +284,6 @@ export class UserRepository extends Repository<User> {
options?: {
detail?: boolean,
includeSecrets?: boolean,
includeHasUnreadNotes?: boolean
}
) {
return Promise.all(users.map(u => this.pack(u, me, options)));