summaryrefslogtreecommitdiff
path: root/src/models
diff options
context:
space:
mode:
Diffstat (limited to 'src/models')
-rw-r--r--src/models/drive-file-thumbnail.ts61
-rw-r--r--src/models/drive-file.ts23
-rw-r--r--src/models/favorite.ts35
-rw-r--r--src/models/following.ts11
-rw-r--r--src/models/messaging-message.ts1
-rw-r--r--src/models/note-reaction.ts13
-rw-r--r--src/models/note.ts136
-rw-r--r--src/models/sw-subscription.ts1
-rw-r--r--src/models/user-list.ts67
-rw-r--r--src/models/user.ts85
10 files changed, 337 insertions, 96 deletions
diff --git a/src/models/drive-file-thumbnail.ts b/src/models/drive-file-thumbnail.ts
new file mode 100644
index 0000000000..46de24379f
--- /dev/null
+++ b/src/models/drive-file-thumbnail.ts
@@ -0,0 +1,61 @@
+import * as mongo from 'mongodb';
+import monkDb, { nativeDbConn } from '../db/mongodb';
+
+const DriveFileThumbnail = monkDb.get<IDriveFileThumbnail>('driveFileThumbnails.files');
+DriveFileThumbnail.createIndex('metadata.originalId', { sparse: true, unique: true });
+export default DriveFileThumbnail;
+
+export const DriveFileThumbnailChunk = monkDb.get('driveFileThumbnails.chunks');
+
+export const getDriveFileThumbnailBucket = async (): Promise<mongo.GridFSBucket> => {
+ const db = await nativeDbConn();
+ const bucket = new mongo.GridFSBucket(db, {
+ bucketName: 'driveFileThumbnails'
+ });
+ return bucket;
+};
+
+export type IMetadata = {
+ originalId: mongo.ObjectID;
+};
+
+export type IDriveFileThumbnail = {
+ _id: mongo.ObjectID;
+ uploadDate: Date;
+ md5: string;
+ filename: string;
+ contentType: string;
+ metadata: IMetadata;
+};
+
+/**
+ * DriveFileThumbnailを物理削除します
+ */
+export async function deleteDriveFileThumbnail(driveFile: string | mongo.ObjectID | IDriveFileThumbnail) {
+ let d: IDriveFileThumbnail;
+
+ // Populate
+ if (mongo.ObjectID.prototype.isPrototypeOf(driveFile)) {
+ d = await DriveFileThumbnail.findOne({
+ _id: driveFile
+ });
+ } else if (typeof driveFile === 'string') {
+ d = await DriveFileThumbnail.findOne({
+ _id: new mongo.ObjectID(driveFile)
+ });
+ } else {
+ d = driveFile as IDriveFileThumbnail;
+ }
+
+ if (d == null) return;
+
+ // このDriveFileThumbnailのチャンクをすべて削除
+ await DriveFileThumbnailChunk.remove({
+ files_id: d._id
+ });
+
+ // このDriveFileThumbnailを削除
+ await DriveFileThumbnail.remove({
+ _id: d._id
+ });
+}
diff --git a/src/models/drive-file.ts b/src/models/drive-file.ts
index fc9c150724..f8cad36f9a 100644
--- a/src/models/drive-file.ts
+++ b/src/models/drive-file.ts
@@ -6,14 +6,16 @@ import monkDb, { nativeDbConn } from '../db/mongodb';
import Note, { deleteNote } from './note';
import MessagingMessage, { deleteMessagingMessage } from './messaging-message';
import User from './user';
+import DriveFileThumbnail, { deleteDriveFileThumbnail } from './drive-file-thumbnail';
const DriveFile = monkDb.get<IDriveFile>('driveFiles.files');
-
+DriveFile.createIndex('md5');
DriveFile.createIndex('metadata.uri', { sparse: true, unique: true });
-
export default DriveFile;
-const getGridFSBucket = async (): Promise<mongo.GridFSBucket> => {
+export const DriveFileChunk = monkDb.get('driveFiles.chunks');
+
+export const getDriveFileBucket = async (): Promise<mongo.GridFSBucket> => {
const db = await nativeDbConn();
const bucket = new mongo.GridFSBucket(db, {
bucketName: 'driveFiles'
@@ -21,14 +23,16 @@ const getGridFSBucket = async (): Promise<mongo.GridFSBucket> => {
return bucket;
};
-export { getGridFSBucket };
-
export type IMetadata = {
properties: any;
userId: mongo.ObjectID;
+ _user: any;
folderId: mongo.ObjectID;
comment: string;
- uri: string;
+ uri?: string;
+ url?: string;
+ deletedAt?: Date;
+ isExpired?: boolean;
};
export type IDriveFile = {
@@ -92,8 +96,13 @@ export async function deleteDriveFile(driveFile: string | mongo.ObjectID | IDriv
}
}
+ // このDriveFileのDriveFileThumbnailをすべて削除
+ await Promise.all((
+ await DriveFileThumbnail.find({ 'metadata.originalId': d._id })
+ ).map(x => deleteDriveFileThumbnail(x)));
+
// このDriveFileのチャンクをすべて削除
- await monkDb.get('driveFiles.chunks').remove({
+ await DriveFileChunk.remove({
files_id: d._id
});
diff --git a/src/models/favorite.ts b/src/models/favorite.ts
index b2c5828088..d24833f191 100644
--- a/src/models/favorite.ts
+++ b/src/models/favorite.ts
@@ -1,7 +1,10 @@
import * as mongo from 'mongodb';
+import deepcopy = require('deepcopy');
import db from '../db/mongodb';
+import { pack as packNote } from './note';
const Favorite = db.get<IFavorite>('favorites');
+Favorite.createIndex(['userId', 'noteId'], { unique: true });
export default Favorite;
export type IFavorite = {
@@ -37,3 +40,35 @@ export async function deleteFavorite(favorite: string | mongo.ObjectID | IFavori
_id: f._id
});
}
+
+/**
+ * Pack a favorite for API response
+ */
+export const pack = (
+ favorite: any,
+ me: any
+) => new Promise<any>(async (resolve, reject) => {
+ let _favorite: any;
+
+ // Populate the favorite if 'favorite' is ID
+ if (mongo.ObjectID.prototype.isPrototypeOf(favorite)) {
+ _favorite = await Favorite.findOne({
+ _id: favorite
+ });
+ } else if (typeof favorite === 'string') {
+ _favorite = await Favorite.findOne({
+ _id: new mongo.ObjectID(favorite)
+ });
+ } else {
+ _favorite = deepcopy(favorite);
+ }
+
+ // Rename _id to id
+ _favorite.id = _favorite._id;
+ delete _favorite._id;
+
+ // Populate note
+ _favorite.note = await packNote(_favorite.noteId, me);
+
+ resolve(_favorite);
+});
diff --git a/src/models/following.ts b/src/models/following.ts
index f10e349ee9..4712379a70 100644
--- a/src/models/following.ts
+++ b/src/models/following.ts
@@ -10,6 +10,17 @@ export type IFollowing = {
createdAt: Date;
followeeId: mongo.ObjectID;
followerId: mongo.ObjectID;
+ stalk: boolean;
+
+ // 非正規化
+ _followee: {
+ host: string;
+ inbox?: string;
+ },
+ _follower: {
+ host: string;
+ inbox?: string;
+ }
};
/**
diff --git a/src/models/messaging-message.ts b/src/models/messaging-message.ts
index 9d62fab4fa..a6a50fc8cf 100644
--- a/src/models/messaging-message.ts
+++ b/src/models/messaging-message.ts
@@ -12,7 +12,6 @@ export interface IMessagingMessage {
_id: mongo.ObjectID;
createdAt: Date;
text: string;
- textHtml: string;
userId: mongo.ObjectID;
recipientId: mongo.ObjectID;
isRead: boolean;
diff --git a/src/models/note-reaction.ts b/src/models/note-reaction.ts
index 9bf467f222..706ae54c16 100644
--- a/src/models/note-reaction.ts
+++ b/src/models/note-reaction.ts
@@ -1,4 +1,5 @@
import * as mongo from 'mongodb';
+import $ from 'cafy';
import deepcopy = require('deepcopy');
import db from '../db/mongodb';
import Reaction from './note-reaction';
@@ -16,6 +17,18 @@ export interface INoteReaction {
reaction: string;
}
+export const validateReaction = $.str.or([
+ 'like',
+ 'love',
+ 'laugh',
+ 'hmm',
+ 'surprise',
+ 'congrats',
+ 'angry',
+ 'confused',
+ 'pudding'
+]);
+
/**
* NoteReactionを物理削除します
*/
diff --git a/src/models/note.ts b/src/models/note.ts
index 3059593540..f42bb2a49d 100644
--- a/src/models/note.ts
+++ b/src/models/note.ts
@@ -12,11 +12,11 @@ import NoteWatching, { deleteNoteWatching } from './note-watching';
import NoteReaction from './note-reaction';
import Favorite, { deleteFavorite } from './favorite';
import Notification, { deleteNotification } from './notification';
+import Following from './following';
const Note = db.get<INote>('notes');
-
Note.createIndex('uri', { sparse: true, unique: true });
-
+Note.createIndex('userId');
export default Note;
export function isValidText(text: string): boolean {
@@ -24,7 +24,7 @@ export function isValidText(text: string): boolean {
}
export function isValidCw(text: string): boolean {
- return text.length <= 100 && text.trim() != '';
+ return text.length <= 100;
}
export type INote = {
@@ -38,7 +38,6 @@ export type INote = {
poll: any; // todo
text: string;
tags: string[];
- textHtml: string;
cw: string;
userId: mongo.ObjectID;
appId: mongo.ObjectID;
@@ -47,7 +46,18 @@ export type INote = {
repliesCount: number;
reactionCounts: any;
mentions: mongo.ObjectID[];
- visibility: 'public' | 'unlisted' | 'private' | 'direct';
+
+ /**
+ * public ... 公開
+ * home ... ホームタイムライン(ユーザーページのタイムライン含む)のみに流す
+ * followers ... フォロワーのみ
+ * specified ... visibleUserIds で指定したユーザーのみ
+ * private ... 自分のみ
+ */
+ visibility: 'public' | 'home' | 'followers' | 'specified' | 'private';
+
+ visibleUserIds: mongo.ObjectID[];
+
geo: {
coordinates: number[];
altitude: number;
@@ -58,6 +68,7 @@ export type INote = {
};
uri: string;
+ // 非正規化
_reply?: {
userId: mongo.ObjectID;
};
@@ -66,9 +77,7 @@ export type INote = {
};
_user: {
host: string;
- account: {
- inbox?: string;
- };
+ inbox?: string;
};
};
@@ -153,9 +162,9 @@ export const pack = async (
detail: boolean
}
) => {
- const opts = options || {
- detail: true,
- };
+ const opts = Object.assign({
+ detail: true
+ }, options);
// Me
const meId: mongo.ObjectID = me
@@ -183,12 +192,61 @@ export const pack = async (
if (!_note) throw `invalid note arg ${note}`;
+ let hide = false;
+
+ // visibility が private かつ投稿者のIDが自分のIDではなかったら非表示
+ if (_note.visibility == 'private' && (meId == null || !meId.equals(_note.userId))) {
+ hide = true;
+ }
+
+ // visibility が specified かつ自分が指定されていなかったら非表示
+ if (_note.visibility == 'specified') {
+ if (meId == null) {
+ hide = true;
+ } else if (meId.equals(_note.userId)) {
+ hide = false;
+ } else {
+ // 指定されているかどうか
+ const specified = _note.visibleUserIds.some(id => id.equals(meId));
+
+ if (specified) {
+ hide = false;
+ } else {
+ hide = true;
+ }
+ }
+ }
+
+ // visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示
+ if (_note.visibility == 'followers') {
+ if (meId == null) {
+ hide = true;
+ } else if (meId.equals(_note.userId)) {
+ hide = false;
+ } else {
+ // フォロワーかどうか
+ const following = await Following.findOne({
+ followeeId: _note.userId,
+ followerId: meId
+ });
+
+ if (following == null) {
+ hide = true;
+ } else {
+ hide = false;
+ }
+ }
+ }
+
const id = _note._id;
// Rename _id to id
_note.id = _note._id;
delete _note._id;
+ delete _note._user;
+ delete _note._reply;
+ delete _note.repost;
delete _note.mentions;
if (_note.geo) delete _note.geo.type;
@@ -206,49 +264,16 @@ export const pack = async (
}
// Populate media
- if (_note.mediaIds) {
- _note.media = Promise.all(_note.mediaIds.map(fileId =>
- packFile(fileId)
- ));
- }
+ _note.media = hide ? [] : Promise.all(_note.mediaIds.map(fileId =>
+ packFile(fileId)
+ ));
// When requested a detailed note data
if (opts.detail) {
- // Get previous note info
- _note.prev = (async () => {
- const prev = await Note.findOne({
- userId: _note.userId,
- _id: {
- $lt: id
- }
- }, {
- fields: {
- _id: true
- },
- sort: {
- _id: -1
- }
- });
- return prev ? prev._id : null;
- })();
-
- // Get next note info
- _note.next = (async () => {
- const next = await Note.findOne({
- userId: _note.userId,
- _id: {
- $gt: id
- }
- }, {
- fields: {
- _id: true
- },
- sort: {
- _id: 1
- }
- });
- return next ? next._id : null;
- })();
+ //#region 重いので廃止
+ _note.prev = null;
+ _note.next = null;
+ //#endregion
if (_note.replyId) {
// Populate reply to note
@@ -265,7 +290,7 @@ export const pack = async (
}
// Poll
- if (meId && _note.poll) {
+ if (meId && _note.poll && !hide) {
_note.poll = (async (poll) => {
const vote = await PollVote
.findOne({
@@ -306,5 +331,12 @@ export const pack = async (
// resolve promises in _note object
_note = await rap(_note);
+ if (hide) {
+ _note.mediaIds = [];
+ _note.text = null;
+ _note.poll = null;
+ _note.isHidden = true;
+ }
+
return _note;
};
diff --git a/src/models/sw-subscription.ts b/src/models/sw-subscription.ts
index 621ac8a9b6..a38edd3a50 100644
--- a/src/models/sw-subscription.ts
+++ b/src/models/sw-subscription.ts
@@ -38,4 +38,3 @@ export async function deleteSwSubscription(swSubscription: string | mongo.Object
_id: s._id
});
}
-
diff --git a/src/models/user-list.ts b/src/models/user-list.ts
new file mode 100644
index 0000000000..7100fced7e
--- /dev/null
+++ b/src/models/user-list.ts
@@ -0,0 +1,67 @@
+import * as mongo from 'mongodb';
+import deepcopy = require('deepcopy');
+import db from '../db/mongodb';
+
+const UserList = db.get<IUserList>('userList');
+export default UserList;
+
+export interface IUserList {
+ _id: mongo.ObjectID;
+ createdAt: Date;
+ title: string;
+ userId: mongo.ObjectID;
+ userIds: mongo.ObjectID[];
+}
+
+/**
+ * UserListを物理削除します
+ */
+export async function deleteUserList(userList: string | mongo.ObjectID | IUserList) {
+ let u: IUserList;
+
+ // Populate
+ if (mongo.ObjectID.prototype.isPrototypeOf(userList)) {
+ u = await UserList.findOne({
+ _id: userList
+ });
+ } else if (typeof userList === 'string') {
+ u = await UserList.findOne({
+ _id: new mongo.ObjectID(userList)
+ });
+ } else {
+ u = userList as IUserList;
+ }
+
+ if (u == null) return;
+
+ // このUserListを削除
+ await UserList.remove({
+ _id: u._id
+ });
+}
+
+export const pack = (
+ userList: string | mongo.ObjectID | IUserList
+) => new Promise<any>(async (resolve, reject) => {
+ let _userList: any;
+
+ if (mongo.ObjectID.prototype.isPrototypeOf(userList)) {
+ _userList = await UserList.findOne({
+ _id: userList
+ });
+ } else if (typeof userList === 'string') {
+ _userList = await UserList.findOne({
+ _id: new mongo.ObjectID(userList)
+ });
+ } else {
+ _userList = deepcopy(userList);
+ }
+
+ if (!_userList) throw `invalid userList arg ${userList}`;
+
+ // Rename _id to id
+ _userList.id = _userList._id;
+ delete _userList._id;
+
+ resolve(_userList);
+});
diff --git a/src/models/user.ts b/src/models/user.ts
index 741306fd27..108111ceca 100644
--- a/src/models/user.ts
+++ b/src/models/user.ts
@@ -1,11 +1,12 @@
import * as mongo from 'mongodb';
import deepcopy = require('deepcopy');
+import sequential = require('promise-sequential');
import rap from '@prezzemolo/rap';
import db from '../db/mongodb';
-import Note, { INote, pack as packNote, deleteNote } from './note';
+import Note, { pack as packNote, deleteNote } from './note';
import Following, { deleteFollowing } from './following';
import Mute, { deleteMute } from './mute';
-import getFriends from '../server/api/common/get-friends';
+import { getFriendIds } from '../server/api/common/get-friends';
import config from '../config';
import AccessToken, { deleteAccessToken } from './access-token';
import NoteWatching, { deleteNoteWatching } from './note-watching';
@@ -20,6 +21,7 @@ import FollowingLog, { deleteFollowingLog } from './following-log';
import FollowedLog, { deleteFollowedLog } from './followed-log';
import SwSubscription, { deleteSwSubscription } from './sw-subscription';
import Notification, { deleteNotification } from './notification';
+import UserList, { deleteUserList } from './user-list';
const User = db.get<IUser>('users');
@@ -35,7 +37,7 @@ export default User;
type IUserBase = {
_id: mongo.ObjectID;
createdAt: Date;
- deletedAt: Date;
+ deletedAt?: Date;
followersCount: number;
followingCount: number;
name?: string;
@@ -47,10 +49,8 @@ type IUserBase = {
bannerId: mongo.ObjectID;
data: any;
description: string;
- latestNote: INote;
pinnedNoteId: mongo.ObjectID;
isSuspended: boolean;
- keywords: string[];
host: string;
};
@@ -80,13 +80,14 @@ export interface ILocalUser extends IUserBase {
isPro: boolean;
twoFactorSecret: string;
twoFactorEnabled: boolean;
- twoFactorTempSecret: string;
+ twoFactorTempSecret?: string;
clientSettings: any;
settings: any;
}
export interface IRemoteUser extends IUserBase {
inbox: string;
+ endpoints: string[];
uri: string;
url?: string;
publicKey: {
@@ -114,7 +115,7 @@ export function validatePassword(password: string): boolean {
}
export function isValidName(name?: string): boolean {
- return name === null || (typeof name == 'string' && name.length < 30 && name.trim() != '');
+ return name === null || (typeof name == 'string' && name.length < 50 && name.trim() != '');
}
export function isValidDescription(description: string): boolean {
@@ -167,9 +168,9 @@ export async function deleteUser(user: string | mongo.ObjectID | IUser) {
).map(x => deleteAccessToken(x)));
// このユーザーのNoteをすべて削除
- await Promise.all((
- await Note.find({ userId: u._id })
- ).map(x => deleteNote(x)));
+ //await sequential((
+ // await Note.find({ userId: u._id })
+ //).map(x => () => deleteNote(x)));
// このユーザーのNoteReactionをすべて削除
await Promise.all((
@@ -261,6 +262,20 @@ export async function deleteUser(user: string | mongo.ObjectID | IUser) {
await Notification.find({ notifierId: u._id })
).map(x => deleteNotification(x)));
+ // このユーザーのUserListをすべて削除
+ await Promise.all((
+ await UserList.find({ userId: u._id })
+ ).map(x => deleteUserList(x)));
+
+ // このユーザーが入っているすべてのUserListからこのユーザーを削除
+ await Promise.all((
+ await UserList.find({ userIds: u._id })
+ ).map(x =>
+ UserList.update({ _id: x._id }, {
+ $pull: { userIds: u._id }
+ })
+ ));
+
// このユーザーを削除
await User.remove({
_id: u._id
@@ -332,9 +347,6 @@ export const pack = (
_user.id = _user._id;
delete _user._id;
- // Remove needless properties
- delete _user.latestNote;
-
if (_user.host == null) {
// Remove private properties
delete _user.keypair;
@@ -359,6 +371,8 @@ export const pack = (
if (!opts.detail) {
delete _user.twoFactorEnabled;
}
+ } else {
+ delete _user.publicKey;
}
_user.avatarUrl = _user.avatarId != null
@@ -377,33 +391,30 @@ export const pack = (
}
if (meId && !meId.equals(_user.id)) {
- // Whether the user is following
- _user.isFollowing = (async () => {
- const follow = await Following.findOne({
+ const [following1, following2, mute] = await Promise.all([
+ Following.findOne({
followerId: meId,
followeeId: _user.id
- });
- return follow !== null;
- })();
-
- // Whether the user is followed
- _user.isFollowed = (async () => {
- const follow2 = await Following.findOne({
+ }),
+ Following.findOne({
followerId: _user.id,
followeeId: meId
- });
- return follow2 !== null;
- })();
+ }),
+ Mute.findOne({
+ muterId: meId,
+ muteeId: _user.id
+ })
+ ]);
+
+ // Whether the user is following
+ _user.isFollowing = following1 !== null;
+ _user.isStalking = following1 && following1.stalk;
+
+ // Whether the user is followed
+ _user.isFollowed = following2 !== null;
// Whether the user is muted
- _user.isMuted = (async () => {
- const mute = await Mute.findOne({
- muterId: meId,
- muteeId: _user.id,
- deletedAt: { $exists: false }
- });
- return mute !== null;
- })();
+ _user.isMuted = mute !== null;
}
if (opts.detail) {
@@ -415,7 +426,7 @@ export const pack = (
}
if (meId && !meId.equals(_user.id)) {
- const myFollowingIds = await getFriends(meId);
+ const myFollowingIds = await getFriendIds(meId);
// Get following you know count
_user.followingYouKnowCount = Following.count({
@@ -448,3 +459,7 @@ function img(url) {
};
}
*/
+
+export function getGhost(): Promise<ILocalUser> {
+ return User.findOne({ _id: new mongo.ObjectId(config.ghost) });
+}