Use PostgreSQL instead of MongoDB (#4572)

* wip

* Update note.ts

* Update timeline.ts

* Update core.ts

* wip

* Update generate-visibility-query.ts

* wip

* wip

* wip

* wip

* wip

* Update global-timeline.ts

* wip

* wip

* wip

* Update vote.ts

* wip

* wip

* Update create.ts

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Update files.ts

* wip

* wip

* Update CONTRIBUTING.md

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Update read-notification.ts

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Update cancel.ts

* wip

* wip

* wip

* Update show.ts

* wip

* wip

* Update gen-id.ts

* Update create.ts

* Update id.ts

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Docker: Update files about Docker (#4599)

* Docker: Use cache if files used by `yarn install` was not updated

This patch reduces the number of times to installing node_modules.
For example, `yarn install` step will be skipped when only ".config/default.yml" is updated.

* Docker: Migrate MongoDB to Postgresql

Misskey uses Postgresql as a database instead of Mongodb since version 11.

* Docker: Uncomment about data persistence

This patch will save a lot of databases.

* wip

* wip

* wip

* Update activitypub.ts

* wip

* wip

* wip

* Update logs.ts

* wip

* Update drive-file.ts

* Update register.ts

* wip

* wip

* Update mentions.ts

* wip

* wip

* wip

* Update recommendation.ts

* wip

* Update index.ts

* wip

* Update recommendation.ts

* Doc: Update docker.ja.md and docker.en.md (#1) (#4608)

Update how to set up misskey.

* wip

* ✌️

* wip

* Update note.ts

* Update postgre.ts

* wip

* wip

* wip

* wip

* Update add-file.ts

* wip

* wip

* wip

* Clean up

* Update logs.ts

* wip

* 🍕

* wip

* Ad notes

* wip

* Update api-visibility.ts

* Update note.ts

* Update add-file.ts

* tests

* tests

* Update postgre.ts

* Update utils.ts

* wip

* wip

* Refactor

* wip

* Refactor

* wip

* wip

* Update show-users.ts

* Update update-instance.ts

* wip

* Update feed.ts

* Update outbox.ts

* Update outbox.ts

* Update user.ts

* wip

* Update list.ts

* Update update-hashtag.ts

* wip

* Update update-hashtag.ts

* Refactor

* Update update.ts

* wip

* wip

* ✌️

* clean up

* docs

* Update push.ts

* wip

* Update api.ts

* wip

* ✌️

* Update make-pagination-query.ts

* ✌️

* Delete hashtags.ts

* Update instances.ts

* Update instances.ts

* Update create.ts

* Update search.ts

* Update reversi-game.ts

* Update signup.ts

* Update user.ts

* id

* Update example.yml

* 🎨

* objectid

* fix

* reversi

* reversi

* Fix bug of chart engine

* Add test of chart engine

* Improve test

* Better testing

* Improve chart engine

* Refactor

* Add test of chart engine

* Refactor

* Add chart test

* Fix bug

* コミットし忘れ

* Refactoring

* ✌️

* Add tests

* Add test

* Extarct note tests

* Refactor

* 存在しないユーザーにメンションできなくなっていた問題を修正

* Fix bug

* Update update-meta.ts

* Fix bug

* Update mention.vue

* Fix bug

* Update meta.ts

* Update CONTRIBUTING.md

* Fix bug

* Fix bug

* Fix bug

* Clean up

* Clean up

* Update notification.ts

* Clean up

* Add mute tests

* Add test

* Refactor

* Add test

* Fix test

* Refactor

* Refactor

* Add tests

* Update utils.ts

* Update utils.ts

* Fix test

* Update package.json

* Update update.ts

* Update manifest.ts

* Fix bug

* Fix bug

* Add test

* 🎨

* Update endpoint permissions

* Updaye permisison

* Update person.ts

#4299

* データベースと同期しないように

* Fix bug

* Fix bug

* Update reversi-game.ts

* Use a feature of Node v11.7.0 to extract a public key (#4644)

* wip

* wip

* ✌️

* Refactoring

#1540

* test

* test

* test

* test

* test

* test

* test

* Fix bug

* Fix test

* 🍣

* wip

* #4471

* Add test for #4335

* Refactor

* Fix test

* Add tests

* 🕓

* Fix bug

* Add test

* Add test

* rename

* Fix bug
This commit is contained in:
syuilo 2019-04-07 21:50:36 +09:00 committed by GitHub
parent 13caf37991
commit f0a29721c9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
592 changed files with 13463 additions and 14147 deletions

View file

@ -0,0 +1,32 @@
import { EntityRepository, Repository } from 'typeorm';
import { Users } from '..';
import rap from '@prezzemolo/rap';
import { AbuseUserReport } from '../entities/abuse-user-report';
@EntityRepository(AbuseUserReport)
export class AbuseUserReportRepository extends Repository<AbuseUserReport> {
public packMany(
reports: any[],
) {
return Promise.all(reports.map(x => this.pack(x)));
}
public async pack(
src: AbuseUserReport['id'] | AbuseUserReport,
) {
const report = typeof src === 'object' ? src : await this.findOne(src);
return await rap({
id: report.id,
createdAt: report.createdAt,
reporterId: report.reporterId,
userId: report.userId,
reporter: Users.pack(report.reporter || report.reporterId, null, {
detail: true
}),
user: Users.pack(report.user || report.userId, null, {
detail: true
}),
});
}
}

View file

@ -0,0 +1,36 @@
import { EntityRepository, Repository } from 'typeorm';
import { App } from '../entities/app';
import { AccessTokens } from '..';
@EntityRepository(App)
export class AppRepository extends Repository<App> {
public async pack(
src: App['id'] | App,
me?: any,
options?: {
detail?: boolean,
includeSecret?: boolean,
includeProfileImageIds?: boolean
}
) {
const opts = Object.assign({
detail: false,
includeSecret: false,
includeProfileImageIds: false
}, options);
const app = typeof src === 'object' ? src : await this.findOne(src);
return {
id: app.id,
name: app.name,
...(opts.includeSecret ? { secret: app.secret } : {}),
...(me ? {
isAuthorized: await AccessTokens.count({
appId: app.id,
userId: me,
}).then(count => count > 0)
} : {})
};
}
}

View file

@ -0,0 +1,19 @@
import { EntityRepository, Repository } from 'typeorm';
import { Apps } from '..';
import rap from '@prezzemolo/rap';
import { AuthSession } from '../entities/auth-session';
@EntityRepository(AuthSession)
export class AuthSessionRepository extends Repository<AuthSession> {
public async pack(
src: AuthSession['id'] | AuthSession,
me?: any
) {
const session = typeof src === 'object' ? src : await this.findOne(src);
return await rap({
id: session.id,
app: Apps.pack(session.appId, me)
});
}
}

View file

@ -0,0 +1,28 @@
import { EntityRepository, Repository } from 'typeorm';
import { Users } from '..';
import rap from '@prezzemolo/rap';
import { Blocking } from '../entities/blocking';
@EntityRepository(Blocking)
export class BlockingRepository extends Repository<Blocking> {
public packMany(
blockings: any[],
me: any
) {
return Promise.all(blockings.map(x => this.pack(x, me)));
}
public async pack(
src: Blocking['id'] | Blocking,
me?: any
) {
const blocking = typeof src === 'object' ? src : await this.findOne(src);
return await rap({
id: blocking.id,
blockee: Users.pack(blocking.blockeeId, me, {
detail: true
})
});
}
}

View file

@ -0,0 +1,113 @@
import { EntityRepository, Repository } from 'typeorm';
import { DriveFile } from '../entities/drive-file';
import { Users, DriveFolders } from '..';
import rap from '@prezzemolo/rap';
import { User } from '../entities/user';
@EntityRepository(DriveFile)
export class DriveFileRepository extends Repository<DriveFile> {
public validateFileName(name: string): boolean {
return (
(name.trim().length > 0) &&
(name.length <= 200) &&
(name.indexOf('\\') === -1) &&
(name.indexOf('/') === -1) &&
(name.indexOf('..') === -1)
);
}
public getPublicUrl(file: DriveFile, thumbnail = false): string {
if (thumbnail) {
return file.thumbnailUrl || file.webpublicUrl || file.url;
} else {
return file.webpublicUrl || file.thumbnailUrl || file.url;
}
}
public async clacDriveUsageOf(user: User['id'] | User): Promise<number> {
const id = typeof user === 'object' ? user.id : user;
const { sum } = await this
.createQueryBuilder('file')
.where('file.userId = :id', { id: id })
.select('SUM(file.size)', 'sum')
.getRawOne();
return parseInt(sum, 10) || 0;
}
public async clacDriveUsageOfHost(host: string): Promise<number> {
const { sum } = await this
.createQueryBuilder('file')
.where('file.userHost = :host', { host: host })
.select('SUM(file.size)', 'sum')
.getRawOne();
return parseInt(sum, 10) || 0;
}
public async clacDriveUsageOfLocal(): Promise<number> {
const { sum } = await this
.createQueryBuilder('file')
.where('file.userHost IS NULL')
.select('SUM(file.size)', 'sum')
.getRawOne();
return parseInt(sum, 10) || 0;
}
public async clacDriveUsageOfRemote(): Promise<number> {
const { sum } = await this
.createQueryBuilder('file')
.where('file.userHost IS NOT NULL')
.select('SUM(file.size)', 'sum')
.getRawOne();
return parseInt(sum, 10) || 0;
}
public packMany(
files: any[],
options?: {
detail?: boolean
self?: boolean,
withUser?: boolean,
}
) {
return Promise.all(files.map(f => this.pack(f, options)));
}
public async pack(
src: DriveFile['id'] | DriveFile,
options?: {
detail?: boolean,
self?: boolean,
withUser?: boolean,
}
) {
const opts = Object.assign({
detail: false,
self: false
}, options);
const file = typeof src === 'object' ? src : await this.findOne(src);
return await rap({
id: file.id,
createdAt: file.createdAt,
name: file.name,
type: file.type,
md5: file.md5,
size: file.size,
isSensitive: file.isSensitive,
properties: file.properties,
url: opts.self ? file.url : this.getPublicUrl(file, false),
thumbnailUrl: this.getPublicUrl(file, true),
folderId: file.folderId,
folder: opts.detail && file.folderId ? DriveFolders.pack(file.folderId, {
detail: true
}) : null,
user: opts.withUser ? Users.pack(file.userId) : null
});
}
}

View file

@ -0,0 +1,49 @@
import { EntityRepository, Repository } from 'typeorm';
import { DriveFolders, DriveFiles } from '..';
import rap from '@prezzemolo/rap';
import { DriveFolder } from '../entities/drive-folder';
@EntityRepository(DriveFolder)
export class DriveFolderRepository extends Repository<DriveFolder> {
public validateFolderName(name: string): boolean {
return (
(name.trim().length > 0) &&
(name.length <= 200)
);
}
public async pack(
src: DriveFolder['id'] | DriveFolder,
options?: {
detail: boolean
}
): Promise<Record<string, any>> {
const opts = Object.assign({
detail: false
}, options);
const folder = typeof src === 'object' ? src : await this.findOne(src);
return await rap({
id: folder.id,
createdAt: folder.createdAt,
name: folder.name,
parentId: folder.parentId,
...(opts.detail ? {
foldersCount: DriveFolders.count({
parentId: folder.id
}),
filesCount: DriveFiles.count({
folderId: folder.id
}),
...(folder.parentId ? {
parent: this.pack(folder.parentId, {
detail: true
})
} : {})
} : {})
});
}
}

View file

@ -0,0 +1,19 @@
import { EntityRepository, Repository } from 'typeorm';
import { FollowRequest } from '../entities/follow-request';
import { Users } from '..';
@EntityRepository(FollowRequest)
export class FollowRequestRepository extends Repository<FollowRequest> {
public async pack(
src: FollowRequest['id'] | FollowRequest,
me?: any
) {
const request = typeof src === 'object' ? src : await this.findOne(src);
return {
id: request.id,
follower: await Users.pack(request.followerId, me),
followee: await Users.pack(request.followeeId, me),
};
}
}

View file

@ -0,0 +1,44 @@
import { EntityRepository, Repository } from 'typeorm';
import { Users } from '..';
import rap from '@prezzemolo/rap';
import { Following } from '../entities/following';
@EntityRepository(Following)
export class FollowingRepository extends Repository<Following> {
public packMany(
followings: any[],
me?: any,
opts?: {
populateFollowee?: boolean;
populateFollower?: boolean;
}
) {
return Promise.all(followings.map(x => this.pack(x, me, opts)));
}
public async pack(
src: Following['id'] | Following,
me?: any,
opts?: {
populateFollowee?: boolean;
populateFollower?: boolean;
}
) {
const following = typeof src === 'object' ? src : await this.findOne(src);
if (opts == null) opts = {};
return await rap({
id: following.id,
createdAt: following.createdAt,
followeeId: following.followeeId,
followerId: following.followerId,
followee: opts.populateFollowee ? Users.pack(following.followee || following.followeeId, me, {
detail: true
}) : null,
follower: opts.populateFollower ? Users.pack(following.follower || following.followerId, me, {
detail: true
}) : null,
});
}
}

View file

@ -0,0 +1,49 @@
import { EntityRepository, Repository } from 'typeorm';
import { Users } from '../../..';
import { ReversiGame } from '../../../entities/games/reversi/game';
@EntityRepository(ReversiGame)
export class ReversiGameRepository extends Repository<ReversiGame> {
public async pack(
src: ReversiGame['id'] | ReversiGame,
me?: any,
options?: {
detail?: boolean
}
) {
const opts = Object.assign({
detail: true
}, options);
const game = typeof src === 'object' ? src : await this.findOne(src);
const meId = me ? typeof me === 'string' ? me : me.id : null;
return {
id: game.id,
createdAt: game.createdAt,
startedAt: game.startedAt,
isStarted: game.isStarted,
isEnded: game.isEnded,
form1: game.form1,
form2: game.form2,
user1Accepted: game.user1Accepted,
user2Accepted: game.user2Accepted,
user1Id: game.user1Id,
user2Id: game.user2Id,
user1: await Users.pack(game.user1Id, meId),
user2: await Users.pack(game.user2Id, meId),
winnerId: game.winnerId,
winner: game.winnerId ? await Users.pack(game.winnerId, meId) : null,
surrendered: game.surrendered,
black: game.black,
bw: game.bw,
isLlotheo: game.isLlotheo,
canPutEverywhere: game.canPutEverywhere,
loopedBoard: game.loopedBoard,
...(opts.detail ? {
logs: game.logs,
map: game.map,
} : {})
};
}
}

View file

@ -0,0 +1,27 @@
import { EntityRepository, Repository } from 'typeorm';
import rap from '@prezzemolo/rap';
import { ReversiMatching } from '../../../entities/games/reversi/matching';
import { Users } from '../../..';
@EntityRepository(ReversiMatching)
export class ReversiMatchingRepository extends Repository<ReversiMatching> {
public async pack(
src: ReversiMatching['id'] | ReversiMatching,
me: any
) {
const matching = typeof src === 'object' ? src : await this.findOne(src);
return await rap({
id: matching.id,
createdAt: matching.createdAt,
parentId: matching.parentId,
parent: Users.pack(matching.parentId, me, {
detail: true
}),
childId: matching.childId,
child: Users.pack(matching.childId, me, {
detail: true
})
});
}
}

View file

@ -0,0 +1,37 @@
import { EntityRepository, Repository } from 'typeorm';
import { MessagingMessage } from '../entities/messaging-message';
import { Users, DriveFiles } from '..';
@EntityRepository(MessagingMessage)
export class MessagingMessageRepository extends Repository<MessagingMessage> {
public isValidText(text: string): boolean {
return text.trim().length <= 1000 && text.trim() != '';
}
public async pack(
src: MessagingMessage['id'] | MessagingMessage,
me?: any,
options?: {
populateRecipient: boolean
}
) {
const opts = options || {
populateRecipient: true
};
const message = typeof src === 'object' ? src : await this.findOne(src);
return {
id: message.id,
createdAt: message.createdAt,
text: message.text,
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) : null,
fileId: message.fileId,
file: message.fileId ? await DriveFiles.pack(message.fileId) : null,
isRead: message.isRead
};
}
}

View file

@ -0,0 +1,28 @@
import { EntityRepository, Repository } from 'typeorm';
import { Users } from '..';
import rap from '@prezzemolo/rap';
import { Muting } from '../entities/muting';
@EntityRepository(Muting)
export class MutingRepository extends Repository<Muting> {
public packMany(
mutings: any[],
me: any
) {
return Promise.all(mutings.map(x => this.pack(x, me)));
}
public async pack(
src: Muting['id'] | Muting,
me?: any
) {
const muting = typeof src === 'object' ? src : await this.findOne(src);
return await rap({
id: muting.id,
mutee: Users.pack(muting.muteeId, me, {
detail: true
})
});
}
}

View file

@ -0,0 +1,25 @@
import { EntityRepository, Repository } from 'typeorm';
import { NoteFavorite } from '../entities/note-favorite';
import { Notes } from '..';
@EntityRepository(NoteFavorite)
export class NoteFavoriteRepository extends Repository<NoteFavorite> {
public packMany(
favorites: any[],
me: any
) {
return Promise.all(favorites.map(x => this.pack(x, me)));
}
public async pack(
src: NoteFavorite['id'] | NoteFavorite,
me?: any
) {
const favorite = typeof src === 'object' ? src : await this.findOne(src);
return {
id: favorite.id,
note: await Notes.pack(favorite.note || favorite.noteId, me),
};
}
}

View file

@ -0,0 +1,18 @@
import { EntityRepository, Repository } from 'typeorm';
import { NoteReaction } from '../entities/note-reaction';
import { Users } from '..';
@EntityRepository(NoteReaction)
export class NoteReactionRepository extends Repository<NoteReaction> {
public async pack(
src: NoteReaction['id'] | NoteReaction,
me?: any
) {
const reaction = typeof src === 'object' ? src : await this.findOne(src);
return {
id: reaction.id,
user: await Users.pack(reaction.userId, me),
};
}
}

View file

@ -0,0 +1,210 @@
import { EntityRepository, Repository, In } from 'typeorm';
import { Note } from '../entities/note';
import { User } from '../entities/user';
import { unique, concat } from '../../prelude/array';
import { nyaize } from '../../misc/nyaize';
import { Emojis, Users, Apps, PollVotes, DriveFiles, NoteReactions, Followings, Polls } from '..';
import rap from '@prezzemolo/rap';
@EntityRepository(Note)
export class NoteRepository extends Repository<Note> {
public validateCw(x: string) {
return x.trim().length <= 100;
}
private async hideNote(packedNote: any, meId: User['id']) {
let hide = false;
// visibility が specified かつ自分が指定されていなかったら非表示
if (packedNote.visibility == 'specified') {
if (meId == null) {
hide = true;
} else if (meId === packedNote.userId) {
hide = false;
} else {
// 指定されているかどうか
const specified = packedNote.visibleUserIds.some((id: any) => meId === id);
if (specified) {
hide = false;
} else {
hide = true;
}
}
}
// visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示
if (packedNote.visibility == 'followers') {
if (meId == null) {
hide = true;
} else if (meId === packedNote.userId) {
hide = false;
} else if (packedNote.reply && (meId === packedNote.reply.userId)) {
// 自分の投稿に対するリプライ
hide = false;
} else if (packedNote.mentions && packedNote.mentions.some((id: any) => meId === id)) {
// 自分へのメンション
hide = false;
} else {
// フォロワーかどうか
const following = await Followings.findOne({
followeeId: packedNote.userId,
followerId: meId
});
if (following == null) {
hide = true;
} else {
hide = false;
}
}
}
if (hide) {
packedNote.visibleUserIds = null;
packedNote.fileIds = [];
packedNote.files = [];
packedNote.text = null;
packedNote.poll = null;
packedNote.cw = null;
packedNote.tags = [];
packedNote.geo = null;
packedNote.isHidden = true;
}
}
public packMany(
notes: (Note['id'] | Note)[],
me?: User['id'] | User,
options?: {
detail?: boolean;
skipHide?: boolean;
}
) {
return Promise.all(notes.map(n => this.pack(n, me, options)));
}
public async pack(
src: Note['id'] | Note,
me?: User['id'] | User,
options?: {
detail?: boolean;
skipHide?: boolean;
}
): Promise<Record<string, any>> {
const opts = Object.assign({
detail: true,
skipHide: false
}, options);
const meId = me ? typeof me === 'string' ? me : me.id : null;
const note = typeof src === 'object' ? src : await this.findOne(src);
const host = note.userHost;
async function populatePoll() {
const poll = await Polls.findOne({ noteId: note.id });
const choices = poll.choices.map(c => ({
text: c,
votes: poll.votes[poll.choices.indexOf(c)],
isVoted: false
}));
if (poll.multiple) {
const votes = await PollVotes.find({
userId: meId,
noteId: note.id
});
const myChoices = votes.map(v => v.choice);
for (const myChoice of myChoices) {
choices[myChoice].isVoted = true;
}
} else {
const vote = await PollVotes.findOne({
userId: meId,
noteId: note.id
});
if (vote) {
choices[vote.choice].isVoted = true;
}
}
return {
multiple: poll.multiple,
expiresAt: poll.expiresAt,
choices
};
}
async function populateMyReaction() {
const reaction = await NoteReactions.findOne({
userId: meId,
noteId: note.id,
});
if (reaction) {
return reaction.reaction;
}
return null;
}
let text = note.text;
if (note.name) {
text = `${note.name}\n${note.text}`;
}
const reactionEmojis = unique(concat([note.emojis, Object.keys(note.reactions)]));
const packed = await rap({
id: note.id,
createdAt: note.createdAt,
app: note.appId ? Apps.pack(note.appId) : null,
userId: note.userId,
user: Users.pack(note.user || note.userId, meId),
text: text,
cw: note.cw,
visibility: note.visibility,
visibleUserIds: note.visibleUserIds,
viaMobile: note.viaMobile,
reactions: note.reactions,
emojis: reactionEmojis.length > 0 ? Emojis.find({
name: In(reactionEmojis),
host: host
}) : [],
tags: note.tags,
fileIds: note.fileIds,
files: DriveFiles.packMany(note.fileIds),
replyId: note.replyId,
renoteId: note.renoteId,
...(opts.detail ? {
reply: note.replyId ? this.pack(note.replyId, meId, {
detail: false
}) : null,
renote: note.renoteId ? this.pack(note.renoteId, meId, {
detail: false
}) : null,
poll: note.hasPoll ? populatePoll() : null,
...(meId ? {
myReaction: populateMyReaction()
} : {})
} : {})
});
if (packed.user.isCat && packed.text) {
packed.text = nyaize(packed.text);
}
if (!opts.skipHide) {
await this.hideNote(packed, meId);
}
return packed;
}
}

View file

@ -0,0 +1,47 @@
import { EntityRepository, Repository } from 'typeorm';
import { Users, Notes } from '..';
import rap from '@prezzemolo/rap';
import { Notification } from '../entities/notification';
@EntityRepository(Notification)
export class NotificationRepository extends Repository<Notification> {
public packMany(
notifications: any[],
) {
return Promise.all(notifications.map(x => this.pack(x)));
}
public async pack(
src: Notification['id'] | Notification,
) {
const notification = typeof src === 'object' ? src : await this.findOne(src);
return await rap({
id: notification.id,
createdAt: notification.createdAt,
type: notification.type,
userId: notification.notifierId,
user: Users.pack(notification.notifier || notification.notifierId),
...(notification.type === 'mention' ? {
note: Notes.pack(notification.note || notification.noteId),
} : {}),
...(notification.type === 'reply' ? {
note: Notes.pack(notification.note || notification.noteId),
} : {}),
...(notification.type === 'renote' ? {
note: Notes.pack(notification.note || notification.noteId),
} : {}),
...(notification.type === 'quote' ? {
note: Notes.pack(notification.note || notification.noteId),
} : {}),
...(notification.type === 'reaction' ? {
note: Notes.pack(notification.note || notification.noteId),
reaction: notification.reaction
} : {}),
...(notification.type === 'pollVote' ? {
note: Notes.pack(notification.note || notification.noteId),
choice: notification.choice
} : {})
});
}
}

View file

@ -0,0 +1,11 @@
import { EntityRepository, Repository } from 'typeorm';
import { Signin } from '../entities/signin';
@EntityRepository(Signin)
export class SigninRepository extends Repository<Signin> {
public async pack(
src: any,
) {
return src;
}
}

View file

@ -0,0 +1,16 @@
import { EntityRepository, Repository } from 'typeorm';
import { UserList } from '../entities/user-list';
@EntityRepository(UserList)
export class UserListRepository extends Repository<UserList> {
public async pack(
src: any,
) {
const userList = typeof src === 'object' ? src : await this.findOne(src);
return {
id: userList.id,
name: userList.name
};
}
}

View file

@ -0,0 +1,198 @@
import { EntityRepository, Repository, In } from 'typeorm';
import { User, ILocalUser, IRemoteUser } from '../entities/user';
import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings } from '..';
import rap from '@prezzemolo/rap';
@EntityRepository(User)
export class UserRepository extends Repository<User> {
public async getRelation(me: User['id'], target: User['id']) {
const [following1, following2, followReq1, followReq2, toBlocking, fromBlocked, mute] = await Promise.all([
Followings.findOne({
followerId: me,
followeeId: target
}),
Followings.findOne({
followerId: target,
followeeId: me
}),
FollowRequests.findOne({
followerId: me,
followeeId: target
}),
FollowRequests.findOne({
followerId: target,
followeeId: me
}),
Blockings.findOne({
blockerId: me,
blockeeId: target
}),
Blockings.findOne({
blockerId: target,
blockeeId: me
}),
Mutings.findOne({
muterId: me,
muteeId: target
})
]);
return {
id: target,
isFollowing: following1 != null,
hasPendingFollowRequestFromYou: followReq1 != null,
hasPendingFollowRequestToYou: followReq2 != null,
isFollowed: following2 != null,
isBlocking: toBlocking != null,
isBlocked: fromBlocked != null,
isMuted: mute != null
};
}
public packMany(
users: (User['id'] | User)[],
me?: User['id'] | User,
options?: {
detail?: boolean,
includeSecrets?: boolean,
includeHasUnreadNotes?: boolean
}
) {
return Promise.all(users.map(u => this.pack(u, me, options)));
}
public async pack(
src: User['id'] | User,
me?: User['id'] | User,
options?: {
detail?: boolean,
includeSecrets?: boolean,
includeHasUnreadNotes?: boolean
}
): Promise<Record<string, any>> {
const opts = Object.assign({
detail: false,
includeSecrets: false
}, options);
const user = typeof src === 'object' ? src : await this.findOne(src);
const meId = me ? typeof me === 'string' ? me : me.id : null;
const relation = meId && (meId !== user.id) && opts.detail ? await this.getRelation(meId, user.id) : null;
const pins = opts.detail ? await UserNotePinings.find({ userId: user.id }) : [];
return await rap({
id: user.id,
name: user.name,
username: user.username,
host: user.host,
avatarUrl: user.avatarUrl,
bannerUrl: user.bannerUrl,
avatarColor: user.avatarColor,
bannerColor: user.bannerColor,
isAdmin: user.isAdmin,
// カスタム絵文字添付
emojis: user.emojis.length > 0 ? Emojis.find({
where: {
name: In(user.emojis),
host: user.host
},
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 ? {
description: user.description,
location: user.location,
birthday: user.birthday,
followersCount: user.followersCount,
followingCount: user.followingCount,
notesCount: user.notesCount,
pinnedNoteIds: pins.map(pin => pin.noteId),
pinnedNotes: Notes.packMany(pins.map(pin => pin.noteId), meId, {
detail: true
}),
} : {}),
...(opts.detail && meId === user.id ? {
avatarId: user.avatarId,
bannerId: user.bannerId,
autoWatch: user.autoWatch,
alwaysMarkNsfw: user.alwaysMarkNsfw,
carefulBot: user.carefulBot,
hasUnreadMessagingMessage: MessagingMessages.count({
where: {
recipientId: user.id,
isRead: false
},
take: 1
}).then(count => count > 0),
hasUnreadNotification: Notifications.count({
where: {
userId: user.id,
isRead: false
},
take: 1
}).then(count => count > 0),
pendingReceivedFollowRequestsCount: FollowRequests.count({
followeeId: user.id
}),
} : {}),
...(relation ? {
isFollowing: relation.isFollowing,
isFollowed: relation.isFollowed,
hasPendingFollowRequestFromYou: relation.hasPendingFollowRequestFromYou,
hasPendingFollowRequestToYou: relation.hasPendingFollowRequestToYou,
isBlocking: relation.isBlocking,
isBlocked: relation.isBlocked,
isMuted: relation.isMuted,
} : {})
});
}
public isLocalUser(user: User): user is ILocalUser {
return user.host === null;
}
public isRemoteUser(user: User): user is IRemoteUser {
return !this.isLocalUser(user);
}
//#region Validators
public validateUsername(username: string, remote = false): boolean {
return typeof username == 'string' && (remote ? /^\w([\w-]*\w)?$/ : /^\w{1,20}$/).test(username);
}
public validatePassword(password: string): boolean {
return typeof password == 'string' && password != '';
}
public isValidName(name?: string): boolean {
return name === null || (typeof name == 'string' && name.length < 50 && name.trim() != '');
}
public isValidDescription(description: string): boolean {
return typeof description == 'string' && description.length < 500 && description.trim() != '';
}
public isValidLocation(location: string): boolean {
return typeof location == 'string' && location.length < 50 && location.trim() != '';
}
public isValidBirthday(birthday: string): boolean {
return typeof birthday == 'string' && /^([0-9]{4})\-([0-9]{2})-([0-9]{2})$/.test(birthday);
}
//#endregion
}