diff options
Diffstat (limited to 'src/models')
| -rw-r--r-- | src/models/drive-file-thumbnail.ts | 61 | ||||
| -rw-r--r-- | src/models/drive-file.ts | 23 | ||||
| -rw-r--r-- | src/models/favorite.ts | 35 | ||||
| -rw-r--r-- | src/models/following.ts | 11 | ||||
| -rw-r--r-- | src/models/messaging-message.ts | 1 | ||||
| -rw-r--r-- | src/models/note-reaction.ts | 13 | ||||
| -rw-r--r-- | src/models/note.ts | 136 | ||||
| -rw-r--r-- | src/models/sw-subscription.ts | 1 | ||||
| -rw-r--r-- | src/models/user-list.ts | 67 | ||||
| -rw-r--r-- | src/models/user.ts | 85 |
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) }); +} |